package oathcandidate import ( "os" "path/filepath" "testing" oath "git.d-ma.be/mathias/swedsl/oath" ) // realOath is cad-atlas#8's actual oath text — the same var block committed to // that issue. Gating it against the REAL checked-out .gitea/workflows/cd.yml // proves the candidate reads real CI config, not a fixture standing in for it. // // Format note (discovered writing this test): var-go's parser requires a // SINGLE-LINE paragraph — sentences are split by "." within that line, not by // newline — and does NOT strip Given/When/Then/And keywords before matching a // step. cad-atlas's older oaths (e.g. issue #1) use a multi-line, keyword-prefixed // style that was never actually exercised against this parser (every prior gate // run errored before reaching real sentence matching). Plain declarative // sentences, period-separated, one line — see swedsl's own gate_test.go fixtures. const realOath = "```var\n" + `the CI workflow file ".gitea/workflows/cd.yml" is parsed. it defines a job named "oath". the job named "oath" invokes "cmd/vargo-gate".` + "\n```\n" // TestBuild_GatesRealWorkflow is named before Build existed (TDD): it fails to // compile until Build() and the workflow-parsing steps exist, and fails to pass // until they parse the REAL committed cd.yml correctly — this is the file that // must go from red to green, not a mock. func TestBuild_GatesRealWorkflow(t *testing.T) { // go test's cwd is already this package's dir (oathcandidate/), matching // SubprocessGate's cmd.Dir = candidateModuleDir contract exactly — no chdir // needed to reproduce it here. verdict, err := oath.Gate([]byte(realOath), Build()) if err != nil { t.Fatalf("Gate returned error: %v", err) } if !verdict.Pass { if verdict.Failure != nil { t.Fatalf("Gate did not pass: failure=%+v", *verdict.Failure) } t.Fatalf("Gate did not pass against the real committed cd.yml: %+v", verdict) } } // TestBuild_FailsClosedOnMissingJob proves the candidate is a REAL check, not a // rubber stamp: gating a workflow file that has no "oath" job must fail. func TestBuild_FailsClosedOnMissingJob(t *testing.T) { dir := t.TempDir() workflowsDir := filepath.Join(dir, ".gitea", "workflows") if err := os.MkdirAll(workflowsDir, 0o755); err != nil { t.Fatal(err) } noOathJob := "jobs:\n check:\n steps:\n - run: go test ./...\n" if err := os.WriteFile(filepath.Join(workflowsDir, "cd.yml"), []byte(noOathJob), 0o644); err != nil { t.Fatal(err) } cwd, err := os.Getwd() if err != nil { t.Fatal(err) } // SubprocessGate always runs the candidate with cmd.Dir = candidateModuleDir, // one level under the repo root (cad-atlas's real layout) — reproduce that by // chdir-ing into a sibling "candidate/" dir under the fixture root. candDir := filepath.Join(dir, "candidate") if err := os.MkdirAll(candDir, 0o755); err != nil { t.Fatal(err) } if err := os.Chdir(candDir); err != nil { t.Fatal(err) } t.Cleanup(func() { _ = os.Chdir(cwd) }) verdict, err := oath.Gate([]byte(realOath), Build()) if err == nil && verdict.Pass { t.Fatalf("expected the gate to fail closed on a workflow with no oath job, got Pass=true") } }