lattice_agent/
diff_review.rs1use std::path::PathBuf;
9
10use lattice_diff::subsystem::DiffOutcome;
11use lattice_diff::{ProgrammaticDiffBus, ProgrammaticDiffRequest};
12use tokio::sync::oneshot;
13
14use crate::error::{AgentError, Result};
15
16#[derive(Debug, Clone, PartialEq, Eq)]
18pub struct DiffReviewRequest {
19 pub old_file_path: PathBuf,
21 pub new_file_path: PathBuf,
24 pub new_contents: String,
26 pub tab_name: String,
29 pub origin_session: u64,
32}
33
34pub async fn review_diff(bus: &ProgrammaticDiffBus, req: DiffReviewRequest) -> Result<DiffOutcome> {
40 let (tx, rx) = oneshot::channel::<DiffOutcome>();
41 let request = ProgrammaticDiffRequest {
42 old_file_path: req.old_file_path,
43 new_file_path: req.new_file_path,
44 new_contents: req.new_contents,
45 tab_name: req.tab_name,
46 origin_session: req.origin_session,
47 response: tx,
48 };
49 if bus.send(request).is_err() {
50 return Err(AgentError::Bus("programmatic diff receiver is gone".into()));
51 }
52 rx.await
53 .map_err(|_| AgentError::Cancelled("diff review was dismissed".into()))
54}
55
56#[cfg(test)]
57mod tests {
58 use lattice_mode::inbound::make_inbound_raw;
59 use std::sync::Arc;
60 use tokio::sync::Notify;
61
62 use super::*;
63
64 fn req(session: u64) -> DiffReviewRequest {
65 DiffReviewRequest {
66 old_file_path: PathBuf::from("/tmp/a.rs"),
67 new_file_path: PathBuf::from("/tmp/a.rs"),
68 new_contents: "fn main() {}\n".to_string(),
69 tab_name: "openDiff".to_string(),
70 origin_session: session,
71 }
72 }
73
74 #[tokio::test]
77 async fn accept_round_trips_through_the_bus() {
78 let (bus, mut rx) = make_inbound_raw::<ProgrammaticDiffRequest>(Arc::new(Notify::new()));
79
80 let host = tokio::spawn(async move {
81 let request = rx.recv().await.expect("a request should arrive");
82 assert_eq!(request.old_file_path, PathBuf::from("/tmp/a.rs"));
83 assert_eq!(request.new_contents, "fn main() {}\n");
84 assert_eq!(request.origin_session, 7);
85 request
86 .response
87 .send(DiffOutcome::Accept)
88 .expect("caller is still awaiting");
89 });
90
91 let outcome = review_diff(&bus, req(7)).await.expect("accept");
92 assert_eq!(outcome, DiffOutcome::Accept);
93 host.await.expect("host task");
94 }
95
96 #[tokio::test]
97 async fn reject_round_trips_through_the_bus() {
98 let (bus, mut rx) = make_inbound_raw::<ProgrammaticDiffRequest>(Arc::new(Notify::new()));
99 tokio::spawn(async move {
100 let request = rx.recv().await.expect("a request should arrive");
101 let _ = request.response.send(DiffOutcome::Reject);
102 });
103 assert_eq!(
104 review_diff(&bus, req(1)).await.expect("reject"),
105 DiffOutcome::Reject
106 );
107 }
108
109 #[tokio::test]
112 async fn dropped_reply_channel_is_cancelled_not_a_hang() {
113 let (bus, mut rx) = make_inbound_raw::<ProgrammaticDiffRequest>(Arc::new(Notify::new()));
114 tokio::spawn(async move {
115 let request = rx.recv().await.expect("a request should arrive");
116 drop(request); });
118 assert!(matches!(
119 review_diff(&bus, req(1)).await,
120 Err(AgentError::Cancelled(_))
121 ));
122 }
123
124 #[tokio::test]
126 async fn dropped_receiver_is_a_bus_error() {
127 let (bus, rx) = make_inbound_raw::<ProgrammaticDiffRequest>(Arc::new(Notify::new()));
128 drop(rx);
129 assert!(matches!(
130 review_diff(&bus, req(1)).await,
131 Err(AgentError::Bus(_))
132 ));
133 }
134}