收敛半自动平台为登录获取 CK 流程
移除角色绑定对客户页、状态展示和完成门禁的阻塞;虎牙与微博统一保存并直接导出 CK;同步精简客服工作台、配置项和权威文档。
This commit is contained in:
@@ -1290,9 +1290,6 @@ function normalizeSemiAutoConfig(payload: JsonObject): {
|
||||
loginInstructions: String(config.loginInstructions || '')
|
||||
.trim()
|
||||
.slice(0, 1000),
|
||||
bindingInstructions: String(config.bindingInstructions || '')
|
||||
.trim()
|
||||
.slice(0, 1000),
|
||||
browserTargets: Array.isArray(config.browserTargets)
|
||||
? config.browserTargets
|
||||
.filter((item): item is Record<string, unknown> =>
|
||||
|
||||
@@ -370,12 +370,6 @@ export async function createSemiAutoMock(
|
||||
: connectorMode === 'mock'
|
||||
? 'Mock 登录:点击获取二维码后,页面轮询两次会自动模拟登录成功。'
|
||||
: '真实登录:点击获取二维码后,请使用虎牙 App 扫码并在手机上确认登录。',
|
||||
bindingInstructions:
|
||||
platform === 'weibo'
|
||||
? '微博登录成功即完成授权,无需再绑定游戏角色。'
|
||||
: connectorMode === 'mock'
|
||||
? 'Mock 绑定:页面轮询两次后会自动识别角色,点击确认即可。'
|
||||
: '真实绑定:扫码后等待 Connector 识别角色,核对账号和区服后点击确认绑定。',
|
||||
...(platform === 'weibo' ? { loginMethods: ['sms', 'qr'] } : {}),
|
||||
browserTargets: [
|
||||
{
|
||||
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
consumeMockBrowserTicket,
|
||||
createMockBrowserTicket,
|
||||
createMockLoginSession,
|
||||
exportMockSessionCredential,
|
||||
getMockBinding,
|
||||
getMockLoginSession,
|
||||
resetMockSessions,
|
||||
@@ -31,6 +32,9 @@ test('Mock Connector 可按轮询自动完成登录、绑定和一次性票据
|
||||
assert.equal(authenticated.status, 'authenticated')
|
||||
assert.equal(authenticated.hasCredential, true)
|
||||
assert.ok(authenticated.huyaUid)
|
||||
const credential = exportMockSessionCredential(created.sessionId)
|
||||
assert.equal(credential.platformUid, authenticated.huyaUid)
|
||||
assert.ok(credential.cookies.length >= 1)
|
||||
|
||||
const binding = startMockBinding(created.sessionId)
|
||||
assert.equal(binding.status, 'qr_pending')
|
||||
@@ -65,6 +69,12 @@ test('Mock Connector 支持控制状态、撤销会话和重置', () => {
|
||||
assert.equal(authenticated.status, 'authenticated')
|
||||
assert.equal(authenticated.hasCredential, true)
|
||||
|
||||
const ticket = createMockBrowserTicket({
|
||||
sessionId: created.sessionId,
|
||||
targetUrl: 'https://www.huya.com/no-binding',
|
||||
})
|
||||
assert.equal(consumeMockBrowserTicket(ticket.ticket).targetUrl, 'https://www.huya.com/no-binding')
|
||||
|
||||
const confirmed = setMockBindingStatus(created.sessionId, 'confirmed')
|
||||
assert.equal(confirmed.status, 'confirmed')
|
||||
assert.ok(confirmed.bindGameRole)
|
||||
|
||||
@@ -12,6 +12,7 @@ import type {
|
||||
ConnectorInjectionPayload,
|
||||
ConnectorLoginSession,
|
||||
} from '../semi-auto/huya-connector-client.js'
|
||||
import type { HuyaStoredCredential } from '../semi-auto/huya-native/huya-native-connector.js'
|
||||
import { createHttpError } from '../../utils/http.js'
|
||||
|
||||
const LOGIN_QR_TTL_MS = 5 * 60 * 1000
|
||||
@@ -203,6 +204,42 @@ export function getMockLoginSession(sessionIdInput: unknown): ConnectorLoginSess
|
||||
return loginView(session)
|
||||
}
|
||||
|
||||
export function exportMockSessionCredential(sessionIdInput: unknown): HuyaStoredCredential {
|
||||
const session = requireSession(sessionIdInput)
|
||||
if (session.loginStatus !== 'authenticated' || !session.hasCredential) {
|
||||
throw createHttpError('Mock 登录尚未完成,不能导出 CK', {
|
||||
statusCode: 409,
|
||||
errorCode: 'mock_semi_auto_credential_not_ready',
|
||||
})
|
||||
}
|
||||
return {
|
||||
platformUid: session.huyaUid,
|
||||
nickname: session.nickname,
|
||||
cookies: [
|
||||
{
|
||||
name: 'mock_huya_uid',
|
||||
value: session.huyaUid,
|
||||
domain: '.huya.com',
|
||||
path: '/',
|
||||
secure: true,
|
||||
httpOnly: false,
|
||||
hostOnly: false,
|
||||
expiresAt: null,
|
||||
},
|
||||
{
|
||||
name: 'mock_huya_session',
|
||||
value: session.sessionId,
|
||||
domain: '.huya.com',
|
||||
path: '/',
|
||||
secure: true,
|
||||
httpOnly: true,
|
||||
hostOnly: false,
|
||||
expiresAt: null,
|
||||
},
|
||||
],
|
||||
}
|
||||
}
|
||||
|
||||
export function startMockBinding(sessionIdInput: unknown): ConnectorBindingSession {
|
||||
const session = requireSession(sessionIdInput)
|
||||
if (session.loginStatus !== 'authenticated') {
|
||||
@@ -259,10 +296,10 @@ export function createMockBrowserTicket(input: {
|
||||
targetUrl?: unknown
|
||||
}): ConnectorBrowserTicket {
|
||||
const session = requireSession(input.sessionId)
|
||||
if (session.bindingStatus !== 'confirmed') {
|
||||
throw createHttpError('Mock 角色尚未绑定,不能生成浏览器票据', {
|
||||
if (session.loginStatus !== 'authenticated' || !session.hasCredential) {
|
||||
throw createHttpError('Mock 登录尚未完成,不能生成浏览器票据', {
|
||||
statusCode: 409,
|
||||
errorCode: 'mock_semi_auto_binding_required',
|
||||
errorCode: 'mock_semi_auto_login_required',
|
||||
})
|
||||
}
|
||||
const ticket = `mock-ticket-${crypto.randomUUID()}`
|
||||
|
||||
@@ -45,6 +45,44 @@ test('创建登录会话按真实 Connector 协议发送蛇形字段', async ()
|
||||
}
|
||||
})
|
||||
|
||||
test('外部虎牙 Connector 支持导出账号级 CK', async () => {
|
||||
const originalFetch = globalThis.fetch
|
||||
let requestUrl = ''
|
||||
globalThis.fetch = (async (url: string | URL | Request) => {
|
||||
requestUrl = String(url)
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
platformUid: '1686664168',
|
||||
nickname: '外部虎牙账号',
|
||||
cookies: [
|
||||
{
|
||||
name: 'udb_cred',
|
||||
value: 'external-cred',
|
||||
domain: '.huya.com',
|
||||
path: '/',
|
||||
secure: true,
|
||||
httpOnly: true,
|
||||
hostOnly: false,
|
||||
expiresAt: null,
|
||||
},
|
||||
],
|
||||
}),
|
||||
{ status: 200, headers: { 'Content-Type': 'application/json' } },
|
||||
)
|
||||
}) as typeof fetch
|
||||
try {
|
||||
const credential = await connector.exportHuyaSessionCredential('session-1', 'external')
|
||||
assert.equal(
|
||||
requestUrl,
|
||||
'http://connector.test/api/internal/huya-semi-auto/sessions/session-1/credential',
|
||||
)
|
||||
assert.equal(credential.platformUid, '1686664168')
|
||||
assert.equal(credential.cookies[0]?.value, 'external-cred')
|
||||
} finally {
|
||||
globalThis.fetch = originalFetch
|
||||
}
|
||||
})
|
||||
|
||||
test('重新扫码换绑会向 Connector 传递 force_rebind', async () => {
|
||||
const originalFetch = globalThis.fetch
|
||||
let requestBody: Record<string, unknown> = {}
|
||||
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
devMockConnectorBaseUrl,
|
||||
} from '../dev-mock/connector-endpoint.js'
|
||||
import { isDevMockEnabled } from '../dev-mock/dev-mock-enabled.js'
|
||||
import { exportMockSessionCredential } from '../dev-mock/mock-semi-auto-connector.js'
|
||||
import {
|
||||
HUYA_NATIVE_TICKET_PREFIX,
|
||||
huyaNativeConnector,
|
||||
@@ -276,17 +277,17 @@ export async function createHuyaBrowserTicket(
|
||||
)
|
||||
}
|
||||
|
||||
export function exportHuyaSessionCredential(
|
||||
export async function exportHuyaSessionCredential(
|
||||
sessionId: string,
|
||||
mode: SemiAutoConnectorMode = defaultSemiAutoConnectorMode(),
|
||||
): HuyaStoredCredential {
|
||||
if (mode !== 'embedded') {
|
||||
throw createHttpError('当前虎牙 Connector 模式不支持直接导出 CK', {
|
||||
statusCode: 409,
|
||||
errorCode: 'semi_auto_credential_export_unsupported',
|
||||
})
|
||||
}
|
||||
return huyaNativeConnector.exportSessionCredential(sessionId)
|
||||
): Promise<HuyaStoredCredential> {
|
||||
if (mode === 'embedded') return huyaNativeConnector.exportSessionCredential(sessionId)
|
||||
if (mode === 'mock') return exportMockSessionCredential(sessionId)
|
||||
return callConnector<HuyaStoredCredential>(
|
||||
`/api/internal/huya-semi-auto/sessions/${encodeURIComponent(sessionId)}/credential`,
|
||||
{},
|
||||
mode,
|
||||
)
|
||||
}
|
||||
|
||||
export function inferSemiAutoConnectorModeFromTicket(ticket: string): SemiAutoConnectorMode {
|
||||
|
||||
@@ -168,25 +168,15 @@ function displayStatus(task: TaskRow) {
|
||||
if (task.credential_status === 'invalid' || task.credential_status === 'error') {
|
||||
return { key: 'credential_expired', label: '登录已失效,需重新授权' }
|
||||
}
|
||||
if (
|
||||
task.execution_status === 'failed' ||
|
||||
task.login_status === 'failed' ||
|
||||
task.binding_status === 'failed'
|
||||
) {
|
||||
if (task.execution_status === 'failed' || task.login_status === 'failed') {
|
||||
return { key: 'failed', label: '需客服处理' }
|
||||
}
|
||||
if (task.binding_status === 'confirmed') {
|
||||
return { key: 'waiting_operator', label: '等待客服处理' }
|
||||
}
|
||||
if (task.binding_status === 'role_ready') {
|
||||
return { key: 'binding_confirm', label: '待确认绑定' }
|
||||
}
|
||||
if (task.login_status === 'authenticated') {
|
||||
return { key: 'waiting_binding', label: '等待扫码绑定' }
|
||||
return { key: 'waiting_operator', label: 'CK 已获取,等待客服处理' }
|
||||
}
|
||||
if (task.login_status === 'scanned') return { key: 'login_scanned', label: '已扫码,确认中' }
|
||||
if (task.login_status === 'qr_pending') return { key: 'waiting_login', label: '等待客户登录' }
|
||||
return { key: 'waiting_customer', label: '等待客户打开链接' }
|
||||
return { key: 'waiting_customer', label: '等待客户授权' }
|
||||
}
|
||||
|
||||
function mapTask(task: TaskRow) {
|
||||
@@ -418,7 +408,6 @@ async function resetTaskForCustomerLogin(
|
||||
UPDATE semi_auto_tasks
|
||||
SET connector_session_id = '',
|
||||
login_status = 'waiting_login',
|
||||
binding_status = 'not_started',
|
||||
execution_status = 'waiting_customer',
|
||||
customer_link_opened_at = NULL,
|
||||
auth_started_at = NULL,
|
||||
@@ -437,8 +426,6 @@ async function resetTaskForCustomerLogin(
|
||||
...(input.actorId ? { actorId: input.actorId } : {}),
|
||||
fromLoginStatus: task.login_status,
|
||||
toLoginStatus: 'waiting_login',
|
||||
fromBindingStatus: task.binding_status,
|
||||
toBindingStatus: 'not_started',
|
||||
fromExecutionStatus: task.execution_status,
|
||||
toExecutionStatus: 'waiting_customer',
|
||||
detail: { reason: input.reason },
|
||||
@@ -673,25 +660,20 @@ async function updateLoginTask(
|
||||
actorType: TaskActorType,
|
||||
) {
|
||||
const mapped = mapLoginUpdate(session)
|
||||
// 微博没有独立的游戏角色绑定步骤:登录成功即视为授权完成。
|
||||
const autoConfirmWeiboBinding =
|
||||
task.platform === 'weibo' &&
|
||||
mapped.status === 'authenticated' &&
|
||||
task.binding_status !== 'confirmed'
|
||||
const nextExecutionStatus: ExecutionStatus =
|
||||
mapped.status === 'authenticated'
|
||||
? task.platform === 'weibo' || task.binding_status === 'confirmed'
|
||||
? 'waiting_operator'
|
||||
: 'waiting_customer'
|
||||
: task.execution_status
|
||||
mapped.status === 'authenticated' ? 'waiting_operator' : task.execution_status
|
||||
const storedCredential =
|
||||
mapped.status === 'authenticated' && mapped.hasCredential
|
||||
? task.platform === 'weibo'
|
||||
? exportWeiboSessionCredential(session.sessionId, task.connector_mode)
|
||||
: task.connector_mode === 'embedded'
|
||||
? exportHuyaSessionCredential(session.sessionId, task.connector_mode)
|
||||
: null
|
||||
: await exportHuyaSessionCredential(session.sessionId, task.connector_mode)
|
||||
: null
|
||||
if (mapped.status === 'authenticated' && !storedCredential) {
|
||||
throw createHttpError('平台登录成功但未能获取账号 CK,请联系客服处理', {
|
||||
statusCode: 502,
|
||||
errorCode: 'semi_auto_credential_export_failed',
|
||||
})
|
||||
}
|
||||
await withTransaction(async (client) => {
|
||||
const credentialId = storedCredential
|
||||
? await upsertSemiAutoCredentialWithClient(client, {
|
||||
@@ -704,17 +686,12 @@ async function updateLoginTask(
|
||||
`
|
||||
UPDATE semi_auto_tasks
|
||||
SET login_status = $2,
|
||||
credential_id = COALESCE($8, credential_id),
|
||||
credential_id = COALESCE($7, credential_id),
|
||||
huya_uid = CASE WHEN $3 <> '' THEN $3 ELSE huya_uid END,
|
||||
platform_nickname = CASE WHEN $4 <> '' THEN $4 ELSE platform_nickname END,
|
||||
authenticated_at = CASE WHEN $2 = 'authenticated' THEN COALESCE(authenticated_at, NOW()) ELSE authenticated_at END,
|
||||
binding_status = CASE WHEN $6::boolean THEN 'confirmed' ELSE binding_status END,
|
||||
bind_game_account = CASE WHEN $6::boolean AND $4 <> '' THEN $4 ELSE bind_game_account END,
|
||||
bind_game_role = CASE WHEN $6::boolean AND $3 <> '' THEN $3 ELSE bind_game_role END,
|
||||
bind_game_channel = CASE WHEN $6::boolean THEN '微博' ELSE bind_game_channel END,
|
||||
bound_at = CASE WHEN $6::boolean THEN COALESCE(bound_at, NOW()) ELSE bound_at END,
|
||||
execution_status = $5,
|
||||
last_error = CASE WHEN $2 IN ('failed', 'expired') THEN $7 ELSE '' END,
|
||||
last_error = CASE WHEN $2 IN ('failed', 'expired') THEN $6 ELSE '' END,
|
||||
updated_at = NOW()
|
||||
WHERE id = $1
|
||||
`,
|
||||
@@ -724,7 +701,6 @@ async function updateLoginTask(
|
||||
mapped.uid,
|
||||
mapped.nickname,
|
||||
nextExecutionStatus,
|
||||
autoConfirmWeiboBinding,
|
||||
mapped.message,
|
||||
credentialId,
|
||||
],
|
||||
@@ -741,22 +717,6 @@ async function updateLoginTask(
|
||||
message: mapped.message,
|
||||
},
|
||||
})
|
||||
if (autoConfirmWeiboBinding) {
|
||||
await appendTaskEvent(client, task.id, {
|
||||
eventType: 'binding_status_changed',
|
||||
actorType,
|
||||
fromBindingStatus: task.binding_status,
|
||||
toBindingStatus: 'confirmed',
|
||||
fromExecutionStatus: task.execution_status,
|
||||
toExecutionStatus: nextExecutionStatus,
|
||||
detail: {
|
||||
gameAccount: mapped.nickname,
|
||||
gameRole: mapped.uid,
|
||||
gameChannel: '微博',
|
||||
message: '微博登录成功,授权已完成',
|
||||
},
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -893,7 +853,6 @@ export async function startPublicLogin(orderNoInput: unknown, taskIdInput: unkno
|
||||
UPDATE semi_auto_tasks
|
||||
SET connector_session_id = $2,
|
||||
login_status = 'qr_pending',
|
||||
binding_status = 'not_started',
|
||||
execution_status = 'waiting_customer',
|
||||
auth_started_at = COALESCE(auth_started_at, NOW()),
|
||||
last_error = '',
|
||||
@@ -1130,16 +1089,11 @@ export async function loginPublicWeiboSms(
|
||||
SET connector_session_id = $2,
|
||||
credential_id = COALESCE($5, credential_id),
|
||||
login_status = 'authenticated',
|
||||
binding_status = 'confirmed',
|
||||
execution_status = 'waiting_operator',
|
||||
huya_uid = CASE WHEN $3 <> '' THEN $3 ELSE huya_uid END,
|
||||
platform_nickname = CASE WHEN $4 <> '' THEN $4 ELSE platform_nickname END,
|
||||
bind_game_account = CASE WHEN $4 <> '' THEN $4 ELSE bind_game_account END,
|
||||
bind_game_role = CASE WHEN $3 <> '' THEN $3 ELSE bind_game_role END,
|
||||
bind_game_channel = '微博',
|
||||
auth_started_at = COALESCE(auth_started_at, NOW()),
|
||||
authenticated_at = COALESCE(authenticated_at, NOW()),
|
||||
bound_at = COALESCE(bound_at, NOW()),
|
||||
last_error = '',
|
||||
updated_at = NOW()
|
||||
WHERE id = $1
|
||||
@@ -1155,23 +1109,6 @@ export async function loginPublicWeiboSms(
|
||||
toExecutionStatus: nextExecutionStatus,
|
||||
detail: { method: 'sms', hasCredential: mapped.hasCredential, message: mapped.message },
|
||||
})
|
||||
if (task.binding_status !== 'confirmed') {
|
||||
await appendTaskEvent(client, task.id, {
|
||||
eventType: 'binding_status_changed',
|
||||
actorType: 'customer',
|
||||
fromBindingStatus: task.binding_status,
|
||||
toBindingStatus: 'confirmed',
|
||||
fromExecutionStatus: task.execution_status,
|
||||
toExecutionStatus: nextExecutionStatus,
|
||||
detail: {
|
||||
gameAccount: mapped.nickname,
|
||||
gameRole: mapped.uid,
|
||||
gameChannel: '微博',
|
||||
method: 'sms',
|
||||
message: '微博登录成功,授权已完成',
|
||||
},
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
if (previousSessionId && previousSessionId !== sessionId) {
|
||||
@@ -1303,15 +1240,8 @@ export async function getAdminSemiAutoTask(taskIdInput: unknown) {
|
||||
}
|
||||
}
|
||||
|
||||
export async function exportAdminSemiAutoCredential(taskIdInput: unknown, session: AdminSession) {
|
||||
export async function exportAdminSemiAutoCredential(taskIdInput: unknown) {
|
||||
const task = await loadTask(normalizeId(taskIdInput))
|
||||
const adminUserId = Number(session.userId || 0)
|
||||
if (task.assigned_admin_user_id && Number(task.assigned_admin_user_id) !== adminUserId) {
|
||||
throw createHttpError('该任务已被其他客服领取', {
|
||||
statusCode: 409,
|
||||
errorCode: 'semi_auto_task_claimed',
|
||||
})
|
||||
}
|
||||
if (!task.credential_id) {
|
||||
throw createHttpError('当前任务尚未保存账号 CK', {
|
||||
statusCode: 404,
|
||||
@@ -1392,10 +1322,6 @@ export async function syncAdminSemiAutoTask(taskIdInput: unknown) {
|
||||
await updateLoginTask(task, login, 'connector')
|
||||
task = await loadTask(taskId)
|
||||
}
|
||||
if (task.login_status === 'authenticated' && task.binding_status !== 'confirmed') {
|
||||
const binding = await getBindingForTask(task, task.connector_session_id)
|
||||
await updateBindingTask(task, binding, 'connector')
|
||||
}
|
||||
return getAdminSemiAutoTask(taskId)
|
||||
}
|
||||
|
||||
@@ -1450,10 +1376,10 @@ export async function createAdminBrowserSession(
|
||||
errorCode: 'semi_auto_task_claimed',
|
||||
})
|
||||
}
|
||||
if (!task.connector_session_id || task.binding_status !== 'confirmed') {
|
||||
throw createHttpError('客户尚未完成登录和绑定', {
|
||||
if (!task.connector_session_id || task.login_status !== 'authenticated') {
|
||||
throw createHttpError('客户尚未完成登录,或登录会话已失效', {
|
||||
statusCode: 409,
|
||||
errorCode: 'semi_auto_binding_required',
|
||||
errorCode: 'semi_auto_login_required',
|
||||
})
|
||||
}
|
||||
const config = safeParseJson(task.semi_auto_config_json)
|
||||
@@ -1591,10 +1517,15 @@ export async function completeAdminSemiAutoTask(
|
||||
errorCode: 'semi_auto_task_cancelled',
|
||||
})
|
||||
}
|
||||
if (!task.connector_session_id || task.binding_status !== 'confirmed') {
|
||||
throw createHttpError('客户尚未完成登录和绑定,不能完成履约', {
|
||||
if (
|
||||
task.login_status !== 'authenticated' ||
|
||||
!task.credential_id ||
|
||||
task.credential_status === 'invalid' ||
|
||||
task.credential_status === 'error'
|
||||
) {
|
||||
throw createHttpError('客户账号 CK 尚未就绪或已失效,不能完成履约', {
|
||||
statusCode: 409,
|
||||
errorCode: 'semi_auto_binding_required',
|
||||
errorCode: 'semi_auto_credential_required',
|
||||
})
|
||||
}
|
||||
if (task.assigned_admin_user_id && Number(task.assigned_admin_user_id) !== adminUserId) {
|
||||
|
||||
@@ -17,6 +17,7 @@ test(
|
||||
const { closeDb, query } = await import('../../db/client.js')
|
||||
const {
|
||||
ensureSemiAutoTasksForOrder,
|
||||
exportAdminSemiAutoCredential,
|
||||
listAdminSemiAutoTasks,
|
||||
listPublicSemiAutoTasks,
|
||||
requestAdminSemiAutoCustomerReauth,
|
||||
@@ -117,7 +118,7 @@ test(
|
||||
last_checked_at, last_check_error, created_at, updated_at
|
||||
) VALUES (
|
||||
'huya', $1, '集成测试虎牙', '{"cookies":[]}',
|
||||
'qr', 'invalid', NOW(), NOW(), 'CK 已失效', NOW(), NOW()
|
||||
'qr', 'valid', NOW(), NOW(), '', NOW(), NOW()
|
||||
) RETURNING id
|
||||
`,
|
||||
[`it-huya-${suffix}`],
|
||||
@@ -129,16 +130,21 @@ test(
|
||||
SET credential_id = $2,
|
||||
connector_session_id = $3,
|
||||
login_status = 'authenticated',
|
||||
binding_status = 'confirmed',
|
||||
binding_status = 'not_started',
|
||||
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 authenticatedList = await listAdminSemiAutoTasks({ keyword: orderNo })
|
||||
assert.equal(authenticatedList.items[0]?.loginStatus, 'authenticated')
|
||||
assert.equal(authenticatedList.items[0]?.displayStatus.label, 'CK 已获取,等待客服处理')
|
||||
const exported = await exportAdminSemiAutoCredential(taskId)
|
||||
assert.equal(exported.credentialId, credentialId)
|
||||
assert.equal(exported.platform, 'huya')
|
||||
|
||||
const reauthorized = await requestAdminSemiAutoCustomerReauth(taskId, {
|
||||
userId: 1,
|
||||
@@ -186,6 +192,7 @@ test(
|
||||
let orderId = 0
|
||||
let configId = 0
|
||||
let adminId = 0
|
||||
let credentialId = 0
|
||||
let triggerCreated = false
|
||||
try {
|
||||
const admin = await query<{ id: number }>(
|
||||
@@ -260,19 +267,33 @@ test(
|
||||
const ensured = await ensureSemiAutoTasksForOrder(orderNo)
|
||||
const taskId = Number(ensured.createdIds[0] || 0)
|
||||
assert.ok(taskId > 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', 'valid', NOW(), NOW(), '', NOW(), NOW()
|
||||
) RETURNING id
|
||||
`,
|
||||
[`it-atomic-huya-${suffix}`],
|
||||
)
|
||||
credentialId = Number(credential.rows[0]?.id || 0)
|
||||
assert.ok(credentialId > 0)
|
||||
await query(
|
||||
`
|
||||
UPDATE semi_auto_tasks
|
||||
SET connector_session_id = $2,
|
||||
SET credential_id = $2,
|
||||
login_status = 'authenticated',
|
||||
binding_status = 'confirmed',
|
||||
binding_status = 'not_started',
|
||||
execution_status = 'waiting_operator',
|
||||
authenticated_at = NOW(),
|
||||
bound_at = NOW(),
|
||||
updated_at = NOW()
|
||||
WHERE id = $1
|
||||
`,
|
||||
[taskId, `it-session-${suffix}`],
|
||||
[taskId, credentialId],
|
||||
)
|
||||
|
||||
await query(
|
||||
@@ -390,6 +411,9 @@ test(
|
||||
if (configId > 0) {
|
||||
await query('DELETE FROM manual_fulfillment_configs WHERE id = $1', [configId])
|
||||
}
|
||||
if (credentialId > 0) {
|
||||
await query('DELETE FROM semi_auto_credentials WHERE id = $1', [credentialId])
|
||||
}
|
||||
if (adminId > 0) {
|
||||
await query('DELETE FROM admin_users WHERE id = $1', [adminId])
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user