고빈도 저장 ID를 64비트로 확장하고 랭킹 갱신의 시퀀스 낭비를 막는다

This commit is contained in:
Hide_D committed 2026-11-14 15:34:21 +09:00
1 parent 087517821e
commit bc54b157c1
25 files changed
+518 -86

No files matched your search

+15 -12
View File
@@ -108,7 +108,7 @@ const buildContext = (options: {
inheritancePoint?: number;
inheritanceRows?: Array<{ key: string; value: number }>;
rankRows?: Array<{ type: string; value: number }>;
inheritanceLogs?: Array<{ id: number; year: number; month: number; text: string; createdAt: Date }>;
inheritanceLogs?: Array<{ id: number | bigint; year: number; month: number; text: string; createdAt: Date }>;
configConst?: Record<string, unknown>;
scenarioCode?: string;
configMap?: Record<string, unknown>;
@@ -460,11 +460,11 @@ 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 }],
inheritanceLogs: [{ id: 2_147_483_648n, 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 },
{ id: 2_147_483_648, year: 200, month: 4, text: '경계 로그', createdAt },
]);
expect(fixture.inheritanceLogFindMany).toHaveBeenCalledWith({
where: { userId: 'user-1' },
@@ -478,23 +478,26 @@ describe('inherit router actor and permission boundaries', () => {
const fixture = buildContext({ inheritanceLogs: [] });
const caller = appRouter.createCaller(fixture.context);
await expect(caller.inherit.getLogs({ lastId: 2_147_483_647 })).resolves.toEqual([]);
await expect(caller.inherit.getLogs({ lastId: 2_147_483_648 })).resolves.toEqual([]);
expect(fixture.inheritanceLogFindMany).toHaveBeenCalledWith({
where: { userId: 'user-1', id: { lt: 2_147_483_647 } },
where: { userId: 'user-1', id: { lt: 2_147_483_648 } },
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({});
it.each([0, -1, 1.5, Number.MAX_SAFE_INTEGER + 1])(
'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();
});
await expect(appRouter.createCaller(fixture.context).inherit.getLogs({ lastId })).rejects.toMatchObject({
code: 'BAD_REQUEST',
});
expect(fixture.inheritanceLogFindMany).not.toHaveBeenCalled();
}
);
it('delegates the authenticated actor and maps a missing-general daemon rejection', async () => {
const fixture = buildContext({
+17 -1
View File
@@ -33,7 +33,7 @@ type LogQuery = {
select: { id: true; text: true; createdAt: true };
};
const buildContext = (findMany: (query: LogQuery) => Promise<Array<{ id: number; text: string }>>) =>
const buildContext = (findMany: (query: LogQuery) => Promise<Array<{ id: number | bigint; text: string }>>) =>
({
auth,
profile: { id: 'che', name: 'che:default', scenario: 'default' },
@@ -94,6 +94,22 @@ describe('general.getRecentRecords', () => {
});
});
it('returns exact numeric JSON cursors above int4 and trims a bigint cursor row', async () => {
const findMany = vi.fn(async () => [
{ id: 2_147_483_650n, text: '최신' },
{ id: 2_147_483_648n, text: '이전 cursor' },
]);
const caller = appRouter.createCaller(buildContext(findMany));
const result = await caller.general.getRecentRecords({
lastGeneralRecordId: 2_147_483_648,
lastWorldHistoryId: 2_147_483_648,
});
expect(result.global).toEqual([{ id: 2_147_483_650, text: '최신' }]);
expect(result.general).toEqual(result.global);
expect(result.history).toEqual(result.global);
expect(() => JSON.stringify(result)).not.toThrow();
});
it('caps an initial bucket at the legacy 15-row limit', async () => {
const rows = Array.from({ length: 16 }, (_, index) => ({
id: 100 - index,
@@ -38,8 +38,8 @@ const buildContext = () => {
},
logEntry: {
findMany: vi.fn(async () => [
{ id: 9, text: '<Y>최근 정세</>' },
{ id: 8, text: '이전 정세' },
{ id: 9n, text: '<Y>최근 정세</>' },
{ id: 8n, text: '이전 정세' },
]),
},
$queryRaw: vi
+27 -24
View File
@@ -70,7 +70,7 @@ const buildDb = (autorunLimit: number | null = null) => {
createMany: async ({ data }: GeneralTurnCreateManyArgs) => {
const dataList = (Array.isArray(data) ? data : [data]) as Record<string, unknown>[];
const rows: GeneralTurnRow[] = dataList.map((row, index: number) => ({
id: index + 1,
id: BigInt(index + 1),
generalId: row.generalId as number,
turnIdx: row.turnIdx as number,
actionCode: row.actionCode as string,
@@ -137,7 +137,7 @@ const buildDb = (autorunLimit: number | null = null) => {
createMany: async ({ data }: NationTurnCreateManyArgs) => {
const dataList = (Array.isArray(data) ? data : [data]) as Record<string, unknown>[];
const rows: NationTurnRow[] = dataList.map((row, index: number) => ({
id: index + 1,
id: BigInt(index + 1),
nationId: row.nationId as number,
officerLevel: row.officerLevel as number,
turnIdx: row.turnIdx as number,
@@ -203,26 +203,29 @@ const buildDb = (autorunLimit: number | null = null) => {
describe('reservedTurns', () => {
it('loads multiple nation officer queues with two batched queries and preserves defaults', async () => {
const findTurns = vi.fn(async () => [
{
id: 1,
nationId: 4,
officerLevel: 12,
turnIdx: 1,
actionCode: 'che_징병',
arg: { amount: 100 },
createdAt: new Date(),
},
{
id: 2,
nationId: 4,
officerLevel: 10,
turnIdx: 0,
actionCode: 'che_훈련',
arg: {},
createdAt: new Date(),
},
] satisfies NationTurnRow[]);
const findTurns = vi.fn(
async () =>
[
{
id: 1n,
nationId: 4,
officerLevel: 12,
turnIdx: 1,
actionCode: 'che_징병',
arg: { amount: 100 },
createdAt: new Date(),
},
{
id: 2n,
nationId: 4,
officerLevel: 10,
turnIdx: 0,
actionCode: 'che_훈련',
arg: {},
createdAt: new Date(),
},
] satisfies NationTurnRow[]
);
const findRevisions = vi.fn(async () => [
{ nationId: 4, officerLevel: 12, revision: 7, updatedAt: new Date() },
]);
@@ -411,7 +414,7 @@ describe('reservedTurns', () => {
nationRevisions.set('6:12', 2);
nationTurns.set('6:12', [
{
id: 1,
id: 1n,
nationId: 6,
officerLevel: 12,
turnIdx: 0,
@@ -420,7 +423,7 @@ describe('reservedTurns', () => {
createdAt: new Date(),
},
{
id: 2,
id: 2n,
nationId: 6,
officerLevel: 12,
turnIdx: 1,
+4 -4
View File
@@ -901,7 +901,7 @@ describe('appRouter', () => {
const general = buildGeneralRow({ id: 11, meta: { autorun_limit: 2408 } });
const generalTurns: GeneralTurnRow[] = [
{
id: 1,
id: 1n,
generalId: 11,
turnIdx: 0,
actionCode: 'che_화계',
@@ -922,7 +922,7 @@ describe('appRouter', () => {
const general = buildGeneralRow({ id: 12, nationId: 3, officerLevel: 5 });
const nationTurns: NationTurnRow[] = [
{
id: 1,
id: 1n,
nationId: 3,
officerLevel: 5,
turnIdx: 0,
@@ -1008,7 +1008,7 @@ describe('appRouter', () => {
general,
generalTurns: [
{
id: 1,
id: 1n,
generalId: general.id,
turnIdx: 0,
actionCode: 'che_훈련',
@@ -1016,7 +1016,7 @@ describe('appRouter', () => {
createdAt: new Date(),
},
{
id: 2,
id: 2n,
generalId: general.id,
turnIdx: 1,
actionCode: 'che_사기진작',