yt 0.32.6 → 0.33.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (97) hide show
  1. checksums.yaml +4 -4
  2. data/.rspec +1 -4
  3. data/CHANGELOG.md +19 -0
  4. data/README.md +22 -32
  5. data/YOUTUBE_IT.md +4 -4
  6. data/lib/yt.rb +0 -1
  7. data/lib/yt/associations/has_reports.rb +9 -14
  8. data/lib/yt/collections/reports.rb +5 -7
  9. data/lib/yt/models/resource.rb +69 -3
  10. data/lib/yt/models/url.rb +2 -60
  11. data/lib/yt/request.rb +6 -2
  12. data/lib/yt/version.rb +1 -1
  13. data/yt.gemspec +5 -2
  14. metadata +31 -169
  15. data/spec/collections/claims_spec.rb +0 -62
  16. data/spec/collections/comment_threads_spec.rb +0 -46
  17. data/spec/collections/playlist_items_spec.rb +0 -44
  18. data/spec/collections/playlists_spec.rb +0 -27
  19. data/spec/collections/policies_spec.rb +0 -30
  20. data/spec/collections/references_spec.rb +0 -30
  21. data/spec/collections/reports_spec.rb +0 -30
  22. data/spec/collections/subscriptions_spec.rb +0 -25
  23. data/spec/collections/videos_spec.rb +0 -43
  24. data/spec/constants/geography_spec.rb +0 -16
  25. data/spec/errors/forbidden_spec.rb +0 -10
  26. data/spec/errors/missing_auth_spec.rb +0 -24
  27. data/spec/errors/no_items_spec.rb +0 -10
  28. data/spec/errors/request_error_spec.rb +0 -44
  29. data/spec/errors/server_error_spec.rb +0 -10
  30. data/spec/errors/unauthorized_spec.rb +0 -10
  31. data/spec/models/account_spec.rb +0 -138
  32. data/spec/models/annotation_spec.rb +0 -180
  33. data/spec/models/asset_spec.rb +0 -32
  34. data/spec/models/channel_spec.rb +0 -127
  35. data/spec/models/claim_event_spec.rb +0 -62
  36. data/spec/models/claim_history_spec.rb +0 -27
  37. data/spec/models/claim_spec.rb +0 -223
  38. data/spec/models/comment_spec.rb +0 -40
  39. data/spec/models/comment_thread_spec.rb +0 -93
  40. data/spec/models/configuration_spec.rb +0 -44
  41. data/spec/models/content_detail_spec.rb +0 -52
  42. data/spec/models/content_owner_detail_spec.rb +0 -6
  43. data/spec/models/file_detail_spec.rb +0 -13
  44. data/spec/models/live_streaming_detail_spec.rb +0 -6
  45. data/spec/models/ownership_spec.rb +0 -59
  46. data/spec/models/player_spec.rb +0 -13
  47. data/spec/models/playlist_item_spec.rb +0 -120
  48. data/spec/models/playlist_spec.rb +0 -138
  49. data/spec/models/policy_rule_spec.rb +0 -63
  50. data/spec/models/policy_spec.rb +0 -41
  51. data/spec/models/rating_spec.rb +0 -12
  52. data/spec/models/reference_spec.rb +0 -249
  53. data/spec/models/request_spec.rb +0 -204
  54. data/spec/models/resource_spec.rb +0 -42
  55. data/spec/models/right_owner_spec.rb +0 -71
  56. data/spec/models/snippet_spec.rb +0 -13
  57. data/spec/models/statistics_set_spec.rb +0 -13
  58. data/spec/models/status_spec.rb +0 -13
  59. data/spec/models/subscription_spec.rb +0 -30
  60. data/spec/models/url_spec.rb +0 -78
  61. data/spec/models/video_category_spec.rb +0 -21
  62. data/spec/models/video_spec.rb +0 -669
  63. data/spec/requests/as_account/account_spec.rb +0 -143
  64. data/spec/requests/as_account/authentications_spec.rb +0 -127
  65. data/spec/requests/as_account/channel_spec.rb +0 -246
  66. data/spec/requests/as_account/channels_spec.rb +0 -18
  67. data/spec/requests/as_account/playlist_item_spec.rb +0 -55
  68. data/spec/requests/as_account/playlist_spec.rb +0 -218
  69. data/spec/requests/as_account/thumbnail.jpg +0 -0
  70. data/spec/requests/as_account/video.mp4 +0 -0
  71. data/spec/requests/as_account/video_spec.rb +0 -408
  72. data/spec/requests/as_content_owner/account_spec.rb +0 -29
  73. data/spec/requests/as_content_owner/advertising_options_set_spec.rb +0 -15
  74. data/spec/requests/as_content_owner/asset_spec.rb +0 -31
  75. data/spec/requests/as_content_owner/bulk_report_job_spec.rb +0 -19
  76. data/spec/requests/as_content_owner/channel_spec.rb +0 -1836
  77. data/spec/requests/as_content_owner/claim_history_spec.rb +0 -20
  78. data/spec/requests/as_content_owner/claim_spec.rb +0 -17
  79. data/spec/requests/as_content_owner/content_owner_spec.rb +0 -370
  80. data/spec/requests/as_content_owner/match_policy_spec.rb +0 -17
  81. data/spec/requests/as_content_owner/ownership_spec.rb +0 -25
  82. data/spec/requests/as_content_owner/playlist_spec.rb +0 -767
  83. data/spec/requests/as_content_owner/video_group_spec.rb +0 -112
  84. data/spec/requests/as_content_owner/video_spec.rb +0 -1223
  85. data/spec/requests/as_server_app/channel_spec.rb +0 -54
  86. data/spec/requests/as_server_app/comment_spec.rb +0 -22
  87. data/spec/requests/as_server_app/comment_thread_spec.rb +0 -27
  88. data/spec/requests/as_server_app/comment_threads_spec.rb +0 -41
  89. data/spec/requests/as_server_app/playlist_item_spec.rb +0 -30
  90. data/spec/requests/as_server_app/playlist_spec.rb +0 -33
  91. data/spec/requests/as_server_app/url_spec.rb +0 -94
  92. data/spec/requests/as_server_app/video_spec.rb +0 -60
  93. data/spec/requests/as_server_app/videos_spec.rb +0 -40
  94. data/spec/requests/unauthenticated/video_spec.rb +0 -14
  95. data/spec/spec_helper.rb +0 -20
  96. data/spec/support/fail_matcher.rb +0 -15
  97. data/spec/support/global_hooks.rb +0 -48
@@ -1,20 +0,0 @@
1
- require 'spec_helper'
2
- require 'yt/models/claim'
3
- require 'yt/models/claim_history'
4
-
5
- describe Yt::ClaimHistory, :partner do
6
- subject(:claim) { Yt::Claim.new id: asset_id, auth: $content_owner }
7
-
8
- context 'given a claim previously administered by the authenticated Content Owner' do
9
- let(:asset_id) { ENV['YT_TEST_CLAIM_ID'] }
10
-
11
- describe 'the claim history can be obtained' do
12
- it { expect(claim.claim_history.events.size > 0).to eq true }
13
- end
14
-
15
- describe 'the claim_create event' do
16
- let(:claim_create_event) { claim.claim_history.events.find {|e| e.type == 'claim_create'} }
17
- it { expect(claim_create_event.time).to be_a Time }
18
- end
19
- end
20
- end
@@ -1,17 +0,0 @@
1
- require 'spec_helper'
2
- require 'yt/models/content_owner'
3
-
4
- describe Yt::Claim, :partner do
5
- describe '.ownership' do
6
- let(:claim) { Yt::Claim.new id: claim_id, auth: $content_owner }
7
-
8
- describe 'given a claim administered by the content owner' do
9
- let(:claim_id) { ENV['YT_TEST_PARTNER_CLAIM_ID'] }
10
-
11
- describe 'the claim can be updated' do
12
- let(:attrs) { {block_outside_ownership: true} }
13
- it { expect(claim.update attrs).to be true }
14
- end
15
- end
16
- end
17
- end
@@ -1,370 +0,0 @@
1
- require 'spec_helper'
2
- require 'yt/models/content_owner'
3
- require 'yt/models/match_policy'
4
-
5
- describe Yt::ContentOwner, :partner do
6
- describe '.partnered_channels' do
7
- let(:partnered_channels) { $content_owner.partnered_channels }
8
-
9
- specify '.first' do
10
- expect(partnered_channels.first).to be_a Yt::Channel
11
- end
12
-
13
- specify '.size', :ruby2 do
14
- expect(partnered_channels.size).to be > 0
15
- end
16
- end
17
-
18
- describe '.videos' do
19
- let(:video) { $content_owner.videos.where(order: 'viewCount').first }
20
-
21
- specify 'returns the videos in network with the content owner with their tags and category ID' do
22
- expect(video).to be_a Yt::Video
23
- expect(video.tags).not_to be_empty
24
- expect(video.category_id).not_to be_nil
25
- end
26
-
27
- describe '.includes(:snippet)' do
28
- let(:video) { $content_owner.videos.includes(:snippet).first }
29
-
30
- specify 'eager-loads the *full* snippet of each video' do
31
- expect(video.instance_variable_defined? :@snippet).to be true
32
- expect(video.channel_title).to be
33
- expect(video.snippet).to be_complete
34
- end
35
- end
36
-
37
- describe '.includes(:statistics, :status)' do
38
- let(:video) { $content_owner.videos.includes(:statistics, :status).first }
39
-
40
- specify 'eager-loads the statistics and status of each video' do
41
- expect(video.instance_variable_defined? :@statistics_set).to be true
42
- expect(video.instance_variable_defined? :@status).to be true
43
- end
44
- end
45
-
46
- describe '.includes(:content_details)' do
47
- let(:video) { $content_owner.videos.includes(:content_details).first }
48
-
49
- specify 'eager-loads the statistics of each video' do
50
- expect(video.instance_variable_defined? :@content_detail).to be true
51
- end
52
- end
53
-
54
- describe '.includes(:claim)' do
55
- let(:videos) { $content_owner.videos.includes(:claim) }
56
- let(:video_with_claim) { videos.find{|v| v.claim.present?} }
57
-
58
- specify 'eager-loads the claim of each video and its asset' do
59
- expect(video_with_claim.claim).to be_a Yt::Claim
60
- expect(video_with_claim.claim.id).to be_a String
61
- expect(video_with_claim.claim.video_id).to eq video_with_claim.id
62
- expect(video_with_claim.claim.asset).to be_a Yt::Asset
63
- expect(video_with_claim.claim.asset.id).to be_a String
64
- end
65
- end
66
- end
67
-
68
- describe '.video_groups' do
69
- let(:video_group) { $content_owner.video_groups.first }
70
-
71
- specify 'returns the first video-group created by the account' do
72
- expect(video_group).to be_a Yt::VideoGroup
73
- expect(video_group.title).to be_a String
74
- expect(video_group.item_count).to be_an Integer
75
- expect(video_group.published_at).to be_a Time
76
- end
77
-
78
- specify 'allows to run reports against each video-group' do
79
- expect(video_group.views).to be
80
- end
81
- end
82
-
83
- describe 'claims' do
84
- let(:asset_id) { ENV['YT_TEST_PARTNER_ASSET_ID'] }
85
- let(:video_id) { ENV['YT_TEST_PARTNER_CLAIMABLE_VIDEO_ID'] }
86
- let(:options) { {asset_id: asset_id, video_id: video_id, content_type: 'audiovisual'} }
87
-
88
- context 'given an existing policy ID' do
89
- let(:policy_id) { ENV['YT_TEST_PARTNER_POLICY_ID'] }
90
- let(:params) { options.merge policy_id: policy_id }
91
-
92
- specify 'can be added' do
93
- begin
94
- expect($content_owner.create_claim params).to be_a Yt::Claim
95
- rescue Yt::Errors::RequestError => e
96
- # @note: Every time this test runs, a claim is inserted for the same
97
- # video and asset, but YouTube does not allow this, and responds with
98
- # an error message like "Video is already claimed. Existing claims
99
- # on this video: AbCdEFg1234".
100
- # For the sake of testing, we delete the duplicate and try again.
101
- raise unless e.reasons.include? 'alreadyClaimed'
102
- id = e.kind['message'].match(/this video: (.*?)$/) {|re| re[1]}
103
- Yt::Claim.new(id: id, auth: $content_owner).delete
104
- expect($content_owner.create_claim params).to be_a Yt::Claim
105
- end
106
- end
107
- end
108
-
109
- context 'given a new policy' do
110
- let(:params) { options.merge policy: {rules: [action: :block]} }
111
-
112
- specify 'can be added' do
113
- begin
114
- expect($content_owner.create_claim params).to be_a Yt::Claim
115
- rescue Yt::Errors::RequestError => e
116
- # @note: Every time this test runs, a claim is inserted for the same
117
- # video and asset, but YouTube does not allow this, and responds with
118
- # an error message like "Video is already claimed. Existing claims
119
- # on this video: AbCdEFg1234".
120
- # For the sake of testing, we delete the duplicate and try again.
121
- raise unless e.reasons.include? 'alreadyClaimed'
122
- id = e.kind['message'].match(/this video: (.*?)$/) {|re| re[1]}
123
- Yt::Claim.new(id: id, auth: $content_owner).delete
124
- expect($content_owner.create_claim params).to be_a Yt::Claim
125
- end
126
- end
127
- end
128
- end
129
-
130
- describe '.claims' do
131
- describe 'given the content owner has claims' do
132
- let(:claim) { $content_owner.claims.first }
133
-
134
- it 'returns valid metadata' do
135
- expect(claim.id).to be_a String
136
- expect(claim.asset_id).to be_a String
137
- expect(claim.video_id).to be_a String
138
- expect(claim.status).to be_a String
139
- expect(claim.content_type).to be_a String
140
- expect(claim.created_at).to be_a Time
141
- expect(claim).not_to be_third_party
142
- end
143
- end
144
-
145
- describe '.where(id: claim_id)' do
146
- let(:count) { $content_owner.claims.where(id: claim_id).count }
147
-
148
- context 'given the ID of a claim administered by the content owner' do
149
- let(:claim_id) { ENV['YT_TEST_PARTNER_CLAIM_ID'] }
150
- it { expect(count).to be > 0 }
151
- end
152
-
153
- context 'given an unknown claim ID' do
154
- let(:claim_id) { '--not-a-matching-claim-id--' }
155
- it { expect(count).to be_zero }
156
- end
157
- end
158
-
159
- describe '.where(asset_id: asset_id)' do
160
- let(:count) { $content_owner.claims.where(asset_id: asset_id).count }
161
-
162
- context 'given the asset ID of a claim administered by the content owner' do
163
- let(:asset_id) { ENV['YT_TEST_PARTNER_ASSET_WITH_CLAIM_ID'] }
164
- it { expect(count).to be > 0 }
165
- end
166
-
167
- context 'given an unknown asset ID' do
168
- let(:asset_id) { 'A123456789012345' }
169
- it { expect(count).to be_zero }
170
- end
171
- end
172
-
173
- describe '.where(video_id: video_id)' do
174
- let(:count) { $content_owner.claims.where(video_id: video_id).count }
175
-
176
- context 'given the video ID of a claim administered by the content owner' do
177
- let(:video_id) { ENV['YT_TEST_PARTNER_VIDEO_ID'] }
178
- it { expect(count).to be > 0 }
179
- end
180
- end
181
-
182
- describe '.where(q: query)' do
183
- let(:first) { $content_owner.claims.where(params).first }
184
-
185
- context 'given a query matching the title of a video of a claim administered by the content owner' do
186
- let(:query) { ENV['YT_TEST_PARTNER_Q'] }
187
-
188
- context 'given no optional filters' do
189
- let(:params) { {q: query} }
190
- it { expect(first).to be }
191
- end
192
-
193
- context 'given an optional filter that does not include that video' do
194
- let(:remote_date) { '2008-01-01T00:00:00.000Z' }
195
-
196
- describe 'applies the filter if the filter name is under_scored' do
197
- let(:params) { {q: query, created_before: remote_date} }
198
- it { expect(first).not_to be }
199
- end
200
-
201
- describe 'applies the filter if the filter name is camelCased' do
202
- let(:params) { {q: query, createdBefore: remote_date} }
203
- it { expect(first).not_to be }
204
- end
205
- end
206
- end
207
-
208
- context 'given an unknown video ID' do
209
- let(:params) { {q: '--not-a-matching-query--'} }
210
- it { expect(first).not_to be }
211
- end
212
- end
213
- end
214
-
215
- describe 'references' do
216
- let(:claim_id) { ENV['YT_TEST_PARTNER_REFERENCE_CLAIM_ID'] }
217
- let(:content_type) { ENV['YT_TEST_PARTNER_REFERENCE_CONTENT_TYPE'] }
218
- let(:params) { {claim_id: claim_id, content_type: content_type} }
219
-
220
- specify 'can be added' do
221
- begin
222
- expect($content_owner.create_reference params).to be_a Yt::Reference
223
- rescue Yt::Errors::RequestError => e
224
- # @note: Every time this test runs, a reference is inserted for the
225
- # same claim, but YouTube does not allow this, and responds with an
226
- # error message like "You attempted to create a reference using the
227
- # content of a previously claimed video, but such a reference already
228
- # exists. The ID of the duplicate reference is xhpACYclOdc."
229
- # For the sake of testing, we delete the duplicate and try again.
230
- # @note: Deleting a reference does not work if the reference status is
231
- # "checking" or "pending" and it can take up to 4 minutes for a new
232
- # reference to be checked. The +sleep+ statement takes care of this
233
- # case in the only way allowed by YouTube: sadly waiting.
234
- raise unless e.reasons.include? 'referenceAlreadyExists'
235
- id = e.kind['message'].match(/reference is (.*?)\.$/) {|re| re[1]}
236
- sleep 15 until Yt::Reference.new(id: id, auth: $content_owner).delete
237
- expect($content_owner.create_reference params).to be_a Yt::Reference
238
- end
239
- end
240
- end
241
-
242
- describe '.references' do
243
- describe '.where(id: reference_id)' do
244
- let(:reference) { $content_owner.references.where(id: reference_id).first }
245
-
246
- context 'given the ID of a reference administered by the content owner' do
247
- let(:reference_id) { ENV['YT_TEST_PARTNER_REFERENCE_ID'] }
248
-
249
- it 'returns valid metadata' do
250
- expect(reference.id).to be_a String
251
- expect(reference.asset_id).to be_a String
252
- expect(reference.length).to be_a Float
253
- expect(reference.video_id).to be_a String
254
- expect(reference.claim_id).to be_a String
255
- expect(reference.audioswap_enabled?).to be_in [true, false]
256
- expect(reference.ignore_fp_match?).to be_in [true, false]
257
- expect(reference.urgent?).to be_in [true, false]
258
- expect(reference.status).to be_a String
259
- expect(reference.content_type).to be_a String
260
- end
261
- end
262
-
263
- context 'given an unknown reference ID' do
264
- let(:reference_id) { '--not-a-matching-reference-id--' }
265
- it { expect(reference).not_to be }
266
- end
267
- end
268
-
269
- describe '.upload_reference_file' do
270
- let(:asset) { Yt::Asset.new id: ENV['YT_TEST_PARTNER_ASSET_ID'], auth: $content_owner }
271
- let(:match_policy) { Yt::MatchPolicy.new asset_id: ENV['YT_TEST_PARTNER_ASSET_ID'], auth: $content_owner }
272
-
273
- let(:upload_params) { {asset_id: asset.id, content_type: 'video'} }
274
- let(:reference) { $content_owner.upload_reference_file path_or_url, upload_params }
275
- after { reference.delete }
276
-
277
- before do
278
- asset.ownership.update(assetId: asset.id) && asset.ownership.obtain!
279
- match_policy.update policy_id: ENV['YT_TEST_PARTNER_POLICY_ID']
280
- end
281
-
282
- context 'given the URL of a remote video file' do
283
- let(:path_or_url) { ENV['YT_REMOTE_VIDEO_URL'] }
284
-
285
- it { expect(reference).to be_a Yt::Reference }
286
- end
287
- end
288
- end
289
-
290
- describe '.policies' do
291
- describe 'given the content owner has policies' do
292
- let(:policy) { $content_owner.policies.first }
293
- let(:rule) { policy.rules.first }
294
-
295
- it 'returns valid metadata' do
296
- expect(policy.id).to be_a String
297
- expect(policy.name).to be_a String
298
- expect(policy.updated_at).to be_a Time
299
- expect(rule.action).to be_a String
300
- expect(rule.included_territories).to be_an Array
301
- expect(rule.excluded_territories).to be_an Array
302
- expect(rule.match_duration).to be_an Array
303
- expect(rule.match_percent).to be_an Array
304
- expect(rule.reference_duration).to be_an Array
305
- expect(rule.reference_percent).to be_an Array
306
- end
307
- end
308
-
309
- describe '.where(id: policy_id) given an unknown policy ID' do
310
- let(:policy) { $content_owner.policies.where(id: policy_id).first }
311
- let(:policy_id) { '--not-a-matching-reference-id--' }
312
- it { expect(policy).not_to be }
313
- end
314
- end
315
-
316
- describe '.assets' do
317
- describe 'given the content owner has assets' do
318
- let(:asset) { $content_owner.assets.first }
319
-
320
- it 'returns valid asset' do
321
- expect(asset.id).to be_a String
322
- expect(asset.type).to be_a String
323
- expect(asset.title).to be_a String
324
- expect(asset.custom_id).to be_a String
325
- end
326
- end
327
- end
328
-
329
- # @note: The following test works, but YouTube API endpoint to mark
330
- # an asset as 'invalid' (soft-delete) does not work, and apparently
331
- # there is no way to update the status of a asset.
332
- # Therefore, the test is commented out, otherwise a new asset would
333
- # be created every time the test is run.
334
- # describe 'assets can be added' do
335
- # let(:params) { {type: 'web', metadata_mine: {title: "Test Asset Title"}} }
336
- # before { @asset = $content_owner.create_asset params }
337
- # after { @asset.delete } # This does not seem to work
338
- # it { expect(@asset).to be_a Yt::Asset }
339
- # end
340
-
341
- describe '.bulk_report_jobs' do
342
- describe 'given the content owner has bulk report jobs' do
343
- let(:job) { $content_owner.bulk_report_jobs.first }
344
-
345
- it 'returns valid job' do
346
- expect(job.id).to be_a String
347
- expect(job.report_type_id).to be_a String
348
- end
349
- end
350
- end
351
-
352
- describe '.content_owners' do
353
- describe '.where(id: content_owner_ids)' do
354
- let(:content_owner_a) { $content_owner.content_owners.where(id: content_owner_ids).first }
355
-
356
- context 'given valid content owner names' do
357
- let(:content_owner_ids) { 'a8MUrfnFEzBX3uLQepd5mg,GIfKLveZoYetfSFgvG2VtQ' }
358
-
359
- it 'returns valid content owner' do
360
- expect(content_owner_a.display_name).to be_a String
361
- end
362
- end
363
-
364
- context 'given an unknown content owner ID' do
365
- let(:content_owner_ids) { '--not-a-valid-owner-id--' }
366
- it { expect(content_owner_a).not_to be }
367
- end
368
- end
369
- end
370
- end
@@ -1,17 +0,0 @@
1
- require 'spec_helper'
2
- require 'yt/models/match_policy'
3
-
4
- describe Yt::MatchPolicy, :partner do
5
- subject(:match_policy) { Yt::MatchPolicy.new asset_id: asset_id, auth: $content_owner }
6
-
7
- context 'given an asset managed by the authenticated Content Owner' do
8
- before { Yt::Ownership.new(asset_id: asset_id, auth: $content_owner).obtain! }
9
- let(:asset_id) { ENV['YT_TEST_PARTNER_ASSET_ID'] }
10
-
11
- describe 'the asset match policy can be updated' do
12
- let(:policy_id) { ENV['YT_TEST_PARTNER_POLICY_ID'] }
13
-
14
- it { expect(match_policy.update policy_id: policy_id).to be true }
15
- end
16
- end
17
- end
@@ -1,25 +0,0 @@
1
- require 'spec_helper'
2
- require 'yt/models/ownership'
3
-
4
- describe Yt::Ownership, :partner do
5
- subject(:ownership) { Yt::Ownership.new asset_id: asset_id, auth: $content_owner }
6
-
7
- context 'given an asset managed by the authenticated Content Owner' do
8
- let(:asset_id) { ENV['YT_TEST_PARTNER_ASSET_ID'] }
9
-
10
- describe 'the ownership can be updated' do
11
- let(:general_owner) { {ratio: 100, owner: 'FullScreen', type: 'include', territories: ['US', 'CA']} }
12
- it { expect(ownership.update general: [general_owner]).to be true }
13
- end
14
-
15
- describe 'the complete ownership can be obtained' do
16
- before { ownership.release! }
17
- it { expect(ownership.obtain!).to be true }
18
- end
19
-
20
- describe 'the complete ownership can be released' do
21
- after { ownership.obtain! }
22
- it { expect(ownership.release!).to be true }
23
- end
24
- end
25
- end