Files
lmxopcua/tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.MTConnect.IntegrationTests/Docker/docker-compose.yml
T
Joseph Doherty cf9ce91e51 test(mtconnect): live-Agent docker fixture + integration suite (Tasks 19+20)
The first and only thing in the MTConnect workstream that exercises the driver
against a real Agent; everything else runs on canned XML.

Fixture (Docker/): two services, both stock images with this folder bind-mounted.
- `agent`   mtconnect/agent:2.7.0.12 on :5000. NOTE the repo name: the source
            project is mtconnect/cppagent but the PUBLISHED image is
            mtconnect/agent; mtconnect/cppagent does not exist on Docker Hub.
- `adapter` a stdlib SHDR feeder. Required, not optional: the agent image ships
            only the binary + schemas/styles, so a lone Agent answers /probe and
            then reports every observation UNAVAILABLE forever, which can prove
            nothing about reads or streaming.

Devices.xml seeds one DataItem per inference branch (SAMPLE+units, TIME_SERIES,
PART_COUNT/LINE_NUMBER, controlled vocab, DATA_SET, CONDITION), gives every named
item `name != id` (the inverse of the hand-authored unit fixtures), and leaves one
EVENT permanently unfed so UNAVAILABLE -> BadNoCommunication has a live subject.

Suite (12 tests) asserts STRUCTURALLY against the Agent's own /probe response, never
by literal id, so it survives an edit to Devices.xml and can be pointed at a real
machine tool via MTCONNECT_AGENT_ENDPOINT. It skips cleanly (12/12) when no Agent
answers, and each test carries a hard [Fact(Timeout)] so a half-up fixture cannot
wedge a build.

Three findings from bringing the fixture up, each now pinned in a comment:
- agent.cfg must be pure ASCII; one non-ASCII byte in a COMMENT makes the config
  parser reject the whole file with a bare "Failed / Stopped at line: N" and exit.
- `sampleCount` is an attribute of a TIME_SERIES observation, not of a DataItem
  declaration. A real Agent meeting one on a declaration DROPS THE ENTIRE DATA ITEM
  from the device model, so the inference only ever sees null and a live TIME_SERIES
  tag is always variable-length. Asserted, not ignored.
- The Agent's own <Agent> self-model publishes update-rate SAMPLEs that tick whether
  or not any adapter is attached. Excluding them is load-bearing: with them included
  the "live stream delivered a changed value" test passed with the fixture's data
  source deliberately stopped.

MTConnectError-under-HTTP-200 is NOT covered live and cannot be: cppagent 2.7 answers
an unknown device 404 and an out-of-range sequence 400, each with a well-formed error
body. That shape stays canned-only, and the suite says so.
2026-07-27 13:58:54 -04:00

80 lines
3.5 KiB
YAML

# MTConnect integration-test fixture — the official MTConnect C++ Agent, fed live data by
# a standard-library SHDR adapter.
#
# TWO services, and both are required:
#
# agent mtconnect/agent (the cppagent image). Serves /probe, /current and /sample on
# :5000. NOTE the repository name — the source project is `mtconnect/cppagent`
# but the published image is `mtconnect/agent`; `mtconnect/cppagent` does not
# exist on Docker Hub.
# adapter The data source. The agent image ships only the agent binary plus schemas and
# styles — there is NO bundled simulator — so without this every observation is
# UNAVAILABLE forever and the suite could prove nothing about reads or streaming.
# The Agent dials OUT to it (agent.cfg's Adapters block); it is not published to
# the host.
#
# Why pinned: the `latest` tag moves and a fixture that silently changes device-model
# behaviour turns a driver regression into an unexplained red. Bump deliberately.
#
# Usage (from the docker host, stack dir /opt/otopcua-mtconnect):
# docker compose up -d --wait
# docker compose down
#
# Or from a dev box, via the helper (see CLAUDE.md "Docker Workflow"):
# lmxopcua-fix sync mtconnect
# lmxopcua-fix up mtconnect # single-service-shaped stack, no profile argument
services:
agent:
image: mtconnect/agent:2.7.0.12
container_name: otopcua-mtconnect-agent
restart: "no"
labels:
project: lmxopcua
depends_on:
adapter:
condition: service_started
ports:
# Host port is overridable because macOS squats :5000 — AirPlay Receiver
# (ControlCenter) binds *:5000 and WINS the race, so `docker port` reports a healthy
# publish while every request answers "403 Forbidden / Server: AirTunes". On the
# Linux docker host the default is correct and needs no override. To run the fixture
# on a Mac:
# MTCONNECT_AGENT_HOST_PORT=5555 docker compose up -d --wait
# MTCONNECT_AGENT_ENDPOINT=http://127.0.0.1:5555 dotnet test ...
- "${MTCONNECT_AGENT_HOST_PORT:-5000}:5000"
volumes:
# Individual FILE binds, not a directory bind: /mtconnect/config is a VOLUME declared
# by the image, and mounting this whole folder over it would also drop docker-compose.yml
# and adapter.py into the Agent's config directory.
- ./agent.cfg:/mtconnect/config/agent.cfg:ro
- ./Devices.xml:/mtconnect/config/Devices.xml:ro
healthcheck:
# The image is alpine-based, so busybox wget is present (there is no curl). A 200 from
# /probe is the real readiness signal — the port accepts connections before the device
# model is parsed.
test: ["CMD-SHELL", "wget -q -O /dev/null http://127.0.0.1:5000/probe || exit 1"]
interval: 5s
timeout: 3s
retries: 12
start_period: 5s
adapter:
image: python:3.13-alpine
container_name: otopcua-mtconnect-adapter
restart: "no"
labels:
project: lmxopcua
volumes:
- ./adapter.py:/fixtures/adapter.py:ro
# Stock image + a bind-mounted script: this fixture needs no `build:` step at all, unlike
# the Modbus / S7 / AB fixtures whose simulators are built from a Dockerfile.
command: ["python", "-u", "/fixtures/adapter.py", "--host", "0.0.0.0", "--port", "7878"]
expose:
- "7878"
healthcheck:
test: ["CMD-SHELL", "python -c \"import socket; socket.create_connection(('127.0.0.1', 7878), timeout=2).close()\" || exit 1"]
interval: 5s
timeout: 3s
retries: 6
start_period: 3s