完善半自动履约凭证与客户重新授权流程

统一虎牙和微博账号级 CK 保存与导出;CK 失效后回到客户重新登录,后台支持重发授权;客户授权页改为统一深色布局并收敛开发迁移配置。
This commit is contained in:
yml
2026-11-14 00:13:00 +08:00
parent e1d16f3a53
commit 8a54c41476
29 changed files with 1788 additions and 827 deletions
@@ -15,13 +15,18 @@ test(
async () => {
process.env.DATABASE_URL = TEST_DATABASE_URL
const { closeDb, query } = await import('../../db/client.js')
const { ensureSemiAutoTasksForOrder, listAdminSemiAutoTasks, listPublicSemiAutoTasks } =
await import('./semi-auto-task-service.js')
const {
ensureSemiAutoTasksForOrder,
listAdminSemiAutoTasks,
listPublicSemiAutoTasks,
requestAdminSemiAutoCustomerReauth,
} = await import('./semi-auto-task-service.js')
const suffix = `${Date.now()}_${Math.floor(Math.random() * 1_000_000)}`
const orderNo = `IT-SEMI-AUTO-${suffix}`
const skuKey = `半自动测试-${suffix}`
let orderId = 0
let configId = 0
let credentialId = 0
try {
const order = await query<{ id: number }>(
`
@@ -102,8 +107,61 @@ test(
assert.equal(tasks[0]?.orderNo, orderNo)
assert.equal(tasks[0]?.quantity, 1)
assert.equal(tasks[0]?.semiAutoConfig.pageTitle, '虎牙授权测试')
const taskId = Number(tasks[0]?.id || 0)
const credential = await query<{ id: number }>(
`
INSERT INTO semi_auto_credentials (
platform, platform_uid, platform_nickname, credential_payload_json,
credential_source, credential_status, credential_updated_at,
last_checked_at, last_check_error, created_at, updated_at
) VALUES (
'huya', $1, '集成测试虎牙', '{"cookies":[]}',
'qr', 'invalid', NOW(), NOW(), 'CK 已失效', NOW(), NOW()
) RETURNING id
`,
[`it-huya-${suffix}`],
)
credentialId = Number(credential.rows[0]?.id || 0)
await query(
`
UPDATE semi_auto_tasks
SET credential_id = $2,
connector_session_id = $3,
login_status = 'authenticated',
binding_status = 'confirmed',
execution_status = 'waiting_operator',
customer_link_opened_at = NOW(),
authenticated_at = NOW(),
bound_at = NOW(),
updated_at = NOW()
WHERE id = $1
`,
[taskId, credentialId, `it-huya-session-${suffix}`],
)
const reauthorized = await requestAdminSemiAutoCustomerReauth(taskId, {
userId: 1,
role: 'support',
displayName: '集成测试客服',
} as never)
assert.equal(reauthorized.task.loginStatus, 'waiting_login')
assert.equal(reauthorized.task.bindingStatus, 'not_started')
assert.equal(reauthorized.task.executionStatus, 'waiting_customer')
assert.equal(reauthorized.task.customerLinkOpenedAt, null)
const retainedCredential = await query<{ credential_id: number }>(
'SELECT credential_id FROM semi_auto_tasks WHERE id = $1',
[taskId],
)
assert.equal(Number(retainedCredential.rows[0]?.credential_id), credentialId)
assert.ok(
reauthorized.events.some((event) => event.eventType === 'customer_reauth_requested'),
)
} finally {
if (orderId > 0) await query('DELETE FROM orders WHERE id = $1', [orderId])
if (credentialId > 0) {
await query('DELETE FROM semi_auto_credentials WHERE id = $1', [credentialId])
}
if (configId > 0) {
await query('DELETE FROM manual_fulfillment_configs WHERE id = $1', [configId])
}
@@ -341,12 +399,10 @@ test(
)
test(
'微博账号 CK 以账号级密文保存并可解密加载',
'微博账号 CK 以账号级 Cookie JSON 保存并可加载',
{ skip: !TEST_DATABASE_URL ? '未设置 SEMI_AUTO_TASK_TEST_DATABASE_URL' : false },
async () => {
process.env.DATABASE_URL = TEST_DATABASE_URL
process.env.SEMI_AUTO_CREDENTIAL_ENCRYPTION_KEY =
'integration-test-semi-auto-credential-key-123456'
const { closeDb, query, withTransaction } = await import('../../db/client.js')
const { loadSemiAutoCredential, upsertSemiAutoCredentialWithClient } =
await import('./semi-auto-credential-service.js')
@@ -374,18 +430,17 @@ test(
}),
)
const raw = await query<{
credential_ciphertext: string
credential_payload_json: string
credential_source: string
credential_status: string
}>(
`SELECT credential_ciphertext, credential_source, credential_status
`SELECT credential_payload_json, credential_source, credential_status
FROM semi_auto_credentials WHERE id = $1`,
[credentialId],
)
assert.equal(raw.rows[0]?.credential_source, 'sms')
assert.equal(raw.rows[0]?.credential_status, 'valid')
assert.ok(raw.rows[0]?.credential_ciphertext.startsWith('enc:v1:'))
assert.equal(raw.rows[0]?.credential_ciphertext.includes(`secret-sub-${suffix}`), false)
assert.match(raw.rows[0]?.credential_payload_json || '', /"cookies"/)
const credential = await loadSemiAutoCredential(credentialId)
assert.equal(credential?.platformUid, `it-weibo-${suffix}`)
@@ -394,7 +449,6 @@ test(
if (credentialId > 0) {
await query('DELETE FROM semi_auto_credentials WHERE id = $1', [credentialId])
}
delete process.env.SEMI_AUTO_CREDENTIAL_ENCRYPTION_KEY
await closeDb()
}
},