fix: repair existing test failures

This commit is contained in:
2026-08-13 00:52:22 +00:00
parent 6da7d2f899
commit d133db32eb
24 changed files with 386 additions and 116 deletions
+6 -5
View File
@@ -27,6 +27,7 @@ interface AuctionRow {
detail: unknown;
status: AuctionStatus;
closeAt: Date;
latestEventId: string;
}
interface AuctionBidRow {
@@ -101,7 +102,8 @@ const loadAuction = async (prisma: QueryClient, auctionId: number): Promise<Auct
host_general_id as "hostGeneralId",
detail,
status,
close_at as "closeAt"
close_at as "closeAt",
latest_event_id as "latestEventId"
FROM auction
WHERE id = ${auctionId}
FOR UPDATE
@@ -375,6 +377,8 @@ export const createAuctionBidder = async (options: {
`
);
// 같은 논리 tick의 연속 입찰은 시각이 같으므로, UUID 정렬이 아니라
// 읽어 둔 이벤트 ID를 버전 토큰으로 사용해 경합만 거절한다.
const updated = await tx.$executeRaw(
GamePrisma.sql`
UPDATE auction
@@ -385,10 +389,7 @@ export const createAuctionBidder = async (options: {
updated_at = ${eventAt}
WHERE id = ${command.auctionId}
AND status = 'OPEN'
AND (
latest_event_at < ${eventAt}
OR (latest_event_at = ${eventAt} AND latest_event_id < ${eventId})
)
AND latest_event_id = ${auction.latestEventId}
`
);
@@ -301,6 +301,7 @@ const zNpcPossessGeneral = z
ownerLegacyPenalty: zRecord.optional(),
generalId: z.number().int().positive(),
tokenNonce: z.number().int().nonnegative(),
acceptedGameAt: z.string().refine(isCanonicalIsoTimestamp).optional(),
})
.strict();
@@ -460,6 +460,7 @@ export const possessNpcGeneral = async (options: {
acceptedAt: Date;
}): Promise<{ ok: true; generalId: number }> => {
const { db, world, worldState, userId, generalId, acceptedAt } = options;
// queue 대기 중 만료된 token도 enqueue 시점에는 유효했으므로 저장된 논리 수락 시각으로 다시 검증한다.
const tokenAcceptedAt = truncateToSeconds(acceptedAt);
requireNpcPossessionWorld(worldState);
await lockNpcPossession(db, userId);
@@ -294,7 +294,9 @@ async function handleNpcPossessGeneral(
throw new Error('NPC possession world state is missing.');
}
const operationalAcceptedAt = await resolveCommandAcceptedAt(db, command);
const acceptedAt = ctx.world.getGameNow(operationalAcceptedAt);
const acceptedAt = command.acceptedGameAt
? new Date(command.acceptedGameAt)
: ctx.world.getGameNow(operationalAcceptedAt);
try {
return {
type: 'npcPossessGeneral',
@@ -563,5 +563,5 @@ describe('NPC 대형 시뮬레이션', () => {
}
throw error;
}
});
}, 30_000);
});
@@ -144,7 +144,7 @@ integration('unification finalization transaction', () => {
{ userId, key: 'tournament', value: 11 },
],
});
const futureCloseAt = new Date(Date.now() + 86_400_000);
const futureCloseAt = new Date('0190-07-02T00:00:00.000Z');
const uniqueAuction = await db.auction.create({
data: {
type: 'UNIQUE_ITEM',
@@ -243,11 +243,15 @@ integration('unification finalization transaction', () => {
where: { generalId_type: { generalId: fixtureId, type: 'inherit_spent_dyn' } },
})
).toMatchObject({ value: 50 });
expect(
(await db.auctionBid.findMany({ where: { auctionId: uniqueAuction.id }, orderBy: { id: 'asc' } })).map(
(bid) => bid.meta
)
).toEqual([
const persistedBids = await db.auctionBid.findMany({
where: { auctionId: uniqueAuction.id },
orderBy: { id: 'asc' },
});
expect(persistedBids.map((bid) => bid.eventAt.toISOString())).toEqual([
'0190-07-01T00:00:00.000Z',
'0190-07-01T00:00:00.000Z',
]);
expect(persistedBids.map((bid) => bid.meta)).toEqual([
expect.objectContaining({ inheritSpentTrackedAmount: 30 }),
expect.objectContaining({ inheritSpentTrackedAmount: 50 }),
]);