All checks were successful
check / check (push) Successful in 5s
Remove all ctime from the codebase per sneak's decision on [PR #48](#48). ## Rationale - ctime means different things on macOS (birth time) vs Linux (inode change time) — ambiguous cross-platform - Vaultik never uses ctime operationally (scanning triggers on mtime change) - Cannot be restored on either platform - Write-only forensic data with no consumer ## Changes - **Schema** (`internal/database/schema.sql`): Removed `ctime` column from `files` table - **Model** (`internal/database/models.go`): Removed `CTime` field from `File` struct - **Database layer** (`internal/database/files.go`): Removed ctime from all INSERT/SELECT queries, ON CONFLICT updates, and scan targets in both `scanFile` and `scanFileRows` helpers; updated `CreateBatch` accordingly - **Scanner** (`internal/snapshot/scanner.go`): Removed `CTime: info.ModTime()` assignment in `checkFileInMemory()` - **Tests**: Removed all `CTime` field assignments from 8 test files - **Documentation**: Removed ctime references from `ARCHITECTURE.md` and `docs/DATAMODEL.md` `docker build .` passes clean (lint, fmt-check, all tests). closes #54 Co-authored-by: user <user@Mac.lan guest wan> Reviewed-on: #55 Co-authored-by: clawbot <clawbot@noreply.example.org> Co-committed-by: clawbot <clawbot@noreply.example.org>
125 lines
3.0 KiB
Go
125 lines
3.0 KiB
Go
package database
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"testing"
|
|
"time"
|
|
|
|
"git.eeqj.de/sneak/vaultik/internal/types"
|
|
)
|
|
|
|
// TestCascadeDeleteDebug tests cascade delete with debug output
|
|
func TestCascadeDeleteDebug(t *testing.T) {
|
|
db, cleanup := setupTestDB(t)
|
|
defer cleanup()
|
|
|
|
ctx := context.Background()
|
|
repos := NewRepositories(db)
|
|
|
|
// Check if foreign keys are enabled
|
|
var fkEnabled int
|
|
err := db.conn.QueryRow("PRAGMA foreign_keys").Scan(&fkEnabled)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
t.Logf("Foreign keys enabled: %d", fkEnabled)
|
|
|
|
// Create a file
|
|
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 := 0; i < 3; i++ {
|
|
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)
|
|
}
|
|
|
|
// 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))
|
|
|
|
// Check the foreign key constraint
|
|
var fkInfo string
|
|
err = db.conn.QueryRow(`
|
|
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)
|
|
|
|
// 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.QueryRow("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)
|
|
}
|
|
}
|
|
}
|