import test from 'node:test'; import assert from 'node:assert/strict'; import { readFileSync } from 'node:fs'; import { Script, createContext } from 'node:vm'; const read = name => JSON.parse(readFileSync(new URL(`../postman/${name}`, import.meta.url), 'utf8')); const collection = read('gold-miner.postman_collection.json'); const template = read('gold-miner.postman_environment.json'); const requests = collection.item.flatMap(folder => folder.item); const request = (folder, prefix) => { const value = collection.item[folder - 1].item.find(item => item.name.startsWith(prefix)); assert.ok(value, `${folder}: ${prefix}`); return value; }; const script = (item, listen) => item.event.find(event => event.listen === listen)?.script.exec.join('\n') || ''; const copy = value => value === undefined ? value : JSON.parse(JSON.stringify(value)); const SKIP = Symbol('skip request'); // Executes the exported scripts without HTTP, Postman packages or real credentials. function sandbox(values = {}) { const env = new Map(template.values.map(({ key, value }) => [key, value])); Object.entries({ baseUrl: 'https://test.invalid', uid: 'test-user', token: 'test-token', ...values }) .forEach(([key, value]) => env.set(key, String(value))); let local = new Map(), counter = 0, response = {}, responseStatus = 200, failures = []; const replace = value => value.replace(/\{\{([^}]+)\}\}/g, (_, key) => { if (key === '$guid') return `00000000-0000-4000-8000-${String(++counter).padStart(12, '0')}`; assert.ok(local.has(key) || env.has(key), `unresolved variable: ${key}`); return local.has(key) ? local.get(key) : env.get(key); }); const pm = { environment: { get: key => env.get(key), set: (key, value) => env.set(key, String(value)), unset: key => env.delete(key) }, variables: { get: key => local.get(key) ?? env.get(key), set: (key, value) => local.set(key, String(value)), replaceIn: replace }, execution: { skipRequest() { throw SKIP; } }, response: { json: () => copy(response), to: { have: { status: status => assert.equal(status, responseStatus) } } }, expect: actual => ({ to: { eql: expected => assert.deepEqual(copy(actual), copy(expected)) } }), test(name, callback) { try { callback(); } catch (error) { failures.push(new Error(`${name}: ${error.message}`)); } }, }; function run(code) { new Script(code).runInContext(createContext({ pm, console: { error() {} } })); } return { env, pre(item) { local = new Map(); try { run(script(collection, 'prerequest')); run(script(item, 'prerequest')); return { body: item.request.body ? JSON.parse(replace(item.request.body.raw)) : undefined }; } catch (error) { if (error === SKIP) return { skipped: true }; throw error; } }, post(item, payload, status = 200) { response = payload; responseStatus = status; failures = []; run(script(item, 'test')); if (failures.length) throw new AggregateError(failures, failures.map(error => error.message).join('\n')); }, }; } const periodId = 'goldMiner:2026-09-17'; const startsAt = Date.parse('2026-09-17T00:00:00+08:00'); function info(overrides = {}) { const d = { status: 'unlocked', periodId, startsAt, endsAt: startsAt + 4 * 86400000, serverTime: startsAt + 1000, productId: 'gold_miner', priceFen: 123, progressWins: 5, maxTarget: 5, ...overrides, }; d.tasks = [3, 5].map((targetWins, index) => ({ taskId: `task_${index + 1}`, targetWins, itemsSnapshot: [{type:'coin',count:50}], progress: Math.min(d.progressWins, targetWins), completed: d.progressWins >= targetWins, claimStatus: d.progressWins >= targetWins ? d.status === 'unlocked' ? 'claimable' : 'pending_unlock' : 'locked', })); return d; } const success = data => ({ code: 1, data, msg: '成功' }); const grant = () => ({ grantId: 'grant-1', taskId: 'task_1', items: [{ type: 'coin', count: 50 }], claimStatus: 'issuing', claimedThrough: 0, requiresClientDelivery: true }); test('collection, environment and every script are well formed', () => { assert.equal(collection.info.schema, 'https://schema.getpostman.com/json/collection/v2.1.0/collection.json'); assert.equal(requests.length, 48); assert.equal(new Set(template.values.map(value => value.key)).size, template.values.length); for (const key of ['token', 'adminToken', 'gmWinBody', 'legacySessionFrom']) { const value = template.values.find(value => value.key === key); assert.equal(value.type, 'secret'); assert.equal(value.value, ''); } for (const item of [collection, ...requests]) { for (const event of item.event) new Script(event.script.exec.join('\n'), { filename: `${item.name || 'collection'}:${event.listen}` }); } for (const item of requests) { assert.ok(['POST','GET'].includes(item.request.method)); if (item.request.method === 'POST') { assert.equal(item.request.body.mode, 'raw'); assert.equal(item.request.header[0].value, 'application/json'); } else { assert.equal(item.request.url.raw, '{{baseUrl}}/activityConfig/list'); assert.equal(item.request.body, undefined); } assert.equal(item.request.url.raw, `{{baseUrl}}/${item.request.url.path.join('/')}`); const functionName = item.request.url.path.join('/'); assert.ok(readFileSync(new URL(`../../${functionName}.ts`, import.meta.url), 'utf8').length > 0); if (functionName.startsWith('goldMiner')) { assert.ok(['goldMiner/index', 'goldMiner/admin', 'goldMiner/merchantNotify'].includes(functionName), 'activity requests must use the current public routes'); const yaml = readFileSync(new URL(`../../${functionName}.yaml`, import.meta.url), 'utf8'); assert.match(yaml, /^name: (.+)$/m); assert.equal(yaml.match(/^name: (.+)$/m)[1].trim(), functionName); assert.match(yaml, /^\s+- POST\s*$/m); } assert.ok(script(item, 'test').includes('pm.test(')); if (item.request.body && !item.request.body.raw.startsWith('{{')) JSON.parse(item.request.body.raw); } }); test('unconfigured application is skipped without producing a request', () => { const s = sandbox({ baseUrl: 'https://.laf.run' }); assert.deepEqual(s.pre(request(1, '01')), { skipped: true }); }); test('changing account clears grants, progress payload and selected targets', () => { const s = sandbox(); s.pre(request(1, '01')); for (const key of ['gmWinBody', 'gmGrant', 'grantId', 'gmSavedGrantId', 'claimPeriodId', 'claimTaskId', 'outTradeNo']) s.env.set(key, 'stale'); s.env.set('uid', 'another-user'); assert.deepEqual(s.pre(request(1, '01')), { skipped: true }); for (const key of ['gmWinBody', 'gmGrant', 'grantId', 'gmSavedGrantId', 'claimPeriodId', 'claimTaskId', 'outTradeNo']) assert.equal(s.env.get(key), undefined); assert.equal(s.pre(request(1, '01')).body.uid, 'another-user'); }); test('active info enforces shared progress and independent task claimability', () => { const s = sandbox(); const item = request(1, '01'); s.post(item, success(info())); assert.equal(s.env.get('periodId'), periodId); assert.equal(s.env.get('secondTaskId'), 'task_2'); const broken = info(); broken.tasks[1].claimStatus = 'locked'; assert.throws(() => s.post(item, success(broken)), /活动状态结构/); }); test('locked and unavailable responses accept missing task/product fields', () => { const s = sandbox(); const item = request(1, '01'); s.post(item, success(info())); s.post(item, success({ status: 'locked', serverTime: startsAt, periodId, tasks: [] })); assert.equal(s.env.get('productId'), undefined); assert.equal(s.env.get('periodId'), periodId); s.post(item, success({ status: 'unavailable', serverTime: startsAt, tasks: [] })); assert.equal(s.env.get('periodId'), undefined); }); test('carry opening assertions check unlocked status and reset progress without exposing source', () => { const s = sandbox({ expectedStatus: 'unlocked', expectedProgressWins: '0' }); const d = info({ progressWins: 0 }); s.post(request(1, '01'), success(d)); d.status = 'purchasable'; assert.throws(() => s.post(request(1, '01'), success(d)), /活动状态结构/); }); test('info scripts reject leaked fields and preserve issuing as the only pending task', () => { const s = sandbox(); const item = request(1, '01'); const d = info(); d.tasks[0].claimStatus = 'issuing'; s.post(item, success(d)); d.tasks[0].grantId = 'internal'; assert.throws(() => s.post(item, success(d)), /活动状态结构/); delete d.tasks[0].grantId; d.entitlement = {status:'unlocked'}; assert.throws(() => s.post(item, success(d)), /活动状态结构/); }); test('purchase disabled info permits progress but blocks native and customer preorders', () => { const s = sandbox({currentMainLevel:41,mainLevelAfterWin:42}); s.post(request(1, '01'), success(info({status:'purchase_disabled',progressWins:0}))); assert.equal(s.pre(request(2, '01')).body.levelAmount,42); assert.deepEqual(s.pre(request(3, '01')), {skipped:true}); assert.deepEqual(s.pre(request(7, '01')), {skipped:true}); }); test('new win creates one payload, retry reuses it even after current period changes', () => { const s = sandbox({ periodId, currentMainLevel: 41, mainLevelAfterWin: 42, gmInfo: JSON.stringify(info({ progressWins: 0 })) }); const first = s.pre(request(2, '01')).body; assert.equal(first.levelAmount, 42); assert.equal(first.goldMiner.clearedMainLevel, 42); s.post(request(2, '01'), success({ levelAmount: 42, goldMiner: { code: 1, activityCounted: true, duplicate: false, progressWins: 1 } })); s.env.set('periodId', 'goldMiner:2026-09-24'); s.env.set('token', 'renewed-token'); const retried = s.pre(request(2, '03')).body; assert.deepEqual(retried.goldMiner, first.goldMiner); assert.equal(retried.token, 'renewed-token'); s.post(request(2, '03'), success({ goldMiner: { code: 1, activityCounted: false, duplicate: true, progressWins: 1 } })); const conflict = s.pre(request(2, '04')).body; assert.equal(conflict.goldMiner.eventId, first.goldMiner.eventId); assert.equal(conflict.levelAmount, first.levelAmount); assert.equal(conflict.goldMiner.clearedMainLevel, 43); assert.deepEqual(JSON.parse(s.env.get('gmWinBody')), first); }); test('progress tests inspect nested failure, not only successful save envelope', () => { const s = sandbox({ gmWinBefore: 0 }); assert.throws(() => s.post(request(2, '01'), success({ goldMiner: { code: 0, errorCode: 'PERIOD_ENDED' } })), /活动子结果与计数/); s.post(request(2, '05'), success({ goldMiner: { code: 0, errorCode: 'PERIOD_ENDED' } })); assert.throws(() => s.post(request(2, '05'), success({ goldMiner: { code: 1, progressWins: 1 } })), /不补记过期进度/); }); test('qualification and capped events assert no progress, endless preserves main save', () => { const s = sandbox({ periodId, currentMainLevel: 40, mainLevelAfterWin: 41, expectedProgressResult: 'qualification', gmInfo: JSON.stringify({ status: 'locked', periodId }) }); assert.equal(s.pre(request(2, '01')).body.levelAmount, 41); s.post(request(2, '01'), success({ levelAmount: 41, goldMiner: { code: 1, activityCounted: false, reason: 'qualification_level', progressWins: 0 } })); s.env.set('expectedProgressResult', 'capped'); s.env.set('endlessSequence', '999'); s.env.set('gmInfo', JSON.stringify(info())); const endless = s.pre(request(2, '02')).body; assert.equal(endless.isWuXian, 'true'); assert.equal(endless.levelAmount, 41); assert.equal(endless.goldMiner.endlessSequence, 999); s.post(request(2, '02'), success({ levelAmount: 41, goldMiner: { code: 1, activityCounted: false, duplicate: false, progressWins: 5 } })); s.env.set('mainLevelAfterWin', '42'); assert.deepEqual(s.pre(request(2, '02')), { skipped: true }); }); test('order creation captures snapshot pricing and reuses original order identity', () => { const s = sandbox(); s.post(request(1, '01'), success(info({ status: 'purchasable' }))); const first = s.pre(request(3, '01')).body; assert.equal(first.itemPrice, 1); assert.equal(first.itemCount, 999); s.post(request(3, '01'), success({ outTradeNo: 'order-1', signData: JSON.stringify({ productId: 'gold_miner', buyQuantity: 1, goodsPrice: 123, outTradeNo: 'order-1' }), signature: 'signature', paySig: 'paySig' })); const retry = s.pre(request(3, '03')).body; assert.notEqual(first.createRequestId, retry.createRequestId); s.post(request(3, '03'), success({ outTradeNo: 'order-1' })); assert.throws(() => s.post(request(3, '03'), success({ outTradeNo: 'order-2' })), /复用原订单/); }); test('payment assertions distinguish pending, paid and late-payment routes', () => { const s = sandbox({ outTradeNo: 'order-1' }); const items = collection.item[2].item.filter(item => item.name.startsWith('查单')); for (const item of items) { assert.equal(s.pre(item).body.outTradeNo, 'order-1'); s.post(item, { code: 0, errorCode: 'PAYMENT_PENDING' }); assert.throws(() => s.post(item, success({ pay_state: 2 })), /查单匹配指定状态/); } s.env.set('expectedPaymentState', 'paid'); assert.deepEqual(s.pre(items[0]), { skipped: true }); s.env.set('expectedTargetPeriodId', 'goldMiner:2026-09-24'); s.env.set('expectedFulfillmentRoute', 'carry_next'); const paid = success({ pay_state: 2, rewardDelivery: 'goldMiner.claim', goldMiner: { fulfillmentStatus: 'fulfilled', fulfillmentRoute: 'carry_next', targetPeriodId: 'goldMiner:2026-09-24' } }); for (const item of items) s.post(item, paid); paid.data.goldMiner.targetPeriodId = periodId; assert.throws(() => s.post(items[0], paid), /查单匹配指定状态/); }); test('claim-save-confirm chain forbids premature confirmation and duplicate credit', () => { const s = sandbox({ claimPeriodId: periodId, claimTaskId: 'task_1' }); s.pre(request(4, '01')); s.post(request(4, '01'), success(grant())); assert.deepEqual(s.pre(request(4, '05')), { skipped: true }); s.post(request(4, '02'), success(grant())); assert.throws(() => s.post(request(4, '02'), success({ ...grant(), grantId: 'grant-2' })), /重复领取无新奖励凭证/); s.post(request(4, '03'), success({ coinAmount: 100 })); assert.deepEqual(s.pre(request(4, '04')), { skipped: true }); s.env.set('coinAfterDelivery', '149'); assert.deepEqual(s.pre(request(4, '04')), { skipped: true }); s.env.set('coinAfterDelivery', '150'); assert.equal(s.pre(request(4, '04')).body.coinAmount, '150'); s.post(request(4, '04'), success({ coinAmount: 150 })); assert.deepEqual(s.pre(request(4, '04')), { skipped: true }); assert.equal(s.pre(request(4, '05')).body.grantId, 'grant-1'); s.post(request(4, '05'), success({ grantId: 'grant-1', claimStatus: 'claimed', claimedThrough: 1 })); s.pre(request(4, '05')); s.post(request(4, '05'), success({ grantId: 'grant-1', claimStatus: 'claimed', claimedThrough: 1 })); assert.throws(() => s.post(request(4, '05'), success({ grantId: 'wrong-grant', claimStatus: 'claimed', claimedThrough: 2 })), /确认同一凭证/); assert.deepEqual(s.pre(request(4, '04')), { skipped: true }); }); test('failed coin save cannot enable delivery confirmation', () => { const s = sandbox({ claimPeriodId: periodId, claimTaskId: 'task_1' }); s.pre(request(4, '01')); s.post(request(4, '01'), success(grant())); assert.throws(() => s.post(request(4, '04'), { code: 0, data: null }), /业务成功/); assert.equal(s.env.get('gmSavedGrantId'), undefined); assert.deepEqual(s.pre(request(4, '05')), { skipped: true }); }); test('selected historical task cannot use another task grant', () => { const s = sandbox({ claimPeriodId: periodId, claimTaskId: 'task_1' }); s.pre(request(4, '01')); s.post(request(4, '01'), success(grant())); s.env.set('claimTaskId', 'task_2'); assert.deepEqual(s.pre(request(4, '04')), { skipped: true }); assert.deepEqual(s.pre(request(4, '05')), { skipped: true }); }); test('empty settlement page retains cursor; terminal page blocks further requests', () => { const s = sandbox(); s.post(request(5, '01'), success({ status:'pending_delivery', items: [], nextCursor: 'history-cursor' })); assert.equal(s.pre(request(5, '02')).body.afterId, 'history-cursor'); s.post(request(5, '02'), success({ status:'pending_delivery', items: [], nextCursor: null })); assert.deepEqual(s.pre(request(5, '02')), { skipped: true }); }); test('admin publication blocks blank commercial values; purchase flag is a JSON boolean', () => { const s = sandbox({ adminToken: 'test-admin', adminPeriodId: periodId }); assert.deepEqual(s.pre(request(6, '02')), { skipped: true }); s.env.set('configJson', JSON.stringify({ priceFen: null, tasks: [] })); assert.deepEqual(s.pre(request(6, '02')), { skipped: true }); s.env.set('configJson', JSON.stringify({ configVersion: 'test-v1', priceFen: 123, tasks: [{ taskId: 'test' }] })); assert.equal(s.pre(request(6, '02')).body.config.priceFen, 123); assert.deepEqual(s.pre(request(6, '03')), { skipped: true }); s.env.set('purchaseEnabled', 'false'); assert.equal(s.pre(request(6, '03')).body.purchaseEnabled, false); }); test('legacy collection preserves scoped session ticket and gates real customer messages', () => { const s = sandbox({ openid: 'openid' }); s.pre(request(1, '01')); s.post(request(1, '01'), success(info({ status: 'purchasable' }))); assert.equal(s.pre(request(7, '01')).body.channel, 'legacy_ios'); const data = {outTradeNo:'order-1',periodId,priceFen:123,productId:'gold_miner',quantity:1,channel:'legacy_ios', sessionFrom:JSON.stringify({outTradeNo:'order-1',propName:'gold_miner',goldMinerTicket:'scoped-ticket'})}; s.post(request(7, '01'), success(data)); s.pre(request(7, '02')); s.post(request(7, '02'), success(data)); assert.throws(() => s.post(request(7, '02'), success({...data,outTradeNo:'different'})), /复用原订单/); assert.deepEqual(s.pre(request(7, '03')), {skipped:true}); s.env.set('allowCustomerMessage', 'true'); const sent = s.pre(request(7, '03')).body; assert.equal(sent.SessionFrom, data.sessionFrom); assert.equal(sent.FromUserName, 'openid'); assert.equal(sent.token, undefined); s.post(request(7, '04'), true); assert.throws(() => s.post(request(7, '04'), {code:0})); s.post(request(7, '05'), false); s.post(request(7, '06'), {code:'FAIL'}, 500); s.post(request(7, '07'), {code:0,errorCode:'PAYMENT_CHANNEL_CONFLICT'}); s.env.set('uid','other'); assert.deepEqual(s.pre(request(1, '01')), {skipped:true}); assert.equal(s.env.get('legacySessionFrom'),undefined); assert.equal(s.env.get('allowCustomerMessage'),undefined); }); test('settling pages advance the cursor and settlement delivery uses original grant without claim',()=>{ const s=sandbox({settlementCursor:'history-cursor'}); s.post(request(5,'02'),success({status:'settling',items:[],nextCursor:'next-history-cursor'})); assert.equal(s.pre(request(5,'02')).body.afterId,'next-history-cursor'); s.post(request(5,'02'),success({status:'settling',items:[],nextCursor:null})); assert.deepEqual(s.pre(request(5,'02')),{skipped:true}); assert.equal(s.pre(request(5,'01')).body.afterId,undefined); s.post(request(5,'05'),success({status:'pending_delivery',items:[{settlementId:'settlement-1',periodId,rewards:[{taskId:'task_1',grantId:'grant-1',items:[{type:'coin',count:50}]}]}],nextCursor:null})); assert.equal(s.env.get('grantId'),'grant-1'); assert.deepEqual(s.pre(request(5,'06')),{skipped:true}); s.post(request(4,'03'),success({coinAmount:100}));s.env.set('coinAfterDelivery','150'); assert.equal(s.pre(request(4,'04')).body.coinAmount,'150'); s.post(request(4,'04'),success({coinAmount:150})); const body=s.pre(request(5,'06')).body;assert.equal(body.action,'confirm_settlement_delivery'); assert.deepEqual(body.grantIds,['grant-1']);assert.equal(body.settlementId,'settlement-1'); s.post(request(5,'06'),success({settlementId:'settlement-1',confirmedGrantIds:['grant-1'],deliveryStatus:'client_saved'})); assert.deepEqual(s.pre(request(4,'04')),{skipped:true}); }); test('settlement collection omits client limits and accepts only globally empty no_pending_rewards responses',()=>{ const s=sandbox(); for(const prefix of ['01','05']) { assert.equal(s.pre(request(5,prefix)).body.limit,undefined); s.post(request(5,prefix),success({status:'no_pending_rewards',items:[],nextCursor:null})); assert.equal(s.env.get('settlementCursor'),''); assert.throws(()=>s.post(request(5,prefix),success({status:'no_pending_rewards',items:[],nextCursor:'next'}))); assert.throws(()=>s.post(request(5,prefix),success({status:'no_pending_rewards',items:[{}],nextCursor:null}))); } assert.deepEqual(s.pre(request(5,'02')),{skipped:true}); const negative=s.pre(request(1,'06')).body; assert.equal(negative.limit,undefined);assert.equal(negative.afterId,42); }); test('temporary info accepts custom duration while weekly info still enforces the normal calendar',()=>{ const s=sandbox(),d=info({periodId:'goldMiner:test:short',endsAt:startsAt+1800000}); s.post(request(1,'01'),success(d));assert.equal(s.env.get('periodId'),d.periodId); assert.throws(()=>s.post(request(1,'01'),success({...d,periodId})),/活动状态结构/); assert.throws(()=>s.post(request(1,'01'),success({...d,serverTime:d.endsAt})),/活动状态结构/); }); const schedule=(activities=[])=>({schemaVersion:1,serverNow:startsAt+1000,windowEndsAt:startsAt+1000+7*86400000, refreshAt:Math.min(startsAt+31000,...activities.flatMap(a=>[a.startsAt,a.endsAt]).filter(t=>t>startsAt+1000)),activities}); const scheduleEntry=(extra={})=>({activityId:'goldMiner',periodId:'goldMiner:test:short',configVersion:'v1',startsAt, endsAt:startsAt+10000,unlockLevel:41,phase:'active',purchaseEnabled:true,...extra}); test('public schedule GET and POST require no login and do not overwrite player period or grant',()=>{ const s=sandbox({uid:'',token:'',adminToken:'',periodId:'player-period',grantId:'original-grant'}); assert.deepEqual(s.pre(request(8,'01')),{body:undefined});assert.deepEqual(s.pre(request(8,'02')),{body:{}}); for(const item of collection.item[7].item){ s.post(item,success(schedule([scheduleEntry()]))); assert.equal(s.env.get('periodId'),'player-period');assert.equal(s.env.get('grantId'),'original-grant'); assert.equal(JSON.parse(s.env.get('gmActivitySchedule')).activities[0].periodId,'goldMiner:test:short'); } const yaml=readFileSync(new URL('../../activityConfig/list.yaml',import.meta.url),'utf8'); assert.match(yaml,/^\s+- GET\s*$/m);assert.match(yaml,/^\s+- POST\s*$/m); }); test('schedule accepts empty lists and cloudRise upcoming entries; errors preserve cached success',()=>{ const s=sandbox(),item=request(8,'01');s.post(item,success(schedule())); assert.deepEqual(JSON.parse(s.env.get('gmActivitySchedule')).activities,[]); const cloud={activityId:'cloudRise',periodId:'race',configVersion:'v1',startsAt:startsAt+5000,endsAt:startsAt+60000,unlockLevel:100,phase:'upcoming'}; const d=schedule([cloud,scheduleEntry()]);s.post(item,success(d)); const cached=s.env.get('gmActivitySchedule'); assert.throws(()=>s.post(item,{code:0,data:null,msg:'数据库暂不可用'}),/公共日程查询成功/); assert.equal(s.env.get('gmActivitySchedule'),cached); assert.throws(()=>s.post(item,success({...d,refreshAt:d.serverNow+30000})),/公共日程字段/); assert.equal(s.env.get('gmActivitySchedule'),cached); assert.throws(()=>s.post(item,success(schedule([scheduleEntry({tasks:[]})]))),/公共日程字段/); assert.throws(()=>s.post(item,success(schedule([scheduleEntry(),cloud]))),/公共日程字段/); s.env.set('expectedListedPeriodId','missing');assert.throws(()=>s.post(item,success(d)),/公共日程字段/); s.env.set('expectedListedPeriodId','goldMiner:test:short');s.post(item,success(d)); }); test('integrated temporary publisher reuses the same draft and validates immutable response',()=>{ const s=sandbox({adminToken:'admin-test'}),item=request(6,'04');const first=s.pre(item).body; assert.equal(first.action,'publish_test_period');assert.equal(first.endsAt-first.startsAt,1800000); assert.match(first.periodId,/^goldMiner:test:/);assert.deepEqual(s.pre(item).body,first); assert.equal(s.env.get('tempPeriodId'),first.periodId);assert.equal(s.env.get('testPeriodRequestBody'),undefined); const result={periodId:first.periodId,startsAt:first.startsAt,endsAt:first.endsAt,nextStartsAt:first.endsAt+1000, configHash:'hash',configSnapshot:{...first.config,effectiveFromPeriodId:first.periodId}}; s.post(item,success(result));assert.throws(()=>s.post(item,success({...result,endsAt:first.endsAt+1})),/时间与请求一致/); const standalone=read('gold-miner-test-period.postman_collection.json').item[0]; assert.equal(script(item,'prerequest'),script(standalone,'prerequest'));assert.equal(script(item,'test'),script(standalone,'test')); }); test('public cached schedule is cleared when switching deployment context',()=>{ const s=sandbox();s.pre(request(8,'01'));s.post(request(8,'01'),success(schedule())); s.env.set('baseUrl','https://another.invalid');assert.deepEqual(s.pre(request(8,'01')),{skipped:true}); assert.equal(s.env.get('gmActivitySchedule'),undefined);assert.deepEqual(s.pre(request(8,'01')),{body:undefined}); }); test('VIP Postman publication and pending-unlock display match the six-tier protocol',()=>{ const config=JSON.parse(readFileSync(new URL('../vip-tiers.example.json',import.meta.url),'utf8')).config; const s=sandbox({adminToken:'admin-test',configJson:JSON.stringify(config)}); assert.deepEqual(s.pre(request(6,'02')).body.config,config); s.post(request(6,'02'),success(config)); const unpaid=info({status:'purchasable',vipLevel:0,priceFen:300,productId:'gold_miner_vip0'}); s.post(request(1,'01'),success(unpaid)); assert.ok(unpaid.tasks.every(t=>t.claimStatus==='pending_unlock')); assert.equal(s.env.get('productId'),'gold_miner_vip0'); const broken=copy(unpaid);broken.tasks[0].claimStatus='claimable'; assert.throws(()=>s.post(request(1,'01'),success(broken)),/活动状态结构/); // Independent tasks can all be ready, including with progress beyond the selected tier's cap. s.post(request(1,'01'),success(info({vipLevel:1,progressWins:61}))); s.env.set('claimPeriodId',periodId);s.env.set('claimTaskId','task_2'); s.pre(request(4,'08'));s.post(request(4,'08'),success({...grant(),taskId:'task_2'})); assert.equal(s.env.get('claimTaskId'),'task_2'); s.post(request(4,'05'),success({grantId:'grant-1',claimStatus:'claimed',claimedThrough:0})); });