Skip to content

Commit feb2d38

Browse files
committed
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.
1 parent 52519c6 commit feb2d38

2 files changed

Lines changed: 145 additions & 7 deletions

File tree

workers/paymaster/src/index.ts

Lines changed: 48 additions & 7 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,15 +116,53 @@ 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');
119+
this.plansCollection = connection.collection<PlanDBScheme>('plans');
115120

116-
this.plans = await plansCollection.find({}).toArray();
121+
await this.fetchPlans();
122+
123+
await super.start();
124+
}
125+
126+
/**
127+
* Fetches tariff plans from database and keeps them cached
128+
*/
129+
private async fetchPlans(): Promise<void> {
130+
if (!this.plansCollection) {
131+
throw new Error('Plans collection is not initialized');
132+
}
133+
134+
this.plans = await this.plansCollection.find({}).toArray();
117135

118136
if (this.plans.length === 0) {
119137
throw new Error('Please add tariff plans to the database');
120138
}
139+
}
121140

122-
await super.start();
141+
/**
142+
* Finds plan by id from cached plans
143+
*/
144+
private findPlanById(planId: WorkspaceDBScheme['tariffPlanId']): PlanDBScheme | undefined {
145+
return this.plans.find((plan) => plan._id.toString() === planId.toString());
146+
}
147+
148+
/**
149+
* Returns workspace plan, refreshes cache when plan is missing
150+
*/
151+
private async getWorkspacePlan(workspace: WorkspaceDBScheme): Promise<PlanDBScheme> {
152+
let currentPlan = this.findPlanById(workspace.tariffPlanId);
153+
154+
if (currentPlan) {
155+
return currentPlan;
156+
}
157+
158+
await this.fetchPlans();
159+
currentPlan = this.findPlanById(workspace.tariffPlanId);
160+
161+
if (!currentPlan) {
162+
throw new Error(`[Paymaster] Tariff plan ${workspace.tariffPlanId.toString()} not found for workspace ${workspace._id.toString()} (${workspace.name})`);
163+
}
164+
165+
return currentPlan;
123166
}
124167

125168
/**
@@ -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)