Skip to content

Commit 48030d0

Browse files
authored
Merge pull request #1768 from rocket-admin/backend_stripe_proxy_usage_reports
feat: add deriveHostedDbPlan function to manage subscription levels
2 parents d02438c + 5842558 commit 48030d0

1 file changed

Lines changed: 138 additions & 123 deletions

File tree

test/proxy-mock-api/server.js

Lines changed: 138 additions & 123 deletions
Original file line numberDiff line numberDiff line change
@@ -12,147 +12,162 @@ const PROXY_USERNAME_PREFIX = process.env.PROXY_CLIENT_USERNAME_PREFIX || 'proxy
1212
const PROXY_DATABASE = process.env.PROXY_CLIENT_DATABASE || 'postgres';
1313

1414
function deriveCompanyId(username) {
15-
// Single-username case (e.g. plain "proxy_user") keeps the legacy id so existing
16-
// tests/clients that don't randomize still hit a stable companyId.
17-
if (username === PROXY_USERNAME_PREFIX) {
18-
return 'test-company-001';
19-
}
20-
const hash = crypto.createHash('sha1').update(username).digest('hex').slice(0, 12);
21-
return `test-company-${hash}`;
15+
// Single-username case (e.g. plain "proxy_user") keeps the legacy id so existing
16+
// tests/clients that don't randomize still hit a stable companyId.
17+
if (username === PROXY_USERNAME_PREFIX) {
18+
return 'test-company-001';
19+
}
20+
const hash = crypto.createHash('sha1').update(username).digest('hex').slice(0, 12);
21+
return `test-company-${hash}`;
2222
}
2323

2424
function deriveConnectionId(username) {
25-
if (username === PROXY_USERNAME_PREFIX) {
26-
return 'test-connection-001';
27-
}
28-
const hash = crypto.createHash('sha1').update(username).digest('hex').slice(0, 12);
29-
return `test-connection-${hash}`;
25+
if (username === PROXY_USERNAME_PREFIX) {
26+
return 'test-connection-001';
27+
}
28+
const hash = crypto.createHash('sha1').update(username).digest('hex').slice(0, 12);
29+
return `test-connection-${hash}`;
3030
}
3131

3232
// Upstream connection info returned when the proxy asks for the above client credentials
3333
const TEST_CONNECTION = {
34-
connectionId: 'test-connection-001',
35-
host: process.env.UPSTREAM_PG_HOST || 'testPg-proxy-e2e',
36-
port: parseInt(process.env.UPSTREAM_PG_PORT || '5432', 10),
37-
database: process.env.UPSTREAM_PG_DATABASE || 'postgres',
38-
username: process.env.UPSTREAM_PG_USERNAME || 'postgres',
39-
password: process.env.UPSTREAM_PG_PASSWORD || 'proxy_test_123',
40-
companyId: 'test-company-001',
41-
subscriptionLevel: 'TEAM_PLAN',
34+
connectionId: 'test-connection-001',
35+
host: process.env.UPSTREAM_PG_HOST || 'testPg-proxy-e2e',
36+
port: parseInt(process.env.UPSTREAM_PG_PORT || '5432', 10),
37+
database: process.env.UPSTREAM_PG_DATABASE || 'postgres',
38+
username: process.env.UPSTREAM_PG_USERNAME || 'postgres',
39+
password: process.env.UPSTREAM_PG_PASSWORD || 'proxy_test_123',
40+
companyId: 'test-company-001',
41+
subscriptionLevel: 'TEAM_PLAN',
4242
};
4343

4444
// Configurable subscription level — e2e tests can change this at runtime
4545
// to test throttling / frozen plan behaviour.
4646
let currentSubscriptionLevel = TEST_CONNECTION.subscriptionLevel;
4747

48+
// The proxy keys throttling off hostedDbPlan, not subscriptionLevel. The
49+
// product reality is two-tier — paid (unlimited) and free (throttled) — plus
50+
// the admin-initiated `frozen` override and a TEST_TINY_PLAN used to drive
51+
// the bucket to exhaustion quickly. Anything else is treated as paid so
52+
// positive-path tests run unthrottled.
53+
function deriveHostedDbPlan(level) {
54+
if (level === 'frozen') return 'frozen';
55+
if (level === 'TEST_TINY_PLAN') return 'TEST_TINY_PLAN';
56+
if (level === 'HOSTED_DB_FREE' || level === 'FREE_PLAN') return 'HOSTED_DB_FREE';
57+
return 'HOSTED_DB_PAID';
58+
}
59+
4860
// Store received usage reports so tests can verify them via GET /api/proxy/usage-reports
4961
const usageReports = [];
5062

5163
const server = http.createServer((req, res) => {
52-
// Health check endpoint (no auth required)
53-
if (req.url === '/healthz') {
54-
res.writeHead(200, { 'Content-Type': 'application/json' });
55-
res.end(JSON.stringify({ status: 'ok' }));
56-
return;
57-
}
58-
59-
const parsedUrl = url.parse(req.url, true);
60-
61-
// Test-only: configure subscription level at runtime (no auth required)
62-
if (req.method === 'PUT' && parsedUrl.pathname === '/api/test/subscription-level') {
63-
let body = '';
64-
req.on('data', (chunk) => (body += chunk));
65-
req.on('end', () => {
66-
const parsed = JSON.parse(body);
67-
currentSubscriptionLevel = parsed.subscriptionLevel;
68-
console.log(`[mock-api] Subscription level changed to: ${currentSubscriptionLevel}`);
69-
res.writeHead(200, { 'Content-Type': 'application/json' });
70-
res.end(JSON.stringify({ ok: true, subscriptionLevel: currentSubscriptionLevel }));
71-
});
72-
return;
73-
}
74-
75-
// Test-only: get collected usage reports (no auth required)
76-
if (req.method === 'GET' && parsedUrl.pathname === '/api/test/usage-reports') {
77-
res.writeHead(200, { 'Content-Type': 'application/json' });
78-
res.end(JSON.stringify(usageReports));
79-
return;
80-
}
81-
82-
// Test-only: clear collected usage reports (no auth required)
83-
if (req.method === 'DELETE' && parsedUrl.pathname === '/api/test/usage-reports') {
84-
usageReports.length = 0;
85-
res.writeHead(200, { 'Content-Type': 'application/json' });
86-
res.end(JSON.stringify({ ok: true }));
87-
return;
88-
}
89-
90-
const apiKey = req.headers['x-proxy-api-key'];
91-
if (apiKey !== EXPECTED_API_KEY) {
92-
res.writeHead(401, { 'Content-Type': 'application/json' });
93-
res.end(JSON.stringify({ error: 'Unauthorized' }));
94-
return;
95-
}
96-
97-
// GET /api/proxy/connections?username=X&database=Y
98-
if (req.method === 'GET' && parsedUrl.pathname === '/api/proxy/connections') {
99-
const { username, database } = parsedUrl.query;
100-
console.log(`[mock-api] GET connection: username=${username}, database=${database}`);
101-
102-
const usernameMatches =
103-
username === PROXY_USERNAME_PREFIX || (typeof username === 'string' && username.startsWith(PROXY_USERNAME_PREFIX + '_'));
104-
105-
if (usernameMatches && database === PROXY_DATABASE) {
106-
const companyId = deriveCompanyId(username);
107-
const connectionId = deriveConnectionId(username);
108-
console.log(
109-
`[mock-api] -> returning connection, companyId=${companyId} connectionId=${connectionId} subscriptionLevel=${currentSubscriptionLevel}`,
110-
);
111-
res.writeHead(200, { 'Content-Type': 'application/json' });
112-
res.end(
113-
JSON.stringify({
114-
...TEST_CONNECTION,
115-
connectionId,
116-
companyId,
117-
subscriptionLevel: currentSubscriptionLevel,
118-
}),
119-
);
120-
} else {
121-
console.log(
122-
`[mock-api] -> 404: username/database mismatch (expected ${PROXY_USERNAME_PREFIX}[_*]/${PROXY_DATABASE})`,
123-
);
124-
res.writeHead(404, { 'Content-Type': 'application/json' });
125-
res.end(JSON.stringify({ error: 'Connection not found' }));
126-
}
127-
return;
128-
}
129-
130-
// POST /api/proxy/usage
131-
if (req.method === 'POST' && parsedUrl.pathname === '/api/proxy/usage') {
132-
let body = '';
133-
req.on('data', (chunk) => (body += chunk));
134-
req.on('end', () => {
135-
console.log(`[mock-api] POST usage: ${body}`);
136-
try {
137-
const reports = JSON.parse(body);
138-
if (Array.isArray(reports)) {
139-
usageReports.push(...reports);
140-
} else {
141-
usageReports.push(reports);
142-
}
143-
} catch (e) {
144-
console.error(`[mock-api] Failed to parse usage body: ${e.message}`);
145-
}
146-
res.writeHead(200, { 'Content-Type': 'application/json' });
147-
res.end(JSON.stringify({ ok: true }));
148-
});
149-
return;
150-
}
151-
152-
res.writeHead(404, { 'Content-Type': 'application/json' });
153-
res.end(JSON.stringify({ error: 'Not found' }));
64+
// Health check endpoint (no auth required)
65+
if (req.url === '/healthz') {
66+
res.writeHead(200, { 'Content-Type': 'application/json' });
67+
res.end(JSON.stringify({ status: 'ok' }));
68+
return;
69+
}
70+
71+
const parsedUrl = url.parse(req.url, true);
72+
73+
// Test-only: configure subscription level at runtime (no auth required)
74+
if (req.method === 'PUT' && parsedUrl.pathname === '/api/test/subscription-level') {
75+
let body = '';
76+
req.on('data', (chunk) => (body += chunk));
77+
req.on('end', () => {
78+
const parsed = JSON.parse(body);
79+
currentSubscriptionLevel = parsed.subscriptionLevel;
80+
console.log(`[mock-api] Subscription level changed to: ${currentSubscriptionLevel}`);
81+
res.writeHead(200, { 'Content-Type': 'application/json' });
82+
res.end(JSON.stringify({ ok: true, subscriptionLevel: currentSubscriptionLevel }));
83+
});
84+
return;
85+
}
86+
87+
// Test-only: get collected usage reports (no auth required)
88+
if (req.method === 'GET' && parsedUrl.pathname === '/api/test/usage-reports') {
89+
res.writeHead(200, { 'Content-Type': 'application/json' });
90+
res.end(JSON.stringify(usageReports));
91+
return;
92+
}
93+
94+
// Test-only: clear collected usage reports (no auth required)
95+
if (req.method === 'DELETE' && parsedUrl.pathname === '/api/test/usage-reports') {
96+
usageReports.length = 0;
97+
res.writeHead(200, { 'Content-Type': 'application/json' });
98+
res.end(JSON.stringify({ ok: true }));
99+
return;
100+
}
101+
102+
const apiKey = req.headers['x-proxy-api-key'];
103+
if (apiKey !== EXPECTED_API_KEY) {
104+
res.writeHead(401, { 'Content-Type': 'application/json' });
105+
res.end(JSON.stringify({ error: 'Unauthorized' }));
106+
return;
107+
}
108+
109+
// GET /api/proxy/connections?username=X&database=Y
110+
if (req.method === 'GET' && parsedUrl.pathname === '/api/proxy/connections') {
111+
const { username, database } = parsedUrl.query;
112+
console.log(`[mock-api] GET connection: username=${username}, database=${database}`);
113+
114+
const usernameMatches =
115+
username === PROXY_USERNAME_PREFIX ||
116+
(typeof username === 'string' && username.startsWith(PROXY_USERNAME_PREFIX + '_'));
117+
118+
if (usernameMatches && database === PROXY_DATABASE) {
119+
const companyId = deriveCompanyId(username);
120+
const connectionId = deriveConnectionId(username);
121+
const hostedDbPlan = deriveHostedDbPlan(currentSubscriptionLevel);
122+
console.log(
123+
`[mock-api] -> returning connection, companyId=${companyId} connectionId=${connectionId} subscriptionLevel=${currentSubscriptionLevel} hostedDbPlan=${hostedDbPlan}`,
124+
);
125+
res.writeHead(200, { 'Content-Type': 'application/json' });
126+
res.end(
127+
JSON.stringify({
128+
...TEST_CONNECTION,
129+
connectionId,
130+
companyId,
131+
subscriptionLevel: currentSubscriptionLevel,
132+
hostedDbPlan,
133+
}),
134+
);
135+
} else {
136+
console.log(
137+
`[mock-api] -> 404: username/database mismatch (expected ${PROXY_USERNAME_PREFIX}[_*]/${PROXY_DATABASE})`,
138+
);
139+
res.writeHead(404, { 'Content-Type': 'application/json' });
140+
res.end(JSON.stringify({ error: 'Connection not found' }));
141+
}
142+
return;
143+
}
144+
145+
// POST /api/proxy/usage
146+
if (req.method === 'POST' && parsedUrl.pathname === '/api/proxy/usage') {
147+
let body = '';
148+
req.on('data', (chunk) => (body += chunk));
149+
req.on('end', () => {
150+
console.log(`[mock-api] POST usage: ${body}`);
151+
try {
152+
const reports = JSON.parse(body);
153+
if (Array.isArray(reports)) {
154+
usageReports.push(...reports);
155+
} else {
156+
usageReports.push(reports);
157+
}
158+
} catch (e) {
159+
console.error(`[mock-api] Failed to parse usage body: ${e.message}`);
160+
}
161+
res.writeHead(200, { 'Content-Type': 'application/json' });
162+
res.end(JSON.stringify({ ok: true }));
163+
});
164+
return;
165+
}
166+
167+
res.writeHead(404, { 'Content-Type': 'application/json' });
168+
res.end(JSON.stringify({ error: 'Not found' }));
154169
});
155170

156171
server.listen(PORT, () => {
157-
console.log(`[mock-api] Proxy mock API listening on port ${PORT}`);
172+
console.log(`[mock-api] Proxy mock API listening on port ${PORT}`);
158173
});

0 commit comments

Comments
 (0)