feat: guidelines
This commit is contained in:
70
internal/pierre/guidelines_test.go
Normal file
70
internal/pierre/guidelines_test.go
Normal file
@@ -0,0 +1,70 @@
|
||||
package pierre
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestParseGuidelinesFromStringValid(t *testing.T) {
|
||||
md := "# Rule One\n\n - Item A \n\n# Rule Two\n"
|
||||
lines, err := parseGuidelinesFromString(md)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
expected := []string{"# Rule One", "- Item A", "# Rule Two"}
|
||||
if got, want := fmt.Sprint(lines), fmt.Sprint(expected); got != want {
|
||||
t.Fatalf("expected %v, got %v", expected, lines)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseGuidelinesFromStringEmpty(t *testing.T) {
|
||||
md := "\n \n"
|
||||
lines, err := parseGuidelinesFromString(md)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if len(lines) != 0 {
|
||||
t.Fatalf("expected empty slice, got %d elements", len(lines))
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseGuidelinesFromStringTooManyLines(t *testing.T) {
|
||||
// generate 1001 non-empty lines
|
||||
var sb strings.Builder
|
||||
for i := 0; i < 1001; i++ {
|
||||
sb.WriteString(fmt.Sprintf("Line %d\n", i))
|
||||
}
|
||||
_, err := parseGuidelinesFromString(sb.String())
|
||||
if err == nil {
|
||||
t.Fatalf("expected error for exceeding line limit, got nil")
|
||||
}
|
||||
}
|
||||
|
||||
func TestWithGuidelinesSuccess(t *testing.T) {
|
||||
svc := &Service{}
|
||||
md := "First line\nSecond line\n"
|
||||
if err := svc.WithGuidelines(md); err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
expected := []string{"First line", "Second line"}
|
||||
if got, want := fmt.Sprint(svc.guidelines), fmt.Sprint(expected); got != want {
|
||||
t.Fatalf("expected guidelines %v, got %v", expected, svc.guidelines)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWithGuidelinesError(t *testing.T) {
|
||||
svc := &Service{guidelines: []string{"old"}}
|
||||
var sb strings.Builder
|
||||
for i := 0; i < 1001; i++ {
|
||||
sb.WriteString("x\n")
|
||||
}
|
||||
err := svc.WithGuidelines(sb.String())
|
||||
if err == nil {
|
||||
t.Fatalf("expected error, got nil")
|
||||
}
|
||||
// ensure old guidelines unchanged
|
||||
if len(svc.guidelines) != 1 || svc.guidelines[0] != "old" {
|
||||
t.Fatalf("guidelines should remain unchanged on error")
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,9 @@ package pierre
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
|
||||
"git.schreifuchs.ch/schreifuchs/pierre-bot/internal/chatter"
|
||||
)
|
||||
@@ -14,13 +16,14 @@ import (
|
||||
// elsewhere in the future.
|
||||
type Service struct {
|
||||
maxChunkSize int
|
||||
guidelines []string
|
||||
guidelines []string // stored as slice of lines; legacy, see WithGuidelines
|
||||
disableComments bool
|
||||
git GitAdapter
|
||||
chat ChatAdapter
|
||||
}
|
||||
|
||||
func New(chat ChatAdapter, git GitAdapter, maxChunkSize int, guidelines []string, disableComments bool) *Service {
|
||||
// Existing constructor retains slice based guidelines for backward compatibility.
|
||||
return &Service{
|
||||
git: git,
|
||||
chat: chat,
|
||||
@@ -30,6 +33,39 @@ func New(chat ChatAdapter, git GitAdapter, maxChunkSize int, guidelines []string
|
||||
}
|
||||
}
|
||||
|
||||
// WithGuidelines parses a raw Markdown string (or any multiline string) into
|
||||
// individual guideline lines, validates the line‑count (max 1000 non‑empty lines),
|
||||
// and stores the result in the Service. It returns an error if validation fails.
|
||||
// This is a convenience mutator for callers that have the guidelines as a
|
||||
// single string.
|
||||
func (s *Service) WithGuidelines(md string) error {
|
||||
lines, err := parseGuidelinesFromString(md)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
s.guidelines = lines
|
||||
return nil
|
||||
}
|
||||
|
||||
// parseGuidelinesFromString splits a markdown string into trimmed, non‑empty
|
||||
// lines and ensures the total number of lines does not exceed 1000.
|
||||
func parseGuidelinesFromString(md string) ([]string, error) {
|
||||
var result []string
|
||||
// Split on newline. Handles both \n and \r\n because TrimSpace removes \r.
|
||||
rawLines := strings.Split(md, "\n")
|
||||
for _, l := range rawLines {
|
||||
trimmed := strings.TrimSpace(l)
|
||||
if trimmed == "" {
|
||||
continue
|
||||
}
|
||||
result = append(result, trimmed)
|
||||
}
|
||||
if len(result) > 1000 {
|
||||
return nil, fmt.Errorf("guidelines exceed 1000 lines (found %d)", len(result))
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
type GitAdapter interface {
|
||||
GetDiff(ctx context.Context, owner, repo string, prID int) (io.ReadCloser, error)
|
||||
AddComment(ctx context.Context, owner, repo string, prID int, comment Comment) error
|
||||
|
||||
Reference in New Issue
Block a user