Skip to main content
Glama
orneryd

M.I.M.I.R - Multi-agent Intelligent Memory & Insight Repository

by orneryd
retention_test.go21.5 kB
// Package retention tests for data lifecycle management. package retention import ( "context" "encoding/json" "os" "path/filepath" "testing" "time" ) func TestPolicyValidate(t *testing.T) { t.Run("valid policy", func(t *testing.T) { p := &Policy{ ID: "test-1", Category: CategoryUser, RetentionPeriod: RetentionPeriod{ Duration: 24 * time.Hour, }, Active: true, } if err := p.Validate(); err != nil { t.Errorf("valid policy should not error: %v", err) } }) t.Run("indefinite policy", func(t *testing.T) { p := &Policy{ ID: "test-2", Category: CategorySystem, RetentionPeriod: RetentionPeriod{ Indefinite: true, }, Active: true, } if err := p.Validate(); err != nil { t.Errorf("indefinite policy should be valid: %v", err) } }) t.Run("missing ID", func(t *testing.T) { p := &Policy{ Category: CategoryUser, RetentionPeriod: RetentionPeriod{ Duration: time.Hour, }, } if err := p.Validate(); err == nil { t.Error("expected error for missing ID") } }) t.Run("missing category", func(t *testing.T) { p := &Policy{ ID: "test", RetentionPeriod: RetentionPeriod{ Duration: time.Hour, }, } if err := p.Validate(); err == nil { t.Error("expected error for missing category") } }) t.Run("missing retention period", func(t *testing.T) { p := &Policy{ ID: "test", Category: CategoryUser, } if err := p.Validate(); err == nil { t.Error("expected error for missing retention period") } }) t.Run("archive without path", func(t *testing.T) { p := &Policy{ ID: "test", Category: CategoryUser, RetentionPeriod: RetentionPeriod{ Duration: time.Hour, }, ArchiveBeforeDelete: true, } if err := p.Validate(); err == nil { t.Error("expected error for archive without path") } }) } func TestPolicyIsExpired(t *testing.T) { t.Run("not expired", func(t *testing.T) { p := &Policy{ RetentionPeriod: RetentionPeriod{ Duration: 24 * time.Hour, }, } if p.IsExpired(time.Now()) { t.Error("recently created should not be expired") } }) t.Run("expired", func(t *testing.T) { p := &Policy{ RetentionPeriod: RetentionPeriod{ Duration: time.Hour, }, } if !p.IsExpired(time.Now().Add(-2 * time.Hour)) { t.Error("old data should be expired") } }) t.Run("indefinite never expires", func(t *testing.T) { p := &Policy{ RetentionPeriod: RetentionPeriod{ Indefinite: true, }, } if p.IsExpired(time.Now().Add(-100 * 365 * 24 * time.Hour)) { t.Error("indefinite retention should never expire") } }) } func TestLegalHold(t *testing.T) { t.Run("active hold", func(t *testing.T) { h := &LegalHold{ ID: "hold-1", Active: true, } if !h.IsActive() { t.Error("should be active") } }) t.Run("inactive hold", func(t *testing.T) { h := &LegalHold{ ID: "hold-1", Active: false, } if h.IsActive() { t.Error("should be inactive") } }) t.Run("expired hold", func(t *testing.T) { h := &LegalHold{ ID: "hold-1", Active: true, ExpiresAt: time.Now().Add(-time.Hour), } if h.IsActive() { t.Error("expired hold should not be active") } }) t.Run("future expiry hold", func(t *testing.T) { h := &LegalHold{ ID: "hold-1", Active: true, ExpiresAt: time.Now().Add(time.Hour), } if !h.IsActive() { t.Error("future expiry should be active") } }) } func TestLegalHoldCoversData(t *testing.T) { t.Run("covers all when empty", func(t *testing.T) { h := &LegalHold{ ID: "hold-1", Active: true, SubjectIDs: []string{}, // Empty = all subjects Categories: []DataCategory{}, } if !h.CoversData("any-user", CategoryUser) { t.Error("empty filters should cover all data") } }) t.Run("covers specific subject", func(t *testing.T) { h := &LegalHold{ ID: "hold-1", Active: true, SubjectIDs: []string{"user-1", "user-2"}, } if !h.CoversData("user-1", CategoryUser) { t.Error("should cover user-1") } if h.CoversData("user-3", CategoryUser) { t.Error("should not cover user-3") } }) t.Run("covers specific category", func(t *testing.T) { h := &LegalHold{ ID: "hold-1", Active: true, Categories: []DataCategory{CategoryPHI, CategoryPII}, } if !h.CoversData("any-user", CategoryPHI) { t.Error("should cover PHI") } if h.CoversData("any-user", CategoryUser) { t.Error("should not cover USER category") } }) t.Run("inactive hold covers nothing", func(t *testing.T) { h := &LegalHold{ ID: "hold-1", Active: false, } if h.CoversData("user-1", CategoryUser) { t.Error("inactive hold should cover nothing") } }) } func TestManager(t *testing.T) { m := NewManager() t.Run("add policy", func(t *testing.T) { p := &Policy{ ID: "policy-1", Name: "Test Policy", Category: CategoryUser, RetentionPeriod: RetentionPeriod{ Duration: 24 * time.Hour, }, Active: true, } if err := m.AddPolicy(p); err != nil { t.Fatalf("AddPolicy() error = %v", err) } }) t.Run("add duplicate policy", func(t *testing.T) { p := &Policy{ ID: "policy-1", Category: CategoryUser, RetentionPeriod: RetentionPeriod{ Duration: 48 * time.Hour, }, } if err := m.AddPolicy(p); err != ErrAlreadyExists { t.Errorf("expected ErrAlreadyExists, got %v", err) } }) t.Run("get policy", func(t *testing.T) { p, err := m.GetPolicy("policy-1") if err != nil { t.Fatalf("GetPolicy() error = %v", err) } if p.Name != "Test Policy" { t.Errorf("expected 'Test Policy', got %s", p.Name) } }) t.Run("get nonexistent policy", func(t *testing.T) { _, err := m.GetPolicy("nonexistent") if err != ErrPolicyNotFound { t.Errorf("expected ErrPolicyNotFound, got %v", err) } }) t.Run("update policy", func(t *testing.T) { p, _ := m.GetPolicy("policy-1") p.Name = "Updated Policy" if err := m.UpdatePolicy(p); err != nil { t.Fatalf("UpdatePolicy() error = %v", err) } updated, _ := m.GetPolicy("policy-1") if updated.Name != "Updated Policy" { t.Error("policy not updated") } }) t.Run("update nonexistent policy", func(t *testing.T) { p := &Policy{ ID: "nonexistent", Category: CategoryUser, RetentionPeriod: RetentionPeriod{ Duration: time.Hour, }, } if err := m.UpdatePolicy(p); err != ErrPolicyNotFound { t.Errorf("expected ErrPolicyNotFound, got %v", err) } }) t.Run("list policies", func(t *testing.T) { policies := m.ListPolicies() if len(policies) != 1 { t.Errorf("expected 1 policy, got %d", len(policies)) } }) t.Run("delete policy", func(t *testing.T) { if err := m.DeletePolicy("policy-1"); err != nil { t.Fatalf("DeletePolicy() error = %v", err) } if len(m.ListPolicies()) != 0 { t.Error("policy not deleted") } }) t.Run("delete nonexistent policy", func(t *testing.T) { if err := m.DeletePolicy("nonexistent"); err != ErrPolicyNotFound { t.Errorf("expected ErrPolicyNotFound, got %v", err) } }) } func TestManagerDefaultPolicy(t *testing.T) { m := NewManager() defaultPolicy := &Policy{ ID: "default", Name: "Default", Category: CategoryUser, RetentionPeriod: RetentionPeriod{ Duration: 30 * 24 * time.Hour, }, Active: true, } if err := m.SetDefaultPolicy(defaultPolicy); err != nil { t.Fatalf("SetDefaultPolicy() error = %v", err) } // Query for category without specific policy p, err := m.GetPolicyForCategory(CategoryAnalytics) if err != nil { t.Fatalf("GetPolicyForCategory() error = %v", err) } if p.ID != "default" { t.Error("should return default policy") } } func TestManagerLegalHolds(t *testing.T) { m := NewManager() t.Run("place hold", func(t *testing.T) { h := &LegalHold{ ID: "hold-1", Description: "Test Hold", PlacedBy: "admin", SubjectIDs: []string{"user-1"}, } if err := m.PlaceLegalHold(h); err != nil { t.Fatalf("PlaceLegalHold() error = %v", err) } retrieved, _ := m.GetLegalHold("hold-1") if !retrieved.Active { t.Error("hold should be active") } }) t.Run("place hold without ID", func(t *testing.T) { h := &LegalHold{} if err := m.PlaceLegalHold(h); err == nil { t.Error("expected error for missing ID") } }) t.Run("is under legal hold", func(t *testing.T) { if !m.IsUnderLegalHold("user-1", CategoryUser) { t.Error("user-1 should be under hold") } if m.IsUnderLegalHold("user-2", CategoryUser) { t.Error("user-2 should not be under hold") } }) t.Run("list holds", func(t *testing.T) { holds := m.ListLegalHolds() if len(holds) != 1 { t.Errorf("expected 1 hold, got %d", len(holds)) } }) t.Run("release hold", func(t *testing.T) { if err := m.ReleaseLegalHold("hold-1"); err != nil { t.Fatalf("ReleaseLegalHold() error = %v", err) } if m.IsUnderLegalHold("user-1", CategoryUser) { t.Error("user-1 should no longer be under hold") } }) t.Run("release nonexistent hold", func(t *testing.T) { if err := m.ReleaseLegalHold("nonexistent"); err != ErrPolicyNotFound { t.Errorf("expected ErrPolicyNotFound, got %v", err) } }) t.Run("get nonexistent hold", func(t *testing.T) { _, err := m.GetLegalHold("nonexistent") if err != ErrPolicyNotFound { t.Errorf("expected ErrPolicyNotFound, got %v", err) } }) } func TestManagerShouldDelete(t *testing.T) { m := NewManager() // Add policy p := &Policy{ ID: "user-policy", Category: CategoryUser, RetentionPeriod: RetentionPeriod{ Duration: time.Hour, }, Active: true, } m.AddPolicy(p) t.Run("should delete expired", func(t *testing.T) { record := &DataRecord{ ID: "rec-1", Category: CategoryUser, CreatedAt: time.Now().Add(-2 * time.Hour), } shouldDelete, reason := m.ShouldDelete(record) if !shouldDelete { t.Errorf("should delete expired record, reason: %s", reason) } }) t.Run("should not delete within period", func(t *testing.T) { record := &DataRecord{ ID: "rec-2", Category: CategoryUser, CreatedAt: time.Now(), } shouldDelete, reason := m.ShouldDelete(record) if shouldDelete { t.Errorf("should not delete recent record, reason: %s", reason) } }) t.Run("should not delete under legal hold", func(t *testing.T) { m.PlaceLegalHold(&LegalHold{ ID: "hold-2", Active: true, SubjectIDs: []string{"user-held"}, }) record := &DataRecord{ ID: "rec-3", SubjectID: "user-held", Category: CategoryUser, CreatedAt: time.Now().Add(-2 * time.Hour), } shouldDelete, reason := m.ShouldDelete(record) if shouldDelete { t.Errorf("should not delete record under legal hold, reason: %s", reason) } if reason != "under legal hold" { t.Errorf("expected 'under legal hold' reason, got %s", reason) } }) t.Run("no policy found", func(t *testing.T) { record := &DataRecord{ ID: "rec-4", Category: CategoryFinancial, // No policy for this } shouldDelete, reason := m.ShouldDelete(record) if shouldDelete { t.Error("should not delete without policy") } if reason != "no policy found" { t.Errorf("expected 'no policy found', got %s", reason) } }) } func TestManagerProcessRecord(t *testing.T) { m := NewManager() var deletedRecords []string var archivedRecords []string m.SetDeleteCallback(func(record *DataRecord) error { deletedRecords = append(deletedRecords, record.ID) return nil }) m.SetArchiveCallback(func(record *DataRecord, path string) error { archivedRecords = append(archivedRecords, record.ID) return nil }) // Add policy with archiving p := &Policy{ ID: "user-archive", Category: CategoryUser, RetentionPeriod: RetentionPeriod{ Duration: time.Hour, }, ArchiveBeforeDelete: true, ArchivePath: "/archive", Active: true, } m.AddPolicy(p) t.Run("process expired record", func(t *testing.T) { record := &DataRecord{ ID: "rec-archive", Category: CategoryUser, CreatedAt: time.Now().Add(-2 * time.Hour), } if err := m.ProcessRecord(context.Background(), record); err != nil { t.Fatalf("ProcessRecord() error = %v", err) } if len(archivedRecords) != 1 || archivedRecords[0] != "rec-archive" { t.Error("record should have been archived") } if len(deletedRecords) != 1 || deletedRecords[0] != "rec-archive" { t.Error("record should have been deleted") } }) t.Run("process non-expired record", func(t *testing.T) { deletedRecords = nil archivedRecords = nil record := &DataRecord{ ID: "rec-keep", Category: CategoryUser, CreatedAt: time.Now(), } if err := m.ProcessRecord(context.Background(), record); err != nil { t.Fatalf("ProcessRecord() error = %v", err) } if len(deletedRecords) != 0 { t.Error("recent record should not be deleted") } }) } func TestErasureRequest(t *testing.T) { m := NewManager() t.Run("create erasure request", func(t *testing.T) { req, err := m.CreateErasureRequest("user-123", "user@example.com") if err != nil { t.Fatalf("CreateErasureRequest() error = %v", err) } if req.SubjectID != "user-123" { t.Error("wrong subject ID") } if req.Status != ErasureStatusPending { t.Error("should be pending") } if req.Deadline.IsZero() { t.Error("should have deadline") } }) t.Run("get erasure request", func(t *testing.T) { reqs := m.ListErasureRequests() if len(reqs) != 1 { t.Fatalf("expected 1 request, got %d", len(reqs)) } req, err := m.GetErasureRequest(reqs[0].ID) if err != nil { t.Fatalf("GetErasureRequest() error = %v", err) } if req.SubjectEmail != "user@example.com" { t.Error("wrong email") } }) t.Run("get nonexistent request", func(t *testing.T) { _, err := m.GetErasureRequest("nonexistent") if err != ErrPolicyNotFound { t.Errorf("expected ErrPolicyNotFound, got %v", err) } }) } func TestProcessErasure(t *testing.T) { m := NewManager() var deletedRecords []string m.SetDeleteCallback(func(record *DataRecord) error { deletedRecords = append(deletedRecords, record.ID) return nil }) t.Run("process erasure request", func(t *testing.T) { req, _ := m.CreateErasureRequest("user-erase", "user@example.com") records := []*DataRecord{ {ID: "rec-1", SubjectID: "user-erase", Category: CategoryUser}, {ID: "rec-2", SubjectID: "user-erase", Category: CategoryUser}, } err := m.ProcessErasure(context.Background(), req.ID, records) if err != nil { t.Fatalf("ProcessErasure() error = %v", err) } updated, _ := m.GetErasureRequest(req.ID) if updated.Status != ErasureStatusCompleted { t.Errorf("expected COMPLETED, got %s", updated.Status) } if updated.ItemsErased != 2 { t.Errorf("expected 2 erased, got %d", updated.ItemsErased) } if len(deletedRecords) != 2 { t.Error("records should be deleted") } }) t.Run("process erasure with legal hold", func(t *testing.T) { m.PlaceLegalHold(&LegalHold{ ID: "hold-erasure", Active: true, SubjectIDs: []string{"user-held-erasure"}, }) req, _ := m.CreateErasureRequest("user-held-erasure", "held@example.com") deletedRecords = nil records := []*DataRecord{ {ID: "rec-held-1", SubjectID: "user-held-erasure", Category: CategoryUser}, {ID: "rec-held-2", SubjectID: "user-held-erasure", Category: CategoryUser}, } err := m.ProcessErasure(context.Background(), req.ID, records) if err != nil { t.Fatalf("ProcessErasure() error = %v", err) } updated, _ := m.GetErasureRequest(req.ID) if updated.Status != ErasureStatusPartial { t.Errorf("expected PARTIAL, got %s", updated.Status) } if updated.ItemsRetained != 2 { t.Errorf("expected 2 retained, got %d", updated.ItemsRetained) } if len(deletedRecords) != 0 { t.Error("records should NOT be deleted (legal hold)") } }) t.Run("process erasure cancelled", func(t *testing.T) { req, _ := m.CreateErasureRequest("user-cancel", "cancel@example.com") ctx, cancel := context.WithCancel(context.Background()) cancel() // Cancel immediately records := []*DataRecord{ {ID: "rec-cancel", SubjectID: "user-cancel", Category: CategoryUser}, } err := m.ProcessErasure(ctx, req.ID, records) if err == nil { t.Error("expected context cancelled error") } }) t.Run("process nonexistent request", func(t *testing.T) { err := m.ProcessErasure(context.Background(), "nonexistent", nil) if err != ErrPolicyNotFound { t.Errorf("expected ErrPolicyNotFound, got %v", err) } }) } func TestSaveLoadPolicies(t *testing.T) { tmpDir := t.TempDir() policyFile := filepath.Join(tmpDir, "policies.json") m := NewManager() m.AddPolicy(&Policy{ ID: "save-test", Name: "Save Test", Category: CategoryUser, RetentionPeriod: RetentionPeriod{ Duration: 24 * time.Hour, }, Active: true, }) t.Run("save policies", func(t *testing.T) { if err := m.SavePolicies(policyFile); err != nil { t.Fatalf("SavePolicies() error = %v", err) } // Verify file exists if _, err := os.Stat(policyFile); os.IsNotExist(err) { t.Error("policy file not created") } // Verify content data, _ := os.ReadFile(policyFile) var policies []*Policy json.Unmarshal(data, &policies) if len(policies) != 1 { t.Errorf("expected 1 policy in file, got %d", len(policies)) } }) t.Run("load policies", func(t *testing.T) { m2 := NewManager() if err := m2.LoadPolicies(policyFile); err != nil { t.Fatalf("LoadPolicies() error = %v", err) } policies := m2.ListPolicies() if len(policies) != 1 { t.Errorf("expected 1 loaded policy, got %d", len(policies)) } if policies[0].ID != "save-test" { t.Error("wrong policy loaded") } }) t.Run("load nonexistent file", func(t *testing.T) { m2 := NewManager() err := m2.LoadPolicies("/nonexistent/path/policies.json") if err == nil { t.Error("expected error for nonexistent file") } }) } func TestDefaultPolicies(t *testing.T) { policies := DefaultPolicies() if len(policies) == 0 { t.Error("should have default policies") } // Check specific expected policies categories := make(map[DataCategory]bool) for _, p := range policies { categories[p.Category] = true if err := p.Validate(); err != nil { t.Errorf("default policy %s invalid: %v", p.ID, err) } } expected := []DataCategory{CategoryAudit, CategoryPHI, CategoryPII, CategoryFinancial, CategoryUser} for _, cat := range expected { if !categories[cat] { t.Errorf("missing default policy for %s", cat) } } } func TestDataCategories(t *testing.T) { categories := []DataCategory{ CategorySystem, CategoryAudit, CategoryUser, CategoryAnalytics, CategoryBackup, CategoryArchive, CategoryPHI, CategoryPII, CategoryFinancial, CategoryLegal, } for _, cat := range categories { if cat == "" { t.Error("category should not be empty") } } } func TestErasureStatus(t *testing.T) { statuses := []ErasureStatus{ ErasureStatusPending, ErasureStatusInProgress, ErasureStatusCompleted, ErasureStatusFailed, ErasureStatusPartial, } for _, s := range statuses { if s == "" { t.Error("status should not be empty") } } } func TestAddInvalidPolicy(t *testing.T) { m := NewManager() // Missing required fields p := &Policy{} if err := m.AddPolicy(p); err == nil { t.Error("expected error for invalid policy") } } func TestSetInvalidDefaultPolicy(t *testing.T) { m := NewManager() p := &Policy{} // Invalid if err := m.SetDefaultPolicy(p); err == nil { t.Error("expected error for invalid default policy") } } func TestGetPolicyForCategoryNoPolicy(t *testing.T) { m := NewManager() _, err := m.GetPolicyForCategory(CategoryUser) if err != ErrPolicyNotFound { t.Errorf("expected ErrPolicyNotFound, got %v", err) } } func TestDuplicateErasureRequest(t *testing.T) { m := NewManager() // Create first request req, _ := m.CreateErasureRequest("user-dup", "user@example.com") // Start processing (set to in progress) m.mu.Lock() req.Status = ErasureStatusInProgress m.mu.Unlock() // Try to create duplicate _, err := m.CreateErasureRequest("user-dup", "user@example.com") if err != ErrErasureInProgress { t.Errorf("expected ErrErasureInProgress, got %v", err) } } func TestPolicyInactive(t *testing.T) { m := NewManager() // Add active policy activePolicy := &Policy{ ID: "active-policy", Category: CategoryUser, RetentionPeriod: RetentionPeriod{ Duration: time.Hour, }, Active: true, } m.AddPolicy(activePolicy) // Add inactive policy for same category - should NOT be used inactivePolicy := &Policy{ ID: "inactive-policy", Category: CategoryUser, RetentionPeriod: RetentionPeriod{ Duration: 24 * time.Hour, // Longer retention }, Active: false, } m.AddPolicy(inactivePolicy) record := &DataRecord{ ID: "rec-inactive", Category: CategoryUser, CreatedAt: time.Now().Add(-2 * time.Hour), // Older than active policy } // Should use active policy, which has 1 hour retention shouldDelete, reason := m.ShouldDelete(record) if !shouldDelete { t.Errorf("should delete using active policy, reason: %s", reason) } // Now test with only inactive policy (remove active one) m.DeletePolicy("active-policy") shouldDelete, reason = m.ShouldDelete(record) if shouldDelete { t.Error("should not delete with no active policy") } if reason != "no policy found" { t.Errorf("expected 'no policy found', got %s", reason) } }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/orneryd/Mimir'

If you have feedback or need assistance with the MCP directory API, please join our Discord server