fix: 갱신 비용에 따라 접속 점수를 기록

revision 확인과 서버 event 기반 선택 갱신은 무가점으로 두고 PostgreSQL projection을 다시 만드는 초기·수동·복구 갱신만 1점을 기록한다. 인증 범위에 묶인 1회용 realtime 증표와 회귀 검증을 추가한다.
This commit is contained in:
2026-08-17 11:10:38 +00:00
parent a80d58f761
commit 19629fe3cc
18 changed files with 643 additions and 96 deletions
@@ -207,10 +207,10 @@ void test('merges browser-safe boolean invalidations and starts at most once per
let nowMs = 0;
let nextTimerId = 1;
const timers = new Map<number, { callback: () => void; at: number }>();
const observed: Array<{ context: boolean; records: boolean }> = [];
const observed: Array<{ context: boolean; records: boolean; refreshGrant: string }> = [];
const queue = createMergedReadModelRefreshQueue(
async (invalidation) => {
observed.push({ context: invalidation.context, records: invalidation.records });
async (invalidation, refreshGrant) => {
observed.push({ context: invalidation.context, records: invalidation.records, refreshGrant });
},
{
minIntervalMs: 1_000,
@@ -232,13 +232,13 @@ void test('merges browser-safe boolean invalidations and starts at most once per
}
};
queue.request({ ...createEmptyRealtimeReadModelInvalidation(), context: true });
queue.request({ ...createEmptyRealtimeReadModelInvalidation(), context: true }, 'grant-a');
runDueTimers();
await new Promise<void>((resolve) => setImmediate(resolve));
assert.deepEqual(observed, [{ context: true, records: false }]);
assert.deepEqual(observed, [{ context: true, records: false, refreshGrant: 'grant-a' }]);
queue.request({ ...createEmptyRealtimeReadModelInvalidation(), context: true });
queue.request({ ...createEmptyRealtimeReadModelInvalidation(), records: true });
queue.request({ ...createEmptyRealtimeReadModelInvalidation(), context: true }, 'grant-b');
queue.request({ ...createEmptyRealtimeReadModelInvalidation(), records: true }, 'grant-c');
nowMs = 999;
runDueTimers();
assert.equal(observed.length, 1);
@@ -246,7 +246,7 @@ void test('merges browser-safe boolean invalidations and starts at most once per
runDueTimers();
await new Promise<void>((resolve) => setImmediate(resolve));
assert.deepEqual(observed, [
{ context: true, records: false },
{ context: true, records: true },
{ context: true, records: false, refreshGrant: 'grant-a' },
{ context: true, records: true, refreshGrant: 'grant-c' },
]);
});
@@ -0,0 +1,39 @@
import assert from 'node:assert/strict';
import test from 'node:test';
import {
createRealtimeRequestOptions,
REALTIME_ACCESS_GRANT_CONTEXT_KEY,
resolveBatchRealtimeAccessGrant,
} from '../src/utils/realtimeAccessGrant.ts';
void test('adds a realtime grant only to server-signaled request options', () => {
assert.deepEqual(createRealtimeRequestOptions('grant-a'), {
context: { [REALTIME_ACCESS_GRANT_CONTEXT_KEY]: 'grant-a' },
});
assert.equal(createRealtimeRequestOptions(undefined), undefined);
});
void test('sets a batch grant only when every operation carries the same proof', () => {
assert.equal(
resolveBatchRealtimeAccessGrant([
{ context: { [REALTIME_ACCESS_GRANT_CONTEXT_KEY]: 'grant-a' } },
{ context: { [REALTIME_ACCESS_GRANT_CONTEXT_KEY]: 'grant-a' } },
]),
'grant-a'
);
assert.equal(
resolveBatchRealtimeAccessGrant([
{ context: { [REALTIME_ACCESS_GRANT_CONTEXT_KEY]: 'grant-a' } },
{ context: {} },
]),
undefined
);
assert.equal(
resolveBatchRealtimeAccessGrant([
{ context: { [REALTIME_ACCESS_GRANT_CONTEXT_KEY]: 'grant-a' } },
{ context: { [REALTIME_ACCESS_GRANT_CONTEXT_KEY]: 'grant-b' } },
]),
undefined
);
});