merge crates

This commit is contained in:
easong-openai
2025-09-05 15:55:06 -07:00
parent 35dec89d8a
commit acb706b553
13 changed files with 85 additions and 126 deletions

12
codex-rs/Cargo.lock generated
View File

@@ -735,7 +735,6 @@ dependencies = [
"chrono",
"clap",
"codex-backend-client",
"codex-cloud-tasks-api",
"codex-cloud-tasks-client",
"codex-common",
"codex-core",
@@ -754,16 +753,6 @@ dependencies = [
"unicode-width 0.1.14",
]
[[package]]
name = "codex-cloud-tasks-api"
version = "0.0.0"
dependencies = [
"async-trait",
"chrono",
"serde",
"thiserror 2.0.16",
]
[[package]]
name = "codex-cloud-tasks-client"
version = "0.0.0"
@@ -772,7 +761,6 @@ dependencies = [
"async-trait",
"chrono",
"codex-backend-client",
"codex-cloud-tasks-api",
"diffy",
"once_cell",
"regex",

View File

@@ -6,7 +6,6 @@ members = [
"arg0",
"codex-backend-openapi-models",
"cloud-tasks",
"cloud-tasks-api",
"cloud-tasks-client",
"cli",
"common",

View File

@@ -1,18 +0,0 @@
[package]
name = "codex-cloud-tasks-api"
version = { workspace = true }
edition = "2024"
[lib]
name = "codex_cloud_tasks_api"
path = "src/lib.rs"
[lints]
workspace = true
[dependencies]
async-trait = "0.1"
chrono = { version = "0.4", features = ["serde"] }
serde = { version = "1", features = ["derive"] }
thiserror = "2.0.12"

View File

@@ -17,7 +17,6 @@ mock = []
[dependencies]
anyhow = "1"
codex-cloud-tasks-api = { path = "../cloud-tasks-api" }
async-trait = "0.1"
chrono = { version = "0.4", features = ["serde"] }
diffy = "0.4.2"

View File

@@ -1,5 +1,3 @@
#![deny(clippy::unwrap_used, clippy::expect_used)]
use chrono::DateTime;
use chrono::Utc;
use serde::Deserialize;

View File

@@ -1,6 +1,7 @@
use crate::ApplyOutcome;
use crate::ApplyStatus;
use crate::CloudBackend;
use crate::DiffSummary;
use crate::Error;
use crate::Result;
use crate::TaskId;
@@ -8,7 +9,6 @@ use crate::TaskStatus;
use crate::TaskSummary;
use chrono::DateTime;
use chrono::Utc;
use codex_cloud_tasks_api::DiffSummary;
use serde_json::Value;
use std::collections::HashMap;
@@ -306,7 +306,7 @@ impl CloudBackend for HttpClient {
prompt: &str,
git_ref: &str,
qa_mode: bool,
) -> Result<codex_cloud_tasks_api::CreatedTask> {
) -> Result<crate::CreatedTask> {
// Build request payload patterned after VSCode/newtask.rs
let mut input_items: Vec<serde_json::Value> = Vec::new();
input_items.push(serde_json::json!({
@@ -341,7 +341,7 @@ impl CloudBackend for HttpClient {
env_id,
prompt.chars().count()
));
Ok(codex_cloud_tasks_api::CreatedTask { id: TaskId(id) })
Ok(crate::CreatedTask { id: TaskId(id) })
}
Err(e) => {
append_error_log(&format!(

View File

@@ -1,14 +1,17 @@
#![deny(clippy::unwrap_used, clippy::expect_used)]
mod api;
pub use api::ApplyOutcome;
pub use api::ApplyStatus;
pub use api::CloudBackend;
pub use api::CreatedTask;
pub use api::DiffSummary;
pub use api::Error;
pub use api::Result;
pub use api::TaskId;
pub use api::TaskStatus;
pub use api::TaskSummary;
use codex_cloud_tasks_api as api;
#[cfg(feature = "mock")]
mod mock;

View File

@@ -1,11 +1,11 @@
use crate::ApplyOutcome;
use crate::CloudBackend;
use crate::DiffSummary;
use crate::Result;
use crate::TaskId;
use crate::TaskStatus;
use crate::TaskSummary;
use chrono::Utc;
use codex_cloud_tasks_api::DiffSummary;
#[derive(Clone, Default)]
pub struct MockClient;
@@ -81,10 +81,10 @@ impl CloudBackend for MockClient {
prompt: &str,
git_ref: &str,
qa_mode: bool,
) -> Result<codex_cloud_tasks_api::CreatedTask> {
) -> Result<crate::CreatedTask> {
let _ = (env_id, prompt, git_ref, qa_mode);
let id = format!("task_local_{}", chrono::Utc::now().timestamp_millis());
Ok(codex_cloud_tasks_api::CreatedTask { id: TaskId(id) })
Ok(crate::CreatedTask { id: TaskId(id) })
}
}

View File

@@ -17,7 +17,6 @@ codex-common = { path = "../common", features = ["cli"] }
tokio = { version = "1", features = ["macros", "rt-multi-thread"] }
tracing = { version = "0.1.41", features = ["log"] }
tracing-subscriber = { version = "0.3.19", features = ["env-filter"] }
codex-cloud-tasks-api = { path = "../cloud-tasks-api" }
codex-cloud-tasks-client = { path = "../cloud-tasks-client", features = ["mock", "online"] }
ratatui = { version = "0.29.0" }
crossterm = { version = "0.28.1", features = ["event-stream"] }

View File

@@ -33,9 +33,9 @@ pub struct ApplyModalState {
}
use crate::scrollable_diff::ScrollableDiff;
use codex_cloud_tasks_api::CloudBackend;
use codex_cloud_tasks_api::TaskId;
use codex_cloud_tasks_api::TaskSummary;
use codex_cloud_tasks_client::CloudBackend;
use codex_cloud_tasks_client::TaskId;
use codex_cloud_tasks_client::TaskSummary;
use throbber_widgets_tui::ThrobberState;
#[derive(Default)]
@@ -151,7 +151,7 @@ pub enum AppEvent {
error: String,
},
/// Background completion of new task submission
NewTaskSubmitted(Result<codex_cloud_tasks_api::CreatedTask, String>),
NewTaskSubmitted(Result<codex_cloud_tasks_client::CreatedTask, String>),
/// Background completion of apply preflight when opening modal or on demand
ApplyPreflightFinished {
id: TaskId,
@@ -175,11 +175,11 @@ mod tests {
}
#[async_trait::async_trait]
impl codex_cloud_tasks_api::CloudBackend for FakeBackend {
impl codex_cloud_tasks_client::CloudBackend for FakeBackend {
async fn list_tasks(
&self,
env: Option<&str>,
) -> codex_cloud_tasks_api::Result<Vec<TaskSummary>> {
) -> codex_cloud_tasks_client::Result<Vec<TaskSummary>> {
let key = env.map(|s| s.to_string());
let titles = self
.by_env
@@ -191,18 +191,18 @@ mod tests {
out.push(TaskSummary {
id: TaskId(format!("T-{i}")),
title: t.to_string(),
status: codex_cloud_tasks_api::TaskStatus::Ready,
status: codex_cloud_tasks_client::TaskStatus::Ready,
updated_at: Utc::now(),
environment_id: env.map(|s| s.to_string()),
environment_label: None,
summary: codex_cloud_tasks_api::DiffSummary::default(),
summary: codex_cloud_tasks_client::DiffSummary::default(),
});
}
Ok(out)
}
async fn get_task_diff(&self, _id: TaskId) -> codex_cloud_tasks_api::Result<String> {
Err(codex_cloud_tasks_api::Error::Unimplemented(
async fn get_task_diff(&self, _id: TaskId) -> codex_cloud_tasks_client::Result<String> {
Err(codex_cloud_tasks_client::Error::Unimplemented(
"not used in test",
))
}
@@ -210,15 +210,15 @@ mod tests {
async fn get_task_messages(
&self,
_id: TaskId,
) -> codex_cloud_tasks_api::Result<Vec<String>> {
) -> codex_cloud_tasks_client::Result<Vec<String>> {
Ok(vec![])
}
async fn apply_task(
&self,
_id: TaskId,
) -> codex_cloud_tasks_api::Result<codex_cloud_tasks_api::ApplyOutcome> {
Err(codex_cloud_tasks_api::Error::Unimplemented(
) -> codex_cloud_tasks_client::Result<codex_cloud_tasks_client::ApplyOutcome> {
Err(codex_cloud_tasks_client::Error::Unimplemented(
"not used in test",
))
}
@@ -229,8 +229,8 @@ mod tests {
_prompt: &str,
_git_ref: &str,
_qa_mode: bool,
) -> codex_cloud_tasks_api::Result<codex_cloud_tasks_api::CreatedTask> {
Err(codex_cloud_tasks_api::Error::Unimplemented(
) -> codex_cloud_tasks_client::Result<codex_cloud_tasks_client::CreatedTask> {
Err(codex_cloud_tasks_client::Error::Unimplemented(
"not used in test",
))
}

View File

@@ -54,7 +54,7 @@ pub async fn run_main(_cli: Cli, _codex_linux_sandbox_exe: Option<PathBuf>) -> a
);
use std::sync::Arc;
let backend: Arc<dyn codex_cloud_tasks_api::CloudBackend> = if use_mock {
let backend: Arc<dyn codex_cloud_tasks_client::CloudBackend> = if use_mock {
Arc::new(codex_cloud_tasks_client::MockClient)
} else {
// Build an HTTP client against the configured (or default) base URL.
@@ -647,8 +647,8 @@ pub async fn run_main(_cli: Cli, _codex_linux_sandbox_exe: Option<PathBuf>) -> a
codex_login::AuthMode::ChatGPT,
"codex_cloud_tasks_tui".to_string(),
);
if let Some(auth) = am.auth() {
if let Ok(tok) = auth.get_token().await && !tok.is_empty() {
if let Some(auth) = am.auth()
&& let Ok(tok) = auth.get_token().await && !tok.is_empty() {
let v = format!("Bearer {tok}");
if let Ok(hv) = reqwest::header::HeaderValue::from_str(&v) { headers.insert(reqwest::header::AUTHORIZATION, hv); }
if let Some(acc) = auth.get_account_id().or_else(|| extract_chatgpt_account_id(&tok))
@@ -657,7 +657,6 @@ pub async fn run_main(_cli: Cli, _codex_linux_sandbox_exe: Option<PathBuf>) -> a
headers.insert(name, hv);
}
}
}
}
let res = crate::env_detect::list_environments(&base_url, &headers).await;
let _ = tx2.send(app::AppEvent::EnvironmentsLoaded(res));
@@ -682,31 +681,29 @@ pub async fn run_main(_cli: Cli, _codex_linux_sandbox_exe: Option<PathBuf>) -> a
_ => {
if page.submitting {
// Ignore input while submitting
} else {
if let codex_tui::ComposerAction::Submitted(text) = page.composer.input(key) {
// Submit only if we have an env id
if let Some(env) = page.env_id.clone() {
append_error_log(format!(
"new-task: submit env={} size={}",
env,
text.chars().count()
));
page.submitting = true;
app.status = "Submitting new task…".to_string();
let tx2 = tx.clone();
let backend2 = backend.clone();
tokio::spawn(async move {
let result = codex_cloud_tasks_api::CloudBackend::create_task(&*backend2, &env, &text, "main", false).await;
let evt = match result {
Ok(ok) => app::AppEvent::NewTaskSubmitted(Ok(ok)),
Err(e) => app::AppEvent::NewTaskSubmitted(Err(format!("{e}"))),
};
let _ = tx2.send(evt);
});
} else {
app.status = "No environment selected (press 'e' to choose)".to_string();
}
}
} else if let codex_tui::ComposerAction::Submitted(text) = page.composer.input(key) {
// Submit only if we have an env id
if let Some(env) = page.env_id.clone() {
append_error_log(format!(
"new-task: submit env={} size={}",
env,
text.chars().count()
));
page.submitting = true;
app.status = "Submitting new task…".to_string();
let tx2 = tx.clone();
let backend2 = backend.clone();
tokio::spawn(async move {
let result = codex_cloud_tasks_client::CloudBackend::create_task(&*backend2, &env, &text, "main", false).await;
let evt = match result {
Ok(ok) => app::AppEvent::NewTaskSubmitted(Ok(ok)),
Err(e) => app::AppEvent::NewTaskSubmitted(Err(format!("{e}"))),
};
let _ = tx2.send(evt);
});
} else {
app.status = "No environment selected (press 'e' to choose)".to_string();
}
}
needs_redraw = true;
// If pasteburst is active, schedule a microflush frame.
@@ -729,10 +726,10 @@ pub async fn run_main(_cli: Cli, _codex_linux_sandbox_exe: Option<PathBuf>) -> a
KeyCode::Char('y') => {
if let Some(m) = app.apply_modal.take() {
app.status = format!("Applying '{}'...", m.title);
match codex_cloud_tasks_api::CloudBackend::apply_task(&*backend, m.task_id.clone()).await {
match codex_cloud_tasks_client::CloudBackend::apply_task(&*backend, m.task_id.clone()).await {
Ok(outcome) => {
app.status = outcome.message.clone();
if matches!(outcome.status, codex_cloud_tasks_api::ApplyStatus::Success) {
if matches!(outcome.status, codex_cloud_tasks_client::ApplyStatus::Success) {
app.diff_overlay = None;
if let Ok(tasks) = app::load_tasks(&*backend, app.env_filter.as_deref()).await { app.tasks = tasks; }
}
@@ -757,14 +754,14 @@ pub async fn run_main(_cli: Cli, _codex_linux_sandbox_exe: Option<PathBuf>) -> a
let title2 = m.title.clone();
tokio::spawn(async move {
unsafe { std::env::set_var("CODEX_APPLY_PREFLIGHT", "1") };
let out = codex_cloud_tasks_api::CloudBackend::apply_task(&*backend2, id2.clone()).await;
let out = codex_cloud_tasks_client::CloudBackend::apply_task(&*backend2, id2.clone()).await;
unsafe { std::env::remove_var("CODEX_APPLY_PREFLIGHT") };
let evt = match out {
Ok(outcome) => {
let level = match outcome.status {
codex_cloud_tasks_api::ApplyStatus::Success => app::ApplyResultLevel::Success,
codex_cloud_tasks_api::ApplyStatus::Partial => app::ApplyResultLevel::Partial,
codex_cloud_tasks_api::ApplyStatus::Error => app::ApplyResultLevel::Error,
codex_cloud_tasks_client::ApplyStatus::Success => app::ApplyResultLevel::Success,
codex_cloud_tasks_client::ApplyStatus::Partial => app::ApplyResultLevel::Partial,
codex_cloud_tasks_client::ApplyStatus::Error => app::ApplyResultLevel::Error,
};
app::AppEvent::ApplyPreflightFinished { id: id2, title: title2, message: outcome.message, level, skipped: outcome.skipped_paths, conflicts: outcome.conflict_paths }
}
@@ -794,14 +791,14 @@ pub async fn run_main(_cli: Cli, _codex_linux_sandbox_exe: Option<PathBuf>) -> a
let title2 = ov.title.clone();
tokio::spawn(async move {
unsafe { std::env::set_var("CODEX_APPLY_PREFLIGHT", "1") };
let out = codex_cloud_tasks_api::CloudBackend::apply_task(&*backend2, id2.clone()).await;
let out = codex_cloud_tasks_client::CloudBackend::apply_task(&*backend2, id2.clone()).await;
unsafe { std::env::remove_var("CODEX_APPLY_PREFLIGHT") };
let evt = match out {
Ok(outcome) => {
let level = match outcome.status {
codex_cloud_tasks_api::ApplyStatus::Success => app::ApplyResultLevel::Success,
codex_cloud_tasks_api::ApplyStatus::Partial => app::ApplyResultLevel::Partial,
codex_cloud_tasks_api::ApplyStatus::Error => app::ApplyResultLevel::Error,
codex_cloud_tasks_client::ApplyStatus::Success => app::ApplyResultLevel::Success,
codex_cloud_tasks_client::ApplyStatus::Partial => app::ApplyResultLevel::Partial,
codex_cloud_tasks_client::ApplyStatus::Error => app::ApplyResultLevel::Error,
};
app::AppEvent::ApplyPreflightFinished { id: id2, title: title2, message: outcome.message, level, skipped: outcome.skipped_paths, conflicts: outcome.conflict_paths }
}
@@ -838,7 +835,7 @@ pub async fn run_main(_cli: Cli, _codex_linux_sandbox_exe: Option<PathBuf>) -> a
codex_login::AuthMode::ChatGPT,
"codex_cloud_tasks_tui".to_string(),
);
if let Some(auth) = am.auth() { if let Ok(tok) = auth.get_token().await && !tok.is_empty() {
if let Some(auth) = am.auth() && let Ok(tok) = auth.get_token().await && !tok.is_empty() {
let v = format!("Bearer {tok}");
if let Ok(hv) = reqwest::header::HeaderValue::from_str(&v) { headers.insert(reqwest::header::AUTHORIZATION, hv); }
if let Some(acc) = auth.get_account_id().or_else(|| extract_chatgpt_account_id(&tok))
@@ -846,7 +843,7 @@ pub async fn run_main(_cli: Cli, _codex_linux_sandbox_exe: Option<PathBuf>) -> a
&& let Ok(hv) = reqwest::header::HeaderValue::from_str(&acc) {
headers.insert(name, hv);
}
}}
}
}
let res = crate::env_detect::list_environments(&base_url, &headers).await;
let _ = tx2.send(app::AppEvent::EnvironmentsLoaded(res));
@@ -900,17 +897,14 @@ pub async fn run_main(_cli: Cli, _codex_linux_sandbox_exe: Option<PathBuf>) -> a
codex_login::AuthMode::ChatGPT,
"codex_cloud_tasks_tui".to_string(),
);
if let Some(auth) = am.auth() {
if let Ok(tok) = auth.get_token().await { if !tok.is_empty() {
if let Some(auth) = am.auth()
&& let Ok(tok) = auth.get_token().await && !tok.is_empty() {
let v = format!("Bearer {tok}");
if let Ok(hv) = reqwest::header::HeaderValue::from_str(&v) { headers.insert(reqwest::header::AUTHORIZATION, hv); }
if let Some(acc) = auth.get_account_id().or_else(|| extract_chatgpt_account_id(&tok)) {
if let Ok(name) = reqwest::header::HeaderName::from_bytes(b"ChatGPT-Account-Id") {
if let Ok(hv) = reqwest::header::HeaderValue::from_str(&acc) { headers.insert(name, hv); }
}
}
}}
}
if let Some(acc) = auth.get_account_id().or_else(|| extract_chatgpt_account_id(&tok))
&& let Ok(name) = reqwest::header::HeaderName::from_bytes(b"ChatGPT-Account-Id")
&& let Ok(hv) = reqwest::header::HeaderValue::from_str(&acc) { headers.insert(name, hv); }
}
}
let res = crate::env_detect::list_environments(&base_url, &headers).await;
let _ = tx2.send(app::AppEvent::EnvironmentsLoaded(res));
@@ -1042,17 +1036,14 @@ pub async fn run_main(_cli: Cli, _codex_linux_sandbox_exe: Option<PathBuf>) -> a
codex_login::AuthMode::ChatGPT,
"codex_cloud_tasks_tui".to_string(),
);
if let Some(auth) = am.auth() {
if let Ok(tok) = auth.get_token().await { if !tok.is_empty() {
if let Some(auth) = am.auth()
&& let Ok(tok) = auth.get_token().await && !tok.is_empty() {
let v = format!("Bearer {tok}");
if let Ok(hv) = reqwest::header::HeaderValue::from_str(&v) { headers.insert(reqwest::header::AUTHORIZATION, hv); }
if let Some(acc) = auth.get_account_id().or_else(|| extract_chatgpt_account_id(&tok)) {
if let Ok(name) = reqwest::header::HeaderName::from_bytes(b"ChatGPT-Account-Id") {
if let Ok(hv) = reqwest::header::HeaderValue::from_str(&acc) { headers.insert(name, hv); }
}
}
}}
}
if let Some(acc) = auth.get_account_id().or_else(|| extract_chatgpt_account_id(&tok))
&& let Ok(name) = reqwest::header::HeaderName::from_bytes(b"ChatGPT-Account-Id")
&& let Ok(hv) = reqwest::header::HeaderValue::from_str(&acc) { headers.insert(name, hv); }
}
}
let res = crate::env_detect::list_environments(&base_url, &headers).await;
let _ = tx2.send(app::AppEvent::EnvironmentsLoaded(res));
@@ -1078,14 +1069,14 @@ pub async fn run_main(_cli: Cli, _codex_linux_sandbox_exe: Option<PathBuf>) -> a
let backend2 = backend.clone();
let tx2 = tx.clone();
tokio::spawn(async move {
match codex_cloud_tasks_api::CloudBackend::get_task_diff(&*backend2, task.id.clone()).await {
match codex_cloud_tasks_client::CloudBackend::get_task_diff(&*backend2, task.id.clone()).await {
Ok(diff) => {
let _ = tx2.send(app::AppEvent::DetailsDiffLoaded { id: task.id, title: task.title, diff });
}
Err(e) => {
// Always log errors while we debug non-success states.
append_error_log(format!("get_task_diff failed for {}: {e}", task.id.0));
match codex_cloud_tasks_api::CloudBackend::get_task_messages(&*backend2, task.id.clone()).await {
match codex_cloud_tasks_client::CloudBackend::get_task_messages(&*backend2, task.id.clone()).await {
Ok(msgs) => {
let _ = tx2.send(app::AppEvent::DetailsMessagesLoaded { id: task.id, title: task.title, messages: msgs });
}
@@ -1102,7 +1093,7 @@ pub async fn run_main(_cli: Cli, _codex_linux_sandbox_exe: Option<PathBuf>) -> a
}
KeyCode::Char('a') => {
if let Some(task) = app.tasks.get(app.selected) {
match codex_cloud_tasks_api::CloudBackend::get_task_diff(&*backend, task.id.clone()).await {
match codex_cloud_tasks_client::CloudBackend::get_task_diff(&*backend, task.id.clone()).await {
Ok(_) => {
app.apply_modal = Some(app::ApplyModalState { task_id: task.id.clone(), title: task.title.clone(), result_message: None, result_level: None, skipped_paths: Vec::new(), conflict_paths: Vec::new() });
app.apply_preflight_inflight = true;
@@ -1113,14 +1104,14 @@ pub async fn run_main(_cli: Cli, _codex_linux_sandbox_exe: Option<PathBuf>) -> a
let title2 = task.title.clone();
tokio::spawn(async move {
unsafe { std::env::set_var("CODEX_APPLY_PREFLIGHT", "1") };
let out = codex_cloud_tasks_api::CloudBackend::apply_task(&*backend2, id2.clone()).await;
let out = codex_cloud_tasks_client::CloudBackend::apply_task(&*backend2, id2.clone()).await;
unsafe { std::env::remove_var("CODEX_APPLY_PREFLIGHT") };
let evt = match out {
Ok(outcome) => {
let level = match outcome.status {
codex_cloud_tasks_api::ApplyStatus::Success => app::ApplyResultLevel::Success,
codex_cloud_tasks_api::ApplyStatus::Partial => app::ApplyResultLevel::Partial,
codex_cloud_tasks_api::ApplyStatus::Error => app::ApplyResultLevel::Error,
codex_cloud_tasks_client::ApplyStatus::Success => app::ApplyResultLevel::Success,
codex_cloud_tasks_client::ApplyStatus::Partial => app::ApplyResultLevel::Partial,
codex_cloud_tasks_client::ApplyStatus::Error => app::ApplyResultLevel::Error,
};
app::AppEvent::ApplyPreflightFinished { id: id2, title: title2, message: outcome.message, level, skipped: outcome.skipped_paths, conflicts: outcome.conflict_paths }
}

View File

@@ -19,7 +19,7 @@ use std::sync::OnceLock;
use crate::app::App;
use chrono::Local;
use chrono::Utc;
use codex_cloud_tasks_api::TaskStatus;
use codex_cloud_tasks_client::TaskStatus;
pub fn draw(frame: &mut Frame, app: &mut App) {
let area = frame.area();
@@ -502,7 +502,7 @@ fn style_diff_line(raw: &str) -> Line<'static> {
Line::from(vec![Span::raw(raw.to_string())])
}
fn render_task_item(_app: &App, t: &codex_cloud_tasks_api::TaskSummary) -> ListItem<'static> {
fn render_task_item(_app: &App, t: &codex_cloud_tasks_client::TaskSummary) -> ListItem<'static> {
let status = match t.status {
TaskStatus::Ready => "READY".green(),
TaskStatus::Pending => "PENDING".magenta(),

View File

@@ -1,6 +1,6 @@
#![deny(clippy::unwrap_used, clippy::expect_used)]
use codex_cloud_tasks_api::CloudBackend;
use codex_cloud_tasks_client::CloudBackend;
use codex_cloud_tasks_client::MockClient;
#[tokio::test]