import { Test, TestingModule } from '@nestjs/testing' import { datatype, random } from 'faker' import { cloneDeep, sample } from 'lodash' import { countryByCode, CountryCode, CountryHelper } from '../../../utils/countries' import { factory } from '../../../../test/factories' import { Firebender } from '../../../services/firebender' import { MockedModule } from '../../../../test/types' import { VideoPlaylist, VideoPlaylistCarouselDirection } from '../../../models' import { VideoPlaylistRepository, VideoRepository } from '../../../repositories' import { Youtube, YoutubePlaylist, YoutubeVideo } from '../../../services/google' import { VideoLibrary } from './video_library' describe('VideoLibrary', () => { let subject: VideoLibrary let videoPlaylistRepository: MockedModule let videoRepository: MockedModule let youtube: MockedModule let firebender: any let videotecaConfig: any beforeAll(async () => { videoPlaylistRepository = { create: jest.fn(), save: jest.fn(), deleteAll: jest.fn(), } videoRepository = { create: jest.fn(), } youtube = { findVideosByPlaylistId: jest.fn(), getPlaylistById: jest.fn(), } firebender = { firebaseAdmin: { remoteConfig: () => ({ getTemplate: jest.fn(), }), }, } videotecaConfig = { pt_BR: { freemium_videos_count: 10, featured: { title: random.words(), video_ids: [datatype.uuid()], }, playlists: [ { title: random.words(), carousel_direction: sample(Object.keys(VideoPlaylistCarouselDirection)), playlist_id: datatype.uuid(), featured_videos: [datatype.uuid()], order: 1, }, ], }, } const app: TestingModule = await Test.createTestingModule({ controllers: [], providers: [ { provide: VideoPlaylistRepository, useValue: videoPlaylistRepository, }, { provide: VideoRepository, useValue: videoRepository, }, { provide: Youtube, useValue: youtube, }, { provide: Firebender, useValue: firebender, }, VideoLibrary, ], }).compile() subject = app.get(VideoLibrary) }) afterEach(() => { jest.resetAllMocks() }) describe('#saveVideo', () => { let youtubeVideo: YoutubeVideo let videoPlaylist: VideoPlaylist const featured = true const featuredMain = false beforeAll(async () => { youtubeVideo = await factory.build('youtube_video_data') videoPlaylist = await factory.build('video_playlist') youtubeVideo.snippet.publishedAt = new Date(youtubeVideo.snippet.publishedAt) }) describe('when video does not exist', () => { it('saves the video', async () => { await subject['saveVideo'](youtubeVideo, videoPlaylist, featuredMain, featured) expect(videoRepository.create).toBeCalledWith( expect.objectContaining({ external_id: youtubeVideo.id, title: youtubeVideo.snippet.title, thumb_url: youtubeVideo.snippet.thumbnails.standard.url, description: youtubeVideo.snippet.description, country_code: videoPlaylist.country_code, playlist_id: videoPlaylist.id, uploaded_at: youtubeVideo.snippet.publishedAt, featured: featured, featured_main: featuredMain, }), ) }) }) }) describe('#savePlaylist', () => { let youtubePlaylist: YoutubeVideo let countryCode: CountryCode let playlist: any beforeAll(async () => { youtubePlaylist = await factory.build('youtube_video_data') countryCode = CountryHelper.parseCode(sample(Object.keys(countryByCode))) playlist = videotecaConfig.pt_BR.playlists[0] }) it('saves the playlist', async () => { await subject['savePlaylist'](youtubePlaylist, countryCode, playlist) expect(videoPlaylistRepository.create).toBeCalledWith( expect.objectContaining({ external_id: youtubePlaylist.id, title: playlist.title, carousel_direction: playlist.carousel_direction, order: playlist.order, country_code: countryCode, }), ) }) }) describe('#syncFromYoutubeAndRemoteConfig', () => { let spyGetRemoteConfig: jest.SpyInstance let youtubePlaylists: YoutubePlaylist[] let spySavePlaylist: jest.SpyInstance let spySaveVideo: jest.SpyInstance beforeAll(async () => { youtubePlaylists = [await factory.build('youtube_playlist_data')] youtubePlaylists[0].snippet.publishedAt = new Date(youtubePlaylists[0].snippet.publishedAt) }) beforeEach(() => { spyGetRemoteConfig = jest.spyOn(subject, 'getRemoteConfig') spySavePlaylist = jest.spyOn(subject, 'savePlaylist') spySaveVideo = jest.spyOn(subject, 'saveVideo') spyGetRemoteConfig.mockResolvedValue(videotecaConfig) }) describe('when does not have playlist', () => { beforeEach(() => { const newConfig = JSON.parse(JSON.stringify(videotecaConfig)) newConfig.pt_BR.playlists = [] spyGetRemoteConfig.mockResolvedValue(newConfig) }) it('does not save any playlist', async () => { await subject['syncFromYoutubeAndRemoteConfig']() expect(spySavePlaylist).not.toBeCalled() }) }) describe('when has playlist', () => { let videoPlaylist: VideoPlaylist beforeAll(async () => { videoPlaylist = await factory.build('video_playlist') }) beforeEach(() => { youtube.findVideosByPlaylistId.mockResolvedValue([]) spySavePlaylist.mockResolvedValue(videoPlaylist) }) describe('and a playlist is invalid', () => { const error = new Error('404 Not Found') beforeEach(() => { youtube.getPlaylistById.mockRejectedValue(error) jest.spyOn(console, 'error') }) it('doesn`t throw an error', async () => { await expect(subject['syncFromYoutubeAndRemoteConfig']()).resolves.not.toThrowError() }) it('consoles an error', async () => { await subject['syncFromYoutubeAndRemoteConfig']() expect(console.error).toHaveBeenCalledWith( `Error when updating playlist "${videotecaConfig.pt_BR.playlists[0].playlist_id}":"${videotecaConfig.pt_BR.playlists[0].title}"/pt_BR`, error, ) }) }) describe('and a playlist is valid', () => { beforeEach(() => { youtube.getPlaylistById.mockResolvedValueOnce(youtubePlaylists[0]) }) it('saves the playlist', async () => { await subject['syncFromYoutubeAndRemoteConfig']() expect(spySavePlaylist).toBeCalledWith(youtubePlaylists[0], 'pt_BR', videotecaConfig.pt_BR.playlists[0]) }) describe('when playlist does not have videos', () => { it('does not save any video', async () => { await subject['syncFromYoutubeAndRemoteConfig']() expect(spySaveVideo).not.toBeCalled() }) }) describe('when playlist has videos', () => { let youtubeVideos: YoutubeVideo[] beforeAll(async () => { youtubeVideos = [await factory.build('youtube_video_data')] youtubeVideos[0].snippet.publishedAt = new Date(youtubeVideos[0].snippet.publishedAt) }) beforeEach(() => { youtube.findVideosByPlaylistId.mockResolvedValueOnce(youtubeVideos) }) it('saves the video', async () => { await subject['syncFromYoutubeAndRemoteConfig']() expect(spySaveVideo).toBeCalledWith(youtubeVideos[0], videoPlaylist, false, false) }) describe('when have featured videos', () => { beforeEach(() => { const configWithFeaturedVideos = cloneDeep(videotecaConfig) configWithFeaturedVideos.pt_BR.playlists[0].featured_videos = [youtubeVideos[0].id] spyGetRemoteConfig.mockResolvedValueOnce(configWithFeaturedVideos) }) it('saves the video as featured', async () => { await subject['syncFromYoutubeAndRemoteConfig']() expect(spySaveVideo).toBeCalledWith(youtubeVideos[0], videoPlaylist, false, true) }) }) describe('when has featured main videos', () => { beforeEach(() => { const configWithFeaturedMainVideos = cloneDeep(videotecaConfig) configWithFeaturedMainVideos.pt_BR.featured.video_ids = [youtubeVideos[0].id] spyGetRemoteConfig.mockResolvedValueOnce(configWithFeaturedMainVideos) }) it('saves the video as featured main', async () => { await subject['syncFromYoutubeAndRemoteConfig']() expect(spySaveVideo).toBeCalledWith(youtubeVideos[0], videoPlaylist, true, false) }) }) }) }) }) }) describe('#sync', () => { let spySyncFromYoutubeAndRemoteConfig: jest.SpyInstance beforeEach(() => { spySyncFromYoutubeAndRemoteConfig = jest .spyOn(subject, 'syncFromYoutubeAndRemoteConfig') .mockResolvedValue(null) }) it('delete all playlists', async () => { await subject.sync() expect(videoPlaylistRepository.deleteAll).toBeCalled() }) it('sync repository with youtube channel and remote config', async () => { await subject.sync() expect(spySyncFromYoutubeAndRemoteConfig).toBeCalled() }) }) })