新建项目
This commit is contained in:
340
tests/game-service.test.js
Normal file
340
tests/game-service.test.js
Normal file
@@ -0,0 +1,340 @@
|
||||
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 })),
|
||||
/游戏时长应为 15~90 分钟/
|
||||
);
|
||||
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");
|
||||
Reference in New Issue
Block a user