1use crate::ports::{ProcessRunner, Repos};
7use std::sync::Arc;
8
9use super::{ChatHistoryService, ModelService, ServerService, SettingsService};
10
11pub struct AppCore {
28 models: ModelService,
29 settings: SettingsService,
30 servers: ServerService,
31 chat_history: ChatHistoryService,
32}
33
34impl AppCore {
35 pub fn new(repos: Repos, runner: Arc<dyn ProcessRunner>) -> Self {
37 Self {
38 models: ModelService::new(repos.models),
39 settings: SettingsService::new(repos.settings),
40 servers: ServerService::new(runner),
41 chat_history: ChatHistoryService::new(repos.chat_history),
42 }
43 }
44
45 pub const fn models(&self) -> &ModelService {
47 &self.models
48 }
49
50 pub const fn settings(&self) -> &SettingsService {
52 &self.settings
53 }
54
55 pub const fn servers(&self) -> &ServerService {
57 &self.servers
58 }
59
60 pub const fn chat_history(&self) -> &ChatHistoryService {
62 &self.chat_history
63 }
64}
65
66#[cfg(test)]
67mod tests {
68 use super::*;
69 use crate::domain::chat::{
70 Conversation, ConversationUpdate, Message, NewConversation, NewMessage,
71 };
72 use crate::domain::mcp::{McpServer, NewMcpServer};
73 use crate::domain::{Model, NewModel};
74 use crate::ports::{
75 ChatHistoryError, ChatHistoryRepository, McpRepositoryError, McpServerRepository,
76 ModelRepository, ProcessError, ProcessHandle, ProcessRunner, RepositoryError, ServerConfig,
77 ServerHealth, SettingsRepository,
78 };
79 use crate::settings::Settings;
80 use async_trait::async_trait;
81 use std::sync::Mutex;
82
83 struct MockModelRepo;
84
85 #[async_trait]
86 impl ModelRepository for MockModelRepo {
87 async fn list(&self) -> Result<Vec<Model>, RepositoryError> {
88 Ok(vec![])
89 }
90 async fn get_by_id(&self, id: i64) -> Result<Model, RepositoryError> {
91 Err(RepositoryError::NotFound(format!("id={id}")))
92 }
93 async fn get_by_name(&self, name: &str) -> Result<Model, RepositoryError> {
94 Err(RepositoryError::NotFound(format!("name={name}")))
95 }
96 async fn insert(&self, _model: &NewModel) -> Result<Model, RepositoryError> {
97 unimplemented!()
98 }
99 async fn update(&self, _model: &Model) -> Result<(), RepositoryError> {
100 unimplemented!()
101 }
102 async fn delete(&self, _id: i64) -> Result<(), RepositoryError> {
103 Ok(())
104 }
105 }
106
107 struct MockMcpRepo;
108
109 #[async_trait]
110 impl McpServerRepository for MockMcpRepo {
111 async fn insert(&self, _server: NewMcpServer) -> Result<McpServer, McpRepositoryError> {
112 unimplemented!()
113 }
114 async fn get_by_id(&self, id: i64) -> Result<McpServer, McpRepositoryError> {
115 Err(McpRepositoryError::NotFound(format!("id={id}")))
116 }
117 async fn get_by_name(&self, name: &str) -> Result<McpServer, McpRepositoryError> {
118 Err(McpRepositoryError::NotFound(format!("name={name}")))
119 }
120 async fn list(&self) -> Result<Vec<McpServer>, McpRepositoryError> {
121 Ok(vec![])
122 }
123 async fn update(&self, _server: &McpServer) -> Result<(), McpRepositoryError> {
124 unimplemented!()
125 }
126 async fn delete(&self, _id: i64) -> Result<(), McpRepositoryError> {
127 Ok(())
128 }
129 async fn update_last_connected(&self, _id: i64) -> Result<(), McpRepositoryError> {
130 Ok(())
131 }
132 }
133
134 struct MockChatHistoryRepo;
135
136 #[async_trait]
137 impl ChatHistoryRepository for MockChatHistoryRepo {
138 async fn create_conversation(
139 &self,
140 _conv: NewConversation,
141 ) -> Result<i64, ChatHistoryError> {
142 Ok(1)
143 }
144 async fn list_conversations(&self) -> Result<Vec<Conversation>, ChatHistoryError> {
145 Ok(vec![])
146 }
147 async fn get_conversation(
148 &self,
149 _id: i64,
150 ) -> Result<Option<Conversation>, ChatHistoryError> {
151 Ok(None)
152 }
153 async fn update_conversation(
154 &self,
155 _id: i64,
156 _update: ConversationUpdate,
157 ) -> Result<(), ChatHistoryError> {
158 Ok(())
159 }
160 async fn delete_conversation(&self, _id: i64) -> Result<(), ChatHistoryError> {
161 Ok(())
162 }
163 async fn get_conversation_count(&self) -> Result<i64, ChatHistoryError> {
164 Ok(0)
165 }
166 async fn get_messages(
167 &self,
168 _conversation_id: i64,
169 ) -> Result<Vec<Message>, ChatHistoryError> {
170 Ok(vec![])
171 }
172 async fn save_message(&self, _msg: NewMessage) -> Result<i64, ChatHistoryError> {
173 Ok(1)
174 }
175 async fn update_message(
176 &self,
177 _id: i64,
178 _content: String,
179 _metadata: Option<serde_json::Value>,
180 ) -> Result<(), ChatHistoryError> {
181 Ok(())
182 }
183 async fn delete_message_and_subsequent(&self, _id: i64) -> Result<i64, ChatHistoryError> {
184 Ok(0)
185 }
186 async fn get_message_count(&self, _conversation_id: i64) -> Result<i64, ChatHistoryError> {
187 Ok(0)
188 }
189 }
190
191 struct MockSettingsRepo {
192 settings: Mutex<Settings>,
193 }
194
195 impl MockSettingsRepo {
196 fn new() -> Self {
197 Self {
198 settings: Mutex::new(Settings::with_defaults()),
199 }
200 }
201 }
202
203 #[async_trait]
204 impl SettingsRepository for MockSettingsRepo {
205 async fn load(&self) -> Result<Settings, RepositoryError> {
206 Ok(self.settings.lock().unwrap().clone())
207 }
208 async fn save(&self, settings: &Settings) -> Result<(), RepositoryError> {
209 *self.settings.lock().unwrap() = settings.clone();
210 Ok(())
211 }
212 }
213
214 struct MockRunner;
215
216 #[async_trait]
217 impl ProcessRunner for MockRunner {
218 async fn start(&self, config: ServerConfig) -> Result<ProcessHandle, ProcessError> {
219 Ok(ProcessHandle::new(
220 config.model_id,
221 config.model_name,
222 Some(12345),
223 9000,
224 0,
225 ))
226 }
227 async fn stop(&self, _handle: &ProcessHandle) -> Result<(), ProcessError> {
228 Ok(())
229 }
230 async fn is_running(&self, _handle: &ProcessHandle) -> bool {
231 false
232 }
233 async fn health(&self, _handle: &ProcessHandle) -> Result<ServerHealth, ProcessError> {
234 Ok(ServerHealth::healthy())
235 }
236 async fn list_running(&self) -> Result<Vec<ProcessHandle>, ProcessError> {
237 Ok(vec![])
238 }
239 }
240
241 #[tokio::test]
242 async fn test_app_core_creation() {
243 let repos = Repos {
244 models: Arc::new(MockModelRepo),
245 settings: Arc::new(MockSettingsRepo::new()),
246 mcp_servers: Arc::new(MockMcpRepo),
247 chat_history: Arc::new(MockChatHistoryRepo),
248 };
249 let runner = Arc::new(MockRunner);
250
251 let core = AppCore::new(repos, runner);
252
253 let models = core.models().list().await.unwrap();
255 assert!(models.is_empty());
256
257 let settings = core.settings().get().await.unwrap();
258 assert_eq!(settings.default_context_size, Some(4096));
259 }
260}