feat(platform): expose restricted game host and commands
This commit is contained in:
@@ -0,0 +1,154 @@
|
||||
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({
|
||||
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',
|
||||
rawConfig: Object.freeze({ data: Object.freeze({ urlserver: 'fixture' }) }),
|
||||
});
|
||||
|
||||
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): 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;
|
||||
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('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('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);
|
||||
});
|
||||
Reference in New Issue
Block a user