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 confirmar_clear() -> Result<bool> {
425 use std::io::Write;
426 print!("Tem certeza que deseja remover TODAS as chaves? [s/N] ");
427 std::io::stdout()
428 .flush()
429 .context("Falha ao limpar buffer de saída")?;
430
431 let mut entrada = String::new();
432 std::io::stdin()
433 .read_line(&mut entrada)
434 .context("Falha ao ler confirmação do usuário")?;
435
436 Ok(matches!(
437 entrada.trim().to_lowercase().as_str(),
438 "s" | "sim" | "y" | "yes"
439 ))
440}
441
442pub fn cmd_keys_add(chave: &str) -> Result<()> {
449 let chave_trimmed = chave.trim();
450 if chave_trimmed.is_empty() {
451 crate::output::exibir_chave_invalida_vazia();
452 bail!(ErroContext7::OperacaoKeysFalhou);
453 }
454 if !chave_trimmed.starts_with("ctx7sk-") || chave_trimmed.len() < 16 {
455 crate::output::exibir_aviso_formato_chave();
456 }
457 if let Some(config) = ler_config_xdg_raw()? {
459 if config.keys.iter().any(|c| c.value == chave_trimmed) {
460 crate::output::exibir_chave_ja_existia();
461 return Ok(());
462 }
463 }
464 let caminho = escrever_config_xdg(chave_trimmed)?;
465 crate::output::exibir_chave_adicionada(&caminho);
466 Ok(())
467}
468
469pub fn cmd_keys_list(json: bool) -> Result<()> {
473 match ler_config_xdg_raw()? {
474 None => {
475 if json {
476 println!("[]");
477 } else {
478 crate::output::exibir_nenhuma_chave();
479 }
480 }
481 Some(config) if config.keys.is_empty() => {
482 if json {
483 println!("[]");
484 } else {
485 crate::output::exibir_nenhuma_chave();
486 }
487 }
488 Some(config) => {
489 if json {
490 let mascaradas: Vec<serde_json::Value> = config
491 .keys
492 .iter()
493 .enumerate()
494 .map(|(i, k)| {
495 serde_json::json!({
496 "index": i + 1,
497 "masked_key": mascarar_chave(&k.value),
498 "added_at": k.added_at
499 })
500 })
501 .collect();
502 println!(
503 "{}",
504 serde_json::to_string_pretty(&mascaradas).with_context(|| crate::i18n::t(
505 crate::i18n::Mensagem::FalhaSerializarJson
506 ))?
507 );
508 } else {
509 crate::output::exibir_chaves_mascaradas(&config.keys, mascarar_chave);
510 }
511 }
512 }
513 Ok(())
514}
515
516pub fn cmd_keys_remove(indice: usize) -> Result<()> {
518 let mut config = match ler_config_xdg_raw()? {
519 None => {
520 crate::output::exibir_nenhuma_chave_para_remover();
521 bail!(ErroContext7::OperacaoKeysFalhou);
522 }
523 Some(c) if c.keys.is_empty() => {
524 crate::output::exibir_nenhuma_chave_para_remover();
525 bail!(ErroContext7::OperacaoKeysFalhou);
526 }
527 Some(c) => c,
528 };
529
530 if indice == 0 || indice > config.keys.len() {
531 crate::output::exibir_indice_invalido(indice, config.keys.len());
532 bail!(ErroContext7::OperacaoKeysFalhou);
533 }
534
535 let removida = config.keys.remove(indice - 1);
536 escrever_config_arquivo(&config)?;
537 crate::output::exibir_chave_removida(&mascarar_chave(&removida.value));
538 Ok(())
539}
540
541pub fn cmd_keys_clear(sim: bool) -> Result<()> {
543 if !sim && !confirmar_clear()? {
544 crate::output::exibir_operacao_cancelada();
545 return Ok(());
546 }
547
548 let config = ConfigArquivo {
549 schema_version: 1,
550 keys: Vec::new(),
551 };
552 escrever_config_arquivo(&config)?;
553 crate::output::exibir_chaves_removidas();
554 Ok(())
555}
556
557pub fn cmd_keys_path() -> Result<()> {
559 match descobrir_caminho_config() {
560 Some(caminho) => println!("{}", caminho.display()),
561 None => crate::output::exibir_xdg_nao_suportado(),
562 }
563 Ok(())
564}
565
566pub fn cmd_keys_import(arquivo: &std::path::Path) -> Result<()> {
570 let conteudo = std::fs::read_to_string(arquivo)
571 .with_context(|| format!("Falha ao ler arquivo: {}", arquivo.display()))?;
572
573 let chaves =
574 extrair_chaves_env(&conteudo).with_context(|| format!("Arquivo: {}", arquivo.display()))?;
575
576 let total = chaves.len();
577 let mut importadas = 0usize;
578
579 for chave in &chaves {
580 escrever_config_xdg(chave)?;
581 importadas += 1;
582 }
583
584 crate::output::exibir_importacao_concluida(importadas, total);
585 Ok(())
586}
587
588pub fn cmd_keys_export() -> Result<()> {
592 match ler_config_xdg_raw()? {
593 None => {}
594 Some(config) if config.keys.is_empty() => {}
595 Some(config) => {
596 for chave in &config.keys {
597 println!("CONTEXT7_API={}", chave.value);
598 }
599 }
600 }
601 Ok(())
602}
603
604#[cfg(test)]
607mod testes {
608 use super::*;
609
610 fn ler_config_toml_do_caminho(caminho: &std::path::Path) -> Result<ConfigArquivo> {
614 let conteudo = std::fs::read_to_string(caminho)
615 .with_context(|| format!("Falha ao ler: {}", caminho.display()))?;
616 toml::from_str(&conteudo)
617 .with_context(|| format!("TOML inválido em: {}", caminho.display()))
618 }
619
620 #[test]
623 fn testa_parsing_env_com_multiplas_chaves_iguais() {
624 let mut conteudo = String::new();
625 for i in 0..17 {
626 conteudo.push_str(&format!("CONTEXT7_API=ctx7sk-chave-{:02}\n", i));
627 }
628 let chaves = extrair_chaves_env(&conteudo).expect("Deve extrair 17 chaves sem erro");
629 assert_eq!(chaves.len(), 17, "Deve retornar exatamente 17 chaves");
630 for (i, chave) in chaves.iter().enumerate() {
631 assert_eq!(
632 chave,
633 &format!("ctx7sk-chave-{:02}", i),
634 "Chave {} deve ter o valor correto",
635 i
636 );
637 }
638 }
639
640 #[test]
641 fn testa_parsing_env_ignora_comentarios_e_linhas_vazias() {
642 let conteudo = "# Este é um comentário\n\
643 CONTEXT7_API=ctx7sk-chave-valida-01\n\
644 \n\
645 # Outro comentário\n\
646 CONTEXT7_API=ctx7sk-chave-valida-02\n\
647 \n";
648 let chaves = extrair_chaves_env(conteudo).expect("Deve extrair chaves sem erro");
649 assert_eq!(chaves.len(), 2, "Deve ignorar comentários e linhas vazias");
650 assert_eq!(chaves[0], "ctx7sk-chave-valida-01");
651 assert_eq!(chaves[1], "ctx7sk-chave-valida-02");
652 }
653
654 #[test]
655 fn testa_parsing_env_remove_aspas_duplas() {
656 let conteudo = "CONTEXT7_API=\"ctx7sk-abc-com-aspas\"\n";
657 let chaves = extrair_chaves_env(conteudo).expect("Deve extrair chave sem erro");
658 assert_eq!(chaves.len(), 1);
659 assert_eq!(
660 chaves[0], "ctx7sk-abc-com-aspas",
661 "Deve remover aspas duplas"
662 );
663 }
664
665 #[test]
666 fn testa_parsing_env_remove_aspas_simples() {
667 let conteudo = "CONTEXT7_API='ctx7sk-abc-aspas-simples'\n";
668 let chaves = extrair_chaves_env(conteudo).expect("Deve extrair chave sem erro");
669 assert_eq!(chaves.len(), 1);
670 assert_eq!(
671 chaves[0], "ctx7sk-abc-aspas-simples",
672 "Deve remover aspas simples"
673 );
674 }
675
676 #[test]
677 fn testa_parsing_env_erro_quando_nenhuma_chave() {
678 let conteudo = "# Apenas comentários\n\
679 OUTRA_VAR=valor\n\
680 \n";
681 let resultado = extrair_chaves_env(conteudo);
682 assert!(
683 resultado.is_err(),
684 "Deve retornar Err quando não há chaves CONTEXT7_API"
685 );
686 let mensagem_erro = resultado.unwrap_err().to_string();
687 assert!(
688 mensagem_erro.contains("chave")
689 || mensagem_erro.contains("CONTEXT7_API")
690 || mensagem_erro.contains("key")
691 || mensagem_erro.contains("API"),
692 "Mensagem de erro deve mencionar CONTEXT7_API, chave, key ou API, obteve: {}",
693 mensagem_erro
694 );
695 }
696
697 #[test]
698 fn testa_parsing_env_ignora_chaves_vazias() {
699 let conteudo = "CONTEXT7_API=\n\
700 CONTEXT7_API=ctx7sk-valida\n";
701 let chaves = extrair_chaves_env(conteudo).expect("Deve extrair chave sem erro");
702 assert_eq!(
703 chaves.len(),
704 1,
705 "Deve ignorar entradas CONTEXT7_API sem valor"
706 );
707 assert_eq!(chaves[0], "ctx7sk-valida");
708 }
709
710 #[test]
711 fn testa_parsing_env_ignora_comentario_inline() {
712 let conteudo = "CONTEXT7_API=ctx7sk-valida # comentário aqui\n";
713 let chaves = extrair_chaves_env(conteudo).expect("Deve extrair chave sem erro");
714 assert_eq!(chaves.len(), 1);
715 assert_eq!(chaves[0], "ctx7sk-valida");
716 }
717
718 #[test]
721 fn testa_mascarar_chave_com_valor_longo_exibe_prefixo_e_sufixo() {
722 let chave = "ctx7sk-abc123-def456-ghi789";
723 assert_eq!(chave.len(), 27, "Pré-condição: chave deve ter 27 chars");
724 let mascarada = mascarar_chave(chave);
725 assert!(
726 mascarada.starts_with("ctx7sk-abc12"),
727 "Deve iniciar com os primeiros 12 chars, obteve: {}",
728 mascarada
729 );
730 assert!(
731 mascarada.ends_with("i789"),
732 "Deve terminar com os últimos 4 chars, obteve: {}",
733 mascarada
734 );
735 assert!(
736 mascarada.contains("..."),
737 "Deve conter '...' entre prefixo e sufixo, obteve: {}",
738 mascarada
739 );
740 }
741
742 #[test]
743 fn testa_mascarar_chave_curta_retorna_asteriscos() {
744 let chave_exatamente_16 = "ctx7sk-abcdef012";
745 assert_eq!(
746 chave_exatamente_16.len(),
747 16,
748 "Pré-condição: chave deve ter 16 chars"
749 );
750 let mascarada = mascarar_chave(chave_exatamente_16);
751 assert_eq!(
752 mascarada, "***",
753 "Chave de 16 chars deve retornar '***', obteve: {}",
754 mascarada
755 );
756 }
757
758 #[test]
759 fn testa_mascarar_chave_vazia_retorna_asteriscos() {
760 let mascarada = mascarar_chave("");
761 assert_eq!(
762 mascarada, "***",
763 "Chave vazia deve retornar '***', obteve: {}",
764 mascarada
765 );
766 }
767
768 #[test]
769 fn testa_mascarar_chave_de_exatamente_17_chars_mascara_corretamente() {
770 let chave = "ctx7sk-abcdef0123"; assert_eq!(chave.len(), 17, "Pré-condição: chave deve ter 17 chars");
772 let mascarada = mascarar_chave(chave);
773 assert!(
774 mascarada.contains("..."),
775 "Chave de 17 chars deve ser mascarada, obteve: {}",
776 mascarada
777 );
778 assert_eq!(
779 &mascarada[..12],
780 &chave[..12],
781 "Prefixo de 12 chars deve ser preservado"
782 );
783 assert!(
784 mascarada.ends_with(&chave[chave.len() - 4..]),
785 "Sufixo de 4 chars deve ser preservado"
786 );
787 }
788
789 #[test]
792 #[serial_test::serial]
793 fn testa_ler_env_var_chave_retorna_some_quando_setada() {
794 unsafe {
797 std::env::set_var("CONTEXT7_API_KEYS", "ctx7sk-chave-teste-01");
798 }
799 let resultado = ler_env_var_chave();
800 unsafe {
801 std::env::remove_var("CONTEXT7_API_KEYS");
802 }
803
804 let chaves = resultado.expect("Deve retornar Some com chave válida");
805 assert_eq!(chaves.len(), 1, "Deve retornar exatamente 1 chave");
806 assert_eq!(chaves[0], "ctx7sk-chave-teste-01");
807 }
808
809 #[test]
810 #[serial_test::serial]
811 fn testa_ler_env_var_chave_aceita_multiplas_separadas_por_virgula() {
812 unsafe {
814 std::env::set_var(
815 "CONTEXT7_API_KEYS",
816 "ctx7sk-chave-a, ctx7sk-chave-b , ctx7sk-chave-c",
817 );
818 }
819 let resultado = ler_env_var_chave();
820 unsafe {
821 std::env::remove_var("CONTEXT7_API_KEYS");
822 }
823
824 let chaves = resultado.expect("Deve retornar Some com múltiplas chaves");
825 assert_eq!(chaves.len(), 3, "Deve retornar 3 chaves");
826 assert_eq!(chaves[0], "ctx7sk-chave-a");
827 assert_eq!(chaves[1], "ctx7sk-chave-b");
828 assert_eq!(chaves[2], "ctx7sk-chave-c");
829 }
830
831 #[test]
832 #[serial_test::serial]
833 fn testa_ler_env_var_chave_retorna_none_quando_vazia() {
834 unsafe {
836 std::env::set_var("CONTEXT7_API_KEYS", "");
837 }
838 let resultado = ler_env_var_chave();
839 unsafe {
840 std::env::remove_var("CONTEXT7_API_KEYS");
841 }
842
843 assert!(
844 resultado.is_none(),
845 "Deve retornar None quando env var está vazia"
846 );
847 }
848
849 #[test]
850 #[serial_test::serial]
851 fn testa_ler_env_var_chave_retorna_none_quando_apenas_whitespace() {
852 unsafe {
854 std::env::set_var("CONTEXT7_API_KEYS", " , , ");
855 }
856 let resultado = ler_env_var_chave();
857 unsafe {
858 std::env::remove_var("CONTEXT7_API_KEYS");
859 }
860
861 assert!(
862 resultado.is_none(),
863 "Deve retornar None quando env var contém apenas whitespace/vírgulas"
864 );
865 }
866
867 #[test]
868 #[serial_test::serial]
869 fn testa_ler_env_var_chave_retorna_none_quando_ausente() {
870 unsafe {
872 std::env::remove_var("CONTEXT7_API_KEYS");
873 }
874 let resultado = ler_env_var_chave();
875
876 assert!(
877 resultado.is_none(),
878 "Deve retornar None quando env var não existe"
879 );
880 }
881
882 #[test]
885 #[serial_test::serial]
886 fn testa_context7_home_rejeita_path_traversal() {
887 let casos = ["../../../etc", "..", "/tmp/../etc"];
888 for caso in &casos {
889 unsafe {
891 std::env::set_var("CONTEXT7_HOME", caso);
892 }
893 let resultado = descobrir_caminho_config();
894 unsafe {
895 std::env::remove_var("CONTEXT7_HOME");
896 }
897
898 if let Some(caminho) = resultado {
900 let s = caminho.to_string_lossy();
901 assert!(
902 !s.contains(".."),
903 "Path traversal '{caso}' não deve resultar em caminho com '..': {s}"
904 );
905 }
906 }
908 }
909
910 #[test]
913 #[serial_test::serial]
914 fn testa_ler_config_xdg_arquivo_inexistente_retorna_none() {
915 let dir_temp = tempfile::TempDir::new().expect("Deve criar diretório temporário");
916 unsafe {
918 std::env::set_var("CONTEXT7_HOME", dir_temp.path());
919 }
920 let resultado = ler_config_xdg();
921 unsafe {
922 std::env::remove_var("CONTEXT7_HOME");
923 }
924
925 let valor = resultado.expect("Deve retornar Ok quando arquivo não existe");
926 assert!(
927 valor.is_none(),
928 "Deve retornar None quando config.toml não existe"
929 );
930 }
931
932 #[test]
933 #[serial_test::serial]
934 fn testa_ler_config_xdg_le_toml_valido_com_multiplas_chaves() {
935 let dir_temp = tempfile::TempDir::new().expect("Deve criar diretório temporário");
936 let dir_context7 = dir_temp.path().join("context7");
937 std::fs::create_dir_all(&dir_context7).expect("Deve criar diretório context7");
938
939 let toml_conteudo = r#"schema_version = 1
940
941[[keys]]
942value = "ctx7sk-chave-xdg-01"
943added_at = "2026-01-01T00:00:00+00:00"
944
945[[keys]]
946value = "ctx7sk-chave-xdg-02"
947added_at = "2026-01-02T00:00:00+00:00"
948"#;
949 std::fs::write(dir_context7.join("config.toml"), toml_conteudo)
950 .expect("Deve escrever config.toml");
951
952 unsafe {
954 std::env::set_var("CONTEXT7_HOME", dir_temp.path());
955 }
956 let resultado = ler_config_xdg();
957 unsafe {
958 std::env::remove_var("CONTEXT7_HOME");
959 }
960
961 let chaves = resultado
962 .expect("Deve retornar Ok")
963 .expect("Deve retornar Some com chaves");
964 assert_eq!(chaves.len(), 2, "Deve retornar 2 chaves");
965 assert_eq!(chaves[0], "ctx7sk-chave-xdg-01");
966 assert_eq!(chaves[1], "ctx7sk-chave-xdg-02");
967 }
968
969 #[test]
970 #[serial_test::serial]
971 fn testa_ler_config_xdg_retorna_err_quando_toml_invalido() {
972 let dir_temp = tempfile::TempDir::new().expect("Deve criar diretório temporário");
973 let dir_context7 = dir_temp.path().join("context7");
974 std::fs::create_dir_all(&dir_context7).expect("Deve criar diretório context7");
975
976 std::fs::write(
977 dir_context7.join("config.toml"),
978 "schema_version = INVALIDO\n[[[malformado",
979 )
980 .expect("Deve escrever TOML inválido");
981
982 unsafe {
984 std::env::set_var("CONTEXT7_HOME", dir_temp.path());
985 }
986 let resultado = ler_config_xdg();
987 unsafe {
988 std::env::remove_var("CONTEXT7_HOME");
989 }
990
991 assert!(
992 resultado.is_err(),
993 "Deve retornar Err quando TOML está malformado"
994 );
995 }
996
997 #[test]
998 #[serial_test::serial]
999 fn testa_ler_config_xdg_preserva_ordem_das_chaves() {
1000 let dir_temp = tempfile::TempDir::new().expect("Deve criar diretório temporário");
1001 let dir_context7 = dir_temp.path().join("context7");
1002 std::fs::create_dir_all(&dir_context7).expect("Deve criar diretório context7");
1003
1004 let toml_conteudo = r#"schema_version = 1
1005
1006[[keys]]
1007value = "ctx7sk-primeira"
1008added_at = "2026-01-01T00:00:00+00:00"
1009
1010[[keys]]
1011value = "ctx7sk-segunda"
1012added_at = "2026-01-02T00:00:00+00:00"
1013
1014[[keys]]
1015value = "ctx7sk-terceira"
1016added_at = "2026-01-03T00:00:00+00:00"
1017"#;
1018 std::fs::write(dir_context7.join("config.toml"), toml_conteudo)
1019 .expect("Deve escrever config.toml");
1020
1021 unsafe {
1023 std::env::set_var("CONTEXT7_HOME", dir_temp.path());
1024 }
1025 let resultado = ler_config_xdg();
1026 unsafe {
1027 std::env::remove_var("CONTEXT7_HOME");
1028 }
1029
1030 let chaves = resultado
1031 .expect("Deve retornar Ok")
1032 .expect("Deve retornar Some");
1033 assert_eq!(chaves[0], "ctx7sk-primeira");
1034 assert_eq!(chaves[1], "ctx7sk-segunda");
1035 assert_eq!(chaves[2], "ctx7sk-terceira");
1036 }
1037
1038 #[test]
1039 #[serial_test::serial]
1040 fn testa_ler_config_xdg_keys_vazio_retorna_none() {
1041 let dir_temp = tempfile::TempDir::new().expect("Deve criar diretório temporário");
1042 let dir_context7 = dir_temp.path().join("context7");
1043 std::fs::create_dir_all(&dir_context7).expect("Deve criar diretório context7");
1044
1045 let toml_sem_chaves = "schema_version = 1\n";
1046 std::fs::write(dir_context7.join("config.toml"), toml_sem_chaves)
1047 .expect("Deve escrever config.toml sem keys");
1048
1049 unsafe {
1051 std::env::set_var("CONTEXT7_HOME", dir_temp.path());
1052 }
1053 let resultado = ler_config_xdg();
1054 unsafe {
1055 std::env::remove_var("CONTEXT7_HOME");
1056 }
1057
1058 let valor = resultado.expect("Deve retornar Ok");
1059 assert!(
1060 valor.is_none(),
1061 "Deve retornar None quando config.toml existe mas keys está vazio"
1062 );
1063 }
1064
1065 #[test]
1068 #[serial_test::serial]
1069 fn testa_escrever_config_xdg_roundtrip_serializa_e_deserializa() {
1070 let dir_temp = tempfile::TempDir::new().expect("Deve criar diretório temporário");
1071 unsafe {
1073 std::env::set_var("CONTEXT7_HOME", dir_temp.path());
1074 }
1075
1076 let caminho =
1077 escrever_config_xdg("ctx7sk-roundtrip-01").expect("Deve escrever config sem erro");
1078
1079 let config_lido = ler_config_toml_do_caminho(&caminho)
1080 .expect("Deve ler TOML escrito por escrever_config_xdg");
1081
1082 unsafe {
1083 std::env::remove_var("CONTEXT7_HOME");
1084 }
1085
1086 assert_eq!(config_lido.schema_version, 1, "schema_version deve ser 1");
1087 assert_eq!(config_lido.keys.len(), 1, "Deve conter 1 chave");
1088 assert_eq!(
1089 config_lido.keys[0].value, "ctx7sk-roundtrip-01",
1090 "Valor da chave deve ser preservado"
1091 );
1092 assert!(
1093 !config_lido.keys[0].added_at.is_empty(),
1094 "added_at não deve ser vazio"
1095 );
1096 }
1097
1098 #[test]
1099 #[serial_test::serial]
1100 fn testa_escrever_config_xdg_cria_diretorios_pai_se_nao_existirem() {
1101 let dir_temp = tempfile::TempDir::new().expect("Deve criar diretório temporário");
1102 let xdg_novo = dir_temp.path().join("xdg_inexistente");
1103 unsafe {
1105 std::env::set_var("CONTEXT7_HOME", &xdg_novo);
1106 }
1107
1108 let resultado = escrever_config_xdg("ctx7sk-mkdir-teste");
1109 unsafe {
1110 std::env::remove_var("CONTEXT7_HOME");
1111 }
1112
1113 let caminho = resultado.expect("Deve criar diretório pai e escrever config");
1114 assert!(
1115 caminho.exists(),
1116 "Arquivo de config deve existir após escrita"
1117 );
1118 }
1119
1120 #[test]
1121 #[serial_test::serial]
1122 fn testa_escrever_config_xdg_nao_duplica_chave_ja_existente() {
1123 let dir_temp = tempfile::TempDir::new().expect("Deve criar diretório temporário");
1124 unsafe {
1126 std::env::set_var("CONTEXT7_HOME", dir_temp.path());
1127 }
1128
1129 escrever_config_xdg("ctx7sk-unica").expect("Primeira escrita deve funcionar");
1130 escrever_config_xdg("ctx7sk-unica").expect("Segunda escrita não deve falhar");
1131
1132 let caminho = descobrir_caminho_config().expect("Deve ter caminho XDG");
1133 let config = ler_config_toml_do_caminho(&caminho).expect("Deve ler config");
1134
1135 unsafe {
1136 std::env::remove_var("CONTEXT7_HOME");
1137 }
1138
1139 assert_eq!(
1140 config.keys.len(),
1141 1,
1142 "Não deve duplicar chave já existente — deve ter apenas 1"
1143 );
1144 }
1145
1146 #[test]
1147 #[serial_test::serial]
1148 fn testa_escrever_config_xdg_acumula_chaves_distintas() {
1149 let dir_temp = tempfile::TempDir::new().expect("Deve criar diretório temporário");
1150 unsafe {
1152 std::env::set_var("CONTEXT7_HOME", dir_temp.path());
1153 }
1154
1155 escrever_config_xdg("ctx7sk-chave-a").expect("Primeira escrita deve funcionar");
1156 escrever_config_xdg("ctx7sk-chave-b").expect("Segunda escrita deve funcionar");
1157 escrever_config_xdg("ctx7sk-chave-c").expect("Terceira escrita deve funcionar");
1158
1159 let caminho = descobrir_caminho_config().expect("Deve ter caminho XDG");
1160 let config = ler_config_toml_do_caminho(&caminho).expect("Deve ler config");
1161
1162 unsafe {
1163 std::env::remove_var("CONTEXT7_HOME");
1164 }
1165
1166 assert_eq!(config.keys.len(), 3, "Deve acumular 3 chaves distintas");
1167 let valores: Vec<&str> = config.keys.iter().map(|c| c.value.as_str()).collect();
1168 assert!(valores.contains(&"ctx7sk-chave-a"));
1169 assert!(valores.contains(&"ctx7sk-chave-b"));
1170 assert!(valores.contains(&"ctx7sk-chave-c"));
1171 }
1172
1173 #[test]
1174 #[cfg(unix)]
1175 #[serial_test::serial]
1176 fn testa_escrever_config_xdg_aplica_permissoes_600_em_unix() {
1177 use std::os::unix::fs::PermissionsExt;
1178
1179 let dir_temp = tempfile::TempDir::new().expect("Deve criar diretório temporário");
1180 unsafe {
1182 std::env::set_var("CONTEXT7_HOME", dir_temp.path());
1183 }
1184
1185 let caminho =
1186 escrever_config_xdg("ctx7sk-perm-600").expect("Deve escrever config sem erro");
1187 unsafe {
1188 std::env::remove_var("CONTEXT7_HOME");
1189 }
1190
1191 let metadados = std::fs::metadata(&caminho).expect("Deve obter metadados do arquivo");
1192 let modo = metadados.permissions().mode() & 0o777;
1193
1194 assert_eq!(modo, 0o600, "Permissões devem ser 600, obteve: {:o}", modo);
1195 }
1196
1197 #[test]
1200 fn testa_config_arquivo_roundtrip_serde_preserva_todos_campos() {
1201 let config_original = ConfigArquivo {
1202 schema_version: 1,
1203 keys: vec![
1204 ChaveArmazenada {
1205 value: "ctx7sk-serde-01".to_string(),
1206 added_at: "2026-01-01T12:00:00+00:00".to_string(),
1207 },
1208 ChaveArmazenada {
1209 value: "ctx7sk-serde-02".to_string(),
1210 added_at: "2026-01-02T12:00:00+00:00".to_string(),
1211 },
1212 ],
1213 };
1214
1215 let toml_str = toml::to_string_pretty(&config_original)
1216 .expect("Deve serializar ConfigArquivo para TOML");
1217 let config_deserializado: ConfigArquivo =
1218 toml::from_str(&toml_str).expect("Deve deserializar TOML de volta para ConfigArquivo");
1219
1220 assert_eq!(
1221 config_deserializado.schema_version, config_original.schema_version,
1222 "schema_version deve ser preservado no roundtrip"
1223 );
1224 assert_eq!(
1225 config_deserializado.keys.len(),
1226 config_original.keys.len(),
1227 "Número de chaves deve ser preservado"
1228 );
1229 assert_eq!(
1230 config_deserializado.keys[0].value, config_original.keys[0].value,
1231 "Valor da primeira chave deve ser preservado"
1232 );
1233 assert_eq!(
1234 config_deserializado.keys[0].added_at, config_original.keys[0].added_at,
1235 "added_at da primeira chave deve ser preservado"
1236 );
1237 }
1238
1239 #[test]
1240 fn testa_config_arquivo_schema_version_sempre_presente_na_serializacao() {
1241 let config = ConfigArquivo {
1242 schema_version: 1,
1243 keys: Vec::new(),
1244 };
1245
1246 let toml_str = toml::to_string_pretty(&config).expect("Deve serializar para TOML");
1247
1248 assert!(
1249 toml_str.contains("schema_version"),
1250 "schema_version deve estar presente na serialização TOML"
1251 );
1252 assert!(toml_str.contains('1'), "Valor 1 deve estar presente");
1253 }
1254
1255 #[test]
1256 fn testa_config_arquivo_keys_vazio_aceito_na_deserializacao() {
1257 let toml_str = "schema_version = 1\n";
1258 let config: ConfigArquivo =
1259 toml::from_str(toml_str).expect("Deve deserializar com keys ausente (default vazio)");
1260
1261 assert_eq!(config.schema_version, 1);
1262 assert!(
1263 config.keys.is_empty(),
1264 "keys deve ser vazio quando não presente no TOML"
1265 );
1266 }
1267
1268 #[test]
1269 fn testa_chave_armazenada_preserva_added_at_como_string_utc() {
1270 let timestamp = "2026-04-08T20:00:00+00:00";
1271 let chave = ChaveArmazenada {
1272 value: "ctx7sk-timestamp".to_string(),
1273 added_at: timestamp.to_string(),
1274 };
1275
1276 let toml_str = toml::to_string_pretty(&chave).expect("Deve serializar ChaveArmazenada");
1277 let chave_de_volta: ChaveArmazenada =
1278 toml::from_str(&toml_str).expect("Deve deserializar ChaveArmazenada");
1279
1280 assert_eq!(
1281 chave_de_volta.added_at, timestamp,
1282 "Timestamp added_at deve ser preservado exatamente"
1283 );
1284 }
1285
1286 #[test]
1289 #[serial_test::serial]
1290 fn testa_carregar_chaves_api_env_var_tem_prioridade_sobre_xdg() {
1291 let dir_temp = tempfile::TempDir::new().expect("Deve criar diretório temporário");
1292 let dir_context7 = dir_temp.path().join("context7");
1293 std::fs::create_dir_all(&dir_context7).expect("Deve criar diretório context7");
1294
1295 let toml_xdg = r#"schema_version = 1
1296[[keys]]
1297value = "ctx7sk-xdg-deve-ser-ignorada"
1298added_at = "2026-01-01T00:00:00+00:00"
1299"#;
1300 std::fs::write(dir_context7.join("config.toml"), toml_xdg)
1301 .expect("Deve escrever config XDG");
1302
1303 unsafe {
1305 std::env::set_var("CONTEXT7_API_KEYS", "ctx7sk-env-var-prioritaria");
1306 std::env::set_var("CONTEXT7_HOME", dir_temp.path());
1307 }
1308
1309 let resultado = carregar_chaves_api();
1310
1311 unsafe {
1312 std::env::remove_var("CONTEXT7_API_KEYS");
1313 std::env::remove_var("CONTEXT7_HOME");
1314 }
1315
1316 let chaves = resultado.expect("Deve carregar chaves via env var");
1317 assert_eq!(chaves.len(), 1);
1318 assert_eq!(
1319 chaves[0], "ctx7sk-env-var-prioritaria",
1320 "Env var deve ter prioridade sobre XDG"
1321 );
1322 }
1323
1324 #[test]
1325 #[serial_test::serial]
1326 fn testa_carregar_chaves_api_xdg_usado_quando_env_var_ausente() {
1327 let dir_temp = tempfile::TempDir::new().expect("Deve criar diretório temporário");
1328 let dir_context7 = dir_temp.path().join("context7");
1329 std::fs::create_dir_all(&dir_context7).expect("Deve criar diretório context7");
1330
1331 let toml_xdg = r#"schema_version = 1
1332[[keys]]
1333value = "ctx7sk-via-xdg"
1334added_at = "2026-01-01T00:00:00+00:00"
1335"#;
1336 std::fs::write(dir_context7.join("config.toml"), toml_xdg)
1337 .expect("Deve escrever config XDG");
1338
1339 unsafe {
1341 std::env::remove_var("CONTEXT7_API_KEYS");
1342 std::env::set_var("CONTEXT7_HOME", dir_temp.path());
1343 }
1344
1345 let resultado = carregar_chaves_api();
1346
1347 unsafe {
1348 std::env::remove_var("CONTEXT7_HOME");
1349 }
1350
1351 let chaves = resultado.expect("Deve carregar chaves via XDG");
1352 assert_eq!(chaves.len(), 1);
1353 assert_eq!(chaves[0], "ctx7sk-via-xdg");
1354 }
1355
1356 #[test]
1357 #[serial_test::serial]
1358 fn testa_carregar_chaves_api_retorna_err_quando_nada_disponivel() {
1359 let dir_temp = tempfile::TempDir::new().expect("Deve criar diretório temporário");
1360 let dir_xdg_vazio = dir_temp.path().join("xdg_vazio");
1361 std::fs::create_dir_all(&dir_xdg_vazio).expect("Deve criar diretório XDG vazio");
1362
1363 let dir_sem_env = dir_temp.path().join("sem_env");
1364 std::fs::create_dir_all(&dir_sem_env).expect("Deve criar diretório sem .env");
1365
1366 unsafe {
1368 std::env::remove_var("CONTEXT7_API_KEYS");
1369 std::env::set_var("CONTEXT7_HOME", &dir_xdg_vazio);
1370 }
1371 let cwd_original = std::env::current_dir().expect("Deve obter CWD atual");
1372 std::env::set_current_dir(&dir_sem_env).expect("Deve mudar CWD");
1373
1374 let resultado = carregar_chaves_api();
1375
1376 std::env::set_current_dir(&cwd_original).expect("Deve restaurar CWD");
1377 unsafe {
1378 std::env::remove_var("CONTEXT7_HOME");
1379 }
1380
1381 assert!(
1382 resultado.is_err(),
1383 "Deve retornar Err quando nenhuma camada fornecer chaves"
1384 );
1385 }
1386
1387 #[test]
1388 #[serial_test::serial]
1389 fn testa_ler_env_cwd_le_env_com_multiplas_chaves_context7_api() {
1390 let dir_temp = tempfile::TempDir::new().expect("Deve criar diretório temporário");
1391 let conteudo_env = "CONTEXT7_API=ctx7sk-cwd-01\nCONTEXT7_API=ctx7sk-cwd-02\n";
1392 std::fs::write(dir_temp.path().join(".env"), conteudo_env)
1393 .expect("Deve escrever .env temporário");
1394
1395 let cwd_original = std::env::current_dir().expect("Deve obter CWD");
1396 std::env::set_current_dir(dir_temp.path()).expect("Deve mudar CWD para temp");
1397
1398 let resultado = ler_env_cwd();
1399
1400 std::env::set_current_dir(&cwd_original).expect("Deve restaurar CWD");
1401
1402 let chaves = resultado.expect("Deve retornar Some com chaves do .env CWD");
1403 assert_eq!(chaves.len(), 2, "Deve ler 2 chaves do .env");
1404 assert_eq!(chaves[0], "ctx7sk-cwd-01");
1405 assert_eq!(chaves[1], "ctx7sk-cwd-02");
1406 }
1407
1408 #[test]
1409 #[serial_test::serial]
1410 fn testa_ler_env_cwd_retorna_none_quando_env_ausente() {
1411 let dir_temp = tempfile::TempDir::new().expect("Deve criar diretório temporário");
1412
1413 let cwd_original = std::env::current_dir().expect("Deve obter CWD");
1414 std::env::set_current_dir(dir_temp.path()).expect("Deve mudar CWD para temp sem .env");
1415
1416 let resultado = ler_env_cwd();
1417
1418 std::env::set_current_dir(&cwd_original).expect("Deve restaurar CWD");
1419
1420 assert!(
1421 resultado.is_none(),
1422 "Deve retornar None quando não há .env no CWD"
1423 );
1424 }
1425
1426 #[test]
1427 fn testa_descobrir_caminho_logs_xdg_retorna_algum_caminho_valido() {
1428 let resultado = descobrir_caminho_logs_xdg();
1429
1430 if let Some(caminho) = resultado {
1431 let caminho_str = caminho.to_string_lossy();
1432 assert!(
1433 caminho_str.contains("context7"),
1434 "Caminho de logs XDG deve conter 'context7', obteve: {}",
1435 caminho_str
1436 );
1437 }
1438 }
1439
1440 #[test]
1441 #[serial_test::serial]
1442 fn testa_carregar_chaves_api_env_cwd_usado_quando_env_var_e_xdg_ausentes() {
1443 let dir_temp = tempfile::TempDir::new().expect("Deve criar diretório temporário");
1444 let dir_xdg_sem_config = dir_temp.path().join("xdg_sem_config");
1445 std::fs::create_dir_all(&dir_xdg_sem_config).expect("Deve criar diretório XDG vazio");
1446
1447 let dir_cwd = dir_temp.path().join("cwd_com_env");
1448 std::fs::create_dir_all(&dir_cwd).expect("Deve criar CWD temporário");
1449 std::fs::write(dir_cwd.join(".env"), "CONTEXT7_API=ctx7sk-cwd-camada-3\n")
1450 .expect("Deve escrever .env no CWD");
1451
1452 unsafe {
1454 std::env::remove_var("CONTEXT7_API_KEYS");
1455 std::env::set_var("CONTEXT7_HOME", &dir_xdg_sem_config);
1456 }
1457 let cwd_original = std::env::current_dir().expect("Deve obter CWD");
1458 std::env::set_current_dir(&dir_cwd).expect("Deve mudar CWD");
1459
1460 let resultado = carregar_chaves_api();
1461
1462 std::env::set_current_dir(&cwd_original).expect("Deve restaurar CWD");
1463 unsafe {
1464 std::env::remove_var("CONTEXT7_HOME");
1465 }
1466
1467 let chaves = resultado.expect("Deve carregar chaves via .env CWD");
1468 assert_eq!(chaves.len(), 1);
1469 assert_eq!(chaves[0], "ctx7sk-cwd-camada-3");
1470 }
1471
1472 #[test]
1473 #[serial_test::serial]
1474 fn testa_carregar_chaves_api_faz_fallback_quando_xdg_invalido() {
1475 let dir_temp = tempfile::TempDir::new().expect("Deve criar diretório temporário");
1476 let dir_context7 = dir_temp.path().join("context7");
1477 std::fs::create_dir_all(&dir_context7).expect("Deve criar diretório context7");
1478
1479 std::fs::write(dir_context7.join("config.toml"), "[[[invalido")
1480 .expect("Deve escrever TOML inválido");
1481
1482 let dir_cwd = dir_temp.path().join("cwd_fallback");
1483 std::fs::create_dir_all(&dir_cwd).expect("Deve criar CWD com .env");
1484 std::fs::write(dir_cwd.join(".env"), "CONTEXT7_API=ctx7sk-fallback-cwd\n")
1485 .expect("Deve escrever .env no CWD");
1486
1487 unsafe {
1489 std::env::remove_var("CONTEXT7_API_KEYS");
1490 std::env::set_var("CONTEXT7_HOME", dir_temp.path());
1491 }
1492 let cwd_original = std::env::current_dir().expect("Deve obter CWD");
1493 std::env::set_current_dir(&dir_cwd).expect("Deve mudar CWD");
1494
1495 let resultado = carregar_chaves_api();
1496
1497 std::env::set_current_dir(&cwd_original).expect("Deve restaurar CWD");
1498 unsafe {
1499 std::env::remove_var("CONTEXT7_HOME");
1500 }
1501
1502 let chaves = resultado.expect("Deve carregar chaves via fallback .env CWD");
1503 assert_eq!(chaves.len(), 1);
1504 assert_eq!(chaves[0], "ctx7sk-fallback-cwd");
1505 }
1506
1507 #[test]
1510 #[serial_test::serial]
1511 fn testa_cmd_keys_add_cria_config_quando_nao_existe() {
1512 let dir_temp = tempfile::TempDir::new().expect("Deve criar diretório temporário");
1513 unsafe {
1515 std::env::set_var("CONTEXT7_HOME", dir_temp.path());
1516 }
1517
1518 let resultado = cmd_keys_add("ctx7sk-nova-chave-add-test");
1519
1520 unsafe {
1521 std::env::remove_var("CONTEXT7_HOME");
1522 }
1523
1524 resultado.expect("cmd_keys_add deve funcionar em config vazio");
1525
1526 let caminho = dir_temp.path().join("context7").join("config.toml");
1527 assert!(
1528 caminho.exists(),
1529 "config.toml deve existir após cmd_keys_add"
1530 );
1531
1532 let config = ler_config_toml_do_caminho(&caminho).expect("Deve ler config criado");
1533 assert_eq!(config.keys.len(), 1, "Config deve ter 1 chave");
1534 assert_eq!(config.keys[0].value, "ctx7sk-nova-chave-add-test");
1535 }
1536
1537 #[test]
1538 #[serial_test::serial]
1539 fn testa_cmd_keys_add_acumula_em_config_existente() {
1540 let dir_temp = tempfile::TempDir::new().expect("Deve criar diretório temporário");
1541 unsafe {
1543 std::env::set_var("CONTEXT7_HOME", dir_temp.path());
1544 }
1545
1546 cmd_keys_add("ctx7sk-chave-um").expect("Primeira adição deve funcionar");
1547 cmd_keys_add("ctx7sk-chave-dois").expect("Segunda adição deve funcionar");
1548
1549 let caminho = descobrir_caminho_config().expect("Deve ter caminho XDG");
1550 let config = ler_config_toml_do_caminho(&caminho).expect("Deve ler config");
1551
1552 unsafe {
1553 std::env::remove_var("CONTEXT7_HOME");
1554 }
1555
1556 assert_eq!(config.keys.len(), 2, "Deve acumular 2 chaves");
1557 assert_eq!(config.keys[0].value, "ctx7sk-chave-um");
1558 assert_eq!(config.keys[1].value, "ctx7sk-chave-dois");
1559 }
1560
1561 #[test]
1562 #[serial_test::serial]
1563 fn testa_cmd_keys_add_nao_duplica_chave_existente() {
1564 let dir_temp = tempfile::TempDir::new().expect("Deve criar diretório temporário");
1565 unsafe {
1567 std::env::set_var("CONTEXT7_HOME", dir_temp.path());
1568 }
1569
1570 cmd_keys_add("ctx7sk-unica-dedup").expect("Primeira adição deve funcionar");
1571 cmd_keys_add("ctx7sk-unica-dedup").expect("Segunda adição da mesma chave não deve falhar");
1572
1573 let caminho = descobrir_caminho_config().expect("Deve ter caminho XDG");
1574 let config = ler_config_toml_do_caminho(&caminho).expect("Deve ler config");
1575
1576 unsafe {
1577 std::env::remove_var("CONTEXT7_HOME");
1578 }
1579
1580 assert_eq!(config.keys.len(), 1, "Não deve duplicar chave já existente");
1581 }
1582
1583 #[test]
1584 #[cfg(unix)]
1585 #[serial_test::serial]
1586 fn testa_cmd_keys_add_aplica_permissoes_600_em_unix() {
1587 use std::os::unix::fs::PermissionsExt;
1588
1589 let dir_temp = tempfile::TempDir::new().expect("Deve criar diretório temporário");
1590 unsafe {
1592 std::env::set_var("CONTEXT7_HOME", dir_temp.path());
1593 }
1594
1595 cmd_keys_add("ctx7sk-perm-600-keys-add").expect("Deve adicionar chave sem erro");
1596
1597 let caminho = descobrir_caminho_config().expect("Deve ter caminho XDG");
1598 unsafe {
1599 std::env::remove_var("CONTEXT7_HOME");
1600 }
1601
1602 let metadados = std::fs::metadata(&caminho).expect("Deve obter metadados");
1603 let modo = metadados.permissions().mode() & 0o777;
1604 assert_eq!(
1605 modo, 0o600,
1606 "Permissões devem ser 600 após cmd_keys_add, obteve: {:o}",
1607 modo
1608 );
1609 }
1610
1611 #[test]
1614 #[serial_test::serial]
1615 fn testa_cmd_keys_remove_indice_1_de_config_com_3_chaves() {
1616 let dir_temp = tempfile::TempDir::new().expect("Deve criar diretório temporário");
1617 unsafe {
1619 std::env::set_var("CONTEXT7_HOME", dir_temp.path());
1620 }
1621
1622 escrever_config_xdg("ctx7sk-rem-alpha").expect("Deve escrever chave 1");
1623 escrever_config_xdg("ctx7sk-rem-beta").expect("Deve escrever chave 2");
1624 escrever_config_xdg("ctx7sk-rem-gamma").expect("Deve escrever chave 3");
1625
1626 cmd_keys_remove(1).expect("Remove índice 1 deve funcionar");
1627
1628 let caminho = descobrir_caminho_config().expect("Deve ter caminho XDG");
1629 let config = ler_config_toml_do_caminho(&caminho).expect("Deve ler config");
1630
1631 unsafe {
1632 std::env::remove_var("CONTEXT7_HOME");
1633 }
1634
1635 assert_eq!(config.keys.len(), 2, "Devem sobrar 2 chaves após remoção");
1636 assert_eq!(config.keys[0].value, "ctx7sk-rem-beta");
1637 assert_eq!(config.keys[1].value, "ctx7sk-rem-gamma");
1638 }
1639
1640 #[test]
1641 #[serial_test::serial]
1642 fn testa_cmd_keys_remove_indice_2_de_config_com_3_chaves() {
1643 let dir_temp = tempfile::TempDir::new().expect("Deve criar diretório temporário");
1644 unsafe {
1646 std::env::set_var("CONTEXT7_HOME", dir_temp.path());
1647 }
1648
1649 escrever_config_xdg("ctx7sk-mid-alpha").expect("Deve escrever chave 1");
1650 escrever_config_xdg("ctx7sk-mid-beta").expect("Deve escrever chave 2");
1651 escrever_config_xdg("ctx7sk-mid-gamma").expect("Deve escrever chave 3");
1652
1653 cmd_keys_remove(2).expect("Remove índice 2 deve funcionar");
1654
1655 let caminho = descobrir_caminho_config().expect("Deve ter caminho XDG");
1656 let config = ler_config_toml_do_caminho(&caminho).expect("Deve ler config");
1657
1658 unsafe {
1659 std::env::remove_var("CONTEXT7_HOME");
1660 }
1661
1662 assert_eq!(
1663 config.keys.len(),
1664 2,
1665 "Devem sobrar 2 chaves após remoção da do meio"
1666 );
1667 assert_eq!(config.keys[0].value, "ctx7sk-mid-alpha");
1668 assert_eq!(config.keys[1].value, "ctx7sk-mid-gamma");
1669 }
1670
1671 #[test]
1672 #[serial_test::serial]
1673 fn testa_cmd_keys_remove_indice_zero_retorna_err_com_mensagem() {
1674 let dir_temp = tempfile::TempDir::new().expect("Deve criar diretório temporário");
1675 unsafe {
1677 std::env::set_var("CONTEXT7_HOME", dir_temp.path());
1678 }
1679
1680 escrever_config_xdg("ctx7sk-idx-zero-test").expect("Deve escrever chave");
1681
1682 let resultado = cmd_keys_remove(0);
1683
1684 unsafe {
1685 std::env::remove_var("CONTEXT7_HOME");
1686 }
1687
1688 assert!(
1689 resultado.is_err(),
1690 "Índice 0 inválido deve retornar Err (exit code 1), obteve: {:?}",
1691 resultado
1692 );
1693 }
1694
1695 #[test]
1696 #[serial_test::serial]
1697 fn testa_cmd_keys_remove_indice_maior_que_len_retorna_err_com_mensagem() {
1698 let dir_temp = tempfile::TempDir::new().expect("Deve criar diretório temporário");
1699 unsafe {
1701 std::env::set_var("CONTEXT7_HOME", dir_temp.path());
1702 }
1703
1704 escrever_config_xdg("ctx7sk-overflow-test").expect("Deve escrever chave");
1705
1706 let resultado = cmd_keys_remove(99);
1707
1708 unsafe {
1709 std::env::remove_var("CONTEXT7_HOME");
1710 }
1711
1712 assert!(
1713 resultado.is_err(),
1714 "Índice fora do range deve retornar Err (exit code 1), obteve: {:?}",
1715 resultado
1716 );
1717 }
1718
1719 #[test]
1720 #[serial_test::serial]
1721 fn testa_cmd_keys_remove_em_config_vazio_retorna_err() {
1722 let dir_temp = tempfile::TempDir::new().expect("Deve criar diretório temporário");
1723 unsafe {
1725 std::env::set_var("CONTEXT7_HOME", dir_temp.path());
1726 }
1727
1728 let resultado = cmd_keys_remove(1);
1729
1730 unsafe {
1731 std::env::remove_var("CONTEXT7_HOME");
1732 }
1733
1734 assert!(
1735 resultado.is_err(),
1736 "Remover de config vazio deve retornar Err (exit code 1), obteve: {:?}",
1737 resultado
1738 );
1739 }
1740
1741 #[test]
1744 #[serial_test::serial]
1745 fn testa_cmd_keys_clear_com_yes_true_limpa_todas_as_chaves() {
1746 let dir_temp = tempfile::TempDir::new().expect("Deve criar diretório temporário");
1747 unsafe {
1749 std::env::set_var("CONTEXT7_HOME", dir_temp.path());
1750 }
1751
1752 escrever_config_xdg("ctx7sk-clear-alpha").expect("Deve escrever chave 1");
1753 escrever_config_xdg("ctx7sk-clear-beta").expect("Deve escrever chave 2");
1754
1755 let caminho = descobrir_caminho_config().expect("Deve ter caminho XDG");
1756 let antes = ler_config_toml_do_caminho(&caminho).expect("Deve ler config antes");
1757 assert_eq!(antes.keys.len(), 2, "Pré-condição: 2 chaves antes do clear");
1758
1759 cmd_keys_clear(true).expect("clear com yes=true deve funcionar");
1760
1761 let depois = ler_config_toml_do_caminho(&caminho).expect("Deve ler config depois");
1762
1763 unsafe {
1764 std::env::remove_var("CONTEXT7_HOME");
1765 }
1766
1767 assert!(
1768 depois.keys.is_empty(),
1769 "Após clear com yes=true, chaves devem estar vazias"
1770 );
1771 }
1772
1773 #[test]
1774 #[serial_test::serial]
1775 fn testa_cmd_keys_clear_com_yes_true_em_config_inexistente_funciona() {
1776 let dir_temp = tempfile::TempDir::new().expect("Deve criar diretório temporário");
1777 unsafe {
1779 std::env::set_var("CONTEXT7_HOME", dir_temp.path());
1780 }
1781
1782 let resultado = cmd_keys_clear(true);
1783
1784 unsafe {
1785 std::env::remove_var("CONTEXT7_HOME");
1786 }
1787
1788 assert!(
1789 resultado.is_ok(),
1790 "clear em config inexistente deve retornar Ok (idempotente), obteve: {:?}",
1791 resultado
1792 );
1793 }
1794
1795 #[test]
1798 #[serial_test::serial]
1799 fn testa_cmd_keys_import_env_valido_com_multiplas_chaves() {
1800 let dir_temp = tempfile::TempDir::new().expect("Deve criar diretório temporário");
1801 let arquivo_env = dir_temp.path().join("chaves.env");
1802 std::fs::write(
1803 &arquivo_env,
1804 "CONTEXT7_API=ctx7sk-import-alpha\nCONTEXT7_API=ctx7sk-import-beta\n",
1805 )
1806 .expect("Deve escrever .env de teste");
1807
1808 unsafe {
1810 std::env::set_var("CONTEXT7_HOME", dir_temp.path());
1811 }
1812
1813 let resultado = cmd_keys_import(&arquivo_env);
1814
1815 let caminho = descobrir_caminho_config().expect("Deve ter caminho XDG");
1816 let config = ler_config_toml_do_caminho(&caminho).expect("Deve ler config após import");
1817
1818 unsafe {
1819 std::env::remove_var("CONTEXT7_HOME");
1820 }
1821
1822 resultado.expect("import de .env válido deve funcionar");
1823 assert_eq!(config.keys.len(), 2, "Deve ter importado 2 chaves");
1824
1825 let valores: Vec<&str> = config.keys.iter().map(|c| c.value.as_str()).collect();
1826 assert!(valores.contains(&"ctx7sk-import-alpha"));
1827 assert!(valores.contains(&"ctx7sk-import-beta"));
1828 }
1829
1830 #[test]
1831 #[serial_test::serial]
1832 fn testa_cmd_keys_import_env_sem_chaves_retorna_err() {
1833 let dir_temp = tempfile::TempDir::new().expect("Deve criar diretório temporário");
1834 let arquivo_env = dir_temp.path().join("vazio.env");
1835 std::fs::write(&arquivo_env, "# apenas comentario\nOUTRA_VAR=valor\n")
1836 .expect("Deve escrever .env sem chaves");
1837
1838 unsafe {
1840 std::env::set_var("CONTEXT7_HOME", dir_temp.path());
1841 }
1842
1843 let resultado = cmd_keys_import(&arquivo_env);
1844
1845 unsafe {
1846 std::env::remove_var("CONTEXT7_HOME");
1847 }
1848
1849 assert!(
1850 resultado.is_err(),
1851 "Import de .env sem chaves CONTEXT7_API deve retornar Err"
1852 );
1853 }
1854
1855 #[test]
1856 #[serial_test::serial]
1857 fn testa_cmd_keys_import_arquivo_inexistente_retorna_err() {
1858 let dir_temp = tempfile::TempDir::new().expect("Deve criar diretório temporário");
1859 let arquivo_inexistente = dir_temp.path().join("nao_existe.env");
1860
1861 unsafe {
1863 std::env::set_var("CONTEXT7_HOME", dir_temp.path());
1864 }
1865
1866 let resultado = cmd_keys_import(&arquivo_inexistente);
1867
1868 unsafe {
1869 std::env::remove_var("CONTEXT7_HOME");
1870 }
1871
1872 assert!(
1873 resultado.is_err(),
1874 "Import de arquivo inexistente deve retornar Err"
1875 );
1876 }
1877
1878 #[test]
1879 #[serial_test::serial]
1880 fn testa_cmd_keys_import_roundtrip_add_depois_list() {
1881 let dir_temp = tempfile::TempDir::new().expect("Deve criar diretório temporário");
1882 let arquivo_env = dir_temp.path().join("roundtrip.env");
1883 std::fs::write(
1884 &arquivo_env,
1885 "CONTEXT7_API=ctx7sk-rtrip-01\nCONTEXT7_API=ctx7sk-rtrip-02\n",
1886 )
1887 .expect("Deve escrever .env de roundtrip");
1888
1889 unsafe {
1891 std::env::set_var("CONTEXT7_HOME", dir_temp.path());
1892 }
1893
1894 cmd_keys_import(&arquivo_env).expect("Import deve funcionar");
1895
1896 let config = ler_config_xdg_raw()
1897 .expect("Deve retornar Ok")
1898 .expect("Deve retornar Some após import");
1899
1900 unsafe {
1901 std::env::remove_var("CONTEXT7_HOME");
1902 }
1903
1904 assert_eq!(
1905 config.keys.len(),
1906 2,
1907 "Roundtrip: deve ter 2 chaves após import"
1908 );
1909 assert_eq!(config.keys[0].value, "ctx7sk-rtrip-01");
1910 assert_eq!(config.keys[1].value, "ctx7sk-rtrip-02");
1911 }
1912
1913 #[test]
1916 #[serial_test::serial]
1917 fn testa_cmd_keys_export_em_config_vazio_retorna_ok() {
1918 let dir_temp = tempfile::TempDir::new().expect("Deve criar diretório temporário");
1919 unsafe {
1921 std::env::set_var("CONTEXT7_HOME", dir_temp.path());
1922 }
1923
1924 let resultado = cmd_keys_export();
1925
1926 unsafe {
1927 std::env::remove_var("CONTEXT7_HOME");
1928 }
1929
1930 assert!(
1931 resultado.is_ok(),
1932 "Export de config vazio deve retornar Ok, obteve: {:?}",
1933 resultado
1934 );
1935 }
1936
1937 #[test]
1938 #[serial_test::serial]
1939 fn testa_cmd_keys_export_retorna_ok_com_chaves_existentes() {
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 escrever_config_xdg("ctx7sk-export-um").expect("Deve escrever chave 1");
1947 escrever_config_xdg("ctx7sk-export-dois").expect("Deve escrever chave 2");
1948
1949 let resultado = cmd_keys_export();
1950
1951 unsafe {
1952 std::env::remove_var("CONTEXT7_HOME");
1953 }
1954
1955 assert!(
1956 resultado.is_ok(),
1957 "Export com chaves existentes deve retornar Ok, obteve: {:?}",
1958 resultado
1959 );
1960 }
1961
1962 #[test]
1965 #[serial_test::serial]
1966 fn testa_ler_config_xdg_raw_retorna_none_sem_arquivo() {
1967 let dir_temp = tempfile::TempDir::new().expect("Deve criar diretório temporário");
1968 unsafe {
1970 std::env::set_var("CONTEXT7_HOME", dir_temp.path());
1971 }
1972
1973 let resultado = ler_config_xdg_raw();
1974
1975 unsafe {
1976 std::env::remove_var("CONTEXT7_HOME");
1977 }
1978
1979 let valor = resultado.expect("Deve retornar Ok");
1980 assert!(
1981 valor.is_none(),
1982 "Deve retornar None quando config.toml não existe"
1983 );
1984 }
1985
1986 #[test]
1987 #[serial_test::serial]
1988 fn testa_ler_config_xdg_raw_retorna_config_com_chaves() {
1989 let dir_temp = tempfile::TempDir::new().expect("Deve criar diretório temporário");
1990 let dir_context7 = dir_temp.path().join("context7");
1991 std::fs::create_dir_all(&dir_context7).expect("Deve criar diretório context7");
1992
1993 let toml = r#"schema_version = 1
1994
1995[[keys]]
1996value = "ctx7sk-raw-01"
1997added_at = "2026-04-08T00:00:00+00:00"
1998
1999[[keys]]
2000value = "ctx7sk-raw-02"
2001added_at = "2026-04-08T00:01:00+00:00"
2002"#;
2003 std::fs::write(dir_context7.join("config.toml"), toml).expect("Deve escrever config.toml");
2004
2005 unsafe {
2007 std::env::set_var("CONTEXT7_HOME", dir_temp.path());
2008 }
2009
2010 let resultado = ler_config_xdg_raw();
2011
2012 unsafe {
2013 std::env::remove_var("CONTEXT7_HOME");
2014 }
2015
2016 let config = resultado
2017 .expect("Deve retornar Ok")
2018 .expect("Deve retornar Some com config");
2019 assert_eq!(config.keys.len(), 2);
2020 assert_eq!(config.keys[0].value, "ctx7sk-raw-01");
2021 assert_eq!(config.keys[1].value, "ctx7sk-raw-02");
2022 }
2023
2024 #[test]
2025 #[serial_test::serial]
2026 fn testa_cmd_keys_path_retorna_ok() {
2027 let dir_temp = tempfile::TempDir::new().expect("Deve criar diretório temporário");
2028 unsafe {
2030 std::env::set_var("CONTEXT7_HOME", dir_temp.path());
2031 }
2032
2033 let resultado = cmd_keys_path();
2034
2035 unsafe {
2036 std::env::remove_var("CONTEXT7_HOME");
2037 }
2038
2039 resultado.expect("cmd_keys_path deve retornar Ok");
2040 }
2041
2042 #[test]
2043 #[serial_test::serial]
2044 fn testa_descobrir_caminho_config_termina_com_config_toml() {
2045 let dir_temp = tempfile::TempDir::new().expect("Deve criar diretório temporário");
2046 unsafe {
2048 std::env::set_var("CONTEXT7_HOME", dir_temp.path());
2049 }
2050
2051 let caminho = descobrir_caminho_config();
2052
2053 unsafe {
2054 std::env::remove_var("CONTEXT7_HOME");
2055 }
2056
2057 let caminho = caminho.expect("Deve retornar caminho XDG válido");
2058 assert!(
2059 caminho.to_string_lossy().ends_with("config.toml"),
2060 "Caminho deve terminar com config.toml, obteve: {}",
2061 caminho.display()
2062 );
2063 assert!(
2064 caminho.to_string_lossy().contains("context7"),
2065 "Caminho deve conter 'context7', obteve: {}",
2066 caminho.display()
2067 );
2068 }
2069
2070 #[test]
2073 #[serial_test::serial]
2074 fn testa_fluxo_completo_add_list_remove_clear() {
2075 let dir_temp = tempfile::TempDir::new().expect("Deve criar diretório temporário");
2076 unsafe {
2078 std::env::set_var("CONTEXT7_HOME", dir_temp.path());
2079 }
2080
2081 cmd_keys_add("ctx7sk-fluxo-01").expect("Add 1 deve funcionar");
2082 cmd_keys_add("ctx7sk-fluxo-02").expect("Add 2 deve funcionar");
2083 cmd_keys_add("ctx7sk-fluxo-03").expect("Add 3 deve funcionar");
2084
2085 let config_antes = ler_config_xdg_raw()
2086 .expect("Ok")
2087 .expect("Some com 3 chaves");
2088 assert_eq!(config_antes.keys.len(), 3, "Deve ter 3 chaves após 3 adds");
2089
2090 cmd_keys_remove(2).expect("Remove índice 2 deve funcionar");
2091
2092 let config_pos_remove = ler_config_xdg_raw()
2093 .expect("Ok")
2094 .expect("Some com 2 chaves");
2095 assert_eq!(
2096 config_pos_remove.keys.len(),
2097 2,
2098 "Deve ter 2 chaves após remove"
2099 );
2100 assert_eq!(config_pos_remove.keys[0].value, "ctx7sk-fluxo-01");
2101 assert_eq!(config_pos_remove.keys[1].value, "ctx7sk-fluxo-03");
2102
2103 cmd_keys_clear(true).expect("Clear com yes=true deve funcionar");
2104
2105 let caminho = descobrir_caminho_config().expect("Deve ter caminho");
2106 let config_final = ler_config_toml_do_caminho(&caminho).expect("Deve ler config final");
2107
2108 unsafe {
2109 std::env::remove_var("CONTEXT7_HOME");
2110 }
2111
2112 assert!(
2113 config_final.keys.is_empty(),
2114 "Após clear, chaves devem estar vazias"
2115 );
2116 }
2117
2118 #[test]
2121 #[serial_test::serial]
2122 fn testa_context7_home_override_config_path() {
2123 let tmp = tempfile::TempDir::new().expect("Deve criar tempdir");
2124 unsafe {
2127 std::env::set_var("CONTEXT7_HOME", tmp.path());
2128 }
2129
2130 let caminho = descobrir_caminho_config();
2131
2132 unsafe {
2133 std::env::remove_var("CONTEXT7_HOME");
2134 }
2135
2136 let caminho = caminho.expect("Deve retornar Some quando CONTEXT7_HOME está definido");
2137 let esperado = tmp.path().join("context7").join("config.toml");
2138 assert_eq!(
2139 caminho, esperado,
2140 "CONTEXT7_HOME deve definir caminho como {{CONTEXT7_HOME}}/context7/config.toml"
2141 );
2142 }
2143
2144 #[test]
2145 #[serial_test::serial]
2146 fn testa_context7_home_override_logs_path() {
2147 let tmp = tempfile::TempDir::new().expect("Deve criar tempdir");
2148 unsafe {
2150 std::env::set_var("CONTEXT7_HOME", tmp.path());
2151 }
2152
2153 let caminho = descobrir_caminho_logs_xdg();
2154
2155 unsafe {
2156 std::env::remove_var("CONTEXT7_HOME");
2157 }
2158
2159 let caminho = caminho.expect("Deve retornar Some quando CONTEXT7_HOME está definido");
2160 let esperado = tmp.path().join("context7").join("logs");
2161 assert_eq!(
2162 caminho, esperado,
2163 "CONTEXT7_HOME deve definir logs como {{CONTEXT7_HOME}}/context7/logs"
2164 );
2165 }
2166
2167 #[test]
2168 #[serial_test::serial]
2169 fn testa_context7_home_vazio_cai_em_projectdirs() {
2170 let tmp = tempfile::TempDir::new().expect("Deve criar tempdir");
2171 unsafe {
2173 std::env::set_var("CONTEXT7_HOME", "");
2174 }
2175
2176 let caminho = descobrir_caminho_config();
2177
2178 unsafe {
2179 std::env::remove_var("CONTEXT7_HOME");
2180 }
2181
2182 if let Some(c) = caminho {
2184 let tmp_str = tmp.path().to_string_lossy();
2185 assert!(
2186 !c.to_string_lossy().starts_with(tmp_str.as_ref()),
2187 "CONTEXT7_HOME vazio não deve usar o tempdir: {}",
2188 c.display()
2189 );
2190 }
2191 }
2193}