Sessions 5-7a: 955 tests, deployment ready
This commit is contained in:
@@ -0,0 +1,118 @@
|
||||
const mockState = { rows: new Map(), upserts: [] };
|
||||
|
||||
function mockKeyOf(s, g, p) { return `${s}|${g}|${p}`; }
|
||||
|
||||
jest.mock('../../src/utils/supabase', () => ({
|
||||
getSupabaseServiceClient: () => ({
|
||||
from() {
|
||||
const ctx = { filters: {} };
|
||||
const proxy = {
|
||||
select() { return proxy; },
|
||||
eq(col, val) { ctx.filters[col] = val; return proxy; },
|
||||
maybeSingle() {
|
||||
const row = mockState.rows.get(mockKeyOf(ctx.filters.sport, ctx.filters.grade, ctx.filters.period));
|
||||
return Promise.resolve({ data: row || null, error: null });
|
||||
},
|
||||
upsert(row) {
|
||||
mockState.upserts.push(row);
|
||||
mockState.rows.set(mockKeyOf(row.sport, row.grade, row.period), { ...row });
|
||||
return Promise.resolve({ error: null });
|
||||
},
|
||||
then(resolve) {
|
||||
// For getAccuracyDashboard — list all rows with period filter.
|
||||
const period = ctx.filters.period;
|
||||
const all = Array.from(mockState.rows.values()).filter((r) => !period || r.period === period);
|
||||
return resolve({ data: all, error: null });
|
||||
},
|
||||
};
|
||||
return proxy;
|
||||
},
|
||||
}),
|
||||
}));
|
||||
|
||||
const at = require('../../src/services/intelligence/accuracyTracker');
|
||||
|
||||
beforeEach(() => {
|
||||
mockState.rows.clear();
|
||||
mockState.upserts.length = 0;
|
||||
});
|
||||
|
||||
describe('accuracyTracker.recordResolution', () => {
|
||||
test('increments hit + total_graded across all three periods', async () => {
|
||||
await at.recordResolution('nba', 'A', 'hit');
|
||||
expect(mockState.upserts).toHaveLength(3); // all_time + 30d + 7d
|
||||
for (const u of mockState.upserts) {
|
||||
expect(u.total_graded).toBe(1);
|
||||
expect(u.total_hit).toBe(1);
|
||||
expect(u.total_miss).toBe(0);
|
||||
}
|
||||
});
|
||||
|
||||
test('push/void don\'t count toward hit rate', async () => {
|
||||
await at.recordResolution('nba', 'A', 'push');
|
||||
await at.recordResolution('nba', 'A', 'void');
|
||||
const row = mockState.rows.get('nba|A|all_time');
|
||||
expect(row.total_hit).toBe(0);
|
||||
expect(row.total_miss).toBe(0);
|
||||
expect(row.total_push).toBe(1);
|
||||
expect(row.total_void).toBe(1);
|
||||
expect(row.hit_rate).toBeNull();
|
||||
});
|
||||
|
||||
test('hit_rate is hits/(hits+misses) only', async () => {
|
||||
for (let i = 0; i < 7; i += 1) await at.recordResolution('nba', 'A', 'hit');
|
||||
for (let i = 0; i < 3; i += 1) await at.recordResolution('nba', 'A', 'miss');
|
||||
const row = mockState.rows.get('nba|A|all_time');
|
||||
expect(row.hit_rate).toBeCloseTo(0.7, 5);
|
||||
});
|
||||
});
|
||||
|
||||
describe('accuracyTracker — baseline lock', () => {
|
||||
test('baseline locks at 100 decisive resolutions', async () => {
|
||||
for (let i = 0; i < 99; i += 1) await at.recordResolution('nba', 'A', 'hit');
|
||||
let row = mockState.rows.get('nba|A|all_time');
|
||||
expect(row.baseline_locked).toBe(false);
|
||||
await at.recordResolution('nba', 'A', 'hit');
|
||||
row = mockState.rows.get('nba|A|all_time');
|
||||
expect(row.baseline_locked).toBe(true);
|
||||
expect(row.baseline_hit_rate).toBe(1.0);
|
||||
});
|
||||
|
||||
test('only locks the all_time period', async () => {
|
||||
for (let i = 0; i < 100; i += 1) await at.recordResolution('nba', 'A', 'hit');
|
||||
expect(mockState.rows.get('nba|A|all_time').baseline_locked).toBe(true);
|
||||
expect(mockState.rows.get('nba|A|last_30d').baseline_locked).toBe(false);
|
||||
});
|
||||
|
||||
test('pushes do not contribute to baseline trigger', async () => {
|
||||
for (let i = 0; i < 99; i += 1) await at.recordResolution('nba', 'A', 'hit');
|
||||
await at.recordResolution('nba', 'A', 'push');
|
||||
expect(mockState.rows.get('nba|A|all_time').baseline_locked).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('accuracyTracker.getAccuracy', () => {
|
||||
test('reports expected hit rate by grade', async () => {
|
||||
const r = await at.getAccuracy('nba', 'A+', 'all_time');
|
||||
expect(r.expected).toBe(0.65);
|
||||
});
|
||||
|
||||
test('returns deltas relative to baseline once locked', async () => {
|
||||
for (let i = 0; i < 80; i += 1) await at.recordResolution('nba', 'A', 'hit');
|
||||
for (let i = 0; i < 20; i += 1) await at.recordResolution('nba', 'A', 'miss');
|
||||
const r = await at.getAccuracy('nba', 'A', 'all_time');
|
||||
expect(r.locked).toBe(true);
|
||||
expect(r.baseline).toBeCloseTo(0.8, 5);
|
||||
expect(r.delta).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('accuracyTracker.getAllAccuracy', () => {
|
||||
test('returns one entry per known grade tier', async () => {
|
||||
const all = await at.getAllAccuracy('nba');
|
||||
const tiers = all.map((r) => r.grade);
|
||||
expect(tiers).toContain('A+');
|
||||
expect(tiers).toContain('F');
|
||||
expect(all.length).toBe(Object.keys(at.EXPECTED_HIT_RATES).length);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user