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