71 lines
3.6 KiB
JavaScript
71 lines
3.6 KiB
JavaScript
import assert from 'node:assert/strict'
|
|
import { readFile } from 'node:fs/promises'
|
|
import test from 'node:test'
|
|
import ts from 'typescript'
|
|
|
|
async function session(t, fetch_) {
|
|
const previous = globalThis.fetch
|
|
globalThis.fetch = fetch_
|
|
t.after(() => { globalThis.fetch = previous })
|
|
const source = await readFile(new URL('../src/services/session.ts', import.meta.url), 'utf8')
|
|
const { outputText } = ts.transpileModule(source, { compilerOptions: { target: ts.ScriptTarget.ES2022, module: ts.ModuleKind.ESNext } })
|
|
return import(`data:text/javascript;base64,${Buffer.from(outputText).toString('base64')}#${t.name}`)
|
|
}
|
|
|
|
test('JSON login and binary export share Cookie/CSRF transport without parsing JSONL as JSON', async t => {
|
|
const content = '{"record_type":"manifest"}\n{"query":"合成测试"}\n'
|
|
const calls = []
|
|
const api = await session(t, async (path, init) => {
|
|
calls.push([path, init])
|
|
if (path === '/api/auth/login') return Response.json({ csrf_token: 'fixture-csrf' })
|
|
return new Response(content, { headers: { 'Content-Disposition': 'attachment; filename="fixture.jsonl"', 'X-Records-SHA256': 'a'.repeat(64) } })
|
|
})
|
|
await api.apiRequest('/api/auth/login', { method: 'POST', body: '{}' })
|
|
const file = await api.apiDownload('/api/retrieval/exports', { method: 'POST', body: '{"run_ids":[]}' }, true)
|
|
assert.equal(await file.blob.text(), content)
|
|
assert.equal(file.filename, 'fixture.jsonl')
|
|
assert.equal(file.recordsSha256, 'a'.repeat(64))
|
|
assert.equal(calls[1][1].credentials, 'include')
|
|
assert.equal(calls[1][1].headers.get('X-CSRF-Token'), 'fixture-csrf')
|
|
assert.equal(calls[1][1].headers.get('Content-Type'), 'application/json')
|
|
assert.equal(calls[1][1].headers.get('Accept'), 'application/x-ndjson')
|
|
assert.equal(calls[0][1].headers.get('Accept'), 'application/json')
|
|
})
|
|
|
|
test('download refreshes missing session token and preserves server authorization errors', async t => {
|
|
const calls = []
|
|
const api = await session(t, async (path, init) => {
|
|
calls.push([path, init])
|
|
if (path === '/api/auth/me') return Response.json({ csrf_token: 'refreshed-csrf' })
|
|
return Response.json({ detail: '需要管理员权限' }, { status: 403 })
|
|
})
|
|
await assert.rejects(api.apiDownload('/api/retrieval/exports', { method: 'POST', body: '{}' }, true), error => {
|
|
assert.ok(error instanceof api.ApiError)
|
|
assert.equal(error.status, 403)
|
|
assert.equal(error.message, '需要管理员权限')
|
|
return true
|
|
})
|
|
assert.equal(calls.length, 2)
|
|
assert.equal(calls[0][0], '/api/auth/me')
|
|
assert.equal(calls[1][1].headers.get('X-CSRF-Token'), 'refreshed-csrf')
|
|
})
|
|
|
|
test('401 download clears stale token and unsafe filename is not propagated', async t => {
|
|
let current = 'old-csrf'
|
|
let failed = false
|
|
const api = await session(t, async (path, init) => {
|
|
if (path === '/api/auth/login' || path === '/api/auth/me') return Response.json({ csrf_token: current })
|
|
assert.equal(init.headers.get('X-CSRF-Token'), current)
|
|
if (!failed) {
|
|
failed = true; current = 'new-csrf'
|
|
return Response.json({ detail: '请先登录' }, { status: 401 })
|
|
}
|
|
return new Response('fixture', { headers: { 'Content-Disposition': 'attachment; filename="../../fixture.jsonl"' } })
|
|
})
|
|
await api.apiRequest('/api/auth/login', { method: 'POST', body: '{}' })
|
|
await assert.rejects(api.apiDownload('/api/retrieval/exports', { method: 'POST' }, true), error => error.status === 401)
|
|
const file = await api.apiDownload('/api/retrieval/exports', { method: 'POST' }, true)
|
|
assert.equal(file.filename, 'download.jsonl')
|
|
assert.equal(file.recordsSha256, null)
|
|
})
|