package github import ( "context" "encoding/json" "net/http" "net/http/httptest" "testing" "time" ) func TestGetFileContent_DelegatesToGetFileContentAtRef(t *testing.T) { var gotRef string srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { gotRef = r.URL.Query().Get("ref") json.NewEncoder(w).Encode(map[string]string{ "content": "dGVzdA==", // "test" in base64 "encoding": "base64", }) })) defer srv.Close() c := NewClient("token", srv.URL) c.SetHTTPClient(srv.Client()) // Call with empty ref — should not include ref param content, err := c.GetFileContent(context.Background(), "owner", "repo", "file.go", "") if err != nil { t.Fatalf("unexpected error: %v", err) } if content != "test" { t.Errorf("expected 'test', got %q", content) } if gotRef != "" { t.Errorf("expected empty ref, got %q", gotRef) } } func TestGetFileContent_WithRef(t *testing.T) { var gotRef string srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { gotRef = r.URL.Query().Get("ref") json.NewEncoder(w).Encode(map[string]string{ "content": "dGVzdA==", "encoding": "base64", }) })) defer srv.Close() c := NewClient("token", srv.URL) c.SetHTTPClient(srv.Client()) _, err := c.GetFileContent(context.Background(), "owner", "repo", "file.go", "abc123") if err != nil { t.Fatalf("unexpected error: %v", err) } if gotRef != "abc123" { t.Errorf("expected ref 'abc123', got %q", gotRef) } } func TestGetFileContent_404(t *testing.T) { srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(404) w.Write([]byte(`{"message":"Not Found"}`)) })) defer srv.Close() c := NewClient("token", srv.URL) c.SetHTTPClient(srv.Client()) _, err := c.GetFileContent(context.Background(), "owner", "repo", "missing.go", "") if err == nil { t.Fatal("expected error for 404") } } func TestGetFileContent_401(t *testing.T) { srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(401) w.Write([]byte(`{"message":"Bad credentials"}`)) })) defer srv.Close() c := NewClient("token", srv.URL) c.SetHTTPClient(srv.Client()) _, err := c.GetFileContent(context.Background(), "owner", "repo", "file.go", "") if err == nil { t.Fatal("expected error for 401") } } func TestGetFileContent_429Retry(t *testing.T) { attempts := 0 srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { attempts++ if attempts == 1 { w.WriteHeader(429) w.Write([]byte(`{"message":"rate limit"}`)) return } json.NewEncoder(w).Encode(map[string]string{ "content": "b2s=", "encoding": "base64", }) })) defer srv.Close() c := NewClient("token", srv.URL) c.SetHTTPClient(srv.Client()) c.RetryBackoff = []time.Duration{1 * time.Millisecond} content, err := c.GetFileContent(context.Background(), "owner", "repo", "file.go", "") if err != nil { t.Fatalf("unexpected error: %v", err) } if content != "ok" { t.Errorf("expected 'ok', got %q", content) } if attempts != 2 { t.Errorf("expected 2 attempts, got %d", attempts) } } func TestGetFileContent_MalformedJSON(t *testing.T) { srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(200) w.Write([]byte(`not json`)) })) defer srv.Close() c := NewClient("token", srv.URL) c.SetHTTPClient(srv.Client()) _, err := c.GetFileContent(context.Background(), "owner", "repo", "file.go", "") if err == nil { t.Fatal("expected error for malformed JSON") } } func TestListContents_HappyPath(t *testing.T) { srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if r.URL.Path != "/repos/owner/repo/contents/src" { t.Errorf("unexpected path: %s", r.URL.Path) } json.NewEncoder(w).Encode([]map[string]string{ {"name": "main.go", "path": "src/main.go", "type": "file"}, {"name": "lib", "path": "src/lib", "type": "dir"}, }) })) defer srv.Close() c := NewClient("token", srv.URL) c.SetHTTPClient(srv.Client()) entries, err := c.ListContents(context.Background(), "owner", "repo", "src") if err != nil { t.Fatalf("unexpected error: %v", err) } if len(entries) != 2 { t.Fatalf("expected 2 entries, got %d", len(entries)) } if entries[0].Name != "main.go" { t.Errorf("expected name 'main.go', got %q", entries[0].Name) } if entries[0].Path != "src/main.go" { t.Errorf("expected path 'src/main.go', got %q", entries[0].Path) } if entries[0].Type != "file" { t.Errorf("expected type 'file', got %q", entries[0].Type) } if entries[1].Name != "lib" { t.Errorf("expected name 'lib', got %q", entries[1].Name) } if entries[1].Type != "dir" { t.Errorf("expected type 'dir', got %q", entries[1].Type) } } func TestListContents_404(t *testing.T) { srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(404) w.Write([]byte(`{"message":"Not Found"}`)) })) defer srv.Close() c := NewClient("token", srv.URL) c.SetHTTPClient(srv.Client()) _, err := c.ListContents(context.Background(), "owner", "repo", "missing") if err == nil { t.Fatal("expected error for 404") } } func TestListContents_401(t *testing.T) { srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(401) w.Write([]byte(`{"message":"Bad credentials"}`)) })) defer srv.Close() c := NewClient("token", srv.URL) c.SetHTTPClient(srv.Client()) _, err := c.ListContents(context.Background(), "owner", "repo", "src") if err == nil { t.Fatal("expected error for 401") } } func TestListContents_429Retry(t *testing.T) { attempts := 0 srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { attempts++ if attempts == 1 { w.WriteHeader(429) w.Write([]byte(`{"message":"rate limit"}`)) return } json.NewEncoder(w).Encode([]map[string]string{ {"name": "file.go", "path": "file.go", "type": "file"}, }) })) defer srv.Close() c := NewClient("token", srv.URL) c.SetHTTPClient(srv.Client()) c.RetryBackoff = []time.Duration{1 * time.Millisecond} entries, err := c.ListContents(context.Background(), "owner", "repo", ".") if err != nil { t.Fatalf("unexpected error: %v", err) } if len(entries) != 1 { t.Fatalf("expected 1 entry, got %d", len(entries)) } if attempts != 2 { t.Errorf("expected 2 attempts, got %d", attempts) } } func TestListContents_MalformedJSON(t *testing.T) { srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(200) w.Write([]byte(`not json`)) })) defer srv.Close() c := NewClient("token", srv.URL) c.SetHTTPClient(srv.Client()) _, err := c.ListContents(context.Background(), "owner", "repo", "src") if err == nil { t.Fatal("expected error for malformed JSON") } } func TestDecodeBase64Content(t *testing.T) { // Test with newlines (GitHub's format) encoded := "cGFja2FnZSBt\nYWlu" decoded, err := decodeBase64Content(encoded) if err != nil { t.Fatalf("unexpected error: %v", err) } if decoded != "package main" { t.Errorf("expected 'package main', got %q", decoded) } } func TestDecodeBase64Content_Invalid(t *testing.T) { _, err := decodeBase64Content("not!!!valid!!!base64") if err == nil { t.Fatal("expected error for invalid base64") } } func TestEscapePath_RejectsDotSegments(t *testing.T) { tests := []struct { input string want string }{ {"src/main.go", "src/main.go"}, {"../etc/passwd", "etc/passwd"}, {"./src/../main.go", "src/main.go"}, {"a/b/c", "a/b/c"}, {"file with spaces.go", "file%20with%20spaces.go"}, {"a/./b/../c", "a/b/c"}, } for _, tt := range tests { got := escapePath(tt.input) if got != tt.want { t.Errorf("escapePath(%q) = %q, want %q", tt.input, got, tt.want) } } } func TestDecodeBase64Content_CRLF(t *testing.T) { // Base64 of "hello world" with CRLF line breaks inserted encoded := "aGVs\r\nbG8g\r\nd29y\r\nbGQ=" decoded, err := decodeBase64Content(encoded) if err != nil { t.Fatalf("unexpected error: %v", err) } if decoded != "hello world" { t.Errorf("expected 'hello world', got %q", decoded) } }