Summary| Tags| Feature: api/notifications-flow/notifications-flow.feature| Ticketing MVP - Notifications Flow As a Ticketing MVP automation I want to validate that notifications are sent after key events So that I can verify the notification system is working correctly
Scenario: [1:19] Scenario 1 - Notification After Approved Purchase
ms: 3214
>>
Background:
12
* def baseUrlEvents = karate.properties['baseUrlEvents'] || 'http://localhost:8081'
0
22:51:53.874 karate.env system property was: null
13
* def baseUrlTicketing = karate.properties['baseUrlTicketing'] || 'http://localhost:8082'
0
14
* def baseUrlNotifications = karate.properties['baseUrlNotifications'] || 'http://localhost:8083'
0
15
* def UUID = Java.type('java.util.UUID')
0
16
* def futureDate = '2026-12-15T20:00:00'
0
21
* def tag = UUID.randomUUID() + ''
0
22
* def buyerId = UUID.randomUUID() + ''
0
23
* def buyerEmail = 'buyer-s1-' + tag + '@karate-test.com'
0
24
* def eventTitle = 'Notif S1 ' + tag
0
# Setup: Create Room
27
Given url baseUrlEvents + '/api/v1/rooms'
0
28
And header X-Role = 'ADMIN'
0
29
And header X-User-Id = '00000000-0000-0000-0000-000000000001'
0
30
And request { name: 'Notifications Room S1', maxCapacity: 100 }
0
31
When method post
12
22:51:53.878 request: 1 > POST http://localhost:8081/api/v1/rooms 1 > X-Role: ADMIN 1 > X-User-Id: 00000000-0000-0000-0000-000000000001 1 > Content-Type: application/json; charset=UTF-8 1 > Content-Length: 50 1 > Host: localhost:8081 1 > Connection: Keep-Alive 1 > User-Agent: Apache-HttpClient/4.5.14 (Java/17.0.17) 1 > Accept-Encoding: gzip,deflate {"name":"Notifications Room S1","maxCapacity":100} 22:51:53.890 response time in milliseconds: 11 1 < 201 1 < Content-Type: application/json 1 < Transfer-Encoding: chunked 1 < Date: Wed, 08 Apr 2026 03:51:53 GMT 1 < Keep-Alive: timeout=60 1 < Connection: keep-alive {"id":"ccab62f0-5fd5-405c-9fc8-83596ffa8e1d","name":"Notifications Room S1","maxCapacity":100,"created_at":"2026-04-08T03:51:53.880657145","updated_at":"2026-04-08T03:51:53.880668941"}
32
Then status 201
0
33
* def roomId = response.id
0
# Setup: Create Draft Event
36
Given url baseUrlEvents + '/api/v1/events'
0
37
And header X-Role = 'ADMIN'
0
38
And header X-User-Id = '00000000-0000-0000-0000-000000000001'
0
39
And request
0
{ "roomId": "#(roomId)", "title": "#(eventTitle)", "description": "Test event for approved purchase notification", "date": "#(futureDate)", "capacity": 50, "enableSeats": false }
50
When method post
19
22:51:53.891 request: 2 > POST http://localhost:8081/api/v1/events 2 > X-Role: ADMIN 2 > X-User-Id: 00000000-0000-0000-0000-000000000001 2 > Content-Type: application/json; charset=UTF-8 2 > Content-Length: 230 2 > Host: localhost:8081 2 > Connection: Keep-Alive 2 > User-Agent: Apache-HttpClient/4.5.14 (Java/17.0.17) 2 > Accept-Encoding: gzip,deflate {"roomId":"ccab62f0-5fd5-405c-9fc8-83596ffa8e1d","title":"Notif S1 adaa5cb0-0484-4259-aeff-383fc9b8857c","description":"Test event for approved purchase notification","date":"2026-12-15T20:00:00","capacity":50,"enableSeats":false} 22:51:53.909 response time in milliseconds: 17 2 < 201 2 < Content-Type: application/json 2 < Transfer-Encoding: chunked 2 < Date: Wed, 08 Apr 2026 03:51:53 GMT 2 < Keep-Alive: timeout=60 2 < Connection: keep-alive {"id":"2c3eeb6f-5770-45ef-96dc-137f0f188696","roomId":"ccab62f0-5fd5-405c-9fc8-83596ffa8e1d","title":"Notif S1 adaa5cb0-0484-4259-aeff-383fc9b8857c","description":"Test event for approved purchase notification","date":"2026-12-15T20:00:00","capacity":50,"status":"DRAFT","createdAt":"2026-04-08T03:51:53.898821104","updatedAt":"2026-04-08T03:51:53.898832868","createdBy":"00000000-0000-0000-0000-000000000001","imageUrl":null,"subtitle":null,"location":null,"director":null,"castMembers":null,"duration":null,"tag":null,"isLimited":false,"isFeatured":false,"enableSeats":false,"author":null}
51
Then status 201
0
52
* def eventId = response.id
0
# Setup: Configure Tier
55
Given url baseUrlEvents + '/api/v1/events/' + eventId + '/tiers'
0
56
And header X-Role = 'ADMIN'
0
57
And request
0
[{ "tierType": "GENERAL", "price": 100, "quota": 40 }]
61
When method post
15
22:51:53.911 request: 3 > POST http://localhost:8081/api/v1/events/2c3eeb6f-5770-45ef-96dc-137f0f188696/tiers 3 > X-Role: ADMIN 3 > Content-Type: application/json; charset=UTF-8 3 > Content-Length: 47 3 > Host: localhost:8081 3 > Connection: Keep-Alive 3 > User-Agent: Apache-HttpClient/4.5.14 (Java/17.0.17) 3 > Accept-Encoding: gzip,deflate [{"tierType":"GENERAL","price":100,"quota":40}] 22:51:53.924 response time in milliseconds: 13 3 < 201 3 < Content-Type: application/json 3 < Transfer-Encoding: chunked 3 < Date: Wed, 08 Apr 2026 03:51:53 GMT 3 < Keep-Alive: timeout=60 3 < Connection: keep-alive {"eventId":"2c3eeb6f-5770-45ef-96dc-137f0f188696","tiers":[{"id":"073be63e-1931-4073-88ae-2eb7ad641b91","tierType":"GENERAL","price":100,"quota":40,"validFrom":null,"validUntil":null,"createdAt":"2026-04-08T03:51:53.917844574","updatedAt":"2026-04-08T03:51:53.917861164"}]}
62
Then status 201
0
63
* def tierId = response.tiers[0].id
0
# Setup: Publish Event
66
Given url baseUrlEvents + '/api/v1/events/' + eventId + '/publish'
0
67
And header X-Role = 'ADMIN'
0
68
And header X-User-Id = '00000000-0000-0000-0000-000000000001'
0
69
When method patch
13
22:51:53.927 request: 4 > PATCH http://localhost:8081/api/v1/events/2c3eeb6f-5770-45ef-96dc-137f0f188696/publish 4 > X-Role: ADMIN 4 > X-User-Id: 00000000-0000-0000-0000-000000000001 4 > Host: localhost:8081 4 > Connection: Keep-Alive 4 > User-Agent: Apache-HttpClient/4.5.14 (Java/17.0.17) 4 > Accept-Encoding: gzip,deflate 22:51:53.938 response time in milliseconds: 11 4 < 200 4 < Content-Type: application/json 4 < Transfer-Encoding: chunked 4 < Date: Wed, 08 Apr 2026 03:51:53 GMT 4 < Keep-Alive: timeout=60 4 < Connection: keep-alive {"id":"2c3eeb6f-5770-45ef-96dc-137f0f188696","roomId":"ccab62f0-5fd5-405c-9fc8-83596ffa8e1d","title":"Notif S1 adaa5cb0-0484-4259-aeff-383fc9b8857c","description":"Test event for approved purchase notification","date":"2026-12-15T20:00:00","capacity":50,"status":"PUBLISHED","createdAt":"2026-04-08T03:51:53.898821","updatedAt":"2026-04-08T03:51:53.931231029","createdBy":"00000000-0000-0000-0000-000000000001","imageUrl":null,"subtitle":null,"location":null,"director":null,"castMembers":null,"duration":null,"tag":null,"isLimited":false,"isFeatured":false,"enableSeats":false,"author":null}
70
Then status 200
0
# Create Reservation
73
Given url baseUrlTicketing + '/api/v1/reservations'
0
74
And header X-User-Id = buyerId
0
75
And request
0
{ "eventId": "#(eventId)", "tierId": "#(tierId)", "buyerEmail": "#(buyerEmail)" }
79
When method post
30
22:51:53.940 request: 5 > POST http://localhost:8082/api/v1/reservations 5 > X-User-Id: 9df475eb-fe43-4e2e-a2ab-5db1592b2c60 5 > Content-Type: application/json; charset=UTF-8 5 > Content-Length: 175 5 > Host: localhost:8082 5 > Connection: Keep-Alive 5 > User-Agent: Apache-HttpClient/4.5.14 (Java/17.0.17) 5 > Accept-Encoding: gzip,deflate {"eventId":"2c3eeb6f-5770-45ef-96dc-137f0f188696","tierId":"073be63e-1931-4073-88ae-2eb7ad641b91","buyerEmail":"buyer-s1-adaa5cb0-0484-4259-aeff-383fc9b8857c@karate-test.com"} 22:51:53.969 response time in milliseconds: 29 5 < 201 5 < Content-Type: application/json 5 < Transfer-Encoding: chunked 5 < Date: Wed, 08 Apr 2026 03:51:53 GMT 5 < Keep-Alive: timeout=60 5 < Connection: keep-alive {"id":"42f81161-3231-4fc2-b601-5dda4d11c2df","eventId":"2c3eeb6f-5770-45ef-96dc-137f0f188696","tierId":"073be63e-1931-4073-88ae-2eb7ad641b91","buyerId":"9df475eb-fe43-4e2e-a2ab-5db1592b2c60","status":"PENDING","createdAt":"2026-04-08T03:51:53.961410929","updatedAt":"2026-04-08T03:51:53.961410929","validUntilAt":"2026-04-08T04:01:53.961410929"}
80
Then status 201
0
81
* def reservationId = response.id
0
# Approved Payment
84
Given url baseUrlTicketing + '/api/v1/reservations/' + reservationId + '/payments'
0
85
And header X-User-Id = buyerId
0
86
And request { amount: 100, paymentMethod: 'MOCK', status: 'APPROVED' }
0
87
When method post
27
22:51:53.971 request: 6 > POST http://localhost:8082/api/v1/reservations/42f81161-3231-4fc2-b601-5dda4d11c2df/payments 6 > X-User-Id: 9df475eb-fe43-4e2e-a2ab-5db1592b2c60 6 > Content-Type: application/json; charset=UTF-8 6 > Content-Length: 57 6 > Host: localhost:8082 6 > Connection: Keep-Alive 6 > User-Agent: Apache-HttpClient/4.5.14 (Java/17.0.17) 6 > Accept-Encoding: gzip,deflate {"amount":100,"paymentMethod":"MOCK","status":"APPROVED"} 22:51:53.997 response time in milliseconds: 26 6 < 200 6 < Content-Type: application/json 6 < Transfer-Encoding: chunked 6 < Date: Wed, 08 Apr 2026 03:51:53 GMT 6 < Keep-Alive: timeout=60 6 < Connection: keep-alive {"reservationId":"42f81161-3231-4fc2-b601-5dda4d11c2df","status":"CONFIRMED","ticketId":"451ad9fe-550e-43ed-8710-f0af2b2f5844","message":"Payment approved. Ticket generated.","ticket":{"ticketId":"451ad9fe-550e-43ed-8710-f0af2b2f5844","eventId":"2c3eeb6f-5770-45ef-96dc-137f0f188696","eventTitle":"Notif S1 adaa5cb0-0484-4259-aeff-383fc9b8857c","eventDate":"2026-12-15T20:00:00","tier":"GENERAL","pricePaid":100,"status":"VALID","purchasedAt":"2026-04-08T03:51:53.983108045","buyerEmail":"buyer-s1-adaa5cb0-0484-4259-aeff-383fc9b8857c@karate-test.com","reservationId":"42f81161-3231-4fc2-b601-5dda4d11c2df"},"timestamp":"2026-04-08T03:51:53.98902228"}
88
Then status 200
0
89
* match response.status == 'CONFIRMED'
0
90
* print 'Payment approved - checking notification for buyer:', buyerId
0
22:51:53.998 [print] Payment approved - checking notification for buyer: 9df475eb-fe43-4e2e-a2ab-5db1592b2c60
# Wait for async notification processing with retry (up to 15 seconds)
93
* configure retry = { count: 5, interval: 3000 }
0
# Validate Notification via GET /api/v1/notifications/buyer/{buyerId}
# Runtime: notifications service at 8083 returns paginated response
97
Given url baseUrlNotifications + '/api/v1/notifications/buyer/' + buyerId
0
98
And header X-User-Id = buyerId
0
99
And retry until response.totalElements >= 1
0
100
When method get
3083
22:51:53.999 request: 7 > GET http://localhost:8083/api/v1/notifications/buyer/9df475eb-fe43-4e2e-a2ab-5db1592b2c60 7 > X-User-Id: 9df475eb-fe43-4e2e-a2ab-5db1592b2c60 7 > Host: localhost:8083 7 > Connection: Keep-Alive 7 > User-Agent: Apache-HttpClient/4.5.14 (Java/17.0.17) 7 > Accept-Encoding: gzip,deflate 22:51:54.061 response time in milliseconds: 62 7 < 200 7 < Content-Type: application/json 7 < Transfer-Encoding: chunked 7 < Date: Wed, 08 Apr 2026 03:51:54 GMT 7 < Keep-Alive: timeout=60 7 < Connection: keep-alive {"content":[],"page":0,"size":20,"totalElements":0,"totalPages":0} 22:51:54.064 retry condition not satisfied: response.totalElements >= 1 22:51:54.064 sleeping before retry #1 22:51:57.067 request: 8 > GET http://localhost:8083/api/v1/notifications/buyer/9df475eb-fe43-4e2e-a2ab-5db1592b2c60 8 > X-User-Id: 9df475eb-fe43-4e2e-a2ab-5db1592b2c60 8 > Host: localhost:8083 8 > Connection: Keep-Alive 8 > User-Agent: Apache-HttpClient/4.5.14 (Java/17.0.17) 8 > Accept-Encoding: gzip,deflate 22:51:57.081 response time in milliseconds: 14 8 < 200 8 < Content-Type: application/json 8 < Transfer-Encoding: chunked 8 < Date: Wed, 08 Apr 2026 03:51:57 GMT 8 < Keep-Alive: timeout=60 8 < Connection: keep-alive {"content":[{"id":"0b73aadc-9c28-49e3-b911-75461a36f63e","reservationId":"42f81161-3231-4fc2-b601-5dda4d11c2df","eventId":"2c3eeb6f-5770-45ef-96dc-137f0f188696","tierId":"073be63e-1931-4073-88ae-2eb7ad641b91","buyerId":"9df475eb-fe43-4e2e-a2ab-5db1592b2c60","type":"PAYMENT_SUCCESS","motif":"PAYMENT_SUCCESS","status":"PROCESSED","read":false,"archived":false,"eventName":"Notif S1 adaa5cb0-0484-4259-aeff-383fc9b8857c","createdAt":"2026-04-08T03:51:55.779392Z"}],"page":0,"size":20,"totalElements":1,"totalPages":1} 22:51:57.082 retry condition satisfied
101
Then status 200
0
102
* print 'Notification response totalElements:', response.totalElements
0
22:51:57.082 [print] Notification response totalElements: 1
103
* assert response.totalElements >= 1
0
104
* def notifications = response.content
0
# Find PAYMENT_SUCCESS notification using Karate JsonPath expression
106
* def paymentSuccessNotifs = $notifications[?(@.type == 'PAYMENT_SUCCESS')]
7
107
* assert paymentSuccessNotifs.length > 0
0
108
* def notif = paymentSuccessNotifs[0]
0
109
* match notif.type == 'PAYMENT_SUCCESS'
0
110
* match notif.status == 'PROCESSED'
0
111
* match notif.buyerId == buyerId
0
112
* print 'Scenario 1 PASS: PAYMENT_SUCCESS notification found for buyer:', buyerId
0
22:51:57.090 [print] Scenario 1 PASS: PAYMENT_SUCCESS notification found for buyer: 9df475eb-fe43-4e2e-a2ab-5db1592b2c60
Scenario: [2:115] Scenario 2 - Notification After Rejected Payment
ms: 6150
>>
Background:
12
* def baseUrlEvents = karate.properties['baseUrlEvents'] || 'http://localhost:8081'
0
22:51:57.092 karate.env system property was: null
13
* def baseUrlTicketing = karate.properties['baseUrlTicketing'] || 'http://localhost:8082'
0
14
* def baseUrlNotifications = karate.properties['baseUrlNotifications'] || 'http://localhost:8083'
0
15
* def UUID = Java.type('java.util.UUID')
0
16
* def futureDate = '2026-12-15T20:00:00'
0
117
* def tag = UUID.randomUUID() + ''
0
118
* def buyerId = UUID.randomUUID() + ''
0
119
* def buyerEmail = 'buyer-s2-' + tag + '@karate-test.com'
0
120
* def eventTitle = 'Notif S2 ' + tag
0
# Setup: Create Room
123
Given url baseUrlEvents + '/api/v1/rooms'
0
124
And header X-Role = 'ADMIN'
0
125
And header X-User-Id = '00000000-0000-0000-0000-000000000001'
0
126
And request { name: 'Notifications Room S2', maxCapacity: 100 }
0
127
When method post
13
22:51:57.094 request: 1 > POST http://localhost:8081/api/v1/rooms 1 > X-Role: ADMIN 1 > X-User-Id: 00000000-0000-0000-0000-000000000001 1 > Content-Type: application/json; charset=UTF-8 1 > Content-Length: 50 1 > Host: localhost:8081 1 > Connection: Keep-Alive 1 > User-Agent: Apache-HttpClient/4.5.14 (Java/17.0.17) 1 > Accept-Encoding: gzip,deflate {"name":"Notifications Room S2","maxCapacity":100} 22:51:57.106 response time in milliseconds: 12 1 < 201 1 < Content-Type: application/json 1 < Transfer-Encoding: chunked 1 < Date: Wed, 08 Apr 2026 03:51:57 GMT 1 < Keep-Alive: timeout=60 1 < Connection: keep-alive {"id":"1204088b-eb89-4a20-8cd7-0cfb2a2cd90b","name":"Notifications Room S2","maxCapacity":100,"created_at":"2026-04-08T03:51:57.096793489","updated_at":"2026-04-08T03:51:57.096809198"}
128
Then status 201
0
129
* def roomId = response.id
0
# Setup: Create Draft Event
132
Given url baseUrlEvents + '/api/v1/events'
0
133
And header X-Role = 'ADMIN'
0
134
And header X-User-Id = '00000000-0000-0000-0000-000000000001'
0
135
And request
0
{ "roomId": "#(roomId)", "title": "#(eventTitle)", "description": "Test event for rejected payment notification", "date": "#(futureDate)", "capacity": 50, "enableSeats": false }
146
When method post
18
22:51:57.107 request: 2 > POST http://localhost:8081/api/v1/events 2 > X-Role: ADMIN 2 > X-User-Id: 00000000-0000-0000-0000-000000000001 2 > Content-Type: application/json; charset=UTF-8 2 > Content-Length: 229 2 > Host: localhost:8081 2 > Connection: Keep-Alive 2 > User-Agent: Apache-HttpClient/4.5.14 (Java/17.0.17) 2 > Accept-Encoding: gzip,deflate {"roomId":"1204088b-eb89-4a20-8cd7-0cfb2a2cd90b","title":"Notif S2 f8900c44-bc60-4736-a261-9b08499264cd","description":"Test event for rejected payment notification","date":"2026-12-15T20:00:00","capacity":50,"enableSeats":false} 22:51:57.124 response time in milliseconds: 17 2 < 201 2 < Content-Type: application/json 2 < Transfer-Encoding: chunked 2 < Date: Wed, 08 Apr 2026 03:51:57 GMT 2 < Keep-Alive: timeout=60 2 < Connection: keep-alive {"id":"317d58ed-4cf2-4d55-be15-d6abbe9b91c8","roomId":"1204088b-eb89-4a20-8cd7-0cfb2a2cd90b","title":"Notif S2 f8900c44-bc60-4736-a261-9b08499264cd","description":"Test event for rejected payment notification","date":"2026-12-15T20:00:00","capacity":50,"status":"DRAFT","createdAt":"2026-04-08T03:51:57.111809938","updatedAt":"2026-04-08T03:51:57.11181941","createdBy":"00000000-0000-0000-0000-000000000001","imageUrl":null,"subtitle":null,"location":null,"director":null,"castMembers":null,"duration":null,"tag":null,"isLimited":false,"isFeatured":false,"enableSeats":false,"author":null}
147
Then status 201
0
148
* def eventId = response.id
0
# Setup: Configure Tier
151
Given url baseUrlEvents + '/api/v1/events/' + eventId + '/tiers'
0
152
And header X-Role = 'ADMIN'
0
153
And request
0
[{ "tierType": "GENERAL", "price": 100, "quota": 40 }]
157
When method post
12
22:51:57.126 request: 3 > POST http://localhost:8081/api/v1/events/317d58ed-4cf2-4d55-be15-d6abbe9b91c8/tiers 3 > X-Role: ADMIN 3 > Content-Type: application/json; charset=UTF-8 3 > Content-Length: 47 3 > Host: localhost:8081 3 > Connection: Keep-Alive 3 > User-Agent: Apache-HttpClient/4.5.14 (Java/17.0.17) 3 > Accept-Encoding: gzip,deflate [{"tierType":"GENERAL","price":100,"quota":40}] 22:51:57.136 response time in milliseconds: 10 3 < 201 3 < Content-Type: application/json 3 < Transfer-Encoding: chunked 3 < Date: Wed, 08 Apr 2026 03:51:57 GMT 3 < Keep-Alive: timeout=60 3 < Connection: keep-alive {"eventId":"317d58ed-4cf2-4d55-be15-d6abbe9b91c8","tiers":[{"id":"f208ebf7-c66b-4f9d-b732-851f9203a096","tierType":"GENERAL","price":100,"quota":40,"validFrom":null,"validUntil":null,"createdAt":"2026-04-08T03:51:57.130613334","updatedAt":"2026-04-08T03:51:57.130623915"}]}
158
Then status 201
0
159
* def tierId = response.tiers[0].id
0
# Setup: Publish Event
162
Given url baseUrlEvents + '/api/v1/events/' + eventId + '/publish'
0
163
And header X-Role = 'ADMIN'
0
164
And header X-User-Id = '00000000-0000-0000-0000-000000000001'
0
165
When method patch
11
22:51:57.137 request: 4 > PATCH http://localhost:8081/api/v1/events/317d58ed-4cf2-4d55-be15-d6abbe9b91c8/publish 4 > X-Role: ADMIN 4 > X-User-Id: 00000000-0000-0000-0000-000000000001 4 > Host: localhost:8081 4 > Connection: Keep-Alive 4 > User-Agent: Apache-HttpClient/4.5.14 (Java/17.0.17) 4 > Accept-Encoding: gzip,deflate 22:51:57.148 response time in milliseconds: 11 4 < 200 4 < Content-Type: application/json 4 < Transfer-Encoding: chunked 4 < Date: Wed, 08 Apr 2026 03:51:57 GMT 4 < Keep-Alive: timeout=60 4 < Connection: keep-alive {"id":"317d58ed-4cf2-4d55-be15-d6abbe9b91c8","roomId":"1204088b-eb89-4a20-8cd7-0cfb2a2cd90b","title":"Notif S2 f8900c44-bc60-4736-a261-9b08499264cd","description":"Test event for rejected payment notification","date":"2026-12-15T20:00:00","capacity":50,"status":"PUBLISHED","createdAt":"2026-04-08T03:51:57.11181","updatedAt":"2026-04-08T03:51:57.140932565","createdBy":"00000000-0000-0000-0000-000000000001","imageUrl":null,"subtitle":null,"location":null,"director":null,"castMembers":null,"duration":null,"tag":null,"isLimited":false,"isFeatured":false,"enableSeats":false,"author":null}
166
Then status 200
0
# Create Reservation
169
Given url baseUrlTicketing + '/api/v1/reservations'
0
170
And header X-User-Id = buyerId
0
171
And request
0
{ "eventId": "#(eventId)", "tierId": "#(tierId)", "buyerEmail": "#(buyerEmail)" }
175
When method post
32
22:51:57.149 request: 5 > POST http://localhost:8082/api/v1/reservations 5 > X-User-Id: 64a2fa4c-aa5b-46d8-86ae-256e85db08f9 5 > Content-Type: application/json; charset=UTF-8 5 > Content-Length: 175 5 > Host: localhost:8082 5 > Connection: Keep-Alive 5 > User-Agent: Apache-HttpClient/4.5.14 (Java/17.0.17) 5 > Accept-Encoding: gzip,deflate {"eventId":"317d58ed-4cf2-4d55-be15-d6abbe9b91c8","tierId":"f208ebf7-c66b-4f9d-b732-851f9203a096","buyerEmail":"buyer-s2-f8900c44-bc60-4736-a261-9b08499264cd@karate-test.com"} 22:51:57.180 response time in milliseconds: 31 5 < 201 5 < Content-Type: application/json 5 < Transfer-Encoding: chunked 5 < Date: Wed, 08 Apr 2026 03:51:57 GMT 5 < Keep-Alive: timeout=60 5 < Connection: keep-alive {"id":"0c1aece3-880a-4367-9352-d4c96af918fe","eventId":"317d58ed-4cf2-4d55-be15-d6abbe9b91c8","tierId":"f208ebf7-c66b-4f9d-b732-851f9203a096","buyerId":"64a2fa4c-aa5b-46d8-86ae-256e85db08f9","status":"PENDING","createdAt":"2026-04-08T03:51:57.172686341","updatedAt":"2026-04-08T03:51:57.172686341","validUntilAt":"2026-04-08T04:01:57.172686341"}
176
Then status 201
0
177
* def reservationId = response.id
0
# Declined Payment (runtime contract: HTTP 400, status=PAYMENT_FAILED)
180
Given url baseUrlTicketing + '/api/v1/reservations/' + reservationId + '/payments'
0
181
And header X-User-Id = buyerId
0
182
And request { amount: 100, paymentMethod: 'MOCK', status: 'DECLINED' }
0
183
When method post
21
22:51:57.181 request: 6 > POST http://localhost:8082/api/v1/reservations/0c1aece3-880a-4367-9352-d4c96af918fe/payments 6 > X-User-Id: 64a2fa4c-aa5b-46d8-86ae-256e85db08f9 6 > Content-Type: application/json; charset=UTF-8 6 > Content-Length: 57 6 > Host: localhost:8082 6 > Connection: Keep-Alive 6 > User-Agent: Apache-HttpClient/4.5.14 (Java/17.0.17) 6 > Accept-Encoding: gzip,deflate {"amount":100,"paymentMethod":"MOCK","status":"DECLINED"} 22:51:57.201 response time in milliseconds: 20 6 < 400 6 < Content-Type: application/json 6 < Transfer-Encoding: chunked 6 < Date: Wed, 08 Apr 2026 03:51:57 GMT 6 < Connection: close {"reservationId":"0c1aece3-880a-4367-9352-d4c96af918fe","error":"Payment declined. Your reservation remains active for 10 minutes","status":"PAYMENT_FAILED","timestamp":"2026-04-08T03:51:57.200567141"}
184
Then status 400
0
185
* match response.status == 'PAYMENT_FAILED'
0
186
* print 'Payment rejected - checking PAYMENT_FAILED notification for buyer:', buyerId
0
22:51:57.202 [print] Payment rejected - checking PAYMENT_FAILED notification for buyer: 64a2fa4c-aa5b-46d8-86ae-256e85db08f9
# Wait for async notification processing with retry (up to 15 seconds)
189
* configure retry = { count: 5, interval: 3000 }
0
# Validate Notification via GET /api/v1/notifications/buyer/{buyerId}
192
Given url baseUrlNotifications + '/api/v1/notifications/buyer/' + buyerId
0
193
And header X-User-Id = buyerId
0
194
And retry until response.totalElements >= 1
0
195
When method get
6039
22:51:57.203 request: 7 > GET http://localhost:8083/api/v1/notifications/buyer/64a2fa4c-aa5b-46d8-86ae-256e85db08f9 7 > X-User-Id: 64a2fa4c-aa5b-46d8-86ae-256e85db08f9 7 > Host: localhost:8083 7 > Connection: Keep-Alive 7 > User-Agent: Apache-HttpClient/4.5.14 (Java/17.0.17) 7 > Accept-Encoding: gzip,deflate 22:51:57.208 response time in milliseconds: 4 7 < 200 7 < Content-Type: application/json 7 < Transfer-Encoding: chunked 7 < Date: Wed, 08 Apr 2026 03:51:57 GMT 7 < Keep-Alive: timeout=60 7 < Connection: keep-alive {"content":[],"page":0,"size":20,"totalElements":0,"totalPages":0} 22:51:57.208 retry condition not satisfied: response.totalElements >= 1 22:51:57.208 sleeping before retry #1 22:52:00.211 request: 8 > GET http://localhost:8083/api/v1/notifications/buyer/64a2fa4c-aa5b-46d8-86ae-256e85db08f9 8 > X-User-Id: 64a2fa4c-aa5b-46d8-86ae-256e85db08f9 8 > Host: localhost:8083 8 > Connection: Keep-Alive 8 > User-Agent: Apache-HttpClient/4.5.14 (Java/17.0.17) 8 > Accept-Encoding: gzip,deflate 22:52:00.223 response time in milliseconds: 12 8 < 200 8 < Content-Type: application/json 8 < Transfer-Encoding: chunked 8 < Date: Wed, 08 Apr 2026 03:52:00 GMT 8 < Keep-Alive: timeout=60 8 < Connection: keep-alive {"content":[],"page":0,"size":20,"totalElements":0,"totalPages":0} 22:52:00.224 retry condition not satisfied: response.totalElements >= 1 22:52:00.224 sleeping before retry #2 22:52:03.228 request: 9 > GET http://localhost:8083/api/v1/notifications/buyer/64a2fa4c-aa5b-46d8-86ae-256e85db08f9 9 > X-User-Id: 64a2fa4c-aa5b-46d8-86ae-256e85db08f9 9 > Host: localhost:8083 9 > Connection: Keep-Alive 9 > User-Agent: Apache-HttpClient/4.5.14 (Java/17.0.17) 9 > Accept-Encoding: gzip,deflate 22:52:03.241 response time in milliseconds: 13 9 < 200 9 < Content-Type: application/json 9 < Transfer-Encoding: chunked 9 < Date: Wed, 08 Apr 2026 03:52:03 GMT 9 < Keep-Alive: timeout=60 9 < Connection: keep-alive {"content":[{"id":"acac043e-eabb-4d9d-b14f-45f017ba4591","reservationId":"0c1aece3-880a-4367-9352-d4c96af918fe","eventId":"317d58ed-4cf2-4d55-be15-d6abbe9b91c8","tierId":"f208ebf7-c66b-4f9d-b732-851f9203a096","buyerId":"64a2fa4c-aa5b-46d8-86ae-256e85db08f9","type":"PAYMENT_FAILED","motif":"Payment declined by mock service","status":"PROCESSED","read":false,"archived":false,"eventName":"Notif S2 f8900c44-bc60-4736-a261-9b08499264cd","createdAt":"2026-04-08T03:52:00.790938Z"}],"page":0,"size":20,"totalElements":1,"totalPages":1} 22:52:03.241 retry condition satisfied
196
Then status 200
0
197
* print 'Notification response totalElements:', response.totalElements
0
22:52:03.241 [print] Notification response totalElements: 1
198
* assert response.totalElements >= 1
0
199
* def notifications = response.content
0
# Find PAYMENT_FAILED notification using Karate JsonPath expression
201
* def paymentFailedNotifs = $notifications[?(@.type == 'PAYMENT_FAILED')]
0
202
* assert paymentFailedNotifs.length > 0
0
203
* def notif = paymentFailedNotifs[0]
0
204
* match notif.type == 'PAYMENT_FAILED'
0
205
* match notif.status == 'PROCESSED'
0
206
* match notif.buyerId == buyerId
0
207
* print 'Scenario 2 PASS: PAYMENT_FAILED notification found for buyer:', buyerId
0
22:52:03.243 [print] Scenario 2 PASS: PAYMENT_FAILED notification found for buyer: 64a2fa4c-aa5b-46d8-86ae-256e85db08f9
Scenario: [3:210] Scenario 3 - Validate Notification Endpoint Availability and Contract
ms: 8
>>
Background:
12
* def baseUrlEvents = karate.properties['baseUrlEvents'] || 'http://localhost:8081'
0
22:52:03.244 karate.env system property was: null
13
* def baseUrlTicketing = karate.properties['baseUrlTicketing'] || 'http://localhost:8082'
0
14
* def baseUrlNotifications = karate.properties['baseUrlNotifications'] || 'http://localhost:8083'
0
15
* def UUID = Java.type('java.util.UUID')
0
16
* def futureDate = '2026-12-15T20:00:00'
0
# This scenario verifies the notification endpoint contract without creating new events
# It uses a pre-existing notification from a previous test run
215
Given url baseUrlNotifications + '/api/v1/notifications/buyer/00000000-0000-0000-0000-000000000999'
0
216
And header X-User-Id = '00000000-0000-0000-0000-000000000999'
0
217
When method get
6
22:52:03.246 request: 1 > GET http://localhost:8083/api/v1/notifications/buyer/00000000-0000-0000-0000-000000000999 1 > X-User-Id: 00000000-0000-0000-0000-000000000999 1 > Host: localhost:8083 1 > Connection: Keep-Alive 1 > User-Agent: Apache-HttpClient/4.5.14 (Java/17.0.17) 1 > Accept-Encoding: gzip,deflate 22:52:03.251 response time in milliseconds: 5 1 < 200 1 < Content-Type: application/json 1 < Transfer-Encoding: chunked 1 < Date: Wed, 08 Apr 2026 03:52:03 GMT 1 < Keep-Alive: timeout=60 1 < Connection: keep-alive {"content":[],"page":0,"size":20,"totalElements":0,"totalPages":0}
218
Then status 200
0
# Runtime: paginated response even when empty
220
* match response.content == '#array'
0
221
* match response.totalElements == '#number'
0
222
* match response.page == '#number'
0
223
* match response.size == '#number'
0
224
* print 'Scenario 3 PASS: Notification endpoint available and returns correct paginated contract'
0
22:52:03.253 [print] Scenario 3 PASS: Notification endpoint available and returns correct paginated contract
Scenario: [4:226] Scenario 4 - Notification After Reservation Expiration (RESERVATION_EXPIRED)
ms: 75124
>>
Background:
12
* def baseUrlEvents = karate.properties['baseUrlEvents'] || 'http://localhost:8081'
0
22:52:03.255 karate.env system property was: null
13
* def baseUrlTicketing = karate.properties['baseUrlTicketing'] || 'http://localhost:8082'
0
14
* def baseUrlNotifications = karate.properties['baseUrlNotifications'] || 'http://localhost:8083'
0
15
* def UUID = Java.type('java.util.UUID')
0
16
* def futureDate = '2026-12-15T20:00:00'
0
228
* def tag = UUID.randomUUID() + ''
0
229
* def buyerId = UUID.randomUUID() + ''
0
230
* def buyerEmail = 'buyer-s4-' + tag + '@karate-test.com'
0
231
* def eventTitle = 'Notif S4 ' + tag
0
# Setup: Create Room
234
Given url baseUrlEvents + '/api/v1/rooms'
0
235
And header X-Role = 'ADMIN'
0
236
And header X-User-Id = '00000000-0000-0000-0000-000000000001'
0
237
And request { name: 'Notifications Room S4', maxCapacity: 100 }
0
238
When method post
11
22:52:03.259 request: 1 > POST http://localhost:8081/api/v1/rooms 1 > X-Role: ADMIN 1 > X-User-Id: 00000000-0000-0000-0000-000000000001 1 > Content-Type: application/json; charset=UTF-8 1 > Content-Length: 50 1 > Host: localhost:8081 1 > Connection: Keep-Alive 1 > User-Agent: Apache-HttpClient/4.5.14 (Java/17.0.17) 1 > Accept-Encoding: gzip,deflate {"name":"Notifications Room S4","maxCapacity":100} 22:52:03.268 response time in milliseconds: 9 1 < 201 1 < Content-Type: application/json 1 < Transfer-Encoding: chunked 1 < Date: Wed, 08 Apr 2026 03:52:03 GMT 1 < Keep-Alive: timeout=60 1 < Connection: keep-alive {"id":"51bf7398-6da5-4d43-b83f-8e542da007ff","name":"Notifications Room S4","maxCapacity":100,"created_at":"2026-04-08T03:52:03.261755346","updated_at":"2026-04-08T03:52:03.261768018"}
239
Then status 201
0
240
* def roomId = response.id
0
# Setup: Create Draft Event
243
Given url baseUrlEvents + '/api/v1/events'
0
244
And header X-Role = 'ADMIN'
0
245
And header X-User-Id = '00000000-0000-0000-0000-000000000001'
0
246
And request
0
{ "roomId": "#(roomId)", "title": "#(eventTitle)", "description": "Test event for expired reservation notification", "date": "#(futureDate)", "capacity": 50, "enableSeats": false }
257
When method post
13
22:52:03.270 request: 2 > POST http://localhost:8081/api/v1/events 2 > X-Role: ADMIN 2 > X-User-Id: 00000000-0000-0000-0000-000000000001 2 > Content-Type: application/json; charset=UTF-8 2 > Content-Length: 232 2 > Host: localhost:8081 2 > Connection: Keep-Alive 2 > User-Agent: Apache-HttpClient/4.5.14 (Java/17.0.17) 2 > Accept-Encoding: gzip,deflate {"roomId":"51bf7398-6da5-4d43-b83f-8e542da007ff","title":"Notif S4 d27047c9-7f5b-4814-99e7-c2873783074f","description":"Test event for expired reservation notification","date":"2026-12-15T20:00:00","capacity":50,"enableSeats":false} 22:52:03.282 response time in milliseconds: 11 2 < 201 2 < Content-Type: application/json 2 < Transfer-Encoding: chunked 2 < Date: Wed, 08 Apr 2026 03:52:03 GMT 2 < Keep-Alive: timeout=60 2 < Connection: keep-alive {"id":"4e340e81-d8ae-4dfe-ae1a-2a109f428c7b","roomId":"51bf7398-6da5-4d43-b83f-8e542da007ff","title":"Notif S4 d27047c9-7f5b-4814-99e7-c2873783074f","description":"Test event for expired reservation notification","date":"2026-12-15T20:00:00","capacity":50,"status":"DRAFT","createdAt":"2026-04-08T03:52:03.275420637","updatedAt":"2026-04-08T03:52:03.275428245","createdBy":"00000000-0000-0000-0000-000000000001","imageUrl":null,"subtitle":null,"location":null,"director":null,"castMembers":null,"duration":null,"tag":null,"isLimited":false,"isFeatured":false,"enableSeats":false,"author":null}
258
Then status 201
0
259
* def eventId = response.id
0
# Setup: Configure Tier
262
Given url baseUrlEvents + '/api/v1/events/' + eventId + '/tiers'
0
263
And header X-Role = 'ADMIN'
0
264
And header X-User-Id = '00000000-0000-0000-0000-000000000001'
0
265
And request
0
[{ "tierType": "GENERAL", "price": 100, "quota": 40 }]
269
When method post
11
22:52:03.283 request: 3 > POST http://localhost:8081/api/v1/events/4e340e81-d8ae-4dfe-ae1a-2a109f428c7b/tiers 3 > X-Role: ADMIN 3 > X-User-Id: 00000000-0000-0000-0000-000000000001 3 > Content-Type: application/json; charset=UTF-8 3 > Content-Length: 47 3 > Host: localhost:8081 3 > Connection: Keep-Alive 3 > User-Agent: Apache-HttpClient/4.5.14 (Java/17.0.17) 3 > Accept-Encoding: gzip,deflate [{"tierType":"GENERAL","price":100,"quota":40}] 22:52:03.293 response time in milliseconds: 10 3 < 201 3 < Content-Type: application/json 3 < Transfer-Encoding: chunked 3 < Date: Wed, 08 Apr 2026 03:52:03 GMT 3 < Keep-Alive: timeout=60 3 < Connection: keep-alive {"eventId":"4e340e81-d8ae-4dfe-ae1a-2a109f428c7b","tiers":[{"id":"d00e78c8-9524-4226-80fe-eb2fae599984","tierType":"GENERAL","price":100,"quota":40,"validFrom":null,"validUntil":null,"createdAt":"2026-04-08T03:52:03.28769226","updatedAt":"2026-04-08T03:52:03.28770041"}]}
270
Then status 201
0
271
* def tierId = response.tiers[0].id
0
# Setup: Publish Event
274
Given url baseUrlEvents + '/api/v1/events/' + eventId + '/publish'
0
275
And header X-Role = 'ADMIN'
0
276
And header X-User-Id = '00000000-0000-0000-0000-000000000001'
0
277
When method patch
12
22:52:03.295 request: 4 > PATCH http://localhost:8081/api/v1/events/4e340e81-d8ae-4dfe-ae1a-2a109f428c7b/publish 4 > X-Role: ADMIN 4 > X-User-Id: 00000000-0000-0000-0000-000000000001 4 > Host: localhost:8081 4 > Connection: Keep-Alive 4 > User-Agent: Apache-HttpClient/4.5.14 (Java/17.0.17) 4 > Accept-Encoding: gzip,deflate 22:52:03.306 response time in milliseconds: 11 4 < 200 4 < Content-Type: application/json 4 < Transfer-Encoding: chunked 4 < Date: Wed, 08 Apr 2026 03:52:03 GMT 4 < Keep-Alive: timeout=60 4 < Connection: keep-alive {"id":"4e340e81-d8ae-4dfe-ae1a-2a109f428c7b","roomId":"51bf7398-6da5-4d43-b83f-8e542da007ff","title":"Notif S4 d27047c9-7f5b-4814-99e7-c2873783074f","description":"Test event for expired reservation notification","date":"2026-12-15T20:00:00","capacity":50,"status":"PUBLISHED","createdAt":"2026-04-08T03:52:03.275421","updatedAt":"2026-04-08T03:52:03.299294585","createdBy":"00000000-0000-0000-0000-000000000001","imageUrl":null,"subtitle":null,"location":null,"director":null,"castMembers":null,"duration":null,"tag":null,"isLimited":false,"isFeatured":false,"enableSeats":false,"author":null}
278
Then status 200
0
# Create Reservation (without payment)
281
Given url baseUrlTicketing + '/api/v1/reservations'
0
282
And header X-User-Id = buyerId
0
283
And request
0
{ "eventId": "#(eventId)", "tierId": "#(tierId)", "buyerEmail": "#(buyerEmail)" }
287
When method post
31
22:52:03.307 request: 5 > POST http://localhost:8082/api/v1/reservations 5 > X-User-Id: 4d4c535c-cb00-48b6-9b3b-6cbd2cfeb406 5 > Content-Type: application/json; charset=UTF-8 5 > Content-Length: 175 5 > Host: localhost:8082 5 > Connection: Keep-Alive 5 > User-Agent: Apache-HttpClient/4.5.14 (Java/17.0.17) 5 > Accept-Encoding: gzip,deflate {"eventId":"4e340e81-d8ae-4dfe-ae1a-2a109f428c7b","tierId":"d00e78c8-9524-4226-80fe-eb2fae599984","buyerEmail":"buyer-s4-d27047c9-7f5b-4814-99e7-c2873783074f@karate-test.com"} 22:52:03.338 response time in milliseconds: 30 5 < 201 5 < Content-Type: application/json 5 < Transfer-Encoding: chunked 5 < Date: Wed, 08 Apr 2026 03:52:03 GMT 5 < Keep-Alive: timeout=60 5 < Connection: keep-alive {"id":"c52858ef-7522-4c46-b727-a8a8e967fecc","eventId":"4e340e81-d8ae-4dfe-ae1a-2a109f428c7b","tierId":"d00e78c8-9524-4226-80fe-eb2fae599984","buyerId":"4d4c535c-cb00-48b6-9b3b-6cbd2cfeb406","status":"PENDING","createdAt":"2026-04-08T03:52:03.330884204","updatedAt":"2026-04-08T03:52:03.330884204","validUntilAt":"2026-04-08T04:02:03.330884204"}
288
Then status 201
0
289
* def reservationId = response.id
0
# Force expiration via SQL
292
* print 'Forcing expiration via SQL...'
1
22:52:03.339 [print] Forcing expiration via SQL...
293
* def sqlResult = call read('classpath:common/sql/db-helper.feature@forceExpiration') { reservationId: '#(reservationId)' }
25
>>
common.sql.db-helper
20
5
* def dbTicketingUrl = karate.properties['dbTicketingUrl'] || 'jdbc:postgresql://localhost:5434/ticketing_db'
0
6
* def dbTicketingUser = karate.properties['dbTicketingUser'] || 'postgres'
0
7
* def dbTicketingPass = karate.properties['dbTicketingPass'] || 'postgres'
0
9
* def dbEventsUrl = karate.properties['dbEventsUrl'] || 'jdbc:postgresql://localhost:5433/events_db'
0
10
* def dbEventsUser = karate.properties['dbEventsUser'] || 'postgres'
0
11
* def dbEventsPass = karate.properties['dbEventsPass'] || 'postgres'
0
13
* def JClass = Java.type('java.lang.Class')
0
14
* def DriverManager = Java.type('java.sql.DriverManager')
0
15
* JClass.forName('org.postgresql.Driver')
1
19
* def sql = "UPDATE reservation SET valid_until_at = NOW() AT TIME ZONE 'UTC' - INTERVAL '2 days' WHERE id = ?::uuid"
0
20
* def conn = DriverManager.getConnection(dbTicketingUrl, dbTicketingUser, dbTicketingPass)
9
21
* def pstmt = conn.prepareStatement(sql)
0
22
* pstmt.setObject(1, reservationId)
0
23
* def rows = pstmt.executeUpdate()
6
24
* pstmt.close()
0
25
* conn.close()
0
26
* print 'SQL Result: Forced expiration for reservationId=' + reservationId + ', Rows updated=' + rows
0
22:52:03.363 [print] SQL Result: Forced expiration for reservationId=c52858ef-7522-4c46-b727-a8a8e967fecc, Rows updated=1
27
* eval if (rows != 1) karate.fail('Expected exactly 1 reservation row to be updated, but updated ' + rows)
0
28
* def response = { rowsUpdated: rows, passed: true }
0
# Wait for scheduler cycle
296
* print 'Waiting 75 seconds for scheduler to process expiration and send notification...'
0
22:52:03.364 [print] Waiting 75 seconds for scheduler to process expiration and send notification...
297
* java.lang.Thread.sleep(75000)
75001
# Validate Notification
300
Given url baseUrlNotifications + '/api/v1/notifications/buyer/' + buyerId
1
301
And header X-User-Id = buyerId
0
302
When method get
13
22:53:18.368 request: 6 > GET http://localhost:8083/api/v1/notifications/buyer/4d4c535c-cb00-48b6-9b3b-6cbd2cfeb406 6 > X-User-Id: 4d4c535c-cb00-48b6-9b3b-6cbd2cfeb406 6 > Host: localhost:8083 6 > Connection: Keep-Alive 6 > User-Agent: Apache-HttpClient/4.5.14 (Java/17.0.17) 6 > Accept-Encoding: gzip,deflate 22:53:18.379 response time in milliseconds: 11 6 < 200 6 < Content-Type: application/json 6 < Transfer-Encoding: chunked 6 < Date: Wed, 08 Apr 2026 03:53:18 GMT 6 < Keep-Alive: timeout=60 6 < Connection: keep-alive {"content":[{"id":"d26f90d1-12fb-4837-866f-9efd5744a720","reservationId":"c52858ef-7522-4c46-b727-a8a8e967fecc","eventId":"4e340e81-d8ae-4dfe-ae1a-2a109f428c7b","tierId":"d00e78c8-9524-4226-80fe-eb2fae599984","buyerId":"4d4c535c-cb00-48b6-9b3b-6cbd2cfeb406","type":"RESERVATION_EXPIRED","motif":"RESERVATION_EXPIRED","status":"PROCESSED","read":false,"archived":false,"eventName":"Notif S4 d27047c9-7f5b-4814-99e7-c2873783074f","createdAt":"2026-04-08T03:52:35.854128Z"}],"page":0,"size":20,"totalElements":1,"totalPages":1}
303
Then status 200
0
304
* print 'Notification response totalElements:', response.totalElements
0
22:53:18.379 [print] Notification response totalElements: 1
305
* assert response.totalElements >= 1
0
306
* def notifications = response.content
0
307
* def expiredNotifs = $notifications[?(@.type == 'RESERVATION_EXPIRED')]
0
308
* assert expiredNotifs.length > 0
0
309
* def notif = expiredNotifs[0]
0
310
* match notif.type == 'RESERVATION_EXPIRED'
0
311
* match notif.status == 'PROCESSED'
0
312
* match notif.buyerId == buyerId
0
313
* print 'Scenario 4 PASS: RESERVATION_EXPIRED notification confirmed'
0
22:53:18.381 [print] Scenario 4 PASS: RESERVATION_EXPIRED notification confirmed