1use anyhow::{bail, Context, Result};
9use chrono::Utc;
10use directories::ProjectDirs;
11use serde::{Deserialize, Serialize};
12use std::path::PathBuf;
13
14use crate::errors::ErroContext7;
15use crate::i18n::{t, Mensagem};
16
17#[derive(Debug, Serialize, Deserialize, Clone)]
23pub struct ChaveArmazenada {
24 pub value: String,
26 pub added_at: String,
28}
29
30#[derive(Debug, Serialize, Deserialize, Default)]
34pub struct ConfigArquivo {
35 pub schema_version: u32,
37 #[serde(default)]
39 pub keys: Vec<ChaveArmazenada>,
40}
41
42pub fn aplicar_permissoes_600(caminho: &std::path::Path) -> Result<()> {
49 #[cfg(unix)]
50 {
51 use std::os::unix::fs::PermissionsExt;
52 let mut perms = std::fs::metadata(caminho)
53 .with_context(|| format!("Falha ao ler metadados de: {}", caminho.display()))?
54 .permissions();
55 perms.set_mode(0o600);
56 std::fs::set_permissions(caminho, perms)
57 .with_context(|| format!("Falha ao definir permissões em: {}", caminho.display()))?;
58 }
59 #[cfg(not(unix))]
60 let _ = caminho;
61 Ok(())
62}
63
64fn resolver_home_override() -> Option<PathBuf> {
72 let home = std::env::var("CONTEXT7_HOME").ok()?;
73 if home.is_empty() {
74 return None;
75 }
76 let base = PathBuf::from(&home);
77 if base
79 .components()
80 .any(|c| c == std::path::Component::ParentDir)
81 {
82 tracing::warn!(
83 "CONTEXT7_HOME='{}' rejeitado (path traversal) — usando padrão XDG",
84 home
85 );
86 return None;
87 }
88 Some(base)
89}
90
91pub fn descobrir_caminho_config() -> Option<PathBuf> {
98 if let Some(base) = resolver_home_override() {
99 return Some(base.join("context7").join("config.toml"));
100 }
101 ProjectDirs::from("", "", "context7").map(|dirs| dirs.config_dir().join("config.toml"))
102}
103
104pub fn descobrir_caminho_logs_xdg() -> Option<PathBuf> {
111 if let Some(base) = resolver_home_override() {
112 return Some(base.join("context7").join("logs"));
113 }
114 ProjectDirs::from("", "", "context7").map(|dirs| {
115 #[cfg(target_os = "linux")]
117 {
118 dirs.state_dir()
119 .unwrap_or_else(|| dirs.data_local_dir())
120 .to_path_buf()
121 }
122 #[cfg(not(target_os = "linux"))]
123 {
124 dirs.data_local_dir().to_path_buf()
125 }
126 })
127}
128
129pub fn ler_env_var_chave() -> Option<Vec<String>> {
138 std::env::var("CONTEXT7_API_KEYS")
139 .ok()
140 .map(|valor| {
141 valor
142 .split(',')
143 .map(|s| s.trim().to_string())
144 .filter(|s| !s.is_empty())
145 .collect::<Vec<_>>()
146 })
147 .filter(|v| !v.is_empty())
148}
149
150pub fn ler_config_xdg() -> Result<Option<Vec<String>>> {
155 let caminho = match descobrir_caminho_config() {
156 Some(p) => p,
157 None => return Ok(None),
158 };
159
160 if !caminho.exists() {
161 return Ok(None);
162 }
163
164 let conteudo = std::fs::read_to_string(&caminho)
165 .with_context(|| format!("Falha ao ler configuração XDG em: {}", caminho.display()))?;
166
167 let config: ConfigArquivo = toml::from_str(&conteudo)
168 .with_context(|| format!("TOML inválido em: {}", caminho.display()))?;
169
170 let chaves: Vec<String> = config
171 .keys
172 .into_iter()
173 .map(|c| c.value)
174 .filter(|v| !v.is_empty())
175 .collect();
176
177 if chaves.is_empty() {
178 Ok(None)
179 } else {
180 Ok(Some(chaves))
181 }
182}
183
184pub fn ler_env_cwd() -> Option<Vec<String>> {
189 let caminho = std::env::current_dir().ok().map(|d| d.join(".env"))?;
190
191 if !caminho.exists() {
192 return None;
193 }
194
195 std::fs::read_to_string(&caminho)
196 .ok()
197 .and_then(|conteudo| extrair_chaves_env(&conteudo).ok())
198}
199
200pub fn ler_env_compile_time() -> Option<Vec<String>> {
211 option_env!("CONTEXT7_API_KEYS").map(|valor| {
212 valor
213 .split(',')
214 .map(|s| s.trim().to_string())
215 .filter(|s| !s.is_empty())
216 .collect()
217 })
218}
219
220pub fn carregar_chaves_api() -> Result<Vec<String>> {
229 use tracing::{info, warn};
230
231 if let Some(chaves) = ler_env_var_chave() {
233 info!("Chaves carregadas via variável de ambiente CONTEXT7_API_KEYS");
234 return Ok(chaves);
235 }
236
237 match ler_config_xdg() {
239 Ok(Some(chaves)) => {
240 info!("Chaves carregadas via configuração XDG");
241 return Ok(chaves);
242 }
243 Ok(None) => {}
244 Err(e) => {
245 warn!("Falha ao ler configuração XDG (continuando): {}", e);
246 }
247 }
248
249 if let Some(chaves) = ler_env_cwd() {
251 info!(
252 "Iniciando context7 com {} chaves de API disponíveis",
253 chaves.len()
254 );
255 return Ok(chaves);
256 }
257
258 if let Some(chaves) = ler_env_compile_time() {
260 info!("Chaves carregadas via compile-time CONTEXT7_API_KEYS");
261 return Ok(chaves);
262 }
263
264 bail!(t(Mensagem::NenhumaChaveConfigurada))
265}
266
267pub fn escrever_config_xdg(nova_chave: &str) -> Result<PathBuf> {
274 let caminho = descobrir_caminho_config()
275 .context("Sistema não suporta diretórios XDG — impossível salvar configuração")?;
276
277 if let Some(pai) = caminho.parent() {
279 std::fs::create_dir_all(pai)
280 .with_context(|| format!("Falha ao criar diretório: {}", pai.display()))?;
281 }
282
283 let mut config = if caminho.exists() {
285 let conteudo = std::fs::read_to_string(&caminho)
286 .with_context(|| format!("Falha ao ler config existente: {}", caminho.display()))?;
287 toml::from_str::<ConfigArquivo>(&conteudo)
288 .with_context(|| format!("TOML inválido em: {}", caminho.display()))?
289 } else {
290 ConfigArquivo {
291 schema_version: 1,
292 keys: Vec::new(),
293 }
294 };
295
296 let ja_existe = config.keys.iter().any(|c| c.value == nova_chave);
298 if !ja_existe {
299 config.keys.push(ChaveArmazenada {
300 value: nova_chave.to_string(),
301 added_at: Utc::now().to_rfc3339(),
302 });
303 }
304
305 let toml_str =
307 toml::to_string_pretty(&config).context("Falha ao serializar configuração para TOML")?;
308 std::fs::write(&caminho, &toml_str)
309 .with_context(|| format!("Falha ao escrever config em: {}", caminho.display()))?;
310
311 aplicar_permissoes_600(&caminho)?;
312
313 Ok(caminho)
314}
315
316pub fn ler_config_xdg_raw() -> Result<Option<ConfigArquivo>> {
321 let caminho = match descobrir_caminho_config() {
322 Some(p) => p,
323 None => return Ok(None),
324 };
325
326 if !caminho.exists() {
327 return Ok(None);
328 }
329
330 let conteudo = std::fs::read_to_string(&caminho)
331 .with_context(|| format!("Falha ao ler configuração XDG em: {}", caminho.display()))?;
332
333 let config: ConfigArquivo = toml::from_str(&conteudo)
334 .with_context(|| format!("TOML inválido em: {}", caminho.display()))?;
335
336 Ok(Some(config))
337}
338
339pub fn escrever_config_arquivo(config: &ConfigArquivo) -> Result<PathBuf> {
344 let caminho = descobrir_caminho_config()
345 .context("Sistema não suporta diretórios XDG — impossível salvar configuração")?;
346
347 if let Some(pai) = caminho.parent() {
348 std::fs::create_dir_all(pai)
349 .with_context(|| format!("Falha ao criar diretório: {}", pai.display()))?;
350 }
351
352 let toml_str =
353 toml::to_string_pretty(config).context("Falha ao serializar configuração para TOML")?;
354 std::fs::write(&caminho, &toml_str)
355 .with_context(|| format!("Falha ao escrever config em: {}", caminho.display()))?;
356
357 aplicar_permissoes_600(&caminho)?;
358
359 Ok(caminho)
360}
361
362pub fn mascarar_chave(chave: &str) -> String {
371 let n_chars = chave.chars().count();
372 let inicio = 12;
373 let fim = 4;
374 if n_chars <= inicio + fim {
375 return "***".to_string();
376 }
377 let prefixo: String = chave.chars().take(inicio).collect();
378 let sufixo: String = chave
379 .chars()
380 .rev()
381 .take(fim)
382 .collect::<String>()
383 .chars()
384 .rev()
385 .collect();
386 format!("{}...{}", prefixo, sufixo)
387}
388
389pub fn extrair_chaves_env(conteudo: &str) -> Result<Vec<String>> {
395 let chaves: Vec<String> = conteudo
396 .lines()
397 .filter_map(|linha| {
398 let linha_sem_comentario = linha.split('#').next().unwrap_or("").trim();
400 linha_sem_comentario
401 .strip_prefix("CONTEXT7_API=")
402 .map(|valor| {
403 valor
405 .trim()
406 .trim_matches('"')
407 .trim_matches('\'')
408 .to_string()
409 })
410 .filter(|v| !v.is_empty())
411 })
412 .collect();
413
414 if chaves.is_empty() {
415 bail!(t(Mensagem::NenhumaChaveContext7NoArquivo));
416 }
417
418 Ok(chaves)
419}
420
421pub fn cmd_keys_add(chave: &str) -> Result<()> {
428 let chave_trimmed = chave.trim();
429 if chave_trimmed.is_empty() {
430 crate::output::exibir_chave_invalida_vazia();
431 bail!(ErroContext7::OperacaoKeysFalhou);
432 }
433 if !chave_trimmed.starts_with("ctx7sk-") || chave_trimmed.len() < 16 {
434 crate::output::exibir_aviso_formato_chave();
435 }
436 if let Some(config) = ler_config_xdg_raw()? {
438 if config.keys.iter().any(|c| c.value == chave_trimmed) {
439 crate::output::exibir_chave_ja_existia();
440 return Ok(());
441 }
442 }
443 let caminho = escrever_config_xdg(chave_trimmed)?;
444 crate::output::exibir_chave_adicionada(&caminho);
445 Ok(())
446}
447
448pub fn cmd_keys_list(json: bool) -> Result<()> {
452 match ler_config_xdg_raw()? {
453 None => {
454 if json {
455 crate::output::exibir_json_array_vazio();
456 } else {
457 crate::output::exibir_nenhuma_chave();
458 }
459 }
460 Some(config) if config.keys.is_empty() => {
461 if json {
462 crate::output::exibir_json_array_vazio();
463 } else {
464 crate::output::exibir_nenhuma_chave();
465 }
466 }
467 Some(config) => {
468 if json {
469 let mascaradas: Vec<serde_json::Value> = config
470 .keys
471 .iter()
472 .enumerate()
473 .map(|(i, k)| {
474 serde_json::json!({
475 "index": i + 1,
476 "masked_key": mascarar_chave(&k.value),
477 "added_at": crate::output::formatar_added_at_display(&k.added_at)
478 })
479 })
480 .collect();
481 crate::output::exibir_json_bruto(
482 &serde_json::to_string_pretty(&mascaradas).with_context(|| {
483 crate::i18n::t(crate::i18n::Mensagem::FalhaSerializarJson)
484 })?,
485 );
486 } else {
487 crate::output::exibir_chaves_mascaradas(&config.keys, mascarar_chave);
488 }
489 }
490 }
491 Ok(())
492}
493
494pub fn cmd_keys_remove(indice: usize) -> Result<()> {
496 let mut config = match ler_config_xdg_raw()? {
497 None => {
498 crate::output::exibir_nenhuma_chave_para_remover();
499 bail!(ErroContext7::OperacaoKeysFalhou);
500 }
501 Some(c) if c.keys.is_empty() => {
502 crate::output::exibir_nenhuma_chave_para_remover();
503 bail!(ErroContext7::OperacaoKeysFalhou);
504 }
505 Some(c) => c,
506 };
507
508 if indice == 0 || indice > config.keys.len() {
509 crate::output::exibir_indice_invalido(indice, config.keys.len());
510 bail!(ErroContext7::OperacaoKeysFalhou);
511 }
512
513 let removida = config.keys.remove(indice - 1);
514 escrever_config_arquivo(&config)?;
515 crate::output::exibir_chave_removida(&mascarar_chave(&removida.value));
516 Ok(())
517}
518
519pub fn cmd_keys_clear(sim: bool) -> Result<()> {
521 if !sim && !crate::output::confirmar_clear()? {
522 crate::output::exibir_operacao_cancelada();
523 return Ok(());
524 }
525
526 let config = ConfigArquivo {
527 schema_version: 1,
528 keys: Vec::new(),
529 };
530 escrever_config_arquivo(&config)?;
531 crate::output::exibir_chaves_removidas();
532 Ok(())
533}
534
535pub fn cmd_keys_path() -> Result<()> {
537 match descobrir_caminho_config() {
538 Some(caminho) => crate::output::exibir_caminho_config(&caminho),
539 None => crate::output::exibir_xdg_nao_suportado(),
540 }
541 Ok(())
542}
543
544pub fn cmd_keys_import(arquivo: &std::path::Path) -> Result<()> {
548 let conteudo = std::fs::read_to_string(arquivo)
549 .with_context(|| format!("Falha ao ler arquivo: {}", arquivo.display()))?;
550
551 let chaves =
552 extrair_chaves_env(&conteudo).with_context(|| format!("Arquivo: {}", arquivo.display()))?;
553
554 let total = chaves.len();
555 let mut importadas = 0usize;
556
557 for chave in &chaves {
558 escrever_config_xdg(chave)?;
559 importadas += 1;
560 }
561
562 crate::output::exibir_importacao_concluida(importadas, total);
563 Ok(())
564}
565
566pub fn cmd_keys_export() -> Result<()> {
570 match ler_config_xdg_raw()? {
571 None => {}
572 Some(config) if config.keys.is_empty() => {}
573 Some(config) => {
574 for chave in &config.keys {
575 crate::output::exibir_chave_exportada(&chave.value);
576 }
577 }
578 }
579 Ok(())
580}
581
582#[cfg(test)]
585mod testes {
586 use super::*;
587
588 fn ler_config_toml_do_caminho(caminho: &std::path::Path) -> Result<ConfigArquivo> {
592 let conteudo = std::fs::read_to_string(caminho)
593 .with_context(|| format!("Falha ao ler: {}", caminho.display()))?;
594 toml::from_str(&conteudo)
595 .with_context(|| format!("TOML inválido em: {}", caminho.display()))
596 }
597
598 #[test]
601 fn testa_parsing_env_com_multiplas_chaves_iguais() {
602 let mut conteudo = String::new();
603 for i in 0..17 {
604 conteudo.push_str(&format!("CONTEXT7_API=ctx7sk-chave-{:02}\n", i));
605 }
606 let chaves = extrair_chaves_env(&conteudo).expect("Deve extrair 17 chaves sem erro");
607 assert_eq!(chaves.len(), 17, "Deve retornar exatamente 17 chaves");
608 for (i, chave) in chaves.iter().enumerate() {
609 assert_eq!(
610 chave,
611 &format!("ctx7sk-chave-{:02}", i),
612 "Chave {} deve ter o valor correto",
613 i
614 );
615 }
616 }
617
618 #[test]
619 fn testa_parsing_env_ignora_comentarios_e_linhas_vazias() {
620 let conteudo = "# Este é um comentário\n\
621 CONTEXT7_API=ctx7sk-chave-valida-01\n\
622 \n\
623 # Outro comentário\n\
624 CONTEXT7_API=ctx7sk-chave-valida-02\n\
625 \n";
626 let chaves = extrair_chaves_env(conteudo).expect("Deve extrair chaves sem erro");
627 assert_eq!(chaves.len(), 2, "Deve ignorar comentários e linhas vazias");
628 assert_eq!(chaves[0], "ctx7sk-chave-valida-01");
629 assert_eq!(chaves[1], "ctx7sk-chave-valida-02");
630 }
631
632 #[test]
633 fn testa_parsing_env_remove_aspas_duplas() {
634 let conteudo = "CONTEXT7_API=\"ctx7sk-abc-com-aspas\"\n";
635 let chaves = extrair_chaves_env(conteudo).expect("Deve extrair chave sem erro");
636 assert_eq!(chaves.len(), 1);
637 assert_eq!(
638 chaves[0], "ctx7sk-abc-com-aspas",
639 "Deve remover aspas duplas"
640 );
641 }
642
643 #[test]
644 fn testa_parsing_env_remove_aspas_simples() {
645 let conteudo = "CONTEXT7_API='ctx7sk-abc-aspas-simples'\n";
646 let chaves = extrair_chaves_env(conteudo).expect("Deve extrair chave sem erro");
647 assert_eq!(chaves.len(), 1);
648 assert_eq!(
649 chaves[0], "ctx7sk-abc-aspas-simples",
650 "Deve remover aspas simples"
651 );
652 }
653
654 #[test]
655 fn testa_parsing_env_erro_quando_nenhuma_chave() {
656 let conteudo = "# Apenas comentários\n\
657 OUTRA_VAR=valor\n\
658 \n";
659 let resultado = extrair_chaves_env(conteudo);
660 assert!(
661 resultado.is_err(),
662 "Deve retornar Err quando não há chaves CONTEXT7_API"
663 );
664 let mensagem_erro = resultado.unwrap_err().to_string();
665 assert!(
666 mensagem_erro.contains("chave")
667 || mensagem_erro.contains("CONTEXT7_API")
668 || mensagem_erro.contains("key")
669 || mensagem_erro.contains("API"),
670 "Mensagem de erro deve mencionar CONTEXT7_API, chave, key ou API, obteve: {}",
671 mensagem_erro
672 );
673 }
674
675 #[test]
676 fn testa_parsing_env_ignora_chaves_vazias() {
677 let conteudo = "CONTEXT7_API=\n\
678 CONTEXT7_API=ctx7sk-valida\n";
679 let chaves = extrair_chaves_env(conteudo).expect("Deve extrair chave sem erro");
680 assert_eq!(
681 chaves.len(),
682 1,
683 "Deve ignorar entradas CONTEXT7_API sem valor"
684 );
685 assert_eq!(chaves[0], "ctx7sk-valida");
686 }
687
688 #[test]
689 fn testa_parsing_env_ignora_comentario_inline() {
690 let conteudo = "CONTEXT7_API=ctx7sk-valida # comentário aqui\n";
691 let chaves = extrair_chaves_env(conteudo).expect("Deve extrair chave sem erro");
692 assert_eq!(chaves.len(), 1);
693 assert_eq!(chaves[0], "ctx7sk-valida");
694 }
695
696 #[test]
699 fn testa_parsing_env_com_line_endings_crlf() {
700 let conteudo = "CONTEXT7_API=ctx7sk-crlf-chave-a\r\nCONTEXT7_API=ctx7sk-crlf-chave-b\r\n";
702 let chaves =
703 extrair_chaves_env(conteudo).expect("Deve extrair 2 chaves de conteúdo CRLF sem erro");
704 assert_eq!(
705 chaves.len(),
706 2,
707 "Deve retornar exatamente 2 chaves com CRLF"
708 );
709 assert_eq!(
710 chaves[0], "ctx7sk-crlf-chave-a",
711 "Primeira chave não deve conter \\r residual"
712 );
713 assert_eq!(
714 chaves[1], "ctx7sk-crlf-chave-b",
715 "Segunda chave não deve conter \\r residual"
716 );
717 }
718
719 #[test]
720 fn testa_parsing_env_com_line_endings_mixed() {
721 let conteudo = "CONTEXT7_API=ctx7sk-mixed-chave-a\nCONTEXT7_API=ctx7sk-mixed-chave-b\r\n";
723 let chaves = extrair_chaves_env(conteudo)
724 .expect("Deve extrair 2 chaves de conteúdo misto LF/CRLF sem erro");
725 assert_eq!(
726 chaves.len(),
727 2,
728 "Deve retornar exatamente 2 chaves com line endings mistos"
729 );
730 assert_eq!(
731 chaves[0], "ctx7sk-mixed-chave-a",
732 "Chave com LF não deve ter \\r residual"
733 );
734 assert_eq!(
735 chaves[1], "ctx7sk-mixed-chave-b",
736 "Chave com CRLF não deve ter \\r residual"
737 );
738 }
739
740 #[test]
743 fn testa_mascarar_chave_com_valor_longo_exibe_prefixo_e_sufixo() {
744 let chave = "ctx7sk-abc123-def456-ghi789";
745 assert_eq!(chave.len(), 27, "Pré-condição: chave deve ter 27 chars");
746 let mascarada = mascarar_chave(chave);
747 assert!(
748 mascarada.starts_with("ctx7sk-abc12"),
749 "Deve iniciar com os primeiros 12 chars, obteve: {}",
750 mascarada
751 );
752 assert!(
753 mascarada.ends_with("i789"),
754 "Deve terminar com os últimos 4 chars, obteve: {}",
755 mascarada
756 );
757 assert!(
758 mascarada.contains("..."),
759 "Deve conter '...' entre prefixo e sufixo, obteve: {}",
760 mascarada
761 );
762 }
763
764 #[test]
765 fn testa_mascarar_chave_curta_retorna_asteriscos() {
766 let chave_exatamente_16 = "ctx7sk-abcdef012";
767 assert_eq!(
768 chave_exatamente_16.len(),
769 16,
770 "Pré-condição: chave deve ter 16 chars"
771 );
772 let mascarada = mascarar_chave(chave_exatamente_16);
773 assert_eq!(
774 mascarada, "***",
775 "Chave de 16 chars deve retornar '***', obteve: {}",
776 mascarada
777 );
778 }
779
780 #[test]
781 fn testa_mascarar_chave_vazia_retorna_asteriscos() {
782 let mascarada = mascarar_chave("");
783 assert_eq!(
784 mascarada, "***",
785 "Chave vazia deve retornar '***', obteve: {}",
786 mascarada
787 );
788 }
789
790 #[test]
791 fn testa_mascarar_chave_de_exatamente_17_chars_mascara_corretamente() {
792 let chave = "ctx7sk-abcdef0123"; assert_eq!(chave.len(), 17, "Pré-condição: chave deve ter 17 chars");
794 let mascarada = mascarar_chave(chave);
795 assert!(
796 mascarada.contains("..."),
797 "Chave de 17 chars deve ser mascarada, obteve: {}",
798 mascarada
799 );
800 assert_eq!(
801 &mascarada[..12],
802 &chave[..12],
803 "Prefixo de 12 chars deve ser preservado"
804 );
805 assert!(
806 mascarada.ends_with(&chave[chave.len() - 4..]),
807 "Sufixo de 4 chars deve ser preservado"
808 );
809 }
810
811 #[test]
814 #[serial_test::serial]
815 fn testa_ler_env_var_chave_retorna_some_quando_setada() {
816 unsafe {
819 std::env::set_var("CONTEXT7_API_KEYS", "ctx7sk-chave-teste-01");
820 }
821 let resultado = ler_env_var_chave();
822 unsafe {
823 std::env::remove_var("CONTEXT7_API_KEYS");
824 }
825
826 let chaves = resultado.expect("Deve retornar Some com chave válida");
827 assert_eq!(chaves.len(), 1, "Deve retornar exatamente 1 chave");
828 assert_eq!(chaves[0], "ctx7sk-chave-teste-01");
829 }
830
831 #[test]
832 #[serial_test::serial]
833 fn testa_ler_env_var_chave_aceita_multiplas_separadas_por_virgula() {
834 unsafe {
836 std::env::set_var(
837 "CONTEXT7_API_KEYS",
838 "ctx7sk-chave-a, ctx7sk-chave-b , ctx7sk-chave-c",
839 );
840 }
841 let resultado = ler_env_var_chave();
842 unsafe {
843 std::env::remove_var("CONTEXT7_API_KEYS");
844 }
845
846 let chaves = resultado.expect("Deve retornar Some com múltiplas chaves");
847 assert_eq!(chaves.len(), 3, "Deve retornar 3 chaves");
848 assert_eq!(chaves[0], "ctx7sk-chave-a");
849 assert_eq!(chaves[1], "ctx7sk-chave-b");
850 assert_eq!(chaves[2], "ctx7sk-chave-c");
851 }
852
853 #[test]
854 #[serial_test::serial]
855 fn testa_ler_env_var_chave_retorna_none_quando_vazia() {
856 unsafe {
858 std::env::set_var("CONTEXT7_API_KEYS", "");
859 }
860 let resultado = ler_env_var_chave();
861 unsafe {
862 std::env::remove_var("CONTEXT7_API_KEYS");
863 }
864
865 assert!(
866 resultado.is_none(),
867 "Deve retornar None quando env var está vazia"
868 );
869 }
870
871 #[test]
872 #[serial_test::serial]
873 fn testa_ler_env_var_chave_retorna_none_quando_apenas_whitespace() {
874 unsafe {
876 std::env::set_var("CONTEXT7_API_KEYS", " , , ");
877 }
878 let resultado = ler_env_var_chave();
879 unsafe {
880 std::env::remove_var("CONTEXT7_API_KEYS");
881 }
882
883 assert!(
884 resultado.is_none(),
885 "Deve retornar None quando env var contém apenas whitespace/vírgulas"
886 );
887 }
888
889 #[test]
890 #[serial_test::serial]
891 fn testa_ler_env_var_chave_retorna_none_quando_ausente() {
892 unsafe {
894 std::env::remove_var("CONTEXT7_API_KEYS");
895 }
896 let resultado = ler_env_var_chave();
897
898 assert!(
899 resultado.is_none(),
900 "Deve retornar None quando env var não existe"
901 );
902 }
903
904 #[test]
907 #[serial_test::serial]
908 fn testa_context7_home_rejeita_path_traversal() {
909 let casos = ["../../../etc", "..", "/tmp/../etc"];
910 for caso in &casos {
911 unsafe {
913 std::env::set_var("CONTEXT7_HOME", caso);
914 }
915 let resultado = descobrir_caminho_config();
916 unsafe {
917 std::env::remove_var("CONTEXT7_HOME");
918 }
919
920 if let Some(caminho) = resultado {
922 let s = caminho.to_string_lossy();
923 assert!(
924 !s.contains(".."),
925 "Path traversal '{caso}' não deve resultar em caminho com '..': {s}"
926 );
927 }
928 }
930 }
931
932 #[test]
935 #[serial_test::serial]
936 fn testa_ler_config_xdg_arquivo_inexistente_retorna_none() {
937 let dir_temp = tempfile::TempDir::new().expect("Deve criar diretório temporário");
938 unsafe {
940 std::env::set_var("CONTEXT7_HOME", dir_temp.path());
941 }
942 let resultado = ler_config_xdg();
943 unsafe {
944 std::env::remove_var("CONTEXT7_HOME");
945 }
946
947 let valor = resultado.expect("Deve retornar Ok quando arquivo não existe");
948 assert!(
949 valor.is_none(),
950 "Deve retornar None quando config.toml não existe"
951 );
952 }
953
954 #[test]
955 #[serial_test::serial]
956 fn testa_ler_config_xdg_le_toml_valido_com_multiplas_chaves() {
957 let dir_temp = tempfile::TempDir::new().expect("Deve criar diretório temporário");
958 let dir_context7 = dir_temp.path().join("context7");
959 std::fs::create_dir_all(&dir_context7).expect("Deve criar diretório context7");
960
961 let toml_conteudo = r#"schema_version = 1
962
963[[keys]]
964value = "ctx7sk-chave-xdg-01"
965added_at = "2026-01-01T00:00:00+00:00"
966
967[[keys]]
968value = "ctx7sk-chave-xdg-02"
969added_at = "2026-01-02T00:00:00+00:00"
970"#;
971 std::fs::write(dir_context7.join("config.toml"), toml_conteudo)
972 .expect("Deve escrever config.toml");
973
974 unsafe {
976 std::env::set_var("CONTEXT7_HOME", dir_temp.path());
977 }
978 let resultado = ler_config_xdg();
979 unsafe {
980 std::env::remove_var("CONTEXT7_HOME");
981 }
982
983 let chaves = resultado
984 .expect("Deve retornar Ok")
985 .expect("Deve retornar Some com chaves");
986 assert_eq!(chaves.len(), 2, "Deve retornar 2 chaves");
987 assert_eq!(chaves[0], "ctx7sk-chave-xdg-01");
988 assert_eq!(chaves[1], "ctx7sk-chave-xdg-02");
989 }
990
991 #[test]
992 #[serial_test::serial]
993 fn testa_ler_config_xdg_retorna_err_quando_toml_invalido() {
994 let dir_temp = tempfile::TempDir::new().expect("Deve criar diretório temporário");
995 let dir_context7 = dir_temp.path().join("context7");
996 std::fs::create_dir_all(&dir_context7).expect("Deve criar diretório context7");
997
998 std::fs::write(
999 dir_context7.join("config.toml"),
1000 "schema_version = INVALIDO\n[[[malformado",
1001 )
1002 .expect("Deve escrever TOML inválido");
1003
1004 unsafe {
1006 std::env::set_var("CONTEXT7_HOME", dir_temp.path());
1007 }
1008 let resultado = ler_config_xdg();
1009 unsafe {
1010 std::env::remove_var("CONTEXT7_HOME");
1011 }
1012
1013 assert!(
1014 resultado.is_err(),
1015 "Deve retornar Err quando TOML está malformado"
1016 );
1017 }
1018
1019 #[test]
1020 #[serial_test::serial]
1021 fn testa_ler_config_xdg_preserva_ordem_das_chaves() {
1022 let dir_temp = tempfile::TempDir::new().expect("Deve criar diretório temporário");
1023 let dir_context7 = dir_temp.path().join("context7");
1024 std::fs::create_dir_all(&dir_context7).expect("Deve criar diretório context7");
1025
1026 let toml_conteudo = r#"schema_version = 1
1027
1028[[keys]]
1029value = "ctx7sk-primeira"
1030added_at = "2026-01-01T00:00:00+00:00"
1031
1032[[keys]]
1033value = "ctx7sk-segunda"
1034added_at = "2026-01-02T00:00:00+00:00"
1035
1036[[keys]]
1037value = "ctx7sk-terceira"
1038added_at = "2026-01-03T00:00:00+00:00"
1039"#;
1040 std::fs::write(dir_context7.join("config.toml"), toml_conteudo)
1041 .expect("Deve escrever config.toml");
1042
1043 unsafe {
1045 std::env::set_var("CONTEXT7_HOME", dir_temp.path());
1046 }
1047 let resultado = ler_config_xdg();
1048 unsafe {
1049 std::env::remove_var("CONTEXT7_HOME");
1050 }
1051
1052 let chaves = resultado
1053 .expect("Deve retornar Ok")
1054 .expect("Deve retornar Some");
1055 assert_eq!(chaves[0], "ctx7sk-primeira");
1056 assert_eq!(chaves[1], "ctx7sk-segunda");
1057 assert_eq!(chaves[2], "ctx7sk-terceira");
1058 }
1059
1060 #[test]
1061 #[serial_test::serial]
1062 fn testa_ler_config_xdg_keys_vazio_retorna_none() {
1063 let dir_temp = tempfile::TempDir::new().expect("Deve criar diretório temporário");
1064 let dir_context7 = dir_temp.path().join("context7");
1065 std::fs::create_dir_all(&dir_context7).expect("Deve criar diretório context7");
1066
1067 let toml_sem_chaves = "schema_version = 1\n";
1068 std::fs::write(dir_context7.join("config.toml"), toml_sem_chaves)
1069 .expect("Deve escrever config.toml sem keys");
1070
1071 unsafe {
1073 std::env::set_var("CONTEXT7_HOME", dir_temp.path());
1074 }
1075 let resultado = ler_config_xdg();
1076 unsafe {
1077 std::env::remove_var("CONTEXT7_HOME");
1078 }
1079
1080 let valor = resultado.expect("Deve retornar Ok");
1081 assert!(
1082 valor.is_none(),
1083 "Deve retornar None quando config.toml existe mas keys está vazio"
1084 );
1085 }
1086
1087 #[test]
1090 #[serial_test::serial]
1091 fn testa_escrever_config_xdg_roundtrip_serializa_e_deserializa() {
1092 let dir_temp = tempfile::TempDir::new().expect("Deve criar diretório temporário");
1093 unsafe {
1095 std::env::set_var("CONTEXT7_HOME", dir_temp.path());
1096 }
1097
1098 let caminho =
1099 escrever_config_xdg("ctx7sk-roundtrip-01").expect("Deve escrever config sem erro");
1100
1101 let config_lido = ler_config_toml_do_caminho(&caminho)
1102 .expect("Deve ler TOML escrito por escrever_config_xdg");
1103
1104 unsafe {
1105 std::env::remove_var("CONTEXT7_HOME");
1106 }
1107
1108 assert_eq!(config_lido.schema_version, 1, "schema_version deve ser 1");
1109 assert_eq!(config_lido.keys.len(), 1, "Deve conter 1 chave");
1110 assert_eq!(
1111 config_lido.keys[0].value, "ctx7sk-roundtrip-01",
1112 "Valor da chave deve ser preservado"
1113 );
1114 assert!(
1115 !config_lido.keys[0].added_at.is_empty(),
1116 "added_at não deve ser vazio"
1117 );
1118 }
1119
1120 #[test]
1121 #[serial_test::serial]
1122 fn testa_escrever_config_xdg_cria_diretorios_pai_se_nao_existirem() {
1123 let dir_temp = tempfile::TempDir::new().expect("Deve criar diretório temporário");
1124 let xdg_novo = dir_temp.path().join("xdg_inexistente");
1125 unsafe {
1127 std::env::set_var("CONTEXT7_HOME", &xdg_novo);
1128 }
1129
1130 let resultado = escrever_config_xdg("ctx7sk-mkdir-teste");
1131 unsafe {
1132 std::env::remove_var("CONTEXT7_HOME");
1133 }
1134
1135 let caminho = resultado.expect("Deve criar diretório pai e escrever config");
1136 assert!(
1137 caminho.exists(),
1138 "Arquivo de config deve existir após escrita"
1139 );
1140 }
1141
1142 #[test]
1143 #[serial_test::serial]
1144 fn testa_escrever_config_xdg_nao_duplica_chave_ja_existente() {
1145 let dir_temp = tempfile::TempDir::new().expect("Deve criar diretório temporário");
1146 unsafe {
1148 std::env::set_var("CONTEXT7_HOME", dir_temp.path());
1149 }
1150
1151 escrever_config_xdg("ctx7sk-unica").expect("Primeira escrita deve funcionar");
1152 escrever_config_xdg("ctx7sk-unica").expect("Segunda escrita não deve falhar");
1153
1154 let caminho = descobrir_caminho_config().expect("Deve ter caminho XDG");
1155 let config = ler_config_toml_do_caminho(&caminho).expect("Deve ler config");
1156
1157 unsafe {
1158 std::env::remove_var("CONTEXT7_HOME");
1159 }
1160
1161 assert_eq!(
1162 config.keys.len(),
1163 1,
1164 "Não deve duplicar chave já existente — deve ter apenas 1"
1165 );
1166 }
1167
1168 #[test]
1169 #[serial_test::serial]
1170 fn testa_escrever_config_xdg_acumula_chaves_distintas() {
1171 let dir_temp = tempfile::TempDir::new().expect("Deve criar diretório temporário");
1172 unsafe {
1174 std::env::set_var("CONTEXT7_HOME", dir_temp.path());
1175 }
1176
1177 escrever_config_xdg("ctx7sk-chave-a").expect("Primeira escrita deve funcionar");
1178 escrever_config_xdg("ctx7sk-chave-b").expect("Segunda escrita deve funcionar");
1179 escrever_config_xdg("ctx7sk-chave-c").expect("Terceira escrita deve funcionar");
1180
1181 let caminho = descobrir_caminho_config().expect("Deve ter caminho XDG");
1182 let config = ler_config_toml_do_caminho(&caminho).expect("Deve ler config");
1183
1184 unsafe {
1185 std::env::remove_var("CONTEXT7_HOME");
1186 }
1187
1188 assert_eq!(config.keys.len(), 3, "Deve acumular 3 chaves distintas");
1189 let valores: Vec<&str> = config.keys.iter().map(|c| c.value.as_str()).collect();
1190 assert!(valores.contains(&"ctx7sk-chave-a"));
1191 assert!(valores.contains(&"ctx7sk-chave-b"));
1192 assert!(valores.contains(&"ctx7sk-chave-c"));
1193 }
1194
1195 #[test]
1196 #[cfg(unix)]
1197 #[serial_test::serial]
1198 fn testa_escrever_config_xdg_aplica_permissoes_600_em_unix() {
1199 use std::os::unix::fs::PermissionsExt;
1200
1201 let dir_temp = tempfile::TempDir::new().expect("Deve criar diretório temporário");
1202 unsafe {
1204 std::env::set_var("CONTEXT7_HOME", dir_temp.path());
1205 }
1206
1207 let caminho =
1208 escrever_config_xdg("ctx7sk-perm-600").expect("Deve escrever config sem erro");
1209 unsafe {
1210 std::env::remove_var("CONTEXT7_HOME");
1211 }
1212
1213 let metadados = std::fs::metadata(&caminho).expect("Deve obter metadados do arquivo");
1214 let modo = metadados.permissions().mode() & 0o777;
1215
1216 assert_eq!(modo, 0o600, "Permissões devem ser 600, obteve: {:o}", modo);
1217 }
1218
1219 #[test]
1222 fn testa_config_arquivo_roundtrip_serde_preserva_todos_campos() {
1223 let config_original = ConfigArquivo {
1224 schema_version: 1,
1225 keys: vec![
1226 ChaveArmazenada {
1227 value: "ctx7sk-serde-01".to_string(),
1228 added_at: "2026-01-01T12:00:00+00:00".to_string(),
1229 },
1230 ChaveArmazenada {
1231 value: "ctx7sk-serde-02".to_string(),
1232 added_at: "2026-01-02T12:00:00+00:00".to_string(),
1233 },
1234 ],
1235 };
1236
1237 let toml_str = toml::to_string_pretty(&config_original)
1238 .expect("Deve serializar ConfigArquivo para TOML");
1239 let config_deserializado: ConfigArquivo =
1240 toml::from_str(&toml_str).expect("Deve deserializar TOML de volta para ConfigArquivo");
1241
1242 assert_eq!(
1243 config_deserializado.schema_version, config_original.schema_version,
1244 "schema_version deve ser preservado no roundtrip"
1245 );
1246 assert_eq!(
1247 config_deserializado.keys.len(),
1248 config_original.keys.len(),
1249 "Número de chaves deve ser preservado"
1250 );
1251 assert_eq!(
1252 config_deserializado.keys[0].value, config_original.keys[0].value,
1253 "Valor da primeira chave deve ser preservado"
1254 );
1255 assert_eq!(
1256 config_deserializado.keys[0].added_at, config_original.keys[0].added_at,
1257 "added_at da primeira chave deve ser preservado"
1258 );
1259 }
1260
1261 #[test]
1262 fn testa_config_arquivo_schema_version_sempre_presente_na_serializacao() {
1263 let config = ConfigArquivo {
1264 schema_version: 1,
1265 keys: Vec::new(),
1266 };
1267
1268 let toml_str = toml::to_string_pretty(&config).expect("Deve serializar para TOML");
1269
1270 assert!(
1271 toml_str.contains("schema_version"),
1272 "schema_version deve estar presente na serialização TOML"
1273 );
1274 assert!(toml_str.contains('1'), "Valor 1 deve estar presente");
1275 }
1276
1277 #[test]
1278 fn testa_config_arquivo_keys_vazio_aceito_na_deserializacao() {
1279 let toml_str = "schema_version = 1\n";
1280 let config: ConfigArquivo =
1281 toml::from_str(toml_str).expect("Deve deserializar com keys ausente (default vazio)");
1282
1283 assert_eq!(config.schema_version, 1);
1284 assert!(
1285 config.keys.is_empty(),
1286 "keys deve ser vazio quando não presente no TOML"
1287 );
1288 }
1289
1290 #[test]
1291 fn testa_chave_armazenada_preserva_added_at_como_string_utc() {
1292 let timestamp = "2026-04-08T20:00:00+00:00";
1293 let chave = ChaveArmazenada {
1294 value: "ctx7sk-timestamp".to_string(),
1295 added_at: timestamp.to_string(),
1296 };
1297
1298 let toml_str = toml::to_string_pretty(&chave).expect("Deve serializar ChaveArmazenada");
1299 let chave_de_volta: ChaveArmazenada =
1300 toml::from_str(&toml_str).expect("Deve deserializar ChaveArmazenada");
1301
1302 assert_eq!(
1303 chave_de_volta.added_at, timestamp,
1304 "Timestamp added_at deve ser preservado exatamente"
1305 );
1306 }
1307
1308 #[test]
1311 #[serial_test::serial]
1312 fn testa_carregar_chaves_api_env_var_tem_prioridade_sobre_xdg() {
1313 let dir_temp = tempfile::TempDir::new().expect("Deve criar diretório temporário");
1314 let dir_context7 = dir_temp.path().join("context7");
1315 std::fs::create_dir_all(&dir_context7).expect("Deve criar diretório context7");
1316
1317 let toml_xdg = r#"schema_version = 1
1318[[keys]]
1319value = "ctx7sk-xdg-deve-ser-ignorada"
1320added_at = "2026-01-01T00:00:00+00:00"
1321"#;
1322 std::fs::write(dir_context7.join("config.toml"), toml_xdg)
1323 .expect("Deve escrever config XDG");
1324
1325 unsafe {
1327 std::env::set_var("CONTEXT7_API_KEYS", "ctx7sk-env-var-prioritaria");
1328 std::env::set_var("CONTEXT7_HOME", dir_temp.path());
1329 }
1330
1331 let resultado = carregar_chaves_api();
1332
1333 unsafe {
1334 std::env::remove_var("CONTEXT7_API_KEYS");
1335 std::env::remove_var("CONTEXT7_HOME");
1336 }
1337
1338 let chaves = resultado.expect("Deve carregar chaves via env var");
1339 assert_eq!(chaves.len(), 1);
1340 assert_eq!(
1341 chaves[0], "ctx7sk-env-var-prioritaria",
1342 "Env var deve ter prioridade sobre XDG"
1343 );
1344 }
1345
1346 #[test]
1347 #[serial_test::serial]
1348 fn testa_carregar_chaves_api_xdg_usado_quando_env_var_ausente() {
1349 let dir_temp = tempfile::TempDir::new().expect("Deve criar diretório temporário");
1350 let dir_context7 = dir_temp.path().join("context7");
1351 std::fs::create_dir_all(&dir_context7).expect("Deve criar diretório context7");
1352
1353 let toml_xdg = r#"schema_version = 1
1354[[keys]]
1355value = "ctx7sk-via-xdg"
1356added_at = "2026-01-01T00:00:00+00:00"
1357"#;
1358 std::fs::write(dir_context7.join("config.toml"), toml_xdg)
1359 .expect("Deve escrever config XDG");
1360
1361 unsafe {
1363 std::env::remove_var("CONTEXT7_API_KEYS");
1364 std::env::set_var("CONTEXT7_HOME", dir_temp.path());
1365 }
1366
1367 let resultado = carregar_chaves_api();
1368
1369 unsafe {
1370 std::env::remove_var("CONTEXT7_HOME");
1371 }
1372
1373 let chaves = resultado.expect("Deve carregar chaves via XDG");
1374 assert_eq!(chaves.len(), 1);
1375 assert_eq!(chaves[0], "ctx7sk-via-xdg");
1376 }
1377
1378 #[test]
1379 #[serial_test::serial]
1380 fn testa_carregar_chaves_api_retorna_err_quando_nada_disponivel() {
1381 let dir_temp = tempfile::TempDir::new().expect("Deve criar diretório temporário");
1382 let dir_xdg_vazio = dir_temp.path().join("xdg_vazio");
1383 std::fs::create_dir_all(&dir_xdg_vazio).expect("Deve criar diretório XDG vazio");
1384
1385 let dir_sem_env = dir_temp.path().join("sem_env");
1386 std::fs::create_dir_all(&dir_sem_env).expect("Deve criar diretório sem .env");
1387
1388 unsafe {
1390 std::env::remove_var("CONTEXT7_API_KEYS");
1391 std::env::set_var("CONTEXT7_HOME", &dir_xdg_vazio);
1392 }
1393 let cwd_original = std::env::current_dir().expect("Deve obter CWD atual");
1394 std::env::set_current_dir(&dir_sem_env).expect("Deve mudar CWD");
1395
1396 let resultado = carregar_chaves_api();
1397
1398 std::env::set_current_dir(&cwd_original).expect("Deve restaurar CWD");
1399 unsafe {
1400 std::env::remove_var("CONTEXT7_HOME");
1401 }
1402
1403 assert!(
1404 resultado.is_err(),
1405 "Deve retornar Err quando nenhuma camada fornecer chaves"
1406 );
1407 }
1408
1409 #[test]
1410 #[serial_test::serial]
1411 fn testa_ler_env_cwd_le_env_com_multiplas_chaves_context7_api() {
1412 let dir_temp = tempfile::TempDir::new().expect("Deve criar diretório temporário");
1413 let conteudo_env = "CONTEXT7_API=ctx7sk-cwd-01\nCONTEXT7_API=ctx7sk-cwd-02\n";
1414 std::fs::write(dir_temp.path().join(".env"), conteudo_env)
1415 .expect("Deve escrever .env temporário");
1416
1417 let cwd_original = std::env::current_dir().expect("Deve obter CWD");
1418 std::env::set_current_dir(dir_temp.path()).expect("Deve mudar CWD para temp");
1419
1420 let resultado = ler_env_cwd();
1421
1422 std::env::set_current_dir(&cwd_original).expect("Deve restaurar CWD");
1423
1424 let chaves = resultado.expect("Deve retornar Some com chaves do .env CWD");
1425 assert_eq!(chaves.len(), 2, "Deve ler 2 chaves do .env");
1426 assert_eq!(chaves[0], "ctx7sk-cwd-01");
1427 assert_eq!(chaves[1], "ctx7sk-cwd-02");
1428 }
1429
1430 #[test]
1431 #[serial_test::serial]
1432 fn testa_ler_env_cwd_retorna_none_quando_env_ausente() {
1433 let dir_temp = tempfile::TempDir::new().expect("Deve criar diretório temporário");
1434
1435 let cwd_original = std::env::current_dir().expect("Deve obter CWD");
1436 std::env::set_current_dir(dir_temp.path()).expect("Deve mudar CWD para temp sem .env");
1437
1438 let resultado = ler_env_cwd();
1439
1440 std::env::set_current_dir(&cwd_original).expect("Deve restaurar CWD");
1441
1442 assert!(
1443 resultado.is_none(),
1444 "Deve retornar None quando não há .env no CWD"
1445 );
1446 }
1447
1448 #[test]
1449 fn testa_descobrir_caminho_logs_xdg_retorna_algum_caminho_valido() {
1450 let resultado = descobrir_caminho_logs_xdg();
1451
1452 if let Some(caminho) = resultado {
1453 let caminho_str = caminho.to_string_lossy();
1454 assert!(
1455 caminho_str.contains("context7"),
1456 "Caminho de logs XDG deve conter 'context7', obteve: {}",
1457 caminho_str
1458 );
1459 }
1460 }
1461
1462 #[test]
1463 #[serial_test::serial]
1464 fn testa_carregar_chaves_api_env_cwd_usado_quando_env_var_e_xdg_ausentes() {
1465 let dir_temp = tempfile::TempDir::new().expect("Deve criar diretório temporário");
1466 let dir_xdg_sem_config = dir_temp.path().join("xdg_sem_config");
1467 std::fs::create_dir_all(&dir_xdg_sem_config).expect("Deve criar diretório XDG vazio");
1468
1469 let dir_cwd = dir_temp.path().join("cwd_com_env");
1470 std::fs::create_dir_all(&dir_cwd).expect("Deve criar CWD temporário");
1471 std::fs::write(dir_cwd.join(".env"), "CONTEXT7_API=ctx7sk-cwd-camada-3\n")
1472 .expect("Deve escrever .env no CWD");
1473
1474 unsafe {
1476 std::env::remove_var("CONTEXT7_API_KEYS");
1477 std::env::set_var("CONTEXT7_HOME", &dir_xdg_sem_config);
1478 }
1479 let cwd_original = std::env::current_dir().expect("Deve obter CWD");
1480 std::env::set_current_dir(&dir_cwd).expect("Deve mudar CWD");
1481
1482 let resultado = carregar_chaves_api();
1483
1484 std::env::set_current_dir(&cwd_original).expect("Deve restaurar CWD");
1485 unsafe {
1486 std::env::remove_var("CONTEXT7_HOME");
1487 }
1488
1489 let chaves = resultado.expect("Deve carregar chaves via .env CWD");
1490 assert_eq!(chaves.len(), 1);
1491 assert_eq!(chaves[0], "ctx7sk-cwd-camada-3");
1492 }
1493
1494 #[test]
1495 #[serial_test::serial]
1496 fn testa_carregar_chaves_api_faz_fallback_quando_xdg_invalido() {
1497 let dir_temp = tempfile::TempDir::new().expect("Deve criar diretório temporário");
1498 let dir_context7 = dir_temp.path().join("context7");
1499 std::fs::create_dir_all(&dir_context7).expect("Deve criar diretório context7");
1500
1501 std::fs::write(dir_context7.join("config.toml"), "[[[invalido")
1502 .expect("Deve escrever TOML inválido");
1503
1504 let dir_cwd = dir_temp.path().join("cwd_fallback");
1505 std::fs::create_dir_all(&dir_cwd).expect("Deve criar CWD com .env");
1506 std::fs::write(dir_cwd.join(".env"), "CONTEXT7_API=ctx7sk-fallback-cwd\n")
1507 .expect("Deve escrever .env no CWD");
1508
1509 unsafe {
1511 std::env::remove_var("CONTEXT7_API_KEYS");
1512 std::env::set_var("CONTEXT7_HOME", dir_temp.path());
1513 }
1514 let cwd_original = std::env::current_dir().expect("Deve obter CWD");
1515 std::env::set_current_dir(&dir_cwd).expect("Deve mudar CWD");
1516
1517 let resultado = carregar_chaves_api();
1518
1519 std::env::set_current_dir(&cwd_original).expect("Deve restaurar CWD");
1520 unsafe {
1521 std::env::remove_var("CONTEXT7_HOME");
1522 }
1523
1524 let chaves = resultado.expect("Deve carregar chaves via fallback .env CWD");
1525 assert_eq!(chaves.len(), 1);
1526 assert_eq!(chaves[0], "ctx7sk-fallback-cwd");
1527 }
1528
1529 #[test]
1532 #[serial_test::serial]
1533 fn testa_cmd_keys_add_cria_config_quando_nao_existe() {
1534 let dir_temp = tempfile::TempDir::new().expect("Deve criar diretório temporário");
1535 unsafe {
1537 std::env::set_var("CONTEXT7_HOME", dir_temp.path());
1538 }
1539
1540 let resultado = cmd_keys_add("ctx7sk-nova-chave-add-test");
1541
1542 unsafe {
1543 std::env::remove_var("CONTEXT7_HOME");
1544 }
1545
1546 resultado.expect("cmd_keys_add deve funcionar em config vazio");
1547
1548 let caminho = dir_temp.path().join("context7").join("config.toml");
1549 assert!(
1550 caminho.exists(),
1551 "config.toml deve existir após cmd_keys_add"
1552 );
1553
1554 let config = ler_config_toml_do_caminho(&caminho).expect("Deve ler config criado");
1555 assert_eq!(config.keys.len(), 1, "Config deve ter 1 chave");
1556 assert_eq!(config.keys[0].value, "ctx7sk-nova-chave-add-test");
1557 }
1558
1559 #[test]
1560 #[serial_test::serial]
1561 fn testa_cmd_keys_add_acumula_em_config_existente() {
1562 let dir_temp = tempfile::TempDir::new().expect("Deve criar diretório temporário");
1563 unsafe {
1565 std::env::set_var("CONTEXT7_HOME", dir_temp.path());
1566 }
1567
1568 cmd_keys_add("ctx7sk-chave-um").expect("Primeira adição deve funcionar");
1569 cmd_keys_add("ctx7sk-chave-dois").expect("Segunda adição deve funcionar");
1570
1571 let caminho = descobrir_caminho_config().expect("Deve ter caminho XDG");
1572 let config = ler_config_toml_do_caminho(&caminho).expect("Deve ler config");
1573
1574 unsafe {
1575 std::env::remove_var("CONTEXT7_HOME");
1576 }
1577
1578 assert_eq!(config.keys.len(), 2, "Deve acumular 2 chaves");
1579 assert_eq!(config.keys[0].value, "ctx7sk-chave-um");
1580 assert_eq!(config.keys[1].value, "ctx7sk-chave-dois");
1581 }
1582
1583 #[test]
1584 #[serial_test::serial]
1585 fn testa_cmd_keys_add_nao_duplica_chave_existente() {
1586 let dir_temp = tempfile::TempDir::new().expect("Deve criar diretório temporário");
1587 unsafe {
1589 std::env::set_var("CONTEXT7_HOME", dir_temp.path());
1590 }
1591
1592 cmd_keys_add("ctx7sk-unica-dedup").expect("Primeira adição deve funcionar");
1593 cmd_keys_add("ctx7sk-unica-dedup").expect("Segunda adição da mesma chave não deve falhar");
1594
1595 let caminho = descobrir_caminho_config().expect("Deve ter caminho XDG");
1596 let config = ler_config_toml_do_caminho(&caminho).expect("Deve ler config");
1597
1598 unsafe {
1599 std::env::remove_var("CONTEXT7_HOME");
1600 }
1601
1602 assert_eq!(config.keys.len(), 1, "Não deve duplicar chave já existente");
1603 }
1604
1605 #[test]
1606 #[cfg(unix)]
1607 #[serial_test::serial]
1608 fn testa_cmd_keys_add_aplica_permissoes_600_em_unix() {
1609 use std::os::unix::fs::PermissionsExt;
1610
1611 let dir_temp = tempfile::TempDir::new().expect("Deve criar diretório temporário");
1612 unsafe {
1614 std::env::set_var("CONTEXT7_HOME", dir_temp.path());
1615 }
1616
1617 cmd_keys_add("ctx7sk-perm-600-keys-add").expect("Deve adicionar chave sem erro");
1618
1619 let caminho = descobrir_caminho_config().expect("Deve ter caminho XDG");
1620 unsafe {
1621 std::env::remove_var("CONTEXT7_HOME");
1622 }
1623
1624 let metadados = std::fs::metadata(&caminho).expect("Deve obter metadados");
1625 let modo = metadados.permissions().mode() & 0o777;
1626 assert_eq!(
1627 modo, 0o600,
1628 "Permissões devem ser 600 após cmd_keys_add, obteve: {:o}",
1629 modo
1630 );
1631 }
1632
1633 #[test]
1636 #[serial_test::serial]
1637 fn testa_cmd_keys_remove_indice_1_de_config_com_3_chaves() {
1638 let dir_temp = tempfile::TempDir::new().expect("Deve criar diretório temporário");
1639 unsafe {
1641 std::env::set_var("CONTEXT7_HOME", dir_temp.path());
1642 }
1643
1644 escrever_config_xdg("ctx7sk-rem-alpha").expect("Deve escrever chave 1");
1645 escrever_config_xdg("ctx7sk-rem-beta").expect("Deve escrever chave 2");
1646 escrever_config_xdg("ctx7sk-rem-gamma").expect("Deve escrever chave 3");
1647
1648 cmd_keys_remove(1).expect("Remove índice 1 deve funcionar");
1649
1650 let caminho = descobrir_caminho_config().expect("Deve ter caminho XDG");
1651 let config = ler_config_toml_do_caminho(&caminho).expect("Deve ler config");
1652
1653 unsafe {
1654 std::env::remove_var("CONTEXT7_HOME");
1655 }
1656
1657 assert_eq!(config.keys.len(), 2, "Devem sobrar 2 chaves após remoção");
1658 assert_eq!(config.keys[0].value, "ctx7sk-rem-beta");
1659 assert_eq!(config.keys[1].value, "ctx7sk-rem-gamma");
1660 }
1661
1662 #[test]
1663 #[serial_test::serial]
1664 fn testa_cmd_keys_remove_indice_2_de_config_com_3_chaves() {
1665 let dir_temp = tempfile::TempDir::new().expect("Deve criar diretório temporário");
1666 unsafe {
1668 std::env::set_var("CONTEXT7_HOME", dir_temp.path());
1669 }
1670
1671 escrever_config_xdg("ctx7sk-mid-alpha").expect("Deve escrever chave 1");
1672 escrever_config_xdg("ctx7sk-mid-beta").expect("Deve escrever chave 2");
1673 escrever_config_xdg("ctx7sk-mid-gamma").expect("Deve escrever chave 3");
1674
1675 cmd_keys_remove(2).expect("Remove índice 2 deve funcionar");
1676
1677 let caminho = descobrir_caminho_config().expect("Deve ter caminho XDG");
1678 let config = ler_config_toml_do_caminho(&caminho).expect("Deve ler config");
1679
1680 unsafe {
1681 std::env::remove_var("CONTEXT7_HOME");
1682 }
1683
1684 assert_eq!(
1685 config.keys.len(),
1686 2,
1687 "Devem sobrar 2 chaves após remoção da do meio"
1688 );
1689 assert_eq!(config.keys[0].value, "ctx7sk-mid-alpha");
1690 assert_eq!(config.keys[1].value, "ctx7sk-mid-gamma");
1691 }
1692
1693 #[test]
1694 #[serial_test::serial]
1695 fn testa_cmd_keys_remove_indice_zero_retorna_err_com_mensagem() {
1696 let dir_temp = tempfile::TempDir::new().expect("Deve criar diretório temporário");
1697 unsafe {
1699 std::env::set_var("CONTEXT7_HOME", dir_temp.path());
1700 }
1701
1702 escrever_config_xdg("ctx7sk-idx-zero-test").expect("Deve escrever chave");
1703
1704 let resultado = cmd_keys_remove(0);
1705
1706 unsafe {
1707 std::env::remove_var("CONTEXT7_HOME");
1708 }
1709
1710 assert!(
1711 resultado.is_err(),
1712 "Índice 0 inválido deve retornar Err (exit code 1), obteve: {:?}",
1713 resultado
1714 );
1715 }
1716
1717 #[test]
1718 #[serial_test::serial]
1719 fn testa_cmd_keys_remove_indice_maior_que_len_retorna_err_com_mensagem() {
1720 let dir_temp = tempfile::TempDir::new().expect("Deve criar diretório temporário");
1721 unsafe {
1723 std::env::set_var("CONTEXT7_HOME", dir_temp.path());
1724 }
1725
1726 escrever_config_xdg("ctx7sk-overflow-test").expect("Deve escrever chave");
1727
1728 let resultado = cmd_keys_remove(99);
1729
1730 unsafe {
1731 std::env::remove_var("CONTEXT7_HOME");
1732 }
1733
1734 assert!(
1735 resultado.is_err(),
1736 "Índice fora do range deve retornar Err (exit code 1), obteve: {:?}",
1737 resultado
1738 );
1739 }
1740
1741 #[test]
1742 #[serial_test::serial]
1743 fn testa_cmd_keys_remove_em_config_vazio_retorna_err() {
1744 let dir_temp = tempfile::TempDir::new().expect("Deve criar diretório temporário");
1745 unsafe {
1747 std::env::set_var("CONTEXT7_HOME", dir_temp.path());
1748 }
1749
1750 let resultado = cmd_keys_remove(1);
1751
1752 unsafe {
1753 std::env::remove_var("CONTEXT7_HOME");
1754 }
1755
1756 assert!(
1757 resultado.is_err(),
1758 "Remover de config vazio deve retornar Err (exit code 1), obteve: {:?}",
1759 resultado
1760 );
1761 }
1762
1763 #[test]
1766 #[serial_test::serial]
1767 fn testa_cmd_keys_clear_com_yes_true_limpa_todas_as_chaves() {
1768 let dir_temp = tempfile::TempDir::new().expect("Deve criar diretório temporário");
1769 unsafe {
1771 std::env::set_var("CONTEXT7_HOME", dir_temp.path());
1772 }
1773
1774 escrever_config_xdg("ctx7sk-clear-alpha").expect("Deve escrever chave 1");
1775 escrever_config_xdg("ctx7sk-clear-beta").expect("Deve escrever chave 2");
1776
1777 let caminho = descobrir_caminho_config().expect("Deve ter caminho XDG");
1778 let antes = ler_config_toml_do_caminho(&caminho).expect("Deve ler config antes");
1779 assert_eq!(antes.keys.len(), 2, "Pré-condição: 2 chaves antes do clear");
1780
1781 cmd_keys_clear(true).expect("clear com yes=true deve funcionar");
1782
1783 let depois = ler_config_toml_do_caminho(&caminho).expect("Deve ler config depois");
1784
1785 unsafe {
1786 std::env::remove_var("CONTEXT7_HOME");
1787 }
1788
1789 assert!(
1790 depois.keys.is_empty(),
1791 "Após clear com yes=true, chaves devem estar vazias"
1792 );
1793 }
1794
1795 #[test]
1796 #[serial_test::serial]
1797 fn testa_cmd_keys_clear_com_yes_true_em_config_inexistente_funciona() {
1798 let dir_temp = tempfile::TempDir::new().expect("Deve criar diretório temporário");
1799 unsafe {
1801 std::env::set_var("CONTEXT7_HOME", dir_temp.path());
1802 }
1803
1804 let resultado = cmd_keys_clear(true);
1805
1806 unsafe {
1807 std::env::remove_var("CONTEXT7_HOME");
1808 }
1809
1810 assert!(
1811 resultado.is_ok(),
1812 "clear em config inexistente deve retornar Ok (idempotente), obteve: {:?}",
1813 resultado
1814 );
1815 }
1816
1817 #[test]
1820 #[serial_test::serial]
1821 fn testa_cmd_keys_import_env_valido_com_multiplas_chaves() {
1822 let dir_temp = tempfile::TempDir::new().expect("Deve criar diretório temporário");
1823 let arquivo_env = dir_temp.path().join("chaves.env");
1824 std::fs::write(
1825 &arquivo_env,
1826 "CONTEXT7_API=ctx7sk-import-alpha\nCONTEXT7_API=ctx7sk-import-beta\n",
1827 )
1828 .expect("Deve escrever .env de teste");
1829
1830 unsafe {
1832 std::env::set_var("CONTEXT7_HOME", dir_temp.path());
1833 }
1834
1835 let resultado = cmd_keys_import(&arquivo_env);
1836
1837 let caminho = descobrir_caminho_config().expect("Deve ter caminho XDG");
1838 let config = ler_config_toml_do_caminho(&caminho).expect("Deve ler config após import");
1839
1840 unsafe {
1841 std::env::remove_var("CONTEXT7_HOME");
1842 }
1843
1844 resultado.expect("import de .env válido deve funcionar");
1845 assert_eq!(config.keys.len(), 2, "Deve ter importado 2 chaves");
1846
1847 let valores: Vec<&str> = config.keys.iter().map(|c| c.value.as_str()).collect();
1848 assert!(valores.contains(&"ctx7sk-import-alpha"));
1849 assert!(valores.contains(&"ctx7sk-import-beta"));
1850 }
1851
1852 #[test]
1853 #[serial_test::serial]
1854 fn testa_cmd_keys_import_env_sem_chaves_retorna_err() {
1855 let dir_temp = tempfile::TempDir::new().expect("Deve criar diretório temporário");
1856 let arquivo_env = dir_temp.path().join("vazio.env");
1857 std::fs::write(&arquivo_env, "# apenas comentario\nOUTRA_VAR=valor\n")
1858 .expect("Deve escrever .env sem chaves");
1859
1860 unsafe {
1862 std::env::set_var("CONTEXT7_HOME", dir_temp.path());
1863 }
1864
1865 let resultado = cmd_keys_import(&arquivo_env);
1866
1867 unsafe {
1868 std::env::remove_var("CONTEXT7_HOME");
1869 }
1870
1871 assert!(
1872 resultado.is_err(),
1873 "Import de .env sem chaves CONTEXT7_API deve retornar Err"
1874 );
1875 }
1876
1877 #[test]
1878 #[serial_test::serial]
1879 fn testa_cmd_keys_import_arquivo_inexistente_retorna_err() {
1880 let dir_temp = tempfile::TempDir::new().expect("Deve criar diretório temporário");
1881 let arquivo_inexistente = dir_temp.path().join("nao_existe.env");
1882
1883 unsafe {
1885 std::env::set_var("CONTEXT7_HOME", dir_temp.path());
1886 }
1887
1888 let resultado = cmd_keys_import(&arquivo_inexistente);
1889
1890 unsafe {
1891 std::env::remove_var("CONTEXT7_HOME");
1892 }
1893
1894 assert!(
1895 resultado.is_err(),
1896 "Import de arquivo inexistente deve retornar Err"
1897 );
1898 }
1899
1900 #[test]
1901 #[serial_test::serial]
1902 fn testa_cmd_keys_import_roundtrip_add_depois_list() {
1903 let dir_temp = tempfile::TempDir::new().expect("Deve criar diretório temporário");
1904 let arquivo_env = dir_temp.path().join("roundtrip.env");
1905 std::fs::write(
1906 &arquivo_env,
1907 "CONTEXT7_API=ctx7sk-rtrip-01\nCONTEXT7_API=ctx7sk-rtrip-02\n",
1908 )
1909 .expect("Deve escrever .env de roundtrip");
1910
1911 unsafe {
1913 std::env::set_var("CONTEXT7_HOME", dir_temp.path());
1914 }
1915
1916 cmd_keys_import(&arquivo_env).expect("Import deve funcionar");
1917
1918 let config = ler_config_xdg_raw()
1919 .expect("Deve retornar Ok")
1920 .expect("Deve retornar Some após import");
1921
1922 unsafe {
1923 std::env::remove_var("CONTEXT7_HOME");
1924 }
1925
1926 assert_eq!(
1927 config.keys.len(),
1928 2,
1929 "Roundtrip: deve ter 2 chaves após import"
1930 );
1931 assert_eq!(config.keys[0].value, "ctx7sk-rtrip-01");
1932 assert_eq!(config.keys[1].value, "ctx7sk-rtrip-02");
1933 }
1934
1935 #[test]
1938 #[serial_test::serial]
1939 fn testa_cmd_keys_export_em_config_vazio_retorna_ok() {
1940 let dir_temp = tempfile::TempDir::new().expect("Deve criar diretório temporário");
1941 unsafe {
1943 std::env::set_var("CONTEXT7_HOME", dir_temp.path());
1944 }
1945
1946 let resultado = cmd_keys_export();
1947
1948 unsafe {
1949 std::env::remove_var("CONTEXT7_HOME");
1950 }
1951
1952 assert!(
1953 resultado.is_ok(),
1954 "Export de config vazio deve retornar Ok, obteve: {:?}",
1955 resultado
1956 );
1957 }
1958
1959 #[test]
1960 #[serial_test::serial]
1961 fn testa_cmd_keys_export_retorna_ok_com_chaves_existentes() {
1962 let dir_temp = tempfile::TempDir::new().expect("Deve criar diretório temporário");
1963 unsafe {
1965 std::env::set_var("CONTEXT7_HOME", dir_temp.path());
1966 }
1967
1968 escrever_config_xdg("ctx7sk-export-um").expect("Deve escrever chave 1");
1969 escrever_config_xdg("ctx7sk-export-dois").expect("Deve escrever chave 2");
1970
1971 let resultado = cmd_keys_export();
1972
1973 unsafe {
1974 std::env::remove_var("CONTEXT7_HOME");
1975 }
1976
1977 assert!(
1978 resultado.is_ok(),
1979 "Export com chaves existentes deve retornar Ok, obteve: {:?}",
1980 resultado
1981 );
1982 }
1983
1984 #[test]
1987 #[serial_test::serial]
1988 fn testa_ler_config_xdg_raw_retorna_none_sem_arquivo() {
1989 let dir_temp = tempfile::TempDir::new().expect("Deve criar diretório temporário");
1990 unsafe {
1992 std::env::set_var("CONTEXT7_HOME", dir_temp.path());
1993 }
1994
1995 let resultado = ler_config_xdg_raw();
1996
1997 unsafe {
1998 std::env::remove_var("CONTEXT7_HOME");
1999 }
2000
2001 let valor = resultado.expect("Deve retornar Ok");
2002 assert!(
2003 valor.is_none(),
2004 "Deve retornar None quando config.toml não existe"
2005 );
2006 }
2007
2008 #[test]
2009 #[serial_test::serial]
2010 fn testa_ler_config_xdg_raw_retorna_config_com_chaves() {
2011 let dir_temp = tempfile::TempDir::new().expect("Deve criar diretório temporário");
2012 let dir_context7 = dir_temp.path().join("context7");
2013 std::fs::create_dir_all(&dir_context7).expect("Deve criar diretório context7");
2014
2015 let toml = r#"schema_version = 1
2016
2017[[keys]]
2018value = "ctx7sk-raw-01"
2019added_at = "2026-04-08T00:00:00+00:00"
2020
2021[[keys]]
2022value = "ctx7sk-raw-02"
2023added_at = "2026-04-08T00:01:00+00:00"
2024"#;
2025 std::fs::write(dir_context7.join("config.toml"), toml).expect("Deve escrever config.toml");
2026
2027 unsafe {
2029 std::env::set_var("CONTEXT7_HOME", dir_temp.path());
2030 }
2031
2032 let resultado = ler_config_xdg_raw();
2033
2034 unsafe {
2035 std::env::remove_var("CONTEXT7_HOME");
2036 }
2037
2038 let config = resultado
2039 .expect("Deve retornar Ok")
2040 .expect("Deve retornar Some com config");
2041 assert_eq!(config.keys.len(), 2);
2042 assert_eq!(config.keys[0].value, "ctx7sk-raw-01");
2043 assert_eq!(config.keys[1].value, "ctx7sk-raw-02");
2044 }
2045
2046 #[test]
2047 #[serial_test::serial]
2048 fn testa_cmd_keys_path_retorna_ok() {
2049 let dir_temp = tempfile::TempDir::new().expect("Deve criar diretório temporário");
2050 unsafe {
2052 std::env::set_var("CONTEXT7_HOME", dir_temp.path());
2053 }
2054
2055 let resultado = cmd_keys_path();
2056
2057 unsafe {
2058 std::env::remove_var("CONTEXT7_HOME");
2059 }
2060
2061 resultado.expect("cmd_keys_path deve retornar Ok");
2062 }
2063
2064 #[test]
2065 #[serial_test::serial]
2066 fn testa_descobrir_caminho_config_termina_com_config_toml() {
2067 let dir_temp = tempfile::TempDir::new().expect("Deve criar diretório temporário");
2068 unsafe {
2070 std::env::set_var("CONTEXT7_HOME", dir_temp.path());
2071 }
2072
2073 let caminho = descobrir_caminho_config();
2074
2075 unsafe {
2076 std::env::remove_var("CONTEXT7_HOME");
2077 }
2078
2079 let caminho = caminho.expect("Deve retornar caminho XDG válido");
2080 assert!(
2081 caminho.to_string_lossy().ends_with("config.toml"),
2082 "Caminho deve terminar com config.toml, obteve: {}",
2083 caminho.display()
2084 );
2085 assert!(
2086 caminho.to_string_lossy().contains("context7"),
2087 "Caminho deve conter 'context7', obteve: {}",
2088 caminho.display()
2089 );
2090 }
2091
2092 #[test]
2095 #[serial_test::serial]
2096 fn testa_fluxo_completo_add_list_remove_clear() {
2097 let dir_temp = tempfile::TempDir::new().expect("Deve criar diretório temporário");
2098 unsafe {
2100 std::env::set_var("CONTEXT7_HOME", dir_temp.path());
2101 }
2102
2103 cmd_keys_add("ctx7sk-fluxo-01").expect("Add 1 deve funcionar");
2104 cmd_keys_add("ctx7sk-fluxo-02").expect("Add 2 deve funcionar");
2105 cmd_keys_add("ctx7sk-fluxo-03").expect("Add 3 deve funcionar");
2106
2107 let config_antes = ler_config_xdg_raw()
2108 .expect("Ok")
2109 .expect("Some com 3 chaves");
2110 assert_eq!(config_antes.keys.len(), 3, "Deve ter 3 chaves após 3 adds");
2111
2112 cmd_keys_remove(2).expect("Remove índice 2 deve funcionar");
2113
2114 let config_pos_remove = ler_config_xdg_raw()
2115 .expect("Ok")
2116 .expect("Some com 2 chaves");
2117 assert_eq!(
2118 config_pos_remove.keys.len(),
2119 2,
2120 "Deve ter 2 chaves após remove"
2121 );
2122 assert_eq!(config_pos_remove.keys[0].value, "ctx7sk-fluxo-01");
2123 assert_eq!(config_pos_remove.keys[1].value, "ctx7sk-fluxo-03");
2124
2125 cmd_keys_clear(true).expect("Clear com yes=true deve funcionar");
2126
2127 let caminho = descobrir_caminho_config().expect("Deve ter caminho");
2128 let config_final = ler_config_toml_do_caminho(&caminho).expect("Deve ler config final");
2129
2130 unsafe {
2131 std::env::remove_var("CONTEXT7_HOME");
2132 }
2133
2134 assert!(
2135 config_final.keys.is_empty(),
2136 "Após clear, chaves devem estar vazias"
2137 );
2138 }
2139
2140 #[test]
2143 #[serial_test::serial]
2144 fn testa_context7_home_override_config_path() {
2145 let tmp = tempfile::TempDir::new().expect("Deve criar tempdir");
2146 unsafe {
2149 std::env::set_var("CONTEXT7_HOME", tmp.path());
2150 }
2151
2152 let caminho = descobrir_caminho_config();
2153
2154 unsafe {
2155 std::env::remove_var("CONTEXT7_HOME");
2156 }
2157
2158 let caminho = caminho.expect("Deve retornar Some quando CONTEXT7_HOME está definido");
2159 let esperado = tmp.path().join("context7").join("config.toml");
2160 assert_eq!(
2161 caminho, esperado,
2162 "CONTEXT7_HOME deve definir caminho como {{CONTEXT7_HOME}}/context7/config.toml"
2163 );
2164 }
2165
2166 #[test]
2167 #[serial_test::serial]
2168 fn testa_context7_home_override_logs_path() {
2169 let tmp = tempfile::TempDir::new().expect("Deve criar tempdir");
2170 unsafe {
2172 std::env::set_var("CONTEXT7_HOME", tmp.path());
2173 }
2174
2175 let caminho = descobrir_caminho_logs_xdg();
2176
2177 unsafe {
2178 std::env::remove_var("CONTEXT7_HOME");
2179 }
2180
2181 let caminho = caminho.expect("Deve retornar Some quando CONTEXT7_HOME está definido");
2182 let esperado = tmp.path().join("context7").join("logs");
2183 assert_eq!(
2184 caminho, esperado,
2185 "CONTEXT7_HOME deve definir logs como {{CONTEXT7_HOME}}/context7/logs"
2186 );
2187 }
2188
2189 #[test]
2190 #[serial_test::serial]
2191 fn testa_context7_home_vazio_cai_em_projectdirs() {
2192 let tmp = tempfile::TempDir::new().expect("Deve criar tempdir");
2193 unsafe {
2195 std::env::set_var("CONTEXT7_HOME", "");
2196 }
2197
2198 let caminho = descobrir_caminho_config();
2199
2200 unsafe {
2201 std::env::remove_var("CONTEXT7_HOME");
2202 }
2203
2204 if let Some(c) = caminho {
2206 let tmp_str = tmp.path().to_string_lossy();
2207 assert!(
2208 !c.to_string_lossy().starts_with(tmp_str.as_ref()),
2209 "CONTEXT7_HOME vazio não deve usar o tempdir: {}",
2210 c.display()
2211 );
2212 }
2213 }
2215}