mirror of
https://github.com/zyycn/codex-proxy-rs.git
synced 2026-11-07 00:05:49 +00:00
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:
@@ -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");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"),
|
||||
|
||||
Reference in New Issue
Block a user