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
+21
View File
@@ -295,6 +295,7 @@ describe('troop router permissions and mutations', () => {
});
expect(requestCommand).toHaveBeenCalledWith({
type: 'troopCreate',
userId: 'user-1',
generalId: 1,
troopName: '백마대',
});
@@ -317,11 +318,30 @@ describe('troop router permissions and mutations', () => {
expect(requestCommand).toHaveBeenCalledWith({
type: 'troopCreate',
requestId: 'http-troop-create:troop.create:engine:0:troopCreate',
userId: 'user-1',
generalId: 1,
troopName: '백마대',
});
});
it('maps an ENGINE actor-binding rejection to a forbidden API response', async () => {
const fixture = buildContext({
result: {
type: 'commandRejected',
ok: false,
commandType: 'troopCreate',
reason: '명령 수행 장수의 현재 소유자가 일치하지 않습니다.',
},
});
await expect(
appRouter.createCaller(fixture.context).troop.create({ troopName: '백마대' })
).rejects.toMatchObject({
code: 'FORBIDDEN',
message: '명령 수행 장수의 현재 소유자가 일치하지 않습니다.',
});
});
it('rejects troop creation before daemon dispatch when already assigned or the name is blank', async () => {
const assigned = buildContext({
me: buildGeneral({ troopId: 9 }),
@@ -372,6 +392,7 @@ describe('troop router permissions and mutations', () => {
).resolves.toEqual({ ok: true });
expect(authorized.requestCommand).toHaveBeenCalledWith({
type: 'troopKick',
userId: 'user-1',
generalId: 1,
troopId: 1,
targetGeneralId: 3,