use std::sync::Arc;
use adk_core::{Agent, Content, EventStream, InvocationContext, Part, Result, SessionId, UserId};
use adk_runner::{Runner, RunnerConfig};
use adk_session::{Event, Events, GetRequest, Session, SessionService, State};
use async_trait::async_trait;
use futures::StreamExt;
use proptest::prelude::*;
struct MockAgent;
#[async_trait]
impl Agent for MockAgent {
fn name(&self) -> &str {
"test-agent"
}
fn description(&self) -> &str {
"mock"
}
fn sub_agents(&self) -> &[Arc<dyn Agent>] {
&[]
}
async fn run(&self, _ctx: Arc<dyn InvocationContext>) -> Result<EventStream> {
Ok(Box::pin(futures::stream::empty()))
}
}
struct MockEvents;
impl Events for MockEvents {
fn all(&self) -> Vec<Event> {
vec![]
}
fn len(&self) -> usize {
0
}
fn at(&self, _index: usize) -> Option<&Event> {
None
}
}
struct MockState;
impl adk_session::ReadonlyState for MockState {
fn get(&self, _key: &str) -> Option<serde_json::Value> {
None
}
fn all(&self) -> std::collections::HashMap<String, serde_json::Value> {
std::collections::HashMap::new()
}
}
impl State for MockState {
fn get(&self, _key: &str) -> Option<serde_json::Value> {
None
}
fn set(&mut self, _key: String, _value: serde_json::Value) {}
fn all(&self) -> std::collections::HashMap<String, serde_json::Value> {
std::collections::HashMap::new()
}
}
struct MockSession {
id: String,
}
impl Session for MockSession {
fn id(&self) -> &str {
&self.id
}
fn app_name(&self) -> &str {
"test-app"
}
fn user_id(&self) -> &str {
"user-1"
}
fn state(&self) -> &dyn State {
&MockState
}
fn events(&self) -> &dyn Events {
&MockEvents
}
fn last_update_time(&self) -> chrono::DateTime<chrono::Utc> {
chrono::Utc::now()
}
}
struct MockSessionService;
#[async_trait]
impl SessionService for MockSessionService {
async fn create(&self, _req: adk_session::CreateRequest) -> Result<Box<dyn Session>> {
unimplemented!()
}
async fn get(&self, req: GetRequest) -> Result<Box<dyn Session>> {
Ok(Box::new(MockSession { id: req.session_id }))
}
async fn list(&self, _req: adk_session::ListRequest) -> Result<Vec<Box<dyn Session>>> {
Ok(vec![])
}
async fn delete(&self, _req: adk_session::DeleteRequest) -> Result<()> {
Ok(())
}
async fn append_event(&self, _session_id: &str, _event: Event) -> Result<()> {
Ok(())
}
}
fn make_runner() -> Runner {
Runner::new(RunnerConfig {
app_name: "test-app".to_string(),
agent: Arc::new(MockAgent),
session_service: Arc::new(MockSessionService),
artifact_service: None,
memory_service: None,
plugin_manager: None,
run_config: None,
compaction_config: None,
context_cache_config: None,
cache_capable: None,
request_context: None,
cancellation_token: None,
})
.unwrap()
}
proptest! {
#![proptest_config(ProptestConfig::with_cases(100))]
#[test]
fn prop_runner_run_accepts_typed_ids(
user_id_str in "[a-zA-Z0-9_-]{1,50}",
session_id_str in "[a-zA-Z0-9_-]{1,50}",
) {
let user_id = UserId::new(&user_id_str).expect("valid user_id should parse");
let session_id = SessionId::new(&session_id_str).expect("valid session_id should parse");
prop_assert_eq!(user_id.as_ref(), user_id_str.as_str());
prop_assert_eq!(session_id.as_ref(), session_id_str.as_str());
}
}
#[tokio::test]
async fn bug_condition_runner_run_accepts_typed_ids() {
let runner = make_runner();
let user_id = UserId::new("user123").unwrap();
let session_id = SessionId::new("session456").unwrap();
let content =
Content { role: "user".to_string(), parts: vec![Part::Text { text: "Hello".to_string() }] };
let result = runner.run(user_id, session_id, content).await;
assert!(result.is_ok(), "Runner::run() with typed IDs should succeed");
let mut stream = result.unwrap();
while let Some(event_result) = stream.next().await {
assert!(event_result.is_ok());
}
}
#[test]
fn bug_condition_empty_user_id_rejected_at_boundary() {
let result = UserId::new("");
assert!(result.is_err(), "empty UserId should be rejected by new()");
}
#[test]
fn bug_condition_empty_session_id_rejected_at_boundary() {
let result = SessionId::new("");
assert!(result.is_err(), "empty SessionId should be rejected by new()");
}