갱신 행의 ID를 보존하고 예약턴의 불필요한 시퀀스를 제거한다

This commit is contained in:
Hide_D committed 2026-11-14 18:44:22 +09:00
1 parent bc54b157c1
commit 81e101ffef
43 files changed
+587 -113

No files matched your search

@@ -12,6 +12,7 @@ import { afterAll, beforeAll, describe, expect, it } from 'vitest';
import {
DeferredGeneralAccessWorker,
flushDeferredGeneralAccessBatch,
enqueueDeferredGeneralAccess,
getDeferredGeneralAccessLimit,
} from '../src/services/deferredGeneralAccess.js';
@@ -91,7 +92,7 @@ liveIntegration('deferred general access with PostgreSQL and Redis', () => {
await redis.disconnect();
}
if (db) {
await db.generalAccessBatch.deleteMany({ where: { id: batchId } });
await db.generalAccessBatch.deleteMany({ where: { id: { startsWith: batchId } } });
await db.generalAccessLog.deleteMany({ where: { generalId } });
await db.general.deleteMany({ where: { id: generalId } });
await db.worldState.deleteMany({ where: { id: worldStateId } });
@@ -153,9 +154,19 @@ liveIntegration('deferred general access with PostgreSQL and Redis', () => {
nextAccessAt,
});
const counters =
await db.$queryRaw`SELECT 'access' AS kind,last_value FROM general_access_log_id_seq UNION ALL SELECT 'period',last_value FROM traffic_period_id_seq`;
const accessId = (await db.generalAccessLog.findUniqueOrThrow({ where: { generalId } })).id;
await flushDeferredGeneralAccessBatch(workerDb as never, `${batchId}-second`, [
{ generalId, userId, weight: 1, lastRefresh: now },
]);
expect(
await db.$queryRaw`SELECT 'access' AS kind,last_value FROM general_access_log_id_seq UNION ALL SELECT 'period',last_value FROM traffic_period_id_seq`
).toEqual(counters);
expect((await db.generalAccessLog.findUniqueOrThrow({ where: { generalId } })).id).toBe(accessId);
await worker.flushOnce();
await expect(db.generalAccessLog.findUniqueOrThrow({ where: { generalId } })).resolves.toMatchObject({
refreshTotal: 2,
refreshTotal: 3,
});
});
});
@@ -25,7 +25,7 @@ const deferredBatchId = `deferred-access-${endpointGeneralId}`;
const endpointAuth = (roles = ['user']): GameSessionTokenPayload => ({
version: 1,
profile: `${yearbookProfile}:default`,
profile: yearbookProfile,
issuedAt: '2026-07-26T00:00:00.000Z',
expiresAt: '2026-07-27T00:00:00.000Z',
sessionId: `access-session-${endpointGeneralId}`,
@@ -167,6 +167,9 @@ integration('general access tracking persistence', () => {
scoreStartedAt: new Date('2026-07-26T03:00:00.000Z'),
};
await upsertGeneralAccess(db, { ...firstWindow, weight: 2 });
const counters =
await db.$queryRaw`SELECT 'access' AS kind,last_value FROM general_access_log_id_seq UNION ALL SELECT 'period',last_value FROM traffic_period_id_seq`;
const accessId = (await db.generalAccessLog.findUniqueOrThrow({ where: { generalId } })).id;
await Promise.all(
Array.from({ length: 20 }, (_, index) =>
upsertGeneralAccess(db, {
@@ -177,6 +180,10 @@ integration('general access tracking persistence', () => {
)
);
expect(
await db.$queryRaw`SELECT 'access' AS kind,last_value FROM general_access_log_id_seq UNION ALL SELECT 'period',last_value FROM traffic_period_id_seq`
).toEqual(counters);
expect((await db.generalAccessLog.findUniqueOrThrow({ where: { generalId } })).id).toBe(accessId);
expect(await db.generalAccessLog.findUniqueOrThrow({ where: { generalId } })).toMatchObject({
userId: 'access-user-a',
refresh: 22,
+2 -8
View File
@@ -69,8 +69,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: BigInt(index + 1),
const rows: GeneralTurnRow[] = dataList.map((row) => ({
generalId: row.generalId as number,
turnIdx: row.turnIdx as number,
actionCode: row.actionCode as string,
@@ -136,8 +135,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: BigInt(index + 1),
const rows: NationTurnRow[] = dataList.map((row) => ({
nationId: row.nationId as number,
officerLevel: row.officerLevel as number,
turnIdx: row.turnIdx as number,
@@ -207,7 +205,6 @@ describe('reservedTurns', () => {
async () =>
[
{
id: 1n,
nationId: 4,
officerLevel: 12,
turnIdx: 1,
@@ -216,7 +213,6 @@ describe('reservedTurns', () => {
createdAt: new Date(),
},
{
id: 2n,
nationId: 4,
officerLevel: 10,
turnIdx: 0,
@@ -414,7 +410,6 @@ describe('reservedTurns', () => {
nationRevisions.set('6:12', 2);
nationTurns.set('6:12', [
{
id: 1n,
nationId: 6,
officerLevel: 12,
turnIdx: 0,
@@ -423,7 +418,6 @@ describe('reservedTurns', () => {
createdAt: new Date(),
},
{
id: 2n,
nationId: 6,
officerLevel: 12,
turnIdx: 1,
-4
View File
@@ -901,7 +901,6 @@ describe('appRouter', () => {
const general = buildGeneralRow({ id: 11, meta: { autorun_limit: 2408 } });
const generalTurns: GeneralTurnRow[] = [
{
id: 1n,
generalId: 11,
turnIdx: 0,
actionCode: 'che_화계',
@@ -922,7 +921,6 @@ describe('appRouter', () => {
const general = buildGeneralRow({ id: 12, nationId: 3, officerLevel: 5 });
const nationTurns: NationTurnRow[] = [
{
id: 1n,
nationId: 3,
officerLevel: 5,
turnIdx: 0,
@@ -1008,7 +1006,6 @@ describe('appRouter', () => {
general,
generalTurns: [
{
id: 1n,
generalId: general.id,
turnIdx: 0,
actionCode: 'che_훈련',
@@ -1016,7 +1013,6 @@ describe('appRouter', () => {
createdAt: new Date(),
},
{
id: 2n,
generalId: general.id,
turnIdx: 1,
actionCode: 'che_사기진작',
@@ -969,7 +969,7 @@ integration('game API security over HTTP transport', () => {
const telemetryAfter = await readAccessTelemetryState();
expect(telemetryAfter.periods).toEqual([
{
id: expect.any(Number),
id: expect.any(BigInt),
worldStateId: fixtureWorldId,
year: 190,
month: 1,
@@ -992,7 +992,7 @@ integration('game API security over HTTP transport', () => {
]);
expect(telemetryAfter.accessLogs).toEqual([
{
id: expect.any(Number),
id: expect.any(BigInt),
generalId,
userId,
lastRefresh: trafficPeriod.lastRefresh,
@@ -1548,8 +1548,8 @@ integration('game API security over HTTP transport', () => {
arg,
}))
).toEqual(databaseAfter.generalTurns);
expect(new Set(committedGeneralTurns.map(({ id }) => id)).size).toBe(30);
expect(committedGeneralTurns.every(({ id, createdAt }) => id > 0 && createdAt instanceof Date)).toBe(true);
expect(new Set(committedGeneralTurns.map(({ generalId, turnIdx }) => `${generalId}:${turnIdx}`)).size).toBe(30);
expect(committedGeneralTurns.every(({ createdAt }) => createdAt instanceof Date)).toBe(true);
expect(allowedTablesAfter.generalTurnRevisions.filter((row) => row.generalId !== generalId)).toEqual(
allowedTablesBefore.generalTurnRevisions.filter((row) => row.generalId !== generalId)
);
@@ -1567,7 +1567,7 @@ integration('game API security over HTTP transport', () => {
);
expect(allowedTablesAfter.generalAccessLogs.filter((row) => row.generalId === generalId)).toEqual([
{
id: expect.any(Number),
id: expect.any(BigInt),
generalId,
userId,
lastRefresh: null,
@@ -1733,8 +1733,14 @@ integration('game API security over HTTP transport', () => {
arg,
}))
).toEqual(expectedTurns);
expect(new Set(committedNationTurns.map(({ id }) => id)).size).toBe(12);
expect(committedNationTurns.every(({ id, createdAt }) => id > 0 && createdAt instanceof Date)).toBe(true);
expect(
new Set(
committedNationTurns.map(
({ nationId, officerLevel, turnIdx }) => `${nationId}:${officerLevel}:${turnIdx}`
)
).size
).toBe(12);
expect(committedNationTurns.every(({ createdAt }) => createdAt instanceof Date)).toBe(true);
expect(
allowedTablesAfter.nationTurnRevisions.filter(
(row) => row.nationId !== ownerNationId || row.officerLevel !== 5
@@ -1763,7 +1769,7 @@ integration('game API security over HTTP transport', () => {
);
expect(allowedTablesAfter.generalAccessLogs.filter((row) => row.generalId === sameNationGeneralId)).toEqual([
{
id: expect.any(Number),
id: expect.any(BigInt),
generalId: sameNationGeneralId,
userId: sameNationUserId,
lastRefresh: null,
@@ -1880,8 +1886,14 @@ integration('game API security over HTTP transport', () => {
arg,
}))
).toEqual(committed.nationTurns);
expect(new Set(committedNationTurns.map(({ id }) => id)).size).toBe(12);
expect(committedNationTurns.every(({ id, createdAt }) => id > 0 && createdAt instanceof Date)).toBe(true);
expect(
new Set(
committedNationTurns.map(
({ nationId, officerLevel, turnIdx }) => `${nationId}:${officerLevel}:${turnIdx}`
)
).size
).toBe(12);
expect(committedNationTurns.every(({ createdAt }) => createdAt instanceof Date)).toBe(true);
expect(
allowedTablesAfter.nationTurnRevisions.filter(
(row) => row.nationId !== ownerNationId || row.officerLevel !== 12
@@ -1910,7 +1922,7 @@ integration('game API security over HTTP transport', () => {
);
expect(allowedTablesAfter.generalAccessLogs.filter((row) => row.generalId === generalId)).toEqual([
{
id: expect.any(Number),
id: expect.any(BigInt),
generalId,
userId,
lastRefresh: null,
@@ -2868,7 +2880,7 @@ integration('game API security over HTTP transport', () => {
result: {
data: {
coverage: 'IDENTIFIED_LOGS_ONLY',
items: [{ id: ownLogs[index]!.id }],
items: [{ id: Number(ownLogs[index]!.id) }],
nextCursor: null,
},
},
@@ -2877,17 +2889,19 @@ integration('game API security over HTTP transport', () => {
}
expect(
(await get('generalLogs', admin, { generalId: logGeneralId, type: 'generalHistory', limit: 1 })).body
).toMatchObject({ result: { data: { items: [{ id: latest.id }], nextCursor: latest.id } } });
).toMatchObject({
result: { data: { items: [{ id: Number(latest.id) }], nextCursor: Number(latest.id) } },
});
expect(
(
await get('generalLogs', admin, {
generalId: logGeneralId,
type: 'generalHistory',
limit: 1,
cursor: latest.id,
cursor: Number(latest.id),
})
).body
).toMatchObject({ result: { data: { items: [{ id: ownLogs[0]!.id }], nextCursor: null } } });
).toMatchObject({ result: { data: { items: [{ id: Number(ownLogs[0]!.id) }], nextCursor: null } } });
for (const invalid of [
{ limit: 201 },
{ cursor: 0 },