1use anyhow::{Context, Result, anyhow};
7use clap::{Args, ValueEnum};
8use dialoguer::{Confirm, Input, Select};
9use std::io::IsTerminal;
10use std::path::Path;
11use std::sync::Arc;
12
13use auths_core::ports::clock::SystemClock;
14use auths_core::signing::{PassphraseProvider, StorageSigner};
15use auths_core::storage::keychain::{KeyAlias, KeyStorage, get_platform_keychain};
16use auths_id::attestation::export::AttestationSink;
17use auths_id::ports::registry::RegistryBackend;
18use auths_id::storage::attestation::AttestationSource;
19use auths_id::storage::identity::IdentityStorage;
20use auths_infra_http::HttpRegistryClient;
21use auths_sdk::context::AuthsContext;
22use auths_sdk::ports::git_config::GitConfigProvider;
23use auths_sdk::registration::DEFAULT_REGISTRY_URL;
24use auths_sdk::types::{
25 CiEnvironment, CiSetupConfig, DeveloperSetupConfig, GitSigningScope, IdentityConflictPolicy,
26};
27use auths_storage::git::{
28 GitRegistryBackend, RegistryAttestationStorage, RegistryConfig, RegistryIdentityStorage,
29};
30
31use crate::adapters::git_config::SystemGitConfigProvider;
32
33use super::init_helpers::{
34 check_git_version, detect_ci_environment, generate_allowed_signers, get_auths_repo_path,
35 offer_shell_completions, select_agent_capabilities, short_did,
36};
37use crate::config::CliConfig;
38use crate::ux::format::Output;
39
40const DEFAULT_KEY_ALIAS: &str = "main";
41
42#[derive(Debug, Clone, Copy, ValueEnum)]
44pub enum InitProfile {
45 Developer,
47 Ci,
49 Agent,
51}
52
53impl std::fmt::Display for InitProfile {
54 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
55 match self {
56 InitProfile::Developer => write!(f, "developer"),
57 InitProfile::Ci => write!(f, "ci"),
58 InitProfile::Agent => write!(f, "agent"),
59 }
60 }
61}
62
63#[derive(Args, Debug, Clone)]
76#[command(
77 name = "init",
78 about = "Set up your cryptographic identity and Git signing"
79)]
80pub struct InitCommand {
81 #[clap(long)]
83 pub non_interactive: bool,
84
85 #[clap(long, value_enum)]
87 pub profile: Option<InitProfile>,
88
89 #[clap(long, default_value = DEFAULT_KEY_ALIAS)]
91 pub key_alias: String,
92
93 #[clap(long)]
95 pub force: bool,
96
97 #[clap(long)]
99 pub dry_run: bool,
100
101 #[clap(long, default_value = DEFAULT_REGISTRY_URL)]
103 pub registry: String,
104
105 #[clap(long)]
107 pub skip_registration: bool,
108}
109
110pub fn handle_init(cmd: InitCommand, ctx: &CliConfig) -> Result<()> {
123 let out = Output::new();
124 let interactive = !cmd.non_interactive && std::io::stdin().is_terminal();
125
126 let profile = match cmd.profile {
127 Some(p) => p,
128 None if !interactive => {
129 out.println("No profile specified in non-interactive mode, defaulting to developer.");
130 InitProfile::Developer
131 }
132 None => prompt_profile(&out)?,
133 };
134
135 out.print_heading(&format!("Auths Setup ({})", profile));
136 out.println("=".repeat(40).as_str());
137 out.newline();
138
139 match profile {
140 InitProfile::Developer => {
141 let (keychain, mut config) = gather_developer_config(interactive, &out, &cmd)?;
143 let registry_path = get_auths_repo_path()?;
144
145 ensure_registry_dir(®istry_path)?;
147
148 let sign_binary_path = which::which("auths-sign").ok();
150 if let Some(ref path) = sign_binary_path {
151 config.sign_binary_path = Some(path.clone());
152 }
153 let git_config_provider: Option<Box<dyn GitConfigProvider>> =
154 match &config.git_signing_scope {
155 GitSigningScope::Skip => None,
156 GitSigningScope::Global => Some(Box::new(SystemGitConfigProvider::global())),
157 GitSigningScope::Local { repo_path } => {
158 Some(Box::new(SystemGitConfigProvider::local(repo_path.clone())))
159 }
160 };
161
162 let sdk_ctx = build_sdk_context(®istry_path)?;
164
165 let signer = StorageSigner::new(keychain);
167 let result = auths_sdk::setup::setup_developer(
168 config,
169 &sdk_ctx,
170 signer.inner(),
171 &signer,
172 ctx.passphrase_provider.as_ref(),
173 git_config_provider.as_deref(),
174 )?;
175
176 out.print_success(&format!(
177 "Identity ready: {}",
178 short_did(&result.identity_did)
179 ));
180 out.print_success(&format!(
181 "Device linked: {}",
182 short_did(result.device_did.as_str())
183 ));
184 out.newline();
185
186 let proof_url = if interactive && !cmd.skip_registration {
188 out.print_info("Claim your Developer Passport");
189 out.newline();
190 match prompt_platform_verification(
191 &out,
192 &result.identity_did,
193 &result.key_alias,
194 ctx.passphrase_provider.as_ref(),
195 &ctx.http_client,
196 )? {
197 Some((url, _username)) => {
198 out.print_success(&format!("Proof anchored: {}", url));
199 Some(url)
200 }
201 None => {
202 out.println(" Continuing as anonymous identity");
203 None
204 }
205 }
206 } else {
207 None
208 };
209 out.newline();
210
211 offer_shell_completions(interactive, &out)?;
212 generate_allowed_signers(&result.key_alias, &out)?;
213
214 let registered = submit_registration(
216 &get_auths_repo_path()?,
217 &cmd.registry,
218 proof_url,
219 cmd.skip_registration,
220 &out,
221 );
222
223 display_developer_result(&out, &result, registered.as_deref());
225 }
226 InitProfile::Ci => {
227 let (ci_env, config) = gather_ci_config(&out)?;
229 let registry_path = config.registry_path.clone();
230
231 ensure_registry_dir(®istry_path)?;
233
234 let sdk_ctx = build_sdk_context(®istry_path)?;
236
237 let result = auths_sdk::setup::setup_ci(config, &sdk_ctx)?;
239
240 display_ci_result(&out, &result, ci_env.as_deref());
242 }
243 InitProfile::Agent => {
244 let (keychain, config) = gather_agent_config(interactive, &out, &cmd)?;
246 let registry_path = config.registry_path.clone();
247
248 if config.dry_run {
249 display_agent_dry_run(&out, &config);
250 } else {
251 ensure_registry_dir(®istry_path)?;
253
254 let sdk_ctx = build_sdk_context(®istry_path)?;
256
257 let result = auths_sdk::setup::setup_agent(
259 config,
260 &sdk_ctx,
261 keychain,
262 ctx.passphrase_provider.as_ref(),
263 )?;
264
265 display_agent_result(&out, &result);
267 }
268 }
269 }
270
271 Ok(())
272}
273
274fn gather_developer_config(
277 interactive: bool,
278 out: &Output,
279 cmd: &InitCommand,
280) -> Result<(Box<dyn KeyStorage + Send + Sync>, DeveloperSetupConfig)> {
281 out.print_info("Checking prerequisites...");
282 let keychain = check_keychain_access(out)?;
283 check_git_version(out)?;
284 out.print_success("Prerequisites OK");
285 out.newline();
286
287 let registry_path = get_auths_repo_path()?;
288 let alias = prompt_for_alias(interactive, cmd)?;
289 let conflict_policy = prompt_for_conflict_policy(interactive, cmd, ®istry_path, out)?;
290 let git_scope = prompt_for_git_scope(interactive)?;
291
292 let mut builder = DeveloperSetupConfig::builder(KeyAlias::new_unchecked(&alias))
293 .with_conflict_policy(conflict_policy)
294 .with_git_signing_scope(git_scope);
295
296 if !cmd.skip_registration {
297 builder = builder.with_registration(&cmd.registry);
298 }
299
300 Ok((keychain, builder.build()))
301}
302
303fn gather_ci_config(out: &Output) -> Result<(Option<String>, CiSetupConfig)> {
304 out.print_info("Detecting CI environment...");
305 let ci_env = detect_ci_environment();
306 if let Some(ref vendor) = ci_env {
307 out.print_success(&format!("Detected: {}", vendor));
308 } else {
309 out.print_warn("No CI environment detected, proceeding anyway");
310 }
311 out.newline();
312
313 let registry_path = std::env::current_dir()?.join(".auths-ci");
314 let passphrase =
315 std::env::var("AUTHS_PASSPHRASE").unwrap_or_else(|_| "Ci-ephemeral-pass1!".to_string());
316
317 unsafe {
319 std::env::set_var("AUTHS_KEYCHAIN_BACKEND", "memory");
320 }
321 let keychain =
322 get_platform_keychain().map_err(|e| anyhow!("Failed to get memory keychain: {}", e))?;
323
324 out.println(&format!(" Using keychain: {}", keychain.backend_name()));
325
326 let config = CiSetupConfig {
327 ci_environment: map_ci_environment(&ci_env),
328 passphrase,
329 registry_path,
330 keychain,
331 };
332
333 Ok((ci_env, config))
334}
335
336fn gather_agent_config(
337 interactive: bool,
338 out: &Output,
339 cmd: &InitCommand,
340) -> Result<(
341 Box<dyn KeyStorage + Send + Sync>,
342 auths_sdk::types::AgentSetupConfig,
343)> {
344 out.print_info("Setting capability scope...");
345 let capabilities = select_agent_capabilities(interactive, out)?;
346 let cap_names: Vec<String> = capabilities.iter().map(|c| c.name.clone()).collect();
347 out.print_success(&format!("Capabilities: {}", cap_names.join(", ")));
348 out.newline();
349
350 let parsed_caps: Vec<auths_verifier::Capability> = cap_names
351 .into_iter()
352 .filter_map(|s| auths_verifier::Capability::parse(&s).ok())
353 .collect();
354
355 let keychain = check_keychain_access(out)?;
356 let registry_path = get_auths_repo_path()?;
357
358 let config = auths_sdk::types::AgentSetupConfig::builder(
359 KeyAlias::new_unchecked("agent"),
360 ®istry_path,
361 )
362 .with_capabilities(parsed_caps)
363 .with_expiry(365 * 24 * 3600)
364 .dry_run(cmd.dry_run)
365 .build();
366
367 Ok((keychain, config))
368}
369
370fn prompt_profile(out: &Output) -> Result<InitProfile> {
373 out.print_heading("Select Setup Profile");
374 out.newline();
375
376 let items = [
377 "Developer - Full local setup with keychain and git signing",
378 "CI - Ephemeral identity for CI/CD pipelines",
379 "Agent - Scoped identity for AI agents",
380 ];
381
382 let selection = Select::new()
383 .with_prompt("Choose your setup profile")
384 .items(items)
385 .default(0)
386 .interact()?;
387
388 Ok(match selection {
389 0 => InitProfile::Developer,
390 1 => InitProfile::Ci,
391 _ => InitProfile::Agent,
392 })
393}
394
395fn prompt_for_alias(interactive: bool, cmd: &InitCommand) -> Result<String> {
396 if interactive {
397 Ok(Input::new()
398 .with_prompt("Key alias")
399 .default(cmd.key_alias.clone())
400 .interact_text()?)
401 } else {
402 Ok(cmd.key_alias.clone())
403 }
404}
405
406fn prompt_for_conflict_policy(
407 interactive: bool,
408 cmd: &InitCommand,
409 registry_path: &Path,
410 out: &Output,
411) -> Result<IdentityConflictPolicy> {
412 if cmd.force {
413 return Ok(IdentityConflictPolicy::ForceNew);
414 }
415
416 let identity_storage = RegistryIdentityStorage::new(registry_path.to_path_buf());
417 if let Ok(existing) = identity_storage.load_identity() {
418 out.println(&format!(
419 " Found existing identity: {}",
420 out.info(&short_did(existing.controller_did.as_str()))
421 ));
422
423 if !interactive {
424 return Ok(IdentityConflictPolicy::ReuseExisting);
425 }
426
427 let use_existing = Confirm::new()
428 .with_prompt("Use existing identity?")
429 .default(true)
430 .interact()?;
431 if use_existing {
432 return Ok(IdentityConflictPolicy::ReuseExisting);
433 }
434
435 let overwrite = Confirm::new()
436 .with_prompt("Create new identity? This will NOT delete the old one.")
437 .default(false)
438 .interact()?;
439 if !overwrite {
440 return Err(anyhow!("Setup cancelled by user"));
441 }
442 }
443
444 Ok(IdentityConflictPolicy::ForceNew)
445}
446
447fn prompt_for_git_scope(interactive: bool) -> Result<GitSigningScope> {
448 if !interactive {
449 return Ok(GitSigningScope::Global);
450 }
451
452 let choice = Select::new()
453 .with_prompt("Configure git signing for")
454 .items([
455 "This repository only (--local)",
456 "All repositories (--global)",
457 ])
458 .default(1)
459 .interact()?;
460
461 if choice == 0 {
462 let repo_path = std::env::current_dir()?;
463 Ok(GitSigningScope::Local { repo_path })
464 } else {
465 Ok(GitSigningScope::Global)
466 }
467}
468
469fn prompt_platform_verification(
470 out: &Output,
471 controller_did: &str,
472 key_alias: &str,
473 passphrase_provider: &dyn PassphraseProvider,
474 http_client: &reqwest::Client,
475) -> Result<Option<(String, String)>> {
476 let items = [
477 "GitHub — link your GitHub identity (recommended)",
478 "GitLab — coming soon",
479 "Anonymous — skip platform verification",
480 ];
481
482 let selection = Select::new()
483 .with_prompt("Claim your Developer Passport")
484 .items(items)
485 .default(0)
486 .interact()?;
487
488 match selection {
489 0 => {
490 use crate::services::providers::github::GitHubProvider;
491 use crate::services::providers::{ClaimContext, PlatformClaimProvider};
492
493 let provider = GitHubProvider;
494 let ctx = ClaimContext {
495 out,
496 controller_did,
497 key_alias,
498 passphrase_provider,
499 http_client,
500 };
501 let auth = provider.authenticate_and_publish(&ctx)?;
502 Ok(Some((auth.proof_url, auth.username)))
503 }
504 1 => {
505 out.print_warn("GitLab integration is coming soon. Continuing as anonymous.");
506 Ok(None)
507 }
508 _ => Ok(None),
509 }
510}
511
512fn display_developer_result(
515 out: &Output,
516 result: &auths_sdk::result::SetupResult,
517 registered: Option<&str>,
518) {
519 out.newline();
520 out.print_heading("You are on the Web of Trust!");
521 out.newline();
522 out.println(&format!(
523 " Identity: {}",
524 out.info(&short_did(&result.identity_did))
525 ));
526 out.println(&format!(" Key alias: {}", out.info(&result.key_alias)));
527 if let Some(registry) = registered {
528 out.println(&format!(" Registry: {}", out.info(registry)));
529 }
530 let did_prefix = result
531 .identity_did
532 .strip_prefix("did:keri:")
533 .unwrap_or(&result.identity_did);
534 out.println(&format!(
535 " Profile: {}",
536 out.info(&format!("https://auths.dev/registry/identity/{did_prefix}"))
537 ));
538 out.newline();
539 out.print_success("Your next commit will be signed with Auths!");
540 out.println(" Run `auths status` to check your identity");
541}
542
543fn display_ci_result(
544 out: &Output,
545 result: &auths_sdk::result::CiSetupResult,
546 ci_vendor: Option<&str>,
547) {
548 out.print_success(&format!("CI identity: {}", short_did(&result.identity_did)));
549 out.newline();
550
551 out.print_heading("Add these to your CI secrets:");
552 out.println("─".repeat(50).as_str());
553 for line in &result.env_block {
554 println!("{}", line);
555 }
556 out.println("─".repeat(50).as_str());
557 out.newline();
558
559 if let Some(vendor) = ci_vendor {
560 write_ci_vendor_hints(out, vendor);
561 }
562
563 out.print_success("CI setup complete!");
564 out.println(" Add the environment variables to your CI secrets");
565 out.println(" Commits made in CI will be signed with the ephemeral identity");
566}
567
568fn display_agent_result(out: &Output, result: &auths_sdk::result::AgentSetupResult) {
569 out.print_heading("Agent Setup Complete!");
570 out.newline();
571 out.println(&format!(
572 " Identity: {}",
573 out.info(&short_did(&result.agent_did))
574 ));
575 let cap_display: Vec<String> = result.capabilities.iter().map(|c| c.to_string()).collect();
576 out.println(&format!(" Capabilities: {}", cap_display.join(", ")));
577 out.newline();
578 out.print_success("Agent is ready to sign commits!");
579 out.println(" Start the agent: auths agent start");
580 out.println(" Check status: auths agent status");
581}
582
583fn display_agent_dry_run(out: &Output, config: &auths_sdk::types::AgentSetupConfig) {
584 out.print_heading("Dry Run — no files or identities will be created");
585 out.newline();
586 out.println(&format!(" Storage: {}", config.registry_path.display()));
587 out.println(&format!(" Capabilities: {:?}", config.capabilities));
588 if let Some(secs) = config.expires_in_secs {
589 out.println(&format!(" Expires in: {}s", secs));
590 }
591 out.newline();
592 out.print_info("TOML config that would be generated:");
593 let provisioning_config = auths_id::agent_identity::AgentProvisioningConfig {
594 agent_name: config.alias.to_string(),
595 capabilities: config.capabilities.iter().map(|c| c.to_string()).collect(),
596 expires_in_secs: config.expires_in_secs,
597 delegated_by: None,
598 storage_mode: auths_id::agent_identity::AgentStorageMode::Persistent { repo_path: None },
599 };
600 out.println(&auths_id::agent_identity::format_agent_toml(
601 "did:keri:E<pending>",
602 "agent-key",
603 &provisioning_config,
604 ));
605}
606
607fn submit_registration(
610 repo_path: &Path,
611 registry_url: &str,
612 proof_url: Option<String>,
613 skip: bool,
614 out: &Output,
615) -> Option<String> {
616 if skip {
617 out.print_info("Registration skipped (--skip-registration)");
618 return None;
619 }
620
621 out.print_info("Publishing identity to Auths Registry...");
622 let rt = match tokio::runtime::Runtime::new() {
623 Ok(rt) => rt,
624 Err(e) => {
625 out.print_warn(&format!("Could not create async runtime: {e}"));
626 return None;
627 }
628 };
629
630 let backend = Arc::new(GitRegistryBackend::from_config_unchecked(
631 RegistryConfig::single_tenant(repo_path),
632 ));
633 let identity_storage: Arc<dyn IdentityStorage + Send + Sync> =
634 Arc::new(RegistryIdentityStorage::new(repo_path.to_path_buf()));
635 let attestation_store = Arc::new(RegistryAttestationStorage::new(repo_path));
636 let attestation_source: Arc<dyn AttestationSource + Send + Sync> = attestation_store;
637
638 let registry_client = HttpRegistryClient::new();
639
640 match rt.block_on(auths_sdk::registration::register_identity(
641 identity_storage,
642 backend,
643 attestation_source,
644 registry_url,
645 proof_url,
646 ®istry_client,
647 )) {
648 Ok(outcome) => {
649 out.print_success(&format!("Identity registered at {}", outcome.registry));
650 Some(outcome.registry)
651 }
652 Err(auths_sdk::error::RegistrationError::AlreadyRegistered) => {
653 out.print_success("Already registered on this registry");
654 Some(registry_url.to_string())
655 }
656 Err(auths_sdk::error::RegistrationError::QuotaExceeded) => {
657 out.print_warn("Registration quota exceeded. Run `auths id register` to retry later.");
658 None
659 }
660 Err(auths_sdk::error::RegistrationError::NetworkError(_)) => {
661 out.print_warn(
662 "Could not reach the registry (offline?). Your local setup is complete.",
663 );
664 out.println(" Run `auths id register` when you're back online.");
665 None
666 }
667 Err(auths_sdk::error::RegistrationError::LocalDataError(e)) => {
668 out.print_warn(&format!("Could not prepare registration payload: {e}"));
669 out.println(" Run `auths id register` to retry.");
670 None
671 }
672 Err(e) => {
673 out.print_warn(&format!("Registration failed: {e}"));
674 None
675 }
676 }
677}
678
679fn ensure_registry_dir(registry_path: &Path) -> Result<()> {
680 if !registry_path.exists() {
681 std::fs::create_dir_all(registry_path).with_context(|| {
682 format!(
683 "Failed to create registry directory: {}",
684 registry_path.display()
685 )
686 })?;
687 }
688 if git2::Repository::open(registry_path).is_err() {
689 git2::Repository::init(registry_path).with_context(|| {
690 format!(
691 "Failed to initialize git repository: {}",
692 registry_path.display()
693 )
694 })?;
695 }
696 auths_sdk::setup::install_registry_hook(registry_path);
697 Ok(())
698}
699
700fn build_sdk_context(registry_path: &Path) -> Result<AuthsContext> {
701 let backend: Arc<dyn RegistryBackend + Send + Sync> = Arc::new(
702 GitRegistryBackend::from_config_unchecked(RegistryConfig::single_tenant(registry_path)),
703 );
704 let identity_storage: Arc<dyn IdentityStorage + Send + Sync> =
705 Arc::new(RegistryIdentityStorage::new(registry_path.to_path_buf()));
706 let attestation_store = Arc::new(RegistryAttestationStorage::new(registry_path));
707 let attestation_sink: Arc<dyn AttestationSink + Send + Sync> =
708 Arc::clone(&attestation_store) as Arc<dyn AttestationSink + Send + Sync>;
709 let attestation_source: Arc<dyn AttestationSource + Send + Sync> =
710 attestation_store as Arc<dyn AttestationSource + Send + Sync>;
711 let key_storage: Arc<dyn KeyStorage + Send + Sync> = Arc::from(
712 get_platform_keychain().map_err(|e| anyhow!("Failed to access keychain: {}", e))?,
713 );
714 Ok(AuthsContext::builder()
715 .registry(backend)
716 .key_storage(key_storage)
717 .clock(Arc::new(SystemClock))
718 .identity_storage(identity_storage)
719 .attestation_sink(attestation_sink)
720 .attestation_source(attestation_source)
721 .build()?)
722}
723
724fn check_keychain_access(out: &Output) -> Result<Box<dyn KeyStorage + Send + Sync>> {
725 match get_platform_keychain() {
726 Ok(keychain) => {
727 out.println(&format!(
728 " Keychain: {} (accessible)",
729 out.success(keychain.backend_name())
730 ));
731 Ok(keychain)
732 }
733 Err(e) => Err(anyhow!("Keychain not accessible: {}", e)),
734 }
735}
736
737fn map_ci_environment(detected: &Option<String>) -> CiEnvironment {
738 match detected.as_deref() {
739 Some("GitHub Actions") => CiEnvironment::GitHubActions,
740 Some("GitLab CI") => CiEnvironment::GitLabCi,
741 Some(name) => CiEnvironment::Custom {
742 name: name.to_string(),
743 },
744 None => CiEnvironment::Unknown,
745 }
746}
747
748fn write_ci_vendor_hints(out: &Output, vendor: &str) {
749 out.newline();
750 out.print_heading(&format!("Hints for {}", vendor));
751
752 match vendor {
753 "GitHub Actions" => {
754 out.println("Add to your workflow (.github/workflows/*.yml):");
755 out.newline();
756 out.println(" env:");
757 out.println(" AUTHS_KEYCHAIN_BACKEND: memory");
758 out.newline();
759 out.println(" steps:");
760 out.println(" - uses: actions/checkout@v4");
761 out.println(" - run: auths init --profile ci --non-interactive");
762 }
763 "GitLab CI" => {
764 out.println("Add to .gitlab-ci.yml:");
765 out.newline();
766 out.println(" variables:");
767 out.println(" AUTHS_KEYCHAIN_BACKEND: memory");
768 out.newline();
769 out.println(" before_script:");
770 out.println(" - auths init --profile ci --non-interactive");
771 }
772 _ => {
773 out.println("Set these environment variables in your CI:");
774 out.println(" AUTHS_KEYCHAIN_BACKEND=memory");
775 }
776 }
777 out.newline();
778}
779
780impl crate::commands::executable::ExecutableCommand for InitCommand {
783 fn execute(&self, ctx: &CliConfig) -> anyhow::Result<()> {
784 handle_init(self.clone(), ctx)
785 }
786}
787
788#[cfg(test)]
789mod tests {
790 use super::*;
791
792 #[test]
793 fn test_setup_profile_display() {
794 assert_eq!(InitProfile::Developer.to_string(), "developer");
795 assert_eq!(InitProfile::Ci.to_string(), "ci");
796 assert_eq!(InitProfile::Agent.to_string(), "agent");
797 }
798
799 #[test]
800 fn test_setup_command_defaults() {
801 let cmd = InitCommand {
802 non_interactive: false,
803 profile: None,
804 key_alias: DEFAULT_KEY_ALIAS.to_string(),
805 force: false,
806 dry_run: false,
807 registry: DEFAULT_REGISTRY_URL.to_string(),
808 skip_registration: false,
809 };
810 assert!(!cmd.non_interactive);
811 assert!(cmd.profile.is_none());
812 assert_eq!(cmd.key_alias, "main");
813 assert!(!cmd.force);
814 assert!(!cmd.dry_run);
815 assert_eq!(cmd.registry, "https://auths-registry.fly.dev");
816 assert!(!cmd.skip_registration);
817 }
818
819 #[test]
820 fn test_setup_command_with_profile() {
821 let cmd = InitCommand {
822 non_interactive: true,
823 profile: Some(InitProfile::Ci),
824 key_alias: "ci-key".to_string(),
825 force: true,
826 dry_run: false,
827 registry: DEFAULT_REGISTRY_URL.to_string(),
828 skip_registration: false,
829 };
830 assert!(cmd.non_interactive);
831 assert!(matches!(cmd.profile, Some(InitProfile::Ci)));
832 assert_eq!(cmd.key_alias, "ci-key");
833 assert!(cmd.force);
834 }
835
836 #[test]
837 fn test_map_ci_environment() {
838 assert!(matches!(
839 map_ci_environment(&Some("GitHub Actions".into())),
840 CiEnvironment::GitHubActions
841 ));
842 assert!(matches!(
843 map_ci_environment(&Some("GitLab CI".into())),
844 CiEnvironment::GitLabCi
845 ));
846 assert!(matches!(map_ci_environment(&None), CiEnvironment::Unknown));
847 }
848}