use crate::app::SessionManager;
use crate::app::session::SessionError;
use crate::domain::session::{Session, SessionHandles};
impl SessionManager {
pub(crate) fn session_index_or_err(&self, session_id: &str) -> Result<usize, SessionError> {
self.session_index_for_id(session_id)
.ok_or(SessionError::NotFound)
}
pub(crate) fn session_or_err(&self, session_id: &str) -> Result<&Session, SessionError> {
let session_index = self.session_index_or_err(session_id)?;
self.session_at(session_index).ok_or(SessionError::NotFound)
}
pub(crate) fn session_handles_or_err(
&self,
session_id: &str,
) -> Result<&SessionHandles, SessionError> {
self.session_handles()
.get(session_id)
.ok_or(SessionError::HandlesNotFound)
}
pub(crate) fn session_and_handles_or_err(
&self,
session_id: &str,
) -> Result<(&Session, &SessionHandles), SessionError> {
let session = self.session_or_err(session_id)?;
let handles = self.session_handles_or_err(session_id)?;
Ok((session, handles))
}
}
#[cfg(test)]
mod tests {
use std::collections::HashMap;
use std::path::PathBuf;
use std::sync::Arc;
use std::time::{Instant, SystemTime};
use ratatui::widgets::TableState;
use crate::app::session::{Clock, SessionDefaults, SessionError};
use crate::app::{SessionManager, SessionState};
use crate::domain::agent::{AgentKind, AgentModel};
use crate::domain::session::{
Session, SessionHandles, SessionId, SessionSize, SessionStats, Status,
};
use crate::infra::git;
struct FixedClock;
impl Clock for FixedClock {
fn now_instant(&self) -> Instant {
Instant::now()
}
fn now_system_time(&self) -> SystemTime {
SystemTime::UNIX_EPOCH
}
}
fn test_session(session_id: &str, status: Status) -> Session {
Session {
base_branch: "main".to_string(),
created_at: 0,
draft_attachments: Vec::new(),
folder: PathBuf::from("/tmp/test"),
follow_up_tasks: Vec::new(),
id: session_id.into(),
in_progress_started_at: None,
in_progress_total_seconds: 0,
is_draft: false,
model: AgentModel::Gemini3FlashPreview,
output: String::new(),
project_name: "project".to_string(),
prompt: String::new(),
queued_messages: Vec::new(),
reasoning_level_override: None,
published_upstream_ref: None,
published_branch_sync_status: crate::domain::session::PublishedBranchSyncStatus::Idle,
questions: Vec::new(),
review_request: None,
size: SessionSize::Xs,
stats: SessionStats::default(),
status,
summary: None,
title: None,
updated_at: 0,
workflow_notice: None,
}
}
fn session_manager_with(
sessions: Vec<Session>,
handles: HashMap<SessionId, SessionHandles>,
) -> SessionManager {
SessionManager::new(
SessionDefaults {
model: AgentKind::Gemini.default_model(),
},
Arc::new(git::MockGitClient::new()),
SessionState::new(
handles,
sessions,
TableState::default(),
Arc::new(FixedClock),
0,
0,
),
Vec::new(),
)
}
#[test]
fn test_session_index_or_err_returns_index_for_existing_session() {
let session = test_session("sess-1", Status::Review);
let manager = session_manager_with(vec![session], HashMap::new());
let index = manager
.session_index_or_err("sess-1")
.expect("session should be found");
assert_eq!(index, 0);
}
#[test]
fn test_session_index_or_err_returns_correct_index_for_second_session() {
let session_a = test_session("sess-a", Status::Review);
let session_b = test_session("sess-b", Status::Draft);
let manager = session_manager_with(vec![session_a, session_b], HashMap::new());
let index = manager
.session_index_or_err("sess-b")
.expect("session should be found");
assert_eq!(index, 1);
}
#[test]
fn test_session_index_or_err_returns_not_found_for_missing_session() {
let manager = session_manager_with(Vec::new(), HashMap::new());
let result = manager.session_index_or_err("nonexistent");
assert!(matches!(result, Err(SessionError::NotFound)));
}
#[test]
fn test_session_or_err_returns_session_reference() {
let session = test_session("sess-1", Status::InProgress);
let manager = session_manager_with(vec![session], HashMap::new());
let found = manager
.session_or_err("sess-1")
.expect("session should be found");
assert_eq!(found.id, "sess-1");
assert_eq!(found.status, Status::InProgress);
}
#[test]
fn test_session_or_err_returns_not_found_for_missing_session() {
let manager = session_manager_with(Vec::new(), HashMap::new());
let result = manager.session_or_err("missing");
assert!(matches!(result, Err(SessionError::NotFound)));
}
#[test]
fn test_session_handles_or_err_returns_handles() {
let mut handles = HashMap::new();
handles.insert(
"sess-1".into(),
SessionHandles::new(String::new(), Status::Review),
);
let manager = session_manager_with(Vec::new(), handles);
let result = manager.session_handles_or_err("sess-1");
assert!(result.is_ok());
}
#[test]
fn test_session_handles_or_err_returns_handles_not_found() {
let manager = session_manager_with(Vec::new(), HashMap::new());
let result = manager.session_handles_or_err("missing");
assert!(matches!(result, Err(SessionError::HandlesNotFound)));
}
#[test]
fn test_session_and_handles_returns_both() {
let session = test_session("sess-1", Status::Review);
let mut handles = HashMap::new();
handles.insert(
"sess-1".into(),
SessionHandles::new("output".to_string(), Status::Review),
);
let manager = session_manager_with(vec![session], handles);
let result = manager.session_and_handles_or_err("sess-1");
assert!(result.is_ok());
if let Ok((found_session, found_handles)) = result {
assert_eq!(found_session.id, "sess-1");
let output = found_handles
.output
.lock()
.expect("failed to lock output")
.clone();
assert_eq!(output, "output");
}
}
#[test]
fn test_session_and_handles_fails_when_session_missing() {
let mut handles = HashMap::new();
handles.insert(
"sess-1".into(),
SessionHandles::new(String::new(), Status::Review),
);
let manager = session_manager_with(Vec::new(), handles);
let result = manager.session_and_handles_or_err("sess-1");
assert!(matches!(result, Err(SessionError::NotFound)));
}
#[test]
fn test_session_and_handles_fails_when_handles_missing() {
let session = test_session("sess-1", Status::Review);
let manager = session_manager_with(vec![session], HashMap::new());
let result = manager.session_and_handles_or_err("sess-1");
assert!(matches!(result, Err(SessionError::HandlesNotFound)));
}
}