package main import ( "bytes" "context" "encoding/json" "errors" "io" "mime/multipart" "net/http" "net/http/httptest" "strings" "testing" "time" ) // scopedVerifier accepts a fixed token and reports a fixed user + scope set. type scopedVerifier struct { token string user string scopes []string err error } func (v *scopedVerifier) Verify(_ context.Context, token string) (*AuthInfo, error) { if v.err != nil { return nil, v.err } if token != v.token { return nil, ErrUnauthenticated } return &AuthInfo{User: v.user, Scopes: v.scopes}, nil } func newTestServer(t *testing.T) (*Server, *Storage, *scopedVerifier) { t.Helper() st := newStorage(t) v := &scopedVerifier{ token: "good-token", user: "user1", scopes: []string{"cloud:rw"}, } return NewServer(st, v, DefaultQuota(1<<20 /* 1 MB */)), st, v } func authReq(t *testing.T, method, path string, body io.Reader) *http.Request { t.Helper() r := httptest.NewRequest(method, path, body) r.Header.Set("Authorization", "Bearer good-token") return r } func TestServer_Auth_MissingBearer_401(t *testing.T) { s, _, _ := newTestServer(t) rec := httptest.NewRecorder() s.ServeHTTP(rec, httptest.NewRequest(http.MethodGet, "/v1/manifest", nil)) if rec.Code != http.StatusUnauthorized { t.Errorf("got %d, want 401", rec.Code) } } func TestServer_Auth_BadToken_401(t *testing.T) { s, _, _ := newTestServer(t) rec := httptest.NewRecorder() r := httptest.NewRequest(http.MethodGet, "/v1/manifest", nil) r.Header.Set("Authorization", "Bearer wrong-token") s.ServeHTTP(rec, r) if rec.Code != http.StatusUnauthorized { t.Errorf("got %d, want 401", rec.Code) } } func TestServer_Auth_Revoked_403(t *testing.T) { s, _, v := newTestServer(t) v.err = ErrRevoked rec := httptest.NewRecorder() s.ServeHTTP(rec, authReq(t, http.MethodGet, "/v1/manifest", nil)) if rec.Code != http.StatusForbidden { t.Errorf("got %d, want 403", rec.Code) } } func TestServer_Auth_MissingScope_403(t *testing.T) { s, _, v := newTestServer(t) v.scopes = []string{"some-other-scope"} rec := httptest.NewRecorder() s.ServeHTTP(rec, authReq(t, http.MethodGet, "/v1/manifest", nil)) if rec.Code != http.StatusForbidden { t.Errorf("got %d, want 403", rec.Code) } } func TestServer_Auth_BackendError_502(t *testing.T) { s, _, v := newTestServer(t) v.err = errors.New("upstream down") rec := httptest.NewRecorder() s.ServeHTTP(rec, authReq(t, http.MethodGet, "/v1/manifest", nil)) if rec.Code != http.StatusBadGateway { t.Errorf("got %d, want 502", rec.Code) } } func TestServer_GetManifest_NoSnapshots_204(t *testing.T) { s, _, _ := newTestServer(t) rec := httptest.NewRecorder() s.ServeHTTP(rec, authReq(t, http.MethodGet, "/v1/manifest", nil)) if rec.Code != http.StatusNoContent { t.Errorf("got %d, want 204", rec.Code) } } func TestServer_PostSnapshot_RoundTrip(t *testing.T) { s, st, _ := newTestServer(t) // Write a blob first (simulates the client side). blob := []byte("file contents") sha, err := st.WriteBlob("user1", blob) if err != nil { t.Fatalf("WriteBlob: %v", err) } manifest := &Manifest{ SnapshotID: "01TESTSNAPSHOT0001", CreatedAt: time.Date(2026, 6, 2, 12, 0, 0, 0, time.UTC), Files: map[string]FileEntry{ "options.txt": { SHA256: sha, Size: int64(len(blob)), Mtime: time.Date(2026, 6, 2, 11, 0, 0, 0, time.UTC), }, }, } manifestJSON, _ := json.Marshal(manifest) body := &bytes.Buffer{} mw := multipart.NewWriter(body) fw, _ := mw.CreateFormFile("manifest", "manifest.json") _, _ = fw.Write(manifestJSON) fw, _ = mw.CreateFormFile("tarball", "snapshot.tar.zst") _, _ = fw.Write([]byte("fake-tarball-bytes")) mw.Close() req := authReq(t, http.MethodPost, "/v1/snapshot", body) req.Header.Set("Content-Type", mw.FormDataContentType()) rec := httptest.NewRecorder() s.ServeHTTP(rec, req) if rec.Code != http.StatusOK { t.Fatalf("got %d, body=%s", rec.Code, rec.Body.String()) } var resp map[string]string _ = json.Unmarshal(rec.Body.Bytes(), &resp) if resp["snapshot_id"] != "01TESTSNAPSHOT0001" { t.Errorf("snapshot_id: got %s", resp["snapshot_id"]) } // Now GET /v1/manifest should return the same rec = httptest.NewRecorder() s.ServeHTTP(rec, authReq(t, http.MethodGet, "/v1/manifest", nil)) if rec.Code != http.StatusOK { t.Fatalf("manifest after push: %d", rec.Code) } var got Manifest _ = json.Unmarshal(rec.Body.Bytes(), &got) if got.SnapshotID != "01TESTSNAPSHOT0001" { t.Errorf("manifest snapshot id: got %s", got.SnapshotID) } } func TestServer_GetBlob_ReturnsContent(t *testing.T) { s, st, _ := newTestServer(t) sha, _ := st.WriteBlob("user1", []byte("blob-payload")) rec := httptest.NewRecorder() s.ServeHTTP(rec, authReq(t, http.MethodGet, "/v1/blob/"+sha, nil)) if rec.Code != http.StatusOK { t.Fatalf("got %d", rec.Code) } if rec.Body.String() != "blob-payload" { t.Errorf("body: got %q", rec.Body.String()) } } func TestServer_GetBlob_404(t *testing.T) { s, _, _ := newTestServer(t) rec := httptest.NewRecorder() missing := strings.Repeat("0", 64) s.ServeHTTP(rec, authReq(t, http.MethodGet, "/v1/blob/"+missing, nil)) if rec.Code != http.StatusNotFound { t.Errorf("got %d, want 404", rec.Code) } } func TestServer_PostSnapshot_BadManifest_400(t *testing.T) { s, _, _ := newTestServer(t) body := &bytes.Buffer{} mw := multipart.NewWriter(body) fw, _ := mw.CreateFormFile("manifest", "manifest.json") _, _ = fw.Write([]byte(`{"snapshot_id":"","created_at":"2026-01-01T00:00:00Z","files":{}}`)) fw, _ = mw.CreateFormFile("tarball", "snapshot.tar.zst") _, _ = fw.Write([]byte("x")) mw.Close() req := authReq(t, http.MethodPost, "/v1/snapshot", body) req.Header.Set("Content-Type", mw.FormDataContentType()) rec := httptest.NewRecorder() s.ServeHTTP(rec, req) if rec.Code != http.StatusBadRequest { t.Errorf("got %d, want 400", rec.Code) } } func TestServer_PostSnapshot_QuotaExceeded_413(t *testing.T) { st := newStorage(t) v := &scopedVerifier{token: "good-token", user: "u", scopes: []string{"cloud:rw"}} s := NewServer(st, v, DefaultQuota(100)) // 100 bytes manifest := &Manifest{ SnapshotID: "01QUOTATEST00000001", CreatedAt: time.Date(2026, 6, 2, 12, 0, 0, 0, time.UTC), Files: map[string]FileEntry{ "f.txt": { SHA256: HashBytes([]byte("x")), Size: 1, Mtime: time.Date(2026, 6, 2, 11, 0, 0, 0, time.UTC), }, }, } manifestJSON, _ := json.Marshal(manifest) body := &bytes.Buffer{} mw := multipart.NewWriter(body) fw, _ := mw.CreateFormFile("manifest", "manifest.json") _, _ = fw.Write(manifestJSON) fw, _ = mw.CreateFormFile("tarball", "tar.zst") _, _ = fw.Write(make([]byte, 200)) // > 100 byte quota mw.Close() req := authReq(t, http.MethodPost, "/v1/snapshot", body) req.Header.Set("Content-Type", mw.FormDataContentType()) rec := httptest.NewRecorder() s.ServeHTTP(rec, req) if rec.Code != http.StatusRequestEntityTooLarge { t.Errorf("got %d, want 413", rec.Code) } } func TestServer_ListSnapshots(t *testing.T) { s, st, _ := newTestServer(t) sha, _ := st.WriteBlob("user1", []byte("y")) for _, id := range []string{"01AAA", "01BBB"} { m := buildManifest(id, sha) if err := st.StoreSnapshot("user1", m, []byte("t")); err != nil { t.Fatal(err) } } rec := httptest.NewRecorder() s.ServeHTTP(rec, authReq(t, http.MethodGet, "/v1/snapshots", nil)) if rec.Code != http.StatusOK { t.Fatalf("got %d", rec.Code) } var resp struct { Snapshots []SnapshotInfo `json:"snapshots"` } _ = json.Unmarshal(rec.Body.Bytes(), &resp) if len(resp.Snapshots) != 2 { t.Errorf("snapshots: got %d, want 2", len(resp.Snapshots)) } } func TestServer_GetQuota(t *testing.T) { s, _, _ := newTestServer(t) rec := httptest.NewRecorder() s.ServeHTTP(rec, authReq(t, http.MethodGet, "/v1/quota", nil)) if rec.Code != http.StatusOK { t.Fatalf("got %d", rec.Code) } var resp map[string]any _ = json.Unmarshal(rec.Body.Bytes(), &resp) if _, ok := resp["limit_bytes"]; !ok { t.Errorf("missing limit_bytes; body=%s", rec.Body.String()) } } func TestServer_Healthz(t *testing.T) { s, _, _ := newTestServer(t) rec := httptest.NewRecorder() s.ServeHTTP(rec, httptest.NewRequest(http.MethodGet, "/healthz", nil)) if rec.Code != http.StatusOK || rec.Body.String() != "ok" { t.Errorf("got code=%d body=%q", rec.Code, rec.Body.String()) } } func TestServer_DeleteSnapshot_RefusesLatest_400(t *testing.T) { s, st, _ := newTestServer(t) sha, _ := st.WriteBlob("user1", []byte("z")) m := buildManifest("01ONLYSNAPSHOT00001", sha) if err := st.StoreSnapshot("user1", m, []byte("tar")); err != nil { t.Fatal(err) } rec := httptest.NewRecorder() s.ServeHTTP(rec, authReq(t, http.MethodDelete, "/v1/snapshot/01ONLYSNAPSHOT00001", nil)) if rec.Code != http.StatusBadRequest { t.Errorf("got %d, want 400", rec.Code) } }