1pub mod ast_grep_lang;
2pub mod backup;
3pub mod callgraph;
4pub mod calls;
5pub mod checkpoint;
6pub mod commands;
7pub mod config;
8pub mod context;
9pub mod edit;
10pub mod error;
11pub mod extract;
12pub mod format;
13pub mod imports;
14pub mod indent;
15pub mod language;
16pub mod lsp;
17pub mod lsp_hints;
18pub mod parser;
19pub mod protocol;
20pub mod symbols;
21
22#[cfg(test)]
23mod tests {
24 use super::*;
25 use config::Config;
26 use error::AftError;
27 use protocol::{RawRequest, Response};
28
29 #[test]
32 fn raw_request_deserializes_ping() {
33 let json = r#"{"id":"1","command":"ping"}"#;
34 let req: RawRequest = serde_json::from_str(json).unwrap();
35 assert_eq!(req.id, "1");
36 assert_eq!(req.command, "ping");
37 assert!(req.lsp_hints.is_none());
38 }
39
40 #[test]
41 fn raw_request_deserializes_echo_with_params() {
42 let json = r#"{"id":"2","command":"echo","message":"hello"}"#;
43 let req: RawRequest = serde_json::from_str(json).unwrap();
44 assert_eq!(req.id, "2");
45 assert_eq!(req.command, "echo");
46 assert_eq!(req.params["message"], "hello");
48 }
49
50 #[test]
51 fn raw_request_preserves_unknown_fields() {
52 let json = r#"{"id":"3","command":"ping","future_field":"abc","nested":{"x":1}}"#;
53 let req: RawRequest = serde_json::from_str(json).unwrap();
54 assert_eq!(req.params["future_field"], "abc");
55 assert_eq!(req.params["nested"]["x"], 1);
56 }
57
58 #[test]
59 fn raw_request_with_lsp_hints() {
60 let json = r#"{"id":"4","command":"ping","lsp_hints":{"completions":["foo","bar"]}}"#;
61 let req: RawRequest = serde_json::from_str(json).unwrap();
62 assert!(req.lsp_hints.is_some());
63 let hints = req.lsp_hints.unwrap();
64 assert_eq!(hints["completions"][0], "foo");
65 }
66
67 #[test]
68 fn response_success_round_trip() {
69 let resp = Response::success("42", serde_json::json!({"command": "pong"}));
70 let json_str = serde_json::to_string(&resp).unwrap();
71 let v: serde_json::Value = serde_json::from_str(&json_str).unwrap();
72 assert_eq!(v["id"], "42");
73 assert_eq!(v["ok"], true);
74 assert_eq!(v["command"], "pong");
75 }
76
77 #[test]
78 fn response_error_round_trip() {
79 let resp = Response::error("99", "unknown_command", "unknown command: foo");
80 let json_str = serde_json::to_string(&resp).unwrap();
81 let v: serde_json::Value = serde_json::from_str(&json_str).unwrap();
82 assert_eq!(v["id"], "99");
83 assert_eq!(v["ok"], false);
84 assert_eq!(v["code"], "unknown_command");
85 assert_eq!(v["message"], "unknown command: foo");
86 }
87
88 #[test]
91 fn error_display_symbol_not_found() {
92 let err = AftError::SymbolNotFound {
93 name: "foo".into(),
94 file: "bar.rs".into(),
95 };
96 assert_eq!(err.to_string(), "symbol 'foo' not found in bar.rs");
97 assert_eq!(err.code(), "symbol_not_found");
98 }
99
100 #[test]
101 fn error_display_ambiguous_symbol() {
102 let err = AftError::AmbiguousSymbol {
103 name: "Foo".into(),
104 candidates: vec!["a.rs:10".into(), "b.rs:20".into()],
105 };
106 let s = err.to_string();
107 assert!(s.contains("Foo"));
108 assert!(s.contains("a.rs:10, b.rs:20"));
109 }
110
111 #[test]
112 fn error_display_parse_error() {
113 let err = AftError::ParseError {
114 message: "unexpected token".into(),
115 };
116 assert_eq!(err.to_string(), "parse error: unexpected token");
117 }
118
119 #[test]
120 fn error_display_file_not_found() {
121 let err = AftError::FileNotFound {
122 path: "/tmp/missing.rs".into(),
123 };
124 assert_eq!(err.to_string(), "file not found: /tmp/missing.rs");
125 }
126
127 #[test]
128 fn error_display_invalid_request() {
129 let err = AftError::InvalidRequest {
130 message: "missing field".into(),
131 };
132 assert_eq!(err.to_string(), "invalid request: missing field");
133 }
134
135 #[test]
136 fn error_display_checkpoint_not_found() {
137 let err = AftError::CheckpointNotFound {
138 name: "pre-refactor".into(),
139 };
140 assert_eq!(err.to_string(), "checkpoint not found: pre-refactor");
141 assert_eq!(err.code(), "checkpoint_not_found");
142 }
143
144 #[test]
145 fn error_display_no_undo_history() {
146 let err = AftError::NoUndoHistory {
147 path: "src/main.rs".into(),
148 };
149 assert_eq!(err.to_string(), "no undo history for: src/main.rs");
150 assert_eq!(err.code(), "no_undo_history");
151 }
152
153 #[test]
154 fn error_display_ambiguous_match() {
155 let err = AftError::AmbiguousMatch {
156 pattern: "TODO".into(),
157 count: 5,
158 };
159 assert_eq!(
160 err.to_string(),
161 "pattern 'TODO' matches 5 occurrences, expected exactly 1"
162 );
163 assert_eq!(err.code(), "ambiguous_match");
164 }
165
166 #[test]
167 fn error_to_json_has_code_and_message() {
168 let err = AftError::FileNotFound { path: "/x".into() };
169 let j = err.to_error_json();
170 assert_eq!(j["code"], "file_not_found");
171 assert!(j["message"].as_str().unwrap().contains("/x"));
172 }
173
174 #[test]
177 fn config_default_values() {
178 let cfg = Config::default();
179 assert!(cfg.project_root.is_none());
180 assert_eq!(cfg.validation_depth, 1);
181 assert_eq!(cfg.checkpoint_ttl_hours, 24);
182 assert_eq!(cfg.max_symbol_depth, 10);
183 assert_eq!(cfg.formatter_timeout_secs, 10);
184 assert_eq!(cfg.type_checker_timeout_secs, 30);
185 }
186}