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