check / check (push) Waiting to run
When a finished blob already existed at the destination, the upload was skipped but the blob row, and so the manifest, kept the size of the local copy. That copy differs from the stored object when compression_level or the recipients changed since it was written, so shallow and deep verify failed on a good snapshot. Marking a blob uploaded now records the size of the object in storage: for an existing blob, the size Stat reports. Judgement call: the test adds a recipient rather than changing the compression level, because an extra age recipient always makes the local copy larger, while two zstd levels need not compress the test data differently. Model: opus-5-5
176 lines
4.1 KiB
Go
176 lines
4.1 KiB
Go
package database_test
|
|
|
|
import (
|
|
"context"
|
|
"testing"
|
|
"time"
|
|
|
|
"sneak.berlin/go/vaultik/internal/database"
|
|
"sneak.berlin/go/vaultik/internal/types"
|
|
)
|
|
|
|
func TestBlobRepository(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
db, cleanup := setupTestDB(t)
|
|
defer cleanup()
|
|
|
|
ctx := context.Background()
|
|
repo := database.NewBlobRepository(db)
|
|
|
|
// Test Create
|
|
blob := &database.Blob{
|
|
ID: types.NewBlobID(),
|
|
Hash: types.BlobHash("blobhash123"),
|
|
CreatedTS: time.Now().Truncate(time.Second),
|
|
}
|
|
|
|
err := repo.Create(ctx, nil, blob)
|
|
if err != nil {
|
|
t.Fatalf("failed to create blob: %v", err)
|
|
}
|
|
|
|
// Test GetByHash
|
|
retrieved, err := repo.GetByHash(ctx, blob.Hash.String())
|
|
if err != nil {
|
|
t.Fatalf("failed to get blob: %v", err)
|
|
}
|
|
|
|
if retrieved == nil {
|
|
t.Fatal("expected blob, got nil")
|
|
}
|
|
|
|
if retrieved.Hash != blob.Hash {
|
|
t.Errorf("blob hash mismatch: got %s, want %s", retrieved.Hash, blob.Hash)
|
|
}
|
|
|
|
if !retrieved.CreatedTS.Equal(blob.CreatedTS) {
|
|
t.Errorf("created timestamp mismatch: got %v, want %v",
|
|
retrieved.CreatedTS, blob.CreatedTS)
|
|
}
|
|
|
|
// Test GetByID
|
|
retrievedByID, err := repo.GetByID(ctx, blob.ID.String())
|
|
if err != nil {
|
|
t.Fatalf("failed to get blob by ID: %v", err)
|
|
}
|
|
|
|
if retrievedByID == nil {
|
|
t.Fatal("expected blob, got nil")
|
|
}
|
|
|
|
if retrievedByID.ID != blob.ID {
|
|
t.Errorf("blob ID mismatch: got %s, want %s", retrievedByID.ID, blob.ID)
|
|
}
|
|
|
|
// Test with second blob
|
|
blob2 := &database.Blob{
|
|
ID: types.NewBlobID(),
|
|
Hash: types.BlobHash("blobhash456"),
|
|
CreatedTS: time.Now().Truncate(time.Second),
|
|
}
|
|
|
|
err = repo.Create(ctx, nil, blob2)
|
|
if err != nil {
|
|
t.Fatalf("failed to create second blob: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestBlobRepositoryUpdates(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
db, cleanup := setupTestDB(t)
|
|
defer cleanup()
|
|
|
|
ctx := context.Background()
|
|
repo := database.NewBlobRepository(db)
|
|
|
|
blob := &database.Blob{
|
|
ID: types.NewBlobID(),
|
|
Hash: types.BlobHash("blobhash123"),
|
|
CreatedTS: time.Now().Truncate(time.Second),
|
|
}
|
|
|
|
err := repo.Create(ctx, nil, blob)
|
|
if err != nil {
|
|
t.Fatalf("failed to create blob: %v", err)
|
|
}
|
|
|
|
// Test UpdateFinished
|
|
now := time.Now()
|
|
|
|
err = repo.UpdateFinished(ctx, nil, blob.ID.String(), blob.Hash.String(), 1000, 500)
|
|
if err != nil {
|
|
t.Fatalf("failed to update blob as finished: %v", err)
|
|
}
|
|
|
|
// Verify update
|
|
updated, err := repo.GetByID(ctx, blob.ID.String())
|
|
if err != nil {
|
|
t.Fatalf("failed to get updated blob: %v", err)
|
|
}
|
|
|
|
if updated.FinishedTS == nil {
|
|
t.Fatal("expected finished timestamp to be set")
|
|
}
|
|
|
|
if updated.UncompressedSize != 1000 {
|
|
t.Errorf("expected uncompressed size 1000, got %d", updated.UncompressedSize)
|
|
}
|
|
|
|
if updated.CompressedSize != 500 {
|
|
t.Errorf("expected compressed size 500, got %d", updated.CompressedSize)
|
|
}
|
|
|
|
// Test UpdateUploaded, with a stored object larger than the local copy
|
|
err = repo.UpdateUploaded(ctx, nil, blob.ID.String(), 600)
|
|
if err != nil {
|
|
t.Fatalf("failed to update blob as uploaded: %v", err)
|
|
}
|
|
|
|
// Verify upload update
|
|
uploaded, err := repo.GetByID(ctx, blob.ID.String())
|
|
if err != nil {
|
|
t.Fatalf("failed to get uploaded blob: %v", err)
|
|
}
|
|
|
|
if uploaded.UploadedTS == nil {
|
|
t.Fatal("expected uploaded timestamp to be set")
|
|
}
|
|
// Allow 1 second tolerance for timestamp comparison
|
|
if uploaded.UploadedTS.Before(now.Add(-1 * time.Second)) {
|
|
t.Error("uploaded timestamp should be around test time")
|
|
}
|
|
|
|
if uploaded.CompressedSize != 600 {
|
|
t.Errorf("expected the stored size 600, got %d", uploaded.CompressedSize)
|
|
}
|
|
}
|
|
|
|
func TestBlobRepositoryDuplicate(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
db, cleanup := setupTestDB(t)
|
|
defer cleanup()
|
|
|
|
ctx := context.Background()
|
|
repo := database.NewBlobRepository(db)
|
|
|
|
blob := &database.Blob{
|
|
ID: types.NewBlobID(),
|
|
Hash: types.BlobHash("duplicate_blob"),
|
|
CreatedTS: time.Now().Truncate(time.Second),
|
|
}
|
|
|
|
err := repo.Create(ctx, nil, blob)
|
|
if err != nil {
|
|
t.Fatalf("failed to create blob: %v", err)
|
|
}
|
|
|
|
// Try to create duplicate - should fail due to unique constraint
|
|
err = repo.Create(ctx, nil, blob)
|
|
if err == nil {
|
|
t.Error("expected error for duplicate blob")
|
|
}
|
|
}
|