From d220eab1b4813af31f30a3369c6913c7c364a216 Mon Sep 17 00:00:00 2001 From: Luka Tarman <83535200+lukatarman@users.noreply.github.com> Date: Tue, 4 Jun 2024 13:56:28 +0200 Subject: [PATCH] feature/NR-74 Introduce release date data model (#230) feature: improve way release dates are stored # introduced new releaseDate data model # rename getGamesWIthoutReleaseDates -> getXUnreleasedGames # used new releaseDate data model where ever there is a release date # games repo now uses comingSoon release date property instead of calculating it on call # moved release date logic into new data model # improved tests, used mocks more often # used new data model in tests/mocks # added steam app raw tests Previously, the release date was a simple date. Now, the release date is a data model with its own internal logic. It also has a coming soon boolean property. A true value indicated the game is still unreleased, i.e. it is coming out soon. Issue-ref: #220 --- .../one.game.unchecked.history.js | 4 + .../two.games.unchecked.history.js | 8 + .../monster.hunter.coming.soon.js | 173 ++++++++++++++ .../party.animals.missing.data.js | 5 + .../game-identifier/game.identifier.js | 2 +- .../game-identifier/game.identifier.spec.js | 2 +- backend/src/core/models/game.js | 35 +-- backend/src/core/models/game.mocks.js | 99 ++++++-- backend/src/core/models/game.spec.js | 192 ++++----------- backend/src/core/models/games.aggregate.js | 4 +- .../src/core/models/games.aggregate.spec.js | 6 +- .../src/core/models/player.history.mocks.js | 4 +- backend/src/core/models/release.date.js | 74 ++++++ backend/src/core/models/release.date.spec.js | 223 ++++++++++++++++++ backend/src/core/models/steam.app.raw.js | 22 +- backend/src/core/models/steam.app.raw.mock.js | 34 ++- backend/src/core/models/steam.app.raw.spec.js | 33 +++ .../src/core/repositories/games.repository.js | 11 +- .../repositories/games.repository.spec.js | 109 ++++----- .../services/player.history.service.spec.js | 25 +- 20 files changed, 762 insertions(+), 303 deletions(-) create mode 100644 backend/assets/steam-api-responses/monster.hunter.coming.soon.js create mode 100644 backend/assets/steam-api-responses/party.animals.missing.data.js create mode 100644 backend/src/core/models/release.date.js create mode 100644 backend/src/core/models/release.date.spec.js create mode 100644 backend/src/core/models/steam.app.raw.spec.js diff --git a/backend/assets/db-responses/one.game.unchecked.history.js b/backend/assets/db-responses/one.game.unchecked.history.js index 4ed2371a..20579c54 100644 --- a/backend/assets/db-responses/one.game.unchecked.history.js +++ b/backend/assets/db-responses/one.game.unchecked.history.js @@ -4,6 +4,10 @@ export const oneGameWithUncheckedPlayerHistory = { }, id: 1838970, name: "Crush the Castle Legacy Collection", + releaseDate: { + date: null, + comingSoon: true, + }, image: null, imageUrl: "https://cdn.akamai.steamstatic.com/steam/apps/1838970/header.jpg", playerHistory: [], diff --git a/backend/assets/db-responses/two.games.unchecked.history.js b/backend/assets/db-responses/two.games.unchecked.history.js index cfbba10c..6d525799 100644 --- a/backend/assets/db-responses/two.games.unchecked.history.js +++ b/backend/assets/db-responses/two.games.unchecked.history.js @@ -5,6 +5,10 @@ export const twoGamesWithUncheckedPlayerHistory = [ }, id: 1838970, name: "Crush the Castle Legacy Collection", + releaseDate: { + date: null, + comingSoon: true, + }, image: null, imageUrl: "https://cdn.akamai.steamstatic.com/steam/apps/1838970/header.jpg", playerHistory: [], @@ -15,6 +19,10 @@ export const twoGamesWithUncheckedPlayerHistory = [ }, id: 1838980, name: "Polyion", + releaseDate: { + date: null, + comingSoon: true, + }, image: null, imageUrl: "https://cdn.akamai.steamstatic.com/steam/apps/1838980/header.jpg", playerHistory: [], diff --git a/backend/assets/steam-api-responses/monster.hunter.coming.soon.js b/backend/assets/steam-api-responses/monster.hunter.coming.soon.js new file mode 100644 index 00000000..50e443c9 --- /dev/null +++ b/backend/assets/steam-api-responses/monster.hunter.coming.soon.js @@ -0,0 +1,173 @@ +export const monsterHunterSteamApiData = { + type: "game", + name: "Monster Hunter Wilds", + steam_appid: 2246340, + required_age: 0, + is_free: false, + detailed_description: "The hunt is on in Monster Hunter Wilds, the latest installment in the Monster Hunter series, now in development. Experience the pinnacle of excellence in hunting action gameplay.

Planned for release in 2025.

Note: Supported languages and other details to be announced at a later date.", + about_the_game: "The hunt is on in Monster Hunter Wilds, the latest installment in the Monster Hunter series, now in development. Experience the pinnacle of excellence in hunting action gameplay.

Planned for release in 2025.

Note: Supported languages and other details to be announced at a later date.", + short_description: "The hunt is on in Monster Hunter Wilds, the latest installment in the Monster Hunter series, now in development. Experience the pinnacle of excellence in hunting action gameplay. Planned for release in 2025.", + supported_languages: "English", + header_image: "https://shared.akamai.steamstatic.com/store_item_assets/steam/apps/2246340/header.jpg?t=1717124069", + capsule_image: "https://shared.akamai.steamstatic.com/store_item_assets/steam/apps/2246340/capsule_231x87.jpg?t=1717124069", + capsule_imagev5: "https://shared.akamai.steamstatic.com/store_item_assets/steam/apps/2246340/capsule_184x69.jpg?t=1717124069", + website: "https://www.monsterhunter.com/wilds/", + pc_requirements: { + minimum: "Minimum:
", + recommended: "Recommended:
", + }, + mac_requirements: [ + ], + linux_requirements: [ + ], + legal_notice: "©CAPCOM", + developers: [ + "CAPCOM Co., Ltd.", + ], + publishers: [ + "CAPCOM Co., Ltd.", + ], + package_groups: [ + ], + platforms: { + windows: true, + mac: false, + linux: false, + }, + categories: [ + { + id: 2, + description: "Single-player", + }, + { + id: 1, + description: "Multi-player", + }, + ], + genres: [ + { + id: "1", + description: "Action", + }, + ], + screenshots: [ + { + id: 0, + path_thumbnail: "https://shared.akamai.steamstatic.com/store_item_assets/steam/apps/2246340/ss_44e68f3f74c173ea10d440dcfdee4d45f9203bf6.600x338.jpg?t=1717124069", + path_full: "https://shared.akamai.steamstatic.com/store_item_assets/steam/apps/2246340/ss_44e68f3f74c173ea10d440dcfdee4d45f9203bf6.1920x1080.jpg?t=1717124069", + }, + { + id: 1, + path_thumbnail: "https://shared.akamai.steamstatic.com/store_item_assets/steam/apps/2246340/ss_161ab6d119fefd78f52b2534fee40b8c456c6bce.600x338.jpg?t=1717124069", + path_full: "https://shared.akamai.steamstatic.com/store_item_assets/steam/apps/2246340/ss_161ab6d119fefd78f52b2534fee40b8c456c6bce.1920x1080.jpg?t=1717124069", + }, + { + id: 2, + path_thumbnail: "https://shared.akamai.steamstatic.com/store_item_assets/steam/apps/2246340/ss_827f69f98cbe1301cb647fb1bcd6364e69a977e5.600x338.jpg?t=1717124069", + path_full: "https://shared.akamai.steamstatic.com/store_item_assets/steam/apps/2246340/ss_827f69f98cbe1301cb647fb1bcd6364e69a977e5.1920x1080.jpg?t=1717124069", + }, + { + id: 3, + path_thumbnail: "https://shared.akamai.steamstatic.com/store_item_assets/steam/apps/2246340/ss_01c4c83d31387049d23be1a7ddd0c636f7119284.600x338.jpg?t=1717124069", + path_full: "https://shared.akamai.steamstatic.com/store_item_assets/steam/apps/2246340/ss_01c4c83d31387049d23be1a7ddd0c636f7119284.1920x1080.jpg?t=1717124069", + }, + { + id: 4, + path_thumbnail: "https://shared.akamai.steamstatic.com/store_item_assets/steam/apps/2246340/ss_11d1e1cba3047bfa8bf0653aee133f8cf0db3d52.600x338.jpg?t=1717124069", + path_full: "https://shared.akamai.steamstatic.com/store_item_assets/steam/apps/2246340/ss_11d1e1cba3047bfa8bf0653aee133f8cf0db3d52.1920x1080.jpg?t=1717124069", + }, + { + id: 5, + path_thumbnail: "https://shared.akamai.steamstatic.com/store_item_assets/steam/apps/2246340/ss_3920e4068e53d526c80fb5b4ba9bbca01629bbfd.600x338.jpg?t=1717124069", + path_full: "https://shared.akamai.steamstatic.com/store_item_assets/steam/apps/2246340/ss_3920e4068e53d526c80fb5b4ba9bbca01629bbfd.1920x1080.jpg?t=1717124069", + }, + { + id: 6, + path_thumbnail: "https://shared.akamai.steamstatic.com/store_item_assets/steam/apps/2246340/ss_c1f44fa0f4a5994cbf331540e46bafa5d7b63836.600x338.jpg?t=1717124069", + path_full: "https://shared.akamai.steamstatic.com/store_item_assets/steam/apps/2246340/ss_c1f44fa0f4a5994cbf331540e46bafa5d7b63836.1920x1080.jpg?t=1717124069", + }, + { + id: 7, + path_thumbnail: "https://shared.akamai.steamstatic.com/store_item_assets/steam/apps/2246340/ss_a41eda5fb16bd283eb64e9ed0aead4fb55a49f24.600x338.jpg?t=1717124069", + path_full: "https://shared.akamai.steamstatic.com/store_item_assets/steam/apps/2246340/ss_a41eda5fb16bd283eb64e9ed0aead4fb55a49f24.1920x1080.jpg?t=1717124069", + }, + { + id: 8, + path_thumbnail: "https://shared.akamai.steamstatic.com/store_item_assets/steam/apps/2246340/ss_ef0d1fc952a50cc8fb63a041fe1b475fa7594715.600x338.jpg?t=1717124069", + path_full: "https://shared.akamai.steamstatic.com/store_item_assets/steam/apps/2246340/ss_ef0d1fc952a50cc8fb63a041fe1b475fa7594715.1920x1080.jpg?t=1717124069", + }, + { + id: 9, + path_thumbnail: "https://shared.akamai.steamstatic.com/store_item_assets/steam/apps/2246340/ss_7772b12d698d02b37f38b2b2af2b56b3a0f011a0.600x338.jpg?t=1717124069", + path_full: "https://shared.akamai.steamstatic.com/store_item_assets/steam/apps/2246340/ss_7772b12d698d02b37f38b2b2af2b56b3a0f011a0.1920x1080.jpg?t=1717124069", + }, + { + id: 10, + path_thumbnail: "https://shared.akamai.steamstatic.com/store_item_assets/steam/apps/2246340/ss_ed88d2006f292d9f2ca08645e61fed8f47a453d5.600x338.jpg?t=1717124069", + path_full: "https://shared.akamai.steamstatic.com/store_item_assets/steam/apps/2246340/ss_ed88d2006f292d9f2ca08645e61fed8f47a453d5.1920x1080.jpg?t=1717124069", + }, + ], + movies: [ + { + id: 257027129, + name: "05_MHWilds_PV1_Multi_ASIA_1080P", + thumbnail: "https://shared.akamai.steamstatic.com/store_item_assets/steam/apps/257027129/movie.293x165.jpg?t=1717124031", + webm: { + "480": "http://cdn.akamai.steamstatic.com/steam/apps/257027129/movie480_vp9.webm?t=1717124031", + max: "http://cdn.akamai.steamstatic.com/steam/apps/257027129/movie_max_vp9.webm?t=1717124031", + }, + mp4: { + "480": "http://cdn.akamai.steamstatic.com/steam/apps/257027129/movie480.mp4?t=1717124031", + max: "http://cdn.akamai.steamstatic.com/steam/apps/257027129/movie_max.mp4?t=1717124031", + }, + highlight: true, + }, + { + id: 256988327, + name: "05_MHWilds_AnnouncePV_Multi_ASIA_1080P", + thumbnail: "https://shared.akamai.steamstatic.com/store_item_assets/steam/apps/256988327/movie.293x165.jpg?t=1702947717", + webm: { + "480": "http://cdn.akamai.steamstatic.com/steam/apps/256988327/movie480_vp9.webm?t=1702947717", + max: "http://cdn.akamai.steamstatic.com/steam/apps/256988327/movie_max_vp9.webm?t=1702947717", + }, + mp4: { + "480": "http://cdn.akamai.steamstatic.com/steam/apps/256988327/movie480.mp4?t=1702947717", + max: "http://cdn.akamai.steamstatic.com/steam/apps/256988327/movie_max.mp4?t=1702947717", + }, + highlight: true, + }, + ], + release_date: { + coming_soon: true, + date: "2025", + }, + support_info: { + url: "http://www.capcom.co.jp/support/contact/", + email: "", + }, + background: "https://shared.akamai.steamstatic.com/store_item_assets/steam/apps/2246340/page_bg_generated_v6b.jpg?t=1717124069", + background_raw: "https://shared.akamai.steamstatic.com/store_item_assets/steam/apps/2246340/page_bg_generated.jpg?t=1717124069", + content_descriptors: { + ids: [ + ], + notes: null, + }, + ratings: { + dejus: { + rating_generated: "1", + rating: "l", + required_age: "0", + banned: "0", + use_age_gate: "0", + descriptors: "", + }, + steam_germany: { + rating_generated: "1", + rating: "0", + required_age: "0", + banned: "0", + use_age_gate: "0", + descriptors: "", + }, + }, + } \ No newline at end of file diff --git a/backend/assets/steam-api-responses/party.animals.missing.data.js b/backend/assets/steam-api-responses/party.animals.missing.data.js new file mode 100644 index 00000000..e7968b98 --- /dev/null +++ b/backend/assets/steam-api-responses/party.animals.missing.data.js @@ -0,0 +1,5 @@ +export const partyAnimalsApiMissingData = { + type: "game", + name: "Party Animals", + steam_appid: 1260320, + } \ No newline at end of file diff --git a/backend/src/core/features/game-identifier/game.identifier.js b/backend/src/core/features/game-identifier/game.identifier.js index e918686b..f94397e5 100644 --- a/backend/src/core/features/game-identifier/game.identifier.js +++ b/backend/src/core/features/game-identifier/game.identifier.js @@ -128,7 +128,7 @@ export class GameIdentifier { updateGamesWithoutReleaseDates = async () => { this.#logger.debugc(`updating games without release dates via Steam API`); - const games = await this.#gamesRepository.getGamesWithoutReleaseDates( + const games = await this.#gamesRepository.getXUnreleasedGames( this.#options.batchSize, ); diff --git a/backend/src/core/features/game-identifier/game.identifier.spec.js b/backend/src/core/features/game-identifier/game.identifier.spec.js index 99b70b56..94a72e24 100644 --- a/backend/src/core/features/game-identifier/game.identifier.spec.js +++ b/backend/src/core/features/game-identifier/game.identifier.spec.js @@ -446,7 +446,7 @@ function createGamesRepositoryMock(gamesRepoRet) { insertManyGames: Promise.resolve(undefined), getGamesWithoutDetails: Promise.resolve(gamesRepoRet), updateGameDetailsFrom: Promise.resolve(undefined), - getGamesWithoutReleaseDates: Promise.resolve(gamesRepoRet), + getXUnreleasedGames: Promise.resolve(gamesRepoRet), updateReleaseDates: Promise.resolve(undefined), }); } diff --git a/backend/src/core/models/game.js b/backend/src/core/models/game.js index 57e40337..9848264c 100644 --- a/backend/src/core/models/game.js +++ b/backend/src/core/models/game.js @@ -1,5 +1,6 @@ import { PlayerHistory } from "./player.history.js"; import cloneDeep from "lodash.clonedeep"; +import { ReleaseDate } from "./release.date.js"; export class Game { id; @@ -30,7 +31,7 @@ export class Game { const game = new Game(); game.id = steamApp.appid; game.name = steamApp.name; - game.releaseDate = game.#extractReleaseDateViaSteamWeb(page); + game.releaseDate = ReleaseDate.fromSteamWeb(page); game.developers = game.#extractDevelopersViaSteamWeb(page); game.genres = game.#extractGenresViaSteamWeb(page); game.description = game.#extractDescriptionViaSteamWeb(page); @@ -40,16 +41,6 @@ export class Game { return game; } - #extractReleaseDateViaSteamWeb(page) { - const releaseDateElement = page.querySelector(".release_date .date"); - - if (!releaseDateElement) return null; - - const releaseDate = new Date(`${releaseDateElement.textContent.trim()} UTC`); - - return releaseDate == "Invalid Date" ? null : releaseDate; - } - #extractDevelopersViaSteamWeb(page) { const developers = page.querySelector(".dev_row #developers_list"); @@ -83,7 +74,7 @@ export class Game { const game = new Game(); game.id = steamApiApp.id; game.name = steamApiApp.name; - game.releaseDate = game.#extractReleaseDateViaSteamApi(steamApiApp); + game.releaseDate = steamApiApp.releaseDate.copy(); game.developers = game.#extractDevelopersViaSteamApi(steamApiApp); game.genres = game.#extractGenresViaSteamApi(steamApiApp); game.description = game.#extractDescriptionViaSteamApi(steamApiApp); @@ -93,14 +84,6 @@ export class Game { return game; } - #extractReleaseDateViaSteamApi(steamApiApp) { - if (!steamApiApp.releaseDate) return null; - - const releaseDate = new Date(`${steamApiApp.releaseDate} UTC`); - - return releaseDate == "Invalid Date" ? null : releaseDate; - } - #extractDevelopersViaSteamApi(steamApiApp) { if (!steamApiApp.developers) return []; @@ -124,7 +107,7 @@ export class Game { const game = new Game(); game.id = dbEntry.id; game.name = dbEntry.name; - game.releaseDate = dbEntry.releaseDate; + game.releaseDate = ReleaseDate.fromDb(dbEntry.releaseDate); game.developers = dbEntry.developers; game.genres = dbEntry.genres; game.description = dbEntry.description; @@ -167,13 +150,9 @@ export class Game { }); } - updateReleaseDateViaSteamApi(steamApiApp) { - if (this.releaseDate) return; - - const date = this.#extractReleaseDateViaSteamApi(steamApiApp); - - if (date === null) return; + updateReleaseDateViaSteamApi(newDate) { + if (!newDate) return; - this.releaseDate = date; + this.releaseDate.updateReleaseDate(newDate); } } diff --git a/backend/src/core/models/game.mocks.js b/backend/src/core/models/game.mocks.js index f9927bc5..5c6fe955 100644 --- a/backend/src/core/models/game.mocks.js +++ b/backend/src/core/models/game.mocks.js @@ -4,6 +4,7 @@ import { daysToMs } from "../../common/time.utils.js"; import { Game } from "./game.js"; import { getSamplePlayerHistory } from "./player.history.mocks.js"; import { getXSampleSteamApps } from "./steam.app.mocks.js"; +import { getXRawSteamApiApps } from "./steam.app.raw.mock.js"; export const getXGamesWithoutDetails = (amount, ids = []) => { const steamApps = getXSampleSteamApps(amount, ids); @@ -21,6 +22,14 @@ export const getOneSteamAppInstantiatedGame = () => { return Game.fromSteamApp(steamApp, page); }; +export const getXGamesWithDetails = (amount) => { + const steamApiApps = getXRawSteamApiApps(amount); + + return Array.from({ length: amount }, (x, index) => { + return Game.fromSteamApi(steamApiApps[index]); + }); +}; + export const getOneGameWithDetails = () => { return [ { @@ -40,7 +49,10 @@ export const getEldenRingGameWithDetails = (hasPlayerHistory = false) => { const dbEntry = { id: 1245620, name: "ELDEN RING", - releaseDate: new Date("Feb 24 2022 UTC"), + releaseDate: { + date: new Date("Feb 24 2022 UTC"), + comingSoon: false, + }, developers: ["FromSoftware Inc."], genres: ["Action", "RPG"], imageUrl: `https://cdn.akamai.steamstatic.com/steam/apps/1245620/header.jpg`, @@ -220,7 +232,10 @@ export const getGamesDatasetMock = () => { { id: 582660, name: "Black Desert", - releaseDate: new Date("24 May, 2017"), + releaseDate: { + date: new Date("24 May, 2017"), + comingSoon: false, + }, developers: ["Pearl Abyss"], image: null, imageUrl: "https://cdn.akamai.steamstatic.com/steam/apps/582660/header.jpg", @@ -230,7 +245,10 @@ export const getGamesDatasetMock = () => { { id: 227300, name: "Euro Truck Simulator 2", - releaseDate: new Date("17 Oct, 2999"), + releaseDate: { + date: new Date("17 Oct, 2999"), + comingSoon: true, + }, developers: ["SCS Software"], image: null, imageUrl: "https://cdn.akamai.steamstatic.com/steam/apps/227300/header.jpg", @@ -243,7 +261,10 @@ export const getGamesDatasetMock = () => { { id: 255710, name: "Cities: Skylines", - releaseDate: new Date("23 Sep, 2015"), + releaseDate: { + date: new Date("23 Sep, 2015"), + comingSoon: false, + }, developers: ["Colossal Order Ltd."], image: null, imageUrl: "https://cdn.akamai.steamstatic.com/steam/apps/255710/header.jpg", @@ -256,7 +277,10 @@ export const getGamesDatasetMock = () => { { id: 648800, name: "Raft", - releaseDate: new Date("19 Jun, 2022"), + releaseDate: { + date: new Date("19 Jun, 2022"), + comingSoon: false, + }, developers: ["Redbeet Interactive"], image: null, imageUrl: "https://cdn.akamai.steamstatic.com/steam/apps/648800/header.jpg", @@ -269,7 +293,10 @@ export const getGamesDatasetMock = () => { { id: 1293830, name: "Forza Horizon 4", - releaseDate: new Date("9 Mar, 2021"), + releaseDate: { + date: new Date("9 Mar, 2021"), + comingSoon: false, + }, developers: ["Playground Games"], image: null, imageUrl: "https://cdn.akamai.steamstatic.com/steam/apps/1293830/header.jpg", @@ -279,7 +306,10 @@ export const getGamesDatasetMock = () => { { id: 1283970, name: "YoloMouse", - releaseDate: new Date("1 May, 2020"), + releaseDate: { + date: new Date("1 May, 2020"), + comingSoon: false, + }, developers: ["Dragonrise Games"], image: null, imageUrl: "https://cdn.akamai.steamstatic.com/steam/apps/1283970/header.jpg", @@ -303,7 +333,10 @@ export const getGamesDatasetMock = () => { { id: 2218750, name: "Halls of Torment", - releaseDate: null, + releaseDate: { + date: null, + comingSoon: true, + }, developers: ["Chasing Carrots"], image: null, imageUrl: "https://cdn.akamai.steamstatic.com/steam/apps/2218750/header.jpg", @@ -327,7 +360,10 @@ export const getGamesDatasetMock = () => { { id: 239140, name: "Dying Light", - releaseDate: null, + releaseDate: { + date: null, + comingSoon: true, + }, developers: ["Sample Dev"], image: null, imageUrl: "https://cdn.akamai.steamstatic.com/steam/apps/239140/header.jpg", @@ -353,7 +389,10 @@ export const getGamesDatasetMock = () => { { id: 1263850, name: "Football Manager 2021", - releaseDate: new Date("24 Nov, 2020"), + releaseDate: { + date: new Date("24 Nov, 2020"), + comingSoon: false, + }, developers: ["Sports Interactive"], image: null, imageUrl: "https://cdn.akamai.steamstatic.com/steam/apps/1263850/header.jpg", @@ -380,7 +419,10 @@ export const getGamesDatasetMock = () => { { id: 311210, name: "Call of Duty: Black Ops III", - releaseDate: new Date("11.09.2005"), + releaseDate: { + date: new Date("11.09.2005"), + comingSoon: false, + }, developers: [""], image: null, imageUrl: "https://cdn.akamai.steamstatic.com/steam/apps/311210/header.jpg", @@ -425,7 +467,10 @@ export const getGamesDatasetMock = () => { { id: 221380, name: "Age of Empires II (2013)", - releaseDate: new Date("22 Aug 2013"), + releaseDate: { + date: new Date("22 Aug 2013"), + comingSoon: false, + }, developers: [ "Skybox Labs", "Hidden Path Entertainment", @@ -457,7 +502,10 @@ export const getGamesDatasetMock = () => { { id: 240, name: "Counter-Strike: Source", - releaseDate: new Date("31 Oct 2004"), + releaseDate: { + date: new Date("31 Oct 2004"), + comingSoon: false, + }, developers: ["Valve"], image: null, imageUrl: "https://cdn.akamai.steamstatic.com/steam/apps/240/header.jpg", @@ -484,7 +532,10 @@ export const getGamesDatasetMock = () => { { id: 232090, name: "Killing Floor 2", - releaseDate: new Date("14.10.2004"), + releaseDate: { + date: new Date("14.10.2004"), + comingSoon: false, + }, developers: [], image: null, imageUrl: "https://cdn.akamai.steamstatic.com/steam/apps/232090/header.jpg", @@ -511,7 +562,10 @@ export const getGamesDatasetMock = () => { { id: 286160, name: "Tabletop Simulator", - releaseDate: new Date("22 Apr 2015"), + releaseDate: { + date: new Date("22 Apr 2015"), + comingSoon: false, + }, developers: ["Berserk Games"], image: null, imageUrl: "https://cdn.akamai.steamstatic.com/steam/apps/286160/header.jpg", @@ -538,7 +592,10 @@ export const getGamesDatasetMock = () => { { id: 881100, name: "Noita", - releaseDate: new Date("14 Oct 2020"), + releaseDate: { + date: new Date("14 Oct 2020"), + comingSoon: false, + }, developers: ["Nolla Games"], image: null, imageUrl: "https://cdn.akamai.steamstatic.com/steam/apps/881100/header.jpg", @@ -565,7 +622,10 @@ export const getGamesDatasetMock = () => { { id: 620, name: "Portal 2", - releaseDate: null, + releaseDate: { + date: null, + comingSoon: false, + }, developers: ["Valve"], image: null, imageUrl: "https://cdn.akamai.steamstatic.com/steam/apps/620/header.jpg", @@ -591,7 +651,10 @@ export const getGamesDatasetMock = () => { { id: 646910, name: "The Crew 2", - releaseDate: null, + releaseDate: { + date: null, + comingSoon: true, + }, developers: [], image: null, imageUrl: "https://cdn.akamai.steamstatic.com/steam/apps/646910/header.jpg", diff --git a/backend/src/core/models/game.spec.js b/backend/src/core/models/game.spec.js index aad17223..74e56679 100644 --- a/backend/src/core/models/game.spec.js +++ b/backend/src/core/models/game.spec.js @@ -1,15 +1,17 @@ import { getParsedHtmlPage } from "../../../assets/html.details.pages.mock.js"; import { eldenRingSteamApiData } from "../../../assets/steam-api-responses/elden.ring.js"; -import { theLastNightSteamApiData } from "../../../assets/steam-api-responses/the.last.night.unreleased.js"; import { crusaderKingsDetailsPage } from "../../../assets/steam-web-html-details-pages/crusader.kings.multiple.developers.html.details.page.js"; import { eldenRingGameHtmlDetailsPage } from "../../../assets/steam-web-html-details-pages/elden.ring.game.html.details.page.js"; import { riskOfRainHtmlDetailsPageMissingInfo } from "../../../assets/steam-web-html-details-pages/risk.of.rain.missing.additional.info.page.js"; -import { theLastNightUnreleasedHtmlDetailsPage } from "../../../assets/steam-web-html-details-pages/the.last.night.unreleased.html.details.page.js"; import { Game } from "./game.js"; -import { getEldenRingGameWithDetails, getXGamesWithoutDetails } from "./game.mocks.js"; +import { + getEldenRingGameWithDetails, + getXGamesWithDetails, + getXGamesWithoutDetails, +} from "./game.mocks.js"; import { PlayerHistory } from "./player.history.js"; +import { getSamplePlayerHistory } from "./player.history.mocks.js"; import { getEldenRingSteamApp, getXSampleSteamApps } from "./steam.app.mocks.js"; -import { SteamAppRaw } from "./steam.app.raw.js"; import { getRawSteamApiApp, getXSampleRawSteamApiApps } from "./steam.app.raw.mock.js"; describe("Game", function () { @@ -45,40 +47,6 @@ describe("Game", function () { }); }); - describe("if the provided HTML page does not include a release date section,", function () { - beforeAll(function () { - const steamApp = getXSampleSteamApps(1)[0]; - const page = getParsedHtmlPage(riskOfRainHtmlDetailsPageMissingInfo); - - this.result = Game.fromSteamApp(steamApp, page); - }); - - it("the result is an instance of game", function () { - expect(this.result).toBeInstanceOf(Game); - }); - - it("the game's release date will be null", function () { - expect(this.result.releaseDate).toBe(null); - }); - }); - - describe("if the provided HTML page includes an invalid date,", function () { - beforeAll(function () { - const steamApp = getXSampleSteamApps(1)[0]; - const page = getParsedHtmlPage(theLastNightUnreleasedHtmlDetailsPage); - - this.result = Game.fromSteamApp(steamApp, page); - }); - - it("the result is an instance of game", function () { - expect(this.result).toBeInstanceOf(Game); - }); - - it("the game's release date is set to null'", function () { - expect(this.result.releaseDate).toBe(null); - }); - }); - describe("if the provided HTML page does not include any developers,", function () { beforeAll(function () { const steamApp = getXSampleSteamApps(1)[0]; @@ -167,38 +135,6 @@ describe("Game", function () { }); }); - describe("if the provided steam api app includes a release date", function () { - describe("and the date is a valid date", function () { - beforeAll(function () { - this.result = Game.fromSteamApi(getRawSteamApiApp(eldenRingSteamApiData)); - }); - - it("the game's release date will have the correct value", function () { - expect(this.result.releaseDate).toEqual(new Date("24 February 2022 UTC")); - }); - }); - - describe("and the date is not a valid date", function () { - beforeAll(function () { - this.result = Game.fromSteamApi(theLastNightSteamApiData); - }); - - it("the game's release date will be null", function () { - expect(this.result.releaseDate).toBe(null); - }); - }); - }); - - describe("if the provided steam api app doesn't include a valid release date", function () { - beforeAll(function () { - this.result = Game.fromSteamApi(getXSampleRawSteamApiApps(1)[0]); - }); - - it("the game's release date will be set to null", function () { - expect(this.result.releaseDate).toBe(null); - }); - }); - describe("if the provided steam api app doesn't include developers", function () { beforeAll(function () { this.result = Game.fromSteamApi(getXSampleRawSteamApiApps(1)[0]); @@ -255,7 +191,10 @@ describe("Game", function () { this.testObject = { id: 123, name: "test game", - releaseDate: "3 Mar, 2022", + releaseDate: { + date: "3 Mar, 2022", + comingSoon: false, + }, developers: ["Crossplatform"], genres: ["Action", "Adventure"], description: "A game's description", @@ -279,7 +218,7 @@ describe("Game", function () { }); it("has a 'releaseDate' property which equals '3 Mar, 2022", function () { - expect(this.result.releaseDate).toBe("3 Mar, 2022"); + expect(this.result.releaseDate.date).toBe("3 Mar, 2022"); }); it("has a 'developers' property which equals 'Crossplatform", function () { @@ -316,39 +255,26 @@ describe("Game", function () { describe("When this month's player history entry already exists,", function () { describe("players are added to the existing entry.", function () { beforeAll(function () { - this.currentPlayers = 45; - - const playerHistory = [ - { - year: new Date().getFullYear(), - month: new Date().getMonth(), - averagePlayers: 0, - trackedPlayers: [], - }, - ]; + jasmine.clock().install(); + jasmine.clock().mockDate(new Date("May 2024")); + this.result = getXGamesWithDetails(1)[0]; - this.historyLength = playerHistory.length; + this.result.playerHistory = getSamplePlayerHistory(); - const game = { - id: 1, - name: "Test Game", - playerHistory: PlayerHistory.manyFromDbEntry(playerHistory), - }; - - this.result = Game.fromDbEntry(game); + this.result.pushCurrentPlayers(45); + }); - this.result.pushCurrentPlayers(this.currentPlayers); + afterAll(function () { + jasmine.clock().uninstall(); }); it("No new entry is created", function () { - expect(this.result.playerHistory.length).toBe(this.historyLength); + expect(this.result.playerHistory.length).toBe(1); }); - it("The existing entry is updated.", function () { + it("The existing entry is updated with the added players.", function () { expect(this.result.playerHistory[0]).toBeInstanceOf(PlayerHistory); - expect(this.result.playerHistory[0].trackedPlayers[0].players).toBe( - this.currentPlayers, - ); + expect(this.result.playerHistory[0].trackedPlayers[2].players).toBe(45); }); }); }); @@ -356,41 +282,18 @@ describe("Game", function () { describe("When this month's player history entry does not exist yet", function () { describe("An entry for the current month is created. So,", function () { beforeAll(function () { - this.currentPlayers = 33; - - const playerHistory = [ - { - year: "2022", - month: "10", - averagePlayers: 75, - trackedPlayers: [], - }, - ]; - - this.game = { - id: 1, - name: "Test Game", - playerHistory: PlayerHistory.manyFromDbEntry(playerHistory), - }; - - this.result = Game.fromDbEntry(this.game); - - this.result.pushCurrentPlayers(this.currentPlayers); + this.result = getXGamesWithDetails(1)[0]; + + this.result.pushCurrentPlayers(33); }); it("this month's entry is created", function () { - expect(this.result.playerHistory.length).toBe(2); - expect(this.result.playerHistory[1]).toBeInstanceOf(PlayerHistory); + expect(this.result.playerHistory.length).toBe(1); + expect(this.result.playerHistory[0]).toBeInstanceOf(PlayerHistory); }); it("the last time we checked the game was played by 33 players", function () { - expect(this.result.playerHistory[1].trackedPlayers[0].players).toBe( - this.currentPlayers, - ); - }); - - it("the existing entry does not change", function () { - expect(this.game.playerHistory[0]).toEqual(this.result.playerHistory[0]); + expect(this.result.playerHistory[0].trackedPlayers[0].players).toBe(33); }); }); }); @@ -430,14 +333,13 @@ describe("Game", function () { }); describe(".updateReleaseDateViaSteamApi", function () { - describe("If the game already has an existing release date", function () { + describe("If the passed in date is null", function () { beforeAll(function () { + this.existingDate = new Date("September 2, 2002"); this.game = getXGamesWithoutDetails(1)[0]; - this.existingDate = new Date("23 July 2023"); - this.game.releaseDate = this.existingDate; - this.game.updateReleaseDateViaSteamApi(getRawSteamApiApp(eldenRingSteamApiData)); + this.game.updateReleaseDateViaSteamApi(null); }); it("the game's release date stays unchanged", function () { @@ -445,33 +347,21 @@ describe("Game", function () { }); }); - describe("When we try to use a game that has no existing release date", function () { - describe("and the provided steam api app doesn't contain a valid release date,", function () { - beforeAll(function () { - this.game = getXGamesWithoutDetails(1)[0]; + describe("When we pass in a date", function () { + beforeAll(function () { + this.game = getXGamesWithDetails(1)[0]; - this.game.updateReleaseDateViaSteamApi( - new SteamAppRaw(theLastNightSteamApiData), - ); - }); + this.date = new Date("September 20, 2004"); - it("the release date stays unchanged", function () { - expect(this.game.releaseDate).toBe(null); - }); + this.game.updateReleaseDateViaSteamApi(this.date); }); - describe("and the provided steam api app contains a valid release date,", function () { - beforeAll(function () { - this.game = getXGamesWithoutDetails(1)[0]; - - this.game.updateReleaseDateViaSteamApi( - getRawSteamApiApp(eldenRingSteamApiData), - ); - }); + it("the release date is properly updated", function () { + expect(this.game.releaseDate.date).toEqual(this.date); + }); - it("the release date is changed to the correct value", function () { - expect(this.game.releaseDate).toEqual(new Date("24 February 2022 UTC")); - }); + it("the release date release status is properly updated", function () { + expect(this.game.releaseDate.comingSoon).toBeFalse(); }); }); }); diff --git a/backend/src/core/models/games.aggregate.js b/backend/src/core/models/games.aggregate.js index e16718fb..aeb011db 100644 --- a/backend/src/core/models/games.aggregate.js +++ b/backend/src/core/models/games.aggregate.js @@ -25,7 +25,9 @@ export class GamesAggregate { const steamApiApp = this.#findGameByAppId(steamApiApps, gameCopy.id); - gameCopy.updateReleaseDateViaSteamApi(steamApiApp); + if (!steamApiApp) return gameCopy; + + gameCopy.updateReleaseDateViaSteamApi(steamApiApp.releaseDate.date); return gameCopy; }); diff --git a/backend/src/core/models/games.aggregate.spec.js b/backend/src/core/models/games.aggregate.spec.js index 86ee453f..07db974f 100644 --- a/backend/src/core/models/games.aggregate.spec.js +++ b/backend/src/core/models/games.aggregate.spec.js @@ -65,7 +65,7 @@ describe("GamesAggregate", function () { }); describe(".extractReleaseDatesViaSteamApi.", function () { - describe("When we try to update two games with missing release dates, one of them having in invalid date", function () { + describe("When we try to update two games with missing release dates, one of them having an invalid date", function () { beforeAll(function () { const gameIds = [1245620, 612400]; @@ -80,13 +80,13 @@ describe("GamesAggregate", function () { }); it("the first game's release date is updated", function () { - expect(this.gamesArray.content[0].releaseDate).toEqual( + expect(this.gamesArray.content[0].releaseDate.date).toEqual( new Date("24 February 2022 UTC"), ); }); it("the second game's release date remains unchanged", function () { - expect(this.gamesArray.content[1].releaseDate).toEqual(null); + expect(this.gamesArray.content[1].releaseDate.date).toEqual(null); }); }); }); diff --git a/backend/src/core/models/player.history.mocks.js b/backend/src/core/models/player.history.mocks.js index eb600edc..ee2e6071 100644 --- a/backend/src/core/models/player.history.mocks.js +++ b/backend/src/core/models/player.history.mocks.js @@ -3,8 +3,8 @@ import { getXSampleTrackedPlayers } from "./tracked.players.mocks.js"; const histories = [ { - year: "2020", - month: "September", + year: 2024, + month: 4, trackedPlayers: getXSampleTrackedPlayers(2), averagePlayers: 105, }, diff --git a/backend/src/core/models/release.date.js b/backend/src/core/models/release.date.js new file mode 100644 index 00000000..476a9e33 --- /dev/null +++ b/backend/src/core/models/release.date.js @@ -0,0 +1,74 @@ +export class ReleaseDate { + date; + comingSoon; + + copy() { + const copy = new ReleaseDate(); + copy.date = this.date; + copy.comingSoon = this.comingSoon; + + return copy; + } + + static fromSteamWeb(page) { + const releaseDate = new ReleaseDate(); + releaseDate.date = releaseDate.#extractReleaseDateViaSteamWeb(page); + releaseDate.comingSoon = releaseDate.#isGettingReleasedSoon; + + return releaseDate; + } + + #extractReleaseDateViaSteamWeb(page) { + const releaseDateElement = page.querySelector(".release_date .date"); + + if (!releaseDateElement) return null; + + const releaseDate = new Date(`${releaseDateElement.textContent.trim()} UTC`); + + return releaseDate == "Invalid Date" ? null : releaseDate; + } + + get #isGettingReleasedSoon() { + if (!this.date) return true; + + return this.date > new Date(); + } + + static fromSteamAppRaw(date, comingSoon) { + const releaseDate = new ReleaseDate(); + releaseDate.date = releaseDate.#getSteamAppRawReleaseDate(date); + releaseDate.comingSoon = releaseDate.#getSteamApiRawReleaseStatus(comingSoon); + + return releaseDate; + } + + #getSteamAppRawReleaseDate(rawDate) { + if (!rawDate) return null; + + const fixedReleaseDate = new Date(`${rawDate} UTC`); + + return fixedReleaseDate == "Invalid Date" ? null : fixedReleaseDate; + } + + #getSteamApiRawReleaseStatus(comingSoon) { + if (typeof comingSoon === "undefined") return true; + + return comingSoon; + } + + static fromDb({ date, comingSoon }) { + const fixedReleaseDate = new ReleaseDate(); + fixedReleaseDate.date = date; + fixedReleaseDate.comingSoon = comingSoon; + + return fixedReleaseDate; + } + + updateReleaseDate(newReleaseDate) { + if (newReleaseDate <= this.date) return; + + this.date = newReleaseDate; + + if (this.date <= new Date()) this.comingSoon = false; + } +} diff --git a/backend/src/core/models/release.date.spec.js b/backend/src/core/models/release.date.spec.js new file mode 100644 index 00000000..1c524a81 --- /dev/null +++ b/backend/src/core/models/release.date.spec.js @@ -0,0 +1,223 @@ +import { getParsedHtmlPage } from "../../../assets/html.details.pages.mock.js"; +import { eldenRingGameHtmlDetailsPage } from "../../../assets/steam-web-html-details-pages/elden.ring.game.html.details.page.js"; +import { riskOfRainHtmlDetailsPageMissingInfo } from "../../../assets/steam-web-html-details-pages/risk.of.rain.missing.additional.info.page.js"; +import { theLastNightUnreleasedHtmlDetailsPage } from "../../../assets/steam-web-html-details-pages/the.last.night.unreleased.html.details.page.js"; +import { ReleaseDate } from "./release.date.js"; + +describe("ReleaseDate", function () { + describe(".copy", function () { + beforeAll(function () { + const page = getParsedHtmlPage(eldenRingGameHtmlDetailsPage); + this.releaseDate = ReleaseDate.fromSteamWeb(page); + this.result = this.releaseDate.copy(); + + this.releaseDate.comingSoon = true; + }); + + it("the result is an instance of ReleaseDate", function () { + expect(this.result).toBeInstanceOf(ReleaseDate); + }); + + it("the copy has identical values to the original", function () { + expect(this.result.date).toEqual(new Date("24 Feb, 2022 UTC")); + expect(this.result.comingSoon).toBeFalsy(); + }); + }); + + describe(".fromSteamWeb", function () { + describe("When the method is used ", function () { + beforeAll(function () { + const page = getParsedHtmlPage(eldenRingGameHtmlDetailsPage); + this.result = ReleaseDate.fromSteamWeb(page); + }); + + it("the result is an instance of release date", function () { + expect(this.result).toBeInstanceOf(ReleaseDate); + }); + + it("the resulting date has the correct value", function () { + expect(this.result.date).toEqual(new Date("24 February 2022 UTC")); + }); + + it("the release date is correctly marked as already released", function () { + expect(this.result.comingSoon).toBeFalsy(); + }); + }); + + describe("if the provided HTML page does not include a release date section,", function () { + beforeAll(function () { + const page = getParsedHtmlPage(riskOfRainHtmlDetailsPageMissingInfo); + + this.result = ReleaseDate.fromSteamWeb(page); + }); + + it("the game's release date will be null", function () { + expect(this.result.date).toBe(null); + }); + + it("the release date is correctly marked as not yet released", function () { + expect(this.result.comingSoon).toBeTruthy(); + }); + }); + + describe("if the provided HTML page contains an invalid date,", function () { + beforeAll(function () { + const page = getParsedHtmlPage(theLastNightUnreleasedHtmlDetailsPage); + + this.result = ReleaseDate.fromSteamWeb(page); + }); + + it("the game's release date will be null", function () { + expect(this.result.date).toBe(null); + }); + + it("the release date is correctly marked as not yet released", function () { + expect(this.result.comingSoon).toBeTruthy(); + }); + }); + }); + + describe(".fromSteamAppRaw", function () { + describe("When the method is used ", function () { + beforeAll(function () { + this.result = ReleaseDate.fromSteamAppRaw("24 Feb, 2022", false); + }); + + it("the result is an instance of release date", function () { + expect(this.result).toBeInstanceOf(ReleaseDate); + }); + + it("the resulting date has the correct value", function () { + expect(this.result.date).toEqual(new Date("24 February 2022 UTC")); + }); + + it("the release date is correctly marked as already released", function () { + expect(this.result.comingSoon).toBeFalsy(); + }); + }); + + describe("when a date is not provided", function () { + beforeAll(function () { + this.result = ReleaseDate.fromSteamAppRaw(null, true); + }); + + it("the result is an instance of release date", function () { + expect(this.result).toBeInstanceOf(ReleaseDate); + }); + + it("the resulting date is null", function () { + expect(this.result.date).toBe(null); + }); + + it("the release date is correctly marked as unreleased", function () { + expect(this.result.comingSoon).toBeTruthy(); + }); + }); + + describe("when an invalid date is provided and coming soon is undefiend", function () { + beforeAll(function () { + this.result = ReleaseDate.fromSteamAppRaw("not a date", undefined); + }); + + it("the result is an instance of release date", function () { + expect(this.result).toBeInstanceOf(ReleaseDate); + }); + + it("the resulting date is null", function () { + expect(this.result.date).toBe(null); + }); + + it("the release date is correctly marked as unreleased", function () { + expect(this.result.comingSoon).toBeTruthy(); + }); + }); + }); + + describe(".fromDb", function () { + describe("When the method is used", function () { + beforeAll(function () { + this.releaseDate = { date: new Date("24 September 2000"), comingSoon: false }; + this.result = ReleaseDate.fromDb(this.releaseDate); + }); + + it("the result is an instance of release date", function () { + expect(this.result).toBeInstanceOf(ReleaseDate); + }); + + it("the resulting date has the correct value", function () { + expect(this.releaseDate.date).toEqual(this.releaseDate.date); + }); + + it("the release date is correctly marked as already released", function () { + expect(this.releaseDate.comingSoon).toBeFalsy(); + }); + }); + }); + + describe(".updateReleaseDate", function () { + describe("When the passed in date is smaller than the existing date", function () { + beforeAll(function () { + this.result = ReleaseDate.fromSteamAppRaw("2025", true); + this.result.updateReleaseDate(new Date("2024")); + }); + + it("the resulting date has the correct value", function () { + expect(this.result.date).toEqual(new Date("2025")); + }); + + it("the resulting release date release status stays unchanged", function () { + expect(this.result.comingSoon).toBe(true); + }); + }); + + describe("When the passed in date is larger than the existing date", function () { + describe("and the existing date is null", function () { + beforeAll(function () { + this.result = ReleaseDate.fromSteamAppRaw(null, true); + this.result.updateReleaseDate(new Date("2025")); + }); + + it("the resulting date has the correct value", function () { + expect(this.result.date).toEqual(new Date("2025")); + }); + }); + + describe("and the passed in date is a future date", function () { + beforeAll(function () { + jasmine.clock().install(); + jasmine.clock().mockDate(new Date("2023")); + + this.result = ReleaseDate.fromSteamAppRaw("2021", true); + this.result.updateReleaseDate(new Date("2024")); + }); + + afterAll(function () { + jasmine.clock().uninstall(); + }); + + it("the resulting date has the correct value", function () { + expect(this.result.date).toEqual(new Date("2024")); + }); + + it("the resulting release date release status stays unchanged", function () { + expect(this.result.comingSoon).toBe(true); + }); + }); + + describe("and the passed in date is a past date", function () { + beforeAll(function () { + this.result = ReleaseDate.fromSteamAppRaw("2021", true); + this.result.updateReleaseDate(new Date("2022")); + }); + + it("the resulting date has the correct value", function () { + expect(this.result.date).toEqual(new Date("2022")); + }); + + it("the resulting release date release status is correctly marked", function () { + expect(this.result.comingSoon).toBe(false); + }); + }); + }); + }); +}); diff --git a/backend/src/core/models/steam.app.raw.js b/backend/src/core/models/steam.app.raw.js index 8ac2fd27..99265138 100644 --- a/backend/src/core/models/steam.app.raw.js +++ b/backend/src/core/models/steam.app.raw.js @@ -1,3 +1,5 @@ +import { ReleaseDate } from "./release.date.js"; + export class SteamAppRaw { id; name; @@ -11,21 +13,33 @@ export class SteamAppRaw { this.id = data.steam_appid; this.name = data.name; this.type = data.type; - this.developers = data.developers; + this.developers = this.#extractDevelopers(data.developers); this.genres = this.#extractGenres(data.genres); - this.description = data.short_description; + this.description = this.#extractDescription(data.short_description); this.releaseDate = this.#extractReleaseDate(data.release_date); } + #extractDevelopers(developers) { + if (!developers) return []; + + return developers.slice(); + } + #extractGenres(genres) { if (!genres) return []; return genres.map((genre) => genre.description); } + #extractDescription(description) { + if (!description) return ""; + + return description; + } + #extractReleaseDate(releaseDate) { - if (!releaseDate) return ""; + if (!releaseDate) return ReleaseDate.fromSteamAppRaw(null, true); - return releaseDate.date; + return ReleaseDate.fromSteamAppRaw(releaseDate.date, releaseDate.coming_soon); } } diff --git a/backend/src/core/models/steam.app.raw.mock.js b/backend/src/core/models/steam.app.raw.mock.js index f211e221..909d5ab4 100644 --- a/backend/src/core/models/steam.app.raw.mock.js +++ b/backend/src/core/models/steam.app.raw.mock.js @@ -4,15 +4,25 @@ export const getRawSteamApiApp = (app = "") => { return new SteamAppRaw(app); }; +export const getXRawSteamApiApps = (amount) => { + return Array.from({ length: amount }, (x, index) => { + return new SteamAppRaw({ + steam_appid: index + 1, + name: `Game ${index + 1}`, + type: getType(index + 1), + }); + }); +}; + export const getXSampleRawSteamApiApps = (amount) => { return Array.from({ length: amount }, (x, index) => { const gameNumber = index + 1; - return { - id: gameNumber, + return new SteamAppRaw({ + steam_appid: gameNumber, name: `Game ${gameNumber}`, type: getType(gameNumber), - }; + }); }); }; @@ -22,3 +32,21 @@ const getType = (gameNumber) => { return "video"; }; + +export const eldenRingRawMockResult = () => { + const data = { + steam_appid: 1245620, + name: "ELDEN RING", + type: "game", + developers: ["FromSoftware Inc."], + genres: [{ description: "Action" }, { description: "RPG" }], + short_description: + "THE NEW FANTASY ACTION RPG. Rise, Tarnished, and be guided by grace to brandish the power of the Elden Ring and become an Elden Lord in the Lands Between.", + release_date: { + date: "24 Feb, 2022", + coming_soon: false, + }, + }; + + return new SteamAppRaw(data); +}; diff --git a/backend/src/core/models/steam.app.raw.spec.js b/backend/src/core/models/steam.app.raw.spec.js new file mode 100644 index 00000000..cb047051 --- /dev/null +++ b/backend/src/core/models/steam.app.raw.spec.js @@ -0,0 +1,33 @@ +import { eldenRingSteamApiData } from "../../../assets/steam-api-responses/elden.ring.js"; +import { partyAnimalsApiMissingData } from "../../../assets/steam-api-responses/party.animals.missing.data.js"; +import { ReleaseDate } from "./release.date.js"; +import { SteamAppRaw } from "./steam.app.raw.js"; +import { eldenRingRawMockResult } from "./steam.app.raw.mock.js"; + +describe("SteamAppRaw", function () { + describe("When the class is instantiated", function () { + describe("and all the data is provided", function () { + beforeAll(function () { + this.result = new SteamAppRaw(eldenRingSteamApiData); + }); + + it("the result has the correct values", function () { + expect(this.result).toEqual(eldenRingRawMockResult()); + }); + }); + + describe("and the developers, genres, description and releaseDate properties are missing", function () { + beforeAll(function () { + this.result = new SteamAppRaw(partyAnimalsApiMissingData); + this.expectedReleaseDate = ReleaseDate.fromSteamAppRaw(null, true); + }); + + it("the result has the correct values", function () { + expect(this.result.developers).toEqual([]); + expect(this.result.genres).toEqual([]); + expect(this.result.description).toEqual(""); + expect(this.result.releaseDate).toEqual(this.expectedReleaseDate); + }); + }); + }); +}); diff --git a/backend/src/core/repositories/games.repository.js b/backend/src/core/repositories/games.repository.js index 1a242145..939c55ad 100644 --- a/backend/src/core/repositories/games.repository.js +++ b/backend/src/core/repositories/games.repository.js @@ -21,17 +21,10 @@ export class GamesRepository { return await this.#dbClient.getAll("games"); } - async getGamesWithoutReleaseDates(amount) { + async getXUnreleasedGames(amount) { const response = await this.#dbClient .get("games") - .aggregate([ - { - $match: { - $or: [{ releaseDate: { $eq: null } }, { releaseDate: { $gt: new Date() } }], - }, - }, - { $limit: amount }, - ]) + .aggregate([{ $match: { "releaseDate.comingSoon": true } }, { $limit: amount }]) .toArray(); return new GamesAggregate(response); diff --git a/backend/src/core/repositories/games.repository.spec.js b/backend/src/core/repositories/games.repository.spec.js index 0fc5f4ea..11c7d123 100644 --- a/backend/src/core/repositories/games.repository.spec.js +++ b/backend/src/core/repositories/games.repository.spec.js @@ -3,10 +3,10 @@ import { GamesRepository } from "./games.repository.js"; import { daysToMs, hoursToMs } from "../../common/time.utils.js"; import { getGamesDatasetMock, - getGamesWithEmptyPlayerHistories, getGamesWithTrackedPlayersNoDate, getOneGameWithDetails, getTrendingGamesMockData, + getXGamesWithDetails, } from "../models/game.mocks.js"; import { Game } from "../models/game.js"; import { GamesAggregate } from "../models/games.aggregate.js"; @@ -126,7 +126,7 @@ describe("GamesRepository", function () { }); }); - describe(".getGamesWithoutReleaseDates", function () { + describe(".getXUnreleasedGames", function () { describe("When 3 games without valid release dates are requested", function () { beforeAll(async function () { this.databaseClient = await initiateInMemoryDatabase(["games"]); @@ -135,7 +135,7 @@ describe("GamesRepository", function () { const gamesRepo = new GamesRepository(this.databaseClient); - this.result = await gamesRepo.getGamesWithoutReleaseDates(3); + this.result = await gamesRepo.getXUnreleasedGames(3); }); afterAll(function () { @@ -150,25 +150,21 @@ describe("GamesRepository", function () { expect(this.result.content.length).toBe(3); }); - it("the games are an instance of Game", function () { - expect(this.result.content[0]).toBeInstanceOf(Game); - expect(this.result.content[1]).toBeInstanceOf(Game); - expect(this.result.content[2]).toBeInstanceOf(Game); - }); - it("the first game has the correct date", function () { expect(this.result.content[0].id).toBe(227300); - expect(this.result.content[0].releaseDate).toEqual(new Date("Thu Oct 17 2999")); + expect(this.result.content[0].releaseDate.date).toEqual( + new Date("Thu Oct 17 2999"), + ); }); - it("the second game is missing the release date", function () { + it("the second game is correctly missing a date", function () { expect(this.result.content[1].id).toBe(2218750); - expect(this.result.content[1].releaseDate).toBe(null); + expect(this.result.content[1].releaseDate.date).toBe(null); }); - it("the third game is missing the release date", function () { + it("the third game is correctly missing a date", function () { expect(this.result.content[2].id).toBe(239140); - expect(this.result.content[2].releaseDate).toBe(null); + expect(this.result.content[2].releaseDate.date).toBe(null); }); }); }); @@ -205,7 +201,7 @@ describe("GamesRepository", function () { beforeAll(async function () { this.databaseClient = await initiateInMemoryDatabase(["games", "history_checks"]); - await this.databaseClient.insertMany("games", getGamesWithEmptyPlayerHistories()); + await this.databaseClient.insertMany("games", getXGamesWithDetails(3)); await this.databaseClient.insertMany("history_checks", [ { gameId: 1, checked: false }, @@ -222,18 +218,18 @@ describe("GamesRepository", function () { this.databaseClient.disconnect(); }); - it("the result has two games", function () { + it("two games are returned", function () { expect(this.result.length).toBe(2); }); - it("the first array has the correct values", function () { + it("the first game has the correct values", function () { expect(this.result[0].id).toBe(1); - expect(this.result[0].name).toBe("Risk of Train"); + expect(this.result[0].name).toBe("Game 1"); }); - it("the second array has the correct values", function () { + it("the second game has the correct values", function () { expect(this.result[1].id).toBe(3); - expect(this.result[1].name).toBe("Risk of Brain"); + expect(this.result[1].name).toBe("Game 3"); }); }); @@ -241,7 +237,7 @@ describe("GamesRepository", function () { beforeAll(async function () { this.databaseClient = await initiateInMemoryDatabase(["games", "history_checks"]); - await this.databaseClient.insertMany("games", getGamesWithEmptyPlayerHistories()); + await this.databaseClient.insertMany("games", getXGamesWithDetails(3)); await this.databaseClient.insertMany("history_checks", [ { gameId: 1, checked: false }, @@ -263,7 +259,7 @@ describe("GamesRepository", function () { it("the first array has the correct values", function () { expect(this.result[0].id).toBe(1); - expect(this.result[0].name).toBe("Risk of Train"); + expect(this.result[0].name).toBe("Game 1"); }); }); }); @@ -275,27 +271,19 @@ describe("GamesRepository", function () { this.databaseClient = await initiateInMemoryDatabase(["games"]); - await this.databaseClient.insertMany("games", [ - { - id: 1, - name: "Risk of Train", - playerHistory: [ - { trackedPlayers: [{ date: new Date("12:00 21 September 2022") }] }, - ], - }, - { - id: 2, - name: "Risk of Rain", - playerHistory: [ - { trackedPlayers: [{ date: new Date("9:00 21 September 2022") }] }, - ], - }, - { - id: 3, - name: "Risk of Brain", - playerHistory: [], - }, - ]); + const games = getXGamesWithDetails(3); + + const firstGameHistories = [ + { trackedPlayers: [{ date: new Date("12:00 21 September 2022") }] }, + ]; + const secondGameHistories = [ + { trackedPlayers: [{ date: new Date("9:00 21 September 2022") }] }, + ]; + + games[0].pushSteamchartsPlayerHistory(firstGameHistories); + games[1].pushSteamchartsPlayerHistory(secondGameHistories); + + await this.databaseClient.insertMany("games", games); const gamesRepo = new GamesRepository(this.databaseClient); @@ -313,12 +301,12 @@ describe("GamesRepository", function () { it("the first array has the correct values", function () { expect(this.result[0].id).toBe(2); - expect(this.result[0].name).toBe("Risk of Rain"); + expect(this.result[0].name).toBe("Game 2"); }); it("the second array has the correct values", function () { expect(this.result[1].id).toBe(3); - expect(this.result[1].name).toBe("Risk of Brain"); + expect(this.result[1].name).toBe("Game 3"); }); }); @@ -328,22 +316,19 @@ describe("GamesRepository", function () { this.databaseClient = await initiateInMemoryDatabase(["games"]); - await this.databaseClient.insertMany("games", [ - { - id: 1, - name: "Risk of Train", - playerHistory: [ - { trackedPlayers: [{ date: new Date("10:00 21 September 2022") }] }, - ], - }, - { - id: 2, - name: "Risk of Rain", - playerHistory: [ - { trackedPlayers: [{ date: new Date("11:24 21 September 2022") }] }, - ], - }, - ]); + const games = getXGamesWithDetails(2); + + const firstGameHistories = [ + { trackedPlayers: [{ date: new Date("10:00 21 September 2022") }] }, + ]; + const secondGameHistories = [ + { trackedPlayers: [{ date: new Date("11:24 21 September 2022") }] }, + ]; + + games[0].pushSteamchartsPlayerHistory(firstGameHistories); + games[1].pushSteamchartsPlayerHistory(secondGameHistories); + + await this.databaseClient.insertMany("games", games); const gamesRepo = new GamesRepository(this.databaseClient); @@ -361,7 +346,7 @@ describe("GamesRepository", function () { it("the first array has the correct values", function () { expect(this.result[0].id).toBe(1); - expect(this.result[0].name).toBe("Risk of Train"); + expect(this.result[0].name).toBe("Game 1"); }); }); }); diff --git a/backend/src/core/services/player.history.service.spec.js b/backend/src/core/services/player.history.service.spec.js index 0f511a18..54875a72 100644 --- a/backend/src/core/services/player.history.service.spec.js +++ b/backend/src/core/services/player.history.service.spec.js @@ -3,6 +3,7 @@ import { Game } from "../models/game.js"; import { eldenRingHttpDetailsSteamcharts } from "../../../assets/steamcharts-html-details-pages/elden.ring.multiple.histories.html.details.page.js"; import { crushTheCastleHtmlDetailsSteamcharts } from "../../../assets/steamcharts-html-details-pages/crush.the.castle.legacy.collection.html.details.page.js"; import { PlayerHistory } from "../models/player.history.js"; +import { getXGamesWithoutDetails } from "../models/game.mocks.js"; describe("player.history.service.js", function () { describe(".addPlayerHistoriesFromSteamcharts adds the player histories from Steamcharts to each game object", function () { @@ -12,20 +13,10 @@ describe("player.history.service.js", function () { const firstPage = eldenRingHttpDetailsSteamcharts; const secondPage = crushTheCastleHtmlDetailsSteamcharts; - const firstGame = { - id: 1, - name: "Elden Ring", - playerHistory: [], - }; + const games = getXGamesWithoutDetails(2); - const secondGame = { - id: 2, - name: "Crush The Castle", - playerHistory: [], - }; - - const instantiatedFirstGame = Game.fromDbEntry(firstGame); - const instantiatedSecondGame = Game.fromDbEntry(secondGame); + const instantiatedFirstGame = Game.fromDbEntry(games[0]); + const instantiatedSecondGame = Game.fromDbEntry(games[1]); map.set(instantiatedFirstGame, firstPage); map.set(instantiatedSecondGame, secondPage); @@ -58,13 +49,7 @@ describe("player.history.service.js", function () { beforeAll(function () { const map = new Map(); - const game = { - id: 1, - name: "Elden Ring", - playerHistory: [], - }; - - map.set(game, ""); + map.set(getXGamesWithoutDetails(1)[0], ""); this.result = addPlayerHistoriesFromSteamcharts(map); });