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") } }