1use crate::domain::{Edge, Properties, PropertyValue, string_to_node_id, NodeId};
2use crate::services::kb::domain::{LinkType, LuhmannId, Note, NoteId, NoteLink, NoteCounter};
3use crate::storage::{GraphStorage, StorageError, SearchQuery, EdgeDirection};
4use async_trait::async_trait;
5use thiserror::Error;
6
7pub mod domain;
8
9#[derive(Error, Debug)]
10pub enum KbError {
11 #[error("Note not found: {0}")]
12 NoteNotFound(NoteId),
13
14 #[error("Note already exists: {0}")]
15 NoteAlreadyExists(NoteId),
16
17 #[error("Invalid Luhmann ID: {0}")]
18 InvalidLuhmannId(String),
19
20 #[error("Storage error: {0}")]
21 Storage(#[from] StorageError),
22
23 #[error("Cannot link note to itself")]
24 SelfLink,
25}
26
27pub type Result<T> = std::result::Result<T, KbError>;
28
29#[async_trait]
31pub trait KnowledgeBaseService: Send + Sync {
32 async fn create_note(
34 &self,
35 title: impl Into<String> + Send,
36 content: impl Into<String> + Send,
37 ) -> Result<Note>;
38
39 async fn create_note_with_id(
40 &self,
41 id: LuhmannId,
42 title: impl Into<String> + Send,
43 content: impl Into<String> + Send,
44 ) -> Result<Note>;
45
46 async fn create_branch(
47 &self,
48 parent_id: &LuhmannId,
49 title: impl Into<String> + Send,
50 content: impl Into<String> + Send,
51 ) -> Result<Note>;
52
53 async fn get_note(&self, note_id: &LuhmannId) -> Result<Note>;
54 async fn list_notes(&self) -> Result<Vec<Note>>;
55 async fn list_notes_by_prefix(&self, prefix: &LuhmannId) -> Result<Vec<Note>>;
56 async fn delete_note(&self, note_id: &LuhmannId) -> Result<()>;
57
58 async fn search_notes(&self, query: &str) -> Result<Vec<Note>>;
60
61 async fn link_notes(
63 &self,
64 from_id: &LuhmannId,
65 to_id: &LuhmannId,
66 context: Option<String>,
67 ) -> Result<()>;
68
69 async fn get_links(&self, note_id: &LuhmannId) -> Result<Vec<NoteLink>>;
70
71 async fn mark_continuation(&self, from_id: &LuhmannId, to_id: &LuhmannId) -> Result<()>;
73
74 async fn create_index(&self, parent_id: &LuhmannId) -> Result<Note>;
76
77 async fn get_context(&self, note_id: &LuhmannId) -> Result<NoteContext>;
79}
80
81#[derive(Debug, Clone)]
83pub struct NoteContext {
84 pub note: Note,
85 pub parent: Option<Note>,
86 pub children: Vec<Note>,
87 pub links_to: Vec<Note>,
88 pub backlinks: Vec<Note>,
89 pub continues_to: Vec<Note>,
90 pub continued_from: Vec<Note>,
91}
92
93fn luhmann_to_node_id(luhmann_id: &LuhmannId) -> NodeId {
95 string_to_node_id(&luhmann_id.to_string())
96}
97
98pub struct KnowledgeBaseServiceImpl<S: GraphStorage> {
99 storage: S,
100}
101
102impl<S: GraphStorage> KnowledgeBaseServiceImpl<S> {
103 pub fn new(storage: S) -> Self {
104 Self { storage }
105 }
106
107 fn to_node_id(&self, luhmann_id: &LuhmannId) -> NodeId {
109 luhmann_to_node_id(luhmann_id)
110 }
111
112 async fn get_or_init_counter(&self) -> Result<NoteCounter> {
114 let counter_id = string_to_node_id("__kb_counter__");
115 match self.storage.get_node(counter_id).await {
116 Ok(node) => {
117 NoteCounter::from_node(&node)
118 .ok_or_else(|| KbError::Storage(StorageError::ConstraintViolation("Invalid counter node".to_string())))
119 }
120 Err(StorageError::NodeNotFound(_)) => {
121 let counter = NoteCounter::new();
123 let node = counter.to_node();
124 self.storage.create_node(&node).await?;
125 Ok(counter)
126 }
127 Err(e) => Err(KbError::Storage(e)),
128 }
129 }
130
131 async fn update_counter(&self, counter: &NoteCounter) -> Result<()> {
133 let node = counter.to_node();
134 self.storage.update_node(&node).await?;
135 Ok(())
136 }
137
138 async fn next_main_id(&self) -> Result<LuhmannId> {
140 let mut counter = self.get_or_init_counter().await?;
141 let id = counter.next_main_topic_id();
142 self.update_counter(&counter).await?;
143 Ok(id)
144 }
145
146 async fn next_child_id(&self, parent_id: &LuhmannId) -> Result<LuhmannId> {
148 let all_notes = self.list_notes().await?;
149
150 let mut children: Vec<LuhmannId> = all_notes
152 .into_iter()
153 .map(|n| n.id)
154 .filter(|id| id.parent().as_ref() == Some(parent_id))
155 .collect();
156
157 if children.is_empty() {
158 Ok(parent_id.first_child())
160 } else {
161 children.sort();
163 let last = children.last().unwrap();
164 Ok(last.next_sibling()
165 .unwrap_or_else(|| last.first_child()))
166 }
167 }
168}
169
170#[async_trait]
171impl<S: GraphStorage> KnowledgeBaseService for KnowledgeBaseServiceImpl<S> {
172 async fn create_note(
173 &self,
174 title: impl Into<String> + Send,
175 content: impl Into<String> + Send,
176 ) -> Result<Note> {
177 let luhmann_id = self.next_main_id().await?;
179
180 let node_id = self.to_node_id(&luhmann_id);
182 match self.storage.get_node(node_id).await {
183 Ok(_) => return Err(KbError::NoteAlreadyExists(luhmann_id)),
184 Err(StorageError::NodeNotFound(_)) => (), Err(e) => return Err(KbError::Storage(e)),
186 }
187
188 let note = Note::new(luhmann_id, title, content);
189 let node = note.to_node();
190 self.storage.create_node(&node).await?;
191
192 Ok(note)
193 }
194
195 async fn create_note_with_id(
196 &self,
197 id: LuhmannId,
198 title: impl Into<String> + Send,
199 content: impl Into<String> + Send,
200 ) -> Result<Note> {
201 let node_id = self.to_node_id(&id);
203 match self.storage.get_node(node_id).await {
204 Ok(_) => return Err(KbError::NoteAlreadyExists(id)),
205 Err(StorageError::NodeNotFound(_)) => (), Err(e) => return Err(KbError::Storage(e)),
207 }
208
209 let note = Note::new(id, title, content);
210 let node = note.to_node();
211 self.storage.create_node(&node).await?;
212
213 Ok(note)
214 }
215
216 async fn create_branch(
217 &self,
218 parent_id: &LuhmannId,
219 title: impl Into<String> + Send,
220 content: impl Into<String> + Send,
221 ) -> Result<Note> {
222 let parent_node_id = self.to_node_id(parent_id);
224 self.storage.get_node(parent_node_id).await
225 .map_err(|e| match e {
226 StorageError::NodeNotFound(_) => KbError::NoteNotFound(parent_id.clone()),
227 _ => KbError::Storage(e),
228 })?;
229
230 let child_id = self.next_child_id(parent_id).await?;
232
233 let child_node_id = self.to_node_id(&child_id);
235 match self.storage.get_node(child_node_id).await {
236 Ok(_) => return Err(KbError::NoteAlreadyExists(child_id)),
237 Err(StorageError::NodeNotFound(_)) => (), Err(e) => return Err(KbError::Storage(e)),
239 }
240
241 let note = Note::new(child_id.clone(), title, content);
243 let node = note.to_node();
244 self.storage.create_node(&node).await?;
245
246 let mut props = Properties::new();
248 props.insert("context".to_string(), PropertyValue::String(format!("Branch of {}", parent_id)));
249
250 let edge = Edge::new(
251 "references",
252 self.to_node_id(&child_id),
253 parent_node_id,
254 props,
255 );
256 self.storage.create_edge(&edge).await?;
257
258 Ok(note)
259 }
260
261 async fn get_note(&self, note_id: &LuhmannId) -> Result<Note> {
262 let node_id = self.to_node_id(note_id);
263 let node = self.storage.get_node(node_id).await
264 .map_err(|e| match e {
265 StorageError::NodeNotFound(_) => KbError::NoteNotFound(note_id.clone()),
266 _ => KbError::Storage(e),
267 })?;
268
269 Note::from_node(&node)
270 .ok_or_else(|| KbError::NoteNotFound(note_id.clone()))
271 }
272
273 async fn list_notes(&self) -> Result<Vec<Note>> {
274 let query = SearchQuery {
276 node_types: vec!["note".to_string()],
277 limit: 10000, ..SearchQuery::default()
279 };
280
281 let results = self.storage.search_nodes(&query).await?;
282
283 let mut notes: Vec<Note> = results.items
284 .into_iter()
285 .filter_map(|node| Note::from_node(&node))
286 .collect();
287
288 notes.sort_by(|a, b| a.id.cmp(&b.id));
290
291 Ok(notes)
292 }
293
294 async fn list_notes_by_prefix(&self, prefix: &LuhmannId) -> Result<Vec<Note>> {
295 let all_notes = self.list_notes().await?;
296
297 let filtered: Vec<Note> = all_notes
298 .into_iter()
299 .filter(|note| {
300 note.id == *prefix || note.id.is_descendant_of(prefix)
301 })
302 .collect();
303
304 Ok(filtered)
305 }
306
307 async fn delete_note(&self, note_id: &LuhmannId) -> Result<()> {
308 let _ = self.get_note(note_id).await?;
310
311 let node_id = self.to_node_id(note_id);
313 self.storage.delete_node(node_id).await
314 .map_err(|e| match e {
315 StorageError::NodeNotFound(_) => KbError::NoteNotFound(note_id.clone()),
316 _ => KbError::Storage(e),
317 })?;
318
319 Ok(())
320 }
321
322 async fn search_notes(&self, query: &str) -> Result<Vec<Note>> {
323 let all_notes = self.list_notes().await?;
324 let query_lower = query.to_lowercase();
325
326 let filtered: Vec<Note> = all_notes.into_iter()
327 .filter(|note| {
328 note.title.to_lowercase().contains(&query_lower) ||
329 note.content.to_lowercase().contains(&query_lower) ||
330 note.tags.iter().any(|tag| tag.to_lowercase().contains(&query_lower))
331 })
332 .collect();
333
334 Ok(filtered)
335 }
336
337 async fn link_notes(
338 &self,
339 from_id: &LuhmannId,
340 to_id: &LuhmannId,
341 context: Option<String>,
342 ) -> Result<()> {
343 if from_id == to_id {
344 return Err(KbError::SelfLink);
345 }
346
347 self.get_note(from_id).await?;
349 self.get_note(to_id).await?;
350
351 let mut props = Properties::new();
353 if let Some(ctx) = context {
354 props.insert("context".to_string(), PropertyValue::String(ctx));
355 }
356
357 let edge = Edge::new(
358 "references",
359 self.to_node_id(from_id),
360 self.to_node_id(to_id),
361 props,
362 );
363
364 self.storage.create_edge(&edge).await?;
365 Ok(())
366 }
367
368 async fn get_links(&self, note_id: &LuhmannId) -> Result<Vec<NoteLink>> {
369 let node_id = self.to_node_id(note_id);
370
371 let edges = self.storage.get_edges_from(node_id, Some("references")).await?;
373
374 let mut links = Vec::new();
375 for edge in edges {
376 match self.storage.get_node(edge.to_node_id).await {
378 Ok(target_node) => {
379 if let Some(target_id) = target_node.properties.get("luhmann_id")
380 .and_then(|v| v.as_str())
381 .and_then(|s| LuhmannId::parse(s))
382 {
383 let context = edge.properties.get("context")
384 .and_then(|v| v.as_str())
385 .map(String::from);
386
387 links.push(NoteLink::new(
388 note_id.clone(),
389 target_id,
390 LinkType::References,
391 context,
392 ));
393 }
394 }
395 Err(_) => continue, }
397 }
398
399 Ok(links)
400 }
401
402 async fn mark_continuation(&self, from_id: &LuhmannId, to_id: &LuhmannId) -> Result<()> {
403 if from_id == to_id {
404 return Err(KbError::SelfLink);
405 }
406
407 self.get_note(from_id).await?;
409 self.get_note(to_id).await?;
410
411 let mut props = Properties::new();
413 props.insert("context".to_string(), PropertyValue::String("Continues on next note".to_string()));
414
415 let edge = Edge::new(
416 "continues",
417 self.to_node_id(from_id),
418 self.to_node_id(to_id),
419 props,
420 );
421
422 self.storage.create_edge(&edge).await?;
423 Ok(())
424 }
425
426 async fn create_index(&self, parent_id: &LuhmannId) -> Result<Note> {
427 let parent_note = self.get_note(parent_id).await?;
429
430 let all_notes = self.list_notes().await?;
432
433 let children: Vec<&Note> = all_notes
434 .iter()
435 .filter(|note| {
436 note.id.parent().as_ref() == Some(parent_id)
438 })
439 .collect();
440
441 let index_id = LuhmannId::parse(&format!("{}0", parent_id))
443 .ok_or_else(|| KbError::InvalidLuhmannId(format!("{}0", parent_id)))?;
444
445 let index_node_id = self.to_node_id(&index_id);
447 match self.storage.get_node(index_node_id).await {
448 Ok(_) => return Err(KbError::NoteAlreadyExists(index_id)),
449 Err(StorageError::NodeNotFound(_)) => (), Err(e) => return Err(KbError::Storage(e)),
451 }
452
453 let mut content = format!("# Index: {}\n\n", parent_note.title);
455 content.push_str(&format!("Parent note: [[{}]]\n\n", parent_id));
456 content.push_str("Children:\n\n");
457
458 if children.is_empty() {
459 content.push_str("(No children)\n");
460 } else {
461 for child in &children {
462 content.push_str(&format!("- [[{}]]: {}\n", child.id, child.title));
463 }
464 }
465
466 let index_note = Note::new(
468 index_id.clone(),
469 format!("Index: {}", parent_note.title),
470 content,
471 );
472
473 let node = index_note.to_node();
474 self.storage.create_node(&node).await?;
475
476 let mut props = Properties::new();
478 props.insert("context".to_string(), PropertyValue::String("Index of children".to_string()));
479
480 let edge = Edge::new(
481 "child_of",
482 index_node_id,
483 self.to_node_id(parent_id),
484 props,
485 );
486 self.storage.create_edge(&edge).await?;
487
488 Ok(index_note)
489 }
490
491 async fn get_context(&self, note_id: &LuhmannId) -> Result<NoteContext> {
492 let note = self.get_note(note_id).await?;
494
495 let parent = if let Some(parent_id) = note.id.parent() {
497 self.get_note(&parent_id).await.ok()
498 } else {
499 None
500 };
501
502 let all_notes = self.list_notes().await?;
504 let children: Vec<Note> = all_notes
505 .into_iter()
506 .filter(|n| n.id.parent().as_ref() == Some(note_id))
507 .collect();
508
509 let links = self.get_links(note_id).await?;
511 let mut links_to = Vec::new();
512 for link in &links {
513 if let Ok(target_note) = self.get_note(&link.to_note_id).await {
514 links_to.push(target_note);
515 }
516 }
517
518 let node_id = self.to_node_id(note_id);
520 let edges = self.storage.get_edges_to(node_id, Some("references")).await?;
521 let mut backlinks = Vec::new();
522 for edge in edges {
523 if let Ok(source_node) = self.storage.get_node(edge.from_node_id).await {
524 if let Some(note) = Note::from_node(&source_node) {
525 backlinks.push(note);
526 }
527 }
528 }
529
530 let note_node_id = self.to_node_id(note_id);
533 let neighbors = self.storage
534 .get_neighbors(note_node_id, Some("continues"), EdgeDirection::Outgoing)
535 .await?;
536
537 let mut continues_to = Vec::new();
538 for node in neighbors {
539 if let Some(luhmann_str) = node.get_property("luhmann_id").and_then(|v| v.as_str()) {
540 if let Some(target_id) = LuhmannId::parse(luhmann_str) {
541 if let Ok(target_note) = self.get_note(&target_id).await {
542 continues_to.push(target_note);
543 }
544 }
545 }
546 }
547
548 let incoming_neighbors = self.storage
550 .get_neighbors(note_node_id, Some("continues"), EdgeDirection::Incoming)
551 .await?;
552
553 let mut continued_from = Vec::new();
554 for node in incoming_neighbors {
555 if let Some(luhmann_str) = node.get_property("luhmann_id").and_then(|v| v.as_str()) {
556 if let Some(source_id) = LuhmannId::parse(luhmann_str) {
557 if let Ok(source_note) = self.get_note(&source_id).await {
558 continued_from.push(source_note);
559 }
560 }
561 }
562 }
563
564 Ok(NoteContext {
565 note,
566 parent,
567 children,
568 links_to,
569 backlinks,
570 continues_to,
571 continued_from,
572 })
573 }
574}
575
576#[cfg(test)]
577mod tests {
578 use super::*;
579 use crate::storage::memory::InMemoryStorage;
580
581 #[tokio::test]
582 async fn test_create_note_auto_id() {
583 let storage = InMemoryStorage::new();
584 let kb = KnowledgeBaseServiceImpl::new(storage);
585
586 let note1 = kb.create_note("First Note", "Content 1").await.unwrap();
588 assert_eq!(note1.id.to_string(), "1");
589
590 let note2 = kb.create_note("Second Note", "Content 2").await.unwrap();
592 assert_eq!(note2.id.to_string(), "2");
593 }
594
595 #[tokio::test]
596 async fn test_create_note_with_specific_id() {
597 let storage = InMemoryStorage::new();
598 let kb = KnowledgeBaseServiceImpl::new(storage);
599
600 let id = LuhmannId::parse("1a").unwrap();
601 let note = kb.create_note_with_id(id.clone(), "Note 1a", "Content").await.unwrap();
602 assert_eq!(note.id, id);
603 }
604
605 #[tokio::test]
606 async fn test_create_branch() {
607 let storage = InMemoryStorage::new();
608 let kb = KnowledgeBaseServiceImpl::new(storage);
609
610 let parent_id = LuhmannId::parse("1").unwrap();
612 kb.create_note_with_id(parent_id.clone(), "Parent", "Parent content").await.unwrap();
613
614 let child = kb.create_branch(&parent_id, "Child", "Child content").await.unwrap();
616 assert_eq!(child.id.to_string(), "1a");
617
618 let child2 = kb.create_branch(&parent_id, "Child 2", "Child content 2").await.unwrap();
620 assert_eq!(child2.id.to_string(), "1b");
621 }
622
623 #[tokio::test]
624 async fn test_duplicate_id_fails() {
625 let storage = InMemoryStorage::new();
626 let kb = KnowledgeBaseServiceImpl::new(storage);
627
628 let id = LuhmannId::parse("1").unwrap();
629 kb.create_note_with_id(id.clone(), "First", "Content").await.unwrap();
630
631 let result = kb.create_note_with_id(id, "Second", "Content").await;
633 assert!(matches!(result, Err(KbError::NoteAlreadyExists(_))));
634 }
635
636 #[tokio::test]
637 async fn test_get_note() {
638 let storage = InMemoryStorage::new();
639 let kb = KnowledgeBaseServiceImpl::new(storage);
640
641 let note = kb.create_note("Test", "Content").await.unwrap();
642 let retrieved = kb.get_note(¬e.id).await.unwrap();
643
644 assert_eq!(retrieved.title, "Test");
645 assert_eq!(retrieved.content, "Content");
646 }
647
648 #[tokio::test]
649 async fn test_list_notes() {
650 let storage = InMemoryStorage::new();
651 let kb = KnowledgeBaseServiceImpl::new(storage);
652
653 kb.create_note_with_id(LuhmannId::parse("2").unwrap(), "Second", "Content").await.unwrap();
654 kb.create_note_with_id(LuhmannId::parse("1").unwrap(), "First", "Content").await.unwrap();
655 kb.create_note_with_id(LuhmannId::parse("1a").unwrap(), "Child", "Content").await.unwrap();
656
657 let notes = kb.list_notes().await.unwrap();
658
659 assert_eq!(notes.len(), 3);
661 assert_eq!(notes[0].id.to_string(), "1");
662 assert_eq!(notes[1].id.to_string(), "1a");
663 assert_eq!(notes[2].id.to_string(), "2");
664 }
665
666 #[tokio::test]
667 async fn test_list_by_prefix() {
668 let storage = InMemoryStorage::new();
669 let kb = KnowledgeBaseServiceImpl::new(storage);
670
671 kb.create_note_with_id(LuhmannId::parse("1").unwrap(), "One", "Content").await.unwrap();
672 kb.create_note_with_id(LuhmannId::parse("1a").unwrap(), "One-A", "Content").await.unwrap();
673 kb.create_note_with_id(LuhmannId::parse("1a1").unwrap(), "One-A-One", "Content").await.unwrap();
674 kb.create_note_with_id(LuhmannId::parse("1b").unwrap(), "One-B", "Content").await.unwrap();
675 kb.create_note_with_id(LuhmannId::parse("2").unwrap(), "Two", "Content").await.unwrap();
676
677 let prefix = LuhmannId::parse("1a").unwrap();
678 let notes = kb.list_notes_by_prefix(&prefix).await.unwrap();
679
680 assert_eq!(notes.len(), 2); assert!(notes.iter().any(|n| n.id.to_string() == "1a"));
682 assert!(notes.iter().any(|n| n.id.to_string() == "1a1"));
683 }
684
685 #[tokio::test]
686 async fn test_search_notes() {
687 let storage = InMemoryStorage::new();
688 let kb = KnowledgeBaseServiceImpl::new(storage);
689
690 kb.create_note("Rust Programming", "A systems language").await.unwrap();
691 kb.create_note("Python Basics", "Easy to learn").await.unwrap();
692 kb.create_note("Rust vs Go", "Comparison").await.unwrap();
693
694 let results = kb.search_notes("rust").await.unwrap();
695 assert_eq!(results.len(), 2);
696 }
697
698 #[tokio::test]
699 async fn test_link_notes() {
700 let storage = InMemoryStorage::new();
701 let kb = KnowledgeBaseServiceImpl::new(storage);
702
703 let note1 = kb.create_note("First", "Content").await.unwrap();
704 let note2 = kb.create_note("Second", "Content").await.unwrap();
705
706 kb.link_notes(¬e1.id, ¬e2.id, Some("See also".to_string())).await.unwrap();
707
708 let links = kb.get_links(¬e1.id).await.unwrap();
709 assert_eq!(links.len(), 1);
710 assert_eq!(links[0].to_note_id, note2.id);
711 }
712
713 #[tokio::test]
714 async fn test_self_link_fails() {
715 let storage = InMemoryStorage::new();
716 let kb = KnowledgeBaseServiceImpl::new(storage);
717
718 let note = kb.create_note("Note", "Content").await.unwrap();
719
720 let result = kb.link_notes(¬e.id, ¬e.id, None).await;
721 assert!(matches!(result, Err(KbError::SelfLink)));
722 }
723
724 #[tokio::test]
725 async fn test_mark_continuation() {
726 let storage = InMemoryStorage::new();
727 let kb = KnowledgeBaseServiceImpl::new(storage);
728
729 let id1 = LuhmannId::parse("1").unwrap();
730 let id2 = LuhmannId::parse("2").unwrap();
731
732 kb.create_note_with_id(id1.clone(), "First", "Content 1").await.unwrap();
733 kb.create_note_with_id(id2.clone(), "Second", "Content 2").await.unwrap();
734
735 kb.mark_continuation(&id1, &id2).await.unwrap();
737
738 let result = kb.mark_continuation(&id1, &id1).await;
740 assert!(matches!(result, Err(KbError::SelfLink)));
741 }
742
743 #[tokio::test]
744 async fn test_create_index() {
745 let storage = InMemoryStorage::new();
746 let kb = KnowledgeBaseServiceImpl::new(storage);
747
748 let parent_id = LuhmannId::parse("1").unwrap();
750 kb.create_note_with_id(parent_id.clone(), "Parent Note", "Parent content").await.unwrap();
751
752 let child1_id = LuhmannId::parse("1a").unwrap();
753 kb.create_note_with_id(child1_id.clone(), "First Child", "Child 1 content").await.unwrap();
754
755 let child2_id = LuhmannId::parse("1b").unwrap();
756 kb.create_note_with_id(child2_id.clone(), "Second Child", "Child 2 content").await.unwrap();
757
758 let grandchild_id = LuhmannId::parse("1a1").unwrap();
760 kb.create_note_with_id(grandchild_id.clone(), "Grandchild", "Grandchild content").await.unwrap();
761
762 let index = kb.create_index(&parent_id).await.unwrap();
764
765 assert_eq!(index.id.to_string(), "10");
767 assert!(index.content.contains("1a"));
769 assert!(index.content.contains("1b"));
770 assert!(index.content.contains("First Child"));
771 assert!(index.content.contains("Second Child"));
772 assert!(!index.content.contains("1a1"));
774 }
775
776 #[tokio::test]
777 async fn test_delete_note() {
778 let storage = InMemoryStorage::new();
779 let kb = KnowledgeBaseServiceImpl::new(storage);
780
781 let id = LuhmannId::parse("1").unwrap();
783 kb.create_note_with_id(id.clone(), "Test Note", "Test content").await.unwrap();
784
785 let note = kb.get_note(&id).await.unwrap();
787 assert_eq!(note.title, "Test Note");
788
789 kb.delete_note(&id).await.unwrap();
791
792 let result = kb.get_note(&id).await;
794 assert!(matches!(result, Err(KbError::NoteNotFound(_))));
795
796 let notes = kb.list_notes().await.unwrap();
798 assert!(notes.is_empty());
799 }
800
801 #[tokio::test]
802 async fn test_delete_nonexistent_note_fails() {
803 let storage = InMemoryStorage::new();
804 let kb = KnowledgeBaseServiceImpl::new(storage);
805
806 let id = LuhmannId::parse("999").unwrap();
808 let result = kb.delete_note(&id).await;
809
810 assert!(matches!(result, Err(KbError::NoteNotFound(_))));
811 }
812}