255 lines
6.2 KiB
Go
255 lines
6.2 KiB
Go
package oci_test
|
|
|
|
import (
|
|
"bytes"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/forgeo/forgebucket/internal/domain/oci"
|
|
)
|
|
|
|
func TestParseOCIPath(t *testing.T) {
|
|
tests := []struct {
|
|
path string
|
|
wantName string
|
|
wantKind string
|
|
wantRef string
|
|
}{
|
|
{"/v2/", "", "", ""},
|
|
{"/v2", "", "", ""},
|
|
{"/v2/alice/myapp/tags/list", "alice/myapp", "tags", ""},
|
|
{"/v2/alice/myapp/manifests/latest", "alice/myapp", "manifest", "latest"},
|
|
{"/v2/alice/myapp/manifests/sha256:abc123", "alice/myapp", "manifest", "sha256:abc123"},
|
|
{"/v2/alice/myapp/blobs/sha256:def456", "alice/myapp", "blob", "sha256:def456"},
|
|
{"/v2/alice/myapp/blobs/uploads/", "alice/myapp", "upload", ""},
|
|
{"/v2/alice/myapp/blobs/uploads/uuid123", "alice/myapp", "upload", "uuid123"},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.path, func(t *testing.T) {
|
|
name, kind, ref := oci.ParseOCIPath(tt.path)
|
|
if name != tt.wantName {
|
|
t.Errorf("name = %q, want %q", name, tt.wantName)
|
|
}
|
|
if kind != tt.wantKind {
|
|
t.Errorf("kind = %q, want %q", kind, tt.wantKind)
|
|
}
|
|
if ref != tt.wantRef {
|
|
t.Errorf("ref = %q, want %q", ref, tt.wantRef)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestValidateName(t *testing.T) {
|
|
if err := oci.ValidateName("alice/myapp"); err != nil {
|
|
t.Errorf("valid name got error: %v", err)
|
|
}
|
|
if err := oci.ValidateName(""); err == nil {
|
|
t.Error("empty name should error")
|
|
}
|
|
if err := oci.ValidateName("alice/my app"); err == nil {
|
|
t.Error("name with spaces should error")
|
|
}
|
|
}
|
|
|
|
func TestBlobPath(t *testing.T) {
|
|
dir := t.TempDir()
|
|
reg, err := oci.New(dir)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
p, err := reg.BlobPath("sha256:" + strings.Repeat("a", 64))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
expectedSuffix := filepath.Join("blobs", "sha256", strings.Repeat("a", 64))
|
|
if !strings.HasSuffix(p, expectedSuffix) {
|
|
t.Errorf("path %q does not end with %q", p, expectedSuffix)
|
|
}
|
|
|
|
if _, err := reg.BlobPath("sha256:bad"); err == nil {
|
|
t.Error("expected error for short hex")
|
|
}
|
|
if _, err := reg.BlobPath("md5:abc"); err == nil {
|
|
t.Error("expected error for non-sha256 algorithm")
|
|
}
|
|
}
|
|
|
|
func TestWriteAndReadBlob(t *testing.T) {
|
|
dir := t.TempDir()
|
|
reg, err := oci.New(dir)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
content := []byte("hello oci blob")
|
|
digest, size, err := reg.WriteBlob(bytes.NewReader(content))
|
|
if err != nil {
|
|
t.Fatalf("WriteBlob: %v", err)
|
|
}
|
|
|
|
if !strings.HasPrefix(digest, "sha256:") {
|
|
t.Errorf("digest should start with sha256:, got %s", digest)
|
|
}
|
|
if size != int64(len(content)) {
|
|
t.Errorf("size = %d, want %d", size, len(content))
|
|
}
|
|
|
|
if !reg.BlobExists(digest) {
|
|
t.Error("blob should exist after write")
|
|
}
|
|
|
|
// Deduplication test: writing same content again should succeed without error.
|
|
d2, s2, err := reg.WriteBlob(bytes.NewReader(content))
|
|
if err != nil {
|
|
t.Fatalf("WriteBlob duplicate: %v", err)
|
|
}
|
|
if d2 != digest {
|
|
t.Errorf("digest mismatch: %s vs %s", d2, digest)
|
|
}
|
|
if s2 != size {
|
|
t.Errorf("size mismatch: %d vs %d", s2, size)
|
|
}
|
|
|
|
f, err := reg.ReadBlob(digest)
|
|
if err != nil {
|
|
t.Fatalf("ReadBlob: %v", err)
|
|
}
|
|
defer f.Close()
|
|
|
|
buf := new(bytes.Buffer)
|
|
buf.ReadFrom(f)
|
|
if buf.String() != string(content) {
|
|
t.Errorf("content mismatch: got %s", buf.String())
|
|
}
|
|
}
|
|
|
|
func TestUploadSession(t *testing.T) {
|
|
dir := t.TempDir()
|
|
reg, _ := oci.New(dir)
|
|
|
|
uploadID := "test-upload-001"
|
|
|
|
// Append content in chunks.
|
|
off, err := reg.AppendUpload(uploadID, strings.NewReader("chunk1"))
|
|
if err != nil {
|
|
t.Fatalf("AppendUpload: %v", err)
|
|
}
|
|
if off != 6 {
|
|
t.Errorf("expected offset 6, got %d", off)
|
|
}
|
|
|
|
off, err = reg.AppendUpload(uploadID, strings.NewReader("-chunk2"))
|
|
if err != nil {
|
|
t.Fatalf("AppendUpload second: %v", err)
|
|
}
|
|
if off != 13 {
|
|
t.Errorf("expected offset 13 after chunk2, got %d", off)
|
|
}
|
|
|
|
if reg.UploadOffset(uploadID) != 13 {
|
|
t.Errorf("UploadOffset = %d, want 13", reg.UploadOffset(uploadID))
|
|
}
|
|
|
|
// Finish upload with digest.
|
|
digest, size, err := reg.FinishUpload(uploadID, "")
|
|
if err != nil {
|
|
t.Fatalf("FinishUpload: %v", err)
|
|
}
|
|
if !strings.HasPrefix(digest, "sha256:") {
|
|
t.Errorf("expected sha256 digest, got %s", digest)
|
|
}
|
|
if size != 13 {
|
|
t.Errorf("expected size 13, got %d", size)
|
|
}
|
|
|
|
if !reg.BlobExists(digest) {
|
|
t.Error("blob should exist after finish upload")
|
|
}
|
|
|
|
// Verify content.
|
|
f, _ := reg.ReadBlob(digest)
|
|
buf := new(bytes.Buffer)
|
|
buf.ReadFrom(f)
|
|
f.Close()
|
|
if buf.String() != "chunk1-chunk2" {
|
|
t.Errorf("content = %q, want %q", buf.String(), "chunk1-chunk2")
|
|
}
|
|
}
|
|
|
|
func TestFinishUploadDigestMismatch(t *testing.T) {
|
|
dir := t.TempDir()
|
|
reg, _ := oci.New(dir)
|
|
|
|
uploadID := "mismatch-upload"
|
|
reg.AppendUpload(uploadID, strings.NewReader("some data"))
|
|
|
|
_, _, err := reg.FinishUpload(uploadID, "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa")
|
|
if err == nil {
|
|
t.Fatal("expected digest mismatch error")
|
|
}
|
|
if !strings.Contains(err.Error(), "digest mismatch") {
|
|
t.Errorf("expected 'digest mismatch', got: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestManifestDescriptor(t *testing.T) {
|
|
body := []byte(`{"schemaVersion":2}`)
|
|
digest, size := oci.ManifestDescriptor(body)
|
|
if !strings.HasPrefix(digest, "sha256:") {
|
|
t.Errorf("digest should be sha256, got %s", digest)
|
|
}
|
|
if size != int64(len(body)) {
|
|
t.Errorf("size = %d, want %d", size, len(body))
|
|
}
|
|
}
|
|
|
|
func TestIsDigestRef(t *testing.T) {
|
|
if !oci.IsDigestRef("sha256:abc") {
|
|
t.Error("sha256:abc should be a digest ref")
|
|
}
|
|
if oci.IsDigestRef("latest") {
|
|
t.Error("latest should NOT be a digest ref")
|
|
}
|
|
}
|
|
|
|
func TestDeleteBlob(t *testing.T) {
|
|
dir := t.TempDir()
|
|
reg, _ := oci.New(dir)
|
|
content := []byte("delete me")
|
|
digest, _, _ := reg.WriteBlob(bytes.NewReader(content))
|
|
|
|
if !reg.BlobExists(digest) {
|
|
t.Fatal("blob should exist after write")
|
|
}
|
|
|
|
if err := reg.DeleteBlob(digest); err != nil {
|
|
t.Fatalf("DeleteBlob: %v", err)
|
|
}
|
|
|
|
if reg.BlobExists(digest) {
|
|
t.Error("blob should not exist after delete")
|
|
}
|
|
}
|
|
|
|
func TestNewCreatesDirectories(t *testing.T) {
|
|
dir := filepath.Join(t.TempDir(), "oci-storage")
|
|
reg, err := oci.New(dir)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
for _, sub := range []string{"blobs/sha256", "uploads"} {
|
|
p := filepath.Join(dir, sub)
|
|
if _, err := os.Stat(p); os.IsNotExist(err) {
|
|
t.Errorf("directory not created: %s", p)
|
|
}
|
|
}
|
|
_ = reg
|
|
}
|