feat(cicontext): rules:changes: path-glob evaluation; 100% test coverage
- Add --changes PATH and --changes-from REF flags to glint check and glint graph
for rules:changes: evaluation. --changes marks files explicitly; --changes-from
runs git diff --name-only <REF> automatically. Both flags can be combined.
- Implement doublestar glob matching (*, ** across path segments) in EvalJob and
EvalWorkflow; extended {paths, compare_to} map form supported.
- Without --changes/--changes-from the condition stays permissive (existing behaviour).
- Context summary line now shows changed-file count when file data is provided.
- Achieve 100% statement coverage: comprehensive tests added across all packages;
removed provably dead code; added testability seams (exit, userHomeDirFn,
execCommandOutput variables) to cover previously unreachable paths.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -52,6 +52,18 @@ func TestCacheWrite_EmptyDir(t *testing.T) {
|
||||
cacheWrite("", "key", []byte("data"))
|
||||
}
|
||||
|
||||
// TestCacheWrite_DirIsFile covers the os.MkdirAll error path (cache.go:29-31)
|
||||
// when the cache dir path is occupied by a regular file.
|
||||
func TestCacheWrite_DirIsFile(t *testing.T) {
|
||||
f := filepath.Join(t.TempDir(), "file")
|
||||
if err := os.WriteFile(f, []byte("occupied"), 0o644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
// MkdirAll(f) fails because f is a file, not a directory.
|
||||
cacheWrite(f, "key", []byte("data"))
|
||||
// No panic, no error returned — the function silently returns.
|
||||
}
|
||||
|
||||
func TestCacheWrite_MkdirAll(t *testing.T) {
|
||||
dir := filepath.Join(t.TempDir(), "sub", "dir")
|
||||
cacheWrite(dir, "k", []byte("v"))
|
||||
|
||||
@@ -1,12 +1,32 @@
|
||||
package fetcher
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// roundTripFunc allows constructing a custom http.RoundTripper from a function.
|
||||
type roundTripFunc func(*http.Request) (*http.Response, error)
|
||||
|
||||
func (f roundTripFunc) RoundTrip(r *http.Request) (*http.Response, error) { return f(r) }
|
||||
|
||||
// errReader is an io.Reader that always returns an error.
|
||||
type errReader struct{}
|
||||
|
||||
func (e errReader) Read([]byte) (int, error) { return 0, errors.New("read error") }
|
||||
|
||||
// replaceTransport temporarily replaces http.DefaultTransport and restores it.
|
||||
func replaceTransport(t *testing.T, rt http.RoundTripper) {
|
||||
t.Helper()
|
||||
orig := http.DefaultTransport
|
||||
http.DefaultTransport = rt
|
||||
t.Cleanup(func() { http.DefaultTransport = orig })
|
||||
}
|
||||
|
||||
// ── firstNonEmpty ─────────────────────────────────────────────────────────────
|
||||
|
||||
func TestFirstNonEmpty(t *testing.T) {
|
||||
@@ -242,6 +262,74 @@ func TestFetchURL_Offline(t *testing.T) {
|
||||
if err == nil { t.Fatal("expected error in offline mode") }
|
||||
}
|
||||
|
||||
// TestFetchFile_NewRequestFails covers gitlab.go:113-115 — http.NewRequest error
|
||||
// when the BaseURL contains a control character (null byte) making the URL invalid.
|
||||
func TestFetchFile_NewRequestFails(t *testing.T) {
|
||||
cfg := GitLabConfig{BaseURL: "https://example.com\x00"}
|
||||
_, err := cfg.FetchFile("p/q", "/f.yml", "main")
|
||||
if err == nil {
|
||||
t.Fatal("expected error for URL with null byte")
|
||||
}
|
||||
}
|
||||
|
||||
// TestFetchFile_DoFails covers gitlab.go:132-134 — http.DefaultClient.Do error.
|
||||
func TestFetchFile_DoFails(t *testing.T) {
|
||||
replaceTransport(t, roundTripFunc(func(r *http.Request) (*http.Response, error) {
|
||||
return nil, errors.New("transport error")
|
||||
}))
|
||||
cfg := GitLabConfig{BaseURL: "https://example.com"}
|
||||
_, err := cfg.FetchFile("p/q", "/f.yml", "main")
|
||||
if err == nil {
|
||||
t.Fatal("expected error when transport fails")
|
||||
}
|
||||
}
|
||||
|
||||
// TestFetchFile_ReadBodyFails covers gitlab.go:138-140 — io.ReadAll error on the
|
||||
// response body.
|
||||
func TestFetchFile_ReadBodyFails(t *testing.T) {
|
||||
replaceTransport(t, roundTripFunc(func(r *http.Request) (*http.Response, error) {
|
||||
return &http.Response{
|
||||
StatusCode: http.StatusOK,
|
||||
Body: io.NopCloser(errReader{}),
|
||||
Header: make(http.Header),
|
||||
}, nil
|
||||
}))
|
||||
cfg := GitLabConfig{BaseURL: "https://example.com"}
|
||||
_, err := cfg.FetchFile("p/q", "/f.yml", "main")
|
||||
if err == nil {
|
||||
t.Fatal("expected error when body read fails")
|
||||
}
|
||||
}
|
||||
|
||||
// TestFetchURL_GetFails covers gitlab.go:173-175 — http.Get error.
|
||||
func TestFetchURL_GetFails(t *testing.T) {
|
||||
replaceTransport(t, roundTripFunc(func(r *http.Request) (*http.Response, error) {
|
||||
return nil, errors.New("get failed")
|
||||
}))
|
||||
cfg := GitLabConfig{}
|
||||
_, err := cfg.FetchURL("https://example.com/template.yml")
|
||||
if err == nil {
|
||||
t.Fatal("expected error when http.Get fails")
|
||||
}
|
||||
}
|
||||
|
||||
// TestFetchURL_ReadBodyFails covers gitlab.go:178-180 — io.ReadAll error on the
|
||||
// FetchURL response body.
|
||||
func TestFetchURL_ReadBodyFails(t *testing.T) {
|
||||
replaceTransport(t, roundTripFunc(func(r *http.Request) (*http.Response, error) {
|
||||
return &http.Response{
|
||||
StatusCode: http.StatusOK,
|
||||
Body: io.NopCloser(errReader{}),
|
||||
Header: make(http.Header),
|
||||
}, nil
|
||||
}))
|
||||
cfg := GitLabConfig{}
|
||||
_, err := cfg.FetchURL("https://example.com/template.yml")
|
||||
if err == nil {
|
||||
t.Fatal("expected error when FetchURL body read fails")
|
||||
}
|
||||
}
|
||||
|
||||
func TestFetchURL_NotOK(t *testing.T) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusForbidden)
|
||||
|
||||
Reference in New Issue
Block a user