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,321 @@
|
|
|
1
|
+
package app
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"context"
|
|
5
|
+
"encoding/json"
|
|
6
|
+
"fmt"
|
|
7
|
+
"io"
|
|
8
|
+
"net/http"
|
|
9
|
+
"net/http/httptest"
|
|
10
|
+
"strings"
|
|
11
|
+
"sync/atomic"
|
|
12
|
+
"testing"
|
|
13
|
+
"unicode/utf8"
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
func TestParseActivityStatisticsDeploymentTargets(t *testing.T) {
|
|
17
|
+
for _, tt := range []struct {
|
|
18
|
+
raw string
|
|
19
|
+
kind GitHubTargetKind
|
|
20
|
+
name string
|
|
21
|
+
}{
|
|
22
|
+
{raw: "https://github.com/o/r/activity", kind: GitHubTargetActivity},
|
|
23
|
+
{raw: "https://github.com/o/r/graphs/contributors", kind: GitHubTargetStatsContributors},
|
|
24
|
+
{raw: "https://github.com/o/r/graphs/commit-activity", kind: GitHubTargetStatsCommitActivity},
|
|
25
|
+
{raw: "https://github.com/o/r/graphs/code-frequency", kind: GitHubTargetStatsCodeFrequency},
|
|
26
|
+
{raw: "https://github.com/o/r/deployments", kind: GitHubTargetDeployments},
|
|
27
|
+
{raw: "https://github.com/o/r/deployments/production", kind: GitHubTargetDeploymentEnvironment, name: "production"},
|
|
28
|
+
{raw: "https://github.com/o/r/deployments/preview%2Fwest", kind: GitHubTargetDeploymentEnvironment, name: "preview/west"},
|
|
29
|
+
} {
|
|
30
|
+
target := parseGitHubTarget(tt.raw)
|
|
31
|
+
if target == nil || target.Kind != tt.kind || target.Name != tt.name {
|
|
32
|
+
t.Fatalf("target %s => %#v", tt.raw, target)
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
for _, raw := range []string{
|
|
36
|
+
"https://github.com/o/r/graphs/traffic",
|
|
37
|
+
"https://github.com/o/r/graphs/punch-card",
|
|
38
|
+
"https://github.com/o/r/activity/extra",
|
|
39
|
+
} {
|
|
40
|
+
if target := parseGitHubTarget(raw); target != nil {
|
|
41
|
+
t.Fatalf("unproven metrics route claimed: %s => %#v", raw, target)
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
func TestRepositoryActivityIsBoundedAndPreservesProviderFilters(t *testing.T) {
|
|
47
|
+
var server *httptest.Server
|
|
48
|
+
server = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
49
|
+
if r.URL.Path != "/repos/o/r/activity" {
|
|
50
|
+
t.Fatalf("activity endpoint=%s", r.URL.Path)
|
|
51
|
+
}
|
|
52
|
+
for key, want := range map[string]string{"ref": "refs/heads/main", "activity_type": "push", "actor": "alice", "page": "2", "per_page": "8"} {
|
|
53
|
+
if got := r.URL.Query().Get(key); got != want {
|
|
54
|
+
t.Errorf("activity %s=%q want %q", key, got, want)
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
w.Header().Set("Content-Type", "application/json")
|
|
58
|
+
w.Header().Set("Link", fmt.Sprintf(`<%s/repos/o/r/activity?ref=refs%%2Fheads%%2Fmain&activity_type=push&actor=alice&per_page=8&page=1>; rel="prev", <%s/repos/o/r/activity?ref=refs%%2Fheads%%2Fmain&activity_type=push&actor=alice&per_page=8&page=3>; rel="next"`, server.URL, server.URL))
|
|
59
|
+
_, _ = io.WriteString(w, `[{"id":1,"before":"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","after":"bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb","ref":"refs/heads/main","timestamp":"2026-08-01T00:00:00Z","activity_type":"push","actor":{"login":"alice"}}]`)
|
|
60
|
+
}))
|
|
61
|
+
defer server.Close()
|
|
62
|
+
out, err := readGitHubActivity(context.Background(), testGitHubClient(server.URL, server.URL, ""), parseGitHubTarget("https://github.com/o/r/activity?ref=refs%2Fheads%2Fmain&activity_type=push&actor=alice&page=2"))
|
|
63
|
+
if err != nil {
|
|
64
|
+
t.Fatal(err)
|
|
65
|
+
}
|
|
66
|
+
for _, want := range []string{"view: \"activity\"", "push by @alice", "`refs/heads/main`", "`aaaaaaaaaaaa` → `bbbbbbbbbbbb`", "Previous", "page=1", "Next", "page=3"} {
|
|
67
|
+
if !strings.Contains(out, want) {
|
|
68
|
+
t.Fatalf("activity missing %q:\n%s", want, out)
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
func TestStatisticsHTTP202IsComputingNotEmptySuccess(t *testing.T) {
|
|
74
|
+
for _, raw := range []string{
|
|
75
|
+
"https://github.com/o/r/graphs/contributors",
|
|
76
|
+
"https://github.com/o/r/graphs/commit-activity",
|
|
77
|
+
"https://github.com/o/r/graphs/code-frequency",
|
|
78
|
+
} {
|
|
79
|
+
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
80
|
+
w.WriteHeader(http.StatusAccepted)
|
|
81
|
+
_, _ = io.WriteString(w, `{}`)
|
|
82
|
+
}))
|
|
83
|
+
target := parseGitHubTarget(raw)
|
|
84
|
+
result := readGitHubNativeWithClient(context.Background(), target, testGitHubClient(server.URL, server.URL, ""))
|
|
85
|
+
server.Close()
|
|
86
|
+
if result.Outcome != GitHubNativeSuccess || !strings.Contains(result.Markdown, "provider_status: computing") || !strings.Contains(result.Markdown, "HTTP 202") || !strings.Contains(result.Markdown, "webctx has no local cache") {
|
|
87
|
+
t.Fatalf("statistics 202 was not truthful for %s: %#v", raw, result)
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
func TestContributorStatisticsRenderProviderCachedTruth(t *testing.T) {
|
|
93
|
+
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
94
|
+
w.Header().Set("Content-Type", "application/json")
|
|
95
|
+
_, _ = io.WriteString(w, `[{"total":5,"author":{"login":"alice"},"weeks":[{"w":1700000000,"a":12,"d":3,"c":2},{"w":1700600000,"a":4,"d":1,"c":3}]}]`)
|
|
96
|
+
}))
|
|
97
|
+
defer server.Close()
|
|
98
|
+
out, err := readGitHubContributorStats(context.Background(), testGitHubClient(server.URL, server.URL, ""), parseGitHubTarget("https://github.com/o/r/graphs/contributors"))
|
|
99
|
+
if err != nil {
|
|
100
|
+
t.Fatal(err)
|
|
101
|
+
}
|
|
102
|
+
if !strings.Contains(out, "@alice — 5 commits · +16 -4") || !strings.Contains(out, "GitHub computes/caches repository statistics upstream") {
|
|
103
|
+
t.Fatalf("contributor stats truth missing:\n%s", out)
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
func TestContributorStatisticsPrioritizeTotalsAndStayBounded(t *testing.T) {
|
|
108
|
+
stats := make([]githubContributorStats, 300)
|
|
109
|
+
for i := range stats {
|
|
110
|
+
stats[i].Total = i
|
|
111
|
+
stats[i].Author.Login = fmt.Sprintf("contributor-%03d", i)
|
|
112
|
+
}
|
|
113
|
+
stats[0].Total = 10000
|
|
114
|
+
stats[0].Author.Login = "zeta"
|
|
115
|
+
stats[1].Total = 10000
|
|
116
|
+
stats[1].Author.Login = "alpha"
|
|
117
|
+
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
118
|
+
w.Header().Set("Content-Type", "application/json")
|
|
119
|
+
_ = json.NewEncoder(w).Encode(stats)
|
|
120
|
+
}))
|
|
121
|
+
defer server.Close()
|
|
122
|
+
|
|
123
|
+
out, err := readGitHubContributorStats(context.Background(), testGitHubClient(server.URL, server.URL, ""), parseGitHubTarget("https://github.com/o/r/graphs/contributors"))
|
|
124
|
+
if err != nil {
|
|
125
|
+
t.Fatal(err)
|
|
126
|
+
}
|
|
127
|
+
if got := utf8.RuneCountInString(out); got > githubOverviewRunes {
|
|
128
|
+
t.Fatalf("contributor overview exceeded shared target: %d runes\n%s", got, out)
|
|
129
|
+
}
|
|
130
|
+
for _, want := range []string{"contributors_returned: 300", "contributors_local_omitted:", "locally omitted from this overview", "@alpha — 10000 commits", "@zeta — 10000 commits"} {
|
|
131
|
+
if !strings.Contains(out, want) {
|
|
132
|
+
t.Fatalf("contributor overview missing %q:\n%s", want, out)
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
if strings.Contains(out, "contributors_local_omitted: 0") || strings.Index(out, "@alpha — 10000 commits") > strings.Index(out, "@zeta — 10000 commits") {
|
|
136
|
+
t.Fatalf("contributor ordering/omission wrong:\n%s", out)
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
func TestCommitActivityAndCodeFrequencyRenderWeeks(t *testing.T) {
|
|
141
|
+
for _, tt := range []struct {
|
|
142
|
+
raw string
|
|
143
|
+
body string
|
|
144
|
+
want string
|
|
145
|
+
}{
|
|
146
|
+
{raw: "https://github.com/o/r/graphs/commit-activity", body: `[{"week":1700000000,"total":7,"days":[1,1,1,1,1,1,1]}]`, want: "7 commits"},
|
|
147
|
+
{raw: "https://github.com/o/r/graphs/code-frequency", body: `[[1700000000,120,-30]]`, want: "+120 -30"},
|
|
148
|
+
} {
|
|
149
|
+
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
150
|
+
w.Header().Set("Content-Type", "application/json")
|
|
151
|
+
_, _ = io.WriteString(w, tt.body)
|
|
152
|
+
}))
|
|
153
|
+
result := readGitHubNativeWithClient(context.Background(), parseGitHubTarget(tt.raw), testGitHubClient(server.URL, server.URL, ""))
|
|
154
|
+
server.Close()
|
|
155
|
+
if result.Outcome != GitHubNativeSuccess || !strings.Contains(result.Markdown, tt.want) || !strings.Contains(result.Markdown, "cached computation") {
|
|
156
|
+
t.Fatalf("statistics render wrong for %s: %#v", tt.raw, result)
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
func TestCodeFrequencyLongHistoryUsesRecentWeeklyIndexAndFullAggregates(t *testing.T) {
|
|
162
|
+
weeks := make([][]int64, 600)
|
|
163
|
+
for i := range weeks {
|
|
164
|
+
weeks[i] = []int64{1700000000 + int64(i*604800), int64(i), -int64(i)}
|
|
165
|
+
}
|
|
166
|
+
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
167
|
+
w.Header().Set("Content-Type", "application/json")
|
|
168
|
+
_ = json.NewEncoder(w).Encode(weeks)
|
|
169
|
+
}))
|
|
170
|
+
defer server.Close()
|
|
171
|
+
|
|
172
|
+
out, err := readGitHubCodeFrequencyStats(context.Background(), testGitHubClient(server.URL, server.URL, ""), parseGitHubTarget("https://github.com/o/r/graphs/code-frequency"))
|
|
173
|
+
if err != nil {
|
|
174
|
+
t.Fatal(err)
|
|
175
|
+
}
|
|
176
|
+
if got := utf8.RuneCountInString(out); got > githubOverviewRunes {
|
|
177
|
+
t.Fatalf("code-frequency overview exceeded shared target: %d runes\n%s", got, out)
|
|
178
|
+
}
|
|
179
|
+
for _, want := range []string{
|
|
180
|
+
"weeks_returned: 600", "weeks_indexed: 52", "weeks_local_omitted: 548",
|
|
181
|
+
"additions_total: 179700", "deletions_total: -179700", "most recent 52 weekly buckets", "+599 -599",
|
|
182
|
+
} {
|
|
183
|
+
if !strings.Contains(out, want) {
|
|
184
|
+
t.Fatalf("long code-frequency overview missing %q:\n%s", want, out)
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
if strings.Contains(out, "+0 0") || strings.Contains(out, "+547 -547") {
|
|
188
|
+
t.Fatalf("old code-frequency buckets leaked into recent index:\n%s", out)
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
func TestDeploymentListIsBoundedAndDoesNotFetchStatuses(t *testing.T) {
|
|
193
|
+
var statusCalls int32
|
|
194
|
+
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
195
|
+
if strings.Contains(r.URL.Path, "/statuses") {
|
|
196
|
+
atomic.AddInt32(&statusCalls, 1)
|
|
197
|
+
t.Fatal("deployment list must not fan out status history")
|
|
198
|
+
}
|
|
199
|
+
if r.URL.Path != "/repos/o/r/deployments" {
|
|
200
|
+
t.Fatalf("deployment list endpoint=%s", r.URL.Path)
|
|
201
|
+
}
|
|
202
|
+
for key, want := range map[string]string{"environment": "production", "page": "2", "per_page": "8"} {
|
|
203
|
+
if got := r.URL.Query().Get(key); got != want {
|
|
204
|
+
t.Errorf("deployments %s=%q want %q", key, got, want)
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
w.Header().Set("Content-Type", "application/json")
|
|
208
|
+
_, _ = io.WriteString(w, `[{"id":101,"sha":"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","ref":"main","task":"deploy","environment":"production","creator":{"login":"alice"},"created_at":"2026-08-01T00:00:00Z"}]`)
|
|
209
|
+
}))
|
|
210
|
+
defer server.Close()
|
|
211
|
+
out, err := readGitHubDeployments(context.Background(), testGitHubClient(server.URL, server.URL, ""), parseGitHubTarget("https://github.com/o/r/deployments?environment=production&page=2"))
|
|
212
|
+
if err != nil {
|
|
213
|
+
t.Fatal(err)
|
|
214
|
+
}
|
|
215
|
+
if atomic.LoadInt32(&statusCalls) != 0 || !strings.Contains(out, "Deployment 101") || !strings.Contains(out, "[production](https://github.com/o/r/deployments/production)") {
|
|
216
|
+
t.Fatalf("deployment list scoping wrong:\n%s", out)
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
func TestDeploymentEnvironmentReadsOnlyLatestStatusPage(t *testing.T) {
|
|
221
|
+
var statusPages int32
|
|
222
|
+
var server *httptest.Server
|
|
223
|
+
server = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
224
|
+
w.Header().Set("Content-Type", "application/json")
|
|
225
|
+
switch r.URL.Path {
|
|
226
|
+
case "/repos/o/r/environments/production":
|
|
227
|
+
_, _ = io.WriteString(w, `{"id":9,"name":"production","html_url":"https://github.com/o/r/deployments/production","created_at":"2026-01-01T00:00:00Z","updated_at":"2026-08-01T00:00:00Z"}`)
|
|
228
|
+
case "/repos/o/r/deployments":
|
|
229
|
+
if r.URL.Query().Get("environment") != "production" || r.URL.Query().Get("per_page") != "8" {
|
|
230
|
+
t.Fatalf("environment deployment query=%s", r.URL.RawQuery)
|
|
231
|
+
}
|
|
232
|
+
_, _ = io.WriteString(w, `[{"id":101,"sha":"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","ref":"main","environment":"production","creator":{"login":"alice"},"created_at":"2026-08-01T00:00:00Z"}]`)
|
|
233
|
+
case "/repos/o/r/deployments/101/statuses":
|
|
234
|
+
atomic.AddInt32(&statusPages, 1)
|
|
235
|
+
if r.URL.Query().Get("page") != "" || r.URL.Query().Get("per_page") != "1" {
|
|
236
|
+
t.Fatalf("deployment status overview followed/changed pagination: %s", r.URL.RawQuery)
|
|
237
|
+
}
|
|
238
|
+
w.Header().Set("Link", fmt.Sprintf(`<%s/repos/o/r/deployments/101/statuses?per_page=1&page=2>; rel="next"`, server.URL))
|
|
239
|
+
_, _ = io.WriteString(w, `[{"id":2,"state":"success","description":"ready","creator":{"login":"bot"},"created_at":"2026-08-01T00:02:00Z","log_url":"https://logs/2","environment_url":"https://env/2"}]`)
|
|
240
|
+
default:
|
|
241
|
+
t.Fatalf("unexpected deployment environment request %s", r.URL.Path)
|
|
242
|
+
}
|
|
243
|
+
}))
|
|
244
|
+
defer server.Close()
|
|
245
|
+
out, err := readGitHubDeploymentEnvironment(context.Background(), testGitHubClient(server.URL, server.URL, ""), parseGitHubTarget("https://github.com/o/r/deployments/production"))
|
|
246
|
+
if err != nil {
|
|
247
|
+
t.Fatal(err)
|
|
248
|
+
}
|
|
249
|
+
for _, want := range []string{"environment: \"production\"", "Deployment 101", "latest status 2 — success — ready", "logs https://logs/2", "Environment: https://env/2", "Older statuses: available upstream", "reads only the latest returned status"} {
|
|
250
|
+
if !strings.Contains(out, want) {
|
|
251
|
+
t.Fatalf("deployment environment missing %q:\n%s", want, out)
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
if strings.Contains(out, "deploying") || atomic.LoadInt32(&statusPages) != 1 {
|
|
255
|
+
t.Fatalf("status pagination pages=%d", statusPages)
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
func TestDeploymentEnvironmentDeepStatusHistoryStaysBounded(t *testing.T) {
|
|
260
|
+
deployments := make([]githubDeployment, 10)
|
|
261
|
+
for i := range deployments {
|
|
262
|
+
deployments[i] = githubDeployment{ID: int64(100 + i), SHA: strings.Repeat("a", 40), Ref: fmt.Sprintf("deploy-%d", i), Environment: "production", CreatedAt: "2026-08-01T00:00:00Z"}
|
|
263
|
+
deployments[i].Creator.Login = "bot"
|
|
264
|
+
}
|
|
265
|
+
var statusCalls int32
|
|
266
|
+
var server *httptest.Server
|
|
267
|
+
server = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
268
|
+
w.Header().Set("Content-Type", "application/json")
|
|
269
|
+
switch {
|
|
270
|
+
case r.URL.Path == "/repos/o/r/environments/production":
|
|
271
|
+
_, _ = io.WriteString(w, `{"id":9,"name":"production"}`)
|
|
272
|
+
case r.URL.Path == "/repos/o/r/deployments":
|
|
273
|
+
_ = json.NewEncoder(w).Encode(deployments)
|
|
274
|
+
case strings.HasPrefix(r.URL.Path, "/repos/o/r/deployments/") && strings.HasSuffix(r.URL.Path, "/statuses"):
|
|
275
|
+
atomic.AddInt32(&statusCalls, 1)
|
|
276
|
+
if r.URL.Query().Get("page") != "" || r.URL.Query().Get("per_page") != "1" {
|
|
277
|
+
t.Fatalf("deep status history was paginated: %s", r.URL.RawQuery)
|
|
278
|
+
}
|
|
279
|
+
w.Header().Set("Link", fmt.Sprintf(`<%s%s?per_page=1&page=2>; rel="next", <%s%s?per_page=1&page=250>; rel="last"`, server.URL, r.URL.Path, server.URL, r.URL.Path))
|
|
280
|
+
_, _ = io.WriteString(w, fmt.Sprintf(`[{"id":999,"state":"success","description":%q,"creator":{"login":"bot"},"created_at":"2026-08-01T00:02:00Z","log_url":"https://logs.example/latest","environment_url":"https://env.example/latest"}]`, strings.Repeat("latest status detail ", 100)))
|
|
281
|
+
default:
|
|
282
|
+
t.Fatalf("unexpected request %s", r.URL.String())
|
|
283
|
+
}
|
|
284
|
+
}))
|
|
285
|
+
defer server.Close()
|
|
286
|
+
|
|
287
|
+
out, err := readGitHubDeploymentEnvironment(context.Background(), testGitHubClient(server.URL, server.URL, ""), parseGitHubTarget("https://github.com/o/r/deployments/production"))
|
|
288
|
+
if err != nil {
|
|
289
|
+
t.Fatal(err)
|
|
290
|
+
}
|
|
291
|
+
if got := utf8.RuneCountInString(out); got > githubOverviewRunes {
|
|
292
|
+
t.Fatalf("deployment environment exceeded shared target: %d runes\n%s", got, out)
|
|
293
|
+
}
|
|
294
|
+
if got := atomic.LoadInt32(&statusCalls); got != 10 {
|
|
295
|
+
t.Fatalf("deployment statuses made %d calls, want one bounded call per deployment", got)
|
|
296
|
+
}
|
|
297
|
+
for _, want := range []string{"deployments_returned: 10", "deployments_with_older_statuses: 10", "latest status 999 — success", "Older statuses: available upstream", "https://logs.example/latest", "https://env.example/latest"} {
|
|
298
|
+
if !strings.Contains(out, want) {
|
|
299
|
+
t.Fatalf("bounded deployment output missing %q:\n%s", want, out)
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
func TestActivityStatisticsDeploymentFailuresStayNative(t *testing.T) {
|
|
305
|
+
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
306
|
+
w.WriteHeader(http.StatusNotFound)
|
|
307
|
+
_, _ = io.WriteString(w, `{"message":"private body"}`)
|
|
308
|
+
}))
|
|
309
|
+
defer server.Close()
|
|
310
|
+
for _, raw := range []string{
|
|
311
|
+
"https://github.com/o/r/activity",
|
|
312
|
+
"https://github.com/o/r/graphs/contributors",
|
|
313
|
+
"https://github.com/o/r/deployments",
|
|
314
|
+
"https://github.com/o/r/deployments/production",
|
|
315
|
+
} {
|
|
316
|
+
result := readGitHubNativeWithClient(context.Background(), parseGitHubTarget(raw), testGitHubClient(server.URL, server.URL, ""))
|
|
317
|
+
if result.Outcome != GitHubNativeFailure || result.Err == nil || strings.Contains(result.Err.Error(), "private body") {
|
|
318
|
+
t.Fatalf("native failure regressed for %s: %#v", raw, result)
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
}
|