Files
mini-zmc/tests/game-service.test.js
2026-07-29 10:50:33 +08:00

341 lines
12 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

const assert = require("assert");
const storage = {};
global.wx = {
getStorageSync(key) {
return storage[key];
},
setStorageSync(key, value) {
storage[key] = JSON.parse(JSON.stringify(value));
},
removeStorageSync(key) {
delete storage[key];
}
};
const service = require("../services/game-service");
function testLegacyRoomMigration() {
storage.zmc_rooms = {
123456: {
code: "123456",
name: "旧房间",
players: [{ id: "legacy-host", name: "旧玩家", ready: true }]
},
broken: "invalid"
};
service.ensureSeedData();
const room = service.getRoom("123456");
assert.strictEqual(room.schemaVersion, 2);
assert.strictEqual(room.status, "waiting");
assert.strictEqual(room.settings.mode, "infection");
assert.strictEqual(room.players[0].status, "ready");
assert.deepStrictEqual(room.events, []);
assert.strictEqual(storage.zmc_rooms.broken, undefined);
assert.ok(service.getRoom("731204"));
}
function testBrokenTopLevelStorage() {
storage.zmc_rooms = [];
assert.doesNotThrow(() => service.ensureSeedData());
assert.ok(service.getRoom("731204"));
}
function testMainFlowAndReplay() {
const joined = service.joinRoom("731204", "测试玩家");
assert.strictEqual(joined.players.length, 4);
const preparing = service.startGame("731204");
assert.strictEqual(preparing.status, "preparing");
assert.strictEqual(
preparing.players.filter((player) => player.role === "seeker").length,
preparing.settings.seekerCount
);
let room = service.beginPlaying("731204");
assert.strictEqual(room.status, "playing");
while (room.status === "playing") {
const target = room.players.find(
(player) => player.role === "hider" && player.status === "hiding"
);
const captureCode = service.getCaptureCode(room.code, target.id);
assert.strictEqual(captureCode.length, 4);
room = service.capture(room.code, target.id, captureCode);
}
assert.strictEqual(room.status, "finished");
assert.strictEqual(room.winner, "seekers");
const replay = service.resetRoom(room.code);
assert.strictEqual(replay.status, "waiting");
assert.ok(replay.players.every((player) => player.role === ""));
assert.ok(replay.players.every((player) => player.captures === 0));
assert.strictEqual(replay.resultSnapshot, null);
assert.strictEqual(replay.nextClueAt, null);
assert.strictEqual(replay.captureTokensUsed.length, 0);
}
function validPayload(overrides = {}) {
return {
name: "第二天测试房间",
mode: "classic",
durationMinutes: 30,
prepareMinutes: 3,
maxPlayers: 4,
seekerCount: 1,
clueIntervalMinutes: 5,
radiusMeters: 500,
skillsEnabled: true,
...overrides
};
}
function testCreateValidation() {
assert.throws(
() => service.createRoom(validPayload({ seekerCount: 4 })),
/初始寻找者必须少于房间人数上限/
);
assert.throws(
() => service.createRoom(validPayload({ durationMinutes: 10 })),
/游戏时长应为 1590 分钟/
);
assert.throws(
() => service.createRoom(validPayload({ name: " " })),
/请填写房间名称/
);
const room = service.createRoom(validPayload());
assert.strictEqual(room.name, "第二天测试房间");
assert.strictEqual(room.settings.seekerCount, 1);
}
function testLobbyPermissionsAndReadiness() {
const room = service.createRoom(validPayload({ maxPlayers: 6 }));
service.addDemoPlayers(room.code, 4);
assert.throws(
() => service.startGame(room.code, "not-the-host"),
/只有房主可以开始游戏/
);
const rooms = storage.zmc_rooms;
rooms[room.code].players[1].ready = false;
rooms[room.code].players[1].status = "idle";
storage.zmc_rooms = rooms;
assert.throws(() => service.startGame(room.code), /还有玩家尚未准备好/);
service.toggleReady(room.code, rooms[room.code].players[1].id);
const started = service.startGame(room.code);
assert.strictEqual(started.status, "preparing");
assert.throws(() => service.startGame(room.code), /不能重复开始/);
}
function testJoinStatusMessages() {
assert.throws(() => service.joinRoom("12", "玩家"), /六位房间码/);
assert.throws(() => service.joinRoom("999999", "玩家"), /没有找到/);
const room = service.createRoom(validPayload());
storage.zmc_rooms[room.code].status = "finished";
assert.throws(() => service.joinRoom(room.code, "玩家"), /已经结束/);
storage.zmc_rooms[room.code].status = "cancelled";
assert.throws(() => service.joinRoom(room.code, "玩家"), /房主取消/);
}
function prepareRoom(overrides = {}) {
const room = service.createRoom(validPayload({ maxPlayers: 6, ...overrides }));
service.addDemoPlayers(room.code, 4);
return room.code;
}
function testStateTransitionsAndDemoTiming() {
const code = prepareRoom();
assert.throws(() => service.beginPlaying(code), /不能进入追逃阶段/);
assert.throws(() => service.revealClue(code), /不能广播线索/);
assert.throws(() => service.useSkill(code, "radar"), /不能使用技能/);
const preparing = service.startGame(code);
assert.strictEqual(
preparing.phaseEndsAt - preparing.phaseStartedAt,
preparing.settings.prepareMinutes * 1000
);
const scheduledStartAt = preparing.phaseEndsAt;
const playing = service.beginPlaying(code);
assert.strictEqual(playing.phaseStartedAt, scheduledStartAt);
assert.strictEqual(playing.phaseEndsAt - playing.phaseStartedAt, 180000);
assert.strictEqual(playing.nextClueAt - playing.phaseStartedAt, 15000);
assert.throws(() => service.beginPlaying(code), /不能进入追逃阶段/);
const finished = service.finishGame(code, "hiders");
assert.strictEqual(finished.status, "finished");
assert.throws(() => service.finishGame(code), /不能结束游戏/);
}
function testConfiguredProductionTiming() {
const code = prepareRoom({
durationMinutes: 20,
prepareMinutes: 5,
clueIntervalMinutes: 8
});
storage.zmc_rooms[code].runtime.demoAccelerated = false;
const preparing = service.startGame(code);
assert.strictEqual(preparing.phaseEndsAt - preparing.phaseStartedAt, 5 * 60 * 1000);
const playing = service.beginPlaying(code);
assert.strictEqual(playing.phaseEndsAt - playing.phaseStartedAt, 20 * 60 * 1000);
assert.strictEqual(playing.nextClueAt - playing.phaseStartedAt, 8 * 60 * 1000);
}
function startPlayableRoom(overrides = {}) {
const code = prepareRoom(overrides);
const preparing = service.startGame(code);
return service.beginPlaying(preparing.code);
}
function testRoleCountsAndGameModes() {
let room = startPlayableRoom({ mode: "classic", maxPlayers: 8, seekerCount: 3 });
assert.strictEqual(room.players.length, 4);
assert.strictEqual(room.players.filter((player) => player.role === "seeker").length, 3);
let target = room.players.find((player) => player.role === "hider");
let token = service.getCaptureCode(room.code, target.id);
room = service.capture(room.code, target.id, token);
target = room.players.find((player) => player.id === target.id);
assert.strictEqual(target.role, "hider");
assert.strictEqual(target.status, "caught");
assert.strictEqual(room.status, "finished");
room = startPlayableRoom({ mode: "infection", seekerCount: 1 });
target = room.players.find((player) => player.role === "hider");
token = service.getCaptureCode(room.code, target.id);
room = service.capture(room.code, target.id, token);
target = room.players.find((player) => player.id === target.id);
assert.strictEqual(target.role, "seeker");
assert.strictEqual(target.status, "seeking");
}
function testPersistedSkills() {
let room = startPlayableRoom({ mode: "classic", skillsEnabled: true });
const seeker = room.players.find((player) => player.role === "seeker");
const hider = room.players.find((player) => player.role === "hider");
room = service.useSkill(room.code, "radar", seeker.id);
assert.strictEqual(
room.players.find((player) => player.id === seeker.id).skillsUsed.radar,
1
);
assert.throws(
() => service.useSkill(room.code, "radar", seeker.id),
/已经使用过/
);
room = service.useSkill(room.code, "silent", hider.id);
assert.strictEqual(
room.players.find((player) => player.id === hider.id).silentPending,
true
);
assert.ok(!room.events[0].message.includes(hider.name));
room = service.revealClue(room.code);
assert.strictEqual(
room.players.find((player) => player.id === hider.id).silentPending,
false
);
assert.match(room.events[0].message, /静默已生效/);
assert.throws(
() => service.useSkill(room.code, "radar", hider.id),
/当前身份不能使用/
);
room = startPlayableRoom({ skillsEnabled: false });
assert.throws(() => service.useSkill(room.code, "radar"), /未启用角色技能/);
}
function testCaptureTokenAndPermissions() {
const now = 1800000000000;
let room = startPlayableRoom({ mode: "classic" });
const seeker = room.players.find((player) => player.role === "seeker");
const target = room.players.find((player) => player.role === "hider");
const anotherHider = room.players.find(
(player) => player.role === "hider" && player.id !== target.id
);
const token = service.getCaptureCode(room.code, target.id, now);
assert.strictEqual(token.length, 4);
assert.notStrictEqual(service.getCaptureCode(room.code, anotherHider.id, now), token);
assert.notStrictEqual(service.getCaptureCode(room.code, target.id, now + 30000), token);
assert.throws(
() => service.capture(room.code, target.id, token, anotherHider.id, now),
/只有正在游戏的寻找者/
);
assert.throws(
() => service.capture(room.code, target.id, token, seeker.id, now + 30000),
/口令不正确或已经失效/
);
room = service.capture(room.code, target.id, token, seeker.id, now);
assert.strictEqual(room.players.find((player) => player.id === seeker.id).captures, 1);
assert.throws(
() => service.capture(room.code, target.id, token, seeker.id, now),
/口令已经使用过/
);
}
function testPauseResumeAndHostFinish() {
let room = startPlayableRoom({ mode: "classic" });
assert.throws(() => service.pauseGame(room.code, "not-host"), /只有房主/);
const phaseEndsAt = room.phaseEndsAt;
room = service.pauseGame(room.code);
assert.strictEqual(room.status, "paused");
assert.ok(room.pausedRemainingMs > 0);
assert.ok(room.pausedRemainingMs <= phaseEndsAt - room.pausedAt + 5);
assert.throws(() => service.pauseGame(room.code), /只有追逃阶段可以暂停/);
room = service.resumeGame(room.code);
assert.strictEqual(room.status, "playing");
assert.strictEqual(room.pausedRemainingMs, 0);
assert.throws(() => service.resumeGame(room.code), /没有暂停/);
assert.throws(
() => service.finishGame(room.code, "", "not-host", "host_ended"),
/只有房主可以提前结束/
);
room = service.finishGame(room.code, "", service.USER_ID, "host_ended");
assert.strictEqual(room.status, "finished");
assert.strictEqual(room.finishReason, "host_ended");
}
function testSafetyQuitAndStableResult() {
let room = startPlayableRoom({ mode: "classic", seekerCount: 1 });
const hiders = room.players.filter((player) => player.role === "hider");
room = service.quitGame(room.code, hiders[0].id);
assert.strictEqual(
room.players.find((player) => player.id === hiders[0].id).status,
"quit"
);
assert.strictEqual(room.status, "playing");
for (let index = 1; index < hiders.length; index += 1) {
room = service.quitGame(room.code, hiders[index].id);
}
assert.strictEqual(room.status, "finished");
assert.strictEqual(room.finishReason, "all_hiders_left");
assert.ok(room.resultSnapshot);
assert.strictEqual(room.resultSnapshot.players.length, room.players.length);
assert.deepStrictEqual(
room.resultSnapshot.players.map((player) => player.id),
service.getRoom(room.code).resultSnapshot.players.map((player) => player.id)
);
assert.throws(() => service.quitGame(room.code, hiders[0].id), /当前不能退出/);
}
testLegacyRoomMigration();
testBrokenTopLevelStorage();
testMainFlowAndReplay();
testCreateValidation();
testLobbyPermissionsAndReadiness();
testJoinStatusMessages();
testStateTransitionsAndDemoTiming();
testConfiguredProductionTiming();
testRoleCountsAndGameModes();
testPersistedSkills();
testCaptureTokenAndPermissions();
testPauseResumeAndHostFinish();
testSafetyQuitAndStableResult();
console.log("game-service tests passed");