package oci import ( "bytes" "io" "net/http" "net/http/httptest" "sort" "sync" "github.com/go-chi/chi/v5" "git.wntrmute.dev/mc/mcr/internal/auth" "git.wntrmute.dev/mc/mcr/internal/db" "git.wntrmute.dev/mc/mcr/internal/policy" "git.wntrmute.dev/mc/mcr/internal/storage" ) // manifestKey uniquely identifies a manifest for test lookup. type manifestKey struct { repoID int64 reference string // tag or digest } // fakeDB implements DBQuerier for tests. type fakeDB struct { mu sync.Mutex repos map[string]int64 // name -> id manifests map[manifestKey]*db.ManifestRow // (repoID, ref) -> manifest blobs map[int64]map[string]bool // repoID -> set of digests allBlobs map[string]bool // global blob digests tags map[int64][]string // repoID -> sorted tag names repoNames []string // sorted repo names uploads map[string]*db.UploadRow // uuid -> upload nextID int64 // auto-increment counter pushed []db.PushManifestParams // record of pushed manifests } func newFakeDB() *fakeDB { return &fakeDB{ repos: make(map[string]int64), manifests: make(map[manifestKey]*db.ManifestRow), blobs: make(map[int64]map[string]bool), allBlobs: make(map[string]bool), tags: make(map[int64][]string), uploads: make(map[string]*db.UploadRow), nextID: 1, } } func (f *fakeDB) GetRepositoryByName(name string) (int64, error) { id, ok := f.repos[name] if !ok { return 0, db.ErrRepoNotFound } return id, nil } func (f *fakeDB) GetManifestByTag(repoID int64, tag string) (*db.ManifestRow, error) { m, ok := f.manifests[manifestKey{repoID, tag}] if !ok { return nil, db.ErrManifestNotFound } return m, nil } func (f *fakeDB) GetManifestByDigest(repoID int64, digest string) (*db.ManifestRow, error) { m, ok := f.manifests[manifestKey{repoID, digest}] if !ok { return nil, db.ErrManifestNotFound } return m, nil } func (f *fakeDB) BlobExistsInRepo(repoID int64, digest string) (bool, error) { digests, ok := f.blobs[repoID] if !ok { return false, nil } return digests[digest], nil } func (f *fakeDB) ListTags(repoID int64, after string, limit int) ([]string, error) { allTags := f.tags[repoID] var result []string for _, t := range allTags { if after != "" && t <= after { continue } result = append(result, t) if len(result) >= limit { break } } return result, nil } func (f *fakeDB) ListRepositoryNames(after string, limit int) ([]string, error) { var result []string for _, n := range f.repoNames { if after != "" && n <= after { continue } result = append(result, n) if len(result) >= limit { break } } return result, nil } func (f *fakeDB) GetOrCreateRepository(name string) (int64, error) { f.mu.Lock() defer f.mu.Unlock() id, ok := f.repos[name] if ok { return id, nil } id = f.nextID f.nextID++ f.repos[name] = id f.repoNames = append(f.repoNames, name) sort.Strings(f.repoNames) return id, nil } func (f *fakeDB) BlobExists(digest string) (bool, error) { return f.allBlobs[digest], nil } func (f *fakeDB) InsertBlob(digest string, size int64) error { f.allBlobs[digest] = true _ = size return nil } func (f *fakeDB) PushManifest(p db.PushManifestParams) error { f.mu.Lock() defer f.mu.Unlock() f.pushed = append(f.pushed, p) // Simulate creating the manifest in our fake data. repoID, ok := f.repos[p.RepoName] if !ok { repoID = f.nextID f.nextID++ f.repos[p.RepoName] = repoID f.repoNames = append(f.repoNames, p.RepoName) sort.Strings(f.repoNames) } m := &db.ManifestRow{ ID: f.nextID, RepositoryID: repoID, Digest: p.Digest, MediaType: p.MediaType, Content: p.Content, Size: p.Size, } f.nextID++ f.manifests[manifestKey{repoID, p.Digest}] = m if p.Tag != "" { f.manifests[manifestKey{repoID, p.Tag}] = m } return nil } func (f *fakeDB) CreateUpload(uuid string, repoID int64) error { f.mu.Lock() defer f.mu.Unlock() f.uploads[uuid] = &db.UploadRow{ ID: f.nextID, UUID: uuid, RepositoryID: repoID, ByteOffset: 0, } f.nextID++ return nil } func (f *fakeDB) GetUpload(uuid string) (*db.UploadRow, error) { f.mu.Lock() defer f.mu.Unlock() u, ok := f.uploads[uuid] if !ok { return nil, db.ErrUploadNotFound } return u, nil } func (f *fakeDB) UpdateUploadOffset(uuid string, offset int64) error { f.mu.Lock() defer f.mu.Unlock() u, ok := f.uploads[uuid] if !ok { return db.ErrUploadNotFound } u.ByteOffset = offset return nil } func (f *fakeDB) DeleteUpload(uuid string) error { f.mu.Lock() defer f.mu.Unlock() if _, ok := f.uploads[uuid]; !ok { return db.ErrUploadNotFound } delete(f.uploads, uuid) return nil } func (f *fakeDB) DeleteManifest(repoID int64, digest string) error { f.mu.Lock() defer f.mu.Unlock() key := manifestKey{repoID, digest} if _, ok := f.manifests[key]; !ok { return db.ErrManifestNotFound } delete(f.manifests, key) // Also remove any tag entries pointing to this digest. for k, m := range f.manifests { if k.repoID == repoID && m.Digest == digest { delete(f.manifests, k) } } return nil } func (f *fakeDB) DeleteBlobFromRepo(repoID int64, digest string) error { f.mu.Lock() defer f.mu.Unlock() digests, ok := f.blobs[repoID] if !ok || !digests[digest] { return db.ErrBlobNotFound } delete(digests, digest) return nil } // addRepo adds a repo to the fakeDB and returns its ID. func (f *fakeDB) addRepo(name string, id int64) { f.repos[name] = id f.repoNames = append(f.repoNames, name) sort.Strings(f.repoNames) if id >= f.nextID { f.nextID = id + 1 } } // addManifest adds a manifest accessible by both tag and digest. func (f *fakeDB) addManifest(repoID int64, tag, digest, mediaType string, content []byte) { m := &db.ManifestRow{ ID: f.nextID, RepositoryID: repoID, Digest: digest, MediaType: mediaType, Content: content, Size: int64(len(content)), } f.nextID++ if tag != "" { f.manifests[manifestKey{repoID, tag}] = m } f.manifests[manifestKey{repoID, digest}] = m } // addBlob registers a blob digest in a repository. func (f *fakeDB) addBlob(repoID int64, digest string) { if f.blobs[repoID] == nil { f.blobs[repoID] = make(map[string]bool) } f.blobs[repoID][digest] = true } // addGlobalBlob registers a blob in the global blob table. func (f *fakeDB) addGlobalBlob(digest string) { f.allBlobs[digest] = true } // addTag adds a tag to a repository's tag list. func (f *fakeDB) addTag(repoID int64, tag string) { f.tags[repoID] = append(f.tags[repoID], tag) sort.Strings(f.tags[repoID]) } // fakeBlobs implements BlobStore for tests. type fakeBlobs struct { data map[string][]byte // digest -> content uploads map[string]*bytes.Buffer } func newFakeBlobs() *fakeBlobs { return &fakeBlobs{ data: make(map[string][]byte), uploads: make(map[string]*bytes.Buffer), } } func (f *fakeBlobs) Open(digest string) (io.ReadCloser, error) { data, ok := f.data[digest] if !ok { return nil, io.ErrUnexpectedEOF } return io.NopCloser(bytes.NewReader(data)), nil } func (f *fakeBlobs) Stat(digest string) (int64, error) { data, ok := f.data[digest] if !ok { return 0, io.ErrUnexpectedEOF } return int64(len(data)), nil } func (f *fakeBlobs) StartUpload(uuid string) (*storage.BlobWriter, error) { // For tests that need real storage, use a real Store in t.TempDir(). // This fake panics to catch unintended usage. panic("fakeBlobs.StartUpload should not be called; use a real storage.Store for upload tests") } // fakePolicy implements PolicyEval, always returning Allow. type fakePolicy struct { effect policy.Effect } func (f *fakePolicy) Evaluate(_ policy.PolicyInput) (policy.Effect, *policy.Rule) { return f.effect, nil } // allowAll returns a fakePolicy that allows all requests. func allowAll() *fakePolicy { return &fakePolicy{effect: policy.Allow} } // testRouter creates a chi.Mux with the OCI handler mounted at /v2. func testRouter(h *Handler) *chi.Mux { parent := chi.NewRouter() parent.Mount("/v2", h.Router()) return parent } // authedRequest creates an HTTP request with authenticated claims in the context. func authedRequest(method, path string, body io.Reader) *http.Request { req := httptest.NewRequest(method, path, body) claims := &auth.Claims{ Subject: "test-user", AccountType: "human", Roles: []string{"user"}, } return req.WithContext(auth.ContextWithClaims(req.Context(), claims)) }