Monorepo for Tangled
tangled.org
1package middleware
2
3import (
4 "context"
5 "net/http"
6 "net/http/httptest"
7 "testing"
8
9 "github.com/bluesky-social/indigo/atproto/identity"
10 "github.com/bluesky-social/indigo/atproto/syntax"
11 "github.com/go-chi/chi/v5"
12 "tangled.org/core/appview/models"
13)
14
15func runCanonicalize(t *testing.T, method, urlPath, urlUser, urlRepo, handle string, repo *models.Repo) *httptest.ResponseRecorder {
16 t.Helper()
17 req := httptest.NewRequest(method, urlPath, nil)
18 rctx := chi.NewRouteContext()
19 rctx.URLParams.Add("user", urlUser)
20 rctx.URLParams.Add("repo", urlRepo)
21 ctx := context.WithValue(req.Context(), chi.RouteCtxKey, rctx)
22 id := identity.Identity{
23 DID: syntax.DID("did:plc:boltless"),
24 Handle: syntax.Handle(handle),
25 }
26 ctx = context.WithValue(ctx, "resolvedId", id)
27 ctx = context.WithValue(ctx, "repo", repo)
28 req = req.WithContext(ctx)
29
30 rec := httptest.NewRecorder()
31 called := false
32 next := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
33 called = true
34 w.WriteHeader(http.StatusOK)
35 })
36 mw := Middleware{}
37 mw.CanonicalizeRepoURL()(next).ServeHTTP(rec, req)
38 if rec.Code == http.StatusFound {
39 if called {
40 t.Errorf("middleware both issued 302 and invoked next handler")
41 }
42 }
43 return rec
44}
45
46func TestCanonicalize_CanonicalUrlPassesThrough(t *testing.T) {
47 repo := &models.Repo{Did: "did:plc:boltless", Name: "anemone", Rkey: "anemone"}
48 rec := runCanonicalize(t, "GET", "/boltless.dev/anemone/issues", "boltless.dev", "anemone", "boltless.dev", repo)
49 if rec.Code != http.StatusOK {
50 t.Errorf("canonical URL got %d, want 200; Location=%q", rec.Code, rec.Header().Get("Location"))
51 }
52}
53
54func TestCanonicalize_EmptyNameUsesRkeyAsSlug(t *testing.T) {
55 repo := &models.Repo{Did: "did:plc:boltless", Name: "", Rkey: "anemone"}
56 rec := runCanonicalize(t, "GET", "/boltless.dev/anemone/pulls", "boltless.dev", "anemone", "boltless.dev", repo)
57 if rec.Code != http.StatusOK {
58 t.Errorf("rkey-as-slug canonical URL got %d, want 200; Location=%q", rec.Code, rec.Header().Get("Location"))
59 }
60}
61
62func TestCanonicalize_EmptyNameOwnerDidRedirectsToHandleRkey(t *testing.T) {
63 repo := &models.Repo{Did: "did:plc:boltless", Name: "", Rkey: "anemone"}
64 rec := runCanonicalize(t, "GET", "/did:plc:boltless/anemone/pulls", "did:plc:boltless", "anemone", "boltless.dev", repo)
65 if rec.Code != http.StatusFound {
66 t.Fatalf("got %d, want 302", rec.Code)
67 }
68 if got, want := rec.Header().Get("Location"), "/boltless.dev/anemone/pulls"; got != want {
69 t.Errorf("Location = %q, want %q", got, want)
70 }
71}
72
73func TestCanonicalize_HandleSlashTIDRedirectsToName(t *testing.T) {
74 repo := &models.Repo{Did: "did:plc:boltless", Name: "anemone", Rkey: "3kabcxyz"}
75 rec := runCanonicalize(t, "GET", "/boltless.dev/3kabcxyz/issues", "boltless.dev", "3kabcxyz", "boltless.dev", repo)
76 if rec.Code != http.StatusFound {
77 t.Fatalf("got %d, want 302", rec.Code)
78 }
79 if got, want := rec.Header().Get("Location"), "/boltless.dev/anemone/issues"; got != want {
80 t.Errorf("Location = %q, want %q", got, want)
81 }
82}
83
84func TestCanonicalize_OwnerDidRedirectsToHandle(t *testing.T) {
85 repo := &models.Repo{Did: "did:plc:boltless", Name: "anemone", Rkey: "anemone"}
86 rec := runCanonicalize(t, "GET", "/did:plc:boltless/anemone/pulls/3", "did:plc:boltless", "anemone", "boltless.dev", repo)
87 if rec.Code != http.StatusFound {
88 t.Fatalf("got %d, want 302", rec.Code)
89 }
90 if got, want := rec.Header().Get("Location"), "/boltless.dev/anemone/pulls/3"; got != want {
91 t.Errorf("Location = %q, want %q", got, want)
92 }
93}
94
95func TestCanonicalize_OwnerDidAndTIDRedirectsToCanonical(t *testing.T) {
96 repo := &models.Repo{Did: "did:plc:boltless", Name: "anemone", Rkey: "3kabcxyz"}
97 rec := runCanonicalize(t, "GET", "/did:plc:boltless/3kabcxyz", "did:plc:boltless", "3kabcxyz", "boltless.dev", repo)
98 if rec.Code != http.StatusFound {
99 t.Fatalf("got %d, want 302", rec.Code)
100 }
101 if got, want := rec.Header().Get("Location"), "/boltless.dev/anemone"; got != want {
102 t.Errorf("Location = %q, want %q", got, want)
103 }
104}
105
106func TestCanonicalize_PreservesQueryString(t *testing.T) {
107 repo := &models.Repo{Did: "did:plc:boltless", Name: "anemone", Rkey: "anemone"}
108 rec := runCanonicalize(t, "GET", "/did:plc:boltless/anemone/issues?state=closed&page=2", "did:plc:boltless", "anemone", "boltless.dev", repo)
109 if got, want := rec.Header().Get("Location"), "/boltless.dev/anemone/issues?state=closed&page=2"; got != want {
110 t.Errorf("Location = %q, want %q", got, want)
111 }
112}
113
114func TestCanonicalize_PostNotRedirected(t *testing.T) {
115 repo := &models.Repo{Did: "did:plc:boltless", Name: "anemone", Rkey: "anemone"}
116 rec := runCanonicalize(t, "POST", "/did:plc:boltless/anemone/issues", "did:plc:boltless", "anemone", "boltless.dev", repo)
117 if rec.Code != http.StatusOK {
118 t.Errorf("POST on non-canonical URL got %d, want 200; Location=%q", rec.Code, rec.Header().Get("Location"))
119 }
120}
121
122func TestCanonicalize_InvalidHandlePassesThrough(t *testing.T) {
123 repo := &models.Repo{Did: "did:plc:boltless", Name: "anemone", Rkey: "anemone"}
124 rec := runCanonicalize(t, "GET", "/did:plc:boltless/anemone", "did:plc:boltless", "anemone", string(syntax.HandleInvalid), repo)
125 if rec.Code != http.StatusOK {
126 t.Errorf("invalid handle got %d, want 200; Location=%q", rec.Code, rec.Header().Get("Location"))
127 }
128}
129
130func TestCanonicalize_DotGitSuffixStripped(t *testing.T) {
131 repo := &models.Repo{Did: "did:plc:boltless", Name: "anemone", Rkey: "anemone"}
132 rec := runCanonicalize(t, "GET", "/boltless.dev/anemone.git/", "boltless.dev", "anemone.git", "boltless.dev", repo)
133 if rec.Code != http.StatusOK {
134 t.Errorf(".git on canonical name got %d, want 200; Location=%q", rec.Code, rec.Header().Get("Location"))
135 }
136}