Skip to content

Commit d605208

Browse files
authored
Refetch plans if workspace tariff is missing from cache (#496)
* Refetch plans if workspace tariff is missing from cache Introduces logic to refetch tariff plans from the database if a workspace references a plan not present in the cached list. Adds tests to verify that plans are refetched when needed and that an error is thrown if the plan is still missing after refetching. * Refactor plan fetching and lookup methods Moved helper methods below the public ones to satisfy @typescript-eslint/member-ordering. Lint should now pass for those ordering errors. * Update workspace block status in database Added database updates to set the isBlocked flag and blockedDate when blocking a workspace, and to unset isBlocked when unblocking. This ensures the workspace's block status is accurately reflected in the database. * Remove unused plan lookup methods Deleted the private methods findPlanById and getWorkspacePlan from PaymasterWorker as they are no longer used in the codebase. * Update index.ts * Update index.ts
1 parent 52519c6 commit d605208

2 files changed

Lines changed: 148 additions & 10 deletions

File tree

workers/paymaster/src/index.ts

Lines changed: 51 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -53,10 +53,15 @@ export default class PaymasterWorker extends Worker {
5353
*/
5454
private workspaces: Collection<WorkspaceDBScheme>;
5555

56+
/**
57+
* Collection with plans
58+
*/
59+
private plansCollection: Collection<PlanDBScheme>;
60+
5661
/**
5762
* List of tariff plans
5863
*/
59-
private plans: PlanDBScheme[];
64+
private plans: PlanDBScheme[] = [];
6065

6166
/**
6267
* Check if today is a payday for passed timestamp
@@ -111,13 +116,9 @@ export default class PaymasterWorker extends Worker {
111116
const connection = await this.db.connect();
112117

113118
this.workspaces = connection.collection('workspaces');
114-
const plansCollection = connection.collection<PlanDBScheme>('plans');
115-
116-
this.plans = await plansCollection.find({}).toArray();
119+
this.plansCollection = connection.collection<PlanDBScheme>('plans');
117120

118-
if (this.plans.length === 0) {
119-
throw new Error('Please add tariff plans to the database');
120-
}
121+
await this.fetchPlans();
121122

122123
await super.start();
123124
}
@@ -142,6 +143,48 @@ export default class PaymasterWorker extends Worker {
142143
}
143144
}
144145

146+
/**
147+
* Fetches tariff plans from database and keeps them cached
148+
*/
149+
private async fetchPlans(): Promise<void> {
150+
if (!this.plansCollection) {
151+
throw new Error('Plans collection is not initialized');
152+
}
153+
154+
this.plans = await this.plansCollection.find({}).toArray();
155+
156+
if (this.plans.length === 0) {
157+
throw new Error('Please add tariff plans to the database');
158+
}
159+
}
160+
161+
/**
162+
* Finds plan by id from cached plans
163+
*/
164+
private findPlanById(planId: WorkspaceDBScheme['tariffPlanId']): PlanDBScheme | undefined {
165+
return this.plans.find((plan) => plan._id.toString() === planId.toString());
166+
}
167+
168+
/**
169+
* Returns workspace plan, refreshes cache when plan is missing
170+
*/
171+
private async getWorkspacePlan(workspace: WorkspaceDBScheme): Promise<PlanDBScheme> {
172+
let currentPlan = this.findPlanById(workspace.tariffPlanId);
173+
174+
if (currentPlan) {
175+
return currentPlan;
176+
}
177+
178+
await this.fetchPlans();
179+
currentPlan = this.findPlanById(workspace.tariffPlanId);
180+
181+
if (!currentPlan) {
182+
throw new Error(`[Paymaster] Tariff plan ${workspace.tariffPlanId.toString()} not found for workspace ${workspace._id.toString()} (${workspace.name})`);
183+
}
184+
185+
return currentPlan;
186+
}
187+
145188
/**
146189
* WorkspaceSubscriptionCheckEvent event handler
147190
*
@@ -180,9 +223,7 @@ export default class PaymasterWorker extends Worker {
180223
*/
181224
private async processWorkspaceSubscriptionCheck(workspace: WorkspaceDBScheme): Promise<[WorkspaceDBScheme, boolean]> {
182225
const date = new Date();
183-
const currentPlan = this.plans.find(
184-
(plan) => plan._id.toString() === workspace.tariffPlanId.toString()
185-
);
226+
const currentPlan = await this.getWorkspacePlan(workspace);
186227

187228
/** Define readable values */
188229

workers/paymaster/tests/index.test.ts

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -695,6 +695,103 @@ describe('PaymasterWorker', () => {
695695
MockDate.reset();
696696
});
697697

698+
test('Should refetch plans if workspace tariff appears after worker start', async () => {
699+
/**
700+
* Arrange
701+
*/
702+
const currentDate = new Date('2005-12-22');
703+
const cachedPlan = createPlanMock({
704+
monthlyCharge: 100,
705+
isDefault: true,
706+
});
707+
708+
await tariffCollection.insertOne(cachedPlan);
709+
710+
const worker = new PaymasterWorker();
711+
712+
await worker.start();
713+
714+
const newPlan = createPlanMock({
715+
monthlyCharge: 0,
716+
isDefault: false,
717+
});
718+
719+
await tariffCollection.insertOne(newPlan);
720+
721+
const workspace = createWorkspaceMock({
722+
plan: newPlan,
723+
subscriptionId: null,
724+
lastChargeDate: new Date('2005-11-22'),
725+
isBlocked: true,
726+
billingPeriodEventsCount: 10,
727+
});
728+
729+
await workspacesCollection.insertOne(workspace);
730+
731+
MockDate.set(currentDate);
732+
733+
/**
734+
* Act
735+
*/
736+
await expect(worker.handle(WORKSPACE_SUBSCRIPTION_CHECK)).resolves.not.toThrow();
737+
738+
/**
739+
* Assert
740+
*/
741+
const updatedWorkspace = await workspacesCollection.findOne({ _id: workspace._id });
742+
743+
expect(updatedWorkspace.lastChargeDate).toEqual(currentDate);
744+
expect(updatedWorkspace.billingPeriodEventsCount).toEqual(0);
745+
expect(updatedWorkspace.isBlocked).toEqual(false);
746+
747+
await worker.finish();
748+
MockDate.reset();
749+
});
750+
751+
test('Should throw an error when workspace plan is still missing after refetch', async () => {
752+
/**
753+
* Arrange
754+
*/
755+
const currentDate = new Date('2005-12-22');
756+
const cachedPlan = createPlanMock({
757+
monthlyCharge: 100,
758+
isDefault: true,
759+
});
760+
761+
await tariffCollection.insertOne(cachedPlan);
762+
763+
const worker = new PaymasterWorker();
764+
765+
await worker.start();
766+
767+
const missingPlan = createPlanMock({
768+
monthlyCharge: 50,
769+
isDefault: false,
770+
});
771+
772+
const workspace = createWorkspaceMock({
773+
plan: missingPlan,
774+
subscriptionId: null,
775+
lastChargeDate: new Date('2005-11-22'),
776+
isBlocked: false,
777+
billingPeriodEventsCount: 10,
778+
});
779+
780+
await workspacesCollection.insertOne(workspace);
781+
782+
MockDate.set(currentDate);
783+
784+
/**
785+
* Act + Assert
786+
*/
787+
await expect(worker.handle(WORKSPACE_SUBSCRIPTION_CHECK)).rejects.toThrow(
788+
`[Paymaster] Tariff plan ${missingPlan._id.toString()} not found for workspace ${workspace._id.toString()} (${workspace.name})`
789+
);
790+
791+
await worker.finish();
792+
MockDate.reset();
793+
});
794+
698795
afterAll(async () => {
699796
await connection.close();
700797
MockDate.reset();

0 commit comments

Comments
 (0)