> ## Documentation Index
> Fetch the complete documentation index at: https://docs.oasm.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Build & test

> Task commands, testing rules, and how connectors are published

Every task is a thin wrapper over `go`. Run the equivalent command directly when working inside a single module.

## Commands

```bash theme={null}
task test        # go test per module
task vet         # go vet per module
task fmt         # gofmt per module
task tidy        # go mod tidy per module with GOWORK=off
task manifest    # regenerate manifest.json
```

Useful when working inside one connector:

```bash theme={null}
cd vulnerabilities/example
go test ./... -count=1
go vet ./...
go build ./...
```

<Info>
  Module-scoped commands must run inside the module's directory. `go test ./...` from the repo root does not reach the connectors unless `go.work` unifies them.
</Info>

## Testing rules

Tests need **no Docker, no network, and no credentials** — that is the primary gate. CI runs the same `go test` on every module with `GOWORK=off`.

A connector is not mergeable until its adapter covers:

* **Parsing** — raw tool output → `Finding`.
* **Severity mapping** — every value the tool emits lands on the enum.
* **Error mapping** — `fatal:` vs `retryable:` prefixes.
* **Cancellation** — `Execute` returns when `ctx.Done()` closes.

Test the adapter, not the SDK. Inject a fake tool binary or an injected client so the tests stay hermetic.

```go theme={null}
func TestExecuteCancellation(t *testing.T) {
    ctx, cancel := context.WithCancel(context.Background())
    out := make(chan connector.Finding)
    done := make(chan error, 1)

    go func() { done <- (&ExampleAdapter{}).Execute(ctx, map[string]any{"target": "x"}, out) }()
    cancel()

    select {
    case err := <-done:
        if !errors.Is(err, context.Canceled) {
            t.Fatalf("expected context.Canceled, got %v", err)
        }
    case <-time.After(time.Second):
        t.Fatal("Execute did not return after cancellation")
    }
}
```

## Building images

Each connector ships its own multi-stage `Dockerfile` with the repo root as build context. Build it directly:

```bash theme={null}
docker build -f vulnerabilities/example/Dockerfile -t connector-example:1.0.0 .
```

Keep the image minimal: build statically (`CGO_ENABLED=0`), land a single binary on a minimal base image, create an unprivileged user, and `USER` it.

## CI and publishing

The connector workflow is path-filtered to `sdk/**`, `ports_scanner/**`, `vulnerabilities/**`, `url_discovery/**`, `scripts/**`, and the workflow file itself.

* **On push / pull request** — discover every `<category>/<slug>/manifest.yaml`, derive a build matrix from `slug` and `version`, run the full test suite, then build each image with `push: false`. Nothing is published.
* **On manual dispatch** — the same pipeline plus a push job that publishes `connector-<slug>:<version>` and `:latest`. A `dry_run` input builds and tests without publishing, and an `image_tag` input overrides the tag.

Because publication is manual and tag-driven, the `version` field in `manifest.yaml` is meaningful: bumping it produces a new immutable image tag.

## Design constraints

* **The SDK is the only shared code.** Anything tool-specific that leaks upward makes every connector heavier and the contract fuzzier.
* **`manifest.json` is derived.** Regenerate it; never edit or patch it by hand.
* **The container is the boundary.** Connectors run unprivileged with no inbound network surface.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.