bend_puzzle/test/v4795-single-world-only-smoke-test.js

54 lines
4.6 KiB
JavaScript
Raw Normal View History

2026-08-01 16:06:14 +09:00
'use strict';
const assert=require('assert/strict');
const fs=require('fs');
const fsp=fs.promises;
const os=require('os');
const path=require('path');
const {spawn,spawnSync}=require('child_process');
const {read,functionSource,root}=require('./helpers/app-source');
const app=read('app.js'),runtime=read('runtime-config.js'),html=read('index.html'),serverSource=read('server.js'),serviceSource=read('scripts/service-control.js');
assert.match(runtime,/cloudApi:true/);
assert.match(runtime,/singleSharedWorld:true/);
assert(!app.includes('同期コード')&&!app.includes('端末のみ'),'Retired local/sync-code UI remains');
assert(html.includes('class="shared-indicator"')&&html.includes('<span>共有</span>')&&!html.includes('共有 · 接続中')&&!html.includes('端末のみ'),'Shared state is not a fixed-size indicator');
assert(!functionSource('setCloudStatus').includes('textContent')&&functionSource('setCloudStatus').includes('cloudBtn.dataset.state'),'Shared status still changes visible text or player names');
assert(functionSource('init').indexOf('await initCloudSync({startup:true})')<functionSource('init').indexOf('refreshWorldView({rebuild:true'),'The local field is rendered before the server-authoritative world is adopted');
assert(functionSource('fetchCurrentSharedWorldStatus').includes("status.singleWorld===true")&&functionSource('fetchCurrentSharedWorldStatus').includes('status.appVersion===APP_VERSION')&&functionSource('initCloudSync').includes('resetClientToSingleSharedWorld()'),'Startup does not require the current single shared world');
assert(functionSource('requestBoardClaim').indexOf("fetchJson('/api/realtime/claim'")<functionSource('requestBoardClaim').indexOf('requestBoardClaimThroughRealtime')&&functionSource('requestBoardClaimThroughRealtime').includes('realtimeSend')&&!functionSource('requestBoardClaim').includes('await waitForRealtimeReady()'),'Board input is not using direct claim with realtime fallback');
assert(!app.includes('BroadcastChannel')&&!app.includes('syncStorageKey')&&!app.includes('queueWorldSignal'),'Retired local cross-tab synchronization remains');
assert(serverSource.includes('INSTANCE_LOCK_FILE')&&serverSource.includes('Another LinkField server is already running'),'Server process lock is missing');
2026-08-01 22:31:04 +09:00
assert(serverSource.includes("process.env.LINK_FIELD_WORLD_DIR || '/link-field/world'"),'Shared data does not use the safe production default or explicit world-directory override');
assert(!serverSource.includes('LINK_FIELD_DATA_ROOT')&&!serverSource.includes("'.local', 'share', 'LinkField'"),'Production can still select an implicit second shared-world directory');
2026-08-01 16:06:14 +09:00
assert(serviceSource.includes('stopLegacyLinkFieldServers')&&serviceSource.includes('Replacing the running LinkField server')&&serviceSource.includes("fsp.unlink(path.join(publicDir,'.linkfield-port'))"),'Old server processes or stale bridge ports can survive deployment');
const sleep=ms=>new Promise(resolve=>setTimeout(resolve,ms));
(async()=>{
const temp=await fsp.mkdtemp(path.join(os.tmpdir(),'linkfield-single-world-'));
const publicDir=path.join(temp,'public');
const dataRoot=path.join(temp,'data-root');
const env={...process.env,HOST:'127.0.0.1',PORT:'0',LINK_FIELD_PUBLIC_DIR:publicDir,LINK_FIELD_TEST_DATA_ROOT:dataRoot,LINK_FIELD_APACHE_BRIDGE:'0'};
const first=spawn(process.execPath,[path.join(root,'server.js')],{env,stdio:['ignore','pipe','pipe']});
let firstErr='';first.stderr.on('data',chunk=>firstErr+=chunk);
try{
let port=0;
for(let i=0;i<100;i++){
try{port=Number((await fsp.readFile(path.join(publicDir,'.linkfield-port'),'utf8')).trim());if(port)break}catch(_){ }
if(first.exitCode!=null)throw new Error(firstErr||`First server exited with ${first.exitCode}`);
await sleep(40);
}
assert(port>0,'First single-world server did not start');
const status=await (await fetch(`http://127.0.0.1:${port}/api/cloud/status`)).json();
assert.equal(status.singleWorld,true);
assert.equal(status.revision,0);
const second=spawnSync(process.execPath,[path.join(root,'server.js')],{env,encoding:'utf8',timeout:10000});
assert.notEqual(second.status,0,'A second shared-world server started against the same data root');
assert.match(`${second.stdout}\n${second.stderr}`,/already running/i);
}finally{
first.kill('SIGTERM');
for(let i=0;i<50&&first.exitCode==null;i++)await sleep(20);
if(first.exitCode==null)first.kill('SIGKILL');
await fsp.rm(temp,{recursive:true,force:true});
}
console.log('LinkField v48.0 /link-field/world single shared world guards passed');
})().catch(error=>{console.error(error);process.exitCode=1});