Files
cursor-byok/server/tests/revision_branch.rs
T
2026-08-26 20:20:38 +08:00

313 lines
9.6 KiB
Rust

#[path = "support/fixtures.rs"]
mod fixtures;
use cursor_server::model::{
ConversationId, ModelSpec, PreparedRun, PromptSpec, RunAction, RunId, RunKind,
};
fn prepared(
run_id: &str,
conversation_id: &ConversationId,
base_revision_id: cursor_server::model::RevisionId,
) -> PreparedRun {
PreparedRun {
run_id: RunId::new(run_id),
cursor_request_id: None,
conversation_id: conversation_id.clone(),
kind: RunKind::Root,
model: ModelSpec::new("test-model"),
prompt: PromptSpec {
instructions: "test".into(),
tools: Vec::new(),
},
initial_messages: Vec::new(),
action: RunAction::Resume {
pending_tool_round: None,
},
base_revision_id,
}
}
#[tokio::test]
async fn selecting_an_old_revision_creates_a_branch_without_old_suffixes() {
let (_directory, store) = fixtures::temp_store().await;
let conversation_id = ConversationId::new("conversation");
let root = store.ensure_conversation(&conversation_id).await.unwrap();
let first = prepared("run-1", &conversation_id, root);
store.claim_run(&first).await.unwrap();
let a = fixtures::user("a", "A");
let revision_a = store
.append_revision(
&conversation_id,
&first.run_id,
root,
std::slice::from_ref(&a),
)
.await
.unwrap();
let b = fixtures::user("b", "B");
let revision_b = store
.append_revision(
&conversation_id,
&first.run_id,
revision_a,
std::slice::from_ref(&b),
)
.await
.unwrap();
let second = prepared("run-2", &conversation_id, revision_a);
let claimed = store.claim_run(&second).await.unwrap();
assert_eq!(claimed.replaced_run_id.as_ref(), Some(&first.run_id));
let c = fixtures::user("c", "C");
let revision_c = store
.append_revision(
&conversation_id,
&second.run_id,
revision_a,
std::slice::from_ref(&c),
)
.await
.unwrap();
assert_eq!(
store.load_revision_messages(revision_b).await.unwrap(),
vec![a.clone(), b]
);
assert_eq!(
store.load_revision_messages(revision_c).await.unwrap(),
vec![a, c]
);
assert!(store
.append_revision(
&conversation_id,
&first.run_id,
revision_b,
&[fixtures::user("late", "late")],
)
.await
.is_err());
}
#[tokio::test]
async fn reused_cursor_request_id_maps_to_the_current_distinct_execution() {
let (_directory, store) = fixtures::temp_store().await;
let conversation_id = ConversationId::new("queued-conversation");
let root = store.ensure_conversation(&conversation_id).await.unwrap();
let mut first = prepared("reused-request:11111111", &conversation_id, root);
first.cursor_request_id = Some("reused-request".into());
store.claim_run(&first).await.unwrap();
assert_eq!(
store
.active_run_for_cursor_request("reused-request")
.await
.unwrap(),
Some(first.run_id.clone())
);
let mut second = prepared("reused-request:22222222", &conversation_id, root);
second.cursor_request_id = Some("reused-request".into());
store.claim_run(&second).await.unwrap();
assert_eq!(
store
.active_run_for_cursor_request("reused-request")
.await
.unwrap(),
Some(second.run_id)
);
}
#[tokio::test]
async fn identical_runtime_event_is_exactly_once_and_conflicts_are_rejected() {
let (_directory, store) = fixtures::temp_store().await;
let conversation_id = ConversationId::new("runtime");
let root = store.ensure_conversation(&conversation_id).await.unwrap();
let run = prepared("run", &conversation_id, root);
store.claim_run(&run).await.unwrap();
let event = cursor_server::model::RuntimeEvent {
event_id: "branch:changed:7".into(),
text: "runtime state changed".into(),
}
.into_message();
let (revision, inserted) = store
.append_message_once(&conversation_id, &run.run_id, root, &event)
.await
.unwrap();
assert!(inserted);
let (same, inserted) = store
.append_message_once(&conversation_id, &run.run_id, revision, &event)
.await
.unwrap();
assert_eq!(same, revision);
assert!(!inserted);
let conflict = cursor_server::model::RuntimeEvent {
event_id: "branch:changed:7".into(),
text: "different".into(),
}
.into_message();
assert!(store
.append_message_once(&conversation_id, &run.run_id, revision, &conflict)
.await
.is_err());
}
#[tokio::test]
async fn editing_a_logical_input_discards_its_active_suffix() {
let (_directory, store) = fixtures::temp_store().await;
let conversation_id = ConversationId::new("edited-conversation");
let root = store.ensure_conversation(&conversation_id).await.unwrap();
let input_id = "cursor:user:stable-id";
assert_eq!(
store
.anchor_input(&conversation_id, input_id, root)
.await
.unwrap(),
root
);
let first = prepared("first-run", &conversation_id, root);
store.claim_run(&first).await.unwrap();
let original = fixtures::user("original", "original text");
let original_revision = store
.append_revision(
&conversation_id,
&first.run_id,
root,
std::slice::from_ref(&original),
)
.await
.unwrap();
let suffix = fixtures::user("suffix", "old suffix");
let old_head = store
.append_revision(
&conversation_id,
&first.run_id,
original_revision,
std::slice::from_ref(&suffix),
)
.await
.unwrap();
let edit_base = store
.anchor_input(&conversation_id, input_id, old_head)
.await
.unwrap();
assert_eq!(edit_base, root);
let second = prepared("second-run", &conversation_id, edit_base);
store.claim_run(&second).await.unwrap();
let edited = fixtures::user("edited", "edited text");
let edited_head = store
.append_revision(
&conversation_id,
&second.run_id,
edit_base,
std::slice::from_ref(&edited),
)
.await
.unwrap();
assert_eq!(
store.load_revision_messages(edited_head).await.unwrap(),
vec![edited]
);
assert_eq!(
store.load_revision_messages(old_head).await.unwrap(),
vec![original, suffix]
);
}
#[tokio::test]
async fn retry_reuses_only_the_matching_initial_child_chain() {
let (_directory, store) = fixtures::temp_store().await;
let conversation_id = ConversationId::new("retry-conversation");
let root = store.ensure_conversation(&conversation_id).await.unwrap();
let first = prepared("first-run", &conversation_id, root);
store.claim_run(&first).await.unwrap();
let context = fixtures::user("request-context:event", "context");
let context_revision = store
.append_revision(
&conversation_id,
&first.run_id,
root,
std::slice::from_ref(&context),
)
.await
.unwrap();
let runtime = cursor_server::model::RuntimeEvent {
event_id: "cursor:user:stable-id:version".into(),
text: "query".into(),
}
.into_message();
let (partial_revision, partial_count) = store
.match_revision_prefix(&conversation_id, root, &[context.clone(), runtime.clone()])
.await
.unwrap();
assert_eq!(partial_revision, context_revision);
assert_eq!(partial_count, 1);
let runtime_revision = store
.append_revision(
&conversation_id,
&first.run_id,
context_revision,
std::slice::from_ref(&runtime),
)
.await
.unwrap();
let suffix = fixtures::user("old-answer", "old answer");
let old_head = store
.append_revision(
&conversation_id,
&first.run_id,
runtime_revision,
std::slice::from_ref(&suffix),
)
.await
.unwrap();
let (retry_base, reused) = store
.match_revision_prefix(&conversation_id, root, &[context.clone(), runtime.clone()])
.await
.unwrap();
assert_eq!(retry_base, runtime_revision);
assert_eq!(reused, 2);
assert_eq!(
store.load_revision_messages(retry_base).await.unwrap(),
vec![context.clone(), runtime.clone()]
);
let retry = prepared("retry-run", &conversation_id, retry_base);
let claimed = store.claim_run(&retry).await.unwrap();
assert_eq!(claimed.replaced_run_id.as_ref(), Some(&first.run_id));
let first_status: String = sqlx::query_scalar("SELECT status FROM runs WHERE run_id = ?")
.bind(first.run_id.as_str())
.fetch_one(store.pool())
.await
.unwrap();
assert_eq!(first_status, "cancelled");
let changed = cursor_server::model::RuntimeEvent {
event_id: "cursor:user:stable-id:changed-version".into(),
text: "edited query".into(),
}
.into_message();
let (changed_base, reused) = store
.match_revision_prefix(&conversation_id, root, &[context, changed])
.await
.unwrap();
assert_eq!(changed_base, context_revision);
assert_eq!(reused, 1);
assert_eq!(
store.load_revision_messages(old_head).await.unwrap(),
vec![
fixtures::user("request-context:event", "context"),
runtime,
suffix
]
);
}