webctx 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +97 -0
- package/CONTRIBUTORS.md +126 -0
- package/LICENSE +202 -0
- package/Makefile +69 -0
- package/README.md +121 -0
- package/bin/webctx.js +28 -0
- package/cmd/webctx/main.go +11 -0
- package/docs/porting-status.md +173 -0
- package/go.mod +3 -0
- package/internal/app/app.go +139 -0
- package/internal/app/app_test.go +194 -0
- package/internal/app/github.go +2084 -0
- package/internal/app/github_acceptance_test.go +166 -0
- package/internal/app/github_actions.go +1008 -0
- package/internal/app/github_actions_test.go +484 -0
- package/internal/app/github_activity_deployments.go +503 -0
- package/internal/app/github_activity_deployments_test.go +321 -0
- package/internal/app/github_commits.go +1004 -0
- package/internal/app/github_commits_test.go +503 -0
- package/internal/app/github_discussions_gists.go +1156 -0
- package/internal/app/github_discussions_gists_test.go +425 -0
- package/internal/app/github_issues.go +1616 -0
- package/internal/app/github_issues_test.go +731 -0
- package/internal/app/github_packages_projects.go +429 -0
- package/internal/app/github_packages_projects_test.go +168 -0
- package/internal/app/github_phase9_overview_stress_test.go +178 -0
- package/internal/app/github_pull_views.go +1141 -0
- package/internal/app/github_pull_views_test.go +603 -0
- package/internal/app/github_pulls.go +1459 -0
- package/internal/app/github_pulls_test.go +715 -0
- package/internal/app/github_refs.go +621 -0
- package/internal/app/github_refs_test.go +260 -0
- package/internal/app/github_search_profiles.go +505 -0
- package/internal/app/github_search_profiles_test.go +352 -0
- package/internal/app/github_test.go +888 -0
- package/internal/app/scrape.go +264 -0
- package/internal/app/tools.go +670 -0
- package/internal/buildinfo/buildinfo.go +16 -0
- package/package.json +66 -0
- package/scripts/postinstall.js +137 -0
|
@@ -0,0 +1,603 @@
|
|
|
1
|
+
package app
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"context"
|
|
5
|
+
"encoding/json"
|
|
6
|
+
"fmt"
|
|
7
|
+
"io"
|
|
8
|
+
"net/http"
|
|
9
|
+
"net/http/httptest"
|
|
10
|
+
"strconv"
|
|
11
|
+
"strings"
|
|
12
|
+
"sync/atomic"
|
|
13
|
+
"testing"
|
|
14
|
+
"unicode/utf8"
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
func TestParseGitHubPullFocusedTargets(t *testing.T) {
|
|
18
|
+
tests := []struct {
|
|
19
|
+
raw string
|
|
20
|
+
kind GitHubTargetKind
|
|
21
|
+
}{
|
|
22
|
+
{raw: "https://github.com/o/r/pull/42/files", kind: GitHubTargetPullFiles},
|
|
23
|
+
{raw: "https://github.com/o/r/pull/42/files#diff-553490f999984ba28c4af0d7ffa919d10b5419f04a73f00141ee0b5a51c142e6R24", kind: GitHubTargetPullFiles},
|
|
24
|
+
{raw: "https://github.com/o/r/pull/42/commits", kind: GitHubTargetPullCommits},
|
|
25
|
+
{raw: "https://github.com/o/r/pull/42/checks?check_run_id=99", kind: GitHubTargetPullChecks},
|
|
26
|
+
{raw: "https://github.com/o/r/pull/42.diff", kind: GitHubTargetPullDiff},
|
|
27
|
+
{raw: "https://github.com/o/r/pull/42.patch", kind: GitHubTargetPullPatch},
|
|
28
|
+
}
|
|
29
|
+
for _, tt := range tests {
|
|
30
|
+
target := parseGitHubTarget(tt.raw)
|
|
31
|
+
if target == nil || target.Kind != tt.kind || target.Number != 42 {
|
|
32
|
+
t.Fatalf("focused target %s parsed as %#v", tt.raw, target)
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
func TestGitHubDiffPathHashMatchesObservedGitHubAnchor(t *testing.T) {
|
|
38
|
+
got := githubDiffPathHash("internal/ghcmd/cmd.go")
|
|
39
|
+
want := "553490f999984ba28c4af0d7ffa919d10b5419f04a73f00141ee0b5a51c142e6"
|
|
40
|
+
if got != want {
|
|
41
|
+
t.Fatalf("path hash mismatch: got %s want %s", got, want)
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
func TestParseGitHubDiffSelector(t *testing.T) {
|
|
46
|
+
hash := strings.Repeat("a", 64)
|
|
47
|
+
for _, tt := range []struct {
|
|
48
|
+
fragment string
|
|
49
|
+
side byte
|
|
50
|
+
start int
|
|
51
|
+
end int
|
|
52
|
+
wantErr bool
|
|
53
|
+
}{
|
|
54
|
+
{fragment: "diff-" + hash},
|
|
55
|
+
{fragment: "diff-" + hash + "L20", side: 'L', start: 20, end: 20},
|
|
56
|
+
{fragment: "diff-" + hash + "R20-R24", side: 'R', start: 20, end: 24},
|
|
57
|
+
{fragment: "diff-" + hash + "L24-R25", wantErr: true},
|
|
58
|
+
{fragment: "diff-" + hash + "R25-R24", wantErr: true},
|
|
59
|
+
{fragment: "diff-not-a-hash", wantErr: true},
|
|
60
|
+
{fragment: "discussion_r1", wantErr: true},
|
|
61
|
+
} {
|
|
62
|
+
selector, ok, err := parseGitHubDiffSelector(tt.fragment)
|
|
63
|
+
if !ok {
|
|
64
|
+
t.Fatalf("selector %q was not claimed", tt.fragment)
|
|
65
|
+
}
|
|
66
|
+
if tt.wantErr {
|
|
67
|
+
if err == nil {
|
|
68
|
+
t.Fatalf("selector %q should fail", tt.fragment)
|
|
69
|
+
}
|
|
70
|
+
continue
|
|
71
|
+
}
|
|
72
|
+
if err != nil || selector.Side != tt.side || selector.Start != tt.start || selector.End != tt.end {
|
|
73
|
+
t.Fatalf("selector %q => %#v, %v", tt.fragment, selector, err)
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
func TestSelectGitHubPatchHunksMapsLeftAndRightLines(t *testing.T) {
|
|
79
|
+
patch := "@@ -10,3 +10,4 @@ func f() {\n context\n-old\n+new\n+added\n context\n@@ -30,2 +31,2 @@\n-old2\n+new2"
|
|
80
|
+
left, err := selectGitHubPatchHunks(patch, 'L', 11, 11)
|
|
81
|
+
if err != nil {
|
|
82
|
+
t.Fatal(err)
|
|
83
|
+
}
|
|
84
|
+
if !strings.Contains(left, "-old") || strings.Contains(left, "old2") {
|
|
85
|
+
t.Fatalf("left selector chose wrong hunk:\n%s", left)
|
|
86
|
+
}
|
|
87
|
+
right, err := selectGitHubPatchHunks(patch, 'R', 12, 12)
|
|
88
|
+
if err != nil {
|
|
89
|
+
t.Fatal(err)
|
|
90
|
+
}
|
|
91
|
+
if !strings.Contains(right, "+added") || strings.Contains(right, "new2") {
|
|
92
|
+
t.Fatalf("right selector chose wrong hunk:\n%s", right)
|
|
93
|
+
}
|
|
94
|
+
if _, err := selectGitHubPatchHunks(patch, 'R', 999, 999); err == nil {
|
|
95
|
+
t.Fatal("expected stale line selector error")
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
func TestReadGitHubPullFilesOverviewStopsAfterFirstPageAndRepresentsProviderMore(t *testing.T) {
|
|
100
|
+
var pages int32
|
|
101
|
+
var server *httptest.Server
|
|
102
|
+
server = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
103
|
+
w.Header().Set("Content-Type", "application/json")
|
|
104
|
+
switch r.URL.Path {
|
|
105
|
+
case "/repos/o/r/pulls/42":
|
|
106
|
+
_, _ = io.WriteString(w, `{"number":42,"title":"conversation text must not appear","changed_files":4}`)
|
|
107
|
+
case "/repos/o/r/pulls/42/files":
|
|
108
|
+
atomic.AddInt32(&pages, 1)
|
|
109
|
+
if r.URL.Query().Get("page") == "2" {
|
|
110
|
+
t.Fatal("bounded Files overview followed provider pagination")
|
|
111
|
+
}
|
|
112
|
+
w.Header().Set("Link", fmt.Sprintf(`<%s/repos/o/r/pulls/42/files?per_page=100&page=2>; rel="next"`, server.URL))
|
|
113
|
+
_, _ = io.WriteString(w, `[
|
|
114
|
+
{"filename":"a.go","status":"modified","additions":2,"deletions":1,"changes":3,"blob_url":"https://github.com/o/r/blob/x/a.go","raw_url":"https://raw.example/a.go","patch":"@@ -10,2 +10,3 @@\n-old\n+new\n+added"},
|
|
115
|
+
{"filename":"new.go","status":"added","additions":1,"deletions":0,"changes":1,"patch":"@@ -0,0 +1 @@\n+hello"}
|
|
116
|
+
]`)
|
|
117
|
+
default:
|
|
118
|
+
t.Fatalf("unexpected request %s", r.URL.Path)
|
|
119
|
+
}
|
|
120
|
+
}))
|
|
121
|
+
defer server.Close()
|
|
122
|
+
out, err := readGitHubPullFiles(context.Background(), testGitHubClient(server.URL, server.URL, ""), parseGitHubTarget("https://github.com/o/r/pull/42/files"))
|
|
123
|
+
if err != nil {
|
|
124
|
+
t.Fatal(err)
|
|
125
|
+
}
|
|
126
|
+
for _, want := range []string{"overview: true", "files_returned: 2", "files_indexed: 2", "provider_more_available: true", "### `a.go`", "```diff", "Selector: https://github.com/o/r/pull/42/files#diff-", "complete: false"} {
|
|
127
|
+
if !strings.Contains(out, want) {
|
|
128
|
+
t.Fatalf("files output missing %q:\n%s", want, out)
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
if strings.Contains(out, "conversation text must not appear") || strings.Contains(out, "image.png") || strings.Contains(out, "old.txt") {
|
|
132
|
+
t.Fatalf("files view leaked PR conversation/title body:\n%s", out)
|
|
133
|
+
}
|
|
134
|
+
if atomic.LoadInt32(&pages) != 1 {
|
|
135
|
+
t.Fatalf("Files overview should fetch one provider page")
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
func TestReadGitHubPullFilesSmallCompleteSetKeepsFullPatches(t *testing.T) {
|
|
140
|
+
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
141
|
+
w.Header().Set("Content-Type", "application/json")
|
|
142
|
+
switch r.URL.Path {
|
|
143
|
+
case "/repos/o/r/pulls/42":
|
|
144
|
+
_, _ = io.WriteString(w, `{"number":42,"changed_files":2}`)
|
|
145
|
+
case "/repos/o/r/pulls/42/files":
|
|
146
|
+
_, _ = io.WriteString(w, `[
|
|
147
|
+
{"filename":"a.go","status":"modified","additions":2,"deletions":1,"changes":3,"patch":"@@ -1 +1 @@\n-old\n+new"},
|
|
148
|
+
{"filename":"image.png","status":"modified","changes":1,"blob_url":"https://github.com/o/r/blob/x/image.png","raw_url":"https://raw.example/image.png"}
|
|
149
|
+
]`)
|
|
150
|
+
default:
|
|
151
|
+
t.Fatalf("unexpected request %s", r.URL.Path)
|
|
152
|
+
}
|
|
153
|
+
}))
|
|
154
|
+
defer server.Close()
|
|
155
|
+
out, err := readGitHubPullFiles(context.Background(), testGitHubClient(server.URL, server.URL, ""), parseGitHubTarget("https://github.com/o/r/pull/42/files"))
|
|
156
|
+
if err != nil {
|
|
157
|
+
t.Fatal(err)
|
|
158
|
+
}
|
|
159
|
+
for _, want := range []string{"files_returned: 2", "files_rendered: 2", "complete: true", "## a.go", "```diff", "-old", "+new", "## image.png", "Patch unavailable from GitHub"} {
|
|
160
|
+
if !strings.Contains(out, want) {
|
|
161
|
+
t.Fatalf("small complete Files view missing %q:\n%s", want, out)
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
if strings.Contains(out, "overview: true") {
|
|
165
|
+
t.Fatalf("small complete Files view should retain full patch presentation:\n%s", out)
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
func TestReadGitHubPullFilesDiffSelectorNarrowsFileAndHunk(t *testing.T) {
|
|
170
|
+
path := "internal/ghcmd/cmd.go"
|
|
171
|
+
hash := githubDiffPathHash(path)
|
|
172
|
+
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
173
|
+
w.Header().Set("Content-Type", "application/json")
|
|
174
|
+
switch r.URL.Path {
|
|
175
|
+
case "/repos/o/r/pulls/42":
|
|
176
|
+
_, _ = io.WriteString(w, `{"number":42,"changed_files":2}`)
|
|
177
|
+
case "/repos/o/r/pulls/42/files":
|
|
178
|
+
_, _ = io.WriteString(w, `[
|
|
179
|
+
{"filename":"internal/ghcmd/cmd.go","status":"modified","patch":"@@ -20,3 +20,4 @@\n same\n-old\n+new\n+selected\n same\n@@ -100 +101 @@\n-old2\n+new2"},
|
|
180
|
+
{"filename":"other.go","status":"modified","patch":"@@ -1 +1 @@\n-x\n+y"}
|
|
181
|
+
]`)
|
|
182
|
+
default:
|
|
183
|
+
t.Fatalf("unexpected request %s", r.URL.Path)
|
|
184
|
+
}
|
|
185
|
+
}))
|
|
186
|
+
defer server.Close()
|
|
187
|
+
target := parseGitHubTarget("https://github.com/o/r/pull/42/files#diff-" + hash + "R22")
|
|
188
|
+
out, err := readGitHubPullFiles(context.Background(), testGitHubClient(server.URL, server.URL, ""), target)
|
|
189
|
+
if err != nil {
|
|
190
|
+
t.Fatal(err)
|
|
191
|
+
}
|
|
192
|
+
if !strings.Contains(out, "## "+path) || !strings.Contains(out, "Selected R22") || !strings.Contains(out, "+selected") || strings.Contains(out, "old2") || strings.Contains(out, "other.go") {
|
|
193
|
+
t.Fatalf("diff selector failed to narrow output:\n%s", out)
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
func TestPullFilesSelectorRejectsStaleHashLineAndUnavailablePatch(t *testing.T) {
|
|
198
|
+
path := "a.go"
|
|
199
|
+
hash := githubDiffPathHash(path)
|
|
200
|
+
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
201
|
+
w.Header().Set("Content-Type", "application/json")
|
|
202
|
+
switch r.URL.Path {
|
|
203
|
+
case "/repos/o/r/pulls/42":
|
|
204
|
+
_, _ = io.WriteString(w, `{"number":42,"changed_files":2}`)
|
|
205
|
+
case "/repos/o/r/pulls/42/files":
|
|
206
|
+
_, _ = io.WriteString(w, `[
|
|
207
|
+
{"filename":"a.go","patch":"@@ -1 +1 @@\n-old\n+new"},
|
|
208
|
+
{"filename":"binary.bin"}
|
|
209
|
+
]`)
|
|
210
|
+
}
|
|
211
|
+
}))
|
|
212
|
+
defer server.Close()
|
|
213
|
+
client := testGitHubClient(server.URL, server.URL, "")
|
|
214
|
+
for _, tt := range []struct {
|
|
215
|
+
fragment string
|
|
216
|
+
want string
|
|
217
|
+
}{
|
|
218
|
+
{fragment: "diff-" + strings.Repeat("f", 64), want: "does not match"},
|
|
219
|
+
{fragment: "diff-" + hash + "R999", want: "stale or out of range"},
|
|
220
|
+
{fragment: "diff-" + githubDiffPathHash("binary.bin") + "R1", want: "patch is unavailable"},
|
|
221
|
+
} {
|
|
222
|
+
_, err := readGitHubPullFiles(context.Background(), client, parseGitHubTarget("https://github.com/o/r/pull/42/files#"+tt.fragment))
|
|
223
|
+
if err == nil || !strings.Contains(err.Error(), tt.want) {
|
|
224
|
+
t.Fatalf("selector %q error=%v want %q", tt.fragment, err, tt.want)
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
func TestPullFilesSurfacesThreeThousandFileProviderCeiling(t *testing.T) {
|
|
230
|
+
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
231
|
+
w.Header().Set("Content-Type", "application/json")
|
|
232
|
+
if strings.HasSuffix(r.URL.Path, "/pulls/42") {
|
|
233
|
+
_, _ = io.WriteString(w, `{"number":42,"changed_files":3001}`)
|
|
234
|
+
return
|
|
235
|
+
}
|
|
236
|
+
_, _ = io.WriteString(w, `[{"filename":"a.go","patch":"@@ -1 +1 @@\n-a\n+b"}]`)
|
|
237
|
+
}))
|
|
238
|
+
defer server.Close()
|
|
239
|
+
out, err := readGitHubPullFiles(context.Background(), testGitHubClient(server.URL, server.URL, ""), parseGitHubTarget("https://github.com/o/r/pull/42/files"))
|
|
240
|
+
if err != nil {
|
|
241
|
+
t.Fatal(err)
|
|
242
|
+
}
|
|
243
|
+
if !strings.Contains(out, "complete: false") || !strings.Contains(out, "3,000-file maximum") {
|
|
244
|
+
t.Fatalf("provider cap truth missing:\n%s", out)
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
func TestLargePullFilesOverviewBoundsHundredsAndProviderCeilingSeparately(t *testing.T) {
|
|
249
|
+
t.Run("provider has more after first page", func(t *testing.T) {
|
|
250
|
+
files := make([]githubPullFile, 0, 100)
|
|
251
|
+
for i := 0; i < 100; i++ {
|
|
252
|
+
patch := fmt.Sprintf("@@ -1 +1 @@\n-old %03d\n+new %03d\n%s\nTAIL PATCH %03d MUST NOT APPEAR", i, i, strings.Repeat("+generated detail\n", 30), i)
|
|
253
|
+
files = append(files, githubPullFile{
|
|
254
|
+
Filename: fmt.Sprintf("pkg/file-%03d.go", i), Status: "modified", Additions: 31, Deletions: 1, Changes: 32,
|
|
255
|
+
BlobURL: fmt.Sprintf("https://github.com/o/r/blob/head/pkg/file-%03d.go", i), RawURL: fmt.Sprintf("https://raw.example/pkg/file-%03d.go", i), Patch: &patch,
|
|
256
|
+
})
|
|
257
|
+
}
|
|
258
|
+
out := renderGitHubPullFiles(&GitHubTarget{Owner: "o", Repo: "r", Number: 42}, githubPullRequest{Number: 42, ChangedFiles: 150}, files, githubPullFilesAvailability{
|
|
259
|
+
ProviderReturned: 100, ProviderMore: true, ProviderComplete: false,
|
|
260
|
+
}, githubDiffSelector{}, false)
|
|
261
|
+
if got := utf8.RuneCountInString(out); got > githubOverviewRunes {
|
|
262
|
+
t.Fatalf("100-file overview exceeded shared target: %d runes\n%s", got, out)
|
|
263
|
+
}
|
|
264
|
+
for _, want := range []string{"overview: true", "changed_files: 150", "files_returned: 100", "provider_more_available: true", "files_local_omitted:", "locally omitted from this overview", "Selector: https://github.com/o/r/pull/42/files#diff-", "Blob: https://github.com/o/r/blob/head/pkg/file-000.go", "Raw: https://raw.example/pkg/file-000.go"} {
|
|
265
|
+
if !strings.Contains(out, want) {
|
|
266
|
+
t.Fatalf("100-file overview missing %q:\n%s", want, out)
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
if strings.Contains(out, "TAIL PATCH 000 MUST NOT APPEAR") {
|
|
270
|
+
t.Fatalf("large Files overview leaked full patch:\n%s", out)
|
|
271
|
+
}
|
|
272
|
+
})
|
|
273
|
+
|
|
274
|
+
t.Run("three thousand provider ceiling remains distinct from local omission", func(t *testing.T) {
|
|
275
|
+
files := make([]githubPullFile, 3000)
|
|
276
|
+
for i := range files {
|
|
277
|
+
files[i] = githubPullFile{Filename: fmt.Sprintf("generated/file-%04d.txt", i), Status: "modified", Changes: 1}
|
|
278
|
+
}
|
|
279
|
+
out := renderGitHubPullFiles(&GitHubTarget{Owner: "o", Repo: "r", Number: 42}, githubPullRequest{Number: 42, ChangedFiles: 3001}, files, githubPullFilesAvailability{
|
|
280
|
+
ProviderReturned: 3000, ProviderComplete: false, ProviderCapReached: true,
|
|
281
|
+
}, githubDiffSelector{}, false)
|
|
282
|
+
if got := utf8.RuneCountInString(out); got > githubOverviewRunes {
|
|
283
|
+
t.Fatalf("3,000-file overview exceeded shared target: %d runes\n%s", got, out)
|
|
284
|
+
}
|
|
285
|
+
for _, want := range []string{"files_returned: 3000", "provider_result_ceiling: 3000", "3,000-file maximum", "files_local_omitted:", "locally omitted from this overview"} {
|
|
286
|
+
if !strings.Contains(out, want) {
|
|
287
|
+
t.Fatalf("provider ceiling/local omission truth missing %q:\n%s", want, out)
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
})
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
func TestReadGitHubPullCommitsIsCommitOnlyAndPaginated(t *testing.T) {
|
|
294
|
+
var server *httptest.Server
|
|
295
|
+
server = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
296
|
+
w.Header().Set("Content-Type", "application/json")
|
|
297
|
+
switch r.URL.Path {
|
|
298
|
+
case "/repos/o/r/pulls/42":
|
|
299
|
+
_, _ = io.WriteString(w, `{"number":42,"body":"PR BODY MUST NOT RENDER","commits":2}`)
|
|
300
|
+
case "/repos/o/r/pulls/42/commits":
|
|
301
|
+
if r.URL.Query().Get("page") == "2" {
|
|
302
|
+
_, _ = io.WriteString(w, `[{"sha":"bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb","html_url":"https://github.com/o/r/commit/b","commit":{"message":"Second\nbody","author":{"name":"Bob","date":"2026-08-02T00:00:00Z"}}}]`)
|
|
303
|
+
return
|
|
304
|
+
}
|
|
305
|
+
w.Header().Set("Link", fmt.Sprintf(`<%s/repos/o/r/pulls/42/commits?per_page=100&page=2>; rel="next"`, server.URL))
|
|
306
|
+
_, _ = io.WriteString(w, `[{"sha":"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","html_url":"https://github.com/o/r/commit/a","author":{"login":"alice"},"commit":{"message":"First","author":{"date":"2026-08-01T00:00:00Z"}}}]`)
|
|
307
|
+
default:
|
|
308
|
+
t.Fatalf("unexpected request %s", r.URL.Path)
|
|
309
|
+
}
|
|
310
|
+
}))
|
|
311
|
+
defer server.Close()
|
|
312
|
+
out, err := readGitHubPullCommits(context.Background(), testGitHubClient(server.URL, server.URL, ""), parseGitHubTarget("https://github.com/o/r/pull/42/commits"))
|
|
313
|
+
if err != nil {
|
|
314
|
+
t.Fatal(err)
|
|
315
|
+
}
|
|
316
|
+
if !strings.Contains(out, "commits_returned: 2") || !strings.Contains(out, "First") || !strings.Contains(out, "Second") || strings.Contains(out, "body\n") || strings.Contains(out, "PR BODY MUST NOT RENDER") || strings.Contains(out, "```diff") {
|
|
317
|
+
t.Fatalf("commit-only view incorrect:\n%s", out)
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
func TestLargePullCommitListUsesProviderCeilingAndBoundedIndex(t *testing.T) {
|
|
322
|
+
var commitPages int32
|
|
323
|
+
var server *httptest.Server
|
|
324
|
+
server = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
325
|
+
w.Header().Set("Content-Type", "application/json")
|
|
326
|
+
switch r.URL.Path {
|
|
327
|
+
case "/repos/o/r/pulls/42":
|
|
328
|
+
_, _ = io.WriteString(w, `{"number":42,"commits":400}`)
|
|
329
|
+
case "/repos/o/r/pulls/42/commits":
|
|
330
|
+
page := r.URL.Query().Get("page")
|
|
331
|
+
if page == "" {
|
|
332
|
+
page = "1"
|
|
333
|
+
}
|
|
334
|
+
atomic.AddInt32(&commitPages, 1)
|
|
335
|
+
pageNumber, _ := strconv.Atoi(page)
|
|
336
|
+
batch := make([]githubPullCommit, 100)
|
|
337
|
+
for i := range batch {
|
|
338
|
+
n := (pageNumber-1)*100 + i
|
|
339
|
+
batch[i].SHA = fmt.Sprintf("%040x", n+1)
|
|
340
|
+
batch[i].HTMLURL = fmt.Sprintf("https://github.com/o/r/commit/%040x", n+1)
|
|
341
|
+
batch[i].Author.Login = "author"
|
|
342
|
+
batch[i].Commit.Message = fmt.Sprintf("commit-%03d %s\nbody must not render", n, strings.Repeat("long subject ", 80))
|
|
343
|
+
batch[i].Commit.Author.Date = "2026-08-01T00:00:00Z"
|
|
344
|
+
}
|
|
345
|
+
if pageNumber < 4 {
|
|
346
|
+
w.Header().Set("Link", fmt.Sprintf(`<%s/repos/o/r/pulls/42/commits?per_page=100&page=%d>; rel="next"`, server.URL, pageNumber+1))
|
|
347
|
+
}
|
|
348
|
+
_ = json.NewEncoder(w).Encode(batch)
|
|
349
|
+
default:
|
|
350
|
+
t.Fatalf("unexpected request %s", r.URL.Path)
|
|
351
|
+
}
|
|
352
|
+
}))
|
|
353
|
+
defer server.Close()
|
|
354
|
+
|
|
355
|
+
out, err := readGitHubPullCommits(context.Background(), testGitHubClient(server.URL, server.URL, ""), parseGitHubTarget("https://github.com/o/r/pull/42/commits"))
|
|
356
|
+
if err != nil {
|
|
357
|
+
t.Fatal(err)
|
|
358
|
+
}
|
|
359
|
+
if got := utf8.RuneCountInString(out); got > githubOverviewRunes {
|
|
360
|
+
t.Fatalf("PR commit overview exceeded shared target: %d runes\n%s", got, out)
|
|
361
|
+
}
|
|
362
|
+
for _, want := range []string{
|
|
363
|
+
"commits_reported: 400", "commits_returned: 250", "provider_result_ceiling: 250", "provider_complete: false",
|
|
364
|
+
"commits_indexed:", "commits_local_omitted:", "locally omitted from this overview", "at most 250 commits",
|
|
365
|
+
"https://github.com/o/r/commit/0000000000000000000000000000000000000001",
|
|
366
|
+
} {
|
|
367
|
+
if !strings.Contains(out, want) {
|
|
368
|
+
t.Fatalf("large PR commit overview missing %q:\n%s", want, out)
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
if strings.Contains(out, "commits_local_omitted: 0") || strings.Contains(out, "body must not render") || atomic.LoadInt32(&commitPages) != 3 {
|
|
372
|
+
t.Fatalf("PR commit ceiling/rendering wrong pages=%d:\n%s", commitPages, out)
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
func TestReadGitHubPullChecksSeparatesRunsAndStatuses(t *testing.T) {
|
|
377
|
+
var server *httptest.Server
|
|
378
|
+
server = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
379
|
+
w.Header().Set("Content-Type", "application/json")
|
|
380
|
+
switch r.URL.Path {
|
|
381
|
+
case "/repos/o/r/pulls/42":
|
|
382
|
+
_, _ = io.WriteString(w, `{"number":42,"head":{"sha":"headsha"}}`)
|
|
383
|
+
case "/repos/o/r/commits/headsha/check-runs":
|
|
384
|
+
if r.URL.Query().Get("page") == "2" {
|
|
385
|
+
_, _ = io.WriteString(w, `{"total_count":2,"check_runs":[{"id":2,"name":"lint","head_sha":"headsha","status":"completed","conclusion":"failure","output":{"annotations_count":2}}]}`)
|
|
386
|
+
return
|
|
387
|
+
}
|
|
388
|
+
w.Header().Set("Link", fmt.Sprintf(`<%s/repos/o/r/commits/headsha/check-runs?per_page=100&page=2>; rel="next"`, server.URL))
|
|
389
|
+
_, _ = io.WriteString(w, `{"total_count":2,"check_runs":[{"id":1,"name":"build","head_sha":"headsha","status":"completed","conclusion":"success","details_url":"https://ci/build","app":{"slug":"actions"},"output":{"title":"Build","summary":"All good","annotations_count":0}}]}`)
|
|
390
|
+
case "/repos/o/r/commits/headsha/status":
|
|
391
|
+
if r.URL.Query().Get("page") == "2" {
|
|
392
|
+
_, _ = io.WriteString(w, `{"state":"pending","total_count":2,"statuses":[{"id":8,"state":"success","context":"legacy-ci","description":"Passed"}]}`)
|
|
393
|
+
return
|
|
394
|
+
}
|
|
395
|
+
w.Header().Set("Link", fmt.Sprintf(`<%s/repos/o/r/commits/headsha/status?per_page=100&page=2>; rel="next"`, server.URL))
|
|
396
|
+
_, _ = io.WriteString(w, `{"state":"pending","total_count":2,"statuses":[{"id":7,"state":"pending","context":"deploy","description":"Waiting","target_url":"https://ci/deploy"}]}`)
|
|
397
|
+
default:
|
|
398
|
+
t.Fatalf("unexpected checks request %s", r.URL.Path)
|
|
399
|
+
}
|
|
400
|
+
}))
|
|
401
|
+
defer server.Close()
|
|
402
|
+
out, err := readGitHubPullChecks(context.Background(), testGitHubClient(server.URL, server.URL, ""), parseGitHubTarget("https://github.com/o/r/pull/42/checks"))
|
|
403
|
+
if err != nil {
|
|
404
|
+
t.Fatal(err)
|
|
405
|
+
}
|
|
406
|
+
for _, want := range []string{"## Rollup", "Check run conclusions: failure 1, success 1", "## Check run index", "### build", "conclusion success", "### lint", "conclusion failure", "Focused check: https://github.com/o/r/pull/42/checks?check_run_id=1", "Focused check: https://github.com/o/r/pull/42/checks?check_run_id=2", "commit_statuses_returned: 2", "## Commit statuses", "Combined status state: `pending`", "**deploy** — pending", "**legacy-ci** — success", "does not infer a branch-protection/merge decision"} {
|
|
407
|
+
if !strings.Contains(out, want) {
|
|
408
|
+
t.Fatalf("checks output missing %q:\n%s", want, out)
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
if strings.Index(out, "### lint") > strings.Index(out, "### build") {
|
|
412
|
+
t.Fatalf("failed check must be prioritized ahead of routine success:\n%s", out)
|
|
413
|
+
}
|
|
414
|
+
if strings.Contains(out, "All good") || strings.Contains(out, "**Build**") {
|
|
415
|
+
t.Fatalf("checks container embedded per-run output summary/title:\n%s", out)
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
func TestLargePullChecksOverviewPrioritizesActionableRunsAndBoundsMachineText(t *testing.T) {
|
|
420
|
+
runs := make([]githubCheckRun, 0, 130)
|
|
421
|
+
for i := 0; i < 128; i++ {
|
|
422
|
+
run := githubCheckRun{ID: int64(i + 1), Name: fmt.Sprintf("success-%03d", i), Status: "completed", Conclusion: "success", DetailsURL: fmt.Sprintf("https://ci.example/success/%d", i)}
|
|
423
|
+
run.Output.Summary = "MACHINE SUMMARY MUST NOT APPEAR " + strings.Repeat("generated success detail ", 80)
|
|
424
|
+
runs = append(runs, run)
|
|
425
|
+
}
|
|
426
|
+
failure := githubCheckRun{ID: 9001, Name: "zzz-hard-failure", Status: "completed", Conclusion: "failure", DetailsURL: "https://ci.example/failure"}
|
|
427
|
+
failure.Output.AnnotationsCount = 3
|
|
428
|
+
failure.Output.Summary = "FAILURE SUMMARY MUST NOT APPEAR " + strings.Repeat("generated failure detail ", 100)
|
|
429
|
+
active := githubCheckRun{ID: 9002, Name: "yyy-active", Status: "in_progress", DetailsURL: "https://ci.example/active"}
|
|
430
|
+
active.Output.Summary = "ACTIVE SUMMARY MUST NOT APPEAR"
|
|
431
|
+
runs = append(runs, active, failure)
|
|
432
|
+
status := githubCombinedStatus{State: "pending", TotalCount: 3, Statuses: []githubCommitStatus{
|
|
433
|
+
{ID: 1, State: "success", Context: "legacy-success", Description: "done"},
|
|
434
|
+
{ID: 2, State: "pending", Context: "legacy-pending", Description: "waiting"},
|
|
435
|
+
{ID: 3, State: "failure", Context: "legacy-failure", Description: "failed"},
|
|
436
|
+
}}
|
|
437
|
+
out := renderGitHubPullChecks(&GitHubTarget{Owner: "o", Repo: "r", Number: 42}, githubPullRequest{Number: 42, Head: githubPullRef{SHA: "headsha"}}, runs, len(runs), status)
|
|
438
|
+
if got := utf8.RuneCountInString(out); got > githubOverviewRunes {
|
|
439
|
+
t.Fatalf("large Checks overview exceeded shared target: %d runes\n%s", got, out)
|
|
440
|
+
}
|
|
441
|
+
for _, want := range []string{
|
|
442
|
+
"check_runs_returned: 130", `check_run_status_counts: {"completed":129,"in_progress":1}`, `check_run_conclusion_counts: {"failure":1,"none":1,"success":128}`,
|
|
443
|
+
"check_runs_local_omitted:", "locally omitted from this overview", "zzz-hard-failure", "yyy-active", "success-000",
|
|
444
|
+
"Focused check: https://github.com/o/r/pull/42/checks?check_run_id=9001", "Focused check: https://github.com/o/r/pull/42/checks?check_run_id=9002",
|
|
445
|
+
"**legacy-failure** — failure", "**legacy-pending** — pending", "**legacy-success** — success",
|
|
446
|
+
} {
|
|
447
|
+
if !strings.Contains(out, want) {
|
|
448
|
+
t.Fatalf("large Checks overview missing %q:\n%s", want, out)
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
failureAt := strings.Index(out, "### zzz-hard-failure")
|
|
452
|
+
activeAt := strings.Index(out, "### yyy-active")
|
|
453
|
+
successAt := strings.Index(out, "### success-000")
|
|
454
|
+
if failureAt < 0 || activeAt < 0 || successAt < 0 || !(failureAt < activeAt && activeAt < successAt) {
|
|
455
|
+
t.Fatalf("check priority is not hard-failure -> active -> routine success:\n%s", out)
|
|
456
|
+
}
|
|
457
|
+
if strings.Contains(out, "MACHINE SUMMARY MUST NOT APPEAR") || strings.Contains(out, "FAILURE SUMMARY MUST NOT APPEAR") || strings.Contains(out, "ACTIVE SUMMARY MUST NOT APPEAR") {
|
|
458
|
+
t.Fatalf("Checks container embedded machine-generated output summaries:\n%s", out)
|
|
459
|
+
}
|
|
460
|
+
indexed := 0
|
|
461
|
+
for _, line := range strings.Split(out, "\n") {
|
|
462
|
+
if strings.HasPrefix(line, "check_runs_indexed: ") {
|
|
463
|
+
_, _ = fmt.Sscanf(line, "check_runs_indexed: %d", &indexed)
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
if indexed <= 0 || strings.Count(out, "Focused check: https://github.com/o/r/pull/42/checks?check_run_id=") != indexed {
|
|
467
|
+
t.Fatalf("every indexed check must expose a focused URL (indexed=%d):\n%s", indexed, out)
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
func TestReadGitHubSelectedCheckFetchesOnlySelectedRunAndAllAnnotations(t *testing.T) {
|
|
472
|
+
var annotationsPages int32
|
|
473
|
+
var server *httptest.Server
|
|
474
|
+
server = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
475
|
+
w.Header().Set("Content-Type", "application/json")
|
|
476
|
+
switch r.URL.Path {
|
|
477
|
+
case "/repos/o/r/pulls/42":
|
|
478
|
+
_, _ = io.WriteString(w, `{"number":42,"head":{"sha":"headsha"}}`)
|
|
479
|
+
case "/repos/o/r/check-runs/9":
|
|
480
|
+
_, _ = io.WriteString(w, `{"id":9,"name":"selected","head_sha":"headsha","status":"completed","conclusion":"failure","output":{"title":"Selected check","summary":"Summary","annotations_count":2}}`)
|
|
481
|
+
case "/repos/o/r/check-runs/9/annotations":
|
|
482
|
+
atomic.AddInt32(&annotationsPages, 1)
|
|
483
|
+
if r.URL.Query().Get("page") == "2" {
|
|
484
|
+
_, _ = io.WriteString(w, `[{"path":"b.go","start_line":20,"end_line":22,"annotation_level":"warning","title":"B","message":"Second"}]`)
|
|
485
|
+
return
|
|
486
|
+
}
|
|
487
|
+
w.Header().Set("Link", fmt.Sprintf(`<%s/repos/o/r/check-runs/9/annotations?per_page=100&page=2>; rel="next"`, server.URL))
|
|
488
|
+
_, _ = io.WriteString(w, `[{"path":"a.go","start_line":10,"end_line":10,"annotation_level":"failure","title":"A","message":"First","raw_details":"detail"}]`)
|
|
489
|
+
case "/repos/o/r/commits/headsha/check-runs", "/repos/o/r/commits/headsha/status":
|
|
490
|
+
t.Fatalf("focused check must not dump all checks/statuses: %s", r.URL.Path)
|
|
491
|
+
default:
|
|
492
|
+
t.Fatalf("unexpected selected-check request %s", r.URL.Path)
|
|
493
|
+
}
|
|
494
|
+
}))
|
|
495
|
+
defer server.Close()
|
|
496
|
+
out, err := readGitHubPullChecks(context.Background(), testGitHubClient(server.URL, server.URL, ""), parseGitHubTarget("https://github.com/o/r/pull/42/checks?check_run_id=9"))
|
|
497
|
+
if err != nil {
|
|
498
|
+
t.Fatal(err)
|
|
499
|
+
}
|
|
500
|
+
if !strings.Contains(out, "check_run_id: 9") || !strings.Contains(out, "annotations_returned: 2") || !strings.Contains(out, "`a.go:10`") || !strings.Contains(out, "`b.go:20-22`") || strings.Contains(out, "Focused check: ") {
|
|
501
|
+
t.Fatalf("focused check output incorrect:\n%s", out)
|
|
502
|
+
}
|
|
503
|
+
if atomic.LoadInt32(&annotationsPages) != 2 {
|
|
504
|
+
t.Fatalf("expected annotation pagination")
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
func TestOversizedFocusedCheckBoundsSummaryRawDetailsAndAnnotationCollection(t *testing.T) {
|
|
509
|
+
run := githubCheckRun{ID: 9, Name: "selected", HeadSHA: "headsha", Status: "completed", Conclusion: "failure", DetailsURL: "https://ci.example/check/9"}
|
|
510
|
+
run.Output.Title = "Selected machine check"
|
|
511
|
+
run.Output.AnnotationsCount = 50
|
|
512
|
+
run.Output.Summary = "SUMMARY START\n\n" + strings.Repeat("generated summary detail. ", 300) + "\nSUMMARY TAIL MUST NOT APPEAR"
|
|
513
|
+
annotations := make([]githubCheckAnnotation, 0, 40)
|
|
514
|
+
for i := 0; i < 40; i++ {
|
|
515
|
+
annotations = append(annotations, githubCheckAnnotation{
|
|
516
|
+
Path: fmt.Sprintf("pkg/file-%02d.go", i), StartLine: i + 10, EndLine: i + 10, AnnotationLevel: "failure", Title: fmt.Sprintf("Failure %02d", i),
|
|
517
|
+
Message: fmt.Sprintf("MESSAGE %02d START %s MESSAGE %02d TAIL MUST NOT APPEAR", i, strings.Repeat("generated message detail. ", 80), i),
|
|
518
|
+
RawDetails: fmt.Sprintf("RAW %02d START %s RAW %02d TAIL MUST NOT APPEAR", i, strings.Repeat("generated raw detail. ", 80), i),
|
|
519
|
+
})
|
|
520
|
+
}
|
|
521
|
+
out := renderGitHubSelectedCheck(&GitHubTarget{Owner: "o", Repo: "r", Number: 42}, githubPullRequest{Number: 42, Head: githubPullRef{SHA: "headsha"}}, run, annotations)
|
|
522
|
+
if got := utf8.RuneCountInString(out); got > githubOverviewRunes {
|
|
523
|
+
t.Fatalf("focused check exceeded shared target: %d runes\n%s", got, out)
|
|
524
|
+
}
|
|
525
|
+
for _, want := range []string{
|
|
526
|
+
"check_run_id: 9", "annotations_reported: 50", "annotations_returned: 40", "annotations_local_omitted:", "SUMMARY START", "Check summary preview locally truncated",
|
|
527
|
+
"`pkg/file-00.go:10`", "MESSAGE 00 START", "Raw details preview locally truncated", "locally omitted from this overview", "provider-incomplete state", "Check details: https://ci.example/check/9",
|
|
528
|
+
} {
|
|
529
|
+
if !strings.Contains(out, want) {
|
|
530
|
+
t.Fatalf("focused check bounded output missing %q:\n%s", want, out)
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
for _, forbidden := range []string{"SUMMARY TAIL MUST NOT APPEAR", "MESSAGE 00 TAIL MUST NOT APPEAR", "RAW 00 TAIL MUST NOT APPEAR", "MESSAGE 39 START"} {
|
|
534
|
+
if strings.Contains(out, forbidden) {
|
|
535
|
+
t.Fatalf("focused check leaked oversized machine text %q:\n%s", forbidden, out)
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
func TestSelectedCheckRejectsRunFromDifferentHead(t *testing.T) {
|
|
541
|
+
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
542
|
+
w.Header().Set("Content-Type", "application/json")
|
|
543
|
+
if strings.HasSuffix(r.URL.Path, "/pulls/42") {
|
|
544
|
+
_, _ = io.WriteString(w, `{"number":42,"head":{"sha":"expected"}}`)
|
|
545
|
+
return
|
|
546
|
+
}
|
|
547
|
+
_, _ = io.WriteString(w, `{"id":9,"name":"wrong","head_sha":"other"}`)
|
|
548
|
+
}))
|
|
549
|
+
defer server.Close()
|
|
550
|
+
_, err := readGitHubPullChecks(context.Background(), testGitHubClient(server.URL, server.URL, ""), parseGitHubTarget("https://github.com/o/r/pull/42/checks?check_run_id=9"))
|
|
551
|
+
if err == nil || !strings.Contains(err.Error(), "not Pull Request head") {
|
|
552
|
+
t.Fatalf("expected check ownership error, got %v", err)
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
func TestRawPullDiffAndPatchPreserveProviderBodyAndMediaType(t *testing.T) {
|
|
557
|
+
for _, tt := range []struct {
|
|
558
|
+
kind GitHubTargetKind
|
|
559
|
+
patch bool
|
|
560
|
+
accept string
|
|
561
|
+
body string
|
|
562
|
+
}{
|
|
563
|
+
{kind: GitHubTargetPullDiff, accept: "application/vnd.github.v3.diff", body: "diff --git a/a b/a\n-old\n+new\n"},
|
|
564
|
+
{kind: GitHubTargetPullPatch, patch: true, accept: "application/vnd.github.v3.patch", body: "From abc Mon Sep 17 00:00:00 2001\nSubject: patch\n\nbody\n"},
|
|
565
|
+
} {
|
|
566
|
+
t.Run(string(tt.kind), func(t *testing.T) {
|
|
567
|
+
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
568
|
+
if r.Header.Get("Accept") != tt.accept {
|
|
569
|
+
t.Errorf("Accept=%q want %q", r.Header.Get("Accept"), tt.accept)
|
|
570
|
+
}
|
|
571
|
+
_, _ = io.WriteString(w, tt.body)
|
|
572
|
+
}))
|
|
573
|
+
defer server.Close()
|
|
574
|
+
target := &GitHubTarget{Owner: "o", Repo: "r", Number: 42, Kind: tt.kind}
|
|
575
|
+
got, err := readGitHubPullRawDiff(context.Background(), testGitHubClient(server.URL, server.URL, ""), target, tt.patch)
|
|
576
|
+
if err != nil {
|
|
577
|
+
t.Fatal(err)
|
|
578
|
+
}
|
|
579
|
+
if got != tt.body {
|
|
580
|
+
t.Fatalf("raw media changed: got %q want %q", got, tt.body)
|
|
581
|
+
}
|
|
582
|
+
})
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
func TestPullFocusedProviderFailureStaysNative(t *testing.T) {
|
|
587
|
+
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
588
|
+
w.WriteHeader(http.StatusNotFound)
|
|
589
|
+
_, _ = io.WriteString(w, `{"message":"private body should not print"}`)
|
|
590
|
+
}))
|
|
591
|
+
defer server.Close()
|
|
592
|
+
for _, raw := range []string{
|
|
593
|
+
"https://github.com/o/r/pull/42/files",
|
|
594
|
+
"https://github.com/o/r/pull/42/commits",
|
|
595
|
+
"https://github.com/o/r/pull/42/checks",
|
|
596
|
+
"https://github.com/o/r/pull/42.diff",
|
|
597
|
+
} {
|
|
598
|
+
result := readGitHubNativeWithClient(context.Background(), parseGitHubTarget(raw), testGitHubClient(server.URL, server.URL, ""))
|
|
599
|
+
if result.Outcome != GitHubNativeFailure || result.Err == nil || !strings.Contains(result.Err.Error(), "may be private") || strings.Contains(result.Err.Error(), "private body") {
|
|
600
|
+
t.Fatalf("focused view %s did not preserve native failure: %#v", raw, result)
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
}
|