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) } }