fix: 특수 유저 커맨드의 Ref 호환 경계를 보강한다

수뇌 국가 설정과 NPC 정책을 actor-bound ENGINE mutation으로 옮기고, 추방·등용·점령·멸망·아이템 폐기의 특수 분기를 Ref와 맞춘다.

요청 ID를 사용자·프로필별로 격리하고 토너먼트 손상 projection을 fail-closed하며 실제 DB 및 Ref 차등 회귀를 보강한다.
This commit is contained in:
2026-08-24 12:10:57 +00:00
parent 5389f8ed94
commit 630bc29100
74 changed files with 3893 additions and 1141 deletions
+25 -12
View File
@@ -67,8 +67,14 @@ const auth: GameSessionTokenPayload = {
sanctions: {},
};
const buildContext = (blockChangeScout: boolean) => {
const requestCommand = vi.fn();
const buildContext = () => {
const requestCommand = vi.fn(async () => ({
type: 'setNationSetting' as const,
ok: false as const,
code: 'FORBIDDEN' as const,
nationId: 1,
reason: '임관 설정을 바꿀 수 없도록 설정되어 있습니다.',
}));
const db = {
general: {
findFirst: vi.fn(async () => general),
@@ -77,7 +83,7 @@ const buildContext = (blockChangeScout: boolean) => {
findUnique: vi.fn(async () => ({ meta: {} })),
},
worldState: {
findFirst: vi.fn(async () => ({ meta: { block_change_scout: blockChangeScout } })),
findFirst: vi.fn(async () => ({ meta: { block_change_scout: true } })),
},
};
const redisClient = {
@@ -102,14 +108,21 @@ const buildContext = (blockChangeScout: boolean) => {
};
describe('nation scout policy lock', () => {
it('rejects policy changes before daemon dispatch while the scenario lock is enabled', async () => {
const fixture = buildContext(true);
await expect(appRouter.createCaller(fixture.context).nation.setBlockScout({ value: false })).rejects.toMatchObject(
{
code: 'FORBIDDEN',
message: '임관 설정을 바꿀 수 없도록 설정되어 있습니다.',
}
);
expect(fixture.requestCommand).not.toHaveBeenCalled();
it('lets the engine recheck the scenario lock and maps its rejection', async () => {
const fixture = buildContext();
await expect(
appRouter.createCaller(fixture.context).nation.setBlockScout({ value: false })
).rejects.toMatchObject({
code: 'FORBIDDEN',
message: '임관 설정을 바꿀 수 없도록 설정되어 있습니다.',
});
expect(fixture.requestCommand).toHaveBeenCalledWith({
type: 'setNationSetting',
userId: 'user-1',
generalId: 1,
nationId: 1,
mutation: { kind: 'blockScout', value: false },
});
expect(fixture.context.db.worldState.findFirst).not.toHaveBeenCalled();
});
});