Files
scadalink-design/docs/test_infra/test_infra_restapi.md
Joseph Doherty d91aa83665 refactor(docs): move requirements and test infra docs into docs/ subdirectories
Organize documentation by moving requirements (HighLevelReqs, Component-*,
lmxproxy_protocol) to docs/requirements/ and test infrastructure docs to
docs/test_infra/. Updates all cross-references in README, CLAUDE.md,
infra/README, component docs, and 23 plan files.
2026-03-21 01:11:35 -04:00

209 lines
6.8 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 (400599) |
### 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 400599; 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`.