mod common;
use std::collections::HashMap;
use std::sync::Arc;
use std::time::Duration;
use serde_json::{json, Value};
use tokio::sync::mpsc::{unbounded_channel, UnboundedReceiver};
use smooth_operator::access_control::AccessContext;
use smooth_operator::adapter::StorageAdapter;
use smooth_operator::domain::ParticipantType;
use smooth_operator::widget_auth::{AgentWidgetAuth, StaticWidgetAuth};
use smooth_operator_adapter_memory::InMemoryStorageAdapter;
use smooth_operator_server::config::{ServerConfig, StorageBackend};
use smooth_operator_server::handler;
use smooth_operator_server::server::SEED_ORG_ID;
use smooth_operator_server::state::AppState;
fn base_config() -> ServerConfig {
ServerConfig {
bind: "127.0.0.1".into(),
port: 0,
gateway_url: "https://example.invalid/v1".into(),
gateway_key: None,
model: "claude-haiku-4-5".into(),
seed_kb: false,
max_iterations: 4,
max_tokens: 128,
storage: StorageBackend::Memory,
widget_auth_strict: false,
confirm_tools: Vec::new(),
judge_model: "claude-haiku-4-5".to_string(),
}
}
async fn create_session(
state: &AppState,
auth_org: Option<&str>,
origin: Option<&str>,
frame: &Value,
) -> String {
let (tx, mut rx) = unbounded_channel::<Value>();
handler::handle_frame(
state,
&AccessContext::anonymous(),
"conn-test",
origin,
auth_org,
&frame.to_string(),
&tx,
)
.await;
recv_conversation_id(&mut rx).await
}
async fn recv_conversation_id(rx: &mut UnboundedReceiver<Value>) -> String {
let ev = tokio::time::timeout(Duration::from_secs(5), rx.recv())
.await
.expect("create-session should emit an event")
.expect("sink open");
assert_eq!(ev["type"], "immediate_response", "got: {ev}");
assert_eq!(ev["status"], 200, "got: {ev}");
ev["data"]["conversationId"]
.as_str()
.expect("conversationId")
.to_string()
}
#[tokio::test]
async fn authed_principal_org_and_payload_agent_carry_through() {
let storage = Arc::new(InMemoryStorageAdapter::new());
let state = AppState::new(storage.clone(), base_config());
let frame = json!({
"action": "create_conversation_session",
"requestId": "cs-1",
"agentId": "agent-Y",
"userName": "Authed User",
});
let conversation_id = create_session(&state, Some("org-X"), None, &frame).await;
let conv = storage
.get_conversation(&conversation_id)
.await
.expect("get conversation")
.expect("conversation persisted");
assert_eq!(conv.organization_id, "org-X");
assert_ne!(conv.organization_id, SEED_ORG_ID);
let participants = storage
.list_participants_by_conversation(&conversation_id)
.await
.expect("list participants");
assert_eq!(participants.len(), 2, "user + agent participants");
for p in &participants {
assert_eq!(p.organization_id, "org-X", "participant org");
}
let agent = participants
.iter()
.find(|p| p.participant_type == ParticipantType::AiAgent)
.expect("agent participant");
assert_eq!(agent.internal_id.as_deref(), Some("agent-Y"));
let sessions = storage
.list_sessions_by_conversation(&conversation_id)
.await
.expect("list sessions");
assert_eq!(sessions.len(), 1);
assert_eq!(sessions[0].agent_id, "agent-Y");
}
#[tokio::test]
async fn widget_policy_org_wins_over_connection_auth_org() {
let storage = Arc::new(InMemoryStorageAdapter::new());
let mut rows = HashMap::new();
rows.insert(
"agent-W".to_string(),
AgentWidgetAuth {
allowed_origins: vec!["*".to_string()],
public_key: None,
organization_id: Some("org-from-widget".to_string()),
},
);
let state = AppState::new(storage.clone(), base_config())
.with_widget_auth(Arc::new(StaticWidgetAuth::new(rows)));
let frame = json!({
"action": "create_conversation_session",
"requestId": "cs-2",
"agentId": "agent-W",
});
let conversation_id = create_session(
&state,
Some("org-from-jwt"),
Some("https://embed.example"),
&frame,
)
.await;
let conv = storage
.get_conversation(&conversation_id)
.await
.expect("get conversation")
.expect("conversation persisted");
assert_eq!(conv.organization_id, "org-from-widget");
}
#[tokio::test]
async fn no_auth_no_widget_org_falls_back_to_seed() {
let storage = Arc::new(InMemoryStorageAdapter::new());
let state = AppState::new(storage.clone(), base_config());
let frame = json!({
"action": "create_conversation_session",
"requestId": "cs-3",
"agentId": "agent-Z",
});
let conversation_id = create_session(&state, None, None, &frame).await;
let conv = storage
.get_conversation(&conversation_id)
.await
.expect("get conversation")
.expect("conversation persisted");
assert_eq!(conv.organization_id, SEED_ORG_ID);
}