Phase 2 — internal/storage/: Content-addressed blob storage with atomic writes via rename. BlobWriter stages data in uploads dir with running SHA-256 hash, commits by verifying digest then renaming to layers/sha256/<prefix>/<hex>. Reader provides Open, Stat, Delete, Exists with digest validation. Phase 3 — internal/auth/ + internal/server/: MCIAS client with Login and ValidateToken, 30s SHA-256-keyed cache with lazy eviction and injectable clock for testing. TLS 1.3 minimum with optional custom CA cert. Chi router with RequireAuth middleware (Bearer token extraction, WWW-Authenticate header, OCI error format), token endpoint (Basic auth → bearer exchange via MCIAS), and /v2/ version check handler. 52 tests passing (14 storage + 9 auth + 9 server + 20 existing). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
69 lines
2.0 KiB
Go
69 lines
2.0 KiB
Go
package storage
|
|
|
|
import (
|
|
"errors"
|
|
"path/filepath"
|
|
"testing"
|
|
)
|
|
|
|
func newTestStore(t *testing.T) *Store {
|
|
t.Helper()
|
|
dir := t.TempDir()
|
|
return New(filepath.Join(dir, "layers"), filepath.Join(dir, "uploads"))
|
|
}
|
|
|
|
func TestNew(t *testing.T) {
|
|
s := newTestStore(t)
|
|
if s == nil {
|
|
t.Fatal("New returned nil")
|
|
}
|
|
if s.layersPath == "" {
|
|
t.Fatal("layersPath is empty")
|
|
}
|
|
if s.uploadsPath == "" {
|
|
t.Fatal("uploadsPath is empty")
|
|
}
|
|
}
|
|
|
|
func TestValidateDigest(t *testing.T) {
|
|
valid := []string{
|
|
"sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
|
|
"sha256:0000000000000000000000000000000000000000000000000000000000000000",
|
|
"sha256:abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789",
|
|
}
|
|
for _, d := range valid {
|
|
if err := validateDigest(d); err != nil {
|
|
t.Errorf("validateDigest(%q) = %v, want nil", d, err)
|
|
}
|
|
}
|
|
|
|
invalid := []string{
|
|
"",
|
|
"sha256:",
|
|
"sha256:abc",
|
|
"sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b85", // 63 chars
|
|
"sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b8555", // 65 chars
|
|
"sha256:E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855", // uppercase
|
|
"md5:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", // wrong algo
|
|
"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", // missing prefix
|
|
"sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b85g", // non-hex char
|
|
}
|
|
for _, d := range invalid {
|
|
if err := validateDigest(d); !errors.Is(err, ErrInvalidDigest) {
|
|
t.Errorf("validateDigest(%q) = %v, want ErrInvalidDigest", d, err)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestBlobPath(t *testing.T) {
|
|
s := New("/data/layers", "/data/uploads")
|
|
|
|
digest := "sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
|
|
got := s.blobPath(digest)
|
|
want := filepath.Join("/data/layers", "sha256", "e3",
|
|
"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855")
|
|
if got != want {
|
|
t.Fatalf("blobPath(%q)\n got %q\nwant %q", digest, got, want)
|
|
}
|
|
}
|