diff --git a/.eslintrc.cjs b/.eslintrc.cjs index 2d41d132..c1c4f993 100644 --- a/.eslintrc.cjs +++ b/.eslintrc.cjs @@ -11,6 +11,12 @@ module.exports = { 'comma-dangle': ['error', 'always-multiline'], 'no-console': ['error', { allow: ['warn', 'error'] }], indent: ['error', 2], + 'max-len': ['error', { + ignoreStrings: true, + ignoreTrailingComments: true, + ignoreUrls: true, + tabWidth: 2, + }], 'object-curly-spacing': ['error', 'always'], quotes: ['error', 'single'], semi: ['error', 'never'], diff --git a/__tests__/controllers/cacheController.spec.ts b/__tests__/controllers/cacheController.spec.ts index f9c43e2f..463bc931 100644 --- a/__tests__/controllers/cacheController.spec.ts +++ b/__tests__/controllers/cacheController.spec.ts @@ -18,6 +18,13 @@ let notionMock: NotionMock const { res, mockClear } = getMockRes() +const newCacheController = () => { + const firestore = new FirestoreAdapter() + const notion = new NotionAdapter() + const tmdbAdapter = new TmdbAdapter() + return new CacheController(firestore, notion, tmdbAdapter) +} + beforeAll(() => { jest.mock('@notionhq/client') jest.mock('firebase-admin/app') @@ -34,9 +41,6 @@ beforeEach(() => { }) describe('cache', () => { - let firestore: FirestoreAdapter - let notion: NotionAdapter - let tmdbAdapter: TmdbAdapter let req: Request describe('when the cache is empty', () => { @@ -47,14 +51,11 @@ describe('cache', () => { NotionMock.mockWeek('id2', '2021-01-08', 'theme2'), NotionMock.mockWeek('id3', '2021-01-15', 'theme3'), ]) - firestore = new FirestoreAdapter() - notion = new NotionAdapter() - tmdbAdapter = new TmdbAdapter() req = getMockReq() }) it('updates all weeks in firestore', async () => { - const cacheController = new CacheController(firestore, notion, tmdbAdapter) + const cacheController = newCacheController() await cacheController.cache(req, res) @@ -107,9 +108,6 @@ describe('cache', () => { NotionMock.mockWeek('id1', '2021-01-01', 'theme1', false, [notionResponse]), ]) notionMock.mockRetrieve(notionResponse) - firestore = new FirestoreAdapter() - notion = new NotionAdapter() - tmdbAdapter = new TmdbAdapter() req = getMockReq() const tmdbMock = new TmdbMock(mockFetch()) tmdbMock.mockSearchMovie(tmdb) @@ -117,7 +115,7 @@ describe('cache', () => { }) it('stores data from tmdb in firestore', async () => { - const cacheController = new CacheController(firestore, notion, tmdbAdapter) + const cacheController = newCacheController() await cacheController.cache(req, res) diff --git a/__tests__/support/fetchMock.ts b/__tests__/support/fetchMock.ts index e76f1ef7..33328131 100644 --- a/__tests__/support/fetchMock.ts +++ b/__tests__/support/fetchMock.ts @@ -1,9 +1,14 @@ import { jest } from '@jest/globals' -export type MockFetch = jest.Mock<(input: RequestInfo | URL, init?: RequestInit | undefined) => Promise> +type FetchFunction = ( + input: RequestInfo | URL, + init?: RequestInit | undefined +) => Promise; + +export type MockFetch = jest.Mock export function mockFetch (): MockFetch { - const mockFetch = jest.fn<(input: RequestInfo | URL, init?: RequestInit | undefined) => Promise>() + const mockFetch = jest.fn() global.fetch = mockFetch return mockFetch diff --git a/__tests__/support/notionMock.ts b/__tests__/support/notionMock.ts index 6e1f8211..58024f1e 100644 --- a/__tests__/support/notionMock.ts +++ b/__tests__/support/notionMock.ts @@ -31,7 +31,9 @@ export class NotionMock { mockRetrieve = (movie: NotionMovie | undefined = undefined) => { const notionMovie = movie ?? NotionMovie.demo() - this.retrieve.mockImplementation(async (args: WithAuth): Promise => { + this.retrieve.mockImplementation(async ( + args: WithAuth + ): Promise => { const { page_id } = args as { page_id: string } if (page_id !== notionMovie.id) { @@ -45,7 +47,9 @@ export class NotionMock { mockQuery = (weeks: PageObjectResponse[] = []) => { this.query.mockImplementation( - async (_args: WithAuth): Promise => ({ + async ( + _args: WithAuth + ): Promise => ({ page_or_database: {}, type: 'page_or_database', object: 'list', diff --git a/__tests__/support/tmdbMock.ts b/__tests__/support/tmdbMock.ts index 5a71079c..8fda67f2 100644 --- a/__tests__/support/tmdbMock.ts +++ b/__tests__/support/tmdbMock.ts @@ -8,163 +8,165 @@ export class TmdbMock { ) {} mockSearchMovie (movie: Movie, id = 1234) { - this.mockFetch.mockImplementationOnce(async () => new Response(JSON.stringify({ - page: 1, - results: [ - { - id: id, - original_title: movie.title, - poster_path: movie.posterUrl?.replace(TMDB_POSTER_URL, ''), - release_date: `${movie.year}-07-19`, - title: movie.title, - adult: false, - backdrop_path: '/2FonLz0RPxbBriOlZ9mWhYdlqCp.jpg', - genre_ids: [ 35, 10749 ], - original_language: 'en', - overview: 'Shallow, rich and socially successful Cher is at the top of her Beverly Hills high school\'s pecking scale. Seeing herself as a matchmaker, Cher first coaxes two teachers into dating each other. Emboldened by her success, she decides to give hopelessly klutzy new student Tai a makeover. When Tai becomes more popular than she is, Cher realizes that her disapproving ex-stepbrother was right about how misguided she was -- and falls for him.', - popularity: 32.244, - video: false, - vote_average: 7.282, - vote_count: 3953, - }, - ], - total_pages: 1, - total_results: 9, - }))) + this.mockFetch + .mockImplementationOnce(async () => new Response(JSON.stringify({ + page: 1, + results: [ + { + id: id, + original_title: movie.title, + poster_path: movie.posterUrl?.replace(TMDB_POSTER_URL, ''), + release_date: `${movie.year}-07-19`, + title: movie.title, + adult: false, + backdrop_path: '/2FonLz0RPxbBriOlZ9mWhYdlqCp.jpg', + genre_ids: [ 35, 10749 ], + original_language: 'en', + overview: 'Shallow, rich and socially successful Cher is at the top of her Beverly Hills high school\'s pecking scale. Seeing herself as a matchmaker, Cher first coaxes two teachers into dating each other. Emboldened by her success, she decides to give hopelessly klutzy new student Tai a makeover. When Tai becomes more popular than she is, Cher realizes that her disapproving ex-stepbrother was right about how misguided she was -- and falls for him.', + popularity: 32.244, + video: false, + vote_average: 7.282, + vote_count: 3953, + }, + ], + total_pages: 1, + total_results: 9, + }))) } mockMovieDetails = (movie: Movie, id = 1234) => { - this.mockFetch.mockImplementationOnce(async () => new Response(JSON.stringify({ - id: id, - original_title: movie.title, - poster_path: movie.posterUrl?.replace(TMDB_POSTER_URL, ''), - release_date: `${movie.year}-07-19`, - title: movie.title, - adult: false, - backdrop_path: '/2FonLz0RPxbBriOlZ9mWhYdlqCp.jpg', - belongs_to_collection: null, - budget: 12000000, - genres: [ - { - id: 35, - name: 'Comedy', - }, - { - id: 10749, - name: 'Romance', - }, - ], - homepage: '', - imdb_id: 'tt0112697', - original_language: 'en', - overview: 'Shallow, rich and socially successful Cher is at the top of her Beverly Hills high school\'s pecking scale. Seeing herself as a matchmaker, Cher first coaxes two teachers into dating each other. Emboldened by her success, she decides to give hopelessly klutzy new student Tai a makeover. When Tai becomes more popular than she is, Cher realizes that her disapproving ex-stepbrother was right about how misguided she was -- and falls for him.', - popularity: 32.244, - production_companies: [ - { - id: 4, - logo_path: '/gz66EfNoYPqHTYI4q9UEN4CbHRc.png', - name: 'Paramount', - origin_country: 'US', - }, - ], - production_countries: [ - { - iso_3166_1: 'US', - name: 'United States of America', - }, - ], - revenue: 56631572, - runtime: movie.length, - spoken_languages: [ - { - english_name: 'Spanish', - iso_639_1: 'es', - name: 'Español', - }, - { - english_name: 'English', - iso_639_1: 'en', - name: 'English', - }, - ], - status: 'Released', - tagline: 'Sex. Clothes. Popularity. Is there a problem here?', - video: false, - vote_average: 7.282, - vote_count: 3953, - credits: { - cast: [ + this.mockFetch + .mockImplementationOnce(async () => new Response(JSON.stringify({ + id: id, + original_title: movie.title, + poster_path: movie.posterUrl?.replace(TMDB_POSTER_URL, ''), + release_date: `${movie.year}-07-19`, + title: movie.title, + adult: false, + backdrop_path: '/2FonLz0RPxbBriOlZ9mWhYdlqCp.jpg', + belongs_to_collection: null, + budget: 12000000, + genres: [ { - adult: false, - gender: 1, - id: 5588, - known_for_department: 'Acting', - name: 'Alicia Silverstone', - original_name: 'Alicia Silverstone', - popularity: 23.419, - profile_path: '/pyxqkP4i0ubVdoRe5hoiiiwkHkb.jpg', - cast_id: 8, - character: 'Cher Horowitz', - credit_id: '52fe4510c3a36847f80ba283', - order: 0, + id: 35, + name: 'Comedy', }, { - adult: false, - gender: 1, - id: 58150, - known_for_department: 'Acting', - name: 'Stacey Dash', - original_name: 'Stacey Dash', - popularity: 9.118, - profile_path: '/mn9QUB95Hxkk5hVjKTZlSAMWGin.jpg', - cast_id: 9, - character: 'Dionne Davenport', - credit_id: '52fe4510c3a36847f80ba287', - order: 1, + id: 10749, + name: 'Romance', }, ], - crew: [ + homepage: '', + imdb_id: 'tt0112697', + original_language: 'en', + overview: 'Shallow, rich and socially successful Cher is at the top of her Beverly Hills high school\'s pecking scale. Seeing herself as a matchmaker, Cher first coaxes two teachers into dating each other. Emboldened by her success, she decides to give hopelessly klutzy new student Tai a makeover. When Tai becomes more popular than she is, Cher realizes that her disapproving ex-stepbrother was right about how misguided she was -- and falls for him.', + popularity: 32.244, + production_companies: [ { - adult: false, - gender: 2, - id: 2997, - known_for_department: 'Production', - name: 'Scott Rudin', - original_name: 'Scott Rudin', - popularity: 1.247, - profile_path: '/zIeKeFgBERBHmabgmqZFmgcxqvO.jpg', - credit_id: '52fe4510c3a36847f80ba273', - department: 'Production', - job: 'Producer', + id: 4, + logo_path: '/gz66EfNoYPqHTYI4q9UEN4CbHRc.png', + name: 'Paramount', + origin_country: 'US', }, + ], + production_countries: [ { - adult: false, - gender: 0, - id: 7240, - known_for_department: 'Sound', - name: 'Cary Weitz', - original_name: 'Cary Weitz', - popularity: 0.694, - profile_path: null, - credit_id: '60db8cc4a12856005eaa867f', - department: 'Sound', - job: 'Boom Operator', + iso_3166_1: 'US', + name: 'United States of America', }, + ], + revenue: 56631572, + runtime: movie.length, + spoken_languages: [ { - adult: false, - gender: 1, - id: 57434, - known_for_department: 'Directing', - name: movie.director, - original_name: movie.director, - popularity: 7.914, - profile_path: '/hIc3bQxLOPAcpGJ1CVFuzpzJRZ0.jpg', - credit_id: '52fe4510c3a36847f80ba261', - department: 'Directing', - job: 'Director', + english_name: 'Spanish', + iso_639_1: 'es', + name: 'Español', + }, + { + english_name: 'English', + iso_639_1: 'en', + name: 'English', }, ], - }, - }))) + status: 'Released', + tagline: 'Sex. Clothes. Popularity. Is there a problem here?', + video: false, + vote_average: 7.282, + vote_count: 3953, + credits: { + cast: [ + { + adult: false, + gender: 1, + id: 5588, + known_for_department: 'Acting', + name: 'Alicia Silverstone', + original_name: 'Alicia Silverstone', + popularity: 23.419, + profile_path: '/pyxqkP4i0ubVdoRe5hoiiiwkHkb.jpg', + cast_id: 8, + character: 'Cher Horowitz', + credit_id: '52fe4510c3a36847f80ba283', + order: 0, + }, + { + adult: false, + gender: 1, + id: 58150, + known_for_department: 'Acting', + name: 'Stacey Dash', + original_name: 'Stacey Dash', + popularity: 9.118, + profile_path: '/mn9QUB95Hxkk5hVjKTZlSAMWGin.jpg', + cast_id: 9, + character: 'Dionne Davenport', + credit_id: '52fe4510c3a36847f80ba287', + order: 1, + }, + ], + crew: [ + { + adult: false, + gender: 2, + id: 2997, + known_for_department: 'Production', + name: 'Scott Rudin', + original_name: 'Scott Rudin', + popularity: 1.247, + profile_path: '/zIeKeFgBERBHmabgmqZFmgcxqvO.jpg', + credit_id: '52fe4510c3a36847f80ba273', + department: 'Production', + job: 'Producer', + }, + { + adult: false, + gender: 0, + id: 7240, + known_for_department: 'Sound', + name: 'Cary Weitz', + original_name: 'Cary Weitz', + popularity: 0.694, + profile_path: null, + credit_id: '60db8cc4a12856005eaa867f', + department: 'Sound', + job: 'Boom Operator', + }, + { + adult: false, + gender: 1, + id: 57434, + known_for_department: 'Directing', + name: movie.director, + original_name: movie.director, + popularity: 7.914, + profile_path: '/hIc3bQxLOPAcpGJ1CVFuzpzJRZ0.jpg', + credit_id: '52fe4510c3a36847f80ba261', + department: 'Directing', + job: 'Director', + }, + ], + }, + }))) } } diff --git a/src/application.ts b/src/application.ts index 8a61e9f0..8c5c8b24 100644 --- a/src/application.ts +++ b/src/application.ts @@ -21,7 +21,11 @@ class Application { * This currently only works for GET requests */ routes (): Map void> { - const cacheController = new CacheController(this.firestore, this.notion, this.tmdb) + const cacheController = new CacheController( + this.firestore, + this.notion, + this.tmdb + ) const weekController = new WeekController(this.firestore) return new Map([ diff --git a/src/data/firestoreAdapter.ts b/src/data/firestoreAdapter.ts index 9187ac54..9796df07 100644 --- a/src/data/firestoreAdapter.ts +++ b/src/data/firestoreAdapter.ts @@ -39,7 +39,10 @@ export default class FirestoreAdapter { return this.getWeeks(where('date', '>=', this.today()), orderBy('date')) } - async getWeeks (where: QueryFieldFilterConstraint, constraint: QueryConstraint): Promise { + async getWeeks ( + where: QueryFieldFilterConstraint, + constraint: QueryConstraint, + ): Promise { const weeks = collection(this.#firestore, FirestoreAdapter.COLLECTION_NAME) const q = query(weeks, where, constraint) const querySnapshot = await getDocs(q) diff --git a/src/data/notionAdapter.ts b/src/data/notionAdapter.ts index 9222f717..f1c19523 100644 --- a/src/data/notionAdapter.ts +++ b/src/data/notionAdapter.ts @@ -61,9 +61,7 @@ export default class NotionAdapter { .map(async (record) => await this.recordToWeek(record))) } - async recordToWeek ( - record: PageObjectResponse | PartialPageObjectResponse | PartialDatabaseObjectResponse | DatabaseObjectResponse - ): Promise { + async recordToWeek (record: NotionQueryResponse): Promise { if (!isFullPageOrDatabase(record)) { throw new Error('Page was not successfully retrieved') } @@ -92,3 +90,9 @@ export default class NotionAdapter { return { NOTION_TOKEN, DATABASE_ID } } } + +type NotionQueryResponse = + PageObjectResponse | + PartialPageObjectResponse | + PartialDatabaseObjectResponse | + DatabaseObjectResponse diff --git a/src/data/tmdb/dtos/responseTypes.ts b/src/data/tmdb/dtos/responseTypes.ts index ca241729..106fd5d0 100644 --- a/src/data/tmdb/dtos/responseTypes.ts +++ b/src/data/tmdb/dtos/responseTypes.ts @@ -34,7 +34,9 @@ export type SearchResponseTmdb = { total_results: number, } -export function isMovieResponseTmdb (movie: unknown): movie is MovieResponseTmdb { +export function isMovieResponseTmdb ( + movie: unknown +): movie is MovieResponseTmdb { return ( !!movie && typeof movie === 'object' && @@ -75,7 +77,8 @@ function isCreditsTmdb (credits: unknown): credits is CreditsTmdb { typeof credits === 'object' && 'crew' in credits && Array.isArray(credits.crew) && - credits.crew.reduce((acc: boolean, crew: unknown) => acc && isCrewResponseTmdb(crew), true) + credits.crew.reduce( + (acc: boolean, crew: unknown) => acc && isCrewResponseTmdb(crew), true) ) } @@ -90,7 +93,9 @@ export function isCrewResponseTmdb (crew: unknown): crew is CrewResponseTmdb { ) } -export function isSearchResponseTmdb (response: unknown): response is SearchResponseTmdb { +export function isSearchResponseTmdb ( + response: unknown +): response is SearchResponseTmdb { return ( !!response && typeof response === 'object' && @@ -100,7 +105,10 @@ export function isSearchResponseTmdb (response: unknown): response is SearchResp 'total_results' in response && typeof response.page === 'number' && Array.isArray(response.results) && - response.results.reduce((acc: boolean, movie: unknown) => acc && isMovieResponseTmdb(movie), true) && + response.results.reduce( + (acc: boolean, movie: unknown) => acc && isMovieResponseTmdb(movie), + true + ) && typeof response.total_pages === 'number' && typeof response.total_results === 'number' ) diff --git a/src/models/week.ts b/src/models/week.ts index 2698a2e9..b49c9db4 100644 --- a/src/models/week.ts +++ b/src/models/week.ts @@ -13,7 +13,9 @@ export default class Week { public movies: Movie[] = [], ) {} - static fromNotion (record: PageObjectResponse | DatabaseObjectResponse): Week { + static fromNotion ( + record: PageObjectResponse | DatabaseObjectResponse + ): Week { const properties = record.properties as unknown as WeekProperties return new Week(