ddr-models 3.0.0.alpha.4 → 3.0.0.beta.1
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
- data/Gemfile +0 -2
- data/app/models/collection.rb +6 -2
- data/config/initializers/active_fedora_base.rb +3 -4
- data/ddr-models.gemspec +4 -3
- data/lib/ddr/auth/effective_roles.rb +1 -5
- data/lib/ddr/auth/inherited_roles.rb +2 -5
- data/lib/ddr/auth/resource_roles.rb +1 -4
- data/lib/ddr/auth/roles.rb +3 -3
- data/lib/ddr/auth/roles/role.rb +54 -101
- data/lib/ddr/auth/roles/role_attribute.rb +16 -0
- data/lib/ddr/auth/roles/role_set.rb +19 -72
- data/lib/ddr/auth/roles/role_set_manager.rb +68 -0
- data/lib/ddr/auth/roles/role_set_query.rb +10 -22
- data/lib/ddr/auth/roles/role_type.rb +1 -0
- data/lib/ddr/auth/roles/role_validator.rb +11 -0
- data/lib/ddr/models.rb +2 -1
- data/lib/ddr/models/base.rb +78 -17
- data/lib/ddr/models/has_admin_metadata.rb +6 -4
- data/lib/ddr/models/has_content.rb +0 -10
- data/lib/ddr/models/solr_document.rb +6 -2
- data/lib/ddr/models/validatable.rb +20 -0
- data/lib/ddr/models/validator.rb +8 -0
- data/lib/ddr/models/version.rb +1 -1
- data/lib/ddr/vocab/roles.rb +14 -10
- data/spec/auth/effective_permissions_spec.rb +1 -1
- data/spec/auth/effective_roles_spec.rb +5 -5
- data/spec/auth/roles/role_set_manager_spec.rb +86 -0
- data/spec/auth/roles/role_set_query_spec.rb +50 -67
- data/spec/auth/roles/role_set_spec.rb +41 -0
- data/spec/auth/roles/role_spec.rb +45 -42
- data/spec/models/collection_spec.rb +1 -1
- data/spec/models/has_admin_metadata_spec.rb +2 -2
- data/spec/models/indexing_spec.rb +2 -2
- data/spec/models/search_builder_spec.rb +3 -3
- data/spec/models/solr_document_spec.rb +3 -3
- data/spec/support/shared_examples_for_non_collection_models.rb +1 -1
- metadata +33 -18
- data/lib/ddr/auth/roles/detached_role_set.rb +0 -59
- data/lib/ddr/auth/roles/property_role_set.rb +0 -46
- data/lib/ddr/auth/roles/roles_datastream.rb +0 -9
- data/lib/ddr/models/describable.rb +0 -79
- data/spec/auth/roles/detached_role_set_spec.rb +0 -50
- data/spec/auth/roles/property_role_set_spec.rb +0 -32
@@ -26,11 +26,11 @@ module Ddr::Models
|
|
26
26
|
describe "#policy_role_policies" do
|
27
27
|
let(:collections) { FactoryGirl.build_list(:collection, 3) }
|
28
28
|
before do
|
29
|
-
collections[0].roles.grant
|
29
|
+
collections[0].roles.grant role_type: "Editor", agent: "foo", scope: "policy"
|
30
30
|
collections[0].save
|
31
|
-
collections[1].roles.grant
|
31
|
+
collections[1].roles.grant role_type: "Contributor", agent: "bar", scope: "policy"
|
32
32
|
collections[1].save
|
33
|
-
collections[2].roles.grant
|
33
|
+
collections[2].roles.grant role_type: "Viewer", agent: "foo:bar", scope: "policy"
|
34
34
|
collections[2].save
|
35
35
|
end
|
36
36
|
it "returns a list of IDs for collections on which the current ability has a role" do
|
@@ -82,12 +82,12 @@ RSpec.describe SolrDocument, type: :model, contacts: true do
|
|
82
82
|
end
|
83
83
|
|
84
84
|
describe "#roles" do
|
85
|
-
let(:json) { "
|
85
|
+
let(:json) { "{\"roles\":[{\"role_type\":\"Editor\",\"agent\":\"Editors\",\"scope\":\"policy\"},{\"role_type\":\"Contributor\",\"agent\":\"bob@example.com\",\"scope\":\"resource\"}]}" }
|
86
86
|
before { subject[Ddr::Index::Fields::ACCESS_ROLE] = json }
|
87
87
|
it "should deserialize the roles from JSON" do
|
88
88
|
expect(subject.roles.to_a)
|
89
|
-
.to eq([Ddr::Auth::Roles::Role.build(
|
90
|
-
Ddr::Auth::Roles::Role.build(
|
89
|
+
.to eq([Ddr::Auth::Roles::Role.build(role_type: "Editor", agent: "Editors", scope: "policy"),
|
90
|
+
Ddr::Auth::Roles::Role.build(role_type: "Contributor", agent: "bob@example.com", scope: "resource")])
|
91
91
|
end
|
92
92
|
end
|
93
93
|
|
@@ -4,7 +4,7 @@ RSpec.shared_examples "a non-collection model" do
|
|
4
4
|
let(:user) { FactoryGirl.build(:user) }
|
5
5
|
before { subject.grant_roles_to_creator(user) }
|
6
6
|
it "should include the Editor role in resource scope" do
|
7
|
-
expect(subject.roles.to_a).to eq([Ddr::Auth::Roles::Role.build(
|
7
|
+
expect(subject.roles.to_a).to eq([Ddr::Auth::Roles::Role.build(role_type: "Editor", agent: user.agent, scope: "resource")])
|
8
8
|
end
|
9
9
|
end
|
10
10
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ddr-models
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 3.0.0.
|
4
|
+
version: 3.0.0.beta.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jim Coble
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2015-11-
|
12
|
+
date: 2015-11-13 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: rails
|
@@ -45,14 +45,14 @@ dependencies:
|
|
45
45
|
requirements:
|
46
46
|
- - "~>"
|
47
47
|
- !ruby/object:Gem::Version
|
48
|
-
version:
|
48
|
+
version: 9.6.2
|
49
49
|
type: :runtime
|
50
50
|
prerelease: false
|
51
51
|
version_requirements: !ruby/object:Gem::Requirement
|
52
52
|
requirements:
|
53
53
|
- - "~>"
|
54
54
|
- !ruby/object:Gem::Version
|
55
|
-
version:
|
55
|
+
version: 9.6.2
|
56
56
|
- !ruby/object:Gem::Dependency
|
57
57
|
name: hydra-validations
|
58
58
|
requirement: !ruby/object:Gem::Requirement
|
@@ -209,16 +209,30 @@ dependencies:
|
|
209
209
|
name: ddr-antivirus
|
210
210
|
requirement: !ruby/object:Gem::Requirement
|
211
211
|
requirements:
|
212
|
-
- -
|
212
|
+
- - "~>"
|
213
213
|
- !ruby/object:Gem::Version
|
214
|
-
version: 2.
|
214
|
+
version: 2.1.1
|
215
215
|
type: :runtime
|
216
216
|
prerelease: false
|
217
217
|
version_requirements: !ruby/object:Gem::Requirement
|
218
218
|
requirements:
|
219
|
-
- -
|
219
|
+
- - "~>"
|
220
|
+
- !ruby/object:Gem::Version
|
221
|
+
version: 2.1.1
|
222
|
+
- !ruby/object:Gem::Dependency
|
223
|
+
name: virtus
|
224
|
+
requirement: !ruby/object:Gem::Requirement
|
225
|
+
requirements:
|
226
|
+
- - "~>"
|
227
|
+
- !ruby/object:Gem::Version
|
228
|
+
version: 1.0.5
|
229
|
+
type: :runtime
|
230
|
+
prerelease: false
|
231
|
+
version_requirements: !ruby/object:Gem::Requirement
|
232
|
+
requirements:
|
233
|
+
- - "~>"
|
220
234
|
- !ruby/object:Gem::Version
|
221
|
-
version:
|
235
|
+
version: 1.0.5
|
222
236
|
- !ruby/object:Gem::Dependency
|
223
237
|
name: bundler
|
224
238
|
requirement: !ruby/object:Gem::Requirement
|
@@ -337,14 +351,14 @@ dependencies:
|
|
337
351
|
requirements:
|
338
352
|
- - "~>"
|
339
353
|
- !ruby/object:Gem::Version
|
340
|
-
version:
|
354
|
+
version: 5.15.0
|
341
355
|
type: :development
|
342
356
|
prerelease: false
|
343
357
|
version_requirements: !ruby/object:Gem::Requirement
|
344
358
|
requirements:
|
345
359
|
- - "~>"
|
346
360
|
- !ruby/object:Gem::Version
|
347
|
-
version:
|
361
|
+
version: 5.15.0
|
348
362
|
description: Models used in the Duke Digital Repository
|
349
363
|
email:
|
350
364
|
- lib-drs@duke.edu
|
@@ -426,14 +440,14 @@ files:
|
|
426
440
|
- lib/ddr/auth/resource_roles.rb
|
427
441
|
- lib/ddr/auth/role_based_access_controls_enforcement.rb
|
428
442
|
- lib/ddr/auth/roles.rb
|
429
|
-
- lib/ddr/auth/roles/detached_role_set.rb
|
430
|
-
- lib/ddr/auth/roles/property_role_set.rb
|
431
443
|
- lib/ddr/auth/roles/role.rb
|
444
|
+
- lib/ddr/auth/roles/role_attribute.rb
|
432
445
|
- lib/ddr/auth/roles/role_set.rb
|
446
|
+
- lib/ddr/auth/roles/role_set_manager.rb
|
433
447
|
- lib/ddr/auth/roles/role_set_query.rb
|
434
448
|
- lib/ddr/auth/roles/role_type.rb
|
435
449
|
- lib/ddr/auth/roles/role_types.rb
|
436
|
-
- lib/ddr/auth/roles/
|
450
|
+
- lib/ddr/auth/roles/role_validator.rb
|
437
451
|
- lib/ddr/auth/superuser_ability.rb
|
438
452
|
- lib/ddr/auth/test_helpers.rb
|
439
453
|
- lib/ddr/auth/user.rb
|
@@ -497,7 +511,6 @@ files:
|
|
497
511
|
- lib/ddr/models/attached_file_profile.rb
|
498
512
|
- lib/ddr/models/attached_files_profile.rb
|
499
513
|
- lib/ddr/models/base.rb
|
500
|
-
- lib/ddr/models/describable.rb
|
501
514
|
- lib/ddr/models/engine.rb
|
502
515
|
- lib/ddr/models/error.rb
|
503
516
|
- lib/ddr/models/event_loggable.rb
|
@@ -531,6 +544,8 @@ files:
|
|
531
544
|
- lib/ddr/models/struct_div.rb
|
532
545
|
- lib/ddr/models/structure.rb
|
533
546
|
- lib/ddr/models/url_safe_id.rb
|
547
|
+
- lib/ddr/models/validatable.rb
|
548
|
+
- lib/ddr/models/validator.rb
|
534
549
|
- lib/ddr/models/version.rb
|
535
550
|
- lib/ddr/models/year_facet.rb
|
536
551
|
- lib/ddr/notifications.rb
|
@@ -554,9 +569,9 @@ files:
|
|
554
569
|
- spec/auth/group_spec.rb
|
555
570
|
- spec/auth/groups_spec.rb
|
556
571
|
- spec/auth/ldap_gateway_spec.rb
|
557
|
-
- spec/auth/roles/
|
558
|
-
- spec/auth/roles/property_role_set_spec.rb
|
572
|
+
- spec/auth/roles/role_set_manager_spec.rb
|
559
573
|
- spec/auth/roles/role_set_query_spec.rb
|
574
|
+
- spec/auth/roles/role_set_spec.rb
|
560
575
|
- spec/auth/roles/role_spec.rb
|
561
576
|
- spec/auth/roles/role_type_spec.rb
|
562
577
|
- spec/auth/superuser_ability_spec.rb
|
@@ -718,9 +733,9 @@ test_files:
|
|
718
733
|
- spec/auth/group_spec.rb
|
719
734
|
- spec/auth/groups_spec.rb
|
720
735
|
- spec/auth/ldap_gateway_spec.rb
|
721
|
-
- spec/auth/roles/
|
722
|
-
- spec/auth/roles/property_role_set_spec.rb
|
736
|
+
- spec/auth/roles/role_set_manager_spec.rb
|
723
737
|
- spec/auth/roles/role_set_query_spec.rb
|
738
|
+
- spec/auth/roles/role_set_spec.rb
|
724
739
|
- spec/auth/roles/role_spec.rb
|
725
740
|
- spec/auth/roles/role_type_spec.rb
|
726
741
|
- spec/auth/superuser_ability_spec.rb
|
@@ -1,59 +0,0 @@
|
|
1
|
-
require "set"
|
2
|
-
|
3
|
-
module Ddr
|
4
|
-
module Auth
|
5
|
-
module Roles
|
6
|
-
#
|
7
|
-
# Wraps a set of Roles detached from a repository object
|
8
|
-
#
|
9
|
-
class DetachedRoleSet < RoleSet
|
10
|
-
|
11
|
-
delegate :each, to: :role_set
|
12
|
-
|
13
|
-
class << self
|
14
|
-
# Deserialize a serialized RoleSet into a DetachedRoleSet
|
15
|
-
def deserialize(serialized)
|
16
|
-
role_set = serialized.map { |role_data| Role.deserialize(role_data) }
|
17
|
-
new(role_set)
|
18
|
-
end
|
19
|
-
|
20
|
-
# Deserialize a JSON representation of a set of Roles into a DetachedRoleSet
|
21
|
-
def from_json(json)
|
22
|
-
deserialize JSON.parse(json)
|
23
|
-
end
|
24
|
-
end
|
25
|
-
|
26
|
-
attr_writer :role_set
|
27
|
-
|
28
|
-
def initialize(role_set = Set.new)
|
29
|
-
super role_set.to_set
|
30
|
-
end
|
31
|
-
|
32
|
-
def grant(*roles)
|
33
|
-
role_set.merge coerce(roles)
|
34
|
-
end
|
35
|
-
|
36
|
-
def revoke(*roles)
|
37
|
-
self.role_set -= coerce(roles)
|
38
|
-
end
|
39
|
-
|
40
|
-
def revoke_all
|
41
|
-
clear
|
42
|
-
end
|
43
|
-
|
44
|
-
def to_a
|
45
|
-
role_set.to_a
|
46
|
-
end
|
47
|
-
|
48
|
-
# Merges the roles from another role set into the role set
|
49
|
-
# @param other [Enumerable<Role>]
|
50
|
-
# @return [DetachedRoleSet] self
|
51
|
-
def merge(other)
|
52
|
-
role_set.merge other
|
53
|
-
self
|
54
|
-
end
|
55
|
-
|
56
|
-
end
|
57
|
-
end
|
58
|
-
end
|
59
|
-
end
|
@@ -1,46 +0,0 @@
|
|
1
|
-
module Ddr
|
2
|
-
module Auth
|
3
|
-
module Roles
|
4
|
-
#
|
5
|
-
# Wraps a set of Roles implemented as an ActiveTriples::Term.
|
6
|
-
#
|
7
|
-
class PropertyRoleSet < RoleSet
|
8
|
-
|
9
|
-
def grant(*roles)
|
10
|
-
if roles.present?
|
11
|
-
role_set << coerce(roles)
|
12
|
-
end
|
13
|
-
end
|
14
|
-
|
15
|
-
# @note We have to destroy resources on the ActiveTriples::Term
|
16
|
-
# because Term#delete does not support isomorphism.
|
17
|
-
# @see https://github.com/ActiveTriples/ActiveTriples/issues/42
|
18
|
-
def revoke(*roles)
|
19
|
-
coerce(roles).each do |role|
|
20
|
-
if role_index = find_index(role)
|
21
|
-
role_set[role_index].destroy
|
22
|
-
end
|
23
|
-
end
|
24
|
-
end
|
25
|
-
|
26
|
-
# @note We have to destroy resources on the
|
27
|
-
# ActiveTriples::Term because Term#delete does not
|
28
|
-
# support isomorphism.
|
29
|
-
# @see https://github.com/ActiveTriples/ActiveTriples/issues/42
|
30
|
-
def revoke_all
|
31
|
-
each(&:destroy)
|
32
|
-
self
|
33
|
-
end
|
34
|
-
|
35
|
-
def each(&block)
|
36
|
-
role_set.map.each(&block)
|
37
|
-
end
|
38
|
-
|
39
|
-
def to_set
|
40
|
-
map.to_set
|
41
|
-
end
|
42
|
-
|
43
|
-
end
|
44
|
-
end
|
45
|
-
end
|
46
|
-
end
|
@@ -1,79 +0,0 @@
|
|
1
|
-
module Ddr::Models
|
2
|
-
module Describable
|
3
|
-
extend ActiveSupport::Concern
|
4
|
-
|
5
|
-
included do
|
6
|
-
DescriptiveMetadata.mapping.each do |name, term|
|
7
|
-
property name, predicate: term.predicate do |index|
|
8
|
-
index.as :stored_searchable
|
9
|
-
end
|
10
|
-
end
|
11
|
-
end
|
12
|
-
|
13
|
-
def descMetadata
|
14
|
-
@descMetadata ||= DescriptiveMetadata.new(self)
|
15
|
-
end
|
16
|
-
|
17
|
-
def has_desc_metadata?
|
18
|
-
descMetadata.has_content?
|
19
|
-
end
|
20
|
-
|
21
|
-
def desc_metadata_terms *args
|
22
|
-
return DescriptiveMetadata.unqualified_names.sort if args.empty?
|
23
|
-
arg = args.pop
|
24
|
-
terms = case arg.to_sym
|
25
|
-
when :empty
|
26
|
-
desc_metadata_terms.select { |t| desc_metadata_values(t).empty? }
|
27
|
-
when :present
|
28
|
-
desc_metadata_terms.select { |t| desc_metadata_values(t).present? }
|
29
|
-
when :defined_attributes
|
30
|
-
desc_metadata_terms & desc_metadata_attributes
|
31
|
-
when :required
|
32
|
-
desc_metadata_terms(:defined_attributes).select {|t| required? t}
|
33
|
-
when :dcterms
|
34
|
-
MetadataMapping.dc11.unqualified_names +
|
35
|
-
(MetadataMapping.dcterms.unqualified_names - MetadataMapping.dc11.unqualified_names)
|
36
|
-
when :dcterms_elements11
|
37
|
-
Ddr::Vocab::Vocabulary.term_names(RDF::DC11)
|
38
|
-
when :duke
|
39
|
-
Ddr::Vocab::Vocabulary.term_names(Ddr::Vocab::DukeTerms)
|
40
|
-
else
|
41
|
-
raise ArgumentError, "Invalid argument: #{arg.inspect}"
|
42
|
-
end
|
43
|
-
if args.empty?
|
44
|
-
terms
|
45
|
-
else
|
46
|
-
terms | desc_metadata_terms(*args)
|
47
|
-
end
|
48
|
-
end
|
49
|
-
|
50
|
-
def desc_metadata_attributes
|
51
|
-
MetadataMapping.dc11.unqualified_names
|
52
|
-
end
|
53
|
-
|
54
|
-
def desc_metadata_values(term)
|
55
|
-
descMetadata.values term
|
56
|
-
end
|
57
|
-
|
58
|
-
def desc_metadata_vocabs
|
59
|
-
descMetadata.class.vocabularies
|
60
|
-
end
|
61
|
-
|
62
|
-
def set_desc_metadata_values(term, values)
|
63
|
-
descMetadata.set_values term, values
|
64
|
-
end
|
65
|
-
|
66
|
-
# Update all descMetadata terms with values in hash
|
67
|
-
# Note that term not having key in hash will be set to nil!
|
68
|
-
def set_desc_metadata(term_values_hash)
|
69
|
-
desc_metadata_terms.each { |t| set_desc_metadata_values(t, term_values_hash[t]) }
|
70
|
-
end
|
71
|
-
|
72
|
-
module ClassMethods
|
73
|
-
def find_by_identifier(identifier)
|
74
|
-
find(Ddr::Index::Fields::IDENTIFIER_ALL => identifier)
|
75
|
-
end
|
76
|
-
end
|
77
|
-
|
78
|
-
end
|
79
|
-
end
|
@@ -1,50 +0,0 @@
|
|
1
|
-
module Ddr::Auth
|
2
|
-
module Roles
|
3
|
-
RSpec.describe DetachedRoleSet do
|
4
|
-
|
5
|
-
subject { described_class.new(wrapped) }
|
6
|
-
|
7
|
-
let(:wrapped) { Set.new }
|
8
|
-
|
9
|
-
it_behaves_like "a role set"
|
10
|
-
|
11
|
-
describe "deserialization" do
|
12
|
-
let(:role1) { {type: "Editor", agent: "bob@example.com", scope: "resource"} }
|
13
|
-
let(:role2) { {type: "Curator", agent: "sue@example.com", scope: "policy"} }
|
14
|
-
|
15
|
-
before { subject.grant role1, role2 }
|
16
|
-
|
17
|
-
it "should deserialize to an equalivalent role set" do
|
18
|
-
expect(described_class.deserialize(subject.serialize)).to eq(subject)
|
19
|
-
end
|
20
|
-
|
21
|
-
it "should load JSON data" do
|
22
|
-
expect(described_class.from_json("[{\"role_type\":[\"Editor\"],\"agent\":[\"bob@example.com\"],\"scope\":[\"resource\"]},{\"role_type\":[\"Curator\"],\"agent\":[\"sue@example.com\"],\"scope\":[\"policy\"]}]")).to eq(subject)
|
23
|
-
end
|
24
|
-
end
|
25
|
-
|
26
|
-
describe "conversion to a plain Array" do
|
27
|
-
let(:role1) { FactoryGirl.build(:role, :curator, :person, :policy) }
|
28
|
-
let(:role2) { FactoryGirl.build(:role, :editor, :group, :resource) }
|
29
|
-
before { subject.grant role1, role2 }
|
30
|
-
its(:to_a) { should eq([role1, role2]) }
|
31
|
-
end
|
32
|
-
|
33
|
-
describe "equality" do
|
34
|
-
let(:role1) { FactoryGirl.build(:role, :curator, :person, :policy) }
|
35
|
-
let(:role2) { FactoryGirl.build(:role, :editor, :group, :resource) }
|
36
|
-
let(:other) { described_class.new(Array.new) }
|
37
|
-
before do
|
38
|
-
subject.grant role1, role2
|
39
|
-
other.grant role2, role1
|
40
|
-
end
|
41
|
-
it "should be equal to another role set if it has the same roles, regardless of order" do
|
42
|
-
expect(subject).to eq(other)
|
43
|
-
end
|
44
|
-
end
|
45
|
-
|
46
|
-
end
|
47
|
-
|
48
|
-
end
|
49
|
-
end
|
50
|
-
|
@@ -1,32 +0,0 @@
|
|
1
|
-
module Ddr::Auth
|
2
|
-
module Roles
|
3
|
-
RSpec.describe PropertyRoleSet, roles: true do
|
4
|
-
|
5
|
-
subject { described_class.new(wrapped) }
|
6
|
-
|
7
|
-
let(:wrapped_class) do
|
8
|
-
Class.new(ActiveTriples::Resource) do
|
9
|
-
property :role, predicate: Ddr::Vocab::Roles.hasRole
|
10
|
-
end
|
11
|
-
end
|
12
|
-
|
13
|
-
let(:wrapped) { wrapped_class.new.role }
|
14
|
-
|
15
|
-
it_behaves_like "a role set"
|
16
|
-
|
17
|
-
describe "equality" do
|
18
|
-
let(:role1) { FactoryGirl.build(:role, :curator, :person, :policy) }
|
19
|
-
let(:role2) { FactoryGirl.build(:role, :editor, :group, :resource) }
|
20
|
-
let(:other) { described_class.new(wrapped_class.new.role) }
|
21
|
-
before do
|
22
|
-
subject.grant role1, role2
|
23
|
-
other.grant role2, role1
|
24
|
-
end
|
25
|
-
it "should be equal to another role set if it has the same roles, regardless of order" do
|
26
|
-
expect(subject).to eq(other)
|
27
|
-
end
|
28
|
-
end
|
29
|
-
|
30
|
-
end
|
31
|
-
end
|
32
|
-
end
|