test: connect Drive publication to client and resource lifetimes
This commit is contained in:
@@ -132,6 +132,16 @@ impl WorkerExecutionBackend for LaunchExecutionBackend {
|
||||
self.execution.create_working_directory(request)
|
||||
}
|
||||
|
||||
fn cleanup_working_directory(
|
||||
&self,
|
||||
id: &str,
|
||||
) -> std::result::Result<
|
||||
worker_runtime::catalog::WorkingDirectoryStatus,
|
||||
worker_runtime::working_directory::WorkingDirectoryDiagnostic,
|
||||
> {
|
||||
self.execution.cleanup_working_directory(id)
|
||||
}
|
||||
|
||||
fn spawn_worker(&self, mut request: WorkerExecutionSpawnRequest) -> WorkerExecutionSpawnResult {
|
||||
self.creates.lock().unwrap().push(request.request.clone());
|
||||
for claim in &request.request.workdir_attachments {
|
||||
@@ -164,9 +174,9 @@ impl WorkerExecutionBackend for LaunchExecutionBackend {
|
||||
}
|
||||
}
|
||||
|
||||
struct LaunchFixture {
|
||||
pub(super) struct LaunchFixture {
|
||||
_temp: tempfile::TempDir,
|
||||
api: WorkspaceApi,
|
||||
pub(super) api: WorkspaceApi,
|
||||
backend: Arc<LaunchExecutionBackend>,
|
||||
runtime_id: String,
|
||||
runtime_server: Option<tokio::task::JoinHandle<()>>,
|
||||
@@ -182,11 +192,51 @@ impl Drop for LaunchFixture {
|
||||
}
|
||||
|
||||
impl LaunchFixture {
|
||||
// Reuse the authenticated Runtime HTTP harness for Drive/source lifetime
|
||||
// assertions without duplicating its trust/bootstrap/listener lifecycle.
|
||||
pub(super) async fn with_source_workdir() -> (
|
||||
Self,
|
||||
worker_runtime::working_directory::WorkingDirectoryBinding,
|
||||
) {
|
||||
let fixture = Self::new(true).await;
|
||||
init_clean_git_workspace(fixture._temp.path());
|
||||
let (_, Json(created)) = create_workspace_working_directory(
|
||||
&fixture.api,
|
||||
TEST_WORKSPACE_ID,
|
||||
Some(&fixture.runtime_id),
|
||||
BrowserWorkingDirectoryCreateRequest {
|
||||
runtime_id: Some(fixture.runtime_id.clone()),
|
||||
display_name: None,
|
||||
repository_key: "test-repository".into(),
|
||||
selector: Some("HEAD".into()),
|
||||
operation_id: Some("drive-source-workdir".into()),
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
let binding = fixture
|
||||
.backend
|
||||
.execution
|
||||
.materializer
|
||||
.bind_working_directory(&created.item.working_directory_id, None)
|
||||
.unwrap();
|
||||
(fixture, binding)
|
||||
}
|
||||
|
||||
async fn new(remote: bool) -> Self {
|
||||
let temp = tempfile::tempdir().unwrap();
|
||||
let config = test_server_config(temp.path());
|
||||
let store = SqliteWorkspaceStore::open_for_test(config.database_path.clone()).unwrap();
|
||||
let backend = Arc::new(LaunchExecutionBackend::default());
|
||||
let backend = Arc::new(LaunchExecutionBackend {
|
||||
execution: DeterministicExecutionBackend {
|
||||
materializer: worker_runtime::working_directory::RuntimeGitMaterializer::new(
|
||||
temp.path()
|
||||
.join(".test-embedded-runtime-store/source-workdirs"),
|
||||
),
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
});
|
||||
let api =
|
||||
WorkspaceApi::new_with_execution_backend(config, Arc::new(store), backend.clone())
|
||||
.await
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
//! Worker tools -> signed WorkspaceClient -> real isolated Server authority.
|
||||
//! No Git checkout, live DB, network service, or DTO-only assertions. Workdir
|
||||
//! import tests attach an explicitly read-scoped temporary source session.
|
||||
//! Tools/native WIP -> signed WorkspaceClient -> isolated Server DB and LocalFileSystem.
|
||||
//! Default fixtures have no Repository/Workdir and substitute Runtime execution.
|
||||
//! Lifetime cases use real embedded Runtime stop/removal or the shared authenticated
|
||||
//! Runtime HTTP harness for actual Git Workdir removal; controller execution remains
|
||||
//! deterministic. Import sessions have explicit read scope (and real materializer
|
||||
//! leases in the removal case). No external model, live data, or browser is used.
|
||||
//! Run with the normal Server lib target and the `worker_drive_tests` filter.
|
||||
use super::*;
|
||||
use agen::tool::{Attachment, ToolError, ToolExecutionContext, ToolOutput};
|
||||
use std::result::Result;
|
||||
@@ -148,28 +152,46 @@ struct Fixture {
|
||||
}
|
||||
impl Fixture {
|
||||
async fn new(access: [&str; 2]) -> Self {
|
||||
Self::with_runtime(access, false).await
|
||||
}
|
||||
async fn with_runtime(access: [&str; 2], embedded: bool) -> Self {
|
||||
let temp = tempfile::tempdir().unwrap();
|
||||
let mut config = test_server_config(temp.path());
|
||||
config.repositories.clear();
|
||||
let store = Arc::new(SqliteWorkspaceStore::open_for_test(&config.database_path).unwrap());
|
||||
let mut api = WorkspaceApi::new_with_execution_backend(
|
||||
config,
|
||||
store,
|
||||
Arc::new(DeterministicExecutionBackend::default()),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
let execution = Arc::new(DeterministicExecutionBackend {
|
||||
materializer: worker_runtime::working_directory::RuntimeGitMaterializer::new(
|
||||
temp.path().join("runtime-workdirs"),
|
||||
),
|
||||
..Default::default()
|
||||
});
|
||||
let api = WorkspaceApi::new_with_execution_backend(config, store, execution)
|
||||
.await
|
||||
.unwrap();
|
||||
Self::on_api(api, temp, access, embedded).await
|
||||
}
|
||||
async fn on_api(
|
||||
mut api: WorkspaceApi,
|
||||
temp: tempfile::TempDir,
|
||||
access: [&str; 2],
|
||||
embedded: bool,
|
||||
) -> Self {
|
||||
let owner = seed_test_api_token(api.store.as_ref(), TEST_WORKSPACE_ID);
|
||||
register_test_runtime(&api, WorkdirlessFixtureRuntime::RUNTIME_ID).await;
|
||||
api.runtime
|
||||
.register_or_replace(WorkdirlessFixtureRuntime::default());
|
||||
let runtime_id = if embedded {
|
||||
EMBEDDED_RUNTIME_ID
|
||||
} else {
|
||||
register_test_runtime(&api, WorkdirlessFixtureRuntime::RUNTIME_ID).await;
|
||||
api.runtime
|
||||
.register_or_replace(WorkdirlessFixtureRuntime::default());
|
||||
WorkdirlessFixtureRuntime::RUNTIME_ID
|
||||
};
|
||||
let mut workers = Vec::new();
|
||||
for name in ["Drive first", "Drive second"] {
|
||||
let Json(created) = create_workspace_worker(
|
||||
State(api.clone()),
|
||||
HeaderMap::new(),
|
||||
Json(CreateWorkspaceWorkerRequest {
|
||||
runtime_id: WorkdirlessFixtureRuntime::RUNTIME_ID.into(),
|
||||
runtime_id: runtime_id.into(),
|
||||
display_name: name.into(),
|
||||
singleton_key: None,
|
||||
profile: Some("builtin:coder".into()),
|
||||
@@ -248,6 +270,45 @@ impl Fixture {
|
||||
.unwrap(),
|
||||
)
|
||||
}
|
||||
async fn upload_text(&self, name: &str, text: &str) -> Value {
|
||||
use sha2::{Digest, Sha256};
|
||||
let root = self.root(0).await;
|
||||
let digest: String = Sha256::digest(text.as_bytes())
|
||||
.iter()
|
||||
.map(|b| format!("{b:02x}"))
|
||||
.collect();
|
||||
let query = url::form_urlencoded::Serializer::new(String::new())
|
||||
.append_pair("operation", "create")
|
||||
.append_pair("request_id", &format!("user-upload-{name}"))
|
||||
.append_pair("entry_workspace_id", TEST_WORKSPACE_ID)
|
||||
.append_pair("parent_id", root["node_id"].as_str().unwrap())
|
||||
.append_pair("name", name)
|
||||
.append_pair("content_type", "text/markdown")
|
||||
.append_pair("size", &text.len().to_string())
|
||||
.append_pair("sha256", &digest)
|
||||
.finish();
|
||||
let response = build_router(self.api.clone())
|
||||
.oneshot(
|
||||
Request::builder()
|
||||
.method("PUT")
|
||||
.uri(format!("{}/upload?{query}", base()))
|
||||
.header("authorization", format!("Bearer {}", self.owner))
|
||||
.header(CONTENT_TYPE, "application/octet-stream")
|
||||
.body(Body::from(text.to_owned()))
|
||||
.unwrap(),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
let status = response.status();
|
||||
let bytes = to_bytes(response.into_body(), 1024 * 1024).await.unwrap();
|
||||
assert_eq!(
|
||||
status,
|
||||
StatusCode::OK,
|
||||
"{}",
|
||||
String::from_utf8_lossy(&bytes)
|
||||
);
|
||||
serde_json::from_slice(&bytes).unwrap()
|
||||
}
|
||||
fn importer(&self, worker: usize) -> DriveFeature {
|
||||
let root = self._temp.path().join("import-source");
|
||||
fs::create_dir_all(root.join("public")).unwrap();
|
||||
@@ -415,6 +476,339 @@ async fn workdirless_workers_share_markdown_through_current_read_and_write_grant
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn user_uploaded_markdown_roundtrips_through_workdirless_tools_and_native_wip_at_one_identity()
|
||||
{
|
||||
let f = Fixture::new(["read_write", "read_only"]).await;
|
||||
let original = "# ユーザー資料\n未処理\n";
|
||||
let updated_text = "# ユーザー資料\n処理済み ✓\n";
|
||||
for native in [false, true] {
|
||||
let uploaded = f
|
||||
.upload_text(if native { "native.md" } else { "tools.md" }, original)
|
||||
.await;
|
||||
let id = uploaded["entry"]["entry"].clone();
|
||||
let updated = if native {
|
||||
let tools = native_tools(&f.features[0]);
|
||||
// Resolve the production path projection, not an invented node path.
|
||||
let metadata = value(
|
||||
tool(&f.features[0], "DriveMetadata", json!({"entry":id}))
|
||||
.await
|
||||
.unwrap(),
|
||||
);
|
||||
let path = metadata["path"].as_str().unwrap();
|
||||
let inspected = value(
|
||||
native_tool(&tools, "Inspect", json!({"path":path,"refresh":true}))
|
||||
.await
|
||||
.unwrap(),
|
||||
);
|
||||
let reference = inspected["interfaces"][0]["reference"].clone();
|
||||
let read = value(
|
||||
native_tool(
|
||||
&tools,
|
||||
"Invoke",
|
||||
json!({"path":path,"interface":reference,"operation":"read","arguments":{}}),
|
||||
)
|
||||
.await
|
||||
.unwrap(),
|
||||
);
|
||||
assert_eq!(read["text"], original);
|
||||
assert_eq!(read["entry"]["metadata"]["entry"], id);
|
||||
assert_eq!(
|
||||
read["entry"]["metadata"]["last_mutation_id"],
|
||||
uploaded["entry"]["last_mutation_id"]
|
||||
);
|
||||
value(native_tool(&tools, "Invoke", json!({"path":path,"interface":reference,"operation":"write","arguments":{"content":updated_text}})).await.unwrap())
|
||||
} else {
|
||||
let read = value(
|
||||
tool(&f.features[0], "DriveRead", json!({"entry":id}))
|
||||
.await
|
||||
.unwrap(),
|
||||
);
|
||||
assert_eq!(read["text"], original);
|
||||
assert_eq!(read["entry"]["metadata"]["entry"], id);
|
||||
assert_eq!(
|
||||
read["entry"]["metadata"]["last_mutation_id"],
|
||||
uploaded["entry"]["last_mutation_id"]
|
||||
);
|
||||
value(
|
||||
tool(
|
||||
&f.features[0],
|
||||
"DriveWrite",
|
||||
json!({"entry":id,"content":updated_text}),
|
||||
)
|
||||
.await
|
||||
.unwrap(),
|
||||
)
|
||||
};
|
||||
assert_eq!(entry(&updated), id);
|
||||
let mutation = &updated["entry"]["metadata"]["last_mutation_id"];
|
||||
assert_ne!(*mutation, uploaded["entry"]["last_mutation_id"]);
|
||||
assert_eq!(*mutation, updated["request_id"]);
|
||||
let independent = value(
|
||||
tool(&f.features[1], "DriveRead", json!({"entry":id}))
|
||||
.await
|
||||
.unwrap(),
|
||||
);
|
||||
assert_eq!(independent["text"], updated_text);
|
||||
assert_eq!(independent["entry"], updated["entry"]);
|
||||
let metadata = owner_call(
|
||||
&f.api,
|
||||
&f.owner,
|
||||
"GET",
|
||||
&format!(
|
||||
"{}/metadata?entry_workspace_id={TEST_WORKSPACE_ID}&id={}",
|
||||
base(),
|
||||
id["node_id"].as_str().unwrap()
|
||||
),
|
||||
None,
|
||||
)
|
||||
.await;
|
||||
assert_eq!(metadata, updated["entry"]["metadata"]);
|
||||
let receipt = owner_call(
|
||||
&f.api,
|
||||
&f.owner,
|
||||
"GET",
|
||||
&format!(
|
||||
"{}/requests/{}",
|
||||
base(),
|
||||
updated["request_id"].as_str().unwrap()
|
||||
),
|
||||
None,
|
||||
)
|
||||
.await;
|
||||
assert_eq!(receipt["state"], "committed");
|
||||
assert_eq!(receipt["response"]["entry"], metadata);
|
||||
let response = build_router(f.api.clone()).oneshot(Request::builder()
|
||||
.uri(format!("{}/download?entry_workspace_id={TEST_WORKSPACE_ID}&id={}&expected_mutation_id={}", base(), id["node_id"].as_str().unwrap(), mutation.as_str().unwrap()))
|
||||
.header("authorization", format!("Bearer {}", f.owner))
|
||||
.body(Body::empty()).unwrap()).await.unwrap();
|
||||
assert_eq!(response.status(), StatusCode::OK, "native={native}");
|
||||
assert_eq!(
|
||||
to_bytes(response.into_body(), 65536)
|
||||
.await
|
||||
.unwrap()
|
||||
.as_ref(),
|
||||
updated_text.as_bytes()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn source_workdir_removal_keeps_published_drive_bytes_and_receipt() {
|
||||
let (source, binding) =
|
||||
super::ticket_worker_launch_tests::LaunchFixture::with_source_workdir().await;
|
||||
let workdir_id = binding.working_directory.id.clone();
|
||||
let f = Fixture::on_api(
|
||||
source.api.clone(),
|
||||
tempfile::tempdir().unwrap(),
|
||||
["read_write", "read_only"],
|
||||
false,
|
||||
)
|
||||
.await;
|
||||
let bytes = b"clean\n";
|
||||
let root_path = binding.root().to_owned();
|
||||
let materialization_root = binding.working_directory_root().to_owned();
|
||||
let scope = manifest::Scope::from_config(&manifest::ScopeConfig {
|
||||
allow: vec![manifest::ScopeRule {
|
||||
target: root_path.clone(),
|
||||
permission: manifest::Permission::Read,
|
||||
recursive: true,
|
||||
symlink_policy: manifest::SymlinkPolicy::Resolved,
|
||||
}],
|
||||
deny: vec![],
|
||||
})
|
||||
.unwrap();
|
||||
let sessions = Arc::new(WorkdirSessionRouter::default());
|
||||
sessions
|
||||
.attach(
|
||||
workdir::WorkdirAttachmentAlias::new("source").unwrap(),
|
||||
Arc::new(
|
||||
workdir::LocalWorkdirSession::materialized_bound_with_environment(
|
||||
workdir::Workdir::new(workdir_id.clone()),
|
||||
root_path.clone(),
|
||||
root_path.clone(),
|
||||
manifest::SharedScope::new(scope),
|
||||
workdir::WorkdirSessionCapabilities::READ_ONLY,
|
||||
binding.command_environment(),
|
||||
binding.session_resources().unwrap(),
|
||||
),
|
||||
),
|
||||
)
|
||||
.unwrap();
|
||||
let importer = DriveFeature::new(f.clients[0].clone(), sessions.clone());
|
||||
let root = f.root(0).await;
|
||||
let saved = value(tool(&importer, "DriveSaveWorkdir", json!({"target_workdir":"source","path":"README.md","parent":root,"name":"retained.md","content_type":"text/markdown"})).await.unwrap());
|
||||
sessions.close_all().await.unwrap();
|
||||
drop(importer);
|
||||
drop(sessions);
|
||||
drop(binding);
|
||||
assert!(root_path.join("README.md").exists());
|
||||
let api = source.api.clone();
|
||||
let removal_id = workdir_id.clone();
|
||||
let removed = tokio::task::spawn_blocking(move || {
|
||||
execute_workdir_removal(
|
||||
&api,
|
||||
&removal_id,
|
||||
"account:drive-test",
|
||||
"source no longer needed",
|
||||
)
|
||||
})
|
||||
.await
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
removed.disposition,
|
||||
server_api::WorkingDirectoryRemovalDisposition::Removed,
|
||||
"{removed:?}"
|
||||
);
|
||||
assert!(!removed.retryable);
|
||||
assert!(
|
||||
!materialization_root.exists(),
|
||||
"actual source materialization must be gone"
|
||||
);
|
||||
assert!(
|
||||
f.api
|
||||
.store
|
||||
.get_workdir_registry(TEST_WORKSPACE_ID, &workdir_id)
|
||||
.unwrap()
|
||||
.is_none()
|
||||
);
|
||||
let fresh = DriveFeature::new(
|
||||
f.clients[1].clone(),
|
||||
Arc::new(WorkdirSessionRouter::default()),
|
||||
);
|
||||
let read = value(
|
||||
tool(&fresh, "DriveRead", json!({"entry":entry(&saved)}))
|
||||
.await
|
||||
.unwrap(),
|
||||
);
|
||||
assert_eq!(read["entry"], saved["entry"]);
|
||||
assert_eq!(read["text"].as_str().unwrap().as_bytes(), bytes);
|
||||
let receipt = value(
|
||||
tool(
|
||||
&fresh,
|
||||
"DriveRequestStatus",
|
||||
json!({"request_id":saved["request_id"]}),
|
||||
)
|
||||
.await
|
||||
.unwrap(),
|
||||
);
|
||||
assert_eq!(receipt["state"], "committed");
|
||||
assert_eq!(receipt["response"]["entry"], saved["entry"]["metadata"]);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn worker_removal_preserves_drive_reference_for_explicitly_granted_replacement() {
|
||||
// Real embedded Runtime lifecycle/retention; only controller execution is
|
||||
// deterministic. No Workdir or external model is involved in this case.
|
||||
let f = Fixture::with_runtime(["read_write", "read_only"], true).await;
|
||||
let saved = f.create("retained.md", "# Beyond the Worker\n").await;
|
||||
let id = entry(&saved);
|
||||
let old = &f.clients[0].worker;
|
||||
let worker = WorkspaceWorker::resolve_async(&f.api, &old.runtime_id, &old.worker_id)
|
||||
.await
|
||||
.unwrap();
|
||||
let stopped = worker
|
||||
.stop(
|
||||
&WorkerOperationContext::Backend,
|
||||
WorkerLifecycleRequest {
|
||||
reason: Some("Drive lifetime test".into()),
|
||||
ticket_assignment: None,
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(stopped.state, InternalWorkerOperationState::Accepted);
|
||||
assert_eq!(f.api.runtime.worker(old).unwrap().state, "stopped");
|
||||
seed_worker_session_for_cleanup(f._temp.path(), old);
|
||||
let plan = build_runtime_cleanup_plan(&f.api, &old.runtime_id).unwrap();
|
||||
let candidate = plan
|
||||
.workers
|
||||
.iter()
|
||||
.find(|c| c.runtime_worker_id == old.worker_id)
|
||||
.unwrap();
|
||||
assert_eq!(candidate.blocking_reason, None);
|
||||
worker
|
||||
.delete_from_plan(&WorkerOperationContext::Backend, candidate)
|
||||
.await
|
||||
.unwrap();
|
||||
assert!(f.api.runtime.worker(old).is_err());
|
||||
assert!(
|
||||
f.api
|
||||
.store
|
||||
.get_worker_registry(TEST_WORKSPACE_ID, old)
|
||||
.unwrap()
|
||||
.is_none()
|
||||
);
|
||||
denied(tool(&f.features[0], "DriveRead", json!({"entry":id})).await);
|
||||
let Json(created) = create_workspace_worker(
|
||||
State(f.api.clone()),
|
||||
HeaderMap::new(),
|
||||
Json(CreateWorkspaceWorkerRequest {
|
||||
runtime_id: EMBEDDED_RUNTIME_ID.into(),
|
||||
display_name: "Replacement reader".into(),
|
||||
singleton_key: None,
|
||||
profile: Some("builtin:coder".into()),
|
||||
ticket_assignment: None,
|
||||
initial_submit: vec![],
|
||||
workdir_attachments: vec![],
|
||||
feature_connections: Default::default(),
|
||||
control_operation_id: None,
|
||||
}),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_ne!(created.worker_id, old.worker_id);
|
||||
let client = Arc::new(RouterClient {
|
||||
api: f.api.clone(),
|
||||
identity: f.clients[0].identity.clone(),
|
||||
worker: RuntimeWorkerRef::new(created.runtime_id, created.worker_id),
|
||||
handle: tokio::runtime::Handle::current(),
|
||||
lose_next_mutation_response: AtomicBool::new(false),
|
||||
lose_next_upload_response: AtomicBool::new(false),
|
||||
requests: Mutex::new(vec![]),
|
||||
});
|
||||
let replacement = DriveFeature::new(client.clone(), Arc::new(WorkdirSessionRouter::default()));
|
||||
denied(tool(&replacement, "DriveRead", json!({"entry":id})).await);
|
||||
owner_call(&f.api, &f.owner, "POST", &format!("{}/grants", base()), Some(json!({"runtime_id":client.worker.runtime_id,"worker_id":client.worker.worker_id,"access":"read_only"}))).await;
|
||||
let read = value(
|
||||
tool(&replacement, "DriveRead", json!({"entry":id}))
|
||||
.await
|
||||
.unwrap(),
|
||||
);
|
||||
assert_eq!(read["entry"], saved["entry"]);
|
||||
assert_eq!(read["text"], "# Beyond the Worker\n");
|
||||
let receipt = owner_call(
|
||||
&f.api,
|
||||
&f.owner,
|
||||
"GET",
|
||||
&format!(
|
||||
"{}/requests/{}",
|
||||
base(),
|
||||
saved["request_id"].as_str().unwrap()
|
||||
),
|
||||
None,
|
||||
)
|
||||
.await;
|
||||
assert_eq!(receipt["state"], "committed");
|
||||
assert_eq!(receipt["response"]["entry"], saved["entry"]["metadata"]);
|
||||
// Stop remaining deterministic controllers before dropping isolated resources.
|
||||
for target in [&f.clients[1].worker, &client.worker] {
|
||||
let stopped = f
|
||||
.api
|
||||
.runtime
|
||||
.stop_worker(
|
||||
target,
|
||||
WorkerLifecycleRequest {
|
||||
reason: None,
|
||||
ticket_assignment: None,
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(stopped.state, InternalWorkerOperationState::Accepted);
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn simultaneous_worker_edits_publish_exactly_one_last_mutation_id_and_preserve_the_loser_conflict()
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user