From 32c8341cf7e0f3ec959006b74c958c81f307f28d Mon Sep 17 00:00:00 2001 From: Aldon Smith Date: Wed, 27 May 2026 09:02:07 -0400 Subject: [PATCH] feat: add compose service images --- .dockerignore | 14 ++ .env.example | 6 + Makefile | 25 +++- README.md | 19 ++- apps/web/.dockerignore | 5 + apps/web/Dockerfile | 16 +++ docs/LEARNING_LOG.md | 63 +++++++++ docs/runtime/DOCKER_COMPOSE.md | 46 ++++++- docs/runtime/TROUBLESHOOTING.md | 8 ++ infra/docker/docker-compose.yml | 128 ++++++++++++++++-- infra/docker/python-runtime.Dockerfile | 18 +++ .../factory_ingestion/opcua_demo_worker.py | 41 ++++-- .../tests/test_docker_compose_runtime_docs.py | 60 ++++++++ .../simulator/tests/test_opcua_demo_server.py | 10 +- 14 files changed, 423 insertions(+), 36 deletions(-) create mode 100644 .dockerignore create mode 100644 apps/web/.dockerignore create mode 100644 apps/web/Dockerfile create mode 100644 infra/docker/python-runtime.Dockerfile diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..8ee4a01 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,14 @@ +.git +.local +.mypy_cache +.next +.pytest_cache +.ruff_cache +.venv +**/__pycache__ +**/*.pyc +apps/web/.next +apps/web/node_modules +apps/web/test-results +node_modules +test-results diff --git a/.env.example b/.env.example index 7d7b1f5..ee0347f 100644 --- a/.env.example +++ b/.env.example @@ -2,6 +2,12 @@ APP_ENV=development LOG_LEVEL=info DATABASE_URL=postgresql://postgres:postgres@localhost:5432/factory_intelligence FACTORY_STORAGE_BACKEND=jsonl +NEXT_PUBLIC_API_BASE_URL=http://localhost:8000 +FIP_API_URL=http://localhost:8000 +DEMO_FACTORY_HOST=host.docker.internal +DEMO_FACTORY_OPCUA_ENDPOINT=opc.tcp://host.docker.internal:4840/ofi/demo +CONNECTOR_INTERVAL_SECONDS=30 +SENTINEL_INTERVAL_SECONDS=60 MQTT_BROKER_URL=mqtt://localhost:1883 SIMULATOR_SEED=42 FACTORY_EVENTS_STORE=.local/storage/events.jsonl diff --git a/Makefile b/Makefile index 3ecf3f1..eb8e1cb 100644 --- a/Makefile +++ b/Makefile @@ -22,10 +22,11 @@ OPCUA_ENDPOINT ?= opc.tcp://localhost:4840/ofi/demo OPCUA_POLL_COUNT ?= 6 OPCUA_POLL_INTERVAL ?= 1 OPCUA_EVENTS_STORE ?= .local/storage/opcua_demo_events.jsonl +COMPOSE_FILE ?= infra/docker/docker-compose.yml PYTHONPATH := packages/factory-events:services/simulator:services/ingestion:services/process-sentinel:services/api export PYTHONPATH -.PHONY: help setup dev dev-db simulate ingest opcua-demo-ingest sentinel-run demo demo-reset demo-data demo-ingest demo-sentinel-run demo-api-smoke api api-reload test test-unit test-integration test-contract test-e2e lint typecheck docs +.PHONY: help setup dev dev-db compose-up compose-down compose-ps compose-logs compose-config simulate ingest opcua-demo-ingest sentinel-run demo demo-reset demo-data demo-ingest demo-sentinel-run demo-api-smoke api api-reload test test-unit test-integration test-contract test-e2e lint typecheck docs help: @echo "Factory Intelligence Platform" @@ -33,6 +34,11 @@ help: @echo "Available commands:" @echo " make setup Install Python development dependencies" @echo " make dev-db Start local PostgreSQL with Docker Compose" + @echo " make compose-up Start the FIP Compose runtime" + @echo " make compose-down Stop the FIP Compose runtime" + @echo " make compose-ps List FIP Compose services" + @echo " make compose-logs Follow FIP Compose logs" + @echo " make compose-config Validate the FIP Compose file" @echo " make simulate Generate simulator JSONL events" @echo " make ingest Validate and ingest simulator events" @echo " make opcua-demo-ingest Poll the local demo OPC UA server into FactoryEvents" @@ -68,7 +74,22 @@ dev: @echo " make api" dev-db: - docker compose -f infra/docker/docker-compose.yml up -d + docker compose -f $(COMPOSE_FILE) up -d postgres + +compose-up: + docker compose -f $(COMPOSE_FILE) up --build + +compose-down: + docker compose -f $(COMPOSE_FILE) down + +compose-ps: + docker compose -f $(COMPOSE_FILE) ps + +compose-logs: + docker compose -f $(COMPOSE_FILE) logs -f + +compose-config: + docker compose -f $(COMPOSE_FILE) config simulate: $(PYTHON) -m factory_simulator.cli --scenario $(SCENARIO) --seed $(SEED) $(if $(DURATION_MINUTES),--duration-minutes $(DURATION_MINUTES),--count $(COUNT)) --output $(OUTPUT) diff --git a/README.md b/README.md index 04fcf55..6d29965 100644 --- a/README.md +++ b/README.md @@ -106,10 +106,20 @@ Start the FIP Compose stack from this repository: ```bash cd ../Factory-Intelligence-Platform +make compose-up +make compose-ps +make compose-logs +curl http://localhost:8000/health +make compose-down +``` + +The wrappers call `docker compose -f infra/docker/docker-compose.yml ...`. +The equivalent direct commands are: + +```bash docker compose -f infra/docker/docker-compose.yml up --build docker compose -f infra/docker/docker-compose.yml ps docker compose -f infra/docker/docker-compose.yml logs -f -curl http://localhost:8000/health docker compose -f infra/docker/docker-compose.yml down ``` @@ -117,8 +127,10 @@ Use `docker compose down` for normal shutdown. Use `docker compose -f infra/docker/docker-compose.yml down -v` only when you intentionally want to delete FIP named volumes, such as local Postgres data. -Some Compose services are still landing in follow-up issues. Until then, -contributors can run the API and Workbench directly for focused development: +The FIP Compose stack includes `postgres`, `api`, `web`, `connector-worker`, +and `sentinel-worker`. It does not start an in-repo simulator container. +Contributors can still run the API and Workbench directly for focused +development: ```bash make setup @@ -158,6 +170,7 @@ Run these before opening a pull request: ```bash docker compose -f infra/docker/docker-compose.yml config +make compose-config make lint make typecheck make test diff --git a/apps/web/.dockerignore b/apps/web/.dockerignore new file mode 100644 index 0000000..cf15524 --- /dev/null +++ b/apps/web/.dockerignore @@ -0,0 +1,5 @@ +.next +node_modules +test-results +playwright-report +*.tsbuildinfo diff --git a/apps/web/Dockerfile b/apps/web/Dockerfile new file mode 100644 index 0000000..6a40192 --- /dev/null +++ b/apps/web/Dockerfile @@ -0,0 +1,16 @@ +FROM node:24-slim + +WORKDIR /app + +ARG NEXT_PUBLIC_API_BASE_URL=http://localhost:8000 +ENV NEXT_PUBLIC_API_BASE_URL=${NEXT_PUBLIC_API_BASE_URL} + +COPY package.json package-lock.json ./ +RUN npm ci + +COPY . . +RUN npm run build + +EXPOSE 3000 + +CMD ["npx", "next", "start", "--hostname", "0.0.0.0", "--port", "3000"] diff --git a/docs/LEARNING_LOG.md b/docs/LEARNING_LOG.md index 0f03a62..a29e0cb 100644 --- a/docs/LEARNING_LOG.md +++ b/docs/LEARNING_LOG.md @@ -22,6 +22,69 @@ This file should be updated by Codex after each meaningful change. ### What to learn next ``` +## 2026-05-27 - Compose service images + +### What changed + +Added Docker build definitions for the shared Python runtime and the Next.js +Workbench, then expanded the FIP Compose stack to include `postgres`, `api`, +`web`, `connector-worker`, and `sentinel-worker`. The default FIP Compose file +no longer starts the in-repo `opcua-simulator`. + +### Why it matters + +Issue #236 establishes the container shape for the Dockerized runtime without +turning this issue into connector or Sentinel feature work. Demo-Factory stays +separate, and the FIP services use explicit configuration for Postgres, API +URLs, Demo-Factory source endpoints, and worker intervals. + +### How it works + +`infra/docker/python-runtime.Dockerfile` builds one Python image that can run +the FastAPI app, the existing OPC-UA demo ingestion entrypoint, or the Process +Sentinel CLI. Compose supplies different commands for each service. The web +image builds the Next.js Workbench and exposes it on port `3000`. Postgres +remains the shared runtime store, initialized from +`services/ingestion/schema.sql`. + +### How to run it + +```bash +cd ../Demo-Factory +docker compose up -d --build + +cd ../Factory-Intelligence-Platform +make compose-up +curl http://localhost:8000/health +make compose-down +``` + +### How to test it + +```bash +make compose-config +make lint +make typecheck +make test +cd apps/web && npm test +``` + +### Key files + +- `.dockerignore` +- `infra/docker/python-runtime.Dockerfile` +- `apps/web/Dockerfile` +- `infra/docker/docker-compose.yml` +- `Makefile` +- `docs/runtime/DOCKER_COMPOSE.md` + +### What to learn next + +Replace the temporary scheduled connector command with the full read-only +connector worker runtime from the next issue, then add the Compose smoke path +that proves API health, ingestion, Sentinel output, and Workbench rendering +together. + ## 2026-05-26 - Postgres-backed runtime storage ### What changed diff --git a/docs/runtime/DOCKER_COMPOSE.md b/docs/runtime/DOCKER_COMPOSE.md index 429fad0..eec7c17 100644 --- a/docs/runtime/DOCKER_COMPOSE.md +++ b/docs/runtime/DOCKER_COMPOSE.md @@ -16,11 +16,11 @@ Demo-Factory protocols -> Next.js Workbench ``` -The Dockerized runtime is being built across epic #232. Some FIP services may -remain placeholders until the child issues for Postgres-backed storage, service -images, connector worker, Sentinel worker, Workbench/API wording, and Compose -smoke tests land. Keep using the commands below as the shared local runtime -shape while those pieces are added. +The Dockerized runtime is being built across epic #232. The FIP Compose stack +now defines the API, Workbench, connector worker, Process Sentinel worker, and +Postgres service shape. Follow-up issues continue to harden the connector +runtime, scheduled Sentinel behavior, API/Workbench wording, and Compose smoke +tests. ## Dependency Order @@ -43,6 +43,18 @@ DATABASE_URL=postgresql://postgres:postgres@postgres:5432/factory_intelligence Use `FACTORY_STORAGE_BACKEND=jsonl` only for focused local tests or direct development loops that intentionally avoid Postgres. +The default Compose services use explicit runtime variables: + +| Variable | Default | Used by | +| --- | --- | --- | +| `DATABASE_URL` | `postgresql://postgres:postgres@postgres:5432/factory_intelligence` | API, connector worker, Sentinel worker | +| `NEXT_PUBLIC_API_BASE_URL` | `http://localhost:8000` | Workbench browser bundle | +| `FIP_API_URL` | `http://api:8000` | Container-to-container API target | +| `DEMO_FACTORY_HOST` | `host.docker.internal` | Connector worker source host note | +| `DEMO_FACTORY_OPCUA_ENDPOINT` | `opc.tcp://host.docker.internal:4840/ofi/demo` | Connector worker read-only source endpoint | +| `CONNECTOR_INTERVAL_SECONDS` | `30` | Connector worker schedule interval | +| `SENTINEL_INTERVAL_SECONDS` | `60` | Process Sentinel worker schedule interval | + ## Start Demo-Factory From a sibling checkout: @@ -76,6 +88,27 @@ rebuilding images, use: docker compose -f infra/docker/docker-compose.yml up ``` +The FIP stack includes these default services: + +- `postgres` +- `api` +- `web` +- `connector-worker` +- `sentinel-worker` + +It does not start an in-repo simulator service. Start Demo-Factory separately +when connector source data is required. + +Equivalent Makefile wrappers are available: + +```bash +make compose-up +make compose-ps +make compose-logs +make compose-config +make compose-down +``` + ## Inspect FIP List containers and their health: @@ -151,8 +184,7 @@ When service dependencies or Dockerfiles change, rebuild FIP: docker compose -f infra/docker/docker-compose.yml up --build ``` -If a future issue adds multiple service images and only one service needs a -rebuild, prefer rebuilding that service by name once it exists: +If only one service needs a rebuild, prefer rebuilding that service by name: ```bash docker compose -f infra/docker/docker-compose.yml up --build api diff --git a/docs/runtime/TROUBLESHOOTING.md b/docs/runtime/TROUBLESHOOTING.md index 701575c..d7cb015 100644 --- a/docs/runtime/TROUBLESHOOTING.md +++ b/docs/runtime/TROUBLESHOOTING.md @@ -125,6 +125,14 @@ docker compose -f infra/docker/docker-compose.yml logs -f curl http://localhost:8000/connection-profiles ``` +The FIP Compose connector worker uses these default local source variables: + +```env +DEMO_FACTORY_HOST=host.docker.internal +DEMO_FACTORY_OPCUA_ENDPOINT=opc.tcp://host.docker.internal:4840/ofi/demo +CONNECTOR_INTERVAL_SECONDS=30 +``` + Then use the read-only test endpoint for a configured profile: ```bash diff --git a/infra/docker/docker-compose.yml b/infra/docker/docker-compose.yml index 262fc9e..06ed3bc 100644 --- a/infra/docker/docker-compose.yml +++ b/infra/docker/docker-compose.yml @@ -1,6 +1,18 @@ x-fip-runtime-environment: &fip-runtime-environment FACTORY_STORAGE_BACKEND: postgres DATABASE_URL: postgresql://postgres:postgres@postgres:5432/factory_intelligence + FIP_API_URL: http://api:8000 + +x-fip-python-service: &fip-python-service + build: + context: ../.. + dockerfile: infra/docker/python-runtime.Dockerfile + environment: + <<: *fip-runtime-environment + depends_on: + postgres: + condition: service_healthy + restart: unless-stopped services: postgres: @@ -20,17 +32,117 @@ services: timeout: 5s retries: 10 - opcua-simulator: + api: + <<: *fip-python-service + command: + - uvicorn + - factory_api.main:app + - --host + - 0.0.0.0 + - --port + - "8000" + ports: + - "8000:8000" + healthcheck: + test: + [ + "CMD", + "python", + "-c", + "import urllib.request; urllib.request.urlopen('http://127.0.0.1:8000/health', timeout=5).read()", + ] + interval: 10s + timeout: 5s + retries: 12 + + web: build: - context: ../.. - dockerfile: infra/docker/opcua-simulator.Dockerfile + context: ../../apps/web + dockerfile: Dockerfile + args: + NEXT_PUBLIC_API_BASE_URL: ${NEXT_PUBLIC_API_BASE_URL:-http://localhost:8000} environment: - OPCUA_DEMO_HOST: 0.0.0.0 - OPCUA_DEMO_PORT: "4840" - OPCUA_DEMO_ENDPOINT_PATH: /ofi/demo - OPCUA_DEMO_SCENARIO: fill_weight_drift_demo + NEXT_PUBLIC_API_BASE_URL: ${NEXT_PUBLIC_API_BASE_URL:-http://localhost:8000} + FIP_API_URL: http://api:8000 + depends_on: + api: + condition: service_healthy ports: - - "4840:4840" + - "3000:3000" + healthcheck: + test: + [ + "CMD", + "node", + "-e", + "fetch('http://127.0.0.1:3000').then((r) => { if (!r.ok) process.exit(1); }).catch(() => process.exit(1))", + ] + interval: 10s + timeout: 5s + retries: 12 + + connector-worker: + <<: *fip-python-service + environment: + <<: *fip-runtime-environment + DEMO_FACTORY_HOST: ${DEMO_FACTORY_HOST:-host.docker.internal} + DEMO_FACTORY_OPCUA_ENDPOINT: ${DEMO_FACTORY_OPCUA_ENDPOINT:-opc.tcp://host.docker.internal:4840/ofi/demo} + CONNECTOR_INTERVAL_SECONDS: ${CONNECTOR_INTERVAL_SECONDS:-30} + FACTORY_EVENTS_STORE: /app/.local/storage/opcua_demo_events.jsonl + extra_hosts: + - "host.docker.internal:host-gateway" + command: + - sh + - -c + - | + while true; do + python -m factory_ingestion.opcua_demo_worker \ + --endpoint "$$DEMO_FACTORY_OPCUA_ENDPOINT" \ + --events-store "$$FACTORY_EVENTS_STORE" \ + --storage-backend "$$FACTORY_STORAGE_BACKEND" \ + --database-url "$$DATABASE_URL" \ + --poll-count 1 \ + --poll-interval 0 + sleep "$$CONNECTOR_INTERVAL_SECONDS" + done + healthcheck: + test: + [ + "CMD", + "python", + "-c", + "import os, factory_ingestion.opcua_demo_worker; raise SystemExit(0 if os.getenv('DEMO_FACTORY_OPCUA_ENDPOINT') else 1)", + ] + interval: 30s + timeout: 5s + retries: 3 + + sentinel-worker: + <<: *fip-python-service + environment: + <<: *fip-runtime-environment + SENTINEL_INTERVAL_SECONDS: ${SENTINEL_INTERVAL_SECONDS:-60} + command: + - sh + - -c + - | + while true; do + python -m process_sentinel.cli \ + --storage-backend "$$FACTORY_STORAGE_BACKEND" \ + --database-url "$$DATABASE_URL" + sleep "$$SENTINEL_INTERVAL_SECONDS" + done + healthcheck: + test: + [ + "CMD", + "python", + "-c", + "import os, process_sentinel.cli; raise SystemExit(0 if os.getenv('SENTINEL_INTERVAL_SECONDS') else 1)", + ] + interval: 30s + timeout: 5s + retries: 3 volumes: factory_postgres_data: diff --git a/infra/docker/python-runtime.Dockerfile b/infra/docker/python-runtime.Dockerfile new file mode 100644 index 0000000..ed331ec --- /dev/null +++ b/infra/docker/python-runtime.Dockerfile @@ -0,0 +1,18 @@ +FROM python:3.13-slim + +ENV PYTHONDONTWRITEBYTECODE=1 \ + PYTHONUNBUFFERED=1 \ + PYTHONPATH=/app/packages/factory-events:/app/services/simulator:/app/services/ingestion:/app/services/process-sentinel:/app/services/api + +WORKDIR /app + +COPY requirements-dev.txt pyproject.toml ./ +RUN pip install --no-cache-dir --upgrade pip \ + && pip install --no-cache-dir -r requirements-dev.txt + +COPY packages ./packages +COPY services ./services + +EXPOSE 8000 + +CMD ["uvicorn", "factory_api.main:app", "--host", "0.0.0.0", "--port", "8000"] diff --git a/services/ingestion/factory_ingestion/opcua_demo_worker.py b/services/ingestion/factory_ingestion/opcua_demo_worker.py index cb5b88d..484d9c1 100644 --- a/services/ingestion/factory_ingestion/opcua_demo_worker.py +++ b/services/ingestion/factory_ingestion/opcua_demo_worker.py @@ -26,7 +26,7 @@ OpcUaDemoNode, ) -from factory_ingestion.storage import JsonlEventStore +from factory_ingestion.storage import EventStore, event_store_from_config DEFAULT_OPCUA_ENDPOINT = "opc.tcp://localhost:4840/ofi/demo" DEFAULT_OPCUA_POLL_COUNT = 6 @@ -49,7 +49,7 @@ class OpcUaDemoIngestionResult: poll_count: int poll_interval_seconds: float emitted_count: int - events_store_path: Path + events_store_target: str def _node_id(node: OpcUaDemoNode) -> str: @@ -76,8 +76,8 @@ async def read_demo_snapshot(endpoint_url: str) -> dict[str, Any]: msg = ( "unable to read OPC UA demo server at " f"{endpoint_url}. Start it with " - "`docker compose -f infra/docker/docker-compose.yml up --build opcua-simulator` " - "or verify the endpoint URL." + "`cd ../Demo-Factory && docker compose up -d --build` " + "or verify DEMO_FACTORY_OPCUA_ENDPOINT." ) raise OpcUaDemoServerUnavailableError(msg) from exc return values @@ -178,7 +178,7 @@ def build_events_from_snapshot( async def run_opcua_demo_worker( *, endpoint_url: str, - events_store: JsonlEventStore, + events_store: EventStore, poll_count: int = DEFAULT_OPCUA_POLL_COUNT, poll_interval_seconds: float = DEFAULT_OPCUA_POLL_INTERVAL_SECONDS, reader: SnapshotReader = read_demo_snapshot, @@ -222,7 +222,7 @@ async def run_opcua_demo_worker( poll_count=poll_count, poll_interval_seconds=poll_interval_seconds, emitted_count=emitted_count, - events_store_path=events_store.path, + events_store_target=_event_store_target(events_store), ) @@ -234,7 +234,7 @@ def format_opcua_demo_summary(result: OpcUaDemoIngestionResult) -> str: f"poll_count: {result.poll_count}", f"poll_interval_seconds: {result.poll_interval_seconds:g}", f"emitted_events: {result.emitted_count}", - f"accepted_output: {result.events_store_path}", + f"accepted_output: {result.events_store_target}", "demo_boundary: simulator-backed demo OPC UA source; not a production connector", ] ) @@ -248,6 +248,17 @@ def main(argv: Sequence[str] | None = None) -> int: type=Path, default=Path(".local/storage/opcua_demo_events.jsonl"), ) + parser.add_argument( + "--storage-backend", + choices=("jsonl", "postgres"), + default=None, + help="Storage backend override. Defaults to FACTORY_STORAGE_BACKEND or jsonl.", + ) + parser.add_argument( + "--database-url", + default=None, + help="Postgres URL when storage backend is postgres. Defaults to DATABASE_URL.", + ) parser.add_argument("--poll-count", type=int, default=DEFAULT_OPCUA_POLL_COUNT) parser.add_argument( "--poll-interval", @@ -261,7 +272,11 @@ def main(argv: Sequence[str] | None = None) -> int: result = asyncio.run( run_opcua_demo_worker( endpoint_url=args.endpoint, - events_store=JsonlEventStore(args.events_store), + events_store=event_store_from_config( + events_store_path=args.events_store, + database_url=args.database_url, + storage_backend=args.storage_backend, + ), poll_count=args.poll_count, poll_interval_seconds=args.poll_interval, ) @@ -274,6 +289,16 @@ def main(argv: Sequence[str] | None = None) -> int: return 0 +def _event_store_target(events_store: EventStore) -> str: + path = getattr(events_store, "path", None) + if path is not None: + return str(path) + database_url = getattr(events_store, "database_url", None) + if database_url is not None: + return "postgres event store" + return type(events_store).__name__ + + def _measurement_context(snapshot: dict[str, Any], *, asset_id: str | None) -> EventContext: return EventContext( site_id=str(snapshot["site_id"]), diff --git a/services/simulator/tests/test_docker_compose_runtime_docs.py b/services/simulator/tests/test_docker_compose_runtime_docs.py index da74690..d22eb2a 100644 --- a/services/simulator/tests/test_docker_compose_runtime_docs.py +++ b/services/simulator/tests/test_docker_compose_runtime_docs.py @@ -88,6 +88,66 @@ def test_docker_compose_runtime_defaults_to_postgres_storage() -> None: assert term.replace(": ", "=") in runtime_doc +def test_docker_compose_runtime_defines_fip_services_without_simulator() -> None: + compose = _read(DOCKER_COMPOSE_FILE) + runtime_doc = _read(DOCKER_COMPOSE_DOC) + + required_compose_terms = [ + "python-runtime.Dockerfile", + "apps/web", + "api:", + "web:", + "connector-worker:", + "sentinel-worker:", + "NEXT_PUBLIC_API_BASE_URL", + "FIP_API_URL: http://api:8000", + "DEMO_FACTORY_HOST", + "DEMO_FACTORY_OPCUA_ENDPOINT", + "CONNECTOR_INTERVAL_SECONDS", + "SENTINEL_INTERVAL_SECONDS", + "healthcheck:", + ] + + for term in required_compose_terms: + assert term in compose + + assert "opcua-simulator:" not in compose + assert "infra/docker/opcua-simulator.Dockerfile" not in compose + + for service in [ + "`postgres`", + "`api`", + "`web`", + "`connector-worker`", + "`sentinel-worker`", + ]: + assert service in runtime_doc + assert "It does not start an in-repo simulator service." in runtime_doc + + +def test_makefile_includes_compose_wrappers() -> None: + makefile = _read(REPO_ROOT / "Makefile") + readme = _read(README) + + for target in [ + "compose-up:", + "compose-down:", + "compose-ps:", + "compose-logs:", + "compose-config:", + ]: + assert target in makefile + + for command in [ + "make compose-up", + "make compose-ps", + "make compose-logs", + "make compose-config", + "make compose-down", + ]: + assert command in readme or command in _read(DOCKER_COMPOSE_DOC) + + def test_runtime_troubleshooting_covers_expected_failure_modes() -> None: content = _read(TROUBLESHOOTING_DOC) diff --git a/services/simulator/tests/test_opcua_demo_server.py b/services/simulator/tests/test_opcua_demo_server.py index 23cdc87..8083df9 100644 --- a/services/simulator/tests/test_opcua_demo_server.py +++ b/services/simulator/tests/test_opcua_demo_server.py @@ -66,14 +66,8 @@ def test_opcua_demo_compose_service_is_documented_and_demo_scoped() -> None: readme = SIMULATOR_README.read_text(encoding="utf-8") namespace_doc = NAMESPACE_DOC.read_text(encoding="utf-8") - required_terms = [ - "opcua-simulator", - "infra/docker/opcua-simulator.Dockerfile", - "4840:4840", - "OPCUA_DEMO_SCENARIO: fill_weight_drift_demo", - ] - for term in required_terms: - assert term in compose + assert "opcua-simulator:" not in compose + assert "infra/docker/opcua-simulator.Dockerfile" not in compose assert '"python", "-m", "factory_simulator.opcua_server"' in dockerfile assert "no longer the default Factory Intelligence Platform" in readme