diff --git a/src/integration/movies/local.spec.ts b/src/integration/movies/internal.spec.ts similarity index 91% rename from src/integration/movies/local.spec.ts rename to src/integration/movies/internal.spec.ts index d3da06f..ecc9f2a 100644 --- a/src/integration/movies/local.spec.ts +++ b/src/integration/movies/internal.spec.ts @@ -1,9 +1,9 @@ import { describe, it, expect } from '@jest/globals'; -import { createLocalMoviesClient } from './local'; +import { createInternalMoviesClient } from './internal'; -describe('createLocalMoviesClient', () => { - const client = createLocalMoviesClient(); +describe('createInternalMoviesClient', () => { + const client = createInternalMoviesClient(); it('returns data for internal movie id', async () => { const result = await client.getMovieMetadataByInternalId(11528860); diff --git a/src/integration/movies/local.ts b/src/integration/movies/internal.ts similarity index 53% rename from src/integration/movies/local.ts rename to src/integration/movies/internal.ts index 29a3c72..73e300c 100644 --- a/src/integration/movies/local.ts +++ b/src/integration/movies/internal.ts @@ -2,12 +2,15 @@ import movie1 from '../../resources/movies/3532674.json'; import movie2 from '../../resources/movies/5979300.json'; import movie3 from '../../resources/movies/11043689.json'; import movie4 from '../../resources/movies/11528860.json'; +import { InternalMoviesProvider } from './types'; const movies = [movie1, movie2, movie3, movie4]; -export const createLocalMoviesClient = () => { +export const createInternalMoviesClient = (): InternalMoviesProvider => { return { - getMovieMetadataByInternalId: async (internalId: number) => movies.find(({ id }) => id == internalId), - getMovieMetadataByImdbId: async (id: string) => movies.find(({ imdbId }) => imdbId == id), + getMovieMetadataByInternalId: async (internalId: number) => + Promise.resolve(movies.find(({ id }) => id == internalId)), + getMovieMetadataByImdbId: async (id: string) => + Promise.resolve(movies.find(({ imdbId }) => imdbId == id)), }; }; diff --git a/src/integration/movies/omdb.spec.ts b/src/integration/movies/omdb.spec.ts index e682ff0..c933254 100644 --- a/src/integration/movies/omdb.spec.ts +++ b/src/integration/movies/omdb.spec.ts @@ -7,31 +7,35 @@ describe('createOmdbClient', () => { it('returns some data for the sample movie id', async () => { const result = await client.getMovieMetadata('tt11873472'); expect(result).toMatchObject({ - 'Actors': 'Cheryl Isheja, Elvis Ngabo, Diogène Ntarindwa', - 'Awards': expect.any(String), - 'BoxOffice': expect.any(String), - 'Country': 'Rwanda, France, Canada, United Kingdom, United States', - 'DVD': expect.any(String), - 'Director': 'Anisia Uzeyman, Saul Williams', - 'Genre': 'Musical, Sci-Fi', - 'Language': 'Kinyarwanda, Kirundi, Swahili, French, English', - 'Metascore': expect.stringMatching(/^\d+$/), - 'Plot': expect.any(String), - 'Poster': expect.stringMatching(/^http/), - 'Production': expect.any(String), - 'Rated': expect.any(String), - 'Ratings': expect.any(Array), - 'Released': '10 May 2023', - 'Response': 'True', - 'Runtime': '105 min', - 'Title': 'Neptune Frost', - 'Type': 'movie', - 'Website': expect.any(String), - 'Writer': 'Saul Williams', - 'Year': '2021', - 'imdbID': 'tt11873472', - 'imdbRating': expect.stringMatching(/^\d?\d(\.\d)?$/), - 'imdbVotes': expect.stringMatching(/^\d+$/), + Actors: 'Cheryl Isheja, Elvis Ngabo, Diogène Ntarindwa', + Awards: expect.any(String), + BoxOffice: expect.any(String), + Country: 'Rwanda, France, Canada, United Kingdom, United States', + DVD: expect.any(String), + Director: 'Anisia Uzeyman, Saul Williams', + Genre: 'Musical, Sci-Fi', + Language: 'Kinyarwanda, Kirundi, Swahili, French, English', + Metascore: expect.stringMatching(/^\d+$/), + Plot: expect.any(String), + Poster: expect.stringMatching(/^http/), + Production: expect.any(String), + Rated: expect.any(String), + Ratings: expect.any(Array), + Released: '10 May 2023', + Runtime: '105 min', + Title: 'Neptune Frost', + Type: 'movie', + Website: expect.any(String), + Writer: 'Saul Williams', + Year: '2021', + imdbID: 'tt11873472', + imdbRating: expect.stringMatching(/^\d?\d(\.\d)?$/), + imdbVotes: expect.stringMatching(/^\d+$/), }); }, 10_000); + + it('returns undefined for non-existend movie id', async () => { + const result = await client.getMovieMetadata('tt99999999999'); + expect(result).toBeUndefined(); + }, 10_000); }); diff --git a/src/integration/movies/omdb.ts b/src/integration/movies/omdb.ts index a045d3c..d343cd9 100644 --- a/src/integration/movies/omdb.ts +++ b/src/integration/movies/omdb.ts @@ -1,7 +1,17 @@ import fetch from 'node-fetch'; import PQueue from 'p-queue'; +import { OmdbMovieData, OmdbMoviesProvider } from './types'; -export const createOmdbClient = (apiKey: string) => { +type OmdbResponse = + | { + Response: 'False'; + Error: string; + } + | (OmdbMovieData & { + Response: 'True'; + }); + +export const createOmdbClient = (apiKey: string): OmdbMoviesProvider => { // Rate limit (according to readme, it's 1k per day; // here we set it 1 per second, should be enough for the demo app goal) const queue = new PQueue({ @@ -18,8 +28,23 @@ export const createOmdbClient = (apiKey: string) => { url.searchParams.append('i', imdbId); url.searchParams.append('apikey', apiKey); url.searchParams.append('plot', 'full'); + const response = await fetch(url); - const body = (await response.json()) as unknown; + if (response.status == 404) { + return undefined; + } + + const body = (await response.json()) as OmdbResponse; + if (body.Response !== 'True') { + if (body.Error === 'Incorrect IMDb ID.') { + return undefined; + } + + throw new Error( + `Cannot fetch data from OMDB: ${body.Error}`, + ); + } + return body; }), }; diff --git a/src/integration/movies/types.ts b/src/integration/movies/types.ts new file mode 100644 index 0000000..756f53a --- /dev/null +++ b/src/integration/movies/types.ts @@ -0,0 +1,77 @@ +export type OmdbMovieData = { + Actors: string; + Awards: string; + BoxOffice: string; + Country: string; + DVD: string; + Director: string; + Genre: string; + Language: string; + Metascore: string; + Plot: string; + Poster: string; + Production: string; + Rated: string; + Ratings: { + Source: string; + Value: string; + }[]; + Released: string; + Runtime: string; + Title: string; + Type: string; + Website: string; + Writer: string; + Year: string; + imdbID: string; + imdbRating: string; + imdbVotes: string; +}; + +type InternalMovieData = { + /** + * Description in local language + */ + description: string; + /** + * A whole number of minutes, presumably + */ + duration: number; + id: number; + imdbId: string; + /** + * Two-letter codes + */ + languages: string[]; + /** + * Two-letter code + */ + originalLanguage: string; + productionYear: number; + studios: string[]; + /** + * Title in local language + */ + title: string; + userrating: { + countStar1: number; + countStar2: number; + countStar3: number; + countStar4: number; + countStar5: number; + countTotal: number; + }; +}; + +export type InternalMoviesProvider = { + getMovieMetadataByInternalId( + internalId: number, + ): Promise; + getMovieMetadataByImdbId( + imdbId: string, + ): Promise; +}; + +export type OmdbMoviesProvider = { + getMovieMetadata(imdbId: string): Promise; +};