Monorepo for Tangled tangled.org
6

Configure Feed

Select the types of activity you want to include in your feed.

1package knotmirror 2 3import ( 4 "context" 5 "io" 6 "log/slog" 7 "net/http" 8 "net/http/httptest" 9 "strings" 10 "testing" 11 12 "tangled.org/core/knotmirror/config" 13 "tangled.org/core/knotmirror/models" 14) 15 16func uploadPackAdvert(capabilities string) string { 17 return "001e# service=git-upload-pack\n" + 18 "0000" + 19 "0000000000000000000000000000000000000000 capabilities^{}\x00" + capabilities + "\n" + 20 "0000" 21} 22 23func TestCheckKnotObjectFormat(t *testing.T) { 24 const gitContentType = "application/x-git-upload-pack-advertisement" 25 26 tests := []struct { 27 name string 28 status int 29 contentType string 30 body string 31 wantFormat models.ObjectFormat 32 wantErr bool 33 wantRateLimit bool 34 }{ 35 { 36 name: "sha256 repo is detected", 37 status: http.StatusOK, 38 contentType: gitContentType, 39 body: uploadPackAdvert("multi_ack thin-pack side-band-64k ofs-delta object-format=sha256 agent=git/2.45.0"), 40 wantFormat: models.ObjectFormatSHA256, 41 }, 42 { 43 name: "explicit sha1 repo stays on sha1", 44 status: http.StatusOK, 45 contentType: gitContentType, 46 body: uploadPackAdvert("multi_ack thin-pack side-band-64k ofs-delta object-format=sha1 agent=git/2.45.0"), 47 wantFormat: models.ObjectFormatSHA1, 48 }, 49 { 50 name: "advertisement without object-format defaults to sha1", 51 status: http.StatusOK, 52 contentType: gitContentType, 53 body: uploadPackAdvert("multi_ack thin-pack side-band-64k ofs-delta agent=git/2.34.0"), 54 wantFormat: models.ObjectFormatSHA1, 55 }, 56 { 57 name: "sha256 detected with content-type parameters", 58 status: http.StatusOK, 59 contentType: gitContentType + "; charset=utf-8", 60 body: uploadPackAdvert("object-format=sha256"), 61 wantFormat: models.ObjectFormatSHA256, 62 }, 63 { 64 name: "rate limited knot", 65 status: http.StatusTooManyRequests, 66 wantErr: true, 67 wantRateLimit: true, 68 }, 69 { 70 name: "missing repo on knot", 71 status: http.StatusNotFound, 72 wantErr: true, 73 }, 74 { 75 name: "non git content-type", 76 status: http.StatusOK, 77 contentType: "text/html", 78 body: "<html>not a git server</html>", 79 wantErr: true, 80 }, 81 } 82 83 for _, tt := range tests { 84 t.Run(tt.name, func(t *testing.T) { 85 var gotPath string 86 srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 87 gotPath = r.URL.Path 88 if tt.contentType != "" { 89 w.Header().Set("Content-Type", tt.contentType) 90 } 91 w.WriteHeader(tt.status) 92 io.WriteString(w, tt.body) 93 })) 94 defer srv.Close() 95 96 r := &Resyncer{ 97 logger: slog.New(slog.NewTextHandler(io.Discard, nil)), 98 cfg: &config.Config{}, 99 httpClient: srv.Client(), 100 } 101 repo := &models.Repo{ 102 RepoDid: "did:plc:boltless", 103 KnotDomain: srv.URL, 104 } 105 106 format, err := r.checkKnot(context.Background(), repo) 107 108 if tt.wantErr { 109 if err == nil { 110 t.Fatalf("expected error, got format %q", format) 111 } 112 if format != "" { 113 t.Errorf("expected empty format on error, got %q", format) 114 } 115 if got := isRateLimitError(err); got != tt.wantRateLimit { 116 t.Errorf("isRateLimitError = %v, want %v", got, tt.wantRateLimit) 117 } 118 return 119 } 120 121 if err != nil { 122 t.Fatalf("unexpected error: %v", err) 123 } 124 if format != tt.wantFormat { 125 t.Errorf("format = %q, want %q", format, tt.wantFormat) 126 } 127 if !strings.HasSuffix(gotPath, "/info/refs") { 128 t.Errorf("expected upstream request to /info/refs, got %q", gotPath) 129 } 130 }) 131 } 132}