3c536c42d5
- gitea/client_test.go: mock HTTP tests for all API methods + error cases - llm/client_test.go: mock tests for completion, errors, timeouts - review/parser_test.go: JSON parsing, markdown fences, validation - review/formatter_test.go: markdown output, empty/multiple findings - review/prompt_test.go: system/user prompt construction - integration_test.go: full end-to-end flow (build tag: integration) - .gitea/workflows/ci.yml: test + vet + build on push, dual LLM review on PRs - CONVENTIONS.md: coding standards for self-review dogfooding - README.md: usage docs, env vars, architecture
104 lines
3.0 KiB
Go
104 lines
3.0 KiB
Go
//go:build integration
|
|
|
|
package main
|
|
|
|
import (
|
|
"os"
|
|
"strconv"
|
|
"testing"
|
|
|
|
"gitea.weiker.me/rodin/review-bot/gitea"
|
|
"gitea.weiker.me/rodin/review-bot/llm"
|
|
"gitea.weiker.me/rodin/review-bot/review"
|
|
)
|
|
|
|
// Integration test requires a running Gitea instance and LLM endpoint.
|
|
// Set environment variables:
|
|
// INTEGRATION_GITEA_URL - Gitea base URL
|
|
// INTEGRATION_GITEA_TOKEN - Gitea API token with repo access
|
|
// INTEGRATION_GITEA_REPO - owner/repo with an open PR
|
|
// INTEGRATION_PR_NUMBER - PR number to test against
|
|
// INTEGRATION_LLM_BASE_URL - LLM API base URL
|
|
// INTEGRATION_LLM_API_KEY - LLM API key
|
|
// INTEGRATION_LLM_MODEL - Model name
|
|
|
|
func TestIntegration_FullReviewFlow(t *testing.T) {
|
|
giteaURL := os.Getenv("INTEGRATION_GITEA_URL")
|
|
giteaToken := os.Getenv("INTEGRATION_GITEA_TOKEN")
|
|
giteaRepo := os.Getenv("INTEGRATION_GITEA_REPO")
|
|
prNumStr := os.Getenv("INTEGRATION_PR_NUMBER")
|
|
llmBaseURL := os.Getenv("INTEGRATION_LLM_BASE_URL")
|
|
llmAPIKey := os.Getenv("INTEGRATION_LLM_API_KEY")
|
|
llmModel := os.Getenv("INTEGRATION_LLM_MODEL")
|
|
|
|
if giteaURL == "" || giteaToken == "" || giteaRepo == "" || prNumStr == "" ||
|
|
llmBaseURL == "" || llmAPIKey == "" || llmModel == "" {
|
|
t.Skip("Integration test env vars not set, skipping")
|
|
}
|
|
|
|
prNumber, err := strconv.Atoi(prNumStr)
|
|
if err != nil {
|
|
t.Fatalf("Invalid PR number %q: %v", prNumStr, err)
|
|
}
|
|
|
|
// Parse owner/repo
|
|
owner, repoName := "", ""
|
|
for i, c := range giteaRepo {
|
|
if c == / {
|
|
owner = giteaRepo[:i]
|
|
repoName = giteaRepo[i+1:]
|
|
break
|
|
}
|
|
}
|
|
if owner == "" || repoName == "" {
|
|
t.Fatalf("Invalid repo format %q", giteaRepo)
|
|
}
|
|
|
|
// Step 1: Fetch PR
|
|
giteaClient := gitea.NewClient(giteaURL, giteaToken)
|
|
pr, err := giteaClient.GetPullRequest(owner, repoName, prNumber)
|
|
if err != nil {
|
|
t.Fatalf("GetPullRequest: %v", err)
|
|
}
|
|
t.Logf("PR: %s (sha: %s)", pr.Title, pr.Head.Sha)
|
|
|
|
// Step 2: Fetch diff
|
|
diff, err := giteaClient.GetPullRequestDiff(owner, repoName, prNumber)
|
|
if err != nil {
|
|
t.Fatalf("GetPullRequestDiff: %v", err)
|
|
}
|
|
if diff == "" {
|
|
t.Fatal("diff is empty")
|
|
}
|
|
t.Logf("Diff size: %d bytes", len(diff))
|
|
|
|
// Step 3: Build prompts
|
|
systemPrompt := review.BuildSystemPrompt("")
|
|
userPrompt := review.BuildUserPrompt(pr.Title, pr.Body, diff, true, "")
|
|
|
|
// Step 4: Call LLM
|
|
llmClient := llm.NewClient(llmBaseURL, llmAPIKey, llmModel)
|
|
response, err := llmClient.Complete([]llm.Message{
|
|
{Role: "system", Content: systemPrompt},
|
|
{Role: "user", Content: userPrompt},
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("LLM Complete: %v", err)
|
|
}
|
|
t.Logf("LLM response: %d bytes", len(response))
|
|
|
|
// Step 5: Parse response
|
|
result, err := review.ParseResponse(response)
|
|
if err != nil {
|
|
t.Fatalf("ParseResponse: %v", err)
|
|
}
|
|
t.Logf("Verdict: %s, Findings: %d", result.Verdict, len(result.Findings))
|
|
|
|
// Step 6: Format (dry-run validation)
|
|
body := review.FormatMarkdown(result, "integration-test")
|
|
if body == "" {
|
|
t.Fatal("formatted review body is empty")
|
|
}
|
|
t.Logf("Review body:\n%s", body)
|
|
}
|