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>
254 lines
9.5 KiB
Go
254 lines
9.5 KiB
Go
package fetcher
|
|
|
|
import (
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"os"
|
|
"testing"
|
|
)
|
|
|
|
// ── firstNonEmpty ─────────────────────────────────────────────────────────────
|
|
|
|
func TestFirstNonEmpty(t *testing.T) {
|
|
if firstNonEmpty("", "", "c") != "c" { t.Error("should return first non-empty") }
|
|
if firstNonEmpty("a", "b") != "a" { t.Error("should return first") }
|
|
if firstNonEmpty("", "") != "" { t.Error("all empty → empty") }
|
|
}
|
|
|
|
// ── AutoConfig ────────────────────────────────────────────────────────────────
|
|
|
|
func TestAutoConfig(t *testing.T) {
|
|
// Clear relevant env vars
|
|
for _, k := range []string{"CI_SERVER_URL", "GITLAB_URL", "GITLAB_TOKEN", "CI_JOB_TOKEN", "GITLAB_PRIVATE_TOKEN"} {
|
|
os.Unsetenv(k)
|
|
}
|
|
cfg := AutoConfig()
|
|
if cfg.BaseURL != "https://gitlab.com" {
|
|
t.Errorf("default BaseURL: %q", cfg.BaseURL)
|
|
}
|
|
if cfg.Token != "" {
|
|
t.Error("expected no token")
|
|
}
|
|
}
|
|
|
|
func TestAutoConfig_EnvVars(t *testing.T) {
|
|
os.Setenv("CI_SERVER_URL", "https://my.gitlab.example.com/")
|
|
os.Setenv("GITLAB_TOKEN", "glpat-test")
|
|
defer func() {
|
|
os.Unsetenv("CI_SERVER_URL")
|
|
os.Unsetenv("GITLAB_TOKEN")
|
|
}()
|
|
cfg := AutoConfig()
|
|
if cfg.BaseURL != "https://my.gitlab.example.com" {
|
|
t.Errorf("trailing slash not trimmed: %q", cfg.BaseURL)
|
|
}
|
|
if cfg.Token != "glpat-test" || cfg.Source != TokenPrivate {
|
|
t.Errorf("token: %q source: %v", cfg.Token, cfg.Source)
|
|
}
|
|
}
|
|
|
|
func TestAutoConfig_CIJobToken(t *testing.T) {
|
|
os.Unsetenv("GITLAB_TOKEN")
|
|
os.Setenv("CI_JOB_TOKEN", "job-token-123")
|
|
defer os.Unsetenv("CI_JOB_TOKEN")
|
|
cfg := AutoConfig()
|
|
if cfg.Token != "job-token-123" || cfg.Source != TokenJobToken {
|
|
t.Errorf("unexpected: %+v", cfg)
|
|
}
|
|
}
|
|
|
|
func TestAutoConfig_PrivateToken(t *testing.T) {
|
|
os.Unsetenv("GITLAB_TOKEN")
|
|
os.Unsetenv("CI_JOB_TOKEN")
|
|
os.Setenv("GITLAB_PRIVATE_TOKEN", "legacy-token")
|
|
defer os.Unsetenv("GITLAB_PRIVATE_TOKEN")
|
|
cfg := AutoConfig()
|
|
if cfg.Token != "legacy-token" || cfg.Source != TokenPrivate {
|
|
t.Errorf("unexpected: %+v", cfg)
|
|
}
|
|
}
|
|
|
|
func TestAutoConfig_GitlabURL(t *testing.T) {
|
|
os.Unsetenv("CI_SERVER_URL")
|
|
os.Setenv("GITLAB_URL", "https://gl.local")
|
|
defer os.Unsetenv("GITLAB_URL")
|
|
cfg := AutoConfig()
|
|
if cfg.BaseURL != "https://gl.local" {
|
|
t.Errorf("unexpected BaseURL: %q", cfg.BaseURL)
|
|
}
|
|
}
|
|
|
|
// ── WithOverrides ─────────────────────────────────────────────────────────────
|
|
|
|
func TestWithOverrides(t *testing.T) {
|
|
base := GitLabConfig{BaseURL: "https://gitlab.com", Token: "old", Source: TokenPrivate}
|
|
got := base.WithOverrides("https://other.com/", "new-token", "/cache", true)
|
|
if got.BaseURL != "https://other.com" { t.Errorf("BaseURL: %q", got.BaseURL) }
|
|
if got.Token != "new-token" { t.Error("Token not overridden") }
|
|
if got.CacheDir != "/cache" { t.Error("CacheDir not set") }
|
|
if !got.Offline { t.Error("Offline not set") }
|
|
// empty string leaves BaseURL alone
|
|
got2 := base.WithOverrides("", "", "", false)
|
|
if got2.BaseURL != "https://gitlab.com" { t.Error("empty override should not change BaseURL") }
|
|
}
|
|
|
|
// ── ForHost ───────────────────────────────────────────────────────────────────
|
|
|
|
func TestForHost(t *testing.T) {
|
|
cfg := GitLabConfig{BaseURL: "https://gitlab.com"}
|
|
got := cfg.ForHost("my-host.example.com")
|
|
if got.BaseURL != "https://my-host.example.com" {
|
|
t.Errorf("unexpected BaseURL: %q", got.BaseURL)
|
|
}
|
|
}
|
|
|
|
// ── HasToken ──────────────────────────────────────────────────────────────────
|
|
|
|
func TestHasToken(t *testing.T) {
|
|
if (GitLabConfig{}).HasToken() { t.Error("empty config should have no token") }
|
|
if !(GitLabConfig{Token: "x"}).HasToken() { t.Error("config with token should have token") }
|
|
}
|
|
|
|
// ── FetchFile ─────────────────────────────────────────────────────────────────
|
|
|
|
func TestFetchFile_FromCache(t *testing.T) {
|
|
dir := t.TempDir()
|
|
key := "https://gitlab.com|my/project|/templates/ci.yml|HEAD"
|
|
cacheWrite(dir, key, []byte("cached: true"))
|
|
cfg := GitLabConfig{BaseURL: "https://gitlab.com", CacheDir: dir}
|
|
data, err := cfg.FetchFile("my/project", "/templates/ci.yml", "")
|
|
if err != nil { t.Fatalf("unexpected error: %v", err) }
|
|
if string(data) != "cached: true" { t.Errorf("got %q", data) }
|
|
}
|
|
|
|
func TestFetchFile_Offline_Miss(t *testing.T) {
|
|
cfg := GitLabConfig{BaseURL: "https://gitlab.com", Offline: true}
|
|
_, err := cfg.FetchFile("p/q", "/f.yml", "main")
|
|
if err == nil { t.Fatal("expected error in offline mode") }
|
|
}
|
|
|
|
func TestFetchFile_OK(t *testing.T) {
|
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
w.WriteHeader(http.StatusOK)
|
|
w.Write([]byte("script: echo ok"))
|
|
}))
|
|
defer srv.Close()
|
|
cfg := GitLabConfig{BaseURL: srv.URL, Token: "tok", Source: TokenPrivate}
|
|
data, err := cfg.FetchFile("ns/proj", "/ci.yml", "main")
|
|
if err != nil { t.Fatalf("unexpected error: %v", err) }
|
|
if string(data) != "script: echo ok" { t.Errorf("got %q", data) }
|
|
}
|
|
|
|
func TestFetchFile_Unauthorized_NoToken(t *testing.T) {
|
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
w.WriteHeader(http.StatusUnauthorized)
|
|
}))
|
|
defer srv.Close()
|
|
cfg := GitLabConfig{BaseURL: srv.URL}
|
|
_, err := cfg.FetchFile("p/q", "/f.yml", "main")
|
|
if err == nil { t.Fatal("expected error") }
|
|
}
|
|
|
|
func TestFetchFile_Unauthorized_WithToken(t *testing.T) {
|
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
w.WriteHeader(http.StatusUnauthorized)
|
|
}))
|
|
defer srv.Close()
|
|
cfg := GitLabConfig{BaseURL: srv.URL, Token: "tok", Source: TokenPrivate}
|
|
_, err := cfg.FetchFile("p/q", "/f.yml", "main")
|
|
if err == nil { t.Fatal("expected error") }
|
|
}
|
|
|
|
func TestFetchFile_NotFound(t *testing.T) {
|
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
w.WriteHeader(http.StatusNotFound)
|
|
w.Write([]byte("not found"))
|
|
}))
|
|
defer srv.Close()
|
|
cfg := GitLabConfig{BaseURL: srv.URL}
|
|
_, err := cfg.FetchFile("p/q", "/f.yml", "main")
|
|
if err == nil { t.Fatal("expected error") }
|
|
}
|
|
|
|
func TestFetchFile_OtherStatus(t *testing.T) {
|
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
w.WriteHeader(http.StatusInternalServerError)
|
|
w.Write([]byte("server error"))
|
|
}))
|
|
defer srv.Close()
|
|
cfg := GitLabConfig{BaseURL: srv.URL}
|
|
_, err := cfg.FetchFile("p/q", "/f.yml", "main")
|
|
if err == nil { t.Fatal("expected error") }
|
|
}
|
|
|
|
func TestFetchFile_JobToken(t *testing.T) {
|
|
var gotHeader string
|
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
gotHeader = r.Header.Get("JOB-TOKEN")
|
|
w.WriteHeader(http.StatusOK)
|
|
w.Write([]byte("ok"))
|
|
}))
|
|
defer srv.Close()
|
|
cfg := GitLabConfig{BaseURL: srv.URL, Token: "ci-job-tok", Source: TokenJobToken}
|
|
cfg.FetchFile("p/q", "/f.yml", "main")
|
|
if gotHeader != "ci-job-tok" {
|
|
t.Errorf("JOB-TOKEN header not sent, got %q", gotHeader)
|
|
}
|
|
}
|
|
|
|
func TestFetchFile_WritesToCache(t *testing.T) {
|
|
dir := t.TempDir()
|
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
w.WriteHeader(http.StatusOK)
|
|
w.Write([]byte("stage: build"))
|
|
}))
|
|
defer srv.Close()
|
|
cfg := GitLabConfig{BaseURL: srv.URL, CacheDir: dir}
|
|
_, err := cfg.FetchFile("p/q", "/ci.yml", "main")
|
|
if err != nil { t.Fatal(err) }
|
|
// Second call should hit cache
|
|
data, ok := cacheRead(dir, srv.URL+"|p/q|/ci.yml|main")
|
|
if !ok { t.Fatal("cache miss after fetch") }
|
|
if string(data) != "stage: build" { t.Errorf("unexpected cache content: %q", data) }
|
|
}
|
|
|
|
// ── FetchURL ──────────────────────────────────────────────────────────────────
|
|
|
|
func TestFetchURL_OK(t *testing.T) {
|
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
w.WriteHeader(http.StatusOK)
|
|
w.Write([]byte("remote: content"))
|
|
}))
|
|
defer srv.Close()
|
|
cfg := GitLabConfig{BaseURL: srv.URL}
|
|
data, err := cfg.FetchURL(srv.URL + "/template.yml")
|
|
if err != nil { t.Fatalf("unexpected error: %v", err) }
|
|
if string(data) != "remote: content" { t.Errorf("got %q", data) }
|
|
}
|
|
|
|
func TestFetchURL_FromCache(t *testing.T) {
|
|
dir := t.TempDir()
|
|
rawURL := "https://example.com/tmpl.yml"
|
|
cacheWrite(dir, rawURL, []byte("cached"))
|
|
cfg := GitLabConfig{CacheDir: dir}
|
|
data, err := cfg.FetchURL(rawURL)
|
|
if err != nil { t.Fatal(err) }
|
|
if string(data) != "cached" { t.Errorf("got %q", data) }
|
|
}
|
|
|
|
func TestFetchURL_Offline(t *testing.T) {
|
|
cfg := GitLabConfig{Offline: true}
|
|
_, err := cfg.FetchURL("https://example.com/tmpl.yml")
|
|
if err == nil { t.Fatal("expected error in offline mode") }
|
|
}
|
|
|
|
func TestFetchURL_NotOK(t *testing.T) {
|
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
w.WriteHeader(http.StatusForbidden)
|
|
}))
|
|
defer srv.Close()
|
|
cfg := GitLabConfig{}
|
|
_, err := cfg.FetchURL(srv.URL)
|
|
if err == nil { t.Fatal("expected error for non-200 status") }
|
|
}
|