clone

a self-hosted multitenant nostr relay with sites, file storage, git hosting, and inbox and outbox delivery

feat/tinyagent
tinyrelay / internal / daemon / collaboration_review_test.go
collaboration_review_test.go 5.2K
1package daemon23import (4 "context"5 "encoding/json"6 "strings"7 "testing"8 "time"910 "github.com/FelineStateMachine/tinyrelay/internal/event"11 "github.com/FelineStateMachine/tinyrelay/internal/replication"12)1314func TestReviewAnchorShape(t *testing.T) {15 base := [][]string{{"a", "30617:" + strings.Repeat("a", 64) + ":test"}, {"E", strings.Repeat("b", 64), "", strings.Repeat("a", 64)}, {"K", "1618"}, {"P", strings.Repeat("a", 64)}}16 comment := func(kind string, extra ...[]string) event.Event {17 tags := append([][]string(nil), base...)18 tags[2] = []string{"K", kind}19 return event.Event{Kind: 1111, Tags: append(tags, extra...)}20 }21 anchor, present, err := reviewAnchorOf(comment("1618", []string{"file", "src/main.go"}, []string{"line", "12", "new"}))22 if err != nil || !present || anchor != (reviewAnchor{File: "src/main.go", Line: 12, Side: "new"}) {23 t.Fatalf("valid anchor: %+v %v %v", anchor, present, err)24 }25 if _, present, err := reviewAnchorOf(comment("1621")); err != nil || present {26 t.Fatalf("plain comment: %v %v", present, err)27 }28 cases := map[string][][]string{29 "missing line": {{"file", "a"}},30 "missing file": {{"line", "1", "new"}},31 "two files": {{"file", "a"}, {"file", "b"}, {"line", "1", "new"}},32 "empty path": {{"file", " "}, {"line", "1", "new"}},33 "zero line": {{"file", "a"}, {"line", "0", "new"}},34 "padded line": {{"file", "a"}, {"line", "01", "new"}},35 "word line": {{"file", "a"}, {"line", "one", "new"}},36 "no side": {{"file", "a"}, {"line", "1"}},37 "unknown side": {{"file", "a"}, {"line", "1", "left"}},38 "newline in path": {{"file", "a\nb"}, {"line", "1", "new"}},39 }40 for name, extra := range cases {41 if err := validateReviewAnchor(comment("1618", extra...)); err == nil || !strings.HasPrefix(err.Error(), "blocked: ") {42 t.Fatalf("%s: %v", name, err)43 }44 }45 if err := validateReviewAnchor(comment("1621", []string{"file", "a"}, []string{"line", "1", "new"})); err == nil || !strings.Contains(err.Error(), "pull request or patch") {46 t.Fatalf("anchor under an issue: %v", err)47 }48 if err := validateReviewAnchor(comment("1617", []string{"file", "a"}, []string{"line", "3", "old"})); err != nil {49 t.Fatalf("anchor under a patch: %v", err)50 }51}5253func TestReviewCommentsAreValidatedAndBrowsedWithTheirAnchor(t *testing.T) {54 ctx := context.Background()55 _, tenant := testTenant(t)56 p := tenant.Policy()57 p.Features.Grasp = true58 if err := tenant.applyPolicy(ctx, p); err != nil {59 t.Fatal(err)60 }61 owner, _ := event.PublicKey(testOwnerSecret)62 now := time.Now().Unix()63 agentTestRepo(t, tenant, "review")64 coordinate := "30617:" + owner + ":review"65 pull := signedEvent(t, testOwnerSecret, event.KIND_GIT_PR, now, [][]string{{"a", coordinate}, {"p", owner}, {"subject", "Change"}, {"c", strings.Repeat("1", 40)}, {"clone", "https://example.test/review.git"}}, "Why")66 if err := publishAs(t, tenant, pull); err != nil {67 t.Fatalf("pull request rejected: %v", err)68 }69 comment := func(createdAt int64, extra ...[]string) event.Event {70 tags := append([][]string{{"a", coordinate}, {"E", pull.ID, "", owner}, {"K", "1618"}, {"P", owner}, {"e", pull.ID, "", owner}, {"k", "1618"}, {"p", owner}}, extra...)71 return signedEvent(t, testOwnerSecret, 1111, createdAt, tags, "Review note")72 }73 if err := publishAs(t, tenant, comment(now+1, []string{"file", "README"}, []string{"line", "2", "left"})); err == nil || !strings.HasPrefix(err.Error(), "blocked: a review comment") {74 t.Fatalf("malformed anchor (bad side): %v", err)75 }76 if err := publishAs(t, tenant, comment(now+2, []string{"file", "README"}, []string{"line", "2"})); err == nil || !strings.HasPrefix(err.Error(), "blocked: a review comment") {77 t.Fatalf("malformed anchor (no side): %v", err)78 }79 if err := tenant.ingest(ctx, comment(now+3, []string{"file", ""}, []string{"line", "2", "new"}), replication.OriginImport); err == nil || !strings.HasPrefix(err.Error(), "blocked: a review comment") {80 t.Fatalf("malformed anchor over import: %v", err)81 }82 anchored := comment(now+4, []string{"file", "README"}, []string{"line", "2", "new"})83 if err := publishAs(t, tenant, anchored); err != nil {84 t.Fatalf("anchored comment rejected: %v", err)85 }86 plain := comment(now + 5)87 if err := publishAs(t, tenant, plain); err != nil {88 t.Fatalf("plain comment rejected: %v", err)89 }90 raw, _ := json.Marshal(map[string]any{"owner": owner, "repo": "review", "event": pull.ID})91 result, err := tenant.Execute(ctx, "", "browsepull", []json.RawMessage{raw})92 if err != nil {93 t.Fatalf("browsepull: %v", err)94 }95 encoded, _ := json.Marshal(result)96 var detail struct {97 Replies []struct {98 ID string `json:"id"`99 File string `json:"file"`100 Line int `json:"line"`101 Side string `json:"side"`102 } `json:"replies"`103 }104 if err := json.Unmarshal(encoded, &detail); err != nil || len(detail.Replies) != 2 {105 t.Fatalf("replies = %s %v", encoded, err)106 }107 if detail.Replies[0].ID != plain.ID || detail.Replies[0].File != "" || detail.Replies[0].Line != 0 || detail.Replies[0].Side != "" {108 t.Fatalf("plain reply carried an anchor: %+v", detail.Replies[0])109 }110 if detail.Replies[1].ID != anchored.ID || detail.Replies[1].File != "README" || detail.Replies[1].Line != 2 || detail.Replies[1].Side != "new" {111 t.Fatalf("anchored reply = %+v", detail.Replies[1])112 }113}