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