const { applyTierGating, __internals } = require('../../src/utils/tierGating'); function sampleResult(overrides = {}) { return { player: 'Jalen Brunson', stat_type: 'points', line: 26.5, direction: 'over', book: 'draftkings', grade: 'A', confidence: 78, edge_pct: 6.2, kill_conditions_triggered: [ { code: 'TRAP', reason: 'Multiple trap signals firing.' }, { code: 'COLD_L5', reason: 'Last-5 average below the line.' }, ], reasoning: { summary: 'Brunson averaging 28.4 last 5; weak NYK defense; rested.', steps: { season_avg: { value: 26.1 }, final_grade: 'A' }, }, ...overrides, }; } describe('applyTierGating — free tier locks the explanation surface', () => { test('keeps grade + confidence + edge_pct (the hook)', () => { const gated = applyTierGating(sampleResult(), 'free'); expect(gated.grade).toBe('A'); expect(gated.confidence).toBe(78); expect(gated.edge_pct).toBe(6.2); expect(gated.player).toBe('Jalen Brunson'); expect(gated.stat_type).toBe('points'); expect(gated.line).toBe(26.5); expect(gated.direction).toBe('over'); }); test('redacts reasoning.summary + steps + marks locked', () => { const gated = applyTierGating(sampleResult(), 'free'); expect(gated.reasoning.summary).toBe(__internals.LOCKED_REASONING_SUMMARY); expect(gated.reasoning.steps).toBeNull(); expect(gated.reasoning.locked).toBe(true); }); test('redacts kill condition reasons but keeps codes for badge rendering', () => { const gated = applyTierGating(sampleResult(), 'free'); expect(gated.kill_conditions_triggered).toHaveLength(2); for (const kc of gated.kill_conditions_triggered) { expect(kc.code).toBeDefined(); expect(kc.reason).toBe(__internals.LOCKED_KILL_REASON); expect(kc.locked).toBe(true); } }); test('tags response with tier_gated + upgrade_hint', () => { const gated = applyTierGating(sampleResult(), 'free'); expect(gated.tier_gated).toBe(true); expect(typeof gated.upgrade_hint).toBe('string'); }); test('does not mutate the input result', () => { const original = sampleResult(); applyTierGating(original, 'free'); expect(original.reasoning.summary).toContain('Brunson averaging'); expect(original.kill_conditions_triggered[0].reason).toContain('Multiple trap'); }); }); describe('applyTierGating — paid tiers pass through unchanged', () => { test('analyst sees full reasoning', () => { const r = sampleResult(); const out = applyTierGating(r, 'analyst'); expect(out.reasoning.summary).toContain('Brunson averaging'); expect(out.reasoning.locked).toBeUndefined(); expect(out.tier_gated).toBeUndefined(); expect(out.kill_conditions_triggered[0].reason).toContain('Multiple trap'); }); test('desk sees full reasoning', () => { const out = applyTierGating(sampleResult(), 'desk'); expect(out.reasoning.summary).toContain('Brunson averaging'); expect(out.tier_gated).toBeUndefined(); }); }); describe('applyTierGating — edge cases', () => { test('null result returns null', () => { expect(applyTierGating(null, 'free')).toBeNull(); expect(applyTierGating(undefined, 'free')).toBeUndefined(); }); test('unknown tier name → treated as free', () => { const out = applyTierGating(sampleResult(), 'pirate'); expect(out.reasoning.locked).toBe(true); }); test('result with no kill_conditions_triggered still produces a clean response', () => { const noKills = { ...sampleResult(), kill_conditions_triggered: [] }; const out = applyTierGating(noKills, 'free'); expect(out.kill_conditions_triggered).toEqual([]); expect(out.reasoning.locked).toBe(true); }); test('result with malformed reasoning still produces a locked placeholder', () => { const out = applyTierGating({ ...sampleResult(), reasoning: null }, 'free'); expect(out.reasoning.summary).toBe(__internals.LOCKED_REASONING_SUMMARY); expect(out.reasoning.locked).toBe(true); }); });