Signed-off-by: Kenneth Obsequio <k80308392@gmail.com>
This commit is contained in:
2026-08-13 19:58:52 +08:00
parent 9018d6d158
commit 2e9c2ad43f
23 changed files with 954 additions and 287 deletions
+52 -16
View File
@@ -10,8 +10,7 @@
jest.mock('../../models/tiers/user_tiers.mdl', () => ({
findAll: jest.fn(),
update: jest.fn(),
count: jest.fn(),
create: jest.fn(),
bulkCreate: jest.fn(),
}));
jest.mock('../../models/users/users.mdl', () => ({ findAll: jest.fn() }));
jest.mock('../../models/notifications/user_notification.mdl', () => ({ bulkCreate: jest.fn() }));
@@ -22,7 +21,7 @@ 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');
const { revokePlanSubscriberAccess, revokePlanSubscriberAccessBulk } = require('../../services/planAccess.service');
function makePlan(overrides = {}) {
return { plan_id: 10, label: 'Premium – 1 Month', ...overrides };
@@ -43,8 +42,9 @@ describe('revokePlanSubscriberAccess()', () => {
});
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_UserTiers.findAll
.mockResolvedValueOnce([{ tier_id: 1, user_id: 5, plan_id: 10 }]) // active rows for the plan
.mockResolvedValueOnce([]); // grouped "still active elsewhere" check — none
mdl_Users.findAll.mockResolvedValue([{ user_id: 5, email: 'a@b.com', personal_info: { name: { full_name: 'Ana' } } }]);
const result = await revokePlanSubscriberAccess(makePlan(), 99);
@@ -53,28 +53,30 @@ describe('revokePlanSubscriberAccess()', () => {
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(mdl_UserTiers.bulkCreate).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_UserTiers.findAll
.mockResolvedValueOnce([{ tier_id: 2, user_id: 6, plan_id: 10 }])
.mockResolvedValueOnce([{ user_id: 6 }]); // still holds a different active tier
mdl_Users.findAll.mockResolvedValue([{ user_id: 6, email: 'c@d.com', personal_info: {} }]);
await revokePlanSubscriberAccess(makePlan(), 99);
expect(mdl_UserTiers.create).not.toHaveBeenCalled();
expect(mdl_UserTiers.bulkCreate).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_UserTiers.findAll
.mockResolvedValueOnce([
{ tier_id: 1, user_id: 5, plan_id: 10 },
{ tier_id: 2, user_id: 6, plan_id: 10 },
])
.mockResolvedValueOnce([{ user_id: 5 }, { user_id: 6 }]);
mdl_Users.findAll.mockResolvedValue([
{ user_id: 5, email: 'a@b.com', personal_info: {} },
{ user_id: 6, email: 'c@d.com', personal_info: {} },
@@ -89,3 +91,37 @@ describe('revokePlanSubscriberAccess()', () => {
expect(result).toEqual({ revoked_user_count: 2 });
});
});
describe('revokePlanSubscriberAccessBulk() — N+1 regression', () => {
test('query count stays flat regardless of plan/subscriber count (no per-user or per-plan loop)', async () => {
const plans = [
{ plan_id: 10, label: 'Premium – 1 Month' },
{ plan_id: 11, label: 'Premium – 1 Year' },
{ plan_id: 12, label: 'Basic' },
];
const activeRows = Array.from({ length: 25 }, (_, i) => ({
tier_id: i + 1,
user_id: i + 1,
plan_id: plans[i % plans.length].plan_id,
}));
mdl_UserTiers.findAll
.mockResolvedValueOnce(activeRows) // active rows across all 3 plans
.mockResolvedValueOnce([]); // grouped "still active" check — nobody else active
mdl_Users.findAll.mockResolvedValue(
activeRows.map((r) => ({ user_id: r.user_id, email: `${r.user_id}@x.com`, personal_info: {} })),
);
const result = await revokePlanSubscriberAccessBulk(plans, 99);
// Exactly 2 findAll calls total (active rows + grouped still-active check),
// 1 bulk update, 1 bulk downgrade create, 1 notification bulkCreate —
// no matter how many plans/users were involved.
expect(mdl_UserTiers.findAll).toHaveBeenCalledTimes(2);
expect(mdl_UserTiers.update).toHaveBeenCalledTimes(1);
expect(mdl_UserTiers.bulkCreate).toHaveBeenCalledTimes(1);
expect(mdl_UserTiers.bulkCreate.mock.calls[0][0]).toHaveLength(25);
expect(UserNotification.bulkCreate).toHaveBeenCalledTimes(1);
expect(UserNotification.bulkCreate.mock.calls[0][0]).toHaveLength(25);
expect(result).toEqual({ revoked_user_count: 25 });
});
});