test: 실제 Game API 명령 전수 근거를 고정한다

This commit is contained in:
2026-08-24 16:30:48 +00:00
parent cca2b3d925
commit a9e2431115
17 changed files with 1138 additions and 48 deletions
+91
View File
@@ -324,6 +324,97 @@ describe('troop router permissions and mutations', () => {
});
});
it('joins a troop with the session-owned general and a stable ENGINE request identity', async () => {
const transaction = vi.fn(async () => {
throw new Error('API transaction must not run');
});
const fixture = buildContext({
me: buildGeneral({ id: 17, userId: 'user-1', troopId: 0 }),
requestId: 'http-troop-join',
transaction,
result: { type: 'troopJoin', ok: true, generalId: 17, troopId: 9 },
});
const input = { troopId: 9, userId: 'forged-user', generalId: 999 };
await expect(appRouter.createCaller(fixture.context).troop.join(input)).resolves.toEqual({ ok: true });
expect(transaction).not.toHaveBeenCalled();
expect(fixture.requestCommand).toHaveBeenCalledWith({
type: 'troopJoin',
requestId: 'http-troop-join:troop.join:engine:0:troopJoin',
userId: 'user-1',
generalId: 17,
troopId: 9,
});
});
it('maps an authoritative troop-join rejection without trusting client actor fields', async () => {
const fixture = buildContext({
me: buildGeneral({ id: 17, userId: 'user-1', troopId: 0 }),
result: {
type: 'troopJoin',
ok: false,
generalId: 17,
troopId: 9,
reason: '다른 국가의 부대입니다.',
},
});
const input = { troopId: 9, userId: 'forged-user', generalId: 999 };
await expect(appRouter.createCaller(fixture.context).troop.join(input)).rejects.toMatchObject({
code: 'PRECONDITION_FAILED',
message: '다른 국가의 부대입니다.',
});
expect(fixture.requestCommand).toHaveBeenCalledWith(
expect.objectContaining({ userId: 'user-1', generalId: 17, troopId: 9 })
);
});
it('exits a troop with the session-owned general and a stable ENGINE request identity', async () => {
const transaction = vi.fn(async () => {
throw new Error('API transaction must not run');
});
const fixture = buildContext({
me: buildGeneral({ id: 17, userId: 'user-1', troopId: 9 }),
requestId: 'http-troop-exit',
transaction,
result: { type: 'troopExit', ok: true, generalId: 17, wasLeader: false },
});
await expect(appRouter.createCaller(fixture.context).troop.exit()).resolves.toEqual({
ok: true,
wasLeader: false,
});
expect(transaction).not.toHaveBeenCalled();
expect(fixture.requestCommand).toHaveBeenCalledWith({
type: 'troopExit',
requestId: 'http-troop-exit:troop.exit:engine:0:troopExit',
userId: 'user-1',
generalId: 17,
});
});
it('maps an authoritative troop-exit rejection for the session-owned general', async () => {
const fixture = buildContext({
me: buildGeneral({ id: 17, userId: 'user-1', troopId: 0 }),
result: {
type: 'troopExit',
ok: false,
generalId: 17,
reason: '부대에 소속되어 있지 않습니다.',
},
});
await expect(appRouter.createCaller(fixture.context).troop.exit()).rejects.toMatchObject({
code: 'PRECONDITION_FAILED',
message: '부대에 소속되어 있지 않습니다.',
});
expect(fixture.requestCommand).toHaveBeenCalledWith({
type: 'troopExit',
userId: 'user-1',
generalId: 17,
});
});
it('maps an ENGINE actor-binding rejection to a forbidden API response', async () => {
const fixture = buildContext({
result: {