1#[cfg(unix)]
2use crate::services::integration::{nushell_paths_file_contains_path, render_nushell_paths_file};
3use crate::services::{
4 integration::CompletionManager,
5 storage::{
6 config_storage::ConfigStorage, manifest_storage::ManifestStorage,
7 trust_storage::TrustStorage,
8 },
9};
10#[cfg(unix)]
11use crate::utils::platform::shells::installed_shell_commands;
12use crate::utils::static_paths::UpstreamPaths;
13use crate::{output, output::Status};
14#[cfg(windows)]
15use anyhow::Context;
16use anyhow::Result;
17#[cfg(unix)]
18use std::collections::BTreeSet;
19use std::fmt;
20use std::fs;
21use std::io;
22#[cfg(unix)]
23use std::io::Write;
24#[cfg(unix)]
25use std::path::Path;
26
27#[cfg(unix)]
29const SOURCE_LINE_BASH: &str =
30 "[ -f $HOME/.upstream/metadata/paths.sh ] && source $HOME/.upstream/metadata/paths.sh";
31#[cfg(unix)]
32const SOURCE_LINE_FISH: &str =
33 "test -f $HOME/.upstream/metadata/paths.sh; and source $HOME/.upstream/metadata/paths.sh";
34#[cfg(unix)]
35const SOURCE_LINE_NUSHELL: &str = r#"const upstream_paths_nu = if ("~/.upstream/metadata/paths.nu" | path expand | path exists) { ("~/.upstream/metadata/paths.nu" | path expand) } else { null }; source-env $upstream_paths_nu"#;
36
37pub struct InitCheckReport {
38 pub ok: bool,
39 pub messages: Vec<String>,
40}
41
42fn check_ok(report: &mut InitCheckReport, detail: impl fmt::Display) {
43 report
44 .messages
45 .push(format!("{} {}", output::status_cell(Status::Ok), detail));
46}
47
48fn check_fail(report: &mut InitCheckReport, detail: impl fmt::Display) {
49 report.ok = false;
50 report
51 .messages
52 .push(format!("{} {}", output::status_cell(Status::Fail), detail));
53}
54
55#[cfg(windows)]
56fn normalize_windows_path(path: &str) -> String {
57 let mut normalized = path.replace('/', "\\").trim().to_ascii_lowercase();
58 while normalized.ends_with('\\') {
59 normalized.pop();
60 }
61 normalized
62}
63
64pub fn initialize(paths: &UpstreamPaths) -> Result<()> {
65 create_package_dirs(paths)?;
66 create_manifest_file(paths)?;
67 create_trust_file(paths)?;
68 create_metadata_files(paths)?;
69 create_default_config_file(paths)?;
70
71 #[cfg(windows)]
72 add_to_windows_path(paths)?;
73
74 #[cfg(unix)]
75 update_shell_profiles(paths)?;
76
77 Ok(())
78}
79
80fn create_manifest_file(paths: &UpstreamPaths) -> Result<()> {
81 ManifestStorage::new(&ManifestStorage::path_for_root(&paths.dirs.data_dir))?.ensure_current()
82}
83
84fn create_trust_file(paths: &UpstreamPaths) -> Result<()> {
85 TrustStorage::new(&paths.config.trust_file)?.ensure_exists()
86}
87
88pub fn purge_data(paths: &UpstreamPaths) -> Result<()> {
89 if paths.dirs.data_dir.exists() {
90 fs::remove_dir_all(&paths.dirs.data_dir)?;
91 }
92 Ok(())
93}
94
95pub fn check(paths: &UpstreamPaths) -> Result<InitCheckReport> {
96 let mut report = InitCheckReport {
97 ok: true,
98 messages: Vec::new(),
99 };
100
101 for (label, path) in [
102 ("config directory", &paths.dirs.config_dir),
103 ("data directory", &paths.dirs.data_dir),
104 ("metadata directory", &paths.dirs.metadata_dir),
105 ("symlinks directory", &paths.integration.symlinks_dir),
106 ("appimages directory", &paths.install.appimages_dir),
107 ("binaries directory", &paths.install.binaries_dir),
108 ("archives directory", &paths.install.archives_dir),
109 ] {
110 if path.exists() {
111 check_ok(&mut report, format!("{} exists: {}", label, path.display()));
112 } else {
113 check_fail(
114 &mut report,
115 format!("{} missing: {}", label, path.display()),
116 );
117 }
118 }
119
120 let completion_manager = CompletionManager::new(paths);
121 let completion_dirs = completion_manager.installed_shell_completion_dirs();
122 if completion_dirs.is_empty() {
123 check_ok(
124 &mut report,
125 "no supported shells detected for completion installation",
126 );
127 }
128 for (shell, path) in completion_dirs {
129 let label = format!("{shell} completions directory");
130 if path.exists() {
131 check_ok(&mut report, format!("{} exists: {}", label, path.display()));
132 } else {
133 check_fail(
134 &mut report,
135 format!("{} missing: {}", label, path.display()),
136 );
137 }
138 }
139
140 if paths.config.config_file.exists() {
141 check_ok(
142 &mut report,
143 format!("config file exists: {}", paths.config.config_file.display()),
144 );
145 } else {
146 check_fail(
147 &mut report,
148 format!(
149 "config file missing: {}",
150 paths.config.config_file.display()
151 ),
152 );
153 }
154
155 let manifest_file = ManifestStorage::path_for_root(&paths.dirs.data_dir);
156 if manifest_file.exists() {
157 check_ok(
158 &mut report,
159 format!("manifest file exists: {}", manifest_file.display()),
160 );
161 } else {
162 check_fail(
163 &mut report,
164 format!("manifest file missing: {}", manifest_file.display()),
165 );
166 }
167
168 if paths.config.trust_file.exists() {
169 check_ok(
170 &mut report,
171 format!(
172 "trust metadata file exists: {}",
173 paths.config.trust_file.display()
174 ),
175 );
176 } else {
177 check_fail(
178 &mut report,
179 format!(
180 "trust metadata file missing: {}",
181 paths.config.trust_file.display()
182 ),
183 );
184 }
185
186 #[cfg(unix)]
187 check_unix_integration(paths, &mut report)?;
188
189 #[cfg(windows)]
190 check_windows_integration(paths, &mut report)?;
191
192 Ok(report)
193}
194
195#[cfg(windows)]
196fn add_to_windows_path(paths: &UpstreamPaths) -> Result<()> {
197 use winreg::RegKey;
198 use winreg::enums::*;
199
200 let hkcu = RegKey::predef(HKEY_CURRENT_USER);
201 let env_key = hkcu
202 .open_subkey_with_flags("Environment", KEY_READ | KEY_WRITE)
203 .context("Failed to open registry key")?;
204
205 let symlinks_path = paths.integration.symlinks_dir.display().to_string();
206 let symlinks_norm = normalize_windows_path(&symlinks_path);
207
208 let current_path: String = env_key.get_value("Path").unwrap_or_else(|_| String::new());
210
211 let path_entries: Vec<&str> = current_path.split(';').collect();
213 if path_entries
214 .iter()
215 .any(|&p| normalize_windows_path(p) == symlinks_norm)
216 {
217 return Ok(()); }
219
220 let new_path = if current_path.is_empty() {
222 symlinks_path
223 } else {
224 format!("{};{}", symlinks_path, current_path)
225 };
226
227 env_key
228 .set_value("Path", &new_path)
229 .context("Failed to set PATH")?;
230
231 broadcast_environment_change();
233
234 Ok(())
235}
236
237#[cfg(windows)]
238fn broadcast_environment_change() {
239 use std::ptr;
240 use winapi::shared::minwindef::LPARAM;
241 use winapi::um::winuser::{
242 HWND_BROADCAST, SMTO_ABORTIFHUNG, SendMessageTimeoutW, WM_SETTINGCHANGE,
243 };
244
245 unsafe {
246 let env_string: Vec<u16> = "Environment\0".encode_utf16().collect();
247 SendMessageTimeoutW(
248 HWND_BROADCAST,
249 WM_SETTINGCHANGE,
250 0,
251 env_string.as_ptr() as LPARAM,
252 SMTO_ABORTIFHUNG,
253 5000,
254 ptr::null_mut(),
255 );
256 }
257}
258
259fn create_package_dirs(paths: &UpstreamPaths) -> io::Result<()> {
260 fs::create_dir_all(&paths.dirs.config_dir)?;
261 fs::create_dir_all(&paths.dirs.data_dir)?;
262 fs::create_dir_all(&paths.dirs.packages_dir)?;
263 fs::create_dir_all(&paths.dirs.cache_dir)?;
264 fs::create_dir_all(&paths.dirs.metadata_dir)?;
265 fs::create_dir_all(&paths.install.appimages_dir)?;
266 fs::create_dir_all(&paths.install.binaries_dir)?;
267 fs::create_dir_all(&paths.install.archives_dir)?;
268 fs::create_dir_all(&paths.install.tmp_dir)?;
269 fs::create_dir_all(&paths.integration.icons_dir)?;
270 fs::create_dir_all(&paths.integration.symlinks_dir)?;
271 for (_shell, dir) in CompletionManager::new(paths).installed_shell_completion_dirs() {
272 fs::create_dir_all(dir)?;
273 }
274 Ok(())
275}
276
277fn create_default_config_file(paths: &UpstreamPaths) -> Result<()> {
278 if paths.config.config_file.exists() {
279 return Ok(());
280 }
281
282 let storage = ConfigStorage::new(&paths.config.config_file)?;
283 storage.save_config()?;
284 Ok(())
285}
286
287#[cfg(unix)]
288fn create_metadata_files(paths: &UpstreamPaths) -> io::Result<()> {
289 if !paths.config.paths_file.exists() {
290 let export_line = format!(
291 r#"export PATH="{}:$PATH""#,
292 paths.integration.symlinks_dir.display()
293 );
294 fs::write(
295 &paths.config.paths_file,
296 format!(
297 "#!/bin/bash\n# Upstream managed PATH additions\n{}\n",
298 export_line
299 ),
300 )?;
301 }
302 if !paths.config.paths_nu_file.exists() {
303 fs::write(
304 &paths.config.paths_nu_file,
305 render_nushell_paths_file(&[paths.integration.symlinks_dir.display().to_string()]),
306 )?;
307 }
308 Ok(())
309}
310
311#[cfg(windows)]
312fn create_metadata_files(_paths: &UpstreamPaths) -> io::Result<()> {
313 Ok(())
315}
316
317#[cfg(unix)]
318fn update_shell_profiles(paths: &UpstreamPaths) -> io::Result<()> {
319 for shell in installed_shell_commands() {
320 match shell.as_str() {
321 "bash" | "sh" => {
322 add_line_to_profile(paths, ".bashrc", SOURCE_LINE_BASH)?;
323 }
324 "zsh" => {
325 add_line_to_profile(paths, ".zshrc", SOURCE_LINE_BASH)?;
326 }
327 "fish" => {
328 let fish_config = Path::new(".config").join("fish").join("config.fish");
329 add_line_to_profile(paths, &fish_config.to_string_lossy(), SOURCE_LINE_FISH)?;
330 }
331 "nu" => {
332 let nushell_config = Path::new(".config").join("nushell").join("config.nu");
333 add_line_to_profile(
334 paths,
335 &nushell_config.to_string_lossy(),
336 SOURCE_LINE_NUSHELL,
337 )?;
338 }
339 _ => {}
340 }
341 }
342 Ok(())
343}
344
345#[cfg(unix)]
346fn check_unix_integration(paths: &UpstreamPaths, report: &mut InitCheckReport) -> io::Result<()> {
347 let expected_line = format!(
348 r#"export PATH="{}:$PATH""#,
349 paths.integration.symlinks_dir.display()
350 );
351
352 if !paths.config.paths_file.exists() {
353 check_fail(
354 report,
355 format!(
356 "PATH metadata file missing: {}",
357 paths.config.paths_file.display()
358 ),
359 );
360 } else {
361 let content = fs::read_to_string(&paths.config.paths_file)?;
362 if content.contains(&expected_line) {
363 check_ok(
364 report,
365 format!(
366 "PATH metadata file contains symlink export: {}",
367 paths.config.paths_file.display()
368 ),
369 );
370 } else {
371 check_fail(
372 report,
373 format!(
374 "PATH metadata file missing expected export line: {}",
375 paths.config.paths_file.display()
376 ),
377 );
378 }
379 }
380
381 let expected_nushell_path = paths.integration.symlinks_dir.display().to_string();
382
383 if !paths.config.paths_nu_file.exists() {
384 check_fail(
385 report,
386 format!(
387 "Nushell PATH metadata file missing: {}",
388 paths.config.paths_nu_file.display()
389 ),
390 );
391 } else {
392 let content = fs::read_to_string(&paths.config.paths_nu_file)?;
393 if nushell_paths_file_contains_path(&content, &expected_nushell_path) {
394 check_ok(
395 report,
396 format!(
397 "Nushell PATH metadata file contains symlink path: {}",
398 paths.config.paths_nu_file.display()
399 ),
400 );
401 } else {
402 check_fail(
403 report,
404 format!(
405 "Nushell PATH metadata file missing expected symlink path: {}",
406 paths.config.paths_nu_file.display()
407 ),
408 );
409 }
410 }
411
412 let mut profiles_to_check: BTreeSet<(String, String)> = BTreeSet::new();
413 for shell in installed_shell_commands() {
414 match shell.as_str() {
415 "bash" | "sh" => {
416 profiles_to_check.insert((".bashrc".to_string(), SOURCE_LINE_BASH.to_string()));
417 }
418 "zsh" => {
419 profiles_to_check.insert((".zshrc".to_string(), SOURCE_LINE_BASH.to_string()));
420 }
421 "fish" => {
422 profiles_to_check.insert((
423 ".config/fish/config.fish".to_string(),
424 SOURCE_LINE_FISH.to_string(),
425 ));
426 }
427 "nu" => {
428 profiles_to_check.insert((
429 ".config/nushell/config.nu".to_string(),
430 SOURCE_LINE_NUSHELL.to_string(),
431 ));
432 }
433 _ => {}
434 }
435 }
436
437 for (profile_rel, expected_line) in profiles_to_check {
438 let profile_path = paths.dirs.user_dir.join(&profile_rel);
439 if !profile_path.exists() {
440 check_fail(
441 report,
442 format!("Shell profile missing: {}", profile_path.display()),
443 );
444 continue;
445 }
446
447 let content = fs::read_to_string(&profile_path)?;
448 if content.contains(&expected_line) {
449 check_ok(
450 report,
451 format!(
452 "Shell profile contains upstream hook: {}",
453 profile_path.display()
454 ),
455 );
456 } else {
457 check_fail(
458 report,
459 format!(
460 "Shell profile missing upstream hook: {}",
461 profile_path.display()
462 ),
463 );
464 }
465 }
466
467 Ok(())
468}
469
470#[cfg(unix)]
471fn add_line_to_profile(paths: &UpstreamPaths, relative_path: &str, line: &str) -> io::Result<()> {
472 let profile_path = paths.dirs.user_dir.join(relative_path);
473
474 if let Some(parent) = profile_path.parent() {
476 fs::create_dir_all(parent)?;
477 }
478
479 if profile_path.exists() {
481 let backup_path = profile_path.with_extension("bak");
482 if !backup_path.exists() {
483 fs::copy(&profile_path, &backup_path)?;
484 }
485 }
486
487 if !profile_path.exists() {
488 fs::write(&profile_path, format!("{}\n", line))?;
489 return Ok(());
490 }
491
492 let content = fs::read_to_string(&profile_path)?;
493 if !content.contains(line) {
494 let mut file = fs::OpenOptions::new().append(true).open(&profile_path)?;
495 writeln!(file, "\n{}", line)?;
496 }
497
498 Ok(())
499}
500
501#[cfg(unix)]
502pub fn cleanup(paths: &UpstreamPaths) -> Result<()> {
503 for shell in installed_shell_commands() {
504 let profile = match shell.as_str() {
505 "bash" | "sh" => Some(".bashrc"),
506 "zsh" => Some(".zshrc"),
507 "fish" => Some(".config/fish/config.fish"),
508 "nu" => Some(".config/nushell/config.nu"),
509 _ => None,
510 };
511 if let Some(profile_rel) = profile {
512 let profile_path = paths.dirs.user_dir.join(profile_rel);
513 if !profile_path.exists() {
514 continue;
515 }
516 let mut content = fs::read_to_string(&profile_path)?;
517 content = content
518 .replace(&format!("{}\n", SOURCE_LINE_BASH), "")
519 .replace(SOURCE_LINE_BASH, "")
520 .replace(&format!("{}\n", SOURCE_LINE_FISH), "")
521 .replace(SOURCE_LINE_FISH, "")
522 .replace(&format!("{}\n", SOURCE_LINE_NUSHELL), "")
523 .replace(SOURCE_LINE_NUSHELL, "");
524 fs::write(&profile_path, content)?;
525 }
526 }
527 Ok(())
528}
529
530#[cfg(windows)]
531pub fn cleanup(paths: &UpstreamPaths) -> Result<()> {
532 remove_from_windows_path(paths)
533}
534
535#[cfg(windows)]
536fn remove_from_windows_path(paths: &UpstreamPaths) -> Result<()> {
537 use winreg::RegKey;
538 use winreg::enums::*;
539
540 let hkcu = RegKey::predef(HKEY_CURRENT_USER);
541 let env_key = hkcu
542 .open_subkey_with_flags("Environment", KEY_READ | KEY_WRITE)
543 .context("Failed to open registry key")?;
544
545 let symlinks_path = paths.integration.symlinks_dir.display().to_string();
546 let symlinks_norm = normalize_windows_path(&symlinks_path);
547
548 let current_path: String = env_key.get_value("Path").unwrap_or_else(|_| String::new());
550
551 let path_entries: Vec<&str> = current_path
553 .split(';')
554 .filter(|&p| normalize_windows_path(p) != symlinks_norm)
555 .collect();
556
557 let new_path = path_entries.join(";");
558
559 env_key
560 .set_value("Path", &new_path)
561 .context("Failed to set PATH")?;
562
563 broadcast_environment_change();
565
566 Ok(())
567}
568
569#[cfg(windows)]
570fn check_windows_integration(paths: &UpstreamPaths, report: &mut InitCheckReport) -> Result<()> {
571 use winreg::RegKey;
572 use winreg::enums::*;
573
574 let hkcu = RegKey::predef(HKEY_CURRENT_USER);
575 let env_key = hkcu
576 .open_subkey_with_flags("Environment", KEY_READ)
577 .context("Failed to open PATH")?;
578
579 let symlinks_path = paths.integration.symlinks_dir.display().to_string();
580 let symlinks_norm = normalize_windows_path(&symlinks_path);
581 let current_path: String = env_key.get_value("Path").unwrap_or_else(|_| String::new());
582
583 let in_path = current_path
584 .split(';')
585 .any(|p| normalize_windows_path(p) == symlinks_norm);
586
587 if in_path {
588 check_ok(report, "Windows PATH contains upstream symlinks directory");
589 } else {
590 check_fail(report, "Windows PATH missing upstream symlinks directory");
591 }
592
593 Ok(())
594}
595
596#[cfg(test)]
597mod tests {
598 use super::purge_data;
599 use crate::services::storage::manifest_storage::{CURRENT_LAYOUT_VERSION, MANIFEST_FILE_NAME};
600 use crate::utils::static_paths::{
601 AppDirs, ConfigPaths, InstallPaths, IntegrationPaths, UpstreamPaths,
602 };
603 use std::path::{Path, PathBuf};
604 use std::time::{SystemTime, UNIX_EPOCH};
605 use std::{fs, io};
606
607 fn temp_root(name: &str) -> PathBuf {
608 let nanos = SystemTime::now()
609 .duration_since(UNIX_EPOCH)
610 .map(|d| d.as_nanos())
611 .unwrap_or(0);
612 std::env::temp_dir().join(format!("upstream-init-test-{name}-{nanos}"))
613 }
614
615 fn test_paths(root: &Path) -> UpstreamPaths {
616 let dirs = AppDirs {
617 user_dir: root.to_path_buf(),
618 config_dir: root.join("config"),
619 data_dir: root.join(".upstream"),
620 packages_dir: root.join(".upstream/packages"),
621 cache_dir: root.join(".upstream/cache"),
622 metadata_dir: root.join(".upstream/metadata"),
623 };
624
625 UpstreamPaths {
626 config: ConfigPaths {
627 config_file: dirs.config_dir.join("config.toml"),
628 packages_file: dirs.metadata_dir.join("packages.json"),
629 metadata_file: dirs.metadata_dir.join("metadata.json"),
630 trust_file: dirs.metadata_dir.join("trust.json"),
631 paths_file: dirs.metadata_dir.join("paths.sh"),
632 paths_nu_file: dirs.metadata_dir.join("paths.nu"),
633 },
634 install: InstallPaths {
635 appimages_dir: dirs.packages_dir.join("appimages"),
636 binaries_dir: dirs.packages_dir.join("binaries"),
637 archives_dir: dirs.packages_dir.join("archives"),
638 rollback_dir: dirs.data_dir.join("rollback"),
639 tmp_dir: dirs.data_dir.join("tmp"),
640 },
641 integration: IntegrationPaths {
642 symlinks_dir: dirs.data_dir.join("symlinks"),
643 xdg_applications_dir: dirs.user_dir.join(".local/share/applications"),
644 icons_dir: dirs.data_dir.join("icons"),
645 bash_completions_dir: dirs
646 .user_dir
647 .join(".local/share/bash-completion/completions"),
648 fish_completions_dir: dirs.user_dir.join(".config/fish/completions"),
649 zsh_completions_dir: dirs.user_dir.join(".local/share/zsh/site-functions"),
650 },
651 dirs,
652 }
653 }
654
655 fn cleanup(path: &Path) -> io::Result<()> {
656 if path.exists() {
657 fs::remove_dir_all(path)?;
658 }
659 Ok(())
660 }
661
662 #[test]
663 fn create_manifest_file_writes_current_layout_manifest() {
664 let root = temp_root("manifest-file");
665 let paths = test_paths(&root);
666
667 super::create_manifest_file(&paths).expect("create manifest file");
668
669 let manifest_path = paths.dirs.data_dir.join(MANIFEST_FILE_NAME);
670 let manifest: serde_json::Value =
671 serde_json::from_slice(&fs::read(&manifest_path).expect("read manifest"))
672 .expect("parse manifest");
673 assert_eq!(
674 manifest["layout_version"].as_u64(),
675 Some(CURRENT_LAYOUT_VERSION as u64)
676 );
677 assert_eq!(
678 manifest["platform"]["os"].as_str(),
679 Some(std::env::consts::OS)
680 );
681
682 cleanup(&root).expect("cleanup");
683 }
684
685 #[test]
686 fn create_trust_file_writes_empty_trust_storage() {
687 let root = temp_root("trust-file");
688 let paths = test_paths(&root);
689
690 super::create_trust_file(&paths).expect("create trust file");
691
692 let trust: serde_json::Value =
693 serde_json::from_slice(&fs::read(&paths.config.trust_file).expect("read trust file"))
694 .expect("parse trust file");
695 assert_eq!(trust["version"].as_u64(), Some(1));
696 assert_eq!(
697 trust["minisign_public_keys"].as_array().map(Vec::len),
698 Some(0)
699 );
700 assert_eq!(
701 trust["cosign_public_keys"].as_array().map(Vec::len),
702 Some(0)
703 );
704
705 cleanup(&root).expect("cleanup");
706 }
707
708 #[test]
709 fn check_reports_manifest_file_status() {
710 let root = temp_root("manifest-check");
711 let paths = test_paths(&root);
712
713 let missing_report = super::check(&paths).expect("check missing manifest");
714 assert!(
715 missing_report
716 .messages
717 .iter()
718 .map(|message| console::strip_ansi_codes(message).to_string())
719 .any(|message| message.contains("[fail]")
720 && message.contains("manifest file missing"))
721 );
722
723 fs::create_dir_all(&paths.dirs.data_dir).expect("create data dir");
724 super::create_manifest_file(&paths).expect("create manifest");
725 let present_report = super::check(&paths).expect("check present manifest");
726 assert!(
727 present_report
728 .messages
729 .iter()
730 .map(|message| console::strip_ansi_codes(message).to_string())
731 .any(|message| message.contains("[ok]") && message.contains("manifest file exists"))
732 );
733
734 cleanup(&root).expect("cleanup");
735 }
736
737 #[test]
738 fn check_reports_trust_file_status() {
739 let root = temp_root("trust-check");
740 let paths = test_paths(&root);
741
742 let missing_report = super::check(&paths).expect("check missing trust");
743 assert!(
744 missing_report
745 .messages
746 .iter()
747 .map(|message| console::strip_ansi_codes(message).to_string())
748 .any(|message| message.contains("[fail]")
749 && message.contains("trust metadata file missing"))
750 );
751
752 fs::create_dir_all(&paths.dirs.metadata_dir).expect("create metadata dir");
753 super::create_trust_file(&paths).expect("create trust");
754 let present_report = super::check(&paths).expect("check present trust");
755 assert!(
756 present_report
757 .messages
758 .iter()
759 .map(|message| console::strip_ansi_codes(message).to_string())
760 .any(|message| message.contains("[ok]")
761 && message.contains("trust metadata file exists"))
762 );
763
764 cleanup(&root).expect("cleanup");
765 }
766
767 #[cfg(unix)]
768 #[test]
769 fn create_metadata_files_creates_posix_and_nushell_path_files() {
770 let root = temp_root("metadata-files");
771 let paths = test_paths(&root);
772 fs::create_dir_all(&paths.dirs.metadata_dir).expect("create metadata dir");
773
774 super::create_metadata_files(&paths).expect("create metadata files");
775
776 let posix_content = fs::read_to_string(&paths.config.paths_file).expect("read paths.sh");
777 assert!(posix_content.contains("export PATH="));
778 assert!(posix_content.contains(&paths.integration.symlinks_dir.display().to_string()));
779
780 let nushell_content =
781 fs::read_to_string(&paths.config.paths_nu_file).expect("read paths.nu");
782 assert!(nushell_content.contains("let upstream_paths = ["));
783 assert!(nushell_content.contains("$env.PATH = ($upstream_paths ++ $env.PATH)"));
784 assert!(nushell_content.contains(&paths.integration.symlinks_dir.display().to_string()));
785
786 cleanup(&root).expect("cleanup");
787 }
788
789 #[test]
790 fn purge_data_removes_data_dir_but_keeps_config_dir() {
791 let root = temp_root("purge");
792 let paths = test_paths(&root);
793 fs::create_dir_all(&paths.dirs.data_dir).expect("create data dir");
794 fs::create_dir_all(&paths.dirs.config_dir).expect("create config dir");
795 fs::write(paths.dirs.data_dir.join("data"), b"data").expect("write data");
796 fs::write(paths.dirs.config_dir.join("config.toml"), b"").expect("write config");
797
798 purge_data(&paths).expect("purge data");
799
800 assert!(!paths.dirs.data_dir.exists());
801 assert!(paths.dirs.config_dir.exists());
802
803 cleanup(&root).expect("cleanup");
804 }
805}