Files
glint/internal/cicontext/context_test.go
T
k3nny 04f17f8616
ci / vet, staticcheck, test, build (push) Successful in 2m25s
test(coverage): add unit tests across all packages; remove dead code
- Added comprehensive table-driven test suites for all packages:
  cmd/glint, cicontext, fetcher, graph, linter, model, resolver.
  Coverage reaches 98%+ statement coverage across the codebase.
- Replaced os.Exit calls in cmd/glint with an `exit` variable so tests
  can capture exit codes without terminating the test process.
- Removed unreachable code found during coverage analysis:
  dead guard in cicontext.parseRegexLiteral; dead len(jobs)==0 branch
  in graph.Pipeline; skipWin struct field and dead continue in
  graph.convertToPNG; pipelineSVG return type simplified to string.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-14 22:03:46 +02:00

254 lines
7.4 KiB
Go

package cicontext
import (
"testing"
)
// ── New / IsEmpty / Get / Inject ──────────────────────────────────────────────
func TestNew_Empty(t *testing.T) {
ctx := New("", "", "", nil)
if !ctx.IsEmpty() {
t.Error("expected empty context")
}
if ctx.Get("CI_COMMIT_BRANCH") != "" {
t.Error("expected empty Get on empty context")
}
}
func TestNew_Branch(t *testing.T) {
ctx := New("feature/abc", "", "", nil)
if ctx.Get("CI_COMMIT_BRANCH") != "feature/abc" {
t.Errorf("unexpected branch: %q", ctx.Get("CI_COMMIT_BRANCH"))
}
if ctx.Get("CI_COMMIT_REF_SLUG") != "feature-abc" {
t.Errorf("unexpected slug: %q", ctx.Get("CI_COMMIT_REF_SLUG"))
}
if ctx.Get("CI_PIPELINE_SOURCE") != "push" {
t.Error("expected default source=push for branch")
}
}
func TestNew_Tag(t *testing.T) {
ctx := New("", "v1.0.0", "", nil)
if ctx.Get("CI_COMMIT_TAG") != "v1.0.0" {
t.Errorf("unexpected tag: %q", ctx.Get("CI_COMMIT_TAG"))
}
// tag should clear CI_COMMIT_BRANCH
if ctx.Get("CI_COMMIT_BRANCH") != "" {
t.Error("CI_COMMIT_BRANCH should be cleared when tag is set")
}
if ctx.Get("CI_PIPELINE_SOURCE") != "push" {
t.Error("expected default source=push for tag")
}
}
func TestNew_BranchAndTagTogether(t *testing.T) {
// branch set first, then tag overwrites REF_NAME and clears BRANCH
ctx := New("main", "v2.0", "", nil)
if ctx.Get("CI_COMMIT_BRANCH") != "" {
t.Error("BRANCH should be cleared by tag")
}
if ctx.Get("CI_COMMIT_TAG") != "v2.0" {
t.Errorf("unexpected tag %q", ctx.Get("CI_COMMIT_TAG"))
}
}
func TestNew_ExtraVars(t *testing.T) {
ctx := New("main", "", "", []string{"DEPLOY_ENV=prod", "BAD_NO_EQUALS"})
if ctx.Get("DEPLOY_ENV") != "prod" {
t.Errorf("extra var not set: %q", ctx.Get("DEPLOY_ENV"))
}
if ctx.Get("BAD_NO_EQUALS") != "" {
t.Error("malformed KEY=VALUE should not be set")
}
}
func TestNew_SourceOverride(t *testing.T) {
ctx := New("", "", "schedule", nil)
if ctx.Get("CI_PIPELINE_SOURCE") != "schedule" {
t.Errorf("unexpected source: %q", ctx.Get("CI_PIPELINE_SOURCE"))
}
}
func TestNew_DefaultBranch(t *testing.T) {
ctx := New("feature", "", "", nil)
if ctx.Get("CI_DEFAULT_BRANCH") != "main" {
t.Errorf("unexpected default branch: %q", ctx.Get("CI_DEFAULT_BRANCH"))
}
}
func TestInject(t *testing.T) {
ctx := New("main", "", "", nil)
// pinned var should not be overwritten
ctx.Inject("CI_COMMIT_BRANCH", "other")
if ctx.Get("CI_COMMIT_BRANCH") != "main" {
t.Error("pinned var was overwritten by Inject")
}
// non-pinned var should be injected
ctx.Inject("MY_VAR", "hello")
if ctx.Get("MY_VAR") != "hello" {
t.Errorf("Inject failed: %q", ctx.Get("MY_VAR"))
}
}
func TestInject_NilVarsMap(t *testing.T) {
ctx := &Context{}
ctx.Inject("K", "v") // should not panic
if ctx.Get("K") != "v" {
t.Error("Inject on nil Vars should initialise the map")
}
}
func TestGet_NilContext(t *testing.T) {
var ctx *Context
if ctx.Get("X") != "" {
t.Error("nil context Get should return empty string")
}
}
// ── Summary ───────────────────────────────────────────────────────────────────
func TestSummary(t *testing.T) {
cases := []struct {
name string
branch string
tag string
source string
want string
}{
{"empty context", "", "", "", ""},
{"branch only", "main", "", "", "branch=main, source=push"},
{"tag only", "", "v1.0", "", "tag=v1.0, source=push"},
{"source only", "", "", "schedule", "source=schedule"},
{"branch + source", "develop", "", "merge_request_event", "branch=develop, source=merge_request_event"},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
ctx := New(tc.branch, tc.tag, tc.source, nil)
got := ctx.Summary()
if got != tc.want {
t.Errorf("got %q want %q", got, tc.want)
}
})
}
}
// ── ScalarString ──────────────────────────────────────────────────────────────
func TestScalarString(t *testing.T) {
cases := []struct {
name string
input any
wantS string
wantOK bool
}{
{"string", "hello", "hello", true},
{"bool true", true, "true", true},
{"bool false", false, "false", true},
{"int", 42, "42", true},
{"float64", 3.14, "3.14", true},
{"map with value key", map[string]any{"value": "v"}, "v", true},
{"map without value key", map[string]any{"description": "x"}, "", false},
{"nil", nil, "", false},
{"slice", []any{1, 2}, "", false},
{"nested map value", map[string]any{"value": 99}, "99", true},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
s, ok := ScalarString(tc.input)
if ok != tc.wantOK || s != tc.wantS {
t.Errorf("got (%q, %v) want (%q, %v)", s, ok, tc.wantS, tc.wantOK)
}
})
}
}
// ── ExpandVars / expandVarRefs ────────────────────────────────────────────────
func TestExpandVars(t *testing.T) {
t.Run("simple expansion", func(t *testing.T) {
ctx := &Context{Vars: map[string]string{"A": "hello", "B": "$A world"}}
ctx.ExpandVars()
if ctx.Vars["B"] != "hello world" {
t.Errorf("got %q", ctx.Vars["B"])
}
})
t.Run("transitive chain", func(t *testing.T) {
ctx := &Context{Vars: map[string]string{"A": "x", "B": "$A", "C": "$B"}}
ctx.ExpandVars()
if ctx.Vars["C"] != "x" {
t.Errorf("got %q", ctx.Vars["C"])
}
})
t.Run("circular reference stops", func(t *testing.T) {
ctx := &Context{Vars: map[string]string{"A": "$B", "B": "$A"}}
ctx.ExpandVars() // must not infinite-loop
})
t.Run("nil context is noop", func(t *testing.T) {
var ctx *Context
ctx.ExpandVars() // must not panic
})
t.Run("empty vars is noop", func(t *testing.T) {
ctx := &Context{}
ctx.ExpandVars() // must not panic
})
}
func TestExpandVarRefs(t *testing.T) {
vars := map[string]string{"FOO": "bar", "X": "123"}
cases := []struct {
name string
input string
want string
}{
{"no dollar", "hello", "hello"},
{"simple ref", "$FOO", "bar"},
{"braced ref", "${FOO}", "bar"},
{"unknown ref unchanged", "$UNKNOWN", "$UNKNOWN"},
{"unknown braced unchanged", "${UNKNOWN}", "${UNKNOWN}"},
{"trailing dollar", "abc$", "abc$"},
{"dollar at end after ident", "$X_", "$X_"},
// Malformed ${…} without closing brace: "${" emitted, ident chars consumed.
{"malformed brace no close", "${FOO", "${"},
{"mixed", "pre_${FOO}_$X", "pre_bar_123"},
{"dollar no ident after", "$ abc", "$ abc"},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
got := expandVarRefs(tc.input, vars)
if got != tc.want {
t.Errorf("got %q want %q", got, tc.want)
}
})
}
}
// ── slugify ───────────────────────────────────────────────────────────────────
func TestSlugify(t *testing.T) {
cases := []struct {
input string
want string
}{
{"main", "main"},
{"feature/my-branch", "feature-my-branch"},
{"Feature_123", "feature-123"},
{"--leading", "leading"},
{"trailing--", "trailing"},
{"v1.2.3", "v1-2-3"},
}
for _, tc := range cases {
t.Run(tc.input, func(t *testing.T) {
got := slugify(tc.input)
if got != tc.want {
t.Errorf("got %q want %q", got, tc.want)
}
})
}
}