Skip to content

Commit 1b40ad1

Browse files
committed
Standalone Stack State Validator - State Logic class
Signed-off-by: Rashed Kamal <[email protected]>
1 parent 85891a0 commit 1b40ad1

File tree

3 files changed

+268
-0
lines changed

3 files changed

+268
-0
lines changed

lib/cloud_controller.rb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,3 +117,5 @@ module VCAP::CloudController; end
117117
require 'cloud_controller/errands/rotate_database_key'
118118

119119
require 'services'
120+
121+
require 'cloud_controller/stack_state_validator'
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
module VCAP::CloudController
2+
class StackStateValidator
3+
class StackValidationError < StandardError; end
4+
class DisabledStackError < StackValidationError; end
5+
class RestrictedStackError < StackValidationError; end
6+
def self.validate_for_new_app!(stack)
7+
return [] if stack.active?
8+
9+
raise DisabledStackError.new("Stack '#{stack.name}' is disabled and cannot be used for staging new applications. #{stack.description}") if stack.disabled?
10+
11+
raise RestrictedStackError.new("Stack '#{stack.name}' is restricted and cannot be used for staging new applications. #{stack.description}") if stack.restricted?
12+
13+
stack.deprecated? ? [build_deprecation_warning(stack)] : []
14+
end
15+
16+
def self.validate_for_restaging!(stack)
17+
return [] if stack.active? || stack.restricted?
18+
19+
raise DisabledStackError.new("Stack '#{stack.name}' is disabled and cannot be used for staging new applications. #{stack.description}") if stack.disabled?
20+
21+
stack.deprecated? ? [build_deprecation_warning(stack)] : []
22+
end
23+
24+
def self.build_deprecation_warning(stack)
25+
"Stack '#{stack.name}' is deprecated and will be removed in the future. #{stack.description}"
26+
end
27+
end
28+
end
Lines changed: 238 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,238 @@
1+
require 'spec_helper'
2+
3+
module VCAP::CloudController
4+
RSpec.describe StackStateValidator do
5+
describe '.validate_for_new_app!' do
6+
context 'when stack is Active' do
7+
let(:stack) { Stack.make(state: StackStates::STACK_ACTIVE, description: 'My ACTIVE stack') }
8+
9+
it 'returns empty warnings' do
10+
result = StackStateValidator.validate_for_new_app!(stack)
11+
expect(result).to eq([])
12+
end
13+
14+
it 'does not raise an error' do
15+
expect { StackStateValidator.validate_for_new_app!(stack) }.not_to raise_error
16+
end
17+
end
18+
19+
context 'when stack is DEPRECATED' do
20+
let(:stack) { Stack.make(state: StackStates::STACK_DEPRECATED, description: 'My DEPRECATED stack') }
21+
22+
it 'returns a warning message' do
23+
result = StackStateValidator.validate_for_new_app!(stack)
24+
expect(result).to be_an(Array)
25+
expect(result.size).to eq(1)
26+
expect(result.first).to include("Stack '#{stack.name}' is deprecated and will be removed in the future. #{stack.description}")
27+
end
28+
29+
it 'returns a warning message with stack name' do
30+
result = StackStateValidator.validate_for_new_app!(stack)
31+
warning = result.first
32+
expect(warning).to include(stack.name)
33+
expect(warning).to include(stack.description)
34+
expect(warning).to include('deprecated')
35+
end
36+
37+
it 'does not raise an error' do
38+
expect { StackStateValidator.validate_for_new_app!(stack) }.not_to raise_error
39+
end
40+
end
41+
42+
context 'when stack is RESTRICTED' do
43+
let(:stack) { Stack.make(state: StackStates::STACK_RESTRICTED, description: 'My RESTRICTED stack') }
44+
45+
it 'raise RestrictedStackError' do
46+
expect do
47+
StackStateValidator.validate_for_new_app!(stack)
48+
end.to raise_error(StackStateValidator::RestrictedStackError, /Stack '#{stack.name}' is restricted and cannot be used for staging new applications./)
49+
end
50+
51+
it 'includes stack name in error message' do
52+
expect do
53+
StackStateValidator.validate_for_new_app!(stack)
54+
end.to raise_error(StackStateValidator::RestrictedStackError, /#{stack.name}/)
55+
end
56+
57+
it 'includes stack description in error message' do
58+
expect do
59+
StackStateValidator.validate_for_new_app!(stack)
60+
end.to raise_error(StackStateValidator::RestrictedStackError, /#{stack.description}/)
61+
end
62+
63+
it 'raises RestrictedStackError which is a StackStateValidator::Error' do
64+
expect do
65+
StackStateValidator.validate_for_new_app!(stack)
66+
end.to raise_error(StackStateValidator::StackValidationError)
67+
end
68+
end
69+
70+
context 'when stack is DISABLED' do
71+
let(:stack) { Stack.make(state: StackStates::STACK_DISABLED, description: 'My DEPRECATED stack') }
72+
73+
it 'returns a disabled error message' do
74+
expect do
75+
StackStateValidator.validate_for_new_app!(stack)
76+
end.to raise_error(StackStateValidator::DisabledStackError, /Stack '#{stack.name}' is disabled and cannot be used for staging new applications./)
77+
end
78+
79+
it 'includes stack name in error message' do
80+
expect do
81+
StackStateValidator.validate_for_new_app!(stack)
82+
end.to raise_error(StackStateValidator::DisabledStackError, /#{stack.name}/)
83+
end
84+
85+
it 'includes stack description in error message' do
86+
expect do
87+
StackStateValidator.validate_for_new_app!(stack)
88+
end.to raise_error(StackStateValidator::DisabledStackError, /#{stack.description}/)
89+
end
90+
end
91+
end
92+
93+
describe '.validate_for_restaging_app!' do
94+
context 'when stack is Active' do
95+
let(:stack) { Stack.make(state: StackStates::STACK_ACTIVE, description: 'My ACTIVE stack') }
96+
97+
it 'for restaging returns empty warnings' do
98+
result = StackStateValidator.validate_for_restaging!(stack)
99+
expect(result).to eq([])
100+
end
101+
102+
it 'does not raise an error' do
103+
expect { StackStateValidator.validate_for_new_app!(stack) }.not_to raise_error
104+
end
105+
end
106+
107+
context 'when stack is DEPRECATED' do
108+
let(:stack) { Stack.make(state: StackStates::STACK_DEPRECATED, description: 'My DEPRECATED stack') }
109+
110+
it 'returns a warning message' do
111+
result = StackStateValidator.validate_for_restaging!(stack)
112+
expect(result).to be_an(Array)
113+
expect(result.size).to eq(1)
114+
expect(result.first).to include("Stack '#{stack.name}' is deprecated and will be removed in the future. #{stack.description}")
115+
end
116+
117+
it 'returns a warning message with stack name' do
118+
result = StackStateValidator.validate_for_restaging!(stack)
119+
warning = result.first
120+
expect(warning).to include(stack.name)
121+
expect(warning).to include(stack.description)
122+
expect(warning).to include('deprecated')
123+
end
124+
125+
it 'does not raise an error' do
126+
expect { StackStateValidator.validate_for_restaging!(stack) }.not_to raise_error
127+
end
128+
end
129+
130+
context 'when stack is RESTRICTED' do
131+
let(:stack) { Stack.make(state: StackStates::STACK_RESTRICTED, description: 'My RESTRICTED stack') }
132+
133+
it 'returns empty warnings' do
134+
result = StackStateValidator.validate_for_restaging!(stack)
135+
expect(result).to eq([])
136+
end
137+
138+
it 'does not raise an error' do
139+
expect { StackStateValidator.validate_for_restaging!(stack) }.not_to raise_error
140+
end
141+
end
142+
143+
context 'when stack is DISABLED' do
144+
let(:stack) { Stack.make(state: StackStates::STACK_DISABLED, description: 'My DEPRECATED stack') }
145+
146+
it 'returns a disabled error message' do
147+
expect do
148+
StackStateValidator.validate_for_restaging!(stack)
149+
end.to raise_error(StackStateValidator::DisabledStackError, /Stack '#{stack.name}' is disabled and cannot be used for staging new applications./)
150+
end
151+
152+
it 'includes stack name in error message' do
153+
expect do
154+
StackStateValidator.validate_for_restaging!(stack)
155+
end.to raise_error(StackStateValidator::DisabledStackError, /#{stack.name}/)
156+
end
157+
158+
it 'includes stack description in error message' do
159+
expect do
160+
StackStateValidator.validate_for_restaging!(stack)
161+
end.to raise_error(StackStateValidator::DisabledStackError, /#{stack.description}/)
162+
end
163+
end
164+
end
165+
166+
describe '.build_deprecation_warning' do
167+
let(:stack) { Stack.make(name: 'cflinuxfs3', description: 'End of life December 2025') }
168+
169+
it 'returns formatted warning string' do
170+
warning = StackStateValidator.build_deprecation_warning(stack)
171+
expect(warning).to be_a(String)
172+
expect(warning).to include('cflinuxfs3')
173+
expect(warning).to include('deprecated')
174+
expect(warning).to include('End of life December 2025')
175+
end
176+
177+
it 'includes stack name when description is empty' do
178+
stack.description = ''
179+
warning = StackStateValidator.build_deprecation_warning(stack)
180+
expect(warning).to include('cflinuxfs3')
181+
end
182+
183+
it 'handles nil description' do
184+
stack.description = nil
185+
warning = StackStateValidator.build_deprecation_warning(stack)
186+
expect(warning).to include('cflinuxfs3')
187+
end
188+
end
189+
190+
describe 'state behavior matrix' do
191+
let(:active_stack) { Stack.make(state: StackStates::STACK_ACTIVE) }
192+
let(:deprecated_stack) { Stack.make(state: StackStates::STACK_DEPRECATED) }
193+
let(:restricted_stack) { Stack.make(state: StackStates::STACK_RESTRICTED) }
194+
let(:disabled_stack) { Stack.make(state: StackStates::STACK_DISABLED) }
195+
196+
describe 'new app creation' do
197+
it 'allows ACTIVE without warnings' do
198+
result = StackStateValidator.validate_for_new_app!(active_stack)
199+
expect(result).to be_empty
200+
end
201+
202+
it 'allows DEPRECATED with warnings' do
203+
result = StackStateValidator.validate_for_new_app!(deprecated_stack)
204+
expect(result).not_to be_empty
205+
end
206+
207+
it 'rejects RESTRICTED' do
208+
expect { StackStateValidator.validate_for_new_app!(restricted_stack) }.to raise_error(StackStateValidator::RestrictedStackError)
209+
end
210+
211+
it 'rejects DISABLED' do
212+
expect { StackStateValidator.validate_for_new_app!(disabled_stack) }.to raise_error(StackStateValidator::DisabledStackError)
213+
end
214+
end
215+
216+
describe 'restaging' do
217+
it 'allows ACTIVE without warnings' do
218+
result = StackStateValidator.validate_for_restaging!(active_stack)
219+
expect(result).to be_empty
220+
end
221+
222+
it 'allows DEPRECATED with warnings' do
223+
result = StackStateValidator.validate_for_restaging!(deprecated_stack)
224+
expect(result).not_to be_empty
225+
end
226+
227+
it 'allows RESTRICTED without warnings' do
228+
result = StackStateValidator.validate_for_restaging!(restricted_stack)
229+
expect(result).to be_empty
230+
end
231+
232+
it 'rejects DISABLED' do
233+
expect { StackStateValidator.validate_for_restaging!(disabled_stack) }.to raise_error(StackStateValidator::DisabledStackError)
234+
end
235+
end
236+
end
237+
end
238+
end

0 commit comments

Comments
 (0)