Files
youle_cocos/cocoscreator_projects/framework-tests/platform/commands.test.ts
T

224 lines
8.3 KiB
TypeScript

import { STARTUP_CONFIG as EDITABLE_STARTUP_CONFIG } from '../../YouleNexus/assets/framework/config/profiles.ts';
// 固定测试来源,允许开发者独立切换项目的 local/remote 开关。
const STARTUP_CONFIG = Object.freeze({...EDITABLE_STARTUP_CONFIG, configSource:'remote' as const});
import { remoteFixture } from '../fixtures/remote-config.ts';
import assert from 'node:assert/strict';
import { readFileSync } from 'node:fs';
import { test } from 'node:test';
import type { RuntimeConfig } from '../../YouleNexus/assets/framework/config/runtime-config.ts';
import type { OutboundEnvelope } from '../../YouleNexus/assets/framework/core/types/envelope.ts';
import { PlatformCommands } from '../../YouleNexus/assets/framework/platform/commands.ts';
import { PlatformStore } from '../../YouleNexus/assets/framework/platform/stores/platform-store.ts';
import {
parseLoginResponse,
} from '../../YouleNexus/assets/framework/protocol/contracts/index.ts';
const RUNTIME_CONFIG: RuntimeConfig = Object.freeze({
startupConfig: {...STARTUP_CONFIG,mode:'release' as const,useLocalServer:false}, mode: 'release',
isDebugger: false,
identity: Object.freeze({
agentid: 'agent-from-config',
channelid: 'channel-from-config',
gameid: 'game-from-config',
marketid: 'market-from-config',
version: 41,
}),
servers: Object.freeze(['ws://fixture']),
source: 'remote',
gameserver: 'https://fixture/config',
...remoteFixture(),
});
function fixture(name: string): { data: Record<string, unknown> } {
return JSON.parse(readFileSync(new URL(`../fixtures/contracts/${name}`, import.meta.url), 'utf8'));
}
function makeStore(location: 'outside' | 'inside', stage = 0, infinite = 0): PlatformStore {
const store = new PlatformStore();
const source = fixture(location === 'inside' ? 'player-login-room.json' : 'player-login-success.json');
if (location === 'inside') {
source.data.isbattle = stage;
source.data.infinite = infinite;
}
store.applyLoginSuccess(parseLoginResponse(source.data));
return store;
}
function makeCommands(store: PlatformStore): {
readonly commands: PlatformCommands;
readonly sent: OutboundEnvelope[];
} {
const sent: OutboundEnvelope[] = [];
return {
commands: new PlatformCommands({
runtimeConfig: RUNTIME_CONFIG,
store,
send: { send: (envelope) => { sent.push(envelope); } },
}),
sent,
};
}
test('VIP query and both entry forms use the exact agent protocol',()=>{
const store=makeStore('outside'),{commands,sent}=makeCommands(store);
commands.getVipRooms(['123456']);commands.joinVipRoom('123456',null);commands.joinVipRoom(900001,{city:'测试'});
assert.deepEqual(sent[0],{app:'youle',route:'agent',rpc:'getInfoByShortCode',data:{agentid:'agent-from-config',gameid:'game-from-config',shortcodeList:['123456']}});
assert.deepEqual(sent[1],{app:'youle',route:'agent',rpc:'self_join_room',data:{agentid:'agent-from-config',playerid:430511,gameid:'game-from-config',roomcode:'123456',joinmode:0,location:null,ip:store.getState().players.entities[430511].ip}});
assert.equal((sent[2].data as {roomcode:number}).roomcode,900001);
assert.throws(()=>makeCommands(new PlatformStore()).commands.getVipRooms([]),/logged-in/);
});
test('task requests preserve legacy identity fields and reject unclaimable entries',()=>{
const store=makeStore('outside');const {commands,sent}=makeCommands(store);
commands.getPlayerTasks();
store.applyTaskList({tasks:[{taskid:7,title:'任务',award:1,finish:1,total:1,state:1}]});
commands.claimTask(7);
assert.deepEqual(sent,[
{app:'youle',route:'agent',rpc:'get_player_task',data:{agentid:'agent-from-config',playerid:430511,gameid:'game-from-config'}},
{app:'youle',route:'agent',rpc:'get_task_award',data:{agentid:'agent-from-config',playerid:430511,taskid:7}},
]);
store.applyTaskAward({taskid:7,taskstate:0});
assert.equal(store.getState().tasks?.[0].state,2);
assert.equal(store.getState().players.entities[430511].taskstate,0);
assert.throws(()=>commands.claimTask(7),/claimable/);
const before=store.getState();
assert.throws(()=>store.applyTaskList({tasks:[{taskid:7}]}));
assert.equal(store.getState(),before);
assert.throws(()=>makeCommands(new PlatformStore()).commands.getPlayerTasks(),/logged-in/);
});
test('task numeric strings normalize at ingestion and invalid values never commit',()=>{
const store=makeStore('outside');
const task={taskid:'7',title:'任务',award:'3',finish:'2',total:'5',state:'0'};
store.applyTaskList({tasks:[task]});
assert.deepEqual(store.getState().tasks,[{...task,award:3,finish:2,total:5,state:0}]);
assert.equal(task.award,'3');
const before=store.getState();
for(const field of ['award','finish','total','state']){
for(const value of ['', ' ', 'abc', 'Infinity', null, true, [], {}, undefined]){
assert.throws(()=>store.applyTaskList({tasks:[{...task,[field]:value}]}),new RegExp(field));
assert.equal(store.getState(),before);
}
}
assert.throws(()=>store.applyTaskList({tasks:[{...task,state:'1.5'}]}),/state/);
});
test('prepare and exitRoom use the first-slice builders with authoritative config and room state', () => {
const { commands, sent } = makeCommands(makeStore('inside', 0));
commands.prepare();
commands.exitRoom();
assert.deepEqual(sent, [
{
app: 'youle',
route: 'room',
rpc: 'player_prepare',
data: {
agentid: 'agent-from-config',
playerid: 430511,
gameid: 'game-from-config',
roomcode: '100001',
},
},
{
app: 'youle',
route: 'room',
rpc: 'self_exit_room',
data: {
agentid: 'agent-from-config',
playerid: 430511,
gameid: 'game-from-config',
roomcode: '100001',
},
},
]);
assert.equal(commands.prepare.length, 0);
assert.equal(commands.exitRoom.length, 0);
});
test('joinRoom uses authenticated Store identity and requires every non-optional environment field', () => {
const { commands, sent } = makeCommands(makeStore('outside'));
const location = { latitude: 28.7, longitude: 115.8 };
commands.joinRoom({
roomcode: '654321',
location,
ip: '203.0.113.7',
vipMatch: 1,
match_id: 'match-9',
});
assert.deepEqual(sent, [{
app: 'youle',
route: 'agent',
rpc: 'self_join_room',
data: {
agentid: 'agent-from-config',
playerid: 430511,
gameid: 'game-from-config',
roomcode: '654321',
location,
ip: '203.0.113.7',
vipMatch: 1,
match_id: 'match-9',
},
}]);
assert.equal((sent[0]!.data as { location: unknown }).location, location);
assert.equal(commands.joinRoom.length, 1);
assert.throws(
() => commands.joinRoom({ roomcode: '654321', ip: '203.0.113.7' } as never),
/location/i,
);
assert.throws(
() => commands.joinRoom({ roomcode: '654321', location } as never),
/ip/i,
);
assert.equal(sent.length, 1);
});
test('exitRoom sends self_exit_room while an infinite room is in battle', () => {
const { commands, sent } = makeCommands(makeStore('inside', 1, 1));
commands.exitRoom();
assert.deepEqual(sent, [{
app: 'youle',
route: 'room',
rpc: 'self_exit_room',
data: {
agentid: 'agent-from-config',
playerid: 430511,
gameid: 'game-from-config',
roomcode: '100001',
},
}]);
});
test('command preconditions reject missing authentication, wrong room location, and battle exit without sending', () => {
const unauthenticated = makeCommands(new PlatformStore());
assert.throws(
() => unauthenticated.commands.joinRoom({ roomcode: '654321', location: null, ip: '' }),
/authenticated|player/i,
);
const outside = makeCommands(makeStore('outside'));
assert.throws(() => outside.commands.prepare(), /inside.*room|room.*inside/i);
assert.throws(() => outside.commands.exitRoom(), /inside.*room|room.*inside/i);
const inside = makeCommands(makeStore('inside', 0));
assert.throws(
() => inside.commands.joinRoom({ roomcode: '654321', location: null, ip: '' }),
/outside.*room|already.*room/i,
);
const battle = makeCommands(makeStore('inside', 1));
assert.throws(() => battle.commands.exitRoom(), /room\.exit.*stage=0/i);
assert.equal(unauthenticated.sent.length, 0);
assert.equal(outside.sent.length, 0);
assert.equal(inside.sent.length, 0);
assert.equal(battle.sent.length, 0);
});