use std::sync::Arc;
use std::time::Duration;
use axum::body::Body;
use axum::http::{Request, StatusCode};
use http_body_util::BodyExt;
use link_assistant_router::admin::AdminClaim;
use link_assistant_router::app_state::AppState;
use link_assistant_router::providers::ProviderStore;
use link_assistant_router::token::TokenManager;
use tower::ServiceExt;
fn state_with(
admin: Arc<AdminClaim>,
tokens: TokenManager,
data_dir: &std::path::Path,
) -> AppState {
AppState {
client: reqwest::Client::new(),
token_manager: tokens,
oauth_provider: link_assistant_router::oauth::OAuthProvider::new(
data_dir.to_str().expect("utf-8 path"),
),
account_router: None,
subscription_reader: None,
subscription_base_url: None,
subscription_readers: vec![],
model_catalogs: Arc::new(link_assistant_router::model_catalog::ModelCatalogCache::new()),
subscription_cache: Arc::new(link_assistant_router::refresh::TokenCache::new()),
upstream_base_url: "https://api.anthropic.com".to_string(),
upstream_provider: link_assistant_router::config::UpstreamProvider::Anthropic,
gonka: None,
bridge_model: None,
bridge_model_policy: link_assistant_router::bridge_selection::BridgeModelPolicy::default(),
crater: None,
openai_compatible: link_assistant_router::config::default_openai_compatible_config(),
provider_store: ProviderStore::open(data_dir, "test-secret").expect("provider store"),
logger: log_lazy::LogLazy::new(),
admin,
admin_key: None,
allow_anonymous_admin: false,
metrics: Arc::new(link_assistant_router::metrics::Metrics::default()),
audit: Arc::new(link_assistant_router::audit::AuditLog::to_path(None)),
request_log: Arc::new(link_assistant_router::request_log::RequestLog::new(
data_dir.join("requests"),
1024 * 1024,
)),
activitypub_actor_base_url: "https://router.example".to_string(),
activitypub_public_key_pem:
link_assistant_router::config::default_activitypub_public_key_pem(),
mpp: link_assistant_router::config::default_mpp_config(),
login_manager: link_assistant_router::login::LoginManager::new(
link_assistant_router::login::LoginConfig::default(),
),
github: link_assistant_router::github_proxy::GitHubProxyConfig::default(),
max_proxy_request_bytes: link_assistant_router::config::DEFAULT_MAX_PROXY_REQUEST_BYTES,
}
}
struct Harness {
state: AppState,
env_key: Option<String>,
ttl: Duration,
dir: tempfile::TempDir,
}
impl Harness {
fn new(env_key: Option<String>, ttl: Duration) -> Self {
let dir = tempfile::tempdir().expect("tempdir");
Self::boot(dir, env_key, ttl)
}
fn boot(dir: tempfile::TempDir, env_key: Option<String>, ttl: Duration) -> Self {
let store: Arc<dyn link_assistant_router::storage::TokenStore> = Arc::new(
link_assistant_router::storage::TextTokenStore::open(dir.path().join("tokens.lino"))
.expect("token store"),
);
let tokens = TokenManager::with_store("test-secret", store);
let admin = Arc::new(
AdminClaim::load(env_key.clone(), dir.path(), ttl).with_token_manager(tokens.clone()),
);
Self {
state: state_with(admin, tokens, dir.path()),
env_key,
ttl,
dir,
}
}
fn restart(self) -> Self {
let Self {
dir, env_key, ttl, ..
} = self;
Self::boot(dir, env_key, ttl)
}
async fn claim(&self, ttl_hours: Option<i64>) -> String {
let body = ttl_hours.map(|hours| serde_json::json!({"ttl_hours": hours}));
let (status, minted) = self.post("/api/admin/bootstrap", None, body).await;
assert_eq!(status, StatusCode::OK, "mint: {minted}");
let token = minted["token"].as_str().expect("token").to_string();
let (status, _) = self
.post(
"/api/admin/bootstrap/confirm",
Some(&token),
Some(serde_json::json!({"claim_id": minted["claim_id"]})),
)
.await;
assert_eq!(status, StatusCode::OK);
token
}
async fn call(&self, request: Request<Body>) -> (StatusCode, serde_json::Value) {
let response = link_assistant_router::admin_api::router(self.state.clone())
.oneshot(request)
.await
.expect("router responds");
let status = response.status();
let bytes = response
.into_body()
.collect()
.await
.expect("body")
.to_bytes();
let body = serde_json::from_slice(&bytes).unwrap_or(serde_json::Value::Null);
(status, body)
}
async fn get(&self, path: &str, token: Option<&str>) -> (StatusCode, serde_json::Value) {
self.call(build(path, "GET", token, None)).await
}
async fn post(
&self,
path: &str,
token: Option<&str>,
body: Option<serde_json::Value>,
) -> (StatusCode, serde_json::Value) {
self.call(build(path, "POST", token, body)).await
}
}
fn build(
path: &str,
method: &str,
token: Option<&str>,
body: Option<serde_json::Value>,
) -> Request<Body> {
let mut request = Request::builder().method(method).uri(path);
if let Some(token) = token {
request = request.header("authorization", format!("Bearer {token}"));
}
match body {
Some(value) => request
.header("content-type", "application/json")
.body(Body::from(value.to_string()))
.expect("request"),
None => request
.header("content-type", "application/json")
.body(Body::from("{}"))
.expect("request"),
}
}
const fn minutes(n: u64) -> Duration {
Duration::from_secs(n * 60)
}
#[tokio::test]
async fn status_reports_an_open_bootstrap_before_any_claim() {
let harness = Harness::new(None, minutes(2));
let (status, body) = harness.get("/api/admin/status", None).await;
assert_eq!(status, StatusCode::OK);
assert_eq!(body["claimed"], false);
assert_eq!(body["bootstrap_open"], true);
}
#[tokio::test]
async fn admin_api_is_closed_before_a_claim_exists() {
let harness = Harness::new(None, minutes(2));
let (status, _) = harness.get("/api/tokens/list", None).await;
assert_eq!(status, StatusCode::UNAUTHORIZED);
let (status, _) = harness.get("/api/admin/summary", None).await;
assert_eq!(status, StatusCode::UNAUTHORIZED);
}
#[tokio::test]
async fn mint_alone_does_not_close_bootstrap_or_authorise() {
let harness = Harness::new(None, minutes(2));
let (status, minted) = harness.post("/api/admin/bootstrap", None, None).await;
assert_eq!(status, StatusCode::OK);
let token = minted["token"].as_str().expect("token").to_string();
let (status, _) = harness.get("/api/tokens/list", Some(&token)).await;
assert_eq!(status, StatusCode::UNAUTHORIZED);
let (_, state) = harness.get("/api/admin/status", None).await;
assert_eq!(state["claimed"], false);
assert_eq!(state["bootstrap_open"], true);
}
#[tokio::test]
async fn confirm_activates_the_credential_and_closes_bootstrap() {
let harness = Harness::new(None, minutes(2));
let (_, minted) = harness.post("/api/admin/bootstrap", None, None).await;
let token = minted["token"].as_str().expect("token").to_string();
let claim_id = minted["claim_id"].as_str().expect("claim_id").to_string();
let (status, body) = harness
.post(
"/api/admin/bootstrap/confirm",
Some(&token),
Some(serde_json::json!({"claim_id": claim_id})),
)
.await;
assert_eq!(status, StatusCode::OK);
assert_eq!(body["claimed"], true);
let (status, _) = harness.get("/api/tokens/list", Some(&token)).await;
assert_eq!(status, StatusCode::OK);
let (status, _) = harness.post("/api/admin/bootstrap", None, None).await;
assert_eq!(status, StatusCode::CONFLICT);
}
#[tokio::test]
async fn confirm_requires_the_candidate_token_itself() {
let harness = Harness::new(None, minutes(2));
let (_, minted) = harness.post("/api/admin/bootstrap", None, None).await;
let claim_id = minted["claim_id"].as_str().expect("claim_id").to_string();
let (status, _) = harness
.post(
"/api/admin/bootstrap/confirm",
None,
Some(serde_json::json!({"claim_id": claim_id.clone()})),
)
.await;
assert_eq!(status, StatusCode::UNAUTHORIZED);
let (status, _) = harness
.post(
"/api/admin/bootstrap/confirm",
Some("la_admin_wrong"),
Some(serde_json::json!({"claim_id": claim_id})),
)
.await;
assert_eq!(status, StatusCode::UNAUTHORIZED);
let (_, state) = harness.get("/api/admin/status", None).await;
assert_eq!(state["bootstrap_open"], true);
}
#[tokio::test]
async fn only_the_first_confirmer_wins() {
let harness = Harness::new(None, minutes(2));
let (_, first) = harness.post("/api/admin/bootstrap", None, None).await;
let (_, second) = harness.post("/api/admin/bootstrap", None, None).await;
let (status, _) = harness
.post(
"/api/admin/bootstrap/confirm",
Some(first["token"].as_str().expect("token")),
Some(serde_json::json!({"claim_id": first["claim_id"]})),
)
.await;
assert_eq!(status, StatusCode::BAD_REQUEST);
let (status, _) = harness
.post(
"/api/admin/bootstrap/confirm",
Some(second["token"].as_str().expect("token")),
Some(serde_json::json!({"claim_id": second["claim_id"]})),
)
.await;
assert_eq!(status, StatusCode::OK);
}
#[tokio::test]
async fn an_expired_candidate_leaves_bootstrap_open() {
let harness = Harness::new(None, Duration::from_secs(0));
let (_, minted) = harness.post("/api/admin/bootstrap", None, None).await;
let (status, _) = harness
.post(
"/api/admin/bootstrap/confirm",
Some(minted["token"].as_str().expect("token")),
Some(serde_json::json!({"claim_id": minted["claim_id"]})),
)
.await;
assert_eq!(status, StatusCode::BAD_REQUEST);
let (_, state) = harness.get("/api/admin/status", None).await;
assert_eq!(state["bootstrap_open"], true);
let (status, _) = harness.post("/api/admin/bootstrap", None, None).await;
assert_eq!(status, StatusCode::OK);
}
#[tokio::test]
async fn an_environment_key_disables_bootstrap_entirely() {
let harness = Harness::new(Some("env-admin-key".to_string()), minutes(2));
let (_, state) = harness.get("/api/admin/status", None).await;
assert_eq!(state["claimed"], true);
assert_eq!(state["bootstrap_open"], false);
assert_eq!(state["provisioned_by_environment"], true);
let (status, _) = harness.post("/api/admin/bootstrap", None, None).await;
assert_eq!(status, StatusCode::CONFLICT);
let (status, _) = harness
.get("/api/admin/summary", Some("env-admin-key"))
.await;
assert_eq!(status, StatusCode::OK);
}
#[tokio::test]
async fn rotation_replaces_the_claimed_credential() {
let harness = Harness::new(None, minutes(2));
let (_, minted) = harness.post("/api/admin/bootstrap", None, None).await;
let old = minted["token"].as_str().expect("token").to_string();
harness
.post(
"/api/admin/bootstrap/confirm",
Some(&old),
Some(serde_json::json!({"claim_id": minted["claim_id"]})),
)
.await;
let (status, rotated) = harness.post("/api/admin/rotate", Some(&old), None).await;
assert_eq!(status, StatusCode::OK);
let new = rotated["token"].as_str().expect("token").to_string();
assert_ne!(new, old);
let (status, _) = harness.get("/api/tokens/list", Some(&new)).await;
assert_eq!(status, StatusCode::OK);
let (status, _) = harness.get("/api/tokens/list", Some(&old)).await;
assert_eq!(status, StatusCode::UNAUTHORIZED);
}
#[tokio::test]
async fn tokens_can_be_issued_listed_and_revoked_through_the_admin_port() {
let harness = Harness::new(Some("env-admin-key".to_string()), minutes(2));
let key = Some("env-admin-key");
let (status, issued) = harness
.post(
"/api/tokens",
key,
Some(serde_json::json!({"label": "ci", "ttl_hours": 1, "max_requests": 5})),
)
.await;
assert_eq!(status, StatusCode::OK);
assert!(issued["token"].as_str().is_some_and(|t| !t.is_empty()));
let (status, listed) = harness.get("/api/tokens/list", key).await;
assert_eq!(status, StatusCode::OK);
let records = listed["data"].as_array().expect("records");
assert_eq!(records.len(), 1);
let id = records[0]["id"].as_str().expect("id").to_string();
assert_eq!(records[0]["label"], "ci");
let (status, _) = harness
.post(
"/api/tokens/revoke",
key,
Some(serde_json::json!({"id": id})),
)
.await;
assert_eq!(status, StatusCode::OK);
let (_, listed) = harness.get("/api/tokens/list", key).await;
assert_eq!(listed["data"][0]["revoked"], true);
}
#[tokio::test]
async fn the_ui_is_served_from_the_embedded_bundle() {
let harness = Harness::new(None, minutes(2));
let response = link_assistant_router::admin_api::router(harness.state.clone())
.oneshot(build("/", "GET", None, None))
.await
.expect("router responds");
assert_eq!(response.status(), StatusCode::OK);
assert_eq!(
response
.headers()
.get("content-type")
.and_then(|value| value.to_str().ok()),
Some("text/html; charset=utf-8")
);
let body = response
.into_body()
.collect()
.await
.expect("body")
.to_bytes();
let html = String::from_utf8_lossy(&body);
assert!(html.contains("<div id=\"root\">"), "index.html is served");
}
#[tokio::test]
async fn unknown_api_paths_do_not_fall_back_to_the_app_shell() {
let harness = Harness::new(None, minutes(2));
let response = link_assistant_router::admin_api::router(harness.state.clone())
.oneshot(build("/api/does-not-exist", "GET", None, None))
.await
.expect("router responds");
assert_eq!(response.status(), StatusCode::NOT_FOUND);
}
#[tokio::test]
async fn the_first_visitor_receives_an_admin_scoped_jwt() {
let harness = Harness::new(None, minutes(2));
let token = harness.claim(None).await;
assert!(
token.starts_with(link_assistant_router::token::TOKEN_PREFIX),
"the web claim mints the same credential model as everything else: {token}"
);
let claims = harness
.state
.token_manager
.validate_admin_token(&token)
.expect("an admin-scoped JWT");
assert!(!claims.sub.is_empty(), "the credential has an identity");
assert_eq!(claims.scope, link_assistant_router::token::ADMIN_SCOPE);
assert!(claims.exp > claims.iat, "and a lifetime");
assert_eq!(claims.label, "first-visitor-admin");
let (_, status) = harness.get("/api/admin/status", None).await;
assert_eq!(status["credential_kind"], "jwt");
assert_eq!(status["token_id"], claims.sub);
}
#[tokio::test]
async fn the_first_administrator_may_limit_the_credential_lifetime() {
let harness = Harness::new(None, minutes(2));
let (_, minted) = harness
.post(
"/api/admin/bootstrap",
None,
Some(serde_json::json!({"ttl_hours": 3})),
)
.await;
assert_eq!(minted["ttl_hours"], 3);
let token = minted["token"].as_str().expect("token").to_string();
harness
.post(
"/api/admin/bootstrap/confirm",
Some(&token),
Some(serde_json::json!({"claim_id": minted["claim_id"]})),
)
.await;
let claims = harness
.state
.token_manager
.validate_admin_token(&token)
.expect("claims");
assert_eq!(
claims.exp - claims.iat,
3 * 3600,
"the chosen TTL is honoured"
);
}
#[tokio::test]
async fn claiming_retires_the_startup_bootstrap_credential() {
let harness = Harness::new(None, minutes(2));
let bootstrap = harness
.state
.token_manager
.issue_admin_token(24, "bootstrap-admin")
.expect("bootstrap token");
let (status, _) = harness.get("/api/tokens/list", Some(&bootstrap)).await;
assert_eq!(status, StatusCode::OK, "it administers the router");
let claimed = harness.claim(None).await;
let (status, _) = harness.get("/api/tokens/list", Some(&bootstrap)).await;
assert_eq!(status, StatusCode::UNAUTHORIZED);
let (_, listed) = harness.get("/api/tokens/list", Some(&claimed)).await;
let records = listed["data"].as_array().expect("records");
let retired = records
.iter()
.find(|record| record["label"] == "bootstrap-admin")
.expect("the startup token is still listed");
assert_eq!(retired["revoked"], true, "and shown as revoked: {retired}");
}
#[tokio::test]
async fn a_claimed_credential_survives_a_restart() {
let harness = Harness::new(None, minutes(2));
let token = harness.claim(None).await;
let harness = harness.restart();
let (_, status) = harness.get("/api/admin/status", None).await;
assert_eq!(status["claimed"], true);
assert_eq!(status["bootstrap_open"], false);
let (status, _) = harness.get("/api/tokens/list", Some(&token)).await;
assert_eq!(
status,
StatusCode::OK,
"the credential the operator stored still works after a restart"
);
}
#[tokio::test]
async fn rotation_is_atomic_across_a_restart() {
let harness = Harness::new(None, minutes(2));
let old = harness.claim(None).await;
let (_, before) = harness.get("/api/admin/status", None).await;
let (status, rotated) = harness.post("/api/admin/rotate", Some(&old), None).await;
assert_eq!(status, StatusCode::OK);
let new = rotated["token"].as_str().expect("token").to_string();
assert_eq!(rotated["credential_kind"], "jwt");
assert_ne!(
rotated["token_id"], before["token_id"],
"rotation mints a new identity"
);
let harness = harness.restart();
let (status, _) = harness.get("/api/tokens/list", Some(&new)).await;
assert_eq!(status, StatusCode::OK);
let (status, _) = harness.get("/api/tokens/list", Some(&old)).await;
assert_eq!(status, StatusCode::UNAUTHORIZED);
}
#[tokio::test]
async fn an_expired_credential_stops_administering() {
let harness = Harness::new(None, minutes(2));
harness.claim(Some(1)).await;
let stale = harness
.state
.token_manager
.issue_admin_token(-1, "aged-admin")
.expect("issue");
let id = harness
.state
.token_manager
.list_tokens()
.expect("list")
.into_iter()
.find(|record| record.label == "aged-admin")
.expect("record")
.id;
std::fs::write(
harness
.dir
.path()
.join(link_assistant_router::admin::CLAIM_FILE_NAME),
serde_json::json!({"token_id": id, "ttl_hours": 1, "claimed_at": 1}).to_string(),
)
.expect("write claim");
let harness = harness.restart();
let (_, status) = harness.get("/api/admin/status", None).await;
assert_eq!(status["claimed"], true, "the claim is still on record");
let (status, _) = harness.get("/api/tokens/list", Some(&stale)).await;
assert_eq!(
status,
StatusCode::UNAUTHORIZED,
"expiry is enforced on the admin surface like any other token"
);
}