yt 0.32.2 → 0.32.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
- data/CHANGELOG.md +6 -0
- data/README.md +1 -1
- data/lib/yt.rb +1 -1
- data/lib/yt/collections/reports.rb +2 -1
- data/lib/yt/collections/subscriptions.rb +1 -1
- data/lib/yt/models/account.rb +1 -1
- data/lib/yt/models/file_detail.rb +1 -0
- data/lib/yt/models/url.rb +99 -0
- data/lib/yt/models/video.rb +4 -0
- data/lib/yt/request.rb +4 -4
- data/lib/yt/version.rb +1 -1
- data/spec/models/url_spec.rb +78 -0
- data/spec/requests/as_account/account_spec.rb +13 -2
- data/spec/requests/as_account/channel_spec.rb +13 -10
- data/spec/requests/as_account/playlist_item_spec.rb +2 -3
- data/spec/requests/as_account/playlist_spec.rb +4 -4
- data/spec/requests/as_account/video_spec.rb +12 -2029
- data/spec/requests/as_server_app/url_spec.rb +94 -0
- metadata +8 -3
|
@@ -176,7 +176,7 @@ describe Yt::Video, :device_app do
|
|
|
176
176
|
|
|
177
177
|
context 'given I update the category ID' do
|
|
178
178
|
let!(:old_category_id) { video.category_id }
|
|
179
|
-
let!(:new_category_id) { old_category_id == '22' ? '
|
|
179
|
+
let!(:new_category_id) { old_category_id == '22' ? '23' : '22' }
|
|
180
180
|
|
|
181
181
|
context 'passing the parameter in underscore syntax' do
|
|
182
182
|
let(:attrs) { {category_id: new_category_id} }
|
|
@@ -242,16 +242,10 @@ describe Yt::Video, :device_app do
|
|
|
242
242
|
|
|
243
243
|
let(:attrs) { {embeddable: new_embeddable} }
|
|
244
244
|
|
|
245
|
-
# @note: This test is a reflection of another irrational behavior of
|
|
246
|
-
# YouTube API. Although 'embeddable' can be passed as an 'update'
|
|
247
|
-
# attribute according to the documentation, it simply does not work.
|
|
248
|
-
# The day YouTube fixes it, then this test will finally fail and will
|
|
249
|
-
# be removed, documenting how to update 'embeddable' too.
|
|
250
245
|
# @see https://developers.google.com/youtube/v3/docs/videos/update
|
|
251
|
-
|
|
252
|
-
specify 'does not update the embeddable status' do
|
|
246
|
+
specify 'does update the embeddable status' do
|
|
253
247
|
expect(update).to be true
|
|
254
|
-
expect(video.embeddable?).to eq
|
|
248
|
+
expect(video.embeddable?).to eq new_embeddable
|
|
255
249
|
end
|
|
256
250
|
end
|
|
257
251
|
|
|
@@ -358,2018 +352,7 @@ describe Yt::Video, :device_app do
|
|
|
358
352
|
|
|
359
353
|
specify 'only updates the timestamp to publish the video' do
|
|
360
354
|
expect(video.update attrs).to be true
|
|
361
|
-
expect(video.scheduled_at).to eq new_scheduled_at
|
|
362
|
-
expect(video.privacy_status).to eq old_privacy_status
|
|
363
|
-
expect(video.title).to eq old_title
|
|
364
|
-
end
|
|
365
|
-
end
|
|
366
|
-
end
|
|
367
|
-
|
|
368
|
-
# @note: This should somehow test that the thumbnail *changes*. However,
|
|
369
|
-
# YouTube does not change the URL of the thumbnail even though the content
|
|
370
|
-
# changes. A full test would have to *download* the thumbnails before and
|
|
371
|
-
# after, and compare the files. For now, not raising error is enough.
|
|
372
|
-
# Eventually, change to `expect{update}.to change{video.thumbnail_url}`
|
|
373
|
-
context 'given one of my own videos for which I want to upload a thumbnail' do
|
|
374
|
-
let(:id) { $account.videos.where(order: 'viewCount').first.id }
|
|
375
|
-
let(:update) { video.upload_thumbnail path_or_url }
|
|
376
|
-
|
|
377
|
-
context 'given the path to a local JPG image file' do
|
|
378
|
-
let(:path_or_url) { File.expand_path '../thumbnail.jpg', __FILE__ }
|
|
379
|
-
|
|
380
|
-
it { expect{update}.not_to raise_error }
|
|
381
|
-
end
|
|
382
|
-
|
|
383
|
-
context 'given the path to a remote PNG image file' do
|
|
384
|
-
let(:path_or_url) { 'https://bit.ly/yt_thumbnail' }
|
|
385
|
-
|
|
386
|
-
it { expect{update}.not_to raise_error }
|
|
387
|
-
end
|
|
388
|
-
|
|
389
|
-
context 'given an invalid URL' do
|
|
390
|
-
let(:path_or_url) { 'this-is-not-a-url' }
|
|
391
|
-
|
|
392
|
-
it { expect{update}.to raise_error Yt::Errors::RequestError }
|
|
393
|
-
end
|
|
394
|
-
end
|
|
395
|
-
|
|
396
|
-
# @note: This test is separated from the block above because YouTube only
|
|
397
|
-
# returns file details for *some videos*: "The fileDetails object will
|
|
398
|
-
# only be returned if the processingDetails.fileAvailability property
|
|
399
|
-
# has a value of available.". Therefore, just to test fileDetails, we use a
|
|
400
|
-
# different video that (for some unknown reason) is marked as 'available'.
|
|
401
|
-
# Also note that I was not able to find a single video returning fileName,
|
|
402
|
-
# therefore video.file_name is not returned by Yt, until it can be tested.
|
|
403
|
-
# @see https://developers.google.com/youtube/v3/docs/videos#processingDetails.fileDetailsAvailability
|
|
404
|
-
context 'given one of my own *available* videos' do
|
|
405
|
-
let(:id) { 'yCmaOvUFhlI' }
|
|
406
|
-
|
|
407
|
-
it 'returns valid file details' do
|
|
408
|
-
expect(video.file_size).to be_an Integer
|
|
409
|
-
expect(video.file_type).to be_a String
|
|
410
|
-
expect(video.container).to be_a String
|
|
411
|
-
end
|
|
412
|
-
end
|
|
413
|
-
end
|
|
414
|
-
# encoding: UTF-8
|
|
415
|
-
|
|
416
|
-
require 'spec_helper'
|
|
417
|
-
require 'yt/models/video'
|
|
418
|
-
|
|
419
|
-
describe Yt::Video, :device_app do
|
|
420
|
-
subject(:video) { Yt::Video.new id: id, auth: $account }
|
|
421
|
-
|
|
422
|
-
context 'given someone else’s video' do
|
|
423
|
-
let(:id) { '9bZkp7q19f0' }
|
|
424
|
-
|
|
425
|
-
it { expect(video.content_detail).to be_a Yt::ContentDetail }
|
|
426
|
-
|
|
427
|
-
it 'returns valid metadata' do
|
|
428
|
-
expect(video.title).to be_a String
|
|
429
|
-
expect(video.description).to be_a String
|
|
430
|
-
expect(video.thumbnail_url).to be_a String
|
|
431
|
-
expect(video.published_at).to be_a Time
|
|
432
|
-
expect(video.privacy_status).to be_a String
|
|
433
|
-
expect(video.tags).to be_an Array
|
|
434
|
-
expect(video.channel_id).to be_a String
|
|
435
|
-
expect(video.channel_title).to be_a String
|
|
436
|
-
expect(video.channel_url).to be_a String
|
|
437
|
-
expect(video.category_id).to be_a String
|
|
438
|
-
expect(video.live_broadcast_content).to be_a String
|
|
439
|
-
expect(video.view_count).to be_an Integer
|
|
440
|
-
expect(video.like_count).to be_an Integer
|
|
441
|
-
expect(video.dislike_count).to be_an Integer
|
|
442
|
-
expect(video.favorite_count).to be_an Integer
|
|
443
|
-
expect(video.comment_count).to be_an Integer
|
|
444
|
-
expect(video.duration).to be_an Integer
|
|
445
|
-
expect(video.hd?).to be_in [true, false]
|
|
446
|
-
expect(video.stereoscopic?).to be_in [true, false]
|
|
447
|
-
expect(video.captioned?).to be_in [true, false]
|
|
448
|
-
expect(video.licensed?).to be_in [true, false]
|
|
449
|
-
expect(video.deleted?).to be_in [true, false]
|
|
450
|
-
expect(video.failed?).to be_in [true, false]
|
|
451
|
-
expect(video.processed?).to be_in [true, false]
|
|
452
|
-
expect(video.rejected?).to be_in [true, false]
|
|
453
|
-
expect(video.uploading?).to be_in [true, false]
|
|
454
|
-
expect(video.uses_unsupported_codec?).to be_in [true, false]
|
|
455
|
-
expect(video.has_failed_conversion?).to be_in [true, false]
|
|
456
|
-
expect(video.empty?).to be_in [true, false]
|
|
457
|
-
expect(video.invalid?).to be_in [true, false]
|
|
458
|
-
expect(video.too_small?).to be_in [true, false]
|
|
459
|
-
expect(video.aborted?).to be_in [true, false]
|
|
460
|
-
expect(video.claimed?).to be_in [true, false]
|
|
461
|
-
expect(video.infringes_copyright?).to be_in [true, false]
|
|
462
|
-
expect(video.duplicate?).to be_in [true, false]
|
|
463
|
-
expect(video.scheduled_at.class).to be_in [NilClass, Time]
|
|
464
|
-
expect(video.scheduled?).to be_in [true, false]
|
|
465
|
-
expect(video.too_long?).to be_in [true, false]
|
|
466
|
-
expect(video.violates_terms_of_use?).to be_in [true, false]
|
|
467
|
-
expect(video.inappropriate?).to be_in [true, false]
|
|
468
|
-
expect(video.infringes_trademark?).to be_in [true, false]
|
|
469
|
-
expect(video.belongs_to_closed_account?).to be_in [true, false]
|
|
470
|
-
expect(video.belongs_to_suspended_account?).to be_in [true, false]
|
|
471
|
-
expect(video.licensed_as_creative_commons?).to be_in [true, false]
|
|
472
|
-
expect(video.licensed_as_standard_youtube?).to be_in [true, false]
|
|
473
|
-
expect(video.has_public_stats_viewable?).to be_in [true, false]
|
|
474
|
-
expect(video.embeddable?).to be_in [true, false]
|
|
475
|
-
expect(video.actual_start_time).to be_nil
|
|
476
|
-
expect(video.actual_end_time).to be_nil
|
|
477
|
-
expect(video.scheduled_start_time).to be_nil
|
|
478
|
-
expect(video.scheduled_end_time).to be_nil
|
|
479
|
-
expect(video.concurrent_viewers).to be_nil
|
|
480
|
-
expect(video.embed_html).to be_a String
|
|
481
|
-
expect(video.category_title).to be_a String
|
|
482
|
-
end
|
|
483
|
-
|
|
484
|
-
it { expect{video.update}.to fail }
|
|
485
|
-
it { expect{video.delete}.to fail.with 'forbidden' }
|
|
486
|
-
|
|
487
|
-
context 'that I like' do
|
|
488
|
-
before { video.like }
|
|
489
|
-
it { expect(video).to be_liked }
|
|
490
|
-
it { expect(video.dislike).to be true }
|
|
491
|
-
end
|
|
492
|
-
|
|
493
|
-
context 'that I dislike' do
|
|
494
|
-
before { video.dislike }
|
|
495
|
-
it { expect(video).not_to be_liked }
|
|
496
|
-
it { expect(video.like).to be true }
|
|
497
|
-
end
|
|
498
|
-
|
|
499
|
-
context 'that I am indifferent to' do
|
|
500
|
-
before { video.unlike }
|
|
501
|
-
it { expect(video).not_to be_liked }
|
|
502
|
-
it { expect(video.like).to be true }
|
|
503
|
-
end
|
|
504
|
-
end
|
|
505
|
-
|
|
506
|
-
context 'given someone else’s live video broadcast scheduled in the future' do
|
|
507
|
-
let(:id) { 'PqzGI8gO_gk' }
|
|
508
|
-
|
|
509
|
-
it 'returns valid live streaming details' do
|
|
510
|
-
expect(video.actual_start_time).to be_nil
|
|
511
|
-
expect(video.actual_end_time).to be_nil
|
|
512
|
-
expect(video.scheduled_start_time).to be_a Time
|
|
513
|
-
expect(video.scheduled_end_time).to be_nil
|
|
514
|
-
end
|
|
515
|
-
end
|
|
516
|
-
|
|
517
|
-
context 'given someone else’s past live video broadcast' do
|
|
518
|
-
let(:id) { 'COOM8_tOy6U' }
|
|
519
|
-
|
|
520
|
-
it 'returns valid live streaming details' do
|
|
521
|
-
expect(video.actual_start_time).to be_a Time
|
|
522
|
-
expect(video.actual_end_time).to be_a Time
|
|
523
|
-
expect(video.scheduled_start_time).to be_a Time
|
|
524
|
-
expect(video.scheduled_end_time).to be_a Time
|
|
525
|
-
expect(video.concurrent_viewers).to be_nil
|
|
526
|
-
end
|
|
527
|
-
end
|
|
528
|
-
|
|
529
|
-
context 'given an unknown video' do
|
|
530
|
-
let(:id) { 'not-a-video-id' }
|
|
531
|
-
|
|
532
|
-
it { expect{video.content_detail}.to raise_error Yt::Errors::NoItems }
|
|
533
|
-
it { expect{video.snippet}.to raise_error Yt::Errors::NoItems }
|
|
534
|
-
it { expect{video.rating}.to raise_error Yt::Errors::NoItems }
|
|
535
|
-
it { expect{video.status}.to raise_error Yt::Errors::NoItems }
|
|
536
|
-
it { expect{video.statistics_set}.to raise_error Yt::Errors::NoItems }
|
|
537
|
-
it { expect{video.file_detail}.to raise_error Yt::Errors::NoItems }
|
|
538
|
-
end
|
|
539
|
-
|
|
540
|
-
context 'given one of my own videos that I want to delete' do
|
|
541
|
-
before(:all) { @tmp_video = $account.upload_video 'https://bit.ly/yt_test', title: "Yt Test Delete Video #{rand}" }
|
|
542
|
-
let(:id) { @tmp_video.id }
|
|
543
|
-
|
|
544
|
-
it { expect(video.delete).to be true }
|
|
545
|
-
end
|
|
546
|
-
|
|
547
|
-
context 'given one of my own videos that I want to update' do
|
|
548
|
-
let(:id) { $account.videos.where(order: 'viewCount').first.id }
|
|
549
|
-
let!(:old_title) { video.title }
|
|
550
|
-
let!(:old_privacy_status) { video.privacy_status }
|
|
551
|
-
let(:update) { video.update attrs }
|
|
552
|
-
|
|
553
|
-
context 'given I update the title' do
|
|
554
|
-
# NOTE: The use of UTF-8 characters is to test that we can pass up to
|
|
555
|
-
# 50 characters, independently of their representation
|
|
556
|
-
let(:attrs) { {title: "Yt Example Update Video #{rand} - ®•♡❥❦❧☙"} }
|
|
557
|
-
|
|
558
|
-
specify 'only updates the title' do
|
|
559
|
-
expect(update).to be true
|
|
560
|
-
expect(video.title).not_to eq old_title
|
|
561
|
-
expect(video.privacy_status).to eq old_privacy_status
|
|
562
|
-
end
|
|
563
|
-
end
|
|
564
|
-
|
|
565
|
-
context 'given I update the description' do
|
|
566
|
-
let!(:old_description) { video.description }
|
|
567
|
-
let(:attrs) { {description: "Yt Example Description #{rand} - ®•♡❥❦❧☙"} }
|
|
568
|
-
|
|
569
|
-
specify 'only updates the description' do
|
|
570
|
-
expect(update).to be true
|
|
571
|
-
expect(video.description).not_to eq old_description
|
|
572
|
-
expect(video.title).to eq old_title
|
|
573
|
-
expect(video.privacy_status).to eq old_privacy_status
|
|
574
|
-
end
|
|
575
|
-
end
|
|
576
|
-
|
|
577
|
-
context 'given I update the tags' do
|
|
578
|
-
let!(:old_tags) { video.tags }
|
|
579
|
-
let(:attrs) { {tags: ["Yt Test Tag #{rand}"]} }
|
|
580
|
-
|
|
581
|
-
specify 'only updates the tag' do
|
|
582
|
-
expect(update).to be true
|
|
583
|
-
expect(video.tags).not_to eq old_tags
|
|
584
|
-
expect(video.title).to eq old_title
|
|
585
|
-
expect(video.privacy_status).to eq old_privacy_status
|
|
586
|
-
end
|
|
587
|
-
end
|
|
588
|
-
|
|
589
|
-
context 'given I update the category ID' do
|
|
590
|
-
let!(:old_category_id) { video.category_id }
|
|
591
|
-
let!(:new_category_id) { old_category_id == '22' ? '21' : '22' }
|
|
592
|
-
|
|
593
|
-
context 'passing the parameter in underscore syntax' do
|
|
594
|
-
let(:attrs) { {category_id: new_category_id} }
|
|
595
|
-
|
|
596
|
-
specify 'only updates the category ID' do
|
|
597
|
-
expect(update).to be true
|
|
598
|
-
expect(video.category_id).not_to eq old_category_id
|
|
599
|
-
expect(video.title).to eq old_title
|
|
600
|
-
expect(video.privacy_status).to eq old_privacy_status
|
|
601
|
-
end
|
|
602
|
-
end
|
|
603
|
-
|
|
604
|
-
context 'passing the parameter in camel-case syntax' do
|
|
605
|
-
let(:attrs) { {categoryId: new_category_id} }
|
|
606
|
-
|
|
607
|
-
specify 'only updates the category ID' do
|
|
608
|
-
expect(update).to be true
|
|
609
|
-
expect(video.category_id).not_to eq old_category_id
|
|
610
|
-
end
|
|
611
|
-
end
|
|
612
|
-
end
|
|
613
|
-
|
|
614
|
-
context 'given I update title, description and/or tags using angle brackets' do
|
|
615
|
-
let(:attrs) { {title: "Example Yt Test < >", description: '< >', tags: ['<tag>']} }
|
|
616
|
-
|
|
617
|
-
specify 'updates them replacing angle brackets with similar unicode characters accepted by YouTube' do
|
|
618
|
-
expect(update).to be true
|
|
619
|
-
expect(video.title).to eq 'Example Yt Test ‹ ›'
|
|
620
|
-
expect(video.description).to eq '‹ ›'
|
|
621
|
-
expect(video.tags).to eq ['‹tag›']
|
|
622
|
-
end
|
|
623
|
-
end
|
|
624
|
-
|
|
625
|
-
# note: 'scheduled' videos cannot be set to 'unlisted'
|
|
626
|
-
context 'given I update the privacy status' do
|
|
627
|
-
before { video.update publish_at: nil if video.scheduled? }
|
|
628
|
-
let!(:new_privacy_status) { old_privacy_status == 'private' ? 'unlisted' : 'private' }
|
|
629
|
-
|
|
630
|
-
context 'passing the parameter in underscore syntax' do
|
|
631
|
-
let(:attrs) { {privacy_status: new_privacy_status} }
|
|
632
|
-
|
|
633
|
-
specify 'only updates the privacy status' do
|
|
634
|
-
expect(update).to be true
|
|
635
|
-
expect(video.privacy_status).not_to eq old_privacy_status
|
|
636
|
-
expect(video.title).to eq old_title
|
|
637
|
-
end
|
|
638
|
-
end
|
|
639
|
-
|
|
640
|
-
context 'passing the parameter in camel-case syntax' do
|
|
641
|
-
let(:attrs) { {privacyStatus: new_privacy_status} }
|
|
642
|
-
|
|
643
|
-
specify 'only updates the privacy status' do
|
|
644
|
-
expect(update).to be true
|
|
645
|
-
expect(video.privacy_status).not_to eq old_privacy_status
|
|
646
|
-
expect(video.title).to eq old_title
|
|
647
|
-
end
|
|
648
|
-
end
|
|
649
|
-
end
|
|
650
|
-
|
|
651
|
-
context 'given I update the embeddable status' do
|
|
652
|
-
let!(:old_embeddable) { video.embeddable? }
|
|
653
|
-
let!(:new_embeddable) { !old_embeddable }
|
|
654
|
-
|
|
655
|
-
let(:attrs) { {embeddable: new_embeddable} }
|
|
656
|
-
|
|
657
|
-
# @note: This test is a reflection of another irrational behavior of
|
|
658
|
-
# YouTube API. Although 'embeddable' can be passed as an 'update'
|
|
659
|
-
# attribute according to the documentation, it simply does not work.
|
|
660
|
-
# The day YouTube fixes it, then this test will finally fail and will
|
|
661
|
-
# be removed, documenting how to update 'embeddable' too.
|
|
662
|
-
# @see https://developers.google.com/youtube/v3/docs/videos/update
|
|
663
|
-
# @see https://code.google.com/p/gdata-issues/issues/detail?id=4861
|
|
664
|
-
specify 'does not update the embeddable status' do
|
|
665
|
-
expect(update).to be true
|
|
666
|
-
expect(video.embeddable?).to eq old_embeddable
|
|
667
|
-
end
|
|
668
|
-
end
|
|
669
|
-
|
|
670
|
-
context 'given I update the public stats viewable setting' do
|
|
671
|
-
let!(:old_public_stats_viewable) { video.has_public_stats_viewable? }
|
|
672
|
-
let!(:new_public_stats_viewable) { !old_public_stats_viewable }
|
|
673
|
-
|
|
674
|
-
context 'passing the parameter in underscore syntax' do
|
|
675
|
-
let(:attrs) { {public_stats_viewable: new_public_stats_viewable} }
|
|
676
|
-
|
|
677
|
-
specify 'only updates the public stats viewable setting' do
|
|
678
|
-
expect(update).to be true
|
|
679
|
-
expect(video.has_public_stats_viewable?).to eq new_public_stats_viewable
|
|
680
|
-
expect(video.privacy_status).to eq old_privacy_status
|
|
681
|
-
expect(video.title).to eq old_title
|
|
682
|
-
end
|
|
683
|
-
end
|
|
684
|
-
|
|
685
|
-
context 'passing the parameter in camel-case syntax' do
|
|
686
|
-
let(:attrs) { {publicStatsViewable: new_public_stats_viewable} }
|
|
687
|
-
|
|
688
|
-
specify 'only updates the public stats viewable setting' do
|
|
689
|
-
expect(update).to be true
|
|
690
|
-
expect(video.has_public_stats_viewable?).to eq new_public_stats_viewable
|
|
691
|
-
expect(video.privacy_status).to eq old_privacy_status
|
|
692
|
-
expect(video.title).to eq old_title
|
|
693
|
-
end
|
|
694
|
-
end
|
|
695
|
-
end
|
|
696
|
-
|
|
697
|
-
it 'returns valid reports for video-related metrics' do
|
|
698
|
-
# Some reports are only available to Content Owners.
|
|
699
|
-
# See content owner test for more details about what the methods return.
|
|
700
|
-
expect{video.views}.not_to raise_error
|
|
701
|
-
expect{video.comments}.not_to raise_error
|
|
702
|
-
expect{video.likes}.not_to raise_error
|
|
703
|
-
expect{video.dislikes}.not_to raise_error
|
|
704
|
-
expect{video.shares}.not_to raise_error
|
|
705
|
-
expect{video.subscribers_gained}.not_to raise_error
|
|
706
|
-
expect{video.subscribers_lost}.not_to raise_error
|
|
707
|
-
expect{video.videos_added_to_playlists}.not_to raise_error
|
|
708
|
-
expect{video.videos_removed_from_playlists}.not_to raise_error
|
|
709
|
-
expect{video.estimated_minutes_watched}.not_to raise_error
|
|
710
|
-
expect{video.average_view_duration}.not_to raise_error
|
|
711
|
-
expect{video.average_view_percentage}.not_to raise_error
|
|
712
|
-
expect{video.annotation_clicks}.not_to raise_error
|
|
713
|
-
expect{video.annotation_click_through_rate}.not_to raise_error
|
|
714
|
-
expect{video.annotation_close_rate}.not_to raise_error
|
|
715
|
-
expect{video.viewer_percentage}.not_to raise_error
|
|
716
|
-
expect{video.estimated_revenue}.to raise_error Yt::Errors::Unauthorized
|
|
717
|
-
expect{video.ad_impressions}.to raise_error Yt::Errors::Unauthorized
|
|
718
|
-
expect{video.monetized_playbacks}.to raise_error Yt::Errors::Unauthorized
|
|
719
|
-
expect{video.playback_based_cpm}.to raise_error Yt::Errors::Unauthorized
|
|
720
|
-
expect{video.advertising_options_set}.to raise_error Yt::Errors::Forbidden
|
|
721
|
-
end
|
|
722
|
-
end
|
|
723
|
-
|
|
724
|
-
# @note: This test is separated from the block above because, for some
|
|
725
|
-
# undocumented reasons, if an existing video was private, then set to
|
|
726
|
-
# unlisted, then set to private again, YouTube _sometimes_ raises a
|
|
727
|
-
# 400 Error when trying to set the publishAt timestamp.
|
|
728
|
-
# Therefore, just to test the updating of publishAt, we use a brand new
|
|
729
|
-
# video (set to private), rather than reusing an existing one as above.
|
|
730
|
-
context 'given one of my own *private* videos that I want to update' do
|
|
731
|
-
before { @tmp_video = $account.upload_video 'https://bit.ly/yt_test', title: old_title, privacy_status: old_privacy_status }
|
|
732
|
-
let(:id) { @tmp_video.id }
|
|
733
|
-
let!(:old_title) { "Yt Test Update publishAt Video #{rand}" }
|
|
734
|
-
let!(:old_privacy_status) { 'private' }
|
|
735
|
-
after { video.delete }
|
|
736
|
-
|
|
737
|
-
let!(:new_scheduled_at) { Yt::Timestamp.parse("#{rand(30) + 1} Jan 2020", Time.now) }
|
|
738
|
-
|
|
739
|
-
context 'passing the parameter in underscore syntax' do
|
|
740
|
-
let(:attrs) { {publish_at: new_scheduled_at} }
|
|
741
|
-
|
|
742
|
-
specify 'only updates the timestamp to publish the video' do
|
|
743
|
-
expect(video.update attrs).to be true
|
|
744
|
-
expect(video.privacy_status).to eq old_privacy_status
|
|
745
|
-
expect(video.title).to eq old_title
|
|
746
|
-
# NOTE: This is another irrational behavior of YouTube API. In short,
|
|
747
|
-
# the response of Video#update *does not* include the publishAt value
|
|
748
|
-
# even if it exists. You need to call Video#list again to get it.
|
|
749
|
-
video = Yt::Video.new id: id, auth: $account
|
|
750
|
-
expect(video.scheduled_at).to eq new_scheduled_at
|
|
751
|
-
# Setting a private (scheduled) video to private has no effect:
|
|
752
|
-
expect(video.update privacy_status: 'private').to be true
|
|
753
|
-
video = Yt::Video.new id: id, auth: $account
|
|
754
|
-
expect(video.scheduled_at).to eq new_scheduled_at
|
|
755
|
-
# Setting a private (scheduled) video to unlisted/public removes publishAt:
|
|
756
|
-
expect(video.update privacy_status: 'unlisted').to be true
|
|
757
|
-
video = Yt::Video.new id: id, auth: $account
|
|
758
|
-
expect(video.scheduled_at).to be_nil
|
|
759
|
-
end
|
|
760
|
-
end
|
|
761
|
-
|
|
762
|
-
context 'passing the parameter in camel-case syntax' do
|
|
763
|
-
let(:attrs) { {publishAt: new_scheduled_at} }
|
|
764
|
-
|
|
765
|
-
specify 'only updates the timestamp to publish the video' do
|
|
766
|
-
expect(video.update attrs).to be true
|
|
767
|
-
expect(video.scheduled_at).to eq new_scheduled_at
|
|
768
|
-
expect(video.privacy_status).to eq old_privacy_status
|
|
769
|
-
expect(video.title).to eq old_title
|
|
770
|
-
end
|
|
771
|
-
end
|
|
772
|
-
end
|
|
773
|
-
|
|
774
|
-
# @note: This should somehow test that the thumbnail *changes*. However,
|
|
775
|
-
# YouTube does not change the URL of the thumbnail even though the content
|
|
776
|
-
# changes. A full test would have to *download* the thumbnails before and
|
|
777
|
-
# after, and compare the files. For now, not raising error is enough.
|
|
778
|
-
# Eventually, change to `expect{update}.to change{video.thumbnail_url}`
|
|
779
|
-
context 'given one of my own videos for which I want to upload a thumbnail' do
|
|
780
|
-
let(:id) { $account.videos.where(order: 'viewCount').first.id }
|
|
781
|
-
let(:update) { video.upload_thumbnail path_or_url }
|
|
782
|
-
|
|
783
|
-
context 'given the path to a local JPG image file' do
|
|
784
|
-
let(:path_or_url) { File.expand_path '../thumbnail.jpg', __FILE__ }
|
|
785
|
-
|
|
786
|
-
it { expect{update}.not_to raise_error }
|
|
787
|
-
end
|
|
788
|
-
|
|
789
|
-
context 'given the path to a remote PNG image file' do
|
|
790
|
-
let(:path_or_url) { 'https://bit.ly/yt_thumbnail' }
|
|
791
|
-
|
|
792
|
-
it { expect{update}.not_to raise_error }
|
|
793
|
-
end
|
|
794
|
-
|
|
795
|
-
context 'given an invalid URL' do
|
|
796
|
-
let(:path_or_url) { 'this-is-not-a-url' }
|
|
797
|
-
|
|
798
|
-
it { expect{update}.to raise_error Yt::Errors::RequestError }
|
|
799
|
-
end
|
|
800
|
-
end
|
|
801
|
-
|
|
802
|
-
# @note: This test is separated from the block above because YouTube only
|
|
803
|
-
# returns file details for *some videos*: "The fileDetails object will
|
|
804
|
-
# only be returned if the processingDetails.fileAvailability property
|
|
805
|
-
# has a value of available.". Therefore, just to test fileDetails, we use a
|
|
806
|
-
# different video that (for some unknown reason) is marked as 'available'.
|
|
807
|
-
# Also note that I was not able to find a single video returning fileName,
|
|
808
|
-
# therefore video.file_name is not returned by Yt, until it can be tested.
|
|
809
|
-
# @see https://developers.google.com/youtube/v3/docs/videos#processingDetails.fileDetailsAvailability
|
|
810
|
-
context 'given one of my own *available* videos' do
|
|
811
|
-
let(:id) { 'yCmaOvUFhlI' }
|
|
812
|
-
|
|
813
|
-
it 'returns valid file details' do
|
|
814
|
-
expect(video.file_size).to be_an Integer
|
|
815
|
-
expect(video.file_type).to be_a String
|
|
816
|
-
expect(video.container).to be_a String
|
|
817
|
-
end
|
|
818
|
-
end
|
|
819
|
-
end
|
|
820
|
-
# encoding: UTF-8
|
|
821
|
-
|
|
822
|
-
require 'spec_helper'
|
|
823
|
-
require 'yt/models/video'
|
|
824
|
-
|
|
825
|
-
describe Yt::Video, :device_app do
|
|
826
|
-
subject(:video) { Yt::Video.new id: id, auth: $account }
|
|
827
|
-
|
|
828
|
-
context 'given someone else’s video' do
|
|
829
|
-
let(:id) { '9bZkp7q19f0' }
|
|
830
|
-
|
|
831
|
-
it { expect(video.content_detail).to be_a Yt::ContentDetail }
|
|
832
|
-
|
|
833
|
-
it 'returns valid metadata' do
|
|
834
|
-
expect(video.title).to be_a String
|
|
835
|
-
expect(video.description).to be_a String
|
|
836
|
-
expect(video.thumbnail_url).to be_a String
|
|
837
|
-
expect(video.published_at).to be_a Time
|
|
838
|
-
expect(video.privacy_status).to be_a String
|
|
839
|
-
expect(video.tags).to be_an Array
|
|
840
|
-
expect(video.channel_id).to be_a String
|
|
841
|
-
expect(video.channel_title).to be_a String
|
|
842
|
-
expect(video.channel_url).to be_a String
|
|
843
|
-
expect(video.category_id).to be_a String
|
|
844
|
-
expect(video.live_broadcast_content).to be_a String
|
|
845
|
-
expect(video.view_count).to be_an Integer
|
|
846
|
-
expect(video.like_count).to be_an Integer
|
|
847
|
-
expect(video.dislike_count).to be_an Integer
|
|
848
|
-
expect(video.favorite_count).to be_an Integer
|
|
849
|
-
expect(video.comment_count).to be_an Integer
|
|
850
|
-
expect(video.duration).to be_an Integer
|
|
851
|
-
expect(video.hd?).to be_in [true, false]
|
|
852
|
-
expect(video.stereoscopic?).to be_in [true, false]
|
|
853
|
-
expect(video.captioned?).to be_in [true, false]
|
|
854
|
-
expect(video.licensed?).to be_in [true, false]
|
|
855
|
-
expect(video.deleted?).to be_in [true, false]
|
|
856
|
-
expect(video.failed?).to be_in [true, false]
|
|
857
|
-
expect(video.processed?).to be_in [true, false]
|
|
858
|
-
expect(video.rejected?).to be_in [true, false]
|
|
859
|
-
expect(video.uploading?).to be_in [true, false]
|
|
860
|
-
expect(video.uses_unsupported_codec?).to be_in [true, false]
|
|
861
|
-
expect(video.has_failed_conversion?).to be_in [true, false]
|
|
862
|
-
expect(video.empty?).to be_in [true, false]
|
|
863
|
-
expect(video.invalid?).to be_in [true, false]
|
|
864
|
-
expect(video.too_small?).to be_in [true, false]
|
|
865
|
-
expect(video.aborted?).to be_in [true, false]
|
|
866
|
-
expect(video.claimed?).to be_in [true, false]
|
|
867
|
-
expect(video.infringes_copyright?).to be_in [true, false]
|
|
868
|
-
expect(video.duplicate?).to be_in [true, false]
|
|
869
|
-
expect(video.scheduled_at.class).to be_in [NilClass, Time]
|
|
870
|
-
expect(video.scheduled?).to be_in [true, false]
|
|
871
|
-
expect(video.too_long?).to be_in [true, false]
|
|
872
|
-
expect(video.violates_terms_of_use?).to be_in [true, false]
|
|
873
|
-
expect(video.inappropriate?).to be_in [true, false]
|
|
874
|
-
expect(video.infringes_trademark?).to be_in [true, false]
|
|
875
|
-
expect(video.belongs_to_closed_account?).to be_in [true, false]
|
|
876
|
-
expect(video.belongs_to_suspended_account?).to be_in [true, false]
|
|
877
|
-
expect(video.licensed_as_creative_commons?).to be_in [true, false]
|
|
878
|
-
expect(video.licensed_as_standard_youtube?).to be_in [true, false]
|
|
879
|
-
expect(video.has_public_stats_viewable?).to be_in [true, false]
|
|
880
|
-
expect(video.embeddable?).to be_in [true, false]
|
|
881
|
-
expect(video.actual_start_time).to be_nil
|
|
882
|
-
expect(video.actual_end_time).to be_nil
|
|
883
|
-
expect(video.scheduled_start_time).to be_nil
|
|
884
|
-
expect(video.scheduled_end_time).to be_nil
|
|
885
|
-
expect(video.concurrent_viewers).to be_nil
|
|
886
|
-
expect(video.embed_html).to be_a String
|
|
887
|
-
expect(video.category_title).to be_a String
|
|
888
|
-
end
|
|
889
|
-
|
|
890
|
-
it { expect{video.update}.to fail }
|
|
891
|
-
it { expect{video.delete}.to fail.with 'forbidden' }
|
|
892
|
-
|
|
893
|
-
context 'that I like' do
|
|
894
|
-
before { video.like }
|
|
895
|
-
it { expect(video).to be_liked }
|
|
896
|
-
it { expect(video.dislike).to be true }
|
|
897
|
-
end
|
|
898
|
-
|
|
899
|
-
context 'that I dislike' do
|
|
900
|
-
before { video.dislike }
|
|
901
|
-
it { expect(video).not_to be_liked }
|
|
902
|
-
it { expect(video.like).to be true }
|
|
903
|
-
end
|
|
904
|
-
|
|
905
|
-
context 'that I am indifferent to' do
|
|
906
|
-
before { video.unlike }
|
|
907
|
-
it { expect(video).not_to be_liked }
|
|
908
|
-
it { expect(video.like).to be true }
|
|
909
|
-
end
|
|
910
|
-
end
|
|
911
|
-
|
|
912
|
-
context 'given someone else’s live video broadcast scheduled in the future' do
|
|
913
|
-
let(:id) { 'PqzGI8gO_gk' }
|
|
914
|
-
|
|
915
|
-
it 'returns valid live streaming details' do
|
|
916
|
-
expect(video.actual_start_time).to be_nil
|
|
917
|
-
expect(video.actual_end_time).to be_nil
|
|
918
|
-
expect(video.scheduled_start_time).to be_a Time
|
|
919
|
-
expect(video.scheduled_end_time).to be_nil
|
|
920
|
-
end
|
|
921
|
-
end
|
|
922
|
-
|
|
923
|
-
context 'given someone else’s past live video broadcast' do
|
|
924
|
-
let(:id) { 'COOM8_tOy6U' }
|
|
925
|
-
|
|
926
|
-
it 'returns valid live streaming details' do
|
|
927
|
-
expect(video.actual_start_time).to be_a Time
|
|
928
|
-
expect(video.actual_end_time).to be_a Time
|
|
929
|
-
expect(video.scheduled_start_time).to be_a Time
|
|
930
|
-
expect(video.scheduled_end_time).to be_a Time
|
|
931
|
-
expect(video.concurrent_viewers).to be_nil
|
|
932
|
-
end
|
|
933
|
-
end
|
|
934
|
-
|
|
935
|
-
context 'given an unknown video' do
|
|
936
|
-
let(:id) { 'not-a-video-id' }
|
|
937
|
-
|
|
938
|
-
it { expect{video.content_detail}.to raise_error Yt::Errors::NoItems }
|
|
939
|
-
it { expect{video.snippet}.to raise_error Yt::Errors::NoItems }
|
|
940
|
-
it { expect{video.rating}.to raise_error Yt::Errors::NoItems }
|
|
941
|
-
it { expect{video.status}.to raise_error Yt::Errors::NoItems }
|
|
942
|
-
it { expect{video.statistics_set}.to raise_error Yt::Errors::NoItems }
|
|
943
|
-
it { expect{video.file_detail}.to raise_error Yt::Errors::NoItems }
|
|
944
|
-
end
|
|
945
|
-
|
|
946
|
-
context 'given one of my own videos that I want to delete' do
|
|
947
|
-
before(:all) { @tmp_video = $account.upload_video 'https://bit.ly/yt_test', title: "Yt Test Delete Video #{rand}" }
|
|
948
|
-
let(:id) { @tmp_video.id }
|
|
949
|
-
|
|
950
|
-
it { expect(video.delete).to be true }
|
|
951
|
-
end
|
|
952
|
-
|
|
953
|
-
context 'given one of my own videos that I want to update' do
|
|
954
|
-
let(:id) { $account.videos.where(order: 'viewCount').first.id }
|
|
955
|
-
let!(:old_title) { video.title }
|
|
956
|
-
let!(:old_privacy_status) { video.privacy_status }
|
|
957
|
-
let(:update) { video.update attrs }
|
|
958
|
-
|
|
959
|
-
context 'given I update the title' do
|
|
960
|
-
# NOTE: The use of UTF-8 characters is to test that we can pass up to
|
|
961
|
-
# 50 characters, independently of their representation
|
|
962
|
-
let(:attrs) { {title: "Yt Example Update Video #{rand} - ®•♡❥❦❧☙"} }
|
|
963
|
-
|
|
964
|
-
specify 'only updates the title' do
|
|
965
|
-
expect(update).to be true
|
|
966
|
-
expect(video.title).not_to eq old_title
|
|
967
|
-
expect(video.privacy_status).to eq old_privacy_status
|
|
968
|
-
end
|
|
969
|
-
end
|
|
970
|
-
|
|
971
|
-
context 'given I update the description' do
|
|
972
|
-
let!(:old_description) { video.description }
|
|
973
|
-
let(:attrs) { {description: "Yt Example Description #{rand} - ®•♡❥❦❧☙"} }
|
|
974
|
-
|
|
975
|
-
specify 'only updates the description' do
|
|
976
|
-
expect(update).to be true
|
|
977
|
-
expect(video.description).not_to eq old_description
|
|
978
|
-
expect(video.title).to eq old_title
|
|
979
|
-
expect(video.privacy_status).to eq old_privacy_status
|
|
980
|
-
end
|
|
981
|
-
end
|
|
982
|
-
|
|
983
|
-
context 'given I update the tags' do
|
|
984
|
-
let!(:old_tags) { video.tags }
|
|
985
|
-
let(:attrs) { {tags: ["Yt Test Tag #{rand}"]} }
|
|
986
|
-
|
|
987
|
-
specify 'only updates the tag' do
|
|
988
|
-
expect(update).to be true
|
|
989
|
-
expect(video.tags).not_to eq old_tags
|
|
990
|
-
expect(video.title).to eq old_title
|
|
991
|
-
expect(video.privacy_status).to eq old_privacy_status
|
|
992
|
-
end
|
|
993
|
-
end
|
|
994
|
-
|
|
995
|
-
context 'given I update the category ID' do
|
|
996
|
-
let!(:old_category_id) { video.category_id }
|
|
997
|
-
let!(:new_category_id) { old_category_id == '22' ? '21' : '22' }
|
|
998
|
-
|
|
999
|
-
context 'passing the parameter in underscore syntax' do
|
|
1000
|
-
let(:attrs) { {category_id: new_category_id} }
|
|
1001
|
-
|
|
1002
|
-
specify 'only updates the category ID' do
|
|
1003
|
-
expect(update).to be true
|
|
1004
|
-
expect(video.category_id).not_to eq old_category_id
|
|
1005
|
-
expect(video.title).to eq old_title
|
|
1006
|
-
expect(video.privacy_status).to eq old_privacy_status
|
|
1007
|
-
end
|
|
1008
|
-
end
|
|
1009
|
-
|
|
1010
|
-
context 'passing the parameter in camel-case syntax' do
|
|
1011
|
-
let(:attrs) { {categoryId: new_category_id} }
|
|
1012
|
-
|
|
1013
|
-
specify 'only updates the category ID' do
|
|
1014
|
-
expect(update).to be true
|
|
1015
|
-
expect(video.category_id).not_to eq old_category_id
|
|
1016
|
-
end
|
|
1017
|
-
end
|
|
1018
|
-
end
|
|
1019
|
-
|
|
1020
|
-
context 'given I update title, description and/or tags using angle brackets' do
|
|
1021
|
-
let(:attrs) { {title: "Example Yt Test < >", description: '< >', tags: ['<tag>']} }
|
|
1022
|
-
|
|
1023
|
-
specify 'updates them replacing angle brackets with similar unicode characters accepted by YouTube' do
|
|
1024
|
-
expect(update).to be true
|
|
1025
|
-
expect(video.title).to eq 'Example Yt Test ‹ ›'
|
|
1026
|
-
expect(video.description).to eq '‹ ›'
|
|
1027
|
-
expect(video.tags).to eq ['‹tag›']
|
|
1028
|
-
end
|
|
1029
|
-
end
|
|
1030
|
-
|
|
1031
|
-
# note: 'scheduled' videos cannot be set to 'unlisted'
|
|
1032
|
-
context 'given I update the privacy status' do
|
|
1033
|
-
before { video.update publish_at: nil if video.scheduled? }
|
|
1034
|
-
let!(:new_privacy_status) { old_privacy_status == 'private' ? 'unlisted' : 'private' }
|
|
1035
|
-
|
|
1036
|
-
context 'passing the parameter in underscore syntax' do
|
|
1037
|
-
let(:attrs) { {privacy_status: new_privacy_status} }
|
|
1038
|
-
|
|
1039
|
-
specify 'only updates the privacy status' do
|
|
1040
|
-
expect(update).to be true
|
|
1041
|
-
expect(video.privacy_status).not_to eq old_privacy_status
|
|
1042
|
-
expect(video.title).to eq old_title
|
|
1043
|
-
end
|
|
1044
|
-
end
|
|
1045
|
-
|
|
1046
|
-
context 'passing the parameter in camel-case syntax' do
|
|
1047
|
-
let(:attrs) { {privacyStatus: new_privacy_status} }
|
|
1048
|
-
|
|
1049
|
-
specify 'only updates the privacy status' do
|
|
1050
|
-
expect(update).to be true
|
|
1051
|
-
expect(video.privacy_status).not_to eq old_privacy_status
|
|
1052
|
-
expect(video.title).to eq old_title
|
|
1053
|
-
end
|
|
1054
|
-
end
|
|
1055
|
-
end
|
|
1056
|
-
|
|
1057
|
-
context 'given I update the public stats viewable setting' do
|
|
1058
|
-
let!(:old_public_stats_viewable) { video.has_public_stats_viewable? }
|
|
1059
|
-
let!(:new_public_stats_viewable) { !old_public_stats_viewable }
|
|
1060
|
-
|
|
1061
|
-
context 'passing the parameter in underscore syntax' do
|
|
1062
|
-
let(:attrs) { {public_stats_viewable: new_public_stats_viewable} }
|
|
1063
|
-
|
|
1064
|
-
specify 'only updates the public stats viewable setting' do
|
|
1065
|
-
expect(update).to be true
|
|
1066
|
-
expect(video.has_public_stats_viewable?).to eq new_public_stats_viewable
|
|
1067
|
-
expect(video.privacy_status).to eq old_privacy_status
|
|
1068
|
-
expect(video.title).to eq old_title
|
|
1069
|
-
end
|
|
1070
|
-
end
|
|
1071
|
-
|
|
1072
|
-
context 'passing the parameter in camel-case syntax' do
|
|
1073
|
-
let(:attrs) { {publicStatsViewable: new_public_stats_viewable} }
|
|
1074
|
-
|
|
1075
|
-
specify 'only updates the public stats viewable setting' do
|
|
1076
|
-
expect(update).to be true
|
|
1077
|
-
expect(video.has_public_stats_viewable?).to eq new_public_stats_viewable
|
|
1078
|
-
expect(video.privacy_status).to eq old_privacy_status
|
|
1079
|
-
expect(video.title).to eq old_title
|
|
1080
|
-
end
|
|
1081
|
-
end
|
|
1082
|
-
end
|
|
1083
|
-
|
|
1084
|
-
it 'returns valid reports for video-related metrics' do
|
|
1085
|
-
# Some reports are only available to Content Owners.
|
|
1086
|
-
# See content owner test for more details about what the methods return.
|
|
1087
|
-
expect{video.views}.not_to raise_error
|
|
1088
|
-
expect{video.comments}.not_to raise_error
|
|
1089
|
-
expect{video.likes}.not_to raise_error
|
|
1090
|
-
expect{video.dislikes}.not_to raise_error
|
|
1091
|
-
expect{video.shares}.not_to raise_error
|
|
1092
|
-
expect{video.subscribers_gained}.not_to raise_error
|
|
1093
|
-
expect{video.subscribers_lost}.not_to raise_error
|
|
1094
|
-
expect{video.videos_added_to_playlists}.not_to raise_error
|
|
1095
|
-
expect{video.videos_removed_from_playlists}.not_to raise_error
|
|
1096
|
-
expect{video.estimated_minutes_watched}.not_to raise_error
|
|
1097
|
-
expect{video.average_view_duration}.not_to raise_error
|
|
1098
|
-
expect{video.average_view_percentage}.not_to raise_error
|
|
1099
|
-
expect{video.annotation_clicks}.not_to raise_error
|
|
1100
|
-
expect{video.annotation_click_through_rate}.not_to raise_error
|
|
1101
|
-
expect{video.annotation_close_rate}.not_to raise_error
|
|
1102
|
-
expect{video.viewer_percentage}.not_to raise_error
|
|
1103
|
-
expect{video.estimated_revenue}.to raise_error Yt::Errors::Unauthorized
|
|
1104
|
-
expect{video.ad_impressions}.to raise_error Yt::Errors::Unauthorized
|
|
1105
|
-
expect{video.monetized_playbacks}.to raise_error Yt::Errors::Unauthorized
|
|
1106
|
-
expect{video.playback_based_cpm}.to raise_error Yt::Errors::Unauthorized
|
|
1107
|
-
expect{video.advertising_options_set}.to raise_error Yt::Errors::Forbidden
|
|
1108
|
-
end
|
|
1109
|
-
end
|
|
1110
|
-
|
|
1111
|
-
# @note: This test is separated from the block above because, for some
|
|
1112
|
-
# undocumented reasons, if an existing video was private, then set to
|
|
1113
|
-
# unlisted, then set to private again, YouTube _sometimes_ raises a
|
|
1114
|
-
# 400 Error when trying to set the publishAt timestamp.
|
|
1115
|
-
# Therefore, just to test the updating of publishAt, we use a brand new
|
|
1116
|
-
# video (set to private), rather than reusing an existing one as above.
|
|
1117
|
-
context 'given one of my own *private* videos that I want to update' do
|
|
1118
|
-
before { @tmp_video = $account.upload_video 'https://bit.ly/yt_test', title: old_title, privacy_status: old_privacy_status }
|
|
1119
|
-
let(:id) { @tmp_video.id }
|
|
1120
|
-
let!(:old_title) { "Yt Test Update publishAt Video #{rand}" }
|
|
1121
|
-
let!(:old_privacy_status) { 'private' }
|
|
1122
|
-
after { video.delete }
|
|
1123
|
-
|
|
1124
|
-
let!(:new_scheduled_at) { Yt::Timestamp.parse("#{rand(30) + 1} Jan 2020", Time.now) }
|
|
1125
|
-
|
|
1126
|
-
context 'passing the parameter in underscore syntax' do
|
|
1127
|
-
let(:attrs) { {publish_at: new_scheduled_at} }
|
|
1128
|
-
|
|
1129
|
-
specify 'only updates the timestamp to publish the video' do
|
|
1130
|
-
expect(video.update attrs).to be true
|
|
1131
|
-
expect(video.privacy_status).to eq old_privacy_status
|
|
1132
|
-
expect(video.title).to eq old_title
|
|
1133
|
-
# NOTE: This is another irrational behavior of YouTube API. In short,
|
|
1134
|
-
# the response of Video#update *does not* include the publishAt value
|
|
1135
|
-
# even if it exists. You need to call Video#list again to get it.
|
|
1136
|
-
video = Yt::Video.new id: id, auth: $account
|
|
1137
|
-
expect(video.scheduled_at).to eq new_scheduled_at
|
|
1138
|
-
# Setting a private (scheduled) video to private has no effect:
|
|
1139
|
-
expect(video.update privacy_status: 'private').to be true
|
|
1140
|
-
video = Yt::Video.new id: id, auth: $account
|
|
1141
|
-
expect(video.scheduled_at).to eq new_scheduled_at
|
|
1142
|
-
# Setting a private (scheduled) video to unlisted/public removes publishAt:
|
|
1143
|
-
expect(video.update privacy_status: 'unlisted').to be true
|
|
1144
|
-
video = Yt::Video.new id: id, auth: $account
|
|
1145
|
-
expect(video.scheduled_at).to be_nil
|
|
1146
|
-
end
|
|
1147
|
-
end
|
|
1148
|
-
|
|
1149
|
-
context 'passing the parameter in camel-case syntax' do
|
|
1150
|
-
let(:attrs) { {publishAt: new_scheduled_at} }
|
|
1151
|
-
|
|
1152
|
-
specify 'only updates the timestamp to publish the video' do
|
|
1153
|
-
expect(video.update attrs).to be true
|
|
1154
|
-
expect(video.scheduled_at).to eq new_scheduled_at
|
|
1155
|
-
expect(video.privacy_status).to eq old_privacy_status
|
|
1156
|
-
expect(video.title).to eq old_title
|
|
1157
|
-
end
|
|
1158
|
-
end
|
|
1159
|
-
end
|
|
1160
|
-
|
|
1161
|
-
# @note: This should somehow test that the thumbnail *changes*. However,
|
|
1162
|
-
# YouTube does not change the URL of the thumbnail even though the content
|
|
1163
|
-
# changes. A full test would have to *download* the thumbnails before and
|
|
1164
|
-
# after, and compare the files. For now, not raising error is enough.
|
|
1165
|
-
# Eventually, change to `expect{update}.to change{video.thumbnail_url}`
|
|
1166
|
-
context 'given one of my own videos for which I want to upload a thumbnail' do
|
|
1167
|
-
let(:id) { $account.videos.where(order: 'viewCount').first.id }
|
|
1168
|
-
let(:update) { video.upload_thumbnail path_or_url }
|
|
1169
|
-
|
|
1170
|
-
context 'given the path to a local JPG image file' do
|
|
1171
|
-
let(:path_or_url) { File.expand_path '../thumbnail.jpg', __FILE__ }
|
|
1172
|
-
|
|
1173
|
-
it { expect{update}.not_to raise_error }
|
|
1174
|
-
end
|
|
1175
|
-
|
|
1176
|
-
context 'given the path to a remote PNG image file' do
|
|
1177
|
-
let(:path_or_url) { 'https://bit.ly/yt_thumbnail' }
|
|
1178
|
-
|
|
1179
|
-
it { expect{update}.not_to raise_error }
|
|
1180
|
-
end
|
|
1181
|
-
|
|
1182
|
-
context 'given an invalid URL' do
|
|
1183
|
-
let(:path_or_url) { 'this-is-not-a-url' }
|
|
1184
|
-
|
|
1185
|
-
it { expect{update}.to raise_error Yt::Errors::RequestError }
|
|
1186
|
-
end
|
|
1187
|
-
end
|
|
1188
|
-
|
|
1189
|
-
# @note: This test is separated from the block above because YouTube only
|
|
1190
|
-
# returns file details for *some videos*: "The fileDetails object will
|
|
1191
|
-
# only be returned if the processingDetails.fileAvailability property
|
|
1192
|
-
# has a value of available.". Therefore, just to test fileDetails, we use a
|
|
1193
|
-
# different video that (for some unknown reason) is marked as 'available'.
|
|
1194
|
-
# Also note that I was not able to find a single video returning fileName,
|
|
1195
|
-
# therefore video.file_name is not returned by Yt, until it can be tested.
|
|
1196
|
-
# @see https://developers.google.com/youtube/v3/docs/videos#processingDetails.fileDetailsAvailability
|
|
1197
|
-
context 'given one of my own *available* videos' do
|
|
1198
|
-
let(:id) { 'yCmaOvUFhlI' }
|
|
1199
|
-
|
|
1200
|
-
it 'returns valid file details' do
|
|
1201
|
-
expect(video.file_size).to be_an Integer
|
|
1202
|
-
expect(video.file_type).to be_a String
|
|
1203
|
-
expect(video.container).to be_a String
|
|
1204
|
-
end
|
|
1205
|
-
end
|
|
1206
|
-
end
|
|
1207
|
-
# encoding: UTF-8
|
|
1208
|
-
|
|
1209
|
-
require 'spec_helper'
|
|
1210
|
-
require 'yt/models/video'
|
|
1211
|
-
|
|
1212
|
-
describe Yt::Video, :device_app do
|
|
1213
|
-
subject(:video) { Yt::Video.new id: id, auth: $account }
|
|
1214
|
-
|
|
1215
|
-
context 'given someone else’s video' do
|
|
1216
|
-
let(:id) { '9bZkp7q19f0' }
|
|
1217
|
-
|
|
1218
|
-
it { expect(video.content_detail).to be_a Yt::ContentDetail }
|
|
1219
|
-
|
|
1220
|
-
it 'returns valid metadata' do
|
|
1221
|
-
expect(video.title).to be_a String
|
|
1222
|
-
expect(video.description).to be_a String
|
|
1223
|
-
expect(video.thumbnail_url).to be_a String
|
|
1224
|
-
expect(video.published_at).to be_a Time
|
|
1225
|
-
expect(video.privacy_status).to be_a String
|
|
1226
|
-
expect(video.tags).to be_an Array
|
|
1227
|
-
expect(video.channel_id).to be_a String
|
|
1228
|
-
expect(video.channel_title).to be_a String
|
|
1229
|
-
expect(video.channel_url).to be_a String
|
|
1230
|
-
expect(video.category_id).to be_a String
|
|
1231
|
-
expect(video.live_broadcast_content).to be_a String
|
|
1232
|
-
expect(video.view_count).to be_an Integer
|
|
1233
|
-
expect(video.like_count).to be_an Integer
|
|
1234
|
-
expect(video.dislike_count).to be_an Integer
|
|
1235
|
-
expect(video.favorite_count).to be_an Integer
|
|
1236
|
-
expect(video.comment_count).to be_an Integer
|
|
1237
|
-
expect(video.duration).to be_an Integer
|
|
1238
|
-
expect(video.hd?).to be_in [true, false]
|
|
1239
|
-
expect(video.stereoscopic?).to be_in [true, false]
|
|
1240
|
-
expect(video.captioned?).to be_in [true, false]
|
|
1241
|
-
expect(video.licensed?).to be_in [true, false]
|
|
1242
|
-
expect(video.deleted?).to be_in [true, false]
|
|
1243
|
-
expect(video.failed?).to be_in [true, false]
|
|
1244
|
-
expect(video.processed?).to be_in [true, false]
|
|
1245
|
-
expect(video.rejected?).to be_in [true, false]
|
|
1246
|
-
expect(video.uploading?).to be_in [true, false]
|
|
1247
|
-
expect(video.uses_unsupported_codec?).to be_in [true, false]
|
|
1248
|
-
expect(video.has_failed_conversion?).to be_in [true, false]
|
|
1249
|
-
expect(video.empty?).to be_in [true, false]
|
|
1250
|
-
expect(video.invalid?).to be_in [true, false]
|
|
1251
|
-
expect(video.too_small?).to be_in [true, false]
|
|
1252
|
-
expect(video.aborted?).to be_in [true, false]
|
|
1253
|
-
expect(video.claimed?).to be_in [true, false]
|
|
1254
|
-
expect(video.infringes_copyright?).to be_in [true, false]
|
|
1255
|
-
expect(video.duplicate?).to be_in [true, false]
|
|
1256
|
-
expect(video.scheduled_at.class).to be_in [NilClass, Time]
|
|
1257
|
-
expect(video.scheduled?).to be_in [true, false]
|
|
1258
|
-
expect(video.too_long?).to be_in [true, false]
|
|
1259
|
-
expect(video.violates_terms_of_use?).to be_in [true, false]
|
|
1260
|
-
expect(video.inappropriate?).to be_in [true, false]
|
|
1261
|
-
expect(video.infringes_trademark?).to be_in [true, false]
|
|
1262
|
-
expect(video.belongs_to_closed_account?).to be_in [true, false]
|
|
1263
|
-
expect(video.belongs_to_suspended_account?).to be_in [true, false]
|
|
1264
|
-
expect(video.licensed_as_creative_commons?).to be_in [true, false]
|
|
1265
|
-
expect(video.licensed_as_standard_youtube?).to be_in [true, false]
|
|
1266
|
-
expect(video.has_public_stats_viewable?).to be_in [true, false]
|
|
1267
|
-
expect(video.embeddable?).to be_in [true, false]
|
|
1268
|
-
expect(video.actual_start_time).to be_nil
|
|
1269
|
-
expect(video.actual_end_time).to be_nil
|
|
1270
|
-
expect(video.scheduled_start_time).to be_nil
|
|
1271
|
-
expect(video.scheduled_end_time).to be_nil
|
|
1272
|
-
expect(video.concurrent_viewers).to be_nil
|
|
1273
|
-
expect(video.embed_html).to be_a String
|
|
1274
|
-
expect(video.category_title).to be_a String
|
|
1275
|
-
end
|
|
1276
|
-
|
|
1277
|
-
it { expect{video.update}.to fail }
|
|
1278
|
-
it { expect{video.delete}.to fail.with 'forbidden' }
|
|
1279
|
-
|
|
1280
|
-
context 'that I like' do
|
|
1281
|
-
before { video.like }
|
|
1282
|
-
it { expect(video).to be_liked }
|
|
1283
|
-
it { expect(video.dislike).to be true }
|
|
1284
|
-
end
|
|
1285
|
-
|
|
1286
|
-
context 'that I dislike' do
|
|
1287
|
-
before { video.dislike }
|
|
1288
|
-
it { expect(video).not_to be_liked }
|
|
1289
|
-
it { expect(video.like).to be true }
|
|
1290
|
-
end
|
|
1291
|
-
|
|
1292
|
-
context 'that I am indifferent to' do
|
|
1293
|
-
before { video.unlike }
|
|
1294
|
-
it { expect(video).not_to be_liked }
|
|
1295
|
-
it { expect(video.like).to be true }
|
|
1296
|
-
end
|
|
1297
|
-
end
|
|
1298
|
-
|
|
1299
|
-
context 'given someone else’s live video broadcast scheduled in the future' do
|
|
1300
|
-
let(:id) { 'PqzGI8gO_gk' }
|
|
1301
|
-
|
|
1302
|
-
it 'returns valid live streaming details' do
|
|
1303
|
-
expect(video.actual_start_time).to be_nil
|
|
1304
|
-
expect(video.actual_end_time).to be_nil
|
|
1305
|
-
expect(video.scheduled_start_time).to be_a Time
|
|
1306
|
-
expect(video.scheduled_end_time).to be_nil
|
|
1307
|
-
end
|
|
1308
|
-
end
|
|
1309
|
-
|
|
1310
|
-
context 'given someone else’s past live video broadcast' do
|
|
1311
|
-
let(:id) { 'COOM8_tOy6U' }
|
|
1312
|
-
|
|
1313
|
-
it 'returns valid live streaming details' do
|
|
1314
|
-
expect(video.actual_start_time).to be_a Time
|
|
1315
|
-
expect(video.actual_end_time).to be_a Time
|
|
1316
|
-
expect(video.scheduled_start_time).to be_a Time
|
|
1317
|
-
expect(video.scheduled_end_time).to be_a Time
|
|
1318
|
-
expect(video.concurrent_viewers).to be_nil
|
|
1319
|
-
end
|
|
1320
|
-
end
|
|
1321
|
-
|
|
1322
|
-
context 'given an unknown video' do
|
|
1323
|
-
let(:id) { 'not-a-video-id' }
|
|
1324
|
-
|
|
1325
|
-
it { expect{video.content_detail}.to raise_error Yt::Errors::NoItems }
|
|
1326
|
-
it { expect{video.snippet}.to raise_error Yt::Errors::NoItems }
|
|
1327
|
-
it { expect{video.rating}.to raise_error Yt::Errors::NoItems }
|
|
1328
|
-
it { expect{video.status}.to raise_error Yt::Errors::NoItems }
|
|
1329
|
-
it { expect{video.statistics_set}.to raise_error Yt::Errors::NoItems }
|
|
1330
|
-
it { expect{video.file_detail}.to raise_error Yt::Errors::NoItems }
|
|
1331
|
-
end
|
|
1332
|
-
|
|
1333
|
-
context 'given one of my own videos that I want to delete' do
|
|
1334
|
-
before(:all) { @tmp_video = $account.upload_video 'https://bit.ly/yt_test', title: "Yt Test Delete Video #{rand}" }
|
|
1335
|
-
let(:id) { @tmp_video.id }
|
|
1336
|
-
|
|
1337
|
-
it { expect(video.delete).to be true }
|
|
1338
|
-
end
|
|
1339
|
-
|
|
1340
|
-
context 'given one of my own videos that I want to update' do
|
|
1341
|
-
let(:id) { $account.videos.where(order: 'viewCount').first.id }
|
|
1342
|
-
let!(:old_title) { video.title }
|
|
1343
|
-
let!(:old_privacy_status) { video.privacy_status }
|
|
1344
|
-
let(:update) { video.update attrs }
|
|
1345
|
-
|
|
1346
|
-
context 'given I update the title' do
|
|
1347
|
-
# NOTE: The use of UTF-8 characters is to test that we can pass up to
|
|
1348
|
-
# 50 characters, independently of their representation
|
|
1349
|
-
let(:attrs) { {title: "Yt Example Update Video #{rand} - ®•♡❥❦❧☙"} }
|
|
1350
|
-
|
|
1351
|
-
specify 'only updates the title' do
|
|
1352
|
-
expect(update).to be true
|
|
1353
|
-
expect(video.title).not_to eq old_title
|
|
1354
|
-
expect(video.privacy_status).to eq old_privacy_status
|
|
1355
|
-
end
|
|
1356
|
-
end
|
|
1357
|
-
|
|
1358
|
-
context 'given I update the description' do
|
|
1359
|
-
let!(:old_description) { video.description }
|
|
1360
|
-
let(:attrs) { {description: "Yt Example Description #{rand} - ®•♡❥❦❧☙"} }
|
|
1361
|
-
|
|
1362
|
-
specify 'only updates the description' do
|
|
1363
|
-
expect(update).to be true
|
|
1364
|
-
expect(video.description).not_to eq old_description
|
|
1365
|
-
expect(video.title).to eq old_title
|
|
1366
|
-
expect(video.privacy_status).to eq old_privacy_status
|
|
1367
|
-
end
|
|
1368
|
-
end
|
|
1369
|
-
|
|
1370
|
-
context 'given I update the tags' do
|
|
1371
|
-
let!(:old_tags) { video.tags }
|
|
1372
|
-
let(:attrs) { {tags: ["Yt Test Tag #{rand}"]} }
|
|
1373
|
-
|
|
1374
|
-
specify 'only updates the tag' do
|
|
1375
|
-
expect(update).to be true
|
|
1376
|
-
expect(video.tags).not_to eq old_tags
|
|
1377
|
-
expect(video.title).to eq old_title
|
|
1378
|
-
expect(video.privacy_status).to eq old_privacy_status
|
|
1379
|
-
end
|
|
1380
|
-
end
|
|
1381
|
-
|
|
1382
|
-
context 'given I update the category ID' do
|
|
1383
|
-
let!(:old_category_id) { video.category_id }
|
|
1384
|
-
let!(:new_category_id) { old_category_id == '22' ? '21' : '22' }
|
|
1385
|
-
|
|
1386
|
-
context 'passing the parameter in underscore syntax' do
|
|
1387
|
-
let(:attrs) { {category_id: new_category_id} }
|
|
1388
|
-
|
|
1389
|
-
specify 'only updates the category ID' do
|
|
1390
|
-
expect(update).to be true
|
|
1391
|
-
expect(video.category_id).not_to eq old_category_id
|
|
1392
|
-
expect(video.title).to eq old_title
|
|
1393
|
-
expect(video.privacy_status).to eq old_privacy_status
|
|
1394
|
-
end
|
|
1395
|
-
end
|
|
1396
|
-
|
|
1397
|
-
context 'passing the parameter in camel-case syntax' do
|
|
1398
|
-
let(:attrs) { {categoryId: new_category_id} }
|
|
1399
|
-
|
|
1400
|
-
specify 'only updates the category ID' do
|
|
1401
|
-
expect(update).to be true
|
|
1402
|
-
expect(video.category_id).not_to eq old_category_id
|
|
1403
|
-
end
|
|
1404
|
-
end
|
|
1405
|
-
end
|
|
1406
|
-
|
|
1407
|
-
context 'given I update title, description and/or tags using angle brackets' do
|
|
1408
|
-
let(:attrs) { {title: "Example Yt Test < >", description: '< >', tags: ['<tag>']} }
|
|
1409
|
-
|
|
1410
|
-
specify 'updates them replacing angle brackets with similar unicode characters accepted by YouTube' do
|
|
1411
|
-
expect(update).to be true
|
|
1412
|
-
expect(video.title).to eq 'Example Yt Test ‹ ›'
|
|
1413
|
-
expect(video.description).to eq '‹ ›'
|
|
1414
|
-
expect(video.tags).to eq ['‹tag›']
|
|
1415
|
-
end
|
|
1416
|
-
end
|
|
1417
|
-
|
|
1418
|
-
# note: 'scheduled' videos cannot be set to 'unlisted'
|
|
1419
|
-
context 'given I update the privacy status' do
|
|
1420
|
-
before { video.update publish_at: nil if video.scheduled? }
|
|
1421
|
-
let!(:new_privacy_status) { old_privacy_status == 'private' ? 'unlisted' : 'private' }
|
|
1422
|
-
|
|
1423
|
-
context 'passing the parameter in underscore syntax' do
|
|
1424
|
-
let(:attrs) { {privacy_status: new_privacy_status} }
|
|
1425
|
-
|
|
1426
|
-
specify 'only updates the privacy status' do
|
|
1427
|
-
expect(update).to be true
|
|
1428
|
-
expect(video.privacy_status).not_to eq old_privacy_status
|
|
1429
|
-
expect(video.title).to eq old_title
|
|
1430
|
-
end
|
|
1431
|
-
end
|
|
1432
|
-
|
|
1433
|
-
context 'passing the parameter in camel-case syntax' do
|
|
1434
|
-
let(:attrs) { {privacyStatus: new_privacy_status} }
|
|
1435
|
-
|
|
1436
|
-
specify 'only updates the privacy status' do
|
|
1437
|
-
expect(update).to be true
|
|
1438
|
-
expect(video.privacy_status).not_to eq old_privacy_status
|
|
1439
|
-
expect(video.title).to eq old_title
|
|
1440
|
-
end
|
|
1441
|
-
end
|
|
1442
|
-
end
|
|
1443
|
-
|
|
1444
|
-
context 'given I update the embeddable status' do
|
|
1445
|
-
let!(:old_embeddable) { video.embeddable? }
|
|
1446
|
-
let!(:new_embeddable) { !old_embeddable }
|
|
1447
|
-
|
|
1448
|
-
let(:attrs) { {embeddable: new_embeddable} }
|
|
1449
|
-
|
|
1450
|
-
# @note: This test is a reflection of another irrational behavior of
|
|
1451
|
-
# YouTube API. Although 'embeddable' can be passed as an 'update'
|
|
1452
|
-
# attribute according to the documentation, it simply does not work.
|
|
1453
|
-
# The day YouTube fixes it, then this test will finally fail and will
|
|
1454
|
-
# be removed, documenting how to update 'embeddable' too.
|
|
1455
|
-
# @see https://developers.google.com/youtube/v3/docs/videos/update
|
|
1456
|
-
# @see https://code.google.com/p/gdata-issues/issues/detail?id=4861
|
|
1457
|
-
specify 'does not update the embeddable status' do
|
|
1458
|
-
expect(update).to be true
|
|
1459
|
-
expect(video.embeddable?).to eq old_embeddable
|
|
1460
|
-
end
|
|
1461
|
-
end
|
|
1462
|
-
|
|
1463
|
-
context 'given I update the public stats viewable setting' do
|
|
1464
|
-
let!(:old_public_stats_viewable) { video.has_public_stats_viewable? }
|
|
1465
|
-
let!(:new_public_stats_viewable) { !old_public_stats_viewable }
|
|
1466
|
-
|
|
1467
|
-
context 'passing the parameter in underscore syntax' do
|
|
1468
|
-
let(:attrs) { {public_stats_viewable: new_public_stats_viewable} }
|
|
1469
|
-
|
|
1470
|
-
specify 'only updates the public stats viewable setting' do
|
|
1471
|
-
expect(update).to be true
|
|
1472
|
-
expect(video.has_public_stats_viewable?).to eq new_public_stats_viewable
|
|
1473
|
-
expect(video.privacy_status).to eq old_privacy_status
|
|
1474
|
-
expect(video.title).to eq old_title
|
|
1475
|
-
end
|
|
1476
|
-
end
|
|
1477
|
-
|
|
1478
|
-
context 'passing the parameter in camel-case syntax' do
|
|
1479
|
-
let(:attrs) { {publicStatsViewable: new_public_stats_viewable} }
|
|
1480
|
-
|
|
1481
|
-
specify 'only updates the public stats viewable setting' do
|
|
1482
|
-
expect(update).to be true
|
|
1483
|
-
expect(video.has_public_stats_viewable?).to eq new_public_stats_viewable
|
|
1484
|
-
expect(video.privacy_status).to eq old_privacy_status
|
|
1485
|
-
expect(video.title).to eq old_title
|
|
1486
|
-
end
|
|
1487
|
-
end
|
|
1488
|
-
end
|
|
1489
|
-
|
|
1490
|
-
it 'returns valid reports for video-related metrics' do
|
|
1491
|
-
# Some reports are only available to Content Owners.
|
|
1492
|
-
# See content owner test for more details about what the methods return.
|
|
1493
|
-
expect{video.views}.not_to raise_error
|
|
1494
|
-
expect{video.comments}.not_to raise_error
|
|
1495
|
-
expect{video.likes}.not_to raise_error
|
|
1496
|
-
expect{video.dislikes}.not_to raise_error
|
|
1497
|
-
expect{video.shares}.not_to raise_error
|
|
1498
|
-
expect{video.subscribers_gained}.not_to raise_error
|
|
1499
|
-
expect{video.subscribers_lost}.not_to raise_error
|
|
1500
|
-
expect{video.videos_added_to_playlists}.not_to raise_error
|
|
1501
|
-
expect{video.videos_removed_from_playlists}.not_to raise_error
|
|
1502
|
-
expect{video.estimated_minutes_watched}.not_to raise_error
|
|
1503
|
-
expect{video.average_view_duration}.not_to raise_error
|
|
1504
|
-
expect{video.average_view_percentage}.not_to raise_error
|
|
1505
|
-
expect{video.annotation_clicks}.not_to raise_error
|
|
1506
|
-
expect{video.annotation_click_through_rate}.not_to raise_error
|
|
1507
|
-
expect{video.annotation_close_rate}.not_to raise_error
|
|
1508
|
-
expect{video.viewer_percentage}.not_to raise_error
|
|
1509
|
-
expect{video.estimated_revenue}.to raise_error Yt::Errors::Unauthorized
|
|
1510
|
-
expect{video.ad_impressions}.to raise_error Yt::Errors::Unauthorized
|
|
1511
|
-
expect{video.monetized_playbacks}.to raise_error Yt::Errors::Unauthorized
|
|
1512
|
-
expect{video.playback_based_cpm}.to raise_error Yt::Errors::Unauthorized
|
|
1513
|
-
expect{video.advertising_options_set}.to raise_error Yt::Errors::Forbidden
|
|
1514
|
-
end
|
|
1515
|
-
end
|
|
1516
|
-
|
|
1517
|
-
# @note: This test is separated from the block above because, for some
|
|
1518
|
-
# undocumented reasons, if an existing video was private, then set to
|
|
1519
|
-
# unlisted, then set to private again, YouTube _sometimes_ raises a
|
|
1520
|
-
# 400 Error when trying to set the publishAt timestamp.
|
|
1521
|
-
# Therefore, just to test the updating of publishAt, we use a brand new
|
|
1522
|
-
# video (set to private), rather than reusing an existing one as above.
|
|
1523
|
-
context 'given one of my own *private* videos that I want to update' do
|
|
1524
|
-
before { @tmp_video = $account.upload_video 'https://bit.ly/yt_test', title: old_title, privacy_status: old_privacy_status }
|
|
1525
|
-
let(:id) { @tmp_video.id }
|
|
1526
|
-
let!(:old_title) { "Yt Test Update publishAt Video #{rand}" }
|
|
1527
|
-
let!(:old_privacy_status) { 'private' }
|
|
1528
|
-
after { video.delete }
|
|
1529
|
-
|
|
1530
|
-
let!(:new_scheduled_at) { Yt::Timestamp.parse("#{rand(30) + 1} Jan 2020", Time.now) }
|
|
1531
|
-
|
|
1532
|
-
context 'passing the parameter in underscore syntax' do
|
|
1533
|
-
let(:attrs) { {publish_at: new_scheduled_at} }
|
|
1534
|
-
|
|
1535
|
-
specify 'only updates the timestamp to publish the video' do
|
|
1536
|
-
expect(video.update attrs).to be true
|
|
1537
|
-
expect(video.privacy_status).to eq old_privacy_status
|
|
1538
|
-
expect(video.title).to eq old_title
|
|
1539
|
-
# NOTE: This is another irrational behavior of YouTube API. In short,
|
|
1540
|
-
# the response of Video#update *does not* include the publishAt value
|
|
1541
|
-
# even if it exists. You need to call Video#list again to get it.
|
|
1542
|
-
video = Yt::Video.new id: id, auth: $account
|
|
1543
|
-
expect(video.scheduled_at).to eq new_scheduled_at
|
|
1544
|
-
# Setting a private (scheduled) video to private has no effect:
|
|
1545
|
-
expect(video.update privacy_status: 'private').to be true
|
|
1546
|
-
video = Yt::Video.new id: id, auth: $account
|
|
1547
|
-
expect(video.scheduled_at).to eq new_scheduled_at
|
|
1548
|
-
# Setting a private (scheduled) video to unlisted/public removes publishAt:
|
|
1549
|
-
expect(video.update privacy_status: 'unlisted').to be true
|
|
1550
|
-
video = Yt::Video.new id: id, auth: $account
|
|
1551
|
-
expect(video.scheduled_at).to be_nil
|
|
1552
|
-
end
|
|
1553
|
-
end
|
|
1554
|
-
|
|
1555
|
-
context 'passing the parameter in camel-case syntax' do
|
|
1556
|
-
let(:attrs) { {publishAt: new_scheduled_at} }
|
|
1557
|
-
|
|
1558
|
-
specify 'only updates the timestamp to publish the video' do
|
|
1559
|
-
expect(video.update attrs).to be true
|
|
1560
|
-
expect(video.scheduled_at).to eq new_scheduled_at
|
|
1561
|
-
expect(video.privacy_status).to eq old_privacy_status
|
|
1562
|
-
expect(video.title).to eq old_title
|
|
1563
|
-
end
|
|
1564
|
-
end
|
|
1565
|
-
end
|
|
1566
|
-
|
|
1567
|
-
# @note: This should somehow test that the thumbnail *changes*. However,
|
|
1568
|
-
# YouTube does not change the URL of the thumbnail even though the content
|
|
1569
|
-
# changes. A full test would have to *download* the thumbnails before and
|
|
1570
|
-
# after, and compare the files. For now, not raising error is enough.
|
|
1571
|
-
# Eventually, change to `expect{update}.to change{video.thumbnail_url}`
|
|
1572
|
-
context 'given one of my own videos for which I want to upload a thumbnail' do
|
|
1573
|
-
let(:id) { $account.videos.where(order: 'viewCount').first.id }
|
|
1574
|
-
let(:update) { video.upload_thumbnail path_or_url }
|
|
1575
|
-
|
|
1576
|
-
context 'given the path to a local JPG image file' do
|
|
1577
|
-
let(:path_or_url) { File.expand_path '../thumbnail.jpg', __FILE__ }
|
|
1578
|
-
|
|
1579
|
-
it { expect{update}.not_to raise_error }
|
|
1580
|
-
end
|
|
1581
|
-
|
|
1582
|
-
context 'given the path to a remote PNG image file' do
|
|
1583
|
-
let(:path_or_url) { 'https://bit.ly/yt_thumbnail' }
|
|
1584
|
-
|
|
1585
|
-
it { expect{update}.not_to raise_error }
|
|
1586
|
-
end
|
|
1587
|
-
|
|
1588
|
-
context 'given an invalid URL' do
|
|
1589
|
-
let(:path_or_url) { 'this-is-not-a-url' }
|
|
1590
|
-
|
|
1591
|
-
it { expect{update}.to raise_error Yt::Errors::RequestError }
|
|
1592
|
-
end
|
|
1593
|
-
end
|
|
1594
|
-
|
|
1595
|
-
# @note: This test is separated from the block above because YouTube only
|
|
1596
|
-
# returns file details for *some videos*: "The fileDetails object will
|
|
1597
|
-
# only be returned if the processingDetails.fileAvailability property
|
|
1598
|
-
# has a value of available.". Therefore, just to test fileDetails, we use a
|
|
1599
|
-
# different video that (for some unknown reason) is marked as 'available'.
|
|
1600
|
-
# Also note that I was not able to find a single video returning fileName,
|
|
1601
|
-
# therefore video.file_name is not returned by Yt, until it can be tested.
|
|
1602
|
-
# @see https://developers.google.com/youtube/v3/docs/videos#processingDetails.fileDetailsAvailability
|
|
1603
|
-
context 'given one of my own *available* videos' do
|
|
1604
|
-
let(:id) { 'yCmaOvUFhlI' }
|
|
1605
|
-
|
|
1606
|
-
it 'returns valid file details' do
|
|
1607
|
-
expect(video.file_size).to be_an Integer
|
|
1608
|
-
expect(video.file_type).to be_a String
|
|
1609
|
-
expect(video.container).to be_a String
|
|
1610
|
-
end
|
|
1611
|
-
end
|
|
1612
|
-
end
|
|
1613
|
-
# encoding: UTF-8
|
|
1614
|
-
|
|
1615
|
-
require 'spec_helper'
|
|
1616
|
-
require 'yt/models/video'
|
|
1617
|
-
|
|
1618
|
-
describe Yt::Video, :device_app do
|
|
1619
|
-
subject(:video) { Yt::Video.new id: id, auth: $account }
|
|
1620
|
-
|
|
1621
|
-
context 'given someone else’s video' do
|
|
1622
|
-
let(:id) { '9bZkp7q19f0' }
|
|
1623
|
-
|
|
1624
|
-
it { expect(video.content_detail).to be_a Yt::ContentDetail }
|
|
1625
|
-
|
|
1626
|
-
it 'returns valid metadata' do
|
|
1627
|
-
expect(video.title).to be_a String
|
|
1628
|
-
expect(video.description).to be_a String
|
|
1629
|
-
expect(video.thumbnail_url).to be_a String
|
|
1630
|
-
expect(video.published_at).to be_a Time
|
|
1631
|
-
expect(video.privacy_status).to be_a String
|
|
1632
|
-
expect(video.tags).to be_an Array
|
|
1633
|
-
expect(video.channel_id).to be_a String
|
|
1634
|
-
expect(video.channel_title).to be_a String
|
|
1635
|
-
expect(video.channel_url).to be_a String
|
|
1636
|
-
expect(video.category_id).to be_a String
|
|
1637
|
-
expect(video.live_broadcast_content).to be_a String
|
|
1638
|
-
expect(video.view_count).to be_an Integer
|
|
1639
|
-
expect(video.like_count).to be_an Integer
|
|
1640
|
-
expect(video.dislike_count).to be_an Integer
|
|
1641
|
-
expect(video.favorite_count).to be_an Integer
|
|
1642
|
-
expect(video.comment_count).to be_an Integer
|
|
1643
|
-
expect(video.duration).to be_an Integer
|
|
1644
|
-
expect(video.hd?).to be_in [true, false]
|
|
1645
|
-
expect(video.stereoscopic?).to be_in [true, false]
|
|
1646
|
-
expect(video.captioned?).to be_in [true, false]
|
|
1647
|
-
expect(video.licensed?).to be_in [true, false]
|
|
1648
|
-
expect(video.deleted?).to be_in [true, false]
|
|
1649
|
-
expect(video.failed?).to be_in [true, false]
|
|
1650
|
-
expect(video.processed?).to be_in [true, false]
|
|
1651
|
-
expect(video.rejected?).to be_in [true, false]
|
|
1652
|
-
expect(video.uploading?).to be_in [true, false]
|
|
1653
|
-
expect(video.uses_unsupported_codec?).to be_in [true, false]
|
|
1654
|
-
expect(video.has_failed_conversion?).to be_in [true, false]
|
|
1655
|
-
expect(video.empty?).to be_in [true, false]
|
|
1656
|
-
expect(video.invalid?).to be_in [true, false]
|
|
1657
|
-
expect(video.too_small?).to be_in [true, false]
|
|
1658
|
-
expect(video.aborted?).to be_in [true, false]
|
|
1659
|
-
expect(video.claimed?).to be_in [true, false]
|
|
1660
|
-
expect(video.infringes_copyright?).to be_in [true, false]
|
|
1661
|
-
expect(video.duplicate?).to be_in [true, false]
|
|
1662
|
-
expect(video.scheduled_at.class).to be_in [NilClass, Time]
|
|
1663
|
-
expect(video.scheduled?).to be_in [true, false]
|
|
1664
|
-
expect(video.too_long?).to be_in [true, false]
|
|
1665
|
-
expect(video.violates_terms_of_use?).to be_in [true, false]
|
|
1666
|
-
expect(video.inappropriate?).to be_in [true, false]
|
|
1667
|
-
expect(video.infringes_trademark?).to be_in [true, false]
|
|
1668
|
-
expect(video.belongs_to_closed_account?).to be_in [true, false]
|
|
1669
|
-
expect(video.belongs_to_suspended_account?).to be_in [true, false]
|
|
1670
|
-
expect(video.licensed_as_creative_commons?).to be_in [true, false]
|
|
1671
|
-
expect(video.licensed_as_standard_youtube?).to be_in [true, false]
|
|
1672
|
-
expect(video.has_public_stats_viewable?).to be_in [true, false]
|
|
1673
|
-
expect(video.embeddable?).to be_in [true, false]
|
|
1674
|
-
expect(video.actual_start_time).to be_nil
|
|
1675
|
-
expect(video.actual_end_time).to be_nil
|
|
1676
|
-
expect(video.scheduled_start_time).to be_nil
|
|
1677
|
-
expect(video.scheduled_end_time).to be_nil
|
|
1678
|
-
expect(video.concurrent_viewers).to be_nil
|
|
1679
|
-
expect(video.embed_html).to be_a String
|
|
1680
|
-
expect(video.category_title).to be_a String
|
|
1681
|
-
end
|
|
1682
|
-
|
|
1683
|
-
it { expect{video.update}.to fail }
|
|
1684
|
-
it { expect{video.delete}.to fail.with 'forbidden' }
|
|
1685
|
-
|
|
1686
|
-
context 'that I like' do
|
|
1687
|
-
before { video.like }
|
|
1688
|
-
it { expect(video).to be_liked }
|
|
1689
|
-
it { expect(video.dislike).to be true }
|
|
1690
|
-
end
|
|
1691
|
-
|
|
1692
|
-
context 'that I dislike' do
|
|
1693
|
-
before { video.dislike }
|
|
1694
|
-
it { expect(video).not_to be_liked }
|
|
1695
|
-
it { expect(video.like).to be true }
|
|
1696
|
-
end
|
|
1697
|
-
|
|
1698
|
-
context 'that I am indifferent to' do
|
|
1699
|
-
before { video.unlike }
|
|
1700
|
-
it { expect(video).not_to be_liked }
|
|
1701
|
-
it { expect(video.like).to be true }
|
|
1702
|
-
end
|
|
1703
|
-
end
|
|
1704
|
-
|
|
1705
|
-
context 'given someone else’s live video broadcast scheduled in the future' do
|
|
1706
|
-
let(:id) { 'PqzGI8gO_gk' }
|
|
1707
|
-
|
|
1708
|
-
it 'returns valid live streaming details' do
|
|
1709
|
-
expect(video.actual_start_time).to be_nil
|
|
1710
|
-
expect(video.actual_end_time).to be_nil
|
|
1711
|
-
expect(video.scheduled_start_time).to be_a Time
|
|
1712
|
-
expect(video.scheduled_end_time).to be_nil
|
|
1713
|
-
end
|
|
1714
|
-
end
|
|
1715
|
-
|
|
1716
|
-
context 'given someone else’s past live video broadcast' do
|
|
1717
|
-
let(:id) { 'COOM8_tOy6U' }
|
|
1718
|
-
|
|
1719
|
-
it 'returns valid live streaming details' do
|
|
1720
|
-
expect(video.actual_start_time).to be_a Time
|
|
1721
|
-
expect(video.actual_end_time).to be_a Time
|
|
1722
|
-
expect(video.scheduled_start_time).to be_a Time
|
|
1723
|
-
expect(video.scheduled_end_time).to be_a Time
|
|
1724
|
-
expect(video.concurrent_viewers).to be_nil
|
|
1725
|
-
end
|
|
1726
|
-
end
|
|
1727
|
-
|
|
1728
|
-
context 'given an unknown video' do
|
|
1729
|
-
let(:id) { 'not-a-video-id' }
|
|
1730
|
-
|
|
1731
|
-
it { expect{video.content_detail}.to raise_error Yt::Errors::NoItems }
|
|
1732
|
-
it { expect{video.snippet}.to raise_error Yt::Errors::NoItems }
|
|
1733
|
-
it { expect{video.rating}.to raise_error Yt::Errors::NoItems }
|
|
1734
|
-
it { expect{video.status}.to raise_error Yt::Errors::NoItems }
|
|
1735
|
-
it { expect{video.statistics_set}.to raise_error Yt::Errors::NoItems }
|
|
1736
|
-
it { expect{video.file_detail}.to raise_error Yt::Errors::NoItems }
|
|
1737
|
-
end
|
|
1738
|
-
|
|
1739
|
-
context 'given one of my own videos that I want to delete' do
|
|
1740
|
-
before(:all) { @tmp_video = $account.upload_video 'https://bit.ly/yt_test', title: "Yt Test Delete Video #{rand}" }
|
|
1741
|
-
let(:id) { @tmp_video.id }
|
|
1742
|
-
|
|
1743
|
-
it { expect(video.delete).to be true }
|
|
1744
|
-
end
|
|
1745
|
-
|
|
1746
|
-
context 'given one of my own videos that I want to update' do
|
|
1747
|
-
let(:id) { $account.videos.where(order: 'viewCount').first.id }
|
|
1748
|
-
let!(:old_title) { video.title }
|
|
1749
|
-
let!(:old_privacy_status) { video.privacy_status }
|
|
1750
|
-
let(:update) { video.update attrs }
|
|
1751
|
-
|
|
1752
|
-
context 'given I update the title' do
|
|
1753
|
-
# NOTE: The use of UTF-8 characters is to test that we can pass up to
|
|
1754
|
-
# 50 characters, independently of their representation
|
|
1755
|
-
let(:attrs) { {title: "Yt Example Update Video #{rand} - ®•♡❥❦❧☙"} }
|
|
1756
|
-
|
|
1757
|
-
specify 'only updates the title' do
|
|
1758
|
-
expect(update).to be true
|
|
1759
|
-
expect(video.title).not_to eq old_title
|
|
1760
|
-
expect(video.privacy_status).to eq old_privacy_status
|
|
1761
|
-
end
|
|
1762
|
-
end
|
|
1763
|
-
|
|
1764
|
-
context 'given I update the description' do
|
|
1765
|
-
let!(:old_description) { video.description }
|
|
1766
|
-
let(:attrs) { {description: "Yt Example Description #{rand} - ®•♡❥❦❧☙"} }
|
|
1767
|
-
|
|
1768
|
-
specify 'only updates the description' do
|
|
1769
|
-
expect(update).to be true
|
|
1770
|
-
expect(video.description).not_to eq old_description
|
|
1771
|
-
expect(video.title).to eq old_title
|
|
1772
|
-
expect(video.privacy_status).to eq old_privacy_status
|
|
1773
|
-
end
|
|
1774
|
-
end
|
|
1775
|
-
|
|
1776
|
-
context 'given I update the tags' do
|
|
1777
|
-
let!(:old_tags) { video.tags }
|
|
1778
|
-
let(:attrs) { {tags: ["Yt Test Tag #{rand}"]} }
|
|
1779
|
-
|
|
1780
|
-
specify 'only updates the tag' do
|
|
1781
|
-
expect(update).to be true
|
|
1782
|
-
expect(video.tags).not_to eq old_tags
|
|
1783
|
-
expect(video.title).to eq old_title
|
|
1784
|
-
expect(video.privacy_status).to eq old_privacy_status
|
|
1785
|
-
end
|
|
1786
|
-
end
|
|
1787
|
-
|
|
1788
|
-
context 'given I update the category ID' do
|
|
1789
|
-
let!(:old_category_id) { video.category_id }
|
|
1790
|
-
let!(:new_category_id) { old_category_id == '22' ? '21' : '22' }
|
|
1791
|
-
|
|
1792
|
-
context 'passing the parameter in underscore syntax' do
|
|
1793
|
-
let(:attrs) { {category_id: new_category_id} }
|
|
1794
|
-
|
|
1795
|
-
specify 'only updates the category ID' do
|
|
1796
|
-
expect(update).to be true
|
|
1797
|
-
expect(video.category_id).not_to eq old_category_id
|
|
1798
|
-
expect(video.title).to eq old_title
|
|
1799
|
-
expect(video.privacy_status).to eq old_privacy_status
|
|
1800
|
-
end
|
|
1801
|
-
end
|
|
1802
|
-
|
|
1803
|
-
context 'passing the parameter in camel-case syntax' do
|
|
1804
|
-
let(:attrs) { {categoryId: new_category_id} }
|
|
1805
|
-
|
|
1806
|
-
specify 'only updates the category ID' do
|
|
1807
|
-
expect(update).to be true
|
|
1808
|
-
expect(video.category_id).not_to eq old_category_id
|
|
1809
|
-
end
|
|
1810
|
-
end
|
|
1811
|
-
end
|
|
1812
|
-
|
|
1813
|
-
context 'given I update title, description and/or tags using angle brackets' do
|
|
1814
|
-
let(:attrs) { {title: "Example Yt Test < >", description: '< >', tags: ['<tag>']} }
|
|
1815
|
-
|
|
1816
|
-
specify 'updates them replacing angle brackets with similar unicode characters accepted by YouTube' do
|
|
1817
|
-
expect(update).to be true
|
|
1818
|
-
expect(video.title).to eq 'Example Yt Test ‹ ›'
|
|
1819
|
-
expect(video.description).to eq '‹ ›'
|
|
1820
|
-
expect(video.tags).to eq ['‹tag›']
|
|
1821
|
-
end
|
|
1822
|
-
end
|
|
1823
|
-
|
|
1824
|
-
# note: 'scheduled' videos cannot be set to 'unlisted'
|
|
1825
|
-
context 'given I update the privacy status' do
|
|
1826
|
-
before { video.update publish_at: nil if video.scheduled? }
|
|
1827
|
-
let!(:new_privacy_status) { old_privacy_status == 'private' ? 'unlisted' : 'private' }
|
|
1828
|
-
|
|
1829
|
-
context 'passing the parameter in underscore syntax' do
|
|
1830
|
-
let(:attrs) { {privacy_status: new_privacy_status} }
|
|
1831
|
-
|
|
1832
|
-
specify 'only updates the privacy status' do
|
|
1833
|
-
expect(update).to be true
|
|
1834
|
-
expect(video.privacy_status).not_to eq old_privacy_status
|
|
1835
|
-
expect(video.title).to eq old_title
|
|
1836
|
-
end
|
|
1837
|
-
end
|
|
1838
|
-
|
|
1839
|
-
context 'passing the parameter in camel-case syntax' do
|
|
1840
|
-
let(:attrs) { {privacyStatus: new_privacy_status} }
|
|
1841
|
-
|
|
1842
|
-
specify 'only updates the privacy status' do
|
|
1843
|
-
expect(update).to be true
|
|
1844
|
-
expect(video.privacy_status).not_to eq old_privacy_status
|
|
1845
|
-
expect(video.title).to eq old_title
|
|
1846
|
-
end
|
|
1847
|
-
end
|
|
1848
|
-
end
|
|
1849
|
-
|
|
1850
|
-
context 'given I update the embeddable status' do
|
|
1851
|
-
let!(:old_embeddable) { video.embeddable? }
|
|
1852
|
-
let!(:new_embeddable) { !old_embeddable }
|
|
1853
|
-
|
|
1854
|
-
let(:attrs) { {embeddable: new_embeddable} }
|
|
1855
|
-
|
|
1856
|
-
# @note: This test is a reflection of another irrational behavior of
|
|
1857
|
-
# YouTube API. Although 'embeddable' can be passed as an 'update'
|
|
1858
|
-
# attribute according to the documentation, it simply does not work.
|
|
1859
|
-
# The day YouTube fixes it, then this test will finally fail and will
|
|
1860
|
-
# be removed, documenting how to update 'embeddable' too.
|
|
1861
|
-
# @see https://developers.google.com/youtube/v3/docs/videos/update
|
|
1862
|
-
# @see https://code.google.com/p/gdata-issues/issues/detail?id=4861
|
|
1863
|
-
specify 'does not update the embeddable status' do
|
|
1864
|
-
expect(update).to be true
|
|
1865
|
-
expect(video.embeddable?).to eq old_embeddable
|
|
1866
|
-
end
|
|
1867
|
-
end
|
|
1868
|
-
|
|
1869
|
-
context 'given I update the public stats viewable setting' do
|
|
1870
|
-
let!(:old_public_stats_viewable) { video.has_public_stats_viewable? }
|
|
1871
|
-
let!(:new_public_stats_viewable) { !old_public_stats_viewable }
|
|
1872
|
-
|
|
1873
|
-
context 'passing the parameter in underscore syntax' do
|
|
1874
|
-
let(:attrs) { {public_stats_viewable: new_public_stats_viewable} }
|
|
1875
|
-
|
|
1876
|
-
specify 'only updates the public stats viewable setting' do
|
|
1877
|
-
expect(update).to be true
|
|
1878
|
-
expect(video.has_public_stats_viewable?).to eq new_public_stats_viewable
|
|
1879
|
-
expect(video.privacy_status).to eq old_privacy_status
|
|
1880
|
-
expect(video.title).to eq old_title
|
|
1881
|
-
end
|
|
1882
|
-
end
|
|
1883
|
-
|
|
1884
|
-
context 'passing the parameter in camel-case syntax' do
|
|
1885
|
-
let(:attrs) { {publicStatsViewable: new_public_stats_viewable} }
|
|
1886
|
-
|
|
1887
|
-
specify 'only updates the public stats viewable setting' do
|
|
1888
|
-
expect(update).to be true
|
|
1889
|
-
expect(video.has_public_stats_viewable?).to eq new_public_stats_viewable
|
|
1890
|
-
expect(video.privacy_status).to eq old_privacy_status
|
|
1891
|
-
expect(video.title).to eq old_title
|
|
1892
|
-
end
|
|
1893
|
-
end
|
|
1894
|
-
end
|
|
1895
|
-
|
|
1896
|
-
it 'returns valid reports for video-related metrics' do
|
|
1897
|
-
# Some reports are only available to Content Owners.
|
|
1898
|
-
# See content owner test for more details about what the methods return.
|
|
1899
|
-
expect{video.views}.not_to raise_error
|
|
1900
|
-
expect{video.comments}.not_to raise_error
|
|
1901
|
-
expect{video.likes}.not_to raise_error
|
|
1902
|
-
expect{video.dislikes}.not_to raise_error
|
|
1903
|
-
expect{video.shares}.not_to raise_error
|
|
1904
|
-
expect{video.subscribers_gained}.not_to raise_error
|
|
1905
|
-
expect{video.subscribers_lost}.not_to raise_error
|
|
1906
|
-
expect{video.videos_added_to_playlists}.not_to raise_error
|
|
1907
|
-
expect{video.videos_removed_from_playlists}.not_to raise_error
|
|
1908
|
-
expect{video.estimated_minutes_watched}.not_to raise_error
|
|
1909
|
-
expect{video.average_view_duration}.not_to raise_error
|
|
1910
|
-
expect{video.average_view_percentage}.not_to raise_error
|
|
1911
|
-
expect{video.annotation_clicks}.not_to raise_error
|
|
1912
|
-
expect{video.annotation_click_through_rate}.not_to raise_error
|
|
1913
|
-
expect{video.annotation_close_rate}.not_to raise_error
|
|
1914
|
-
expect{video.viewer_percentage}.not_to raise_error
|
|
1915
|
-
expect{video.estimated_revenue}.to raise_error Yt::Errors::Unauthorized
|
|
1916
|
-
expect{video.ad_impressions}.to raise_error Yt::Errors::Unauthorized
|
|
1917
|
-
expect{video.monetized_playbacks}.to raise_error Yt::Errors::Unauthorized
|
|
1918
|
-
expect{video.playback_based_cpm}.to raise_error Yt::Errors::Unauthorized
|
|
1919
|
-
expect{video.advertising_options_set}.to raise_error Yt::Errors::Forbidden
|
|
1920
|
-
|
|
1921
|
-
end
|
|
1922
|
-
end
|
|
1923
|
-
|
|
1924
|
-
# @note: This test is separated from the block above because, for some
|
|
1925
|
-
# undocumented reasons, if an existing video was private, then set to
|
|
1926
|
-
# unlisted, then set to private again, YouTube _sometimes_ raises a
|
|
1927
|
-
# 400 Error when trying to set the publishAt timestamp.
|
|
1928
|
-
# Therefore, just to test the updating of publishAt, we use a brand new
|
|
1929
|
-
# video (set to private), rather than reusing an existing one as above.
|
|
1930
|
-
context 'given one of my own *private* videos that I want to update' do
|
|
1931
|
-
before { @tmp_video = $account.upload_video 'https://bit.ly/yt_test', title: old_title, privacy_status: old_privacy_status }
|
|
1932
|
-
let(:id) { @tmp_video.id }
|
|
1933
|
-
let!(:old_title) { "Yt Test Update publishAt Video #{rand}" }
|
|
1934
|
-
let!(:old_privacy_status) { 'private' }
|
|
1935
|
-
after { video.delete }
|
|
1936
|
-
|
|
1937
|
-
let!(:new_scheduled_at) { Yt::Timestamp.parse("#{rand(30) + 1} Jan 2020", Time.now) }
|
|
1938
|
-
|
|
1939
|
-
context 'passing the parameter in underscore syntax' do
|
|
1940
|
-
let(:attrs) { {publish_at: new_scheduled_at} }
|
|
1941
|
-
|
|
1942
|
-
specify 'only updates the timestamp to publish the video' do
|
|
1943
|
-
expect(video.update attrs).to be true
|
|
1944
|
-
expect(video.privacy_status).to eq old_privacy_status
|
|
1945
|
-
expect(video.title).to eq old_title
|
|
1946
|
-
# NOTE: This is another irrational behavior of YouTube API. In short,
|
|
1947
|
-
# the response of Video#update *does not* include the publishAt value
|
|
1948
|
-
# even if it exists. You need to call Video#list again to get it.
|
|
1949
|
-
video = Yt::Video.new id: id, auth: $account
|
|
1950
|
-
expect(video.scheduled_at).to eq new_scheduled_at
|
|
1951
|
-
# Setting a private (scheduled) video to private has no effect:
|
|
1952
|
-
expect(video.update privacy_status: 'private').to be true
|
|
1953
|
-
video = Yt::Video.new id: id, auth: $account
|
|
1954
|
-
expect(video.scheduled_at).to eq new_scheduled_at
|
|
1955
|
-
# Setting a private (scheduled) video to unlisted/public removes publishAt:
|
|
1956
|
-
expect(video.update privacy_status: 'unlisted').to be true
|
|
1957
|
-
video = Yt::Video.new id: id, auth: $account
|
|
1958
|
-
expect(video.scheduled_at).to be_nil
|
|
1959
|
-
end
|
|
1960
|
-
end
|
|
1961
|
-
|
|
1962
|
-
context 'passing the parameter in camel-case syntax' do
|
|
1963
|
-
let(:attrs) { {publishAt: new_scheduled_at} }
|
|
1964
|
-
|
|
1965
|
-
specify 'only updates the timestamp to publish the video' do
|
|
1966
|
-
expect(video.update attrs).to be true
|
|
1967
|
-
expect(video.scheduled_at).to eq new_scheduled_at
|
|
1968
|
-
expect(video.privacy_status).to eq old_privacy_status
|
|
1969
|
-
expect(video.title).to eq old_title
|
|
1970
|
-
end
|
|
1971
|
-
end
|
|
1972
|
-
end
|
|
1973
|
-
|
|
1974
|
-
# @note: This should somehow test that the thumbnail *changes*. However,
|
|
1975
|
-
# YouTube does not change the URL of the thumbnail even though the content
|
|
1976
|
-
# changes. A full test would have to *download* the thumbnails before and
|
|
1977
|
-
# after, and compare the files. For now, not raising error is enough.
|
|
1978
|
-
# Eventually, change to `expect{update}.to change{video.thumbnail_url}`
|
|
1979
|
-
context 'given one of my own videos for which I want to upload a thumbnail' do
|
|
1980
|
-
let(:id) { $account.videos.where(order: 'viewCount').first.id }
|
|
1981
|
-
let(:update) { video.upload_thumbnail path_or_url }
|
|
1982
|
-
|
|
1983
|
-
context 'given the path to a local JPG image file' do
|
|
1984
|
-
let(:path_or_url) { File.expand_path '../thumbnail.jpg', __FILE__ }
|
|
1985
|
-
|
|
1986
|
-
it { expect{update}.not_to raise_error }
|
|
1987
|
-
end
|
|
1988
|
-
|
|
1989
|
-
context 'given the path to a remote PNG image file' do
|
|
1990
|
-
let(:path_or_url) { 'https://bit.ly/yt_thumbnail' }
|
|
1991
|
-
|
|
1992
|
-
it { expect{update}.not_to raise_error }
|
|
1993
|
-
end
|
|
1994
|
-
|
|
1995
|
-
context 'given an invalid URL' do
|
|
1996
|
-
let(:path_or_url) { 'this-is-not-a-url' }
|
|
1997
|
-
|
|
1998
|
-
it { expect{update}.to raise_error Yt::Errors::RequestError }
|
|
1999
|
-
end
|
|
2000
|
-
end
|
|
2001
|
-
|
|
2002
|
-
# @note: This test is separated from the block above because YouTube only
|
|
2003
|
-
# returns file details for *some videos*: "The fileDetails object will
|
|
2004
|
-
# only be returned if the processingDetails.fileAvailability property
|
|
2005
|
-
# has a value of available.". Therefore, just to test fileDetails, we use a
|
|
2006
|
-
# different video that (for some unknown reason) is marked as 'available'.
|
|
2007
|
-
# Also note that I was not able to find a single video returning fileName,
|
|
2008
|
-
# therefore video.file_name is not returned by Yt, until it can be tested.
|
|
2009
|
-
# @see https://developers.google.com/youtube/v3/docs/videos#processingDetails.fileDetailsAvailability
|
|
2010
|
-
context 'given one of my own *available* videos' do
|
|
2011
|
-
let(:id) { 'yCmaOvUFhlI' }
|
|
2012
|
-
|
|
2013
|
-
it 'returns valid file details' do
|
|
2014
|
-
expect(video.file_size).to be_an Integer
|
|
2015
|
-
expect(video.file_type).to be_a String
|
|
2016
|
-
expect(video.container).to be_a String
|
|
2017
|
-
end
|
|
2018
|
-
end
|
|
2019
|
-
end
|
|
2020
|
-
# encoding: UTF-8
|
|
2021
|
-
|
|
2022
|
-
require 'spec_helper'
|
|
2023
|
-
require 'yt/models/video'
|
|
2024
|
-
|
|
2025
|
-
describe Yt::Video, :device_app do
|
|
2026
|
-
subject(:video) { Yt::Video.new id: id, auth: $account }
|
|
2027
|
-
|
|
2028
|
-
context 'given someone else’s video' do
|
|
2029
|
-
let(:id) { '9bZkp7q19f0' }
|
|
2030
|
-
|
|
2031
|
-
it { expect(video.content_detail).to be_a Yt::ContentDetail }
|
|
2032
|
-
|
|
2033
|
-
it 'returns valid metadata' do
|
|
2034
|
-
expect(video.title).to be_a String
|
|
2035
|
-
expect(video.description).to be_a String
|
|
2036
|
-
expect(video.thumbnail_url).to be_a String
|
|
2037
|
-
expect(video.published_at).to be_a Time
|
|
2038
|
-
expect(video.privacy_status).to be_a String
|
|
2039
|
-
expect(video.tags).to be_an Array
|
|
2040
|
-
expect(video.channel_id).to be_a String
|
|
2041
|
-
expect(video.channel_title).to be_a String
|
|
2042
|
-
expect(video.channel_url).to be_a String
|
|
2043
|
-
expect(video.category_id).to be_a String
|
|
2044
|
-
expect(video.live_broadcast_content).to be_a String
|
|
2045
|
-
expect(video.view_count).to be_an Integer
|
|
2046
|
-
expect(video.like_count).to be_an Integer
|
|
2047
|
-
expect(video.dislike_count).to be_an Integer
|
|
2048
|
-
expect(video.favorite_count).to be_an Integer
|
|
2049
|
-
expect(video.comment_count).to be_an Integer
|
|
2050
|
-
expect(video.duration).to be_an Integer
|
|
2051
|
-
expect(video.hd?).to be_in [true, false]
|
|
2052
|
-
expect(video.stereoscopic?).to be_in [true, false]
|
|
2053
|
-
expect(video.captioned?).to be_in [true, false]
|
|
2054
|
-
expect(video.licensed?).to be_in [true, false]
|
|
2055
|
-
expect(video.deleted?).to be_in [true, false]
|
|
2056
|
-
expect(video.failed?).to be_in [true, false]
|
|
2057
|
-
expect(video.processed?).to be_in [true, false]
|
|
2058
|
-
expect(video.rejected?).to be_in [true, false]
|
|
2059
|
-
expect(video.uploading?).to be_in [true, false]
|
|
2060
|
-
expect(video.uses_unsupported_codec?).to be_in [true, false]
|
|
2061
|
-
expect(video.has_failed_conversion?).to be_in [true, false]
|
|
2062
|
-
expect(video.empty?).to be_in [true, false]
|
|
2063
|
-
expect(video.invalid?).to be_in [true, false]
|
|
2064
|
-
expect(video.too_small?).to be_in [true, false]
|
|
2065
|
-
expect(video.aborted?).to be_in [true, false]
|
|
2066
|
-
expect(video.claimed?).to be_in [true, false]
|
|
2067
|
-
expect(video.infringes_copyright?).to be_in [true, false]
|
|
2068
|
-
expect(video.duplicate?).to be_in [true, false]
|
|
2069
|
-
expect(video.scheduled_at.class).to be_in [NilClass, Time]
|
|
2070
|
-
expect(video.scheduled?).to be_in [true, false]
|
|
2071
|
-
expect(video.too_long?).to be_in [true, false]
|
|
2072
|
-
expect(video.violates_terms_of_use?).to be_in [true, false]
|
|
2073
|
-
expect(video.inappropriate?).to be_in [true, false]
|
|
2074
|
-
expect(video.infringes_trademark?).to be_in [true, false]
|
|
2075
|
-
expect(video.belongs_to_closed_account?).to be_in [true, false]
|
|
2076
|
-
expect(video.belongs_to_suspended_account?).to be_in [true, false]
|
|
2077
|
-
expect(video.licensed_as_creative_commons?).to be_in [true, false]
|
|
2078
|
-
expect(video.licensed_as_standard_youtube?).to be_in [true, false]
|
|
2079
|
-
expect(video.has_public_stats_viewable?).to be_in [true, false]
|
|
2080
|
-
expect(video.embeddable?).to be_in [true, false]
|
|
2081
|
-
expect(video.actual_start_time).to be_nil
|
|
2082
|
-
expect(video.actual_end_time).to be_nil
|
|
2083
|
-
expect(video.scheduled_start_time).to be_nil
|
|
2084
|
-
expect(video.scheduled_end_time).to be_nil
|
|
2085
|
-
expect(video.concurrent_viewers).to be_nil
|
|
2086
|
-
expect(video.embed_html).to be_a String
|
|
2087
|
-
expect(video.category_title).to be_a String
|
|
2088
|
-
end
|
|
2089
|
-
|
|
2090
|
-
it { expect{video.update}.to fail }
|
|
2091
|
-
it { expect{video.delete}.to fail.with 'forbidden' }
|
|
2092
|
-
|
|
2093
|
-
context 'that I like' do
|
|
2094
|
-
before { video.like }
|
|
2095
|
-
it { expect(video).to be_liked }
|
|
2096
|
-
it { expect(video.dislike).to be true }
|
|
2097
|
-
end
|
|
2098
|
-
|
|
2099
|
-
context 'that I dislike' do
|
|
2100
|
-
before { video.dislike }
|
|
2101
|
-
it { expect(video).not_to be_liked }
|
|
2102
|
-
it { expect(video.like).to be true }
|
|
2103
|
-
end
|
|
2104
|
-
|
|
2105
|
-
context 'that I am indifferent to' do
|
|
2106
|
-
before { video.unlike }
|
|
2107
|
-
it { expect(video).not_to be_liked }
|
|
2108
|
-
it { expect(video.like).to be true }
|
|
2109
|
-
end
|
|
2110
|
-
end
|
|
2111
|
-
|
|
2112
|
-
context 'given someone else’s live video broadcast scheduled in the future' do
|
|
2113
|
-
let(:id) { 'PqzGI8gO_gk' }
|
|
2114
|
-
|
|
2115
|
-
it 'returns valid live streaming details' do
|
|
2116
|
-
expect(video.actual_start_time).to be_nil
|
|
2117
|
-
expect(video.actual_end_time).to be_nil
|
|
2118
|
-
expect(video.scheduled_start_time).to be_a Time
|
|
2119
|
-
expect(video.scheduled_end_time).to be_nil
|
|
2120
|
-
end
|
|
2121
|
-
end
|
|
2122
|
-
|
|
2123
|
-
context 'given someone else’s past live video broadcast' do
|
|
2124
|
-
let(:id) { 'COOM8_tOy6U' }
|
|
2125
|
-
|
|
2126
|
-
it 'returns valid live streaming details' do
|
|
2127
|
-
expect(video.actual_start_time).to be_a Time
|
|
2128
|
-
expect(video.actual_end_time).to be_a Time
|
|
2129
|
-
expect(video.scheduled_start_time).to be_a Time
|
|
2130
|
-
expect(video.scheduled_end_time).to be_a Time
|
|
2131
|
-
expect(video.concurrent_viewers).to be_nil
|
|
2132
|
-
end
|
|
2133
|
-
end
|
|
2134
|
-
|
|
2135
|
-
context 'given an unknown video' do
|
|
2136
|
-
let(:id) { 'not-a-video-id' }
|
|
2137
|
-
|
|
2138
|
-
it { expect{video.content_detail}.to raise_error Yt::Errors::NoItems }
|
|
2139
|
-
it { expect{video.snippet}.to raise_error Yt::Errors::NoItems }
|
|
2140
|
-
it { expect{video.rating}.to raise_error Yt::Errors::NoItems }
|
|
2141
|
-
it { expect{video.status}.to raise_error Yt::Errors::NoItems }
|
|
2142
|
-
it { expect{video.statistics_set}.to raise_error Yt::Errors::NoItems }
|
|
2143
|
-
it { expect{video.file_detail}.to raise_error Yt::Errors::NoItems }
|
|
2144
|
-
end
|
|
2145
|
-
|
|
2146
|
-
context 'given one of my own videos that I want to delete' do
|
|
2147
|
-
before(:all) { @tmp_video = $account.upload_video 'https://bit.ly/yt_test', title: "Yt Test Delete Video #{rand}" }
|
|
2148
|
-
let(:id) { @tmp_video.id }
|
|
2149
|
-
|
|
2150
|
-
it { expect(video.delete).to be true }
|
|
2151
|
-
end
|
|
2152
|
-
|
|
2153
|
-
context 'given one of my own videos that I want to update' do
|
|
2154
|
-
let(:id) { $account.videos.where(order: 'viewCount').first.id }
|
|
2155
|
-
let!(:old_title) { video.title }
|
|
2156
|
-
let!(:old_privacy_status) { video.privacy_status }
|
|
2157
|
-
let(:update) { video.update attrs }
|
|
2158
|
-
|
|
2159
|
-
context 'given I update the title' do
|
|
2160
|
-
# NOTE: The use of UTF-8 characters is to test that we can pass up to
|
|
2161
|
-
# 50 characters, independently of their representation
|
|
2162
|
-
let(:attrs) { {title: "Yt Example Update Video #{rand} - ®•♡❥❦❧☙"} }
|
|
2163
|
-
|
|
2164
|
-
specify 'only updates the title' do
|
|
2165
|
-
expect(update).to be true
|
|
2166
|
-
expect(video.title).not_to eq old_title
|
|
2167
|
-
expect(video.privacy_status).to eq old_privacy_status
|
|
2168
|
-
end
|
|
2169
|
-
end
|
|
2170
|
-
|
|
2171
|
-
context 'given I update the description' do
|
|
2172
|
-
let!(:old_description) { video.description }
|
|
2173
|
-
let(:attrs) { {description: "Yt Example Description #{rand} - ®•♡❥❦❧☙"} }
|
|
2174
|
-
|
|
2175
|
-
specify 'only updates the description' do
|
|
2176
|
-
expect(update).to be true
|
|
2177
|
-
expect(video.description).not_to eq old_description
|
|
2178
|
-
expect(video.title).to eq old_title
|
|
2179
|
-
expect(video.privacy_status).to eq old_privacy_status
|
|
2180
|
-
end
|
|
2181
|
-
end
|
|
2182
|
-
|
|
2183
|
-
context 'given I update the tags' do
|
|
2184
|
-
let!(:old_tags) { video.tags }
|
|
2185
|
-
let(:attrs) { {tags: ["Yt Test Tag #{rand}"]} }
|
|
2186
|
-
|
|
2187
|
-
specify 'only updates the tag' do
|
|
2188
|
-
expect(update).to be true
|
|
2189
|
-
expect(video.tags).not_to eq old_tags
|
|
2190
|
-
expect(video.title).to eq old_title
|
|
2191
|
-
expect(video.privacy_status).to eq old_privacy_status
|
|
2192
|
-
end
|
|
2193
|
-
end
|
|
2194
|
-
|
|
2195
|
-
context 'given I update the category ID' do
|
|
2196
|
-
let!(:old_category_id) { video.category_id }
|
|
2197
|
-
let!(:new_category_id) { old_category_id == '22' ? '21' : '22' }
|
|
2198
|
-
|
|
2199
|
-
context 'passing the parameter in underscore syntax' do
|
|
2200
|
-
let(:attrs) { {category_id: new_category_id} }
|
|
2201
|
-
|
|
2202
|
-
specify 'only updates the category ID' do
|
|
2203
|
-
expect(update).to be true
|
|
2204
|
-
expect(video.category_id).not_to eq old_category_id
|
|
2205
|
-
expect(video.title).to eq old_title
|
|
2206
|
-
expect(video.privacy_status).to eq old_privacy_status
|
|
2207
|
-
end
|
|
2208
|
-
end
|
|
2209
|
-
|
|
2210
|
-
context 'passing the parameter in camel-case syntax' do
|
|
2211
|
-
let(:attrs) { {categoryId: new_category_id} }
|
|
2212
|
-
|
|
2213
|
-
specify 'only updates the category ID' do
|
|
2214
|
-
expect(update).to be true
|
|
2215
|
-
expect(video.category_id).not_to eq old_category_id
|
|
2216
|
-
end
|
|
2217
|
-
end
|
|
2218
|
-
end
|
|
2219
|
-
|
|
2220
|
-
context 'given I update title, description and/or tags using angle brackets' do
|
|
2221
|
-
let(:attrs) { {title: "Example Yt Test < >", description: '< >', tags: ['<tag>']} }
|
|
2222
|
-
|
|
2223
|
-
specify 'updates them replacing angle brackets with similar unicode characters accepted by YouTube' do
|
|
2224
|
-
expect(update).to be true
|
|
2225
|
-
expect(video.title).to eq 'Example Yt Test ‹ ›'
|
|
2226
|
-
expect(video.description).to eq '‹ ›'
|
|
2227
|
-
expect(video.tags).to eq ['‹tag›']
|
|
2228
|
-
end
|
|
2229
|
-
end
|
|
2230
|
-
|
|
2231
|
-
# note: 'scheduled' videos cannot be set to 'unlisted'
|
|
2232
|
-
context 'given I update the privacy status' do
|
|
2233
|
-
before { video.update publish_at: nil if video.scheduled? }
|
|
2234
|
-
let!(:new_privacy_status) { old_privacy_status == 'private' ? 'unlisted' : 'private' }
|
|
2235
|
-
|
|
2236
|
-
context 'passing the parameter in underscore syntax' do
|
|
2237
|
-
let(:attrs) { {privacy_status: new_privacy_status} }
|
|
2238
|
-
|
|
2239
|
-
specify 'only updates the privacy status' do
|
|
2240
|
-
expect(update).to be true
|
|
2241
|
-
expect(video.privacy_status).not_to eq old_privacy_status
|
|
2242
|
-
expect(video.title).to eq old_title
|
|
2243
|
-
end
|
|
2244
|
-
end
|
|
2245
|
-
|
|
2246
|
-
context 'passing the parameter in camel-case syntax' do
|
|
2247
|
-
let(:attrs) { {privacyStatus: new_privacy_status} }
|
|
2248
|
-
|
|
2249
|
-
specify 'only updates the privacy status' do
|
|
2250
|
-
expect(update).to be true
|
|
2251
|
-
expect(video.privacy_status).not_to eq old_privacy_status
|
|
2252
|
-
expect(video.title).to eq old_title
|
|
2253
|
-
end
|
|
2254
|
-
end
|
|
2255
|
-
end
|
|
2256
|
-
|
|
2257
|
-
context 'given I update the embeddable status' do
|
|
2258
|
-
let!(:old_embeddable) { video.embeddable? }
|
|
2259
|
-
let!(:new_embeddable) { !old_embeddable }
|
|
2260
|
-
|
|
2261
|
-
let(:attrs) { {embeddable: new_embeddable} }
|
|
2262
|
-
|
|
2263
|
-
# @note: This test is a reflection of another irrational behavior of
|
|
2264
|
-
# YouTube API. Although 'embeddable' can be passed as an 'update'
|
|
2265
|
-
# attribute according to the documentation, it simply does not work.
|
|
2266
|
-
# The day YouTube fixes it, then this test will finally fail and will
|
|
2267
|
-
# be removed, documenting how to update 'embeddable' too.
|
|
2268
|
-
# @see https://developers.google.com/youtube/v3/docs/videos/update
|
|
2269
|
-
# @see https://code.google.com/p/gdata-issues/issues/detail?id=4861
|
|
2270
|
-
specify 'does not update the embeddable status' do
|
|
2271
|
-
expect(update).to be true
|
|
2272
|
-
expect(video.embeddable?).to eq old_embeddable
|
|
2273
|
-
end
|
|
2274
|
-
end
|
|
2275
|
-
|
|
2276
|
-
context 'given I update the public stats viewable setting' do
|
|
2277
|
-
let!(:old_public_stats_viewable) { video.has_public_stats_viewable? }
|
|
2278
|
-
let!(:new_public_stats_viewable) { !old_public_stats_viewable }
|
|
2279
|
-
|
|
2280
|
-
context 'passing the parameter in underscore syntax' do
|
|
2281
|
-
let(:attrs) { {public_stats_viewable: new_public_stats_viewable} }
|
|
2282
|
-
|
|
2283
|
-
specify 'only updates the public stats viewable setting' do
|
|
2284
|
-
expect(update).to be true
|
|
2285
|
-
expect(video.has_public_stats_viewable?).to eq new_public_stats_viewable
|
|
2286
|
-
expect(video.privacy_status).to eq old_privacy_status
|
|
2287
|
-
expect(video.title).to eq old_title
|
|
2288
|
-
end
|
|
2289
|
-
end
|
|
2290
|
-
|
|
2291
|
-
context 'passing the parameter in camel-case syntax' do
|
|
2292
|
-
let(:attrs) { {publicStatsViewable: new_public_stats_viewable} }
|
|
2293
|
-
|
|
2294
|
-
specify 'only updates the public stats viewable setting' do
|
|
2295
|
-
expect(update).to be true
|
|
2296
|
-
expect(video.has_public_stats_viewable?).to eq new_public_stats_viewable
|
|
2297
|
-
expect(video.privacy_status).to eq old_privacy_status
|
|
2298
|
-
expect(video.title).to eq old_title
|
|
2299
|
-
end
|
|
2300
|
-
end
|
|
2301
|
-
end
|
|
2302
|
-
|
|
2303
|
-
it 'returns valid reports for video-related metrics' do
|
|
2304
|
-
# Some reports are only available to Content Owners.
|
|
2305
|
-
# See content owner test for more details about what the methods return.
|
|
2306
|
-
expect{video.views}.not_to raise_error
|
|
2307
|
-
expect{video.comments}.not_to raise_error
|
|
2308
|
-
expect{video.likes}.not_to raise_error
|
|
2309
|
-
expect{video.dislikes}.not_to raise_error
|
|
2310
|
-
expect{video.shares}.not_to raise_error
|
|
2311
|
-
expect{video.subscribers_gained}.not_to raise_error
|
|
2312
|
-
expect{video.subscribers_lost}.not_to raise_error
|
|
2313
|
-
expect{video.videos_added_to_playlists}.not_to raise_error
|
|
2314
|
-
expect{video.videos_removed_from_playlists}.not_to raise_error
|
|
2315
|
-
expect{video.estimated_minutes_watched}.not_to raise_error
|
|
2316
|
-
expect{video.average_view_duration}.not_to raise_error
|
|
2317
|
-
expect{video.average_view_percentage}.not_to raise_error
|
|
2318
|
-
expect{video.annotation_clicks}.not_to raise_error
|
|
2319
|
-
expect{video.annotation_click_through_rate}.not_to raise_error
|
|
2320
|
-
expect{video.annotation_close_rate}.not_to raise_error
|
|
2321
|
-
expect{video.viewer_percentage}.not_to raise_error
|
|
2322
|
-
expect{video.estimated_revenue}.to raise_error Yt::Errors::Unauthorized
|
|
2323
|
-
expect{video.ad_impressions}.to raise_error Yt::Errors::Unauthorized
|
|
2324
|
-
expect{video.monetized_playbacks}.to raise_error Yt::Errors::Unauthorized
|
|
2325
|
-
expect{video.playback_based_cpm}.to raise_error Yt::Errors::Unauthorized
|
|
2326
|
-
expect{video.advertising_options_set}.to raise_error Yt::Errors::Forbidden
|
|
2327
|
-
end
|
|
2328
|
-
end
|
|
2329
|
-
|
|
2330
|
-
# @note: This test is separated from the block above because, for some
|
|
2331
|
-
# undocumented reasons, if an existing video was private, then set to
|
|
2332
|
-
# unlisted, then set to private again, YouTube _sometimes_ raises a
|
|
2333
|
-
# 400 Error when trying to set the publishAt timestamp.
|
|
2334
|
-
# Therefore, just to test the updating of publishAt, we use a brand new
|
|
2335
|
-
# video (set to private), rather than reusing an existing one as above.
|
|
2336
|
-
context 'given one of my own *private* videos that I want to update' do
|
|
2337
|
-
before { @tmp_video = $account.upload_video 'https://bit.ly/yt_test', title: old_title, privacy_status: old_privacy_status }
|
|
2338
|
-
let(:id) { @tmp_video.id }
|
|
2339
|
-
let!(:old_title) { "Yt Test Update publishAt Video #{rand}" }
|
|
2340
|
-
let!(:old_privacy_status) { 'private' }
|
|
2341
|
-
after { video.delete }
|
|
2342
|
-
|
|
2343
|
-
let!(:new_scheduled_at) { Yt::Timestamp.parse("#{rand(30) + 1} Jan 2020", Time.now) }
|
|
2344
|
-
|
|
2345
|
-
context 'passing the parameter in underscore syntax' do
|
|
2346
|
-
let(:attrs) { {publish_at: new_scheduled_at} }
|
|
2347
|
-
|
|
2348
|
-
specify 'only updates the timestamp to publish the video' do
|
|
2349
|
-
expect(video.update attrs).to be true
|
|
2350
|
-
expect(video.privacy_status).to eq old_privacy_status
|
|
2351
|
-
expect(video.title).to eq old_title
|
|
2352
|
-
# NOTE: This is another irrational behavior of YouTube API. In short,
|
|
2353
|
-
# the response of Video#update *does not* include the publishAt value
|
|
2354
|
-
# even if it exists. You need to call Video#list again to get it.
|
|
2355
|
-
video = Yt::Video.new id: id, auth: $account
|
|
2356
|
-
expect(video.scheduled_at).to eq new_scheduled_at
|
|
2357
|
-
# Setting a private (scheduled) video to private has no effect:
|
|
2358
|
-
expect(video.update privacy_status: 'private').to be true
|
|
2359
|
-
video = Yt::Video.new id: id, auth: $account
|
|
2360
|
-
expect(video.scheduled_at).to eq new_scheduled_at
|
|
2361
|
-
# Setting a private (scheduled) video to unlisted/public removes publishAt:
|
|
2362
|
-
expect(video.update privacy_status: 'unlisted').to be true
|
|
2363
355
|
video = Yt::Video.new id: id, auth: $account
|
|
2364
|
-
expect(video.scheduled_at).to be_nil
|
|
2365
|
-
end
|
|
2366
|
-
end
|
|
2367
|
-
|
|
2368
|
-
context 'passing the parameter in camel-case syntax' do
|
|
2369
|
-
let(:attrs) { {publishAt: new_scheduled_at} }
|
|
2370
|
-
|
|
2371
|
-
specify 'only updates the timestamp to publish the video' do
|
|
2372
|
-
expect(video.update attrs).to be true
|
|
2373
356
|
expect(video.scheduled_at).to eq new_scheduled_at
|
|
2374
357
|
expect(video.privacy_status).to eq old_privacy_status
|
|
2375
358
|
expect(video.title).to eq old_title
|
|
@@ -2408,18 +391,18 @@ describe Yt::Video, :device_app do
|
|
|
2408
391
|
# @note: This test is separated from the block above because YouTube only
|
|
2409
392
|
# returns file details for *some videos*: "The fileDetails object will
|
|
2410
393
|
# only be returned if the processingDetails.fileAvailability property
|
|
2411
|
-
# has a value of available."
|
|
2412
|
-
# different video
|
|
2413
|
-
#
|
|
2414
|
-
# therefore video.file_name is not returned by Yt, until it can be tested.
|
|
394
|
+
# has a value of 'available'." Therefore, just to test fileDetails, we use a
|
|
395
|
+
# different video (I couldn't find any video marked as 'available').
|
|
396
|
+
# @see https://developers.google.com/youtube/v3/docs/videos#fileDetails
|
|
2415
397
|
# @see https://developers.google.com/youtube/v3/docs/videos#processingDetails.fileDetailsAvailability
|
|
2416
|
-
context 'given one of my own
|
|
2417
|
-
let(:id) { '
|
|
398
|
+
context 'given one of my own videos' do
|
|
399
|
+
let(:id) { 'nHz3FnAMH3U' }
|
|
2418
400
|
|
|
2419
401
|
it 'returns valid file details' do
|
|
2420
|
-
expect(video.
|
|
2421
|
-
expect(video.
|
|
2422
|
-
expect(video.
|
|
402
|
+
expect(video.file_name).to be_a String
|
|
403
|
+
expect(video.file_size).to be_nil
|
|
404
|
+
expect(video.file_type).to be_nil
|
|
405
|
+
expect(video.container).to be_nil
|
|
2423
406
|
end
|
|
2424
407
|
end
|
|
2425
408
|
end
|