Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions tests/blackbox/executor.go
Original file line number Diff line number Diff line change
Expand Up @@ -1768,6 +1768,11 @@ func (h *TestHarness) executeTriggerDiscovery(t *testing.T, model *StateModel) {
}
t.Logf("TriggerDiscovery: inventory not converged (attempt %d)", attempt+1)
}
if _, unmanagedInventory, err := h.extractManagedAndUnmanagedInventory(); err == nil {
for _, v := range CheckUnmanagedModelVsInventory(model, unmanagedInventory) {
t.Logf("TriggerDiscovery: unconverged: %s", v.Message)
}
}
require.Failf(t, "discovery did not ingest",
"expected unmanaged resources were not ingested after %d attempts", maxDiscoverAttempts)
}
Expand Down Expand Up @@ -1954,6 +1959,16 @@ func (h *TestHarness) waitForAbsorbedInventory(t *testing.T, query, nativeID, ex

func (h *TestHarness) executeCloudCreate(t *testing.T, op *Operation, model *StateModel) {
t.Helper()
// The generator draws native ids from a small pool, so an id can repeat
// within a sequence. Re-creating an entry the model already tracks as
// present is an out-of-band MODIFY of that resource, not a create:
// discovery only ingests unknown native ids, so it can never converge
// the model's fresh CloudProperties with the already-ingested row (that
// is sync absorption, which OpCloudModify exercises). Skip the op.
if res := model.UnmanagedResources[op.NativeID]; res != nil && (res.PresentInCloud || res.PresentInInventory) {
t.Logf("[op %d] CloudCreate: %s → skipped (already present out-of-band)", op.SequenceNum, op.NativeID)
return
}
h.putCloudStateWithRetry(t, op.NativeID, op.ResourceType, op.Properties)
model.ApplyUnmanagedCloudCreate(op.NativeID, op.ResourceType, op.Properties)
t.Logf("[op %d] CloudCreate: %s (%s)", op.SequenceNum, op.NativeID, op.ResourceType)
Expand Down
49 changes: 49 additions & 0 deletions tests/blackbox/unmanaged_recreate_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
// © 2026 Platform Engineering Labs Inc.
//
// SPDX-License-Identifier: FSL-1.1-ALv2

//go:build integration || property

package blackbox

import (
"testing"
"time"

"github.com/platform-engineering-labs/formae/internal/metastructure/testutil"
"github.com/stretchr/testify/require"
)

// A CloudCreate that reuses the native id of an already-ingested unmanaged
// resource is skipped: discovery only ingests unknown native ids, so fresh
// CloudProperties expectations for a known id could never converge through
// the discovery path. After the skip, a discovery trigger must still
// converge cleanly.
func TestCloudCreate_RepeatedNativeIDIsSkipped(t *testing.T) {
testutil.RunTestFromProjectRoot(t, func(t *testing.T) {
h := NewTestHarness(t, 30*time.Second)
defer h.Cleanup()
h.ResetAgentState(t)
config := PropertyTestConfig{ResourceCount: 10, StackCount: 1}
model := NewStateModel(config.StackCount, config.ResourceCount)
h.SetupStacks(t, model, config)

create := Operation{Kind: OpCloudCreate, NativeID: "cloud-1",
ResourceType: "Test::Generic::Resource",
Properties: `{"Name":"cp-1","Value":"v1","SetTags":[],"EntityTags":[],"OrderedItems":[]}`}
h.ExecuteOperation(t, &create, model)
h.executeTriggerDiscovery(t, model)
require.True(t, model.UnmanagedResources["cloud-1"].PresentInInventory)
ingested := model.UnmanagedResources["cloud-1"].CloudProperties

recreate := Operation{Kind: OpCloudCreate, NativeID: "cloud-1",
ResourceType: "Test::Generic::Resource",
Properties: `{"Name":"cp-other","Value":"v2","SetTags":[],"EntityTags":[],"OrderedItems":[]}`}
h.ExecuteOperation(t, &recreate, model)

require.Equal(t, ingested, model.UnmanagedResources["cloud-1"].CloudProperties,
"a repeated CloudCreate must not rewrite the tracked cloud properties")
require.True(t, h.waitForUnmanagedInventoryExpectations(t, model, 10*time.Second),
"the unmanaged inventory must still converge after the skipped re-create")
})
}
Loading