109 lines
4.0 KiB
JavaScript
109 lines
4.0 KiB
JavaScript
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);
|
|
});
|
|
});
|