@@ -12,147 +12,162 @@ const PROXY_USERNAME_PREFIX = process.env.PROXY_CLIENT_USERNAME_PREFIX || 'proxy
1212const PROXY_DATABASE = process . env . PROXY_CLIENT_DATABASE || 'postgres' ;
1313
1414function 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
2424function 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
3333const 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.
4646let 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
4961const usageReports = [ ] ;
5062
5163const 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
156171server . 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