use std::sync::Arc;
use aion_core::{AssistantSessionId, AssistantSessionState};
use aion_store::InMemoryStore;
use aion_store::assistant::AssistantSessionStore;
use crate::assistant::sessions::fixture::{
OPERATOR, open_session, registry, registry_over, shared_context,
};
use crate::assistant::sessions::registry::AssistantSessions;
use crate::assistant::sessions::token::{self, MintedSessionToken, SESSION_ID_HEADER};
use super::caller::{AssistantMcpAuthError, resolve};
type TestResult = Result<(), String>;
async fn session_with_bearer(
sessions: &AssistantSessions,
store: &dyn AssistantSessionStore,
) -> Result<(AssistantSessionId, MintedSessionToken), String> {
let summary = open_session(sessions).await?;
let minted = MintedSessionToken::mint();
let mut record = store
.get_assistant_session(&summary.session_id)
.await
.map_err(|error| error.to_string())?
.ok_or_else(|| "the created session has a record".to_owned())?;
record.mcp_token_digest = Some(minted.digest().to_owned());
store
.put_assistant_session(record)
.await
.map_err(|error| error.to_string())?;
Ok((summary.session_id, minted))
}
fn presented(session_id: AssistantSessionId, secret: &str) -> Vec<(String, String)> {
vec![
(
"authorization".to_owned(),
token::authorization_value(secret),
),
(
SESSION_ID_HEADER.to_owned(),
token::session_header_value(session_id),
),
]
}
#[tokio::test]
async fn a_live_sessions_own_bearer_is_admitted_for_that_session() -> TestResult {
let (sessions, store) = registry();
let (session_id, minted) = session_with_bearer(&sessions, store.as_ref()).await?;
let caller = resolve(&sessions, &presented(session_id, minted.secret()))
.await
.map_err(|error| format!("a live session's own bearer must be admitted: {error}"))?;
assert_eq!(caller.session_id(), session_id);
Ok(())
}
#[tokio::test]
async fn a_bearer_stops_being_honoured_the_moment_its_session_ends() -> TestResult {
let (sessions, store) = registry();
let (session_id, minted) = session_with_bearer(&sessions, store.as_ref()).await?;
resolve(&sessions, &presented(session_id, minted.secret()))
.await
.map_err(|error| format!("the bearer must be good before the session ends: {error}"))?;
sessions
.delete(OPERATOR, session_id)
.await
.map_err(|error| error.to_string())?;
let (state, _reason) = sessions
.state_of_session(session_id)
.await
.map_err(|error| error.to_string())?;
assert_eq!(state, AssistantSessionState::Ended);
match resolve(&sessions, &presented(session_id, minted.secret())).await {
Err(AssistantMcpAuthError::SessionEnded { session_id: named }) => {
assert_eq!(named, session_id);
Ok(())
}
other => Err(format!(
"an ended session's bearer must be refused however unexpired it is, got {other:?}"
)),
}
}
#[tokio::test]
async fn one_sessions_bearer_does_not_authorize_another_session() -> TestResult {
let (sessions, store) = registry();
let (first, first_token) = session_with_bearer(&sessions, store.as_ref()).await?;
let (second, second_token) = session_with_bearer(&sessions, store.as_ref()).await?;
assert_ne!(first, second);
match resolve(&sessions, &presented(second, first_token.secret())).await {
Err(AssistantMcpAuthError::WrongToken { session_id }) => assert_eq!(session_id, second),
other => {
return Err(format!(
"session A's bearer against session B must be refused, got {other:?}"
));
}
}
match resolve(&sessions, &presented(first, second_token.secret())).await {
Err(AssistantMcpAuthError::WrongToken { session_id }) => assert_eq!(session_id, first),
other => {
return Err(format!(
"session B's bearer against session A must be refused, got {other:?}"
));
}
}
Ok(())
}
#[tokio::test]
async fn a_bearer_survives_the_restart_of_the_process_that_minted_it() -> TestResult {
let store = Arc::new(InMemoryStore::default());
let minting = registry_over(Arc::clone(&store) as Arc<dyn AssistantSessionStore>);
let (session_id, minted) = session_with_bearer(&minting, store.as_ref()).await?;
let (state, _reason) = minting
.state_of_session(session_id)
.await
.map_err(|error| error.to_string())?;
assert!(
state.is_continuable(),
"the fixture session must still be continuable for this pin to be about the restart"
);
drop(minting);
let after_restart = registry_over(Arc::clone(&store) as Arc<dyn AssistantSessionStore>);
let caller = resolve(&after_restart, &presented(session_id, minted.secret()))
.await
.map_err(|error| {
format!("a bearer minted before a restart must still be honoured after one: {error}")
})?;
assert_eq!(caller.session_id(), session_id);
Ok(())
}
#[tokio::test]
async fn a_human_token_does_not_authorize_the_assistant_route() -> TestResult {
let (sessions, store) = registry();
let (session_id, _minted) = session_with_bearer(&sessions, store.as_ref()).await?;
match resolve(
&sessions,
&presented(session_id, "an-operator-bearer-token"),
)
.await
{
Err(AssistantMcpAuthError::WrongToken { .. }) => {}
other => {
return Err(format!(
"a human token must not authorize a session's context, got {other:?}"
));
}
}
match resolve(
&sessions,
&[(
"authorization".to_owned(),
"Bearer an-operator-bearer-token".to_owned(),
)],
)
.await
{
Err(AssistantMcpAuthError::NoSession { header }) => {
assert_eq!(header, SESSION_ID_HEADER);
Ok(())
}
other => Err(format!(
"a call naming no session must be refused, got {other:?}"
)),
}
}
#[tokio::test]
async fn one_sessions_credential_never_reads_another_sessions_context() -> TestResult {
let (sessions, store) = registry();
let (first, first_token) = session_with_bearer(&sessions, store.as_ref()).await?;
let (second, _second_token) = session_with_bearer(&sessions, store.as_ref()).await?;
shared_context(&sessions, first, "/studio/a.awl").await?;
shared_context(&sessions, second, "/runs/b").await?;
let caller = resolve(&sessions, &presented(first, first_token.secret()))
.await
.map_err(|error| error.to_string())?;
let answered = super::context_tool::assistant_context(&sessions, &caller)
.await
.map_err(|failure| failure.to_string())?;
assert_eq!(
answered.structured["url"],
serde_json::json!("/studio/a.awl")
);
assert_ne!(
answered.structured["url"],
serde_json::json!("/runs/b"),
"session A's credential must never read session B's screen"
);
Ok(())
}
#[tokio::test]
async fn a_session_with_no_minted_bearer_admits_nothing() -> TestResult {
let (sessions, _store) = registry();
let summary = open_session(&sessions).await?;
match resolve(&sessions, &presented(summary.session_id, "anything")).await {
Err(AssistantMcpAuthError::NoTokenMinted { session_id }) => {
assert_eq!(session_id, summary.session_id);
Ok(())
}
other => Err(format!(
"a session with no stored digest must admit nothing, got {other:?}"
)),
}
}
#[tokio::test]
async fn a_bearer_naming_a_session_that_does_not_exist_is_refused() -> TestResult {
let (sessions, _store) = registry();
let absent = AssistantSessionId::new_v4();
match resolve(&sessions, &presented(absent, "anything")).await {
Err(AssistantMcpAuthError::UnknownSession { session_id }) => {
assert_eq!(session_id, absent);
Ok(())
}
other => Err(format!(
"a bearer naming no session must be refused, got {other:?}"
)),
}
}