Files
starr-philproperties/tests/services/planAccess.service.test.js
T
2026-08-05 04:32:43 +08:00

92 lines
3.9 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
'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 });
});
});