use super::*; #[tokio::test] async fn server_owned_lifecycle_events_are_idempotent_and_completion_uses_the_eligible_cohort() { let database = TestDatabase::new().await; let registry = database.registry().await; let workspace_id = WorkspaceId::new("ws_default"); let eligible_at = truncate_to_micros(OffsetDateTime::now_utc() - Duration::minutes(3)); let eligible = registry .ensure_onboarding_eligibility(&workspace_id, eligible_at) .await .unwrap(); assert_eq!(eligible.eligible_since, Some(eligible_at)); let repeated = registry .ensure_onboarding_eligibility(&workspace_id, OffsetDateTime::now_utc()) .await .unwrap(); assert_eq!(repeated.eligible_since, Some(eligible_at)); let (operation_id, agent_id, key_id) = published_path(®istry).await; let mut test_log = test_invocation_log( "log_onboarding_server_test", &operation_id, None, InvocationStatus::Ok, 20, "2026-03-25T12:14:00Z", ); test_log.source = InvocationSource::AdminTestRun; registry .create_invocation_log(CreateInvocationLogRequest { log: &test_log }) .await; registry .touch_platform_api_key(&workspace_id, &key_id, &OffsetDateTime::now_utc()) .await .unwrap(); let mut success = test_invocation_log( "log_onboarding_server_call", &operation_id, Some(agent_id), InvocationStatus::Ok, 25, "2026-03-25T12:16:00Z", ); success.platform_api_key_id = Some(key_id); success.created_at = truncate_to_micros(OffsetDateTime::now_utc()); registry .create_invocation_log(CreateInvocationLogRequest { log: &success }) .await; let completed = registry .ensure_onboarding_completion(&workspace_id, OffsetDateTime::now_utc()) .await .unwrap(); assert!(completed.completed); let later_draft = test_operation("op_onboarding_later_draft", 1, OperationStatus::Draft); registry .create_operation(&workspace_id, &later_draft, None) .await .unwrap(); let after_later_draft = registry .get_onboarding_projection(&workspace_id) .await .unwrap(); assert_eq!(after_later_draft.operation_id, Some(operation_id)); assert!(after_later_draft.completed); let events = registry .list_product_events(crank_registry::ListProductEventsQuery { workspace_id: &workspace_id, kind: Some(crank_core::ProductEventKind::OnboardingCompleted), created_after: OffsetDateTime::UNIX_EPOCH, created_before: OffsetDateTime::now_utc() + Duration::minutes(1), limit: 10, }) .await .unwrap(); assert_eq!(events.len(), 1); assert_eq!(events[0].event.eligible_since, Some(eligible_at)); assert_eq!(events[0].event.occurred_at, success.created_at); registry .ensure_onboarding_completion(&workspace_id, OffsetDateTime::now_utc()) .await .unwrap(); let events = registry .list_product_events(crank_registry::ListProductEventsQuery { workspace_id: &workspace_id, kind: Some(crank_core::ProductEventKind::OnboardingCompleted), created_after: OffsetDateTime::UNIX_EPOCH, created_before: OffsetDateTime::now_utc() + Duration::minutes(1), limit: 10, }) .await .unwrap(); assert_eq!(events.len(), 1); database.cleanup().await; } #[tokio::test] async fn first_call_before_first_eligibility_is_repaired_with_the_invocation_timestamp() { let database = TestDatabase::new().await; let registry = database.registry().await; let workspace_id = WorkspaceId::new("ws_default"); let (operation_id, agent_id, key_id) = published_path(®istry).await; let call_at = truncate_to_micros(OffsetDateTime::now_utc() + Duration::seconds(1)); record_onboarding_test_and_call( ®istry, &operation_id, &agent_id, &key_id, "before_eligibility", call_at, ) .await; let before_eligibility = registry .get_onboarding_projection(&workspace_id) .await .unwrap(); assert!(before_eligibility.completed); assert!(!before_eligibility.was_completed); let eligible_at = call_at + Duration::seconds(1); let repaired = registry .ensure_onboarding_eligibility(&workspace_id, eligible_at) .await .unwrap(); assert!(repaired.completed); assert!(repaired.was_completed); let events = registry .list_product_events(crank_registry::ListProductEventsQuery { workspace_id: &workspace_id, kind: Some(crank_core::ProductEventKind::OnboardingCompleted), created_after: OffsetDateTime::UNIX_EPOCH, created_before: eligible_at + Duration::minutes(1), limit: 10, }) .await .unwrap(); assert_eq!(events.len(), 1); assert_eq!(events[0].event.occurred_at, call_at); assert_eq!(events[0].event.eligible_since, Some(eligible_at)); database.cleanup().await; } #[tokio::test] async fn projection_keeps_agent_and_key_on_the_same_terminal_path() { let database = TestDatabase::new().await; let registry = database.registry().await; let workspace_id = WorkspaceId::new("ws_default"); let (operation_id, _earlier_agent_id, _earlier_key_id) = published_path(®istry).await; let (later_agent_id, later_key_id) = publish_additional_agent_path(®istry, &operation_id).await; let mut success = test_invocation_log( "log_onboarding_later_agent_success", &operation_id, Some(later_agent_id.clone()), InvocationStatus::Ok, 25, "2026-03-25T12:18:00Z", ); success.platform_api_key_id = Some(later_key_id.clone()); success.created_at = OffsetDateTime::now_utc(); registry .create_invocation_log(CreateInvocationLogRequest { log: &success }) .await; let projection = registry .get_onboarding_projection(&workspace_id) .await .unwrap(); assert_eq!(projection.agent_id, Some(later_agent_id)); assert_eq!(projection.platform_api_key_id, Some(later_key_id)); assert_eq!(projection.first_call_log_id, Some(success.id)); database.cleanup().await; } #[tokio::test] async fn presentation_milestone_is_idempotent_and_revision_guarded() { let database = TestDatabase::new().await; let registry = database.registry().await; let workspace_id = WorkspaceId::new("ws_default"); let initial = registry .get_onboarding_projection(&workspace_id) .await .unwrap(); let event_id = ProductEventId::new("pe_onboarding_eligible"); let request = RecordOnboardingMilestoneRequest { workspace_id: &workspace_id, event_id: &event_id, milestone: OnboardingPresentationMilestone::Eligible, idempotency_key: "eligible:first-login", expected_revision: initial.revision, occurred_at: OffsetDateTime::now_utc(), eligible_since: Some(OffsetDateTime::now_utc()), }; let first = registry .record_onboarding_milestone(request.clone()) .await .unwrap(); assert!(first.accepted); let replay = registry .record_onboarding_milestone(RecordOnboardingMilestoneRequest { expected_revision: first.projection.revision, ..request.clone() }) .await .unwrap(); assert!(!replay.accepted); assert_eq!(replay.projection.revision, first.projection.revision); let stale_replay = registry.record_onboarding_milestone(request).await.unwrap(); assert!(!stale_replay.accepted); assert_eq!(stale_replay.projection.revision, first.projection.revision); database.cleanup().await; }