package main import ( "io" "net/http" "net/http/httptest" "strings" "testing" ) // A bad PR reference must fail the command (so main exits non-zero) rather than // return nil. Parsing rejects the ref before any Vault/Gitea call, so this stays // hermetic. func TestExecuteBadRefErrors(t *testing.T) { cmd := newRootCmd() cmd.SetArgs([]string{"--once", "not-a-ref"}) cmd.SetOut(io.Discard) cmd.SetErr(io.Discard) if err := cmd.Execute(); err == nil { t.Fatal("Execute() = nil, want error for a bad PR reference") } } // No arguments is also an error (nothing to watch). func TestExecuteNoArgsErrors(t *testing.T) { cmd := newRootCmd() cmd.SetArgs(nil) cmd.SetOut(io.Discard) cmd.SetErr(io.Discard) if err := cmd.Execute(); err == nil { t.Fatal("Execute() = nil, want error when no PR references are given") } } // Watching a public repo with no credentials available must work: the failed // mint is a warning, the poll goes out unauthenticated, and the command exits 0. func TestOnceRunsAnonymouslyWhenNoTokenIsAvailable(t *testing.T) { vault := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusForbidden) })) defer vault.Close() authHeaders := 0 mux := http.NewServeMux() mux.HandleFunc("/api/v1/repos/unkin/repo/pulls/7", func(w http.ResponseWriter, r *http.Request) { if r.Header.Get("Authorization") != "" { authHeaders++ } _, _ = io.WriteString(w, `{"number":7,"state":"open","mergeable":true,"head":{"sha":"cafebabe"}}`) }) mux.HandleFunc("/api/v1/repos/unkin/repo/commits/cafebabe/status", func(w http.ResponseWriter, r *http.Request) { _, _ = io.WriteString(w, `{"state":"success"}`) }) mux.HandleFunc("/api/v1/repos/unkin/repo/issues/7/comments", func(w http.ResponseWriter, r *http.Request) { _, _ = io.WriteString(w, `[]`) }) gitea := httptest.NewServer(mux) defer gitea.Close() t.Setenv("VAULT_ADDR", vault.URL) t.Setenv("GITEA_URL", gitea.URL) cmd := newRootCmd() cmd.SetArgs([]string{"--once", "unkin/repo#7"}) cmd.SetOut(io.Discard) cmd.SetErr(io.Discard) if err := cmd.Execute(); err != nil { t.Fatalf("anonymous --once should succeed without a token: %v", err) } if authHeaders != 0 { t.Errorf("sent %d Authorization headers, want none", authHeaders) } } // A bare integer interval means seconds and must survive flag parsing: the // command should fail on the missing PR reference, not on the flag value. func TestExecuteBareIntervalIsSeconds(t *testing.T) { cmd := newRootCmd() cmd.SetArgs([]string{"--interval", "15"}) cmd.SetOut(io.Discard) cmd.SetErr(io.Discard) err := cmd.Execute() if err == nil { t.Fatal("Execute() = nil, want the no-references error") } if !strings.Contains(err.Error(), "no PR references given") { t.Fatalf("Execute() error = %v, want the no-references error", err) } } // An unparseable interval is rejected before any Vault/Gitea call, with an // error naming the flag and showing valid forms. func TestExecuteBadIntervalErrors(t *testing.T) { cmd := newRootCmd() cmd.SetArgs([]string{"--interval", "soon", "unkin/repo#1"}) cmd.SetOut(io.Discard) cmd.SetErr(io.Discard) err := cmd.Execute() if err == nil { t.Fatal("Execute() = nil, want error for an unparseable --interval") } for _, want := range []string{"--interval", "30s"} { if !strings.Contains(err.Error(), want) { t.Errorf("Execute() error %q does not mention %q", err, want) } } }