diff --git a/pluginkit/evaluation_orchestrator.go b/pluginkit/evaluation_orchestrator.go index c60f204..d74b161 100644 --- a/pluginkit/evaluation_orchestrator.go +++ b/pluginkit/evaluation_orchestrator.go @@ -119,6 +119,12 @@ func (v *EvaluationOrchestrator) AddEvaluationSuiteForAllCatalogs(loader DataLoa } func (v *EvaluationOrchestrator) addEvaluationSuite(catalog *gemara.ControlCatalog, loader DataLoader, steps map[string][]gemara.AssessmentStep) { + for _, existing := range v.possibleSuites { + if existing.CatalogId == catalog.Metadata.Id { + return + } + } + importedControls := getImportedControls(catalog, v.referenceCatalogs) catalog.Controls = append(catalog.Controls, importedControls...) @@ -212,6 +218,7 @@ func (v *EvaluationOrchestrator) Mobilize() error { }, } v.Evaluation_Suites = append(v.Evaluation_Suites, suite) + break } } if !matched { diff --git a/pluginkit/evaluation_orchestrator_test.go b/pluginkit/evaluation_orchestrator_test.go index 1344307..06fb3cf 100644 --- a/pluginkit/evaluation_orchestrator_test.go +++ b/pluginkit/evaluation_orchestrator_test.go @@ -116,6 +116,71 @@ func TestEvaluationOrchestrator_AddEvaluationSuite(t *testing.T) { }) } +func TestEvaluationOrchestrator_AddEvaluationSuite_DeduplicatesCatalogId(t *testing.T) { + orchestrator := &EvaluationOrchestrator{} + catalog := getTestCatalogWithRequirements() + orchestrator.referenceCatalogs = map[string]*gemara.ControlCatalog{ + catalog.Metadata.Id: catalog, + } + + steps := createPassingStepsMap() + + err := orchestrator.AddEvaluationSuite(catalog.Metadata.Id, nil, steps) + if err != nil { + t.Fatalf("First AddEvaluationSuite failed: %v", err) + } + + // Second call with same catalog ID should be silently ignored + err = orchestrator.AddEvaluationSuite(catalog.Metadata.Id, nil, steps) + if err != nil { + t.Fatalf("Second AddEvaluationSuite failed: %v", err) + } + + if len(orchestrator.possibleSuites) != 1 { + t.Errorf("Expected 1 suite (deduped), got %d", len(orchestrator.possibleSuites)) + } +} + +func TestEvaluationOrchestrator_Mobilize_BreaksAfterMatch(t *testing.T) { + tmpDir, err := os.MkdirTemp("", "test-mobilize-break-") + if err != nil { + t.Fatalf("Failed to create temp directory: %v", err) + } + defer func() { + _ = os.RemoveAll(tmpDir) + }() + + cfg := setBasicConfig() + cfg.Policy.ControlCatalogs = []string{"CCC.ObjStor"} + cfg.Write = true + cfg.WriteDirectory = tmpDir + cfg.Output = "yaml" + + catalog := getTestCatalogWithRequirements() + steps := createPassingStepsMap() + + // Manually construct two suites with the same CatalogId to test break behavior + orchestrator := &EvaluationOrchestrator{ + ServiceName: "test-service", + PluginName: "test-plugin", + config: cfg, + possibleSuites: []*EvaluationSuite{ + {CatalogId: "CCC.ObjStor", catalog: catalog, steps: steps, config: cfg}, + {CatalogId: "CCC.ObjStor", catalog: catalog, steps: steps, config: cfg}, + }, + } + + err = orchestrator.Mobilize() + if err != nil { + t.Fatalf("Mobilize failed: %v", err) + } + + // Should only execute the first matching suite due to break + if len(orchestrator.Evaluation_Suites) != 1 { + t.Errorf("Expected 1 suite (break after first match), got %d", len(orchestrator.Evaluation_Suites)) + } +} + func TestEvaluationOrchestrator_AddEvaluationSuiteForAllCatalogs(t *testing.T) { t.Run("Error Without Reference Catalogs", func(t *testing.T) { orchestrator := &EvaluationOrchestrator{}