Summary| Tags| Feature: api/expiration-release-flow/expiration-release-flow-with-sql.feature| Ticketing MVP - Expiration and Automatic Release Flow (Path B with SQL Validation) As a Ticketing MVP automation I want to execute the complete flow with rejected payment, automatic release, and SQL validation So that I can conclusively verify that blocked inventory is released and available for new buyers
Scenario: [1:18] Path B - Rejected Payment, Automatic Release, and SQL Validation
ms: 75469
>>
Background:
7
* def baseUrlEvents = karate.properties['baseUrlEvents'] || 'http://localhost:8081'
0
22:50:38.151 karate.env system property was: null
8
* def baseUrlTicketing = karate.properties['baseUrlTicketing'] || 'http://localhost:8082'
0
9
* def UUID = Java.type('java.util.UUID')
0
10
* def buyer1Id = UUID.randomUUID() + ''
0
11
* def buyer2Id = UUID.randomUUID() + ''
0
12
* def buyer1Email = 'buyer1-' + java.lang.System.currentTimeMillis() + '@karate-test.com'
1
13
* def buyer2Email = 'buyer2-' + java.lang.System.currentTimeMillis() + '@karate-test.com'
0
14
* def eventTitle = 'Karate Release Event ' + java.lang.System.currentTimeMillis()
0
15
* def futureDate = '2026-12-15T20:00:00'
0
16
* def adminUserId = '00000000-0000-0000-0000-000000000001'
0
# ========================================
# SETUP PHASE
# ========================================
# Setup: Create Room
25
Given url baseUrlEvents + '/api/v1/rooms'
0
26
And header X-Role = 'ADMIN'
0
27
And header X-User-Id = adminUserId
0
28
And request
0
{ "name": "Karate Release Flow Room", "maxCapacity": 50 }
35
When method post
11
22:50:38.157 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: 52 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":"Karate Release Flow Room","maxCapacity":50} 22:50:38.167 response time in milliseconds: 10 1 < 201 1 < Content-Type: application/json 1 < Transfer-Encoding: chunked 1 < Date: Wed, 08 Apr 2026 03:50:37 GMT 1 < Keep-Alive: timeout=60 1 < Connection: keep-alive {"id":"93a36f70-1072-46ec-a673-207597ad8ed8","name":"Karate Release Flow Room","maxCapacity":50,"created_at":"2026-04-08T03:50:38.159439052","updated_at":"2026-04-08T03:50:38.159453866"}
36
Then status 201
0
37
* def roomId = response.id ? response.id : response.roomId
0
38
* match roomId == '#uuid'
0
39
* print '[SETUP] Room created:', roomId
1
22:50:38.168 [print] [SETUP] Room created: 93a36f70-1072-46ec-a673-207597ad8ed8
# Setup: Create Event in DRAFT
42
Given url baseUrlEvents + '/api/v1/events'
0
43
And header X-Role = 'ADMIN'
0
44
And header X-User-Id = adminUserId
0
45
And request
0
{ "roomId": "#(roomId)", "title": "#(eventTitle)", "description": "Test event for expiration and release flow", "date": "#(futureDate)", "capacity": 50, "enableSeats": false }
56
When method post
14
22:50:38.170 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: 216 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":"93a36f70-1072-46ec-a673-207597ad8ed8","title":"Karate Release Event 1775620238155","description":"Test event for expiration and release flow","date":"2026-12-15T20:00:00","capacity":50,"enableSeats":false} 22:50:38.183 response time in milliseconds: 13 2 < 201 2 < Content-Type: application/json 2 < Transfer-Encoding: chunked 2 < Date: Wed, 08 Apr 2026 03:50:37 GMT 2 < Keep-Alive: timeout=60 2 < Connection: keep-alive {"id":"fd6335c5-9850-4ff0-b208-25c7ad3ae626","roomId":"93a36f70-1072-46ec-a673-207597ad8ed8","title":"Karate Release Event 1775620238155","description":"Test event for expiration and release flow","date":"2026-12-15T20:00:00","capacity":50,"status":"DRAFT","createdAt":"2026-04-08T03:50:38.176419533","updatedAt":"2026-04-08T03:50:38.176433733","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}
57
Then status 201
0
58
* def eventId = response.id ? response.id : response.eventId
0
59
* match eventId == '#uuid'
0
60
* match response.status == 'DRAFT'
0
61
* print '[SETUP] Event created in DRAFT:', eventId
0
22:50:38.185 [print] [SETUP] Event created in DRAFT: fd6335c5-9850-4ff0-b208-25c7ad3ae626
# Setup: Configure Tier with quota=40
64
Given url baseUrlEvents + '/api/v1/events/' + eventId + '/tiers'
0
65
And header X-Role = 'ADMIN'
0
66
And request
0
[ { "tierType": "GENERAL", "price": 100, "quota": 40 } ]
76
When method post
14
22:50:38.186 request: 3 > POST http://localhost:8081/api/v1/events/fd6335c5-9850-4ff0-b208-25c7ad3ae626/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:50:38.199 response time in milliseconds: 13 3 < 201 3 < Content-Type: application/json 3 < Transfer-Encoding: chunked 3 < Date: Wed, 08 Apr 2026 03:50:37 GMT 3 < Keep-Alive: timeout=60 3 < Connection: keep-alive {"eventId":"fd6335c5-9850-4ff0-b208-25c7ad3ae626","tiers":[{"id":"1f71b49f-d4db-45a7-a72c-3b82b4259630","tierType":"GENERAL","price":100,"quota":40,"validFrom":null,"validUntil":null,"createdAt":"2026-04-08T03:50:38.192292147","updatedAt":"2026-04-08T03:50:38.192313593"}]}
77
Then status 201
0
78
* def tierBlock = response.tiers ? response.tiers[0] : response[0]
1
79
* def tierId = tierBlock.id ? tierBlock.id : tierBlock.tierId
0
80
* match tierId == '#uuid'
0
81
* match tierBlock.tierType == 'GENERAL'
0
82
* match tierBlock.quota == 40
0
83
* print '[SETUP] Tier configured with quota 40:', tierId
0
22:50:38.201 [print] [SETUP] Tier configured with quota 40: 1f71b49f-d4db-45a7-a72c-3b82b4259630
# Setup: Publish Event
86
Given url baseUrlEvents + '/api/v1/events/' + eventId + '/publish'
0
87
And header X-Role = 'ADMIN'
0
88
And header X-User-Id = adminUserId
0
89
When method patch
13
22:50:38.202 request: 4 > PATCH http://localhost:8081/api/v1/events/fd6335c5-9850-4ff0-b208-25c7ad3ae626/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:50:38.215 response time in milliseconds: 12 4 < 200 4 < Content-Type: application/json 4 < Transfer-Encoding: chunked 4 < Date: Wed, 08 Apr 2026 03:50:37 GMT 4 < Keep-Alive: timeout=60 4 < Connection: keep-alive {"id":"fd6335c5-9850-4ff0-b208-25c7ad3ae626","roomId":"93a36f70-1072-46ec-a673-207597ad8ed8","title":"Karate Release Event 1775620238155","description":"Test event for expiration and release flow","date":"2026-12-15T20:00:00","capacity":50,"status":"PUBLISHED","createdAt":"2026-04-08T03:50:38.17642","updatedAt":"2026-04-08T03:50:38.207261619","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}
90
Then status 200
0
91
* match response.status == 'PUBLISHED'
0
92
* print '[SETUP] Event published'
0
22:50:38.215 [print] [SETUP] Event published
# ========================================
# PATH B PHASE 1: Buyer 1 creates reservation
# ========================================
98
Given url baseUrlTicketing + '/api/v1/reservations'
0
99
And header X-User-Id = buyer1Id
0
100
And request
0
{ "eventId": "#(eventId)", "tierId": "#(tierId)", "buyerEmail": "#(buyer1Email)" }
108
When method post
37
22:50:38.217 request: 5 > POST http://localhost:8082/api/v1/reservations 5 > X-User-Id: 636bc5cc-cef0-4d41-a3ab-31221f06d970 5 > Content-Type: application/json; charset=UTF-8 5 > Content-Length: 150 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":"fd6335c5-9850-4ff0-b208-25c7ad3ae626","tierId":"1f71b49f-d4db-45a7-a72c-3b82b4259630","buyerEmail":"buyer1-1775620238154@karate-test.com"} 22:50:38.252 response time in milliseconds: 35 5 < 201 5 < Content-Type: application/json 5 < Transfer-Encoding: chunked 5 < Date: Wed, 08 Apr 2026 03:50:37 GMT 5 < Keep-Alive: timeout=60 5 < Connection: keep-alive {"id":"2c2cbf10-e9ef-46ec-bccd-cf1a1ad387c3","eventId":"fd6335c5-9850-4ff0-b208-25c7ad3ae626","tierId":"1f71b49f-d4db-45a7-a72c-3b82b4259630","buyerId":"636bc5cc-cef0-4d41-a3ab-31221f06d970","status":"PENDING","createdAt":"2026-04-08T03:50:38.244734007","updatedAt":"2026-04-08T03:50:38.244734007","validUntilAt":"2026-04-08T04:00:38.244734007"}
109
Then status 201
0
110
* def reservation1Id = response.id
0
111
* match response.status == 'PENDING'
0
112
* match response.buyerId == buyer1Id
0
113
* print '[PATH B-1] Buyer 1 reservation created (PENDING, inventory blocked):', reservation1Id
1
22:50:38.254 [print] [PATH B-1] Buyer 1 reservation created (PENDING, inventory blocked): 2c2cbf10-e9ef-46ec-bccd-cf1a1ad387c3
# ========================================
# PATH B PHASE 2: Buyer 1 payment DECLINED
# ========================================
119
Given url baseUrlTicketing + '/api/v1/reservations/' + reservation1Id + '/payments'
0
120
And header X-User-Id = buyer1Id
0
121
And request
0
{ "amount": 100, "paymentMethod": "MOCK", "status": "DECLINED" }
129
When method post
41
22:50:38.256 request: 6 > POST http://localhost:8082/api/v1/reservations/2c2cbf10-e9ef-46ec-bccd-cf1a1ad387c3/payments 6 > X-User-Id: 636bc5cc-cef0-4d41-a3ab-31221f06d970 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:50:38.296 response time in milliseconds: 40 6 < 400 6 < Content-Type: application/json 6 < Transfer-Encoding: chunked 6 < Date: Wed, 08 Apr 2026 03:50:37 GMT 6 < Connection: close {"reservationId":"2c2cbf10-e9ef-46ec-bccd-cf1a1ad387c3","error":"Payment declined. Your reservation remains active for 10 minutes","status":"PAYMENT_FAILED","timestamp":"2026-04-08T03:50:38.293628478"}
130
Then status 400
0
131
* match response == { error: '#string', reservationId: '#uuid', status: 'PAYMENT_FAILED', timestamp: '#string' }
1
132
* match response.reservationId == reservation1Id
0
133
* print '[PATH B-2] Payment rejected. Status:', response.status, 'Timestamp:', response.timestamp
1
22:50:38.298 [print] [PATH B-2] Payment rejected. Status: PAYMENT_FAILED Timestamp: 2026-04-08T03:50:38.293628478
# ========================================
# PATH B PHASE 3: FORCE EXPIRATION & WAIT
# ========================================
139
* print '[PATH B-3] Forcing expiration by moving valid_until_at to the past via SQL...'
0
22:50:38.298 [print] [PATH B-3] Forcing expiration by moving valid_until_at to the past via SQL...
140
* def forceResult = call read('classpath:common/sql/db-helper.feature@forceExpiration') { reservationId: '#(reservation1Id)' }
196
>>
common.sql.db-helper
185
5
* def dbTicketingUrl = karate.properties['dbTicketingUrl'] || 'jdbc:postgresql://localhost:5434/ticketing_db'
1
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')
1
15
* JClass.forName('org.postgresql.Driver')
10
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)
144
21
* def pstmt = conn.prepareStatement(sql)
8
22
* pstmt.setObject(1, reservationId)
4
23
* def rows = pstmt.executeUpdate()
10
24
* pstmt.close()
1
25
* conn.close()
1
26
* print 'SQL Result: Forced expiration for reservationId=' + reservationId + ', Rows updated=' + rows
0
22:50:38.490 [print] SQL Result: Forced expiration for reservationId=2c2cbf10-e9ef-46ec-bccd-cf1a1ad387c3, Rows updated=1
27
* eval if (rows != 1) karate.fail('Expected exactly 1 reservation row to be updated, but updated ' + rows)
3
28
* def response = { rowsUpdated: rows, passed: true }
0
141
* match forceResult.rows == 1
0
143
* def waitTimeMs = 75000
0
144
* print '[PATH B-3] Waiting for scheduler to sweep expired reservations (60s cycle + buffer)...'
0
22:50:38.495 [print] [PATH B-3] Waiting for scheduler to sweep expired reservations (60s cycle + buffer)...
145
* java.lang.Thread.sleep(waitTimeMs)
75002
146
* print '[PATH B-3] Wait complete.'
1
22:51:53.498 [print] [PATH B-3] Wait complete.
# ========================================
# PATH B PHASE 4: SQL VALIDATION - Reservation status
# ========================================
152
* print '[SQL-1] Querying ticketing_db for reservation status...'
1
22:51:53.499 [print] [SQL-1] Querying ticketing_db for reservation status...
153
* def reservationStatusResult = call read('classpath:common/sql/db-helper.feature@checkReservationStatus') { reservationId: '#(reservation1Id)', expectedStatus: 'EXPIRED' }
50
>>
common.sql.db-helper
40
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')
0
32
* def sql = 'SELECT status, updated_at FROM reservation WHERE id = ?::uuid ORDER BY updated_at DESC LIMIT 1'
0
33
* def conn = DriverManager.getConnection(dbTicketingUrl, dbTicketingUser, dbTicketingPass)
19
34
* def pstmt = conn.prepareStatement(sql)
0
35
* pstmt.setObject(1, reservationId)
0
36
* def rs = pstmt.executeQuery()
14
38
* def found = rs.next()
2
39
* eval if (!found) karate.fail('Reservation not found in database for id=' + reservationId)
0
41
* def actualStatus = rs.getString('status')
1
42
* def updatedAt = rs.getString('updated_at')
0
44
* rs.close()
1
45
* pstmt.close()
0
46
* conn.close()
0
48
* print 'SQL Result: reservationId=' + reservationId + ', status=' + actualStatus + ', updated_at=' + updatedAt
0
22:51:53.548 [print] SQL Result: reservationId=2c2cbf10-e9ef-46ec-bccd-cf1a1ad387c3, status=EXPIRED, updated_at=2026-04-08 03:51:35.561688
49
* eval if (actualStatus != expectedStatus) karate.fail('Expected status ' + expectedStatus + ' but found ' + actualStatus)
0
51
* def response = { actualStatus: actualStatus, updatedAt: updatedAt, passed: true }
0
154
* print '[SQL-1] Reservation status validation passed. Status:', reservationStatusResult.actualStatus
0
22:51:53.550 [print] [SQL-1] Reservation status validation passed. Status: EXPIRED
# ========================================
# PATH B PHASE 5: SQL VALIDATION - Tier quota restoration
# ========================================
160
* print '[SQL-2] Querying events_db for tier quota...'
0
22:51:53.550 [print] [SQL-2] Querying events_db for tier quota...
161
* def tierQuotaResult = call read('classpath:common/sql/db-helper.feature@checkTierQuota') { tierId: '#(tierId)', expectedQuota: 40 }
27
>>
common.sql.db-helper
23
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')
0
55
* def sql = 'SELECT quota, updated_at FROM tier WHERE id = ?::uuid'
0
56
* def conn = DriverManager.getConnection(dbEventsUrl, dbEventsUser, dbEventsPass)
16
57
* def pstmt = conn.prepareStatement(sql)
0
58
* pstmt.setObject(1, tierId)
0
59
* def rs = pstmt.executeQuery()
1
61
* def found = rs.next()
0
62
* eval if (!found) karate.fail('Tier not found in database for id=' + tierId)
0
64
* def actualQuota = rs.getInt('quota')
2
65
* def updatedAt = rs.getString('updated_at')
0
67
* rs.close()
0
68
* pstmt.close()
0
69
* conn.close()
0
71
* print 'SQL Result: tierId=' + tierId + ', quota=' + actualQuota + ', updated_at=' + updatedAt
0
22:51:53.577 [print] SQL Result: tierId=1f71b49f-d4db-45a7-a72c-3b82b4259630, quota=40, updated_at=2026-04-08 03:51:35.542644
72
* eval if (actualQuota != expectedQuota) karate.fail('Expected quota ' + expectedQuota + ' but found ' + actualQuota)
0
74
* def response = { actualQuota: actualQuota, updatedAt: updatedAt, passed: true }
0
162
* print '[SQL-2] Tier quota validation passed. Quota:', tierQuotaResult.actualQuota
0
22:51:53.577 [print] [SQL-2] Tier quota validation passed. Quota: 40
# ========================================
# PATH B PHASE 6: HTTP verification - Buyer 2 creates reservation
# ========================================
168
* print '[PATH B-4] Attempting Buyer 2 reservation (final HTTP verification)...'
0
22:51:53.578 [print] [PATH B-4] Attempting Buyer 2 reservation (final HTTP verification)...
170
Given url baseUrlTicketing + '/api/v1/reservations'
0
171
And header X-User-Id = buyer2Id
0
172
And request
0
{ "eventId": "#(eventId)", "tierId": "#(tierId)", "buyerEmail": "#(buyer2Email)" }
180
When method post
42
22:51:53.579 request: 7 > POST http://localhost:8082/api/v1/reservations 7 > X-User-Id: 36f0abaa-0077-4c92-950b-c76150a90e39 7 > Content-Type: application/json; charset=UTF-8 7 > Content-Length: 150 7 > Host: localhost:8082 7 > Connection: Keep-Alive 7 > User-Agent: Apache-HttpClient/4.5.14 (Java/17.0.17) 7 > Accept-Encoding: gzip,deflate {"eventId":"fd6335c5-9850-4ff0-b208-25c7ad3ae626","tierId":"1f71b49f-d4db-45a7-a72c-3b82b4259630","buyerEmail":"buyer2-1775620238155@karate-test.com"} 22:51:53.619 response time in milliseconds: 40 7 < 201 7 < Content-Type: application/json 7 < Transfer-Encoding: chunked 7 < Date: Wed, 08 Apr 2026 03:51:53 GMT 7 < Keep-Alive: timeout=60 7 < Connection: keep-alive {"id":"c8a83e98-4bab-4105-b0a2-09b618267e1b","eventId":"fd6335c5-9850-4ff0-b208-25c7ad3ae626","tierId":"1f71b49f-d4db-45a7-a72c-3b82b4259630","buyerId":"36f0abaa-0077-4c92-950b-c76150a90e39","status":"PENDING","createdAt":"2026-04-08T03:51:53.611134302","updatedAt":"2026-04-08T03:51:53.611134302","validUntilAt":"2026-04-08T04:01:53.611134302"}
181
Then status 201
0
182
* def reservation2Id = response.id
0
183
* match response.status == 'PENDING'
0
184
* match response.buyerId == buyer2Id
0
185
* eval if (reservation2Id == reservation1Id) karate.fail('Buyer 2 must have different reservation ID than Buyer 1')
0
186
* print '[PATH B-4] Buyer 2 reservation created successfully (CONFIRMS RELEASE):', reservation2Id
0
22:51:53.621 [print] [PATH B-4] Buyer 2 reservation created successfully (CONFIRMS RELEASE): c8a83e98-4bab-4105-b0a2-09b618267e1b
# ========================================
# FINAL SUMMARY
# ========================================
192
* print ''
0
22:51:53.621 [print]
193
* print '=========================================='
0
22:51:53.621 [print] ==========================================
194
* print 'PATH B VALIDATION COMPLETE'
0
22:51:53.621 [print] PATH B VALIDATION COMPLETE
195
* print '=========================================='
0
22:51:53.621 [print] ==========================================
196
* print 'Buyer 1: Reservation', reservation1Id
0
22:51:53.622 [print] Buyer 1: Reservation 2c2cbf10-e9ef-46ec-bccd-cf1a1ad387c3
197
* print ' - Created: PENDING'
0
22:51:53.622 [print] - Created: PENDING
198
* print ' - Payment: DECLINED (HTTP 400, PAYMENT_FAILED)'
0
22:51:53.622 [print] - Payment: DECLINED (HTTP 400, PAYMENT_FAILED)
199
* print ' - Status after release: EXPIRED (SQL validated)'
0
22:51:53.622 [print] - Status after release: EXPIRED (SQL validated)
200
* print ''
0
22:51:53.622 [print]
201
* print 'Tier #' + tierId
0
22:51:53.622 [print] Tier #1f71b49f-d4db-45a7-a72c-3b82b4259630
202
* print ' - Original quota: 40'
0
22:51:53.622 [print] - Original quota: 40
203
* print ' - After release: 40 (SQL validated)'
0
22:51:53.623 [print] - After release: 40 (SQL validated)
204
* print ''
0
22:51:53.623 [print]
205
* print 'Buyer 2: Reservation', reservation2Id
0
22:51:53.623 [print] Buyer 2: Reservation c8a83e98-4bab-4105-b0a2-09b618267e1b
206
* print ' - Created: PENDING (proves inventory was released)'
0
22:51:53.623 [print] - Created: PENDING (proves inventory was released)
207
* print ''
0
22:51:53.623 [print]
208
* print 'CONCLUSION: Automatic release mechanism working correctly'
0
22:51:53.623 [print] CONCLUSION: Automatic release mechanism working correctly
209
* print '=========================================='
0
22:51:53.623 [print] ==========================================
Scenario: [2:211] Scenario 2 - Early Bird Tier Expiration via Time Travel (TC-006 / TC-011)
ms: 81
>>
Background:
7
* def baseUrlEvents = karate.properties['baseUrlEvents'] || 'http://localhost:8081'
0
22:51:53.625 karate.env system property was: null
8
* def baseUrlTicketing = karate.properties['baseUrlTicketing'] || 'http://localhost:8082'
0
9
* def UUID = Java.type('java.util.UUID')
0
10
* def buyer1Id = UUID.randomUUID() + ''
0
11
* def buyer2Id = UUID.randomUUID() + ''
0
12
* def buyer1Email = 'buyer1-' + java.lang.System.currentTimeMillis() + '@karate-test.com'
0
13
* def buyer2Email = 'buyer2-' + java.lang.System.currentTimeMillis() + '@karate-test.com'
0
14
* def eventTitle = 'Karate Release Event ' + java.lang.System.currentTimeMillis()
0
15
* def futureDate = '2026-12-15T20:00:00'
0
16
* def adminUserId = '00000000-0000-0000-0000-000000000001'
0
# Setup: Create Room
214
Given url baseUrlEvents + '/api/v1/rooms'
0
215
And header X-Role = 'ADMIN'
0
216
And header X-User-Id = '00000000-0000-0000-0000-000000000001'
0
217
And request
0
{ "name": "EB Expiration Room", "maxCapacity": 100 }
224
When method post
10
22:51:53.628 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: 47 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":"EB Expiration Room","maxCapacity":100} 22:51:53.638 response time in milliseconds: 10 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":"5765d445-ed24-495a-bee5-681b1af59855","name":"EB Expiration Room","maxCapacity":100,"created_at":"2026-04-08T03:51:53.630992967","updated_at":"2026-04-08T03:51:53.631010252"}
225
Then status 201
0
226
* def roomId = response.id ? response.id : response.roomId
0
# Setup: Create Event
229
Given url baseUrlEvents + '/api/v1/events'
0
230
And header X-Role = 'ADMIN'
0
231
And header X-User-Id = '00000000-0000-0000-0000-000000000001'
0
232
And request
0
{ "roomId": "#(roomId)", "title": "#(eventTitle + ' EB')", "description": "Test event for EB expiration", "date": "#(futureDate)", "capacity": 50, "enableSeats": false }
243
When method post
15
22:51:53.639 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: 205 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":"5765d445-ed24-495a-bee5-681b1af59855","title":"Karate Release Event 1775620313627 EB","description":"Test event for EB expiration","date":"2026-12-15T20:00:00","capacity":50,"enableSeats":false} 22:51:53.653 response time in milliseconds: 14 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":"478b50b1-b88f-4f83-8b06-91d0da99dca5","roomId":"5765d445-ed24-495a-bee5-681b1af59855","title":"Karate Release Event 1775620313627 EB","description":"Test event for EB expiration","date":"2026-12-15T20:00:00","capacity":50,"status":"DRAFT","createdAt":"2026-04-08T03:51:53.645615523","updatedAt":"2026-04-08T03:51:53.645626366","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}
244
Then status 201
0
245
* def eventId = response.id ? response.id : response.eventId
0
# Setup: Configure EB Tier
248
Given url baseUrlEvents + '/api/v1/events/' + eventId + '/tiers'
0
249
And header X-Role = 'ADMIN'
0
250
And header X-User-Id = '00000000-0000-0000-0000-000000000001'
0
251
* def validFrom = java.time.LocalDateTime.now(java.time.ZoneOffset.UTC).plusDays(1).toString()
4
252
* def validUntil = java.time.LocalDateTime.now(java.time.ZoneOffset.UTC).plusDays(5).toString()
1
253
And request
0
[ { "tierType": "EARLY_BIRD", "price": 100, "quota": 40, "validFrom": "#(validFrom)", "validUntil": "#(validUntil)" } ]
265
When method post
13
22:51:53.660 request: 3 > POST http://localhost:8081/api/v1/events/478b50b1-b88f-4f83-8b06-91d0da99dca5/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: 139 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":"EARLY_BIRD","price":100,"quota":40,"validFrom":"2026-04-09T03:51:53.657846044","validUntil":"2026-04-13T03:51:53.659419041"}] 22:51:53.672 response time in milliseconds: 12 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":"478b50b1-b88f-4f83-8b06-91d0da99dca5","tiers":[{"id":"cb0ef955-e667-496e-9746-635617d01b79","tierType":"EARLY_BIRD","price":100,"quota":40,"validFrom":"2026-04-09T03:51:53.657846044","validUntil":"2026-04-13T03:51:53.659419041","createdAt":"2026-04-08T03:51:53.666459915","updatedAt":"2026-04-08T03:51:53.66646654"}]}
266
Then status 201
0
267
* def tierBlock = response.tiers ? response.tiers[0] : response[0]
0
268
* def tierId = tierBlock.id ? tierBlock.id : tierBlock.tierId
0
# Setup: Publish Event
271
Given url baseUrlEvents + '/api/v1/events/' + eventId + '/publish'
0
272
And header X-Role = 'ADMIN'
0
273
And header X-User-Id = '00000000-0000-0000-0000-000000000001'
0
274
When method patch
13
22:51:53.674 request: 4 > PATCH http://localhost:8081/api/v1/events/478b50b1-b88f-4f83-8b06-91d0da99dca5/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.686 response time in milliseconds: 12 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":"478b50b1-b88f-4f83-8b06-91d0da99dca5","roomId":"5765d445-ed24-495a-bee5-681b1af59855","title":"Karate Release Event 1775620313627 EB","description":"Test event for EB expiration","date":"2026-12-15T20:00:00","capacity":50,"status":"PUBLISHED","createdAt":"2026-04-08T03:51:53.645616","updatedAt":"2026-04-08T03:51:53.679123379","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}
275
Then status 200
0
# Act: Force Tier Expiration via Testability Time Travel (advance clock by 6 days = 8640 minutes)
278
* print 'Forcing EB Tier Expiration smoothly using Testability Time Travel...'
1
22:51:53.687 [print] Forcing EB Tier Expiration smoothly using Testability Time Travel...
279
Given url baseUrlEvents + '/api/v1/testability/clock/advance'
0
280
And param minutes = 8640
0
281
When method post
6
22:51:53.688 request: 5 > POST http://localhost:8081/api/v1/testability/clock/advance?minutes=8640 5 > Host: localhost:8081 5 > Connection: Keep-Alive 5 > User-Agent: Apache-HttpClient/4.5.14 (Java/17.0.17) 5 > Accept-Encoding: gzip,deflate 22:51:53.693 response time in milliseconds: 4 5 < 200 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 {"current_time":"2026-04-14T03:51:53.691621778","status":"advanced"}
282
Then status 200
0
# Check: Verify isAvailable is false
285
Given url baseUrlEvents + '/api/v1/events/' + eventId
0
286
When method get
8
22:51:53.694 request: 6 > GET http://localhost:8081/api/v1/events/478b50b1-b88f-4f83-8b06-91d0da99dca5 6 > Host: localhost:8081 6 > Connection: Keep-Alive 6 > User-Agent: Apache-HttpClient/4.5.14 (Java/17.0.17) 6 > Accept-Encoding: gzip,deflate 22:51:53.701 response time in milliseconds: 7 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 {"id":"478b50b1-b88f-4f83-8b06-91d0da99dca5","title":"Karate Release Event 1775620313627 EB","description":"Test event for EB expiration","date":"2026-12-15T20:00:00","capacity":50,"room":{"id":"5765d445-ed24-495a-bee5-681b1af59855","name":"EB Expiration Room","maxCapacity":100,"created_at":"2026-04-08T03:51:53.630993","updated_at":"2026-04-08T03:51:53.63101"},"availableTiers":[{"id":"cb0ef955-e667-496e-9746-635617d01b79","tierType":"EARLY_BIRD","price":100.00,"quota":40,"validFrom":"2026-04-09T03:51:53.657846","validUntil":"2026-04-13T03:51:53.659419","isAvailable":false,"reason":"EXPIRED"}],"created_at":"2026-04-08T03:51:53.645616","imageUrl":null,"subtitle":null,"location":null,"director":null,"castMembers":null,"duration":null,"tag":null,"isLimited":false,"isFeatured":false,"enableSeats":false,"author":null}
287
Then status 200
0
288
* def ebTier = response.availableTiers[0]
0
289
* match ebTier.isAvailable == false
0
290
* match ebTier.reason == 'EXPIRED'
0
# Cleanup: Reset SystemClock
293
Given url baseUrlEvents + '/api/v1/testability/clock/reset'
0
294
When method post
4
22:51:53.703 request: 7 > POST http://localhost:8081/api/v1/testability/clock/reset 7 > Host: localhost:8081 7 > Connection: Keep-Alive 7 > User-Agent: Apache-HttpClient/4.5.14 (Java/17.0.17) 7 > Accept-Encoding: gzip,deflate 22:51:53.706 response time in milliseconds: 3 7 < 200 7 < Content-Type: application/json 7 < Transfer-Encoding: chunked 7 < Date: Wed, 08 Apr 2026 03:51:53 GMT 7 < Keep-Alive: timeout=60 7 < Connection: keep-alive {"current_time":"2026-04-08T03:51:53.705396573","status":"reset"}
295
Then status 200
0
297
* print 'TC-006 and TC-011: Early Bird Tier Expiration verified correctly without DB hacks'
1
22:51:53.707 [print] TC-006 and TC-011: Early Bird Tier Expiration verified correctly without DB hacks