90 lines
5.0 KiB
JavaScript
90 lines
5.0 KiB
JavaScript
import test from 'node:test';
|
|
import assert from 'node:assert/strict';
|
|
import {collect, send, TEST_APP_ID, PRODUCTION_APP_ID} from '../analytics.ts';
|
|
|
|
function fixture(options = {}) {
|
|
const instances = [];
|
|
const sdkModule = { initWithBatchMode(appId, url, config) {
|
|
const instance = { appId, url, config, events: [], closed: false,
|
|
trackFirst(event) { this.events.push(event); if(options.validationError) event.callback(Error('invalid')); },
|
|
flush(callback) { if(!options.silent) callback(options.failure ? Error('network') : undefined); },
|
|
close() { this.closed=true; },
|
|
};
|
|
instances.push(instance); return instance;
|
|
}};
|
|
return {instances, sdkModule};
|
|
}
|
|
const user = {openid:'player-openid', distinctId:'player-distinct'};
|
|
const event = { id:'stable-id', event:'cloud_rise_start', properties:{stage:1,end_count:17}, appId:TEST_APP_ID, time:1789880000000 };
|
|
|
|
test('analytics SDK uses first-event IDs, correct identities and explicit flush before acknowledging', async () => {
|
|
const f=fixture(); assert.deepEqual(await send([event],user,f.sdkModule),['stable-id']);
|
|
const sdk=f.instances[0], actual=sdk.events[0];
|
|
assert.equal(sdk.appId,TEST_APP_ID); assert.equal(sdk.url,'https://data.nika4fun.com/sync_data');
|
|
assert.equal(sdk.config.batchSize,2); assert.equal(sdk.closed,true);
|
|
assert.equal(actual.accountId,user.openid); assert.equal(actual.distinctId,user.distinctId);
|
|
assert.equal(actual.event,event.event); assert.equal(actual.firstCheckId,event.id);
|
|
assert.deepEqual(actual.properties,event.properties); assert.equal(actual.time.getTime(),event.time+8*3600000);
|
|
});
|
|
|
|
test('analytics SDK separates test and production batches even when retried together', async () => {
|
|
const f=fixture(), prod={...event,id:'prod-id',appId:PRODUCTION_APP_ID};
|
|
assert.deepEqual((await send([event,prod],user,f.sdkModule)).sort(),['prod-id','stable-id']);
|
|
assert.deepEqual(f.instances.map(i=>[i.appId,i.events.map(e=>e.firstCheckId)]),
|
|
[[TEST_APP_ID,['stable-id']],[PRODUCTION_APP_ID,['prod-id']]]);
|
|
});
|
|
|
|
test('analytics delivery errors and validation errors remain retryable with stable IDs', async () => {
|
|
for(const options of [{failure:true},{validationError:true}]) {
|
|
const f=fixture(options); assert.deepEqual(await send([event],user,f.sdkModule),[]);
|
|
assert.equal(f.instances[0].closed,true);
|
|
const retry=fixture(); assert.deepEqual(await send([event],user,retry.sdkModule),[event.id]);
|
|
assert.equal(retry.instances[0].events[0].firstCheckId,f.instances[0].events[0].firstCheckId);
|
|
}
|
|
});
|
|
|
|
test('analytics timeout is bounded and does not acknowledge an uncertain delivery', async () => {
|
|
const f=fixture({silent:true}), start=Date.now();
|
|
assert.deepEqual(await send([event],user,f.sdkModule),[]);
|
|
assert.ok(Date.now()-start < 3000); assert.equal(f.instances[0].closed,true);
|
|
});
|
|
|
|
test('analytics waits for a real user identity and skips empty queues', async () => {
|
|
const f=fixture(); assert.deepEqual(await send([event],{},f.sdkModule),[]);
|
|
assert.deepEqual(await send([],user,f.sdkModule),[]); assert.equal(f.instances.length,0);
|
|
assert.deepEqual(await send([event],{distinctId:'visitor'},f.sdkModule),[event.id]);
|
|
assert.equal(f.instances[0].events[0].accountId,undefined);
|
|
});
|
|
|
|
test('analytics maps legacy isDebug semantics consistently and does not count the player as a bot', () => {
|
|
for(const stageNumber of [1,2,3]) {
|
|
const target=[5,7,9][stageNumber-1];
|
|
for(const value of [true,'true',false,'false',undefined]) {
|
|
const stage={stage:stageNumber,startedAt:123,target,status:'playing',success_num:0,results:[],
|
|
opponents:Array.from({length:99},(_,i)=>({success_num:i<21?target:target-1}))};
|
|
const run={id:'r',stages:[stage]};
|
|
const events=collect(null,run,value,123);
|
|
assert.equal(events.length,1); assert.deepEqual(events[0].properties,{stage:stageNumber,end_count:21});
|
|
assert.equal(events[0].appId,value===true||value==='true'?PRODUCTION_APP_ID:TEST_APP_ID);
|
|
assert.deepEqual(collect(structuredClone(run),run,value,124),[]);
|
|
}
|
|
}
|
|
});
|
|
|
|
test('analytics reports final failures at stage targets and exact saved awards without recalculation', () => {
|
|
for(const stage of [1,2,3]) {
|
|
const target=[5,7,9][stage-1];
|
|
const before={id:'r',stages:[{stage,startedAt:123,target,status:'playing',success_num:target-1,
|
|
results:Array.from({length:target-1},(_,i)=>({id:String(i),outcome:'win'})),opponents:[]}]};
|
|
const after=structuredClone(before), last=after.stages[0];
|
|
last.results.push({id:'last',outcome:'lose'});last.status='lost';
|
|
assert.deepEqual(collect(before,after,false,124).map(e=>[e.event,e.properties]),[
|
|
['cloud_rise_step',{stage,step:target,result:'failure'}],['cloud_rise_fail',{stage,step:target}],
|
|
]);
|
|
last.results.at(-1).outcome='win';last.status='won';last.success_num=target;last.rewardSaved=true;last.reward=210;
|
|
assert.deepEqual(collect(before,after,false,124).map(e=>[e.event,e.properties]),[
|
|
['cloud_rise_step',{stage,step:target,result:'success'}],['cloud_rise_succeed',{stage,coin_amount:210}],
|
|
]);
|
|
}
|
|
});
|