|
27 | 27 | end |
28 | 28 | end |
29 | 29 |
|
| 30 | + context 'when creating stack with state_reason' do |
| 31 | + it 'creates stack with state_reason' do |
| 32 | + request_body = { |
| 33 | + name: 'deprecated-with-reason', |
| 34 | + state: 'DEPRECATED', |
| 35 | + state_reason: 'This stack will be removed on 2026-12-31' |
| 36 | + }.to_json |
| 37 | + |
| 38 | + post '/v3/stacks', request_body, headers |
| 39 | + |
| 40 | + expect(last_response.status).to eq(201) |
| 41 | + expect(parsed_response['state']).to eq('DEPRECATED') |
| 42 | + expect(parsed_response['state_reason']).to eq('This stack will be removed on 2026-12-31') |
| 43 | + end |
| 44 | + |
| 45 | + it 'creates stack without state_reason' do |
| 46 | + request_body = { |
| 47 | + name: 'active-no-reason', |
| 48 | + state: 'ACTIVE' |
| 49 | + }.to_json |
| 50 | + |
| 51 | + post '/v3/stacks', request_body, headers |
| 52 | + |
| 53 | + expect(last_response.status).to eq(201) |
| 54 | + expect(parsed_response['state']).to eq('ACTIVE') |
| 55 | + expect(parsed_response['state_reason']).to be_nil |
| 56 | + end |
| 57 | + |
| 58 | + it 'rejects state_reason exceeding maximum length' do |
| 59 | + request_body = { |
| 60 | + name: 'long-reason-stack', |
| 61 | + state: 'DEPRECATED', |
| 62 | + state_reason: 'A' * 1001 |
| 63 | + }.to_json |
| 64 | + |
| 65 | + post '/v3/stacks', request_body, headers |
| 66 | + |
| 67 | + expect(last_response.status).to eq(422) |
| 68 | + expect(parsed_response['errors'].first['detail']).to include('is too long') |
| 69 | + end |
| 70 | + end |
| 71 | + |
30 | 72 | context 'when creating stack without state' do |
31 | 73 | it 'defaults to ACTIVE' do |
32 | 74 | request_body = { |
|
165 | 207 | end |
166 | 208 | end |
167 | 209 |
|
| 210 | + context 'when updating state_reason' do |
| 211 | + it 'updates state_reason along with state' do |
| 212 | + request_body = { |
| 213 | + state: 'DEPRECATED', |
| 214 | + state_reason: 'Stack will be removed on 2026-12-31' |
| 215 | + }.to_json |
| 216 | + |
| 217 | + patch "/v3/stacks/#{stack.guid}", request_body, headers |
| 218 | + |
| 219 | + expect(last_response.status).to eq(200) |
| 220 | + expect(parsed_response['state']).to eq('DEPRECATED') |
| 221 | + expect(parsed_response['state_reason']).to eq('Stack will be removed on 2026-12-31') |
| 222 | + |
| 223 | + stack.reload |
| 224 | + expect(stack.state_reason).to eq('Stack will be removed on 2026-12-31') |
| 225 | + end |
| 226 | + |
| 227 | + it 'updates state_reason independently' do |
| 228 | + stack.update(state: 'DEPRECATED') |
| 229 | + |
| 230 | + request_body = { |
| 231 | + state_reason: 'Updated reason for deprecation' |
| 232 | + }.to_json |
| 233 | + |
| 234 | + patch "/v3/stacks/#{stack.guid}", request_body, headers |
| 235 | + |
| 236 | + expect(last_response.status).to eq(200) |
| 237 | + expect(parsed_response['state']).to eq('DEPRECATED') |
| 238 | + expect(parsed_response['state_reason']).to eq('Updated reason for deprecation') |
| 239 | + end |
| 240 | + |
| 241 | + it 'clears state_reason when set to null' do |
| 242 | + stack.update(state: 'DEPRECATED', state_reason: 'Initial reason') |
| 243 | + |
| 244 | + request_body = { |
| 245 | + state_reason: nil |
| 246 | + }.to_json |
| 247 | + |
| 248 | + patch "/v3/stacks/#{stack.guid}", request_body, headers |
| 249 | + |
| 250 | + expect(last_response.status).to eq(200) |
| 251 | + expect(parsed_response['state_reason']).to be_nil |
| 252 | + |
| 253 | + stack.reload |
| 254 | + expect(stack.state_reason).to be_nil |
| 255 | + end |
| 256 | + |
| 257 | + it 'preserves state_reason when not included in request' do |
| 258 | + stack.update(state: 'DEPRECATED', state_reason: 'Existing reason') |
| 259 | + |
| 260 | + request_body = { |
| 261 | + state: 'RESTRICTED' |
| 262 | + }.to_json |
| 263 | + |
| 264 | + patch "/v3/stacks/#{stack.guid}", request_body, headers |
| 265 | + |
| 266 | + expect(last_response.status).to eq(200) |
| 267 | + expect(parsed_response['state']).to eq('RESTRICTED') |
| 268 | + expect(parsed_response['state_reason']).to eq('Existing reason') |
| 269 | + end |
| 270 | + |
| 271 | + it 'rejects state_reason exceeding maximum length' do |
| 272 | + request_body = { |
| 273 | + state_reason: 'A' * 1001 |
| 274 | + }.to_json |
| 275 | + |
| 276 | + patch "/v3/stacks/#{stack.guid}", request_body, headers |
| 277 | + |
| 278 | + expect(last_response.status).to eq(422) |
| 279 | + expect(parsed_response['errors'].first['detail']).to include('is too long') |
| 280 | + end |
| 281 | + end |
| 282 | + |
168 | 283 | context 'as non-admin user' do |
169 | 284 | let(:non_admin_user) { make_user } |
170 | 285 | let(:non_admin_headers) { headers_for(non_admin_user) } |
|
190 | 305 | expect(last_response.status).to eq(200) |
191 | 306 | expect(parsed_response['state']).to eq('DEPRECATED') |
192 | 307 | end |
| 308 | + |
| 309 | + context 'when stack has state_reason' do |
| 310 | + let!(:stack_with_reason) do |
| 311 | + VCAP::CloudController::Stack.make( |
| 312 | + state: 'DEPRECATED', |
| 313 | + state_reason: 'EOL on 2026-12-31' |
| 314 | + ) |
| 315 | + end |
| 316 | + |
| 317 | + it 'returns state_reason in response' do |
| 318 | + get "/v3/stacks/#{stack_with_reason.guid}", nil, reader_headers |
| 319 | + |
| 320 | + expect(last_response.status).to eq(200) |
| 321 | + expect(parsed_response['state']).to eq('DEPRECATED') |
| 322 | + expect(parsed_response['state_reason']).to eq('EOL on 2026-12-31') |
| 323 | + end |
| 324 | + end |
| 325 | + |
| 326 | + context 'when stack has no state_reason' do |
| 327 | + let!(:stack_without_reason) do |
| 328 | + VCAP::CloudController::Stack.make(state: 'ACTIVE', state_reason: nil) |
| 329 | + end |
| 330 | + |
| 331 | + it 'returns null state_reason in response' do |
| 332 | + get "/v3/stacks/#{stack_without_reason.guid}", nil, reader_headers |
| 333 | + |
| 334 | + expect(last_response.status).to eq(200) |
| 335 | + expect(parsed_response['state']).to eq('ACTIVE') |
| 336 | + expect(parsed_response['state_reason']).to be_nil |
| 337 | + end |
| 338 | + end |
193 | 339 | end |
194 | 340 |
|
195 | 341 | describe 'GET /v3/stacks' do |
196 | 342 | before { VCAP::CloudController::Stack.dataset.destroy } |
197 | 343 |
|
198 | 344 | let!(:active_stack) { VCAP::CloudController::Stack.make(name: 'active', state: 'ACTIVE') } |
199 | | - let!(:deprecated_stack) { VCAP::CloudController::Stack.make(name: 'deprecated', state: 'DEPRECATED') } |
| 345 | + let!(:deprecated_stack) { VCAP::CloudController::Stack.make(name: 'deprecated', state: 'DEPRECATED', state_reason: 'Deprecated reason') } |
200 | 346 | let!(:restricted_stack) { VCAP::CloudController::Stack.make(name: 'restricted', state: 'RESTRICTED') } |
201 | | - let!(:disabled_stack) { VCAP::CloudController::Stack.make(name: 'disabled', state: 'DISABLED') } |
| 347 | + let!(:disabled_stack) { VCAP::CloudController::Stack.make(name: 'disabled', state: 'DISABLED', state_reason: 'Disabled reason') } |
202 | 348 |
|
203 | 349 | let(:reader_user) { make_user } |
204 | 350 | let(:reader_headers) { headers_for(reader_user) } |
|
211 | 357 | resources = parsed_response['resources'] |
212 | 358 | expect(resources.pluck('state')).to contain_exactly('ACTIVE', 'DEPRECATED', 'RESTRICTED', 'DISABLED') |
213 | 359 | end |
| 360 | + |
| 361 | + it 'includes state_reason for stacks that have it' do |
| 362 | + get '/v3/stacks', nil, reader_headers |
| 363 | + |
| 364 | + expect(last_response.status).to eq(200) |
| 365 | + |
| 366 | + resources = parsed_response['resources'] |
| 367 | + |
| 368 | + deprecated = resources.find { |r| r['name'] == 'deprecated' } |
| 369 | + expect(deprecated['state_reason']).to eq('Deprecated reason') |
| 370 | + |
| 371 | + disabled = resources.find { |r| r['name'] == 'disabled' } |
| 372 | + expect(disabled['state_reason']).to eq('Disabled reason') |
| 373 | + |
| 374 | + active = resources.find { |r| r['name'] == 'active' } |
| 375 | + expect(active['state_reason']).to be_nil |
| 376 | + |
| 377 | + restricted = resources.find { |r| r['name'] == 'restricted' } |
| 378 | + expect(restricted['state_reason']).to be_nil |
| 379 | + end |
214 | 380 | end |
215 | 381 | end |
0 commit comments