diff --git a/apps/backend/src/services/admin/platform-config/khhao.js b/apps/backend/src/services/admin/platform-config/khhao.js new file mode 100644 index 00000000..a4a4e69b --- /dev/null +++ b/apps/backend/src/services/admin/platform-config/khhao.js @@ -0,0 +1,83 @@ +// @ts-check + +import { getKhhaoSourceConfig } from '../../platforms/khhao/source-config-service.js' +import { mapKhhaoOrderPreviewList } from '../../platforms/khhao/order-mapper-service.js' +import { maskSecret } from './mappers.js' + +export function resolveAdminKhhaoCredentialPayload( + payload = {}, + options = {}, +) { + const savedSource = /** @type {Record} */ ( + options.savedSource || getKhhaoSourceConfig() + ) + + return { + baseUrl: String(payload.baseUrl || savedSource.baseUrl || '').trim(), + username: String(payload.username || savedSource.username || '').trim(), + password: String(payload.password || savedSource.password || '').trim(), + maxCaptchaAttempts: payload.maxCaptchaAttempts || savedSource.maxCaptchaAttempts, + } +} + +export function buildAdminKhhaoLoginPayload( + payload = {}, + options = {}, +) { + const credential = resolveAdminKhhaoCredentialPayload(payload, options) + const requestIdScope = String(options.requestIdScope || 'test-login').trim() || 'test-login' + + return { + ...credential, + includeImageBase64: payload.includeImageBase64, + requestId: `admin-khhao-${requestIdScope}:${credential.username || 'anonymous'}`, + } +} + +export function buildAdminKhhaoSyncPayload( + payload = {}, + options = {}, +) { + return { + ...payload, + ...resolveAdminKhhaoCredentialPayload(payload, options), + } +} + +export function buildAdminKhhaoLoginResult(session = {}) { + const cookieKeys = Object.keys(session.cookieMap || {}) + + return { + baseUrl: String(session.baseUrl || '').trim(), + username: String(session.username || '').trim(), + loggedInAt: String(session.loggedInAt || '').trim(), + attempt: Number(session.attempt || 0), + responseMessage: String(session.responseMessage || '').trim(), + captcha: { + recognizedText: String(session.captchaText || '').trim(), + imageBase64: String(session.captchaImageBase64 || '').trim(), + }, + session: { + cookieKeys, + cookieCount: cookieKeys.length, + cookieHeaderMasked: maskSecret(session.cookieHeader), + }, + } +} + +export function buildAdminKhhaoOrderQueryResult( + session = {}, + result = {}, +) { + const items = Array.isArray(result.items) ? result.items : [] + + return { + baseUrl: String(session.baseUrl || '').trim(), + page: Number(result.page || 1), + limit: Number(result.limit || 0), + total: Number(result.total || 0), + itemCount: items.length, + items: mapKhhaoOrderPreviewList(items), + rawItems: items, + } +} diff --git a/apps/backend/src/services/admin/platform-config/khhao.test.js b/apps/backend/src/services/admin/platform-config/khhao.test.js new file mode 100644 index 00000000..9a65fc0a --- /dev/null +++ b/apps/backend/src/services/admin/platform-config/khhao.test.js @@ -0,0 +1,166 @@ +import test from 'node:test' +import assert from 'node:assert/strict' + +import { + buildAdminKhhaoLoginPayload, + buildAdminKhhaoLoginResult, + buildAdminKhhaoOrderQueryResult, + buildAdminKhhaoSyncPayload, + resolveAdminKhhaoCredentialPayload, +} from './khhao.js' + +test('resolveAdminKhhaoCredentialPayload merges payload and saved source defaults', () => { + assert.deepEqual( + resolveAdminKhhaoCredentialPayload( + { + baseUrl: ' https://override.example.com ', + maxCaptchaAttempts: 5, + }, + { + savedSource: { + baseUrl: 'https://saved.example.com', + username: 'saved-user', + password: 'saved-pass', + maxCaptchaAttempts: 3, + }, + }, + ), + { + baseUrl: 'https://override.example.com', + username: 'saved-user', + password: 'saved-pass', + maxCaptchaAttempts: 5, + }, + ) +}) + +test('buildAdminKhhaoLoginPayload appends request id and includeImageBase64', () => { + assert.deepEqual( + buildAdminKhhaoLoginPayload( + { + username: ' user ', + includeImageBase64: true, + }, + { + requestIdScope: 'query-orders', + savedSource: { + baseUrl: 'https://saved.example.com', + password: 'saved-pass', + maxCaptchaAttempts: 3, + }, + }, + ), + { + baseUrl: 'https://saved.example.com', + username: 'user', + password: 'saved-pass', + maxCaptchaAttempts: 3, + includeImageBase64: true, + requestId: 'admin-khhao-query-orders:user', + }, + ) +}) + +test('buildAdminKhhaoSyncPayload merges normalized credential fields into payload', () => { + assert.deepEqual( + buildAdminKhhaoSyncPayload( + { + page: 2, + limit: 20, + username: ' user ', + }, + { + savedSource: { + baseUrl: 'https://saved.example.com', + password: 'saved-pass', + maxCaptchaAttempts: 4, + }, + }, + ), + { + page: 2, + limit: 20, + username: 'user', + baseUrl: 'https://saved.example.com', + password: 'saved-pass', + maxCaptchaAttempts: 4, + }, + ) +}) + +test('buildAdminKhhaoLoginResult assembles login response payload', () => { + assert.deepEqual( + buildAdminKhhaoLoginResult({ + baseUrl: ' https://admin.khhao.com ', + username: ' test-user ', + loggedInAt: ' 2026-05-04T12:00:00.000Z ', + attempt: 2, + responseMessage: ' success ', + captchaText: ' abcd ', + captchaImageBase64: ' image-data ', + cookieMap: { + PHPSESSID: 'a', + foo: 'b', + }, + cookieHeader: 'abcdef1234567890', + }), + { + baseUrl: 'https://admin.khhao.com', + username: 'test-user', + loggedInAt: '2026-05-04T12:00:00.000Z', + attempt: 2, + responseMessage: 'success', + captcha: { + recognizedText: 'abcd', + imageBase64: 'image-data', + }, + session: { + cookieKeys: ['PHPSESSID', 'foo'], + cookieCount: 2, + cookieHeaderMasked: 'abcdef****567890', + }, + }, + ) +}) + +test('buildAdminKhhaoOrderQueryResult maps preview list and preserves raw items', () => { + const rawItems = [ + { + ordersn: ' A1 ', + pingtai: '3', + pingtaiName: '快手', + shopid: '1001', + shopName: '店铺A', + goodid: 'g1', + goodName: '商品A', + sku: 'sku-1', + num: '2', + fee: '12.34', + status: '2', + statusName: '已支付', + addtime: '2026-05-04 10:00:00', + }, + ] + + const result = buildAdminKhhaoOrderQueryResult( + { + baseUrl: ' https://admin.khhao.com ', + }, + { + page: 2, + limit: 20, + total: 50, + items: rawItems, + }, + ) + + assert.equal(result.baseUrl, 'https://admin.khhao.com') + assert.equal(result.page, 2) + assert.equal(result.limit, 20) + assert.equal(result.total, 50) + assert.equal(result.itemCount, 1) + assert.equal(result.rawItems, rawItems) + assert.equal(result.items[0].platformOrderId, 'A1') + assert.equal(result.items[0].platform, 'kuaishou') + assert.equal(result.items[0].itemTitle, '商品A') +}) diff --git a/apps/backend/src/services/admin/platform-config/service.js b/apps/backend/src/services/admin/platform-config/service.js index d22bdd48..068699aa 100644 --- a/apps/backend/src/services/admin/platform-config/service.js +++ b/apps/backend/src/services/admin/platform-config/service.js @@ -9,7 +9,6 @@ import { saveAgisoMessagingConfig, } from '../../platforms/agiso/shop-config-service.js' import { enrichAgisoXianyuTradeOrder } from '../../platforms/agiso/xianyu/order-detail-service.js' -import { mapKhhaoOrderPreviewList } from '../../platforms/khhao/order-mapper-service.js' import { queryKhhaoOrderList } from '../../platforms/khhao/order-query-service.js' import { syncKhhaoOrders } from '../../platforms/khhao/order-sync-service.js' import { getKhhaoSourceConfig, getKhhaoSourcesFilePath, saveKhhaoSourceConfig } from '../../platforms/khhao/source-config-service.js' @@ -87,6 +86,13 @@ import { normalizeFulfillmentLookupPayload, resolveFulfillmentLookupDetail, } from './fulfillment.js' +import { + buildAdminKhhaoLoginPayload, + buildAdminKhhaoLoginResult, + buildAdminKhhaoOrderQueryResult, + buildAdminKhhaoSyncPayload, + resolveAdminKhhaoCredentialPayload, +} from './khhao.js' import { buildAdminCloudtentaclesLoginResult, buildAdminCloudtentaclesPersistedSessionPayload, @@ -116,7 +122,6 @@ import { mapAdminKuaishouCloudFulfillmentSource, mapAdminKuaishouEticketSourceConfig, maskPhone, - maskSecret, } from './mappers.js' /** @typedef {import('../../../types/admin-write-inputs.js').AdminAgisoShopConfigSaveInput} AdminAgisoShopConfigSaveInput */ @@ -241,74 +246,30 @@ export function updateAdminKhhaoSourceConfig(payload = /** @type {AdminKhhaoSour /** @param {AdminKhhaoTestLoginInput} [payload] */ export async function testAdminKhhaoLogin(payload = /** @type {AdminKhhaoTestLoginInput} */ ({})) { - const savedSource = getKhhaoSourceConfig() - const session = await loginKhhaoSession({ - baseUrl: String(payload.baseUrl || savedSource.baseUrl || '').trim(), - username: String(payload.username || savedSource.username || '').trim(), - password: String(payload.password || savedSource.password || '').trim(), - maxCaptchaAttempts: payload.maxCaptchaAttempts || savedSource.maxCaptchaAttempts, - includeImageBase64: payload.includeImageBase64, - requestId: `admin-khhao-test-login:${String(payload.username || savedSource.username || '').trim() || 'anonymous'}`, - }) - - return { - baseUrl: session.baseUrl, - username: session.username, - loggedInAt: session.loggedInAt, - attempt: session.attempt, - responseMessage: session.responseMessage, - captcha: { - recognizedText: session.captchaText, - imageBase64: session.captchaImageBase64, - }, - session: { - cookieKeys: Object.keys(session.cookieMap), - cookieCount: Object.keys(session.cookieMap).length, - cookieHeaderMasked: maskSecret(session.cookieHeader), - }, - } + const session = await loginKhhaoSession(buildAdminKhhaoLoginPayload(payload)) + return buildAdminKhhaoLoginResult(session) } /** @param {AdminKhhaoOrderQueryInput} [payload] */ export async function queryAdminKhhaoOrders(payload = /** @type {AdminKhhaoOrderQueryInput} */ ({})) { - const savedSource = getKhhaoSourceConfig() - const session = await loginKhhaoSession({ - baseUrl: String(payload.baseUrl || savedSource.baseUrl || '').trim(), - username: String(payload.username || savedSource.username || '').trim(), - password: String(payload.password || savedSource.password || '').trim(), - maxCaptchaAttempts: payload.maxCaptchaAttempts || savedSource.maxCaptchaAttempts, - requestId: `admin-khhao-query-orders:${String(payload.username || savedSource.username || '').trim() || 'anonymous'}`, - }) + const credential = resolveAdminKhhaoCredentialPayload(payload) + const session = await loginKhhaoSession(buildAdminKhhaoLoginPayload(payload, { + requestIdScope: 'query-orders', + })) const result = await queryKhhaoOrderList({ - baseUrl: String(payload.baseUrl || savedSource.baseUrl || '').trim(), + baseUrl: credential.baseUrl, page: payload.page, limit: payload.limit, session, }) - const previews = mapKhhaoOrderPreviewList(result.items) - return { - baseUrl: session.baseUrl, - page: result.page, - limit: result.limit, - total: result.total, - itemCount: result.items.length, - items: previews, - rawItems: result.items, - } + return buildAdminKhhaoOrderQueryResult(session, result) } /** @param {AdminKhhaoOrderQueryInput} [payload] */ export async function syncAdminKhhaoOrders(payload = /** @type {AdminKhhaoOrderQueryInput} */ ({})) { - const savedSource = getKhhaoSourceConfig() - return syncKhhaoOrders({ - ...payload, - baseUrl: String(payload.baseUrl || savedSource.baseUrl || '').trim(), - username: String(payload.username || savedSource.username || '').trim(), - password: String(payload.password || savedSource.password || '').trim(), - maxCaptchaAttempts: payload.maxCaptchaAttempts || savedSource.maxCaptchaAttempts, - }) + return syncKhhaoOrders(buildAdminKhhaoSyncPayload(payload)) } /** @param {AdminKuaishouEticketDetailQueryInput} [payload] */