integration/README.md
This directory contains the integration test suites that CI runs before merging PRs and before building main.
These tests exist to catch regressions that are easy to miss with unit tests alone, especially when different PRs touch adjacent code paths and the breakage only appears once the pieces are wired together. Typical examples are:
PicoClaw currently uses two related mechanisms:
*_integration_test.go files and guarded by //go:build integrationintegration/suites/ that start real dependencies and run one or more of those Go tests in CIThat distinction matters:
If a test should actively protect merges between PRs, it should be reachable from scripts/run-integration-tests.sh, either by extending an existing suite or by adding a new one.
Some integration-tagged tests are intentionally opt-in and not part of the Docker suites. For example, real CLI smoke tests under pkg/providers/cli/ depend on external binaries being installed locally. Those are useful for manual verification, but they do not gate PR merges.
The integration jobs in .github/workflows/pr.yml and .github/workflows/build.yml both execute:
bash ./scripts/run-integration-tests.sh
The runner auto-discovers every suite under integration/suites/, so adding a suite does not require editing the GitHub Actions workflow.
integration/docker-compose.runner.yml.integration/suites/<suite-name>/.suite.env, merges the shared compose file with the suite-specific compose files, starts dependency services, runs the suite command, and then tears everything down.GOFLAGS=-tags=goolm,stdjson,integration, so tests run with the same build tags used by CI.In practice, each suite gives us:
integration/suites/mcp-streamable/ is the reference example today.
It does three things:
integration/fixtures/mcp-streamable-server/TestIntegration_RealConfiguredServer to verify that PicoClaw can connect to a real server, discover tools, invoke one, and validate the response payloadThat suite complements TestIntegration_StreamableHTTPCompatibility, which exercises the same area in-process. Together they cover both protocol behavior and real service wiring.
Each suite directory must contain:
suite.envdocker-compose.yml or docker-compose.*.ymlExample:
integration/suites/my-suite/
├── docker-compose.yml
└── suite.env
suite.env is sourced by the runner script and must define:
TEST_COMMAND: shell command executed inside the integration runner containerOptional fields:
RUNNER_SERVICE: override the default runner service name (integration-runner)Example:
TEST_COMMAND='go test ./pkg/mcp -run TestIntegration_RealConfiguredServer -v'
docker compose plugin for Docker-backed suitesmake integration-test
Equivalent direct command:
bash ./scripts/run-integration-tests.sh
bash ./scripts/run-integration-tests.sh mcp-streamable
This is the fastest way to reproduce exactly what the CI integration job does for one suite.
For faster iteration while writing the test, you can run the Go test itself without Docker:
go test -tags=goolm,stdjson,integration ./pkg/mcp -run TestIntegration_StreamableHTTPCompatibility -v
You can also run the real-server smoke test directly if you provide the same environment variables that the Docker suite would inject:
PICOCLAW_MCP_REAL_SERVER_JSON='{"enabled":true,"type":"http","url":"http://127.0.0.1:8080/mcp"}' \
PICOCLAW_MCP_REAL_TOOL_NAME=echo \
PICOCLAW_MCP_REAL_TOOL_ARGS_JSON='{"message":"hello"}' \
PICOCLAW_MCP_REAL_EXPECT_SUBSTRING=hello \
go test -tags=goolm,stdjson,integration ./pkg/mcp -run TestIntegration_RealConfiguredServer -v
Notes:
-short: the current integration tests skip in short modego test for tight feedback loops, then validate the Docker suite before committingReach for an integration test when the risk lives in the interaction, not just in the function body. Good candidates include:
Prefer a unit test when the behavior is pure, local, and fully controllable in-process.
Describe the real workflow that could break after a merge. The sharper the scenario, the better the test will age.
Examples:
Add or extend a *_integration_test.go file in the package that owns the behavior and gate it with:
//go:build integration
Guidelines:
Use this rule of thumb:
If an existing suite already exercises the same subsystem, extend it. Otherwise create:
integration/suites/<name>/
├── docker-compose.yml
└── suite.env
Use integration/fixtures/ for reusable helper services or fake servers.
In suite.env, point TEST_COMMAND at the Go test you want CI to run.
Examples:
TEST_COMMAND='go test ./pkg/mcp -run TestIntegration_RealConfiguredServer -v'
TEST_COMMAND='go test ./pkg/somepkg -run TestIntegration_MyScenario -v'
You can also run multiple tests if they share the same environment, but keep suites cohesive and easy to diagnose when they fail.
Suite compose files can:
integration-runner servicePractical advice:
At minimum:
go test -tags=goolm,stdjson,integration ./path/to/package -run TestIntegration_Name -v
bash ./scripts/run-integration-tests.sh <suite-name>
The first command helps while authoring. The second proves that the CI path works end to end.
Before opening the PR, check that the new suite:
Once committed, the suite will be auto-discovered by the CI integration job.