04f17f8616
ci / vet, staticcheck, test, build (push) Successful in 2m25s
- 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>
143 lines
4.4 KiB
Go
143 lines
4.4 KiB
Go
package linter
|
|
|
|
import (
|
|
"testing"
|
|
|
|
"git.k3nny.fr/glint/internal/model"
|
|
)
|
|
|
|
// TestCheckDefault exercises the non-nil default block code path.
|
|
func TestCheckDefault(t *testing.T) {
|
|
// nil default → nil return (already covered implicitly; included for clarity)
|
|
if got := checkDefault(&model.Pipeline{}); got != nil {
|
|
t.Errorf("nil default: want nil findings, got %v", got)
|
|
}
|
|
|
|
// non-nil default with empty timeout → no findings
|
|
if got := checkDefault(&model.Pipeline{Default: &model.DefaultConfig{}}); got != nil {
|
|
t.Errorf("empty default: want no findings, got %v", got)
|
|
}
|
|
|
|
// non-nil default with invalid timeout → finding produced
|
|
findings := checkDefault(&model.Pipeline{Default: &model.DefaultConfig{Timeout: "bad-value"}})
|
|
if len(findings) == 0 {
|
|
t.Error("invalid timeout in default block should produce a finding")
|
|
}
|
|
}
|
|
|
|
// TestCheckJob_MissingScript covers the error/warning paths for jobs without a
|
|
// script field.
|
|
func TestCheckJob_MissingScript(t *testing.T) {
|
|
stageSet := map[string]bool{"build": true}
|
|
|
|
// No script, no extends → Error.
|
|
findings := checkJob("my-job", model.Job{Stage: "build"}, stageSet)
|
|
var gotErr bool
|
|
for _, f := range findings {
|
|
if f.Rule == RuleMissingScript && f.Severity == Error {
|
|
gotErr = true
|
|
}
|
|
}
|
|
if !gotErr {
|
|
t.Error("job with no script and no extends: expected Error finding GL003")
|
|
}
|
|
}
|
|
|
|
// TestCheckJob_ExtendsNoScript covers the Warning variant: a job that extends
|
|
// a base template but has no script (the script may come from the base, which
|
|
// could not be fetched).
|
|
func TestCheckJob_ExtendsNoScript(t *testing.T) {
|
|
stageSet := map[string]bool{"build": true}
|
|
job := model.Job{Stage: "build", Extends: ".base-template"}
|
|
findings := checkJob("my-job", job, stageSet)
|
|
var gotWarn bool
|
|
for _, f := range findings {
|
|
if f.Rule == RuleMissingScript && f.Severity == Warning {
|
|
gotWarn = true
|
|
}
|
|
}
|
|
if !gotWarn {
|
|
t.Error("job with extends but no script: expected Warning finding GL003")
|
|
}
|
|
}
|
|
|
|
// TestCheckJob_StageInputPlaceholder verifies that a stage value containing
|
|
// "$[[" (a component input placeholder) skips the unknown-stage check.
|
|
func TestCheckJob_StageInputPlaceholder(t *testing.T) {
|
|
stageSet := map[string]bool{"build": true}
|
|
job := model.Job{Stage: "$[[inputs.stage]]", Script: []any{"echo"}}
|
|
for _, f := range checkJob("my-job", job, stageSet) {
|
|
if f.Rule == RuleUnknownStage {
|
|
t.Error("stage with $[[ placeholder should not produce GL004")
|
|
}
|
|
}
|
|
}
|
|
|
|
// TestCheckJob_OnlyAndRules covers the only+rules conflict (GL005).
|
|
func TestCheckJob_OnlyAndRules(t *testing.T) {
|
|
stageSet := map[string]bool{"build": true}
|
|
job := model.Job{
|
|
Stage: "build",
|
|
Script: []any{"echo"},
|
|
Only: []any{"branches"},
|
|
Rules: []model.Rule{{When: "on_success"}},
|
|
}
|
|
var gotConflict bool
|
|
for _, f := range checkJob("my-job", job, stageSet) {
|
|
if f.Rule == RuleOnlyRulesConflict {
|
|
gotConflict = true
|
|
}
|
|
}
|
|
if !gotConflict {
|
|
t.Error("only+rules: expected GL005 conflict finding")
|
|
}
|
|
}
|
|
|
|
// TestCheckJob_ExceptAndRules covers the except+rules conflict (GL006).
|
|
func TestCheckJob_ExceptAndRules(t *testing.T) {
|
|
stageSet := map[string]bool{"build": true}
|
|
job := model.Job{
|
|
Stage: "build",
|
|
Script: []any{"echo"},
|
|
Except: []any{"tags"},
|
|
Rules: []model.Rule{{When: "on_success"}},
|
|
}
|
|
var gotConflict bool
|
|
for _, f := range checkJob("my-job", job, stageSet) {
|
|
if f.Rule == RuleExceptRulesConflict {
|
|
gotConflict = true
|
|
}
|
|
}
|
|
if !gotConflict {
|
|
t.Error("except+rules: expected GL006 conflict finding")
|
|
}
|
|
}
|
|
|
|
// TestCheckJob_RunField verifies that a job with a 'run:' block (instead of
|
|
// 'script:') is not flagged for missing script.
|
|
func TestCheckJob_RunField(t *testing.T) {
|
|
stageSet := map[string]bool{"build": true}
|
|
job := model.Job{Stage: "build", Run: map[string]any{"steps": []any{"echo hi"}}}
|
|
for _, f := range checkJob("my-job", job, stageSet) {
|
|
if f.Rule == RuleMissingScript {
|
|
t.Error("job with run: should not produce GL003 (missing script)")
|
|
}
|
|
}
|
|
}
|
|
|
|
// TestCheckJob_UnknownStage verifies that a job with a declared stage value that
|
|
// is not in the stageSet produces a GL004 finding (line 178-185).
|
|
func TestCheckJob_UnknownStage(t *testing.T) {
|
|
stageSet := map[string]bool{"build": true, "test": true}
|
|
job := model.Job{Stage: "unknown-stage", Script: []any{"echo"}}
|
|
var gotGL004 bool
|
|
for _, f := range checkJob("my-job", job, stageSet) {
|
|
if f.Rule == RuleUnknownStage {
|
|
gotGL004 = true
|
|
}
|
|
}
|
|
if !gotGL004 {
|
|
t.Error("job with undeclared stage: expected GL004 finding")
|
|
}
|
|
}
|