1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
//! The `deliver_to` session grammar resolves a target session (fork #144).
//!
//! Cron jobs can now deliver results into a session's notify queue via
//! `session:<uuid|prefix>` — the target is resolved through the shared
//! session-id resolver (`crate::cli::session_resolve`). These tests pin the
//! pure resolution core (`resolve_session_target`) against in-memory session
//! sets: full UUIDs pass through, unambiguous prefixes of any length
//! resolve, garbage rejects, duplicate rows are ambiguous. The DB-loading wrapper (`parse_session_target`) is a thin
//! async shell over this core — prefix behavior against a live DB is the
//! resolver's own test-suite concern.
use crate::cron::scheduler::resolve_session_target;
use uuid::Uuid;
fn session_with_id(id: Uuid) -> crate::db::models::Session {
crate::db::models::Session {
id,
title: None,
model: None,
provider_name: None,
created_at: chrono::Utc::now(),
updated_at: chrono::Utc::now(),
archived_at: None,
token_count: 0,
total_cost: 0.0,
working_directory: None,
auto_title_attempted: false,
project_id: None,
}
}
/// A full UUID passes through untouched — the resolver's fast path, no DB.
#[test]
fn full_uuid_resolves_without_db() {
let id = Uuid::new_v4();
let resolved = resolve_session_target(&[], &id.to_string()).unwrap();
assert_eq!(resolved, id);
}
/// A full UUID resolves even when the session set is non-empty and doesn't
/// contain it (fast-path passthrough parity — presence is the caller's check).
#[test]
fn full_uuid_passthrough_ignores_set() {
let id = Uuid::new_v4();
let other = session_with_id(Uuid::new_v4());
let resolved = resolve_session_target(&[other], &id.to_string()).unwrap();
assert_eq!(resolved, id);
}
/// A valid prefix (8+ chars, exactly one match) resolves to the full id.
#[test]
fn valid_prefix_resolves() {
let id = Uuid::new_v4();
let sessions = vec![session_with_id(id)];
let prefix = id.to_string()[..8].to_string();
assert_eq!(resolve_session_target(&sessions, &prefix), Some(id));
}
/// A prefix matching no session is `None` (resolver: 0 matches -> Err).
#[test]
fn unknown_prefix_rejects() {
let sessions = vec![session_with_id(Uuid::new_v4())];
assert_eq!(resolve_session_target(&sessions, "zzzzzzzz"), None);
}
/// A prefix shorter than the 8 chars `session list` displays still resolves
/// when unambiguous — the resolver matches any-length prefixes; the 8-char
/// figure is a display convention, not a matching rule.
#[test]
fn short_unambiguous_prefix_resolves() {
let id = Uuid::new_v4();
let sessions = vec![session_with_id(id)];
let short = id.to_string()[..4].to_string();
assert_eq!(resolve_session_target(&sessions, &short), Some(id));
}
/// Garbage (not a UUID, not a prefix of anything) is `None`, not a panic.
#[test]
fn garbage_rejects() {
assert_eq!(
resolve_session_target(&[], "not-a-uuid-or-prefix-at-all"),
None
);
assert_eq!(resolve_session_target(&[], ""), None);
}
/// The resolver is row-count-based: two rows sharing a prefix are ambiguous
/// even when the rows carry the SAME id — `None`, matching the shared
/// resolver's contract verbatim (row count, not distinct-id count).
#[test]
fn duplicate_rows_are_ambiguous() {
let id = Uuid::new_v4();
let sessions = vec![session_with_id(id), session_with_id(id)];
let prefix = id.to_string()[..8].to_string();
assert_eq!(resolve_session_target(&sessions, &prefix), None);
}