merge: 최신 main을 개인 봉급 표시에 통합한다
This commit is contained in:
@@ -79,7 +79,7 @@ export const troopRouter = router({
|
|||||||
troopLeaderIds.length === 0
|
troopLeaderIds.length === 0
|
||||||
? []
|
? []
|
||||||
: await ctx.db.generalTurn.findMany({
|
: await ctx.db.generalTurn.findMany({
|
||||||
where: { generalId: { in: troopLeaderIds } },
|
where: { generalId: { in: troopLeaderIds }, turnIdx: { lt: 5 } },
|
||||||
select: { generalId: true, turnIdx: true, actionCode: true },
|
select: { generalId: true, turnIdx: true, actionCode: true },
|
||||||
orderBy: [{ generalId: 'asc' }, { turnIdx: 'asc' }],
|
orderBy: [{ generalId: 'asc' }, { turnIdx: 'asc' }],
|
||||||
});
|
});
|
||||||
@@ -94,7 +94,8 @@ export const troopRouter = router({
|
|||||||
: 30;
|
: 30;
|
||||||
for (const turn of turns) {
|
for (const turn of turns) {
|
||||||
const list = reservedByLeader.get(turn.generalId) ?? [];
|
const list = reservedByLeader.get(turn.generalId) ?? [];
|
||||||
list.push(turn.actionCode);
|
// Ref 부대 편성은 앞쪽 슬롯이 집합인지 여부만 공개하고 다른 명령은 가립니다.
|
||||||
|
list.push(turn.actionCode === 'che_집합' ? '집합' : '-');
|
||||||
reservedByLeader.set(turn.generalId, list);
|
reservedByLeader.set(turn.generalId, list);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -77,10 +77,12 @@ const buildContext = (options: {
|
|||||||
auth?: GameSessionTokenPayload | null;
|
auth?: GameSessionTokenPayload | null;
|
||||||
requestId?: string;
|
requestId?: string;
|
||||||
transaction?: ReturnType<typeof vi.fn>;
|
transaction?: ReturnType<typeof vi.fn>;
|
||||||
|
turns?: Array<{ generalId: number; turnIdx: number; actionCode: string }>;
|
||||||
result: Awaited<ReturnType<TurnDaemonTransport['requestCommand']>>;
|
result: Awaited<ReturnType<TurnDaemonTransport['requestCommand']>>;
|
||||||
}) => {
|
}) => {
|
||||||
const me = options.me ?? buildGeneral();
|
const me = options.me ?? buildGeneral();
|
||||||
const requestCommand = vi.fn(async () => options.result);
|
const requestCommand = vi.fn(async () => options.result);
|
||||||
|
const generalTurnFindMany = vi.fn(async () => options.turns ?? []);
|
||||||
const db = {
|
const db = {
|
||||||
...(options.transaction ? { $transaction: options.transaction } : {}),
|
...(options.transaction ? { $transaction: options.transaction } : {}),
|
||||||
general: {
|
general: {
|
||||||
@@ -110,7 +112,7 @@ const buildContext = (options: {
|
|||||||
},
|
},
|
||||||
city: { findMany: vi.fn(async () => [{ id: 1, name: '북평' }]) },
|
city: { findMany: vi.fn(async () => [{ id: 1, name: '북평' }]) },
|
||||||
worldState: { findFirst: vi.fn(async () => ({ config: { const: { upgradeLimit: 20 } } })) },
|
worldState: { findFirst: vi.fn(async () => ({ config: { const: { upgradeLimit: 20 } } })) },
|
||||||
generalTurn: { findMany: vi.fn(async () => []) },
|
generalTurn: { findMany: generalTurnFindMany },
|
||||||
};
|
};
|
||||||
const accessTokenStore = new RedisAccessTokenStore(
|
const accessTokenStore = new RedisAccessTokenStore(
|
||||||
{
|
{
|
||||||
@@ -134,7 +136,7 @@ const buildContext = (options: {
|
|||||||
flushStore: new InMemoryFlushStore(),
|
flushStore: new InMemoryFlushStore(),
|
||||||
gameTokenSecret: 'test-secret',
|
gameTokenSecret: 'test-secret',
|
||||||
};
|
};
|
||||||
return { context, requestCommand };
|
return { context, requestCommand, generalTurnFindMany };
|
||||||
};
|
};
|
||||||
|
|
||||||
describe('troop router permissions and mutations', () => {
|
describe('troop router permissions and mutations', () => {
|
||||||
@@ -175,6 +177,30 @@ describe('troop router permissions and mutations', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('returns only the first five Ref-redacted troop command labels without exposing action codes', async () => {
|
||||||
|
const fixture = buildContext({
|
||||||
|
me: buildGeneral({ troopId: 1 }),
|
||||||
|
turns: [
|
||||||
|
{ generalId: 1, turnIdx: 0, actionCode: 'che_집합' },
|
||||||
|
{ generalId: 1, turnIdx: 1, actionCode: 'che_이동' },
|
||||||
|
{ generalId: 1, turnIdx: 2, actionCode: 'che_징병' },
|
||||||
|
{ generalId: 1, turnIdx: 3, actionCode: '휴식' },
|
||||||
|
{ generalId: 1, turnIdx: 4, actionCode: 'che_화계' },
|
||||||
|
],
|
||||||
|
result: null,
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await appRouter.createCaller(fixture.context).troop.getList();
|
||||||
|
|
||||||
|
expect(result.troops[0]?.reservedCommands).toEqual(['집합', '-', '-', '-', '-']);
|
||||||
|
expect(JSON.stringify(result.troops)).not.toContain('che_');
|
||||||
|
expect(fixture.generalTurnFindMany).toHaveBeenCalledWith({
|
||||||
|
where: { generalId: { in: [1] }, turnIdx: { lt: 5 } },
|
||||||
|
select: { generalId: true, turnIdx: true, actionCode: true },
|
||||||
|
orderBy: [{ generalId: 'asc' }, { turnIdx: 'asc' }],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it('creates a troop only for the general owned by the authenticated user', async () => {
|
it('creates a troop only for the general owned by the authenticated user', async () => {
|
||||||
const { context, requestCommand } = buildContext({
|
const { context, requestCommand } = buildContext({
|
||||||
result: { type: 'troopCreate', ok: true, generalId: 1, troopId: 1, troopName: '백마대' },
|
result: { type: 'troopCreate', ok: true, generalId: 1, troopId: 1, troopName: '백마대' },
|
||||||
|
|||||||
@@ -80,7 +80,7 @@ const baseTroops = (): TroopFixture[] => [
|
|||||||
name: '백마대',
|
name: '백마대',
|
||||||
nationId: 1,
|
nationId: 1,
|
||||||
turnTime: '2026-07-25T08:20:30.000Z',
|
turnTime: '2026-07-25T08:20:30.000Z',
|
||||||
reservedCommands: ['che_집합', 'che_이동'],
|
reservedCommands: ['집합', '-'],
|
||||||
leader: {
|
leader: {
|
||||||
id: 1,
|
id: 1,
|
||||||
name: '공손찬',
|
name: '공손찬',
|
||||||
@@ -96,7 +96,7 @@ const baseTroops = (): TroopFixture[] => [
|
|||||||
name: '청룡대',
|
name: '청룡대',
|
||||||
nationId: 1,
|
nationId: 1,
|
||||||
turnTime: '2026-07-25T08:30:30.000Z',
|
turnTime: '2026-07-25T08:30:30.000Z',
|
||||||
reservedCommands: ['che_징병'],
|
reservedCommands: ['-'],
|
||||||
leader: {
|
leader: {
|
||||||
id: 2,
|
id: 2,
|
||||||
name: '관우',
|
name: '관우',
|
||||||
@@ -224,6 +224,27 @@ const installApiFixture = async (page: Page, state: FixtureState) => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
test('shows only the Ref-safe first-five troop command labels on desktop and mobile', async ({ page }) => {
|
||||||
|
await installApiFixture(page, {
|
||||||
|
me: { id: 1, troopId: 1 },
|
||||||
|
permission: 4,
|
||||||
|
troops: baseTroops(),
|
||||||
|
});
|
||||||
|
|
||||||
|
for (const viewport of [
|
||||||
|
{ width: 1000, height: 800 },
|
||||||
|
{ width: 500, height: 800 },
|
||||||
|
]) {
|
||||||
|
await page.setViewportSize(viewport);
|
||||||
|
await gotoTroop(page);
|
||||||
|
|
||||||
|
const firstTroopCommands = page.locator('.troopReservedCommand').first();
|
||||||
|
await expect(firstTroopCommands).toContainText('1: 집합');
|
||||||
|
await expect(firstTroopCommands).toContainText('2: -');
|
||||||
|
await expect(page.locator('#troopList')).not.toContainText('che_');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
test('renders the legacy desktop grid with matching computed geometry and states', async ({ page }) => {
|
test('renders the legacy desktop grid with matching computed geometry and states', async ({ page }) => {
|
||||||
await installApiFixture(page, {
|
await installApiFixture(page, {
|
||||||
me: { id: 1, troopId: 1 },
|
me: { id: 1, troopId: 1 },
|
||||||
@@ -233,6 +254,9 @@ test('renders the legacy desktop grid with matching computed geometry and states
|
|||||||
await page.setViewportSize({ width: 1000, height: 800 });
|
await page.setViewportSize({ width: 1000, height: 800 });
|
||||||
await gotoTroop(page);
|
await gotoTroop(page);
|
||||||
await expect(page.locator('.troopInfo').filter({ hasText: '백마대' })).toBeVisible();
|
await expect(page.locator('.troopInfo').filter({ hasText: '백마대' })).toBeVisible();
|
||||||
|
await expect(page.locator('.troopReservedCommand').first()).toContainText('1: 집합');
|
||||||
|
await expect(page.locator('.troopReservedCommand').first()).toContainText('2: -');
|
||||||
|
await expect(page.locator('#troopList')).not.toContainText('che_');
|
||||||
|
|
||||||
const geometry = await page
|
const geometry = await page
|
||||||
.locator('.troopItem')
|
.locator('.troopItem')
|
||||||
@@ -319,6 +343,9 @@ test('matches the legacy 500px responsive placement', async ({ page }) => {
|
|||||||
await page.setViewportSize({ width: 500, height: 800 });
|
await page.setViewportSize({ width: 500, height: 800 });
|
||||||
await gotoTroop(page);
|
await gotoTroop(page);
|
||||||
await expect(page.locator('.troopInfo').filter({ hasText: '백마대' })).toBeVisible();
|
await expect(page.locator('.troopInfo').filter({ hasText: '백마대' })).toBeVisible();
|
||||||
|
await expect(page.locator('.troopReservedCommand').first()).toContainText('1: 집합');
|
||||||
|
await expect(page.locator('.troopReservedCommand').first()).toContainText('2: -');
|
||||||
|
await expect(page.locator('#troopList')).not.toContainText('che_');
|
||||||
|
|
||||||
const geometry = await page
|
const geometry = await page
|
||||||
.locator('.troopItem')
|
.locator('.troopItem')
|
||||||
|
|||||||
Reference in New Issue
Block a user