4ce7f86d4d
- `glint explain <RULE>`: new subcommand printing rule description, rationale, bad-YAML example and fix for every GL001–GL043 rule. `glint explain` (no arg) lists all rules with ID, severity, title. Rule IDs are case-insensitive. - GL042 (rules:if: evaluated reachability): warns when every rules:if: condition evaluates to false given the values of variables declared in the pipeline YAML, making the job statically unreachable. Conservative: only fires when all referenced variables are declared in YAML; predefined CI_* / GITLAB_* variables are skipped to avoid false positives. - GL043 (inherit: completeness): warns when inherit: default: is declared but there is no default: block in the pipeline (dead declaration), or when the list form names fields not set in the default: block. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
95 lines
2.6 KiB
Go
95 lines
2.6 KiB
Go
package linter
|
|
|
|
import (
|
|
"testing"
|
|
|
|
"git.k3nny.fr/glint/internal/model"
|
|
)
|
|
|
|
func TestCheckInheritCompleteness(t *testing.T) {
|
|
cases := []struct {
|
|
name string
|
|
default_ *model.DefaultConfig
|
|
inherit any // job.Inherit value
|
|
wantHit bool
|
|
}{
|
|
{
|
|
name: "no inherit — no finding",
|
|
inherit: nil,
|
|
wantHit: false,
|
|
},
|
|
{
|
|
name: "inherit: default: false, no default block — GL043",
|
|
inherit: map[string]any{"default": false},
|
|
wantHit: true,
|
|
},
|
|
{
|
|
name: "inherit: default: true, no default block — GL043",
|
|
inherit: map[string]any{"default": true},
|
|
wantHit: true,
|
|
},
|
|
{
|
|
name: "inherit: default: [image], no default block — GL043",
|
|
inherit: map[string]any{"default": []any{"image"}},
|
|
wantHit: true,
|
|
},
|
|
{
|
|
name: "inherit: default: false, default block exists — no finding",
|
|
default_: &model.DefaultConfig{Image: "node:20"},
|
|
inherit: map[string]any{"default": false},
|
|
wantHit: false,
|
|
},
|
|
{
|
|
name: "inherit: default: [image], image in default — no finding",
|
|
default_: &model.DefaultConfig{Image: "node:20"},
|
|
inherit: map[string]any{"default": []any{"image"}},
|
|
wantHit: false,
|
|
},
|
|
{
|
|
name: "inherit: default: [before_script], before_script NOT in default — GL043",
|
|
default_: &model.DefaultConfig{Image: "node:20"},
|
|
inherit: map[string]any{"default": []any{"before_script"}},
|
|
wantHit: true,
|
|
},
|
|
{
|
|
name: "inherit: default: [image, before_script], only image in default — GL043 for before_script",
|
|
default_: &model.DefaultConfig{Image: "node:20"},
|
|
inherit: map[string]any{"default": []any{"image", "before_script"}},
|
|
wantHit: true,
|
|
},
|
|
{
|
|
name: "inherit: variables only — no default check",
|
|
default_: nil,
|
|
inherit: map[string]any{"variables": false},
|
|
wantHit: false,
|
|
},
|
|
{
|
|
name: "inherit: unknown field in list — conservative, no finding",
|
|
default_: &model.DefaultConfig{Image: "node:20"},
|
|
inherit: map[string]any{"default": []any{"nonexistent_field"}},
|
|
wantHit: false, // unknown fields return true from defaultBlockHasField
|
|
},
|
|
}
|
|
|
|
for _, tc := range cases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
job := model.Job{Inherit: tc.inherit}
|
|
p := &model.Pipeline{
|
|
Default: tc.default_,
|
|
Jobs: map[string]model.Job{"test-job": job},
|
|
}
|
|
findings := checkInheritCompleteness(p)
|
|
|
|
hit := false
|
|
for _, f := range findings {
|
|
if f.Rule == RuleInheritNoDefault {
|
|
hit = true
|
|
}
|
|
}
|
|
if hit != tc.wantHit {
|
|
t.Errorf("GL043 hit=%v, want=%v; findings: %v", hit, tc.wantHit, findings)
|
|
}
|
|
})
|
|
}
|
|
}
|