package mysql import ( "context" "os" "testing" "time" ) // TestIntegrationOperationalSnapshot verifies the bounded sampler against the // real MySQL grammar and the indexes installed by migration 000026. func TestIntegrationOperationalSnapshot(t *testing.T) { dsn := os.Getenv("ORBIT_TEST_MYSQL_DSN") if dsn == "" { t.Skip("set ORBIT_TEST_MYSQL_DSN to a disposable database, or run make integration") } ctx, cancel := context.WithTimeout(context.Background(), 90*time.Second) defer cancel() db, err := Open(ctx, dsn, 16, 4) if err != nil { t.Fatal(err) } defer db.Close() if err := Migrate(ctx, db); err != nil { t.Fatal(err) } verification, err := VerifyOperationalSchema(ctx, db) if err != nil || !verification.Complete || verification.ForeignKeyCount != 0 || verification.RequiredIndexCount != 6 { t.Fatalf("schema preflight=%+v err=%v", verification, err) } if err := CheckSchema(ctx, db); err != nil { t.Fatal(err) } for _, want := range []struct { table string index string }{ {"pulse_tasks", "ix_task_observe_state_available"}, {"orbit_deliveries", "ix_delivery_observe_state_available"}, {"weave_instances", "ix_workflow_observe_state_updated"}, {"orbit_events", "ix_event_observe_type_occurred"}, {"pulse_tasks", "ix_task_observe_terminal_completed"}, {"weave_instances", "ix_workflow_observe_terminal_completed"}, } { var n int if err := db.QueryRowContext(ctx, `SELECT COUNT(*) FROM information_schema.statistics WHERE table_schema=DATABASE() AND table_name=? AND index_name=?`, want.table, want.index).Scan(&n); err != nil { t.Fatal(err) } if n == 0 { t.Fatalf("missing operational index %s.%s", want.table, want.index) } } var foreignKeys int if err := db.QueryRowContext(ctx, `SELECT COUNT(*) FROM information_schema.REFERENTIAL_CONSTRAINTS WHERE CONSTRAINT_SCHEMA=DATABASE()`).Scan(&foreignKeys); err != nil { t.Fatal(err) } if foreignKeys != 0 { t.Fatalf("operational migration introduced foreign keys: %d", foreignKeys) } f := newExecutionFixture(t, ctx, db) task := f.submit(2) snapshot, err := ReadOperationalSnapshot(ctx, db) if err != nil { t.Fatal(err) } if got := snapshot.TaskStates["WAITING"]; got < 1 { t.Fatalf("waiting task was not observed: %#v", snapshot.TaskStates) } if got := snapshot.RecentEvents["task.accepted"]; got < 1 { t.Fatalf("accepted event was not observed: %#v", snapshot.RecentEvents) } if snapshot.TaskOldestAgeSeconds < 0 || snapshot.DeliveryOldestAgeSeconds < 0 || snapshot.WorkflowOldestAgeSeconds < 0 { t.Fatalf("negative age in snapshot: %#v", snapshot) } for _, state := range []string{"WAITING", "LEASED", "RUNNING", "NEEDS_REVIEW", "SUCCEEDED", "FAILED", "CANCELLED"} { if _, ok := snapshot.TaskStates[state]; !ok { t.Fatalf("missing fixed task state %q", state) } } if _, ok := snapshot.DeliveryStates["PENDING"]; !ok { t.Fatal("missing pending delivery state") } if _, ok := snapshot.WorkflowStates["RUNNING"]; !ok { t.Fatal("missing running workflow state") } f.exec("UPDATE pulse_tasks SET state='SUCCEEDED', completed_at=UTC_TIMESTAMP(6), updated_at=UTC_TIMESTAMP(6), revision=revision+1 WHERE namespace_id=? AND id=?", f.resources.NamespaceID, task.ID) completed, err := ReadOperationalSnapshot(ctx, db) if err != nil { t.Fatal(err) } if completed.TaskTerminalLatencySeconds < 0 || completed.TaskTerminalLatencyMaxSeconds < 0 { t.Fatalf("negative terminal latency: %#v", completed) } cancelled, cancelNow := context.WithCancel(ctx) cancelNow() if _, err := ReadOperationalSnapshot(cancelled, db); err == nil { t.Fatal("cancelled context unexpectedly completed snapshot") } f.exec("UPDATE pulse_tasks SET completed_at=DATE_SUB(UTC_TIMESTAMP(6), INTERVAL 2 HOUR), updated_at=UTC_TIMESTAMP(6) WHERE namespace_id=? AND id=?", f.resources.NamespaceID, task.ID) inventory, err := ReadRetentionInventory(ctx, db, time.Now().UTC().Add(-time.Hour)) if err != nil { t.Fatal(err) } if inventory.Cutoff.IsZero() || inventory.TerminalTasks == 0 || inventory.UncertainTasks != 0 { t.Fatalf("retention inventory=%+v", inventory) } if _, err := ReadRetentionInventory(ctx, db, time.Now().UTC().Add(time.Hour)); err == nil { t.Fatal("future retention cutoff unexpectedly accepted") } }