generated from mathias/template-go-web
oathcandidate/ is a separate Go module (mirrors swedsl's own oath/testdata/selfcandidate pattern, keeping var-go's transitive deps out of the deployed atlas binary) whose Build() parses the committed .gitea/workflows/cd.yml and checks the "oath" job exists and invokes cmd/vargo-gate. TDD: passes against the real file, fails closed on a fixture missing the job. Rewires the oath CI job to go-run vargo-gate from its real module path (git.d-ma.be/mathias/swedsl/oath/cmd/vargo-gate@oath/v0.28.0, unblocked by swedsl#35/#38) against VARGO_CANDIDATE_DIR=oathcandidate, instead of checking out swedsl and gating its hardcoded toy fixture. Private-module auth via a short-lived GIT_ASKPASS script (token never in argv, never written to git config, matches act_runner's env:-block-with-secrets gotcha). Discovered along the way: var-go's parser needs single-line, period-separated oath sentences with no Given/When/Then/And keyword stripping — this repo's older oaths (incl. #1) used an unverified multi-line keyword-prefixed style. #8's oath uses the proven format. Still not required by branch protection pending a real-PR confirmation. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
80 lines
3.1 KiB
Go
80 lines
3.1 KiB
Go
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")
|
|
}
|
|
}
|