597 lines
22 KiB
TypeScript
597 lines
22 KiB
TypeScript
/**
|
|
* 半自动任务真实 PostgreSQL 集成测试。
|
|
*
|
|
* SEMI_AUTO_TASK_TEST_DATABASE_URL=postgres://... \
|
|
* npx tsx --test src/services/semi-auto/semi-auto-task.integration.test.ts
|
|
*/
|
|
import assert from 'node:assert/strict'
|
|
import test from 'node:test'
|
|
|
|
const TEST_DATABASE_URL = String(process.env.SEMI_AUTO_TASK_TEST_DATABASE_URL || '').trim()
|
|
|
|
test(
|
|
'人工渠道半自动任务可由客户入口和后台列表幂等回填',
|
|
{ skip: !TEST_DATABASE_URL ? '未设置 SEMI_AUTO_TASK_TEST_DATABASE_URL' : false },
|
|
async () => {
|
|
process.env.DATABASE_URL = TEST_DATABASE_URL
|
|
const { closeDb, query } = await import('../../db/client.js')
|
|
const {
|
|
ensureSemiAutoTasksForOrder,
|
|
exportAdminSemiAutoCredential,
|
|
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 }>(
|
|
`
|
|
INSERT INTO orders (
|
|
provider, platform, shop_id, shop_name, platform_order_id,
|
|
order_status, pay_status, total_amount, paid_at, created_at, updated_at
|
|
) VALUES (
|
|
'91kaquan', 'kuaishou-91', $1, '半自动测试店', $2,
|
|
'paid', 'paid', 1000, NOW(), NOW(), NOW()
|
|
) RETURNING id
|
|
`,
|
|
[`semi-auto-shop-${suffix}`, orderNo],
|
|
)
|
|
orderId = Number(order.rows[0]?.id || 0)
|
|
assert.ok(orderId > 0)
|
|
const item = await query<{ id: number }>(
|
|
`
|
|
INSERT INTO order_items (
|
|
order_id, sku_code, sku_name, quantity, spec_json, item_snapshot_json,
|
|
revenue_amount_fen, revenue_basis, created_at, updated_at
|
|
) VALUES (
|
|
$1, $2, $3, 1, $4::jsonb, $4::jsonb,
|
|
1000, 'order_total', NOW(), NOW()
|
|
) RETURNING id
|
|
`,
|
|
[orderId, `semi-${suffix}`, skuKey, JSON.stringify({ skuNick: skuKey })],
|
|
)
|
|
const orderItemId = Number(item.rows[0]?.id || 0)
|
|
assert.ok(orderItemId > 0)
|
|
const fulfillment = await query<{ id: number }>(
|
|
'SELECT id FROM fulfillments WHERE order_item_id = $1 LIMIT 1',
|
|
[orderItemId],
|
|
)
|
|
const fulfillmentId = Number(fulfillment.rows[0]?.id || 0)
|
|
assert.ok(fulfillmentId > 0)
|
|
|
|
const config = await query<{ id: number }>(
|
|
`
|
|
INSERT INTO manual_fulfillment_configs (
|
|
sku_key, platform_key, credit_quantity, cost_mode, enabled, note,
|
|
channels_json, semi_auto_enabled, semi_auto_platform,
|
|
semi_auto_config_json, created_at, updated_at
|
|
) VALUES (
|
|
$1, 'fund_cash', 100, 'fixed', TRUE, '集成测试',
|
|
'[{"platformKey":"fund_cash","creditQuantity":100,"costMode":"fixed","enabled":true}]'::jsonb,
|
|
TRUE, 'huya', '{"pageTitle":"虎牙授权测试"}'::jsonb,
|
|
NOW(), NOW()
|
|
) RETURNING id
|
|
`,
|
|
[skuKey],
|
|
)
|
|
configId = Number(config.rows[0]?.id || 0)
|
|
assert.ok(configId > 0)
|
|
await query(
|
|
`
|
|
UPDATE fulfillments
|
|
SET channel = 'manual', status = 'assigned', fulfillment_config_id = $2,
|
|
source = 'manual_confirm', updated_at = NOW()
|
|
WHERE id = $1
|
|
`,
|
|
[fulfillmentId, configId],
|
|
)
|
|
|
|
// 客服工作台先打开时也必须看到任务,不能依赖客户先访问 `/c/订单号`。
|
|
const adminList = await listAdminSemiAutoTasks({ keyword: orderNo })
|
|
assert.equal(adminList.pagination.total, 1)
|
|
assert.equal(adminList.items[0]?.orderNo, orderNo)
|
|
|
|
const first = await ensureSemiAutoTasksForOrder(orderNo)
|
|
const second = await ensureSemiAutoTasksForOrder(orderNo)
|
|
assert.equal(first.createdIds.length, 0)
|
|
assert.equal(second.createdIds.length, 0)
|
|
|
|
const tasks = await listPublicSemiAutoTasks(orderNo)
|
|
assert.equal(tasks.length, 1)
|
|
assert.equal(tasks[0]?.orderItemId, orderItemId)
|
|
assert.equal(tasks[0]?.platform, 'huya')
|
|
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', 'valid', NOW(), NOW(), '', 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 = 'not_started',
|
|
execution_status = 'waiting_operator',
|
|
customer_link_opened_at = NOW(),
|
|
authenticated_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,
|
|
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])
|
|
}
|
|
await closeDb()
|
|
}
|
|
},
|
|
)
|
|
|
|
test(
|
|
'半自动完成失败会连同财务确认一起回滚,成功后重复完成不重复扣成本',
|
|
{ skip: !TEST_DATABASE_URL ? '未设置 SEMI_AUTO_TASK_TEST_DATABASE_URL' : false },
|
|
async () => {
|
|
process.env.DATABASE_URL = TEST_DATABASE_URL
|
|
const { closeDb, query } = await import('../../db/client.js')
|
|
const { completeAdminSemiAutoTask, ensureSemiAutoTasksForOrder } =
|
|
await import('./semi-auto-task-service.js')
|
|
const suffix = `${Date.now()}_${Math.floor(Math.random() * 1_000_000)}`
|
|
const orderNo = `IT-SEMI-AUTO-ATOMIC-${suffix}`
|
|
const skuKey = `半自动原子性-${suffix}`
|
|
const triggerName = `trg_semi_auto_atomic_${suffix}`
|
|
const functionName = `fn_semi_auto_atomic_${suffix}`
|
|
let orderId = 0
|
|
let configId = 0
|
|
let adminId = 0
|
|
let credentialId = 0
|
|
let triggerCreated = false
|
|
try {
|
|
const admin = await query<{ id: number }>(
|
|
`INSERT INTO admin_users (username, password_hash, role, status, created_at, updated_at)
|
|
VALUES ($1, 'x', 'support', 'active', NOW(), NOW()) RETURNING id`,
|
|
[`it_semi_auto_admin_${suffix}`],
|
|
)
|
|
adminId = Number(admin.rows[0]?.id || 0)
|
|
assert.ok(adminId > 0)
|
|
|
|
const order = await query<{ id: number }>(
|
|
`
|
|
INSERT INTO orders (
|
|
provider, platform, shop_id, shop_name, platform_order_id,
|
|
order_status, pay_status, total_amount, paid_at, created_at, updated_at
|
|
) VALUES (
|
|
'91kaquan', 'kuaishou-91', $1, '半自动原子性测试店', $2,
|
|
'paid', 'paid', 1000, NOW(), NOW(), NOW()
|
|
) RETURNING id
|
|
`,
|
|
[`semi-auto-atomic-shop-${suffix}`, orderNo],
|
|
)
|
|
orderId = Number(order.rows[0]?.id || 0)
|
|
assert.ok(orderId > 0)
|
|
const item = await query<{ id: number }>(
|
|
`
|
|
INSERT INTO order_items (
|
|
order_id, sku_code, sku_name, quantity, spec_json, item_snapshot_json,
|
|
revenue_amount_fen, revenue_basis, created_at, updated_at
|
|
) VALUES (
|
|
$1, $2, $3, 1, $4::jsonb, $4::jsonb,
|
|
1000, 'order_total', NOW(), NOW()
|
|
) RETURNING id
|
|
`,
|
|
[orderId, `semi-atomic-${suffix}`, skuKey, JSON.stringify({ skuNick: skuKey })],
|
|
)
|
|
const orderItemId = Number(item.rows[0]?.id || 0)
|
|
assert.ok(orderItemId > 0)
|
|
const fulfillment = await query<{ id: number }>(
|
|
'SELECT id FROM fulfillments WHERE order_item_id = $1 LIMIT 1',
|
|
[orderItemId],
|
|
)
|
|
const fulfillmentId = Number(fulfillment.rows[0]?.id || 0)
|
|
assert.ok(fulfillmentId > 0)
|
|
|
|
const config = await query<{ id: number }>(
|
|
`
|
|
INSERT INTO manual_fulfillment_configs (
|
|
sku_key, platform_key, credit_quantity, cost_mode, enabled, note,
|
|
channels_json, semi_auto_enabled, semi_auto_platform,
|
|
semi_auto_config_json, created_at, updated_at
|
|
) VALUES (
|
|
$1, 'on_account', 0, 'variable', TRUE, '原子性集成测试',
|
|
'[{"platformKey":"on_account","creditQuantity":0,"costMode":"variable","enabled":true}]'::jsonb,
|
|
TRUE, 'huya', '{}'::jsonb, NOW(), NOW()
|
|
) RETURNING id
|
|
`,
|
|
[skuKey],
|
|
)
|
|
configId = Number(config.rows[0]?.id || 0)
|
|
assert.ok(configId > 0)
|
|
await query(
|
|
`
|
|
UPDATE fulfillments
|
|
SET channel = 'manual', status = 'assigned', fulfillment_config_id = $2,
|
|
source = 'manual_confirm', updated_at = NOW()
|
|
WHERE id = $1
|
|
`,
|
|
[fulfillmentId, configId],
|
|
)
|
|
|
|
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 credential_id = $2,
|
|
login_status = 'authenticated',
|
|
binding_status = 'not_started',
|
|
execution_status = 'waiting_operator',
|
|
authenticated_at = NOW(),
|
|
updated_at = NOW()
|
|
WHERE id = $1
|
|
`,
|
|
[taskId, credentialId],
|
|
)
|
|
|
|
await query(
|
|
`
|
|
CREATE OR REPLACE FUNCTION ${functionName}() RETURNS trigger
|
|
LANGUAGE plpgsql AS $$
|
|
BEGIN
|
|
IF NEW.execution_status = 'completed' THEN
|
|
RAISE EXCEPTION 'forced semi auto completion failure';
|
|
END IF;
|
|
RETURN NEW;
|
|
END;
|
|
$$
|
|
`,
|
|
)
|
|
await query(
|
|
`
|
|
CREATE TRIGGER ${triggerName}
|
|
BEFORE UPDATE OF execution_status ON semi_auto_tasks
|
|
FOR EACH ROW EXECUTE FUNCTION ${functionName}()
|
|
`,
|
|
)
|
|
triggerCreated = true
|
|
|
|
const session = {
|
|
userId: adminId,
|
|
role: 'support',
|
|
displayName: '半自动原子性客服',
|
|
} as never
|
|
await assert.rejects(
|
|
() =>
|
|
completeAdminSemiAutoTask(
|
|
taskId,
|
|
{ costFen: 123, note: '触发任务更新失败以验证回滚' },
|
|
session,
|
|
),
|
|
/forced semi auto completion failure/,
|
|
)
|
|
|
|
const rolledBack = await query<{
|
|
execution_status: string
|
|
snapshot_count: number
|
|
bill_count: number
|
|
fulfillment_channel: string
|
|
}>(
|
|
`
|
|
SELECT
|
|
task.execution_status,
|
|
(SELECT COUNT(*)::int FROM cost_snapshots cs WHERE cs.order_item_id = $2) AS snapshot_count,
|
|
(SELECT COUNT(*)::int FROM on_account_bills oab WHERE oab.order_item_id = $2) AS bill_count,
|
|
f.channel AS fulfillment_channel
|
|
FROM semi_auto_tasks task
|
|
JOIN fulfillments f ON f.id = task.fulfillment_id
|
|
WHERE task.id = $1
|
|
`,
|
|
[taskId, orderItemId],
|
|
)
|
|
assert.equal(rolledBack.rows[0]?.execution_status, 'waiting_operator')
|
|
assert.equal(Number(rolledBack.rows[0]?.snapshot_count), 0)
|
|
assert.equal(Number(rolledBack.rows[0]?.bill_count), 0)
|
|
assert.equal(rolledBack.rows[0]?.fulfillment_channel, 'manual')
|
|
|
|
await query(`DROP TRIGGER ${triggerName} ON semi_auto_tasks`)
|
|
triggerCreated = false
|
|
await query(`DROP FUNCTION ${functionName}()`)
|
|
|
|
const completed = await completeAdminSemiAutoTask(
|
|
taskId,
|
|
{ costFen: 123, note: '事务重试成功' },
|
|
session,
|
|
)
|
|
assert.equal(completed.task.executionStatus, 'completed')
|
|
const afterCompletion = await query<{ snapshot_count: number; bill_count: number }>(
|
|
`
|
|
SELECT
|
|
(SELECT COUNT(*)::int FROM cost_snapshots cs WHERE cs.order_item_id = $1) AS snapshot_count,
|
|
(SELECT COUNT(*)::int FROM on_account_bills oab WHERE oab.order_item_id = $1) AS bill_count
|
|
`,
|
|
[orderItemId],
|
|
)
|
|
assert.equal(Number(afterCompletion.rows[0]?.snapshot_count), 1)
|
|
assert.equal(Number(afterCompletion.rows[0]?.bill_count), 1)
|
|
|
|
await completeAdminSemiAutoTask(taskId, { costFen: 123 }, session)
|
|
const afterDuplicate = await query<{ snapshot_count: number }>(
|
|
'SELECT COUNT(*)::int AS snapshot_count FROM cost_snapshots WHERE order_item_id = $1',
|
|
[orderItemId],
|
|
)
|
|
assert.equal(Number(afterDuplicate.rows[0]?.snapshot_count), 1)
|
|
} finally {
|
|
if (triggerCreated) {
|
|
await query(`DROP TRIGGER IF EXISTS ${triggerName} ON semi_auto_tasks`)
|
|
}
|
|
await query(`DROP FUNCTION IF EXISTS ${functionName}()`)
|
|
if (orderId > 0) {
|
|
await query(
|
|
'DELETE FROM cost_snapshots WHERE order_item_id IN (SELECT id FROM order_items WHERE order_id = $1)',
|
|
[orderId],
|
|
)
|
|
await query(
|
|
'DELETE FROM on_account_bills WHERE order_item_id IN (SELECT id FROM order_items WHERE order_id = $1)',
|
|
[orderId],
|
|
)
|
|
await query(
|
|
'DELETE FROM cost_exceptions WHERE subject_id IN (SELECT id FROM order_items WHERE order_id = $1)',
|
|
[orderId],
|
|
)
|
|
await query('DELETE FROM orders WHERE id = $1', [orderId])
|
|
}
|
|
// 已过账总账凭证按财务规则不可物理删除;测试只清理可重建销售投影。
|
|
await query(
|
|
`DELETE FROM sales_daily_facts
|
|
WHERE stat_date = (NOW() AT TIME ZONE 'Asia/Shanghai')::date`,
|
|
)
|
|
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])
|
|
}
|
|
await closeDb()
|
|
}
|
|
},
|
|
)
|
|
|
|
test(
|
|
'微博账号 CK 以账号级 Cookie JSON 保存并可加载',
|
|
{ skip: !TEST_DATABASE_URL ? '未设置 SEMI_AUTO_TASK_TEST_DATABASE_URL' : false },
|
|
async () => {
|
|
process.env.DATABASE_URL = TEST_DATABASE_URL
|
|
const { closeDb, query, withTransaction } = await import('../../db/client.js')
|
|
const { loadSemiAutoCredential, upsertSemiAutoCredentialWithClient } =
|
|
await import('./semi-auto-credential-service.js')
|
|
const suffix = `${Date.now()}_${Math.floor(Math.random() * 1_000_000)}`
|
|
let credentialId = 0
|
|
try {
|
|
credentialId = await withTransaction((client) =>
|
|
upsertSemiAutoCredentialWithClient(client, {
|
|
platform: 'weibo',
|
|
source: 'sms',
|
|
payload: {
|
|
platformUid: `it-weibo-${suffix}`,
|
|
nickname: '集成测试微博',
|
|
cookies: [
|
|
{
|
|
name: 'SUB',
|
|
value: `secret-sub-${suffix}`,
|
|
domain: '.weibo.com',
|
|
path: '/',
|
|
secure: true,
|
|
httpOnly: true,
|
|
},
|
|
],
|
|
},
|
|
}),
|
|
)
|
|
const raw = await query<{
|
|
credential_payload_json: string
|
|
credential_source: string
|
|
credential_status: string
|
|
}>(
|
|
`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.match(raw.rows[0]?.credential_payload_json || '', /"cookies"/)
|
|
|
|
const credential = await loadSemiAutoCredential(credentialId)
|
|
assert.equal(credential?.platformUid, `it-weibo-${suffix}`)
|
|
assert.equal(credential?.cookies[0]?.value, `secret-sub-${suffix}`)
|
|
} finally {
|
|
if (credentialId > 0) {
|
|
await query('DELETE FROM semi_auto_credentials WHERE id = $1', [credentialId])
|
|
}
|
|
await closeDb()
|
|
}
|
|
},
|
|
)
|
|
|
|
test(
|
|
'开发 Mock 可生成半自动履约测试单并回填任务',
|
|
{ skip: !TEST_DATABASE_URL ? '未设置 SEMI_AUTO_TASK_TEST_DATABASE_URL' : false },
|
|
async () => {
|
|
process.env.DATABASE_URL = TEST_DATABASE_URL
|
|
process.env.ENABLE_DEV_MOCK = '1'
|
|
const { closeDb, query } = await import('../../db/client.js')
|
|
const { createSemiAutoMock } = await import('../dev-mock/dev-mock-service.js')
|
|
const suffix = `${Date.now()}_${Math.floor(Math.random() * 1_000_000)}`
|
|
const orderNo = `IT-MOCK-SA-${suffix}`
|
|
const skuKey = `半自动Mock-${suffix}`
|
|
let orderId = 0
|
|
let configId = 0
|
|
try {
|
|
const result = await createSemiAutoMock({
|
|
orderNo,
|
|
skuName: skuKey,
|
|
quantity: 1,
|
|
pageTitle: 'Mock 集成测试',
|
|
connectorMode: 'embedded',
|
|
})
|
|
orderId = result.orderId
|
|
configId = result.configId
|
|
assert.ok(result.orderItemId > 0)
|
|
assert.ok(result.fulfillmentId > 0)
|
|
assert.ok(result.taskId > 0)
|
|
assert.equal(result.connectorMode, 'embedded')
|
|
assert.equal(result.customerPath, `/c/${encodeURIComponent(orderNo)}`)
|
|
|
|
const row = await query<{
|
|
channel: string
|
|
fulfillment_status: string
|
|
fulfillment_config_id: number
|
|
semi_auto_enabled: boolean
|
|
semi_auto_platform: string
|
|
connector_mode: string
|
|
login_status: string
|
|
binding_status: string
|
|
execution_status: string
|
|
}>(
|
|
`
|
|
SELECT
|
|
f.channel,
|
|
f.status AS fulfillment_status,
|
|
f.fulfillment_config_id,
|
|
cfg.semi_auto_enabled,
|
|
cfg.semi_auto_platform,
|
|
task.connector_mode,
|
|
task.login_status,
|
|
task.binding_status,
|
|
task.execution_status
|
|
FROM semi_auto_tasks task
|
|
JOIN fulfillments f ON f.id = task.fulfillment_id
|
|
JOIN manual_fulfillment_configs cfg ON cfg.id = task.manual_fulfillment_config_id
|
|
WHERE task.order_item_id = $1
|
|
`,
|
|
[result.orderItemId],
|
|
)
|
|
assert.equal(row.rows[0]?.channel, 'manual')
|
|
assert.equal(row.rows[0]?.fulfillment_status, 'assigned')
|
|
assert.equal(Number(row.rows[0]?.fulfillment_config_id), result.configId)
|
|
assert.equal(row.rows[0]?.semi_auto_enabled, true)
|
|
assert.equal(row.rows[0]?.semi_auto_platform, 'huya')
|
|
assert.equal(row.rows[0]?.connector_mode, 'embedded')
|
|
assert.equal(row.rows[0]?.login_status, 'waiting_login')
|
|
assert.equal(row.rows[0]?.binding_status, 'not_started')
|
|
assert.equal(row.rows[0]?.execution_status, 'waiting_customer')
|
|
} finally {
|
|
if (orderId > 0) await query('DELETE FROM orders WHERE id = $1', [orderId])
|
|
if (configId > 0) {
|
|
await query('DELETE FROM manual_fulfillment_configs WHERE id = $1', [configId])
|
|
}
|
|
delete process.env.ENABLE_DEV_MOCK
|
|
await closeDb()
|
|
}
|
|
},
|
|
)
|
|
|
|
test(
|
|
'开发 Mock 可显式选择内置 Mock Connector',
|
|
{ skip: !TEST_DATABASE_URL ? '未设置 SEMI_AUTO_TASK_TEST_DATABASE_URL' : false },
|
|
async () => {
|
|
process.env.DATABASE_URL = TEST_DATABASE_URL
|
|
process.env.ENABLE_DEV_MOCK = '1'
|
|
const { closeDb, query } = await import('../../db/client.js')
|
|
const { createSemiAutoMock } = await import('../dev-mock/dev-mock-service.js')
|
|
const suffix = `${Date.now()}_${Math.floor(Math.random() * 1_000_000)}`
|
|
const orderNo = `IT-MOCK-SA-MOCK-${suffix}`
|
|
let orderId = 0
|
|
let configId = 0
|
|
try {
|
|
const result = await createSemiAutoMock({
|
|
orderNo,
|
|
skuName: `半自动Mock连接器-${suffix}`,
|
|
quantity: 1,
|
|
connectorMode: 'mock',
|
|
})
|
|
orderId = result.orderId
|
|
configId = result.configId
|
|
assert.equal(result.connectorMode, 'mock')
|
|
|
|
const row = await query<{ connector_mode: string }>(
|
|
'SELECT connector_mode FROM semi_auto_tasks WHERE id = $1',
|
|
[result.taskId],
|
|
)
|
|
assert.equal(row.rows[0]?.connector_mode, 'mock')
|
|
} finally {
|
|
if (orderId > 0) await query('DELETE FROM orders WHERE id = $1', [orderId])
|
|
if (configId > 0) {
|
|
await query('DELETE FROM manual_fulfillment_configs WHERE id = $1', [configId])
|
|
}
|
|
delete process.env.ENABLE_DEV_MOCK
|
|
await closeDb()
|
|
}
|
|
},
|
|
)
|