'use strict'; // ── Regression tests for the "revoke plan access, no refund" feature ──────── // revokePlanSubscriberAccess() must replicate revokeTier's single-record // "auto-downgrade to Free only if the user holds no other active tier" rule // (controllers/admin/tiers.controller.js) instead of blanket-downgrading // everyone, and must fire exactly one in-app notification batch plus one // email per affected user. jest.mock('../../models/tiers/user_tiers.mdl', () => ({ findAll: jest.fn(), update: jest.fn(), count: jest.fn(), create: jest.fn(), })); jest.mock('../../models/users/users.mdl', () => ({ findAll: jest.fn() })); jest.mock('../../models/notifications/user_notification.mdl', () => ({ bulkCreate: jest.fn() })); jest.mock('../../services/email.service', () => ({ sendEmail: jest.fn().mockResolvedValue(true) })); const mdl_UserTiers = require('../../models/tiers/user_tiers.mdl'); const mdl_Users = require('../../models/users/users.mdl'); const UserNotification = require('../../models/notifications/user_notification.mdl'); const { sendEmail } = require('../../services/email.service'); const { revokePlanSubscriberAccess } = require('../../services/planAccess.service'); function makePlan(overrides = {}) { return { plan_id: 10, label: 'Premium – 1 Month', ...overrides }; } beforeEach(() => jest.clearAllMocks()); describe('revokePlanSubscriberAccess()', () => { test('no active subscribers → no-op, returns zero count', async () => { mdl_UserTiers.findAll.mockResolvedValue([]); const result = await revokePlanSubscriberAccess(makePlan(), 99); expect(result).toEqual({ revoked_user_count: 0 }); expect(mdl_UserTiers.update).not.toHaveBeenCalled(); expect(UserNotification.bulkCreate).not.toHaveBeenCalled(); expect(sendEmail).not.toHaveBeenCalled(); }); test('a user with ONLY this plan active gets auto-downgraded to Free', async () => { mdl_UserTiers.findAll.mockResolvedValue([{ tier_id: 1, user_id: 5 }]); mdl_UserTiers.count.mockResolvedValue(0); // no other active tier remains mdl_Users.findAll.mockResolvedValue([{ user_id: 5, email: 'a@b.com', personal_info: { name: { full_name: 'Ana' } } }]); const result = await revokePlanSubscriberAccess(makePlan(), 99); expect(mdl_UserTiers.update).toHaveBeenCalledWith( expect.objectContaining({ status: 'revoked', revoked_by: 99 }), { where: { tier_id: [1] } } ); expect(mdl_UserTiers.create).toHaveBeenCalledWith( expect.objectContaining({ user_id: '5', tier: 'free', status: 'active' }) ); expect(result).toEqual({ revoked_user_count: 1 }); }); test('a user with ANOTHER concurrently-active plan does NOT get downgraded', async () => { mdl_UserTiers.findAll.mockResolvedValue([{ tier_id: 2, user_id: 6 }]); mdl_UserTiers.count.mockResolvedValue(1); // still holds a different active plan mdl_Users.findAll.mockResolvedValue([{ user_id: 6, email: 'c@d.com', personal_info: {} }]); await revokePlanSubscriberAccess(makePlan(), 99); expect(mdl_UserTiers.create).not.toHaveBeenCalled(); }); test('fires exactly one notification batch and one email per affected user', async () => { mdl_UserTiers.findAll.mockResolvedValue([ { tier_id: 1, user_id: 5 }, { tier_id: 2, user_id: 6 }, ]); mdl_UserTiers.count.mockResolvedValue(1); mdl_Users.findAll.mockResolvedValue([ { user_id: 5, email: 'a@b.com', personal_info: {} }, { user_id: 6, email: 'c@d.com', personal_info: {} }, ]); const result = await revokePlanSubscriberAccess(makePlan(), 99); expect(UserNotification.bulkCreate).toHaveBeenCalledTimes(1); expect(UserNotification.bulkCreate.mock.calls[0][0]).toHaveLength(2); expect(sendEmail).toHaveBeenCalledTimes(2); expect(sendEmail).toHaveBeenCalledWith(expect.objectContaining({ to: 'a@b.com', type: 'TIER_ACCESS_REVOKED' })); expect(result).toEqual({ revoked_user_count: 2 }); }); });