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.
166 lines
3.6 KiB
Go
166 lines
3.6 KiB
Go
//nolint:testpackage // inspects the unexported database connection
|
|
package database
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"testing"
|
|
"time"
|
|
|
|
"sneak.berlin/go/vaultik/internal/types"
|
|
)
|
|
|
|
// createCascadeFixtures creates a file with three chunk mappings for the
|
|
// cascade-delete test.
|
|
func createCascadeFixtures(t *testing.T, repos *Repositories) *File {
|
|
t.Helper()
|
|
|
|
ctx := context.Background()
|
|
|
|
file := &File{
|
|
Path: "/cascade-test.txt",
|
|
MTime: time.Now().Truncate(time.Second),
|
|
Size: 1024,
|
|
Mode: 0644,
|
|
UID: 1000,
|
|
GID: 1000,
|
|
}
|
|
|
|
err := repos.Files.Create(ctx, nil, file)
|
|
if err != nil {
|
|
t.Fatalf("failed to create file: %v", err)
|
|
}
|
|
|
|
t.Logf("Created file with ID: %s", file.ID)
|
|
|
|
// Create chunks and file-chunk mappings
|
|
for i := range 3 {
|
|
chunk := &Chunk{
|
|
ChunkHash: types.ChunkHash(fmt.Sprintf("cascade-chunk-%d", i)),
|
|
Size: 1024,
|
|
}
|
|
|
|
err = repos.Chunks.Create(ctx, nil, chunk)
|
|
if err != nil {
|
|
t.Fatalf("failed to create chunk: %v", err)
|
|
}
|
|
|
|
fc := &FileChunk{
|
|
FileID: file.ID,
|
|
Idx: i,
|
|
ChunkHash: chunk.ChunkHash,
|
|
}
|
|
|
|
err = repos.FileChunks.Create(ctx, nil, fc)
|
|
if err != nil {
|
|
t.Fatalf("failed to create file chunk: %v", err)
|
|
}
|
|
|
|
t.Logf("Created file chunk mapping: file_id=%s, idx=%d, chunk=%s",
|
|
fc.FileID, fc.Idx, fc.ChunkHash)
|
|
}
|
|
|
|
return file
|
|
}
|
|
|
|
// logCascadeDebugInfo logs foreign-key state and the file_chunks table
|
|
// definition for cascade-delete debugging.
|
|
func logCascadeDebugInfo(ctx context.Context, t *testing.T, db *DB) {
|
|
t.Helper()
|
|
|
|
// Check if foreign keys are enabled
|
|
var fkEnabled int
|
|
|
|
err := db.conn.QueryRowContext(ctx, "PRAGMA foreign_keys").Scan(&fkEnabled)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
t.Logf("Foreign keys enabled: %d", fkEnabled)
|
|
|
|
// Check the foreign key constraint
|
|
var fkInfo string
|
|
|
|
err = db.conn.QueryRowContext(ctx, `
|
|
SELECT sql FROM sqlite_master
|
|
WHERE type='table' AND name='file_chunks'
|
|
`).Scan(&fkInfo)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
t.Logf("file_chunks table definition:\n%s", fkInfo)
|
|
}
|
|
|
|
// TestCascadeDeleteDebug tests cascade delete with debug output
|
|
func TestCascadeDeleteDebug(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
db, cleanup := setupTestDB(t)
|
|
defer cleanup()
|
|
|
|
ctx := context.Background()
|
|
repos := NewRepositories(db)
|
|
|
|
logCascadeDebugInfo(ctx, t, db)
|
|
|
|
file := createCascadeFixtures(t, repos)
|
|
|
|
// Verify file chunks exist
|
|
fileChunks, err := repos.FileChunks.GetByFileID(ctx, file.ID)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
t.Logf("File chunks before delete: %d", len(fileChunks))
|
|
|
|
// Delete the file
|
|
t.Log("Deleting file...")
|
|
|
|
err = repos.Files.DeleteByID(ctx, nil, file.ID)
|
|
if err != nil {
|
|
t.Fatalf("failed to delete file: %v", err)
|
|
}
|
|
|
|
// Verify file is gone
|
|
deletedFile, err := repos.Files.GetByID(ctx, file.ID)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if deletedFile != nil {
|
|
t.Error("file should have been deleted")
|
|
} else {
|
|
t.Log("File was successfully deleted")
|
|
}
|
|
|
|
// Check file chunks after delete
|
|
fileChunks, err = repos.FileChunks.GetByFileID(ctx, file.ID)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
t.Logf("File chunks after delete: %d", len(fileChunks))
|
|
|
|
// Manually check the database
|
|
var count int
|
|
|
|
err = db.conn.QueryRowContext(ctx,
|
|
"SELECT COUNT(*) FROM file_chunks WHERE file_id = ?", file.ID,
|
|
).Scan(&count)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
t.Logf("Manual count of file_chunks for deleted file: %d", count)
|
|
|
|
if len(fileChunks) != 0 {
|
|
t.Errorf("expected 0 file chunks after cascade delete, got %d", len(fileChunks))
|
|
// List the remaining chunks
|
|
for _, fc := range fileChunks {
|
|
t.Logf("Remaining chunk: file_id=%s, idx=%d, chunk=%s",
|
|
fc.FileID, fc.Idx, fc.ChunkHash)
|
|
}
|
|
}
|
|
}
|