convection 0.2.32 → 0.2.33
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.rubocop.yml +0 -1
- data/.rubocop_todo.yml +1 -2
- data/Gemfile +2 -1
- data/Rakefile +3 -7
- data/lib/convection.rb +2 -2
- data/lib/convection/control/stack.rb +49 -10
- data/lib/convection/model/event.rb +3 -2
- data/lib/convection/model/template.rb +13 -1
- data/lib/convection/model/template/metadata.rb +22 -0
- data/lib/convection/model/template/resource/aws_ec2_subnet.rb +1 -0
- data/lib/convection/model/template/resource/aws_events_rule.rb +4 -0
- data/spec/cf_client_context.rb +10 -0
- data/spec/collect_availability_zones_task_context.rb +17 -0
- data/spec/convection/control/stack/after_create_tasks_spec.rb +51 -0
- data/spec/convection/control/stack/after_delete_tasks_spec.rb +51 -0
- data/spec/convection/control/stack/after_update_tasks_spec.rb +54 -0
- data/spec/convection/control/stack/before_create_tasks_spec.rb +52 -0
- data/spec/convection/control/stack/before_delete_tasks_spec.rb +51 -0
- data/spec/convection/control/stack/before_update_tasks_spec.rb +55 -0
- data/spec/convection/dsl/intrinsic_functions_spec.rb +88 -0
- data/spec/convection/model/template/condition_spec.rb +38 -0
- data/spec/convection/model/template/resource/directoryservice_simple_ad_spec.rb +39 -0
- data/spec/convection/model/template/resource/ec2_security_group_spec.rb +39 -0
- data/spec/convection/model/template/resource/ec2_subnet_spec.rb +48 -0
- data/spec/convection/model/template/resource/elasticache_cache_cluster_spec.rb +52 -0
- data/spec/convection/model/template/resource/elasticache_parameter_group_spec.rb +38 -0
- data/spec/convection/model/template/resource/elasticache_security_group_ingress_spec.rb +40 -0
- data/spec/convection/model/template/resource/elasticache_security_group_spec.rb +32 -0
- data/spec/convection/model/template/resource/events_rule_spec.rb +44 -0
- data/spec/convection/model/template/resource/iam_role_spec.rb +37 -0
- data/spec/convection/model/template/resource/lambdas_spec.rb +70 -0
- data/spec/convection/model/template/resource/loggroups_spec.rb +34 -0
- data/spec/convection/model/template/resource/permission_spec.rb +43 -0
- data/spec/convection/model/template/resource/rds_security_groups_spec.rb +50 -0
- data/spec/convection/model/template/resource/vpc_endpoints_spec.rb +65 -0
- data/spec/convection/model/template/resource_attribute/update_policies_spec.rb +66 -0
- data/spec/convection/model/template/template_spec.rb +60 -0
- data/spec/convection/model/template/validate_bytesize_spec.rb +49 -0
- data/spec/convection/model/template/validate_description_spec.rb +31 -0
- data/spec/convection/model/template/validate_mappings_spec.rb +88 -0
- data/spec/convection/model/template/validate_outputs_spec.rb +62 -0
- data/spec/convection/model/template/validate_parameters_spec.rb +84 -0
- data/spec/convection/model/template/validate_resources_spec.rb +50 -0
- data/spec/ec2_client_context.rb +18 -0
- data/spec/spec_helper.rb +11 -0
- metadata +72 -40
- data/test/convection/model/test_conditions.rb +0 -121
- data/test/convection/model/test_directory_service.rb +0 -40
- data/test/convection/model/test_elasticache.rb +0 -97
- data/test/convection/model/test_lambdas.rb +0 -53
- data/test/convection/model/test_loggroups.rb +0 -25
- data/test/convection/model/test_permission.rb +0 -31
- data/test/convection/model/test_rds.rb +0 -76
- data/test/convection/model/test_template.rb +0 -64
- data/test/convection/model/test_trust.rb +0 -28
- data/test/convection/model/test_update_policies.rb +0 -54
- data/test/convection/model/test_validation.rb +0 -216
- data/test/convection/model/test_vpc_endpoint.rb +0 -51
- data/test/convection/tasks/test_after_create_tasks.rb +0 -66
- data/test/convection/tasks/test_after_delete_tasks.rb +0 -66
- data/test/convection/tasks/test_after_update_tasks.rb +0 -71
- data/test/convection/tasks/test_before_create_tasks.rb +0 -66
- data/test/convection/tasks/test_before_delete_tasks.rb +0 -66
- data/test/convection/tasks/test_before_update_tasks.rb +0 -71
- data/test/test_helper.rb +0 -72
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 70262a473c642f00cc51b9077ca02c2617194320
|
4
|
+
data.tar.gz: bba7acb1a3242c6b354a384e9924f33c8a69ce13
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: df10a72ab338b4e6dec65b93942a0577d53db0d88bbe30e820a1536918192a8adee745b016c4067e3973a1bffccac2ee3cf9016d0930e3a52774185e7bca1edc
|
7
|
+
data.tar.gz: 61f8a1e4761e464a1a3a18139c02caa49839d93fb89be9cc68affe13d4baa9dc2bfbbd47cdcea31e442d4e1d154e6878da50dae25304fa09ce6f7850949eb677
|
data/.rubocop.yml
CHANGED
data/.rubocop_todo.yml
CHANGED
@@ -70,13 +70,12 @@ Style/Alias:
|
|
70
70
|
Style/ClassAndModuleChildren:
|
71
71
|
Exclude:
|
72
72
|
- 'lib/convection/model/template.rb'
|
73
|
-
- '
|
73
|
+
- 'spec/**/*'
|
74
74
|
|
75
75
|
# Offense count: 4
|
76
76
|
Style/Documentation:
|
77
77
|
Exclude:
|
78
78
|
- 'spec/**/*'
|
79
|
-
- 'test/**/*'
|
80
79
|
- 'lib/convection/model/attributes.rb'
|
81
80
|
- 'lib/convection/model/mixin/colorize.rb'
|
82
81
|
- 'lib/convection/model/template/condition.rb'
|
data/Gemfile
CHANGED
data/Rakefile
CHANGED
@@ -1,12 +1,8 @@
|
|
1
1
|
require 'bundler/gem_tasks'
|
2
2
|
require 'rubocop/rake_task'
|
3
|
-
require '
|
3
|
+
require 'rspec/core/rake_task'
|
4
4
|
|
5
5
|
RuboCop::RakeTask.new
|
6
|
+
RSpec::Core::RakeTask.new(:spec)
|
6
7
|
|
7
|
-
|
8
|
-
t.libs << 'test'
|
9
|
-
t.pattern = 'test/**/test_*.rb'
|
10
|
-
end
|
11
|
-
|
12
|
-
task :default => [:test, :rubocop]
|
8
|
+
task :default => [:spec, :rubocop]
|
data/lib/convection.rb
CHANGED
@@ -52,6 +52,9 @@ module Convection
|
|
52
52
|
|
53
53
|
## Internal status
|
54
54
|
NOT_CREATED = 'NOT_CREATED'.freeze
|
55
|
+
TASK_COMPLETE = 'TASK_COMPLETE'.freeze
|
56
|
+
TASK_FAILED = 'TASK_FAILED'.freeze
|
57
|
+
TASK_IN_PROGRESS = 'TASK_IN_PROGRESS'.freeze
|
55
58
|
|
56
59
|
def initialize(name, template, options = {}, &block)
|
57
60
|
@name = name
|
@@ -177,6 +180,26 @@ module Convection
|
|
177
180
|
@template.diff(@current_template)
|
178
181
|
end
|
179
182
|
|
183
|
+
def resource_changes?
|
184
|
+
ours = { 'Resources' => @template.resources.map(&:render) }
|
185
|
+
thiers = { 'Resources' => @current_template['Resources'] }
|
186
|
+
|
187
|
+
ours.diff(thiers).any?
|
188
|
+
end
|
189
|
+
|
190
|
+
def resource_dependent_changes?
|
191
|
+
ours = {
|
192
|
+
'Conditions' => @template.conditions.map(&:render),
|
193
|
+
'Outputs' => @template.outputs.map(&:render)
|
194
|
+
}
|
195
|
+
theirs = {
|
196
|
+
'Conditions' => @current_template['Conditions'],
|
197
|
+
'Outputs' => @current_template['Outputs']
|
198
|
+
}
|
199
|
+
|
200
|
+
ours.diff(theirs).any?
|
201
|
+
end
|
202
|
+
|
180
203
|
##
|
181
204
|
# Controllers
|
182
205
|
##
|
@@ -194,12 +217,16 @@ module Convection
|
|
194
217
|
block.call(Model::Event.new(:complete, "Stack #{ name } has no changes", :info)) if block
|
195
218
|
get_status
|
196
219
|
return
|
220
|
+
elsif !resource_changes? && resource_dependent_changes?
|
221
|
+
message = "Stack #{ name } has no convergable changes (you must update Resources to update Conditions, Metadata, or Outputs)"
|
222
|
+
block.call(Model::Event.new(UPDATE_FAILED, message, :warn)) if block
|
223
|
+
get_status
|
224
|
+
return
|
197
225
|
end
|
198
226
|
|
199
227
|
## Execute before update tasks
|
200
228
|
@tasks[:before_update].delete_if do |task|
|
201
|
-
task
|
202
|
-
task.success?
|
229
|
+
run_task(:before_update, task, &block)
|
203
230
|
end
|
204
231
|
|
205
232
|
## Update
|
@@ -209,8 +236,7 @@ module Convection
|
|
209
236
|
else
|
210
237
|
## Execute before create tasks
|
211
238
|
@tasks[:before_create].delete_if do |task|
|
212
|
-
task
|
213
|
-
task.success?
|
239
|
+
run_task(:before_create, task, &block)
|
214
240
|
end
|
215
241
|
|
216
242
|
## Create
|
@@ -229,8 +255,7 @@ module Convection
|
|
229
255
|
## Execute after create tasks
|
230
256
|
after_task_type = existing_stack ? :after_update : :after_create
|
231
257
|
@tasks[after_task_type].delete_if do |task|
|
232
|
-
task
|
233
|
-
task.success?
|
258
|
+
run_task(after_task_type, task, &block)
|
234
259
|
end
|
235
260
|
rescue Aws::Errors::ServiceError => e
|
236
261
|
@errors << e
|
@@ -239,8 +264,7 @@ module Convection
|
|
239
264
|
def delete(&block)
|
240
265
|
## Execute before delete tasks
|
241
266
|
@tasks[:before_delete].delete_if do |task|
|
242
|
-
task
|
243
|
-
task.success?
|
267
|
+
run_task(:before_delete, task, &block)
|
244
268
|
end
|
245
269
|
|
246
270
|
@cf_client.delete_stack(
|
@@ -254,8 +278,7 @@ module Convection
|
|
254
278
|
|
255
279
|
## Execute after delete tasks
|
256
280
|
@tasks[:after_delete].delete_if do |task|
|
257
|
-
task
|
258
|
-
task.success?
|
281
|
+
run_task(:after_delete, task, &block)
|
259
282
|
end
|
260
283
|
rescue Aws::Errors::ServiceError => e
|
261
284
|
@errors << e
|
@@ -426,6 +449,22 @@ module Convection
|
|
426
449
|
}
|
427
450
|
end
|
428
451
|
end
|
452
|
+
|
453
|
+
def run_task(phase, task, &block)
|
454
|
+
phase = phase.to_s.split.join(' ')
|
455
|
+
block.call(Model::Event.new(TASK_IN_PROGRESS, "Task (#{phase}) #{task} in progress for stack #{name}.", :info)) if block
|
456
|
+
|
457
|
+
task.call(self)
|
458
|
+
return task.success? unless block
|
459
|
+
|
460
|
+
if task.success?
|
461
|
+
block.call(Model::Event.new(TASK_COMPLETE, "Task (#{phase}) #{task} successfully completed for stack #{name}.", :info))
|
462
|
+
true
|
463
|
+
else
|
464
|
+
block.call(Model::Event.new(TASK_FAILED, "Task (#{phase}) #{task} failed to complete for stack #{name}.", :error))
|
465
|
+
false
|
466
|
+
end
|
467
|
+
end
|
429
468
|
end
|
430
469
|
end
|
431
470
|
end
|
@@ -14,11 +14,12 @@ module Convection
|
|
14
14
|
attr_accessor :level
|
15
15
|
attr_accessor :timestamp
|
16
16
|
colorize :level,
|
17
|
-
:green => [:info, :success, Control::Stack::CREATE_COMPLETE, Control::Stack::UPDATE_COMPLETE, Control::Stack::UPDATE_ROLLBACK_COMPLETE
|
17
|
+
:green => [:info, :success, Control::Stack::CREATE_COMPLETE, Control::Stack::UPDATE_COMPLETE, Control::Stack::UPDATE_ROLLBACK_COMPLETE,
|
18
|
+
Control::Stack::TASK_COMPLETE],
|
18
19
|
:red => [:error, :fail, Control::Stack::CREATE_FAILED, Control::Stack::ROLLBACK_FAILED,
|
19
20
|
Control::Stack::DELETE_FAILED, Control::Stack::UPDATE_FAILED,
|
20
21
|
Control::Stack::UPDATE_ROLLBACK_IN_PROGRESS, Control::Stack::UPDATE_ROLLBACK_COMPLETE_CLEANUP_IN_PROGRESS,
|
21
|
-
Control::Stack::UPDATE_ROLLBACK_FAILED],
|
22
|
+
Control::Stack::UPDATE_ROLLBACK_FAILED, Control::Stack::TASK_FAILED],
|
22
23
|
:default => :yellow
|
23
24
|
|
24
25
|
class << self
|
@@ -82,6 +82,15 @@ module Convection
|
|
82
82
|
o.instance_exec(&block) if block
|
83
83
|
outputs[name] = o
|
84
84
|
end
|
85
|
+
|
86
|
+
# @param name [String] the name of the new metadata configuration to set
|
87
|
+
# @param value [Hash] an arbritrary JSON object to set as the
|
88
|
+
# value of the new metadata configuration
|
89
|
+
def metadata(name = nil, value = nil)
|
90
|
+
return @metadata unless name
|
91
|
+
|
92
|
+
@metadata[name] = Model::Template::Metadata.new(name, value)
|
93
|
+
end
|
85
94
|
end
|
86
95
|
end
|
87
96
|
|
@@ -199,6 +208,7 @@ module Convection
|
|
199
208
|
@conditions = Collection.new
|
200
209
|
@resources = Collection.new
|
201
210
|
@outputs = Collection.new
|
211
|
+
@metadata = Collection.new
|
202
212
|
end
|
203
213
|
|
204
214
|
def clone(stack_)
|
@@ -222,7 +232,8 @@ module Convection
|
|
222
232
|
'Mappings' => mappings.map(&:render),
|
223
233
|
'Conditions' => conditions.map(&:render),
|
224
234
|
'Resources' => resources.map(&:render),
|
225
|
-
'Outputs' => outputs.map(&:render)
|
235
|
+
'Outputs' => outputs.map(&:render),
|
236
|
+
'Metadata' => metadata.map(&:render)
|
226
237
|
}
|
227
238
|
end
|
228
239
|
|
@@ -343,3 +354,4 @@ require_relative 'template/resource'
|
|
343
354
|
require_relative 'template/resource_property'
|
344
355
|
require_relative 'template/resource_attribute'
|
345
356
|
require_relative 'template/output'
|
357
|
+
require_relative 'template/metadata'
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module Convection
|
2
|
+
module Model
|
3
|
+
class Template
|
4
|
+
##
|
5
|
+
# Metadata Attribute
|
6
|
+
##
|
7
|
+
class Metadata
|
8
|
+
attr_accessor :name
|
9
|
+
attr_accessor :value
|
10
|
+
|
11
|
+
def initialize(name, value)
|
12
|
+
@name = name
|
13
|
+
@value = value
|
14
|
+
end
|
15
|
+
|
16
|
+
def render
|
17
|
+
value
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -11,6 +11,10 @@ module Convection
|
|
11
11
|
type 'AWS::Events::Rule'
|
12
12
|
property :description, 'Description'
|
13
13
|
property :domain, 'Domain'
|
14
|
+
# Event patterns are documented as the type "JSON Object".
|
15
|
+
# We can define it here as a Hash. Example usage of the
|
16
|
+
# `event_pattern` method property being used can be found in
|
17
|
+
# the EventsRule spec.
|
14
18
|
property :event_pattern, 'EventPattern', :type => :hash
|
15
19
|
property :name, 'Name'
|
16
20
|
property :role_arn, 'RoleArn'
|
@@ -0,0 +1,10 @@
|
|
1
|
+
RSpec.shared_context 'with a mock CloudFormation client' do
|
2
|
+
let(:cf_client) do
|
3
|
+
client = double(:cf_client, create_stack: nil, delete_stack: nil, update_stack: nil)
|
4
|
+
allow(client).to receive(:describe_stacks) {
|
5
|
+
fail Aws::CloudFormation::Errors::ValidationError.new(nil, 'Stack does not exist.')
|
6
|
+
}
|
7
|
+
|
8
|
+
client
|
9
|
+
end
|
10
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
RSpec.shared_context 'with a CollectAvailabilityZonesTask defined' do
|
2
|
+
class CollectAvailabilityZonesTask
|
3
|
+
attr_writer :availability_zones
|
4
|
+
|
5
|
+
def availability_zones
|
6
|
+
@availability_zones ||= []
|
7
|
+
end
|
8
|
+
|
9
|
+
def call(stack)
|
10
|
+
self.availability_zones += stack.availability_zones
|
11
|
+
end
|
12
|
+
|
13
|
+
def success?
|
14
|
+
availability_zones.any?
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module Convection::Control
|
4
|
+
describe Stack do
|
5
|
+
let(:template) do
|
6
|
+
Convection.template do
|
7
|
+
description 'EC2 VPC Test Template'
|
8
|
+
|
9
|
+
ec2_vpc 'TargetVPC' do
|
10
|
+
network '10.0.0.0'
|
11
|
+
subnet_length 24
|
12
|
+
enable_dns
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
describe 'after create tasks' do
|
18
|
+
include_context 'with a CollectAvailabilityZonesTask defined'
|
19
|
+
include_context 'with a mock CloudFormation client'
|
20
|
+
include_context 'with a mock EC2 client'
|
21
|
+
|
22
|
+
before do
|
23
|
+
allow(Aws::CloudFormation::Client).to receive(:new).and_return(cf_client)
|
24
|
+
allow(Aws::EC2::Client).to receive(:new).and_return(ec2_client)
|
25
|
+
end
|
26
|
+
let(:task) { CollectAvailabilityZonesTask.new }
|
27
|
+
subject do
|
28
|
+
scope = self
|
29
|
+
Convection::Control::Stack.new('EC2 VPC Test Stack', template) do
|
30
|
+
after_create_task scope.task
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
it 'is registered after Stack#apply is called' do
|
35
|
+
expect(subject.tasks[:after_create]).to_not be_empty
|
36
|
+
end
|
37
|
+
|
38
|
+
it 'is executed on Stack#apply' do
|
39
|
+
subject.apply
|
40
|
+
|
41
|
+
expect(task.availability_zones).to include('eu-central-1')
|
42
|
+
end
|
43
|
+
|
44
|
+
it 'is deregistered after Stack#apply is called' do
|
45
|
+
subject.apply
|
46
|
+
|
47
|
+
expect(subject.tasks[:after_create]).to be_empty
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module Convection::Control
|
4
|
+
describe Stack do
|
5
|
+
let(:template) do
|
6
|
+
Convection.template do
|
7
|
+
description 'EC2 VPC Test Template'
|
8
|
+
|
9
|
+
ec2_vpc 'TargetVPC' do
|
10
|
+
network '10.0.0.0'
|
11
|
+
subnet_length 24
|
12
|
+
enable_dns
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
describe 'after delete tasks' do
|
18
|
+
include_context 'with a CollectAvailabilityZonesTask defined'
|
19
|
+
include_context 'with a mock CloudFormation client'
|
20
|
+
include_context 'with a mock EC2 client'
|
21
|
+
|
22
|
+
before do
|
23
|
+
allow(Aws::CloudFormation::Client).to receive(:new).and_return(cf_client)
|
24
|
+
allow(Aws::EC2::Client).to receive(:new).and_return(ec2_client)
|
25
|
+
end
|
26
|
+
let(:task) { CollectAvailabilityZonesTask.new }
|
27
|
+
subject do
|
28
|
+
scope = self
|
29
|
+
Convection::Control::Stack.new('EC2 VPC Test Stack', template) do
|
30
|
+
after_delete_task scope.task
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
it 'is registered before Stack#delete is called' do
|
35
|
+
expect(subject.tasks[:after_delete]).to_not be_empty
|
36
|
+
end
|
37
|
+
|
38
|
+
it 'is executed on Stack#delete' do
|
39
|
+
subject.delete
|
40
|
+
|
41
|
+
expect(task.availability_zones).to include('eu-central-1')
|
42
|
+
end
|
43
|
+
|
44
|
+
it 'is deregistered after Stack#delete is called' do
|
45
|
+
subject.delete
|
46
|
+
|
47
|
+
expect(subject.tasks[:after_delete]).to be_empty
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module Convection::Control
|
4
|
+
describe Stack do
|
5
|
+
let(:template) do
|
6
|
+
Convection.template do
|
7
|
+
description 'EC2 VPC Test Template'
|
8
|
+
|
9
|
+
ec2_vpc 'TargetVPC' do
|
10
|
+
network '10.0.0.0'
|
11
|
+
subnet_length 24
|
12
|
+
enable_dns
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
describe 'after update tasks' do
|
18
|
+
include_context 'with a CollectAvailabilityZonesTask defined'
|
19
|
+
include_context 'with a mock CloudFormation client'
|
20
|
+
include_context 'with a mock EC2 client'
|
21
|
+
|
22
|
+
before do
|
23
|
+
allow(Aws::CloudFormation::Client).to receive(:new).and_return(cf_client)
|
24
|
+
allow(Aws::EC2::Client).to receive(:new).and_return(ec2_client)
|
25
|
+
end
|
26
|
+
let(:task) { CollectAvailabilityZonesTask.new }
|
27
|
+
subject do
|
28
|
+
scope = self
|
29
|
+
stack = Convection::Control::Stack.new('EC2 VPC Test Stack', template) do
|
30
|
+
after_update_task scope.task
|
31
|
+
end
|
32
|
+
allow(stack).to receive(:exist).and_return(true)
|
33
|
+
allow(stack).to receive(:exist?).and_return(true)
|
34
|
+
stack
|
35
|
+
end
|
36
|
+
|
37
|
+
it 'is registered after Stack#apply is called' do
|
38
|
+
expect(subject.tasks[:after_update]).to_not be_empty
|
39
|
+
end
|
40
|
+
|
41
|
+
it 'is executed on Stack#apply' do
|
42
|
+
subject.apply
|
43
|
+
|
44
|
+
expect(task.availability_zones).to include('eu-central-1')
|
45
|
+
end
|
46
|
+
|
47
|
+
it 'is deregistered after Stack#apply is called' do
|
48
|
+
subject.apply
|
49
|
+
|
50
|
+
expect(subject.tasks[:after_update]).to be_empty
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|