'use strict'; // ── Regression tests for item-specific entitlement (Tier Plans v2) ────────── // Before this change, canAccessCourse/canAccessUnit/canAccessLesson unlocked // content purely off tier RANK (any active Premium purchase unlocked every // premium-tagged course). Now a Tier Plan purchase only grants the EXACT // items its bundle contained at purchase time (recorded in // user_tier_grants) — plain tier rank alone must no longer be sufficient. // These tests exercise the real controller/model objects (Sequelize doesn't // connect until a query actually runs) and stub only the specific static // methods each scenario touches, via jest.spyOn. const { Course } = require('../../models/courses/courses.associations'); const { mdl_UserTierGrants } = require('../../models/tiers/tier.associations'); const mdl_UserTiers = require('../../models/tiers/user_tiers.mdl'); const mdl_TierCategories = require('../../models/tiers/tier_categories.mdl'); const { mdl_UserGroupMembers } = require('../../models/users/user_groups.mdl'); const mdl_Product = require('../../models/courses/products.mdl'); const mdl_CoursePurchase = require('../../models/courses/course_purchases.mdl'); const { canAccessCourse } = require('../../controllers/client/courses.controller'); function mockActiveTierUnused() { jest.spyOn(mdl_UserTiers, 'findAll').mockResolvedValue([]); jest.spyOn(mdl_TierCategories, 'findAll').mockResolvedValue([]); jest.spyOn(mdl_UserGroupMembers, 'findAll').mockResolvedValue([]); } afterEach(() => jest.restoreAllMocks()); describe('canAccessCourse() — item-specific entitlement', () => { test('free/ungated course is always accessible, no grant lookup needed', async () => { jest.spyOn(Course, 'findOne').mockResolvedValue({ subscription: 'free', status: 'published' }); const grantSpy = jest.spyOn(mdl_UserTierGrants, 'findOne'); const result = await canAccessCourse(1, 100); expect(result).toBe(true); expect(grantSpy).not.toHaveBeenCalled(); }); test('unpublished course is never accessible regardless of entitlement', async () => { jest.spyOn(Course, 'findOne').mockResolvedValue({ subscription: 'premium', status: 'draft' }); const result = await canAccessCourse(1, 100); expect(result).toBe(false); }); test('premium course with no grant, no purchase, no active tier → denied (tier rank alone no longer grants access)', async () => { jest.spyOn(Course, 'findOne').mockResolvedValue({ subscription: 'premium', status: 'published' }); jest.spyOn(mdl_UserTierGrants, 'findOne').mockResolvedValue(null); jest.spyOn(mdl_Product, 'findOne').mockResolvedValue(null); mockActiveTierUnused(); const result = await canAccessCourse(1, 100); expect(result).toBe(false); }); test('premium course unlocked by an item-specific grant for THIS exact course', async () => { jest.spyOn(Course, 'findOne').mockResolvedValue({ subscription: 'premium', status: 'published' }); jest.spyOn(mdl_UserTierGrants, 'findOne').mockResolvedValue({ item_id: 100 }); // active grant found mockActiveTierUnused(); const result = await canAccessCourse(1, 100); expect(result).toBe(true); }); test('premium course still unlocked via a valid individual (a la carte) purchase, unrelated to any tier grant', async () => { jest.spyOn(Course, 'findOne').mockResolvedValue({ subscription: 'premium', status: 'published' }); jest.spyOn(mdl_UserTierGrants, 'findOne').mockResolvedValue(null); jest.spyOn(mdl_Product, 'findOne').mockResolvedValue({ id: 55 }); jest.spyOn(mdl_CoursePurchase, 'findOne').mockResolvedValue({ id: 1 }); mockActiveTierUnused(); const result = await canAccessCourse(1, 100); expect(result).toBe(true); }); });