yt 0.32.2 → 0.32.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -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' ? '21' : '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
- # @see https://code.google.com/p/gdata-issues/issues/detail?id=4861
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 old_embeddable
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.". Therefore, just to test fileDetails, we use a
2412
- # different video that (for some unknown reason) is marked as 'available'.
2413
- # Also note that I was not able to find a single video returning fileName,
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 *available* videos' do
2417
- let(:id) { 'yCmaOvUFhlI' }
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.file_size).to be_an Integer
2421
- expect(video.file_type).to be_a String
2422
- expect(video.container).to be_a String
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