# Test Infrastructure: REST API Server (Flask) ## Overview The test REST API server is a lightweight Python/Flask application that provides HTTP endpoints matching the patterns used by ScadaLink's External System Gateway and Inbound API components. It supports simple parameter/response methods, complex nested object/list methods, authentication, and error simulation. ## Image & Ports - **Image**: Custom build from `infra/restapi/Dockerfile` (Python 3.13 + Flask) - **API port**: 5200 ## Configuration | Setting | Value | Description | |---------|-------|-------------| | `API_NO_AUTH` | `0` | Set to `1` to disable API key authentication | | `PORT` | `5200` | Server listen port | ## Authentication The server validates requests using one of two methods: - **API Key**: `X-API-Key: scadalink-test-key-1` header - **Basic Auth**: Any username/password (accepts all credentials) The `GET /api/Ping` endpoint is always unauthenticated (health check). Auth can be disabled entirely by setting `API_NO_AUTH=1` in the Docker Compose environment or passing `--no-auth` when running directly. For `appsettings.Development.json` (External System Gateway): ```json { "ExternalSystems": { "TestApi": { "BaseUrl": "http://localhost:5200", "AuthMode": "ApiKey", "ApiKey": "scadalink-test-key-1" } } } ``` ## Endpoints ### Simple Methods | Method | Path | HTTP | Params | Response | Description | |--------|------|------|--------|----------|-------------| | Ping | `/api/Ping` | GET | — | `{"pong": true}` | Health check (no auth) | | Add | `/api/Add` | POST | `{"a": 5, "b": 3}` | `{"result": 8}` | Add two numbers | | Multiply | `/api/Multiply` | POST | `{"a": 4, "b": 7}` | `{"result": 28}` | Multiply two numbers | | Echo | `/api/Echo` | POST | `{"message": "hello"}` | `{"message": "hello"}` | Echo back input | | GetStatus | `/api/GetStatus` | POST | `{}` | `{"status": "running", "uptime": 123.4}` | Server status | ### Complex Methods (nested objects + lists) | Method | Path | HTTP | Description | |--------|------|------|-------------| | GetProductionReport | `/api/GetProductionReport` | POST | Production report with line details | | GetRecipe | `/api/GetRecipe` | POST | Recipe with ingredients list | | SubmitBatch | `/api/SubmitBatch` | POST | Submit batch with items (complex input) | | GetEquipmentStatus | `/api/GetEquipmentStatus` | POST | Equipment list with nested status objects | ### Error Simulation | Method | Path | HTTP | Params | Description | |--------|------|------|--------|-------------| | SimulateTimeout | `/api/SimulateTimeout` | POST | `{"seconds": 5}` | Delay response (max 60s) | | SimulateError | `/api/SimulateError` | POST | `{"code": 500}` | Return specified HTTP error (400–599) | ### Method Discovery | Method | Path | HTTP | Description | |--------|------|------|-------------| | methods | `/api/methods` | GET | List all available methods with signatures | ## Response Examples **GetProductionReport** (`{"siteId": "SiteA", "startDate": "2026-03-01", "endDate": "2026-03-16"}`): ```json { "siteName": "Site SiteA", "totalUnits": 14250, "lines": [ { "lineName": "Line-1", "units": 8200, "efficiency": 92.5 }, { "lineName": "Line-2", "units": 6050, "efficiency": 88.1 } ] } ``` **GetRecipe** (`{"recipeId": "R-100"}`): ```json { "recipeId": "R-100", "name": "Standard Mix", "version": 3, "ingredients": [ { "name": "Material-A", "quantity": 45.0, "unit": "kg" }, { "name": "Material-B", "quantity": 12.5, "unit": "L" } ] } ``` **SubmitBatch** (complex input with items list): ```json { "siteId": "SiteA", "recipeId": "R-100", "items": [ { "materialId": "MAT-001", "quantity": 45.0, "lotNumber": "LOT-2026-001" }, { "materialId": "MAT-002", "quantity": 12.5, "lotNumber": "LOT-2026-002" } ] } ``` Response: `{"batchId": "BATCH-A1B2C3D4", "accepted": true, "itemCount": 2}` **GetEquipmentStatus** (`{"siteId": "SiteA"}`): ```json { "siteId": "SiteA", "equipment": [ { "equipmentId": "PUMP-001", "name": "Feed Pump A", "status": { "state": "running", "health": 98.5, "lastMaintenance": "2026-02-15" } }, { "equipmentId": "TANK-001", "name": "Mix Tank 1", "status": { "state": "idle", "health": 100.0, "lastMaintenance": "2026-03-01" } }, { "equipmentId": "CONV-001", "name": "Conveyor B", "status": { "state": "alarm", "health": 72.3, "lastMaintenance": "2026-01-20" } } ] } ``` ## Verification 1. Check the container is running: ```bash docker ps --filter name=scadalink-restapi ``` 2. Test the health endpoint: ```bash curl http://localhost:5200/api/Ping ``` 3. Test an authenticated call: ```bash curl -X POST http://localhost:5200/api/Add \ -H "X-API-Key: scadalink-test-key-1" \ -H "Content-Type: application/json" \ -d '{"a": 2, "b": 3}' ``` ## CLI Tool The `infra/tools/restapi_tool.py` script provides a CLI for interacting with the REST API server. This tool requires the `requests` library (included in `tools/requirements.txt`). **Commands**: ```bash # Check API server connectivity and status python infra/tools/restapi_tool.py check # Call a simple method python infra/tools/restapi_tool.py call --method Add --params '{"a": 2, "b": 3}' # Call a complex method python infra/tools/restapi_tool.py call --method GetProductionReport --params '{"siteId": "SiteA", "startDate": "2026-03-01", "endDate": "2026-03-16"}' # Simulate an error python infra/tools/restapi_tool.py call --method SimulateError --params '{"code": 503}' # List all available methods python infra/tools/restapi_tool.py methods ``` Use `--url` to override the base URL (default: `http://localhost:5200`), `--api-key` for the API key. Run with `--help` for full usage. ## Relevance to ScadaLink Components - **External System Gateway** — test HTTP/REST calls (`ExternalSystem.Call()` and `CachedCall()`), API key authentication, error classification (5xx vs 4xx), and timeout handling. - **Inbound API** — test the `POST /api/{methodName}` pattern, flat JSON parameters, and extended type system (Object, List) with complex nested responses. - **Store-and-Forward Engine** — verify buffered retry by using `SimulateError` to return transient errors (503, 408, 429) and observing store-and-forward behavior. ## Notes - The server is stateless — no data persistence between container restarts. - `SimulateTimeout` caps at 60 seconds to prevent accidental container hangs. - `SimulateError` accepts codes 400–599; other values default to 500. - `SubmitBatch` generates a unique `batchId` per call (UUID-based). - The `/api/methods` endpoint provides machine-readable method discovery (useful for CLI tool and automated testing). - To simulate connection failures for store-and-forward testing, stop the container: `docker compose stop restapi`. Restart with `docker compose start restapi`.