525 lines
		
	
	
		
			20 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			525 lines
		
	
	
		
			20 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
"""Unit tests for the note_manager module."""
 | 
						|
import unittest
 | 
						|
import tempfile
 | 
						|
import os
 | 
						|
import sqlite3
 | 
						|
from pathlib import Path
 | 
						|
from unittest.mock import patch
 | 
						|
from datetime import datetime
 | 
						|
import time
 | 
						|
 | 
						|
# Add the src directory to the path
 | 
						|
import sys
 | 
						|
sys.path.insert(0, str(Path(__file__).parent.parent / 'src'))
 | 
						|
 | 
						|
from forensictrails.core.note_manager import NoteManager
 | 
						|
from forensictrails.core.case_manager import CaseManager
 | 
						|
from forensictrails.db.database import create_fresh_database
 | 
						|
 | 
						|
 | 
						|
class TestNoteManager(unittest.TestCase):
 | 
						|
    """Test cases for NoteManager class."""
 | 
						|
    
 | 
						|
    def setUp(self):
 | 
						|
        """Set up test fixtures."""
 | 
						|
        self.temp_dir = tempfile.mkdtemp()
 | 
						|
        self.test_db_path = os.path.join(self.temp_dir, 'test.db')
 | 
						|
        self.schema_path = Path(__file__).parent.parent / 'src' / 'forensictrails' / 'db' / 'schema.sql'
 | 
						|
        
 | 
						|
        # Create a fresh database for testing
 | 
						|
        create_fresh_database(self.test_db_path, self.schema_path)
 | 
						|
        
 | 
						|
        # Create manager instances
 | 
						|
        self.case_manager = CaseManager(self.test_db_path)
 | 
						|
        self.note_manager = NoteManager(self.test_db_path)
 | 
						|
        
 | 
						|
        # Create a test case for notes
 | 
						|
        self.case_id = self.case_manager.create_case(
 | 
						|
            description='Test Case for Notes',
 | 
						|
            investigator_name='Detective Smith'
 | 
						|
        )
 | 
						|
    
 | 
						|
    def tearDown(self):
 | 
						|
        """Clean up test fixtures."""
 | 
						|
        if hasattr(self, 'note_manager') and hasattr(self.note_manager, 'conn'):
 | 
						|
            self.note_manager.conn.close()
 | 
						|
        if hasattr(self, 'case_manager') and hasattr(self.case_manager, 'conn'):
 | 
						|
            self.case_manager.conn.close()
 | 
						|
        
 | 
						|
        if os.path.exists(self.test_db_path):
 | 
						|
            os.remove(self.test_db_path)
 | 
						|
        os.rmdir(self.temp_dir)
 | 
						|
    
 | 
						|
    def test_note_manager_initialization(self):
 | 
						|
        """Test NoteManager initializes correctly."""
 | 
						|
        self.assertEqual(self.note_manager.db_path, self.test_db_path)
 | 
						|
        self.assertIsNotNone(self.note_manager.conn)
 | 
						|
        self.assertIsNotNone(self.note_manager.cursor)
 | 
						|
    
 | 
						|
    def test_create_note_basic(self):
 | 
						|
        """Test creating a basic note."""
 | 
						|
        note_id = self.note_manager.create_note(
 | 
						|
            case_id=self.case_id,
 | 
						|
            content='This is a test note.',
 | 
						|
            investigator='Detective Smith'
 | 
						|
        )
 | 
						|
        
 | 
						|
        self.assertIsNotNone(note_id)
 | 
						|
        self.assertIsInstance(note_id, int)
 | 
						|
        
 | 
						|
        # Verify note was created
 | 
						|
        note = self.note_manager.get_note(note_id)
 | 
						|
        self.assertIsNotNone(note)
 | 
						|
        assert note is not None  # Type narrowing for Pylance
 | 
						|
        self.assertEqual(note['note_id'], note_id)
 | 
						|
        self.assertEqual(note['case_id'], self.case_id)
 | 
						|
        self.assertEqual(note['content'], 'This is a test note.')
 | 
						|
        self.assertEqual(note['investigator'], 'Detective Smith')
 | 
						|
        self.assertEqual(note['question_tags'], [])
 | 
						|
    
 | 
						|
    def test_create_note_with_tags(self):
 | 
						|
        """Test creating a note with question tags."""
 | 
						|
        note_id = self.note_manager.create_note(
 | 
						|
            case_id=self.case_id,
 | 
						|
            content='Who was at the scene?',
 | 
						|
            investigator='Detective Jones',
 | 
						|
            question_tags=['WHO', 'WHEN']
 | 
						|
        )
 | 
						|
        
 | 
						|
        note = self.note_manager.get_note(note_id)
 | 
						|
        self.assertIsNotNone(note)
 | 
						|
        assert note is not None  # Type narrowing for Pylance
 | 
						|
        self.assertIn('WHO', note['question_tags'])
 | 
						|
        self.assertIn('WHEN', note['question_tags'])
 | 
						|
    
 | 
						|
    def test_get_note_nonexistent(self):
 | 
						|
        """Test getting a non-existent note returns None."""
 | 
						|
        note = self.note_manager.get_note(99999)
 | 
						|
        self.assertIsNone(note)
 | 
						|
    
 | 
						|
    def test_get_notes_empty(self):
 | 
						|
        """Test getting notes for a case with no notes."""
 | 
						|
        notes = self.note_manager.get_notes(self.case_id)
 | 
						|
        self.assertEqual(notes, [])
 | 
						|
    
 | 
						|
    def test_get_notes_multiple(self):
 | 
						|
        """Test getting multiple notes for a case."""
 | 
						|
        # Create multiple notes
 | 
						|
        note_id1 = self.note_manager.create_note(
 | 
						|
            self.case_id, 'First note', 'Detective A'
 | 
						|
        )
 | 
						|
        time.sleep(0.01)  # Ensure different timestamps
 | 
						|
        note_id2 = self.note_manager.create_note(
 | 
						|
            self.case_id, 'Second note', 'Detective B'
 | 
						|
        )
 | 
						|
        time.sleep(0.01)
 | 
						|
        note_id3 = self.note_manager.create_note(
 | 
						|
            self.case_id, 'Third note', 'Detective C'
 | 
						|
        )
 | 
						|
        
 | 
						|
        notes = self.note_manager.get_notes(self.case_id)
 | 
						|
        
 | 
						|
        self.assertEqual(len(notes), 3)
 | 
						|
        note_ids = [n['note_id'] for n in notes]
 | 
						|
        self.assertIn(note_id1, note_ids)
 | 
						|
        self.assertIn(note_id2, note_ids)
 | 
						|
        self.assertIn(note_id3, note_ids)
 | 
						|
        
 | 
						|
        # Verify they're ordered by creation time (DESC)
 | 
						|
        # Check that timestamps are in descending order
 | 
						|
        timestamps = [n['timestamp'] for n in notes]
 | 
						|
        self.assertEqual(timestamps, sorted(timestamps, reverse=True))
 | 
						|
    
 | 
						|
    def test_update_note_creates_revision(self):
 | 
						|
        """Test that updating a note creates a new revision."""
 | 
						|
        note_id = self.note_manager.create_note(
 | 
						|
            self.case_id, 'Original content', 'Detective Smith'
 | 
						|
        )
 | 
						|
        
 | 
						|
        # Update the note
 | 
						|
        result = self.note_manager.update_note(note_id, 'Updated content')
 | 
						|
        self.assertTrue(result)
 | 
						|
        
 | 
						|
        # Verify the note now shows updated content
 | 
						|
        note = self.note_manager.get_note(note_id)
 | 
						|
        assert note is not None  # Type narrowing for Pylance
 | 
						|
        self.assertEqual(note['content'], 'Updated content')
 | 
						|
        
 | 
						|
        # Verify history exists
 | 
						|
        history = self.note_manager.get_note_history(note_id)
 | 
						|
        self.assertEqual(len(history), 2)
 | 
						|
        self.assertEqual(history[0]['content'], 'Updated content')  # Newest first
 | 
						|
        self.assertEqual(history[0]['revision_number'], 2)
 | 
						|
        self.assertEqual(history[1]['content'], 'Original content')
 | 
						|
        self.assertEqual(history[1]['revision_number'], 1)
 | 
						|
    
 | 
						|
    def test_update_note_multiple_times(self):
 | 
						|
        """Test updating a note multiple times."""
 | 
						|
        note_id = self.note_manager.create_note(
 | 
						|
            self.case_id, 'Version 1', 'Detective Smith'
 | 
						|
        )
 | 
						|
        
 | 
						|
        self.note_manager.update_note(note_id, 'Version 2')
 | 
						|
        self.note_manager.update_note(note_id, 'Version 3')
 | 
						|
        self.note_manager.update_note(note_id, 'Version 4')
 | 
						|
        
 | 
						|
        # Verify latest version
 | 
						|
        note = self.note_manager.get_note(note_id)
 | 
						|
        assert note is not None  # Type narrowing for Pylance
 | 
						|
        self.assertEqual(note['content'], 'Version 4')
 | 
						|
        
 | 
						|
        # Verify all revisions exist
 | 
						|
        history = self.note_manager.get_note_history(note_id)
 | 
						|
        self.assertEqual(len(history), 4)
 | 
						|
        for i, rev in enumerate(history):
 | 
						|
            self.assertEqual(rev['revision_number'], 4 - i)
 | 
						|
    
 | 
						|
    def test_get_note_history_empty(self):
 | 
						|
        """Test getting history for non-existent note."""
 | 
						|
        history = self.note_manager.get_note_history(99999)
 | 
						|
        self.assertEqual(history, [])
 | 
						|
    
 | 
						|
    def test_update_note_tags(self):
 | 
						|
        """Test updating question tags for a note."""
 | 
						|
        note_id = self.note_manager.create_note(
 | 
						|
            self.case_id, 'Test note', 'Detective Smith',
 | 
						|
            question_tags=['WHO']
 | 
						|
        )
 | 
						|
        
 | 
						|
        # Verify initial tags
 | 
						|
        note = self.note_manager.get_note(note_id)
 | 
						|
        assert note is not None  # Type narrowing for Pylance
 | 
						|
        self.assertEqual(note['question_tags'], ['WHO'])
 | 
						|
        
 | 
						|
        # Update tags
 | 
						|
        result = self.note_manager.update_note_tags(note_id, ['WHAT', 'WHEN', 'WHERE'])
 | 
						|
        self.assertTrue(result)
 | 
						|
        
 | 
						|
        # Verify updated tags
 | 
						|
        note = self.note_manager.get_note(note_id)
 | 
						|
        assert note is not None  # Type narrowing for Pylance
 | 
						|
        self.assertIn('WHAT', note['question_tags'])
 | 
						|
        self.assertIn('WHEN', note['question_tags'])
 | 
						|
        self.assertIn('WHERE', note['question_tags'])
 | 
						|
        self.assertNotIn('WHO', note['question_tags'])
 | 
						|
    
 | 
						|
    def test_update_note_tags_remove_all(self):
 | 
						|
        """Test removing all tags from a note."""
 | 
						|
        note_id = self.note_manager.create_note(
 | 
						|
            self.case_id, 'Test note', 'Detective Smith',
 | 
						|
            question_tags=['WHO', 'WHAT']
 | 
						|
        )
 | 
						|
        
 | 
						|
        # Remove all tags
 | 
						|
        result = self.note_manager.update_note_tags(note_id, [])
 | 
						|
        self.assertTrue(result)
 | 
						|
        
 | 
						|
        # Verify no tags
 | 
						|
        note = self.note_manager.get_note(note_id)
 | 
						|
        assert note is not None  # Type narrowing for Pylance
 | 
						|
        self.assertEqual(note['question_tags'], [])
 | 
						|
    
 | 
						|
    def test_update_note_tags_nonexistent_note(self):
 | 
						|
        """Test updating tags for non-existent note."""
 | 
						|
        result = self.note_manager.update_note_tags(99999, ['WHO'])
 | 
						|
        self.assertFalse(result)
 | 
						|
    
 | 
						|
    def test_delete_note(self):
 | 
						|
        """Test deleting a note."""
 | 
						|
        note_id = self.note_manager.create_note(
 | 
						|
            self.case_id, 'Test note', 'Detective Smith'
 | 
						|
        )
 | 
						|
        
 | 
						|
        # Verify note exists
 | 
						|
        note = self.note_manager.get_note(note_id)
 | 
						|
        self.assertIsNotNone(note)
 | 
						|
        
 | 
						|
        # Delete note
 | 
						|
        result = self.note_manager.delete_note(note_id)
 | 
						|
        self.assertTrue(result)
 | 
						|
        
 | 
						|
        # Verify note is gone
 | 
						|
        note = self.note_manager.get_note(note_id)
 | 
						|
        self.assertIsNone(note)
 | 
						|
    
 | 
						|
    def test_delete_note_deletes_revisions(self):
 | 
						|
        """Test that deleting a note also deletes all revisions."""
 | 
						|
        note_id = self.note_manager.create_note(
 | 
						|
            self.case_id, 'Original', 'Detective Smith'
 | 
						|
        )
 | 
						|
        self.note_manager.update_note(note_id, 'Updated')
 | 
						|
        self.note_manager.update_note(note_id, 'Updated again')
 | 
						|
        
 | 
						|
        # Delete note
 | 
						|
        self.note_manager.delete_note(note_id)
 | 
						|
        
 | 
						|
        # Verify revisions are gone
 | 
						|
        history = self.note_manager.get_note_history(note_id)
 | 
						|
        self.assertEqual(history, [])
 | 
						|
    
 | 
						|
    def test_delete_nonexistent_note(self):
 | 
						|
        """Test deleting a non-existent note returns False."""
 | 
						|
        result = self.note_manager.delete_note(99999)
 | 
						|
        self.assertFalse(result)
 | 
						|
    
 | 
						|
    def test_search_notes_by_case(self):
 | 
						|
        """Test searching notes by case ID."""
 | 
						|
        # Create second case
 | 
						|
        case_id2 = self.case_manager.create_case(
 | 
						|
            description='Second Case',
 | 
						|
            investigator_name='Detective Jones'
 | 
						|
        )
 | 
						|
        
 | 
						|
        # Create notes in both cases
 | 
						|
        self.note_manager.create_note(self.case_id, 'Case 1 Note 1', 'Detective A')
 | 
						|
        self.note_manager.create_note(self.case_id, 'Case 1 Note 2', 'Detective B')
 | 
						|
        self.note_manager.create_note(case_id2, 'Case 2 Note 1', 'Detective C')
 | 
						|
        
 | 
						|
        # Search by case
 | 
						|
        case1_notes = self.note_manager.search_notes(case_id=self.case_id)
 | 
						|
        case2_notes = self.note_manager.search_notes(case_id=case_id2)
 | 
						|
        
 | 
						|
        self.assertEqual(len(case1_notes), 2)
 | 
						|
        self.assertEqual(len(case2_notes), 1)
 | 
						|
    
 | 
						|
    def test_search_notes_by_content(self):
 | 
						|
        """Test searching notes by content text."""
 | 
						|
        self.note_manager.create_note(self.case_id, 'This is about murder', 'Detective A')
 | 
						|
        self.note_manager.create_note(self.case_id, 'This is about theft', 'Detective B')
 | 
						|
        self.note_manager.create_note(self.case_id, 'Another murder note', 'Detective C')
 | 
						|
        
 | 
						|
        # Search for "murder"
 | 
						|
        results = self.note_manager.search_notes(case_id=self.case_id, search_term='murder')
 | 
						|
        self.assertEqual(len(results), 2)
 | 
						|
        
 | 
						|
        # Search for "theft"
 | 
						|
        results = self.note_manager.search_notes(case_id=self.case_id, search_term='theft')
 | 
						|
        self.assertEqual(len(results), 1)
 | 
						|
    
 | 
						|
    def test_search_notes_by_question_tags(self):
 | 
						|
        """Test searching notes by question tags."""
 | 
						|
        self.note_manager.create_note(
 | 
						|
            self.case_id, 'Note 1', 'Detective A', question_tags=['WHO', 'WHAT']
 | 
						|
        )
 | 
						|
        self.note_manager.create_note(
 | 
						|
            self.case_id, 'Note 2', 'Detective B', question_tags=['WHEN', 'WHERE']
 | 
						|
        )
 | 
						|
        self.note_manager.create_note(
 | 
						|
            self.case_id, 'Note 3', 'Detective C', question_tags=['WHO', 'WHEN']
 | 
						|
        )
 | 
						|
        
 | 
						|
        # Search for WHO tags
 | 
						|
        results = self.note_manager.search_notes(case_id=self.case_id, question_tags=['WHO'])
 | 
						|
        self.assertEqual(len(results), 2)
 | 
						|
        
 | 
						|
        # Search for WHERE tags
 | 
						|
        results = self.note_manager.search_notes(case_id=self.case_id, question_tags=['WHERE'])
 | 
						|
        self.assertEqual(len(results), 1)
 | 
						|
    
 | 
						|
    def test_search_notes_combined_filters(self):
 | 
						|
        """Test searching notes with multiple filters."""
 | 
						|
        self.note_manager.create_note(
 | 
						|
            self.case_id, 'Murder investigation note', 'Detective A', question_tags=['WHO']
 | 
						|
        )
 | 
						|
        self.note_manager.create_note(
 | 
						|
            self.case_id, 'Theft investigation note', 'Detective B', question_tags=['WHO']
 | 
						|
        )
 | 
						|
        self.note_manager.create_note(
 | 
						|
            self.case_id, 'Murder witness statement', 'Detective C', question_tags=['WHAT']
 | 
						|
        )
 | 
						|
        
 | 
						|
        # Search for "murder" with WHO tag
 | 
						|
        results = self.note_manager.search_notes(
 | 
						|
            case_id=self.case_id,
 | 
						|
            search_term='murder',
 | 
						|
            question_tags=['WHO']
 | 
						|
        )
 | 
						|
        self.assertEqual(len(results), 1)
 | 
						|
        self.assertIn('Murder investigation', results[0]['content'])
 | 
						|
    
 | 
						|
    def test_search_notes_all_cases(self):
 | 
						|
        """Test searching notes across all cases."""
 | 
						|
        case_id2 = self.case_manager.create_case(
 | 
						|
            description='Second Case',
 | 
						|
            investigator_name='Detective Jones'
 | 
						|
        )
 | 
						|
        
 | 
						|
        self.note_manager.create_note(self.case_id, 'Case 1 evidence', 'Detective A')
 | 
						|
        self.note_manager.create_note(case_id2, 'Case 2 evidence', 'Detective B')
 | 
						|
        
 | 
						|
        # Search without case_id filter
 | 
						|
        results = self.note_manager.search_notes(search_term='evidence')
 | 
						|
        self.assertEqual(len(results), 2)
 | 
						|
    
 | 
						|
    def test_investigator_reuse(self):
 | 
						|
        """Test that investigators are reused across notes."""
 | 
						|
        note_id1 = self.note_manager.create_note(
 | 
						|
            self.case_id, 'Note 1', 'Detective Smith'
 | 
						|
        )
 | 
						|
        note_id2 = self.note_manager.create_note(
 | 
						|
            self.case_id, 'Note 2', 'Detective Smith'
 | 
						|
        )
 | 
						|
        
 | 
						|
        # Both notes should reference the same investigator
 | 
						|
        note1 = self.note_manager.get_note(note_id1)
 | 
						|
        note2 = self.note_manager.get_note(note_id2)
 | 
						|
        assert note1 is not None and note2 is not None  # Type narrowing for Pylance
 | 
						|
        
 | 
						|
        self.assertEqual(note1['investigator'], note2['investigator'])
 | 
						|
    
 | 
						|
    def test_question_definition_reuse(self):
 | 
						|
        """Test that question definitions are reused within a case."""
 | 
						|
        note_id1 = self.note_manager.create_note(
 | 
						|
            self.case_id, 'Note 1', 'Detective A', question_tags=['WHO']
 | 
						|
        )
 | 
						|
        note_id2 = self.note_manager.create_note(
 | 
						|
            self.case_id, 'Note 2', 'Detective B', question_tags=['WHO']
 | 
						|
        )
 | 
						|
        
 | 
						|
        # Both notes should use the same question definition
 | 
						|
        # Verify by checking the database directly
 | 
						|
        cursor = self.note_manager.cursor
 | 
						|
        cursor.execute("""
 | 
						|
            SELECT COUNT(DISTINCT question_id) as count
 | 
						|
            FROM question_definition
 | 
						|
            WHERE case_id = ? AND question_text = 'WHO'
 | 
						|
        """, (self.case_id,))
 | 
						|
        
 | 
						|
        result = cursor.fetchone()
 | 
						|
        self.assertEqual(result['count'], 1)
 | 
						|
    
 | 
						|
    def test_note_manager_with_default_db_path(self):
 | 
						|
        """Test NoteManager uses config default when no path provided."""
 | 
						|
        with patch('forensictrails.core.note_manager.config') as mock_config:
 | 
						|
            mock_config.database_path = self.test_db_path
 | 
						|
            
 | 
						|
            nm = NoteManager()
 | 
						|
            self.assertEqual(nm.db_path, self.test_db_path)
 | 
						|
            nm.conn.close()
 | 
						|
 | 
						|
 | 
						|
class TestNoteManagerIntegration(unittest.TestCase):
 | 
						|
    """Integration tests for NoteManager."""
 | 
						|
    
 | 
						|
    def setUp(self):
 | 
						|
        """Set up test fixtures."""
 | 
						|
        self.temp_dir = tempfile.mkdtemp()
 | 
						|
        self.test_db_path = os.path.join(self.temp_dir, 'integration.db')
 | 
						|
        self.schema_path = Path(__file__).parent.parent / 'src' / 'forensictrails' / 'db' / 'schema.sql'
 | 
						|
        
 | 
						|
        create_fresh_database(self.test_db_path, self.schema_path)
 | 
						|
        
 | 
						|
        self.case_manager = CaseManager(self.test_db_path)
 | 
						|
        self.note_manager = NoteManager(self.test_db_path)
 | 
						|
        
 | 
						|
        self.case_id = self.case_manager.create_case(
 | 
						|
            description='Integration Test Case',
 | 
						|
            investigator_name='Detective Smith'
 | 
						|
        )
 | 
						|
    
 | 
						|
    def tearDown(self):
 | 
						|
        """Clean up test fixtures."""
 | 
						|
        if hasattr(self, 'note_manager') and hasattr(self.note_manager, 'conn'):
 | 
						|
            self.note_manager.conn.close()
 | 
						|
        if hasattr(self, 'case_manager') and hasattr(self.case_manager, 'conn'):
 | 
						|
            self.case_manager.conn.close()
 | 
						|
        
 | 
						|
        if os.path.exists(self.test_db_path):
 | 
						|
            os.remove(self.test_db_path)
 | 
						|
        os.rmdir(self.temp_dir)
 | 
						|
    
 | 
						|
    def test_full_note_lifecycle(self):
 | 
						|
        """Test complete note lifecycle: create, update, tag, delete."""
 | 
						|
        # Create note
 | 
						|
        note_id = self.note_manager.create_note(
 | 
						|
            self.case_id,
 | 
						|
            'Initial observation',
 | 
						|
            'Detective Smith',
 | 
						|
            question_tags=['WHAT']
 | 
						|
        )
 | 
						|
        
 | 
						|
        # Verify creation
 | 
						|
        note = self.note_manager.get_note(note_id)
 | 
						|
        assert note is not None  # Type narrowing for Pylance
 | 
						|
        self.assertEqual(note['content'], 'Initial observation')
 | 
						|
        self.assertEqual(note['question_tags'], ['WHAT'])
 | 
						|
        
 | 
						|
        # Update content
 | 
						|
        self.note_manager.update_note(note_id, 'Updated observation with more details')
 | 
						|
        note = self.note_manager.get_note(note_id)
 | 
						|
        assert note is not None  # Type narrowing for Pylance
 | 
						|
        self.assertEqual(note['content'], 'Updated observation with more details')
 | 
						|
        
 | 
						|
        # Update tags
 | 
						|
        self.note_manager.update_note_tags(note_id, ['WHAT', 'WHERE', 'WHEN'])
 | 
						|
        note = self.note_manager.get_note(note_id)
 | 
						|
        assert note is not None  # Type narrowing for Pylance
 | 
						|
        self.assertEqual(len(note['question_tags']), 3)
 | 
						|
        
 | 
						|
        # Verify history
 | 
						|
        history = self.note_manager.get_note_history(note_id)
 | 
						|
        self.assertEqual(len(history), 2)
 | 
						|
        
 | 
						|
        # Delete note
 | 
						|
        self.note_manager.delete_note(note_id)
 | 
						|
        note = self.note_manager.get_note(note_id)
 | 
						|
        self.assertIsNone(note)
 | 
						|
    
 | 
						|
    def test_case_with_multiple_notes_and_tags(self):
 | 
						|
        """Test a case with multiple notes and complex tagging."""
 | 
						|
        # Create various notes with different tags
 | 
						|
        notes_data = [
 | 
						|
            ('Suspect identified as John Doe', ['WHO']),
 | 
						|
            ('Crime occurred at 123 Main St', ['WHERE']),
 | 
						|
            ('Incident happened on Jan 15, 2025', ['WHEN']),
 | 
						|
            ('Weapon used was a knife', ['WITH_WHAT', 'WHAT']),
 | 
						|
            ('Motive appears to be robbery', ['WHY', 'WHAT']),
 | 
						|
        ]
 | 
						|
        
 | 
						|
        note_ids = []
 | 
						|
        for content, tags in notes_data:
 | 
						|
            note_id = self.note_manager.create_note(
 | 
						|
                self.case_id, content, 'Detective Smith', question_tags=tags
 | 
						|
            )
 | 
						|
            note_ids.append(note_id)
 | 
						|
        
 | 
						|
        # Verify all notes created
 | 
						|
        all_notes = self.note_manager.get_notes(self.case_id)
 | 
						|
        self.assertEqual(len(all_notes), 5)
 | 
						|
        
 | 
						|
        # Search by specific tags
 | 
						|
        who_notes = self.note_manager.search_notes(case_id=self.case_id, question_tags=['WHO'])
 | 
						|
        self.assertEqual(len(who_notes), 1)
 | 
						|
        
 | 
						|
        what_notes = self.note_manager.search_notes(case_id=self.case_id, question_tags=['WHAT'])
 | 
						|
        # Should find notes with WHAT tag: 'Weapon used was a knife' and 'Motive appears to be robbery'
 | 
						|
        self.assertEqual(len(what_notes), 2)
 | 
						|
    
 | 
						|
    def test_note_revision_immutability(self):
 | 
						|
        """Test that note revisions are truly immutable."""
 | 
						|
        note_id = self.note_manager.create_note(
 | 
						|
            self.case_id, 'Version 1', 'Detective Smith'
 | 
						|
        )
 | 
						|
        
 | 
						|
        # Create multiple revisions
 | 
						|
        for i in range(2, 6):
 | 
						|
            self.note_manager.update_note(note_id, f'Version {i}')
 | 
						|
        
 | 
						|
        # Get full history
 | 
						|
        history = self.note_manager.get_note_history(note_id)
 | 
						|
        
 | 
						|
        # Verify all revisions are preserved
 | 
						|
        self.assertEqual(len(history), 5)
 | 
						|
        
 | 
						|
        # Verify each revision has correct content
 | 
						|
        for i, revision in enumerate(reversed(history)):
 | 
						|
            self.assertEqual(revision['revision_number'], i + 1)
 | 
						|
            self.assertEqual(revision['content'], f'Version {i + 1}')
 | 
						|
            
 | 
						|
            # Verify timestamp exists
 | 
						|
            self.assertIsNotNone(revision['timestamp'])
 | 
						|
 | 
						|
 | 
						|
if __name__ == '__main__':
 | 
						|
    unittest.main()
 |