use crate::commands::Command;
use crate::error::{CliError, CliResult};
use serde::{Deserialize, Serialize};
use std::fs;
use std::path::PathBuf;
#[derive(Debug, Clone)]
pub enum SessionsAction {
List,
Create { name: String },
Delete { id: String },
Rename { id: String, name: String },
Switch { id: String },
Info { id: String },
Share {
expires_in: Option<u64>,
no_history: bool,
no_context: bool,
},
ShareList,
ShareRevoke { share_id: String },
ShareInfo { share_id: String },
ShareView { share_id: String },
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SessionInfo {
pub id: String,
pub name: String,
pub created_at: u64,
pub modified_at: u64,
pub message_count: usize,
}
pub struct SessionsCommand {
action: SessionsAction,
}
impl SessionsCommand {
pub fn new(action: SessionsAction) -> Self {
Self { action }
}
fn sessions_dir() -> CliResult<PathBuf> {
let home = dirs::home_dir()
.ok_or_else(|| CliError::Internal("Could not determine home directory".to_string()))?;
let sessions_dir = home.join(".ricecoder").join("sessions");
fs::create_dir_all(&sessions_dir).map_err(|e| {
CliError::Internal(format!("Failed to create sessions directory: {}", e))
})?;
Ok(sessions_dir)
}
fn sessions_index() -> CliResult<PathBuf> {
let sessions_dir = Self::sessions_dir()?;
Ok(sessions_dir.join("index.json"))
}
fn load_sessions() -> CliResult<Vec<SessionInfo>> {
let index_path = Self::sessions_index()?;
if !index_path.exists() {
return Ok(Vec::new());
}
let content = fs::read_to_string(&index_path)
.map_err(|e| CliError::Internal(format!("Failed to read sessions index: {}", e)))?;
if content.trim().is_empty() {
return Ok(Vec::new());
}
let sessions: Vec<SessionInfo> = serde_json::from_str(&content)
.map_err(|e| CliError::Internal(format!("Failed to parse sessions index: {}", e)))?;
Ok(sessions)
}
fn save_sessions(sessions: &[SessionInfo]) -> CliResult<()> {
let index_path = Self::sessions_index()?;
let content = serde_json::to_string_pretty(sessions)
.map_err(|e| CliError::Internal(format!("Failed to serialize sessions: {}", e)))?;
fs::write(&index_path, content)
.map_err(|e| CliError::Internal(format!("Failed to write sessions index: {}", e)))?;
Ok(())
}
}
impl Command for SessionsCommand {
fn execute(&self) -> CliResult<()> {
match &self.action {
SessionsAction::List => list_sessions(),
SessionsAction::Create { name } => create_session(name),
SessionsAction::Delete { id } => delete_session(id),
SessionsAction::Rename { id, name } => rename_session(id, name),
SessionsAction::Switch { id } => switch_session(id),
SessionsAction::Info { id } => show_session_info(id),
SessionsAction::Share {
expires_in,
no_history,
no_context,
} => handle_share(*expires_in, *no_history, *no_context),
SessionsAction::ShareList => handle_share_list(),
SessionsAction::ShareRevoke { share_id } => handle_share_revoke(share_id),
SessionsAction::ShareInfo { share_id } => handle_share_info(share_id),
SessionsAction::ShareView { share_id } => handle_share_view(share_id),
}
}
}
fn list_sessions() -> CliResult<()> {
let sessions = SessionsCommand::load_sessions()?;
if sessions.is_empty() {
println!("No sessions found. Create one with: rice sessions create <name>");
return Ok(());
}
println!("Sessions:");
println!();
for session in sessions {
println!(" {} - {}", session.id, session.name);
println!(" Messages: {}", session.message_count);
println!(" Created: {}", format_timestamp(session.created_at));
println!(" Modified: {}", format_timestamp(session.modified_at));
println!();
}
Ok(())
}
fn create_session(name: &str) -> CliResult<()> {
let mut sessions = SessionsCommand::load_sessions()?;
let now = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.map(|d| d.as_secs())
.unwrap_or(0);
let id = format!("session-{}", now);
let session = SessionInfo {
id: id.clone(),
name: name.to_string(),
created_at: now,
modified_at: now,
message_count: 0,
};
sessions.push(session);
SessionsCommand::save_sessions(&sessions)?;
println!("Created session: {} ({})", id, name);
Ok(())
}
fn delete_session(id: &str) -> CliResult<()> {
let mut sessions = SessionsCommand::load_sessions()?;
let initial_len = sessions.len();
sessions.retain(|s| s.id != id);
if sessions.len() == initial_len {
return Err(CliError::Internal(format!("Session not found: {}", id)));
}
SessionsCommand::save_sessions(&sessions)?;
println!("Deleted session: {}", id);
Ok(())
}
fn rename_session(id: &str, name: &str) -> CliResult<()> {
let mut sessions = SessionsCommand::load_sessions()?;
let session = sessions
.iter_mut()
.find(|s| s.id == id)
.ok_or_else(|| CliError::Internal(format!("Session not found: {}", id)))?;
let old_name = session.name.clone();
session.name = name.to_string();
session.modified_at = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.map(|d| d.as_secs())
.unwrap_or(0);
SessionsCommand::save_sessions(&sessions)?;
println!("Renamed session from '{}' to '{}'", old_name, name);
Ok(())
}
fn switch_session(id: &str) -> CliResult<()> {
let sessions = SessionsCommand::load_sessions()?;
let session = sessions
.iter()
.find(|s| s.id == id)
.ok_or_else(|| CliError::Internal(format!("Session not found: {}", id)))?;
let config_path = dirs::home_dir()
.ok_or_else(|| CliError::Internal("Could not determine home directory".to_string()))?
.join(".ricecoder")
.join("current_session.txt");
fs::write(&config_path, &session.id)
.map_err(|e| CliError::Internal(format!("Failed to save current session: {}", e)))?;
println!("Switched to session: {} ({})", session.id, session.name);
Ok(())
}
fn show_session_info(id: &str) -> CliResult<()> {
let sessions = SessionsCommand::load_sessions()?;
let session = sessions
.iter()
.find(|s| s.id == id)
.ok_or_else(|| CliError::Internal(format!("Session not found: {}", id)))?;
println!("Session: {}", session.id);
println!(" Name: {}", session.name);
println!(" Messages: {}", session.message_count);
println!(" Created: {}", format_timestamp(session.created_at));
println!(" Modified: {}", format_timestamp(session.modified_at));
Ok(())
}
fn format_timestamp(secs: u64) -> String {
use std::time::UNIX_EPOCH;
let duration = std::time::Duration::from_secs(secs);
let datetime = UNIX_EPOCH + duration;
format!(
"{} seconds ago",
std::time::SystemTime::now()
.duration_since(datetime)
.map(|d| d.as_secs())
.unwrap_or(0)
)
}
fn handle_share(expires_in: Option<u64>, no_history: bool, no_context: bool) -> CliResult<()> {
use ricecoder_sessions::{ShareService, SharePermissions};
use chrono::Duration;
let share_service = ShareService::new();
let include_history = !no_history;
let include_context = !no_context;
let session_id = "current-session";
let permissions = SharePermissions {
read_only: true,
include_history,
include_context,
};
let expires_in_duration = expires_in.map(|secs| Duration::seconds(secs as i64));
let share = share_service
.generate_share_link(session_id, permissions, expires_in_duration)
.map_err(|e| CliError::Internal(format!("Failed to generate share link: {}", e)))?;
println!("Share link: {}", share.id);
println!();
println!("Permissions:");
println!(" History: {}", if include_history { "Yes" } else { "No" });
println!(" Context: {}", if include_context { "Yes" } else { "No" });
if let Some(expiration) = expires_in {
println!(" Expires in: {} seconds", expiration);
} else {
println!(" Expires: Never");
}
println!();
println!("Share this link with others to grant access to your session.");
Ok(())
}
fn handle_share_list() -> CliResult<()> {
use ricecoder_sessions::ShareService;
let share_service = ShareService::new();
let shares = share_service
.list_shares()
.map_err(|e| CliError::Internal(format!("Failed to list shares: {}", e)))?;
if shares.is_empty() {
println!("Active shares:");
println!();
println!(" No shares found. Create one with: rice sessions share");
println!();
return Ok(());
}
println!("Active shares:");
println!();
println!("{:<40} {:<20} {:<20} {:<20} {:<30}", "Share ID", "Session ID", "Created", "Expires", "Permissions");
println!("{}", "-".repeat(130));
for share in shares {
let created = share.created_at.format("%Y-%m-%d %H:%M:%S").to_string();
let expires = share
.expires_at
.map(|dt| dt.format("%Y-%m-%d %H:%M:%S").to_string())
.unwrap_or_else(|| "Never".to_string());
let permissions = format!(
"History: {}, Context: {}",
if share.permissions.include_history { "Yes" } else { "No" },
if share.permissions.include_context { "Yes" } else { "No" }
);
println!(
"{:<40} {:<20} {:<20} {:<20} {:<30}",
&share.id[..40.min(share.id.len())],
&share.session_id[..20.min(share.session_id.len())],
created,
expires,
&permissions[..30.min(permissions.len())]
);
}
println!();
Ok(())
}
fn handle_share_revoke(share_id: &str) -> CliResult<()> {
use ricecoder_sessions::ShareService;
let share_service = ShareService::new();
share_service
.revoke_share(share_id)
.map_err(|e| CliError::Internal(format!("Failed to revoke share: {}", e)))?;
println!("Share {} revoked successfully", share_id);
Ok(())
}
fn handle_share_info(share_id: &str) -> CliResult<()> {
use ricecoder_sessions::ShareService;
let share_service = ShareService::new();
let share = share_service
.get_share(share_id)
.map_err(|e| CliError::Internal(format!("Failed to get share info: {}", e)))?;
println!("Share: {}", share.id);
println!(" Session: {}", share.session_id);
println!(" Created: {}", share.created_at.format("%Y-%m-%d %H:%M:%S"));
println!(
" Expires: {}",
share
.expires_at
.map(|dt| dt.format("%Y-%m-%d %H:%M:%S").to_string())
.unwrap_or_else(|| "Never".to_string())
);
println!(" Permissions:");
println!(" History: {}", if share.permissions.include_history { "Yes" } else { "No" });
println!(" Context: {}", if share.permissions.include_context { "Yes" } else { "No" });
println!(" Read-Only: {}", if share.permissions.read_only { "Yes" } else { "No" });
println!(" Status: Active");
Ok(())
}
fn handle_share_view(share_id: &str) -> CliResult<()> {
use ricecoder_sessions::{ShareService, Session, SessionContext, SessionMode};
let share_service = ShareService::new();
let share = share_service
.get_share(share_id)
.map_err(|e| match e {
ricecoder_sessions::SessionError::ShareNotFound(_) => {
CliError::Internal(format!("Share not found: {}", share_id))
}
ricecoder_sessions::SessionError::ShareExpired(_) => {
CliError::Internal(format!("Share has expired: {}", share_id))
}
_ => CliError::Internal(format!("Failed to access share: {}", e)),
})?;
let mock_session = Session::new(
format!("Shared Session ({})", &share.session_id[..8.min(share.session_id.len())]),
SessionContext::new("openai".to_string(), "gpt-4".to_string(), SessionMode::Chat),
);
let shared_session = share_service.create_shared_session_view(&mock_session, &share.permissions);
display_shared_session(&shared_session, &share)
}
fn display_shared_session(
session: &ricecoder_sessions::Session,
share: &ricecoder_sessions::SessionShare,
) -> CliResult<()> {
use ricecoder_sessions::MessageRole;
println!();
println!("╔════════════════════════════════════════════════════════════════╗");
println!("║ Shared Session: {} [Read-Only]", session.name);
println!("║ Permissions: [History: {}] [Context: {}]",
if share.permissions.include_history { "Yes" } else { "No" },
if share.permissions.include_context { "Yes" } else { "No" }
);
println!("╚════════════════════════════════════════════════════════════════╝");
println!();
println!("Session Information:");
println!(" Created: {}", session.created_at.format("%Y-%m-%d %H:%M:%S"));
if let Some(expires_at) = share.expires_at {
println!(" Expires: {}", expires_at.format("%Y-%m-%d %H:%M:%S"));
} else {
println!(" Expires: Never");
}
println!(" Status: Read-Only");
println!();
if share.permissions.include_history {
if session.history.is_empty() {
println!("Messages: (empty)");
} else {
println!("Messages ({} total):", session.history.len());
println!();
let messages_per_page = 10;
let total_messages = session.history.len();
let pages = (total_messages + messages_per_page - 1) / messages_per_page;
let current_page = 1;
let start_idx = (current_page - 1) * messages_per_page;
let end_idx = (start_idx + messages_per_page).min(total_messages);
for (idx, msg) in session.history[start_idx..end_idx].iter().enumerate() {
let role_str = match msg.role {
MessageRole::User => "User",
MessageRole::Assistant => "Assistant",
MessageRole::System => "System",
};
println!("[{}] {}: {}", start_idx + idx + 1, role_str, msg.content);
println!(" Timestamp: {}", msg.timestamp.format("%Y-%m-%d %H:%M:%S"));
println!();
}
println!("Message {} - {} of {} (Page {} of {})",
start_idx + 1,
end_idx,
total_messages,
current_page,
pages
);
if pages > 1 {
println!("(Use 'rice sessions share view <share_id> --page <N>' to view other pages)");
}
}
} else {
println!("Messages: (history excluded from share)");
}
println!();
if share.permissions.include_context {
println!("Context:");
if let Some(project_path) = &session.context.project_path {
println!(" Project: {}", project_path);
}
println!(" Provider: {}", session.context.provider);
println!(" Model: {}", session.context.model);
if !session.context.files.is_empty() {
println!(" Files:");
for file in &session.context.files {
println!(" - {}", file);
}
} else {
println!(" Files: (none)");
}
} else {
println!("Context: (context excluded from share)");
}
println!();
println!("This is a read-only view. You cannot modify this shared session.");
println!();
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_sessions_command_creation() {
let cmd = SessionsCommand::new(SessionsAction::List);
assert!(matches!(cmd.action, SessionsAction::List));
}
#[test]
fn test_create_session_action() {
let cmd = SessionsCommand::new(SessionsAction::Create {
name: "test".to_string(),
});
assert!(matches!(cmd.action, SessionsAction::Create { .. }));
}
#[test]
fn test_delete_session_action() {
let cmd = SessionsCommand::new(SessionsAction::Delete {
id: "session-1".to_string(),
});
assert!(matches!(cmd.action, SessionsAction::Delete { .. }));
}
#[test]
fn test_session_info_serialization() {
let session = SessionInfo {
id: "session-1".to_string(),
name: "Test Session".to_string(),
created_at: 1000,
modified_at: 2000,
message_count: 5,
};
let json = serde_json::to_string(&session).unwrap();
let deserialized: SessionInfo = serde_json::from_str(&json).unwrap();
assert_eq!(session.id, deserialized.id);
assert_eq!(session.name, deserialized.name);
assert_eq!(session.message_count, deserialized.message_count);
}
#[test]
fn test_rename_session_action() {
let cmd = SessionsCommand::new(SessionsAction::Rename {
id: "session-1".to_string(),
name: "New Name".to_string(),
});
assert!(matches!(cmd.action, SessionsAction::Rename { .. }));
}
#[test]
fn test_switch_session_action() {
let cmd = SessionsCommand::new(SessionsAction::Switch {
id: "session-1".to_string(),
});
assert!(matches!(cmd.action, SessionsAction::Switch { .. }));
}
#[test]
fn test_info_session_action() {
let cmd = SessionsCommand::new(SessionsAction::Info {
id: "session-1".to_string(),
});
assert!(matches!(cmd.action, SessionsAction::Info { .. }));
}
#[test]
fn test_share_action() {
let cmd = SessionsCommand::new(SessionsAction::Share {
expires_in: Some(3600),
no_history: false,
no_context: false,
});
assert!(matches!(cmd.action, SessionsAction::Share { .. }));
}
#[test]
fn test_share_action_with_flags() {
let cmd = SessionsCommand::new(SessionsAction::Share {
expires_in: None,
no_history: true,
no_context: true,
});
assert!(matches!(cmd.action, SessionsAction::Share { .. }));
}
#[test]
fn test_share_list_action() {
let cmd = SessionsCommand::new(SessionsAction::ShareList);
assert!(matches!(cmd.action, SessionsAction::ShareList));
}
#[test]
fn test_share_revoke_action() {
let cmd = SessionsCommand::new(SessionsAction::ShareRevoke {
share_id: "share-123".to_string(),
});
assert!(matches!(cmd.action, SessionsAction::ShareRevoke { .. }));
}
#[test]
fn test_share_info_action() {
let cmd = SessionsCommand::new(SessionsAction::ShareInfo {
share_id: "share-123".to_string(),
});
assert!(matches!(cmd.action, SessionsAction::ShareInfo { .. }));
}
#[test]
fn test_share_command_with_expiration() {
let cmd = SessionsCommand::new(SessionsAction::Share {
expires_in: Some(3600),
no_history: false,
no_context: false,
});
match cmd.action {
SessionsAction::Share {
expires_in,
no_history,
no_context,
} => {
assert_eq!(expires_in, Some(3600));
assert!(!no_history);
assert!(!no_context);
}
_ => panic!("Expected Share action"),
}
}
#[test]
fn test_share_command_without_history() {
let cmd = SessionsCommand::new(SessionsAction::Share {
expires_in: None,
no_history: true,
no_context: false,
});
match cmd.action {
SessionsAction::Share {
expires_in,
no_history,
no_context,
} => {
assert_eq!(expires_in, None);
assert!(no_history);
assert!(!no_context);
}
_ => panic!("Expected Share action"),
}
}
#[test]
fn test_share_command_without_context() {
let cmd = SessionsCommand::new(SessionsAction::Share {
expires_in: None,
no_history: false,
no_context: true,
});
match cmd.action {
SessionsAction::Share {
expires_in,
no_history,
no_context,
} => {
assert_eq!(expires_in, None);
assert!(!no_history);
assert!(no_context);
}
_ => panic!("Expected Share action"),
}
}
#[test]
fn test_share_command_all_restrictions() {
let cmd = SessionsCommand::new(SessionsAction::Share {
expires_in: Some(7200),
no_history: true,
no_context: true,
});
match cmd.action {
SessionsAction::Share {
expires_in,
no_history,
no_context,
} => {
assert_eq!(expires_in, Some(7200));
assert!(no_history);
assert!(no_context);
}
_ => panic!("Expected Share action"),
}
}
#[test]
fn test_share_revoke_action_with_id() {
let share_id = "test-share-id-12345";
let cmd = SessionsCommand::new(SessionsAction::ShareRevoke {
share_id: share_id.to_string(),
});
match cmd.action {
SessionsAction::ShareRevoke { share_id: id } => {
assert_eq!(id, share_id);
}
_ => panic!("Expected ShareRevoke action"),
}
}
#[test]
fn test_share_info_action_with_id() {
let share_id = "test-share-id-67890";
let cmd = SessionsCommand::new(SessionsAction::ShareInfo {
share_id: share_id.to_string(),
});
match cmd.action {
SessionsAction::ShareInfo { share_id: id } => {
assert_eq!(id, share_id);
}
_ => panic!("Expected ShareInfo action"),
}
}
#[test]
fn test_session_info_with_zero_messages() {
let session = SessionInfo {
id: "session-1".to_string(),
name: "Empty Session".to_string(),
created_at: 1000,
modified_at: 1000,
message_count: 0,
};
assert_eq!(session.message_count, 0);
assert_eq!(session.name, "Empty Session");
}
#[test]
fn test_session_info_with_many_messages() {
let session = SessionInfo {
id: "session-2".to_string(),
name: "Busy Session".to_string(),
created_at: 1000,
modified_at: 5000,
message_count: 100,
};
assert_eq!(session.message_count, 100);
assert!(session.modified_at > session.created_at);
}
#[test]
fn test_share_permissions_all_enabled() {
use ricecoder_sessions::SharePermissions;
let perms = SharePermissions {
read_only: true,
include_history: true,
include_context: true,
};
assert!(perms.read_only);
assert!(perms.include_history);
assert!(perms.include_context);
}
#[test]
fn test_share_permissions_history_only() {
use ricecoder_sessions::SharePermissions;
let perms = SharePermissions {
read_only: true,
include_history: true,
include_context: false,
};
assert!(perms.read_only);
assert!(perms.include_history);
assert!(!perms.include_context);
}
#[test]
fn test_share_permissions_context_only() {
use ricecoder_sessions::SharePermissions;
let perms = SharePermissions {
read_only: true,
include_history: false,
include_context: true,
};
assert!(perms.read_only);
assert!(!perms.include_history);
assert!(perms.include_context);
}
#[test]
fn test_share_permissions_nothing_included() {
use ricecoder_sessions::SharePermissions;
let perms = SharePermissions {
read_only: true,
include_history: false,
include_context: false,
};
assert!(perms.read_only);
assert!(!perms.include_history);
assert!(!perms.include_context);
}
#[test]
fn test_share_view_action() {
let cmd = SessionsCommand::new(SessionsAction::ShareView {
share_id: "share-123".to_string(),
});
assert!(matches!(cmd.action, SessionsAction::ShareView { .. }));
}
#[test]
fn test_share_view_action_with_id() {
let share_id = "test-share-view-id";
let cmd = SessionsCommand::new(SessionsAction::ShareView {
share_id: share_id.to_string(),
});
match cmd.action {
SessionsAction::ShareView { share_id: id } => {
assert_eq!(id, share_id);
}
_ => panic!("Expected ShareView action"),
}
}
#[test]
fn test_share_service_get_share() {
use ricecoder_sessions::{ShareService, SharePermissions};
use chrono::Duration;
let service = ShareService::new();
let permissions = SharePermissions {
read_only: true,
include_history: true,
include_context: true,
};
let share = service
.generate_share_link("session-1", permissions, None)
.expect("Failed to generate share");
let retrieved = service
.get_share(&share.id)
.expect("Failed to retrieve share");
assert_eq!(retrieved.id, share.id);
assert_eq!(retrieved.session_id, "session-1");
assert!(retrieved.permissions.read_only);
}
#[test]
fn test_share_service_get_nonexistent_share() {
use ricecoder_sessions::ShareService;
let service = ShareService::new();
let result = service.get_share("nonexistent-share");
assert!(result.is_err());
}
#[test]
fn test_share_service_revoke_share() {
use ricecoder_sessions::{ShareService, SharePermissions};
let service = ShareService::new();
let permissions = SharePermissions {
read_only: true,
include_history: true,
include_context: true,
};
let share = service
.generate_share_link("session-1", permissions, None)
.expect("Failed to generate share");
service
.revoke_share(&share.id)
.expect("Failed to revoke share");
let result = service.get_share(&share.id);
assert!(result.is_err());
}
#[test]
fn test_share_service_list_shares() {
use ricecoder_sessions::{ShareService, SharePermissions};
let service = ShareService::new();
let permissions = SharePermissions {
read_only: true,
include_history: true,
include_context: true,
};
let share1 = service
.generate_share_link("session-1", permissions.clone(), None)
.expect("Failed to generate share 1");
let share2 = service
.generate_share_link("session-2", permissions.clone(), None)
.expect("Failed to generate share 2");
let shares = service.list_shares().expect("Failed to list shares");
assert!(shares.len() >= 2);
assert!(shares.iter().any(|s| s.id == share1.id));
assert!(shares.iter().any(|s| s.id == share2.id));
}
#[test]
fn test_share_service_create_shared_session_view_with_history() {
use ricecoder_sessions::{ShareService, SharePermissions, Session, SessionContext, SessionMode, Message, MessageRole};
let service = ShareService::new();
let mut session = Session::new(
"Test Session".to_string(),
SessionContext::new("openai".to_string(), "gpt-4".to_string(), SessionMode::Chat),
);
session.history.push(Message::new(
MessageRole::User,
"Hello".to_string(),
));
session.history.push(Message::new(
MessageRole::Assistant,
"Hi there!".to_string(),
));
let permissions = SharePermissions {
read_only: true,
include_history: true,
include_context: true,
};
let view = service.create_shared_session_view(&session, &permissions);
assert_eq!(view.history.len(), 2);
}
#[test]
fn test_share_service_create_shared_session_view_without_history() {
use ricecoder_sessions::{ShareService, SharePermissions, Session, SessionContext, SessionMode, Message, MessageRole};
let service = ShareService::new();
let mut session = Session::new(
"Test Session".to_string(),
SessionContext::new("openai".to_string(), "gpt-4".to_string(), SessionMode::Chat),
);
session.history.push(Message::new(
MessageRole::User,
"Hello".to_string(),
));
session.history.push(Message::new(
MessageRole::Assistant,
"Hi there!".to_string(),
));
let permissions = SharePermissions {
read_only: true,
include_history: false,
include_context: true,
};
let view = service.create_shared_session_view(&session, &permissions);
assert_eq!(view.history.len(), 0);
}
#[test]
fn test_share_service_create_shared_session_view_without_context() {
use ricecoder_sessions::{ShareService, SharePermissions, Session, SessionContext, SessionMode};
let service = ShareService::new();
let mut session = Session::new(
"Test Session".to_string(),
SessionContext::new("openai".to_string(), "gpt-4".to_string(), SessionMode::Chat),
);
session.context.files.push("file1.rs".to_string());
session.context.files.push("file2.rs".to_string());
let permissions = SharePermissions {
read_only: true,
include_history: true,
include_context: false,
};
let view = service.create_shared_session_view(&session, &permissions);
assert_eq!(view.context.files.len(), 0);
}
#[test]
fn test_share_service_list_shares_for_session() {
use ricecoder_sessions::{ShareService, SharePermissions};
let service = ShareService::new();
let permissions = SharePermissions {
read_only: true,
include_history: true,
include_context: true,
};
let share1 = service
.generate_share_link("session-1", permissions.clone(), None)
.expect("Failed to generate share 1");
let share2 = service
.generate_share_link("session-1", permissions.clone(), None)
.expect("Failed to generate share 2");
let share3 = service
.generate_share_link("session-2", permissions.clone(), None)
.expect("Failed to generate share 3");
let session1_shares = service
.list_shares_for_session("session-1")
.expect("Failed to list shares for session-1");
assert_eq!(session1_shares.len(), 2);
assert!(session1_shares.iter().any(|s| s.id == share1.id));
assert!(session1_shares.iter().any(|s| s.id == share2.id));
assert!(!session1_shares.iter().any(|s| s.id == share3.id));
}
#[test]
fn test_share_service_invalidate_session_shares() {
use ricecoder_sessions::{ShareService, SharePermissions};
let service = ShareService::new();
let permissions = SharePermissions {
read_only: true,
include_history: true,
include_context: true,
};
let share1 = service
.generate_share_link("session-1", permissions.clone(), None)
.expect("Failed to generate share 1");
let share2 = service
.generate_share_link("session-1", permissions.clone(), None)
.expect("Failed to generate share 2");
let invalidated = service
.invalidate_session_shares("session-1")
.expect("Failed to invalidate shares");
assert_eq!(invalidated, 2);
let result1 = service.get_share(&share1.id);
let result2 = service.get_share(&share2.id);
assert!(result1.is_err());
assert!(result2.is_err());
}
#[test]
fn test_share_service_read_only_enforcement() {
use ricecoder_sessions::{ShareService, SharePermissions};
let service = ShareService::new();
let permissions = SharePermissions {
read_only: true,
include_history: true,
include_context: true,
};
let share = service
.generate_share_link("session-1", permissions, None)
.expect("Failed to generate share");
assert!(share.permissions.read_only);
}
}