auu
This commit is contained in:
parent
d995726462
commit
0a343ecbc5
326 changed files with 15584 additions and 0 deletions
47
test/shared-world-complete-smoke-test.js
Normal file
47
test/shared-world-complete-smoke-test.js
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
'use strict';
|
||||
const {spawn}=require('child_process');
|
||||
const fs=require('fs');
|
||||
const os=require('os');
|
||||
const path=require('path');
|
||||
const assert=require('assert/strict');
|
||||
const {root,starterPuzzle,read,functionSource}=require('./helpers/app-source');
|
||||
const {connectRealtime}=require('./helpers/realtime-client');
|
||||
|
||||
const app=read('app.js'),html=read('index.html'),css=read('style.css'),serverSource=read('server.js'),realtimeSource=read('realtime-server.js');
|
||||
assert(html.includes('id="reactionCanvas"')&&html.includes('id="reactionRadial"')&&css.includes('#reactionCanvas')&&css.includes('z-index:2'),'Reaction layer is not behind boards');
|
||||
assert(functionSource('beginReactionGesture').includes('REACTION_LONG_PRESS_MS')&&functionSource('reactionAllowedAt').includes('metaState(boardId).solved'),'Reaction click/long-press eligibility is missing');
|
||||
assert(functionSource('drawReactionLayer').includes('REALTIME_REACTION_DURATION')||app.includes('REALTIME_REACTION_DURATION=4500'),'Reaction animation is not bounded');
|
||||
assert(functionSource('inventoryEntries').includes('onlinePlayerEconomy()')&&functionSource('purchaseStoreItem').includes('buyPersonalStoreItem'),'Player inventory or personal purchasing is not server-backed');
|
||||
assert(serverSource.includes("url.pathname==='/api/player/purchase'")&&serverSource.includes('assertPlayerCanAfford')&&!serverSource.includes("url.pathname==='/api/player/place-field'"),'Player purchase validation is missing or retired field-placement API remains');
|
||||
assert(realtimeSource.includes("message.type === 'reaction'")&&realtimeSource.includes('REACTION_MIN_INTERVAL_MS')&&!realtimeSource.includes('broadcastFieldEffect'),'Realtime reaction throttling is missing or field broadcasts remain');
|
||||
|
||||
const port=24000+Math.floor(Math.random()*8000),dataDir=fs.mkdtempSync(path.join(os.tmpdir(),'bend-field-complete-'));
|
||||
const child=spawn(process.execPath,[path.join(root,'server.js')],{env:{...process.env,PORT:String(port),HOST:'127.0.0.1',BEND_FIELD_DATA_DIR:dataDir},stdio:['ignore','pipe','pipe']});
|
||||
let stderr='',aliceWs=null,bobWs=null;child.stderr.on('data',chunk=>stderr+=chunk);
|
||||
const base=`http://127.0.0.1:${port}`,sleep=ms=>new Promise(resolve=>setTimeout(resolve,ms));
|
||||
async function request(url,options={}){const response=await fetch(base+url,options),body=await response.json();return{response,body}}
|
||||
function auth(session){return{authorization:`Bearer ${session.playerId}.${session.token}`,'content-type':'application/json'}}
|
||||
function boardMeta(id,x,seed,puzzle){return{id,x,y:0,chunks:[[0,0]],level:1,targetLevel:1,seed,axis:'MIX',sealedSides:[],puzzle,rev:1,revAuthor:'client'}}
|
||||
function solvedState(puzzle){return{paths:puzzle.solution.map(route=>({startGate:route.startGate,endGate:route.endGate,cells:route.cells.map(cell=>[...cell])})),solved:true,expanded:false,scoreAwarded:100000,scoreVersion:6,store:{owner:'',pathIndex:0,cellIndex:0,itemIds:['score-lens'],purchases:[],priceVersion:1,priceCoefficient:1},rev:2,revAuthor:'client'}}
|
||||
|
||||
(async()=>{
|
||||
for(let i=0;i<100;i++){try{if((await request('/api/cloud/status')).response.ok)break}catch(_){}await sleep(40)}
|
||||
const status=(await request('/api/cloud/status')).body;assert.equal(status.reactions,true);assert.equal(status.playerEconomy,true);assert.equal(status.sharedItems,false);
|
||||
const alice=(await request('/api/cloud/session',{method:'POST',headers:{'content-type':'application/json'},body:JSON.stringify({name:'Alice'})})).body;
|
||||
const bob=(await request('/api/cloud/session',{method:'POST',headers:{'content-type':'application/json'},body:JSON.stringify({name:'Bob'})})).body;
|
||||
const puzzle=starterPuzzle(),b0=boardMeta('B0',0,15,puzzle);
|
||||
let pushed=await request('/api/cloud/push',{method:'POST',headers:auth(alice),body:JSON.stringify({baseRevision:0,global:{schema:31,appVersion:'47.70',generatorVersion:5,worldGeneration:'v47-field-reset-20260728-interaction-fix',nextId:1},metas:[b0],states:[{id:'B0',value:{paths:[],solved:false,rev:1,revAuthor:'alice'}}],deleted:[]})});assert.equal(pushed.response.status,200);
|
||||
aliceWs=await connectRealtime(base,alice);bobWs=await connectRealtime(base,bob);aliceWs.send({type:'viewport',minX:-8,minY:-8,maxX:8,maxY:8});bobWs.send({type:'viewport',minX:-8,minY:-8,maxX:8,maxY:8});await aliceWs.waitFor('snapshot');await bobWs.waitFor('snapshot');
|
||||
aliceWs.send({type:'reaction',id:'reaction-one',emoji:'🎉',x:.4,y:.6});const reaction=await bobWs.waitFor(message=>message.type==='reaction'&&message.reaction?.id==='reaction-one');assert.equal(reaction.reaction.emoji,'🎉');assert.equal(reaction.reaction.playerName,'Alice');assert(reaction.reaction.expiresAt>reaction.reaction.createdAt);
|
||||
aliceWs.send({type:'claim',requestId:'claim-b0',boardId:'B0'});assert.equal((await aliceWs.waitFor(message=>message.type==='claim-result'&&message.requestId==='claim-b0')).ok,true);
|
||||
pushed=await request('/api/cloud/push',{method:'POST',headers:auth(alice),body:JSON.stringify({baseRevision:1,global:{nextId:1},metas:[],states:[{id:'B0',value:solvedState(puzzle)}],deleted:[]})});assert.equal(pushed.response.status,200);let revision=pushed.body.revision;
|
||||
const alicePath=path.join(dataDir,`${alice.playerId}.json`),aliceRecord=JSON.parse(fs.readFileSync(alicePath,'utf8'));aliceRecord.earnedScore=250000;fs.writeFileSync(alicePath,JSON.stringify(aliceRecord));
|
||||
const purchase=await request('/api/player/purchase',{method:'POST',headers:auth(alice),body:JSON.stringify({boardId:'B0',itemId:'score-lens'})});assert.equal(purchase.response.status,201);assert.equal(purchase.body.player.purchases.length,1);assert.equal(purchase.body.purchase.itemId,'score-lens');
|
||||
const duplicate=await request('/api/player/purchase',{method:'POST',headers:auth(alice),body:JSON.stringify({boardId:'B0',itemId:'score-lens'})});assert.equal(duplicate.response.status,200);assert.equal(duplicate.body.purchase.purchaseId,purchase.body.purchase.purchaseId);
|
||||
const bobState=await request('/api/player/state',{headers:auth(bob)});assert.deepEqual(bobState.body.player.purchases,[]);
|
||||
const retiredPurchase=await request('/api/player/purchase',{method:'POST',headers:auth(alice),body:JSON.stringify({boardId:'B0',itemId:'level-min-10'})});assert.equal(retiredPurchase.response.status,400);
|
||||
const retiredPlacement=await request('/api/player/place-field',{method:'POST',headers:auth(alice),body:JSON.stringify({boardId:'B0',itemId:'level-min-10',x:4.25,y:-2.5})});assert.equal(retiredPlacement.response.status,404);
|
||||
const pull=await request('/api/cloud/pull?since=0&eventsSince=0',{headers:auth(bob)});assert.equal(pull.response.status,200);assert.equal(pull.body.page.global.fieldEffects,undefined);
|
||||
assert(!app.includes('level-min-10')&&!app.includes('level-max-10')&&!app.includes('fieldOverlayCanvas'),'Retired difficulty items remain in the client bundle');
|
||||
console.log('Shared-world reactions and personal item economy smoke test passed');
|
||||
})().catch(error=>{console.error(error);if(stderr)console.error(stderr);process.exitCode=1}).finally(()=>{aliceWs?.close();bobWs?.close();child.kill('SIGTERM');fs.rmSync(dataDir,{recursive:true,force:true})});
|
||||
Loading…
Add table
Add a link
Reference in a new issue