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)