Files
kandev/apps/backend/internal/github/mock_client_test.go
T
2026-08-13 15:20:35 +01:00

364 lines
9.3 KiB
Go

package github
import (
"context"
"errors"
"strings"
"testing"
"time"
)
// Compile-time interface check.
var _ Client = (*MockClient)(nil)
func TestMockClient_IsAuthenticated(t *testing.T) {
m := NewMockClient()
ok, err := m.IsAuthenticated(context.Background())
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if !ok {
t.Fatal("expected authenticated=true")
}
}
func TestMockClient_GetAuthenticatedUser(t *testing.T) {
m := NewMockClient()
user, err := m.GetAuthenticatedUser(context.Background())
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if user != mockDefaultUser {
t.Fatalf("expected mock-user, got %q", user)
}
}
func TestMockClient_SetUser(t *testing.T) {
m := NewMockClient()
m.SetUser("custom-user")
user, _ := m.GetAuthenticatedUser(context.Background())
if user != "custom-user" {
t.Fatalf("expected custom-user, got %q", user)
}
}
func TestMockClient_AddPR_GetPR(t *testing.T) {
m := NewMockClient()
ctx := context.Background()
// Not found
_, err := m.GetPR(ctx, "owner", "repo", 1)
if err == nil {
t.Fatal("expected error for missing PR")
}
// Add and retrieve
m.AddPR(&PR{
Number: 1,
Title: "Test PR",
RepoOwner: "owner",
RepoName: "repo",
HeadBranch: "feature",
})
pr, err := m.GetPR(ctx, "owner", "repo", 1)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if pr.Title != "Test PR" {
t.Fatalf("expected title 'Test PR', got %q", pr.Title)
}
}
func TestMockClient_PRCommitsFailuresAreConsumed(t *testing.T) {
m := NewMockClient()
m.AddPRCommits("owner", "repo", 7, []PRCommitInfo{{SHA: "commit-1"}})
m.SetPRCommitsFailures("owner", "repo", 7, 1)
if _, err := m.ListPRCommits(context.Background(), "owner", "repo", 7); err == nil {
t.Fatal("expected the queued PR commit failure")
}
commits, err := m.ListPRCommits(context.Background(), "owner", "repo", 7)
if err != nil {
t.Fatalf("expected the next PR commit request to succeed: %v", err)
}
if len(commits) != 1 || commits[0].SHA != "commit-1" {
t.Fatalf("unexpected commits after retry: %+v", commits)
}
}
func TestMockClient_AddIssueGetIssueAndReset(t *testing.T) {
m := NewMockClient()
ctx := context.Background()
_, err := m.GetIssue(ctx, "owner", "repo", 1456)
if err == nil || err.Error() != "mock: issue owner/repo#1456 not found" {
t.Fatalf("expected missing issue error, got %v", err)
}
m.AddIssue(&Issue{
Number: 1456,
Title: "Test issue",
RepoOwner: "owner",
RepoName: "repo",
})
issue, err := m.GetIssue(ctx, "owner", "repo", 1456)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if issue.Title != "Test issue" {
t.Fatalf("expected title 'Test issue', got %q", issue.Title)
}
m.Reset()
_, err = m.GetIssue(ctx, "owner", "repo", 1456)
if err == nil || err.Error() != "mock: issue owner/repo#1456 not found" {
t.Fatalf("expected reset issue error, got %v", err)
}
}
func TestMockClient_ListIssuesPaged(t *testing.T) {
m := NewMockClient()
m.AddIssue(&Issue{Number: 1, RepoOwner: "owner", RepoName: "repo"})
m.AddIssue(&Issue{Number: 2, RepoOwner: "owner", RepoName: "repo"})
m.AddIssue(&Issue{Number: 3, RepoOwner: "other", RepoName: "repo"})
page, err := m.ListIssuesPaged(context.Background(), "state:open repo:owner/repo", "", 2, 10)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if len(page.Issues) != 2 || page.TotalCount != 2 {
t.Fatalf("expected two issues, got %#v", page)
}
if page.Page != 2 || page.PerPage != 10 {
t.Fatalf("expected pagination metadata to be preserved, got %#v", page)
}
}
func TestMockClient_FindPRByBranch(t *testing.T) {
m := NewMockClient()
ctx := context.Background()
// Not found returns nil, nil
pr, err := m.FindPRByBranch(ctx, "owner", "repo", "feature")
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if pr != nil {
t.Fatal("expected nil PR for missing branch")
}
// Add and find
m.AddPR(&PR{
Number: 1,
RepoOwner: "owner",
RepoName: "repo",
HeadBranch: "feature",
Title: "Branch PR",
})
pr, err = m.FindPRByBranch(ctx, "owner", "repo", "feature")
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if pr == nil || pr.Title != "Branch PR" {
t.Fatalf("expected Branch PR, got %v", pr)
}
}
func TestMockClient_ListAuthoredPRs(t *testing.T) {
m := NewMockClient()
ctx := context.Background()
m.AddPR(&PR{Number: 1, RepoOwner: "o", RepoName: "r", AuthorLogin: mockDefaultUser})
m.AddPR(&PR{Number: 2, RepoOwner: "o", RepoName: "r", AuthorLogin: "other"})
m.AddPR(&PR{Number: 3, RepoOwner: "x", RepoName: "y", AuthorLogin: mockDefaultUser})
prs, err := m.ListAuthoredPRs(ctx, "o", "r")
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if len(prs) != 1 {
t.Fatalf("expected 1 authored PR, got %d", len(prs))
}
if prs[0].Number != 1 {
t.Fatalf("expected PR #1, got #%d", prs[0].Number)
}
}
func TestMockClient_ListReviewRequestedPRs(t *testing.T) {
m := NewMockClient()
ctx := context.Background()
m.AddPR(&PR{Number: 1, RepoOwner: "o", RepoName: "r"})
m.AddPR(&PR{
Number: 2,
RepoOwner: "o",
RepoName: "r",
RequestedReviewers: []RequestedReviewer{{Login: "user1", Type: "user"}},
})
prs, err := m.ListReviewRequestedPRs(ctx, "", "", "")
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if len(prs) != 1 {
t.Fatalf("expected 1 review-requested PR, got %d", len(prs))
}
if prs[0].Number != 2 {
t.Fatalf("expected PR #2, got #%d", prs[0].Number)
}
}
func TestMockClient_ListPRComments_Since(t *testing.T) {
m := NewMockClient()
ctx := context.Background()
now := time.Now()
old := now.Add(-1 * time.Hour)
recent := now.Add(1 * time.Hour)
m.AddComments("o", "r", 1, []PRComment{
{ID: 1, UpdatedAt: old},
{ID: 2, UpdatedAt: recent},
})
// Without since — all
all, err := m.ListPRComments(ctx, "o", "r", 1, nil)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if len(all) != 2 {
t.Fatalf("expected 2 comments, got %d", len(all))
}
// With since — only recent
filtered, err := m.ListPRComments(ctx, "o", "r", 1, &now)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if len(filtered) != 1 || filtered[0].ID != 2 {
t.Fatalf("expected 1 filtered comment (ID=2), got %v", filtered)
}
}
func TestMockClient_SubmitReview(t *testing.T) {
m := NewMockClient()
ctx := context.Background()
err := m.SubmitReview(ctx, "o", "r", 1, "APPROVE", "LGTM")
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
reviews := m.SubmittedReviews()
if len(reviews) != 1 {
t.Fatalf("expected 1 submitted review, got %d", len(reviews))
}
if reviews[0].Event != "APPROVE" || reviews[0].Body != "LGTM" {
t.Fatalf("unexpected review: %+v", reviews[0])
}
}
func TestMockClient_RequestReviewers_RecordsAndDeduplicatesCaseInsensitively(t *testing.T) {
m := NewMockClient()
m.AddPR(&PR{
RepoOwner: "o",
RepoName: "r",
Number: 1,
RequestedReviewers: []RequestedReviewer{
{Login: "OctoCat", Type: reviewerTypeUser},
},
})
if err := m.RequestReviewers(context.Background(), "o", "r", 1, []string{"octocat", "hubot"}); err != nil {
t.Fatalf("RequestReviewers: %v", err)
}
requests := m.RequestedReviews()
if len(requests) != 1 || strings.Join(requests[0].Reviewers, ",") != "octocat,hubot" {
t.Fatalf("recorded requests = %#v", requests)
}
pr, err := m.GetPR(context.Background(), "o", "r", 1)
if err != nil {
t.Fatalf("GetPR: %v", err)
}
if got := strings.Join([]string{pr.RequestedReviewers[0].Login, pr.RequestedReviewers[1].Login}, ","); got != "OctoCat,hubot" {
t.Fatalf("requested reviewers = %q", got)
}
}
func TestMockClient_CreateAndDeleteGist(t *testing.T) {
m := NewMockClient()
ctx := context.Background()
resp, err := m.CreateGist(ctx, CreateGistInput{
Description: "snapshot",
Public: false,
Files: map[string]GistFile{
"snapshot.json": {Content: `{"x":1}`},
"README.md": {Content: "hi"},
},
})
if err != nil {
t.Fatalf("CreateGist error: %v", err)
}
if resp.ID == "" || resp.HTMLURL == "" {
t.Fatalf("expected non-empty id/url, got %+v", resp)
}
gists := m.Gists()
g, ok := gists[resp.ID]
if !ok {
t.Fatalf("gist %s not stored", resp.ID)
}
if g.Public {
t.Fatal("gist should default to secret (Public=false)")
}
if _, ok := g.Files["snapshot.json"]; !ok {
t.Fatal("snapshot.json missing from stored gist")
}
if err := m.DeleteGist(ctx, resp.ID); err != nil {
t.Fatalf("DeleteGist error: %v", err)
}
if _, ok := m.Gists()[resp.ID]; ok {
t.Fatal("gist still present after delete")
}
if got := m.DeletedGists(); len(got) != 1 || got[0] != resp.ID {
t.Fatalf("expected DeletedGists=[%q], got %v", resp.ID, got)
}
err = m.DeleteGist(ctx, resp.ID)
var apiErr *GitHubAPIError
if !errors.As(err, &apiErr) || apiErr.StatusCode != 404 {
t.Fatalf("expected 404 GitHubAPIError on second delete, got %v", err)
}
}
func TestMockClient_Reset(t *testing.T) {
m := NewMockClient()
ctx := context.Background()
m.SetUser("custom")
m.AddPR(&PR{Number: 1, RepoOwner: "o", RepoName: "r", HeadBranch: "b"})
m.AddOrgs([]GitHubOrg{{Login: "org1"}})
m.Reset()
user, _ := m.GetAuthenticatedUser(ctx)
if user != mockDefaultUser {
t.Fatalf("expected user reset to mock-user, got %q", user)
}
_, err := m.GetPR(ctx, "o", "r", 1)
if err == nil {
t.Fatal("expected error after reset")
}
orgs, _ := m.ListUserOrgs(ctx)
if len(orgs) != 0 {
t.Fatalf("expected 0 orgs after reset, got %d", len(orgs))
}
}