feat(admin): 重构管理控制台布局并完善管理功能

This commit is contained in:
2026-10-01 10:31:06 +08:00
parent f31691c300
commit 86b6383251
11 changed files with 3008 additions and 2213 deletions
+75
View File
@@ -0,0 +1,75 @@
import assert from 'node:assert/strict'
import { afterEach, mock, test } from 'node:test'
import { createUser, reviewClouds } from '../src/features/admin/api.ts'
import { serializeCsv, visibilityLabel } from '../src/features/admin/models.ts'
afterEach(() => mock.restoreAll())
test('admin review preserves completed batches when a later batch fails', async () => {
const completed = []
let call = 0
mock.method(globalThis, 'fetch', async (url, options) => {
assert.equal(new URL(url).pathname, '/admin/clouds/review')
assert.equal(options.credentials, 'include')
const body = JSON.parse(options.body)
assert.equal(body.review_status, 'approved')
assert.ok(body.cloud_ids.length <= 100)
return ++call === 1
? Response.json({ message: '成功' })
: Response.json({ message: '服务暂不可用' }, { status: 503 })
})
const ids = Array.from({ length: 101 }, (_, index) => `cloud-${index}`)
await assert.rejects(
reviewClouds(ids, 'approved', batch => completed.push(...batch)),
/服务暂不可用/,
)
assert.deepEqual(completed, ids.slice(0, 100))
assert.equal(call, 2)
})
test('admin account creation returns email delivery status without retrying creation', async () => {
const body = {
username: '观云者',
email: 'observer@example.test',
password: 'test-password',
role: 'user',
}
const fetch = mock.method(globalThis, 'fetch', async (url, options) => {
assert.equal(new URL(url).pathname, '/admin/users')
assert.equal(options.method, 'POST')
assert.equal(options.credentials, 'include')
assert.deepEqual(JSON.parse(options.body), body)
return Response.json(
{ message: '已创建,邮件发送失败', user_id: 'new-user', email_sent: false },
{ status: 201 },
)
})
assert.equal((await createUser(body)).email_sent, false)
assert.equal(fetch.mock.callCount(), 1)
})
test('CSV exports escape untrusted text, formulas, quotes and multiline descriptions', () => {
const csv = serializeCsv(
['用户名', '说明'],
[
['=HYPERLINK("test")', '第一行\n第二行'],
[' @SUM(1)', '云,"雨"'],
['-1+2', '\t=1'],
],
)
assert.ok(csv.startsWith('\uFEFF'))
assert.ok(csv.includes('"\'=HYPERLINK(""test"")"'))
assert.ok(csv.includes('"\' @SUM(1)"'))
assert.ok(csv.includes('"\'-1+2"'))
assert.ok(csv.includes('"\'\t=1"'))
assert.ok(csv.includes('"云,""雨"""'))
assert.ok(csv.includes('"第一行\n第二行"'))
})
test('pending and rejected images are never labeled publicly visible', () => {
for (const status of ['pending', 'rejected']) {
assert.equal(visibilityLabel({ status, is_hidden: false }), '尚未公开')
}
assert.equal(visibilityLabel({ status: 'approved', is_hidden: false }), '公开可见')
assert.equal(visibilityLabel({ status: 'approved', is_hidden: true }), '用户隐藏')
})