Skip to content

Commit 58dd6d5

Browse files
fix(billing): address review feedback on requireQuota middleware
- Rename billing.quota.middleware.js → billing.requireQuota.js for naming consistency with billing.requirePlan.js - Add JSDoc to inner middleware function per repository coding guidelines - Document bypass cases (null/undefined limit, Infinity) in outer JSDoc - Use responses.error envelope for 429 quota response instead of raw JSON - Update test assertions to match standardized response envelope
1 parent f17f27e commit 58dd6d5

2 files changed

Lines changed: 29 additions & 17 deletions

File tree

modules/billing/middlewares/billing.quota.middleware.js renamed to modules/billing/middlewares/billing.requireQuota.js

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,25 @@ import responses from '../../../lib/helpers/responses.js';
1212
* Reads limits from `config.billing.quotas[plan][resource][action]` and
1313
* compares against the current month's usage via BillingUsageService.
1414
*
15+
* When no quota is configured for the plan/resource/action combination
16+
* (limit is `null` or `undefined`), the request is allowed through.
17+
* When the limit is `Infinity`, the request is also allowed without
18+
* checking usage (unlimited plan).
19+
*
1520
* Expects `req.organization` to be set by resolveOrganization upstream.
1621
*
1722
* @param {string} resource - The quota resource name (e.g. 'scraps').
1823
* @param {string} action - The quota action name (e.g. 'create', 'execute').
1924
* @returns {Function} Express middleware function.
2025
*/
2126
function requireQuota(resource, action) {
27+
/**
28+
* Enforce quota for a resource/action and block requests when limit is reached.
29+
* @param {import('express').Request} req - Express request object.
30+
* @param {import('express').Response} res - Express response object.
31+
* @param {import('express').NextFunction} next - Express next callback.
32+
* @returns {Promise<void>} Resolves when middleware handling completes.
33+
*/
2234
return async function requireQuotaMiddleware(req, res, next) {
2335
if (!req.organization) {
2436
return responses.error(res, 403, 'Forbidden', 'Organization context is required to check quota')();
@@ -45,7 +57,7 @@ function requireQuota(resource, action) {
4557
const current = usage.counters[counterKey] || 0;
4658

4759
if (current >= limit) {
48-
return res.status(429).json({
60+
return responses.error(res, 429, 'Quota exceeded', 'You have reached the usage limit for this resource')({
4961
type: 'QUOTA_EXCEEDED',
5062
resource,
5163
action,

modules/billing/tests/billing.quota.unit.tests.js

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ describe('requireQuota middleware:', () => {
4848
default: mockConfig,
4949
}));
5050

51-
const mod = await import('../middlewares/billing.quota.middleware.js');
51+
const mod = await import('../middlewares/billing.requireQuota.js');
5252
requireQuota = mod.default;
5353

5454
req = {
@@ -86,11 +86,10 @@ describe('requireQuota middleware:', () => {
8686
expect(next).not.toHaveBeenCalled();
8787
expect(res.status).toHaveBeenCalledWith(429);
8888
expect(res.json).toHaveBeenCalledWith(expect.objectContaining({
89-
type: 'QUOTA_EXCEEDED',
90-
resource: 'scraps',
91-
action: 'create',
92-
limit: 3,
93-
current: 3,
89+
type: 'error',
90+
message: 'Quota exceeded',
91+
code: 429,
92+
status: 429,
9493
}));
9594
});
9695

@@ -113,7 +112,8 @@ describe('requireQuota middleware:', () => {
113112
expect(next).not.toHaveBeenCalled();
114113
expect(res.status).toHaveBeenCalledWith(429);
115114
expect(res.json).toHaveBeenCalledWith(expect.objectContaining({
116-
limit: 3,
115+
type: 'error',
116+
code: 429,
117117
}));
118118
});
119119

@@ -126,7 +126,8 @@ describe('requireQuota middleware:', () => {
126126
expect(next).not.toHaveBeenCalled();
127127
expect(res.status).toHaveBeenCalledWith(429);
128128
expect(res.json).toHaveBeenCalledWith(expect.objectContaining({
129-
limit: 3,
129+
type: 'error',
130+
code: 429,
130131
}));
131132
});
132133

@@ -146,14 +147,13 @@ describe('requireQuota middleware:', () => {
146147
await requireQuota('scraps', 'execute')(req, res, next);
147148

148149
expect(res.status).toHaveBeenCalledWith(429);
149-
expect(res.json).toHaveBeenCalledWith({
150-
type: 'QUOTA_EXCEEDED',
151-
resource: 'scraps',
152-
action: 'execute',
153-
limit: 100,
154-
current: 100,
155-
upgradeUrl: '/billing/plans',
156-
});
150+
expect(res.json).toHaveBeenCalledWith(expect.objectContaining({
151+
type: 'error',
152+
message: 'Quota exceeded',
153+
code: 429,
154+
status: 429,
155+
description: 'You have reached the usage limit for this resource',
156+
}));
157157
});
158158

159159
test('should return 403 when organization context is missing', async () => {

0 commit comments

Comments
 (0)