66
77import pytest
88
9- from intentproof import client , configure , wrap
9+ from intentproof import client , configure , run_with_correlation_id , wrap
1010
1111
1212def test_cancelled_error_not_recorded (tmp_path ) -> None :
@@ -25,3 +25,94 @@ async def cancelled() -> None:
2525 asyncio .run (fn ())
2626
2727 assert client .get_outbox ().get_events () == []
28+
29+
30+ def test_async_wrap_records_success (tmp_path ) -> None :
31+ configure (
32+ db_path = str (tmp_path / "outbox.db" ),
33+ data_dir = str (tmp_path / "data" ),
34+ tenant_id = "tnt_async" ,
35+ )
36+
37+ async def add (a : int , b : int ) -> int :
38+ return a + b
39+
40+ fn = wrap (intent = "Async" , action = "async.add" , fn = add )
41+ result = run_with_correlation_id ("corr-async-ok" , lambda : asyncio .run (fn (2 , 3 )))
42+
43+ assert result == 5
44+ events = client .get_outbox ().get_events ()
45+ assert len (events ) == 1
46+ assert events [0 ]["status" ] == "ok"
47+ assert events [0 ]["output" ] == 5
48+
49+
50+ def test_async_wrap_records_error (tmp_path ) -> None :
51+ configure (
52+ db_path = str (tmp_path / "outbox.db" ),
53+ data_dir = str (tmp_path / "data" ),
54+ tenant_id = "tnt_async" ,
55+ )
56+
57+ async def boom () -> None :
58+ raise ValueError ("async boom" )
59+
60+ fn = wrap (intent = "Async" , action = "async.boom" , fn = boom )
61+ with pytest .raises (ValueError , match = "async boom" ):
62+ asyncio .run (fn ())
63+
64+ events = client .get_outbox ().get_events ()
65+ assert events [- 1 ]["status" ] == "error"
66+ assert events [- 1 ]["error" ] == {"message" : "async boom" }
67+
68+
69+ def test_async_wrap_preserves_app_error_when_record_fails (
70+ tmp_path , monkeypatch : pytest .MonkeyPatch
71+ ) -> None :
72+ configure (
73+ db_path = str (tmp_path / "outbox.db" ),
74+ data_dir = str (tmp_path / "data" ),
75+ tenant_id = "tnt_async" ,
76+ )
77+
78+ async def boom () -> None :
79+ raise ValueError ("async boom" )
80+
81+ fn = wrap (intent = "Async" , action = "async.boom" , fn = boom )
82+
83+ def fail_record (** _kwargs : object ) -> None :
84+ raise RuntimeError ("outbox unavailable" )
85+
86+ monkeypatch .setattr (
87+ "intentproof.instrumentation._record_execution" , fail_record
88+ )
89+
90+ with pytest .raises (ValueError , match = "async boom" ) as exc_info :
91+ asyncio .run (fn ())
92+
93+ assert isinstance (exc_info .value .__cause__ , RuntimeError )
94+
95+
96+ def test_async_wrap_record_failure_without_app_error_raises (
97+ tmp_path , monkeypatch : pytest .MonkeyPatch
98+ ) -> None :
99+ configure (
100+ db_path = str (tmp_path / "outbox.db" ),
101+ data_dir = str (tmp_path / "data" ),
102+ tenant_id = "tnt_async" ,
103+ )
104+
105+ async def ok () -> int :
106+ return 1
107+
108+ fn = wrap (intent = "Async" , action = "async.ok" , fn = ok )
109+
110+ def fail_record (** _kwargs : object ) -> None :
111+ raise RuntimeError ("outbox unavailable" )
112+
113+ monkeypatch .setattr (
114+ "intentproof.instrumentation._record_execution" , fail_record
115+ )
116+
117+ with pytest .raises (RuntimeError , match = "outbox unavailable" ):
118+ asyncio .run (fn ())
0 commit comments