hashematics 1.0.0

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.
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ #
4
+ # Copyright (c) 2019-present, Blue Marble Payroll, LLC
5
+ #
6
+ # This source code is licensed under the MIT license found in the
7
+ # LICENSE file in the root directory of this source tree.
8
+ #
9
+
10
+ class Person
11
+ attr_accessor :dob, :first, :id, :last, :smoker
12
+
13
+ def initialize(attrs = {})
14
+ attrs.each do |k, v|
15
+ send("#{k}=", v) if respond_to?(k)
16
+ end
17
+ end
18
+
19
+ def eql?(other)
20
+ to_hash == other.to_hash
21
+ end
22
+
23
+ def ==(other)
24
+ eql?(other)
25
+ end
26
+
27
+ def to_hash
28
+ {
29
+ id: id,
30
+ first: first,
31
+ last: last,
32
+ smoker: smoker,
33
+ dob: dob
34
+ }
35
+ end
36
+ end
@@ -0,0 +1,44 @@
1
+ types:
2
+ person:
3
+ properties:
4
+ :id: 'ID #'
5
+ :first: First
6
+ :last: Last
7
+ :smoker: Smoker
8
+ :dob: DOB
9
+ car:
10
+ properties:
11
+ id: 'Car ID #'
12
+ make: Make
13
+ model: Model
14
+ year: Year
15
+ house:
16
+ :properties:
17
+ id: 'House ID #'
18
+ st1: Street 1
19
+ st2: Street 2
20
+ city: City
21
+ st: St
22
+ zip: Zip
23
+ plate:
24
+ :properties:
25
+ id: 'Plate ID #'
26
+ number: Plate Number
27
+ groups:
28
+ people:
29
+ by: 'ID #'
30
+ type: person
31
+ groups:
32
+ :cars:
33
+ by: 'Car ID #'
34
+ type: car
35
+ groups:
36
+ plates:
37
+ by: 'Plate ID #'
38
+ type: plate
39
+ houses:
40
+ by: 'House ID #'
41
+ type: house
42
+ cars:
43
+ by: 'Car ID #'
44
+ type: car
@@ -0,0 +1,9 @@
1
+ ID #,First,Last,Smoker,DOB,Car ID #,Make,Model,Year,House ID #,Street 1,Street 2,City,St,Zip,Plate ID #,Plate Number
2
+ 1,Matt,Rizzo,false,1902-03-04,2,Jeep,Wrangler,1964,7,555 N. Michigan Ave,Suite 203,Chicago,IL,60075,12,JEEPY123
3
+ 1,Matt,Rizzo,false,1902-03-04,3,Mercedes,Benz,1921,7,555 N. Michigan Ave,Suite 203,Chicago,IL,60075,13,BENZY456
4
+ 1,Matt,Rizzo,false,1902-03-04,2,Jeep,Wrangler,1964,8,2813 N. Drivey Ave,Apt. 399,Roanoke,GA,99998,12,JEEPY123
5
+ 1,Matt,Rizzo,false,1902-03-04,3,Mercedes,Benz,1921,8,2813 N. Drivey Ave,Apt. 399,Roanoke,GA,99998,13,BENZY4565
6
+ 2,Katie,Bunny,true,1919-09-02,4,GMC,Terrainia,1999,9,123 Happy Ln.,Apt. 45,Mayhusee,RI,53210,14,GMC394
7
+ 2,Katie,Bunny,true,1919-09-02,5,Buick,Macartha,1982,9,123 Happy Ln.,Apt. 45,Mayhusee,RI,53210,15,BUICKY93
8
+ 3,Ralphie,Dog,true,1999-12-11,6,Audi,A1,2012,10,4500 Century Ave.,Floor 3A,Pakato,ZA,093821,,
9
+ 3,Ralphie,Dog,true,1999-12-11,6,Audi,A1,2012,11,9990 Lake St.,,Pakato,ZA,093822,,
@@ -0,0 +1,84 @@
1
+ - :id: '1'
2
+ :first: Matt
3
+ :last: Rizzo
4
+ :smoker: 'false'
5
+ :dob: '1902-03-04'
6
+ :cars:
7
+ - id: '2'
8
+ make: Jeep
9
+ model: Wrangler
10
+ year: '1964'
11
+ plates:
12
+ - id: '12'
13
+ number: JEEPY123
14
+ - id: '3'
15
+ make: Mercedes
16
+ model: Benz
17
+ year: '1921'
18
+ plates:
19
+ - id: '13'
20
+ number: BENZY4565
21
+ houses:
22
+ - id: '7'
23
+ st1: 555 N. Michigan Ave
24
+ st2: Suite 203
25
+ city: Chicago
26
+ st: IL
27
+ zip: '60075'
28
+ - id: '8'
29
+ st1: 2813 N. Drivey Ave
30
+ st2: Apt. 399
31
+ city: Roanoke
32
+ st: GA
33
+ zip: '99998'
34
+ - :id: '2'
35
+ :first: Katie
36
+ :last: Bunny
37
+ :smoker: 'true'
38
+ :dob: '1919-09-02'
39
+ :cars:
40
+ - id: '4'
41
+ make: GMC
42
+ model: Terrainia
43
+ year: '1999'
44
+ plates:
45
+ - id: '14'
46
+ number: GMC394
47
+ - id: '5'
48
+ make: Buick
49
+ model: Macartha
50
+ year: '1982'
51
+ plates:
52
+ - id: '15'
53
+ number: BUICKY93
54
+ houses:
55
+ - id: '9'
56
+ st1: 123 Happy Ln.
57
+ st2: Apt. 45
58
+ city: Mayhusee
59
+ st: RI
60
+ zip: '53210'
61
+ - :id: '3'
62
+ :first: Ralphie
63
+ :last: Dog
64
+ :smoker: 'true'
65
+ :dob: '1999-12-11'
66
+ :cars:
67
+ - id: '6'
68
+ make: Audi
69
+ model: A1
70
+ year: '2012'
71
+ plates: []
72
+ houses:
73
+ - id: '10'
74
+ st1: 4500 Century Ave.
75
+ st2: Floor 3A
76
+ city: Pakato
77
+ st: ZA
78
+ zip: '093821'
79
+ - id: '11'
80
+ st1: 9990 Lake St.
81
+ st2: null
82
+ city: Pakato
83
+ st: ZA
84
+ zip: '093822'
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+
3
+ #
4
+ # Copyright (c) 2019-present, Blue Marble Payroll, LLC
5
+ #
6
+ # This source code is licensed under the MIT license found in the
7
+ # LICENSE file in the root directory of this source tree.
8
+ #
9
+
10
+ require 'spec_helper'
11
+
12
+ describe ::Hashematics::Category do
13
+ let(:csv_rows) { csv_fixture('data.csv') }
14
+
15
+ it 'should require an id_key' do
16
+ expect { described_class.new(id_key: nil) }.to raise_error ArgumentError
17
+ end
18
+
19
+ describe '#add' do
20
+ context 'with id key but no parent key' do
21
+ specify '#records should return last unique rows as records' do
22
+ category = described_class.new(id_key: 'ID #')
23
+
24
+ csv_rows.each do |csv_row|
25
+ record = ::Hashematics::Record.new(csv_row)
26
+ category.add(record)
27
+ end
28
+
29
+ expected_records = [
30
+ ::Hashematics::Record.new(csv_rows[3]),
31
+ ::Hashematics::Record.new(csv_rows[5]),
32
+ ::Hashematics::Record.new(csv_rows[7])
33
+ ]
34
+
35
+ expect(category.records).to eq(expected_records)
36
+ end
37
+ end
38
+
39
+ context 'with parent and id keys' do
40
+ specify '#records should return unique rows as records specific to a parent' do
41
+ category = described_class.new(
42
+ parent_key: 'ID #',
43
+ id_key: 'Car ID #'
44
+ )
45
+
46
+ csv_rows.each do |csv_row|
47
+ record = ::Hashematics::Record.new(csv_row)
48
+ category.add(record)
49
+ end
50
+
51
+ expected_records = [
52
+ ::Hashematics::Record.new(csv_rows[2]),
53
+ ::Hashematics::Record.new(csv_rows[3])
54
+ ]
55
+
56
+ parent = ::Hashematics::Record.new(csv_rows.first)
57
+
58
+ expect(category.records(parent)).to eq(expected_records)
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,572 @@
1
+ # frozen_string_literal: true
2
+
3
+ #
4
+ # Copyright (c) 2019-present, Blue Marble Payroll, LLC
5
+ #
6
+ # This source code is licensed under the MIT license found in the
7
+ # LICENSE file in the root directory of this source tree.
8
+ #
9
+
10
+ require 'spec_helper'
11
+
12
+ describe ::Hashematics::Graph do
13
+ let(:csv_rows) { csv_fixture('data.csv') }
14
+
15
+ let(:configuration) { yaml_fixture('config.yml') }
16
+
17
+ let(:people) { yaml_fixture('people.yml') }
18
+
19
+ let(:groups) { ::Hashematics::Configuration.new(configuration).groups }
20
+
21
+ describe '#children' do
22
+ it 'returns list of child group names' do
23
+ graph = described_class.new(groups).add(csv_rows)
24
+
25
+ actual_children = graph.children
26
+
27
+ expected_children = groups.map(&:name)
28
+
29
+ expect(actual_children).to eq(expected_children)
30
+ end
31
+ end
32
+
33
+ describe '#data' do
34
+ context 'with no object_class specifications' do
35
+ it 'should parse configuration and return object graph' do
36
+ graph = described_class.new(groups).add(csv_rows)
37
+
38
+ actual_people = graph.data('people')
39
+
40
+ # binding.pry
41
+
42
+ expect(actual_people).to eq(people)
43
+ end
44
+ end
45
+
46
+ context 'with object_class open_struct specifications' do
47
+ let(:modified_configuration) do
48
+ yaml_fixture('config.yml').tap do |c|
49
+ c.dig('types', 'person')['object_class'] = 'open_struct'
50
+ end
51
+ end
52
+
53
+ let(:modified_groups) do
54
+ ::Hashematics::Configuration.new(modified_configuration).groups
55
+ end
56
+
57
+ let(:modified_people) do
58
+ yaml_fixture('people.yml').map { |h| OpenStruct.new(h) }
59
+ end
60
+
61
+ it 'should parse configuration and return object graph' do
62
+ graph = described_class.new(modified_groups).add(csv_rows)
63
+
64
+ actual_people = graph.data(:people)
65
+
66
+ expect(actual_people).to eq(modified_people)
67
+ end
68
+ end
69
+ end
70
+
71
+ describe 'README examples' do
72
+ specify 'Getting Started should work' do
73
+ rows = [
74
+ {
75
+ id: 1,
76
+ first: 'Bruce',
77
+ last: 'Banner'
78
+ },
79
+ {
80
+ id: 2,
81
+ first: 'Tony',
82
+ last: 'Stark'
83
+ }
84
+ ]
85
+
86
+ graph = ::Hashematics.graph(rows: rows)
87
+ objects = graph.rows
88
+
89
+ expect(objects).to eq(rows)
90
+ end
91
+
92
+ specify 'Introduction to Shaping should work' do
93
+ config = {
94
+ types: {
95
+ person: {
96
+ properties: %i[id first]
97
+ }
98
+ },
99
+ groups: {
100
+ avengers: {
101
+ by: :id,
102
+ type: :person
103
+ }
104
+ }
105
+ }
106
+
107
+ rows = [
108
+ {
109
+ id: 1,
110
+ first: 'Bruce',
111
+ last: 'Banner'
112
+ },
113
+ {
114
+ id: 2,
115
+ first: 'Tony',
116
+ last: 'Stark'
117
+ }
118
+ ]
119
+
120
+ graph = ::Hashematics.graph(config: config, rows: rows)
121
+ objects = graph.data(:avengers)
122
+
123
+ expected = [
124
+ {
125
+ id: 1,
126
+ first: 'Bruce'
127
+ },
128
+ {
129
+ id: 2,
130
+ first: 'Tony'
131
+ }
132
+ ]
133
+
134
+ expect(objects).to eq(expected)
135
+ end
136
+
137
+ specify 'Cross-Mapping Shape Attribute Names should work' do
138
+ config = {
139
+ types: {
140
+ person: {
141
+ properties: {
142
+ id_number: :id,
143
+ first_name: :first
144
+ }
145
+ }
146
+ },
147
+ groups: {
148
+ avengers: {
149
+ by: :id,
150
+ type: :person
151
+ }
152
+ }
153
+ }
154
+
155
+ rows = [
156
+ {
157
+ id: 1,
158
+ first: 'Bruce',
159
+ last: 'Banner'
160
+ },
161
+ {
162
+ id: 2,
163
+ first: 'Tony',
164
+ last: 'Stark'
165
+ }
166
+ ]
167
+
168
+ graph = ::Hashematics.graph(config: config, rows: rows)
169
+ objects = graph.data(:avengers)
170
+
171
+ expected = [
172
+ {
173
+ id_number: 1,
174
+ first_name: 'Bruce'
175
+ },
176
+ {
177
+ id_number: 2,
178
+ first_name: 'Tony'
179
+ }
180
+ ]
181
+
182
+ expect(objects).to eq(expected)
183
+ end
184
+
185
+ specify 'Nested Shaping should work' do
186
+ config = {
187
+ types: {
188
+ person: {
189
+ properties: {
190
+ id: 'ID #',
191
+ first: 'First Name',
192
+ last: 'Last Name'
193
+ }
194
+ },
195
+ costume: {
196
+ properties: {
197
+ id: 'Costume ID #',
198
+ name: 'Costume Name',
199
+ color: 'Costume Color'
200
+ }
201
+ }
202
+ },
203
+ groups: {
204
+ avengers: {
205
+ by: 'ID #',
206
+ type: :person,
207
+ groups: {
208
+ costumes: {
209
+ by: 'Costume ID #',
210
+ type: :costume
211
+ }
212
+ }
213
+ }
214
+ }
215
+ }
216
+
217
+ rows = [
218
+ {
219
+ 'ID #' => 1,
220
+ 'First Name' => 'Bruce',
221
+ 'Last Name' => 'Banner',
222
+ 'Costume ID #' => 3,
223
+ 'Costume Name' => 'Basic Hulk',
224
+ 'Costume Color' => 'Green'
225
+ },
226
+ {
227
+ 'ID #' => 1,
228
+ 'First Name' => 'Bruce',
229
+ 'Last Name' => 'Banner',
230
+ 'Costume ID #' => 4,
231
+ 'Costume Name' => 'Mad Hulk',
232
+ 'Costume Color' => 'Red'
233
+ },
234
+ {
235
+ 'ID #' => 2,
236
+ 'First Name' => 'Tony',
237
+ 'Last Name' => 'Stark',
238
+ 'Costume ID #' => 5,
239
+ 'Costume Name' => 'Mark I',
240
+ 'Costume Color' => 'Gray'
241
+ },
242
+ {
243
+ 'ID #' => 2,
244
+ 'First Name' => 'Tony',
245
+ 'Last Name' => 'Stark',
246
+ 'Costume ID #' => 6,
247
+ 'Costume Name' => 'Mark IV',
248
+ 'Costume Color' => 'Red'
249
+ },
250
+ {
251
+ 'ID #' => 2,
252
+ 'First Name' => 'Tony',
253
+ 'Last Name' => 'Stark',
254
+ 'Costume ID #' => 7,
255
+ 'Costume Name' => 'Mark VI',
256
+ 'Costume Color' => 'Nano-Blue'
257
+ }
258
+ ]
259
+
260
+ graph = ::Hashematics.graph(config: config, rows: rows)
261
+ objects = graph.data(:avengers)
262
+
263
+ expected = [
264
+ {
265
+ id: 1,
266
+ first: 'Bruce',
267
+ last: 'Banner',
268
+ costumes: [
269
+ { id: 3, name: 'Basic Hulk', color: 'Green' },
270
+ { id: 4, name: 'Mad Hulk', color: 'Red' }
271
+ ]
272
+ },
273
+ {
274
+ id: 2,
275
+ first: 'Tony',
276
+ last: 'Stark',
277
+ costumes: [
278
+ { id: 5, name: 'Mark I', color: 'Gray' },
279
+ { id: 6, name: 'Mark IV', color: 'Red' },
280
+ { id: 7, name: 'Mark VI', color: 'Nano-Blue' }
281
+ ]
282
+ }
283
+ ]
284
+
285
+ expect(objects).to eq(expected)
286
+ end
287
+
288
+ specify 'Multiple Top-Level Graphs should work' do
289
+ config = {
290
+ types: {
291
+ person: {
292
+ properties: {
293
+ id: 'ID #',
294
+ first: 'First Name',
295
+ last: 'Last Name'
296
+ }
297
+ },
298
+ costume: {
299
+ properties: {
300
+ id: 'Costume ID #',
301
+ name: 'Costume Name',
302
+ color: 'Costume Color'
303
+ }
304
+ }
305
+ },
306
+ groups: {
307
+ avengers: {
308
+ by: 'ID #',
309
+ type: :person,
310
+ groups: {
311
+ costumes: {
312
+ by: 'Costume ID #',
313
+ type: :costume
314
+ }
315
+ }
316
+ },
317
+ costumes: {
318
+ by: 'Costume ID #',
319
+ type: :costume
320
+ }
321
+ }
322
+ }
323
+
324
+ rows = [
325
+ {
326
+ 'ID #' => 1,
327
+ 'First Name' => 'Bruce',
328
+ 'Last Name' => 'Banner',
329
+ 'Costume ID #' => 3,
330
+ 'Costume Name' => 'Basic Hulk',
331
+ 'Costume Color' => 'Green'
332
+ },
333
+ {
334
+ 'ID #' => 1,
335
+ 'First Name' => 'Bruce',
336
+ 'Last Name' => 'Banner',
337
+ 'Costume ID #' => 4,
338
+ 'Costume Name' => 'Mad Hulk',
339
+ 'Costume Color' => 'Red'
340
+ },
341
+ {
342
+ 'ID #' => 2,
343
+ 'First Name' => 'Tony',
344
+ 'Last Name' => 'Stark',
345
+ 'Costume ID #' => 5,
346
+ 'Costume Name' => 'Mark I',
347
+ 'Costume Color' => 'Gray'
348
+ },
349
+ {
350
+ 'ID #' => 2,
351
+ 'First Name' => 'Tony',
352
+ 'Last Name' => 'Stark',
353
+ 'Costume ID #' => 6,
354
+ 'Costume Name' => 'Mark IV',
355
+ 'Costume Color' => 'Red'
356
+ },
357
+ {
358
+ 'ID #' => 2,
359
+ 'First Name' => 'Tony',
360
+ 'Last Name' => 'Stark',
361
+ 'Costume ID #' => 7,
362
+ 'Costume Name' => 'Mark VI',
363
+ 'Costume Color' => 'Nano-Blue'
364
+ }
365
+ ]
366
+
367
+ graph = ::Hashematics.graph(config: config, rows: rows)
368
+ objects = graph.data(:costumes)
369
+
370
+ expected = [
371
+ { id: 3, name: 'Basic Hulk', color: 'Green' },
372
+ { id: 4, name: 'Mad Hulk', color: 'Red' },
373
+ { id: 5, name: 'Mark I', color: 'Gray' },
374
+ { id: 6, name: 'Mark IV', color: 'Red' },
375
+ { id: 7, name: 'Mark VI', color: 'Nano-Blue' }
376
+ ]
377
+
378
+ expect(objects).to eq(expected)
379
+ end
380
+
381
+ specify 'Handling Blanks (skip - default) should work' do
382
+ config = {
383
+ types: {
384
+ person: {
385
+ properties: {
386
+ id: 'ID #',
387
+ first: 'First Name',
388
+ last: 'Last Name'
389
+ }
390
+ },
391
+ costume: {
392
+ properties: {
393
+ id: 'Costume ID #',
394
+ name: 'Costume Name',
395
+ color: 'Costume Color'
396
+ }
397
+ }
398
+ },
399
+ groups: {
400
+ avengers: {
401
+ by: 'ID #',
402
+ type: :person,
403
+ groups: {
404
+ costumes: {
405
+ by: 'Costume ID #',
406
+ type: :costume
407
+ }
408
+ }
409
+ },
410
+ costumes: {
411
+ by: 'Costume ID #',
412
+ type: :costume
413
+ }
414
+ }
415
+ }
416
+
417
+ rows = [
418
+ {
419
+ 'ID #' => 1,
420
+ 'First Name' => 'Bruce',
421
+ 'Last Name' => 'Banner',
422
+ 'Costume ID #' => 3,
423
+ 'Costume Name' => 'Basic Hulk',
424
+ 'Costume Color' => 'Green'
425
+ },
426
+ {
427
+ 'ID #' => 2,
428
+ 'First Name' => 'Tony',
429
+ 'Last Name' => 'Stark',
430
+ 'Costume ID #' => '',
431
+ 'Costume Name' => '',
432
+ 'Costume Color' => ''
433
+ },
434
+ {
435
+ 'Costume ID #' => 4,
436
+ 'Costume Name' => 'Undercover',
437
+ 'Costume Color' => 'Purple'
438
+ }
439
+ ]
440
+
441
+ graph = ::Hashematics.graph(config: config, rows: rows)
442
+ avengers = graph.data(:avengers)
443
+ costumes = graph.data(:costumes)
444
+
445
+ expected_avengers = [
446
+ {
447
+ id: 1,
448
+ first: 'Bruce',
449
+ last: 'Banner',
450
+ costumes: [
451
+ { id: 3, name: 'Basic Hulk', color: 'Green' }
452
+ ]
453
+ },
454
+ {
455
+ id: 2,
456
+ first: 'Tony',
457
+ last: 'Stark',
458
+ costumes: []
459
+ }
460
+ ]
461
+
462
+ expected_costumes = [
463
+ { id: 3, name: 'Basic Hulk', color: 'Green' },
464
+ { id: 4, name: 'Undercover', color: 'Purple' }
465
+ ]
466
+
467
+ expect(avengers).to eq(expected_avengers)
468
+ expect(costumes).to eq(expected_costumes)
469
+ end
470
+
471
+ specify 'Handling Blanks (include) should work' do
472
+ config = {
473
+ types: {
474
+ person: {
475
+ properties: {
476
+ id: 'ID #',
477
+ first: 'First Name',
478
+ last: 'Last Name'
479
+ }
480
+ },
481
+ costume: {
482
+ properties: {
483
+ id: 'Costume ID #',
484
+ name: 'Costume Name',
485
+ color: 'Costume Color'
486
+ }
487
+ }
488
+ },
489
+ groups: {
490
+ avengers: {
491
+ by: 'ID #',
492
+ include_blank: true,
493
+ type: :person,
494
+ groups: {
495
+ costumes: {
496
+ by: 'Costume ID #',
497
+ type: :costume
498
+ }
499
+ }
500
+ },
501
+ costumes: {
502
+ by: 'Costume ID #',
503
+ include_blank: true,
504
+ type: :costume
505
+ }
506
+ }
507
+ }
508
+
509
+ rows = [
510
+ {
511
+ 'ID #' => 1,
512
+ 'First Name' => 'Bruce',
513
+ 'Last Name' => 'Banner',
514
+ 'Costume ID #' => 3,
515
+ 'Costume Name' => 'Basic Hulk',
516
+ 'Costume Color' => 'Green'
517
+ },
518
+ {
519
+ 'ID #' => 2,
520
+ 'First Name' => 'Tony',
521
+ 'Last Name' => 'Stark',
522
+ 'Costume ID #' => '',
523
+ 'Costume Name' => '',
524
+ 'Costume Color' => ''
525
+ },
526
+ {
527
+ 'Costume ID #' => 4,
528
+ 'Costume Name' => 'Undercover',
529
+ 'Costume Color' => 'Purple'
530
+ }
531
+ ]
532
+
533
+ graph = ::Hashematics.graph(config: config, rows: rows)
534
+ avengers = graph.data(:avengers)
535
+ costumes = graph.data(:costumes)
536
+
537
+ expected_avengers = [
538
+ {
539
+ id: 1,
540
+ first: 'Bruce',
541
+ last: 'Banner',
542
+ costumes: [
543
+ { id: 3, name: 'Basic Hulk', color: 'Green' }
544
+ ]
545
+ },
546
+ {
547
+ id: 2,
548
+ first: 'Tony',
549
+ last: 'Stark',
550
+ costumes: []
551
+ },
552
+ {
553
+ id: nil,
554
+ first: nil,
555
+ last: nil,
556
+ costumes: [
557
+ { id: 4, name: 'Undercover', color: 'Purple' }
558
+ ]
559
+ }
560
+ ]
561
+
562
+ expected_costumes = [
563
+ { id: 3, name: 'Basic Hulk', color: 'Green' },
564
+ { id: '', name: '', color: '' },
565
+ { id: 4, name: 'Undercover', color: 'Purple' }
566
+ ]
567
+
568
+ expect(avengers).to eq(expected_avengers)
569
+ expect(costumes).to eq(expected_costumes)
570
+ end
571
+ end
572
+ end