use axum::{
Json, Router,
extract::State,
http::{HeaderMap, StatusCode},
response::IntoResponse,
routing::{get, post},
};
use serde_json::{Value, json};
use std::{
path::PathBuf,
process::Output,
sync::{Arc, Mutex},
};
use tokio::process::Command;
const ENVIRONMENT: &str = "01900000-0000-7000-8000-000000000001";
struct Home(PathBuf);
impl Drop for Home {
fn drop(&mut self) {
let _ = std::fs::remove_dir_all(&self.0);
}
}
fn session(actor: &str, kind: &str, org: Option<&str>, token: &str) -> Value {
json!({"tokens":{"access_token":token,"refresh_token":"ort_fixture","token_type":"Bearer",
"expires_in":3600,"scopes":[],"actor":{"type":kind,"id":actor},"org_id":org},
"expires_at":4_000_000_000u64})
}
type Requests = Arc<Mutex<Vec<(String, Option<String>, Option<String>)>>>;
async fn status(State(requests): State<Requests>, headers: HeaderMap) -> axum::response::Response {
let org = headers.get("x-org-id").and_then(|v| v.to_str().ok());
requests.lock().unwrap().push((
"status".into(),
org.map(str::to_owned),
headers
.get("x-hook-test-key")
.and_then(|v| v.to_str().ok())
.map(str::to_owned),
));
let status = match headers.get("authorization").and_then(|v| v.to_str().ok()) {
Some("Bearer revoked") => StatusCode::UNAUTHORIZED,
Some("Bearer forbidden") => StatusCode::FORBIDDEN,
Some("Bearer unavailable") => StatusCode::SERVICE_UNAVAILABLE,
_ if org.is_none() => StatusCode::BAD_REQUEST,
_ => StatusCode::OK,
};
if status != StatusCode::OK {
return (
status,
Json(json!({"error":{"code":"fixture_error","message":"status failed"}})),
)
.into_response();
}
Json(json!({"authenticated":true,"actor":{"type":"silicon","id":"testsi:tos"},"org_id":org}))
.into_response()
}
async fn run(
mut profile: Value,
args: &[&str],
) -> (Output, Vec<(String, Option<String>, Option<String>)>) {
let requests: Requests = Arc::default();
let app =
Router::new()
.route(
"/api/version",
get(|| async {
Json(json!({"service":"silicon-hook","selected_api_version":"v1"}))
}),
)
.route("/api/v1/auth/status", get(status))
.route(
"/api/v1/auth/refresh",
post(
|State(requests): State<Requests>,
headers: HeaderMap,
Json(body): Json<Value>| async move {
requests.lock().unwrap().push((
"refresh".into(),
headers
.get("x-org-id")
.and_then(|v| v.to_str().ok())
.map(str::to_owned),
None,
));
let mut tokens =
session("testsi:tos", "silicon", None, "oat_refreshed")["tokens"]
.clone();
tokens["refresh_token"] =
json!(format!("{}_next", body["refresh_token"].as_str().unwrap()));
Json(tokens)
},
),
)
.with_state(requests.clone());
let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
profile["url"] = json!(format!("http://{}", listener.local_addr().unwrap()));
profile["telemetry"] = json!(false);
let server = tokio::spawn(async move { axum::serve(listener, app).await.unwrap() });
let home = Home(std::env::temp_dir().join(format!("hook-status-{}", uuid::Uuid::new_v4())));
let state = home.0.join(".silicon-hook");
std::fs::create_dir_all(&state).unwrap();
std::fs::write(
state.join("state.json"),
json!({"profiles":{"default":profile}}).to_string(),
)
.unwrap();
let output = Command::new(env!("CARGO_BIN_EXE_hook"))
.env("SILICON_HOME", &home.0)
.env_remove("SILICON_HOOK_HOME")
.env_remove("SILICON_HOOK_URL")
.env_remove("SILICON_HOOK_ORG")
.env("SILICON_HOOK_TELEMETRY", "off")
.args(["login", "status", "--json"])
.args(args)
.output()
.await
.unwrap();
server.abort();
let requests = requests.lock().unwrap().clone();
(output, requests)
}
fn authenticated(output: &Output, expected: bool) {
assert!(
output.status.success(),
"{}",
String::from_utf8_lossy(&output.stderr)
);
let body: Value = serde_json::from_slice(&output.stdout).unwrap();
assert_eq!(body["authenticated"], expected);
assert!(body.get("access_token").is_none());
}
#[tokio::test]
async fn unscoped_silicon_status_resolves_its_organization_and_checks_online() {
let profile = json!({"session":session("testsi:tos","silicon",None,"oat_fixture")});
let (output, requests) = run(profile.clone(), &[]).await;
authenticated(&output, true);
assert_eq!(requests, vec![("status".into(), Some("tos".into()), None)]);
let (output, requests) = run(profile, &["--org", "chosen"]).await;
authenticated(&output, true);
assert_eq!(requests[0].1.as_deref(), Some("chosen"));
}
#[tokio::test]
async fn token_org_and_profile_selection_take_precedence_over_identity() {
let profile = json!({"session":session("alice","carbon",Some("token-org"),"oat_fixture")});
let (output, requests) = run(profile.clone(), &[]).await;
authenticated(&output, true);
assert_eq!(requests[0].1.as_deref(), Some("token-org"));
let mut profile = profile;
profile["org"] = json!("selected-org");
let (output, requests) = run(profile, &[]).await;
authenticated(&output, true);
assert_eq!(requests[0].1.as_deref(), Some("selected-org"));
}
#[tokio::test]
async fn test_status_uses_only_the_selected_test_sessions_context() {
let profile = json!({"org":"production-org","session":session("live:production-org","silicon",None,"oat_production"),
"selected_test":ENVIRONMENT,"test_keys":{ENVIRONMENT:"ABCDEFGHIJKLMNOPQRSTUVWXYZ123456"},
"test_sessions":{ENVIRONMENT:session("testsi:testing-org","silicon",None,"oat_test")}});
let (output, requests) = run(profile, &[]).await;
authenticated(&output, true);
assert_eq!(
requests,
vec![(
"status".into(),
Some("testing-org".into()),
Some("ABCDEFGHIJKLMNOPQRSTUVWXYZ123456".into())
)]
);
}
#[tokio::test]
async fn refresh_keeps_the_inferred_organization_for_the_following_status() {
let mut saved = session("testsi:tos", "silicon", None, "oat_expired");
saved["expires_at"] = json!(0);
let (output, requests) = run(json!({"session":saved}), &[]).await;
authenticated(&output, true);
assert_eq!(
requests,
vec![
("refresh".into(), Some("tos".into()), None),
("status".into(), Some("tos".into()), None)
]
);
}
#[tokio::test]
async fn old_or_delayed_pending_refresh_is_replayed_then_renewed_before_status() {
for started in [Value::Null, json!(1)] {
let mut saved = session("testsi:tos", "silicon", None, "oat_expired");
saved["expires_at"] = json!(0);
saved["pending_refresh_key"] = json!("original-refresh-attempt");
saved["refresh_started_at"] = started;
let (output, requests) = run(json!({"session":saved}), &[]).await;
authenticated(&output, true);
assert_eq!(
requests
.iter()
.map(|request| request.0.as_str())
.collect::<Vec<_>>(),
["refresh", "refresh", "status"]
);
}
}
#[tokio::test]
async fn absent_and_revoked_sessions_are_false_but_permission_and_service_errors_fail() {
let (output, requests) = run(json!({}), &[]).await;
authenticated(&output, false);
assert!(requests.is_empty());
let (output, requests) = run(
json!({"session":session("testsi:tos","silicon",None,"revoked")}),
&[],
)
.await;
authenticated(&output, false);
assert_eq!(requests.len(), 1);
for token in ["forbidden", "unavailable"] {
let (output, requests) = run(
json!({"session":session("testsi:tos","silicon",None,token)}),
&[],
)
.await;
assert!(!output.status.success());
assert!(output.stdout.is_empty());
assert_eq!(requests.len(), 1);
}
}