| name | project-testing |
| description | Guide for writing and running tests in kubectl-mtv, including Go unit tests, MCP e2e tests (Python/pytest), and linting. Use when adding tests, running the test suite, or debugging test failures. |
Testing
Quick Reference
| Command | Purpose |
|---|
make test | Unit tests with coverage |
make lint | go vet + golangci-lint |
make fmt | Format Go code |
make test-e2e | CLI e2e smoke tests (requires cluster) |
make test-e2e-mcp | E2E MCP tests (local binary) |
make test-e2e-mcp-image | E2E MCP tests (container image) |
Unit Tests (Go)
Tests live alongside source files: pkg/mcp/tools/mtv_read_test.go next to mtv_read.go.
Table-Driven Pattern
func TestMyFunction(t *testing.T) {
tests := []struct {
name string
input string
expected string
wantErr bool
}{
{name: "valid input", input: "foo", expected: "bar"},
{name: "empty input", input: "", wantErr: true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result, err := MyFunction(tt.input)
if tt.wantErr {
if err == nil {
t.Errorf("expected error, got nil")
}
return
}
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if result != tt.expected {
t.Errorf("got %q, want %q", result, tt.expected)
}
})
}
}
Test Helpers
Common helpers used in the codebase:
testRegistry() -- creates a registry from test data for MCP tool tests
buildTestBinary() -- builds the kubectl-mtv binary for integration-style tests
testdataPath() -- resolves paths to testdata/ fixtures
findRepoRoot() -- locates the repo root from any test directory
loadRealRegistry() -- loads testdata/help_machine_output.json for registry tests
Use t.Helper() in shared helper functions and t.Skipf() when fixtures or binaries are unavailable.
Test Data
Fixtures live in testdata/ directories next to test files:
pkg/mcp/discovery/testdata/help_machine_output.json -- snapshot of help --machine output
Running
make test
go test ./pkg/mcp/tools/...
go test -run TestMyFunc ./pkg/...
Coverage output: coverage.out (generated by make test).
CLI E2E Smoke Tests (Python)
Located in tests/e2e_smoke.py. Tests the CLI binary end-to-end against a live OpenShift cluster with MTV/Forklift installed.
What It Tests
- version:
--client, full version, JSON output
- health: default,
--skip-logs, JSON/markdown output
- settings: list,
--all, get, specific setting, JSON/YAML output
- get: providers, plans, mappings (all formats), dynamic name lookups
- describe: providers and plans (dynamically discovered)
- positional args:
get provider <name>, describe provider <name>, get plan <name>, describe plan <name>
- create/patch/delete lifecycle: creates a test namespace, creates an OpenShift provider, gets/describes/patches/deletes it, cleans up
- help: topics (tsl, karl),
--machine schema (JSON/YAML, --read-only, --write, --short)
- error handling: missing required flags, conflicting flags, unknown commands
Running
make test-e2e
make build && python3 tests/e2e_smoke.py
Pattern
Uses stdlib only (no pytest). A main() calls test_* functions, each invoking the binary via subprocess.run with 60s timeout. Helpers assert_exit_ok, assert_exit_fail, assert_contains, assert_valid_json track pass/fail counts. Exits 0 on all pass, 1 on any failure.
Dynamic tests (describe, get by name) discover resources at runtime and skip gracefully if none exist.
E2E MCP Tests (Python/pytest)
Located in e2e/mcp/. These test the MCP server end-to-end via Streamable HTTP.
Structure
e2e/mcp/
├── conftest.py # Shared fixtures and helpers
├── pyproject.toml # pytest config, dependencies
├── Makefile # Server lifecycle + test targets
├── setup/ # Setup verification tests
├── providers/ # Provider CRUD tests
├── hosts/ # Host CRUD tests
├── plans/ # Plan CRUD tests
├── mappings/ # Mapping read tests
├── inventory/ # Inventory read tests
├── health/ # Health check tests
└── auth/ # Auth tests
Key Fixtures (conftest.py)
mcp_session (session) -- Streamable HTTP ClientSession using MCP_HTTP_URL with K8s auth
mcp_server_process (session) -- verifies server reachability
cleanup_test_resources (session, autouse) -- deletes test resources after suite
Helper: call_tool
result = await call_tool(mcp_session, "mtv_read", {
"command": "get plan",
"flags": {"namespace": "my-ns", "output": "json"}
})
Environment Variables
Required: GOVC_URL, GOVC_USERNAME, GOVC_PASSWORD, KUBE_API_URL, KUBE_TOKEN, ESXI_HOST_NAME, COLD_VMS, WARM_VMS, NETWORK_PAIRS, STORAGE_PAIRS.
Optional: MCP_HTTP_URL (default http://127.0.0.1:18443/mcp).
Running
make test-e2e-mcp
make test-e2e-mcp-image MCP_IMAGE=quay.io/yaacov/kubectl-mtv-mcp-server:latest
Or manually:
cd e2e/mcp
make server-start
make test
make server-stop
Test Ordering
Tests use @pytest.mark.order(N) for execution order. The testpaths in pyproject.toml controls directory order: setup -> providers -> hosts -> plans -> mappings -> inventory -> health.
Linting
make lint
make fmt
Fix lint issues before committing. The CI does not run linting automatically on PR, but it is expected to pass locally.