fix(game): keep in-game status pages available

This commit is contained in:
2026-08-01 15:50:01 +00:00
parent 65d489d21d
commit fd47d6999c
4 changed files with 124 additions and 8 deletions
+4 -3
View File
@@ -48,6 +48,8 @@ const BUFF_LABELS: Record<InheritBuffType, string> = {
warMagicTrialProbOppose: '상대 전투계략 시도 확률 감소',
};
const POSTGRES_INTEGER_MAX = 2_147_483_647;
const parseBuffRecord = (raw: unknown): Record<string, number> => {
if (typeof raw === 'string') {
const parsed = parseJson<Record<string, number>>(raw);
@@ -319,7 +321,7 @@ export const inheritRouter = router({
getLogs: authedProcedure
.input(
z.object({
lastId: z.number().int().optional(),
lastId: z.number().int().min(1).max(POSTGRES_INTEGER_MAX).optional(),
})
)
.query(async ({ ctx, input }) => {
@@ -327,11 +329,10 @@ export const inheritRouter = router({
if (!userId) {
throw new TRPCError({ code: 'UNAUTHORIZED' });
}
const lastId = input.lastId ?? Number.MAX_SAFE_INTEGER;
const logs = await ctx.db.inheritanceLog.findMany({
where: {
userId,
id: { lt: lastId },
...(input.lastId === undefined ? {} : { id: { lt: input.lastId } }),
},
orderBy: { id: 'desc' },
take: 30,
+43 -2
View File
@@ -95,6 +95,7 @@ const buildContext = (options: {
general?: GeneralRow | null;
target?: GeneralRow | null;
inheritancePoint?: number;
inheritanceLogs?: Array<{ id: number; year: number; month: number; text: string; createdAt: Date }>;
}) => {
const auth = options.auth === undefined ? buildAuth() : options.auth;
const general = options.general === undefined ? buildGeneral() : options.general;
@@ -110,6 +111,7 @@ const buildContext = (options: {
const pointUpsert = vi.fn(async () => ({}));
const logCreate = vi.fn(async () => ({}));
const findMany = vi.fn(async () => (target ? [{ id: target.id, name: target.name }] : []));
const inheritanceLogFindMany = vi.fn(async () => options.inheritanceLogs ?? []);
const db = {
$queryRaw: vi.fn(async () => [{ value: options.inheritancePoint ?? 10_000 }]),
worldState: {
@@ -129,7 +131,7 @@ const buildContext = (options: {
},
inheritanceLog: {
create: logCreate,
findMany: vi.fn(async () => []),
findMany: inheritanceLogFindMany,
},
inheritanceUserState: {
findUnique: vi.fn(async () => null),
@@ -157,7 +159,7 @@ const buildContext = (options: {
flushStore: new InMemoryFlushStore(),
gameTokenSecret: 'test-secret',
};
return { context, requestCommand, pointUpsert, logCreate, findMany };
return { context, requestCommand, pointUpsert, logCreate, findMany, inheritanceLogFindMany };
};
describe('inherit router actor and permission boundaries', () => {
@@ -189,6 +191,45 @@ describe('inherit router actor and permission boundaries', () => {
});
});
it('loads the first inheritance-log page without an out-of-range integer cursor', async () => {
const createdAt = new Date('2026-07-26T00:00:00Z');
const fixture = buildContext({
inheritanceLogs: [{ id: 2_147_483_647, year: 200, month: 4, text: '경계 로그', createdAt }],
});
await expect(appRouter.createCaller(fixture.context).inherit.getLogs({})).resolves.toEqual([
{ id: 2_147_483_647, year: 200, month: 4, text: '경계 로그', createdAt },
]);
expect(fixture.inheritanceLogFindMany).toHaveBeenCalledWith({
where: { userId: 'user-1' },
orderBy: { id: 'desc' },
take: 30,
select: { id: true, year: true, month: true, text: true, createdAt: true },
});
});
it('uses a bounded cursor for following and empty inheritance-log pages', async () => {
const fixture = buildContext({ inheritanceLogs: [] });
const caller = appRouter.createCaller(fixture.context);
await expect(caller.inherit.getLogs({ lastId: 2_147_483_647 })).resolves.toEqual([]);
expect(fixture.inheritanceLogFindMany).toHaveBeenCalledWith({
where: { userId: 'user-1', id: { lt: 2_147_483_647 } },
orderBy: { id: 'desc' },
take: 30,
select: { id: true, year: true, month: true, text: true, createdAt: true },
});
});
it.each([0, -1, 1.5, 2_147_483_648])('rejects an invalid inheritance-log cursor: %s', async (lastId) => {
const fixture = buildContext({});
await expect(appRouter.createCaller(fixture.context).inherit.getLogs({ lastId })).rejects.toMatchObject({
code: 'BAD_REQUEST',
});
expect(fixture.inheritanceLogFindMany).not.toHaveBeenCalled();
});
it('does not dispatch or charge when the authenticated user owns no general', async () => {
const fixture = buildContext({
auth: buildAuth('user-2'),