Fix every finding surfaced by the canonical .golangci.yml with golangci-lint v2.12.2 (refs #61), behavior-preserving throughout: - err113: dynamic errors replaced with package-level sentinels and %w wrapping; direct comparisons converted to errors.Is - goprintffuncname: printf-style helpers renamed with an f suffix (ui.Writer message methods, cli.ReportErrorf, database.Fatalf, vaultik stdoutf) and all call sites updated - revive: stuttering type names renamed (blob.Handler, blob.WithReader, blob.ChunkPosition, storage.URL, storage.Info), doc comments added, unused parameters blanked, package comments added - contextcheck/noctx: ctx threaded through blob.Packer (AddChunk/Flush/FinalizeBlob/PackChunks) and scanner call sites; context-aware exec and sql variants used - funlen/cyclop/gocognit/nestif/dupl: oversized or duplicated functions split into focused helpers across production and test code - paralleltest/tparallel/thelper/usetesting/testpackage: tests parallelized where safe (global log.Initialize kept in the serial phase), helpers marked, t.TempDir adopted, external test packages where only exported API is used - gosec: integer conversions clamped or justified, header timeouts added, remaining findings suppressed with per-site justifications - mnd/goconst/lll/wsl_v5/nlreturn/noinlineerr/errcheck and other mechanical findings fixed directly Remove the deprecated log.LogOptions alias (callers migrated to log.Options). make check is green.
263 lines
6.1 KiB
Go
263 lines
6.1 KiB
Go
package database_test
|
|
|
|
import (
|
|
"context"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"sneak.berlin/go/vaultik/internal/database"
|
|
"sneak.berlin/go/vaultik/internal/types"
|
|
)
|
|
|
|
// Chunk hashes used across the blob_chunks tests.
|
|
const (
|
|
chunk1Hash = "chunk1"
|
|
chunk2Hash = "chunk2"
|
|
chunk3Hash = "chunk3"
|
|
)
|
|
|
|
// mustCreateChunks registers the given chunk hashes (1024 bytes each).
|
|
func mustCreateChunks(
|
|
t *testing.T,
|
|
repos *database.Repositories,
|
|
hashes ...types.ChunkHash,
|
|
) {
|
|
t.Helper()
|
|
|
|
ctx := context.Background()
|
|
|
|
for _, chunkHash := range hashes {
|
|
chunk := &database.Chunk{
|
|
ChunkHash: chunkHash,
|
|
Size: 1024,
|
|
}
|
|
|
|
err := repos.Chunks.Create(ctx, nil, chunk)
|
|
if err != nil {
|
|
t.Fatalf("failed to create chunk %s: %v", chunkHash, err)
|
|
}
|
|
}
|
|
}
|
|
|
|
// mustCreateBlob creates a blob row with the given hash.
|
|
func mustCreateBlob(
|
|
t *testing.T,
|
|
repos *database.Repositories,
|
|
hash types.BlobHash,
|
|
) *database.Blob {
|
|
t.Helper()
|
|
|
|
blob := &database.Blob{
|
|
ID: types.NewBlobID(),
|
|
Hash: hash,
|
|
CreatedTS: time.Now(),
|
|
}
|
|
|
|
err := repos.Blobs.Create(context.Background(), nil, blob)
|
|
if err != nil {
|
|
t.Fatalf("failed to create blob %s: %v", hash, err)
|
|
}
|
|
|
|
return blob
|
|
}
|
|
|
|
func TestBlobChunkRepository(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
db, cleanup := setupTestDB(t)
|
|
defer cleanup()
|
|
|
|
ctx := context.Background()
|
|
repos := database.NewRepositories(db)
|
|
|
|
blob := mustCreateBlob(t, repos, "blob1-hash")
|
|
mustCreateChunks(t, repos, chunk1Hash, chunk2Hash, chunk3Hash)
|
|
|
|
// Test Create
|
|
bc1 := &database.BlobChunk{
|
|
BlobID: blob.ID,
|
|
ChunkHash: types.ChunkHash(chunk1Hash),
|
|
Offset: 0,
|
|
Length: 1024,
|
|
}
|
|
|
|
err := repos.BlobChunks.Create(ctx, nil, bc1)
|
|
if err != nil {
|
|
t.Fatalf("failed to create blob chunk: %v", err)
|
|
}
|
|
|
|
// Add more chunks to the same blob
|
|
bc2 := &database.BlobChunk{
|
|
BlobID: blob.ID,
|
|
ChunkHash: types.ChunkHash(chunk2Hash),
|
|
Offset: 1024,
|
|
Length: 2048,
|
|
}
|
|
|
|
err = repos.BlobChunks.Create(ctx, nil, bc2)
|
|
if err != nil {
|
|
t.Fatalf("failed to create second blob chunk: %v", err)
|
|
}
|
|
|
|
bc3 := &database.BlobChunk{
|
|
BlobID: blob.ID,
|
|
ChunkHash: types.ChunkHash(chunk3Hash),
|
|
Offset: 3072,
|
|
Length: 512,
|
|
}
|
|
|
|
err = repos.BlobChunks.Create(ctx, nil, bc3)
|
|
if err != nil {
|
|
t.Fatalf("failed to create third blob chunk: %v", err)
|
|
}
|
|
|
|
// Test GetByBlobID
|
|
blobChunks, err := repos.BlobChunks.GetByBlobID(ctx, blob.ID.String())
|
|
if err != nil {
|
|
t.Fatalf("failed to get blob chunks: %v", err)
|
|
}
|
|
|
|
if len(blobChunks) != 3 {
|
|
t.Errorf("expected 3 chunks, got %d", len(blobChunks))
|
|
}
|
|
|
|
// Verify order by offset
|
|
expectedOffsets := []int64{0, 1024, 3072}
|
|
for i, bc := range blobChunks {
|
|
if bc.Offset != expectedOffsets[i] {
|
|
t.Errorf("wrong chunk order: expected offset %d, got %d",
|
|
expectedOffsets[i], bc.Offset)
|
|
}
|
|
}
|
|
|
|
// Test duplicate insert (should fail due to primary key constraint)
|
|
err = repos.BlobChunks.Create(ctx, nil, bc1)
|
|
if err == nil {
|
|
t.Fatal("duplicate blob_chunk insert should fail due to primary key constraint")
|
|
}
|
|
|
|
if !strings.Contains(err.Error(), "UNIQUE") &&
|
|
!strings.Contains(err.Error(), "constraint") {
|
|
t.Fatalf("expected constraint error, got: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestBlobChunkRepositoryGetByChunkHash(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
db, cleanup := setupTestDB(t)
|
|
defer cleanup()
|
|
|
|
ctx := context.Background()
|
|
repos := database.NewRepositories(db)
|
|
|
|
blob := mustCreateBlob(t, repos, "blob-gbch-hash")
|
|
mustCreateChunks(t, repos, chunk2Hash)
|
|
|
|
bc2 := &database.BlobChunk{
|
|
BlobID: blob.ID,
|
|
ChunkHash: types.ChunkHash(chunk2Hash),
|
|
Offset: 1024,
|
|
Length: 2048,
|
|
}
|
|
|
|
err := repos.BlobChunks.Create(ctx, nil, bc2)
|
|
if err != nil {
|
|
t.Fatalf("failed to create blob chunk: %v", err)
|
|
}
|
|
|
|
// Test GetByChunkHash
|
|
bc, err := repos.BlobChunks.GetByChunkHash(ctx, chunk2Hash)
|
|
if err != nil {
|
|
t.Fatalf("failed to get blob chunk by chunk hash: %v", err)
|
|
}
|
|
|
|
if bc == nil {
|
|
t.Fatal("expected blob chunk, got nil")
|
|
}
|
|
|
|
if bc.BlobID != blob.ID {
|
|
t.Errorf("wrong blob ID: expected %s, got %s", blob.ID, bc.BlobID)
|
|
}
|
|
|
|
if bc.Offset != 1024 {
|
|
t.Errorf("wrong offset: expected 1024, got %d", bc.Offset)
|
|
}
|
|
|
|
// Test non-existent chunk
|
|
bc, err = repos.BlobChunks.GetByChunkHash(ctx, "nonexistent")
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
|
|
if bc != nil {
|
|
t.Error("expected nil for non-existent chunk")
|
|
}
|
|
}
|
|
|
|
func TestBlobChunkRepositoryMultipleBlobs(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
db, cleanup := setupTestDB(t)
|
|
defer cleanup()
|
|
|
|
ctx := context.Background()
|
|
repos := database.NewRepositories(db)
|
|
|
|
blob1 := mustCreateBlob(t, repos, "blob1-hash")
|
|
blob2 := mustCreateBlob(t, repos, "blob2-hash")
|
|
mustCreateChunks(t, repos, chunk1Hash, chunk2Hash, chunk3Hash)
|
|
|
|
// Create chunks across multiple blobs
|
|
// Some chunks are shared between blobs (deduplication scenario)
|
|
blobChunks := []database.BlobChunk{
|
|
{BlobID: blob1.ID, ChunkHash: chunk1Hash, Offset: 0, Length: 1024},
|
|
{BlobID: blob1.ID, ChunkHash: chunk2Hash, Offset: 1024, Length: 1024},
|
|
// chunk2 is shared between the blobs
|
|
{BlobID: blob2.ID, ChunkHash: chunk2Hash, Offset: 0, Length: 1024},
|
|
{BlobID: blob2.ID, ChunkHash: chunk3Hash, Offset: 1024, Length: 1024},
|
|
}
|
|
|
|
for _, bc := range blobChunks {
|
|
err := repos.BlobChunks.Create(ctx, nil, &bc)
|
|
if err != nil {
|
|
t.Fatalf("failed to create blob chunk: %v", err)
|
|
}
|
|
}
|
|
|
|
// Verify blob1 chunks
|
|
chunks, err := repos.BlobChunks.GetByBlobID(ctx, blob1.ID.String())
|
|
if err != nil {
|
|
t.Fatalf("failed to get blob1 chunks: %v", err)
|
|
}
|
|
|
|
if len(chunks) != 2 {
|
|
t.Errorf("expected 2 chunks for blob1, got %d", len(chunks))
|
|
}
|
|
|
|
// Verify blob2 chunks
|
|
chunks, err = repos.BlobChunks.GetByBlobID(ctx, blob2.ID.String())
|
|
if err != nil {
|
|
t.Fatalf("failed to get blob2 chunks: %v", err)
|
|
}
|
|
|
|
if len(chunks) != 2 {
|
|
t.Errorf("expected 2 chunks for blob2, got %d", len(chunks))
|
|
}
|
|
|
|
// Verify shared chunk
|
|
bc, err := repos.BlobChunks.GetByChunkHash(ctx, chunk2Hash)
|
|
if err != nil {
|
|
t.Fatalf("failed to get shared chunk: %v", err)
|
|
}
|
|
|
|
if bc == nil {
|
|
t.Fatal("expected shared chunk, got nil")
|
|
}
|
|
// GetByChunkHash returns first match, should be blob1
|
|
if bc.BlobID != blob1.ID {
|
|
t.Errorf("expected %s for shared chunk, got %s", blob1.ID, bc.BlobID)
|
|
}
|
|
}
|