test-go — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited test-go (Agent Skill) and scored it 45/100 (orange). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 2 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 2 flagged
A base64 string of 128+ characters appears in a documentation file. Encoded prompt injection hides the hostile instruction in base64 — invisible to keyword filters — and relies on the agent's ability to decode it at runtime. There is no normal authoring reason to embed a multi-hundred-byte base64 blob in skill docs.
*.sig, SIGNATURES) outside the documentation.Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
Examples below use two placeholders. They are not literal - substitute the actual names from the project being worked on, matching the case of the surrounding context:
{PROJECT} - the project's identifier (e.g. env-var prefix, repo name). In env vars: MYAPP_…. In prose: MyApp or myapp.{integration} - an external system or adapter name (e.g. jira, stripe, claude). In paths: internal/tracker/jira/. In env vars: JIRA_…. In type names: JiraAdapter..go files in assets/ and the examples below use sample names (package example, Adapter, NewAdapter, FunctionName, MYAPP_INTEGRATION_TEST) so they stay syntactically valid Go. Rename each to the project's real name. The templates carry no comments; see Comments before adding one.
Before writing any test, determine which category it belongs to:
| Category | Characteristics | Run condition |
|---|---|---|
| Unit | Deterministic, no I/O, no network | Always (make test) |
| Unit with fixtures | Reads testdata/ files, uses t.TempDir() | Always |
| Unit with httptest | Spins up httptest.NewServer, tests HTTP adapters | Always |
| Integration | Talks to real external service | Env-gated by per-adapter variable: {PROJECT}_{INTEGRATION}_TEST=1 |
Pick the lightest category that validates the behavior.
Every test file in this project follows this skeleton: helpers first, then test functions. Internalize it - do not deviate. Declaration order carries the structure, so do not add banner comments to mark the sections; the go-codestyle rule bans them.
package pkg
import "testing"
func mustThing(t *testing.T) *Thing {
t.Helper()
thing, err := NewThing()
if err != nil {
t.Fatalf("NewThing: %v", err)
}
t.Cleanup(thing.Close)
return thing
}
func TestThingDoesWork(t *testing.T) {
t.Parallel()
thing := mustThing(t)
if err := thing.DoWork(); err != nil {
t.Fatalf("DoWork() = %v, want nil", err)
}
}The package clause is pkg, or pkg_test for a black-box test.
Key rules this project enforces:
t.Helper() is the first statement in every helper - no exceptions.t.Cleanup() for teardown in helpers; defer only in test functions themselves, and never to wait on background work.t.Parallel() at both test and subtest level for independent cases.t.TempDir() for filesystem isolation - never write to fixed paths.t.Setenv() for environment variables and t.Chdir() for the working directory, never in a parallel test.errors.As() / errors.Is() - never string comparison.t.Fatal / t.Fatalf / t.FailNow only from the goroutine running the test; t.Error never from one that can outlive it.t.Context() is canceled just before Cleanup functions run, and a defer in the test body runs before both. So defer wg.Wait() on a goroutine that exits on t.Context().Done() waits for something nothing has told to stop, and the test hangs to panic: test timed out. Register the wait with t.Cleanup(wg.Wait), which runs after the cancellation. This is the one exception to rule 2.
t.Setenv and t.Chdir mutate the whole process, so calling either in a parallel test or a test with a parallel ancestor panics with testing: test using t.Setenv, t.Chdir, or cryptotest.SetGlobalRandom can not use t.Parallel. Two ways out: drop t.Parallel() from that one test, or have the code under test take the value as a parameter instead of reading the process, which keeps the test parallel. testing.AllocsPerRun is the same class: it panics when a parallel test is running while it is called, so never call it from a parallel test or from a subtest of one.
FailNow stops the goroutine it runs on and nothing else, so t.Fatal from a server handler fails the test without stopping it and the caller sees a connection error instead of the real cause. t.Error is no safer: once the test has returned, it panics with Fail in goroutine after <test> has completed, net/http recovers that panic inside the handler, and the assertion disappears - the test prints PASS while the suite exits FAIL naming no test at all. Record what the goroutine saw and assert on it from the test body.
A test file carries no comments by default. The test name, the case names, and the failure messages say what is checked; a comment that repeats them is one more thing the next reader has to verify, and it goes stale when the test changes. Write the test so it needs no explanation.
Test, Benchmark, or Fuzz function, a helper, or a test double. When the name does not say what the test checks, rename it.loadFixture comment in HTTP Adapter Testing is one.Use when multiple cases share identical execution logic. This is the dominant pattern in this project.
func TestSanitizeKey(t *testing.T) {
t.Parallel()
tests := []struct {
name string
input string
want string
wantErr bool
}{
{"simple key", "ABC-123", "ABC-123", false},
{"empty input", "", "", true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
got, err := SanitizeKey(tt.input)
if tt.wantErr {
if err == nil {
t.Fatalf("SanitizeKey(%q) = %q, want error", tt.input, got)
}
return
}
if err != nil {
t.Fatalf("SanitizeKey(%q) unexpected error: %v", tt.input, err)
}
if got != tt.want {
t.Errorf("SanitizeKey(%q) = %q, want %q", tt.input, got, tt.want)
}
})
}
}When NOT to use tables: cases needing different setup, conditional mocking, or complex branching. Write separate t.Run blocks or separate test functions instead.
Table struct conventions:
name string as the first fieldwantErr bool for error presence; add wantKind field for typed error checkingFuncName(%v) = %v, want %vThis project uses custom typed errors extensively. Test error semantics, never strings.
func assertTrackerErrorKind(t *testing.T, err error, want domain.TrackerErrorKind) {
t.Helper()
if err == nil {
t.Fatalf("expected error with kind %q, got nil", want)
}
var te *domain.TrackerError
if !errors.As(err, &te) {
t.Fatalf("error type = %T, want *domain.TrackerError", err)
}
if te.Kind != want {
t.Errorf("TrackerError.Kind = %q, want %q", te.Kind, want)
}
}Error testing rules:
errors.As() for type assertion - validates the error chain, not just the toperrors.Is() for sentinel comparisonKind/Field/Op of typed errors, not .Error() stringst.Fatal when nil-error means subsequent assertions will panic; t.Error otherwiseHelpers belong at the top of the test file, before test functions. Each adapter package defines its own helpers - do not create a shared testutil package.
Common helper patterns in this project:
func mustAdapter(t *testing.T, config map[string]any) *Adapter {
t.Helper()
a, err := NewAdapter(config)
if err != nil {
t.Fatalf("NewAdapter: %v", err)
}
return a.(*Adapter)
}
func loadFixture(t *testing.T, name string) []byte {
t.Helper()
data, err := os.ReadFile("testdata/" + name)
if err != nil {
t.Fatalf("reading fixture %s: %v", name, err)
}
return data
}
func validConfig(endpoint string) map[string]any {
return map[string]any{
"endpoint": endpoint,
"api_key": "[email protected]:api_token_123",
"project": "PROJ",
}
}
func closeStore(t *testing.T, s *Store) {
t.Helper()
if err := s.Close(); err != nil {
t.Errorf("Close: %v", err)
}
}Naming conventions:
mustX - creates X or fatals (setup that cannot fail gracefully)validX / defaultX - returns baseline config/params for test customizationloadFixture - reads from testdata/assertX / requireX - assertion helpers (require fatals, assert errors)Adapter tests use httptest.NewServer with handler functions that return fixture data. Never mock the http.Client itself.
func TestFetchIssues(t *testing.T) {
t.Parallel()
// loadFixture fatals, and t.Fatal cannot stop the test from a handler.
fixture := loadFixture(t, "search_single_page.json")
var gotAuth atomic.Value
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
gotAuth.Store(r.Header.Get("Authorization"))
w.Header().Set("Content-Type", "application/json")
w.Write(fixture)
}))
defer srv.Close()
adapter := mustAdapter(t, validConfig(srv.URL))
issues, err := adapter.FetchIssuesByStates(context.Background(), []string{"To Do"})
if err != nil {
t.Fatalf("FetchIssuesByStates: %v", err)
}
if auth, _ := gotAuth.Load().(string); auth == "" {
t.Errorf("Authorization header = %q, want non-empty", auth)
}
if len(issues) != 1 {
t.Errorf("FetchIssuesByStates returned %d issues, want 1", len(issues))
}
}Rules for httptest usage:
httptest.NewServer; the handler writes bytes it already holds. A handler that calls a t.Fatal-ing helper leaves the client with Get "http://127.0.0.1:PORT": EOF instead of the real causetestdata/ - do not inline large JSON stringsatomic values and counters for anything the handler records, because the handler runs on another goroutineIntegration tests talk to real external services. They MUST be gated by environment variables and skip cleanly when disabled.
Read references/integration-tests.md for the full integration testing protocol including skip helpers, required env vars, and CI configuration.
Quick reference:
func skipUnlessIntegration(t *testing.T) {
t.Helper()
if os.Getenv("MYAPP_INTEGRATION_TEST") != "1" {
t.Skip("skipping integration test: set MYAPP_INTEGRATION_TEST=1 to enable")
}
}integration_test.go (separate from unit tests)t.Skip, not silent pass - skipped tests are visible in outputEvery adapter (tracker or agent) must prove it satisfies the domain interface. Use compile-time interface checks and conformance test suites.
var _ domain.TrackerAdapter = (*Adapter)(nil)
var _ domain.AgentAdapter = (*mockAgentAdapter)(nil)What conformance tests must cover (per architecture Section 17):
This project uses three kinds of test doubles - pick the lightest one that works.
| Double | Purpose | Example |
|---|---|---|
| Stub | Returns fixed data | validConfig() returning a map |
| Fake | Simplified working implementation | internal/agent/mock package, internal/tracker/file adapter |
| Spy | Records interactions for later assertion | httptest handler with atomic counters |
Registered fakes (when this project provides them, e.g. internal/agent/mock/, internal/tracker/file/) are first-class adapters in the registry. Use them for orchestrator-level tests that need controllable adapter behavior - check the project layout for the actual package paths.
Mock struct pattern:
type mockTrackerAdapter struct{}
var _ domain.TrackerAdapter = (*mockTrackerAdapter)(nil)
func (m *mockTrackerAdapter) FetchIssuesByStates(ctx context.Context, states []string) ([]domain.Issue, error) {
return nil, nil
}Implement every method of the interface; the compile-time check fails until you do.
Store test data in testdata/ within the package directory. Go tooling ignores this directory during builds.
internal/tracker/{integration}/testdata/
search_single_page.json
search_multi_page_1.json
search_multi_page_2.json
issue_detail.json
comments.jsonRules:
testdata/ directory per package that needs fixturessearch_empty.json, malformed.json, comments_multi_page_1.jsonloadFixture(t, name) helper - never hardcode paths in test functionsEvery assertion must produce a message diagnosable without reading the test source.
Format: FuncName(inputs) = got, want expectedCorrect, naming the function, the input, got, and want:
t.Errorf("SanitizeKey(%q) = %q, want %q", tt.input, got, tt.want)
t.Errorf("TrackerError.Kind = %q, want %q", te.Kind, want)Incorrect, missing context:
t.Errorf("got %q, want %q", got, tt.want)
t.Error("wrong result")got before want in message ordering%q for strings (shows quotes and escapes), %v for general values%d for integers, %f for floats - match the typeAfter writing or modifying tests, verify:
make test passes with -race (the default)t.Parallel(); those using t.Setenv, t.Chdir or testing.AllocsPerRun do nott.Fatal / t.Error inside an http.HandlerFunc or any other spawned goroutinet.Helper() as first statementerrors.As() / errors.Is(), not string comparisoncmp.Diff if needed)testdata/ and are loaded via helpert.TempDir() used for any filesystem operations~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.