const { TIERS, VALID_TIERS, getTier, getScanLimit, canAccess } = require('../../src/config/tiers'); describe('tiers config', () => { test('VALID_TIERS includes free, analyst, desk', () => { expect(VALID_TIERS).toEqual(['free', 'analyst', 'desk']); }); test('api_access is FALSE on every tier (non-negotiable)', () => { for (const tierName of VALID_TIERS) { expect(TIERS[tierName].api_access).toBe(false); } }); test('free tier has the most restrictive defaults', () => { const free = TIERS.free; expect(free.scans_per_day).toBe(3); expect(free.reasoning_visible).toBe(false); expect(free.kill_conditions_detail).toBe(false); expect(free.alerts).toBe(false); expect(free.portfolio).toBe(false); expect(free.engine2).toBe(false); // Grade is the hook — it must be visible even on free. expect(free.grade_visible).toBe(true); // stat_dashboard is the entry product — visible to everyone. expect(free.stat_dashboard).toBe(true); }); test('analyst opens reasoning + kill conditions + alerts', () => { const a = TIERS.analyst; expect(a.scans_per_day).toBe(15); expect(a.reasoning_visible).toBe(true); expect(a.kill_conditions_detail).toBe(true); expect(a.alerts).toBe(true); // Still no portfolio or engine2 — those are Desk perks. expect(a.portfolio).toBe(false); expect(a.engine2).toBe(false); }); test('desk unlocks everything except api_access', () => { const d = TIERS.desk; expect(d.scans_per_day).toBe(Infinity); expect(d.reasoning_visible).toBe(true); expect(d.kill_conditions_detail).toBe(true); expect(d.alerts).toBe(true); expect(d.portfolio).toBe(true); expect(d.engine2).toBe(true); expect(d.api_access).toBe(false); }); test('getTier falls back to free for unknown tier names', () => { expect(getTier('mystery')).toBe(TIERS.free); expect(getTier(null)).toBe(TIERS.free); expect(getTier(undefined)).toBe(TIERS.free); expect(getTier('')).toBe(TIERS.free); }); test('getScanLimit returns the per-tier daily cap', () => { expect(getScanLimit('free')).toBe(3); expect(getScanLimit('analyst')).toBe(15); expect(getScanLimit('desk')).toBe(Infinity); }); test('canAccess returns the right boolean per (tier, feature) pair', () => { expect(canAccess('free', 'grade_visible')).toBe(true); expect(canAccess('free', 'reasoning_visible')).toBe(false); expect(canAccess('free', 'api_access')).toBe(false); expect(canAccess('analyst', 'reasoning_visible')).toBe(true); expect(canAccess('analyst', 'engine2')).toBe(false); expect(canAccess('desk', 'engine2')).toBe(true); expect(canAccess('desk', 'api_access')).toBe(false); }); test('canAccess returns false for unknown features (defensive)', () => { expect(canAccess('desk', 'no_such_feature')).toBe(false); }); test('TIERS objects are frozen — nobody can mutate the matrix at runtime', () => { expect(Object.isFrozen(TIERS)).toBe(true); expect(Object.isFrozen(TIERS.free)).toBe(true); expect(Object.isFrozen(TIERS.analyst)).toBe(true); expect(Object.isFrozen(TIERS.desk)).toBe(true); }); });