Files
ITWorx-Pulse-Public/internal/reconciliation/engine_test.go
T
ITWorx Pulse release export bd774932d5
Public source validation / validate (push) Failing after 3m8s
Publish ITWorx Pulse source
2026-09-03 02:09:19 +02:00

85 lines
3.5 KiB
Go

package reconciliation
import (
"testing"
"time"
"github.com/itworx/pulse/internal/datasource"
)
func TestRepeatedSnapshotIsIdempotent(t *testing.T) {
now := time.Now().UTC()
obs := []Observation{{SourceID: "source", ExternalType: "container", ExternalID: "abc", EntityType: "container", CanonicalName: "app", DisplayName: "App", ObservedAt: now}}
first, err := Reconcile("source", datasource.HealthHealthy, obs, nil, nil, now)
if err != nil {
t.Fatal(err)
}
second, err := Reconcile("source", datasource.HealthHealthy, obs, first.Entities, nil, now)
if err != nil {
t.Fatal(err)
}
if len(second.Added) != 0 || len(second.Updated) != 1 || first.Aliases["source\x00container\x00abc"] != second.Aliases["source\x00container\x00abc"] {
t.Fatalf("not idempotent: first=%+v second=%+v", first, second)
}
}
func TestSourceOutageDoesNotTombstone(t *testing.T) {
now := time.Now().UTC()
previous := []Existing{{EntityID: StableEntityID("source", "container", "abc"), SourceID: "source", ExternalType: "container", ExternalID: "abc"}}
result, err := Reconcile("source", datasource.HealthUnknown, nil, previous, nil, now)
if err != nil {
t.Fatal(err)
}
if len(result.Tombstoned) != 0 || len(result.Entities) != 1 {
t.Fatalf("outage changed inventory: %+v", result)
}
}
func TestOverrideSurvivesDiscovery(t *testing.T) {
now := time.Now().UTC()
id := StableEntityID("source", "container", "abc")
result, err := Reconcile("source", datasource.HealthHealthy, []Observation{{SourceID: "source", ExternalType: "container", ExternalID: "abc", EntityType: "container", CanonicalName: "app", DisplayName: "Discovered", ObservedAt: now}}, nil, []Override{{EntityID: id, FieldName: "displayName", Value: "Manual"}}, now)
if err != nil {
t.Fatal(err)
}
if result.Aliases["source\x00container\x00abc"] != id || result.Entities[0].DisplayName != "Manual" {
t.Fatalf("override was not preserved: %+v", result)
}
}
func TestMissingObservationStampsTombstoneTime(t *testing.T) {
now := time.Date(2026, time.August, 1, 22, 0, 0, 0, time.UTC)
obs := []Observation{{SourceID: "source", ExternalType: "container", ExternalID: "abc", EntityType: "container", CanonicalName: "app", DisplayName: "App", ObservedAt: now}}
first, err := Reconcile("source", datasource.HealthHealthy, obs, nil, nil, now)
if err != nil {
t.Fatal(err)
}
if !first.Entities[0].TombstonedAt.IsZero() {
t.Fatalf("live entity carries a tombstone time: %+v", first.Entities[0])
}
later := now.Add(time.Hour).In(time.FixedZone("CEST", 2*60*60))
second, err := Reconcile("source", datasource.HealthHealthy, nil, first.Entities, nil, later)
if err != nil {
t.Fatal(err)
}
if len(second.Tombstoned) != 1 || len(second.Entities) != 1 {
t.Fatalf("expected a single tombstone: %+v", second)
}
entity := second.Entities[0]
if !entity.Tombstoned || !entity.TombstonedAt.Equal(later) || entity.TombstonedAt.Location() != time.UTC {
t.Fatalf("tombstone time was not stamped in UTC: %+v", entity)
}
third, err := Reconcile("source", datasource.HealthHealthy, nil, second.Entities, nil, later.Add(time.Hour))
if err != nil {
t.Fatal(err)
}
if len(third.Tombstoned) != 0 {
t.Fatalf("already tombstoned entity was tombstoned again: %+v", third)
}
revived, err := Reconcile("source", datasource.HealthHealthy, obs, second.Entities, nil, later.Add(2*time.Hour))
if err != nil {
t.Fatal(err)
}
if revived.Entities[0].Tombstoned || !revived.Entities[0].TombstonedAt.IsZero() {
t.Fatalf("revived entity kept its tombstone: %+v", revived.Entities[0])
}
}