use super::*;
use axum::Router;
use axum::extract::{Request, State};
use axum::http::StatusCode;
use axum::response::IntoResponse as _;
use axum::routing::any;
use std::sync::{Arc, Mutex};
#[derive(Clone)]
struct CandidateVendor {
provider: SubscriptionProvider,
reject_refresh: bool,
fail_catalog: bool,
requests: Arc<Mutex<Vec<(String, String, String)>>>,
}
async fn candidate_vendor(
State(state): State<CandidateVendor>,
request: Request,
) -> axum::response::Response {
let method = request.method().to_string();
let path = request.uri().path().to_string();
let authorization = request
.headers()
.get("authorization")
.and_then(|value| value.to_str().ok())
.unwrap_or_default()
.to_string();
state
.requests
.lock()
.expect("candidate requests")
.push((method, path.clone(), authorization));
if path == "/token" {
if state.reject_refresh {
return (
StatusCode::BAD_REQUEST,
axum::Json(serde_json::json!({"error":"invalid_grant"})),
)
.into_response();
}
return axum::Json(serde_json::json!({
"access_token":"fresh-access",
"refresh_token":"fresh-refresh",
"expires_in":3600
}))
.into_response();
}
if state.fail_catalog {
return (
StatusCode::SERVICE_UNAVAILABLE,
axum::Json(serde_json::json!({"error":"temporary catalog outage"})),
)
.into_response();
}
let catalog = match state.provider {
SubscriptionProvider::Claude => serde_json::json!({"data":[{"id":"claude-live"}]}),
SubscriptionProvider::Codex => serde_json::json!({"models":[{"slug":"gpt-live"}]}),
SubscriptionProvider::Gemini => serde_json::json!({
"models":[{"name":"models/gemini-live","supportedGenerationMethods":["generateContent"]}]
}),
SubscriptionProvider::Qwen => serde_json::json!({"data":[{"id":"qwen-live"}]}),
};
axum::Json(catalog).into_response()
}
async fn start_candidate_vendor(
provider: SubscriptionProvider,
reject_refresh: bool,
fail_catalog: bool,
) -> (
String,
Arc<Mutex<Vec<(String, String, String)>>>,
tokio::task::JoinHandle<()>,
) {
let requests = Arc::new(Mutex::new(Vec::new()));
let app = Router::new()
.fallback(any(candidate_vendor))
.with_state(CandidateVendor {
provider,
reject_refresh,
fail_catalog,
requests: Arc::clone(&requests),
});
let listener = tokio::net::TcpListener::bind("127.0.0.1:0")
.await
.expect("candidate vendor listener");
let url = format!("http://{}", listener.local_addr().unwrap());
let task = tokio::spawn(async move { axum::serve(listener, app).await.unwrap() });
(url, requests, task)
}
fn candidate_document(provider: SubscriptionProvider) -> String {
match provider {
SubscriptionProvider::Claude => serde_json::json!({
"claudeAiOauth": {
"accessToken":"stale-access",
"refreshToken":"stale-refresh",
"expiresAt":9_999_999_999_999_i64
},
"vendor_marker":"preserved"
}),
SubscriptionProvider::Codex => serde_json::json!({
"auth_mode":"chatgpt",
"tokens": {
"access_token":"stale-access",
"refresh_token":"stale-refresh",
"account_id":"acct-import"
},
"vendor_marker":"preserved"
}),
SubscriptionProvider::Gemini => serde_json::json!({
"access_token":"stale-access",
"refresh_token":"stale-refresh",
"expiry_date":9_999_999_999_999_i64,
"vendor_marker":"preserved"
}),
SubscriptionProvider::Qwen => serde_json::json!({
"access_token":"stale-access",
"refresh_token":"stale-refresh",
"expiry_date":9_999_999_999_999_i64,
"resource_url":"portal.qwen.ai",
"vendor_marker":"preserved"
}),
}
.to_string()
}
#[test]
fn a_credential_reports_its_expiry_and_whether_it_can_renew() {
let now = chrono::Utc::now().timestamp_millis();
let live = link_assistant_router::subscription::SubscriptionToken {
access_token: "a".into(),
refresh_token: Some("r".into()),
expires_at_ms: Some(now + 3 * 3_600_000),
account_id: None,
resource_url: None,
};
let report = describe_credential(&live);
assert!(report.contains("expires in"), "{report}");
assert!(report.contains("refresh token present"), "{report}");
}
#[test]
fn an_expired_credential_is_named_as_expired() {
let now = chrono::Utc::now().timestamp_millis();
let dead = link_assistant_router::subscription::SubscriptionToken {
access_token: "a".into(),
refresh_token: None,
expires_at_ms: Some(now - 3 * 3_600_000),
account_id: None,
resource_url: None,
};
let report = describe_credential(&dead);
assert!(report.contains("EXPIRED"), "{report}");
assert!(
report.contains("NO refresh token"),
"a credential that cannot be renewed must say so: {report}"
);
}
#[test]
fn an_unrecorded_expiry_is_not_reported_as_expired() {
let unknown = link_assistant_router::subscription::SubscriptionToken {
access_token: "a".into(),
refresh_token: Some("r".into()),
expires_at_ms: None,
account_id: None,
resource_url: None,
};
let report = describe_credential(&unknown);
assert!(report.contains("no recorded expiry"), "{report}");
assert!(!report.contains("EXPIRED"), "{report}");
}
#[test]
fn durations_read_at_a_glance_at_each_threshold() {
assert_eq!(humanize_minutes(45), "45 minutes");
assert_eq!(
humanize_minutes(89),
"89 minutes",
"the last minute reading"
);
assert_eq!(humanize_minutes(90), "1 hours", "the first hour reading");
assert_eq!(
humanize_minutes(119),
"1 hours",
"truncation the doc comment claims to have removed still applies here"
);
assert_eq!(humanize_minutes(120), "2 hours");
assert_eq!(
humanize_minutes(60 * 47),
"47 hours",
"the last hour reading"
);
assert_eq!(humanize_minutes(60 * 48), "2 days", "the first day reading");
}
#[tokio::test]
async fn refresh_chain_validation_precedes_promotion_for_every_provider() {
for provider in SubscriptionProvider::ALL {
let (url, requests, server) = start_candidate_vendor(provider, false, false).await;
let root = tempfile::tempdir().expect("import root");
let destination_home = root.path().join("destination");
std::fs::create_dir_all(&destination_home).expect("destination home");
let destination = SubscriptionReader::new(provider, &destination_home);
let destination_path = destination_home.join(provider.canonical_credential_filename());
let current = candidate_document(provider).replace("stale-", "current-");
std::fs::write(&destination_path, ¤t).expect("current destination");
let validated = validate_candidate_with(
root.path(),
provider,
&candidate_document(provider),
Some(&format!("{url}/token")),
Some(&url),
)
.await
.expect("refresh chain and catalog must validate");
let staged: serde_json::Value =
serde_json::from_str(validated.document()).expect("staged document");
assert_eq!(staged["vendor_marker"], "preserved", "{provider}");
assert!(
validated.document().contains("fresh-access")
&& validated.document().contains("fresh-refresh"),
"{provider} did not return the durably rotated candidate: {}",
validated.document()
);
assert_eq!(
std::fs::read_to_string(&destination_path).unwrap(),
current,
"{provider} changed destination during validation"
);
install_candidate(
&destination,
root.path(),
validated.document(),
CredentialProbe::Accepted,
ImportPolicy::default(),
)
.await
.expect("validated candidate promotion");
assert_eq!(
std::fs::read_to_string(&destination_path).unwrap(),
validated.document(),
"{provider} did not promote the staged bytes"
);
let seen = requests.lock().expect("candidate requests");
assert_eq!(seen.len(), 2, "{provider}: {seen:?}");
assert_eq!(seen[0].0, "POST", "{provider}: {seen:?}");
assert_eq!(seen[0].1, "/token", "{provider}: {seen:?}");
assert_eq!(seen[1].0, "GET", "{provider}: {seen:?}");
assert_eq!(seen[1].2, "Bearer fresh-access", "{provider}: {seen:?}");
drop(seen);
server.abort();
}
}
#[tokio::test]
async fn fresh_import_keeps_one_authoritative_refresh_chain_for_every_provider() {
for provider in SubscriptionProvider::ALL {
let (url, requests, server) = start_candidate_vendor(provider, false, false).await;
let root = tempfile::tempdir().expect("import root");
let source_home = root.path().join("source");
let destination_home = root.path().join("destination");
let data_dir = root.path().join("data");
std::fs::create_dir_all(&source_home).expect("source home");
std::fs::create_dir_all(&destination_home).expect("destination home");
let source_path = source_home.join(provider.canonical_credential_filename());
let source_document = candidate_document(provider);
std::fs::write(&source_path, &source_document).expect("vendor credential");
let execution = import_provider_with_paths(
&data_dir,
root.path().to_str().expect("UTF-8 test path"),
&destination_home,
provider,
source_home.to_str().expect("UTF-8 source path"),
ImportPolicy::default(),
None,
Some(&url),
)
.await
.expect("fresh external import");
assert!(execution.is_promoted(), "{provider} was not promoted");
assert_eq!(
std::fs::read_to_string(&source_path).unwrap(),
source_document,
"{provider} source changed during import"
);
let destination_path = destination_home.join(provider.canonical_credential_filename());
let pointer = std::fs::read_to_string(&destination_path).expect("Router reference");
assert!(
pointer.contains("credential_source"),
"{provider}: {pointer}"
);
assert!(!pointer.contains("stale-access"), "{provider}: {pointer}");
assert!(!pointer.contains("stale-refresh"), "{provider}: {pointer}");
let reader = SubscriptionReader::new(provider, &destination_home);
let (token, origin) = reader.read_token_from().expect("read adopted source");
assert_eq!(
origin,
link_assistant_router::platform_keychain::Origin::AdoptedFile
);
assert_eq!(token.access_token, "stale-access");
assert_eq!(
requests.lock().expect("candidate requests").as_slice(),
&[(
"GET".to_string(),
match provider {
SubscriptionProvider::Claude => "/v1/models".to_string(),
SubscriptionProvider::Codex | SubscriptionProvider::Qwen => {
"/models".to_string()
}
SubscriptionProvider::Gemini => "/v1beta/models".to_string(),
},
"Bearer stale-access".to_string(),
)],
"{provider} made an OAuth exchange"
);
server.abort();
}
}
#[tokio::test]
async fn fresh_import_refuses_near_expiry_before_any_vendor_request() {
for provider in SubscriptionProvider::ALL {
let (url, requests, server) = start_candidate_vendor(provider, false, false).await;
let root = tempfile::tempdir().expect("import root");
let source_home = root.path().join("source");
let destination_home = root.path().join("destination");
std::fs::create_dir_all(&source_home).expect("source home");
std::fs::create_dir_all(&destination_home).expect("destination home");
let mut document: serde_json::Value =
serde_json::from_str(&candidate_document(provider)).expect("candidate JSON");
let expired = chrono::Utc::now().timestamp_millis();
if provider == SubscriptionProvider::Claude {
document["claudeAiOauth"]["expiresAt"] = expired.into();
} else {
document["expiry_date"] = expired.into();
}
let source_document = document.to_string();
let source_path = source_home.join(provider.canonical_credential_filename());
std::fs::write(&source_path, &source_document).expect("vendor credential");
let Err(error) = import_provider_with_paths(
&root.path().join("data"),
root.path().to_str().expect("UTF-8 test path"),
&destination_home,
provider,
source_home.to_str().expect("UTF-8 source path"),
ImportPolicy::default(),
None,
Some(&url),
)
.await
else {
panic!("{provider} near-expiry external credential was imported");
};
assert!(
error.contains("expired or near expiry"),
"{provider}: {error}"
);
assert!(requests.lock().expect("candidate requests").is_empty());
assert_eq!(
std::fs::read_to_string(source_path).unwrap(),
source_document
);
assert!(
!destination_home
.join(provider.canonical_credential_filename())
.exists()
);
server.abort();
}
}
#[tokio::test]
async fn fresh_import_catalog_failure_spends_no_refresh_token_or_transaction() {
for provider in SubscriptionProvider::ALL {
let (url, requests, server) = start_candidate_vendor(provider, false, true).await;
let root = tempfile::tempdir().expect("import root");
let source_home = root.path().join("source");
let destination_home = root.path().join("destination");
std::fs::create_dir_all(&source_home).expect("source home");
std::fs::create_dir_all(&destination_home).expect("destination home");
let source_path = source_home.join(provider.canonical_credential_filename());
let source_document = candidate_document(provider);
std::fs::write(&source_path, &source_document).expect("vendor credential");
let Err(error) = import_provider_with_paths(
&root.path().join("data"),
root.path().to_str().expect("UTF-8 test path"),
&destination_home,
provider,
source_home.to_str().expect("UTF-8 source path"),
ImportPolicy::default(),
None,
Some(&url),
)
.await
else {
panic!("{provider} catalog failure was imported");
};
assert!(error.previous_credential_safe, "{provider}: {error}");
assert!(error.transaction_id.is_none(), "{provider}: {error}");
let seen = requests.lock().expect("candidate requests");
assert_eq!(seen.len(), 1, "{provider}: {seen:?}");
assert_eq!(seen[0].0, "GET", "{provider}: {seen:?}");
drop(seen);
assert_eq!(
std::fs::read_to_string(source_path).unwrap(),
source_document
);
assert!(
!destination_home
.join(provider.canonical_credential_filename())
.exists()
);
assert_eq!(
std::fs::read_dir(root.path().join("data/auth-import-candidates"))
.expect("staging root")
.count(),
0,
"{provider} retained an externally owned copy"
);
server.abort();
}
}
#[tokio::test]
async fn a_rejected_refresh_chain_never_reaches_catalog_or_destination() {
for provider in SubscriptionProvider::ALL {
let (url, requests, server) = start_candidate_vendor(provider, true, false).await;
let root = tempfile::tempdir().expect("import root");
let destination_home = root.path().join("destination");
std::fs::create_dir_all(&destination_home).expect("destination home");
let destination_path = destination_home.join(provider.canonical_credential_filename());
let current = candidate_document(provider).replace("stale-", "current-");
std::fs::write(&destination_path, ¤t).expect("current destination");
let error = validate_candidate_with(
root.path(),
provider,
&candidate_document(provider),
Some(&format!("{url}/token")),
Some(&url),
)
.await
.expect_err("spent refresh chain must be rejected");
assert!(error.contains("invalid_grant"), "{provider}: {error}");
assert_eq!(error.outcome, ImportOutcome::ExchangeRejected);
assert_eq!(error.phase, ImportPhase::Exchange);
assert!(error.previous_credential_safe);
assert_eq!(error.transaction_id, None);
assert!(!error.contains("retained as transaction"), "{error}");
let transactions = std::fs::read_dir(root.path().join("auth-import-candidates"))
.expect("retained staging root")
.collect::<Result<Vec<_>, _>>()
.expect("retained transactions");
assert!(transactions.is_empty(), "{provider}: {error}");
assert_eq!(
std::fs::read_to_string(destination_path).unwrap(),
current,
"{provider} destination changed"
);
let seen = requests.lock().expect("candidate requests");
assert_eq!(
seen.len(),
1,
"{provider} catalog was reached after refresh failure: {seen:?}"
);
assert_eq!(seen[0].1, "/token");
drop(seen);
server.abort();
}
}
#[tokio::test]
async fn an_inconclusive_exchange_reports_uncertain_retained_state() {
let listener = std::net::TcpListener::bind("127.0.0.1:0").expect("reserve dead endpoint");
let token_url = format!("http://{}/token", listener.local_addr().unwrap());
drop(listener);
let root = tempfile::tempdir().expect("import root");
let error = validate_candidate_with(
root.path(),
SubscriptionProvider::Claude,
&candidate_document(SubscriptionProvider::Claude),
Some(&token_url),
Some("http://127.0.0.1:1"),
)
.await
.expect_err("connection loss must remain uncertain");
assert_eq!(error.outcome, ImportOutcome::ExchangeUncertain);
assert_eq!(error.phase, ImportPhase::Exchange);
assert!(!error.previous_credential_safe);
assert!(error.transaction_id.is_none());
assert!(!error.contains("retained as transaction"), "{error}");
assert_eq!(
std::fs::read_dir(root.path().join("auth-import-candidates"))
.expect("staging root")
.count(),
0
);
}
#[test]
fn qwen_import_uses_the_vendor_issued_catalog_origin() {
let token = link_assistant_router::subscription::SubscriptionToken {
access_token: "redacted".into(),
refresh_token: Some("redacted".into()),
expires_at_ms: None,
account_id: None,
resource_url: Some("portal.qwen.ai".into()),
};
assert_eq!(
catalog_base_for_candidate(SubscriptionProvider::Qwen, &token).unwrap(),
"https://portal.qwen.ai/v1"
);
}
#[test]
fn qwen_import_rejects_non_vendor_catalog_origins() {
for resource_url in [
"http://portal.qwen.ai",
"https://127.0.0.1",
"https://portal.qwen.ai.attacker.example",
"https://user@portal.qwen.ai",
"https://portal.qwen.ai:8443",
"https://portal.qwen.ai?redirect=https://attacker.example",
] {
let token = link_assistant_router::subscription::SubscriptionToken {
access_token: "redacted".into(),
refresh_token: Some("redacted".into()),
expires_at_ms: None,
account_id: None,
resource_url: Some(resource_url.into()),
};
assert!(
catalog_base_for_candidate(SubscriptionProvider::Qwen, &token).is_err(),
"untrusted Qwen origin was accepted: {resource_url}"
);
}
}
#[test]
fn catalog_validation_uses_only_provider_owned_defaults() {
let token = link_assistant_router::subscription::SubscriptionToken {
access_token: "redacted".into(),
refresh_token: Some("redacted".into()),
expires_at_ms: None,
account_id: None,
resource_url: None,
};
assert_eq!(
catalog_base_for_candidate(SubscriptionProvider::Gemini, &token).unwrap(),
"https://generativelanguage.googleapis.com"
);
assert_eq!(
catalog_base_for_candidate(SubscriptionProvider::Claude, &token).unwrap(),
SubscriptionProvider::Claude.default_base_url()
);
assert_eq!(
catalog_base_for_candidate(SubscriptionProvider::Qwen, &token).unwrap(),
SubscriptionProvider::Qwen.default_base_url()
);
}
#[tokio::test]
async fn validated_candidate_diagnostics_are_redacted_and_retention_is_durable() {
let provider = SubscriptionProvider::Claude;
let (url, _requests, server) = start_candidate_vendor(provider, false, false).await;
let root = tempfile::tempdir().expect("staging root");
let candidate = validate_candidate_with(
root.path(),
provider,
&candidate_document(provider),
Some(&format!("{url}/token")),
Some(&url),
)
.await
.expect("candidate acceptance");
let transaction_id = candidate.transaction_id().to_string();
let diagnostic = format!("{candidate:?}");
assert!(diagnostic.contains(&transaction_id), "{diagnostic}");
assert!(!diagnostic.contains("secret"), "{diagnostic}");
assert_eq!(candidate.retain(), transaction_id);
let retained = std::fs::read_dir(root.path().join("auth-import-candidates"))
.expect("retained root")
.next()
.expect("retained transaction")
.expect("retained entry")
.path();
assert!(
retained
.join(provider.as_str())
.join(provider.canonical_credential_filename())
.is_file()
);
server.abort();
}
#[test]
fn machine_results_are_stable_and_credential_free() {
let retained = ImportExecution::failed(
Some("codex"),
ImportFailure::retained(
ImportPhase::Catalog,
"opaque-transaction".to_string(),
"must-not-leak access-token refresh-token credential document",
),
);
let promoted = ImportExecution::promoted(
"qwen",
vec!["human output may name must-not-leak".to_string()],
);
let rejected = ImportExecution::failed(
Some("claude"),
ImportFailure::from_refresh_kind_for_test(
link_assistant_router::refresh::ImportRefreshFailureKind::ExchangeRejected,
"unused-transaction",
),
);
let exchange_uncertain = ImportExecution::failed(
Some("claude"),
ImportFailure::from_refresh_kind_for_test(
link_assistant_router::refresh::ImportRefreshFailureKind::ExchangeUncertain,
"exchange-transaction",
),
);
let persistence_uncertain = ImportExecution::failed(
Some("codex"),
ImportFailure::from_refresh_kind_for_test(
link_assistant_router::refresh::ImportRefreshFailureKind::PersistenceUncertain,
"persistence-transaction",
),
);
let already_present = ImportExecution::already_present("gemini", Vec::new());
let value = import_result::json_value(&[
retained,
promoted,
rejected,
exchange_uncertain,
persistence_uncertain,
already_present,
]);
let serialized = value.to_string();
assert_eq!(value["schema_version"], 1);
assert_eq!(value["results"][0]["provider"], "codex");
assert_eq!(value["results"][0]["outcome"], "successor_retained");
assert_eq!(value["results"][0]["phase"], "catalog");
assert_eq!(value["results"][0]["previous_credential_safe"], false);
assert_eq!(value["results"][0]["transaction_id"], "opaque-transaction");
assert_eq!(value["results"][1]["outcome"], "promoted");
assert_eq!(value["results"][2]["outcome"], "exchange_rejected");
assert_eq!(value["results"][2]["previous_credential_safe"], true);
assert!(value["results"][2]["transaction_id"].is_null());
assert_eq!(value["results"][3]["outcome"], "exchange_uncertain");
assert_eq!(value["results"][3]["phase"], "exchange");
assert!(value["results"][3]["transaction_id"].is_null());
assert_eq!(value["results"][4]["outcome"], "persistence_uncertain");
assert_eq!(value["results"][4]["phase"], "persistence");
assert!(value["results"][4]["transaction_id"].is_null());
assert_eq!(value["results"][5]["outcome"], "already_present");
assert!(!serialized.contains("must-not-leak"), "{serialized}");
assert!(!serialized.contains("access-token"), "{serialized}");
assert!(!serialized.contains("refresh-token"), "{serialized}");
}
#[test]
fn persistence_uncertainty_does_not_claim_an_unproven_successor() {
let failure = ImportFailure::from_refresh_kind_for_test(
link_assistant_router::refresh::ImportRefreshFailureKind::PersistenceUncertain,
"persistence-transaction",
);
assert_eq!(failure.outcome, ImportOutcome::PersistenceUncertain);
assert_eq!(failure.phase, ImportPhase::Persistence);
assert!(!failure.previous_credential_safe);
assert!(failure.transaction_id.is_none());
}
#[test]
fn retained_transactions_resolve_by_opaque_id_only() {
let root = tempfile::tempdir().expect("router data");
let transaction_id = "opaque-transaction";
let transaction = root
.path()
.join("auth-import-candidates")
.join(format!("{transaction_id}-random"));
let provider = transaction.join("qwen");
std::fs::create_dir_all(&provider).expect("retained provider");
let resolved = import_resume::resolve(root.path(), transaction_id).expect("resume candidate");
assert_eq!(resolved.provider, ImportProvider::Qwen);
assert_eq!(resolved.source, provider.to_string_lossy());
assert_eq!(resolved.transaction_id, transaction_id);
let error = import_resume::resolve(root.path(), "../opaque-transaction")
.expect_err("path syntax must not be accepted as an opaque ID");
assert_eq!(error.outcome, ImportOutcome::NotAttempted);
}
#[tokio::test]
async fn retained_transaction_has_one_exclusive_resume_claim() {
let root = tempfile::tempdir().expect("router data");
let transaction_id = "exclusive-transaction";
let provider = root
.path()
.join("auth-import-candidates")
.join(format!("{transaction_id}-random"))
.join("qwen");
std::fs::create_dir_all(&provider).expect("retained provider");
let first = import_resume::resolve_claimed(root.path(), transaction_id)
.await
.expect("first resume claim");
let second = import_resume::resolve_claimed(root.path(), transaction_id)
.await
.expect_err("a transaction must have only one active resume");
assert_eq!(second.outcome, ImportOutcome::NotAttempted);
assert!(second.contains("already being resumed"), "{second}");
drop(first);
assert!(
import_resume::resolve_claimed(root.path(), transaction_id)
.await
.is_ok(),
"a failed attempt must release the durable claim for recovery"
);
}
#[tokio::test]
async fn invalid_resume_id_is_rejected_before_any_lock_path_is_created() {
let root = tempfile::tempdir().expect("router data");
let error = import_resume::resolve_claimed(root.path(), "x/../../../outside")
.await
.expect_err("path syntax must be rejected before lock creation");
assert_eq!(error.outcome, ImportOutcome::NotAttempted);
assert!(!root.path().join("auth-import-candidates").exists());
assert!(!root.path().parent().unwrap().join("outside.lock").exists());
}
#[tokio::test]
async fn candidate_install_does_not_invent_external_ownership() {
let router_home = tempfile::tempdir().expect("Router destination");
let data = tempfile::tempdir().expect("router data");
let document = r#"{"access_token":"accepted","refresh_token":"rotating"}"#;
let reader = SubscriptionReader::new(SubscriptionProvider::Qwen, router_home.path());
install_candidate(
&reader,
data.path(),
document,
CredentialProbe::Accepted,
ImportPolicy::default(),
)
.await
.expect("accepted candidate install");
assert_eq!(
reader
.read_document_for_import()
.expect("installed document")
.origin,
link_assistant_router::platform_keychain::Origin::File
);
}
#[test]
fn cleanup_failure_is_machine_readable_and_keeps_the_transaction_id() {
let mut execution = ImportExecution::promoted("qwen", Vec::new());
execution.mark_cleanup_pending(
"cleanup-transaction".into(),
"redacted cleanup failure".into(),
);
let value = import_result::json_value(&[execution]);
assert_eq!(value["results"][0]["outcome"], "promotion_cleanup_pending");
assert_eq!(value["results"][0]["phase"], "promotion");
assert_eq!(value["results"][0]["transaction_id"], "cleanup-transaction");
}
#[test]
fn every_import_target_has_the_expected_label_and_subscription() {
let cases = [
(
ImportProvider::Claude,
"claude",
Some(SubscriptionProvider::Claude),
),
(
ImportProvider::Codex,
"codex",
Some(SubscriptionProvider::Codex),
),
(
ImportProvider::Gemini,
"gemini",
Some(SubscriptionProvider::Gemini),
),
(
ImportProvider::Qwen,
"qwen",
Some(SubscriptionProvider::Qwen),
),
(ImportProvider::Gh, "github", None),
];
for (target, label, subscription) in cases {
assert_eq!(provider_label(target), label);
assert_eq!(subscription_of(target), subscription);
}
}
include!("auth_import_adoption_tests.rs");
#[path = "auth_import_rejection_tests.rs"]
mod rejection_tests;
#[path = "auth_import_source_tests.rs"]
mod source_tests;