|
| 1 | +# Integration Tests |
| 2 | + |
| 3 | +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. |
| 4 | + |
| 5 | +## Architecture |
| 6 | + |
| 7 | +``` |
| 8 | +┌──────────────┐ HTTP ┌──────────────┐ |
| 9 | +│ Test Runner │─────────────▶│ API (Go) │ |
| 10 | +│ (Go test) │ │ Port 8000 │ |
| 11 | +└──────────────┘ └──────┬───────┘ |
| 12 | + │ |
| 13 | + FCM Push │ Events |
| 14 | + (HTTP) │ (HTTP) |
| 15 | + ▼ |
| 16 | + ┌──────────────┐ |
| 17 | + │ Emulator │ |
| 18 | + │ (Fiber v3) │ |
| 19 | + │ Port 9090 │ |
| 20 | + └──────────────┘ |
| 21 | + │ |
| 22 | + ┌──────┴───────┐ |
| 23 | + │ PostgreSQL │ │ Redis │ |
| 24 | + │ Port 5435 │ │ Port 6379 │ |
| 25 | + └──────────────┘ └─────────────┘ |
| 26 | +``` |
| 27 | + |
| 28 | +### Components |
| 29 | + |
| 30 | +| Component | Description | |
| 31 | +| --------------- | ------------------------------------------------------- | |
| 32 | +| **API** | The httpSMS Go API server running in Docker | |
| 33 | +| **Emulator** | A Fiber v3 Go service that simulates an Android phone | |
| 34 | +| **PostgreSQL** | Database for the API | |
| 35 | +| **Redis** | Cache and queue backend | |
| 36 | +| **Seed** | One-shot container that seeds test data into PostgreSQL | |
| 37 | +| **Test Runner** | Go test binary that runs on the host machine | |
| 38 | + |
| 39 | +### How It Works |
| 40 | + |
| 41 | +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` |
| 42 | + |
| 43 | +2. **Receive SMS flow**: Test sends `POST /v1/messages/receive` (as the phone) → API stores message → Test verifies via `GET /v1/messages/{id}` |
| 44 | + |
| 45 | +### FCM Redirect |
| 46 | + |
| 47 | +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: |
| 48 | + |
| 49 | +- `/token` — Fake OAuth2 token endpoint (Firebase SDK requests tokens before sending) |
| 50 | +- `/v1/projects/:project/messages:send` — Fake FCM push endpoint |
| 51 | + |
| 52 | +## Test Coverage |
| 53 | + |
| 54 | +- [x] **Send SMS E2E** — Full send lifecycle: API → FCM push → emulator responds with SENT/DELIVERED events → message reaches `delivered` status |
| 55 | +- [x] **Receive SMS E2E** — Phone submits received message to API → message is stored and retrievable via GET endpoint |
| 56 | + |
| 57 | +## Prerequisites |
| 58 | + |
| 59 | +- [Docker](https://docs.docker.com/get-docker/) with Docker Compose |
| 60 | +- [Go 1.22+](https://go.dev/dl/) |
| 61 | +- [jq](https://jqlang.github.io/jq/download/) (for Firebase credentials generation) |
| 62 | +- [OpenSSL](https://www.openssl.org/) (for RSA key generation) |
| 63 | + |
| 64 | +## Running Locally |
| 65 | + |
| 66 | +### 1. Generate Firebase Credentials |
| 67 | + |
| 68 | +The integration tests use a fake Firebase service account. Generate it with: |
| 69 | + |
| 70 | +```bash |
| 71 | +cd tests |
| 72 | +bash generate-firebase-credentials.sh |
| 73 | +``` |
| 74 | + |
| 75 | +This creates `firebase-credentials.json` with a throwaway RSA key (the emulator doesn't validate tokens). |
| 76 | + |
| 77 | +### 2. Set Environment Variable |
| 78 | + |
| 79 | +```bash |
| 80 | +export FIREBASE_CREDENTIALS=$(jq -c . firebase-credentials.json) |
| 81 | +``` |
| 82 | + |
| 83 | +### 3. Start the Stack |
| 84 | + |
| 85 | +```bash |
| 86 | +docker compose up -d --build --wait |
| 87 | +``` |
| 88 | + |
| 89 | +This starts PostgreSQL, Redis, the API, and the emulator. The `--wait` flag blocks until all health checks pass. |
| 90 | + |
| 91 | +### 4. Wait for Seeding |
| 92 | + |
| 93 | +```bash |
| 94 | +docker compose wait seed |
| 95 | +sleep 2 |
| 96 | +``` |
| 97 | + |
| 98 | +The seed container inserts test users, phones, and API keys into PostgreSQL after the API has run its GORM migrations. |
| 99 | + |
| 100 | +### 5. Run Tests |
| 101 | + |
| 102 | +```bash |
| 103 | +go test -v -timeout 120s ./... |
| 104 | +``` |
| 105 | + |
| 106 | +### 6. Tear Down |
| 107 | + |
| 108 | +```bash |
| 109 | +docker compose down -v |
| 110 | +``` |
| 111 | + |
| 112 | +The `-v` flag removes volumes (database data) for a clean slate next run. |
| 113 | + |
| 114 | +### One-Liner |
| 115 | + |
| 116 | +```bash |
| 117 | +cd tests && \ |
| 118 | + bash generate-firebase-credentials.sh && \ |
| 119 | + export FIREBASE_CREDENTIALS=$(jq -c . firebase-credentials.json) && \ |
| 120 | + docker compose up -d --build --wait && \ |
| 121 | + docker compose wait seed && \ |
| 122 | + sleep 2 && \ |
| 123 | + go test -v -timeout 120s ./... ; \ |
| 124 | + docker compose down -v |
| 125 | +``` |
| 126 | + |
| 127 | +## CI/CD |
| 128 | + |
| 129 | +Integration tests run automatically via GitHub Actions (`.github/workflows/integration-test.yml`): |
| 130 | + |
| 131 | +- **Trigger**: Push to `main` or pull request targeting `main` |
| 132 | +- **Flow**: Generates credentials → Starts Docker stack → Seeds DB → Runs tests → Collects logs on failure → Tears down |
| 133 | +- **Gate**: Deployment should only proceed if integration tests pass |
| 134 | + |
| 135 | +## Test Data |
| 136 | + |
| 137 | +| Entity | Value | |
| 138 | +| -------------- | -------------------------------------- | |
| 139 | +| User API Key | `test-user-api-key` | |
| 140 | +| Phone API Key | `pk_test-phone-api-key` | |
| 141 | +| Phone Number | `+18005550199` | |
| 142 | +| Contact Number | `+18005550100` | |
| 143 | +| User ID | `test-user-id` | |
| 144 | +| Phone ID | `a1b2c3d4-e5f6-7890-abcd-ef1234567890` | |
| 145 | + |
| 146 | +See [`seed.sql`](./seed.sql) for the complete seed data. |
| 147 | + |
| 148 | +## Project Structure |
| 149 | + |
| 150 | +``` |
| 151 | +tests/ |
| 152 | +├── docker-compose.yml # Full stack orchestration |
| 153 | +├── seed.sql # Database seed data |
| 154 | +├── .env.test # API environment variables |
| 155 | +├── generate-firebase-credentials.sh # Generates fake Firebase credentials |
| 156 | +├── go.mod # Test runner Go module |
| 157 | +├── go.sum |
| 158 | +├── helpers_test.go # Test utilities (HTTP client, polling) |
| 159 | +├── integration_test.go # E2E test cases |
| 160 | +└── emulator/ # Phone emulator service |
| 161 | + ├── Dockerfile |
| 162 | + ├── go.mod |
| 163 | + ├── go.sum |
| 164 | + ├── main.go # Fiber v3 entry point |
| 165 | + ├── emulator.go # Emulator struct and config |
| 166 | + ├── token_handler.go # Fake OAuth2 token endpoint |
| 167 | + ├── fcm_handler.go # Fake FCM push receiver |
| 168 | + └── events.go # Event firing logic (SENT/DELIVERED) |
| 169 | +``` |
| 170 | + |
| 171 | +## Troubleshooting |
| 172 | + |
| 173 | +### API fails to start |
| 174 | + |
| 175 | +Check the API logs: |
| 176 | + |
| 177 | +```bash |
| 178 | +docker compose logs api |
| 179 | +``` |
| 180 | + |
| 181 | +Common issues: |
| 182 | + |
| 183 | +- `FIREBASE_CREDENTIALS` env var not set or malformed |
| 184 | +- PostgreSQL not ready (increase `start_period` in healthcheck) |
| 185 | + |
| 186 | +### Tests timeout waiting for `delivered` status |
| 187 | + |
| 188 | +Check the emulator logs: |
| 189 | + |
| 190 | +```bash |
| 191 | +docker compose logs emulator |
| 192 | +``` |
| 193 | + |
| 194 | +The emulator should show: |
| 195 | + |
| 196 | +1. `[FCM]` — Receiving the push notification |
| 197 | +2. `[EVENTS]` — Fetching outstanding messages and firing events |
| 198 | + |
| 199 | +If no `[FCM]` entries appear, the API isn't reaching the emulator (check `FCM_ENDPOINT` in `.env.test`). |
| 200 | + |
| 201 | +### Seed container fails |
| 202 | + |
| 203 | +```bash |
| 204 | +docker compose logs seed |
| 205 | +``` |
| 206 | + |
| 207 | +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`. |
| 208 | + |
| 209 | +## Adding New Tests |
| 210 | + |
| 211 | +1. Add test functions to `integration_test.go` (or create new `*_test.go` files) |
| 212 | +2. Use `doRequest()` helper for authenticated HTTP calls |
| 213 | +3. Use `pollMessageStatus()` to wait for async state changes |
| 214 | +4. Update the test coverage checklist in this README |
0 commit comments