2727# Add project root to path for imports
2828sys .path .insert (0 , os .path .abspath (os .path .join (os .path .dirname (__file__ ), '..' )))
2929
30+ # Define test environment variables used throughout the test file
31+ TEST_ENV_VARS = {
32+ 'GITHUB_REPOSITORY' : 'mock/repo' ,
33+ 'GITHUB_TOKEN' : 'mock-token' ,
34+ 'BASE_BRANCH' : 'main' ,
35+ 'CONTRAST_HOST' : 'test.contrastsecurity.com' ,
36+ 'CONTRAST_ORG_ID' : 'test-org-id' ,
37+ 'CONTRAST_APP_ID' : 'test-app-id' ,
38+ 'CONTRAST_AUTHORIZATION_KEY' : 'test-auth-key' ,
39+ 'CONTRAST_API_KEY' : 'test-api-key' ,
40+ 'GITHUB_WORKSPACE' : '/tmp' ,
41+ 'RUN_TASK' : 'closed' ,
42+ 'BUILD_COMMAND' : 'echo "Test build command"' ,
43+ 'GITHUB_EVENT_PATH' : '/tmp/github_event.json' ,
44+ 'REPO_ROOT' : '/tmp/test_repo' ,
45+ }
46+
47+ # Set environment variables before importing modules to prevent initialization errors
48+ os .environ .update (TEST_ENV_VARS )
49+
3050# Now import project modules (after path modification)
3151from src .config import reset_config , get_config # noqa: E402
3252from src import closed_handler # noqa: E402
@@ -43,16 +63,8 @@ def setUp(self):
4363
4464 reset_config ()
4565
46- # Mock environment variables
47- self .env_patcher = patch .dict (os .environ , {
48- 'CONTRAST_HOST' : 'test.contrastsecurity.com' ,
49- 'CONTRAST_ORG_ID' : 'test-org-id' ,
50- 'CONTRAST_APP_ID' : 'test-app-id' ,
51- 'CONTRAST_AUTHORIZATION_KEY' : 'test-auth-key' ,
52- 'CONTRAST_API_KEY' : 'test-api-key' ,
53- 'GITHUB_EVENT_PATH' : '/tmp/github_event.json' ,
54- 'REPO_ROOT' : '/tmp/test_repo' ,
55- })
66+ # Mock environment variables with complete required vars
67+ self .env_patcher = patch .dict (os .environ , TEST_ENV_VARS )
5668 self .env_patcher .start ()
5769
5870 self .config = get_config ()
@@ -272,6 +284,87 @@ def test_handle_closed_pr_integration(self, mock_init_telemetry, mock_load_event
272284 mock_extract_vuln .assert_called_once_with ([])
273285 mock_notify .assert_called_once_with ("REM-123" , 123 )
274286 mock_send_telemetry .assert_called_once ()
287+
288+ @patch ('src.telemetry_handler.update_telemetry' )
289+ def test_extract_remediation_info_copilot_branch (self , mock_update_telemetry ):
290+ """Test _extract_remediation_info with Copilot branch"""
291+ with patch ('src.closed_handler.extract_issue_number_from_branch' ) as mock_extract_issue :
292+ with patch ('src.closed_handler.extract_remediation_id_from_labels' ) as mock_extract_remediation_id :
293+ # Setup
294+ mock_extract_issue .return_value = 42
295+ mock_extract_remediation_id .return_value = "REM-456"
296+
297+ pull_request = {
298+ "head" : {"ref" : "copilot/fix-42" },
299+ "labels" : [{"name" : "smartfix-id:REM-456" }]
300+ }
301+
302+ # Execute
303+ result = closed_handler ._extract_remediation_info (pull_request )
304+
305+ # Assert
306+ self .assertEqual (result , ("REM-456" , [{"name" : "smartfix-id:REM-456" }]))
307+ mock_extract_issue .assert_called_once_with ("copilot/fix-42" )
308+ mock_extract_remediation_id .assert_called_once_with ([{"name" : "smartfix-id:REM-456" }])
309+
310+ # Verify telemetry updates
311+ mock_update_telemetry .assert_any_call ("additionalAttributes.externalIssueNumber" , 42 )
312+ mock_update_telemetry .assert_any_call ("additionalAttributes.codingAgent" , "EXTERNAL-COPILOT" )
313+
314+ @patch ('src.telemetry_handler.update_telemetry' )
315+ def test_extract_remediation_info_claude_branch (self , mock_update_telemetry ):
316+ """Test _extract_remediation_info with Claude Code branch"""
317+ with patch ('src.closed_handler.extract_issue_number_from_branch' ) as mock_extract_issue :
318+ with patch ('src.closed_handler.extract_remediation_id_from_labels' ) as mock_extract_remediation_id :
319+ # Setup
320+ mock_extract_issue .return_value = 75
321+ mock_extract_remediation_id .return_value = "REM-789"
322+
323+ pull_request = {
324+ "head" : {"ref" : "claude/issue-75-20250908-1723" },
325+ "labels" : [{"name" : "smartfix-id:REM-789" }]
326+ }
327+
328+ # Execute
329+ result = closed_handler ._extract_remediation_info (pull_request )
330+
331+ # Assert
332+ self .assertEqual (result , ("REM-789" , [{"name" : "smartfix-id:REM-789" }]))
333+ mock_extract_issue .assert_called_once_with ("claude/issue-75-20250908-1723" )
334+ mock_extract_remediation_id .assert_called_once_with ([{"name" : "smartfix-id:REM-789" }])
335+
336+ # Verify telemetry updates - key assertions for Claude Code
337+ mock_update_telemetry .assert_any_call ("additionalAttributes.externalIssueNumber" , 75 )
338+ mock_update_telemetry .assert_any_call ("additionalAttributes.codingAgent" , "EXTERNAL-CLAUDE-CODE" )
339+
340+ @patch ('src.telemetry_handler.update_telemetry' )
341+ def test_extract_remediation_info_claude_branch_no_issue_number (self , mock_update_telemetry ):
342+ """Test _extract_remediation_info with Claude Code branch without extractable issue number"""
343+ with patch ('src.closed_handler.extract_issue_number_from_branch' ) as mock_extract_issue :
344+ with patch ('src.closed_handler.extract_remediation_id_from_labels' ) as mock_extract_remediation_id :
345+ # Setup - simulate issue number not found
346+ mock_extract_issue .return_value = None
347+ mock_extract_remediation_id .return_value = "REM-789"
348+
349+ pull_request = {
350+ "head" : {"ref" : "claude/issue-75-20250908-1723" },
351+ "labels" : [{"name" : "smartfix-id:REM-789" }]
352+ }
353+
354+ # Execute
355+ result = closed_handler ._extract_remediation_info (pull_request )
356+
357+ # Assert
358+ self .assertEqual (result , ("REM-789" , [{"name" : "smartfix-id:REM-789" }]))
359+ mock_extract_issue .assert_called_once_with ("claude/issue-75-20250908-1723" )
360+ mock_extract_remediation_id .assert_called_once_with ([{"name" : "smartfix-id:REM-789" }])
361+
362+ # Should NOT call update_telemetry for externalIssueNumber, but SHOULD call it for codingAgent
363+ # Verify it is not called with externalIssueNumber
364+ for call in mock_update_telemetry .call_args_list :
365+ self .assertNotEqual (call [0 ][0 ], "additionalAttributes.externalIssueNumber" )
366+ # But should still identify as Claude Code agent
367+ mock_update_telemetry .assert_any_call ("additionalAttributes.codingAgent" , "EXTERNAL-CLAUDE-CODE" )
275368
276369
277370if __name__ == '__main__' :
0 commit comments