mirror of
https://github.com/zyycn/codex-proxy-rs.git
synced 2026-11-13 08:06:41 +00:00
fix: hot reload fingerprint updates
Co-authored-by: Codex <noreply@openai.com>
This commit is contained in:
@@ -1033,7 +1033,8 @@ fn dashboard_usage_record_data(
|
||||
}
|
||||
|
||||
fn service_status_data(state: &AppState) -> Vec<DashboardServiceStatusData> {
|
||||
let fingerprint = fingerprint_diagnostics(&state.services.fingerprint);
|
||||
let current_fingerprint = state.services.fingerprint.snapshot();
|
||||
let fingerprint = fingerprint_diagnostics(¤t_fingerprint);
|
||||
vec![
|
||||
DashboardServiceStatusData {
|
||||
label: "客户端版本".to_string(),
|
||||
|
||||
@@ -13,7 +13,7 @@ use crate::runtime::services::{BackgroundTaskStores, Services};
|
||||
use crate::runtime::shutdown::{restart_executable_path, shutdown_signal};
|
||||
use crate::runtime::state::{AppState, RuntimeConfig};
|
||||
use crate::runtime::tasks::coordinator::TaskCoordinator;
|
||||
use crate::upstream::fingerprint::{Fingerprint, FingerprintRepository};
|
||||
use crate::upstream::fingerprint::{Fingerprint, FingerprintRepository, RuntimeFingerprint};
|
||||
|
||||
const RESTART_DELAY_ENV: &str = "CPR_RESTART_DELAY_MS";
|
||||
|
||||
@@ -81,6 +81,7 @@ async fn build_application(
|
||||
let runtime_fingerprint = fingerprint_repository
|
||||
.ensure_current_seed(&default_fingerprint)
|
||||
.await?;
|
||||
let runtime_fingerprint = RuntimeFingerprint::new(runtime_fingerprint);
|
||||
let runtime_config = RuntimeConfig::from(config.clone());
|
||||
let services = Services::try_with_installation_id(
|
||||
&config,
|
||||
|
||||
@@ -35,7 +35,7 @@ use crate::{
|
||||
},
|
||||
},
|
||||
upstream::{
|
||||
fingerprint::{Fingerprint, FingerprintRepository},
|
||||
fingerprint::{FingerprintRepository, RuntimeFingerprint},
|
||||
models::{
|
||||
config::ModelConfig,
|
||||
service::{ModelService, ModelServiceError},
|
||||
@@ -98,7 +98,7 @@ pub struct Services {
|
||||
pub session_affinity: StdArc<RuntimeSessionAffinityService>,
|
||||
pub codex: StdArc<CodexBackendClient>,
|
||||
pub websocket_pool: Option<StdArc<CodexWebSocketPool>>,
|
||||
pub fingerprint: Fingerprint,
|
||||
pub fingerprint: RuntimeFingerprint,
|
||||
pub installation_id: Option<String>,
|
||||
pub background_tasks: BackgroundTaskStores,
|
||||
}
|
||||
@@ -120,7 +120,11 @@ impl UsageRecordOptions {
|
||||
}
|
||||
|
||||
impl Services {
|
||||
pub fn new(config: &AppConfig, stores: BackgroundTaskStores, fingerprint: Fingerprint) -> Self {
|
||||
pub fn new(
|
||||
config: &AppConfig,
|
||||
stores: BackgroundTaskStores,
|
||||
fingerprint: RuntimeFingerprint,
|
||||
) -> Self {
|
||||
Self::try_new(config, stores, fingerprint)
|
||||
.expect("failed to build runtime services with configured TLS transport")
|
||||
}
|
||||
@@ -128,7 +132,7 @@ impl Services {
|
||||
pub fn try_new(
|
||||
config: &AppConfig,
|
||||
stores: BackgroundTaskStores,
|
||||
fingerprint: Fingerprint,
|
||||
fingerprint: RuntimeFingerprint,
|
||||
) -> Result<Self, CustomCaError> {
|
||||
Self::try_with_installation_id(config, stores, fingerprint, None)
|
||||
}
|
||||
@@ -136,7 +140,7 @@ impl Services {
|
||||
pub(crate) fn try_with_installation_id(
|
||||
config: &AppConfig,
|
||||
stores: BackgroundTaskStores,
|
||||
fingerprint: Fingerprint,
|
||||
fingerprint: RuntimeFingerprint,
|
||||
installation_id: Option<String>,
|
||||
) -> Result<Self, CustomCaError> {
|
||||
Self::try_with_installation_id_and_usage_record_options(
|
||||
@@ -151,7 +155,7 @@ impl Services {
|
||||
pub fn try_with_installation_id_and_usage_record_options(
|
||||
config: &AppConfig,
|
||||
stores: BackgroundTaskStores,
|
||||
fingerprint: Fingerprint,
|
||||
fingerprint: RuntimeFingerprint,
|
||||
installation_id: Option<String>,
|
||||
usage_record_options: UsageRecordOptions,
|
||||
) -> Result<Self, CustomCaError> {
|
||||
|
||||
@@ -86,16 +86,17 @@ impl TaskCoordinator {
|
||||
.with_account_pool(services.account_pool.clone())
|
||||
.start(),
|
||||
);
|
||||
coordinator.push(
|
||||
"fingerprint_update",
|
||||
coordinator.push("fingerprint_update", {
|
||||
let fingerprint = services.fingerprint.snapshot();
|
||||
start_fingerprint_update_task(
|
||||
Some(stores.fingerprints.clone()),
|
||||
stores.fingerprints.clone(),
|
||||
services.fingerprint.clone(),
|
||||
CODEX_DESKTOP_APPCAST_URL.to_string(),
|
||||
PathBuf::from(DEFAULT_EXTRACTED_FINGERPRINT_PATH),
|
||||
services.fingerprint.app_version.clone(),
|
||||
services.fingerprint.build_number.clone(),
|
||||
),
|
||||
);
|
||||
fingerprint.app_version,
|
||||
fingerprint.build_number,
|
||||
)
|
||||
});
|
||||
if let Some(pool) = &services.websocket_pool {
|
||||
coordinator.push(
|
||||
"websocket_pool",
|
||||
|
||||
@@ -2,9 +2,11 @@
|
||||
|
||||
use std::path::PathBuf;
|
||||
|
||||
use tracing::warn;
|
||||
use tracing::{info, warn};
|
||||
|
||||
use crate::upstream::fingerprint::{FingerprintRepository, UpdateChecker, APPCAST_POLL_INTERVAL};
|
||||
use crate::upstream::fingerprint::{
|
||||
FingerprintRepository, RuntimeFingerprint, UpdateChecker, APPCAST_POLL_INTERVAL,
|
||||
};
|
||||
|
||||
use super::{
|
||||
coordinator::SchedulerHandle,
|
||||
@@ -19,7 +21,8 @@ pub const DEFAULT_EXTRACTED_FINGERPRINT_PATH: &str = "data/extracted-fingerprint
|
||||
|
||||
/// 启动指纹自动更新后台任务。
|
||||
pub fn start_fingerprint_update_task(
|
||||
repository: Option<FingerprintRepository>,
|
||||
repository: FingerprintRepository,
|
||||
runtime_fingerprint: RuntimeFingerprint,
|
||||
appcast_url: String,
|
||||
extracted_fingerprint_path: PathBuf,
|
||||
current_version: String,
|
||||
@@ -36,6 +39,7 @@ pub fn start_fingerprint_update_task(
|
||||
spawn_periodic_task(
|
||||
FingerprintUpdateTask {
|
||||
checker: update_checker,
|
||||
runtime_fingerprint,
|
||||
first_tick: true,
|
||||
},
|
||||
PeriodicTaskConfig::new(
|
||||
@@ -48,6 +52,7 @@ pub fn start_fingerprint_update_task(
|
||||
|
||||
struct FingerprintUpdateTask {
|
||||
checker: UpdateChecker,
|
||||
runtime_fingerprint: RuntimeFingerprint,
|
||||
first_tick: bool,
|
||||
}
|
||||
|
||||
@@ -55,11 +60,24 @@ impl PeriodicTaskRunner for FingerprintUpdateTask {
|
||||
fn tick(&mut self) -> super::periodic::TaskFuture<'_, ()> {
|
||||
Box::pin(async move {
|
||||
let first_tick = std::mem::take(&mut self.first_tick);
|
||||
if let Err(error) = self.checker.check_and_apply_update().await {
|
||||
if first_tick {
|
||||
warn!(error = %error, "fingerprint 首次版本检查失败");
|
||||
} else {
|
||||
warn!(error = %error, "fingerprint 定期版本检查失败");
|
||||
match self.checker.check_and_apply_update().await {
|
||||
Ok(Some(fingerprint)) => {
|
||||
let app_version = fingerprint.app_version.clone();
|
||||
let build_number = fingerprint.build_number.clone();
|
||||
self.runtime_fingerprint.replace(fingerprint);
|
||||
info!(
|
||||
app_version = %app_version,
|
||||
build_number = %build_number,
|
||||
"fingerprint 运行时版本已更新"
|
||||
);
|
||||
}
|
||||
Ok(None) => {}
|
||||
Err(error) => {
|
||||
if first_tick {
|
||||
warn!(error = %error, "fingerprint 首次版本检查失败");
|
||||
} else {
|
||||
warn!(error = %error, "fingerprint 定期版本检查失败");
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
//! 指纹类型、更新与持久化。
|
||||
|
||||
use std::{path::PathBuf, sync::Arc, time::Duration};
|
||||
use std::{
|
||||
path::PathBuf,
|
||||
sync::{Arc, RwLock},
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
use chrono::{DateTime, Utc};
|
||||
use indexmap::IndexMap;
|
||||
@@ -89,6 +93,38 @@ impl Fingerprint {
|
||||
}
|
||||
}
|
||||
|
||||
/// 运行时共享指纹快照。
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct RuntimeFingerprint {
|
||||
current: Arc<RwLock<Arc<Fingerprint>>>,
|
||||
}
|
||||
|
||||
impl RuntimeFingerprint {
|
||||
pub fn new(fingerprint: Fingerprint) -> Self {
|
||||
Self {
|
||||
current: Arc::new(RwLock::new(Arc::new(fingerprint))),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn current(&self) -> Arc<Fingerprint> {
|
||||
self.current
|
||||
.read()
|
||||
.unwrap_or_else(std::sync::PoisonError::into_inner)
|
||||
.clone()
|
||||
}
|
||||
|
||||
pub fn snapshot(&self) -> Fingerprint {
|
||||
self.current().as_ref().clone()
|
||||
}
|
||||
|
||||
pub fn replace(&self, fingerprint: Fingerprint) {
|
||||
*self
|
||||
.current
|
||||
.write()
|
||||
.unwrap_or_else(std::sync::PoisonError::into_inner) = Arc::new(fingerprint);
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 指纹历史记录与仓储
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -345,7 +381,7 @@ fn json_error(error: serde_json::Error) -> sqlx::Error {
|
||||
/// Appcast 指纹更新检查器。
|
||||
#[derive(Clone)]
|
||||
pub struct UpdateChecker {
|
||||
repository: Option<FingerprintRepository>,
|
||||
repository: FingerprintRepository,
|
||||
client: reqwest::Client,
|
||||
appcast_url: String,
|
||||
extracted_fingerprint_path: PathBuf,
|
||||
@@ -360,7 +396,7 @@ struct InternalState {
|
||||
impl UpdateChecker {
|
||||
/// 使用自定义 client 和路径构造检查器。
|
||||
pub fn with_client(
|
||||
repository: Option<FingerprintRepository>,
|
||||
repository: FingerprintRepository,
|
||||
client: reqwest::Client,
|
||||
appcast_url: impl Into<String>,
|
||||
extracted_fingerprint_path: PathBuf,
|
||||
@@ -406,18 +442,18 @@ impl UpdateChecker {
|
||||
}
|
||||
|
||||
/// 检查并在需要时应用更新。
|
||||
pub async fn check_and_apply_update(&self) -> Result<bool, UpdateError> {
|
||||
pub async fn check_and_apply_update(&self) -> Result<Option<Fingerprint>, UpdateError> {
|
||||
let update_state = self.check_for_update().await?;
|
||||
|
||||
if !update_state.update_available {
|
||||
return Ok(false);
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
let (Some(version), Some(build)) = (
|
||||
update_state.latest_version.as_deref(),
|
||||
update_state.latest_build.as_deref(),
|
||||
) else {
|
||||
return Ok(false);
|
||||
return Ok(None);
|
||||
};
|
||||
|
||||
let state = self.state.lock().await;
|
||||
@@ -433,13 +469,13 @@ impl UpdateChecker {
|
||||
"发现新的 fingerprint 版本"
|
||||
);
|
||||
|
||||
self.apply_version_update(version, build).await?;
|
||||
let updated_fingerprint = self.apply_version_update(version, build).await?;
|
||||
|
||||
let mut state = self.state.lock().await;
|
||||
state.current_version = version.to_string();
|
||||
state.current_build = build.to_string();
|
||||
|
||||
Ok(true)
|
||||
Ok(Some(updated_fingerprint))
|
||||
}
|
||||
|
||||
async fn fetch_appcast(&self) -> Result<String, UpdateError> {
|
||||
@@ -457,20 +493,26 @@ impl UpdateChecker {
|
||||
response.text().await.map_err(UpdateError::from)
|
||||
}
|
||||
|
||||
async fn apply_version_update(&self, version: &str, build: &str) -> Result<(), UpdateError> {
|
||||
async fn apply_version_update(
|
||||
&self,
|
||||
version: &str,
|
||||
build: &str,
|
||||
) -> Result<Fingerprint, UpdateError> {
|
||||
let chromium_version =
|
||||
load_matching_chromium_version(&self.extracted_fingerprint_path, version, build);
|
||||
|
||||
if let Some(repository) = &self.repository {
|
||||
repository
|
||||
.update_current_version(version, build, chromium_version.as_deref())
|
||||
.await?;
|
||||
repository
|
||||
.insert_update_history(version, build, chromium_version.as_deref(), None)
|
||||
.await?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
self.repository
|
||||
.update_current_version(version, build, chromium_version.as_deref())
|
||||
.await?;
|
||||
self.repository
|
||||
.insert_update_history(version, build, chromium_version.as_deref(), None)
|
||||
.await?;
|
||||
let updated = self
|
||||
.repository
|
||||
.load_current()
|
||||
.await?
|
||||
.ok_or(sqlx::Error::RowNotFound)?;
|
||||
Ok(updated)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@ use thiserror::Error;
|
||||
use tokio_tungstenite::tungstenite::handshake::client::generate_key;
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::upstream::fingerprint::Fingerprint;
|
||||
use crate::upstream::fingerprint::{Fingerprint, RuntimeFingerprint};
|
||||
use crate::upstream::models::backend_entry::BackendModelEntry;
|
||||
use crate::upstream::protocol::events::{extract_sse_usage, retry_after_seconds_from_body};
|
||||
use crate::upstream::protocol::responses::{
|
||||
@@ -333,14 +333,18 @@ pub struct CodexCompactResponse {
|
||||
pub struct CodexBackendClient {
|
||||
pub(super) client: Client,
|
||||
pub(super) base_url: String,
|
||||
fingerprint: Fingerprint,
|
||||
fingerprint: RuntimeFingerprint,
|
||||
websocket_pool: Option<Arc<CodexWebSocketPool>>,
|
||||
websocket_first_token_timeout: Option<Duration>,
|
||||
}
|
||||
|
||||
impl CodexBackendClient {
|
||||
/// 构造客户端。
|
||||
pub fn new(client: Client, base_url: impl Into<String>, fingerprint: Fingerprint) -> Self {
|
||||
pub fn new(
|
||||
client: Client,
|
||||
base_url: impl Into<String>,
|
||||
fingerprint: RuntimeFingerprint,
|
||||
) -> Self {
|
||||
Self {
|
||||
client,
|
||||
base_url: base_url.into().trim_end_matches('/').to_string(),
|
||||
@@ -798,17 +802,18 @@ impl CodexBackendClient {
|
||||
&self,
|
||||
context: CodexRequestContext<'_>,
|
||||
) -> CodexClientResult<Vec<BackendModelEntry>> {
|
||||
let fingerprint = self.fingerprint.current();
|
||||
let endpoints = [
|
||||
format!(
|
||||
"{}/codex/models?client_version={}",
|
||||
self.base_url, self.fingerprint.app_version
|
||||
self.base_url, fingerprint.app_version
|
||||
),
|
||||
format!("{}/models", self.base_url),
|
||||
format!("{}/sentinel/chat-requirements", self.base_url),
|
||||
];
|
||||
|
||||
for endpoint in endpoints {
|
||||
let headers = self.auxiliary_request_headers(context)?;
|
||||
let headers = self.auxiliary_request_headers(&fingerprint, context)?;
|
||||
let response = self.client.get(endpoint).headers(headers).send().await?;
|
||||
if !response.status().is_success() {
|
||||
continue;
|
||||
@@ -845,9 +850,10 @@ impl CodexBackendClient {
|
||||
}
|
||||
|
||||
fn request_headers(&self, context: CodexRequestContext<'_>) -> CodexClientResult<HeaderMap> {
|
||||
let fingerprint = self.fingerprint.current();
|
||||
let request_id = context.session_id.unwrap_or(context.request_id);
|
||||
let ordered_headers = build_ordered_codex_base_headers(
|
||||
&self.fingerprint,
|
||||
&fingerprint,
|
||||
context.access_token,
|
||||
context.account_id,
|
||||
);
|
||||
@@ -897,13 +903,11 @@ impl CodexBackendClient {
|
||||
|
||||
fn auxiliary_request_headers(
|
||||
&self,
|
||||
fingerprint: &Fingerprint,
|
||||
context: CodexRequestContext<'_>,
|
||||
) -> CodexClientResult<HeaderMap> {
|
||||
let ordered_headers = build_ordered_codex_base_headers(
|
||||
&self.fingerprint,
|
||||
context.access_token,
|
||||
context.account_id,
|
||||
);
|
||||
let ordered_headers =
|
||||
build_ordered_codex_base_headers(fingerprint, context.access_token, context.account_id);
|
||||
|
||||
let mut headers = HeaderMap::new();
|
||||
insert_ordered_headers(&mut headers, &ordered_headers)?;
|
||||
@@ -924,10 +928,11 @@ impl CodexBackendClient {
|
||||
&self,
|
||||
context: CodexRequestContext<'_>,
|
||||
) -> CodexClientResult<HeaderMap> {
|
||||
let fingerprint = self.fingerprint.current();
|
||||
let mut headers = HeaderMap::new();
|
||||
headers.insert(
|
||||
USER_AGENT,
|
||||
HeaderValue::from_str(&self.fingerprint.user_agent())?,
|
||||
HeaderValue::from_str(&fingerprint.user_agent())?,
|
||||
);
|
||||
headers.insert(
|
||||
AUTHORIZATION,
|
||||
@@ -935,7 +940,7 @@ impl CodexBackendClient {
|
||||
);
|
||||
headers.insert(
|
||||
HeaderName::from_static("originator"),
|
||||
HeaderValue::from_str(&self.fingerprint.originator)?,
|
||||
HeaderValue::from_str(&fingerprint.originator)?,
|
||||
);
|
||||
insert_optional_header(&mut headers, "chatgpt-account-id", context.account_id)?;
|
||||
insert_optional_header(&mut headers, "cookie", context.cookie_header)?;
|
||||
@@ -947,8 +952,9 @@ impl CodexBackendClient {
|
||||
&self,
|
||||
context: CodexRequestContext<'_>,
|
||||
) -> CodexClientResult<HeaderMap> {
|
||||
let fingerprint = self.fingerprint.current();
|
||||
let ordered_headers = build_ordered_codex_base_headers(
|
||||
&self.fingerprint,
|
||||
&fingerprint,
|
||||
context.access_token,
|
||||
context.account_id,
|
||||
);
|
||||
|
||||
@@ -30,7 +30,11 @@ async fn admin_accounts_import_should_store_cpr_account_tokens() {
|
||||
usage_records: SqliteUsageRecordStore::new(pool.clone()),
|
||||
};
|
||||
let fingerprint = crate::support::fingerprint::test_fingerprint();
|
||||
let services = Arc::new(Services::new(&config, stores, fingerprint));
|
||||
let services = Arc::new(Services::new(
|
||||
&config,
|
||||
stores,
|
||||
runtime_fingerprint(fingerprint),
|
||||
));
|
||||
let state = AppState {
|
||||
services: (*services).clone(),
|
||||
};
|
||||
@@ -639,7 +643,11 @@ async fn admin_accounts_import_should_fetch_usage_to_complete_missing_plan_and_q
|
||||
usage_records: SqliteUsageRecordStore::new(pool.clone()),
|
||||
};
|
||||
let fingerprint = crate::support::fingerprint::test_fingerprint();
|
||||
let services = Arc::new(Services::new(&config, stores, fingerprint));
|
||||
let services = Arc::new(Services::new(
|
||||
&config,
|
||||
stores,
|
||||
runtime_fingerprint(fingerprint),
|
||||
));
|
||||
let state = AppState {
|
||||
services: (*services).clone(),
|
||||
};
|
||||
@@ -855,7 +863,11 @@ async fn admin_accounts_import_should_require_admin_session_cookie() {
|
||||
usage_records: SqliteUsageRecordStore::new(pool.clone()),
|
||||
};
|
||||
let fingerprint = crate::support::fingerprint::test_fingerprint();
|
||||
let services = Arc::new(Services::new(&config, stores, fingerprint));
|
||||
let services = Arc::new(Services::new(
|
||||
&config,
|
||||
stores,
|
||||
runtime_fingerprint(fingerprint),
|
||||
));
|
||||
let state = AppState {
|
||||
services: (*services).clone(),
|
||||
};
|
||||
|
||||
@@ -23,7 +23,11 @@ async fn admin_accounts_lifecycle_should_update_and_delete_accounts() {
|
||||
usage_records: SqliteUsageRecordStore::new(pool.clone()),
|
||||
};
|
||||
let fingerprint = crate::support::fingerprint::test_fingerprint();
|
||||
let services = Arc::new(Services::new(&config, stores, fingerprint));
|
||||
let services = Arc::new(Services::new(
|
||||
&config,
|
||||
stores,
|
||||
runtime_fingerprint(fingerprint),
|
||||
));
|
||||
let state = AppState {
|
||||
services: (*services).clone(),
|
||||
};
|
||||
@@ -748,7 +752,11 @@ async fn admin_account_create_should_derive_claims_and_store_plain_tokens() {
|
||||
usage_records: SqliteUsageRecordStore::new(pool.clone()),
|
||||
};
|
||||
let fingerprint = crate::support::fingerprint::test_fingerprint();
|
||||
let services = Arc::new(Services::new(&config, stores, fingerprint));
|
||||
let services = Arc::new(Services::new(
|
||||
&config,
|
||||
stores,
|
||||
runtime_fingerprint(fingerprint),
|
||||
));
|
||||
let state = AppState {
|
||||
services: (*services).clone(),
|
||||
};
|
||||
@@ -791,7 +799,11 @@ async fn admin_account_manual_create_should_reject_missing_invalid_expired_or_un
|
||||
usage_records: SqliteUsageRecordStore::new(pool.clone()),
|
||||
};
|
||||
let fingerprint = crate::support::fingerprint::test_fingerprint();
|
||||
let services = Arc::new(Services::new(&config, stores, fingerprint));
|
||||
let services = Arc::new(Services::new(
|
||||
&config,
|
||||
stores,
|
||||
runtime_fingerprint(fingerprint),
|
||||
));
|
||||
let state = AppState {
|
||||
services: (*services).clone(),
|
||||
};
|
||||
|
||||
@@ -27,7 +27,11 @@ async fn admin_accounts_list_should_not_expose_account_tokens() {
|
||||
usage_records: SqliteUsageRecordStore::new(pool.clone()),
|
||||
};
|
||||
let fingerprint = crate::support::fingerprint::test_fingerprint();
|
||||
let services = Arc::new(Services::new(&config, stores, fingerprint));
|
||||
let services = Arc::new(Services::new(
|
||||
&config,
|
||||
stores,
|
||||
runtime_fingerprint(fingerprint),
|
||||
));
|
||||
let state = AppState {
|
||||
services: (*services).clone(),
|
||||
};
|
||||
|
||||
@@ -26,7 +26,10 @@ use sqlx::SqlitePool;
|
||||
use tower::util::ServiceExt;
|
||||
|
||||
use crate::support::jwt::unsigned_jwt;
|
||||
use crate::support::{admin::seed_admin_session, config::test_config, http::response_json};
|
||||
use crate::support::{
|
||||
admin::seed_admin_session, config::test_config, fingerprint::runtime_fingerprint,
|
||||
http::response_json,
|
||||
};
|
||||
|
||||
mod exporting;
|
||||
mod importing;
|
||||
@@ -170,7 +173,11 @@ async fn admin_accounts_test_app_with_overrides(
|
||||
usage_records: SqliteUsageRecordStore::new(pool.clone()),
|
||||
};
|
||||
let fingerprint = crate::support::fingerprint::test_fingerprint();
|
||||
let services = Arc::new(Services::new(&config, stores, fingerprint));
|
||||
let services = Arc::new(Services::new(
|
||||
&config,
|
||||
stores,
|
||||
runtime_fingerprint(fingerprint),
|
||||
));
|
||||
let state = AppState {
|
||||
services: (*services).clone(),
|
||||
};
|
||||
|
||||
@@ -7,7 +7,7 @@ use codex_proxy_rs::infra::{database::connect_sqlite, identity::hash_admin_passw
|
||||
use sqlx::SqlitePool;
|
||||
use tower::util::ServiceExt;
|
||||
|
||||
use crate::support::{config::test_config, http::response_json};
|
||||
use crate::support::{config::test_config, fingerprint::runtime_fingerprint, http::response_json};
|
||||
|
||||
#[tokio::test]
|
||||
async fn admin_login_should_issue_http_only_session_cookie() {
|
||||
@@ -202,7 +202,7 @@ async fn admin_login_test_app(
|
||||
let services = std::sync::Arc::new(codex_proxy_rs::runtime::services::Services::new(
|
||||
&config,
|
||||
stores,
|
||||
fingerprint,
|
||||
runtime_fingerprint(fingerprint),
|
||||
));
|
||||
let state = codex_proxy_rs::runtime::state::AppState {
|
||||
services: (*services).clone(),
|
||||
|
||||
@@ -17,7 +17,10 @@ use codex_proxy_rs::{
|
||||
use serde_json::json;
|
||||
use tower::util::ServiceExt;
|
||||
|
||||
use crate::support::{admin::seed_admin_session, config::test_config, http::response_json};
|
||||
use crate::support::{
|
||||
admin::seed_admin_session, config::test_config, fingerprint::runtime_fingerprint,
|
||||
http::response_json,
|
||||
};
|
||||
|
||||
mod authorization;
|
||||
mod lifecycle;
|
||||
@@ -41,7 +44,11 @@ async fn admin_client_key_test_app(db_name: &str) -> (axum::Router, tempfile::Te
|
||||
usage_records: SqliteUsageRecordStore::new(pool.clone()),
|
||||
};
|
||||
let fingerprint = crate::support::fingerprint::test_fingerprint();
|
||||
let services = std::sync::Arc::new(Services::new(&config, stores, fingerprint));
|
||||
let services = std::sync::Arc::new(Services::new(
|
||||
&config,
|
||||
stores,
|
||||
runtime_fingerprint(fingerprint),
|
||||
));
|
||||
let state = AppState {
|
||||
services: (*services).clone(),
|
||||
};
|
||||
|
||||
@@ -32,7 +32,9 @@ use serde_json::{json, Value};
|
||||
use sqlx::SqlitePool;
|
||||
use tower::util::ServiceExt;
|
||||
|
||||
use crate::support::{admin::seed_admin_session, http::response_json};
|
||||
use crate::support::{
|
||||
admin::seed_admin_session, fingerprint::runtime_fingerprint, http::response_json,
|
||||
};
|
||||
|
||||
#[tokio::test]
|
||||
async fn dashboard_summary_should_render_fingerprint_updated_at_as_beijing_time() {
|
||||
@@ -654,7 +656,11 @@ async fn dashboard_test_app(
|
||||
client_keys: SqliteClientKeyStore::new(pool.clone()),
|
||||
usage_records: SqliteUsageRecordStore::new(pool.clone()),
|
||||
};
|
||||
let services = std::sync::Arc::new(Services::new(&config, stores, fingerprint));
|
||||
let services = std::sync::Arc::new(Services::new(
|
||||
&config,
|
||||
stores,
|
||||
runtime_fingerprint(fingerprint),
|
||||
));
|
||||
let state = AppState {
|
||||
services: (*services).clone(),
|
||||
};
|
||||
|
||||
@@ -23,7 +23,10 @@ use serde_json::json;
|
||||
use sqlx::SqlitePool;
|
||||
use tower::util::ServiceExt;
|
||||
|
||||
use crate::support::{admin::seed_admin_session, config::test_config, http::response_json};
|
||||
use crate::support::{
|
||||
admin::seed_admin_session, config::test_config, fingerprint::runtime_fingerprint,
|
||||
http::response_json,
|
||||
};
|
||||
|
||||
#[tokio::test]
|
||||
async fn admin_usage_records_should_require_admin_session_cookie() {
|
||||
@@ -883,7 +886,11 @@ async fn admin_usage_records_test_app_with_config(
|
||||
usage_records: SqliteUsageRecordStore::new(pool.clone()),
|
||||
};
|
||||
let fingerprint = crate::support::fingerprint::test_fingerprint();
|
||||
let services = std::sync::Arc::new(Services::new(&config, stores, fingerprint));
|
||||
let services = std::sync::Arc::new(Services::new(
|
||||
&config,
|
||||
stores,
|
||||
runtime_fingerprint(fingerprint),
|
||||
));
|
||||
let state = AppState {
|
||||
services: (*services).clone(),
|
||||
};
|
||||
|
||||
@@ -28,7 +28,8 @@ use sqlx::SqlitePool;
|
||||
use tower::util::ServiceExt;
|
||||
|
||||
use crate::support::{
|
||||
admin::seed_admin_session, config::test_config as base_test_config, http::response_json,
|
||||
admin::seed_admin_session, config::test_config as base_test_config,
|
||||
fingerprint::runtime_fingerprint, http::response_json,
|
||||
};
|
||||
|
||||
#[tokio::test]
|
||||
@@ -277,7 +278,7 @@ async fn admin_settings_update_should_persist_runtime_settings_to_database() {
|
||||
usage_records: SqliteUsageRecordStore::new(pool.clone()),
|
||||
};
|
||||
let fingerprint = crate::support::fingerprint::test_fingerprint();
|
||||
let services = Services::new(&config, stores, fingerprint);
|
||||
let services = Services::new(&config, stores, runtime_fingerprint(fingerprint));
|
||||
let services = std::sync::Arc::new(services);
|
||||
let state = AppState {
|
||||
services: (*services).clone(),
|
||||
@@ -382,7 +383,11 @@ async fn admin_settings_update_should_apply_runtime_services() {
|
||||
usage_records: SqliteUsageRecordStore::new(pool.clone()),
|
||||
};
|
||||
let fingerprint = crate::support::fingerprint::test_fingerprint();
|
||||
let services = std::sync::Arc::new(Services::new(&config, stores, fingerprint));
|
||||
let services = std::sync::Arc::new(Services::new(
|
||||
&config,
|
||||
stores,
|
||||
runtime_fingerprint(fingerprint),
|
||||
));
|
||||
services
|
||||
.account_pool
|
||||
.restore_from_repository()
|
||||
@@ -480,7 +485,11 @@ async fn admin_settings_update_should_reject_unsupported_or_invalid_fields() {
|
||||
usage_records: SqliteUsageRecordStore::new(pool.clone()),
|
||||
};
|
||||
let fingerprint = crate::support::fingerprint::test_fingerprint();
|
||||
let services = std::sync::Arc::new(Services::new(&config, stores, fingerprint));
|
||||
let services = std::sync::Arc::new(Services::new(
|
||||
&config,
|
||||
stores,
|
||||
runtime_fingerprint(fingerprint),
|
||||
));
|
||||
let state = AppState {
|
||||
services: (*services).clone(),
|
||||
};
|
||||
@@ -522,7 +531,11 @@ async fn admin_settings_test_app(db_name: &str) -> (axum::Router, tempfile::Temp
|
||||
usage_records: SqliteUsageRecordStore::new(pool.clone()),
|
||||
};
|
||||
let fingerprint = crate::support::fingerprint::test_fingerprint();
|
||||
let services = std::sync::Arc::new(Services::new(&config, stores, fingerprint));
|
||||
let services = std::sync::Arc::new(Services::new(
|
||||
&config,
|
||||
stores,
|
||||
runtime_fingerprint(fingerprint),
|
||||
));
|
||||
let state = AppState {
|
||||
services: (*services).clone(),
|
||||
};
|
||||
|
||||
@@ -35,7 +35,9 @@ use wiremock::{
|
||||
};
|
||||
|
||||
use crate::support::{
|
||||
admin::seed_admin_session, config::test_config, fingerprint::test_fingerprint,
|
||||
admin::seed_admin_session,
|
||||
config::test_config,
|
||||
fingerprint::{runtime_fingerprint, test_fingerprint},
|
||||
http::response_json,
|
||||
};
|
||||
|
||||
@@ -1218,7 +1220,11 @@ async fn admin_system_test_app(db_name: &str) -> (axum::Router, tempfile::TempDi
|
||||
seed_admin_session(&pool, "session_1").await;
|
||||
let config = test_config(url);
|
||||
let stores = stores(pool);
|
||||
let services = Arc::new(Services::new(&config, stores, test_fingerprint()));
|
||||
let services = Arc::new(Services::new(
|
||||
&config,
|
||||
stores,
|
||||
runtime_fingerprint(test_fingerprint()),
|
||||
));
|
||||
let state = AppState {
|
||||
services: (*services).clone(),
|
||||
};
|
||||
|
||||
@@ -10,7 +10,9 @@ use serde_json::Value;
|
||||
use sqlx::SqlitePool;
|
||||
use tower::ServiceExt;
|
||||
|
||||
use crate::support::{config::test_config as base_test_config, http::response_json};
|
||||
use crate::support::{
|
||||
config::test_config as base_test_config, fingerprint::runtime_fingerprint, http::response_json,
|
||||
};
|
||||
|
||||
#[tokio::test]
|
||||
async fn server_router_should_serve_frontend_assets_without_shadowing_api_routes() {
|
||||
@@ -200,7 +202,7 @@ async fn router_with_dist_and_database(dist: &Path, database_url: &str) -> (Rout
|
||||
let services = std::sync::Arc::new(codex_proxy_rs::runtime::services::Services::new(
|
||||
&config,
|
||||
stores,
|
||||
fingerprint,
|
||||
runtime_fingerprint(fingerprint),
|
||||
));
|
||||
let state = codex_proxy_rs::runtime::state::AppState {
|
||||
services: (*services).clone(),
|
||||
|
||||
@@ -271,7 +271,7 @@ async fn test_app_state_with_pool_installation_id_and_usage_record_options(
|
||||
let services = Services::try_with_installation_id_and_usage_record_options(
|
||||
&config,
|
||||
stores,
|
||||
crate::support::fingerprint::test_fingerprint(),
|
||||
crate::support::fingerprint::runtime_test_fingerprint(),
|
||||
Some(installation_id),
|
||||
usage_record_options,
|
||||
)
|
||||
|
||||
@@ -20,7 +20,8 @@ use codex_proxy_rs::{
|
||||
use tower::util::ServiceExt;
|
||||
|
||||
use crate::support::{
|
||||
client_keys::insert_client_api_key, config::test_config, http::response_json,
|
||||
client_keys::insert_client_api_key, config::test_config, fingerprint::runtime_fingerprint,
|
||||
http::response_json,
|
||||
};
|
||||
|
||||
#[tokio::test]
|
||||
@@ -164,7 +165,11 @@ async fn build_test_app(
|
||||
usage_records: SqliteUsageRecordStore::new(pool.clone()),
|
||||
};
|
||||
let fingerprint = crate::support::fingerprint::test_fingerprint();
|
||||
let services = std::sync::Arc::new(Services::new(&config, stores, fingerprint));
|
||||
let services = std::sync::Arc::new(Services::new(
|
||||
&config,
|
||||
stores,
|
||||
runtime_fingerprint(fingerprint),
|
||||
));
|
||||
services
|
||||
.initialize_hot_path_state()
|
||||
.await
|
||||
|
||||
@@ -18,7 +18,8 @@ use sqlx::SqlitePool;
|
||||
use tower::util::ServiceExt;
|
||||
|
||||
use crate::support::{
|
||||
client_keys::insert_client_api_key, config::test_config, http::response_json,
|
||||
client_keys::insert_client_api_key, config::test_config, fingerprint::runtime_fingerprint,
|
||||
http::response_json,
|
||||
};
|
||||
|
||||
#[tokio::test]
|
||||
@@ -154,7 +155,11 @@ async fn test_app_with_client_api_key() -> (axum::Router, String, tempfile::Temp
|
||||
usage_records: SqliteUsageRecordStore::new(pool.clone()),
|
||||
};
|
||||
let fingerprint = crate::support::fingerprint::test_fingerprint();
|
||||
let services = std::sync::Arc::new(Services::new(&config, stores, fingerprint));
|
||||
let services = std::sync::Arc::new(Services::new(
|
||||
&config,
|
||||
stores,
|
||||
runtime_fingerprint(fingerprint),
|
||||
));
|
||||
services
|
||||
.initialize_hot_path_state()
|
||||
.await
|
||||
|
||||
@@ -49,7 +49,7 @@ async fn services_try_new_should_use_configured_tls_transport_builder() {
|
||||
let Err(error) = Services::try_new(
|
||||
&config,
|
||||
stores,
|
||||
crate::support::fingerprint::test_fingerprint(),
|
||||
crate::support::fingerprint::runtime_test_fingerprint(),
|
||||
) else {
|
||||
panic!("invalid custom CA should fail service transport construction");
|
||||
};
|
||||
@@ -90,7 +90,7 @@ async fn services_try_new_should_expose_websocket_pool_when_enabled() {
|
||||
let services = Services::try_new(
|
||||
&config,
|
||||
stores(pool),
|
||||
crate::support::fingerprint::test_fingerprint(),
|
||||
crate::support::fingerprint::runtime_test_fingerprint(),
|
||||
)
|
||||
.expect("services should build");
|
||||
|
||||
@@ -111,7 +111,7 @@ async fn services_try_new_should_not_expose_websocket_pool_when_disabled() {
|
||||
let services = Services::try_new(
|
||||
&config,
|
||||
stores(pool),
|
||||
crate::support::fingerprint::test_fingerprint(),
|
||||
crate::support::fingerprint::runtime_test_fingerprint(),
|
||||
)
|
||||
.expect("services should build");
|
||||
|
||||
|
||||
@@ -11,9 +11,11 @@ async fn fingerprint_update_task_should_start_background_checker() {
|
||||
repo.ensure_current_seed(&crate::support::fingerprint::test_fingerprint())
|
||||
.await
|
||||
.expect("seed current fingerprint");
|
||||
let runtime_fingerprint = runtime_fingerprint_from_repo(&repo).await;
|
||||
|
||||
let handle = codex_proxy_rs::runtime::tasks::fingerprint_update::start_fingerprint_update_task(
|
||||
Some(repo),
|
||||
repo,
|
||||
runtime_fingerprint,
|
||||
"https://example.invalid/appcast.xml".to_string(),
|
||||
dir.path().join("extracted-fingerprint.json"),
|
||||
"26.800.1".to_string(),
|
||||
@@ -49,6 +51,7 @@ async fn fingerprint_update_task_should_apply_initial_appcast_update_to_reposito
|
||||
repo.ensure_current_seed(&crate::support::fingerprint::test_fingerprint())
|
||||
.await
|
||||
.expect("seed current fingerprint");
|
||||
let runtime_fingerprint = runtime_fingerprint_from_repo(&repo).await;
|
||||
let extracted_path = dir.path().join("extracted-fingerprint.json");
|
||||
std::fs::write(
|
||||
&extracted_path,
|
||||
@@ -57,7 +60,8 @@ async fn fingerprint_update_task_should_apply_initial_appcast_update_to_reposito
|
||||
.expect("extracted fingerprint should be written");
|
||||
|
||||
let handle = codex_proxy_rs::runtime::tasks::fingerprint_update::start_fingerprint_update_task(
|
||||
Some(repo.clone()),
|
||||
repo.clone(),
|
||||
runtime_fingerprint.clone(),
|
||||
format!("{}/appcast.xml", server.uri()),
|
||||
extracted_path,
|
||||
"26.800.1".to_string(),
|
||||
@@ -75,6 +79,15 @@ async fn fingerprint_update_task_should_apply_initial_appcast_update_to_reposito
|
||||
),
|
||||
("26.900.1", "7001", "147")
|
||||
);
|
||||
let runtime_snapshot = runtime_fingerprint.snapshot();
|
||||
assert_eq!(
|
||||
(
|
||||
runtime_snapshot.app_version.as_str(),
|
||||
runtime_snapshot.build_number.as_str(),
|
||||
runtime_snapshot.chromium_version.as_str()
|
||||
),
|
||||
("26.900.1", "7001", "147")
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
@@ -103,8 +116,10 @@ async fn fingerprint_update_task_should_not_persist_when_appcast_matches_current
|
||||
repo.ensure_current_seed(&crate::support::fingerprint::test_fingerprint())
|
||||
.await
|
||||
.expect("seed current fingerprint");
|
||||
let runtime_fingerprint = runtime_fingerprint_from_repo(&repo).await;
|
||||
let handle = codex_proxy_rs::runtime::tasks::fingerprint_update::start_fingerprint_update_task(
|
||||
Some(repo.clone()),
|
||||
repo.clone(),
|
||||
runtime_fingerprint,
|
||||
format!("{}/appcast.xml", server.uri()),
|
||||
dir.path().join("missing-extracted-fingerprint.json"),
|
||||
"26.900.1".to_string(),
|
||||
@@ -120,33 +135,3 @@ async fn fingerprint_update_task_should_not_persist_when_appcast_matches_current
|
||||
|
||||
assert_eq!(history_count, 0);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn fingerprint_update_task_should_check_appcast_without_repository() {
|
||||
let server = MockServer::start().await;
|
||||
mount_appcast(
|
||||
&server,
|
||||
r#"
|
||||
<rss>
|
||||
<channel>
|
||||
<item>
|
||||
<enclosure url="https://example.invalid/download" sparkle:shortVersionString="26.900.1" sparkle:version="7001" />
|
||||
</item>
|
||||
</channel>
|
||||
</rss>
|
||||
"#,
|
||||
)
|
||||
.await;
|
||||
|
||||
let dir = tempfile::tempdir().expect("temp dir");
|
||||
let handle = codex_proxy_rs::runtime::tasks::fingerprint_update::start_fingerprint_update_task(
|
||||
None,
|
||||
format!("{}/appcast.xml", server.uri()),
|
||||
dir.path().join("missing-extracted-fingerprint.json"),
|
||||
"26.800.1".to_string(),
|
||||
"6001".to_string(),
|
||||
);
|
||||
|
||||
wait_for_appcast_requests(&server, 1).await;
|
||||
handle.shutdown().await;
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ use codex_proxy_rs::upstream::accounts::cookies::SqliteCookieStore;
|
||||
use codex_proxy_rs::upstream::accounts::model::{Account, AccountStatus};
|
||||
use codex_proxy_rs::upstream::accounts::model::{AccountModelUsageDelta, AccountUsageDelta};
|
||||
use codex_proxy_rs::upstream::accounts::store::{AccountStore, AccountStoreResult};
|
||||
use codex_proxy_rs::upstream::fingerprint::FingerprintRepository;
|
||||
use codex_proxy_rs::upstream::fingerprint::{FingerprintRepository, RuntimeFingerprint};
|
||||
use codex_proxy_rs::upstream::models::config::ModelConfig;
|
||||
use codex_proxy_rs::upstream::models::service::ModelService;
|
||||
use wiremock::{
|
||||
@@ -137,6 +137,15 @@ async fn wait_for_current_fingerprint_version(
|
||||
}
|
||||
}
|
||||
|
||||
async fn runtime_fingerprint_from_repo(repo: &FingerprintRepository) -> RuntimeFingerprint {
|
||||
RuntimeFingerprint::new(
|
||||
repo.load_current()
|
||||
.await
|
||||
.expect("current fingerprint should load")
|
||||
.expect("current fingerprint should exist"),
|
||||
)
|
||||
}
|
||||
|
||||
async fn wait_for_appcast_requests(server: &MockServer, expected_count: usize) {
|
||||
let deadline = tokio::time::Instant::now() + StdDuration::from_secs(2);
|
||||
loop {
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
use codex_proxy_rs::{config::schema::FingerprintConfig, upstream::fingerprint::Fingerprint};
|
||||
use codex_proxy_rs::{
|
||||
config::schema::FingerprintConfig,
|
||||
upstream::fingerprint::{Fingerprint, RuntimeFingerprint},
|
||||
};
|
||||
|
||||
pub(crate) fn test_fingerprint() -> Fingerprint {
|
||||
Fingerprint::from_config(&FingerprintConfig::default())
|
||||
@@ -9,3 +12,11 @@ pub(crate) fn test_fingerprint_with_updated_at(updated_at: Option<&str>) -> Fing
|
||||
fingerprint.updated_at = updated_at.map(ToString::to_string);
|
||||
fingerprint
|
||||
}
|
||||
|
||||
pub(crate) fn runtime_test_fingerprint() -> RuntimeFingerprint {
|
||||
RuntimeFingerprint::new(test_fingerprint())
|
||||
}
|
||||
|
||||
pub(crate) fn runtime_fingerprint(fingerprint: Fingerprint) -> RuntimeFingerprint {
|
||||
RuntimeFingerprint::new(fingerprint)
|
||||
}
|
||||
|
||||
@@ -312,7 +312,7 @@ async fn quota_refresh_service_should_send_usage_cookie_when_cookie_store_is_con
|
||||
let codex = CodexBackendClient::new(
|
||||
reqwest::Client::new(),
|
||||
server.uri(),
|
||||
crate::support::fingerprint::test_fingerprint(),
|
||||
crate::support::fingerprint::runtime_test_fingerprint(),
|
||||
);
|
||||
let service =
|
||||
RuntimeQuotaRefreshService::new(store, Arc::new(codex)).with_cookie_store(cookies);
|
||||
@@ -393,7 +393,7 @@ async fn quota_refresh_service_should_fetch_usage_for_quota_locked_accounts_and_
|
||||
let codex = CodexBackendClient::new(
|
||||
reqwest::Client::new(),
|
||||
server.uri(),
|
||||
crate::support::fingerprint::test_fingerprint(),
|
||||
crate::support::fingerprint::runtime_test_fingerprint(),
|
||||
);
|
||||
let service = RuntimeQuotaRefreshService::new(store.clone(), Arc::new(codex));
|
||||
|
||||
@@ -468,7 +468,7 @@ async fn quota_refresh_service_should_refresh_quota_exhausted_accounts() {
|
||||
let codex = CodexBackendClient::new(
|
||||
reqwest::Client::new(),
|
||||
server.uri(),
|
||||
crate::support::fingerprint::test_fingerprint(),
|
||||
crate::support::fingerprint::runtime_test_fingerprint(),
|
||||
);
|
||||
let service = RuntimeQuotaRefreshService::new(store, Arc::new(codex));
|
||||
|
||||
@@ -505,7 +505,7 @@ async fn quota_refresh_service_should_skip_recent_locked_account_before_cooldown
|
||||
let codex = CodexBackendClient::new(
|
||||
reqwest::Client::new(),
|
||||
server.uri(),
|
||||
crate::support::fingerprint::test_fingerprint(),
|
||||
crate::support::fingerprint::runtime_test_fingerprint(),
|
||||
);
|
||||
let service =
|
||||
RuntimeQuotaRefreshService::with_min_refresh_interval_secs(store, Arc::new(codex), 1_800);
|
||||
@@ -546,7 +546,7 @@ async fn quota_refresh_service_should_skip_recent_locked_account_inside_cooldown
|
||||
let codex = CodexBackendClient::new(
|
||||
reqwest::Client::new(),
|
||||
server.uri(),
|
||||
crate::support::fingerprint::test_fingerprint(),
|
||||
crate::support::fingerprint::runtime_test_fingerprint(),
|
||||
);
|
||||
let service =
|
||||
RuntimeQuotaRefreshService::with_min_refresh_interval_secs(store, Arc::new(codex), 1_800);
|
||||
@@ -604,7 +604,7 @@ async fn quota_refresh_service_should_bypass_recent_skip_after_cooldown_grace()
|
||||
let codex = CodexBackendClient::new(
|
||||
reqwest::Client::new(),
|
||||
server.uri(),
|
||||
crate::support::fingerprint::test_fingerprint(),
|
||||
crate::support::fingerprint::runtime_test_fingerprint(),
|
||||
);
|
||||
let service =
|
||||
RuntimeQuotaRefreshService::with_min_refresh_interval_secs(store, Arc::new(codex), 1_800);
|
||||
@@ -658,7 +658,7 @@ async fn quota_refresh_service_should_stagger_multiple_locked_account_requests()
|
||||
let codex = CodexBackendClient::new(
|
||||
reqwest::Client::new(),
|
||||
server.uri(),
|
||||
crate::support::fingerprint::test_fingerprint(),
|
||||
crate::support::fingerprint::runtime_test_fingerprint(),
|
||||
);
|
||||
let service = RuntimeQuotaRefreshService::new(store, Arc::new(codex))
|
||||
.with_request_spacing(StdDuration::from_millis(200));
|
||||
|
||||
@@ -57,14 +57,21 @@ async fn update_checker_should_report_available_update_from_appcast() {
|
||||
.mount(&server)
|
||||
.await;
|
||||
|
||||
let dir = tempfile::tempdir().expect("temp dir");
|
||||
let db = dir.path().join("fingerprints-check.sqlite");
|
||||
let pool = connect_sqlite(&format!("sqlite://{}", db.display()))
|
||||
.await
|
||||
.expect("sqlite pool");
|
||||
let repo = FingerprintRepository::new(pool);
|
||||
repo.ensure_current_seed(&crate::support::fingerprint::test_fingerprint())
|
||||
.await
|
||||
.expect("seed current fingerprint");
|
||||
|
||||
let checker = UpdateChecker::with_client(
|
||||
None,
|
||||
repo,
|
||||
reqwest::Client::new(),
|
||||
format!("{}/appcast.xml", server.uri()),
|
||||
tempfile::tempdir()
|
||||
.expect("temp dir")
|
||||
.path()
|
||||
.join("extracted-fingerprint.json"),
|
||||
dir.path().join("extracted-fingerprint.json"),
|
||||
"26.800.1",
|
||||
"6001",
|
||||
);
|
||||
@@ -113,7 +120,7 @@ async fn update_checker_should_apply_available_update_to_repository() {
|
||||
.expect("write extracted fingerprint");
|
||||
|
||||
let checker = UpdateChecker::with_client(
|
||||
Some(repo.clone()),
|
||||
repo.clone(),
|
||||
reqwest::Client::new(),
|
||||
format!("{}/appcast.xml", server.uri()),
|
||||
extracted_path,
|
||||
@@ -121,17 +128,20 @@ async fn update_checker_should_apply_available_update_to_repository() {
|
||||
"6001",
|
||||
);
|
||||
|
||||
let applied = checker
|
||||
let updated = checker
|
||||
.check_and_apply_update()
|
||||
.await
|
||||
.expect("apply update");
|
||||
.expect("apply update")
|
||||
.expect("available update should return current fingerprint");
|
||||
let stored = repo
|
||||
.load_current()
|
||||
.await
|
||||
.expect("load current")
|
||||
.expect("stored fingerprint");
|
||||
|
||||
assert!(applied);
|
||||
assert_eq!(updated.app_version, "26.900.1");
|
||||
assert_eq!(updated.build_number, "7001");
|
||||
assert_eq!(updated.chromium_version, "147");
|
||||
assert_eq!(stored.app_version, "26.900.1");
|
||||
assert_eq!(stored.build_number, "7001");
|
||||
assert_eq!(stored.chromium_version, "147");
|
||||
|
||||
@@ -59,7 +59,7 @@ async fn codex_backend_client_should_apply_configured_websocket_pool() {
|
||||
let backend = CodexBackendClient::new(
|
||||
reqwest::Client::new(),
|
||||
format!("http://{addr}"),
|
||||
crate::support::fingerprint::test_fingerprint(),
|
||||
crate::support::fingerprint::runtime_test_fingerprint(),
|
||||
)
|
||||
.with_websocket_pool(Arc::new(pool));
|
||||
let mut request = CodexResponsesRequest::new_http_sse("gpt-5.5", "be brief", Vec::new());
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
use super::*;
|
||||
use codex_proxy_rs::upstream::fingerprint::RuntimeFingerprint;
|
||||
use codex_proxy_rs::upstream::transport::build_ordered_codex_headers;
|
||||
use codex_proxy_rs::upstream::transport::websocket::CodexWebSocketConnection;
|
||||
use serde_json::Value;
|
||||
@@ -279,7 +280,7 @@ async fn codex_backend_client_websocket_should_forward_security_chain_headers_an
|
||||
let backend = CodexBackendClient::new(
|
||||
reqwest::Client::builder().no_proxy().build().unwrap(),
|
||||
format!("http://{addr}"),
|
||||
crate::support::fingerprint::test_fingerprint(),
|
||||
crate::support::fingerprint::runtime_test_fingerprint(),
|
||||
)
|
||||
.with_websocket_pool(pool);
|
||||
|
||||
@@ -420,7 +421,7 @@ async fn codex_backend_client_should_send_desktop_headers_and_capture_response_m
|
||||
let client = CodexBackendClient::new(
|
||||
reqwest::Client::builder().no_proxy().build().unwrap(),
|
||||
server.uri(),
|
||||
crate::support::fingerprint::test_fingerprint(),
|
||||
crate::support::fingerprint::runtime_test_fingerprint(),
|
||||
);
|
||||
let mut request =
|
||||
codex_proxy_rs::upstream::protocol::responses::CodexResponsesRequest::new_http_sse(
|
||||
@@ -471,6 +472,91 @@ async fn codex_backend_client_should_send_desktop_headers_and_capture_response_m
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn codex_backend_client_should_use_latest_runtime_fingerprint_for_new_requests() {
|
||||
let server = wiremock::MockServer::start().await;
|
||||
let sse_body = include_str!("../../fixtures/responses/http_sse/completed_usage_basic.sse");
|
||||
wiremock::Mock::given(wiremock::matchers::method("POST"))
|
||||
.and(wiremock::matchers::path("/codex/responses"))
|
||||
.respond_with(
|
||||
wiremock::ResponseTemplate::new(200)
|
||||
.insert_header("content-type", "text/event-stream")
|
||||
.set_body_string(sse_body),
|
||||
)
|
||||
.mount(&server)
|
||||
.await;
|
||||
|
||||
let mut initial = crate::support::fingerprint::test_fingerprint();
|
||||
initial.app_version = "26.800.1".to_string();
|
||||
let runtime_fingerprint = RuntimeFingerprint::new(initial);
|
||||
let client = CodexBackendClient::new(
|
||||
reqwest::Client::builder().no_proxy().build().unwrap(),
|
||||
server.uri(),
|
||||
runtime_fingerprint.clone(),
|
||||
);
|
||||
let mut request =
|
||||
codex_proxy_rs::upstream::protocol::responses::CodexResponsesRequest::new_http_sse(
|
||||
"gpt-5.5",
|
||||
"",
|
||||
Vec::new(),
|
||||
);
|
||||
request.force_http_sse = true;
|
||||
let request_context = |request_id| CodexRequestContext {
|
||||
access_token: "access-token",
|
||||
account_id: Some("chatgpt-account"),
|
||||
request_id,
|
||||
turn_state: None,
|
||||
turn_metadata: None,
|
||||
beta_features: None,
|
||||
include_timing_metrics: None,
|
||||
version: None,
|
||||
codex_window_id: None,
|
||||
parent_thread_id: None,
|
||||
cookie_header: None,
|
||||
installation_id: None,
|
||||
session_id: None,
|
||||
};
|
||||
|
||||
client
|
||||
.create_response(&request, request_context("req_fingerprint_1"))
|
||||
.await
|
||||
.expect("first response should succeed");
|
||||
|
||||
let mut updated = runtime_fingerprint.snapshot();
|
||||
updated.app_version = "26.900.1".to_string();
|
||||
updated.build_number = "7001".to_string();
|
||||
runtime_fingerprint.replace(updated);
|
||||
|
||||
client
|
||||
.create_response(&request, request_context("req_fingerprint_2"))
|
||||
.await
|
||||
.expect("second response should succeed");
|
||||
|
||||
let user_agents = server
|
||||
.received_requests()
|
||||
.await
|
||||
.expect("received requests should load")
|
||||
.iter()
|
||||
.map(|request| {
|
||||
request
|
||||
.headers
|
||||
.get("user-agent")
|
||||
.expect("user-agent should be sent")
|
||||
.to_str()
|
||||
.expect("user-agent should be valid")
|
||||
.to_string()
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
assert_eq!(
|
||||
user_agents,
|
||||
vec![
|
||||
"Codex Desktop/26.800.1 (darwin; arm64)".to_string(),
|
||||
"Codex Desktop/26.900.1 (darwin; arm64)".to_string(),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn codex_backend_client_usage_should_use_wham_usage_headers() {
|
||||
let server = wiremock::MockServer::start().await;
|
||||
@@ -486,7 +572,7 @@ async fn codex_backend_client_usage_should_use_wham_usage_headers() {
|
||||
let client = CodexBackendClient::new(
|
||||
reqwest::Client::builder().no_proxy().build().unwrap(),
|
||||
server.uri(),
|
||||
crate::support::fingerprint::test_fingerprint(),
|
||||
crate::support::fingerprint::runtime_test_fingerprint(),
|
||||
);
|
||||
|
||||
let usage = client
|
||||
@@ -571,7 +657,7 @@ async fn codex_backend_client_models_should_use_original_auxiliary_headers() {
|
||||
let client = CodexBackendClient::new(
|
||||
reqwest::Client::builder().no_proxy().build().unwrap(),
|
||||
server.uri(),
|
||||
crate::support::fingerprint::test_fingerprint(),
|
||||
crate::support::fingerprint::runtime_test_fingerprint(),
|
||||
);
|
||||
|
||||
let models = client
|
||||
@@ -638,7 +724,7 @@ async fn codex_backend_client_should_send_http_sse_headers_in_fingerprint_order(
|
||||
let client = CodexBackendClient::new(
|
||||
reqwest::Client::builder().no_proxy().build().unwrap(),
|
||||
format!("http://{addr}"),
|
||||
crate::support::fingerprint::test_fingerprint(),
|
||||
crate::support::fingerprint::runtime_test_fingerprint(),
|
||||
);
|
||||
|
||||
client
|
||||
@@ -713,7 +799,7 @@ async fn codex_backend_client_should_send_compact_headers_in_fingerprint_order()
|
||||
let client = CodexBackendClient::new(
|
||||
reqwest::Client::builder().no_proxy().build().unwrap(),
|
||||
format!("http://{addr}"),
|
||||
crate::support::fingerprint::test_fingerprint(),
|
||||
crate::support::fingerprint::runtime_test_fingerprint(),
|
||||
);
|
||||
|
||||
client
|
||||
|
||||
@@ -107,7 +107,7 @@ async fn codex_backend_client_should_cap_non_success_error_body_at_one_mib() {
|
||||
let client = CodexBackendClient::new(
|
||||
reqwest::Client::builder().no_proxy().build().unwrap(),
|
||||
server.uri(),
|
||||
crate::support::fingerprint::test_fingerprint(),
|
||||
crate::support::fingerprint::runtime_test_fingerprint(),
|
||||
);
|
||||
let mut request =
|
||||
codex_proxy_rs::upstream::protocol::responses::CodexResponsesRequest::new_http_sse(
|
||||
@@ -161,7 +161,7 @@ async fn codex_backend_client_should_parse_retry_after_from_rate_limit_error_bod
|
||||
let client = CodexBackendClient::new(
|
||||
reqwest::Client::builder().no_proxy().build().unwrap(),
|
||||
server.uri(),
|
||||
crate::support::fingerprint::test_fingerprint(),
|
||||
crate::support::fingerprint::runtime_test_fingerprint(),
|
||||
);
|
||||
let mut request =
|
||||
codex_proxy_rs::upstream::protocol::responses::CodexResponsesRequest::new_http_sse(
|
||||
|
||||
@@ -107,7 +107,7 @@ async fn codex_backend_client_should_decode_permessage_deflate_context_takeover_
|
||||
let backend = CodexBackendClient::new(
|
||||
reqwest::Client::builder().no_proxy().build().unwrap(),
|
||||
format!("http://{addr}"),
|
||||
crate::support::fingerprint::test_fingerprint(),
|
||||
crate::support::fingerprint::runtime_test_fingerprint(),
|
||||
)
|
||||
.with_websocket_pool(pool);
|
||||
let mut request =
|
||||
@@ -1000,7 +1000,7 @@ async fn codex_backend_client_stream_should_reject_binary_websocket_event() {
|
||||
let backend = CodexBackendClient::new(
|
||||
reqwest::Client::builder().no_proxy().build().unwrap(),
|
||||
format!("http://{addr}"),
|
||||
crate::support::fingerprint::test_fingerprint(),
|
||||
crate::support::fingerprint::runtime_test_fingerprint(),
|
||||
);
|
||||
|
||||
let Err(error) = backend
|
||||
@@ -1073,7 +1073,7 @@ async fn codex_backend_client_stream_should_retry_without_pool_when_first_conten
|
||||
let backend = CodexBackendClient::new(
|
||||
reqwest::Client::builder().no_proxy().build().unwrap(),
|
||||
format!("http://{addr}"),
|
||||
crate::support::fingerprint::test_fingerprint(),
|
||||
crate::support::fingerprint::runtime_test_fingerprint(),
|
||||
)
|
||||
.with_websocket_first_token_timeout(Some(Duration::from_millis(30)));
|
||||
let request = pooled_websocket_request("conversation-first-token-no-pool-timeout");
|
||||
@@ -1133,7 +1133,7 @@ async fn codex_backend_client_stream_should_error_when_websocket_closes_before_t
|
||||
let backend = CodexBackendClient::new(
|
||||
reqwest::Client::builder().no_proxy().build().unwrap(),
|
||||
format!("http://{addr}"),
|
||||
crate::support::fingerprint::test_fingerprint(),
|
||||
crate::support::fingerprint::runtime_test_fingerprint(),
|
||||
);
|
||||
|
||||
let mut response = backend
|
||||
@@ -1203,7 +1203,7 @@ async fn codex_backend_client_stream_should_wait_for_terminal_after_active_webso
|
||||
let backend = CodexBackendClient::new(
|
||||
reqwest::Client::builder().no_proxy().build().unwrap(),
|
||||
format!("http://{addr}"),
|
||||
crate::support::fingerprint::test_fingerprint(),
|
||||
crate::support::fingerprint::runtime_test_fingerprint(),
|
||||
);
|
||||
|
||||
let mut response = backend
|
||||
@@ -1267,7 +1267,7 @@ async fn codex_backend_client_stream_should_timeout_when_active_websocket_stalls
|
||||
let backend = CodexBackendClient::new(
|
||||
reqwest::Client::builder().no_proxy().build().unwrap(),
|
||||
format!("http://{addr}"),
|
||||
crate::support::fingerprint::test_fingerprint(),
|
||||
crate::support::fingerprint::runtime_test_fingerprint(),
|
||||
);
|
||||
|
||||
let mut response = backend
|
||||
@@ -1363,7 +1363,7 @@ async fn codex_backend_client_should_use_websocket_when_previous_response_id_is_
|
||||
let backend = CodexBackendClient::new(
|
||||
reqwest::Client::builder().no_proxy().build().unwrap(),
|
||||
format!("http://{addr}"),
|
||||
crate::support::fingerprint::test_fingerprint(),
|
||||
crate::support::fingerprint::runtime_test_fingerprint(),
|
||||
)
|
||||
.with_websocket_pool(pool);
|
||||
|
||||
|
||||
@@ -39,7 +39,7 @@ async fn codex_backend_client_should_reuse_pooled_websocket_for_same_account_and
|
||||
let backend = CodexBackendClient::new(
|
||||
reqwest::Client::builder().no_proxy().build().unwrap(),
|
||||
format!("http://{addr}"),
|
||||
crate::support::fingerprint::test_fingerprint(),
|
||||
crate::support::fingerprint::runtime_test_fingerprint(),
|
||||
)
|
||||
.with_websocket_pool(Arc::clone(&pool));
|
||||
let mut request =
|
||||
@@ -117,7 +117,7 @@ async fn codex_backend_client_should_open_fresh_socket_when_idle_pooled_websocke
|
||||
let backend = CodexBackendClient::new(
|
||||
reqwest::Client::builder().no_proxy().build().unwrap(),
|
||||
format!("http://{addr}"),
|
||||
crate::support::fingerprint::test_fingerprint(),
|
||||
crate::support::fingerprint::runtime_test_fingerprint(),
|
||||
)
|
||||
.with_websocket_pool(Arc::clone(&pool));
|
||||
let mut request =
|
||||
@@ -212,7 +212,7 @@ async fn codex_backend_client_stream_should_retry_fresh_socket_when_first_conten
|
||||
let backend = CodexBackendClient::new(
|
||||
reqwest::Client::builder().no_proxy().build().unwrap(),
|
||||
format!("http://{addr}"),
|
||||
crate::support::fingerprint::test_fingerprint(),
|
||||
crate::support::fingerprint::runtime_test_fingerprint(),
|
||||
)
|
||||
.with_websocket_pool(Arc::clone(&pool));
|
||||
let request = pooled_websocket_request("conversation-first-token-fresh-timeout");
|
||||
@@ -305,7 +305,7 @@ async fn codex_backend_client_stream_should_retry_reused_socket_when_first_conte
|
||||
let backend = CodexBackendClient::new(
|
||||
reqwest::Client::builder().no_proxy().build().unwrap(),
|
||||
format!("http://{addr}"),
|
||||
crate::support::fingerprint::test_fingerprint(),
|
||||
crate::support::fingerprint::runtime_test_fingerprint(),
|
||||
)
|
||||
.with_websocket_pool(Arc::clone(&pool));
|
||||
let request = pooled_websocket_request("conversation-first-token-reuse-timeout");
|
||||
@@ -399,7 +399,7 @@ async fn codex_backend_client_stream_should_retry_bypass_socket_when_first_conte
|
||||
let backend = CodexBackendClient::new(
|
||||
reqwest::Client::builder().no_proxy().build().unwrap(),
|
||||
format!("http://{addr}"),
|
||||
crate::support::fingerprint::test_fingerprint(),
|
||||
crate::support::fingerprint::runtime_test_fingerprint(),
|
||||
)
|
||||
.with_websocket_pool(Arc::clone(&pool));
|
||||
let request = pooled_websocket_request("conversation-first-token-bypass-timeout");
|
||||
@@ -454,7 +454,7 @@ async fn codex_backend_client_should_not_reuse_pooled_websocket_across_local_acc
|
||||
let backend = CodexBackendClient::new(
|
||||
reqwest::Client::builder().no_proxy().build().unwrap(),
|
||||
format!("http://{addr}"),
|
||||
crate::support::fingerprint::test_fingerprint(),
|
||||
crate::support::fingerprint::runtime_test_fingerprint(),
|
||||
)
|
||||
.with_websocket_pool(Arc::clone(&pool));
|
||||
let mut request =
|
||||
@@ -547,7 +547,7 @@ async fn websocket_pool_should_bypass_busy_key_with_one_shot_connections() {
|
||||
let backend = CodexBackendClient::new(
|
||||
reqwest::Client::builder().no_proxy().build().unwrap(),
|
||||
format!("http://{addr}"),
|
||||
crate::support::fingerprint::test_fingerprint(),
|
||||
crate::support::fingerprint::runtime_test_fingerprint(),
|
||||
)
|
||||
.with_websocket_pool(pool);
|
||||
let request = pooled_websocket_request("conversation-busy");
|
||||
@@ -640,7 +640,7 @@ async fn websocket_pool_should_release_slot_when_client_drops_stream() {
|
||||
let backend = CodexBackendClient::new(
|
||||
reqwest::Client::builder().no_proxy().build().unwrap(),
|
||||
format!("http://{addr}"),
|
||||
crate::support::fingerprint::test_fingerprint(),
|
||||
crate::support::fingerprint::runtime_test_fingerprint(),
|
||||
)
|
||||
.with_websocket_pool(pool);
|
||||
let request = pooled_websocket_request("conversation-drop");
|
||||
@@ -714,7 +714,7 @@ async fn websocket_pool_should_bypass_new_keys_after_account_cap() {
|
||||
let backend = CodexBackendClient::new(
|
||||
reqwest::Client::builder().no_proxy().build().unwrap(),
|
||||
format!("http://{addr}"),
|
||||
crate::support::fingerprint::test_fingerprint(),
|
||||
crate::support::fingerprint::runtime_test_fingerprint(),
|
||||
)
|
||||
.with_websocket_pool(pool);
|
||||
let first_request = pooled_websocket_request("conversation-cap-one");
|
||||
@@ -800,7 +800,7 @@ async fn websocket_pool_should_evict_idle_connection_when_liveness_lapses_despit
|
||||
let backend = CodexBackendClient::new(
|
||||
reqwest::Client::builder().no_proxy().build().unwrap(),
|
||||
format!("http://{addr}"),
|
||||
crate::support::fingerprint::test_fingerprint(),
|
||||
crate::support::fingerprint::runtime_test_fingerprint(),
|
||||
)
|
||||
.with_websocket_pool(Arc::clone(&pool));
|
||||
let request = pooled_websocket_request("conversation-no-pong");
|
||||
@@ -874,7 +874,7 @@ async fn websocket_pool_should_gc_expired_idle_connections() {
|
||||
let backend = CodexBackendClient::new(
|
||||
reqwest::Client::builder().no_proxy().build().unwrap(),
|
||||
format!("http://{addr}"),
|
||||
crate::support::fingerprint::test_fingerprint(),
|
||||
crate::support::fingerprint::runtime_test_fingerprint(),
|
||||
)
|
||||
.with_websocket_pool(Arc::clone(&pool));
|
||||
let request = pooled_websocket_request("conversation-gc");
|
||||
@@ -978,7 +978,7 @@ async fn codex_backend_client_should_keep_idle_pooled_websocket_alive_across_rep
|
||||
let backend = CodexBackendClient::new(
|
||||
reqwest::Client::builder().no_proxy().build().unwrap(),
|
||||
format!("http://{addr}"),
|
||||
crate::support::fingerprint::test_fingerprint(),
|
||||
crate::support::fingerprint::runtime_test_fingerprint(),
|
||||
)
|
||||
.with_websocket_pool(Arc::clone(&pool));
|
||||
let mut request =
|
||||
@@ -1084,7 +1084,7 @@ async fn codex_backend_client_should_close_idle_pooled_websocket_when_account_is
|
||||
let backend = CodexBackendClient::new(
|
||||
reqwest::Client::builder().no_proxy().build().unwrap(),
|
||||
format!("http://{addr}"),
|
||||
crate::support::fingerprint::test_fingerprint(),
|
||||
crate::support::fingerprint::runtime_test_fingerprint(),
|
||||
)
|
||||
.with_websocket_pool(Arc::clone(&pool));
|
||||
let mut request =
|
||||
@@ -1188,7 +1188,7 @@ async fn codex_backend_client_should_stop_reusing_pooled_websockets_after_shutdo
|
||||
let backend = CodexBackendClient::new(
|
||||
reqwest::Client::builder().no_proxy().build().unwrap(),
|
||||
format!("http://{addr}"),
|
||||
crate::support::fingerprint::test_fingerprint(),
|
||||
crate::support::fingerprint::runtime_test_fingerprint(),
|
||||
)
|
||||
.with_websocket_pool(Arc::clone(&pool));
|
||||
let mut request =
|
||||
@@ -1292,7 +1292,7 @@ async fn codex_backend_client_should_close_idle_pooled_websocket_after_liveness_
|
||||
let backend = CodexBackendClient::new(
|
||||
reqwest::Client::builder().no_proxy().build().unwrap(),
|
||||
format!("http://{addr}"),
|
||||
crate::support::fingerprint::test_fingerprint(),
|
||||
crate::support::fingerprint::runtime_test_fingerprint(),
|
||||
)
|
||||
.with_websocket_pool(Arc::clone(&pool));
|
||||
let mut request =
|
||||
@@ -1387,7 +1387,7 @@ async fn codex_backend_client_should_discard_pooled_websocket_after_upstream_err
|
||||
let backend = CodexBackendClient::new(
|
||||
reqwest::Client::builder().no_proxy().build().unwrap(),
|
||||
format!("http://{addr}"),
|
||||
crate::support::fingerprint::test_fingerprint(),
|
||||
crate::support::fingerprint::runtime_test_fingerprint(),
|
||||
)
|
||||
.with_websocket_pool(pool);
|
||||
let mut request =
|
||||
@@ -1467,7 +1467,7 @@ async fn codex_backend_client_should_reuse_pooled_websocket_after_unmapped_respo
|
||||
let backend = CodexBackendClient::new(
|
||||
reqwest::Client::builder().no_proxy().build().unwrap(),
|
||||
format!("http://{addr}"),
|
||||
crate::support::fingerprint::test_fingerprint(),
|
||||
crate::support::fingerprint::runtime_test_fingerprint(),
|
||||
)
|
||||
.with_websocket_pool(pool);
|
||||
let request = pooled_websocket_request("conversation-pool-unmapped-failed");
|
||||
|
||||
Reference in New Issue
Block a user