Spaces:
Sleeping
Sleeping
| """Tests for app.py functionality - Upload Meeting and Create Project.""" | |
| import pytest | |
| import os | |
| from pathlib import Path | |
| import tempfile | |
| import shutil | |
| # Import app functions | |
| import sys | |
| sys.path.insert(0, str(Path(__file__).parent.parent)) | |
| from src.rag import ProjectRAG | |
| from src.parsers import MeetingParser | |
| # Sample raw meeting notes for testing | |
| SAMPLE_RAW_NOTES_QUANTUM = """ | |
| We had a sync meeting about the quantum error correction project. | |
| Dr. Chen presented the new surface code implementation results. | |
| The decoder is showing 15ms latency which is too slow for real-time correction. | |
| Sarah mentioned we got access to IBM's 127-qubit Eagle processor next month. | |
| Marcus is still blocked on the FPGA development - waiting for the new boards. | |
| We decided to switch from Union-Find to MWPM decoder for better accuracy. | |
| Also agreed to target 1ms latency for the final system. | |
| Tasks: | |
| - Dr. Chen will optimize the decoder by end of week | |
| - Sarah needs to prepare the calibration scripts for IBM hardware | |
| - Marcus to follow up with vendor about FPGA delivery | |
| """ | |
| SAMPLE_RAW_NOTES_NEW_PROJECT = """ | |
| Kickoff meeting for the new recommendation engine project. | |
| Team: Alice, Bob, Carol, David | |
| Discussed architecture options - decided on collaborative filtering approach. | |
| Bob raised concern about cold start problem for new users. | |
| Alice will research embedding models this week. | |
| Carol to set up the data pipeline by Friday. | |
| David blocked on getting production database access. | |
| Next meeting scheduled for Monday. | |
| """ | |
| class TestMeetingParser: | |
| """Test meeting parsing functionality.""" | |
| def test_parse_action_items_from_raw_notes(self): | |
| """Test that action items can be extracted from structured notes.""" | |
| # Create a structured meeting file | |
| structured_content = """# Meeting: Test Meeting | |
| Date: 2025-01-30 | |
| Participants: Alice, Bob | |
| ## Discussion | |
| Test discussion. | |
| ## Action Items | |
| - [ ] Alice: Complete task by 2025-02-05 | |
| - [ ] Bob: Review code | |
| - [x] Carol: Setup done (completed) | |
| ## Blockers | |
| - Waiting for API access | |
| """ | |
| with tempfile.NamedTemporaryFile(mode='w', suffix='.md', delete=False) as f: | |
| f.write(structured_content) | |
| temp_path = Path(f.name) | |
| try: | |
| meeting = MeetingParser.parse(temp_path, "test_project") | |
| assert meeting is not None | |
| assert len(meeting.action_items) == 3 | |
| # Check open items | |
| open_items = [a for a in meeting.action_items if not a.completed] | |
| assert len(open_items) == 2 | |
| # Check assignees | |
| assignees = [a.assignee for a in meeting.action_items if a.assignee] | |
| assert "Alice" in assignees | |
| assert "Bob" in assignees | |
| finally: | |
| os.unlink(temp_path) | |
| def test_parse_blockers(self): | |
| """Test blocker extraction.""" | |
| structured_content = """# Meeting: Blocker Test | |
| Date: 2025-01-30 | |
| Participants: Team | |
| ## Discussion | |
| Discussed blockers. | |
| ## Blockers | |
| - Waiting for hardware delivery | |
| - Need security clearance for data access | |
| - Vendor has not responded to queries | |
| """ | |
| with tempfile.NamedTemporaryFile(mode='w', suffix='.md', delete=False) as f: | |
| f.write(structured_content) | |
| temp_path = Path(f.name) | |
| try: | |
| meeting = MeetingParser.parse(temp_path, "test_project") | |
| assert meeting is not None | |
| assert len(meeting.blockers) == 3 | |
| # blockers is a List[str], not objects | |
| assert any("hardware" in b.lower() for b in meeting.blockers) | |
| finally: | |
| os.unlink(temp_path) | |
| class TestProjectCreation: | |
| """Test creating new projects.""" | |
| def test_create_new_project_directory(self): | |
| """Test that new project directories are created correctly.""" | |
| with tempfile.TemporaryDirectory() as temp_dir: | |
| project_name = "test_new_project" | |
| project_dir = Path(temp_dir) / project_name / "meetings" | |
| # Simulate what app.py does | |
| project_dir.mkdir(parents=True, exist_ok=True) | |
| assert project_dir.exists() | |
| assert project_dir.is_dir() | |
| assert (Path(temp_dir) / project_name).exists() | |
| def test_save_meeting_to_new_project(self): | |
| """Test saving a meeting file to a new project.""" | |
| with tempfile.TemporaryDirectory() as temp_dir: | |
| project_name = "recommendation_engine" | |
| meeting_date = "2025-01-30" | |
| meeting_title = "kickoff" | |
| project_dir = Path(temp_dir) / project_name / "meetings" | |
| project_dir.mkdir(parents=True, exist_ok=True) | |
| filename = f"{meeting_date}-{meeting_title}.md" | |
| file_path = project_dir / filename | |
| content = """# Meeting: Kickoff | |
| Date: 2025-01-30 | |
| Participants: Alice, Bob | |
| ## Discussion | |
| Initial project discussion. | |
| ## Action Items | |
| - [ ] Alice: Research models by 2025-02-05 | |
| ## Blockers | |
| - Need database access | |
| """ | |
| with open(file_path, 'w') as f: | |
| f.write(content) | |
| assert file_path.exists() | |
| # Verify RAG can load it | |
| rag = ProjectRAG(Path(temp_dir)) | |
| rag.load_and_index() | |
| projects = rag.get_all_projects() | |
| assert project_name in projects | |
| # Verify action items are indexed | |
| action_items = rag.get_open_action_items(project=project_name) | |
| assert len(action_items) >= 1 | |
| def test_create_project_with_special_characters(self): | |
| """Test project creation handles names properly.""" | |
| with tempfile.TemporaryDirectory() as temp_dir: | |
| # Test with underscores and numbers | |
| project_name = "project_v2_2025" | |
| project_dir = Path(temp_dir) / project_name / "meetings" | |
| project_dir.mkdir(parents=True, exist_ok=True) | |
| assert project_dir.exists() | |
| class TestUploadMeeting: | |
| """Test the upload meeting functionality.""" | |
| def test_meeting_file_naming(self): | |
| """Test that meeting files are named correctly.""" | |
| meeting_date = "2025-01-30" | |
| meeting_title = "Sprint Planning" | |
| # Simulate the naming logic from app.py | |
| filename = f"{meeting_date}-{meeting_title.lower().replace(' ', '-')}.md" | |
| assert filename == "2025-01-30-sprint-planning.md" | |
| def test_meeting_file_naming_no_title(self): | |
| """Test meeting file naming when no title provided.""" | |
| meeting_date = "2025-01-30" | |
| meeting_title = None | |
| filename = f"{meeting_date}-{meeting_title.lower().replace(' ', '-') if meeting_title else 'meeting'}.md" | |
| assert filename == "2025-01-30-meeting.md" | |
| def test_quantum_project_upload(self): | |
| """Test uploading a meeting to quantum_computing project.""" | |
| with tempfile.TemporaryDirectory() as temp_dir: | |
| # Create existing quantum_computing project | |
| quantum_dir = Path(temp_dir) / "quantum_computing" / "meetings" | |
| quantum_dir.mkdir(parents=True) | |
| # Add an existing meeting | |
| existing_meeting = """# Meeting: Previous Sync | |
| Date: 2025-01-25 | |
| Participants: Dr. Chen, Sarah | |
| ## Discussion | |
| Previous work discussion. | |
| """ | |
| (quantum_dir / "2025-01-25-previous-sync.md").write_text(existing_meeting) | |
| # Now simulate uploading a new meeting | |
| new_meeting_content = """# Meeting: Decoder Optimization Review | |
| Date: 2025-01-30 | |
| Participants: Dr. Chen, Sarah, Marcus | |
| ## Discussion | |
| Reviewed decoder performance. Current latency is 15ms, target is 1ms. | |
| Discussed MWPM vs Union-Find approaches. | |
| ## Decisions | |
| - Switch to MWPM decoder for better accuracy | |
| - Target 1ms latency for production | |
| ## Action Items | |
| - [ ] Dr. Chen: Implement MWPM decoder by 2025-02-07 | |
| - [ ] Sarah: Benchmark on IBM simulator by 2025-02-10 | |
| - [ ] Marcus: Order additional FPGA boards | |
| ## Blockers | |
| - FPGA delivery delayed by 2 weeks | |
| - Need IBM quantum credits approval | |
| """ | |
| new_file = quantum_dir / "2025-01-30-decoder-optimization-review.md" | |
| new_file.write_text(new_meeting_content) | |
| # Verify both meetings exist | |
| meetings = list(quantum_dir.glob("*.md")) | |
| assert len(meetings) == 2 | |
| # Verify RAG indexes both | |
| rag = ProjectRAG(Path(temp_dir)) | |
| rag.load_and_index() | |
| assert "quantum_computing" in rag.get_all_projects() | |
| assert len(rag.meetings) == 2 | |
| # Verify action items | |
| action_items = rag.get_open_action_items(project="quantum_computing") | |
| assert len(action_items) >= 3 | |
| # Verify blockers | |
| blockers = rag.get_blockers(project="quantum_computing") | |
| assert len(blockers) >= 2 | |
| class TestRAGWithMultipleProjects: | |
| """Test RAG functionality with multiple projects.""" | |
| def test_multiple_projects_isolation(self): | |
| """Test that queries can be isolated to specific projects.""" | |
| with tempfile.TemporaryDirectory() as temp_dir: | |
| # Create two projects | |
| project1_dir = Path(temp_dir) / "quantum_computing" / "meetings" | |
| project2_dir = Path(temp_dir) / "covid_prediction" / "meetings" | |
| project1_dir.mkdir(parents=True) | |
| project2_dir.mkdir(parents=True) | |
| # Add meeting to project 1 | |
| (project1_dir / "2025-01-30-quantum.md").write_text("""# Meeting: Quantum Sync | |
| Date: 2025-01-30 | |
| Participants: Dr. Chen | |
| ## Action Items | |
| - [ ] Dr. Chen: Optimize decoder | |
| ## Blockers | |
| - FPGA delivery delayed | |
| """) | |
| # Add meeting to project 2 | |
| (project2_dir / "2025-01-30-covid.md").write_text("""# Meeting: COVID Analysis | |
| Date: 2025-01-30 | |
| Participants: Dr. Foster | |
| ## Action Items | |
| - [ ] Dr. Foster: Review model accuracy | |
| ## Blockers | |
| - Data quality issues with GISAID | |
| """) | |
| rag = ProjectRAG(Path(temp_dir)) | |
| rag.load_and_index() | |
| # Test project filtering | |
| quantum_items = rag.get_open_action_items(project="quantum_computing") | |
| covid_items = rag.get_open_action_items(project="covid_prediction") | |
| assert any("decoder" in item['task'].lower() for item in quantum_items) | |
| assert any("model" in item['task'].lower() for item in covid_items) | |
| # Test blocker filtering | |
| quantum_blockers = rag.get_blockers(project="quantum_computing") | |
| covid_blockers = rag.get_blockers(project="covid_prediction") | |
| assert any("fpga" in b['blocker'].lower() for b in quantum_blockers) | |
| assert any("gisaid" in b['blocker'].lower() for b in covid_blockers) | |
| def test_all_projects_query(self): | |
| """Test querying across all projects.""" | |
| with tempfile.TemporaryDirectory() as temp_dir: | |
| # Create two projects with meetings | |
| for project in ["project_a", "project_b"]: | |
| project_dir = Path(temp_dir) / project / "meetings" | |
| project_dir.mkdir(parents=True) | |
| (project_dir / "2025-01-30-meeting.md").write_text(f"""# Meeting: {project} Sync | |
| Date: 2025-01-30 | |
| Participants: Team | |
| ## Action Items | |
| - [ ] Someone: Task for {project} | |
| """) | |
| rag = ProjectRAG(Path(temp_dir)) | |
| rag.load_and_index() | |
| # Get all action items without filter | |
| all_items = rag.get_open_action_items() | |
| assert len(all_items) >= 2 | |
| class TestIntegrationUploadMeeting: | |
| """Integration tests for upload meeting with LLM (requires tokens).""" | |
| def test_structure_meeting_hf(self): | |
| """Test meeting structuring with HuggingFace.""" | |
| hf_token = os.getenv("HF_TOKEN") | |
| if not hf_token: | |
| pytest.skip("HF_TOKEN not set") | |
| from langchain_huggingface import ChatHuggingFace, HuggingFaceEndpoint | |
| from langchain_core.messages import SystemMessage, HumanMessage | |
| endpoint = HuggingFaceEndpoint( | |
| repo_id="meta-llama/Llama-3.2-3B-Instruct", | |
| temperature=0.3, | |
| max_new_tokens=1024, | |
| huggingfacehub_api_token=hf_token, | |
| timeout=60 | |
| ) | |
| llm = ChatHuggingFace(llm=endpoint) | |
| system_prompt = """Structure these meeting notes into markdown with sections: | |
| # Meeting: [title], Date:, Participants:, ## Discussion, ## Action Items, ## Blockers""" | |
| messages = [ | |
| SystemMessage(content=system_prompt), | |
| HumanMessage(content=f"Raw notes: {SAMPLE_RAW_NOTES_QUANTUM}") | |
| ] | |
| response = llm.invoke(messages) | |
| assert response.content is not None | |
| assert len(response.content) > 100 | |
| # Should have some structure | |
| assert "action" in response.content.lower() or "task" in response.content.lower() | |
| def test_structure_meeting_google(self): | |
| """Test meeting structuring with Google.""" | |
| api_key = os.getenv("GOOGLE_API_KEY") | |
| if not api_key: | |
| pytest.skip("GOOGLE_API_KEY not set") | |
| from langchain_google_genai import ChatGoogleGenerativeAI | |
| from langchain_core.messages import SystemMessage, HumanMessage | |
| llm = ChatGoogleGenerativeAI( | |
| model="gemini-2.0-flash", | |
| temperature=0.3, | |
| google_api_key=api_key, | |
| timeout=60 | |
| ) | |
| system_prompt = """Structure these meeting notes into markdown with sections: | |
| # Meeting: [title], Date:, Participants:, ## Discussion, ## Action Items, ## Blockers""" | |
| messages = [ | |
| SystemMessage(content=system_prompt), | |
| HumanMessage(content=f"Raw notes: {SAMPLE_RAW_NOTES_NEW_PROJECT}") | |
| ] | |
| response = llm.invoke(messages) | |
| assert response.content is not None | |
| assert len(response.content) > 100 | |