blob: c94df45f4f3fa071aaae8f676d01d23a3199e37e [file] [log] [blame]
// Copyright 2024 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package rules
import (
"context"
"encoding/json"
"slices"
"strings"
"testing"
"time"
"golang.org/x/oscar/internal/actions"
"golang.org/x/oscar/internal/github"
"golang.org/x/oscar/internal/llm"
"golang.org/x/oscar/internal/storage"
"golang.org/x/oscar/internal/testutil"
)
func TestIssue(t *testing.T) {
ctx := context.Background()
db := storage.MemDB()
llm := ruleTestGenerator()
// Construct a test issue.
i := new(github.Issue)
i.URL = "https://api.github.com/repos/golang/go/" // for project name
i.Number = 999
i.User = github.User{Login: "user"}
i.Title = "title"
i.Body = "body"
// Ask about rule violations.
r, err := Issue(ctx, db, llm, i, false)
if err != nil {
t.Fatalf("IssueRules failed with %v", err)
}
// Check result.
if !strings.Contains(r.Response, "\n- The issue title must start") {
t.Errorf("expected the issue title rule failed, but it didn't. Total output: %s", r.Response)
}
if n := strings.Count(r.Response, "\n- "); n != 1 {
t.Errorf("wanted 1 rule violation, got %d", n)
}
}
func TestClassify(t *testing.T) {
ctx := context.Background()
db := storage.MemDB()
llm := ruleTestGenerator()
// Construct a test issue.
i := new(github.Issue)
i.URL = "https://api.github.com/repos/golang/go/" // for project name
i.Number = 999
i.User = github.User{Login: "user"}
i.Title = "title"
i.Body = "body"
// Run classifier.
r, _, err := Classify(ctx, db, llm, i)
if err != nil {
t.Fatalf("Classify failed with %v", err)
}
// Check result.
want := "bug"
if r.Name != want {
t.Errorf("Classify got %q, want %q", r.Name, want)
}
}
func ruleTestGenerator() llm.ContentGenerator {
return llm.TestContentGenerator(
"ruleTestGenerator",
func(_ context.Context, schema *llm.Schema, promptParts []llm.Part) (string, error) {
var strs []string
for _, p := range promptParts {
strs = append(strs, string(p.(llm.Text)))
}
req := strings.Join(strs, " ")
if strings.Contains(req, "Your job is to categorize") {
// categorize request. Always report it as a "bug".
return `{"CategoryName":"bug","Explanation":"I think this is a bug."}`, nil
}
if strings.Contains(req, "Your job is to decide") {
// rule request. Report that the title rule failed and the others succeeded.
if strings.Contains(req, "The issue title must start") {
return "no\nThe title is malformed.", nil
}
return "yes\nThe rule is obeyed.", nil
}
return "UNK", nil
})
}
func TestRun(t *testing.T) {
ctx := context.Background()
check := testutil.Checker(t)
lg := testutil.Slogger(t)
db := storage.MemDB()
gh := github.New(lg, db, nil, nil)
llm := ruleTestGenerator()
tc := gh.Testing()
testIssue := &github.Issue{
Number: 888,
Title: "title",
Body: "body",
CreatedAt: time.Now().Add(-1 * time.Hour).UTC().Format(time.RFC3339),
}
tc.AddIssue("golang/go", testIssue)
p := New(lg, db, gh, llm, "postname")
p.EnableProject("golang/go")
p.EnablePosts()
check(p.Run(ctx))
check(actions.Run(ctx, lg, db))
entries := slices.Collect(actions.ScanAfter(lg, db, time.Time{}, nil))
if len(entries) != 1 {
t.Fatalf("too many action entries. Got %d, want 1", len(entries))
}
e := entries[0]
var a action
if err := json.Unmarshal(e.Action, &a); err != nil {
t.Fatal(err)
}
if got, want := a.Issue.Project(), "golang/go"; got != want {
t.Fatalf("posted to unexpected project: got %s, want %s", got, want)
return
}
if got, want := a.Issue.Number, int64(888); got != want {
t.Fatalf("posted to unexpected issue: got %d, want %d", got, want)
}
wantBody := strings.TrimSpace(`
We've identified some possible problems with your issue. Please review
these findings and fix any that you think are appropriate to fix.
- The issue title must start with a package name followed by a colon.
I'm just a bot; you probably know better than I do whether these findings really need fixing.
<sub>(Emoji vote if this was helpful or unhelpful; more detailed feedback welcome in [this discussion](https://github.com/golang/go/discussions/67901).)</sub>
`)
if got := strings.TrimSpace(a.Changes.Body); got != wantBody {
t.Fatalf("body wrong: got %s, want %s", got, wantBody)
}
}
func TestOld(t *testing.T) {
ctx := context.Background()
check := testutil.Checker(t)
lg := testutil.Slogger(t)
db := storage.MemDB()
gh := github.New(lg, db, nil, nil)
llm := ruleTestGenerator()
tc := gh.Testing()
testIssue := &github.Issue{
Number: 888,
Title: "title",
Body: "body",
CreatedAt: time.Now().Add(-100 * time.Hour).UTC().Format(time.RFC3339),
}
tc.AddIssue("golang/go", testIssue)
p := New(lg, db, gh, llm, "postname")
p.EnableProject("golang/go")
p.EnablePosts()
check(p.Run(ctx))
check(actions.Run(ctx, lg, db))
entries := slices.Collect(actions.ScanAfter(lg, db, time.Time{}, nil))
if len(entries) != 0 {
t.Fatalf("too many action entries. Got %d, want 0", len(entries))
}
}
func TestClosed(t *testing.T) {
ctx := context.Background()
check := testutil.Checker(t)
lg := testutil.Slogger(t)
db := storage.MemDB()
gh := github.New(lg, db, nil, nil)
llm := ruleTestGenerator()
tc := gh.Testing()
testIssue := &github.Issue{
Number: 888,
Title: "title",
Body: "body",
CreatedAt: time.Now().Add(-1 * time.Hour).UTC().Format(time.RFC3339),
State: "closed",
}
tc.AddIssue("golang/go", testIssue)
p := New(lg, db, gh, llm, "postname")
p.EnableProject("golang/go")
p.EnablePosts()
check(p.Run(ctx))
check(actions.Run(ctx, lg, db))
entries := slices.Collect(actions.ScanAfter(lg, db, time.Time{}, nil))
if len(entries) != 0 {
t.Fatalf("too many action entries. Got %d, want 0", len(entries))
}
}
func TestProjectFilter(t *testing.T) {
ctx := context.Background()
check := testutil.Checker(t)
lg := testutil.Slogger(t)
db := storage.MemDB()
gh := github.New(lg, db, nil, nil)
llm := ruleTestGenerator()
tc := gh.Testing()
testIssue := &github.Issue{
Number: 888,
Title: "title",
Body: "body",
CreatedAt: time.Now().Add(-1 * time.Hour).UTC().Format(time.RFC3339),
}
tc.AddIssue("golang/go1", testIssue)
p := New(lg, db, gh, llm, "postname")
p.EnableProject("golang/go2")
p.EnablePosts()
check(p.Run(ctx))
check(actions.Run(ctx, lg, db))
entries := slices.Collect(actions.ScanAfter(lg, db, time.Time{}, nil))
if len(entries) != 0 {
t.Fatalf("too many action entries. Got %d, want 0", len(entries))
}
}
func TestRegexpMatch(t *testing.T) {
ctx := context.Background()
check := testutil.Checker(t)
lg := testutil.Slogger(t)
db := storage.MemDB()
gh := github.New(lg, db, nil, nil)
llm := ruleTestGenerator()
tc := gh.Testing()
testIssue := &github.Issue{
Number: 888,
Title: "title",
Body: "Hello. ### What did you expect to see?\n\n### Goodbye.",
CreatedAt: time.Now().Add(-1 * time.Hour).UTC().Format(time.RFC3339),
}
tc.AddIssue("golang/go", testIssue)
p := New(lg, db, gh, llm, "postname")
p.EnableProject("golang/go")
p.EnablePosts()
check(p.Run(ctx))
check(actions.Run(ctx, lg, db))
entries := slices.Collect(actions.ScanAfter(lg, db, time.Time{}, nil))
if len(entries) != 1 {
t.Fatalf("too many action entries. Got %d, want 1", len(entries))
}
e := entries[0]
var a action
if err := json.Unmarshal(e.Action, &a); err != nil {
t.Fatal(err)
}
if got, want := a.Issue.Project(), "golang/go"; got != want {
t.Fatalf("posted to unexpected project: got %s, want %s", got, want)
return
}
if got, want := a.Issue.Number, int64(888); got != want {
t.Fatalf("posted to unexpected issue: got %d, want %d", got, want)
}
wantBody := strings.TrimSpace(`
We've identified some possible problems with your issue. Please review
these findings and fix any that you think are appropriate to fix.
- The issue title must start with a package name followed by a colon.
- The "What did you expect to see?" section should not be empty.
I'm just a bot; you probably know better than I do whether these findings really need fixing.
<sub>(Emoji vote if this was helpful or unhelpful; more detailed feedback welcome in [this discussion](https://github.com/golang/go/discussions/67901).)</sub>
`)
if got := strings.TrimSpace(a.Changes.Body); got != wantBody {
t.Fatalf("body wrong: got %s, want %s", got, wantBody)
}
}
func TestRegexpNotMatch(t *testing.T) {
ctx := context.Background()
check := testutil.Checker(t)
lg := testutil.Slogger(t)
db := storage.MemDB()
gh := github.New(lg, db, nil, nil)
llm := ruleTestGenerator()
tc := gh.Testing()
testIssue := &github.Issue{
Number: 888,
Title: "title",
Body: "Hello. ### What did you expect to see?\n\nSomething\n\n### Goodbye.",
CreatedAt: time.Now().Add(-1 * time.Hour).UTC().Format(time.RFC3339),
}
tc.AddIssue("golang/go", testIssue)
p := New(lg, db, gh, llm, "postname")
p.EnableProject("golang/go")
p.EnablePosts()
check(p.Run(ctx))
check(actions.Run(ctx, lg, db))
entries := slices.Collect(actions.ScanAfter(lg, db, time.Time{}, nil))
if len(entries) != 1 {
t.Fatalf("too many action entries. Got %d, want 1", len(entries))
}
e := entries[0]
var a action
if err := json.Unmarshal(e.Action, &a); err != nil {
t.Fatal(err)
}
if got, want := a.Issue.Project(), "golang/go"; got != want {
t.Fatalf("posted to unexpected project: got %s, want %s", got, want)
return
}
if got, want := a.Issue.Number, int64(888); got != want {
t.Fatalf("posted to unexpected issue: got %d, want %d", got, want)
}
wantBody := strings.TrimSpace(`
We've identified some possible problems with your issue. Please review
these findings and fix any that you think are appropriate to fix.
- The issue title must start with a package name followed by a colon.
I'm just a bot; you probably know better than I do whether these findings really need fixing.
<sub>(Emoji vote if this was helpful or unhelpful; more detailed feedback welcome in [this discussion](https://github.com/golang/go/discussions/67901).)</sub>
`)
if got := strings.TrimSpace(a.Changes.Body); got != wantBody {
t.Fatalf("body wrong: got %s, want %s", got, wantBody)
}
}