返回 CodeWhale
tests.rs
根目录 / crates / config / src / legacy_root / tests.rs
1 use super::*;
2
3 fn table(body: &str) -> toml::Table {
4 toml::from_str(body).expect("fixture parses")
5 }
6
7 fn canonical(body: &str) -> (toml::Table, LegacyRootMigration) {
8 let mut root = table(body);
9 let receipt = apply_to_table(&mut root);
10 (root, receipt)
11 }
12
13 fn at<'a>(root: &'a toml::Table, path: &[&str]) -> Option<&'a str> {
14 let (last, parents) = path.split_last()?;
15 let mut current = root;
16 for part in parents {
17 current = current.get(*part)?.as_table()?;
18 }
19 current.get(*last)?.as_str()
20 }
21
22 fn document(body: &str) -> toml_edit::DocumentMut {
23 body.parse().expect("fixture parses as a document")
24 }
25
26 #[test]
27 fn rule_three_moves_a_deepseek_endpoint_and_key_into_the_deepseek_table() {
28 let (root, receipt) = canonical(
29 r#"
30 base_url = "https://proxy.example.test/v1"
31 api_key = "sk-root"
32 "#,
33 );
34 assert_eq!(
35 at(&root, &["providers", "deepseek", "base_url"]),
36 Some("https://proxy.example.test/v1")
37 );
38 assert_eq!(
39 at(&root, &["providers", "deepseek", "api_key"]),
40 Some("sk-root")
41 );
42 assert!(!root.contains_key("base_url") && !root.contains_key("api_key"));
43 assert!(root.get("provider").is_none(), "no guess for a plain host");
44 assert_eq!(
45 receipt.summary().as_deref(),
46 Some("moved top-level base_url to [providers.deepseek], api_key to [providers.deepseek]")
47 );
48 }
49
50 #[test]
51 fn camel_case_aliases_move_too() {
52 let (root, _) = canonical("baseUrl = \"https://proxy.example.test/v1\"\napiKey = \"sk\"\n");
53 assert_eq!(
54 at(&root, &["providers", "deepseek", "base_url"]),
55 Some("https://proxy.example.test/v1")
56 );
57 assert_eq!(at(&root, &["providers", "deepseek", "api_key"]), Some("sk"));
58 assert!(!has_legacy_root_keys(&root));
59 }
60
61 #[test]
62 fn rule_one_turns_the_literal_custom_route_into_a_custom_table() {
63 let (root, receipt) = canonical(
64 r#"
65 provider = "custom"
66 base_url = "http://127.0.0.1:18181/v1"
67 api_key = "sk-custom"
68 default_text_model = "my-local-model"
69 "#,
70 );
71 assert_eq!(
72 at(&root, &["providers", "custom", "base_url"]),
73 Some("http://127.0.0.1:18181/v1")
74 );
75 assert_eq!(
76 at(&root, &["providers", "custom", "api_key"]),
77 Some("sk-custom")
78 );
79 assert_eq!(
80 at(&root, &["providers", "custom", "model"]),
81 Some("my-local-model")
82 );
83 assert_eq!(at(&root, &["default_text_model"]), Some("my-local-model"));
84 assert!(
85 root["providers"]
86 .as_table()
87 .unwrap()
88 .get("deepseek")
89 .is_none()
90 );
91 assert!(
92 receipt
93 .notes
94 .iter()
95 .any(|note| matches!(note, LegacyRootNote::CopiedModel { .. }))
96 );
97 }
98
99 #[test]
100 fn literal_custom_with_its_own_table_leaves_the_root_to_deepseek() {
101 let (root, _) = canonical(
102 r#"
103 provider = "custom"
104 base_url = "https://proxy.example.test/v1"
105
106 [providers.custom]
107 base_url = "http://127.0.0.1:9/v1"
108 "#,
109 );
110 assert_eq!(
111 at(&root, &["providers", "custom", "base_url"]),
112 Some("http://127.0.0.1:9/v1")
113 );
114 assert_eq!(
115 at(&root, &["providers", "deepseek", "base_url"]),
116 Some("https://proxy.example.test/v1")
117 );
118 }
119
120 #[test]
121 fn rule_two_sends_a_foreign_official_host_to_its_vendor() {
122 for (url, table) in [
123 ("https://api.xiaomimimo.com/v1", "xiaomi_mimo"),
124 ("https://token-plan-sgp.xiaomimimo.com/v1", "xiaomi_mimo"),
125 ("https://openrouter.ai/api/v1", "openrouter"),
126 ("https://api.moonshot.ai/v1", "moonshot"),
127 ("https://api.openai.com/v1", "openai"),
128 ("https://chatgpt.com/backend-api", "openai_codex"),
129 ("https://integrate.api.nvidia.com/v1", "nvidia_nim"),
130 (
131 "https://ark.cn-beijing.volces.com/api/coding/v3",
132 "volcengine",
133 ),
134 ] {
135 let (root, _) = canonical(&format!(
136 "provider = \"deepseek\"\nbase_url = \"{url}\"\napi_key = \"sk-deepseek\"\n"
137 ));
138 assert_eq!(
139 at(&root, &["providers", table, "base_url"]),
140 Some(url),
141 "{url} belongs to {table}"
142 );
143 // A leftover DeepSeek key never follows a URL to another vendor.
144 assert_eq!(
145 at(&root, &["providers", "deepseek", "api_key"]),
146 Some("sk-deepseek")
147 );
148 assert!(at(&root, &["providers", table, "api_key"]).is_none());
149 }
150 }
151
152 #[test]
153 fn loopback_and_deepseek_hosts_stay_with_deepseek() {
154 for url in [
155 "http://localhost:11434/v1",
156 "https://api.deepseek.com/beta",
157 "https://api.deepseek.com/anthropic",
158 ] {
159 let (root, _) = canonical(&format!("base_url = \"{url}\"\n"));
160 assert_eq!(at(&root, &["providers", "deepseek", "base_url"]), Some(url));
161 }
162 }
163
164 #[test]
165 fn provider_guesses_are_written_only_when_no_layer_sets_provider() {
166 let (root, receipt) = canonical("base_url = \"https://integrate.api.nvidia.com/v1\"\n");
167 assert_eq!(at(&root, &["provider"]), Some("nvidia-nim"));
168 assert!(receipt.notes.iter().any(|note| matches!(
169 note,
170 LegacyRootNote::GuessedProvider {
171 provider: "nvidia-nim",
172 ..
173 }
174 )));
175
176 let (root, _) = canonical("base_url = \"https://api.deepseeki.com/v1\"\n");
177 assert_eq!(at(&root, &["provider"]), Some("deepseek-cn"));
178 assert_eq!(
179 at(&root, &["providers", "deepseek", "base_url"]),
180 Some("https://api.deepseeki.com/v1")
181 );
182
183 let (root, _) =
184 canonical("provider = \"deepseek\"\nbase_url = \"https://integrate.api.nvidia.com/v1\"\n");
185 assert_eq!(at(&root, &["provider"]), Some("deepseek"));
186
187 // A profile inherits the base file's provider: no guess inside it.
188 let (root, _) = canonical(
189 r#"
190 provider = "deepseek"
191
192 [profiles.nim]
193 base_url = "https://integrate.api.nvidia.com/v1"
194 "#,
195 );
196 assert!(at(&root, &["profiles", "nim", "provider"]).is_none());
197 }
198
199 #[test]
200 fn a_key_follows_the_endpoint_only_to_an_explicitly_selected_vendor() {
201 // The shipped v0.10.0 `[profiles.nvidia-nim]` shape.
202 let (root, _) = canonical(
203 r#"
204 [profiles.nvidia-nim]
205 provider = "nvidia-nim"
206 api_key = "nvapi-key"
207 base_url = "https://integrate.api.nvidia.com/v1"
208 "#,
209 );
210 assert_eq!(
211 at(
212 &root,
213 &[
214 "profiles",
215 "nvidia-nim",
216 "providers",
217 "nvidia_nim",
218 "api_key"
219 ]
220 ),
221 Some("nvapi-key")
222 );
223 assert_eq!(
224 at(
225 &root,
226 &[
227 "profiles",
228 "nvidia-nim",
229 "providers",
230 "nvidia_nim",
231 "base_url"
232 ]
233 ),
234 Some("https://integrate.api.nvidia.com/v1")
235 );
236
237 // The vendor already has a key: the root key stays DeepSeek's.
238 let (root, _) = canonical(
239 r#"
240 provider = "nvidia-nim"
241 api_key = "sk-root"
242 base_url = "https://integrate.api.nvidia.com/v1"
243
244 [providers.nvidia_nim]
245 api_key = "nvapi-own"
246 "#,
247 );
248 assert_eq!(
249 at(&root, &["providers", "nvidia_nim", "api_key"]),
250 Some("nvapi-own")
251 );
252 assert_eq!(
253 at(&root, &["providers", "deepseek", "api_key"]),
254 Some("sk-root")
255 );
256
257 // No explicit provider: the key stays DeepSeek's even on a NIM host.
258 let (root, _) =
259 canonical("api_key = \"sk-root\"\nbase_url = \"https://integrate.api.nvidia.com/v1\"\n");
260 assert_eq!(
261 at(&root, &["providers", "deepseek", "api_key"]),
262 Some("sk-root")
263 );
264 assert!(at(&root, &["providers", "nvidia_nim", "api_key"]).is_none());
265 }
266
267 #[test]
268 fn empty_values_are_dropped() {
269 let (root, receipt) = canonical("base_url = \" \"\napi_key = \"\"\n");
270 assert!(!has_legacy_root_keys(&root));
271 assert!(root.get("providers").is_none());
272 assert_eq!(
273 receipt
274 .notes
275 .iter()
276 .filter(|note| matches!(note, LegacyRootNote::DroppedEmpty { .. }))
277 .count(),
278 2
279 );
280 }
281
282 #[test]
283 fn equal_values_merge_silently() {
284 let (root, receipt) = canonical(
285 r#"
286 base_url = "https://api.deepseek.com/beta"
287
288 [providers.deepseek]
289 base_url = "https://api.deepseek.com/beta"
290 "#,
291 );
292 assert!(!has_legacy_root_keys(&root));
293 assert!(matches!(receipt.notes[..], [LegacyRootNote::Merged { .. }]));
294 }
295
296 const CONFLICT: &str = r#"# my config
297 provider = "deepseek"
298 base_url = "https://root.example.test/v1"
299 api_key = "sk-root"
300
301 [providers.deepseek]
302 base_url = "https://table.example.test/v1"
303 api_key = "sk-table"
304 "#;
305
306 #[test]
307 fn memory_resolves_a_conflict_with_the_runtime_precedence() {
308 let (root, receipt) = canonical(CONFLICT);
309 // base_url: the table wins (both crates already did this).
310 assert_eq!(
311 at(&root, &["providers", "deepseek", "base_url"]),
312 Some("https://table.example.test/v1")
313 );
314 // api_key: the top-level key wins (the key the TUI sent).
315 assert_eq!(
316 at(&root, &["providers", "deepseek", "api_key"]),
317 Some("sk-root")
318 );
319 assert!(!has_legacy_root_keys(&root));
320 assert_eq!(receipt.unresolved_conflicts().count(), 2);
321 assert!(!receipt.changes_file());
322 for line in receipt.lines() {
323 assert!(!line.contains("sk-"), "never prints a value: {line}");
324 assert!(
325 !line.contains("example.test"),
326 "never prints a value: {line}"
327 );
328 }
329 }
330
331 #[test]
332 fn a_document_keeps_a_conflict_unless_told_which_side_to_keep() {
333 let mut doc = document(CONFLICT);
334 let receipt = apply_to_document(&mut doc, None);
335 assert_eq!(
336 doc.to_string(),
337 CONFLICT,
338 "conflicts are never resolved silently"
339 );
340 assert!(!receipt.changes_file());
341
342 let mut doc = document(CONFLICT);
343 apply_to_document(&mut doc, Some(LegacyRootPrefer::TopLevel));
344 let root = table(&doc.to_string());
345 assert!(!has_legacy_root_keys(&root));
346 assert_eq!(
347 at(&root, &["providers", "deepseek", "base_url"]),
348 Some("https://root.example.test/v1")
349 );
350 assert_eq!(
351 at(&root, &["providers", "deepseek", "api_key"]),
352 Some("sk-root")
353 );
354 assert!(doc.to_string().starts_with("# my config\n"));
355
356 let mut doc = document(CONFLICT);
357 apply_to_document(&mut doc, Some(LegacyRootPrefer::Table));
358 let root = table(&doc.to_string());
359 assert!(!has_legacy_root_keys(&root));
360 assert_eq!(
361 at(&root, &["providers", "deepseek", "base_url"]),
362 Some("https://table.example.test/v1")
363 );
364 assert_eq!(
365 at(&root, &["providers", "deepseek", "api_key"]),
366 Some("sk-table")
367 );
368 }
369
370 #[test]
371 fn a_document_migration_keeps_comments_and_is_idempotent() {
372 let body = r#"# header comment
373 provider = "deepseek"
374 # the endpoint
375 base_url = "https://proxy.example.test/v1"
376 reasoning_effort = "max"
377
378 [tui]
379 alternate_screen = "auto"
380 "#;
381 let mut doc = document(body);
382 let receipt = apply_to_document(&mut doc, None);
383 assert!(receipt.changes_file());
384 let migrated = doc.to_string();
385 assert!(migrated.starts_with("# header comment\n"), "{migrated}");
386 assert!(
387 migrated.contains("reasoning_effort = \"max\""),
388 "{migrated}"
389 );
390 assert!(migrated.contains("[tui]"), "{migrated}");
391 let root = table(&migrated);
392 assert_eq!(
393 at(&root, &["providers", "deepseek", "base_url"]),
394 Some("https://proxy.example.test/v1")
395 );
396 assert!(!has_legacy_root_keys(&root));
397
398 let mut again = document(&migrated);
399 assert!(apply_to_document(&mut again, None).is_empty());
400 assert_eq!(again.to_string(), migrated);
401 }
402
403 #[test]
404 fn vision_keeps_the_key_it_inherited() {
405 let (root, _) = canonical(
406 r#"
407 api_key = "sk-root"
408
409 [vision_model]
410 model = "vision-x"
411 base_url = "https://vision.example.test/v1"
412 "#,
413 );
414 assert_eq!(at(&root, &["vision_model", "api_key"]), Some("sk-root"));
415 assert_eq!(
416 at(&root, &["providers", "deepseek", "api_key"]),
417 Some("sk-root")
418 );
419
420 // A vision table with its own key keeps it.
421 let (root, _) = canonical("api_key = \"sk-root\"\n[vision_model]\napi_key = \"sk-vision\"\n");
422 assert_eq!(at(&root, &["vision_model", "api_key"]), Some("sk-vision"));
423 }
424
425 #[test]
426 fn profiles_move_into_their_own_provider_tables() {
427 let (root, _) = canonical(
428 r#"
429 [providers.deepseek]
430 base_url = "https://api.deepseek.com/beta"
431
432 [profiles.work]
433 api_key = "WORK"
434 base_url = "https://work.example.test/v1"
435 "#,
436 );
437 assert_eq!(
438 at(
439 &root,
440 &["profiles", "work", "providers", "deepseek", "base_url"]
441 ),
442 Some("https://work.example.test/v1")
443 );
444 assert_eq!(
445 at(
446 &root,
447 &["profiles", "work", "providers", "deepseek", "api_key"]
448 ),
449 Some("WORK")
450 );
451 assert_eq!(
452 at(&root, &["providers", "deepseek", "base_url"]),
453 Some("https://api.deepseek.com/beta")
454 );
455 }
456
457 #[test]
458 fn restore_conflicts_puts_back_a_pair_a_typed_save_dropped() {
459 // What a typed save renders: the in-memory view, root keys gone.
460 let (canonical_root, _) = canonical(CONFLICT);
461 let rendered = toml::to_string(&canonical_root).unwrap();
462 let mut doc = document(&rendered);
463 assert!(restore_conflicts(&mut doc, CONFLICT));
464 let root = table(&doc.to_string());
465 assert_eq!(
466 at(&root, &["base_url"]),
467 Some("https://root.example.test/v1")
468 );
469 assert_eq!(at(&root, &["api_key"]), Some("sk-root"));
470 assert_eq!(
471 at(&root, &["providers", "deepseek", "base_url"]),
472 Some("https://table.example.test/v1")
473 );
474 assert_eq!(
475 at(&root, &["providers", "deepseek", "api_key"]),
476 Some("sk-table")
477 );
478
479 // The user changed the key during the session: their value wins and the
480 // top-level key stays gone.
481 let mut changed = canonical_root.clone();
482 changed["providers"]["deepseek"]
483 .as_table_mut()
484 .unwrap()
485 .insert("api_key".into(), "sk-new".into());
486 let mut doc = document(&toml::to_string(&changed).unwrap());
487 restore_conflicts(&mut doc, CONFLICT);
488 let root = table(&doc.to_string());
489 assert!(root.get("api_key").is_none());
490 assert_eq!(
491 at(&root, &["providers", "deepseek", "api_key"]),
492 Some("sk-new")
493 );
494 assert_eq!(
495 at(&root, &["base_url"]),
496 Some("https://root.example.test/v1")
497 );
498 }
499
500 #[test]
501 fn settle_after_write_ends_a_conflict_the_write_touched() {
502 let mut doc = document(CONFLICT);
503 let snapshot = conflict_snapshot(&doc);
504 assert_eq!(snapshot.len(), 2);
505 crate::set_config_document_value(&mut doc, &["providers", "deepseek", "api_key"], "sk-set")
506 .unwrap();
507 settle_conflicts_after_write(&mut doc, snapshot);
508 let root = table(&doc.to_string());
509 assert!(root.get("api_key").is_none());
510 assert_eq!(
511 at(&root, &["base_url"]),
512 Some("https://root.example.test/v1")
513 );
514 }
515
516 #[test]
517 fn notices_drain_exactly_once() {
518 // Other tests may queue notices concurrently; follow only this one.
519 let marker = "/tmp/notices-drain-once/config.toml.pre-migrate.bak";
520 let (_, receipt) = canonical("base_url = \"https://proxy.example.test/v1\"\n");
521 queue_notice(&receipt, std::path::Path::new(marker));
522 queue_notice(&receipt, std::path::Path::new(marker));
523 let ours: Vec<String> = take_notices()
524 .into_iter()
525 .filter(|notice| notice.contains(marker))
526 .collect();
527 assert_eq!(ours.len(), 1, "{ours:?}");
528 assert!(ours[0].contains("[providers.deepseek]"), "{ours:?}");
529 assert!(!take_notices().iter().any(|notice| notice.contains(marker)));
530 }
531
531 lines RUST