1pub mod cloud;
14pub mod models;
15pub mod orchestrator;
16pub mod settings;
17pub mod theme;
18
19#[cfg(test)]
20mod tests {
21 use crate::models::ModelValidator;
22 use crate::settings::Settings;
23 use crate::theme::ThemeVariant;
24
25 #[test]
26 fn test_settings_default() {
27 let settings = Settings::default();
28 assert_eq!(settings.theme, ThemeVariant::EverforestDark);
29 assert_eq!(settings.endpoint, "localhost:11434");
30 assert_eq!(settings.local_model, "[SELECT]");
31 assert_eq!(settings.api_key, "sk-or-v1-982...b52");
32 assert_eq!(settings.cloud_model, "[SELECT]");
33 }
34
35 #[test]
36 fn test_settings_validation() {
37 let settings = Settings::default();
38 let result = settings.is_valid();
39
40 assert!(result.is_err());
42 }
43
44 #[tokio::test]
45 async fn test_openrouter_integration() {
46 let validator = ModelValidator::new();
47 let api_key = "sk-or-v1-test-key-redacted";
48
49 let result = validator.fetch_openrouter_models(api_key).await;
50
51 match result {
52 Ok(models) => {
53 println!("โ
OpenRouter test passed! Found {} models", models.len());
54 assert!(!models.is_empty(), "Should find at least some models");
55
56 let free_models: Vec<_> = models
58 .iter()
59 .filter(|m| m.pricing.prompt == "0" && m.pricing.completion == "0")
60 .collect();
61 let paid_models: Vec<_> = models
62 .iter()
63 .filter(|m| m.pricing.prompt != "0" || m.pricing.completion != "0")
64 .collect();
65
66 println!(
67 " ๐ Found {} free models, {} paid models",
68 free_models.len(),
69 paid_models.len()
70 );
71 assert!(
72 !free_models.is_empty(),
73 "Should have at least some free models"
74 );
75 assert!(
76 !paid_models.is_empty(),
77 "Should have at least some paid models"
78 );
79
80 println!(" ๐ Free models:");
82 for (i, model) in free_models.iter().enumerate().take(3) {
83 println!(
84 " {}. {} ({}k context)",
85 i + 1,
86 model.name,
87 model.context_length / 1000
88 );
89 }
90
91 println!(" ๐ฐ Paid models:");
93 for (i, model) in paid_models.iter().enumerate().take(2) {
94 println!(
95 " {}. {} ({}k context)",
96 i + 1,
97 model.name,
98 model.context_length / 1000
99 );
100 }
101 }
102 Err(e) => {
103 println!("โ OpenRouter test failed: {}", e);
104 panic!("OpenRouter integration test failed: {}", e);
105 }
106 }
107 }
108
109 #[tokio::test]
110 async fn test_ollama_integration() {
111 let validator = ModelValidator::new();
112 let endpoint = "localhost:11434";
113
114 let result = validator.fetch_ollama_models(endpoint).await;
115
116 match result {
117 Ok(models) => {
118 println!("โ
Ollama test passed! Found {} local models", models.len());
119
120 for (i, model) in models.iter().enumerate() {
122 println!(" {}. {} ({})", i + 1, model.name, model.size);
123 }
124
125 if let Some(first_model) = models.first() {
127 println!("๐ Testing validation with model: {}", first_model.name);
128 let validation_result = validator
129 .validate_local_endpoint(endpoint, &first_model.name)
130 .await;
131 match validation_result {
132 Ok(()) => println!("โ
Local endpoint validation passed!"),
133 Err(e) => println!("โ Local endpoint validation failed: {}", e),
134 }
135 }
136 }
137 Err(e) => {
138 println!("โ ๏ธ Ollama test skipped - not running or accessible: {}", e);
139 }
141 }
142 }
143
144 #[tokio::test]
145 async fn test_dual_endpoint_validation() {
146 use crate::settings::Settings;
147
148 println!("๐ Testing dual endpoint validation...");
149
150 let validator = ModelValidator::new();
151
152 let ollama_models = validator.fetch_ollama_models("localhost:11434").await;
154 let api_key = "sk-or-v1-test-key-redacted";
155 let openrouter_models = validator.fetch_openrouter_models(api_key).await;
156
157 match (ollama_models, openrouter_models) {
158 (Ok(local_models), Ok(cloud_models))
159 if !local_models.is_empty() && !cloud_models.is_empty() =>
160 {
161 println!("๐ฆ Testing with real models:");
162 println!(" Local: {}", local_models[0].name);
163 println!(" Cloud: {}", cloud_models[0].name);
164
165 let mut settings = Settings::default();
167 settings.local_model = local_models[0].name.clone();
168 settings.cloud_model = cloud_models[0].id.clone();
169 settings.api_key = api_key.to_string();
170
171 match settings.validate_local_only().await {
173 Ok(()) => println!("โ
Local-only validation passed!"),
174 Err(e) => println!("โ Local-only validation failed: {:?}", e),
175 }
176
177 match settings.validate_cloud_only().await {
179 Ok(()) => println!("โ
Cloud-only validation passed!"),
180 Err(e) => println!("โ Cloud-only validation failed: {:?}", e),
181 }
182
183 match settings.validate_endpoints().await {
185 Ok(()) => println!("โ
Dual endpoint validation passed!"),
186 Err(e) => println!("โ Dual endpoint validation failed: {:?}", e),
187 }
188 }
189 (Ok(local_models), Ok(cloud_models)) => {
190 println!("๐ฆ Models found but lists might be empty:");
192 println!(" Local models: {}", local_models.len());
193 println!(" Cloud models: {}", cloud_models.len());
194 }
195 (Err(local_err), Ok(cloud_models)) => {
196 println!("โ ๏ธ Local endpoint not available: {}", local_err);
197 println!(
198 "โ
Cloud endpoint working with {} models",
199 cloud_models.len()
200 );
201 }
202 (Ok(local_models), Err(cloud_err)) => {
203 println!(
204 "โ
Local endpoint working with {} models",
205 local_models.len()
206 );
207 println!("โ ๏ธ Cloud endpoint not available: {}", cloud_err);
208 }
209 (Err(local_err), Err(cloud_err)) => {
210 println!("โ ๏ธ Both endpoints unavailable:");
211 println!(" Local: {}", local_err);
212 println!(" Cloud: {}", cloud_err);
213 }
214 }
215 }
216
217 #[tokio::test]
218 async fn test_modal_model_loading() {
219 println!("๐งช Testing modal model loading scenarios...");
220
221 let validator = ModelValidator::new();
222 let api_key = "sk-or-v1-test-key-redacted";
223
224 match validator.fetch_openrouter_models(api_key).await {
226 Ok(models) => {
227 println!("โ
Cloud models loaded for modal: {} models", models.len());
228 assert!(!models.is_empty(), "Should have models for display");
229
230 let mut free_count = 0;
232 let mut paid_count = 0;
233 let mut found_paid_after_free = false;
234
235 for model in models.iter() {
236 let is_free = model.pricing.prompt == "0" && model.pricing.completion == "0";
237 if is_free {
238 free_count += 1;
239 assert!(
241 !found_paid_after_free,
242 "Free models should come before paid models"
243 );
244 } else {
245 paid_count += 1;
246 found_paid_after_free = true;
247 }
248 }
249
250 println!(
251 " ๐ Model breakdown: {} free, {} paid",
252 free_count, paid_count
253 );
254 assert!(free_count > 0, "Should have at least some free models");
255 assert!(paid_count > 0, "Should have at least some paid models");
256
257 for model in models.iter().take(3) {
259 let pricing_type =
260 if model.pricing.prompt == "0" && model.pricing.completion == "0" {
261 "(free)"
262 } else {
263 "(paid)"
264 };
265 println!(
266 " - {} {} ({} tokens)",
267 model.name, pricing_type, model.context_length
268 );
269 assert!(!model.name.is_empty(), "Model name should not be empty");
270 assert!(!model.id.is_empty(), "Model ID should not be empty");
271 assert!(
272 model.context_length > 0,
273 "Context length should be positive"
274 );
275 }
276 }
277 Err(e) => {
278 panic!("Failed to load OpenRouter models for modal: {}", e);
279 }
280 }
281
282 match validator.fetch_ollama_models("localhost:11434").await {
284 Ok(models) => {
285 println!("โ
Local models loaded for modal: {} models", models.len());
286 for model in models.iter().take(3) {
287 println!(" - {} ({})", model.name, model.size);
288 assert!(!model.name.is_empty(), "Model name should not be empty");
289 assert!(!model.size.is_empty(), "Model size should not be empty");
290 }
291 }
292 Err(e) => {
293 println!("โ ๏ธ Local models test skipped (Ollama not running): {}", e);
294 }
295 }
296 }
297
298 #[test]
299 fn test_api_key_truncation() {
300 fn format_api_key_display(api_key: &str) -> String {
302 if api_key.is_empty() {
303 return String::new();
304 }
305
306 if api_key.len() <= 21 {
307 api_key.to_string()
308 } else {
309 format!("{}...{}", &api_key[..15], &api_key[api_key.len() - 3..])
310 }
311 }
312
313 let test_key = "sk-or-v1-test-key-redacted";
314 let formatted = format_api_key_display(test_key);
315
316 println!("๐งช Testing API key truncation:");
317 println!(" Original: {} (len: {})", test_key, test_key.len());
318 println!(" Formatted: {} (len: {})", formatted, formatted.len());
319
320 assert_eq!(formatted, "sk-or-v1-7d9200...3ac");
321 assert_eq!(formatted.len(), 21); let short_key = "sk-test-123";
325 let short_formatted = format_api_key_display(short_key);
326 assert_eq!(short_formatted, short_key);
327
328 let empty_formatted = format_api_key_display("");
330 assert_eq!(empty_formatted, "");
331
332 println!("โ
API key truncation test passed!");
333 }
334}