11use std:: sync:: Arc ;
2- use std:: time:: Duration ;
32
43use axum:: { body:: Body , http:: Request } ;
54use common_redis:: MockRedisClient ;
6- use cymbal:: { app_context:: AppContext , modes:: processing:: ProcessingConfig , router:: get_router} ;
7- use httpmock:: prelude:: * ;
8- use serde_json:: json;
5+ use cymbal:: {
6+ app_context:: AppContext , error:: UnhandledError , modes:: processing:: ProcessingConfig ,
7+ router:: get_router,
8+ } ;
9+ use serde_json:: { json, Value } ;
10+ use shimforge:: { mock, Session } ;
911use sqlx:: PgPool ;
1012use tower:: ServiceExt ;
1113use uuid:: Uuid ;
1214
1315mod common;
1416mod utils;
1517
16- // One test per binary: common_posthog::init configures a process-wide global
17- // client, so a second init with a different mock server would be ignored.
1818#[ sqlx:: test( migrations = "./tests/test_migrations" ) ]
1919async fn pipeline_failure_is_captured_as_posthog_exception ( db : PgPool ) {
20- let posthog = MockServer :: start_async ( ) . await ;
21- let capture = posthog
22- . mock_async ( |when, then| {
23- when. method ( POST )
24- . path ( "/i/v1/analytics/events" )
25- . body_contains ( "\" $exception\" " )
26- . body_contains ( "UnhandledError" )
27- . body_contains ( "\" service\" :\" cymbal-test\" " )
28- . body_contains ( "\" request_id\" " ) ;
29- then. status ( 200 ) . body ( "{\" results\" :{}}" ) ;
30- } )
31- . await ;
32- // Catch-all so an unexpected payload shape fails the specific assertion
33- // below instead of surfacing as a connection-level SDK error.
34- let fallback = posthog
35- . mock_async ( |when, then| {
36- when. path_contains ( "/" ) ;
37- then. status ( 200 ) . body ( "{\" results\" :{}}" ) ;
38- } )
39- . await ;
40-
41- common_posthog:: init ( "cymbal-test" , Some ( "test-api-key" ) , & posthog. base_url ( ) )
42- . await
43- . expect ( "posthog init" ) ;
44-
4520 let ( addr, _) = common:: spawn_stub_server ( common:: ServerBehavior :: Happy ) . await ;
4621 let mut config = ProcessingConfig :: init_with_defaults ( ) . unwrap ( ) ;
4722 config. remote_resolution_host = "127.0.0.1" . to_string ( ) ;
@@ -68,11 +43,32 @@ async fn pipeline_failure_is_captured_as_posthog_exception(db: PgPool) {
6843 } ,
6944 } ] ) ;
7045
46+ // The route calls capture on this task; SDK delivery is covered by
47+ // common/posthog/tests/panic_capture.rs.
48+ let mut session = Session :: new ( ) ;
49+ let capture = mock ! (
50+ session,
51+ common_posthog:: capture_exception:: <UnhandledError >,
52+ fn ( Arc <UnhandledError >, [ ( & ' static str , Value ) ; 3 ] )
53+ ) ;
54+ capture
55+ . expect ( )
56+ . with ( |error, properties| {
57+ matches ! ( & * * error, UnhandledError :: SqlxError ( sqlx:: Error :: PoolClosed ) )
58+ && properties. contains ( & ( "request_id" , json ! ( "capture-test-request" ) ) )
59+ && properties. contains ( & ( "batch_event_count" , json ! ( 1 ) ) )
60+ && properties. contains ( & ( "team_count" , json ! ( 1 ) ) )
61+ } )
62+ . once ( )
63+ . returns_default ( ) ;
64+ // Enforced when `session` drops, not at call time.
65+
7166 let response = router
7267 . oneshot (
7368 Request :: builder ( )
7469 . method ( "POST" )
7570 . header ( "content-type" , "application/json" )
71+ . header ( "x-request-id" , "capture-test-request" )
7672 . uri ( "/process" )
7773 . body ( Body :: from ( serde_json:: to_vec ( & event) . unwrap ( ) ) )
7874 . unwrap ( ) ,
@@ -83,22 +79,4 @@ async fn pipeline_failure_is_captured_as_posthog_exception(db: PgPool) {
8379 response. status( ) ,
8480 reqwest:: StatusCode :: INTERNAL_SERVER_ERROR
8581 ) ;
86-
87- // The capture is fire-and-forget, and the SDK only buffers it: one event
88- // never reaches `flush_at`, so delivery would otherwise wait on the 5s
89- // `flush_interval_ms`. Flush every turn so this waits on the spawned send
90- // rather than racing that interval.
91- for _ in 0 ..100 {
92- posthog_rs:: flush ( ) . await ;
93- if capture. hits_async ( ) . await > 0 {
94- break ;
95- }
96- tokio:: time:: sleep ( Duration :: from_millis ( 50 ) ) . await ;
97- }
98- assert_eq ! (
99- capture. hits_async( ) . await ,
100- 1 ,
101- "expected a matching $exception capture; total capture requests seen: {}" ,
102- fallback. hits_async( ) . await
103- ) ;
10482}
0 commit comments