Move unit tests from tests/unit/ to internal/ following Go conventions: - tests/unit/api/* -> internal/api/* (WebSocket handlers, helpers, duplicate detection) - tests/unit/audit/* -> internal/audit/* (alert, sealed, verifier tests) - tests/unit/auth/* -> internal/auth/* (API key, keychain, user manager) - tests/unit/crypto/kms/* -> internal/auth/kms/* (cache, protocol tests) Update import paths in test files to reflect new locations. Benefits: - Tests live alongside the code they test - Easier navigation and maintenance - Clearer package boundaries - Follows standard Go project layout
171 lines
4.7 KiB
Go
171 lines
4.7 KiB
Go
package audit_test
|
|
|
|
import (
|
|
"os"
|
|
"path/filepath"
|
|
"testing"
|
|
|
|
"github.com/jfraeys/fetch_ml/internal/audit"
|
|
)
|
|
|
|
func TestSealedStateManager_CheckpointAndRecover(t *testing.T) {
|
|
tmpDir := t.TempDir()
|
|
chainFile := filepath.Join(tmpDir, "state.chain")
|
|
currentFile := filepath.Join(tmpDir, "state.current")
|
|
|
|
ssm := audit.NewSealedStateManager(chainFile, currentFile)
|
|
|
|
// Test initial state - no files yet
|
|
seq, hash, err := ssm.RecoverState()
|
|
if err != nil {
|
|
t.Fatalf("RecoverState on empty files failed: %v", err)
|
|
}
|
|
if seq != 0 || hash != "" {
|
|
t.Errorf("Expected empty state, got seq=%d hash=%s", seq, hash)
|
|
}
|
|
|
|
// Test checkpoint
|
|
if err := ssm.Checkpoint(1, "abc123"); err != nil {
|
|
t.Fatalf("Checkpoint failed: %v", err)
|
|
}
|
|
|
|
// Test recovery
|
|
seq, hash, err = ssm.RecoverState()
|
|
if err != nil {
|
|
t.Fatalf("RecoverState failed: %v", err)
|
|
}
|
|
if seq != 1 || hash != "abc123" {
|
|
t.Errorf("Expected seq=1 hash=abc123, got seq=%d hash=%s", seq, hash)
|
|
}
|
|
|
|
// Test multiple checkpoints
|
|
if err := ssm.Checkpoint(2, "def456"); err != nil {
|
|
t.Fatalf("Second checkpoint failed: %v", err)
|
|
}
|
|
if err := ssm.Checkpoint(3, "ghi789"); err != nil {
|
|
t.Fatalf("Third checkpoint failed: %v", err)
|
|
}
|
|
|
|
// Recovery should return latest
|
|
seq, hash, err = ssm.RecoverState()
|
|
if err != nil {
|
|
t.Fatalf("RecoverState after multiple checkpoints failed: %v", err)
|
|
}
|
|
if seq != 3 || hash != "ghi789" {
|
|
t.Errorf("Expected seq=3 hash=ghi789, got seq=%d hash=%s", seq, hash)
|
|
}
|
|
}
|
|
|
|
func TestSealedStateManager_ChainFileIntegrity(t *testing.T) {
|
|
tmpDir := t.TempDir()
|
|
chainFile := filepath.Join(tmpDir, "state.chain")
|
|
currentFile := filepath.Join(tmpDir, "state.current")
|
|
|
|
ssm := audit.NewSealedStateManager(chainFile, currentFile)
|
|
|
|
// Create several checkpoints
|
|
for i := uint64(1); i <= 5; i++ {
|
|
if err := ssm.Checkpoint(i, "hash"+string(rune('a'+i-1))); err != nil {
|
|
t.Fatalf("Checkpoint %d failed: %v", i, err)
|
|
}
|
|
}
|
|
|
|
// Verify chain integrity
|
|
validCount, err := ssm.VerifyChainIntegrity()
|
|
if err != nil {
|
|
t.Fatalf("VerifyChainIntegrity failed: %v", err)
|
|
}
|
|
if validCount != 5 {
|
|
t.Errorf("Expected 5 valid entries, got %d", validCount)
|
|
}
|
|
}
|
|
|
|
func TestSealedStateManager_RecoverFromChain(t *testing.T) {
|
|
tmpDir := t.TempDir()
|
|
chainFile := filepath.Join(tmpDir, "state.chain")
|
|
currentFile := filepath.Join(tmpDir, "state.current")
|
|
|
|
ssm := audit.NewSealedStateManager(chainFile, currentFile)
|
|
|
|
// Create checkpoints
|
|
if err := ssm.Checkpoint(1, "first"); err != nil {
|
|
t.Fatalf("Checkpoint 1 failed: %v", err)
|
|
}
|
|
if err := ssm.Checkpoint(2, "second"); err != nil {
|
|
t.Fatalf("Checkpoint 2 failed: %v", err)
|
|
}
|
|
|
|
// Delete current file to force recovery from chain
|
|
if err := os.Remove(currentFile); err != nil {
|
|
t.Fatalf("Failed to remove current file: %v", err)
|
|
}
|
|
|
|
// Recovery should still work from chain file
|
|
seq, hash, err := ssm.RecoverState()
|
|
if err != nil {
|
|
t.Fatalf("RecoverState from chain failed: %v", err)
|
|
}
|
|
if seq != 2 || hash != "second" {
|
|
t.Errorf("Expected seq=2 hash=second, got seq=%d hash=%s", seq, hash)
|
|
}
|
|
}
|
|
|
|
func TestSealedStateManager_CorruptedCurrentFile(t *testing.T) {
|
|
tmpDir := t.TempDir()
|
|
chainFile := filepath.Join(tmpDir, "state.chain")
|
|
currentFile := filepath.Join(tmpDir, "state.current")
|
|
|
|
ssm := audit.NewSealedStateManager(chainFile, currentFile)
|
|
|
|
// Create valid checkpoints
|
|
if err := ssm.Checkpoint(1, "valid"); err != nil {
|
|
t.Fatalf("Checkpoint failed: %v", err)
|
|
}
|
|
|
|
// Corrupt the current file
|
|
if err := os.WriteFile(currentFile, []byte("not valid json"), 0o600); err != nil {
|
|
t.Fatalf("Failed to corrupt current file: %v", err)
|
|
}
|
|
|
|
// Recovery should fall back to chain file
|
|
seq, hash, err := ssm.RecoverState()
|
|
if err != nil {
|
|
t.Fatalf("RecoverState with corrupted current file failed: %v", err)
|
|
}
|
|
if seq != 1 || hash != "valid" {
|
|
t.Errorf("Expected seq=1 hash=valid, got seq=%d hash=%s", seq, hash)
|
|
}
|
|
}
|
|
|
|
func TestSealedStateManager_FilePermissions(t *testing.T) {
|
|
tmpDir := t.TempDir()
|
|
chainFile := filepath.Join(tmpDir, "state.chain")
|
|
currentFile := filepath.Join(tmpDir, "state.current")
|
|
|
|
ssm := audit.NewSealedStateManager(chainFile, currentFile)
|
|
|
|
if err := ssm.Checkpoint(1, "test"); err != nil {
|
|
t.Fatalf("Checkpoint failed: %v", err)
|
|
}
|
|
|
|
// Check chain file permissions
|
|
info, err := os.Stat(chainFile)
|
|
if err != nil {
|
|
t.Fatalf("Failed to stat chain file: %v", err)
|
|
}
|
|
mode := info.Mode().Perm()
|
|
// Should be 0o600 (owner read/write only)
|
|
if mode != 0o600 {
|
|
t.Errorf("Chain file mode %o, expected %o", mode, 0o600)
|
|
}
|
|
|
|
// Check current file permissions
|
|
info, err = os.Stat(currentFile)
|
|
if err != nil {
|
|
t.Fatalf("Failed to stat current file: %v", err)
|
|
}
|
|
mode = info.Mode().Perm()
|
|
if mode != 0o600 {
|
|
t.Errorf("Current file mode %o, expected %o", mode, 0o600)
|
|
}
|
|
}
|