2020class TestCloudTraceSink :
2121 """Test CloudTraceSink functionality."""
2222
23+ @pytest .fixture (autouse = True )
24+ def mock_home_dir (self ):
25+ """
26+ Automatically patch Path.home() to use a temporary directory for all tests.
27+ This isolates file operations and prevents FileNotFoundError on CI runners.
28+ """
29+ with tempfile .TemporaryDirectory () as tmp_dir :
30+ mock_home = Path (tmp_dir )
31+
32+ # Patch Path.home in the cloud_tracing module
33+ with patch ("sentience.cloud_tracing.Path.home" , return_value = mock_home ):
34+ # Also patch it in the current test module if used directly
35+ with patch ("pathlib.Path.home" , return_value = mock_home ):
36+ yield mock_home
37+
2338 def test_cloud_trace_sink_upload_success (self ):
2439 """Test CloudTraceSink successfully uploads trace to cloud."""
2540 upload_url = "https://sentience.nyc3.digitaloceanspaces.com/user123/run456/trace.jsonl.gz"
@@ -138,10 +153,7 @@ def test_cloud_trace_sink_network_error_graceful_degradation(self, capsys):
138153 sink = CloudTraceSink (upload_url , run_id = run_id )
139154 sink .emit ({"v" : 1 , "type" : "test" , "seq" : 1 })
140155
141- # Ensure file is written before close
142- sink ._trace_file .flush ()
143- sink ._trace_file .close ()
144-
156+ # Close triggers upload (which will fail due to network error)
145157 # Should not raise, just print warning
146158 sink .close ()
147159
@@ -151,10 +163,7 @@ def test_cloud_trace_sink_network_error_graceful_degradation(self, capsys):
151163 # Verify file was preserved
152164 cache_dir = Path .home () / ".sentience" / "traces" / "pending"
153165 trace_path = cache_dir / f"{ run_id } .jsonl"
154- # File should exist if emit was called (even if close fails)
155- if trace_path .exists ():
156- # Cleanup
157- os .remove (trace_path )
166+ assert trace_path .exists (), "Trace file should be preserved on network error"
158167
159168 def test_cloud_trace_sink_multiple_close_safe (self ):
160169 """Test CloudTraceSink.close() is idempotent."""
@@ -391,61 +400,45 @@ class TestTracerFactory:
391400
392401 def test_create_tracer_pro_tier_success (self , capsys ):
393402 """Test create_tracer returns CloudTraceSink for Pro tier."""
394- with patch ("sentience.tracer_factory.requests.post" ) as mock_post :
395- with patch ("sentience.cloud_tracing.requests.put" ) as mock_put :
396- # Mock API response
397- mock_response = Mock ()
398- mock_response .status_code = 200
399- mock_response .json .return_value = {
400- "upload_url" : "https://sentience.nyc3.digitaloceanspaces.com/upload"
401- }
402- mock_post .return_value = mock_response
403-
404- # Mock upload response
405- mock_put .return_value = Mock (status_code = 200 )
406-
407- run_id = f"test-run-{ uuid .uuid4 ().hex [:8 ]} "
408- tracer = create_tracer (
409- api_key = "sk_pro_test123" ,
410- run_id = run_id ,
411- upload_trace = True ,
412- goal = "Test goal for trace name" ,
413- agent_type = "SentienceAgent" ,
414- llm_model = "gpt-4-turbo" ,
415- start_url = "https://example.com" ,
416- )
417-
418- # Verify Pro tier message
419- captured = capsys .readouterr ()
420- assert "☁️ [Sentience] Cloud tracing enabled (Pro tier)" in captured .out
403+ # Patch orphaned trace recovery to avoid extra API calls
404+ with patch ("sentience.tracer_factory._recover_orphaned_traces" ):
405+ with patch ("sentience.tracer_factory.requests.post" ) as mock_post :
406+ with patch ("sentience.cloud_tracing.requests.put" ) as mock_put :
407+ # Mock API response
408+ mock_response = Mock ()
409+ mock_response .status_code = 200
410+ mock_response .json .return_value = {
411+ "upload_url" : "https://sentience.nyc3.digitaloceanspaces.com/upload"
412+ }
413+ mock_post .return_value = mock_response
421414
422- # Verify tracer works
423- assert tracer .run_id == run_id
424- # Check if sink is CloudTraceSink (it should be)
425- assert isinstance (
426- tracer .sink , CloudTraceSink
427- ), f"Expected CloudTraceSink, got { type (tracer .sink )} "
428- assert tracer .sink .run_id == run_id # Verify run_id is passed
415+ # Mock upload response
416+ mock_put .return_value = Mock (status_code = 200 )
429417
430- # Verify the init API was called
431- assert mock_post .called
432- assert mock_post .call_count == 1
418+ run_id = f"test-run-{ uuid .uuid4 ().hex [:8 ]} "
419+ tracer = create_tracer (
420+ api_key = "sk_pro_test123" , run_id = run_id , upload_trace = True
421+ )
433422
434- # Verify metadata was sent correctly
435- call_args = mock_post .call_args
436- request_payload = call_args [1 ]["json" ]
437- assert "run_id" in request_payload
438- assert request_payload ["run_id" ] == run_id
439- assert "metadata" in request_payload
440- metadata = request_payload ["metadata" ]
441- assert metadata ["goal" ] == "Test goal for trace name"
442- assert metadata ["agent_type" ] == "SentienceAgent"
443- assert metadata ["llm_model" ] == "gpt-4-turbo"
444- assert metadata ["start_url" ] == "https://example.com"
445-
446- # Cleanup - emit at least one event so file exists before close
447- tracer .emit ("test" , {"v" : 1 , "seq" : 1 })
448- tracer .close ()
423+ # Verify Pro tier message
424+ captured = capsys .readouterr ()
425+ assert "☁️ [Sentience] Cloud tracing enabled (Pro tier)" in captured .out
426+
427+ # Verify tracer works
428+ assert tracer .run_id == run_id
429+ # Check if sink is CloudTraceSink (it should be)
430+ assert isinstance (
431+ tracer .sink , CloudTraceSink
432+ ), f"Expected CloudTraceSink, got { type (tracer .sink )} "
433+ assert tracer .sink .run_id == run_id # Verify run_id is passed
434+
435+ # Verify the init API was called (only once, since orphaned recovery is patched)
436+ assert mock_post .called
437+ assert mock_post .call_count == 1
438+
439+ # Cleanup - emit at least one event so file exists before close
440+ tracer .emit ("test" , {"v" : 1 , "seq" : 1 })
441+ tracer .close ()
449442
450443 def test_create_tracer_free_tier_fallback (self , capsys ):
451444 """Test create_tracer falls back to local for free tier."""
0 commit comments