use crate::cli_runner::{
run_cs_with_env,
stdout, stderr, assert_exit,
write_credentials, write_claude_json, write_claude_json_full, write_settings_json,
write_claude_json_extended, write_account, write_account_roles_json,
FAR_FUTURE_MS,
};
use tempfile::TempDir;
#[ test ]
fn cred01_no_credential_store_succeeds()
{
let dir = TempDir::new().unwrap();
let home = dir.path().to_str().unwrap();
write_credentials( dir.path(), "pro", "standard", FAR_FUTURE_MS );
let out = run_cs_with_env( &[ ".credentials.status" ], &[ ( "HOME", home ) ] );
assert_exit( &out, 0 );
let text = stdout( &out );
assert!(
text.contains( "pro" ),
"output must contain subscription type, got:\n{text}",
);
assert!(
text.to_lowercase().contains( "valid" ) || text.to_lowercase().contains( "expir" ),
"output must contain token state, got:\n{text}",
);
assert!( text.contains( "Account:" ), "Account: line must appear, got:\n{text}" );
assert!( text.contains( "N/A" ), "Account: N/A must appear (no _active marker), got:\n{text}" );
}
#[ test ]
fn cred02_default_with_claude_json()
{
let dir = TempDir::new().unwrap();
let home = dir.path().to_str().unwrap();
write_credentials( dir.path(), "pro", "standard", FAR_FUTURE_MS );
write_claude_json( dir.path(), "user@example.com" );
let out = run_cs_with_env( &[ ".credentials.status" ], &[ ( "HOME", home ) ] );
assert_exit( &out, 0 );
let text = stdout( &out );
assert!( text.contains( "Account:" ), "Account: line must appear, got:\n{text}" );
assert!( text.contains( "pro" ), "sub must appear, got:\n{text}" );
assert!( text.contains( "standard" ), "tier must appear, got:\n{text}" );
assert!( text.contains( "user@example.com" ), "email must appear, got:\n{text}" );
assert!(
text.contains( "Expires" ) || text.contains( "expires" ),
"Expires: line must appear in default output, got:\n{text}",
);
}
#[ test ]
fn cred03_format_json()
{
let dir = TempDir::new().unwrap();
let home = dir.path().to_str().unwrap();
write_credentials( dir.path(), "pro", "standard", FAR_FUTURE_MS );
write_claude_json( dir.path(), "user@example.com" );
let out = run_cs_with_env( &[ ".credentials.status", "format::json" ], &[ ( "HOME", home ) ] );
assert_exit( &out, 0 );
let text = stdout( &out ).trim().to_string();
assert!( text.starts_with( '{' ) && text.ends_with( '}' ), "output must be JSON object, got:\n{text}" );
assert!( text.contains( "\"subscription\"" ), "JSON must have subscription field, got:\n{text}" );
assert!( text.contains( "\"tier\"" ), "JSON must have tier field, got:\n{text}" );
assert!( text.contains( "\"token\"" ), "JSON must have token field, got:\n{text}" );
assert!( text.contains( "\"expires_in_secs\"" ), "JSON must have expires_in_secs field, got:\n{text}" );
assert!( text.contains( "\"email\"" ), "JSON must have email field, got:\n{text}" );
assert!( text.contains( "\"account\"" ), "JSON must have account field, got:\n{text}" );
assert!( text.contains( "\"file\"" ), "JSON must have file field, got:\n{text}" );
assert!( text.contains( "\"saved\"" ), "JSON must have saved field, got:\n{text}" );
}
#[ test ]
fn cred04_missing_credentials_file_exits_nonzero()
{
let dir = TempDir::new().unwrap();
let home = dir.path().to_str().unwrap();
std::fs::create_dir_all( dir.path().join( ".claude" ) ).unwrap();
let out = run_cs_with_env( &[ ".credentials.status" ], &[ ( "HOME", home ) ] );
let code = out.status.code().unwrap_or( -1 );
assert!( code != 0, "must exit non-zero when .credentials.json absent, got code {code}" );
let err = stderr( &out );
assert!(
err.to_lowercase().contains( "credential" ),
"error must reference the credential file, got:\n{err}",
);
}
#[ test ]
fn cred05_no_claude_json_shows_na()
{
let dir = TempDir::new().unwrap();
let home = dir.path().to_str().unwrap();
write_credentials( dir.path(), "pro", "standard", FAR_FUTURE_MS );
let out = run_cs_with_env( &[ ".credentials.status" ], &[ ( "HOME", home ) ] );
assert_exit( &out, 0 );
let text = stdout( &out );
let na_count = text.matches( "N/A" ).count();
assert!(
na_count >= 2,
"default output without .claude.json and no _active must show N/A for account, email \
(found {na_count} N/A), got:\n{text}",
);
}
#[ test ]
fn cred06_suppress_all_default_on()
{
let dir = TempDir::new().unwrap();
let home = dir.path().to_str().unwrap();
write_credentials( dir.path(), "pro", "standard", FAR_FUTURE_MS );
let out = run_cs_with_env(
&[ ".credentials.status", "account::0", "sub::0", "tier::0", "expires::0", "email::0" ],
&[ ( "HOME", home ) ],
);
assert_exit( &out, 0 );
let text = stdout( &out );
assert!(
text.to_lowercase().contains( "valid" ) || text.to_lowercase().contains( "expir" ),
"token state must be present, got:\n{text}",
);
assert!( !text.contains( "Sub:" ), "Sub: must be suppressed, got:\n{text}" );
assert!( !text.contains( "Tier:" ), "Tier: must be suppressed, got:\n{text}" );
assert!( !text.contains( "Expires:" ), "Expires: must be suppressed, got:\n{text}" );
assert!( !text.contains( "Email:" ), "Email: must be suppressed, got:\n{text}" );
assert!( !text.contains( "Account:" ), "Account: must be suppressed, got:\n{text}" );
}
#[ test ]
fn cred07_opt_in_file_and_saved()
{
let dir = TempDir::new().unwrap();
let home = dir.path().to_str().unwrap();
write_credentials( dir.path(), "pro", "standard", FAR_FUTURE_MS );
let out = run_cs_with_env(
&[ ".credentials.status", "file::1", "saved::1" ],
&[ ( "HOME", home ) ],
);
assert_exit( &out, 0 );
let text = stdout( &out );
assert!( text.contains( "File:" ), "File: line must appear, got:\n{text}" );
assert!( text.contains( "Saved:" ), "Saved: line must appear, got:\n{text}" );
assert!(
text.contains( ".credentials.json" ),
"File: line must contain credentials path, got:\n{text}",
);
}
#[ test ]
fn cred08_display_name_opt_in()
{
let dir = TempDir::new().unwrap();
let home = dir.path().to_str().unwrap();
write_credentials( dir.path(), "pro", "standard", FAR_FUTURE_MS );
write_claude_json_full( dir.path(), "user@example.com", "alice", "admin", "stripe_subscription" );
let out_default = run_cs_with_env( &[ ".credentials.status" ], &[ ( "HOME", home ) ] );
assert_exit( &out_default, 0 );
let text_default = stdout( &out_default );
assert!( !text_default.contains( "Display:" ), "Display: must be absent by default, got:\n{text_default}" );
let out = run_cs_with_env( &[ ".credentials.status", "display_name::1" ], &[ ( "HOME", home ) ] );
assert_exit( &out, 0 );
let text = stdout( &out );
assert!( text.contains( "Display:" ), "Display: line must appear with display_name::1, got:\n{text}" );
assert!( text.contains( "alice" ), "Display: must contain displayName value, got:\n{text}" );
}
#[ test ]
fn cred09_role_opt_in()
{
let dir = TempDir::new().unwrap();
let home = dir.path().to_str().unwrap();
write_credentials( dir.path(), "pro", "standard", FAR_FUTURE_MS );
write_claude_json_full( dir.path(), "user@example.com", "alice", "admin", "stripe_subscription" );
let out_default = run_cs_with_env( &[ ".credentials.status" ], &[ ( "HOME", home ) ] );
assert_exit( &out_default, 0 );
let text_default = stdout( &out_default );
assert!( !text_default.contains( "Role:" ), "Role: must be absent by default, got:\n{text_default}" );
let out = run_cs_with_env( &[ ".credentials.status", "role::1" ], &[ ( "HOME", home ) ] );
assert_exit( &out, 0 );
let text = stdout( &out );
assert!( text.contains( "Role:" ), "Role: line must appear with role::1, got:\n{text}" );
assert!( text.contains( "admin" ), "Role: must contain organizationRole value, got:\n{text}" );
}
#[ test ]
fn cred10_billing_opt_in()
{
let dir = TempDir::new().unwrap();
let home = dir.path().to_str().unwrap();
write_credentials( dir.path(), "pro", "standard", FAR_FUTURE_MS );
write_claude_json_full( dir.path(), "user@example.com", "alice", "admin", "stripe_subscription" );
let out_default = run_cs_with_env( &[ ".credentials.status" ], &[ ( "HOME", home ) ] );
assert_exit( &out_default, 0 );
let text_default = stdout( &out_default );
assert!( !text_default.contains( "Billing:" ), "Billing: must be absent by default, got:\n{text_default}" );
let out = run_cs_with_env( &[ ".credentials.status", "billing::1" ], &[ ( "HOME", home ) ] );
assert_exit( &out, 0 );
let text = stdout( &out );
assert!( text.contains( "Billing:" ), "Billing: line must appear with billing::1, got:\n{text}" );
assert!( text.contains( "stripe_subscription" ), "Billing: must contain billingType value, got:\n{text}" );
}
#[ test ]
fn cred11_model_opt_in()
{
let dir = TempDir::new().unwrap();
let home = dir.path().to_str().unwrap();
write_credentials( dir.path(), "pro", "standard", FAR_FUTURE_MS );
write_settings_json( dir.path(), "sonnet" );
let out_default = run_cs_with_env( &[ ".credentials.status" ], &[ ( "HOME", home ) ] );
assert_exit( &out_default, 0 );
let text_default = stdout( &out_default );
assert!( !text_default.contains( "Model:" ), "Model: must be absent by default, got:\n{text_default}" );
let out = run_cs_with_env( &[ ".credentials.status", "model::1" ], &[ ( "HOME", home ) ] );
assert_exit( &out, 0 );
let text = stdout( &out );
assert!( text.contains( "Model:" ), "Model: line must appear with model::1, got:\n{text}" );
assert!( text.contains( "sonnet" ), "Model: must contain model value from settings.json, got:\n{text}" );
}
#[ test ]
fn cred12_json_extended_shape()
{
let dir = TempDir::new().unwrap();
let home = dir.path().to_str().unwrap();
write_credentials( dir.path(), "pro", "standard", FAR_FUTURE_MS );
write_claude_json_full( dir.path(), "user@example.com", "alice", "admin", "stripe_subscription" );
write_settings_json( dir.path(), "sonnet" );
let out = run_cs_with_env( &[ ".credentials.status", "format::json" ], &[ ( "HOME", home ) ] );
assert_exit( &out, 0 );
let text = stdout( &out ).trim().to_string();
assert!( text.starts_with( '{' ) && text.ends_with( '}' ), "output must be JSON object, got:\n{text}" );
assert!( text.contains( "\"display_name\"" ), "JSON must have display_name key, got:\n{text}" );
assert!( text.contains( "\"role\"" ), "JSON must have role key, got:\n{text}" );
assert!( text.contains( "\"billing\"" ), "JSON must have billing key, got:\n{text}" );
assert!( text.contains( "\"model\"" ), "JSON must have model key, got:\n{text}" );
assert!( text.contains( "alice" ), "display_name value must be present, got:\n{text}" );
assert!( text.contains( "admin" ), "role value must be present, got:\n{text}" );
assert!( text.contains( "stripe_subscription" ), "billing value must be present, got:\n{text}" );
assert!( text.contains( "sonnet" ), "model value must be present, got:\n{text}" );
}
#[ test ]
fn cred13_new_params_absent_by_default()
{
let dir = TempDir::new().unwrap();
let home = dir.path().to_str().unwrap();
write_credentials( dir.path(), "pro", "standard", FAR_FUTURE_MS );
write_claude_json_full( dir.path(), "user@example.com", "alice", "admin", "stripe_subscription" );
write_settings_json( dir.path(), "sonnet" );
let out = run_cs_with_env( &[ ".credentials.status" ], &[ ( "HOME", home ) ] );
assert_exit( &out, 0 );
let text = stdout( &out );
assert!( !text.contains( "Display:" ), "Display: must be absent by default, got:\n{text}" );
assert!( !text.contains( "Role:" ), "Role: must be absent by default, got:\n{text}" );
assert!( !text.contains( "Billing:" ), "Billing: must be absent by default, got:\n{text}" );
assert!( !text.contains( "Model:" ), "Model: must be absent by default, got:\n{text}" );
}
#[ test ]
fn cred14_save_writes_active_shown_in_credentials_status()
{
let dir = TempDir::new().unwrap();
let home = dir.path().to_str().unwrap();
write_credentials( dir.path(), "max", "default_claude_max_20x", FAR_FUTURE_MS );
let save_out = run_cs_with_env(
&[ ".account.save", "name::test@example.com" ],
&[ ( "HOME", home ) ],
);
assert_exit( &save_out, 0 );
let status_out = run_cs_with_env(
&[ ".credentials.status" ],
&[ ( "HOME", home ) ],
);
assert_exit( &status_out, 0 );
let text = stdout( &status_out );
assert!(
text.contains( "Account: test@example.com" ),
"Account: must show saved name after .account.save, got:\n{text}",
);
}
#[ test ]
fn cred15_save_infers_name_from_active_marker()
{
let dir = TempDir::new().unwrap();
let home = dir.path().to_str().unwrap();
write_credentials( dir.path(), "max", "default_claude_max_20x", FAR_FUTURE_MS );
let store = dir.path()
.join( ".persistent" ).join( "claude" ).join( "credential" );
std::fs::create_dir_all( &store ).unwrap();
std::fs::write(
store.join( claude_profile::account::active_marker_filename() ),
"inferred@example.com",
).unwrap();
let save_out = run_cs_with_env( &[ ".account.save" ], &[ ( "HOME", home ) ] );
assert_exit( &save_out, 0 );
let status_out = run_cs_with_env( &[ ".credentials.status" ], &[ ( "HOME", home ) ] );
assert_exit( &status_out, 0 );
let text = stdout( &status_out );
assert!(
text.contains( "Account: inferred@example.com" ),
"Account: must show inferred name after nameless .account.save, got:\n{text}",
);
}
#[ test ]
fn cred16_uuid_opt_in_shows_id_line()
{
let dir = TempDir::new().unwrap();
let home = dir.path().to_str().unwrap();
write_credentials( dir.path(), "max", "tier4", FAR_FUTURE_MS );
write_claude_json_extended( dir.path(), "user_abc123", "some-uuid", &[ "claude_code" ] );
let out = run_cs_with_env( &[ ".credentials.status", "uuid::1" ], &[ ( "HOME", home ) ] );
assert_exit( &out, 0 );
let text = stdout( &out );
assert!( text.contains( "ID:" ), "uuid::1 must emit ID: line, got:\n{text}" );
assert!( text.contains( "user_abc123" ), "ID: line must show taggedId value, got:\n{text}" );
}
#[ test ]
fn cred17_uuid_out_of_range_rejected()
{
let dir = TempDir::new().unwrap();
let home = dir.path().to_str().unwrap();
write_credentials( dir.path(), "max", "tier4", FAR_FUTURE_MS );
let out = run_cs_with_env( &[ ".credentials.status", "uuid::2" ], &[ ( "HOME", home ) ] );
assert_exit( &out, 1 );
}
#[ test ]
fn cred18_uuid_string_value_rejected()
{
let dir = TempDir::new().unwrap();
let home = dir.path().to_str().unwrap();
write_credentials( dir.path(), "max", "tier4", FAR_FUTURE_MS );
let out = run_cs_with_env( &[ ".credentials.status", "uuid::yes" ], &[ ( "HOME", home ) ] );
assert_exit( &out, 1 );
}
#[ test ]
fn cred19_uuid_absent_by_default()
{
let dir = TempDir::new().unwrap();
let home = dir.path().to_str().unwrap();
write_credentials( dir.path(), "max", "tier4", FAR_FUTURE_MS );
write_claude_json_extended( dir.path(), "user_abc123", "some-uuid", &[ "claude_code" ] );
let out = run_cs_with_env( &[ ".credentials.status" ], &[ ( "HOME", home ) ] );
assert_exit( &out, 0 );
let text = stdout( &out );
assert!( !text.contains( "ID:" ), "ID: must be absent by default, got:\n{text}" );
}
#[ test ]
fn cred20_uuid_explicit_zero_no_id_line()
{
let dir = TempDir::new().unwrap();
let home = dir.path().to_str().unwrap();
write_credentials( dir.path(), "max", "tier4", FAR_FUTURE_MS );
write_claude_json_extended( dir.path(), "user_abc123", "some-uuid", &[ "claude_code" ] );
let out = run_cs_with_env( &[ ".credentials.status", "uuid::0" ], &[ ( "HOME", home ) ] );
assert_exit( &out, 0 );
let text = stdout( &out );
assert!( !text.contains( "ID:" ), "ID: must be absent with uuid::0, got:\n{text}" );
}
#[ test ]
fn cred21_uuid_json_always_includes_tagged_id()
{
let dir = TempDir::new().unwrap();
let home = dir.path().to_str().unwrap();
write_credentials( dir.path(), "max", "tier4", FAR_FUTURE_MS );
write_claude_json_extended( dir.path(), "user_abc123", "some-uuid", &[] );
let out = run_cs_with_env(
&[ ".credentials.status", "format::json", "uuid::0" ],
&[ ( "HOME", home ) ],
);
assert_exit( &out, 0 );
let text = stdout( &out );
assert!( text.contains( "\"tagged_id\"" ), "format::json must emit tagged_id key, got:\n{text}" );
assert!( text.contains( "user_abc123" ), "tagged_id must contain taggedId value, got:\n{text}" );
}
#[ test ]
fn cred22_uuid_missing_tagged_id_shows_na()
{
let dir = TempDir::new().unwrap();
let home = dir.path().to_str().unwrap();
write_credentials( dir.path(), "max", "tier4", FAR_FUTURE_MS );
write_claude_json( dir.path(), "user@example.com" );
let out = run_cs_with_env( &[ ".credentials.status", "uuid::1" ], &[ ( "HOME", home ) ] );
assert_exit( &out, 0 );
let text = stdout( &out );
assert!( text.contains( "ID:" ), "ID: line must appear with uuid::1, got:\n{text}" );
assert!( text.contains( "N/A" ), "missing taggedId must show N/A, got:\n{text}" );
}
#[ test ]
fn cred23_capabilities_opt_in_shows_list()
{
let dir = TempDir::new().unwrap();
let home = dir.path().to_str().unwrap();
write_credentials( dir.path(), "max", "tier4", FAR_FUTURE_MS );
write_claude_json_extended( dir.path(), "", "", &[ "claude_code", "pro" ] );
let out = run_cs_with_env( &[ ".credentials.status", "capabilities::1" ], &[ ( "HOME", home ) ] );
assert_exit( &out, 0 );
let text = stdout( &out );
assert!( text.contains( "Capabilities:" ), "capabilities::1 must emit Capabilities: line, got:\n{text}" );
assert!( text.contains( "claude_code" ), "Capabilities: must list claude_code, got:\n{text}" );
assert!( text.contains( "pro" ), "Capabilities: must list pro, got:\n{text}" );
}
#[ test ]
fn cred24_capabilities_out_of_range_rejected()
{
let dir = TempDir::new().unwrap();
let home = dir.path().to_str().unwrap();
write_credentials( dir.path(), "max", "tier4", FAR_FUTURE_MS );
let out = run_cs_with_env( &[ ".credentials.status", "capabilities::2" ], &[ ( "HOME", home ) ] );
assert_exit( &out, 1 );
}
#[ test ]
fn cred25_capabilities_string_value_rejected()
{
let dir = TempDir::new().unwrap();
let home = dir.path().to_str().unwrap();
write_credentials( dir.path(), "max", "tier4", FAR_FUTURE_MS );
let out = run_cs_with_env( &[ ".credentials.status", "capabilities::yes" ], &[ ( "HOME", home ) ] );
assert_exit( &out, 1 );
}
#[ test ]
fn cred26_capabilities_absent_by_default()
{
let dir = TempDir::new().unwrap();
let home = dir.path().to_str().unwrap();
write_credentials( dir.path(), "max", "tier4", FAR_FUTURE_MS );
write_claude_json_extended( dir.path(), "", "", &[ "claude_code" ] );
let out = run_cs_with_env( &[ ".credentials.status" ], &[ ( "HOME", home ) ] );
assert_exit( &out, 0 );
let text = stdout( &out );
assert!( !text.contains( "Capabilities:" ), "Capabilities: must be absent by default, got:\n{text}" );
}
#[ test ]
fn cred27_capabilities_explicit_zero_absent()
{
let dir = TempDir::new().unwrap();
let home = dir.path().to_str().unwrap();
write_credentials( dir.path(), "max", "tier4", FAR_FUTURE_MS );
write_claude_json_extended( dir.path(), "", "", &[ "claude_code" ] );
let out = run_cs_with_env( &[ ".credentials.status", "capabilities::0" ], &[ ( "HOME", home ) ] );
assert_exit( &out, 0 );
let text = stdout( &out );
assert!( !text.contains( "Capabilities:" ), "Capabilities: must be absent with capabilities::0, got:\n{text}" );
}
#[ test ]
fn cred28_capabilities_json_always_emits_key()
{
let dir = TempDir::new().unwrap();
let home = dir.path().to_str().unwrap();
write_credentials( dir.path(), "max", "tier4", FAR_FUTURE_MS );
write_claude_json_extended( dir.path(), "", "", &[ "claude_code" ] );
let out = run_cs_with_env(
&[ ".credentials.status", "format::json", "capabilities::0" ],
&[ ( "HOME", home ) ],
);
assert_exit( &out, 0 );
let text = stdout( &out );
assert!( text.contains( "\"capabilities\"" ), "format::json must emit capabilities key, got:\n{text}" );
assert!( text.contains( "claude_code" ), "capabilities array must contain the value, got:\n{text}" );
}
#[ test ]
fn cred29_capabilities_empty_array_shows_na()
{
let dir = TempDir::new().unwrap();
let home = dir.path().to_str().unwrap();
write_credentials( dir.path(), "max", "tier4", FAR_FUTURE_MS );
write_claude_json_extended( dir.path(), "", "", &[] );
let out = run_cs_with_env( &[ ".credentials.status", "capabilities::1" ], &[ ( "HOME", home ) ] );
assert_exit( &out, 0 );
let text = stdout( &out );
assert!( text.contains( "Capabilities:" ), "Capabilities: line must appear, got:\n{text}" );
assert!( text.contains( "N/A" ), "empty capabilities must show N/A, got:\n{text}" );
}
#[ test ]
fn cred30_capabilities_missing_field_shows_na()
{
let dir = TempDir::new().unwrap();
let home = dir.path().to_str().unwrap();
write_credentials( dir.path(), "max", "tier4", FAR_FUTURE_MS );
write_claude_json( dir.path(), "user@example.com" );
let out = run_cs_with_env( &[ ".credentials.status", "capabilities::1" ], &[ ( "HOME", home ) ] );
assert_exit( &out, 0 );
let text = stdout( &out );
assert!( text.contains( "Capabilities:" ), "Capabilities: line must appear, got:\n{text}" );
assert!( text.contains( "N/A" ), "missing capabilities must show N/A, got:\n{text}" );
}
#[ test ]
fn cred31_org_uuid_shows_org_id_line()
{
let dir = TempDir::new().unwrap();
let home = dir.path().to_str().unwrap();
write_credentials( dir.path(), "max", "tier4", FAR_FUTURE_MS );
write_account( dir.path(), "user@example.com", "max", "tier4", FAR_FUTURE_MS, true );
write_account_roles_json( dir.path(), "user@example.com", "org-xyz-789", "Acme Corp", "admin" );
let out = run_cs_with_env( &[ ".credentials.status", "org_uuid::1" ], &[ ( "HOME", home ) ] );
assert_exit( &out, 0 );
let text = stdout( &out );
assert!( text.contains( "Org ID:" ), "org_uuid::1 must emit Org ID: line, got:\n{text}" );
assert!( text.contains( "org-xyz-789" ), "Org ID: must show organization_uuid, got:\n{text}" );
}
#[ test ]
fn cred32_org_uuid_out_of_range_rejected()
{
let dir = TempDir::new().unwrap();
let home = dir.path().to_str().unwrap();
write_credentials( dir.path(), "max", "tier4", FAR_FUTURE_MS );
let out = run_cs_with_env( &[ ".credentials.status", "org_uuid::2" ], &[ ( "HOME", home ) ] );
assert_exit( &out, 1 );
let err = stderr( &out );
assert!( err.contains( "org_uuid" ), "error must reference org_uuid::, got:\n{err}" );
}
#[ test ]
fn cred33_org_uuid_string_value_rejected()
{
let dir = TempDir::new().unwrap();
let home = dir.path().to_str().unwrap();
write_credentials( dir.path(), "max", "tier4", FAR_FUTURE_MS );
let out = run_cs_with_env( &[ ".credentials.status", "org_uuid::yes" ], &[ ( "HOME", home ) ] );
assert_exit( &out, 1 );
}
#[ test ]
fn cred34_org_uuid_absent_by_default()
{
let dir = TempDir::new().unwrap();
let home = dir.path().to_str().unwrap();
write_credentials( dir.path(), "max", "tier4", FAR_FUTURE_MS );
write_account( dir.path(), "user@example.com", "max", "tier4", FAR_FUTURE_MS, true );
write_account_roles_json( dir.path(), "user@example.com", "org-xyz-789", "Acme Corp", "admin" );
let out = run_cs_with_env( &[ ".credentials.status" ], &[ ( "HOME", home ) ] );
assert_exit( &out, 0 );
let text = stdout( &out );
assert!( !text.contains( "Org ID:" ), "Org ID: must be absent by default, got:\n{text}" );
}
#[ test ]
fn cred35_org_uuid_explicit_zero_absent()
{
let dir = TempDir::new().unwrap();
let home = dir.path().to_str().unwrap();
write_credentials( dir.path(), "max", "tier4", FAR_FUTURE_MS );
write_account( dir.path(), "user@example.com", "max", "tier4", FAR_FUTURE_MS, true );
write_account_roles_json( dir.path(), "user@example.com", "org-xyz-789", "Acme Corp", "admin" );
let out = run_cs_with_env( &[ ".credentials.status", "org_uuid::0" ], &[ ( "HOME", home ) ] );
assert_exit( &out, 0 );
let text = stdout( &out );
assert!( !text.contains( "Org ID:" ), "Org ID: must be absent with org_uuid::0, got:\n{text}" );
}
#[ test ]
fn cred36_org_uuid_json_always_emits_key()
{
let dir = TempDir::new().unwrap();
let home = dir.path().to_str().unwrap();
write_credentials( dir.path(), "max", "tier4", FAR_FUTURE_MS );
write_account( dir.path(), "user@example.com", "max", "tier4", FAR_FUTURE_MS, true );
write_account_roles_json( dir.path(), "user@example.com", "org-xyz", "Acme Corp", "admin" );
let out = run_cs_with_env(
&[ ".credentials.status", "format::json", "org_uuid::0" ],
&[ ( "HOME", home ) ],
);
assert_exit( &out, 0 );
let text = stdout( &out );
assert!( text.contains( "\"organization_uuid\"" ), "format::json must emit organization_uuid key, got:\n{text}" );
assert!( text.contains( "org-xyz" ), "organization_uuid must contain the value, got:\n{text}" );
}
#[ test ]
fn cred37_org_uuid_missing_roles_json_na()
{
let dir = TempDir::new().unwrap();
let home = dir.path().to_str().unwrap();
write_credentials( dir.path(), "max", "tier4", FAR_FUTURE_MS );
write_account( dir.path(), "user@example.com", "max", "tier4", FAR_FUTURE_MS, true );
let out = run_cs_with_env( &[ ".credentials.status", "org_uuid::1" ], &[ ( "HOME", home ) ] );
assert_exit( &out, 0 );
let text = stdout( &out );
assert!( text.contains( "Org ID:" ), "Org ID: line must appear with org_uuid::1, got:\n{text}" );
assert!( text.contains( "N/A" ), "missing roles.json must show N/A, got:\n{text}" );
}
#[ test ]
fn cred38_org_name_shows_org_line()
{
let dir = TempDir::new().unwrap();
let home = dir.path().to_str().unwrap();
write_credentials( dir.path(), "max", "tier4", FAR_FUTURE_MS );
write_account( dir.path(), "user@example.com", "max", "tier4", FAR_FUTURE_MS, true );
write_account_roles_json( dir.path(), "user@example.com", "org-xyz-789", "Acme Corp", "admin" );
let out = run_cs_with_env( &[ ".credentials.status", "org_name::1" ], &[ ( "HOME", home ) ] );
assert_exit( &out, 0 );
let text = stdout( &out );
assert!( text.contains( "Org:" ), "org_name::1 must emit Org: line, got:\n{text}" );
assert!( text.contains( "Acme Corp" ), "Org: must show organization_name, got:\n{text}" );
}
#[ test ]
fn cred39_org_name_out_of_range_rejected()
{
let dir = TempDir::new().unwrap();
let home = dir.path().to_str().unwrap();
write_credentials( dir.path(), "max", "tier4", FAR_FUTURE_MS );
let out = run_cs_with_env( &[ ".credentials.status", "org_name::2" ], &[ ( "HOME", home ) ] );
assert_exit( &out, 1 );
let err = stderr( &out );
assert!( err.contains( "org_name" ), "error must reference org_name::, got:\n{err}" );
}
#[ test ]
fn cred40_org_name_string_value_rejected()
{
let dir = TempDir::new().unwrap();
let home = dir.path().to_str().unwrap();
write_credentials( dir.path(), "max", "tier4", FAR_FUTURE_MS );
let out = run_cs_with_env( &[ ".credentials.status", "org_name::yes" ], &[ ( "HOME", home ) ] );
assert_exit( &out, 1 );
}
#[ test ]
fn cred41_org_name_absent_by_default()
{
let dir = TempDir::new().unwrap();
let home = dir.path().to_str().unwrap();
write_credentials( dir.path(), "max", "tier4", FAR_FUTURE_MS );
write_account( dir.path(), "user@example.com", "max", "tier4", FAR_FUTURE_MS, true );
write_account_roles_json( dir.path(), "user@example.com", "org-xyz-789", "Acme Corp", "admin" );
let out = run_cs_with_env( &[ ".credentials.status" ], &[ ( "HOME", home ) ] );
assert_exit( &out, 0 );
let text = stdout( &out );
assert!( !text.contains( "Org:" ), "Org: must be absent by default, got:\n{text}" );
}
#[ test ]
fn cred42_org_name_explicit_zero_absent()
{
let dir = TempDir::new().unwrap();
let home = dir.path().to_str().unwrap();
write_credentials( dir.path(), "max", "tier4", FAR_FUTURE_MS );
write_account( dir.path(), "user@example.com", "max", "tier4", FAR_FUTURE_MS, true );
write_account_roles_json( dir.path(), "user@example.com", "org-xyz-789", "Acme Corp", "admin" );
let out = run_cs_with_env( &[ ".credentials.status", "org_name::0" ], &[ ( "HOME", home ) ] );
assert_exit( &out, 0 );
let text = stdout( &out );
assert!( !text.contains( "Org:" ), "Org: must be absent with org_name::0, got:\n{text}" );
}
#[ test ]
fn cred43_org_name_json_always_emits_key()
{
let dir = TempDir::new().unwrap();
let home = dir.path().to_str().unwrap();
write_credentials( dir.path(), "max", "tier4", FAR_FUTURE_MS );
write_account( dir.path(), "user@example.com", "max", "tier4", FAR_FUTURE_MS, true );
write_account_roles_json( dir.path(), "user@example.com", "org-xyz-789", "Acme Corp", "admin" );
let out = run_cs_with_env(
&[ ".credentials.status", "format::json", "org_name::0" ],
&[ ( "HOME", home ) ],
);
assert_exit( &out, 0 );
let text = stdout( &out );
assert!( text.contains( "\"organization_name\"" ), "format::json must emit organization_name key, got:\n{text}" );
assert!( text.contains( "Acme Corp" ), "organization_name must contain the value, got:\n{text}" );
}
#[ test ]
fn cred44_org_name_missing_roles_json_na()
{
let dir = TempDir::new().unwrap();
let home = dir.path().to_str().unwrap();
write_credentials( dir.path(), "max", "tier4", FAR_FUTURE_MS );
write_account( dir.path(), "user@example.com", "max", "tier4", FAR_FUTURE_MS, true );
let out = run_cs_with_env( &[ ".credentials.status", "org_name::1" ], &[ ( "HOME", home ) ] );
assert_exit( &out, 0 );
let text = stdout( &out );
assert!( text.contains( "Org:" ), "Org: line must appear with org_name::1, got:\n{text}" );
assert!( text.contains( "N/A" ), "missing roles.json must show N/A, got:\n{text}" );
}
#[ test ]
fn cred45_ft09_format_json_includes_all_5_org_fields()
{
let dir = TempDir::new().unwrap();
let home = dir.path().to_str().unwrap();
write_credentials( dir.path(), "max", "tier4", FAR_FUTURE_MS );
write_account( dir.path(), "user@example.com", "max", "tier4", FAR_FUTURE_MS, true );
write_account_roles_json( dir.path(), "user@example.com", "uuid-org-001", "Test Org", "member" );
let out = run_cs_with_env(
&[ ".credentials.status", "format::json" ],
&[ ( "HOME", home ) ],
);
assert_exit( &out, 0 );
let text = stdout( &out );
assert!( text.contains( "\"organization_uuid\"" ), "format::json must emit organization_uuid key, got:\n{text}" );
assert!( text.contains( "\"organization_name\"" ), "format::json must emit organization_name key, got:\n{text}" );
assert!( text.contains( "\"organization_role\"" ), "format::json must emit organization_role key, got:\n{text}" );
assert!( text.contains( "\"workspace_uuid\"" ), "format::json must emit workspace_uuid key, got:\n{text}" );
assert!( text.contains( "\"workspace_name\"" ), "format::json must emit workspace_name key, got:\n{text}" );
}
#[ test ]
fn cred46_ft11_null_workspace_fields_render_as_empty_string()
{
let dir = TempDir::new().unwrap();
let home = dir.path().to_str().unwrap();
write_credentials( dir.path(), "max", "tier4", FAR_FUTURE_MS );
write_account( dir.path(), "user@example.com", "max", "tier4", FAR_FUTURE_MS, true );
write_account_roles_json( dir.path(), "user@example.com", "uuid-org-002", "Personal Org", "owner" );
let out = run_cs_with_env(
&[ ".credentials.status", "format::json" ],
&[ ( "HOME", home ) ],
);
assert_exit( &out, 0 );
let text = stdout( &out );
assert!( text.contains( "\"workspace_uuid\":\"\"" ), "null workspace_uuid must render as empty string in JSON, got:\n{text}" );
assert!( text.contains( "\"workspace_name\":\"\"" ), "null workspace_name must render as empty string in JSON, got:\n{text}" );
}
#[ test ]
fn cred47_absent_settings_json_model_shows_na()
{
let dir = TempDir::new().unwrap();
let home = dir.path().to_str().unwrap();
write_credentials( dir.path(), "pro", "standard", FAR_FUTURE_MS );
let out = run_cs_with_env( &[ ".credentials.status", "model::1" ], &[ ( "HOME", home ) ] );
assert_exit( &out, 0 );
let text = stdout( &out );
assert!(
text.contains( "Model:" ),
"Model: line must appear even when settings.json is absent, got:\n{text}",
);
assert!(
text.contains( "N/A" ),
"Model: value must be N/A when settings.json is absent, got:\n{text}",
);
}
#[ test ]
fn it_trace_credentials_status_accepted()
{
let dir = TempDir::new().unwrap();
let home = dir.path().to_str().unwrap();
write_credentials( dir.path(), "pro", "standard", FAR_FUTURE_MS );
let out = run_cs_with_env( &[ ".credentials.status", "trace::1" ], &[ ( "HOME", home ) ] );
let err = stderr( &out );
assert!(
!err.contains( "Unknown parameter" ),
"trace::1 must be accepted by .credentials.status, got stderr:\n{err}",
);
assert_exit( &out, 0 );
assert!(
err.contains( "[trace]" ),
"trace::1 must emit [trace] lines to stderr for .credentials.status, got:\n{err}",
);
}