fix: 투표 UTC wall과 migration 시간대 경계를 고정한다
This commit is contained in:
@@ -2479,12 +2479,14 @@ const insertVoteSelection = async (
|
||||
if (!ctx.commandDb) {
|
||||
return 'missing';
|
||||
}
|
||||
const createdAt = new Date();
|
||||
const rows = await ctx.commandDb.$queryRaw<Array<{ id: number }>>(GamePrisma.sql`
|
||||
INSERT INTO vote (vote_id, general_id, nation_id, selection)
|
||||
INSERT INTO vote (vote_id, general_id, nation_id, selection, created_at)
|
||||
SELECT poll.id,
|
||||
${general.id},
|
||||
${general.nationId},
|
||||
CAST(${JSON.stringify(selection)} AS jsonb)
|
||||
CAST(${JSON.stringify(selection)} AS jsonb),
|
||||
${createdAt}
|
||||
FROM vote_poll poll
|
||||
WHERE poll.id = ${command.voteId}
|
||||
ON CONFLICT (vote_id, general_id) DO NOTHING
|
||||
|
||||
@@ -286,4 +286,37 @@ integration('game cancellation transaction', () => {
|
||||
generalMode: 'DELETE',
|
||||
});
|
||||
});
|
||||
|
||||
it('keeps a native inheritance log inside the cancellation boundary under a KST session', async () => {
|
||||
const text = '신규/복귀 생성으로 포인트 1500 지급';
|
||||
await db.inheritanceLog.deleteMany({ where: { userId, text } });
|
||||
const [session] = await db.$queryRaw<Array<{ timeZone: string }>>`
|
||||
SELECT current_setting('TIMEZONE') AS "timeZone"
|
||||
`;
|
||||
expect(session?.timeZone).toBe('Asia/Seoul');
|
||||
|
||||
const createdAfter = Date.now();
|
||||
const log = await db.inheritanceLog.create({
|
||||
data: { userId, serverId, year: 190, month: 7, text },
|
||||
});
|
||||
const createdBefore = Date.now();
|
||||
expect(log.createdAt.getTime()).toBeGreaterThanOrEqual(createdAfter);
|
||||
expect(log.createdAt.getTime()).toBeLessThanOrEqual(createdBefore);
|
||||
|
||||
const result = await cancelGame({
|
||||
cancellationId: 'game-cancellation-kst-default-fixture',
|
||||
databaseUrl: databaseUrl!,
|
||||
cancelledBy: 'admin',
|
||||
reason: 'KST 기본값 경계 검증',
|
||||
historyMode: 'RETAIN_ABANDONED',
|
||||
generalMode: 'RETAIN',
|
||||
earnedPointRetentionPercent: 40,
|
||||
cancelledAt: new Date(createdBefore + 1_000),
|
||||
});
|
||||
expect(result.settlements[userId]).toMatchObject({
|
||||
earnedPoint: 1_790.005,
|
||||
retainedEarnedPoint: 716,
|
||||
finalPoint: 10_716,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -222,11 +222,12 @@ describe('voteReward command', () => {
|
||||
|
||||
let voteInserted = false;
|
||||
let voteQueryCount = 0;
|
||||
let voteInsertQuery: { strings: readonly string[]; values: readonly unknown[] } | undefined;
|
||||
const commandDb = {
|
||||
auction: {
|
||||
findMany: async () => [],
|
||||
},
|
||||
$queryRaw: async (query: { strings: readonly string[] }) => {
|
||||
$queryRaw: async (query: { strings: readonly string[]; values: readonly unknown[] }) => {
|
||||
voteQueryCount += 1;
|
||||
if (query.strings.join(' ').includes('SELECT options')) {
|
||||
return [
|
||||
@@ -239,6 +240,7 @@ describe('voteReward command', () => {
|
||||
},
|
||||
];
|
||||
}
|
||||
voteInsertQuery = query;
|
||||
if (voteInserted) return [];
|
||||
voteInserted = true;
|
||||
return [{ id: 11 }];
|
||||
@@ -255,12 +257,23 @@ describe('voteReward command', () => {
|
||||
acceptedGameTick: 0,
|
||||
};
|
||||
|
||||
const writerWindowStart = Date.now();
|
||||
const result = await handler.handle(command, { db: commandDb as any });
|
||||
const writerWindowEnd = Date.now();
|
||||
expect(result && result.type).toBe('voteReward');
|
||||
if (!result || result.type !== 'voteReward' || !result.ok) {
|
||||
throw new Error('voteReward result missing');
|
||||
}
|
||||
expect(result.awardedUnique).toBe(true);
|
||||
expect(voteInsertQuery?.strings.join(' ')).toContain('created_at');
|
||||
expect(
|
||||
voteInsertQuery?.values.filter(
|
||||
(value) =>
|
||||
value instanceof Date &&
|
||||
value.getTime() >= writerWindowStart &&
|
||||
value.getTime() <= writerWindowEnd
|
||||
)
|
||||
).toHaveLength(1);
|
||||
|
||||
const updated = world.getGeneralById(1);
|
||||
// ENGINE is the single reward linearization point, so it uses the
|
||||
|
||||
Reference in New Issue
Block a user