feat: 通过 Host 日志同步类型化任务记录
This commit is contained in:
@@ -146,6 +146,122 @@ async fn real_core_notes_roundtrip_only_through_bound_host_and_confirm_commits()
|
||||
.await;
|
||||
assert_eq!(status, 409, "{denied}");
|
||||
assert_eq!(denied["error"]["code"], "VAULT_PERMISSION_CHANGED");
|
||||
|
||||
let legacy_database = rusqlite::Connection::open(
|
||||
temp.path()
|
||||
.join("core/vault-state")
|
||||
.join(&vault)
|
||||
.join("core.sqlite3"),
|
||||
)
|
||||
.unwrap();
|
||||
legacy_database.execute("INSERT INTO tasks VALUES ('task_00000000000000000000000000000002','Legacy task','','todo',?1,NULL,'2026-09-08T00:00:00+00:00','2026-09-08T00:00:00+00:00')",[file_id]).unwrap();
|
||||
let task_operation = uuid::Uuid::new_v4().to_string();
|
||||
let task_body = json!({"title":"Host task","note_id":file_id});
|
||||
let (status, task) = request(
|
||||
&mut core,
|
||||
"POST",
|
||||
"/api/tasks",
|
||||
&vault,
|
||||
&task_operation,
|
||||
Some(task_body.clone()),
|
||||
)
|
||||
.await;
|
||||
assert_eq!(status, 200, "{task}");
|
||||
let task_id = task["task_id"].as_str().unwrap();
|
||||
for _ in 0..20 {
|
||||
let (status, replay) = request(
|
||||
&mut core,
|
||||
"POST",
|
||||
"/api/tasks",
|
||||
&vault,
|
||||
&task_operation,
|
||||
Some(task_body.clone()),
|
||||
)
|
||||
.await;
|
||||
assert_eq!(status, 200, "{replay}");
|
||||
assert_eq!(replay, task);
|
||||
}
|
||||
let (status, changed_request) = request(
|
||||
&mut core,
|
||||
"POST",
|
||||
"/api/tasks",
|
||||
&vault,
|
||||
&task_operation,
|
||||
Some(json!({"title":"changed","note_id":file_id})),
|
||||
)
|
||||
.await;
|
||||
assert_eq!(status, 409, "{changed_request}");
|
||||
let record_path = root.join(format!("opennexus-records/v1/tasks/{task_id}.json"));
|
||||
assert!(record_path.is_file());
|
||||
let (status, task_updated) = request(
|
||||
&mut core,
|
||||
"PATCH",
|
||||
&format!("/api/tasks/{task_id}"),
|
||||
&vault,
|
||||
&uuid::Uuid::new_v4().to_string(),
|
||||
Some(json!({"status":"done"})),
|
||||
)
|
||||
.await;
|
||||
assert_eq!(status, 200, "{task_updated}");
|
||||
assert_eq!(task_updated["status"], "done");
|
||||
let (status, tasks) = request(
|
||||
&mut core,
|
||||
"GET",
|
||||
"/api/tasks",
|
||||
&vault,
|
||||
&uuid::Uuid::new_v4().to_string(),
|
||||
None,
|
||||
)
|
||||
.await;
|
||||
assert_eq!(status, 200, "{tasks}");
|
||||
assert_eq!(tasks["items"].as_array().unwrap().len(), 2);
|
||||
let (status, task_denied) = request(
|
||||
&mut core,
|
||||
"GET",
|
||||
"/api/tasks",
|
||||
&uuid::Uuid::new_v4().to_string(),
|
||||
&uuid::Uuid::new_v4().to_string(),
|
||||
None,
|
||||
)
|
||||
.await;
|
||||
assert_eq!(status, 409, "{task_denied}");
|
||||
let deletion = uuid::Uuid::new_v4().to_string();
|
||||
for _ in 0..2 {
|
||||
let (status, result) = request(
|
||||
&mut core,
|
||||
"DELETE",
|
||||
&format!("/api/tasks/{task_id}"),
|
||||
&vault,
|
||||
&deletion,
|
||||
None,
|
||||
)
|
||||
.await;
|
||||
assert_eq!(status, 200, "{result}");
|
||||
}
|
||||
assert!(!record_path.exists());
|
||||
let source: String = legacy_database
|
||||
.query_row(
|
||||
"SELECT title FROM tasks WHERE task_id='task_00000000000000000000000000000002'",
|
||||
[],
|
||||
|r| r.get(0),
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(source, "Legacy task");
|
||||
legacy_database
|
||||
.execute("DELETE FROM index_meta WHERE key='tasks_host_owned_v1'", [])
|
||||
.unwrap();
|
||||
let (status, after_migration) = request(
|
||||
&mut core,
|
||||
"GET",
|
||||
"/api/tasks",
|
||||
&vault,
|
||||
&uuid::Uuid::new_v4().to_string(),
|
||||
None,
|
||||
)
|
||||
.await;
|
||||
assert_eq!(status, 200, "{after_migration}");
|
||||
assert_eq!(after_migration["items"].as_array().unwrap().len(), 1);
|
||||
|
||||
let (status, deleted) = request(
|
||||
&mut core,
|
||||
"DELETE",
|
||||
@@ -168,7 +284,7 @@ async fn real_core_notes_roundtrip_only_through_bound_host_and_confirm_commits()
|
||||
.await;
|
||||
assert_eq!(status, 200, "{search}");
|
||||
assert!(!search.to_string().contains(file_id));
|
||||
assert_eq!(workspace.lock().unwrap().pending_count().unwrap(), 4);
|
||||
assert_eq!(workspace.lock().unwrap().pending_count().unwrap(), 8);
|
||||
assert!(!temp
|
||||
.path()
|
||||
.join("core/unbound-vault/Core fixture.md")
|
||||
|
||||
@@ -423,6 +423,67 @@ async fn actual_service_accepts_ordered_push_and_repeat_commit_without_duplicate
|
||||
.content,
|
||||
"only-local"
|
||||
);
|
||||
|
||||
let task_id = "task_00000000000000000000000000000001";
|
||||
let task_path = format!("opennexus-records/v1/tasks/{task_id}.json");
|
||||
let task_record = json!({"schema":1,"kind":"task","id":task_id,"data":{"title":"Synchronized task","description":"","status":"todo","note_id":first.file_id,"due_at_ms":null,"created_at_ms":0,"updated_at_ms":0}});
|
||||
workspace
|
||||
.lock()
|
||||
.unwrap()
|
||||
.write(
|
||||
&task_path,
|
||||
"",
|
||||
&serde_json::to_vec(&task_record).unwrap(),
|
||||
"local",
|
||||
)
|
||||
.unwrap();
|
||||
client.push_one(&workspace, &binding).await.unwrap();
|
||||
client_b.pull_page(&workspace_b, &binding_b).await.unwrap();
|
||||
{
|
||||
let mut ws = workspace_b.lock().unwrap();
|
||||
let task = ws.record_get(task_id).unwrap().unwrap();
|
||||
assert_eq!(task["record"], task_record);
|
||||
let mut next = task["record"].clone();
|
||||
next["data"]["status"] = json!("done");
|
||||
ws.write(
|
||||
&task_path,
|
||||
task["hash"].as_str().unwrap(),
|
||||
&serde_json::to_vec(&next).unwrap(),
|
||||
"local",
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
client_b.push_one(&workspace_b, &binding_b).await.unwrap();
|
||||
client.pull_page(&workspace, &binding).await.unwrap();
|
||||
assert_eq!(
|
||||
workspace
|
||||
.lock()
|
||||
.unwrap()
|
||||
.record_get(task_id)
|
||||
.unwrap()
|
||||
.unwrap()["record"]["data"]["status"],
|
||||
"done"
|
||||
);
|
||||
{
|
||||
let mut ws = workspace_b.lock().unwrap();
|
||||
let task = ws.record_get(task_id).unwrap().unwrap();
|
||||
ws.mutate_operation(
|
||||
"delete",
|
||||
&task_path,
|
||||
"",
|
||||
task["hash"].as_str().unwrap(),
|
||||
&uuid::Uuid::new_v4().to_string(),
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
client_b.push_one(&workspace_b, &binding_b).await.unwrap();
|
||||
client.pull_page(&workspace, &binding).await.unwrap();
|
||||
assert!(workspace
|
||||
.lock()
|
||||
.unwrap()
|
||||
.record_get(task_id)
|
||||
.unwrap()
|
||||
.is_none());
|
||||
// Kill the actual client process after each durable 10 MiB server offset,
|
||||
// before its response reaches the client. The next process must query offset.
|
||||
use sha2::{Digest, Sha256};
|
||||
|
||||
Reference in New Issue
Block a user