Skip to content

Backend pg proxy#1719

Merged
Artuomka merged 19 commits into
mainfrom
backend_pg_proxy
Apr 23, 2026
Merged

Backend pg proxy#1719
Artuomka merged 19 commits into
mainfrom
backend_pg_proxy

Conversation

@Artuomka
Copy link
Copy Markdown
Collaborator

@Artuomka Artuomka commented Apr 17, 2026

Summary by CodeRabbit

  • New Features

    • Added an authenticated API endpoint to retrieve hosted connection credentials.
  • Tests

    • Added a comprehensive Postgres-proxy end-to-end test suite (CRUD, schema, usage reporting, budget/frozen-plan scenarios).
    • Updated hosted-connection e2e tests to support the new credential flow.
  • Chores

    • Enhanced test infrastructure and Docker Compose for proxy E2E testing; added a proxy mock service and Dockerfile.
    • Set PostgreSQL connection application_name for telemetry.

Artuomka added 16 commits April 3, 2026 06:53
- Updated the `createProxyConnectionDto` function to generate unique usernames for each test, allowing for isolated connection pools.
- Implemented `expectedCompanyId` and `expectedConnectionId` functions to derive company and connection IDs based on the generated usernames.
- Modified the mock API server to handle dynamic usernames and return appropriate connection and company IDs.
- Improved usage reporting tests to ensure metrics are accurately reported after queries through the proxy.
@Artuomka Artuomka requested review from Copilot and gugu and removed request for Copilot April 17, 2026 13:40
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 17, 2026

📝 Walkthrough

Walkthrough

Adds a new POST endpoint to fetch hosted connection credentials, introducing DTO/RO types, a request-scoped use case and interface, controller wiring and module provider registration, updates to E2E tests and test infra (Postgres proxy + mock API), and a small knex configuration change.

Changes

Cohort / File(s) Summary
Use Case Type Definition
backend/src/common/data-injection.tokens.ts
Added SAAS_GET_HOSTED_CONNECTION_CREDENTIALS enum member to UseCaseType.
DTOs / Response Objects
backend/src/microservices/saas-microservice/data-structures/get-hosted-connection-credentials.dto.ts, backend/src/microservices/saas-microservice/data-structures/hosted-connection-credentials.ro.ts
Added GetHostedConnectionCredentialsDto (hostedDatabaseId) and HostedConnectionCredentialsRO (connectionId, host, port, database, username, password, is_frozen).
Use Case & Interface
backend/src/microservices/saas-microservice/use-cases/get-hosted-connection-credentials.use.case.ts, backend/src/microservices/saas-microservice/use-cases/saas-use-cases.interface.ts
Added GetHostedConnectionCredentialsUseCase (queries connection by id, throws NotFound) and IGetHostedConnectionCredentials interface.
Controller & Module
backend/src/microservices/saas-microservice/saas.controller.ts, backend/src/microservices/saas-microservice/saas.module.ts
New authenticated POST /connection/hosted/credentials endpoint; controller injects use case; module registers provider and applies SaaS auth middleware route.
E2E Tests - Hosted Connection
backend/test/ava-tests/saas-tests/hosted-connection-e2e.test.ts
Updated test payloads to include hostedDatabaseId, adapted response parsing/field usage, permission lookup changes, and added logging.
E2E Tests - Postgres Proxy
backend/test/ava-tests/saas-tests/saas-postgres-proxy-e2e.test.ts
Added comprehensive proxy E2E suite (table CRUD, schema, usage reporting, budget exhaustion, frozen plan) with setup/teardown and proxy reachability gating.
Docker Compose & Test Infra
docker-compose.tst.yml, docker-compose.yml, test/proxy-mock-api/Dockerfile, test/proxy-mock-api/server.js
Added Postgres-proxy testing topology, proxy mock API server and Dockerfile, updated compose service config, healthchecks, and mounted contract fixtures.
DB Client Config
shared-code/src/knex-manager/knex-manager.ts
Added application_name: 'rocketadmin' to Postgres connection config.
Frontend / Minor
frontend/src/app/components/connect-db/connect-db.component.css, frontend/src/app/components/connect-db/connect-db.component.ts, frontend/src/app/validators/connection-string.validator.ts
Whitespace and import reordering, minor reformatting in connection-string validator (no behavioral change).

Sequence Diagram

sequenceDiagram
    autonumber
    participant Client as Client
    participant Controller as SaasController
    participant UseCase as GetHostedConnectionCredentialsUseCase
    participant Repo as ConnectionRepository
    participant DB as GlobalDBContext

    Client->>Controller: POST /connection/hosted/credentials\n{ hostedDatabaseId }
    Controller->>UseCase: execute(GetHostedConnectionCredentialsDto)
    UseCase->>Repo: findOne({ id: hostedDatabaseId })
    Repo->>DB: query connection by id
    DB-->>Repo: connection record | null
    alt connection found
        Repo-->>UseCase: connection entity
        UseCase->>UseCase: map to HostedConnectionCredentialsRO
        UseCase-->>Controller: HostedConnectionCredentialsRO
        Controller-->>Client: 200 OK (credentials)
    else not found
        Repo-->>UseCase: null
        UseCase-->>Controller: NotFoundException
        Controller-->>Client: 404 Not Found
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Suggested reviewers

  • lyubov-voloshko

Poem

🐰 I hopped the code and found a key,
A DTO here, credentials for thee.
Tests and proxies danced in cheer,
The endpoint sings — the data's near.
Nibble, hop, deploy, hooray! 🥕

🚥 Pre-merge checks | ✅ 3 | ❌ 3

❌ Failed checks (2 warnings, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 25.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Security Check ⚠️ Warning Pull request exposes database credentials including plaintext password in API response, violating OWASP sensitive data exposure principles. Remove password field from HostedConnectionCredentialsRO, encrypt sensitive data, implement proper authorization checks, and add audit logging for credential access.
Title check ❓ Inconclusive The title 'Backend pg proxy' is vague and generic, using abbreviations without conveying meaningful information about the actual changes implemented. Provide a more specific title that clearly describes the main change, such as 'Add hosted connection credentials endpoint' or 'Implement SaaS hosted connection credentials API'.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch backend_pg_proxy

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

// predict which companyId/connectionId the proxy will see for a username.
function expectedCompanyId(username: string): string {
if (username === 'proxy_user') return 'test-company-001';
return `test-company-${createHash('sha1').update(username).digest('hex').slice(0, 12)}`;

function expectedConnectionId(username: string): string {
if (username === 'proxy_user') return 'test-connection-001';
return `test-connection-${createHash('sha1').update(username).digest('hex').slice(0, 12)}`;
if (username === PROXY_USERNAME_PREFIX) {
return 'test-company-001';
}
const hash = crypto.createHash('sha1').update(username).digest('hex').slice(0, 12);
if (username === PROXY_USERNAME_PREFIX) {
return 'test-connection-001';
}
const hash = crypto.createHash('sha1').update(username).digest('hex').slice(0, 12);
);
} else {
console.log(
`[mock-api] -> 404: username/database mismatch (expected ${PROXY_USERNAME_PREFIX}[_*]/${PROXY_DATABASE})`,
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 11

🧹 Nitpick comments (9)
backend/src/microservices/saas-microservice/data-structures/hosted-connection-credentials.ro.ts (1)

3-24: Inconsistent field naming: is_frozen vs connectionId.

This RO mixes camelCase (connectionId) with snake_case (is_frozen). Consumers of a single response object typically expect one convention; consider isFrozen for consistency with the rest of the fields (and with common JSON/TS style in this codebase).

✏️ Proposed change
-	`@ApiProperty`()
-	is_frozen: boolean;
+	`@ApiProperty`()
+	isFrozen: boolean;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@backend/src/microservices/saas-microservice/data-structures/hosted-connection-credentials.ro.ts`
around lines 3 - 24, The response object HostedConnectionCredentialsRO mixes
camelCase and snake_case (e.g., connectionId vs is_frozen); rename the property
is_frozen to isFrozen on the HostedConnectionCredentialsRO class and update its
`@ApiProperty`() declaration to use the new name, then find and update all
references/usages/mappings/serializers, DTOs, tests and any database-to-RO
transformation code that read or write the old is_frozen key so they now use
isFrozen to keep naming consistent across the response object.
docker-compose.tst.yml (1)

35-36: Backend now hard-depends on postgres-proxy being healthy.

Adding postgres-proxy: service_healthy to the backend's depends_on means the entire E2E suite won't start if the proxy build fails or its healthcheck misbehaves — even for tests unrelated to the proxy. If that's acceptable (single suite, single compose), fine; otherwise consider a dedicated compose profile for the proxy E2E so regular E2E runs aren't gated on it.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docker-compose.tst.yml` around lines 35 - 36, The backend service now
hard-depends on postgres-proxy being healthy via the depends_on condition
"service_healthy", which gates all E2E runs on the proxy; remove the "condition:
service_healthy" from the backend's depends_on or move postgres-proxy into a
separate docker-compose profile (e.g., profile: "proxy-e2e") and update backend
to only depend on the proxy in that profile so regular E2E runs aren't blocked;
reference the backend service's depends_on entry and the "postgres-proxy"
service/healthcheck when making the change.
docker-compose.yml (1)

265-265: Centralize the shared proxy API key.

PROXY_API_KEY: test-proxy-api-key is duplicated on proxy-mock-api (Line 265) and postgres-proxy (Line 284). If they ever drift, the proxy's outbound calls will 401 against the mock API and tests will fail opaquely. Consider a YAML anchor or shared .env var (e.g. ${E2E_PROXY_API_KEY}) so the two values can't diverge.

Also applies to: 284-284

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docker-compose.yml` at line 265, The duplicated hard-coded PROXY_API_KEY
value should be centralized so the two services (proxy-mock-api and
postgres-proxy) cannot drift; replace the literal PROXY_API_KEY:
test-proxy-api-key in both service env sections by referencing a single source
(either define an env var like ${E2E_PROXY_API_KEY} and use that for both
services, or create a YAML anchor/alias for the key and reference the anchor in
both places), and ensure the new shared variable/anchor is documented in the
compose file or .env so tests use the same value.
backend/src/microservices/saas-microservice/saas.controller.ts (1)

353-364: Endpoint wiring looks correct.

Constructor injection, DTO binding, and route shape follow the conventions of the other hosted-connection endpoints in this controller. The real behavior (password handling, authorization) lives in the use case; concerns there are raised on get-hosted-connection-credentials.use.case.ts.

One minor note: @ApiResponse status is 200 but NestJS defaults @Post to 201 Created. If the intent is 200, add @HttpCode(200); otherwise set the Swagger status to 201 to match the runtime response (the sibling endpoints in this file consistently document 201 for @Post).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@backend/src/microservices/saas-microservice/saas.controller.ts` around lines
353 - 364, The Swagger response status for the POST handler
getHostedConnectionCredentials (method getHostedConnectionCredentials using
GetHostedConnectionCredentialsDto and returning HostedConnectionCredentialsRO)
is inconsistent with Nest's POST default; either add `@HttpCode`(200) to the
getHostedConnectionCredentials method to keep the documented 200 status, or
change the `@ApiResponse` status to 201 so Swagger matches the runtime POST
behavior—update the decorator accordingly to make the doc and runtime
consistent.
backend/test/ava-tests/saas-tests/hosted-connection-e2e.test.ts (1)

94-94: Remove debug console.log statements before merge.

🚀 ~ createdConnection: / 🚀 ~ responseBody: look like leftover debugging aids. They add noise to CI logs and, for the credentials work being introduced in this PR, risk leaking sensitive fields (passwords, tokens) into build output if the response shape ever grows.

♻️ Proposed cleanup
-		const createdConnection = JSON.parse(createHostedConnectionResult.text);
-		console.log('🚀 ~ createdConnection:', createdConnection);
-
-		t.is(createHostedConnectionResult.status, 201);
+		t.is(createHostedConnectionResult.status, 201);
+		const createdConnection = JSON.parse(createHostedConnectionResult.text);
-		const responseBody = JSON.parse(result.text);
-		console.log('🚀 ~ responseBody:', responseBody);
 		t.is(result.status, 500);
-		const createdConnection = JSON.parse(createResult.text);
-		console.log('🚀 ~ createdConnection:', createdConnection);
-		t.is(createResult.status, 201);
+		t.is(createResult.status, 201);
+		const createdConnection = JSON.parse(createResult.text);

Also applies to: 185-185, 230-230

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@backend/test/ava-tests/saas-tests/hosted-connection-e2e.test.ts` at line 94,
Remove the leftover debug console.log statements that print request/response
objects to CI (they may leak sensitive data); search for and delete the
console.log calls that reference createdConnection and responseBody in the
hosted-connection-e2e.test.ts tests (and any other console.log debug prints in
the same file) so tests only use assertions and structured test logging instead
of dumping full objects.
backend/test/ava-tests/saas-tests/saas-postgres-proxy-e2e.test.ts (3)

30-33: Defaults target docker-compose network; won't work for local runs.

PROXY_HOST and MOCK_API_HOST default to postgres-proxy / proxy-mock-api, which only resolve inside the compose network. A developer running yarn test on their host machine without these env vars will just hit the TCP probe timeout and see every test silently skip (see earlier comment about silent skips). A README note or a sensible localhost fallback when not running inside the compose network would help onboarding.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@backend/test/ava-tests/saas-tests/saas-postgres-proxy-e2e.test.ts` around
lines 30 - 33, The test defaults use docker-compose hostnames (PROXY_HOST and
MOCK_API_HOST), which fail for local runs; change the defaults in
backend/test/ava-tests/saas-tests/saas-postgres-proxy-e2e.test.ts to fall back
to 'localhost' when the corresponding env var is not set (keep parseInt logic
for PROXY_PORT and MOCK_API_PORT intact), and optionally add a short README note
about running tests inside compose versus locally so developers know to override
these env vars when needed.

567-623: Budget-exhaustion test: tighten the regex and bound the loop.

/budget|exceeded|plan|53400/i will match words like "plan" that appear in plenty of unrelated error bodies (e.g. "explain plan", "connection plan", generic NestJS messages mentioning "plan"). Prefer a tighter anchor that specifically ties the assertion to the proxy's 53400 rejection, e.g. /53400|budget\s+exceeded|plan\s+limit/i.

Also, the 30-call loop will always run to completion when the budget check fails to trigger, adding significant time to CI. Consider t.timeout(60000) (already set) plus an explicit assertion that the loop terminated before i === 29, so a silent regression (budget never exhausts) surfaces as a clear failure rather than a post-loop rejected === false with no diagnostics.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@backend/test/ava-tests/saas-tests/saas-postgres-proxy-e2e.test.ts` around
lines 567 - 623, The test 'should reject queries once query-time budget is
exhausted' uses a too-broad regex and an unbounded 30-iteration loop; tighten
the rejection check to a more specific pattern (e.g.
/53400|budget\s+exceeded|plan\s+limit/i) and add an explicit assertion that the
loop exited early (budget hit) rather than completing all 30 iterations—use the
existing loop index or a separate attempts counter (from the for loop variable i
or a new attempts variable) and assert it is < 30 (or i < 29) when rejected is
true so a silent regression (budget never exhausts) fails fast. Ensure you
update the regex reference where lastBody is tested and add the iteration-bound
assertion after the loop using lastStatus/ rejected/ i.

302-312: Remove redundant JSON.stringify around the request body for consistency.

Supertest passes plain objects directly to the body parser when Content-Type: application/json is set. Explicitly wrapping in JSON.stringify() causes the backend to receive a string instead of an object. Other E2E tests in this file (/connection, /saas/connection/hosted) pass plain objects; keep this consistent.

♻️ Proposed diff
-			.send(
-				JSON.stringify({
-					[testTableColumnName]: newName,
-					[testTableSecondColumnName]: newEmail,
-				}),
-			)
+			.send({
+				[testTableColumnName]: newName,
+				[testTableSecondColumnName]: newEmail,
+			})

(same for the update-row call at Lines 361‑366)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@backend/test/ava-tests/saas-tests/saas-postgres-proxy-e2e.test.ts` around
lines 302 - 312, The POST to add a row is serializing the body with
JSON.stringify which causes the backend to receive a string; update the request
in the addRow call (the request built with
request(app.getHttpServer()).post(`/table/row/${createConnectionRO.id}?tableName=${testTableName}`)
that assigns to addRowResponse) to pass a plain object literal instead of
JSON.stringify(...) and do the same for the corresponding update-row request
later (the update-row call around lines 361‑366), leaving headers
(.set('Content-Type','application/json') and .set('Accept','application/json'))
unchanged so supertest/body-parser handle JSON correctly.
test/proxy-mock-api/server.js (1)

33-46: TEST_CONNECTION.connectionId / companyId are dead defaults.

Every response path (Lines 112‑119) overrides connectionId and companyId via deriveConnectionId / deriveCompanyId, and currentSubscriptionLevel is re-initialized from TEST_CONNECTION.subscriptionLevel anyway. The 'test-connection-001' / 'test-company-001' fields on TEST_CONNECTION are never observed by clients and just duplicate the legacy-case logic inside deriveXxx. Consider dropping them from TEST_CONNECTION to make it obvious that the response's IDs always come from the derive functions.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/proxy-mock-api/server.js` around lines 33 - 46, Remove the unused
dead-default fields connectionId and companyId from the TEST_CONNECTION const
and keep only the real defaults (host, port, database, username, password,
subscriptionLevel); leave currentSubscriptionLevel initialization as-is using
TEST_CONNECTION.subscriptionLevel and ensure all response code continues to use
deriveConnectionId and deriveCompanyId (these are the actual sources of IDs).
Update any nearby comments to reflect that connectionId/companyId are derived at
runtime and no other code should be reading TEST_CONNECTION.connectionId or
TEST_CONNECTION.companyId.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@backend/src/microservices/saas-microservice/data-structures/hosted-connection-credentials.ro.ts`:
- Around line 19-20: The password property on HostedConnectionCredentials (the
password field currently annotated only with `@ApiProperty`()) must be either
redacted from serialization or explicitly documented: add the `@Exclude`()
decorator to the password property and ensure the class is serialized via
ClassSerializerInterceptor, or add a clear code comment on the
HostedConnectionCredentials class/property explaining why plaintext password is
exposed and list required safeguards (no caching, response/log redaction,
error-tracking filtering). Update related serialization configuration to
guarantee `@Exclude`() is honored if you choose that approach.

In
`@backend/src/microservices/saas-microservice/use-cases/get-hosted-connection-credentials.use.case.ts`:
- Around line 22-28: The lookup in implementation
(get-hosted-connection-credentials.use.case.ts -> protected async
implementation) uses connectionRepository.findOne({ where: { id:
inputData.hostedDatabaseId } }) without scoping to tenant/company or hosted-only
connections; update the code and DTO to prevent cross-tenant exposure by either
(A) adding companyId to GetHostedConnectionCredentialsDto and changing the query
to connectionRepository.findOne({ where: { id: inputData.hostedDatabaseId,
company: { id: inputData.companyId } } }) with validation that the caller's
company matches, or (B) at minimum filter for hosted connections only by using
connectionRepository.findOne({ where: { id: inputData.hostedDatabaseId, hosted:
true } }) so only system-hosted DBs can be returned; apply the chosen change in
the implementation, adjust any DTO/validator usages, and keep error handling
(NotFoundException) the same if no record matches.
- Around line 22-39: The implementation of GetHostedConnectionCredentialsUseCase
returns the encrypted password; fetch the connection via
_dbContext.connectionRepository.findOne (as already done), then call
decryptConnectionCredentialsAsync(connection) (the same helper used by
UpdateHostedConnectionPasswordUseCase) before constructing and returning the
HostedConnectionCredentialsRO so the password is decrypted; add the appropriate
import for decryptConnectionCredentialsAsync from
entities/connection/utils/decrypt-connection-credentials-async.js and keep the
existing NotFoundException(Messages.CONNECTION_NOT_FOUND) check.

In `@backend/test/ava-tests/saas-tests/hosted-connection-e2e.test.ts`:
- Around line 184-186: The test in hosted-connection-e2e.test.ts is asserting a
500 for an unknown/missing userId (result.status === 500), which should be a
4xx; update the test to expect the correct client error (preferably 404 to match
how get-hosted-connection-credentials.use.case.ts throws NotFoundException) by
changing the assertion to expect 404 (or, if you cannot update upstream yet,
replace the hard 500 assertion with a TODO comment and a looser check that the
response indicates a client error range) so the test no longer locks in a
server-error behavior.

In `@backend/test/ava-tests/saas-tests/saas-postgres-proxy-e2e.test.ts`:
- Around line 662-669: The test's assertion using
t.not(getTablesResponse.status, 200) is too weak; update the assertion to
explicitly check for the expected rejection due to frozen plan by asserting the
exact status code(s) produced by the proxy (e.g., t.is(getTablesResponse.status,
403) or t.true([403,503].includes(getTablesResponse.status))) and also assert
the response body contains the expected error marker string (e.g., that
getTablesResponse.body or getTablesResponse.text includes "CONNECTION_IS_FROZEN"
or "NonAvailableInFreePlanException" as defined in validate-connection.util.ts)
so the test fails for unrelated non-200 responses and only passes when the
frozen-plan path is triggered.
- Around line 143-149: The test suite currently silently sets skipAll = true
when isProxyReachable() returns false, causing AVA to report all tests as passed
even when the proxy is down; change the behavior to require an explicit opt-in
env var (e.g., PROXY_E2E) before allowing silent skips: in the test.before and
the corresponding later guard (references: isProxyReachable, PROXY_HOST,
PROXY_PORT, skipAll), check process.env.PROXY_E2E (or similar) and only set
skipAll=true when the env flag is present, otherwise throw or fail the before
hook (or call t.fail) so CI surfaces a hard failure; additionally emit a clear
console.warn including PROXY_HOST:PROXY_PORT and the env decision so skipped
runs are obviously intentional on developer machines.

In `@docker-compose.tst.yml`:
- Around line 245-259: The Postgres service testPg-proxy-e2e is intentionally
enabling deprecated md5 auth via POSTGRES_HOST_AUTH_METHOD, POSTGRES_INITDB_ARGS
and command (password_encryption=md5) for the proxy E2E tests; add an inline
comment above the service explaining that md5 is required only for this test
because the proxy implementation authenticates using md5, that md5 is deprecated
in Postgres 16 and must never be used in production configs, and ensure these
md5-specific envs/flags remain only in this test compose file.
- Around line 282-284: The docker-compose service "postgres-proxy" uses a
parent-relative build context ("../postgres-proxy") which requires a sibling
checkout; update docker-compose.tst.yml to make the E2E stack reproducible by
either (A) replacing the build context with a pinned pre-built image reference
(e.g. set the service to use image: <registry>/postgres-proxy:<tag>) and
removing the build block, or (B) document the required repo layout and checkout
step in the testing README so CI and contributors know to clone the sibling
"postgres-proxy" repo; modify the "postgres-proxy" service entry accordingly and
ensure any CI scripts pull the pinned image or perform the sibling checkout.

In `@docker-compose.yml`:
- Around line 246-289: The docker-compose services lack healthcheck blocks and
still use short-form depends_on, causing startup races; add healthchecks and
switch to long-form depends_on with condition: service_healthy so ordering is
enforced: add a healthcheck for testPg-proxy-e2e using pg_isready (or a TCP
probe to 5432) that retries until MD5 auth is active, add an HTTP/ TCP
healthcheck for proxy-mock-api (e.g., hit /healthz or TCP 3333) and for
postgres-proxy (HTTP /healthz or TCP 5432), then replace the short depends_on
entries for proxy-mock-api and postgres-proxy with the long-form depends_on
referencing testPg-proxy-e2e and proxy-mock-api and requiring condition:
service_healthy so isProxyReachable in saas-postgres-proxy-e2e.test.ts won’t
skip flaky startups.

In `@test/proxy-mock-api/Dockerfile`:
- Around line 1-8: The Dockerfile currently runs the container as root; update
it to switch to the non-root node user provided by the node base image by adding
a USER directive (e.g., USER node) after copying files and before CMD, and
ensure file permissions are correct by chowning or copying files as that user if
necessary (adjust COPY/ownership steps so server.js is readable/executable by
the node user and avoid running processes as root in CMD).

In `@test/proxy-mock-api/server.js`:
- Around line 62-73: The PUT /api/test/subscription-level handler calls
JSON.parse(body) unguarded which can throw and crash the server; wrap the parse
and subsequent handling in a try/catch around JSON.parse (and the parsed usage
of currentSubscriptionLevel) and on error log the failure and send an HTTP error
response (e.g., 400 with a JSON error body) before calling res.end so the
request always completes; follow the pattern used in the POST /api/proxy/usage
handler (try/catch around JSON.parse) to locate and implement the fix.

---

Nitpick comments:
In
`@backend/src/microservices/saas-microservice/data-structures/hosted-connection-credentials.ro.ts`:
- Around line 3-24: The response object HostedConnectionCredentialsRO mixes
camelCase and snake_case (e.g., connectionId vs is_frozen); rename the property
is_frozen to isFrozen on the HostedConnectionCredentialsRO class and update its
`@ApiProperty`() declaration to use the new name, then find and update all
references/usages/mappings/serializers, DTOs, tests and any database-to-RO
transformation code that read or write the old is_frozen key so they now use
isFrozen to keep naming consistent across the response object.

In `@backend/src/microservices/saas-microservice/saas.controller.ts`:
- Around line 353-364: The Swagger response status for the POST handler
getHostedConnectionCredentials (method getHostedConnectionCredentials using
GetHostedConnectionCredentialsDto and returning HostedConnectionCredentialsRO)
is inconsistent with Nest's POST default; either add `@HttpCode`(200) to the
getHostedConnectionCredentials method to keep the documented 200 status, or
change the `@ApiResponse` status to 201 so Swagger matches the runtime POST
behavior—update the decorator accordingly to make the doc and runtime
consistent.

In `@backend/test/ava-tests/saas-tests/hosted-connection-e2e.test.ts`:
- Line 94: Remove the leftover debug console.log statements that print
request/response objects to CI (they may leak sensitive data); search for and
delete the console.log calls that reference createdConnection and responseBody
in the hosted-connection-e2e.test.ts tests (and any other console.log debug
prints in the same file) so tests only use assertions and structured test
logging instead of dumping full objects.

In `@backend/test/ava-tests/saas-tests/saas-postgres-proxy-e2e.test.ts`:
- Around line 30-33: The test defaults use docker-compose hostnames (PROXY_HOST
and MOCK_API_HOST), which fail for local runs; change the defaults in
backend/test/ava-tests/saas-tests/saas-postgres-proxy-e2e.test.ts to fall back
to 'localhost' when the corresponding env var is not set (keep parseInt logic
for PROXY_PORT and MOCK_API_PORT intact), and optionally add a short README note
about running tests inside compose versus locally so developers know to override
these env vars when needed.
- Around line 567-623: The test 'should reject queries once query-time budget is
exhausted' uses a too-broad regex and an unbounded 30-iteration loop; tighten
the rejection check to a more specific pattern (e.g.
/53400|budget\s+exceeded|plan\s+limit/i) and add an explicit assertion that the
loop exited early (budget hit) rather than completing all 30 iterations—use the
existing loop index or a separate attempts counter (from the for loop variable i
or a new attempts variable) and assert it is < 30 (or i < 29) when rejected is
true so a silent regression (budget never exhausts) fails fast. Ensure you
update the regex reference where lastBody is tested and add the iteration-bound
assertion after the loop using lastStatus/ rejected/ i.
- Around line 302-312: The POST to add a row is serializing the body with
JSON.stringify which causes the backend to receive a string; update the request
in the addRow call (the request built with
request(app.getHttpServer()).post(`/table/row/${createConnectionRO.id}?tableName=${testTableName}`)
that assigns to addRowResponse) to pass a plain object literal instead of
JSON.stringify(...) and do the same for the corresponding update-row request
later (the update-row call around lines 361‑366), leaving headers
(.set('Content-Type','application/json') and .set('Accept','application/json'))
unchanged so supertest/body-parser handle JSON correctly.

In `@docker-compose.tst.yml`:
- Around line 35-36: The backend service now hard-depends on postgres-proxy
being healthy via the depends_on condition "service_healthy", which gates all
E2E runs on the proxy; remove the "condition: service_healthy" from the
backend's depends_on or move postgres-proxy into a separate docker-compose
profile (e.g., profile: "proxy-e2e") and update backend to only depend on the
proxy in that profile so regular E2E runs aren't blocked; reference the backend
service's depends_on entry and the "postgres-proxy" service/healthcheck when
making the change.

In `@docker-compose.yml`:
- Line 265: The duplicated hard-coded PROXY_API_KEY value should be centralized
so the two services (proxy-mock-api and postgres-proxy) cannot drift; replace
the literal PROXY_API_KEY: test-proxy-api-key in both service env sections by
referencing a single source (either define an env var like ${E2E_PROXY_API_KEY}
and use that for both services, or create a YAML anchor/alias for the key and
reference the anchor in both places), and ensure the new shared variable/anchor
is documented in the compose file or .env so tests use the same value.

In `@test/proxy-mock-api/server.js`:
- Around line 33-46: Remove the unused dead-default fields connectionId and
companyId from the TEST_CONNECTION const and keep only the real defaults (host,
port, database, username, password, subscriptionLevel); leave
currentSubscriptionLevel initialization as-is using
TEST_CONNECTION.subscriptionLevel and ensure all response code continues to use
deriveConnectionId and deriveCompanyId (these are the actual sources of IDs).
Update any nearby comments to reflect that connectionId/companyId are derived at
runtime and no other code should be reading TEST_CONNECTION.connectionId or
TEST_CONNECTION.companyId.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 161dcb0d-ab15-4a40-830b-5dcab5d3d7f3

📥 Commits

Reviewing files that changed from the base of the PR and between 6cac9f9 and bfa5e85.

📒 Files selected for processing (14)
  • backend/src/common/data-injection.tokens.ts
  • backend/src/microservices/saas-microservice/data-structures/get-hosted-connection-credentials.dto.ts
  • backend/src/microservices/saas-microservice/data-structures/hosted-connection-credentials.ro.ts
  • backend/src/microservices/saas-microservice/saas.controller.ts
  • backend/src/microservices/saas-microservice/saas.module.ts
  • backend/src/microservices/saas-microservice/use-cases/get-hosted-connection-credentials.use.case.ts
  • backend/src/microservices/saas-microservice/use-cases/saas-use-cases.interface.ts
  • backend/test/ava-tests/saas-tests/hosted-connection-e2e.test.ts
  • backend/test/ava-tests/saas-tests/saas-postgres-proxy-e2e.test.ts
  • docker-compose.tst.yml
  • docker-compose.yml
  • shared-code/src/knex-manager/knex-manager.ts
  • test/proxy-mock-api/Dockerfile
  • test/proxy-mock-api/server.js

Comment on lines +19 to +20
@ApiProperty()
password: string;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Look for logging interceptors / middlewares that might serialize responses or DTOs.
rg -nP --type=ts -C3 '\b(LoggerInterceptor|intercept\s*\(|logResponse|res\.body|response\.body)\b'
# Confirm no toJSON/serializer strips sensitive fields yet.
rg -nP --type=ts -C2 '\bHostedConnectionCredentialsRO\b'

Repository: rocket-admin/rocketadmin

Length of output: 28104


🏁 Script executed:

cat -n backend/src/microservices/saas-microservice/data-structures/hosted-connection-credentials.ro.ts

Repository: rocket-admin/rocketadmin

Length of output: 579


🏁 Script executed:

# Check for serialization/redaction interceptors or utilities
rg -nP --type=ts -C3 '(ClassSerializerInterceptor|Exclude|Transform|toJSON|serializer)' backend/src/interceptors/

Repository: rocket-admin/rocketadmin

Length of output: 50


🏁 Script executed:

# Check SentryInterceptor for password/sensitive field handling
cat -n backend/src/interceptors/sentry.interceptor.ts

Repository: rocket-admin/rocketadmin

Length of output: 1424


🏁 Script executed:

# Look for password/sensitive field redaction lists or patterns
rg -nP --type=ts 'password|redact|sensitive|mask' backend/src/interceptors/

Repository: rocket-admin/rocketadmin

Length of output: 50


🏁 Script executed:

# Check the use case implementation
cat -n backend/src/microservices/saas-microservice/use-cases/get-hosted-connection-credentials.use.case.ts

Repository: rocket-admin/rocketadmin

Length of output: 1958


Add @Exclude() decorator to redact the password field from serialization, or document the security handling.

The password field is returned plaintext from this endpoint without any automatic redaction. While this may be required for the SaaS-to-proxy flow, there are no serialization interceptors (e.g., ClassSerializerInterceptor with @Exclude()) or middleware configured to strip this field from logs, error tracking, or cached responses. Add either:

  • @Exclude() decorator on the password property if it should never be serialized, or
  • A code comment explaining why the field is exposed and confirming that callers and middleware handle it securely (no caching, request/response logging redaction, etc.).

Without explicit redaction or documentation, future changes risk accidentally leaking credentials.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@backend/src/microservices/saas-microservice/data-structures/hosted-connection-credentials.ro.ts`
around lines 19 - 20, The password property on HostedConnectionCredentials (the
password field currently annotated only with `@ApiProperty`()) must be either
redacted from serialization or explicitly documented: add the `@Exclude`()
decorator to the password property and ensure the class is serialized via
ClassSerializerInterceptor, or add a clear code comment on the
HostedConnectionCredentials class/property explaining why plaintext password is
exposed and list required safeguards (no caching, response/log redaction,
error-tracking filtering). Update related serialization configuration to
guarantee `@Exclude`() is honored if you choose that approach.

Comment on lines +22 to +28
protected async implementation(inputData: GetHostedConnectionCredentialsDto): Promise<HostedConnectionCredentialsRO> {
const connection = await this._dbContext.connectionRepository.findOne({
where: { id: inputData.hostedDatabaseId },
});
if (!connection) {
throw new NotFoundException(Messages.CONNECTION_NOT_FOUND);
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

No tenant/company scoping on lookup.

connectionRepository.findOne({ where: { id: inputData.hostedDatabaseId } }) returns any connection by id, regardless of which company the caller belongs to. The DTO only carries hostedDatabaseId. Even though this route is behind the SaaS-to-SaaS JWT, a compromised or misbehaving caller (or a future non-SaaS caller) could enumerate UUIDs to exfiltrate credentials for any hosted DB in the system.

Recommend either: (a) require and validate a companyId in the DTO and filter where: { id, company: { id: companyId } }, or (b) explicitly filter to connections that are actually hosted (hosted: true / equivalent flag) so this path can't return credentials for customer-owned BYOD connections.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@backend/src/microservices/saas-microservice/use-cases/get-hosted-connection-credentials.use.case.ts`
around lines 22 - 28, The lookup in implementation
(get-hosted-connection-credentials.use.case.ts -> protected async
implementation) uses connectionRepository.findOne({ where: { id:
inputData.hostedDatabaseId } }) without scoping to tenant/company or hosted-only
connections; update the code and DTO to prevent cross-tenant exposure by either
(A) adding companyId to GetHostedConnectionCredentialsDto and changing the query
to connectionRepository.findOne({ where: { id: inputData.hostedDatabaseId,
company: { id: inputData.companyId } } }) with validation that the caller's
company matches, or (B) at minimum filter for hosted connections only by using
connectionRepository.findOne({ where: { id: inputData.hostedDatabaseId, hosted:
true } }) so only system-hosted DBs can be returned; apply the chosen change in
the implementation, adjust any DTO/validator usages, and keep error handling
(NotFoundException) the same if no record matches.

Comment on lines +22 to +39
protected async implementation(inputData: GetHostedConnectionCredentialsDto): Promise<HostedConnectionCredentialsRO> {
const connection = await this._dbContext.connectionRepository.findOne({
where: { id: inputData.hostedDatabaseId },
});
if (!connection) {
throw new NotFoundException(Messages.CONNECTION_NOT_FOUND);
}

return {
connectionId: connection.id,
host: connection.host,
port: connection.port,
database: connection.database,
username: connection.username,
password: connection.password,
is_frozen: connection.is_frozen,
};
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# 1) See how hosted-db connection creation stores the password (encryption?).
fd -t f 'create-connection-for-hosted-db.use.case.ts' | xargs -I{} sed -n '1,220p' {}

# 2) Look for decryption helpers used elsewhere when returning connection credentials.
rg -nP -C2 '\b(decrypt|encrypt)\w*\s*\(' --type=ts -g 'backend/src/**'

# 3) See whether master/azure/connection encryption is applied on hosted connection writes.
rg -nP -C3 '(masterEncryption|azure_encryption|encryptData|decryptData)' --type=ts -g 'backend/src/**'

Repository: rocket-admin/rocketadmin

Length of output: 50381


🏁 Script executed:

# Check ConnectionEntity definition for BeforeInsert/AfterLoad hooks
fd -t f 'connection.entity.ts' | xargs -I{} cat -n {} | head -150

# Check if there's connection-level encryption happening at entity level
rg -B5 -A5 'BeforeInsert|BeforeUpdate|AfterLoad' --type=ts -g 'backend/src/**/connection.entity.ts'

# Verify if masterEncryption should be true for hosted connections
rg -C5 'hosted.*masterEncryption|masterEncryption.*hosted' --type=ts -g 'backend/src/**'

Repository: rocket-admin/rocketadmin

Length of output: 8372


🏁 Script executed:

# Get the complete BeforeInsert hook implementation
rg -A30 'encryptCredentialsAndGenerateNanoid' --type=ts -g 'backend/src/**/connection.entity.ts'

# Also check how decryptConnectionsCredentialsAsync works
rg -B5 -A15 'decryptConnectionsCredentialsAsync' --type=ts -g 'backend/src/**' | head -100

Repository: rocket-admin/rocketadmin

Length of output: 16265


🏁 Script executed:

# Check the actual current implementation of get-hosted-connection-credentials.use.case.ts
cat -n backend/src/microservices/saas-microservice/use-cases/get-hosted-connection-credentials.use.case.ts

Repository: rocket-admin/rocketadmin

Length of output: 1958


Add missing decryption step before returning credentials.

The connection password is encrypted by ConnectionEntity's @BeforeInsert hook (via Encryptor.encryptData()), but this use case returns the encrypted value directly without decrypting. The API advertises "decrypted credentials" while returning ciphertext, causing authentication to fail silently.

Follow the pattern in UpdateHostedConnectionPasswordUseCase by calling decryptConnectionsCredentialsAsync() on the fetched connection before returning:

import { decryptConnectionCredentialsAsync } from '../../../entities/connection/utils/decrypt-connection-credentials-async.js';

protected async implementation(inputData: GetHostedConnectionCredentialsDto): Promise<HostedConnectionCredentialsRO> {
    const connection = await this._dbContext.connectionRepository.findOne({
        where: { id: inputData.hostedDatabaseId },
    });
    if (!connection) {
        throw new NotFoundException(Messages.CONNECTION_NOT_FOUND);
    }

    await decryptConnectionCredentialsAsync(connection);

    return { ... };
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@backend/src/microservices/saas-microservice/use-cases/get-hosted-connection-credentials.use.case.ts`
around lines 22 - 39, The implementation of
GetHostedConnectionCredentialsUseCase returns the encrypted password; fetch the
connection via _dbContext.connectionRepository.findOne (as already done), then
call decryptConnectionCredentialsAsync(connection) (the same helper used by
UpdateHostedConnectionPasswordUseCase) before constructing and returning the
HostedConnectionCredentialsRO so the password is decrypted; add the appropriate
import for decryptConnectionCredentialsAsync from
entities/connection/utils/decrypt-connection-credentials-async.js and keep the
existing NotFoundException(Messages.CONNECTION_NOT_FOUND) check.

Comment on lines +184 to 186
const responseBody = JSON.parse(result.text);
console.log('🚀 ~ responseBody:', responseBody);
t.is(result.status, 500);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Asserting HTTP 500 codifies a server-error response for an invalid user id.

A missing/unknown userId is a caller/input problem and should surface as a 4xx (e.g. 404 Not Found or 400 Bad Request), not 500 Internal Server Error. Pinning the test at 500 freezes the buggy behavior in place and will mask a future fix in the use case.

Consider either fixing the upstream use case to throw NotFoundException (consistent with how get-hosted-connection-credentials.use.case.ts handles missing connections) and asserting 404 here, or at minimum leaving a TODO so this doesn't ossify.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@backend/test/ava-tests/saas-tests/hosted-connection-e2e.test.ts` around lines
184 - 186, The test in hosted-connection-e2e.test.ts is asserting a 500 for an
unknown/missing userId (result.status === 500), which should be a 4xx; update
the test to expect the correct client error (preferably 404 to match how
get-hosted-connection-credentials.use.case.ts throws NotFoundException) by
changing the assertion to expect 404 (or, if you cannot update upstream yet,
replace the hard 500 assertion with a TODO comment and a looser check that the
response indicates a client error range) so the test no longer locks in a
server-error behavior.

Comment on lines +143 to +149
test.before(async () => {
const reachable = await isProxyReachable();
if (!reachable) {
console.log(`[postgres-proxy e2e] Proxy not reachable at ${PROXY_HOST}:${PROXY_PORT}, skipping tests`);
skipAll = true;
return;
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Silent skip of the whole suite when the proxy isn't reachable will hide regressions in CI.

If isProxyReachable() returns false (2s TCP timeout), every test short-circuits to t.pass('skipped: proxy not available') — which AVA reports as a passing test. In CI, that means a broken/misconfigured postgres-proxy service, a DNS change, or a port change all produce a green build even though zero behavior was actually exercised.

Recommend gating on an explicit env flag (e.g. PROXY_E2E=1) so the skip path only fires in developer laptops, while CI treats missing proxy as a hard failure. At minimum, emit a visible warning/counter so the "all tests passed" signal isn't misleading.

Also applies to: 184-190

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@backend/test/ava-tests/saas-tests/saas-postgres-proxy-e2e.test.ts` around
lines 143 - 149, The test suite currently silently sets skipAll = true when
isProxyReachable() returns false, causing AVA to report all tests as passed even
when the proxy is down; change the behavior to require an explicit opt-in env
var (e.g., PROXY_E2E) before allowing silent skips: in the test.before and the
corresponding later guard (references: isProxyReachable, PROXY_HOST, PROXY_PORT,
skipAll), check process.env.PROXY_E2E (or similar) and only set skipAll=true
when the env flag is present, otherwise throw or fail the before hook (or call
t.fail) so CI surfaces a hard failure; additionally emit a clear console.warn
including PROXY_HOST:PROXY_PORT and the env decision so skipped runs are
obviously intentional on developer machines.

Comment thread docker-compose.tst.yml Outdated
Comment thread docker-compose.tst.yml Outdated
Comment thread docker-compose.yml
Comment on lines +246 to +289
# === Postgres Proxy E2E Testing ===

testPg-proxy-e2e:
image: postgres:16
ports:
- 9003:5432
environment:
POSTGRES_PASSWORD: proxy_test_123
POSTGRES_HOST_AUTH_METHOD: md5
POSTGRES_INITDB_ARGS: "--auth-host=md5"
command: postgres -c 'max_connections=300' -c 'password_encryption=md5'

proxy-mock-api:
build:
context: ./test/proxy-mock-api
ports:
- 3333:3333
environment:
MOCK_API_PORT: 3333
PROXY_API_KEY: test-proxy-api-key
UPSTREAM_PG_HOST: testPg-proxy-e2e
UPSTREAM_PG_PORT: 5432
UPSTREAM_PG_DATABASE: postgres
UPSTREAM_PG_USERNAME: postgres
UPSTREAM_PG_PASSWORD: proxy_test_123
depends_on:
- testPg-proxy-e2e

postgres-proxy:
build:
context: ../postgres-proxy
ports:
- 9005:5432
- 8085:8080
environment:
PROXY_LISTEN_ADDR: ":5432"
PROXY_HEALTH_ADDR: ":8080"
PROXY_BACKEND_API_URL: "http://proxy-mock-api:3333"
PROXY_API_KEY: "test-proxy-api-key"
PROXY_USAGE_REPORT_INTERVAL: "10s"
PROXY_LOG_LEVEL: "debug"
depends_on:
- proxy-mock-api
- testPg-proxy-e2e
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Missing healthchecks and service_healthy gating — ordering is not what it looks like.

The AI summary describes pg_isready / /healthz healthchecks and depends_on … condition: service_healthy for the new proxy topology, but the committed code uses the short-list depends_on: form (Lines 271‑272, 287‑289) with no healthcheck: blocks on testPg-proxy-e2e, proxy-mock-api, or postgres-proxy.

Consequences:

  • proxy-mock-api can start before Postgres is accepting MD5 auth, and postgres-proxy can start before the mock API is actually listening on :3333, producing flaky E2E startup.
  • The E2E test's isProxyReachable() TCP probe (in saas-postgres-proxy-e2e.test.ts) will silently skip the whole suite if the proxy isn't up yet, which can turn genuine regressions into green builds.

Either (a) update the summary to reflect reality, or (b) add the healthchecks and long-form depends_on so the ordering is real:

🛠️ Proposed healthchecks + gated depends_on
   testPg-proxy-e2e:
     image: postgres:16
     ports:
       - 9003:5432
     environment:
       POSTGRES_PASSWORD: proxy_test_123
       POSTGRES_HOST_AUTH_METHOD: md5
       POSTGRES_INITDB_ARGS: "--auth-host=md5"
     command: postgres -c 'max_connections=300' -c 'password_encryption=md5'
+    healthcheck:
+      test: ["CMD-SHELL", "pg_isready -U postgres -d postgres"]
+      interval: 5s
+      timeout: 5s
+      retries: 20

   proxy-mock-api:
     build:
       context: ./test/proxy-mock-api
     ports:
       - 3333:3333
     environment:
       MOCK_API_PORT: 3333
       PROXY_API_KEY: test-proxy-api-key
       UPSTREAM_PG_HOST: testPg-proxy-e2e
       UPSTREAM_PG_PORT: 5432
       UPSTREAM_PG_DATABASE: postgres
       UPSTREAM_PG_USERNAME: postgres
       UPSTREAM_PG_PASSWORD: proxy_test_123
-    depends_on:
-      - testPg-proxy-e2e
+    healthcheck:
+      test: ["CMD", "wget", "--spider", "-q", "http://localhost:3333/healthz"]
+      interval: 5s
+      timeout: 5s
+      retries: 20
+    depends_on:
+      testPg-proxy-e2e:
+        condition: service_healthy

   postgres-proxy:
     build:
       context: ../postgres-proxy
     ports:
       - 9005:5432
       - 8085:8080
     environment:
       PROXY_LISTEN_ADDR: ":5432"
       PROXY_HEALTH_ADDR: ":8080"
       PROXY_BACKEND_API_URL: "http://proxy-mock-api:3333"
       PROXY_API_KEY: "test-proxy-api-key"
       PROXY_USAGE_REPORT_INTERVAL: "10s"
       PROXY_LOG_LEVEL: "debug"
-    depends_on:
-      - proxy-mock-api
-      - testPg-proxy-e2e
+    healthcheck:
+      test: ["CMD", "wget", "--spider", "-q", "http://localhost:8080/healthz"]
+      interval: 5s
+      timeout: 5s
+      retries: 20
+    depends_on:
+      proxy-mock-api:
+        condition: service_healthy
+      testPg-proxy-e2e:
+        condition: service_healthy
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
# === Postgres Proxy E2E Testing ===
testPg-proxy-e2e:
image: postgres:16
ports:
- 9003:5432
environment:
POSTGRES_PASSWORD: proxy_test_123
POSTGRES_HOST_AUTH_METHOD: md5
POSTGRES_INITDB_ARGS: "--auth-host=md5"
command: postgres -c 'max_connections=300' -c 'password_encryption=md5'
proxy-mock-api:
build:
context: ./test/proxy-mock-api
ports:
- 3333:3333
environment:
MOCK_API_PORT: 3333
PROXY_API_KEY: test-proxy-api-key
UPSTREAM_PG_HOST: testPg-proxy-e2e
UPSTREAM_PG_PORT: 5432
UPSTREAM_PG_DATABASE: postgres
UPSTREAM_PG_USERNAME: postgres
UPSTREAM_PG_PASSWORD: proxy_test_123
depends_on:
- testPg-proxy-e2e
postgres-proxy:
build:
context: ../postgres-proxy
ports:
- 9005:5432
- 8085:8080
environment:
PROXY_LISTEN_ADDR: ":5432"
PROXY_HEALTH_ADDR: ":8080"
PROXY_BACKEND_API_URL: "http://proxy-mock-api:3333"
PROXY_API_KEY: "test-proxy-api-key"
PROXY_USAGE_REPORT_INTERVAL: "10s"
PROXY_LOG_LEVEL: "debug"
depends_on:
- proxy-mock-api
- testPg-proxy-e2e
# === Postgres Proxy E2E Testing ===
testPg-proxy-e2e:
image: postgres:16
ports:
- 9003:5432
environment:
POSTGRES_PASSWORD: proxy_test_123
POSTGRES_HOST_AUTH_METHOD: md5
POSTGRES_INITDB_ARGS: "--auth-host=md5"
command: postgres -c 'max_connections=300' -c 'password_encryption=md5'
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres -d postgres"]
interval: 5s
timeout: 5s
retries: 20
proxy-mock-api:
build:
context: ./test/proxy-mock-api
ports:
- 3333:3333
environment:
MOCK_API_PORT: 3333
PROXY_API_KEY: test-proxy-api-key
UPSTREAM_PG_HOST: testPg-proxy-e2e
UPSTREAM_PG_PORT: 5432
UPSTREAM_PG_DATABASE: postgres
UPSTREAM_PG_USERNAME: postgres
UPSTREAM_PG_PASSWORD: proxy_test_123
healthcheck:
test: ["CMD", "wget", "--spider", "-q", "http://localhost:3333/healthz"]
interval: 5s
timeout: 5s
retries: 20
depends_on:
testPg-proxy-e2e:
condition: service_healthy
postgres-proxy:
build:
context: ../postgres-proxy
ports:
- 9005:5432
- 8085:8080
environment:
PROXY_LISTEN_ADDR: ":5432"
PROXY_HEALTH_ADDR: ":8080"
PROXY_BACKEND_API_URL: "http://proxy-mock-api:3333"
PROXY_API_KEY: "test-proxy-api-key"
PROXY_USAGE_REPORT_INTERVAL: "10s"
PROXY_LOG_LEVEL: "debug"
healthcheck:
test: ["CMD", "wget", "--spider", "-q", "http://localhost:8080/healthz"]
interval: 5s
timeout: 5s
retries: 20
depends_on:
proxy-mock-api:
condition: service_healthy
testPg-proxy-e2e:
condition: service_healthy
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docker-compose.yml` around lines 246 - 289, The docker-compose services lack
healthcheck blocks and still use short-form depends_on, causing startup races;
add healthchecks and switch to long-form depends_on with condition:
service_healthy so ordering is enforced: add a healthcheck for testPg-proxy-e2e
using pg_isready (or a TCP probe to 5432) that retries until MD5 auth is active,
add an HTTP/ TCP healthcheck for proxy-mock-api (e.g., hit /healthz or TCP 3333)
and for postgres-proxy (HTTP /healthz or TCP 5432), then replace the short
depends_on entries for proxy-mock-api and postgres-proxy with the long-form
depends_on referencing testPg-proxy-e2e and proxy-mock-api and requiring
condition: service_healthy so isProxyReachable in
saas-postgres-proxy-e2e.test.ts won’t skip flaky startups.

Comment on lines +1 to +8
FROM node:24-alpine

WORKDIR /app
COPY server.js .

EXPOSE 3333

CMD ["node", "server.js"]
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Run the container as a non-root user.

Even for a test-only image, running as root is flagged by Trivy (DS-0002) and is trivial to fix — the node base images already ship a node user.

🛡️ Proposed fix
 FROM node:24-alpine

 WORKDIR /app
 COPY server.js .

 EXPOSE 3333

+USER node
 CMD ["node", "server.js"]
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
FROM node:24-alpine
WORKDIR /app
COPY server.js .
EXPOSE 3333
CMD ["node", "server.js"]
FROM node:24-alpine
WORKDIR /app
COPY server.js .
EXPOSE 3333
USER node
CMD ["node", "server.js"]
🧰 Tools
🪛 Trivy (0.69.3)

[error] 1-1: Image user should not be 'root'

Specify at least 1 USER command in Dockerfile with non-root user as argument

Rule: DS-0002

Learn more

(IaC/Dockerfile)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/proxy-mock-api/Dockerfile` around lines 1 - 8, The Dockerfile currently
runs the container as root; update it to switch to the non-root node user
provided by the node base image by adding a USER directive (e.g., USER node)
after copying files and before CMD, and ensure file permissions are correct by
chowning or copying files as that user if necessary (adjust COPY/ownership steps
so server.js is readable/executable by the node user and avoid running processes
as root in CMD).

Comment on lines +62 to +73
if (req.method === 'PUT' && parsedUrl.pathname === '/api/test/subscription-level') {
let body = '';
req.on('data', (chunk) => (body += chunk));
req.on('end', () => {
const parsed = JSON.parse(body);
currentSubscriptionLevel = parsed.subscriptionLevel;
console.log(`[mock-api] Subscription level changed to: ${currentSubscriptionLevel}`);
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ ok: true, subscriptionLevel: currentSubscriptionLevel }));
});
return;
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

JSON.parse on request body can crash the handler — no response will be sent.

If a test (or a misbehaving caller) posts a malformed body to PUT /api/test/subscription-level, JSON.parse(body) throws inside the 'end' callback, the server never calls res.end, and the Node process emits uncaughtException. In the containerized test topology that means the mock-api may die mid-run and subsequent E2E assertions become flaky for reasons unrelated to the code under test.

Contrast with POST /api/proxy/usage at Lines 136-145, which already wraps its parse in try/catch.

🛡️ Proposed fix
   if (req.method === 'PUT' && parsedUrl.pathname === '/api/test/subscription-level') {
     let body = '';
     req.on('data', (chunk) => (body += chunk));
     req.on('end', () => {
-      const parsed = JSON.parse(body);
-      currentSubscriptionLevel = parsed.subscriptionLevel;
-      console.log(`[mock-api] Subscription level changed to: ${currentSubscriptionLevel}`);
-      res.writeHead(200, { 'Content-Type': 'application/json' });
-      res.end(JSON.stringify({ ok: true, subscriptionLevel: currentSubscriptionLevel }));
+      try {
+        const parsed = JSON.parse(body);
+        currentSubscriptionLevel = parsed.subscriptionLevel;
+        console.log(`[mock-api] Subscription level changed to: ${currentSubscriptionLevel}`);
+        res.writeHead(200, { 'Content-Type': 'application/json' });
+        res.end(JSON.stringify({ ok: true, subscriptionLevel: currentSubscriptionLevel }));
+      } catch (e) {
+        res.writeHead(400, { 'Content-Type': 'application/json' });
+        res.end(JSON.stringify({ error: `Invalid JSON: ${e.message}` }));
+      }
     });
     return;
   }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/proxy-mock-api/server.js` around lines 62 - 73, The PUT
/api/test/subscription-level handler calls JSON.parse(body) unguarded which can
throw and crash the server; wrap the parse and subsequent handling in a
try/catch around JSON.parse (and the parsed usage of currentSubscriptionLevel)
and on error log the failure and send an HTTP error response (e.g., 400 with a
JSON error body) before calling res.end so the request always completes; follow
the pattern used in the POST /api/proxy/usage handler (try/catch around
JSON.parse) to locate and implement the fix.

Copilot AI review requested due to automatic review settings April 20, 2026 08:29
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds infrastructure and backend API support for exercising a Postgres proxy flow end-to-end, including a mock backend API, Docker Compose wiring, and AVA e2e coverage. Also introduces a SaaS endpoint to fetch hosted connection credentials and tags Postgres connections with an application_name.

Changes:

  • Added Postgres proxy E2E environment (proxy + upstream Postgres + mock API) and comprehensive AVA tests for proxy CRUD/usage/budget scenarios.
  • Added SaaS endpoint /saas/connection/hosted/credentials with DTO/RO + use-case wiring.
  • Set application_name: 'rocketadmin' for Postgres connections created via Knex.

Reviewed changes

Copilot reviewed 14 out of 14 changed files in this pull request and generated 8 comments.

Show a summary per file
File Description
test/proxy-mock-api/server.js Implements a mock backend API for the proxy contract + test-only endpoints for subscription level/usage reports.
test/proxy-mock-api/Dockerfile Containerizes the proxy mock API used by compose-based E2E runs.
shared-code/src/knex-manager/knex-manager.ts Adds application_name to Postgres driver connection config for better observability.
docker-compose.yml Wires up upstream Postgres, proxy mock API, and the Go postgres-proxy service for E2E testing.
docker-compose.tst.yml Updates test compose command/formatting and healthchecks to support revised test execution.
backend/test/ava-tests/saas-tests/saas-postgres-proxy-e2e.test.ts New AVA E2E suite validating RocketAdmin functionality through the Postgres proxy plus usage/budget behaviors.
backend/test/ava-tests/saas-tests/hosted-connection-e2e.test.ts Updates hosted connection E2E payload/expectations to include hostedDatabaseId and align response fields.
backend/src/microservices/saas-microservice/use-cases/saas-use-cases.interface.ts Exposes a new use-case interface for hosted connection credential retrieval.
backend/src/microservices/saas-microservice/use-cases/get-hosted-connection-credentials.use.case.ts Implements hosted connection credential lookup for the new SaaS endpoint.
backend/src/microservices/saas-microservice/saas.module.ts Registers the new use-case provider and route in the SaaS module.
backend/src/microservices/saas-microservice/saas.controller.ts Adds the /saas/connection/hosted/credentials controller endpoint with Swagger metadata.
backend/src/microservices/saas-microservice/data-structures/hosted-connection-credentials.ro.ts Defines the response object returned by the new credentials endpoint.
backend/src/microservices/saas-microservice/data-structures/get-hosted-connection-credentials.dto.ts Defines request DTO for the credentials endpoint.
backend/src/common/data-injection.tokens.ts Adds a new DI token for the hosted-credentials use-case.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +22 to +25
protected async implementation(inputData: GetHostedConnectionCredentialsDto): Promise<HostedConnectionCredentialsRO> {
const connection = await this._dbContext.connectionRepository.findOne({
where: { id: inputData.hostedDatabaseId },
});
Copy link

Copilot AI Apr 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

connectionRepository.findOne() returns the ConnectionEntity with encrypted credentials (host/username/password/database) because no decrypt helper is called. As a result this endpoint will return encrypted values instead of the advertised decrypted credentials. Use a repository method that decrypts (e.g., connectionRepository.findOneById() / findOneConnection() / findAndDecryptConnection() as appropriate) before building the response, and consider how to handle masterEncryption connections.

Suggested change
protected async implementation(inputData: GetHostedConnectionCredentialsDto): Promise<HostedConnectionCredentialsRO> {
const connection = await this._dbContext.connectionRepository.findOne({
where: { id: inputData.hostedDatabaseId },
});
private async findHostedConnectionWithDecryptedCredentials(hostedDatabaseId: string) {
const connectionRepository = this._dbContext.connectionRepository as typeof this._dbContext.connectionRepository & {
findOneById?: (id: string) => Promise<any>;
findOneConnection?: (id: string) => Promise<any>;
findAndDecryptConnection?: (id: string) => Promise<any>;
};
if (typeof connectionRepository.findAndDecryptConnection === 'function') {
return connectionRepository.findAndDecryptConnection(hostedDatabaseId);
}
if (typeof connectionRepository.findOneConnection === 'function') {
return connectionRepository.findOneConnection(hostedDatabaseId);
}
if (typeof connectionRepository.findOneById === 'function') {
return connectionRepository.findOneById(hostedDatabaseId);
}
throw new Error('Connection repository does not expose a decryption-aware lookup method.');
}
protected async implementation(inputData: GetHostedConnectionCredentialsDto): Promise<HostedConnectionCredentialsRO> {
const connection = await this.findHostedConnectionWithDecryptedCredentials(inputData.hostedDatabaseId);

Copilot uses AI. Check for mistakes.
export class GetHostedConnectionCredentialsDto {
@ApiProperty()
@IsString()
@IsNotEmpty()
Copy link

Copilot AI Apr 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This DTO only accepts hostedDatabaseId. Since the SaaS auth middleware validates only that a JWT is signed (no tenant scoping), an internal caller could fetch credentials for any connection ID. Consider requiring companyId (or other tenant identifier) and validating the connection belongs to that tenant before returning credentials.

Suggested change
@IsNotEmpty()
@IsNotEmpty()
companyId: string;
@ApiProperty()
@IsString()
@IsNotEmpty()

Copilot uses AI. Check for mistakes.
Comment on lines 76 to 101
// Create hosted connection via SaaS endpoint
const createHostedConnectionResult = await request(app.getHttpServer())
.post('/saas/connection/hosted')
.send({
companyId: companyId,
userId: userId,
hostedDatabaseId: faker.string.uuid(),
databaseName: 'postgres',
hostname: 'testPg-e2e-testing',
port: 5432,
username: 'postgres',
password: '123',
})
.set('Authorization', `Bearer ${saasToken}`)
.set('Content-Type', 'application/json')
.set('Accept', 'application/json');

t.is(createHostedConnectionResult.status, 201);

const createdConnection = JSON.parse(createHostedConnectionResult.text);
const connectionId = createdConnection.id;
console.log('🚀 ~ createdConnection:', createdConnection);

t.is(createHostedConnectionResult.status, 201);
const connectionId = createdConnection.connectionId;

// Verify connection was created
t.truthy(connectionId);
t.is(createdConnection.type, 'postgres');
t.is(createdConnection.database, 'postgres');
t.is(createdConnection.host, 'testPg-e2e-testing');
t.is(createdConnection.port, 5432);

// Verify admin group was created
t.truthy(createdConnection.groups);
t.is(createdConnection.groups.length, 1);
const adminGroup = createdConnection.groups[0];
t.truthy(adminGroup.id);
t.is(adminGroup.isMain, true);

Copy link

Copilot AI Apr 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new hosted-connection credentials endpoint (/saas/connection/hosted/credentials) is introduced, but this e2e suite doesn’t exercise it. Adding a test that creates a hosted connection, calls the credentials endpoint, and asserts the returned credentials are decrypted (and that auth is enforced) would prevent regressions.

Copilot uses AI. Check for mistakes.
Comment thread docker-compose.tst.yml
Comment on lines +114 to +117
"/opt/mssql-tools18/bin/sqlcmd -S localhost -U sa -P
'yNuXf@6T#BgoQ%U6knMp' -C -Q 'SELECT 1' ||
/opt/mssql-tools/bin/sqlcmd -S localhost -U sa -P
'yNuXf@6T#BgoQ%U6knMp' -Q 'SELECT 1'",
Copy link

Copilot AI Apr 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This MSSQL healthcheck command is now split across multiple lines inside a double-quoted YAML string. YAML preserves the newlines, so the shell will see them as command separators and sqlcmd will be invoked with a dangling -P (then the next line will be treated as a separate command), breaking the healthcheck. Keep the command on one line, or use a folded scalar (>) / explicit \ line continuations so the shell receives a single valid command string.

Suggested change
"/opt/mssql-tools18/bin/sqlcmd -S localhost -U sa -P
'yNuXf@6T#BgoQ%U6knMp' -C -Q 'SELECT 1' ||
/opt/mssql-tools/bin/sqlcmd -S localhost -U sa -P
'yNuXf@6T#BgoQ%U6knMp' -Q 'SELECT 1'",
"/opt/mssql-tools18/bin/sqlcmd -S localhost -U sa -P 'yNuXf@6T#BgoQ%U6knMp' -C -Q 'SELECT 1' || /opt/mssql-tools/bin/sqlcmd -S localhost -U sa -P 'yNuXf@6T#BgoQ%U6knMp' -Q 'SELECT 1'",

Copilot uses AI. Check for mistakes.
Comment thread docker-compose.yml
Comment on lines +246 to +289
# === Postgres Proxy E2E Testing ===

testPg-proxy-e2e:
image: postgres:16
ports:
- 9003:5432
environment:
POSTGRES_PASSWORD: proxy_test_123
POSTGRES_HOST_AUTH_METHOD: md5
POSTGRES_INITDB_ARGS: "--auth-host=md5"
command: postgres -c 'max_connections=300' -c 'password_encryption=md5'

proxy-mock-api:
build:
context: ./test/proxy-mock-api
ports:
- 3333:3333
environment:
MOCK_API_PORT: 3333
PROXY_API_KEY: test-proxy-api-key
UPSTREAM_PG_HOST: testPg-proxy-e2e
UPSTREAM_PG_PORT: 5432
UPSTREAM_PG_DATABASE: postgres
UPSTREAM_PG_USERNAME: postgres
UPSTREAM_PG_PASSWORD: proxy_test_123
depends_on:
- testPg-proxy-e2e

postgres-proxy:
build:
context: ../postgres-proxy
ports:
- 9005:5432
- 8085:8080
environment:
PROXY_LISTEN_ADDR: ":5432"
PROXY_HEALTH_ADDR: ":8080"
PROXY_BACKEND_API_URL: "http://proxy-mock-api:3333"
PROXY_API_KEY: "test-proxy-api-key"
PROXY_USAGE_REPORT_INTERVAL: "10s"
PROXY_LOG_LEVEL: "debug"
depends_on:
- proxy-mock-api
- testPg-proxy-e2e
Copy link

Copilot AI Apr 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Postgres proxy E2E services added here will be started/built on a default docker compose up, and postgres-proxy depends on a sibling ../postgres-proxy checkout. To avoid breaking normal dev workflows (and unnecessary resource usage/port conflicts), consider placing these services behind a Compose profiles entry or moving them into a dedicated compose file used only for proxy E2E runs.

Copilot uses AI. Check for mistakes.
Comment on lines +66 to +71
const parsed = JSON.parse(body);
currentSubscriptionLevel = parsed.subscriptionLevel;
console.log(`[mock-api] Subscription level changed to: ${currentSubscriptionLevel}`);
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ ok: true, subscriptionLevel: currentSubscriptionLevel }));
});
Copy link

Copilot AI Apr 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

JSON.parse(body) here is not wrapped in a try/catch. If the request body is empty or invalid JSON, the exception will crash the mock API process (and likely the whole e2e run). Handle parse failures and return a 400 with a helpful error message instead of throwing.

Copilot uses AI. Check for mistakes.
Comment on lines +135 to +148
console.log(`[mock-api] POST usage: ${body}`);
try {
const reports = JSON.parse(body);
if (Array.isArray(reports)) {
usageReports.push(...reports);
} else {
usageReports.push(reports);
}
} catch (e) {
console.error(`[mock-api] Failed to parse usage body: ${e.message}`);
}
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ ok: true }));
});
Copy link

Copilot AI Apr 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If parsing the usage body fails, the handler logs an error but still returns 200 { ok: true }. That can mask proxy/reporting bugs and make tests falsely pass. Consider returning a 400 on JSON parse errors (or at least ok: false) so failures are surfaced.

Copilot uses AI. Check for mistakes.
Comment on lines +511 to +515
// Wait for the proxy's usage report interval (configured at 10s in docker-compose)
// plus a small buffer for the HTTP call to complete
await sleep(12000);

const reports = await getUsageReports();
Copy link

Copilot AI Apr 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test uses a fixed sleep(12000) to wait for usage reporting. That makes the suite slower and can still be flaky if the report interval drifts. Prefer polling getUsageReports() until a new relevant report arrives (with an overall timeout based on the configured report interval) so the test proceeds as soon as the report is available.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♻️ Duplicate comments (1)
docker-compose.yml (1)

248-289: ⚠️ Potential issue | 🟡 Minor

Add readiness healthchecks before relying on this E2E topology.

The new services still use short-form depends_on, so Compose starts containers in order but does not wait for Postgres auth, the mock API, or the proxy health endpoint to be ready. This can make proxy E2E startup flaky.

Suggested direction
   testPg-proxy-e2e:
     image: postgres:16
@@
     command: postgres -c 'max_connections=300' -c 'password_encryption=md5'
+    healthcheck:
+      test: ["CMD-SHELL", "pg_isready -U postgres -d postgres"]
+      interval: 5s
+      timeout: 5s
+      retries: 20

   proxy-mock-api:
@@
-    depends_on:
-      - testPg-proxy-e2e
+    healthcheck:
+      test: ["CMD-SHELL", "wget --spider -q http://localhost:3333/healthz || nc -z localhost 3333"]
+      interval: 5s
+      timeout: 5s
+      retries: 20
+    depends_on:
+      testPg-proxy-e2e:
+        condition: service_healthy

   postgres-proxy:
@@
-    depends_on:
-      - proxy-mock-api
-      - testPg-proxy-e2e
+    healthcheck:
+      test: ["CMD-SHELL", "wget --spider -q http://localhost:8080/healthz || nc -z localhost 5432"]
+      interval: 5s
+      timeout: 5s
+      retries: 20
+    depends_on:
+      proxy-mock-api:
+        condition: service_healthy
+      testPg-proxy-e2e:
+        condition: service_healthy

Adjust the probe command to tools actually available in each image.

Run this read-only check to confirm whether the proxy topology still lacks health-gated dependencies:

#!/bin/bash
# Verifies healthcheck and depends_on shape for the Postgres proxy E2E services.

python3 - <<'PY'
from pathlib import Path
import re

text = Path("docker-compose.yml").read_text()

def service_block(name: str) -> str:
    pattern = rf"^  {re.escape(name)}:\n(?P<body>.*?)(?=^  [A-Za-z0-9_.-]+:|\Z)"
    match = re.search(pattern, text, re.M | re.S)
    return match.group("body") if match else ""

for service in ["testPg-proxy-e2e", "proxy-mock-api", "postgres-proxy"]:
    body = service_block(service)
    print(f"\n[{service}]")
    print("healthcheck:", "present" if re.search(r"^\s+healthcheck:", body, re.M) else "missing")
    print("service_healthy:", "present" if "condition: service_healthy" in body else "missing")
    print("short depends_on list:", "present" if re.search(r"^\s+depends_on:\n\s+- ", body, re.M) else "not detected")
PY
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docker-compose.yml` around lines 248 - 289, The Compose services lack
healthchecks so startup order via depends_on is flaky; add healthcheck sections
and make postgres-proxy depend on healthy services: for testPg-proxy-e2e add a
healthcheck using pg_isready (service name testPg-proxy-e2e), for proxy-mock-api
add a healthcheck that curls its health endpoint (proxy-mock-api, use
MOCK_API_PORT/3333 and e.g. /health or /ready), and for postgres-proxy add a
healthcheck that probes its PROXY_HEALTH_ADDR (port 8080) and change
postgres-proxy's depends_on to wait for service_healthy for both
testPg-proxy-e2e and proxy-mock-api so the proxy only starts when both backends
report healthy.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@docker-compose.yml`:
- Around line 248-289: The Compose services lack healthchecks so startup order
via depends_on is flaky; add healthcheck sections and make postgres-proxy depend
on healthy services: for testPg-proxy-e2e add a healthcheck using pg_isready
(service name testPg-proxy-e2e), for proxy-mock-api add a healthcheck that curls
its health endpoint (proxy-mock-api, use MOCK_API_PORT/3333 and e.g. /health or
/ready), and for postgres-proxy add a healthcheck that probes its
PROXY_HEALTH_ADDR (port 8080) and change postgres-proxy's depends_on to wait for
service_healthy for both testPg-proxy-e2e and proxy-mock-api so the proxy only
starts when both backends report healthy.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: a600598d-2146-4509-86d8-0004d01639d5

📥 Commits

Reviewing files that changed from the base of the PR and between d921aa1 and e464e21.

📒 Files selected for processing (5)
  • docker-compose.tst.yml
  • docker-compose.yml
  • frontend/src/app/components/connect-db/connect-db.component.css
  • frontend/src/app/components/connect-db/connect-db.component.ts
  • frontend/src/app/validators/connection-string.validator.ts
💤 Files with no reviewable changes (1)
  • frontend/src/app/components/connect-db/connect-db.component.css
✅ Files skipped from review due to trivial changes (2)
  • frontend/src/app/validators/connection-string.validator.ts
  • frontend/src/app/components/connect-db/connect-db.component.ts

@Artuomka Artuomka enabled auto-merge April 23, 2026 08:42
@Artuomka Artuomka merged commit 0ac237d into main Apr 23, 2026
17 of 19 checks passed
@Artuomka Artuomka deleted the backend_pg_proxy branch April 23, 2026 08:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants