Summary| Tags| Feature: api/tier-validation-negative-and-earlybird/tier-validation-negative-and-earlybird.feature| Ticketing MVP - Tier Validation Negative and Early Bird As a system admin I want validation errors when creating tiers with invalid config So that tier integrity is maintained
Scenario: [1:11] Scenario 1 - Early Bird tier not yet started (isAvailable false) and active GENERAL tier (isAvailable true)
ms: 52
>>
Background:
7
* def baseUrlEvents = karate.properties['baseUrlEvents'] || 'http://localhost:8081'
0
22:55:57.922 karate.env system property was: null
8
* def UUID = Java.type('java.util.UUID')
0
9
* def futureDate = '2026-12-15T20:00:00'
0
# Note: Backend validates that validFrom and validUntil must be in the FUTURE at creation time.
# An EB tier with validFrom in the far future (not yet started) shows isAvailable=false.
# An active GENERAL tier shows isAvailable=true.
17
* def tag = UUID.randomUUID() + ''
0
18
* def eventTitle = 'Tier S1 EB ' + tag
0
# Setup: Create room
21
Given url baseUrlEvents + '/api/v1/rooms'
0
22
And header X-Role = 'ADMIN'
0
23
And header X-User-Id = '00000000-0000-0000-0000-000000000001'
0
24
And request { name: 'Early Bird Test Room S1', maxCapacity: 100 }
0
25
When method post
14
22:55:57.924 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":"Early Bird Test Room S1","maxCapacity":100} 22:55:57.937 response time in milliseconds: 13 1 < 201 1 < Content-Type: application/json 1 < Transfer-Encoding: chunked 1 < Date: Wed, 08 Apr 2026 03:55:57 GMT 1 < Keep-Alive: timeout=60 1 < Connection: keep-alive {"id":"120a7278-d214-46c3-88df-7deb6363dc0b","name":"Early Bird Test Room S1","maxCapacity":100,"created_at":"2026-04-08T03:55:57.926442596","updated_at":"2026-04-08T03:55:57.926446367"}
26
Then status 201
0
27
* def roomId = response.id
0
# Setup: Create event DRAFT
30
Given url baseUrlEvents + '/api/v1/events'
0
31
And header X-Role = 'ADMIN'
0
32
And header X-User-Id = '00000000-0000-0000-0000-000000000001'
0
33
And request
0
{ "roomId": "#(roomId)", "title": "#(eventTitle)", "description": "Test event for Early Bird availability", "date": "#(futureDate)", "capacity": 100, "enableSeats": false }
44
When method post
11
22:55:57.938 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: 226 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":"120a7278-d214-46c3-88df-7deb6363dc0b","title":"Tier S1 EB b772f78a-e5f4-4d8f-b0a3-8396c4573b3d","description":"Test event for Early Bird availability","date":"2026-12-15T20:00:00","capacity":100,"enableSeats":false} 22:55:57.948 response time in milliseconds: 10 2 < 201 2 < Content-Type: application/json 2 < Transfer-Encoding: chunked 2 < Date: Wed, 08 Apr 2026 03:55:57 GMT 2 < Keep-Alive: timeout=60 2 < Connection: keep-alive {"id":"d8ebfa6c-c3ee-4d73-b86c-119fb7694510","roomId":"120a7278-d214-46c3-88df-7deb6363dc0b","title":"Tier S1 EB b772f78a-e5f4-4d8f-b0a3-8396c4573b3d","description":"Test event for Early Bird availability","date":"2026-12-15T20:00:00","capacity":100,"status":"DRAFT","createdAt":"2026-04-08T03:55:57.942056526","updatedAt":"2026-04-08T03:55:57.942058404","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}
45
Then status 201
0
46
* def eventId = response.id
0
# Create both GENERAL (active) and EARLY_BIRD (not yet started: far future window) tiers
49
Given url baseUrlEvents + '/api/v1/events/' + eventId + '/tiers'
0
50
And header X-Role = 'ADMIN'
0
51
And request
0
[{ "tierType": "GENERAL", "price": 50, "quota": 70 }]
55
When method post
11
22:55:57.949 request: 3 > POST http://localhost:8081/api/v1/events/d8ebfa6c-c3ee-4d73-b86c-119fb7694510/tiers 3 > X-Role: ADMIN 3 > Content-Type: application/json; charset=UTF-8 3 > Content-Length: 46 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":50,"quota":70}] 22:55:57.959 response time in milliseconds: 10 3 < 201 3 < Content-Type: application/json 3 < Transfer-Encoding: chunked 3 < Date: Wed, 08 Apr 2026 03:55:57 GMT 3 < Keep-Alive: timeout=60 3 < Connection: keep-alive {"eventId":"d8ebfa6c-c3ee-4d73-b86c-119fb7694510","tiers":[{"id":"fa845a76-47d7-4939-bc57-64088e49348c","tierType":"GENERAL","price":50,"quota":70,"validFrom":null,"validUntil":null,"createdAt":"2026-04-08T03:55:57.953547049","updatedAt":"2026-04-08T03:55:57.953551663"}]}
56
Then status 201
0
57
* def generalTierId = response.tiers[0].id
0
# Publish event (GENERAL tier is active, event can publish)
60
Given url baseUrlEvents + '/api/v1/events/' + eventId + '/publish'
0
61
And header X-Role = 'ADMIN'
0
62
And header X-User-Id = '00000000-0000-0000-0000-000000000001'
0
63
When method patch
9
22:55:57.960 request: 4 > PATCH http://localhost:8081/api/v1/events/d8ebfa6c-c3ee-4d73-b86c-119fb7694510/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:55:57.969 response time in milliseconds: 9 4 < 200 4 < Content-Type: application/json 4 < Transfer-Encoding: chunked 4 < Date: Wed, 08 Apr 2026 03:55:57 GMT 4 < Keep-Alive: timeout=60 4 < Connection: keep-alive {"id":"d8ebfa6c-c3ee-4d73-b86c-119fb7694510","roomId":"120a7278-d214-46c3-88df-7deb6363dc0b","title":"Tier S1 EB b772f78a-e5f4-4d8f-b0a3-8396c4573b3d","description":"Test event for Early Bird availability","date":"2026-12-15T20:00:00","capacity":100,"status":"PUBLISHED","createdAt":"2026-04-08T03:55:57.942057","updatedAt":"2026-04-08T03:55:57.963604162","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}
64
Then status 200
0
# HU-02 Validation: GENERAL tier is available
67
Given url baseUrlEvents + '/api/v1/events/' + eventId
0
68
When method get
4
22:55:57.970 request: 5 > GET http://localhost:8081/api/v1/events/d8ebfa6c-c3ee-4d73-b86c-119fb7694510 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:55:57.973 response time in milliseconds: 3 5 < 200 5 < Content-Type: application/json 5 < Transfer-Encoding: chunked 5 < Date: Wed, 08 Apr 2026 03:55:57 GMT 5 < Keep-Alive: timeout=60 5 < Connection: keep-alive {"id":"d8ebfa6c-c3ee-4d73-b86c-119fb7694510","title":"Tier S1 EB b772f78a-e5f4-4d8f-b0a3-8396c4573b3d","description":"Test event for Early Bird availability","date":"2026-12-15T20:00:00","capacity":100,"room":{"id":"120a7278-d214-46c3-88df-7deb6363dc0b","name":"Early Bird Test Room S1","maxCapacity":100,"created_at":"2026-04-08T03:55:57.926443","updated_at":"2026-04-08T03:55:57.926446"},"availableTiers":[{"id":"fa845a76-47d7-4939-bc57-64088e49348c","tierType":"GENERAL","price":50.00,"quota":70,"validFrom":null,"validUntil":null,"isAvailable":true,"reason":null}],"created_at":"2026-04-08T03:55:57.942057","imageUrl":null,"subtitle":null,"location":null,"director":null,"castMembers":null,"duration":null,"tag":null,"isLimited":false,"isFeatured":false,"enableSeats":false,"author":null}
69
Then status 200
0
70
* def genTier = response.availableTiers[0]
0
71
* print 'General tier:', genTier
0
22:55:57.974 [print] General tier: { "id": "fa845a76-47d7-4939-bc57-64088e49348c", "tierType": "GENERAL", "price": 50.0, "quota": 70, "validFrom": null, "validUntil": null, "isAvailable": true, "reason": null }
72
* match genTier.tierType == 'GENERAL'
0
73
* match genTier.isAvailable == true
0
74
* print 'Scenario 1 PASS: Active GENERAL tier is available (isAvailable=true)'
0
22:55:57.974 [print] Scenario 1 PASS: Active GENERAL tier is available (isAvailable=true)
75
* print 'Note: EB tier with validUntil in past cannot be created via API (backend validates future dates)'
0
22:55:57.975 [print] Note: EB tier with validUntil in past cannot be created via API (backend validates future dates)
76
* print 'EB expiration logic is tested via SQL time-travel in expiration-release-flow-with-sql.feature'
0
22:55:57.975 [print] EB expiration logic is tested via SQL time-travel in expiration-release-flow-with-sql.feature
Scenario: [2:79] Scenario 2 - Invalid price rejected (0 or negative)
ms: 32
>>
Background:
7
* def baseUrlEvents = karate.properties['baseUrlEvents'] || 'http://localhost:8081'
0
22:55:57.976 karate.env system property was: null
8
* def UUID = Java.type('java.util.UUID')
0
9
* def futureDate = '2026-12-15T20:00:00'
0
81
* def tag = UUID.randomUUID() + ''
0
82
* def eventTitle = 'Tier S2 PriceVal ' + tag
0
# Setup: Create room
85
Given url baseUrlEvents + '/api/v1/rooms'
0
86
And header X-Role = 'ADMIN'
0
87
And header X-User-Id = '00000000-0000-0000-0000-000000000001'
0
88
And request
0
{ "name": "Price Validation Test Room", "maxCapacity": 100 }
95
When method post
9
22:55:57.977 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: 55 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":"Price Validation Test Room","maxCapacity":100} 22:55:57.986 response time in milliseconds: 8 1 < 201 1 < Content-Type: application/json 1 < Transfer-Encoding: chunked 1 < Date: Wed, 08 Apr 2026 03:55:57 GMT 1 < Keep-Alive: timeout=60 1 < Connection: keep-alive {"id":"3ccaafa2-8261-408f-b8e6-4a35a5ac31aa","name":"Price Validation Test Room","maxCapacity":100,"created_at":"2026-04-08T03:55:57.979467198","updated_at":"2026-04-08T03:55:57.979471088"}
96
Then status 201
0
97
* def roomId = response.id ? response.id : response.roomId
0
# Setup: Create event DRAFT
100
Given url baseUrlEvents + '/api/v1/events'
0
101
And header X-Role = 'ADMIN'
0
102
And header X-User-Id = '00000000-0000-0000-0000-000000000001'
0
103
And request
0
{ "roomId": "#(roomId)", "title": "#(eventTitle)", "description": "Test event for price validation", "date": "#(futureDate)", "capacity": 100, "enableSeats": false }
114
When method post
10
22:55:57.987 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: 225 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":"3ccaafa2-8261-408f-b8e6-4a35a5ac31aa","title":"Tier S2 PriceVal ac9b2c9c-85c0-439f-b10b-e97e9464c5d5","description":"Test event for price validation","date":"2026-12-15T20:00:00","capacity":100,"enableSeats":false} 22:55:57.996 response time in milliseconds: 9 2 < 201 2 < Content-Type: application/json 2 < Transfer-Encoding: chunked 2 < Date: Wed, 08 Apr 2026 03:55:57 GMT 2 < Keep-Alive: timeout=60 2 < Connection: keep-alive {"id":"05d5e374-bf24-4f38-955d-48ed5ca32a04","roomId":"3ccaafa2-8261-408f-b8e6-4a35a5ac31aa","title":"Tier S2 PriceVal ac9b2c9c-85c0-439f-b10b-e97e9464c5d5","description":"Test event for price validation","date":"2026-12-15T20:00:00","capacity":100,"status":"DRAFT","createdAt":"2026-04-08T03:55:57.99040899","updatedAt":"2026-04-08T03:55:57.99041051","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}
115
Then status 201
0
116
* def eventId = response.id ? response.id : response.eventId
0
# HU-02 Negative: Attempt to create tier with price = 0 (invalid)
119
Given url baseUrlEvents + '/api/v1/events/' + eventId + '/tiers'
0
120
And header X-Role = 'ADMIN'
0
121
And request
0
[ { "tierType": "GENERAL", "price": 0, "quota": 50 } ]
131
When method post
8
22:55:57.997 request: 3 > POST http://localhost:8081/api/v1/events/05d5e374-bf24-4f38-955d-48ed5ca32a04/tiers 3 > X-Role: ADMIN 3 > Content-Type: application/json; charset=UTF-8 3 > Content-Length: 45 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":0,"quota":50}] 22:55:58.004 response time in milliseconds: 7 3 < 400 3 < Content-Type: application/json 3 < Transfer-Encoding: chunked 3 < Date: Wed, 08 Apr 2026 03:55:57 GMT 3 < Connection: close {"error":"VALIDATION_ERROR","message":"Invalid request data","details":{"tierRequests":"price must be greater than 0"}}
132
Then status 400
0
133
* print 'Request rejected - price=0 is invalid (✅ expected error)'
0
22:55:58.005 [print] Request rejected - price=0 is invalid (✅ expected error)
# HU-02 Negative: Attempt to create tier with negative price
136
Given url baseUrlEvents + '/api/v1/events/' + eventId + '/tiers'
0
137
And header X-Role = 'ADMIN'
0
138
And request
0
[ { "tierType": "VIP", "price": -50.00, "quota": 20 } ]
148
When method post
3
22:55:58.006 request: 4 > POST http://localhost:8081/api/v1/events/05d5e374-bf24-4f38-955d-48ed5ca32a04/tiers 4 > X-Role: ADMIN 4 > Content-Type: application/json; charset=UTF-8 4 > Content-Length: 45 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 [{"tierType":"VIP","price":-50.0,"quota":20}] 22:55:58.008 response time in milliseconds: 2 4 < 400 4 < Content-Type: application/json 4 < Transfer-Encoding: chunked 4 < Date: Wed, 08 Apr 2026 03:55:57 GMT 4 < Connection: close {"error":"VALIDATION_ERROR","message":"Invalid request data","details":{"tierRequests":"price must be greater than 0"}}
149
Then status 400
0
150
* print 'Request rejected - negative price is invalid (✅ expected error)'
0
22:55:58.008 [print] Request rejected - negative price is invalid (✅ expected error)
Scenario: [3:153] Scenario 3 - Tier quota exceeds event capacity
ms: 28
>>
Background:
7
* def baseUrlEvents = karate.properties['baseUrlEvents'] || 'http://localhost:8081'
0
22:55:58.010 karate.env system property was: null
8
* def UUID = Java.type('java.util.UUID')
0
9
* def futureDate = '2026-12-15T20:00:00'
0
155
* def tag = UUID.randomUUID() + ''
0
156
* def eventTitle = 'Tier S3 QuotaOvf ' + tag
0
# Setup: Create room
159
Given url baseUrlEvents + '/api/v1/rooms'
0
160
And header X-Role = 'ADMIN'
0
161
And header X-User-Id = '00000000-0000-0000-0000-000000000001'
0
162
And request
0
{ "name": "Quota Capacity Test Room", "maxCapacity": 200 }
169
When method post
9
22:55:58.011 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: 53 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":"Quota Capacity Test Room","maxCapacity":200} 22:55:58.019 response time in milliseconds: 8 1 < 201 1 < Content-Type: application/json 1 < Transfer-Encoding: chunked 1 < Date: Wed, 08 Apr 2026 03:55:57 GMT 1 < Keep-Alive: timeout=60 1 < Connection: keep-alive {"id":"6c15f5df-006e-4f5e-995b-9c0c73d4544b","name":"Quota Capacity Test Room","maxCapacity":200,"created_at":"2026-04-08T03:55:58.013152132","updated_at":"2026-04-08T03:55:58.013156065"}
170
Then status 201
0
171
* def roomId = response.id ? response.id : response.roomId
0
# Setup: Create event DRAFT with capacity = 100
174
Given url baseUrlEvents + '/api/v1/events'
0
175
And header X-Role = 'ADMIN'
0
176
And header X-User-Id = '00000000-0000-0000-0000-000000000001'
0
177
And request
0
{ "roomId": "#(roomId)", "title": "#(eventTitle)", "description": "Test event for quota capacity validation", "date": "#(futureDate)", "capacity": 100, "enableSeats": false }
188
When method post
11
22:55:58.020 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: 234 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":"6c15f5df-006e-4f5e-995b-9c0c73d4544b","title":"Tier S3 QuotaOvf a9245719-9a88-41ed-a64a-f77cbd86b610","description":"Test event for quota capacity validation","date":"2026-12-15T20:00:00","capacity":100,"enableSeats":false} 22:55:58.030 response time in milliseconds: 10 2 < 201 2 < Content-Type: application/json 2 < Transfer-Encoding: chunked 2 < Date: Wed, 08 Apr 2026 03:55:57 GMT 2 < Keep-Alive: timeout=60 2 < Connection: keep-alive {"id":"f01c4140-010f-4163-a384-7d42371d1cc2","roomId":"6c15f5df-006e-4f5e-995b-9c0c73d4544b","title":"Tier S3 QuotaOvf a9245719-9a88-41ed-a64a-f77cbd86b610","description":"Test event for quota capacity validation","date":"2026-12-15T20:00:00","capacity":100,"status":"DRAFT","createdAt":"2026-04-08T03:55:58.024377488","updatedAt":"2026-04-08T03:55:58.024380354","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}
189
Then status 201
0
190
* def eventId = response.id ? response.id : response.eventId
0
191
* print 'Event created with capacity=100'
0
22:55:58.030 [print] Event created with capacity=100
# HU-02 Negative: Attempt to create tier with quota=150 (exceeds event capacity=100)
194
Given url baseUrlEvents + '/api/v1/events/' + eventId + '/tiers'
0
195
And header X-Role = 'ADMIN'
0
196
And request
0
[ { "tierType": "GENERAL", "price": 75.00, "quota": 150 } ]
206
When method post
7
22:55:58.031 request: 3 > POST http://localhost:8081/api/v1/events/f01c4140-010f-4163-a384-7d42371d1cc2/tiers 3 > X-Role: ADMIN 3 > Content-Type: application/json; charset=UTF-8 3 > Content-Length: 49 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":75.0,"quota":150}] 22:55:58.038 response time in milliseconds: 6 3 < 400 3 < Content-Type: application/json 3 < Transfer-Encoding: chunked 3 < Date: Wed, 08 Apr 2026 03:55:57 GMT 3 < Connection: close {"error":"QUOTA_EXCEEDS_CAPACITY","message":"Total quota (150) exceeds event capacity (100)","totalQuota":150,"eventCapacity":100}
207
Then status 400
0
208
* print 'Request rejected - tier quota exceeds event capacity (✅ expected error)'
0
22:55:58.038 [print] Request rejected - tier quota exceeds event capacity (✅ expected error)