# Integration Tests End-to-end integration tests for the httpSMS API. These tests validate the complete SMS lifecycle by running the full application stack in Docker alongside a phone emulator service. ## Architecture ``` ┌──────────────┐ HTTP ┌──────────────┐ │ Test Runner │─────────────▶│ API (Go) │ │ (Go test) │ │ Port 8000 │ └──────────────┘ └──────┬───────┘ │ FCM Push │ Events (HTTP) │ (HTTP) ▼ ┌──────────────┐ │ Emulator │ │ (Fiber v3) │ │ Port 9090 │ └──────────────┘ │ ┌──────┴───────┐ │ CockroachDB │ │ Redis │ │ Port 26257 │ │ Port 6379 │ └──────────────┘ └─────────────┘ ``` ### Components | Component | Description | | --------------- | -------------------------------------------------------- | | **API** | The httpSMS Go API server running in Docker | | **Emulator** | A Fiber v3 Go service that simulates an Android phone | | **CockroachDB** | Database for the API (single-node, insecure mode) | | **Redis** | Cache and queue backend | | **Seed** | One-shot container that seeds test data into CockroachDB | | **Test Runner** | Go test binary that runs on the host machine | ### How It Works 1. **Send SMS flow**: Test sends `POST /v1/messages/send` → API pushes FCM notification to emulator → Emulator calls `GET /v1/messages/outstanding` → Emulator fires `SENT` and `DELIVERED` events → Test polls `GET /v1/messages/{id}` until status is `delivered` 2. **Receive SMS flow**: Test sends `POST /v1/messages/receive` (as the phone) → API stores message → Test verifies via `GET /v1/messages/{id}` ### FCM Redirect The API's Firebase SDK is configured (via `FCM_ENDPOINT` env var) to redirect all FCM HTTP requests to the emulator instead of Google's servers. The emulator serves: - `/token` — Fake OAuth2 token endpoint (Firebase SDK requests tokens before sending) - `/v1/projects/:project/messages:send` — Fake FCM push endpoint ## Test Coverage - [x] **Send SMS E2E** — Full send lifecycle: API → FCM push → emulator responds with SENT/DELIVERED events → message reaches `delivered` status - [x] **Receive SMS E2E** — Phone submits received message to API → message is stored and retrievable via GET endpoint ## Prerequisites - [Docker](https://docs.docker.com/get-docker/) with Docker Compose - [Go 1.22+](https://go.dev/dl/) - [jq](https://jqlang.github.io/jq/download/) (for Firebase credentials generation) - [OpenSSL](https://www.openssl.org/) (for RSA key generation) ## Running Locally ### 1. Generate Firebase Credentials The integration tests use a fake Firebase service account. Generate it with: ```bash cd tests bash generate-firebase-credentials.sh ``` This creates `firebase-credentials.json` with a throwaway RSA key (the emulator doesn't validate tokens). ### 2. Set Environment Variable ```bash export FIREBASE_CREDENTIALS=$(jq -c . firebase-credentials.json) ``` ### 3. Start the Stack ```bash docker compose up -d --build --wait ``` This starts CockroachDB, Redis, the API, and the emulator. The `--wait` flag blocks until all health checks pass. ### 4. Wait for Seeding ```bash docker compose wait seed sleep 2 ``` The seed container inserts test users, phones, and API keys into CockroachDB after the API has run its GORM migrations. ### 5. Run Tests ```bash go test -v -timeout 120s ./... ``` ### 6. Tear Down ```bash docker compose down -v ``` The `-v` flag removes volumes (database data) for a clean slate next run. ### One-Liner ```bash cd tests && \ bash generate-firebase-credentials.sh && \ export FIREBASE_CREDENTIALS=$(jq -c . firebase-credentials.json) && \ docker compose up -d --build --wait && \ docker compose wait seed && \ sleep 2 && \ go test -v -timeout 120s ./... ; \ docker compose down -v ``` ## CI/CD Integration tests run automatically via GitHub Actions (`.github/workflows/integration-test.yml`): - **Trigger**: Push to `main` or pull request targeting `main` - **Flow**: Generates credentials → Starts Docker stack → Seeds DB → Runs tests → Collects logs on failure → Tears down - **Gate**: Deployment should only proceed if integration tests pass ## Test Data | Entity | Value | | -------------- | -------------------------------------- | | User API Key | `test-user-api-key` | | Phone API Key | `pk_test-phone-api-key` | | Phone Number | `+18005550199` | | Contact Number | `+18005550100` | | User ID | `test-user-id` | | Phone ID | `a1b2c3d4-e5f6-7890-abcd-ef1234567890` | See [`seed.sql`](./seed.sql) for the complete seed data. ## Project Structure ``` tests/ ├── docker-compose.yml # Full stack orchestration ├── seed.sql # Database seed data ├── .env.test # API environment variables ├── generate-firebase-credentials.sh # Generates fake Firebase credentials ├── go.mod # Test runner Go module ├── go.sum ├── helpers_test.go # Test utilities (HTTP client, polling) ├── integration_test.go # E2E test cases └── emulator/ # Phone emulator service ├── Dockerfile ├── go.mod ├── go.sum ├── main.go # Fiber v3 entry point ├── emulator.go # Emulator struct and config ├── token_handler.go # Fake OAuth2 token endpoint ├── fcm_handler.go # Fake FCM push receiver └── events.go # Event firing logic (SENT/DELIVERED) ``` ## Troubleshooting ### API fails to start Check the API logs: ```bash docker compose logs api ``` Common issues: - `FIREBASE_CREDENTIALS` env var not set or malformed - CockroachDB not ready (increase `start_period` in healthcheck) ### Tests timeout waiting for `delivered` status Check the emulator logs: ```bash docker compose logs emulator ``` The emulator should show: 1. `[FCM]` — Receiving the push notification 2. `[EVENTS]` — Fetching outstanding messages and firing events If no `[FCM]` entries appear, the API isn't reaching the emulator (check `FCM_ENDPOINT` in `.env.test`). ### Seed container fails ```bash docker compose logs seed ``` If you see "relation does not exist" errors, the API hasn't finished GORM migrations yet. Increase the API's `start_period` in `docker-compose.yml`. ## Adding New Tests 1. Add test functions to `integration_test.go` (or create new `*_test.go` files) 2. Use `doRequest()` helper for authenticated HTTP calls 3. Use `pollMessageStatus()` to wait for async state changes 4. Update the test coverage checklist in this README