package engine import ( "reflect" "testing" "gitea.maximumdirect.net/eric/ConfigKit/internal/document" ) func TestHistorySourceObservationsPreserveRawAndCompositeDefinitions(t *testing.T) { t.Parallel() baseline := parseDocumentForDecode(t, "defaults: {nullable: null}\n") base := parseDocumentForDecode(t, "section: {same: equal, empty: {}}\n") overlay := parseDocumentForDecode(t, "section: {same: equal, empty: {}}\n") setHistoryDocumentSource(base, "base") setHistoryDocumentSource(overlay, "overlay") baseOrigins := make(map[string]Origin) collectOverlayOrigins(baseOrigins, base, Path{}, LayerMain) baseApplication := historyApplication{ layer: LayerMain, value: base, origin: overlayOrigin(LayerMain, base.Location), } composition := &composedDocument{ root: document.Clone(base), origins: baseOrigins, applications: []historyApplication{baseApplication}, } overlayOrigins := make(map[string]Origin) collectOverlayOrigins(overlayOrigins, overlay, Path{}, LayerOverlay) assembly := newAssemblyAccumulator(composition) overlayApplication := historyApplication{ layer: LayerOverlay, value: overlay, origin: overlayOrigin(LayerOverlay, overlay.Location), } if diagnostics := assembly.applyOverlay(overlay, overlayOrigins, []historyApplication{overlayApplication}, LayerOverlay); len(diagnostics) != 0 { t.Fatalf("apply overlay diagnostics = %#v", diagnostics) } builder := newHistoryBuilder() builder.observeBaseline(baseline) builder.observeSourceEvidence(assembly.current.root, assembly.current.origins, assembly.current.applications, assembly.current.overlays) builder.model.final = assembly.current.root model := builder.freeze() defaultPath := Keys("defaults", "nullable") baselineSelections := model.selectHistory(defaultPath) if len(baselineSelections) != 1 || baselineSelections[0].phase != historyPhaseBaseline || baselineSelections[0].attribution.origin.Layer != LayerDefault || !baselineSelections[0].after { t.Fatalf("baseline definition selections = %#v", baselineSelections) } if selections := model.selectHistory(Keys("defaults", "absent")); len(selections) != 0 { t.Fatalf("absent baseline path selections = %#v", selections) } sameSelections := model.selectHistory(Keys("section", "same")) var rawSources []string var replacement bool for _, selection := range sameSelections { if selection.role == historyRoleContribution && selection.hasAttribution { rawSources = append(rawSources, selection.attribution.origin.Locations[0].SourceID) } if selection.role == historyRoleOperation { replacement = selection.before && selection.after } } if !reflect.DeepEqual(rawSources, []string{"base", "overlay"}) || !replacement { t.Fatalf("equal raw contributions and replacement = sources %#v, selections %#v", rawSources, sameSelections) } emptySelections := model.selectHistory(Keys("section", "empty")) var composite *historyAttribution for index := range emptySelections { if emptySelections[index].role == historyRoleCompositeDefinition { composite = &emptySelections[index].attribution } } if composite == nil || !reflect.DeepEqual(locationSourceIDs(composite.origin.Locations), []string{"base", "overlay"}) { t.Fatalf("empty mapping composite attribution = %#v; selections %#v", composite, emptySelections) } metadata := Metadata{historyModel: model} metadataHistory := metadata.History(Keys("section", "empty")) var compositeSources []string for _, change := range metadataHistory { if len(change.Origin.Locations) > 1 { compositeSources = locationSourceIDs(change.Origin.Locations) } } inspection := &Inspection{ metadata: metadata, history: model, final: assembly.current.root, } explanation, explained := inspection.Explain(Keys("section", "empty")) var contributionSources []string for _, change := range explanation.History { if change.Origin.Layer == LayerMain || change.Origin.Layer == LayerOverlay { contributionSources = append(contributionSources, locationSourceIDs(change.Origin.Locations)...) } } if !explained || !reflect.DeepEqual(compositeSources, []string{"base", "overlay"}) || !reflect.DeepEqual(contributionSources, []string{"base", "overlay"}) { t.Fatalf("metadata composite attribution and explanation contributions differ: metadata %#v, explanation %#v", metadataHistory, explanation.History) } } func TestHistorySourceReplacementTracksConcreteBeforeAndAfterShapes(t *testing.T) { t.Parallel() base := parseDocumentForDecode(t, "shape: {'0': {leaf: old}}\n") overlay := parseDocumentForDecode(t, "shape: [{leaf: new}]\n") setHistoryDocumentSource(base, "base") setHistoryDocumentSource(overlay, "overlay") baseOrigins := make(map[string]Origin) collectOverlayOrigins(baseOrigins, base, Path{}, LayerMain) overlayOrigins := make(map[string]Origin) collectOverlayOrigins(overlayOrigins, overlay, Path{}, LayerOverlay) composition := &composedDocument{ root: document.Clone(base), origins: baseOrigins, applications: []historyApplication{{layer: LayerMain, value: base, origin: overlayOrigin(LayerMain, base.Location)}}, } assembly := newAssemblyAccumulator(composition) if diagnostics := assembly.applyOverlay(overlay, overlayOrigins, []historyApplication{{layer: LayerOverlay, value: overlay, origin: overlayOrigin(LayerOverlay, overlay.Location)}}, LayerOverlay); len(diagnostics) != 0 { t.Fatalf("apply overlay diagnostics = %#v", diagnostics) } if len(assembly.current.overlays) != 1 { t.Fatalf("replacement records = %#v", assembly.current.overlays) } record := assembly.current.overlays[0] if len(record.displaced) != 3 || len(record.introduced) != 3 { t.Fatalf("displaced/introduction path counts = %d/%d", len(record.displaced), len(record.introduced)) } if got := record.introduced[2].path; !sameConcreteHistoryPath(got, Keys("shape").Index(0).Key("leaf")) { t.Fatalf("introduced descendant path = %#v", got) } builder := newHistoryBuilder() builder.observeSourceEvidence(assembly.current.root, assembly.current.origins, assembly.current.applications, assembly.current.overlays) model := builder.freeze() modelIntroduction := model.sourcePayloads[2].overlay.introduced[2] cloned := cloneOverlayRecords(assembly.current.overlays) record.introduced[2].path.segments[2].key = "mutated" record.introduced[2].origin.Locations[0].SourceID = "mutated" if got := cloned[0].introduced[2]; !sameConcreteHistoryPath(got.path, Keys("shape").Index(0).Key("leaf")) || got.origin.Locations[0].SourceID != "overlay" { t.Fatalf("cloned introduced evidence changed with producer: %#v", got) } if !sameConcreteHistoryPath(modelIntroduction.path, Keys("shape").Index(0).Key("leaf")) || modelIntroduction.origin.Locations[0].SourceID != "overlay" { t.Fatalf("model-owned introduced evidence changed with producer: %#v", modelIntroduction) } oldPath := Keys("shape", "0", "leaf") newPath := Keys("shape").Index(0).Key("leaf") oldSelections := model.selectHistory(oldPath) newSelections := model.selectHistory(newPath) if len(oldSelections) < 2 || oldSelections[len(oldSelections)-1].role != historyRoleOperation || !oldSelections[len(oldSelections)-1].before || oldSelections[len(oldSelections)-1].after { t.Fatalf("removed key path selections = %#v", oldSelections) } if len(newSelections) < 2 || newSelections[len(newSelections)-1].role != historyRoleOperation || newSelections[len(newSelections)-1].before || !newSelections[len(newSelections)-1].after { t.Fatalf("introduced index path selections = %#v", newSelections) } parsed, err := ParsePath("/shape/0/leaf") if err != nil { t.Fatal(err) } parsedSelections := model.selectHistory(parsed) if len(parsedSelections) == 0 || !parsedSelections[len(parsedSelections)-1].before || !parsedSelections[len(parsedSelections)-1].after { t.Fatalf("parsed path did not combine replacement sides: %#v", parsedSelections) } } func TestOverlayReplacementCapturesIntroducedPathOrigins(t *testing.T) { t.Parallel() existing := parseDocumentForDecode(t, "old: value\n") incoming := parseDocumentForDecode(t, "new: value\n") setHistoryDocumentSource(existing, "base") setHistoryDocumentSource(incoming, "overlay") path := Keys("section") origins := make(map[string]Origin) collectOverlayOrigins(origins, existing, path, LayerMain) incomingOrigins := make(map[string]Origin) collectOverlayOrigins(incomingOrigins, incoming, path, LayerOverlay) result := &composedDocument{ origins: origins, } recordOverlayReplacement(result, path, existing, incoming, LayerOverlay, true, incomingOrigins) if len(result.overlays) != 1 || len(result.overlays[0].introduced) != 2 { t.Fatalf("replacement introduced evidence = %#v", result.overlays) } introduced := result.overlays[0].introduced[1] if !sameConcreteHistoryPath(introduced.path, path.Key("new")) || introduced.origin.Layer != LayerOverlay || introduced.origin.Locations[0].SourceID != "overlay" { t.Fatalf("introduced replacement evidence = %#v", introduced) } } func TestHistorySourceEvidenceLeavesEmptySourcePhaseUnpopulated(t *testing.T) { t.Parallel() builder := newHistoryBuilder() builder.observeBaseline(parseDocumentForDecode(t, "defaults: {enabled: true}\n")) builder.observeSourceEvidence(nil, nil, nil, nil) model := builder.freeze() if len(model.sourcePayloads) != 0 { t.Fatalf("empty source input retained payloads: %#v", model.sourcePayloads) } for _, observation := range model.observations { if observation.phase == historyPhaseSources { t.Fatalf("empty source phase has an observation: %#v", observation) } } } func setHistoryDocumentSource(root *document.Node, sourceID string) { walkDocument(root, Path{}, func(_ Path, node *document.Node) { node.Location.SourceID = sourceID }) } func locationSourceIDs(locations []Location) []string { ids := make([]string, len(locations)) for index, location := range locations { ids[index] = location.SourceID } return ids }