diff --git a/apps/api-rust/tests/acquisition.rs b/apps/api-rust/tests/acquisition.rs index 866d07a06..c33219a77 100644 --- a/apps/api-rust/tests/acquisition.rs +++ b/apps/api-rust/tests/acquisition.rs @@ -728,3 +728,126 @@ async fn postgres_registration_respects_visit_time_boundaries() { fixture.cleanup().await; } } + +#[tokio::test] +#[ignore = "requires isolated PostgreSQL via LMM_TEST_DATABASE_URL"] +async fn postgres_visit_consent_and_owner_boundaries_are_atomic() { + let fixture = PgFixture::new().await; + let visitor = "f".repeat(64); + + let missing_consent = fixture + .store + .observe( + &visitor, + 7, + &input(json!({ + "consent":true, + "consent_version":2, + "nonce":"88888888888888888888888888888888", + "landing":"/guide", + "source":"documentation" + })), + &["api.lmm.best"], + ) + .await; + assert!(matches!(missing_consent, Err(Error::Invalid(_)))); + let empty: (i64, i64) = sqlx::query_as( + "SELECT (SELECT COUNT(*) FROM acquisition_visitors),\ + (SELECT COUNT(*) FROM acquisition_visits)", + ) + .fetch_one(&fixture.pg) + .await + .unwrap(); + assert_eq!( + empty, + (0, 0), + "rejected consent must not persist partial rows" + ); + + let legacy = fixture + .store + .observe( + &visitor, + 7, + &input(json!({ + "consent":true, + "consent_version":1, + "nonce":"99999999999999999999999999999999", + "landing":"/guide", + "source":"documentation" + })), + &["api.lmm.best"], + ) + .await + .unwrap(); + assert_eq!(legacy.consent_version, 1); + + sqlx::query("INSERT INTO users VALUES(8,1700000000,NULL)") + .execute(&fixture.pg) + .await + .unwrap(); + fixture.store.grant(8).await.unwrap(); + let foreign_owner = fixture + .store + .observe( + &visitor, + 8, + &input(json!({ + "consent":true, + "consent_version":2, + "nonce":"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "landing":"/pricing", + "source":"community" + })), + &["api.lmm.best"], + ) + .await; + assert!(matches!(foreign_owner, Err(Error::Invalid(_)))); + + let state: (i64, i64, i64) = sqlx::query_as( + "SELECT (SELECT user_id FROM acquisition_visitors WHERE id=$1),\ + (SELECT COUNT(*) FROM acquisition_visits WHERE visitor_id=$1),\ + (SELECT COUNT(*) FROM acquisition_visits \ + WHERE visitor_id=$1 AND nonce='aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa')", + ) + .bind(&visitor) + .fetch_one(&fixture.pg) + .await + .unwrap(); + assert_eq!( + state, + (7, 1, 0), + "owner conflict must roll back every write" + ); + + fixture.store.grant(7).await.unwrap(); + let upgraded = fixture + .store + .observe( + &visitor, + 7, + &input(json!({ + "consent":true, + "consent_version":2, + "nonce":"bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", + "landing":"/pricing", + "source":"community" + })), + &["api.lmm.best"], + ) + .await + .unwrap(); + assert_eq!(upgraded.consent_version, 2); + let final_state: (i64, i64, bool, i64) = sqlx::query_as( + "SELECT (SELECT user_id FROM acquisition_visitors WHERE id=$1),\ + (SELECT COUNT(*) FROM acquisition_visits WHERE visitor_id=$1),\ + (SELECT allowed FROM acquisition_consents WHERE user_id=7),\ + (SELECT version FROM acquisition_consents WHERE user_id=7)", + ) + .bind(&visitor) + .fetch_one(&fixture.pg) + .await + .unwrap(); + assert_eq!(final_state, (7, 2, true, 2)); + fixture.cleanup().await; +} diff --git a/apps/api-rust/tests/scripts/check-real-integration-gates.sh b/apps/api-rust/tests/scripts/check-real-integration-gates.sh index 7743b1f10..c3b066a5a 100644 --- a/apps/api-rust/tests/scripts/check-real-integration-gates.sh +++ b/apps/api-rust/tests/scripts/check-real-integration-gates.sh @@ -20,7 +20,7 @@ declare -A requirements=( [scripts.rs]='repository_options_commit_refresh_runtime_invalidate_cache_and_redact_audit|LMM_TEST_DATABASE_URL|LMM_AUTH_TEST_VALKEY_URL' [ai_directory.rs]='postgres_cache_and_audit_failures_do_not_reverse_committed_wallet_changes,postgres_create_replay_quote_changes_and_concurrency_charge_once,postgres_hide_refunds_once_and_wallet_failure_rolls_back_visibility,postgres_public_private_pagination_expiry_and_http_contract|LMM_TEST_DATABASE_URL|LMM_AUTH_TEST_VALKEY_URL' [token_queries.rs]='configured_token_prices_match_current_go_reference_live_maps_and_limits,persisted_usage_is_exact_token_scoped_utc_and_never_changes_credentials,quota_query_auth_checks_exact_key_expiry_owner_oauth_and_ip_without_status_writes,quota_query_limiter_is_shared_per_owner_across_keys_and_instances,token_pricing_checks_permissions_before_query_validation_and_never_mutates_key,token_pricing_uses_shared_credited_trust_facts_and_excludes_internal_credits|LMM_TEST_DATABASE_URL|LMM_AUTH_TEST_VALKEY_URL' - [acquisition.rs]='postgres_consent_visit_report_and_withdrawal_round_trip,postgres_link_policy_and_lookback_contracts_are_durable,postgres_registration_cannot_override_explicit_consent_denial,postgres_registration_keeps_first_touch_and_selects_last_external_visit,postgres_registration_respects_visit_time_boundaries|LMM_TEST_DATABASE_URL|' + [acquisition.rs]='postgres_consent_visit_report_and_withdrawal_round_trip,postgres_link_policy_and_lookback_contracts_are_durable,postgres_registration_cannot_override_explicit_consent_denial,postgres_registration_keeps_first_touch_and_selects_last_external_visit,postgres_registration_respects_visit_time_boundaries,postgres_visit_consent_and_owner_boundaries_are_atomic|LMM_TEST_DATABASE_URL|' ) total_ignored=0 diff --git a/apps/api-rust/tests/scripts/run-real-integration-gates.sh b/apps/api-rust/tests/scripts/run-real-integration-gates.sh index 42b4a6008..a624c85c4 100644 --- a/apps/api-rust/tests/scripts/run-real-integration-gates.sh +++ b/apps/api-rust/tests/scripts/run-real-integration-gates.sh @@ -193,8 +193,8 @@ run_token_queries() ( run_acquisition() { require_loopback_url LMM_TEST_DATABASE_URL - require_api_ignored_test_count acquisition 5 - run_counted_api_integration_tests acquisition 5 + require_api_ignored_test_count acquisition 6 + run_counted_api_integration_tests acquisition 6 } run_shared_trust() {