package invoice import ( "bytes" "errors" "io" "log/slog" "strings" "testing" "time" "code.gitea.io/sdk/gitea" "git.schreifuchs.ch/lou-taylor/accounting/pkg/invoice/model" ) // MockGiteaClient is a mock implementation of the GiteaClient interface. type MockGiteaClient struct { ListRepoIssuesFunc func(owner, repo string, opt gitea.ListIssueOption) ([]*gitea.Issue, *gitea.Response, error) } func (m *MockGiteaClient) ListRepoIssues(owner, repo string, opt gitea.ListIssueOption) ([]*gitea.Issue, *gitea.Response, error) { if m.ListRepoIssuesFunc != nil { return m.ListRepoIssuesFunc(owner, repo, opt) } return nil, nil, errors.New("ListRepoIssuesFunc not implemented") } // MockPdfGenerator is a mock implementation of the PdfGenerator interface. type MockPdfGenerator struct { HtmlToPdfFunc func(html string) (io.ReadCloser, error) } func (m *MockPdfGenerator) HtmlToPdf(html string) (io.ReadCloser, error) { if m.HtmlToPdfFunc != nil { return m.HtmlToPdfFunc(html) } return nil, errors.New("HtmlToPdfFunc not implemented") } func TestGenerate(t *testing.T) { creditor := model.Entity{ Name: "creditor", } debtor := model.Entity{ Name: "deptor", } rate := 100.0 repos := []Repo{ {Owner: "owner", Repo: "repo"}, } testCases := []struct { name string setupMocks func(*MockGiteaClient, *MockPdfGenerator) config *Options expectedError string }{ { name: "successful generation", setupMocks: func(g *MockGiteaClient, p *MockPdfGenerator) { g.ListRepoIssuesFunc = func(owner, repo string, opt gitea.ListIssueOption) ([]*gitea.Issue, *gitea.Response, error) { return []*gitea.Issue{ {ID: 1, Title: "Test Issue", Body: "```info\nduration: 1h\n```"}, }, &gitea.Response{}, nil } p.HtmlToPdfFunc = func(html string) (io.ReadCloser, error) { return io.NopCloser(bytes.NewReader([]byte("pdf"))), nil } }, config: &DefaultOptions, expectedError: "", }, { name: "gitea error", setupMocks: func(g *MockGiteaClient, p *MockPdfGenerator) { g.ListRepoIssuesFunc = func(owner, repo string, opt gitea.ListIssueOption) ([]*gitea.Issue, *gitea.Response, error) { return nil, nil, errors.New("gitea error") } }, config: &DefaultOptions, expectedError: "gitea error", }, { name: "pdf error", setupMocks: func(g *MockGiteaClient, p *MockPdfGenerator) { g.ListRepoIssuesFunc = func(owner, repo string, opt gitea.ListIssueOption) ([]*gitea.Issue, *gitea.Response, error) { return []*gitea.Issue{ {ID: 1, Title: "Test Issue", Body: "```info\nduration: 1h\n```"}, }, &gitea.Response{}, nil } p.HtmlToPdfFunc = func(html string) (io.ReadCloser, error) { return nil, errors.New("pdf error") } }, config: &DefaultOptions, expectedError: "pdf error", }, { name: "no issues", setupMocks: func(g *MockGiteaClient, p *MockPdfGenerator) { g.ListRepoIssuesFunc = func(owner, repo string, opt gitea.ListIssueOption) ([]*gitea.Issue, *gitea.Response, error) { return []*gitea.Issue{}, &gitea.Response{}, nil } p.HtmlToPdfFunc = func(html string) (io.ReadCloser, error) { return io.NopCloser(bytes.NewReader([]byte("pdf"))), nil } }, config: &DefaultOptions, expectedError: "", }, { name: "custom template", setupMocks: func(g *MockGiteaClient, p *MockPdfGenerator) { g.ListRepoIssuesFunc = func(owner, repo string, opt gitea.ListIssueOption) ([]*gitea.Issue, *gitea.Response, error) { return []*gitea.Issue{ {ID: 1, Title: "Test Issue", Body: "```info\nduration: 1h\n```"}, }, &gitea.Response{}, nil } p.HtmlToPdfFunc = func(html string) (io.ReadCloser, error) { return io.NopCloser(bytes.NewReader([]byte("pdf"))), nil } }, config: &Options{ Mindur: time.Minute * 15, Since: time.Now().AddDate(0, -1, 0), Before: time.Now(), IssueState: gitea.StateClosed, IssueFilter: func(i *gitea.Issue) bool { return true }, CustomTemplate: "custom template", }, expectedError: "", }, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { giteaClient := new(MockGiteaClient) pdfGenerator := new(MockPdfGenerator) tc.setupMocks(giteaClient, pdfGenerator) service := New(slog.Default(), giteaClient, pdfGenerator) doc, _, err := service.Generate(creditor, &debtor, rate, repos, tc.config) if tc.expectedError != "" { if err == nil { t.Fatalf("expected an error, but got none") } if !strings.Contains(err.Error(), tc.expectedError) { t.Errorf("expected error to contain '%s', but got '%s'", tc.expectedError, err.Error()) } } else { if err != nil { t.Fatalf("expected no error, but got: %v", err) } if doc == nil { t.Fatal("expected a document, but got nil") } } }) } }