diff --git a/src/seer/automation/autofix/components/root_cause/models.py b/src/seer/automation/autofix/components/root_cause/models.py index 2bc5251c5..cc69aa536 100644 --- a/src/seer/automation/autofix/components/root_cause/models.py +++ b/src/seer/automation/autofix/components/root_cause/models.py @@ -64,8 +64,8 @@ class RootCauseAnalysisItem(BaseModel): id: int = -1 title: str description: str - # unit_test: UnitTestSnippet | None = None - # reproduction: str | None = None + unit_test: Optional[UnitTestSnippet] = None + reproduction: Optional[str] = None code_context: Optional[list[RootCauseRelevantContext]] = None def to_markdown_string(self) -> str: @@ -91,25 +91,25 @@ def to_markdown_string(self) -> str: class RootCauseAnalysisItemPrompt(BaseModel): title: str description: str - # reproduction_instructions: str | None = None - # unit_test: UnitTestSnippetPrompt | None = None relevant_code: Optional[RootCauseAnalysisRelevantContext] + unit_test: Optional[UnitTestSnippetPrompt] = None + reproduction_instructions: Optional[str] = None @classmethod def from_model(cls, model: RootCauseAnalysisItem): return cls( title=model.title, description=model.description, - # reproduction_instructions=model.reproduction, - # unit_test=( - # UnitTestSnippetPrompt( - # file_path=model.unit_test.file_path, - # code_snippet=model.unit_test.snippet, - # description=model.unit_test.description, - # ) - # if model.unit_test - # else None - # ), + reproduction_instructions=model.reproduction, + unit_test=( + UnitTestSnippetPrompt( + file_path=model.unit_test.file_path, + code_snippet=model.unit_test.snippet, + description=model.unit_test.description, + ) + if model.unit_test + else None + ), relevant_code=( RootCauseAnalysisRelevantContext( snippets=[ @@ -131,16 +131,12 @@ def to_model(self): return RootCauseAnalysisItem.model_validate( { **self.model_dump(), - # "reproduction": self.reproduction_instructions, - # "unit_test": ( - # { - # "file_path": self.unit_test.file_path, - # "snippet": self.unit_test.code_snippet, - # "description": self.unit_test.description, - # } - # if self.unit_test - # else None - # ), + "reproduction": self.reproduction_instructions, + "unit_test": { + "file_path": self.unit_test.file_path, + "snippet": self.unit_test.code_snippet, + "description": self.unit_test.description, + } if self.unit_test else None, "code_context": ( self.relevant_code.model_dump()["snippets"] if self.relevant_code else None ), diff --git a/tests/automation/autofix/components/test_root_cause.py b/tests/automation/autofix/components/test_root_cause.py index cca8ae315..5a0c85b79 100644 --- a/tests/automation/autofix/components/test_root_cause.py +++ b/tests/automation/autofix/components/test_root_cause.py @@ -17,6 +17,7 @@ MultipleRootCauseAnalysisOutputPrompt, RootCauseAnalysisItemPrompt, RootCauseAnalysisRelevantContext, + RootCauseAnalysisItem, RootCauseAnalysisRequest, RootCauseRelevantCodeSnippet, RootCauseRelevantContext, @@ -329,6 +330,56 @@ def test_root_cause_line_numbers_file_not_found(self, component, mock_agent): assert output.causes[0].code_context[0].snippet.start_line is None assert output.causes[0].code_context[0].snippet.end_line is None + def test_root_cause_analysis_item_validation(self): + # Test that model validates without unit_test field + item = RootCauseAnalysisItem( + id=0, + title="Test Title", + description="Test Description", + code_context=None + ) + assert item.model_dump() == { + "id": 0, + "title": "Test Title", + "description": "Test Description", + "code_context": None, + "unit_test": None, + "reproduction": None + } + + # Test that prompt to_model() works without optional fields + prompt = RootCauseAnalysisItemPrompt( + title="Test Title", + description="Test Description", + relevant_code=None + ) + model = prompt.to_model() + assert model.unit_test is None + assert model.reproduction is None + + # Test with all optional fields present + snippet = RootCauseRelevantCodeSnippet( + file_path="test.py", + snippet="def test():\n pass" + ) + context = RootCauseRelevantContext( + id=0, + title="Test Context", + description="Test Description", + snippet=snippet + ) + item = RootCauseAnalysisItem( + id=0, + title="Test Title", + description="Test Description", + code_context=[context], + unit_test=None, + reproduction=None + ) + assert item.model_dump()["code_context"] is not None + assert item.model_dump()["unit_test"] is None + assert item.model_dump()["reproduction"] is None + def test_root_cause_line_numbers_no_match(self, component, mock_agent): mock_agent.return_value.run.side_effect = [ "Some root cause analysis", @@ -377,3 +428,53 @@ def test_root_cause_line_numbers_no_match(self, component, mock_agent): # Verify that the output is still generated but without line numbers assert output.causes[0].code_context[0].snippet.start_line is None assert output.causes[0].code_context[0].snippet.end_line is None + + def test_root_cause_analysis_item_validation(self): + # Test that model validates without unit_test field + item = RootCauseAnalysisItem( + id=0, + title="Test Title", + description="Test Description", + code_context=None + ) + assert item.model_dump() == { + "id": 0, + "title": "Test Title", + "description": "Test Description", + "code_context": None, + "unit_test": None, + "reproduction": None + } + + # Test that prompt to_model() works without optional fields + prompt = RootCauseAnalysisItemPrompt( + title="Test Title", + description="Test Description", + relevant_code=None + ) + model = prompt.to_model() + assert model.unit_test is None + assert model.reproduction is None + + # Test with all optional fields present + snippet = RootCauseRelevantCodeSnippet( + file_path="test.py", + snippet="def test():\n pass" + ) + context = RootCauseRelevantContext( + id=0, + title="Test Context", + description="Test Description", + snippet=snippet + ) + item = RootCauseAnalysisItem( + id=0, + title="Test Title", + description="Test Description", + code_context=[context], + unit_test=None, + reproduction=None + ) + assert item.model_dump()["code_context"] is not None + assert item.model_dump()["unit_test"] is None + assert item.model_dump()["reproduction"] is None