tinyrelayfile
tinyrelay/tinyclient/review_test.go/

tinyclient/review_test.go raw
1package tinyclient
2
3import (
4 "context"
5 "encoding/json"
6 "net/http"
7 "net/http/httptest"
8 "net/url"
9 "strings"
10 "testing"
11
12 "github.com/FelineStateMachine/tinyrelay/internal/policy"
13)
14
15const reviewTestDiff = "diff --git a/README b/README\nindex 1111111..2222222 100644\n--- a/README\n+++ b/README\n@@ -1,3 +1,4 @@\n first\n-second\n+two\n+three\n fourth\ndiff --git a/docs/new.md b/docs/new.md\nnew file mode 100644\n--- /dev/null\n+++ b/docs/new.md\n@@ -0,0 +1,2 @@\n+# New\n+body\n\\ No newline at end of file\n"
16
17func TestDiffLinesNumberBothSidesAndTrackFiles(t *testing.T) {
18 lines, _ := previewLines(reviewTestDiff)
19 rows := diffLines(lines)
20 want := map[int]diffLine{
21 3: {Wrapper: "del", File: "README"},
22 5: {Wrapper: "b", File: "README"},
23 6: {File: "README", Old: 1, New: 1},
24 7: {Wrapper: "del", File: "README", Old: 2},
25 8: {Wrapper: "ins", File: "README", New: 2},
26 9: {Wrapper: "ins", File: "README", New: 3},
27 10: {File: "README", Old: 3, New: 4},
28 14: {Wrapper: "ins", File: "docs/new.md"},
29 16: {Wrapper: "ins", File: "docs/new.md", New: 1},
30 17: {Wrapper: "ins", File: "docs/new.md", New: 2},
31 18: {File: "docs/new.md"},
32 }
33 for number, expected := range want {
34 got := rows[number-1]
35 expected.Number, expected.Text = number, got.Text
36 if got != expected {
37 t.Fatalf("row %d = %+v, want %+v", number, got, expected)
38 }
39 }
40 if got := rows[11]; got.File != "" || got.Old != 0 || got.New != 0 {
41 t.Fatalf("a new diff header kept the previous file: %+v", got)
42 }
43}
44
45type reviewBackend struct{ *fakeBackend }
46
47func (b *reviewBackend) Query(ctx context.Context, method string, params []json.RawMessage, actor string) (any, error) {
48 b.call = method
49 b.params = params
50 owner := b.policy.Owner
51 root := map[string]any{"id": strings.Repeat("b", 64), "title": "Rename second", "kind": 1618, "author": owner, "content": "Why", "status": "open"}
52 reply := func(id, content string, extra ...[]string) map[string]any {
53 tags := append([][]string{{"a", "30617:" + owner + ":test"}, {"E", strings.Repeat("b", 64), "", owner}, {"K", "1618"}, {"P", owner}}, extra...)
54 return map[string]any{"id": id, "kind": 1111, "pubkey": strings.Repeat("c", 64), "created_at": 1700000000, "content": content, "tags": tags}
55 }
56 anchored := reply(strings.Repeat("1", 64), "Prefer a longer word.", []string{"file", "README"}, []string{"line", "2", "new"})
57 anchored["file"], anchored["line"], anchored["side"] = "README", 2, "new"
58 lost := reply(strings.Repeat("2", 64), "Outside the diff.", []string{"file", "missing.txt"}, []string{"line", "9", "old"})
59 lost["file"], lost["line"], lost["side"] = "missing.txt", 9, "old"
60 plain := reply(strings.Repeat("3", 64), "Looks good overall.")
61 return map[string]any{"item": root, "replies": []any{plain, lost, anchored}, "can_status": true, "diff": reviewTestDiff, "repository": map[string]any{"owner": owner, "clone": []string{"https://git.example/test.git"}}}, nil
62}
63
64func TestPullRequestPagePlacesAnchoredCommentsUnderDiffLines(t *testing.T) {
65 for _, viewer := range []string{"", strings.Repeat("a", 64)} {
66 backend := &reviewBackend{&fakeBackend{policy: policy.Defaults(strings.Repeat("a", 64))}}
67 app, err := New(backend, Options{Actor: func(*http.Request) (string, error) { return viewer, nil }})
68 if err != nil {
69 t.Fatal(err)
70 }
71 query := url.Values{"owner": {backend.policy.Owner}, "repo": {"test"}}
72 link := repoURL(query, "pr", strings.Repeat("b", 64))
73 w := httptest.NewRecorder()
74 app.ServeHTTP(w, httptest.NewRequest(http.MethodGet, link, nil))
75 body := w.Body.String()
76 if w.Code != http.StatusOK || backend.call != "browsepull" {
77 t.Fatalf("viewer %q: %d %s", viewer, w.Code, body)
78 }
79 if strings.Contains(body, "class=") {
80 t.Fatalf("viewer %q: markup carries class attributes", viewer)
81 }
82 // The anchored comment sits in the row after new line 2 of README,
83 // which is rendered diff row 8; the plain and unplaced comments stay
84 // in the thread, the unplaced one with its anchor shown.
85 row := strings.Index(body, `<tr id="D8">`)
86 nextRow := strings.Index(body, `<tr id="D9">`)
87 if row < 0 || nextRow < row || !strings.Contains(body[row:nextRow], "Prefer a longer word.") || !strings.Contains(body[row:nextRow], `<nostr-event id="event-`+strings.Repeat("1", 64)+`">`) {
88 t.Fatalf("viewer %q: anchored comment not placed under its line: %s", viewer, body[row:nextRow])
89 }
90 thread := body[strings.Index(body, `<ol id="events">`):]
91 if strings.Contains(thread, "Prefer a longer word.") || !strings.Contains(thread, "Looks good overall.") || !strings.Contains(thread, "on missing.txt line 9 (old)") {
92 t.Fatalf("viewer %q: thread = %s", viewer, thread)
93 }
94 commentLink := strings.Contains(body, `file=README&amp;line=2&amp;side=new#reply-compose">comment</a>`)
95 removedLink := strings.Contains(body, `file=README&amp;line=2&amp;side=old#reply-compose">comment</a>`)
96 if viewer == "" {
97 if commentLink || strings.Contains(body, "<nostr-compose") {
98 t.Fatal("guest received comment links or a compose")
99 }
100 continue
101 }
102 if !commentLink || !removedLink || !strings.Contains(body, `&amp;file=docs%2Fnew.md&amp;line=1&amp;side=new#reply-compose">comment</a>`) {
103 t.Fatalf("comment links missing: %s", body)
104 }
105 if strings.Contains(body, `<input type="hidden" name="file"`) {
106 t.Fatal("compose was preset without a query anchor")
107 }
108 w = httptest.NewRecorder()
109 app.ServeHTTP(w, httptest.NewRequest(http.MethodGet, link+"&file=README&line=2&side=new", nil))
110 body = w.Body.String()
111 for _, want := range []string{`<nostr-compose id="reply-compose" kind="1111"`, `<input type="hidden" name="file" value="README">`, `<input type="hidden" name="line" value="2">`, `<input type="hidden" name="side" value="new">`, `<p id="reply-anchor">Commenting on <code>README</code> line 2 (new side).`} {
112 if !strings.Contains(body, want) {
113 t.Fatalf("preset compose missing %s: %s", want, body)
114 }
115 }
116 w = httptest.NewRecorder()
117 app.ServeHTTP(w, httptest.NewRequest(http.MethodGet, link+"&file=README&line=0&side=left", nil))
118 if strings.Contains(w.Body.String(), `name="file"`) {
119 t.Fatal("an invalid query anchor preset the compose")
120 }
121 }
122}

About

ownerc01503e8035f
visibilitypublic
headd730679639bf

Maintainers

  • c01503e8035f owner
  • ba1b5beed5b5 agent hermes

Clone

  • https://012.run/npub1cq2s86qrtadf36eqq3epm38wmhdynrrcwxf84eldk3f94dkv7ttsrulrz5/tinyrelay.git

Refs