tenseleyflow/shithub / c6bb030

Browse files

githttp: regression test for anonymous clone of an org-owned public repo

Authored by espadonne
SHA
c6bb030c9bfffe3bd05dddb7619168495e5e2f84
Parents
7ff2dc1
Tree
5294a94

1 changed file

StatusFile+-
M internal/web/handlers/githttp/githttp_test.go 51 0
internal/web/handlers/githttp/githttp_test.gomodified
@@ -23,6 +23,7 @@ import (
2323
 	"github.com/tenseleyFlow/shithub/internal/auth/pat"
2424
 	"github.com/tenseleyFlow/shithub/internal/auth/throttle"
2525
 	"github.com/tenseleyFlow/shithub/internal/infra/storage"
26
+	"github.com/tenseleyFlow/shithub/internal/orgs"
2627
 	"github.com/tenseleyFlow/shithub/internal/repos"
2728
 	"github.com/tenseleyFlow/shithub/internal/testing/dbtest"
2829
 	usersdb "github.com/tenseleyFlow/shithub/internal/users/sqlc"
@@ -289,3 +290,53 @@ func TestGitHTTP_PushToArchivedRejected(t *testing.T) {
289290
 func writeFile(path, body string) error {
290291
 	return os.WriteFile(path, []byte(body), 0o600)
291292
 }
293
+
294
+// TestGitHTTP_AnonCloneOrgOwnedPublic is a regression for an outage
295
+// on 2026-05-09: pushing to https://shithub.sh/tenseleyflow/shithub.git
296
+// returned 404 because authorizeForService only resolved owner-slugs
297
+// as users (the comment in handler.go admitted "orgs come in S31").
298
+// Once that landed in production with an org-owned repo, the smart-
299
+// HTTP route became silently unusable for any org repo.
300
+func TestGitHTTP_AnonCloneOrgOwnedPublic(t *testing.T) {
301
+	t.Parallel()
302
+	env := setupEnv(t)
303
+
304
+	// Create an org owned by alice and a public repo under it.
305
+	org, err := orgs.Create(context.Background(), orgs.Deps{
306
+		Pool:   env.pool,
307
+		Logger: slog.New(slog.NewTextHandler(io.Discard, nil)),
308
+		Audit:  audit.NewRecorder(),
309
+	}, orgs.CreateParams{
310
+		Slug: "myorg", DisplayName: "MyOrg", CreatedByUserID: env.userID,
311
+	})
312
+	if err != nil {
313
+		t.Fatalf("orgs.Create: %v", err)
314
+	}
315
+	rfs, err := storage.NewRepoFS(env.root)
316
+	if err != nil {
317
+		t.Fatalf("NewRepoFS: %v", err)
318
+	}
319
+	rdeps := repos.Deps{
320
+		Pool: env.pool, RepoFS: rfs, Audit: audit.NewRecorder(), Limiter: throttle.NewLimiter(),
321
+		Logger: slog.New(slog.NewTextHandler(io.Discard, nil)),
322
+	}
323
+	if _, err := repos.Create(context.Background(), rdeps, repos.Params{
324
+		OwnerOrgID: org.ID, OwnerSlug: org.Slug, ActorUserID: env.userID,
325
+		Name: "org-public", Visibility: "public", InitReadme: true,
326
+	}); err != nil {
327
+		t.Fatalf("create org repo: %v", err)
328
+	}
329
+
330
+	dst := filepath.Join(t.TempDir(), "clone")
331
+	out, err := gitCmd("clone", env.srv.URL+"/myorg/org-public.git", dst).CombinedOutput()
332
+	if err != nil {
333
+		t.Fatalf("clone org-owned repo: %v\n%s", err, out)
334
+	}
335
+	out, err = gitCmd("-C", dst, "rev-list", "--count", "HEAD").CombinedOutput()
336
+	if err != nil {
337
+		t.Fatalf("rev-list: %v\n%s", err, out)
338
+	}
339
+	if got := strings.TrimSpace(string(out)); got != "1" {
340
+		t.Fatalf("rev-list = %q, want 1", got)
341
+	}
342
+}