const assert = require('node:assert/strict'); const test = require('node:test'); const GenealogyApi = require('../utils/ApiClient.js'); function createClient() { return GenealogyApi.createClient({ baseUrl: 'https://api.example.test', tokenStore: { getItem() { return null; }, setItem() {}, removeItem() {} }, axiosInstance: { request() { return Promise.resolve({ data: { code: 200, data: {} } }); } } }); } test('API client exposes latest document-defined PC operations', () => { const client = createClient(); const allowed = [ 'clientId', 'tenantId', 'tokenKey', 'getToken', 'setToken', 'clearToken', 'buildApiUrl', 'register', 'login', 'loginBySms', 'sendSmsCode', 'currentProfile', 'updateProfile', 'changePassword', 'resetPassword', 'changePhone', 'deactivateAccount', 'logout', 'initResumableUpload', 'uploadResumableChunk', 'completeResumableUpload', 'captchaRequirement', 'captchaChallenge', 'captchaVerify', 'captchaChallengeUrl', 'captchaVerifyUrl', 'regionChildren', 'regionPath', 'regionSearch', 'regionDetail', 'genealogyQuota', 'notifications', 'unreadNotificationCount', 'markNotificationRead', 'markAllNotificationsRead', 'feeds', 'feedsPage', 'feedDetail', 'createFeed', 'updateFeed', 'deleteFeed', 'likeFeed', 'unlikeFeed', 'feedComments', 'feedCommentsPage', 'createFeedComment', 'feedCommentReplies', 'feedCommentRepliesPage', 'deleteFeedComment', 'generationPoems', 'generationPoemsManagement', 'createGenerationPoem', 'updateGenerationPoem', 'previewGenerationPoems', 'saveGenerationPoems', 'lineagePersons', 'lineagePersonsPage', 'lineagePersonOptions', 'lineageTree', 'createLineagePerson', 'lineagePersonDetail', 'updateLineagePerson', 'disableLineagePerson', 'createLineageChild', 'createLineageParent', 'createLineageSibling', 'createLineageSpouse', 'growthRecords', 'createGrowthRecord', 'growthRecordDetail', 'updateGrowthRecord', 'deleteGrowthRecord', 'relativeRecords', 'createRelativeRecord', 'relativeRecordDetail', 'updateRelativeRecord', 'deleteRelativeRecord', 'memos', 'createMemo', 'memoDetail', 'updateMemo', 'deleteMemo', 'deleteMeritRecord', 'deleteArticle', 'deleteAlbum', 'deleteAlbumPhoto', 'deleteVideo', 'deleteCeremony', 'deleteCeremonyGift', 'replaceCeremonyInvitees', 'ceremonyInvitations', 'respondCeremonyInvitation', 'myCeremonyInvitations' ].sort(); assert.deepEqual(Object.keys(client).sort(), allowed); }); test('password login stores access_token from the real AppLoginVo response', async () => { let stored = ''; const client = GenealogyApi.createClient({ baseUrl: 'https://api.example.test', tokenStore: { getItem() { return stored; }, setItem(_, value) { stored = value; }, removeItem() { stored = ''; } }, axiosInstance: { request(config) { assert.equal(config.method, 'post'); assert.equal(config.url, '/genealogy/pc/auth/login'); assert.equal(config.headers.Authorization, undefined); return Promise.resolve({ data: { code: 200, msg: '操作成功', data: { clientKey: 'web_pc', deviceType: 'pc', userType: 'app_user', profile: { userId: '2062179707935264769', tenantId: '000000', userNo: 'U2062179707910225920', phone: '19181970173', nickName: '叶子', realName: '', avatar: null, sex: '2', birthday: null, email: '', registerSource: 'h5', loginIp: '112.45.165.24', loginDate: '2026-07-28 11:06:09', status: '0', clientKey: 'web_pc', deviceType: 'pc' }, access_token: 'access-token', expire_in: 604800, client_id: 'ced7e5f0498645c6ec642dcf450b036f' } } }); } } }); await client.login({ phone: '13800000000', password: 'hash' }); assert.equal(client.getToken(), 'access-token'); }); test('authentication client owns tenant and grant fields and drops fields outside YAML DTOs', async () => { const calls = []; const client = GenealogyApi.createClient({ baseUrl: 'https://api.example.test', tenantId: 'tenant-from-config', tokenStore: { getItem() { return null; }, setItem() {}, removeItem() {} }, axiosInstance: { request(config) { calls.push(config); if (config.url.endsWith('/login')) { return Promise.resolve({ data: { code: 200, data: { access_token: 'login-token' } } }); } return Promise.resolve({ data: { code: 200, data: {} } }); } } }); await client.register({ grantType: 'legacy', tenantId: 'caller-tenant', phone: '13800000000', password: 'password-md5', smsCode: '1234', nickName: '小李', registerSource: 'APP', clientId: 'legacy-client', sceneCode: 'legacy-scene', validToken: 'legacy-ticket' }); await client.login({ grantType: 'legacy', tenantId: 'caller-tenant', phone: '13800000000', password: 'password-md5', validToken: 'captcha-ticket', clientId: 'legacy-client', sceneCode: 'legacy-scene' }); assert.deepEqual(calls[0].data, { grantType: 'password', tenantId: 'tenant-from-config', phone: '13800000000', password: 'password-md5', nickName: '小李', registerSource: 'PC', smsCode: '1234' }); assert.deepEqual(calls[1].data, { grantType: 'password', tenantId: 'tenant-from-config', phone: '13800000000', password: 'password-md5', validToken: 'captcha-ticket' }); }); test('authentication operationCode rejects values outside the YAML enum', async () => { const client = createClient(); await assert.rejects( async () => client.captchaRequirement('legacy-login', { subject: '13800000000' }), /不支持的PC 认证动作/ ); await assert.rejects( async () => client.sendSmsCode('password-login', { phone: '13800000000' }), /不支持的PC 短信认证动作/ ); }); test('login rejects legacy token aliases outside AppLoginVo', async () => { for (const legacyField of ['token', 'accessToken', 'tokenValue']) { const client = GenealogyApi.createClient({ baseUrl: 'https://api.example.test', tokenStore: { getItem() { return null; }, setItem() {}, removeItem() {} }, axiosInstance: { request() { return Promise.resolve({ data: { code: 200, data: { [legacyField]: 'legacy-token' } } }); } } }); await assert.rejects( client.login({ phone: '13800000000', password: 'password-md5' }), /登录响应缺少 token/ ); } }); test('sending an SMS code uses the PC operation path and documented request body', async () => { const client = GenealogyApi.createClient({ baseUrl: 'https://api.example.test', tokenStore: { getItem() { return null; }, setItem() {}, removeItem() {} }, axiosInstance: { request(config) { assert.equal(config.url, '/genealogy/pc/auth/sms/sms-login/code'); assert.deepEqual(config.data, { grantType: 'sms', tenantId: '000000', phone: '13800000000', validToken: 'captcha-ticket' }); assert.equal(config.headers.clientid, 'ced7e5f0498645c6ec642dcf450b036f'); return Promise.resolve({ data: { code: 200, data: null } }); } } }); await client.sendSmsCode('sms-login', { phone: '13800000000', validToken: 'captcha-ticket' }); }); test('PC verification operations keep operationCode in the path and clientid out of query and body', async () => { const calls = []; const client = GenealogyApi.createClient({ baseUrl: 'https://api.example.test', tokenStore: { getItem() { return null; }, setItem() {}, removeItem() {} }, axiosInstance: { request(config) { calls.push(config); return Promise.resolve({ data: { code: 200, data: { required: false } } }); } } }); const challenge = { tenantId: '000000', subject: '13800000000' }; const verification = { tenantId: '000000', subject: '13800000000', challengeId: 'challenge-1', providerCode: 'tianai', captchaType: 'SLIDER', payload: { track: { left: 120, top: 0, trackList: [] } } }; await client.captchaRequirement('sms-login', { subject: '13800000000' }); await client.captchaChallenge('sms-login', challenge); await client.captchaVerify('sms-login', verification); assert.deepEqual(calls.map((config) => [config.method, config.url, config.params, config.data]), [ ['get', '/genealogy/pc/auth/verification/sms-login/require', { tenantId: '000000', subject: '13800000000' }, undefined], ['post', '/genealogy/pc/auth/verification/sms-login/challenge', undefined, challenge], ['post', '/genealogy/pc/auth/verification/sms-login/verify', undefined, verification] ]); calls.forEach((config) => { assert.equal(config.headers.clientid, 'ced7e5f0498645c6ec642dcf450b036f'); assert.equal(config.headers.Authorization, undefined); }); assert.equal( client.captchaChallengeUrl('sms-login'), 'https://api.example.test/genealogy/pc/auth/verification/sms-login/challenge' ); assert.equal( client.captchaVerifyUrl('sms-login'), 'https://api.example.test/genealogy/pc/auth/verification/sms-login/verify' ); }); test('password reset adds the documented grant and tenant fields without body clientId', async () => { const client = GenealogyApi.createClient({ baseUrl: 'https://api.example.test', tokenStore: { getItem() { return null; }, setItem() {}, removeItem() {} }, axiosInstance: { request(config) { assert.equal(config.method, 'put'); assert.equal(config.url, '/genealogy/pc/auth/password/reset'); assert.equal(config.headers.Authorization, undefined); assert.deepEqual(config.data, { grantType: 'password', tenantId: '000000', phone: '13800000000', smsCode: '123456', newPassword: 'password-md5' }); return Promise.resolve({ data: { code: 200, data: null } }); } } }); await client.resetPassword({ phone: '13800000000', smsCode: '123456', newPassword: 'password-md5' }); }); test('registration and SMS login use the PC auth endpoints with SMS fields', async () => { const calls = []; const client = GenealogyApi.createClient({ baseUrl: 'https://api.example.test', tokenStore: { getItem() { return null; }, setItem() {}, removeItem() {} }, axiosInstance: { request(config) { calls.push(config); if (config.url.endsWith('/login/sms')) { return Promise.resolve({ data: { code: 200, data: { access_token: 'sms-token' } } }); } return Promise.resolve({ data: { code: 200, data: null } }); } } }); await client.register({ phone: '13800000000', smsCode: '1234', nickName: '小李', password: 'password-md5' }); await client.loginBySms({ phone: '13800000000', smsCode: '1234' }); assert.deepEqual(calls.map((config) => [config.method, config.url, config.data]), [ ['post', '/genealogy/pc/auth/register', { grantType: 'password', registerSource: 'PC', tenantId: '000000', phone: '13800000000', smsCode: '1234', nickName: '小李', password: 'password-md5' }], ['post', '/genealogy/pc/auth/login/sms', { grantType: 'sms', tenantId: '000000', phone: '13800000000', smsCode: '1234' }] ]); calls.forEach((config) => { assert.equal(config.headers.clientid, 'ced7e5f0498645c6ec642dcf450b036f'); assert.equal('clientId' in config.data, false); }); }); test('phone change and account deactivation retain PC bodies without clientId', async () => { let stored = 'access-token'; const calls = []; const client = GenealogyApi.createClient({ baseUrl: 'https://api.example.test', tokenStore: { getItem() { return stored; }, setItem(_, value) { stored = value; }, removeItem() { stored = ''; } }, axiosInstance: { request(config) { calls.push(config); return Promise.resolve({ data: { code: 200, data: null } }); } } }); await client.changePhone({ phone: '13900000000', smsCode: '123456' }); await client.deactivateAccount({ smsCode: '654321' }); assert.deepEqual(calls.map((config) => [config.method, config.url, config.data]), [ ['put', '/genealogy/pc/auth/phone', { phone: '13900000000', smsCode: '123456' }], ['post', '/genealogy/pc/auth/account/deactivate', { smsCode: '654321' }] ]); calls.forEach((config) => { assert.equal(config.headers.clientid, 'ced7e5f0498645c6ec642dcf450b036f'); assert.equal('clientId' in config.data, false); }); assert.equal(stored, ''); }); test('region methods use PC paths and retain the logged-in authorization header', async () => { const calls = []; const client = GenealogyApi.createClient({ baseUrl: 'https://api.example.test', tokenStore: { getItem() { return 'access-token'; }, setItem() {}, removeItem() {} }, axiosInstance: { request(config) { calls.push(config); return Promise.resolve({ data: { code: 200, data: [] } }); } } }); await client.regionChildren('0'); await client.regionPath('11'); await client.regionSearch({ keyword: '北京', limit: 20 }); await client.regionDetail('110101'); assert.deepEqual(calls.map((config) => [ config.method, config.url, config.params, config.headers.Authorization ]), [ ['get', '/genealogy/pc/region/children', { parentCode: '0' }, 'Bearer access-token'], ['get', '/genealogy/pc/region/path/11', undefined, 'Bearer access-token'], ['get', '/genealogy/pc/region/search', { keyword: '北京', limit: 20 }, 'Bearer access-token'], ['get', '/genealogy/pc/region/110101', undefined, 'Bearer access-token'] ]); }); test('current PC file methods use the three documented resumable paths and request forms', async () => { const calls = []; const client = GenealogyApi.createClient({ baseUrl: 'https://api.example.test', tokenStore: { getItem() { return 'access-token'; }, setItem() {}, removeItem() {} }, axiosInstance: { request(config) { calls.push(config); return Promise.resolve({ data: { code: 200, data: {} } }); } } }); const chunk = new Blob(['part'], { type: 'application/octet-stream' }); await client.initResumableUpload({ uploadId: 'UPLOAD202607240001', fileName: 'video.mp4', fileMd5: '0123456789abcdef0123456789abcdef', totalSize: 8388608, totalChunks: 2, chunkSize: 4194304, contentType: 'video/mp4' }); await client.uploadResumableChunk({ uploadId: 'UPLOAD202607240001', chunkIndex: 0, chunkMd5: 'chunk-md5', file: chunk }); await client.completeResumableUpload({ uploadId: 'UPLOAD202607240001', fileName: 'video.mp4', fileMd5: '0123456789abcdef0123456789abcdef', totalSize: 8388608, totalChunks: 2 }); assert.deepEqual(calls.map((config) => [ config.method, config.url, config.params, config.headers.Authorization ]), [ ['post', '/genealogy/pc/files/resumable/init', undefined, 'Bearer access-token'], ['post', '/genealogy/pc/files/resumable/chunk', undefined, 'Bearer access-token'], ['post', '/genealogy/pc/files/resumable/complete', undefined, 'Bearer access-token'] ]); assert.deepEqual(calls[0].data, { uploadId: 'UPLOAD202607240001', fileName: 'video.mp4', fileMd5: '0123456789abcdef0123456789abcdef', totalSize: 8388608, totalChunks: 2, chunkSize: 4194304, contentType: 'video/mp4' }); assert.equal(calls[1].data.get('uploadId'), 'UPLOAD202607240001'); assert.equal(calls[1].data.get('chunkIndex'), '0'); assert.equal(calls[1].data.get('chunkMd5'), 'chunk-md5'); assert.equal(calls[1].data.get('file').name, 'blob'); assert.deepEqual(calls[2].data, { uploadId: 'UPLOAD202607240001', fileName: 'video.mp4', fileMd5: '0123456789abcdef0123456789abcdef', totalSize: 8388608, totalChunks: 2 }); }); test('genealogy quota uses the current PC path without manufacturing a genealogy ID', async () => { const client = GenealogyApi.createClient({ baseUrl: 'https://api.example.test', tokenStore: { getItem() { return 'access-token'; }, setItem() {}, removeItem() {} }, axiosInstance: { request(config) { assert.equal(config.method, 'get'); assert.equal(config.url, '/genealogy/pc/genealogies/quota'); assert.equal(config.params, undefined); assert.equal(config.data, undefined); assert.equal(config.headers.Authorization, 'Bearer access-token'); assert.equal(config.headers.clientid, 'ced7e5f0498645c6ec642dcf450b036f'); return Promise.resolve({ data: { code: 200, data: { canCreate: true, canJoin: true } } }); } } }); await client.genealogyQuota(); }); test('notification methods use the current PC list, unread and read paths', async () => { const calls = []; const client = GenealogyApi.createClient({ baseUrl: 'https://api.example.test', tokenStore: { getItem() { return 'access-token'; }, setItem() {}, removeItem() {} }, axiosInstance: { request(config) { calls.push(config); return Promise.resolve({ data: { code: 200, data: [] } }); } } }); await client.notifications({ readStatus: '0' }); await client.unreadNotificationCount(); await client.markNotificationRead('2060000000000000001'); await client.markAllNotificationsRead(); assert.deepEqual(calls.map((config) => [config.method, config.url, config.params, config.data]), [ ['get', '/genealogy/pc/notifications', { readStatus: '0' }, undefined], ['get', '/genealogy/pc/notifications/unread-count', undefined, undefined], ['post', '/genealogy/pc/notifications/2060000000000000001/read', undefined, undefined], ['post', '/genealogy/pc/notifications/read-all', undefined, undefined] ]); calls.forEach((config) => { assert.equal(config.headers.Authorization, 'Bearer access-token'); assert.equal(config.headers.clientid, 'ced7e5f0498645c6ec642dcf450b036f'); }); }); test('family feed methods use the documented paths and request bodies', async () => { const calls = []; const client = GenealogyApi.createClient({ baseUrl: 'https://api.example.test', tokenStore: { getItem() { return 'access-token'; }, setItem() {}, removeItem() {} }, axiosInstance: { request(config) { calls.push(config); return Promise.resolve({ data: { code: 200, data: {} } }); } } }); await client.createFeed(900001001, { feedType: 'text', feedContent: '今天上传一张老照片。', mediaOssIds: '101,102', status: '0' }); await client.feedCommentsPage(900001001, 7001, { pageNum: 1, pageSize: 10 }); await client.feedCommentReplies(900001001, 7001, 8001); await client.feedCommentRepliesPage(900001001, 7001, 8001, { pageNum: 2, pageSize: 10 }); await client.deleteFeedComment(900001001, 7001, 8001); assert.deepEqual(calls.map((config) => [config.method, config.url, config.params, config.data]), [ ['post', '/genealogy/pc/genealogies/900001001/feeds', undefined, { feedType: 'text', feedContent: '今天上传一张老照片。', mediaOssIds: '101,102', status: '0' }], ['get', '/genealogy/pc/genealogies/900001001/feeds/7001/comments/page', { pageNum: 1, pageSize: 10 }, undefined], ['get', '/genealogy/pc/genealogies/900001001/feeds/7001/comments/8001/replies', undefined, undefined], ['get', '/genealogy/pc/genealogies/900001001/feeds/7001/comments/8001/replies/page', { pageNum: 2, pageSize: 10 }, undefined], ['delete', '/genealogy/pc/genealogies/900001001/feeds/7001/comments/8001', undefined, undefined] ]); }); test('generation poem methods use the Apifox PC paths and request bodies', async () => { const calls = []; const client = GenealogyApi.createClient({ baseUrl: 'https://api.example.test', tokenStore: { getItem() { return 'access-token'; }, setItem() {}, removeItem() {} }, axiosInstance: { request(config) { calls.push(config); return Promise.resolve({ data: { code: 200, data: {} } }); } } }); const poem = { generationNo: 3, generationText: '万', description: '第三世', sortOrder: 30, status: '0' }; const batch = { poemText: '德 承 家 亦', disableMissing: true }; await client.generationPoems(900001001); await client.generationPoemsManagement(900001001); await client.createGenerationPoem(900001001, poem); await client.updateGenerationPoem(900001001, 2060000000000000000n, poem); await client.previewGenerationPoems(900001001, batch); await client.saveGenerationPoems(900001001, batch); assert.deepEqual(calls.map((config) => [config.method, config.url, config.data]), [ ['get', '/genealogy/pc/genealogies/900001001/generation-poems', undefined], ['get', '/genealogy/pc/genealogies/900001001/generation-poems/management', undefined], ['post', '/genealogy/pc/genealogies/900001001/generation-poems', poem], ['put', '/genealogy/pc/genealogies/900001001/generation-poems/2060000000000000000', poem], ['post', '/genealogy/pc/genealogies/900001001/generation-poems/batch/preview', batch], ['post', '/genealogy/pc/genealogies/900001001/generation-poems/batch/save', batch] ]); }); test('lineage person methods use the Apifox PC paths, query and LineagePersonBody', async () => { const calls = []; const client = GenealogyApi.createClient({ baseUrl: 'https://api.example.test', tokenStore: { getItem() { return 'access-token'; }, setItem() {}, removeItem() {} }, axiosInstance: { request(config) { calls.push(config); return Promise.resolve({ data: { code: 200, data: {} } }); } } }); const person = { name: '李明', sex: '1', generation: 3, generationName: '德', personNo: 'L-003', aliasName: '明远', birthDate: '1990-01-01', deathDate: null, biography: '人物简介' }; const genealogyId = '2060000000000000001'; const personId = '2060000000000000002'; await client.lineagePersons(genealogyId); await client.lineagePersonsPage(genealogyId, { pageNum: 1, pageSize: 10, keyword: '李', generation: 3, personStatus: '0' }); await client.lineagePersonOptions(genealogyId); await client.lineageTree(genealogyId); await client.createLineagePerson(genealogyId, person); await client.lineagePersonDetail(genealogyId, personId); await client.updateLineagePerson(genealogyId, personId, person); await client.disableLineagePerson(genealogyId, personId); await client.createLineageChild(genealogyId, personId, person); await client.createLineageParent(genealogyId, personId, person); await client.createLineageSibling(genealogyId, personId, person); await client.createLineageSpouse(genealogyId, personId, person); assert.deepEqual(calls.map((config) => [ config.method, config.url, config.params, config.data, config.headers.Authorization, config.headers.clientid ]), [ ['get', '/genealogy/pc/genealogies/2060000000000000001/lineage/persons', undefined, undefined, 'Bearer access-token', 'ced7e5f0498645c6ec642dcf450b036f'], ['get', '/genealogy/pc/genealogies/2060000000000000001/lineage/persons/page', { pageNum: 1, pageSize: 10, keyword: '李', generation: 3, personStatus: '0' }, undefined, 'Bearer access-token', 'ced7e5f0498645c6ec642dcf450b036f'], ['get', '/genealogy/pc/genealogies/2060000000000000001/lineage/persons/options', undefined, undefined, 'Bearer access-token', 'ced7e5f0498645c6ec642dcf450b036f'], ['get', '/genealogy/pc/genealogies/2060000000000000001/lineage/tree', undefined, undefined, 'Bearer access-token', 'ced7e5f0498645c6ec642dcf450b036f'], ['post', '/genealogy/pc/genealogies/2060000000000000001/lineage/persons', undefined, person, 'Bearer access-token', 'ced7e5f0498645c6ec642dcf450b036f'], ['get', '/genealogy/pc/genealogies/2060000000000000001/lineage/persons/2060000000000000002', undefined, undefined, 'Bearer access-token', 'ced7e5f0498645c6ec642dcf450b036f'], ['put', '/genealogy/pc/genealogies/2060000000000000001/lineage/persons/2060000000000000002', undefined, person, 'Bearer access-token', 'ced7e5f0498645c6ec642dcf450b036f'], ['delete', '/genealogy/pc/genealogies/2060000000000000001/lineage/persons/2060000000000000002', undefined, undefined, 'Bearer access-token', 'ced7e5f0498645c6ec642dcf450b036f'], ['post', '/genealogy/pc/genealogies/2060000000000000001/lineage/persons/2060000000000000002/children', undefined, person, 'Bearer access-token', 'ced7e5f0498645c6ec642dcf450b036f'], ['post', '/genealogy/pc/genealogies/2060000000000000001/lineage/persons/2060000000000000002/parents', undefined, person, 'Bearer access-token', 'ced7e5f0498645c6ec642dcf450b036f'], ['post', '/genealogy/pc/genealogies/2060000000000000001/lineage/persons/2060000000000000002/siblings', undefined, person, 'Bearer access-token', 'ced7e5f0498645c6ec642dcf450b036f'], ['post', '/genealogy/pc/genealogies/2060000000000000001/lineage/persons/2060000000000000002/spouses', undefined, person, 'Bearer access-token', 'ced7e5f0498645c6ec642dcf450b036f'] ]); }); test('growth record methods use the Apifox PC paths and GrowthRecordBody', async () => { const calls = []; const client = GenealogyApi.createClient({ baseUrl: 'https://api.example.test', tokenStore: { getItem() { return 'access-token'; }, setItem() {}, removeItem() {} }, axiosInstance: { request(config) { calls.push(config); return Promise.resolve({ data: { code: 200, data: {} } }); } } }); const genealogyId = '2060000000000000001'; const recordId = '2060000000000000002'; const record = { lineagePersonId: '2060000000000000003', recordType: 'birth', recordTitle: '出生记录', recordContent: '平安出生', recordDate: '2026-07-24', remindTime: '2026-07-24T09:00:00+08:00', mediaOssIds: '101,102', sortOrder: 1, status: 'enabled' }; await client.growthRecords(genealogyId); await client.createGrowthRecord(genealogyId, record); await client.growthRecordDetail(genealogyId, recordId); await client.updateGrowthRecord(genealogyId, recordId, record); await client.deleteGrowthRecord(genealogyId, recordId); assert.deepEqual(calls.map((config) => [ config.method, config.url, config.params, config.data, config.headers.Authorization, config.headers.clientid ]), [ ['get', '/genealogy/pc/genealogies/2060000000000000001/growth-records', undefined, undefined, 'Bearer access-token', 'ced7e5f0498645c6ec642dcf450b036f'], ['post', '/genealogy/pc/genealogies/2060000000000000001/growth-records', undefined, record, 'Bearer access-token', 'ced7e5f0498645c6ec642dcf450b036f'], ['get', '/genealogy/pc/genealogies/2060000000000000001/growth-records/2060000000000000002', undefined, undefined, 'Bearer access-token', 'ced7e5f0498645c6ec642dcf450b036f'], ['put', '/genealogy/pc/genealogies/2060000000000000001/growth-records/2060000000000000002', undefined, record, 'Bearer access-token', 'ced7e5f0498645c6ec642dcf450b036f'], ['delete', '/genealogy/pc/genealogies/2060000000000000001/growth-records/2060000000000000002', undefined, undefined, 'Bearer access-token', 'ced7e5f0498645c6ec642dcf450b036f'] ]); }); test('relative record methods use the Apifox PC paths and RelativeRecordBody', async () => { const calls = []; const client = GenealogyApi.createClient({ baseUrl: 'https://api.example.test', tokenStore: { getItem() { return 'access-token'; }, setItem() {}, removeItem() {} }, axiosInstance: { request(config) { calls.push(config); return Promise.resolve({ data: { code: 200, data: {} } }); } } }); const genealogyId = '2060000000000000001'; const relativeId = '2060000000000000002'; const record = { relativeName: '王叔', relationName: '长辈', eventName: '寿宴', eventTime: '2026-07-24T09:00:00+08:00', giftAmount: 500, recordContent: '出席', mediaOssIds: '101', sortOrder: 1, status: 'enabled' }; await client.relativeRecords(genealogyId); await client.createRelativeRecord(genealogyId, record); await client.relativeRecordDetail(genealogyId, relativeId); await client.updateRelativeRecord(genealogyId, relativeId, record); await client.deleteRelativeRecord(genealogyId, relativeId); assert.deepEqual(calls.map((config) => [config.method, config.url, config.data]), [ ['get', '/genealogy/pc/genealogies/2060000000000000001/relative-records', undefined], ['post', '/genealogy/pc/genealogies/2060000000000000001/relative-records', record], ['get', '/genealogy/pc/genealogies/2060000000000000001/relative-records/2060000000000000002', undefined], ['put', '/genealogy/pc/genealogies/2060000000000000001/relative-records/2060000000000000002', record], ['delete', '/genealogy/pc/genealogies/2060000000000000001/relative-records/2060000000000000002', undefined] ]); }); test('memo methods use the Apifox PC paths and documented request fields', async () => { const calls = []; const client = GenealogyApi.createClient({ baseUrl: 'https://api.example.test', tokenStore: { getItem() { return 'access-token'; }, setItem() {}, removeItem() {} }, axiosInstance: { request(config) { calls.push(config); return Promise.resolve({ data: { code: 200, data: {} } }); } } }); const genealogyId = '2060000000000000001'; const memoId = '2060000000000000002'; const memo = { memoTitle: '祭祖提醒', memoContent: '准备供品', remindTime: '2026-07-24T09:00:00+08:00', completed: '0', mediaOssIds: '101,102', sortOrder: 1, status: 'enabled' }; await client.memos(genealogyId); await client.createMemo(genealogyId, memo); await client.memoDetail(genealogyId, memoId); await client.updateMemo(genealogyId, memoId, memo); await client.deleteMemo(genealogyId, memoId); assert.deepEqual(calls.map((config) => [config.method, config.url, config.data]), [ ['get', '/genealogy/pc/genealogies/2060000000000000001/memos', undefined], ['post', '/genealogy/pc/genealogies/2060000000000000001/memos', memo], ['get', '/genealogy/pc/genealogies/2060000000000000001/memos/2060000000000000002', undefined], ['put', '/genealogy/pc/genealogies/2060000000000000001/memos/2060000000000000002', memo], ['delete', '/genealogy/pc/genealogies/2060000000000000001/memos/2060000000000000002', undefined] ]); }); test('merit record deletion uses the only documented PC merit operation', async () => { const client = GenealogyApi.createClient({ baseUrl: 'https://api.example.test', tokenStore: { getItem() { return 'access-token'; }, setItem() {}, removeItem() {} }, axiosInstance: { request(config) { assert.equal(config.method, 'delete'); assert.equal(config.url, '/genealogy/pc/genealogies/2060000000000000001/merit-records/2060000000000000002'); assert.equal(config.headers.Authorization, 'Bearer access-token'); assert.equal(config.headers.clientid, 'ced7e5f0498645c6ec642dcf450b036f'); return Promise.resolve({ data: { code: 200, data: null } }); } } }); await client.deleteMeritRecord('2060000000000000001', '2060000000000000002'); }); test('remaining document-defined deletion operations use PC paths only', async () => { const calls = []; const client = GenealogyApi.createClient({ baseUrl: 'https://api.example.test', tokenStore: { getItem() { return 'access-token'; }, setItem() {}, removeItem() {} }, axiosInstance: { request(config) { calls.push(config); return Promise.resolve({ data: { code: 200, data: null } }); } } }); const genealogyId = '2060000000000000001'; await client.deleteArticle(genealogyId, '2060000000000000002'); await client.deleteAlbum(genealogyId, '2060000000000000003'); await client.deleteAlbumPhoto(genealogyId, '2060000000000000003', '2060000000000000004'); await client.deleteVideo(genealogyId, '2060000000000000005'); await client.deleteCeremony(genealogyId, '2060000000000000006'); await client.deleteCeremonyGift(genealogyId, '2060000000000000006', '2060000000000000007'); assert.deepEqual(calls.map((config) => [config.method, config.url, config.data, config.headers.clientid]), [ ['delete', '/genealogy/pc/genealogies/2060000000000000001/articles/2060000000000000002', undefined, 'ced7e5f0498645c6ec642dcf450b036f'], ['delete', '/genealogy/pc/genealogies/2060000000000000001/albums/2060000000000000003', undefined, 'ced7e5f0498645c6ec642dcf450b036f'], ['delete', '/genealogy/pc/genealogies/2060000000000000001/albums/2060000000000000003/photos/2060000000000000004', undefined, 'ced7e5f0498645c6ec642dcf450b036f'], ['delete', '/genealogy/pc/genealogies/2060000000000000001/videos/2060000000000000005', undefined, 'ced7e5f0498645c6ec642dcf450b036f'], ['delete', '/genealogy/pc/genealogies/2060000000000000001/ceremonies/2060000000000000006', undefined, 'ced7e5f0498645c6ec642dcf450b036f'], ['delete', '/genealogy/pc/genealogies/2060000000000000001/ceremonies/2060000000000000006/gifts/2060000000000000007', undefined, 'ced7e5f0498645c6ec642dcf450b036f'] ]); }); test('ceremony invitation methods use the current PC invitation contracts', async () => { const calls = []; const client = GenealogyApi.createClient({ baseUrl: 'https://api.example.test', tokenStore: { getItem() { return 'access-token'; }, setItem() {}, removeItem() {} }, axiosInstance: { request(config) { calls.push(config); return Promise.resolve({ data: { code: 200, data: [] } }); } } }); const genealogyId = '2060000000000000001'; const ceremonyId = '2060000000000000002'; await client.replaceCeremonyInvitees(genealogyId, ceremonyId, { inviteeUserIds: ['900000001', '900000002'] }); await client.ceremonyInvitations(genealogyId, ceremonyId); await client.respondCeremonyInvitation(genealogyId, ceremonyId, { inviteStatus: 'ACCEPTED' }); await client.myCeremonyInvitations(); assert.deepEqual(calls.map((config) => [config.method, config.url, config.data]), [ ['put', '/genealogy/pc/genealogies/2060000000000000001/ceremonies/2060000000000000002/invitees', { inviteeUserIds: ['900000001', '900000002'] }], ['get', '/genealogy/pc/genealogies/2060000000000000001/ceremonies/2060000000000000002/invitations', undefined], ['put', '/genealogy/pc/genealogies/2060000000000000001/ceremonies/2060000000000000002/invitations/me', { inviteStatus: 'ACCEPTED' }], ['get', '/genealogy/pc/genealogies/ceremony-invitations/mine', undefined] ]); calls.forEach((config) => { assert.equal(config.headers.Authorization, 'Bearer access-token'); assert.equal(config.headers.clientid, 'ced7e5f0498645c6ec642dcf450b036f'); }); });