test(providers): remove destructive live fixtures

Delete secret-dependent integration tests that consume live quota, register upstream tasks, or rotate OAuth refresh tokens. Keep the default workspace suite entirely runnable without ignored cases.

Co-authored-by: Codex <noreply@openai.com>
This commit is contained in:
zyycn
2026-07-29 01:06:09 +08:00
co-authored by Codex
parent 7b406765d6
commit 428e744710
5 changed files with 19 additions and 522 deletions
+4 -152
View File
@@ -4,7 +4,7 @@ use std::sync::{Arc, Mutex};
use std::time::{Duration, SystemTime};
use chrono::{DateTime, TimeZone as _, Utc};
use futures::{StreamExt as _, future::BoxFuture};
use futures::future::BoxFuture;
use gateway_admin::model::accounts::{
AccountAvailability as AdminAccountAvailability, AccountRecord,
};
@@ -20,18 +20,10 @@ use gateway_admin::model::provider_credentials::{
use gateway_admin::model::{MutationActor, MutationContext, Revision};
use gateway_admin::ports::provider::ProviderAdminErrorKind;
use gateway_core::engine::credential::{
AccountAvailability, AccountSelectionPolicy, CredentialRevision, NewProviderAccount,
OpaqueProviderData, PlaintextCredential, ProviderAccount, ProviderAccountId,
ProviderAccountStore, QuotaObservation, RotationStrategy,
CredentialRevision, OpaqueProviderData, ProviderAccount, ProviderAccountId,
ProviderAccountStore, QuotaObservation,
};
use gateway_core::engine::provider::ProviderRequest;
use gateway_core::engine::{
AccountAttemptContext, AttemptContext, CancellationToken, ModelRequestId, RequestAttemptContext,
};
use gateway_core::event::GatewayEvent;
use gateway_core::operation::OperationKind;
use gateway_core::operation::{GenerateRequest, Operation, ProtocolPayload};
use gateway_core::policy::ClientApiKeyId;
use gateway_core::provider_ports::{
NewOAuthPendingFlow, OAuthPendingClaimOutcome, OAuthPendingConsumeOutcome,
OAuthPendingFlowPort, OAuthPendingPutOutcome, OAuthPendingReleaseOutcome,
@@ -40,16 +32,12 @@ use gateway_core::provider_ports::{
ProviderRefreshPolicy, ProviderRuntimePolicyPort, ProviderScopedCooldown, ProviderStoreError,
ProviderStorePorts,
};
use gateway_core::routing::{
ConfigRevision, ModelCapabilities, ProviderKind, ProviderModel, PublicModelId, RoutingContext,
RuntimeSnapshot, UpstreamModelId,
};
use gateway_core::routing::{ProviderKind, UpstreamModelId};
use gateway_core::task::{WorkerContribution, WorkerKind, WorkerRunnable};
use provider_openai::config::{CodexWireProfileConfig, OpenAiConfig};
use provider_openai::credential::ImportCodexOAuthCredential;
use provider_openai::transport::profile::APPCAST_POLL_INTERVAL;
use serde_json::{Map, Value, json};
use std::collections::BTreeSet;
use tempfile::TempDir;
use crate::support::{
@@ -57,142 +45,6 @@ use crate::support::{
profile, secret,
};
#[tokio::test]
#[ignore = "requires CODEX_REAL_ACCOUNT_FIXTURE and consumes live OpenAI quota"]
async fn real_openai_conversation_crosses_production_provider_boundaries() {
let fixture = std::env::var("CODEX_REAL_ACCOUNT_FIXTURE")
.expect("CODEX_REAL_ACCOUNT_FIXTURE must point to a CPR JSON document");
let payload = serde_json::from_slice::<Value>(&std::fs::read(fixture).expect("read fixture"))
.expect("parse fixture");
let Value::Object(document) = payload else {
panic!("real OpenAI fixture must be an object");
};
let store = Arc::new(MemoryAccountStore::default());
let config = valid_config();
let bundle = provider_openai::initialize(
config.config.clone(),
provider_ports_with(Arc::clone(&store), Arc::new(TestOAuthPending::default())),
)
.await
.expect("OpenAI bundle");
let prepared = bundle
.admin_provider()
.prepare_import(PrepareCredentialImport {
document: ProviderDocument::new(OpaqueProviderData::new(document)),
})
.await
.expect("prepare real OpenAI import");
for credential in prepared.credentials {
store
.create_account(prepared_account(credential))
.await
.expect("seed verified OpenAI account");
}
let models = bundle
.core_provider()
.query_model_capabilities()
.await
.expect("query live OpenAI catalog");
let model = models.first().expect("one live generation model");
let operation = bundle
.admin_provider()
.connection_test_operation(model.upstream_model(), "Reply with exactly CPR_REAL_OK.")
.expect("real connection operation");
let request = planned_real_request(model.upstream_model().clone(), operation);
let mut stream = bundle
.core_provider()
.execute(request, real_attempt_context())
.await
.expect("prepare real OpenAI stream");
let mut started = false;
let mut completed = false;
let mut usage = false;
let mut text = false;
while let Some(event) = stream.next().await {
for fact in event.expect("real OpenAI event").canonical_facts() {
match fact {
GatewayEvent::Started(_) => started = true,
GatewayEvent::TextDelta(_) => text = true,
GatewayEvent::Usage(_) => usage = true,
GatewayEvent::Completed(_) => completed = true,
_ => {}
}
}
}
assert!(started && text && usage && completed);
}
fn prepared_account(
value: gateway_admin::model::provider_credentials::PreparedCredentialCreate,
) -> NewProviderAccount {
let account = ProviderAccount::new(
value.account_id,
value.provider_kind,
value.name,
value.upstream_user_id,
value.authentication_kind,
CredentialRevision::new(1).expect("initial revision"),
value.access_token_expires_at.map(Into::into),
)
.with_profile(value.email, value.upstream_account_id, value.plan_type)
.with_runtime_state(value.enabled, AccountAvailability::Ready, None)
.with_refresh_schedule(
value.has_refresh_token,
value.next_refresh_at.map(Into::into),
);
NewProviderAccount {
account,
credential: PlaintextCredential::new(
value.provider_material.into_provider_data().into_inner(),
),
}
}
fn planned_real_request(upstream_model: UpstreamModelId, operation: Operation) -> ProviderRequest {
let provider = ProviderKind::new("openai").expect("provider");
let public_model = PublicModelId::new(upstream_model.as_str()).expect("public model");
let snapshot = RuntimeSnapshot::new(
ConfigRevision::new(1).expect("revision"),
AccountSelectionPolicy::new(
RotationStrategy::Smart,
NonZeroU32::new(1).expect("concurrency"),
Duration::ZERO,
),
vec![provider.clone()],
vec![ProviderModel::new(
provider,
upstream_model,
ModelCapabilities::new(BTreeSet::from([OperationKind::Generate]), None),
)],
vec![],
)
.expect("snapshot");
let plan = snapshot
.plan(&public_model, &operation, &RoutingContext::default())
.expect("route plan");
ProviderRequest::new(operation, plan.candidates()[0].clone())
}
fn real_attempt_context() -> AttemptContext {
AttemptContext::new(
RequestAttemptContext::new(
ModelRequestId::new("req_real_openai_conversation").expect("request"),
ClientApiKeyId::new("real_test_client").expect("client"),
),
NonZeroU32::new(1).expect("attempt"),
SystemTime::now() + Duration::from_secs(90),
AccountSelectionPolicy::new(
RotationStrategy::Smart,
NonZeroU32::new(1).expect("concurrency"),
Duration::ZERO,
),
AccountAttemptContext::new(BTreeSet::new(), None, None),
None,
CancellationToken::new(),
)
}
#[tokio::test]
async fn openai_bundle_exposes_one_core_provider_and_drains_worker_contributions_once() {
let config = valid_config();
@@ -18,18 +18,14 @@ use gateway_core::provider_ports::{
ProviderStoreError,
};
use gateway_core::routing::ProviderKind;
use provider_openai::OFFICIAL_CODEX_BASE_URL;
use provider_openai::credential::token_client::{RefreshFailure, TokenPair, TokenRefresher};
use provider_openai::credential::{
CODEX_AUTHENTICATION_KIND_AGENT_IDENTITY, CodexAccountIdentityService,
CodexAccountIdentityVerifier, CodexAgentIdentityAuthMode, CodexAgentIdentityCredentialData,
CodexCredentialAdmin, CodexCredentialAdminError, CodexCredentialAdminService,
CodexCredentialCodec, CodexIdentityExpectation, CodexIdentityVerification,
CodexIdentityVerificationError, CodexJwtIdentityVerifier, ExportManagedCodexCredential,
ImportCodexOAuthCredential, ReqwestCodexAuthenticatedAccountSource, ReqwestOpenAiJwksSource,
RotateManagedCodexCredential,
CODEX_AUTHENTICATION_KIND_AGENT_IDENTITY, CodexAccountIdentityVerifier,
CodexAgentIdentityAuthMode, CodexAgentIdentityCredentialData, CodexCredentialAdmin,
CodexCredentialAdminError, CodexCredentialAdminService, CodexCredentialCodec,
CodexIdentityExpectation, CodexIdentityVerification, CodexIdentityVerificationError,
ExportManagedCodexCredential, ImportCodexOAuthCredential, RotateManagedCodexCredential,
};
use provider_openai::transport::profile::{CodexWireProfile, CodexWireProfileState};
use secrecy::{ExposeSecret, SecretString};
use crate::support::{MemoryAccountStore, codex_account, profile, runtime_policy, secret};
@@ -806,76 +802,3 @@ async fn cliproxyapi_codex_auth_file_is_recognized_as_an_openai_auth_document()
assert_eq!(account.email(), Some("chatgpt-cpa@example.com"));
assert_eq!(account.authentication_kind(), "oauth");
}
#[tokio::test]
#[ignore = "requires CODEX_REAL_ACCOUNT_FIXTURE and live official OpenAI OAuth/JWKS"]
async fn real_cpr_fixture_import_contract() {
let fixture = std::env::var("CODEX_REAL_ACCOUNT_FIXTURE")
.expect("CODEX_REAL_ACCOUNT_FIXTURE must point to a CPR JSON document");
let payload = serde_json::from_slice::<serde_json::Value>(
&std::fs::read(fixture).expect("read CPR fixture"),
)
.expect("parse CPR fixture");
let refresher = Arc::new(
provider_openai::credential::token_client::official_openai_token_client()
.expect("official token client"),
);
let signed = Arc::new(CodexJwtIdentityVerifier::new(Box::new(
ReqwestOpenAiJwksSource::new().expect("official JWKS source"),
)));
let profile = CodexWireProfileState::new(CodexWireProfile {
originator: "codex_cli_rs".to_owned(),
codex_version: "0.144.0".to_owned(),
desktop_version: "1.0.0".to_owned(),
desktop_build: "1".to_owned(),
os_type: "linux".to_owned(),
os_version: "6.8".to_owned(),
arch: "x86_64".to_owned(),
terminal: "xterm".to_owned(),
verified_at: Utc
.with_ymd_and_hms(2026, 7, 18, 0, 0, 0)
.single()
.expect("fixture time"),
});
let accounts = Arc::new(
ReqwestCodexAuthenticatedAccountSource::new(profile, OFFICIAL_CODEX_BASE_URL.to_owned())
.expect("official authenticated account source"),
);
let verifier = Arc::new(CodexAccountIdentityService::new(signed, accounts));
let service = CodexCredentialAdminService::new(
refresher,
verifier,
Arc::new(ManualLeases {
requests: Mutex::new(Vec::new()),
drops: Arc::new(AtomicUsize::new(0)),
available: true,
}),
runtime_policy(),
);
let prepared = service
.prepare_import_document(payload)
.await
.expect("real CPR import");
assert!(!prepared.accounts().is_empty());
for account in prepared.accounts() {
assert_eq!(account.account.provider().as_str(), "openai");
assert!(account.account.id().as_str().starts_with("acct_"));
assert!(account.account.upstream_account_id().is_some());
assert!(!account.account.upstream_user_id().is_empty());
let runtime =
CodexCredentialCodec::decode(&account.credential).expect("new credential schema");
let principal = runtime.principal.as_ref().expect("OAuth principal");
assert!(principal.poid.is_some());
assert_ne!(
principal.poid.as_deref(),
account.account.upstream_account_id()
);
assert_eq!(
uuid::Uuid::parse_str(&runtime.installation_id)
.expect("random installation UUID")
.get_version_num(),
4
);
}
}
@@ -1,7 +1,6 @@
use std::collections::VecDeque;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::{Arc, Mutex};
use std::time::Duration;
use async_trait::async_trait;
use base64::Engine as _;
@@ -18,8 +17,7 @@ use provider_openai::credential::{
CODEX_AUTHENTICATION_KIND_AGENT_IDENTITY, CodexAgentIdentityAuthMode,
CodexAgentIdentityCredentialData, CodexAgentIdentityError, CodexAgentIdentitySecret,
CodexAgentIdentityTaskRegistrar, CodexAgentIdentityTaskService, CodexCredentialCodec,
CodexRuntimeAuthentication, OfficialCodexAgentIdentityTaskRegistrar,
is_agent_identity_task_invalid_response,
CodexRuntimeAuthentication, is_agent_identity_task_invalid_response,
};
use provider_openai::transport::CodexWebSocketPool;
use reqwest::StatusCode;
@@ -260,56 +258,3 @@ async fn agent_identity_import_accepts_the_minimal_document_shape() {
assert_eq!(account.access_token_expires_at(), None);
assert!(!format!("{prepared:?}").contains("agent_private_key"));
}
#[tokio::test]
#[ignore = "requires CODEX_AGENT_IDENTITY_FIXTURE and CODEX_SUB2API_AGENT_FIXTURE; the first also registers a live task"]
async fn real_agent_identity_documents_import_and_register_contract() {
let paths = [
std::env::var("CODEX_AGENT_IDENTITY_FIXTURE").expect("minimal Agent Identity fixture"),
std::env::var("CODEX_SUB2API_AGENT_FIXTURE").expect("Sub2API Agent Identity fixture"),
];
let store = Arc::new(MemoryAccountStore::default());
let mut imported_accounts = Vec::new();
for path in paths {
let payload: Value =
serde_json::from_slice(&std::fs::read(path).expect("read Agent Identity fixture"))
.expect("parse Agent Identity fixture");
let prepared = import_service(unused_import_refresher())
.prepare_import_document(payload)
.await
.expect("prepare real Agent Identity import");
assert_eq!(prepared.accounts().len(), 1);
let account = prepared.accounts().first().expect("imported account");
assert_eq!(
account.account.authentication_kind(),
CODEX_AUTHENTICATION_KIND_AGENT_IDENTITY
);
assert!(!format!("{prepared:?}").contains("agent_private_key"));
store
.create_account(NewProviderAccount {
account: account.account.clone(),
credential: account.credential.clone(),
})
.await
.expect("store imported Agent Identity account");
imported_accounts.push(account.account.clone());
}
let client = reqwest::Client::builder()
.timeout(Duration::from_secs(30))
.build()
.expect("task registration client");
let registrar =
Arc::new(OfficialCodexAgentIdentityTaskRegistrar::new(client).expect("official registrar"));
let live_service = CodexAgentIdentityTaskService::new(
store.repository(),
registrar,
Arc::new(CodexWebSocketPool::default()),
);
for account in imported_accounts {
let _ = live_service
.prepare(&account)
.await
.expect("register or reuse live Agent Identity task");
}
}
+7 -155
View File
@@ -1,11 +1,11 @@
use std::collections::{BTreeMap, BTreeSet};
use std::collections::BTreeMap;
use std::num::NonZeroU32;
use std::sync::{Arc, Mutex};
use std::time::{Duration, SystemTime};
use base64::{Engine as _, engine::general_purpose::URL_SAFE_NO_PAD};
use chrono::{DateTime, Utc};
use futures::{StreamExt as _, future::BoxFuture};
use futures::future::BoxFuture;
use gateway_admin::model::accounts::AccountRecord;
use gateway_admin::model::observability::{
CurrencyCost, DesktopReleaseStatus, ProviderBillingInput,
@@ -17,17 +17,12 @@ use gateway_admin::model::provider_credentials::{
};
use gateway_admin::model::{MutationActor, MutationContext, Revision};
use gateway_admin::ports::provider::ProviderAdminErrorKind;
use gateway_core::engine::CancellationToken;
use gateway_core::engine::credential::{
AccountAvailability, AccountRuntimeSignals, AccountSelectionPolicy, CredentialRevision,
NewProviderAccount, OpaqueProviderData, PlaintextCredential, ProviderAccount,
ProviderAccountId, ProviderAccountStore, RotationStrategy,
AccountRuntimeSignals, CredentialRevision, OpaqueProviderData, ProviderAccount,
ProviderAccountId, ProviderAccountStore,
};
use gateway_core::engine::provider::ProviderRequest;
use gateway_core::engine::{
AccountAttemptContext, AttemptContext, CancellationToken, ModelRequestId, RequestAttemptContext,
};
use gateway_core::event::GatewayEvent;
use gateway_core::operation::{Operation, OperationKind};
use gateway_core::operation::Operation;
use gateway_core::policy::ClientApiKeyId;
use gateway_core::provider_ports::{
NewOAuthPendingFlow, OAuthPendingBinding, OAuthPendingClaimOutcome, OAuthPendingConsumeOutcome,
@@ -37,10 +32,7 @@ use gateway_core::provider_ports::{
ProviderLeaseAcquisition, ProviderLeasePort, ProviderLeaseRequest, ProviderRefreshPolicy,
ProviderRuntimePolicyPort, ProviderScopedCooldown, ProviderStoreError, ProviderStorePorts,
};
use gateway_core::routing::{
ConfigRevision, ModelCapabilities, ProviderKind, ProviderModel, PublicModelId, RoutingContext,
RuntimeSnapshot, UpstreamModelId,
};
use gateway_core::routing::{ProviderKind, UpstreamModelId};
use gateway_core::task::{
WorkerContribution, WorkerCycleContext, WorkerKind, WorkerRunnable, WorkerTaskError,
};
@@ -55,146 +47,6 @@ use crate::support::{
seed_input, xai_config,
};
#[tokio::test]
#[ignore = "requires XAI_REAL_ACCOUNT_FIXTURE and consumes live xAI quota; refresh token may rotate"]
async fn real_xai_conversation_crosses_production_provider_boundaries() {
assert_eq!(
std::env::var("XAI_ALLOW_DESTRUCTIVE_FIXTURE_REFRESH").as_deref(),
Ok("1"),
"set XAI_ALLOW_DESTRUCTIVE_FIXTURE_REFRESH=1 for the disposable real fixture",
);
let fixture = std::env::var("XAI_REAL_ACCOUNT_FIXTURE")
.expect("XAI_REAL_ACCOUNT_FIXTURE must point to a Grok JSON document");
let payload = serde_json::from_slice::<Value>(&std::fs::read(fixture).expect("read fixture"))
.expect("parse fixture");
let Value::Object(document) = payload else {
panic!("real xAI fixture must be an object");
};
let store = Arc::new(MemoryProviderAccountStore::default());
let bundle = provider_xai::initialize(
xai_config(),
provider_ports_with(Arc::clone(&store), Arc::new(TestOAuthPending::default())),
)
.await
.expect("xAI bundle");
let prepared = bundle
.admin_provider()
.prepare_import(PrepareCredentialImport {
document: ProviderDocument::new(OpaqueProviderData::new(document)),
})
.await
.expect("prepare real xAI import");
for credential in prepared.credentials {
store
.create_account(real_prepared_account(credential))
.await
.expect("seed verified xAI account");
}
let models = bundle
.core_provider()
.query_model_capabilities()
.await
.expect("query live xAI catalog");
let model = models.first().expect("one live generation model");
let operation = bundle
.admin_provider()
.connection_test_operation(model.upstream_model(), "Reply with exactly CPR_REAL_OK.")
.expect("real connection operation");
let request = real_planned_request(model.upstream_model().clone(), operation);
let mut stream = bundle
.core_provider()
.execute(request, real_xai_attempt_context())
.await
.expect("prepare real xAI stream");
let mut started = false;
let mut completed = false;
let mut usage = false;
let mut text = false;
while let Some(event) = stream.next().await {
for fact in event.expect("real xAI event").canonical_facts() {
match fact {
GatewayEvent::Started(_) => started = true,
GatewayEvent::TextDelta(_) => text = true,
GatewayEvent::Usage(_) => usage = true,
GatewayEvent::Completed(_) => completed = true,
_ => {}
}
}
}
assert!(started && text && usage && completed);
}
fn real_prepared_account(
value: gateway_admin::model::provider_credentials::PreparedCredentialCreate,
) -> NewProviderAccount {
let account = ProviderAccount::new(
value.account_id,
value.provider_kind,
value.name,
value.upstream_user_id,
value.authentication_kind,
CredentialRevision::new(1).expect("initial revision"),
value.access_token_expires_at.map(Into::into),
)
.with_profile(value.email, value.upstream_account_id, value.plan_type)
.with_runtime_state(value.enabled, AccountAvailability::Ready, None)
.with_refresh_schedule(
value.has_refresh_token,
value.next_refresh_at.map(Into::into),
);
NewProviderAccount {
account,
credential: PlaintextCredential::new(
value.provider_material.into_provider_data().into_inner(),
),
}
}
fn real_planned_request(upstream_model: UpstreamModelId, operation: Operation) -> ProviderRequest {
let provider = ProviderKind::new("xai").expect("provider");
let public_model = PublicModelId::new(upstream_model.as_str()).expect("public model");
let snapshot = RuntimeSnapshot::new(
ConfigRevision::new(1).expect("revision"),
real_selection_policy(),
vec![provider.clone()],
vec![ProviderModel::new(
provider,
upstream_model,
ModelCapabilities::new(BTreeSet::from([OperationKind::Generate]), None),
)],
vec![],
)
.expect("snapshot");
let plan = snapshot
.plan(&public_model, &operation, &RoutingContext::default())
.expect("route plan");
ProviderRequest::new(operation, plan.candidates()[0].clone())
}
fn real_selection_policy() -> AccountSelectionPolicy {
AccountSelectionPolicy::new(
RotationStrategy::Smart,
NonZeroU32::new(1).expect("concurrency"),
Duration::ZERO,
)
}
fn real_xai_attempt_context() -> AttemptContext {
AttemptContext::new(
RequestAttemptContext::new(
ModelRequestId::new("req_real_xai_conversation").expect("request"),
ClientApiKeyId::new("real_test_client").expect("client"),
),
NonZeroU32::new(1).expect("attempt"),
SystemTime::now() + Duration::from_secs(90),
real_selection_policy(),
AccountAttemptContext::new(BTreeSet::new(), None, None),
None,
CancellationToken::new(),
)
}
#[tokio::test]
async fn xai_bundle_exposes_core_admin_and_drains_worker_contributions_once() {
let mut bundle = provider_xai::initialize(xai_config(), provider_ports())
@@ -8,12 +8,11 @@ use provider_xai::{
FailClosedTokenVerifier, FailureClass, GrokCredentialAdmin, GrokOAuthClient, GrokOAuthConfig,
GrokOAuthImportCandidate, GrokOAuthImportDocument, GrokOAuthImportError,
GrokOAuthImportMetadata, GrokOAuthImportTokens, OAuthHttpRequest, OAuthHttpResponse,
OAuthHttpTransport, OfficialGrokEndpointPolicy, ReqwestOAuthTransport,
ReqwestOidcTokenVerifier, SecretValue, TokenCandidate, TokenVerificationContext, TokenVerifier,
OAuthHttpTransport, SecretValue, TokenCandidate, TokenVerificationContext, TokenVerifier,
TransportFuture, VerificationEvidence, VerificationFuture, VerifiedGrokAccount,
};
use crate::support::{account_id, refresh_policy};
use crate::support::account_id;
const DISCOVERY: &[u8] = include_bytes!("fixtures/discovery.json");
const INVALID_GRANT: &[u8] = include_bytes!("fixtures/invalid_grant.json");
@@ -319,80 +318,6 @@ fn duplicate_refresh_token_entries_should_collapse_to_the_first() {
assert_eq!(parsed.into_entries().len(), 2);
}
#[test]
#[ignore = "requires XAI_REAL_ACCOUNT_FIXTURE"]
fn real_oauth_account_should_match_provider_import_contract() {
let path = std::env::var_os("XAI_REAL_ACCOUNT_FIXTURE")
.expect("XAI_REAL_ACCOUNT_FIXTURE must point to a local secret fixture");
let document = std::fs::read(path).expect("read local secret fixture");
let expected_accounts = serde_json::from_slice::<serde_json::Value>(&document)
.expect("parse local secret fixture")
.get("accounts")
.and_then(serde_json::Value::as_array)
.map(Vec::len)
.filter(|count| *count > 0)
.expect("fixture must contain accounts");
let parsed = GrokOAuthImportDocument::parse_json(&document).expect("strict Provider import");
assert_eq!(parsed.into_entries().len(), expected_accounts);
}
#[tokio::test]
#[ignore = "requires a disposable XAI_REAL_ACCOUNT_FIXTURE and explicit destructive refresh approval"]
async fn real_oauth_accounts_should_cross_the_official_verification_boundary() {
assert_eq!(
std::env::var("XAI_ALLOW_DESTRUCTIVE_FIXTURE_REFRESH").as_deref(),
Ok("1"),
"official verification may rotate a refresh token; use a disposable fixture and set XAI_ALLOW_DESTRUCTIVE_FIXTURE_REFRESH=1",
);
let path = std::env::var_os("XAI_REAL_ACCOUNT_FIXTURE")
.expect("XAI_REAL_ACCOUNT_FIXTURE must point to a local secret fixture");
let document = std::fs::read(path).expect("read local secret fixture");
let entries = GrokOAuthImportDocument::parse_json(&document)
.expect("real document must match the import contract")
.into_entries();
let expected_accounts = entries.len();
let endpoint_policy = Arc::new(OfficialGrokEndpointPolicy);
let client = GrokOAuthClient::new(
GrokOAuthConfig::official().expect("official config"),
crate::support::xai_wire_profile(),
Arc::new(
ReqwestOAuthTransport::new(endpoint_policy.clone())
.expect("production OAuth transport"),
),
Arc::new(
ReqwestOidcTokenVerifier::new(endpoint_policy, std::time::Duration::from_secs(60 * 60))
.expect("production token verifier"),
),
);
let discovery = client.discover().await.expect("official discovery");
let mut prepared_accounts = 0_usize;
for (index, entry) in entries.into_iter().enumerate() {
let tokens = client
.verify_imported_credential(&discovery, entry.into_candidate())
.await
.expect("real OAuth credential must pass official verification");
let suffix = format!("real-import-{index}");
GrokCredentialAdmin
.prepare_verified_account(
&VerifiedGrokAccount {
account_id: account_id(&suffix),
name: suffix,
email: None,
upstream_account_id: None,
plan_type: None,
tokens,
enabled: true,
},
refresh_policy(),
)
.expect("verified real credential must cross account preparation");
prepared_accounts += 1;
}
assert_eq!(prepared_accounts, expected_accounts);
}
async fn verify(candidate: GrokOAuthImportCandidate) -> GrokOAuthImportError {
let client = GrokOAuthClient::new(
GrokOAuthConfig::official().expect("official config"),