geoengineer 0.1.2 → 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- checksums.yaml.gz.sig +0 -0
- data.tar.gz.sig +0 -0
- data/lib/geoengineer/cli/geo_cli.rb +16 -5
- data/lib/geoengineer/cli/status_command.rb +38 -46
- data/lib/geoengineer/cli/terraform_commands.rb +8 -2
- data/lib/geoengineer/environment.rb +5 -1
- data/lib/geoengineer/resource.rb +54 -35
- data/lib/geoengineer/resources/aws_cloudwatch_metric_alarm.rb +48 -0
- data/lib/geoengineer/resources/aws_iam_account_password_policy.rb +32 -0
- data/lib/geoengineer/resources/aws_iam_instance_profile.rb +28 -0
- data/lib/geoengineer/resources/aws_iam_policy.rb +5 -5
- data/lib/geoengineer/resources/aws_iam_user.rb +3 -3
- data/lib/geoengineer/resources/aws_kms_key.rb +27 -0
- data/lib/geoengineer/resources/aws_lambda_function.rb +3 -0
- data/lib/geoengineer/resources/aws_lambda_permission.rb +24 -18
- data/lib/geoengineer/resources/aws_ses_receipt_rule.rb +2 -2
- data/lib/geoengineer/resources/aws_ses_receipt_rule_set.rb +2 -2
- data/lib/geoengineer/resources/aws_sns_topic.rb +3 -3
- data/lib/geoengineer/resources/aws_sns_topic_subscription.rb +17 -6
- data/lib/geoengineer/resources/aws_sqs_queue.rb +3 -3
- data/lib/geoengineer/template.rb +3 -0
- data/lib/geoengineer/utils/aws_clients.rb +7 -0
- data/lib/geoengineer/version.rb +1 -1
- data/spec/resource_spec.rb +119 -18
- data/spec/resources/aws_cloudwatch_metric_alarm_spec.rb +38 -0
- data/spec/resources/aws_iam_account_password_policy_spec.rb +51 -0
- data/spec/resources/aws_iam_instance_profile_spec.rb +40 -0
- data/spec/resources/aws_kinesis_stream_spec.rb +1 -0
- data/spec/resources/aws_kms_key_spec.rb +44 -0
- data/spec/resources/aws_lambda_permission_spec.rb +0 -38
- metadata +17 -5
- metadata.gz.sig +0 -0
@@ -0,0 +1,32 @@
|
|
1
|
+
########################################################################
|
2
|
+
# AwsIamPasswordPolicy +aws_iam_password_policy+ terrform resource,
|
3
|
+
#
|
4
|
+
# {https://www.terraform.io/docs/providers/aws/r/iam_account_password_policy.html Terraform Docs}
|
5
|
+
########################################################################
|
6
|
+
class GeoEngineer::Resources::AwsIamAccountPasswordPolicy < GeoEngineer::Resource
|
7
|
+
# There can only be a single IAM account password policy - use this constant as the Geo ID
|
8
|
+
SINGLETON_ID = 'GEO_SINGLETON_RESOURCE_ID'.freeze
|
9
|
+
|
10
|
+
validate -> { validate_required_attributes([:allow_users_to_change_password]) }
|
11
|
+
|
12
|
+
after :initialize, -> {
|
13
|
+
_terraform_id -> { NullObject.maybe(remote_resource)._terraform_id }
|
14
|
+
}
|
15
|
+
|
16
|
+
after :initialize, -> { _geo_id -> { SINGLETON_ID } }
|
17
|
+
|
18
|
+
def support_tags?
|
19
|
+
false
|
20
|
+
end
|
21
|
+
|
22
|
+
def find_remote_as_individual?
|
23
|
+
true
|
24
|
+
end
|
25
|
+
|
26
|
+
def remote_resource_params
|
27
|
+
password_policy = AwsClients.iam.get_account_password_policy.password_policy.to_h
|
28
|
+
password_policy.merge({ _geo_id: SINGLETON_ID, _terraform_id: SINGLETON_ID })
|
29
|
+
rescue Aws::IAM::Errors::NoSuchEntity
|
30
|
+
{}
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
########################################################################
|
2
|
+
# AwsIamInstanceProfile +aws_iam_instance_profile+ terrform resource,
|
3
|
+
#
|
4
|
+
# {https://www.terraform.io/docs/providers/aws/r/iam_instance_profile.html Terraform Docs}
|
5
|
+
########################################################################
|
6
|
+
class GeoEngineer::Resources::AwsIamInstanceProfile < GeoEngineer::Resource
|
7
|
+
validate -> { validate_required_attributes([:name, :roles]) }
|
8
|
+
|
9
|
+
before :validation, -> { policy_arn _policy.to_ref(:arn) if _policy }
|
10
|
+
|
11
|
+
after :initialize, -> { _terraform_id -> { NullObject.maybe(remote_resource)._terraform_id } }
|
12
|
+
after :initialize, -> { _geo_id -> { name.to_s } }
|
13
|
+
|
14
|
+
def support_tags?
|
15
|
+
false
|
16
|
+
end
|
17
|
+
|
18
|
+
def self._fetch_remote_resources
|
19
|
+
profiles = AwsClients.iam.list_instance_profiles['instance_profiles'].map(&:to_h)
|
20
|
+
profiles.map do |p|
|
21
|
+
{
|
22
|
+
name: p[:instance_profile_name],
|
23
|
+
_geo_id: p[:instance_profile_name],
|
24
|
+
_terraform_id: p[:instance_profile_name]
|
25
|
+
}
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -51,11 +51,11 @@ class GeoEngineer::Resources::AwsIamPolicy < GeoEngineer::Resource
|
|
51
51
|
def self._fetch_remote_resources
|
52
52
|
_all_remote_policies.map(&:to_h).map do |policy|
|
53
53
|
{
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
54
|
+
_terraform_id: policy[:arn],
|
55
|
+
_geo_id: policy[:policy_name],
|
56
|
+
arn: policy[:arn],
|
57
|
+
default_version_id: policy[:default_version_id],
|
58
|
+
name: policy[:policy_name]
|
59
59
|
}
|
60
60
|
end
|
61
61
|
end
|
@@ -33,9 +33,9 @@ class GeoEngineer::Resources::AwsIamUser < GeoEngineer::Resource
|
|
33
33
|
def self._fetch_remote_resources
|
34
34
|
_all_remote_users.map do |user|
|
35
35
|
{
|
36
|
-
|
37
|
-
|
38
|
-
|
36
|
+
_terraform_id: user[:user_name],
|
37
|
+
_geo_id: user[:user_name],
|
38
|
+
name: user[:user_name]
|
39
39
|
}
|
40
40
|
end
|
41
41
|
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
########################################################################
|
2
|
+
# AwsKmsKey is the +aws_kms_key+ terrform resource,
|
3
|
+
#
|
4
|
+
# {https://www.terraform.io/docs/providers/aws/r/kms_key.html}
|
5
|
+
########################################################################
|
6
|
+
class GeoEngineer::Resources::AwsKmsKey < GeoEngineer::Resource
|
7
|
+
validate -> { validate_required_attributes([:description]) }
|
8
|
+
|
9
|
+
after :initialize, -> { _terraform_id -> { NullObject.maybe(remote_resource)._terraform_id } }
|
10
|
+
after :initialize, -> { _geo_id -> { description } }
|
11
|
+
|
12
|
+
def self._fetch_remote_resources
|
13
|
+
keys = AwsClients.kms.list_keys[:keys].map do |i|
|
14
|
+
AwsClients.kms.describe_key({ key_id: i.key_id }).key_metadata.to_h
|
15
|
+
end
|
16
|
+
|
17
|
+
keys.map do |k|
|
18
|
+
k[:_terraform_id] = k[:key_id]
|
19
|
+
k[:_geo_id] = k[:description]
|
20
|
+
k
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def support_tags?
|
25
|
+
false
|
26
|
+
end
|
27
|
+
end
|
@@ -40,38 +40,26 @@ class GeoEngineer::Resources::AwsLambdaPermission < GeoEngineer::Resource
|
|
40
40
|
nil
|
41
41
|
end
|
42
42
|
|
43
|
-
def self._deep_symbolize_keys(obj)
|
44
|
-
if obj.is_a?(Hash)
|
45
|
-
obj.each_with_object({}) do |(key, value), hash|
|
46
|
-
hash[key.to_sym] = _deep_symbolize_keys(value)
|
47
|
-
end
|
48
|
-
elsif obj.is_a?(Array)
|
49
|
-
obj.map { |value| _deep_symbolize_keys(value) }
|
50
|
-
else
|
51
|
-
obj
|
52
|
-
end
|
53
|
-
end
|
54
|
-
|
55
43
|
def self._create_permission(function)
|
56
44
|
policy = function[:policy]
|
57
45
|
policy[:Statement].map do |statement|
|
58
|
-
# Note that the keys for a statement
|
46
|
+
# Note that the keys for a statement object are all CamelCased
|
59
47
|
# Whereas most other keys in this repo are snake_cased
|
60
48
|
statement.merge(
|
61
49
|
{
|
62
50
|
_terraform_id: statement[:Sid],
|
63
51
|
function_name: function[:function_name],
|
64
|
-
function_version: function[:version]
|
52
|
+
function_version: function[:version],
|
53
|
+
qualifer: function[:qualifer] || "$LATEST",
|
54
|
+
statement_id: statement[:Sid]
|
65
55
|
}
|
66
56
|
)
|
67
57
|
end
|
68
58
|
end
|
69
59
|
|
70
60
|
# Right now, this only fetches policies for the $LATEST version
|
71
|
-
# If
|
72
|
-
#
|
73
|
-
# Same deal if we need to support older versions...
|
74
|
-
# (excluding any extra calls for pagination). Less than ideal...
|
61
|
+
# If you want to fetch the policy for a version other than $LATEST
|
62
|
+
# set `find_remote_as_individual?` to `true` for that resource
|
75
63
|
def self._fetch_remote_resources
|
76
64
|
_fetch_functions
|
77
65
|
.map { |function| _fetch_policy(function) }
|
@@ -80,4 +68,22 @@ class GeoEngineer::Resources::AwsLambdaPermission < GeoEngineer::Resource
|
|
80
68
|
.flatten
|
81
69
|
.compact
|
82
70
|
end
|
71
|
+
|
72
|
+
def remote_resource_params
|
73
|
+
params = { function_name: function_name }
|
74
|
+
params[:qualifier] = qualifier if qualifier
|
75
|
+
|
76
|
+
begin
|
77
|
+
policy = _fetch_policy(params)[:policy]
|
78
|
+
return {} if policy.nil?
|
79
|
+
rescue Aws::Lambda::Errors::ResourceNotFoundException
|
80
|
+
return {}
|
81
|
+
end
|
82
|
+
|
83
|
+
permission = _create_permission(policy).find do |statement|
|
84
|
+
statement[:_terraform_id] == statement_id
|
85
|
+
end
|
86
|
+
|
87
|
+
permission || {}
|
88
|
+
end
|
83
89
|
end
|
@@ -30,8 +30,8 @@ class GeoEngineer::Resources::AwsSesReceiptRule < GeoEngineer::Resource
|
|
30
30
|
def self._fetch_remote_resources
|
31
31
|
AwsClients.ses.describe_active_receipt_rule_set.rules.map(&:to_h).map do |rule|
|
32
32
|
{
|
33
|
-
|
34
|
-
|
33
|
+
_terraform_id: rule[:name],
|
34
|
+
_geo_id: rule[:name]
|
35
35
|
}
|
36
36
|
end
|
37
37
|
end
|
@@ -20,8 +20,8 @@ class GeoEngineer::Resources::AwsSesReceiptRuleSet < GeoEngineer::Resource
|
|
20
20
|
def self._fetch_remote_resources
|
21
21
|
AwsClients.ses.list_receipt_rule_sets.rule_sets.map(&:to_h).map do |rule_set|
|
22
22
|
{
|
23
|
-
|
24
|
-
|
23
|
+
_terraform_id: rule_set[:name],
|
24
|
+
_geo_id: rule_set[:name]
|
25
25
|
}
|
26
26
|
end
|
27
27
|
end
|
@@ -19,9 +19,9 @@ class GeoEngineer::Resources::AwsSnsTopic < GeoEngineer::Resource
|
|
19
19
|
def self._fetch_remote_resources
|
20
20
|
AwsClients.sns.list_topics.topics.map(&:to_h).map do |topic|
|
21
21
|
{
|
22
|
-
|
23
|
-
|
24
|
-
|
22
|
+
_terraform_id: topic[:topic_arn],
|
23
|
+
_geo_id: topic[:topic_arn],
|
24
|
+
name: topic[:topic_arn].split(':').last
|
25
25
|
}
|
26
26
|
end
|
27
27
|
end
|
@@ -20,7 +20,8 @@ class GeoEngineer::Resources::AwsSnsTopicSubscription < GeoEngineer::Resource
|
|
20
20
|
'endpoint' => endpoint,
|
21
21
|
'protocol' => protocol,
|
22
22
|
'confirmation_timeout_in_minutes' => "1",
|
23
|
-
'endpoint_auto_confirms' => "false"
|
23
|
+
'endpoint_auto_confirms' => "false",
|
24
|
+
'raw_message_delivery' => "false"
|
24
25
|
}
|
25
26
|
tfstate
|
26
27
|
end
|
@@ -30,13 +31,23 @@ class GeoEngineer::Resources::AwsSnsTopicSubscription < GeoEngineer::Resource
|
|
30
31
|
end
|
31
32
|
|
32
33
|
def self._fetch_remote_resources
|
33
|
-
|
34
|
+
_get_all_subscriptions.map do |subscription|
|
34
35
|
{
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
36
|
+
_terraform_id: subscription[:subscription_arn],
|
37
|
+
_geo_id: "#{subscription[:topic_arn]}::" \
|
38
|
+
"#{subscription[:protocol]}::" \
|
39
|
+
"#{subscription[:endpoint]}"
|
39
40
|
}
|
40
41
|
end
|
41
42
|
end
|
43
|
+
|
44
|
+
def self._get_all_subscriptions
|
45
|
+
subs_page = AwsClients.sns.list_subscriptions
|
46
|
+
subs = subs_page.subscriptions.map(&:to_h)
|
47
|
+
while subs_page.next_token
|
48
|
+
subs_page = AwsClients.sns.list_subscriptions({ next_token: subs_page.next_token })
|
49
|
+
subs.concat subs_page.subscriptions.map(&:to_h)
|
50
|
+
end
|
51
|
+
subs
|
52
|
+
end
|
42
53
|
end
|
@@ -28,9 +28,9 @@ class GeoEngineer::Resources::AwsSqsQueue < GeoEngineer::Resource
|
|
28
28
|
def self._fetch_remote_resources
|
29
29
|
AwsClients.sqs.list_queues['queue_urls'].map do |queue|
|
30
30
|
{
|
31
|
-
|
32
|
-
|
33
|
-
|
31
|
+
_terraform_id: queue,
|
32
|
+
_geo_id: queue,
|
33
|
+
name: URI.parse(queue).path.split('/').last
|
34
34
|
}
|
35
35
|
end
|
36
36
|
end
|
data/lib/geoengineer/template.rb
CHANGED
@@ -5,8 +5,11 @@ class GeoEngineer::Template
|
|
5
5
|
include HasAttributes
|
6
6
|
include HasResources
|
7
7
|
|
8
|
+
attr_accessor :name, :parameters
|
9
|
+
|
8
10
|
def initialize(name, parent, parameters = {})
|
9
11
|
@name = name
|
12
|
+
@parameters = parameters
|
10
13
|
case parent
|
11
14
|
when GeoEngineer::Project then add_project_attributes(parent)
|
12
15
|
when GeoEngineer::Environment then add_env_attributes(parent)
|
@@ -12,6 +12,9 @@ class AwsClients
|
|
12
12
|
end
|
13
13
|
|
14
14
|
# Clients
|
15
|
+
def self.cloudwatch
|
16
|
+
@aws_cloudwatch ||= Aws::CloudWatch::Client.new({ stub_responses: stubbed? })
|
17
|
+
end
|
15
18
|
|
16
19
|
def self.cloudwatchevents
|
17
20
|
@aws_cloudwatchevents ||= Aws::CloudWatchEvents::Client.new({ stub_responses: stubbed? })
|
@@ -80,4 +83,8 @@ class AwsClients
|
|
80
83
|
def self.cloudtrail
|
81
84
|
@aws_cloudtrail ||= Aws::CloudTrail::Client.new({ stub_responses: stubbed? })
|
82
85
|
end
|
86
|
+
|
87
|
+
def self.kms
|
88
|
+
@aws_kms ||= Aws::KMS::Client.new({ stub_responses: stubbed? })
|
89
|
+
end
|
83
90
|
end
|
data/lib/geoengineer/version.rb
CHANGED
data/spec/resource_spec.rb
CHANGED
@@ -6,7 +6,9 @@ class GeoEngineer::RemoteResources < GeoEngineer::Resource
|
|
6
6
|
end
|
7
7
|
end
|
8
8
|
|
9
|
-
describe
|
9
|
+
describe GeoEngineer::Resource do
|
10
|
+
let(:env) { GeoEngineer::Environment.new("testing") }
|
11
|
+
|
10
12
|
describe '#remote_resource' do
|
11
13
|
it 'should return a list of resources' do
|
12
14
|
rem_res = GeoEngineer::RemoteResources.new('rem', 'id') {
|
@@ -99,6 +101,24 @@ describe("GeoEngineer::Resource") do
|
|
99
101
|
end
|
100
102
|
end
|
101
103
|
|
104
|
+
describe '#_resources_to_ignore' do
|
105
|
+
it 'lets you ignore certain resources' do
|
106
|
+
class GeoEngineer::IgnorableResources < GeoEngineer::Resource
|
107
|
+
def self._fetch_remote_resources
|
108
|
+
[{ _geo_id: "geoid1" }, { _geo_id: "geoid2" }]
|
109
|
+
end
|
110
|
+
|
111
|
+
def self._resources_to_ignore
|
112
|
+
["geoid1"]
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
resources = GeoEngineer::IgnorableResources.fetch_remote_resources()
|
117
|
+
expect(resources.length).to eq 1
|
118
|
+
expect(resources[0]._geo_id).to eq "geoid2"
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
102
122
|
describe '#validate_required_subresource' do
|
103
123
|
it 'should return errors if it does not have a tag' do
|
104
124
|
class GeoEngineer::HasSRAttrResource < GeoEngineer::Resource
|
@@ -168,57 +188,100 @@ describe("GeoEngineer::Resource") do
|
|
168
188
|
end
|
169
189
|
|
170
190
|
describe '#validate_tag_merge' do
|
171
|
-
it '
|
172
|
-
|
191
|
+
it 'combines resource and parent tags' do
|
192
|
+
environment = GeoEngineer::Environment.new('test') {
|
193
|
+
tags {
|
194
|
+
a '1'
|
195
|
+
}
|
196
|
+
}
|
197
|
+
project = GeoEngineer::Project.new('org', 'project_name', environment) {
|
198
|
+
tags {
|
199
|
+
b '2'
|
200
|
+
}
|
201
|
+
}
|
202
|
+
resource = project.resource('type', '1') {
|
203
|
+
tags {
|
204
|
+
c '3'
|
205
|
+
}
|
206
|
+
}
|
207
|
+
resource.merge_parent_tags
|
208
|
+
expect(resource.tags.attributes).to eq({ 'a' => '1', 'b' => '2', 'c' => '3' })
|
209
|
+
end
|
210
|
+
|
211
|
+
it 'works if just project is present' do
|
212
|
+
project = GeoEngineer::Project.new('org', 'project_name', nil) {
|
173
213
|
tags {
|
174
214
|
a '1'
|
175
215
|
}
|
176
216
|
}
|
177
217
|
resource = project.resource('type', '1') {
|
178
218
|
tags {
|
179
|
-
|
219
|
+
b '2'
|
180
220
|
}
|
181
221
|
}
|
182
|
-
resource.
|
183
|
-
expect(resource.tags.attributes).to eq({ 'a' => '1', '
|
222
|
+
resource.merge_parent_tags
|
223
|
+
expect(resource.tags.attributes).to eq({ 'a' => '1', 'b' => '2' })
|
184
224
|
end
|
185
225
|
|
186
|
-
it '
|
187
|
-
|
226
|
+
it 'works if just environment is present' do
|
227
|
+
environment = GeoEngineer::Environment.new('test') {
|
188
228
|
tags {
|
189
|
-
a '
|
229
|
+
a '1'
|
230
|
+
}
|
231
|
+
}
|
232
|
+
resource = environment.resource('type', '1') {
|
233
|
+
tags {
|
234
|
+
b '2'
|
235
|
+
}
|
236
|
+
}
|
237
|
+
resource.merge_parent_tags
|
238
|
+
expect(resource.tags.attributes).to eq({ 'a' => '1', 'b' => '2' })
|
239
|
+
end
|
240
|
+
|
241
|
+
it 'uses priority: resource > project > environment' do
|
242
|
+
environment = GeoEngineer::Environment.new('test') {
|
243
|
+
tags {
|
244
|
+
a '1'
|
245
|
+
}
|
246
|
+
}
|
247
|
+
project = GeoEngineer::Project.new('org', 'project_name', environment) {
|
248
|
+
tags {
|
249
|
+
a '2'
|
250
|
+
b '1'
|
190
251
|
}
|
191
252
|
}
|
192
253
|
resource = project.resource('type', '1') {
|
193
254
|
tags {
|
194
|
-
a '
|
255
|
+
a '3'
|
256
|
+
b '2'
|
257
|
+
c '1'
|
195
258
|
}
|
196
259
|
}
|
197
|
-
resource.
|
198
|
-
expect(resource.tags.attributes).to eq({ 'a' => '
|
260
|
+
resource.merge_parent_tags
|
261
|
+
expect(resource.tags.attributes).to eq({ 'a' => '3', 'b' => '2', 'c' => '1' })
|
199
262
|
end
|
200
263
|
|
201
|
-
it '
|
202
|
-
project = GeoEngineer::Project.new('org', 'project_name',
|
264
|
+
it 'returns project tags if there are no resource tags' do
|
265
|
+
project = GeoEngineer::Project.new('org', 'project_name', env) {
|
203
266
|
tags {
|
204
267
|
a '1'
|
205
268
|
b '2'
|
206
269
|
}
|
207
270
|
}
|
208
271
|
resource = project.resource('type', '1') {}
|
209
|
-
resource.
|
272
|
+
resource.merge_parent_tags
|
210
273
|
expect(resource.tags.attributes).to eq({ 'a' => '1', 'b' => '2' })
|
211
274
|
end
|
212
275
|
|
213
|
-
it '
|
214
|
-
project = GeoEngineer::Project.new('org', 'project_name',
|
276
|
+
it 'returns resource tags if there are no project tags' do
|
277
|
+
project = GeoEngineer::Project.new('org', 'project_name', env) {}
|
215
278
|
resource = project.resource('type', '1') {
|
216
279
|
tags {
|
217
280
|
c '3'
|
218
281
|
d '4'
|
219
282
|
}
|
220
283
|
}
|
221
|
-
resource.
|
284
|
+
resource.merge_parent_tags
|
222
285
|
expect(resource.tags.attributes).to eq({ 'c' => '3', 'd' => '4' })
|
223
286
|
end
|
224
287
|
end
|
@@ -261,4 +324,42 @@ describe("GeoEngineer::Resource") do
|
|
261
324
|
end
|
262
325
|
end
|
263
326
|
end
|
327
|
+
|
328
|
+
describe '#_deep_symbolize_keys' do
|
329
|
+
let(:simple_obj) { JSON.parse({ foo: "bar", baz: "qux" }.to_json) }
|
330
|
+
let(:complex_obj) do
|
331
|
+
JSON.parse(
|
332
|
+
{
|
333
|
+
foo: {
|
334
|
+
bar: {
|
335
|
+
baz: [
|
336
|
+
{ qux: "quack" }
|
337
|
+
]
|
338
|
+
}
|
339
|
+
},
|
340
|
+
bar: [
|
341
|
+
{ foo: "bar" },
|
342
|
+
nil,
|
343
|
+
[{ baz: "qux" }],
|
344
|
+
1,
|
345
|
+
"baz"
|
346
|
+
]
|
347
|
+
}.to_json
|
348
|
+
)
|
349
|
+
end
|
350
|
+
|
351
|
+
it "converts top level keys to symbols" do
|
352
|
+
expect(simple_obj.keys.include?(:foo)).to eq(false)
|
353
|
+
expect(simple_obj.keys.include?("foo")).to eq(true)
|
354
|
+
converted = described_class._deep_symbolize_keys(simple_obj)
|
355
|
+
expect(converted.keys.include?(:foo)).to eq(true)
|
356
|
+
expect(converted.keys.include?("foo")).to eq(false)
|
357
|
+
end
|
358
|
+
|
359
|
+
it "converts deeply nested keys to symbols" do
|
360
|
+
converted = described_class._deep_symbolize_keys(complex_obj)
|
361
|
+
expect(converted[:foo][:bar][:baz].first[:qux]).to eq("quack")
|
362
|
+
expect(converted[:bar].first[:foo]).to eq("bar")
|
363
|
+
end
|
364
|
+
end
|
264
365
|
end
|