package memory import ( "context" "sync" "testing" "github.com/orchard9/rdev/internal/domain" ) func TestProjectRepository_RegisterAndGet(t *testing.T) { repo := NewProjectRepository() ctx := context.Background() project := &domain.Project{ ID: "test-project", Name: "Test Project", Description: "A test project", PodName: "test-pod-0", Workspace: "/workspace", Status: domain.ProjectStatusRunning, } // Register if err := repo.Register(ctx, project); err != nil { t.Fatalf("Register() error = %v", err) } // Get retrieved, err := repo.Get(ctx, "test-project") if err != nil { t.Fatalf("Get() error = %v", err) } if retrieved.ID != project.ID { t.Errorf("ID = %q, want %q", retrieved.ID, project.ID) } if retrieved.Name != project.Name { t.Errorf("Name = %q, want %q", retrieved.Name, project.Name) } if retrieved.Status != project.Status { t.Errorf("Status = %q, want %q", retrieved.Status, project.Status) } } func TestProjectRepository_GetNotFound(t *testing.T) { repo := NewProjectRepository() ctx := context.Background() _, err := repo.Get(ctx, "nonexistent") if err != domain.ErrProjectNotFound { t.Errorf("Get() error = %v, want %v", err, domain.ErrProjectNotFound) } } func TestProjectRepository_List(t *testing.T) { repo := NewProjectRepository() ctx := context.Background() // Empty list initially projects, err := repo.List(ctx) if err != nil { t.Fatalf("List() error = %v", err) } if len(projects) != 0 { t.Errorf("Initial List() length = %d, want 0", len(projects)) } // Register some projects for i := 0; i < 3; i++ { p := &domain.Project{ ID: domain.ProjectID("project-" + string(rune('a'+i))), Name: "Project " + string(rune('A'+i)), } _ = repo.Register(ctx, p) } // List should return all projects, err = repo.List(ctx) if err != nil { t.Fatalf("List() error = %v", err) } if len(projects) != 3 { t.Errorf("List() length = %d, want 3", len(projects)) } } func TestProjectRepository_Exists(t *testing.T) { repo := NewProjectRepository() ctx := context.Background() project := &domain.Project{ ID: "existing-project", Name: "Existing", } _ = repo.Register(ctx, project) tests := []struct { id domain.ProjectID want bool }{ {"existing-project", true}, {"nonexistent", false}, } for _, tt := range tests { exists, err := repo.Exists(ctx, tt.id) if err != nil { t.Errorf("Exists(%q) error = %v", tt.id, err) } if exists != tt.want { t.Errorf("Exists(%q) = %v, want %v", tt.id, exists, tt.want) } } } func TestProjectRepository_Unregister(t *testing.T) { repo := NewProjectRepository() ctx := context.Background() project := &domain.Project{ ID: "to-remove", Name: "To Remove", } repo.Register(ctx, project) // Verify it exists exists, _ := repo.Exists(ctx, "to-remove") if !exists { t.Fatal("Project should exist after register") } // Unregister if err := repo.Unregister(ctx, "to-remove"); err != nil { t.Fatalf("Unregister() error = %v", err) } // Verify it's gone exists, _ = repo.Exists(ctx, "to-remove") if exists { t.Error("Project should not exist after unregister") } } func TestProjectRepository_UnregisterNonexistent(t *testing.T) { repo := NewProjectRepository() ctx := context.Background() // Unregistering non-existent project should not error if err := repo.Unregister(ctx, "nonexistent"); err != nil { t.Errorf("Unregister(nonexistent) error = %v, want nil", err) } } func TestProjectRepository_SetStatus(t *testing.T) { repo := NewProjectRepository() ctx := context.Background() project := &domain.Project{ ID: "status-test", Name: "Status Test", Status: domain.ProjectStatusPending, } repo.Register(ctx, project) // Change status repo.SetStatus("status-test", domain.ProjectStatusRunning) // Verify retrieved, _ := repo.Get(ctx, "status-test") if retrieved.Status != domain.ProjectStatusRunning { t.Errorf("Status = %q, want %q", retrieved.Status, domain.ProjectStatusRunning) } // Change to error repo.SetStatus("status-test", domain.ProjectStatusError) retrieved, _ = repo.Get(ctx, "status-test") if retrieved.Status != domain.ProjectStatusError { t.Errorf("Status = %q, want %q", retrieved.Status, domain.ProjectStatusError) } } func TestProjectRepository_SetStatusNonexistent(t *testing.T) { repo := NewProjectRepository() // Should not panic on nonexistent project repo.SetStatus("nonexistent", domain.ProjectStatusRunning) // No error expected, just a no-op } func TestProjectRepository_RefreshStatus(t *testing.T) { repo := NewProjectRepository() ctx := context.Background() // RefreshStatus is a no-op for memory implementation if err := repo.RefreshStatus(ctx); err != nil { t.Errorf("RefreshStatus() error = %v, want nil", err) } } func TestProjectRepository_RegisterOverwrite(t *testing.T) { repo := NewProjectRepository() ctx := context.Background() // Register initial p1 := &domain.Project{ ID: "overwrite-test", Name: "Original", Status: domain.ProjectStatusPending, } repo.Register(ctx, p1) // Register with same ID, different data p2 := &domain.Project{ ID: "overwrite-test", Name: "Updated", Status: domain.ProjectStatusRunning, } repo.Register(ctx, p2) // Should have updated data retrieved, _ := repo.Get(ctx, "overwrite-test") if retrieved.Name != "Updated" { t.Errorf("Name = %q, want %q", retrieved.Name, "Updated") } if retrieved.Status != domain.ProjectStatusRunning { t.Errorf("Status = %q, want %q", retrieved.Status, domain.ProjectStatusRunning) } } func TestProjectRepository_ConcurrentAccess(t *testing.T) { repo := NewProjectRepository() ctx := context.Background() var wg sync.WaitGroup // Concurrent register for i := 0; i < 50; i++ { wg.Add(1) go func(id int) { defer wg.Done() p := &domain.Project{ ID: domain.ProjectID(string(rune('a' + id%26))), Name: "Project", } repo.Register(ctx, p) }(i) } // Concurrent read for i := 0; i < 50; i++ { wg.Add(1) go func() { defer wg.Done() repo.List(ctx) }() } // Concurrent exists for i := 0; i < 50; i++ { wg.Add(1) go func(id int) { defer wg.Done() _, _ = repo.Exists(ctx, domain.ProjectID(string(rune('a'+id%26)))) }(i) } wg.Wait() // Test passes if no race/deadlock }