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.
This commit is contained in:
208
docs/test_infra/test_infra_restapi.md
Normal file
208
docs/test_infra/test_infra_restapi.md
Normal file
@@ -0,0 +1,208 @@
|
||||
# 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`.
|
||||
Reference in New Issue
Block a user