Skip to content

feat: add integration test infrastructure with E2E SMS tests#879

Open
AchoArnold wants to merge 15 commits intomainfrom
feature/integration-tests
Open

feat: add integration test infrastructure with E2E SMS tests#879
AchoArnold wants to merge 15 commits intomainfrom
feature/integration-tests

Conversation

@AchoArnold
Copy link
Copy Markdown
Member

Adds integration test setup with phone emulator, Docker Compose stack, and E2E SMS send/receive tests. CI workflow runs before deployment.

AchoArnold and others added 6 commits May 3, 2026 18:16
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@gitguardian
Copy link
Copy Markdown

gitguardian Bot commented May 3, 2026

⚠️ GitGuardian has uncovered 1 secret following the scan of your pull request.

Please consider investigating the findings and remediating the incidents. Failure to do so may lead to compromising the associated services or software components.

🔎 Detected hardcoded secret in your pull request
GitGuardian id GitGuardian status Secret Commit Filename
32442174 Triggered RSA Private Key 5e2aae1 tests/firebase-credentials.json View secret
🛠 Guidelines to remediate hardcoded secrets
  1. Understand the implications of revoking this secret by investigating where it is used in your code.
  2. Replace and store your secret safely. Learn here the best practices.
  3. Revoke and rotate this secret.
  4. If possible, rewrite git history. Rewriting git history is not a trivial act. You might completely break other contributing developers' workflow and you risk accidentally deleting legitimate data.

To avoid such incidents in the future consider


🦉 GitGuardian detects secrets in your source code to help developers and security teams secure the modern development process. You are seeing this because you or someone else with access to this repository has authorized GitGuardian to scan your pull request.

Comment thread .github/workflows/integration-test.yml Fixed
@greptile-apps
Copy link
Copy Markdown

greptile-apps Bot commented May 3, 2026

Greptile Summary

This PR adds an integration test infrastructure with a Go-based phone emulator, Docker Compose stack, and two E2E tests (send and receive SMS) that run in CI before deployment.

  • P1 — Go version mismatch: tests/go.mod declares go 1.25.0 but the CI workflow installs Go 1.22; the toolchain mismatch will break the integration test step.
  • P1 — RoundTripper contract violation: fcmRedirectTransport.RoundTrip mutates req.URL in-place; the http.RoundTripper contract prohibits request mutation, which can cause data races on retries.

Confidence Score: 3/5

Not safe to merge as-is — the Go version mismatch will break CI and the RoundTripper mutation needs fixing.

Two P1 findings: the go.mod/CI toolchain mismatch will cause the integration test step to fail on every run, and the RoundTripper contract violation can produce data races. These both need to be resolved before the PR can function as intended.

tests/go.mod (go version), api/pkg/di/fcm_transport.go (request mutation), tests/firebase-credentials.json (committed key material)

Security Review

  • Committed RSA private key (tests/firebase-credentials.json): An RSA private key is embedded directly in the repository. Although it is labelled as a test credential, it will trigger secret-scanning tools and sets a precedent for committing key material. Consider generating the credential dynamically in CI from a GitHub Actions secret instead.

Important Files Changed

Filename Overview
api/pkg/di/fcm_transport.go New RoundTripper that redirects FCM traffic to the emulator; mutates the request URL in place, violating the http.RoundTripper contract and risking data races.
tests/go.mod Declares go 1.25.0 while CI installs Go 1.22; toolchain mismatch will cause the integration test step to fail or require an unplanned toolchain download.
tests/firebase-credentials.json Fake service-account JSON with an embedded RSA private key; will trigger secret-scanning tools despite being test-only.
tests/integration_test.go E2E tests for SMS send and receive; uses bare type assertions that will panic on unexpected API responses instead of failing cleanly.
tests/docker-compose.yml Compose stack for integration tests; redis:latest is unpinned and could introduce instability across runs.
.github/workflows/integration-test.yml New CI workflow that builds the stack, seeds data, and runs E2E tests before deployment; go-version: "1.22" conflicts with tests/go.mod requiring 1.25.0.
tests/helpers_test.go Test helpers including a polling helper for message status; implementation is clean.
tests/seed.sql Seeds test user, system user, phone, and phone API key using idempotent ON CONFLICT DO NOTHING; looks correct.

Sequence Diagram

sequenceDiagram
    participant CI as GitHub Actions CI
    participant API as API (Docker)
    participant Emulator as Phone Emulator
    participant DB as PostgreSQL

    CI->>API: docker compose up (build + wait)
    CI->>DB: seed.sql (users, phone, api_key)
    CI->>API: POST /v1/messages/send (userAPIKey)
    API->>Emulator: FCM push (via fcmRedirectTransport)
    Emulator-->>API: 200 fake-message-id
    Emulator->>API: GET /v1/messages/outstanding (phoneAPIKey)
    API-->>Emulator: [pending messages]
    Emulator->>API: POST /v1/messages/{id}/events (SENT)
    Emulator->>API: POST /v1/messages/{id}/events (DELIVERED)
    CI->>API: GET /v1/messages/{id} poll until delivered
    API-->>CI: 200 status=delivered
    CI->>API: POST /v1/messages/receive (phoneAPIKey)
    API-->>CI: 200 status=received
    CI->>API: GET /v1/messages/{id}
    API-->>CI: 200 status=received
Loading

Reviews (1): Last reviewed commit: "ci: add integration test workflow" | Re-trigger Greptile

Comment thread api/pkg/di/fcm_transport.go Outdated
Comment on lines +15 to +18
func (t *fcmRedirectTransport) RoundTrip(req *http.Request) (*http.Response, error) {
req.URL.Scheme = t.target.Scheme
req.URL.Host = t.target.Host
return t.base.RoundTrip(req)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 RoundTrip mutates the shared request object

The http.RoundTripper contract explicitly states that an implementation must not modify the request (only consume/close the Body). Directly writing to req.URL.Scheme and req.URL.Host mutates the caller's *http.Request, which can cause data races when the Firebase SDK reuses or retries the same request. The URL should be cloned before modification.

Suggested change
func (t *fcmRedirectTransport) RoundTrip(req *http.Request) (*http.Response, error) {
req.URL.Scheme = t.target.Scheme
req.URL.Host = t.target.Host
return t.base.RoundTrip(req)
func (t *fcmRedirectTransport) RoundTrip(req *http.Request) (*http.Response, error) {
cloned := req.Clone(req.Context())
cloned.URL.Scheme = t.target.Scheme
cloned.URL.Host = t.target.Host
return t.base.RoundTrip(cloned)
}

Comment thread tests/go.mod Outdated
@@ -0,0 +1,11 @@
module github.com/NdoleStudio/httpsms/tests

go 1.25.0
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Go version mismatch with CI toolchain

tests/go.mod declares go 1.25.0 but the CI workflow installs go-version: "1.22". With Go 1.21+ toolchain management, running go test ./... under Go 1.22 on a module requiring 1.25.0 will cause the toolchain to attempt to download Go 1.25 (GOTOOLCHAIN=auto) or fail outright (GOTOOLCHAIN=local). The emulator's go.mod correctly uses go 1.22; tests/go.mod should match or the CI should pin a version that satisfies this directive.

Suggested change
go 1.25.0
go 1.22

Comment thread tests/firebase-credentials.json Outdated
Comment on lines +1 to +12
{
"type": "service_account",
"project_id": "httpsms-test",
"private_key_id": "test-key-id",
"private_key": "-----BEGIN RSA PRIVATE KEY-----\\nMIIEogIBAAKCAQEAqx29E5Eea63wVdoWUiFetrCw0B/wpsi2/mD6eHmkeue6n0mS\\noVpw5Mb11z/iiDUDL1eX6b3oPdCruaKfvnJ4t2J2yXAT6hoPy+RiWJDRUZazHyKH\\nx68YkRDI1xMhFpn8CbC16vW9DGz2mSdEwI3M8VDxw0UDcCgB3U7WdsBuIaXS6Vs0\\n9pohYuRY3yk+CC6Q5MBh5RlRpO4xOQ2JHNJkMkG+av952Uro7J8/b1cnH3dMRpbO\\nDXtXgq0bSjuJVbd8c9ymDHcl7A5uAKdu8HCd5xbYVp9yKTzwanl2Kxx3Dphp8YaL\\nmJ1k5bgXaHJ2fhx7ESw2gv5lhTbxuMtd4esqdQIDAQABAoIBAHOqqoBre/C1ptuh\\ni60AuZEsZpiIvpc+3dOdojGFqFUcBt5dUSyYge9jPhK+MFZ53ylFQH7TzATc5Pea\\nofiOUGNFv53ykMOR0lO0kXXkjllkULgfE0E7bpPAkMIxQBCTDfdO5+lnKt8XWKm2\\nDZdLQtlsKcAhCm3p3TjHbdjfwpIi9VTV7bDoS/XbNi/vDKrL5KaH8w8zmg3M5VPV\\nuXwM5JempH4IfF0O57ZaZPjgeGgeSt3ZBBha8gD5xsVbnrpN0uIa8HBfx65sK9kX\\ny5mD9yBezvRZTYvNIZAajGl5kY/G0sOMSJZ+K6CwRxUT11Cnph9F2cPzX40vszY9\\noN1CEpECgYEAwXB5OFQ832xV8IS2zgztJHb6Jb+jdhFbz8uCy7rpc99L1m8XdprA\\nYAgjfXVQcoQrhxWKRN+RiWzNb2iNBqMujP1ebUi212JGa5QT5zX7YkExXOyL0b1U\\nead7hu+zox8MXi5cnxn/OWHpCV5VuSLxe1X/DRxl3/yQNWtthC3rSfMCgYEA4nUM\\nyA3Wj5I9zhCWSRdWUqQ0/lF6tGNiAXMeQLxGvHlESes8EYssCSDBaFE7MUlEllwL\\nNQJetSr0uxK9xBoiOmEfyCDtT8YQ/nDmoAPIGNWv84xsm+JzN1OfZAJxP1IhfmBl\\n+eKk6EeSQ213oEZBODsSksWvUOW9gGw+WYNZK/cCgYAi0rKf11pWBlS6Rcn68gzG\\n4bxKi2NL3/gdQk+7iVx5hZtLcRhSO62iHBT3guUGmJCVcuU/XNgAW1voUHQC2+GH\\ndB1Joyt3PrParf+fQfKNT5spVVQeX+0TMXllY0V9ehtp4QK/iZiOpfejvh3EhGvg\\nfiy6GYClf/weAcbnYrTWzQKBgEDnKS2hAbCOSlZn3JrALIZs9583/QH8RdeChdYp\\n3+AepVrGJ6YjjBBlqeja9ysOA7FQWgnsTvZ/hDqliWvNzaeLtI8oGLu3WaGC/CY6\\nTzwG6nHT+kDHKxxdRB5msMxkHqNYv4FZ2seT07CtjA6MdB7Il28nZzK5VZGb9Fa1\\nVXZLAoGAJRTsLzFyjlj/yCEhDLjeawRKbaVCQp5Qvb+wCqLpH1Q4u9tDg5e7A0H3\\nOXOUNUAOv7ufpofSWzKnujvjEURhScKnxiuAz/7a0Cd9Cen3ZK3vyaUr1AcvqNOE\\nCygZOEMRktGpP9zFX+RZV5jqGBEMkFft+IZ1bRwyUYsxKxTByqc=\\n-----END RSA PRIVATE KEY-----\\n",
"client_email": "test@httpsms-test.iam.gserviceaccount.com",
"client_id": "123456789",
"auth_uri": "http://emulator:9090/auth",
"token_uri": "http://emulator:9090/token",
"auth_provider_x509_cert_url": "http://emulator:9090/certs",
"client_x509_cert_url": "http://emulator:9090/certs/test"
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 security Private RSA key committed to repository

This file embeds what appears to be an RSA private key (-----BEGIN RSA PRIVATE KEY-----). Even though it is a fake/test credential, committing it will immediately trigger secret-scanning tools (GitHub Advanced Security, truffleHog, etc.) and may block merges or generate alerts. A better approach is to generate this JSON dynamically in the workflow (e.g., using a jq one-liner) or move the key material into a GitHub Actions secret.

Comment thread tests/integration_test.go
Comment on lines +34 to +35
data := sendResult["data"].(map[string]interface{})
messageID := data["id"].(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.

P2 Unchecked type assertions will panic on unexpected response

sendResult["data"].(map[string]interface{}) and data["id"].(string) are bare type assertions. If the API returns an error body (e.g., a "data": null response), these will panic with a non-descriptive runtime error rather than failing the test cleanly. The same pattern appears on lines 69-70 and 85. Prefer the two-value form v, ok := ...; require.True(t, ok) to get a readable failure message.

Comment thread tests/docker-compose.yml
start_period: 5s

redis:
image: redis:latest
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Non-pinned redis:latest image

Using redis:latest means a breaking Redis major version upgrade can silently change the test environment and cause non-deterministic failures. Pin to a specific minor version (e.g., redis:7-alpine) to control upgrades explicitly.

Suggested change
image: redis:latest
image: redis:7-alpine

@codacy-production
Copy link
Copy Markdown

codacy-production Bot commented May 3, 2026

Not up to standards ⛔

🔴 Issues 3 critical · 23 high · 62 medium · 2 minor

Alerts:
⚠ 90 issues (≤ 0 issues of at least minor severity)

Results:
90 new issues

Category Results
Compatibility 1 medium
BestPractice 4 medium
Security 2 minor
23 high
3 critical
57 medium

View in Codacy

🟢 Metrics 41 complexity · 2 duplication

Metric Results
Complexity 41
Duplication 2

View in Codacy

NEW Get contextual insights on your PRs based on Codacy's metrics, along with PR and Jira context, without leaving GitHub. Enable AI reviewer
TIP This summary will be updated as you push new changes.

AchoArnold and others added 9 commits May 3, 2026 18:31
…ntain permissions'

Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
…committing

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…ollection

When FCM_ENDPOINT is set, provide both the custom HTTP client (for
transport redirection) AND credentials (for project ID resolution).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Introduce FCMClient interface with two implementations:
- FirebaseFCMClient: wraps the real Firebase messaging.Client
- EmulatorFCMClient: sends notifications directly to phone emulator via HTTP

This removes the custom HTTP transport layer (fcm_transport.go) that
redirected Firebase SDK traffic. The container now switches between
implementations based on FCM_ENDPOINT env var.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…CI health wait

The Redis client was unconditionally applying TLS config, causing
connection failures when using plain redis:// URLs (like in tests).
Now TLS is only applied when the URL scheme is rediss://.

Also replaced docker compose --wait with a manual polling loop that
dumps API logs on failure for better debugging.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
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.

2 participants