package workerruntime import ( "context" "fmt" "os" "testing" "time" "github.com/itworx/pulse/internal/container" "github.com/itworx/pulse/internal/database" "github.com/itworx/pulse/internal/discovery" "github.com/itworx/pulse/internal/inventory" ) // TestPostgreSQLDiscoveryPersistsInventoryProvenanceAtTargetScale proves the // complete worker-to-inventory persistence path against real PostgreSQL. The // v1 target of 150 active containers is replayed in a later observation window // to verify stable application IDs, fact upserts and relation uniqueness. func TestPostgreSQLDiscoveryPersistsInventoryProvenanceAtTargetScale(t *testing.T) { dsn := os.Getenv("PULSE_TEST_DATABASE_URL") if dsn == "" { t.Skip("PULSE_TEST_DATABASE_URL is not set") } ctx, cancel := context.WithTimeout(context.Background(), 120*time.Second) defer cancel() pool, err := database.NewPool(ctx, database.Config{URL: dsn, MaxConns: 8, MinConns: 1}) if err != nil { t.Fatal(err) } defer pool.Close() if err := database.Migrate(ctx, pool); err != nil { t.Fatal(err) } sourceID := newID() if _, err := pool.Exec(ctx, `INSERT INTO data_sources (id,type,name,configuration_ref) VALUES ($1,'agent',$2,'integration')`, sourceID, "m12-discovery-"+sourceID); err != nil { t.Fatal(err) } t.Cleanup(func() { cleanupCtx, cleanupCancel := context.WithTimeout(context.Background(), 30*time.Second) defer cleanupCancel() _, _ = pool.Exec(cleanupCtx, `DELETE FROM entity_relations WHERE source_id=$1`, sourceID) _, _ = pool.Exec(cleanupCtx, `DELETE FROM entity_facts WHERE source_id=$1`, sourceID) _, _ = pool.Exec(cleanupCtx, `DELETE FROM container_aliases WHERE source_id=$1`, sourceID) _, _ = pool.Exec(cleanupCtx, `DELETE FROM entities WHERE id IN (SELECT entity_id FROM entity_aliases WHERE source_id=$1)`, sourceID) _, _ = pool.Exec(cleanupCtx, `DELETE FROM data_sources WHERE id=$1`, sourceID) }) repository, err := inventory.NewRepository(pool) if err != nil { t.Fatal(err) } now := time.Now().UTC().Truncate(time.Second) items := make([]container.Container, 0, 150) for index := 0; index < 150; index++ { name := fmt.Sprintf("m12-scale-%03d", index) items = append(items, container.Container{ID: "runtime-" + name, Name: name, State: "running", Health: "healthy", MetricsAvailable: true, LifecycleAvailable: true}) } provider := &fakeContainerProvider{snapshot: healthySnapshot(now, items...)} job := DiscoveryJob{ SourceID: sourceID, Provider: provider, Aliases: PostgresContainerAliasStore{Pool: pool}, Inventory: repository, Runner: discovery.Runner{Store: discovery.NewMemoryStore(), MaxAttempts: 1}, } if _, err := job.Run(ctx); err != nil { t.Fatal(err) } assertInventoryScale := func() { t.Helper() var entities, facts, relations int if err := pool.QueryRow(ctx, `SELECT count(DISTINCT entity_id) FROM entity_aliases WHERE source_id=$1`, sourceID).Scan(&entities); err != nil { t.Fatal(err) } if err := pool.QueryRow(ctx, `SELECT count(*) FROM entity_facts WHERE source_id=$1`, sourceID).Scan(&facts); err != nil { t.Fatal(err) } if err := pool.QueryRow(ctx, `SELECT count(*) FROM entity_relations WHERE source_id=$1`, sourceID).Scan(&relations); err != nil { t.Fatal(err) } if entities != 300 || facts < 1_200 || relations != 150 { t.Fatalf("inventory scale entities=%d facts=%d relations=%d", entities, facts, relations) } } assertInventoryScale() provider.set(healthySnapshot(now.Add(time.Minute), items...)) if _, err := job.Run(ctx); err != nil { t.Fatal(err) } assertInventoryScale() provider.set(healthySnapshot(now.Add(2 * time.Minute))) if _, err := job.Run(ctx); err != nil { t.Fatal(err) } var activeEntities, tombstonedRelations int if err := pool.QueryRow(ctx, `SELECT count(*) FROM entities e JOIN entity_aliases a ON a.entity_id=e.id WHERE a.source_id=$1 AND e.tombstoned_at IS NULL`, sourceID).Scan(&activeEntities); err != nil { t.Fatal(err) } if err := pool.QueryRow(ctx, `SELECT count(*) FROM entity_relations WHERE source_id=$1 AND tombstoned_at IS NOT NULL`, sourceID).Scan(&tombstonedRelations); err != nil { t.Fatal(err) } if activeEntities != 0 || tombstonedRelations != 150 { t.Fatalf("disappearance reconciliation active=%d tombstonedRelations=%d", activeEntities, tombstonedRelations) } }