Skip to content

Commit 3e6beda

Browse files
test: add more tests
1 parent 100afa8 commit 3e6beda

File tree

4 files changed

+53
-37
lines changed

4 files changed

+53
-37
lines changed

Diff for: src/lib/credits.ts

+4-13
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ const ER_CONSTRAINT_FAILED_CODE = 4025;
77
export class Credits {
88
constructor (private readonly sql: Knex) {}
99

10-
async consume (userId: string, credits: number): Promise<{ isConsumed: boolean, remainingCredits: number }> {
10+
async consume (userId: string, credits: number): Promise<{ isConsumed: boolean, remainingCredits?: number }> {
1111
try {
1212
const result = await this.sql.raw<[[{amount: number | null}]]>(`
1313
INSERT INTO ?? (user_id, amount)
@@ -20,27 +20,18 @@ export class Credits {
2020
const remainingCredits = result[0]?.[0]?.amount;
2121

2222
if (remainingCredits || remainingCredits === 0) {
23-
return this.returnSuccess(remainingCredits);
23+
return { isConsumed: true, remainingCredits };
2424
}
2525

26-
return this.returnFail(userId);
26+
return { isConsumed: false };
2727
} catch (error) {
2828
if (error && (error as Error & {errno?: number}).errno === ER_CONSTRAINT_FAILED_CODE) {
29-
return this.returnFail(userId);
29+
return { isConsumed: false };
3030
}
3131

3232
throw error;
3333
}
3434
}
35-
36-
returnSuccess (remainingCredits: number) {
37-
return { isConsumed: true, remainingCredits };
38-
}
39-
40-
async returnFail (userId: string) {
41-
const result = await this.sql(CREDITS_TABLE).where({ user_id: userId }).select<[{amount: number}]>('amount');
42-
return { isConsumed: false, remainingCredits: result[0]?.amount || 0 };
43-
}
4435
}
4536

4637
export const credits = new Credits(client);

Diff for: src/lib/rate-limiter.ts

+4-9
Original file line numberDiff line numberDiff line change
@@ -44,16 +44,14 @@ export const rateLimit = async (ctx: ExtendedContext, numberOfProbes: number) =>
4444
} catch (error) {
4545
if (error instanceof RateLimiterRes) {
4646
if (ctx.state.userId) {
47-
const { isConsumed, consumedCredits, remainingCredits, pointsToReward } = await consumeCredits(ctx, error, numberOfProbes);
47+
const { isConsumed, consumedCredits, remainingCredits, pointsToReward } = await consumeCredits(ctx.state.userId, error, numberOfProbes);
4848

4949
if (isConsumed) {
5050
const result = await rateLimiter.reward(id, pointsToReward);
51-
setCreditsHeaders(ctx, consumedCredits, remainingCredits);
51+
setCreditsHeaders(ctx, consumedCredits!, remainingCredits!);
5252
setRateLimitHeaders(ctx, result, rateLimiter);
5353
return;
5454
}
55-
56-
setCreditsHeaders(ctx, consumedCredits, remainingCredits);
5755
}
5856

5957
const result = await rateLimiter.reward(id, numberOfProbes);
@@ -65,12 +63,12 @@ export const rateLimit = async (ctx: ExtendedContext, numberOfProbes: number) =>
6563
}
6664
};
6765

68-
const consumeCredits = async (ctx: ExtendedContext, rateLimiterRes: RateLimiterRes, numberOfProbes: number) => {
66+
const consumeCredits = async (userId: string, rateLimiterRes: RateLimiterRes, numberOfProbes: number) => {
6967
const freePoints = config.get<number>('measurement.authenticatedRateLimit');
7068
const alreadyUsedPoints = rateLimiterRes.consumedPoints - numberOfProbes;
7169
const remainingFreePoints = freePoints - alreadyUsedPoints;
7270
const requiredPoints = numberOfProbes - remainingFreePoints;
73-
const { isConsumed, remainingCredits } = await credits.consume(ctx.state.userId!, requiredPoints);
71+
const { isConsumed, remainingCredits } = await credits.consume(userId, requiredPoints);
7472

7573
if (isConsumed) {
7674
return {
@@ -83,9 +81,6 @@ const consumeCredits = async (ctx: ExtendedContext, rateLimiterRes: RateLimiterR
8381

8482
return {
8583
isConsumed: false,
86-
consumedCredits: 0,
87-
remainingCredits,
88-
pointsToReward: 0,
8984
};
9085
};
9186

Diff for: test/tests/integration/ratelimit.test.ts

+43-11
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,7 @@ describe('rate limiter', () => {
145145
target: 'jsdelivr.com',
146146
}).expect(202) as Response;
147147

148-
expect(Number(response.headers['x-ratelimit-remaining'])).to.equal(99999);
148+
expect(response.headers['x-ratelimit-remaining']).to.equal('99999');
149149
});
150150

151151
it('should fail (limit reached)', async () => {
@@ -156,7 +156,7 @@ describe('rate limiter', () => {
156156
target: 'jsdelivr.com',
157157
}).expect(429) as Response;
158158

159-
expect(Number(response.headers['x-ratelimit-remaining'])).to.equal(0);
159+
expect(response.headers['x-ratelimit-remaining']).to.equal('0');
160160
});
161161

162162
it('should consume all points successfully or none at all (cost > remaining > 0)', async () => {
@@ -168,7 +168,7 @@ describe('rate limiter', () => {
168168
limit: 2,
169169
}).expect(429) as Response;
170170

171-
expect(Number(response.headers['x-ratelimit-remaining'])).to.equal(1);
171+
expect(response.headers['x-ratelimit-remaining']).to.equal('1');
172172
});
173173
});
174174

@@ -183,7 +183,7 @@ describe('rate limiter', () => {
183183
target: 'jsdelivr.com',
184184
}).expect(202) as Response;
185185

186-
expect(Number(response.headers['x-ratelimit-remaining'])).to.equal(249);
186+
expect(response.headers['x-ratelimit-remaining']).to.equal('249');
187187
});
188188

189189
it('should fail (limit reached)', async () => {
@@ -196,7 +196,7 @@ describe('rate limiter', () => {
196196
target: 'jsdelivr.com',
197197
}).expect(429) as Response;
198198

199-
expect(Number(response.headers['x-ratelimit-remaining'])).to.equal(0);
199+
expect(response.headers['x-ratelimit-remaining']).to.equal('0');
200200
});
201201

202202
it('should consume all points successfully or none at all (cost > remaining > 0)', async () => {
@@ -210,7 +210,7 @@ describe('rate limiter', () => {
210210
limit: 2,
211211
}).expect(429) as Response;
212212

213-
expect(Number(response.headers['x-ratelimit-remaining'])).to.equal(1);
213+
expect(response.headers['x-ratelimit-remaining']).to.equal('1');
214214
});
215215
});
216216

@@ -235,7 +235,9 @@ describe('rate limiter', () => {
235235
limit: 2,
236236
}).expect(202) as Response;
237237

238-
expect(Number(response.headers['x-ratelimit-remaining'])).to.equal(248);
238+
expect(response.headers['x-ratelimit-remaining']).to.equal('248');
239+
expect(response.headers['x-credits-cost']).to.not.exist;
240+
expect(response.headers['x-credits-remaining']).to.not.exist;
239241
const [{ amount }] = await client(CREDITS_TABLE).select('amount').where({ user_id: '89da69bd-a236-4ab7-9c5d-b5f52ce09959' });
240242
expect(amount).to.equal(10);
241243
});
@@ -251,7 +253,9 @@ describe('rate limiter', () => {
251253
limit: 2,
252254
}).expect(202) as Response;
253255

254-
expect(Number(response.headers['x-ratelimit-remaining'])).to.equal(0);
256+
expect(response.headers['x-ratelimit-remaining']).to.equal('0');
257+
expect(response.headers['x-credits-cost']).to.equal('2');
258+
expect((response.headers['x-credits-remaining'])).to.equal('8');
255259
const [{ amount }] = await client(CREDITS_TABLE).select('amount').where({ user_id: '89da69bd-a236-4ab7-9c5d-b5f52ce09959' });
256260
expect(amount).to.equal(8);
257261
});
@@ -267,7 +271,9 @@ describe('rate limiter', () => {
267271
limit: 2,
268272
}).expect(202) as Response;
269273

270-
expect(Number(response.headers['x-ratelimit-remaining'])).to.equal(0);
274+
expect(response.headers['x-ratelimit-remaining']).to.equal('0');
275+
expect(response.headers['x-credits-cost']).to.equal('1');
276+
expect((response.headers['x-credits-remaining'])).to.equal('9');
271277
const [{ amount }] = await client(CREDITS_TABLE).select('amount').where({ user_id: '89da69bd-a236-4ab7-9c5d-b5f52ce09959' });
272278
expect(amount).to.equal(9);
273279
});
@@ -289,7 +295,9 @@ describe('rate limiter', () => {
289295
limit: 2,
290296
}).expect(429) as Response;
291297

292-
expect(Number(response.headers['x-ratelimit-remaining'])).to.equal(0);
298+
expect(response.headers['x-ratelimit-remaining']).to.equal('0');
299+
expect(response.headers['x-credits-cost']).to.not.exist;
300+
expect(response.headers['x-credits-remaining']).to.not.exist;
293301
const [{ amount }] = await client(CREDITS_TABLE).select('amount').where({ user_id: '89da69bd-a236-4ab7-9c5d-b5f52ce09959' });
294302
expect(amount).to.equal(1);
295303
});
@@ -311,9 +319,33 @@ describe('rate limiter', () => {
311319
limit: 2,
312320
}).expect(429) as Response;
313321

314-
expect(Number(response.headers['x-ratelimit-remaining'])).to.equal(1);
322+
expect(response.headers['x-ratelimit-remaining']).to.equal('1');
323+
expect(response.headers['x-credits-cost']).to.not.exist;
324+
expect(response.headers['x-credits-remaining']).to.not.exist;
315325
const [{ amount }] = await client(CREDITS_TABLE).select('amount').where({ user_id: '89da69bd-a236-4ab7-9c5d-b5f52ce09959' });
316326
expect(amount).to.equal(0);
317327
});
328+
329+
it('should work fine if there is no credits row for that user', async () => {
330+
await authenticatedRateLimiter.set('89da69bd-a236-4ab7-9c5d-b5f52ce09959', 250, 0);
331+
332+
await client(CREDITS_TABLE).where({
333+
user_id: '89da69bd-a236-4ab7-9c5d-b5f52ce09959',
334+
}).delete();
335+
336+
const response = await requestAgent.post('/v1/measurements')
337+
.set('Authorization', 'Bearer v2lUHEVLtVSskaRKDBabpyp4AkzdMnob')
338+
.send({
339+
type: 'ping',
340+
target: 'jsdelivr.com',
341+
limit: 2,
342+
}).expect(429) as Response;
343+
344+
expect(response.headers['x-ratelimit-remaining']).to.equal('0');
345+
expect(response.headers['x-credits-cost']).to.not.exist;
346+
expect(response.headers['x-credits-remaining']).to.not.exist;
347+
const [{ amount }] = await client(CREDITS_TABLE).select('amount').where({ user_id: '89da69bd-a236-4ab7-9c5d-b5f52ce09959' });
348+
expect(amount).to.equal(null);
349+
});
318350
});
319351
});

Diff for: test/tests/unit/credits.test.ts

+2-4
Original file line numberDiff line numberDiff line change
@@ -26,20 +26,18 @@ describe('Credits', () => {
2626

2727
it(`should return false if row wasn't updated`, async () => {
2828
sqlStub.raw.resolves([ [{ amount: null }] ]);
29-
selectStub.resolves([{ amount: null }]);
3029
const credits = new Credits(sqlStub as unknown as Knex);
3130
const result = await credits.consume('userId', 10);
32-
expect(result).to.deep.equal({ isConsumed: false, remainingCredits: 0 });
31+
expect(result).to.deep.equal({ isConsumed: false });
3332
});
3433

3534
it(`should return false if update throws ER_CONSTRAINT_FAILED_CODE`, async () => {
3635
const error: Error & {errno?: number} = new Error('constraint');
37-
selectStub.resolves([{ amount: 5 }]);
3836
error.errno = 4025;
3937
sqlStub.raw.rejects(error);
4038
const credits = new Credits(sqlStub as unknown as Knex);
4139
const result = await credits.consume('userId', 10);
42-
expect(result).to.deep.equal({ isConsumed: false, remainingCredits: 5 });
40+
expect(result).to.deep.equal({ isConsumed: false });
4341
});
4442

4543
it(`should throw if update throws other error`, async () => {

0 commit comments

Comments
 (0)