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 insert(&self, _model: &NewModel) -> Result<Model, RepositoryError> {
104 unimplemented!()
105 }
106 async fn update(&self, _model: &Model) -> Result<(), RepositoryError> {
107 unimplemented!()
108 }
109 async fn delete(&self, _id: i64) -> Result<(), RepositoryError> {
110 Ok(())
111 }
112 }
113
114 struct MockMcpRepo;
115
116 #[async_trait]
117 impl McpServerRepository for MockMcpRepo {
118 async fn insert(&self, _server: NewMcpServer) -> Result<McpServer, McpRepositoryError> {
119 unimplemented!()
120 }
121 async fn get_by_id(&self, id: i64) -> Result<McpServer, McpRepositoryError> {
122 Err(McpRepositoryError::NotFound(format!("id={id}")))
123 }
124 async fn get_by_name(&self, name: &str) -> Result<McpServer, McpRepositoryError> {
125 Err(McpRepositoryError::NotFound(format!("name={name}")))
126 }
127 async fn list(&self) -> Result<Vec<McpServer>, McpRepositoryError> {
128 Ok(vec![])
129 }
130 async fn update(&self, _server: &McpServer) -> Result<(), McpRepositoryError> {
131 unimplemented!()
132 }
133 async fn delete(&self, _id: i64) -> Result<(), McpRepositoryError> {
134 Ok(())
135 }
136 async fn update_last_connected(&self, _id: i64) -> Result<(), McpRepositoryError> {
137 Ok(())
138 }
139 }
140
141 struct MockChatHistoryRepo;
142
143 #[async_trait]
144 impl ChatHistoryRepository for MockChatHistoryRepo {
145 async fn create_conversation(
146 &self,
147 _conv: NewConversation,
148 ) -> Result<i64, ChatHistoryError> {
149 Ok(1)
150 }
151 async fn list_conversations(&self) -> Result<Vec<Conversation>, ChatHistoryError> {
152 Ok(vec![])
153 }
154 async fn get_conversation(
155 &self,
156 _id: i64,
157 ) -> Result<Option<Conversation>, ChatHistoryError> {
158 Ok(None)
159 }
160 async fn update_conversation(
161 &self,
162 _id: i64,
163 _update: ConversationUpdate,
164 ) -> Result<(), ChatHistoryError> {
165 Ok(())
166 }
167 async fn delete_conversation(&self, _id: i64) -> Result<(), ChatHistoryError> {
168 Ok(())
169 }
170 async fn get_conversation_count(&self) -> Result<i64, ChatHistoryError> {
171 Ok(0)
172 }
173 async fn get_messages(
174 &self,
175 _conversation_id: i64,
176 ) -> Result<Vec<Message>, ChatHistoryError> {
177 Ok(vec![])
178 }
179 async fn save_message(&self, _msg: NewMessage) -> Result<i64, ChatHistoryError> {
180 Ok(1)
181 }
182 async fn update_message(
183 &self,
184 _id: i64,
185 _content: String,
186 _metadata: Option<serde_json::Value>,
187 ) -> Result<(), ChatHistoryError> {
188 Ok(())
189 }
190 async fn delete_message_and_subsequent(&self, _id: i64) -> Result<i64, ChatHistoryError> {
191 Ok(0)
192 }
193 async fn get_message_count(&self, _conversation_id: i64) -> Result<i64, ChatHistoryError> {
194 Ok(0)
195 }
196 }
197
198 struct MockSettingsRepo {
199 settings: Mutex<Settings>,
200 }
201
202 impl MockSettingsRepo {
203 fn new() -> Self {
204 Self {
205 settings: Mutex::new(Settings::with_defaults()),
206 }
207 }
208 }
209
210 #[async_trait]
211 impl SettingsRepository for MockSettingsRepo {
212 async fn load(&self) -> Result<Settings, RepositoryError> {
213 Ok(self.settings.lock().unwrap().clone())
214 }
215 async fn save(&self, settings: &Settings) -> Result<(), RepositoryError> {
216 *self.settings.lock().unwrap() = settings.clone();
217 Ok(())
218 }
219 }
220
221 #[tokio::test]
222 async fn test_app_core_creation() {
223 let repos = Repos {
224 models: Arc::new(MockModelRepo),
225 settings: Arc::new(MockSettingsRepo::new()),
226 mcp_servers: Arc::new(MockMcpRepo),
227 chat_history: Arc::new(MockChatHistoryRepo),
228 };
229
230 let core = AppCore::new(repos);
231
232 let models = core.models().list().await.unwrap();
234 assert!(models.is_empty());
235
236 let settings = core.settings().get().await.unwrap();
237 assert_eq!(settings.default_context_size, Some(4096));
238 }
239}