Files
MatchMaster/tools/test-activity-resource-tracking.cjs

121 lines
6.1 KiB
JavaScript

const test = require('node:test');
const assert = require('node:assert/strict');
const fs = require('node:fs');
const vm = require('node:vm');
const ts = require('typescript');
function loadClass(file, names, globals = {}, deps = {}) {
const source = fs.readFileSync(file, 'utf8');
const parsed = ts.createSourceFile(file, source, ts.ScriptTarget.Latest, true);
const cls = parsed.statements.find(ts.isClassDeclaration);
const code = names ? 'export default class ' + cls.name.text + ' {\n' + cls.members
.filter(member => names.includes(member.name?.getText(parsed)))
.map(member => member.getText(parsed)).join('\n') + '\n}' : source;
const exports = {};
vm.runInNewContext(ts.transpileModule(code, { compilerOptions: {
target: ts.ScriptTarget.ES2020, module: ts.ModuleKind.CommonJS,
} }).outputText, { exports, require: name => deps[name], console: { log() {} }, ...globals });
return exports.default;
}
function fixture() {
const info = { uid: 'u', coin: 10, freezeAmount: 1, hammerAmount: 2, magicAmount: 3 };
const storage = { prop: {} }, events = [];
const cc = { find: () => null, isValid: () => false, fx: {
GameConfig: { GM_INFO: info, PASS_CHECK: [] },
StorageMessage: { getStorage: key => storage[key], setStorage: (key, value) => storage[key] = value },
GameTool: {
changeCoin: amount => info.coin += amount,
setUserProp() {},
shushu_Track: (name, data) => events.push({ name, ...data }),
// Existing shared helper owns the infinite-health event.
setUserPowerTime: (num, change_reason) => events.push({ name: 'resource_get', id: '2005', num, change_reason, compensate: false }),
},
} };
return { cc, info, storage, events };
}
const rows = events => events.map(e => [String(e.id), e.num]).sort((a, b) => a[0].localeCompare(b[0]));
test('seven-day mixed rewards report each resource once and skip a processed receipt', () => {
const f = fixture();
const Reward = loadClass('assets/seven_day_gift/SevenDayGiftReward.ts', null, { cc: f.cc }, {
'./SevenDayGiftApi': { default: { getUid: () => 'u', saveProps: (_, cb) => cb({ code: 1 }) } },
});
const receipt = { claimId: 'day-2', rewards: [
{ type: 'coin', count: 300 }, { type: 'freeze', count: 1 },
{ type: 'hammer', count: 2 }, { type: 'magic_wand', count: 3 },
{ type: 'infinite_health', count: 900 },
] };
Reward.grant(receipt, ok => assert.equal(ok, true));
Reward.grant(receipt, ok => assert.equal(ok, true));
assert.deepEqual(rows(f.events), [['1001', 300], ['2001', 1], ['2002', 2], ['2003', 3], ['2005', 900]]);
assert.ok(f.events.every(e => e.change_reason === 'seven_day_gift' && e.compensate === false));
assert.equal(f.info.coin, 310);
});
test('seven-day zero rewards produce no resource events', () => {
const f = fixture();
const Reward = loadClass('assets/seven_day_gift/SevenDayGiftReward.ts', null, { cc: f.cc }, {
'./SevenDayGiftApi': { default: { getUid: () => 'u' } },
});
Reward.grant({ claimId: 'empty', rewards: [{ type: 'coin', count: 0 }] }, ok => assert.equal(ok, true));
assert.equal(f.events.length, 0);
});
test('weekly chest reports coins and all three props, with infinite health reported once', () => {
const f = fixture();
const Daily = loadClass('assets/action_bundle/script/DailyQuests.ts', ['openReward', 'getShopProp'], {
cc: f.cc, wx: {}, DailyTaskProgressTracker: { trackTask() {} },
});
const view = new Daily();
view.grantFutureProps = () => {};
view.openReward('weekly_140', [
{ type: 'coin', count: 2000 }, { type: 'freeze', count: 1 },
{ type: 'hammer', count: 1 }, { type: 'magic_wand', count: 1 },
{ type: 'infinite_health', count: 3600 },
], false);
assert.deepEqual(rows(f.events), [['1001', 2000], ['2001', 1], ['2002', 1], ['2003', 1], ['2005', 3600]]);
assert.ok(f.events.filter(e => e.id !== '2005').every(e => e.change_reason === 'quests'));
f.events.length = 0;
view.openReward('empty', [], false);
assert.equal(f.events.length, 0);
});
function passFixture() {
const f = fixture();
const Utils = loadClass('assets/Script/module/Pay/Utils.ts',
['passGiftPending', 'applyPassGift', 'commitPassSettlement', 'syncPassResources'], {
cc: f.cc, settlementRewards: () => [], settlementClaimFlags: () => ({}),
});
Utils.withCoinSave = work => work();
return { ...f, Utils };
}
test('battle-pass bonus reports the actual 3000 coins once per pending order', () => {
const f = passFixture();
f.Utils.passGiftPending['u:paid'] = 'pending';
f.Utils.applyPassGift('paid');
f.Utils.applyPassGift('paid');
f.Utils.applyPassGift('unknown');
assert.deepEqual(f.events, [{ name: 'resource_get', change_reason: 'battlepass', id: '1001', num: 3000, compensate: false }]);
assert.equal(f.info.coin, 3010);
});
for (const action of ['claim', 'purchase_claim']) test(`battle-pass ${action} reports saved rewards and skips duplicate receipts`, async () => {
const f = passFixture();
const result = { data: { alreadyGranted: false,
resources: { coinAmount: 310, freezeAmount: 2, hammerAmount: 4, magicAmount: 6, userPowerTime: 900 },
rewards: [{ type: 'coin', count: 300 }, { type: 'freeze', count: 1 },
{ type: 'hammer', count: 2 }, { type: 'magic', count: 3 }, { type: 'infinite_health', count: 900 }],
} };
f.Utils.settlementRequest = async () => result;
await f.Utils.commitPassSettlement(action, { end: 1 }, 'paid');
result.data.alreadyGranted = true;
await f.Utils.commitPassSettlement(action, { end: 1 }, 'paid');
assert.deepEqual(rows(f.events), [['1001', 300], ['2001', 1], ['2002', 2], ['2003', 3], ['2005', 900]]);
assert.ok(f.events.every(e => e.change_reason === 'battlepass' && e.compensate === true));
f.Utils.settlementRequest = async () => { throw new Error('offline'); };
await assert.rejects(f.Utils.commitPassSettlement(action, { end: 2 }), /offline/);
assert.equal(f.events.length, 5);
});