141 lines
7.3 KiB
JavaScript
141 lines
7.3 KiB
JavaScript
import test, { beforeEach, afterEach } from 'node:test';
|
|
import assert from 'node:assert/strict';
|
|
import { registerHooks } from 'node:module';
|
|
|
|
const NOW = Date.parse('2026-09-20T14:20:00+08:00');
|
|
const DAY = 86400000;
|
|
const players = [];
|
|
let unavailable = false;
|
|
let enabled = true;
|
|
const clone = value => structuredClone(value);
|
|
globalThis.__activityListCloud = { mongo: { db: { collection(name) {
|
|
if (name === 'activityConfigs') return {find(query) {
|
|
assert.equal(query.activityId,'cloudRise');
|
|
return {sort(){return this;},limit(){return this;},async toArray(){
|
|
if(unavailable)throw Error('simulated database outage');
|
|
return [{configVersion:'v1',enabled,config:{unlockLevel:100,durationHours:24,pools:[10000,15000,20000]}}];
|
|
}};
|
|
}};
|
|
assert.equal(name, 'users', 'CloudRise-only list must not query gold miner or orders');
|
|
return {async findOne(query) {
|
|
if (unavailable) throw Error('simulated database outage');
|
|
return clone(players.find(p => p.onlyId === query.onlyId) || null);
|
|
}};
|
|
} } } };
|
|
const hooks = registerHooks({
|
|
resolve(specifier, context, next) {
|
|
assert(!specifier.includes('goldMiner'), 'unpublished gold miner modules must not be loaded');
|
|
if (specifier === '@lafjs/cloud') return {url:'activitylist:cloud',shortCircuit:true};
|
|
if (specifier === '@/Utils') return {url:'activitylist:utils',shortCircuit:true};
|
|
if (specifier.startsWith('@/')) return {url:new URL('../../'+specifier.slice(2)+'.ts',import.meta.url).href,shortCircuit:true};
|
|
return next(specifier,context);
|
|
},
|
|
load(url,context,next) {
|
|
if(url === 'activitylist:cloud') return {format:'module',source:'export default globalThis.__activityListCloud;',shortCircuit:true};
|
|
if(url === 'activitylist:utils') return {format:'module',source:'export default {checkToken(a,b){return a===b}};',shortCircuit:true};
|
|
return next(url,context);
|
|
},
|
|
});
|
|
const {default:api,activityList} = await import('../list.ts');
|
|
hooks.deregister();
|
|
const realNow = Date.now;
|
|
beforeEach(() => {players.length=0;unavailable=false;enabled=true;Date.now=()=>NOW;});
|
|
afterEach(() => {Date.now=realNow;});
|
|
|
|
function personalUser(id=1001) {
|
|
return {onlyId:id,token:'token-'+id,cloudRisePeriod:{periodId:'personal-'+id,configVersion:'v1',
|
|
startsAt:NOW-1000,endsAt:NOW+DAY,unlockLevel:100,pools:[10000,15000,20000],durationHours:24},
|
|
cloudRiseState:JSON.stringify({version:1,playedPeriods:[],run:null})};
|
|
}
|
|
|
|
test('authenticated list returns only own CloudRise period and hides private state', async () => {
|
|
players.push(personalUser());
|
|
const result = await api({body:{uid:'1001',token:'token-1001'}});
|
|
assert.equal(result.code,1);
|
|
assert.deepEqual(result.data.activities,[{
|
|
activityId:'cloudRise',periodId:'personal-1001',configVersion:'v1',startsAt:NOW-1000,
|
|
endsAt:NOW+DAY,unlockLevel:100,phase:'active'}]);
|
|
assert(!JSON.stringify(result).includes('token-'));
|
|
assert(!JSON.stringify(result).includes('cloudRiseState'));
|
|
});
|
|
|
|
test('anonymous list exposes neither personal CloudRise nor gold miner', async () => {
|
|
players.push(personalUser());
|
|
const result = await api({});
|
|
assert.equal(result.code,1);
|
|
assert.deepEqual(result.data.activities,[]);
|
|
});
|
|
|
|
test('list returns opening facts without starting a countdown and respects disabled templates',async()=>{
|
|
const user={onlyId:1001,token:'token-1001'};players.push(user);
|
|
const first=await api({body:{uid:1001,token:user.token}});
|
|
assert.deepEqual(first.data.activities,[]);
|
|
assert.deepEqual(first.data.openingRules,[{activityId:'cloudRise',enabled:true,unlockLevel:100,cooldownMs:43200000,lastEndedAt:null,hasOpenPeriod:false}]);
|
|
assert.equal(user.cloudRisePeriod,undefined);
|
|
Object.assign(user,personalUser());
|
|
user.cloudRiseState=JSON.stringify({run:{periodId:user.cloudRisePeriod.periodId,status:'failed',stages:[{endedAt:NOW-60000}]}});
|
|
const failed=await api({body:{uid:1001,token:user.token}});
|
|
assert.equal(failed.data.openingRules[0].lastEndedAt,NOW-60000);
|
|
enabled=false;
|
|
assert.equal((await api({body:{uid:1001,token:user.token}})).data.openingRules[0].enabled,false);
|
|
});
|
|
|
|
test('list validates account identity and never returns another player period', async () => {
|
|
players.push(personalUser(1001),personalUser(2002));
|
|
for (const body of [{uid:'1001',token:'wrong'},{uid:'2002',token:'token-1001'},{uid:'1x',token:'token-1001'}]) {
|
|
const result = await api({body});assert.equal(result.code,0);assert.equal(result.data,null);
|
|
}
|
|
const result = await api({body:{uid:2002,token:'token-2002'}});
|
|
assert.equal(result.data.activities[0].periodId,'personal-2002');
|
|
});
|
|
|
|
test('list neither opens nor extends personal periods and excludes ended challenges', async () => {
|
|
const user=personalUser();players.push(user);const before=clone(user);
|
|
await api({body:{uid:1001,token:user.token}});assert.deepEqual(user,before);
|
|
for (const status of ['failed','completed','expired']) {
|
|
user.cloudRiseState=JSON.stringify({run:{periodId:user.cloudRisePeriod.periodId,status}});
|
|
assert.deepEqual((await activityList(NOW,user)).activities,[]);
|
|
}
|
|
user.cloudRiseState=JSON.stringify({run:{periodId:'older',status:'failed'}});
|
|
assert.equal((await activityList(NOW,user)).activities.length,1);
|
|
delete user.cloudRisePeriod;user.cloudRiseState='{}';
|
|
assert.deepEqual((await activityList(NOW,user)).activities,[],'polling must not create a period');
|
|
});
|
|
|
|
test('personal boundaries are inclusive at start, exclusive at end and update refreshAt', async () => {
|
|
const user=personalUser();user.cloudRisePeriod.startsAt=NOW;user.cloudRisePeriod.endsAt=NOW+5000;
|
|
assert.equal((await activityList(NOW,user)).refreshAt,NOW+5000);
|
|
assert.equal((await activityList(NOW,user)).activities.length,1);
|
|
assert.equal((await activityList(NOW-1,user)).activities.length,0);
|
|
assert.equal((await activityList(NOW+5000,user)).activities.length,0);
|
|
});
|
|
|
|
test('active legacy challenges remain visible without creating a new period', async () => {
|
|
const user={cloudRiseState:JSON.stringify({run:{periodId:'legacy',startedAt:NOW-1000,expiresAt:NOW+5000,status:'playing'}})};
|
|
const result=await activityList(NOW,user);assert.equal(result.activities[0].periodId,'legacy');
|
|
assert.equal(result.activities[0].endsAt,NOW+5000);assert.equal(user.cloudRisePeriod,undefined);
|
|
});
|
|
|
|
test('empty successful lists and user database failures have distinct responses', async () => {
|
|
assert.deepEqual((await api({})).data.activities,[]);unavailable=true;
|
|
const error=console.error;console.error=()=>{};
|
|
try {
|
|
const result=await api({body:{uid:1001,token:'token-1001'}});
|
|
assert.equal(result.code,0);assert.equal(result.data,null);
|
|
} finally {console.error=error;}
|
|
});
|
|
|
|
test('existing gold miner state cannot expose gold miner even in test mode', async () => {
|
|
const oldEnv=process.env.PAYMENT_APP_ENV, oldFlag=process.env.GOLD_MINER_TEST_PERIODS_ENABLED;
|
|
try {
|
|
process.env.PAYMENT_APP_ENV='test';process.env.GOLD_MINER_TEST_PERIODS_ENABLED='true';
|
|
const user={...personalUser(),goldMiner:{periodId:'goldMiner:test:catalog'}};
|
|
players.push(user);
|
|
const result=await api({body:{uid:1001,token:user.token}});
|
|
assert.deepEqual(result.data.activities.map(a=>a.activityId),['cloudRise']);
|
|
} finally {
|
|
if(oldEnv===undefined) delete process.env.PAYMENT_APP_ENV; else process.env.PAYMENT_APP_ENV=oldEnv;
|
|
if(oldFlag===undefined) delete process.env.GOLD_MINER_TEST_PERIODS_ENABLED; else process.env.GOLD_MINER_TEST_PERIODS_ENABLED=oldFlag;
|
|
}
|
|
});
|