@@ -65,6 +65,103 @@ func TestMaterializer_PersistLog(t *testing.T) {
6565 })
6666}
6767
68+ func TestMaterializer_RecordsLifecycleMetrics (t * testing.T ) {
69+ started := testRequestSummary ()
70+ started .Status , started .RequestVersion , started .StatusTimestampMs = entity .RequestStatusStarted , 1 , 15
71+ landed := started
72+ landed .Status , landed .RequestVersion , landed .StatusTimestampMs = entity .RequestStatusLanded , 2 , 20
73+ unreceived := started
74+ unreceived .ReceivedAtMs = 0
75+ statusLog := func (status entity.RequestStatus , version int32 , timestampMs int64 ) entity.RequestLog {
76+ return entity.RequestLog {Queue : "q" , RequestID : "1" , Type : entity .RequestLogTypeStatus , Status : status , RequestVersion : version , TimestampMs : timestampMs }
77+ }
78+
79+ tests := []struct {
80+ name string
81+ before entity.RequestSummary
82+ log entity.RequestLog
83+ wantUpdate bool
84+ wantCounters map [string ]int64
85+ wantHistograms []string
86+ }{
87+ {
88+ name : "accepted log moves the request out of accepting" ,
89+ before : testRequestSummary (),
90+ log : statusLog (entity .RequestStatusAccepted , 0 , 20 ),
91+ wantUpdate : true ,
92+ wantCounters : map [string ]int64 {"request_lifecycle.accepted+queue=q" : 1 },
93+ },
94+ {
95+ name : "a later status overtaking the accepted log still counts acceptance once" ,
96+ before : testRequestSummary (),
97+ log : statusLog (entity .RequestStatusStarted , 1 , 20 ),
98+ wantUpdate : true ,
99+ wantCounters : map [string ]int64 {"request_lifecycle.accepted+queue=q" : 1 },
100+ },
101+ {
102+ name : "terminal log finishes the request and records its land time" ,
103+ before : started ,
104+ log : statusLog (entity .RequestStatusLanded , 2 , 20 ),
105+ wantUpdate : true ,
106+ wantCounters : map [string ]int64 {"request_lifecycle.terminated+from_status=started,queue=q,status=landed" : 1 },
107+ wantHistograms : []string {"request_lifecycle.end_to_end_latency+queue=q,status=landed" },
108+ },
109+ {
110+ name : "missing receipt time counts the finish without a land time" ,
111+ before : unreceived ,
112+ log : statusLog (entity .RequestStatusError , 2 , 20 ),
113+ wantUpdate : true ,
114+ wantCounters : map [string ]int64 {"request_lifecycle.terminated+from_status=started,queue=q,status=error" : 1 },
115+ },
116+ {
117+ name : "re-published terminal log with a newer timestamp rewrites the summary without counting again" ,
118+ before : landed ,
119+ log : statusLog (entity .RequestStatusLanded , 2 , 30 ),
120+ wantUpdate : true ,
121+ },
122+ {
123+ name : "identical terminal redelivery leaves the summary and the metrics alone" ,
124+ before : landed ,
125+ log : statusLog (entity .RequestStatusLanded , 2 , 20 ),
126+ },
127+ {
128+ name : "cancelling is not a finished status" ,
129+ before : started ,
130+ log : statusLog (entity .RequestStatusCancelling , 0 , 20 ),
131+ wantUpdate : true ,
132+ },
133+ }
134+ for _ , tt := range tests {
135+ t .Run (tt .name , func (t * testing.T ) {
136+ f := newMaterializerReceiptFixture (gomock .NewController (t ))
137+ f .logs .EXPECT ().Insert (gomock .Any (), tt .log ).Return (nil )
138+ f .summaries .EXPECT ().Get (gomock .Any (), tt .before .RequestID ).Return (tt .before , nil )
139+ if tt .wantUpdate {
140+ f .summaries .EXPECT ().Update (gomock .Any (), gomock .Any (), tt .before .Version , tt .before .Version + 1 ).Return (nil )
141+ }
142+ f .uris .EXPECT ().Create (gomock .Any (), gomock .Any ()).Return (nil ).AnyTimes ()
143+ f .receipts .EXPECT ().Create (gomock .Any (), gomock .Any ()).Return (nil ).AnyTimes ()
144+
145+ require .NoError (t , f .materializer .PersistLog (context .Background (), tt .log ))
146+
147+ snapshot := f .metrics .Snapshot ()
148+ counters := map [string ]int64 {}
149+ for key , counter := range snapshot .Counters () {
150+ counters [key ] = counter .Value ()
151+ }
152+ if tt .wantCounters == nil {
153+ tt .wantCounters = map [string ]int64 {}
154+ }
155+ assert .Equal (t , tt .wantCounters , counters )
156+ histograms := []string {}
157+ for key := range snapshot .Histograms () {
158+ histograms = append (histograms , key )
159+ }
160+ assert .ElementsMatch (t , tt .wantHistograms , histograms )
161+ })
162+ }
163+ }
164+
68165func TestMaterializer_PersistenceFailures (t * testing.T ) {
69166 writeErr := errors .New ("storage unavailable" )
70167 tests := []struct {
0 commit comments