diff --git a/.env.example b/.env.example index 4fbcb22..baaa604 100644 --- a/.env.example +++ b/.env.example @@ -13,4 +13,5 @@ MONGO_DOMAIN= # MONGO DATABASES (names do not matter) JSR_DB= JSRF_DB= +BRC_DB= CORE_DB= diff --git a/package-lock.json b/package-lock.json index f284c09..648c072 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "jetsetradio-api", - "version": "1.1.1", + "version": "1.1.2", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 84a3e68..8fcbb8a 100644 --- a/package.json +++ b/package.json @@ -1,14 +1,15 @@ { "name": "jetsetradio-api", - "version": "1.1.1", + "version": "1.1.2", "description": "A Data Provider relating to the JSR/JSRF universe", "type": "module", "main": "src/app.js", "scripts": { - "test": "export $(cat ./qa.env | egrep -v '#|^$' | xargs) && node --no-warnings --experimental-vm-modules ./node_modules/jest/bin/jest.js", "build": "npm install", "prod": "export $(cat ./prod.env | egrep -v '#|^$' | xargs) && node src/utils/swagger.js", - "qa": "export $(cat ./qa.env | egrep -v '#|^$' | xargs) && nodemon --inspect --ignore src/utils/ src/utils/swagger.js" + "qa": "export $(cat ./qa.env | egrep -v '#|^$' | xargs) && nodemon --inspect --ignore src/utils/ src/utils/swagger.js", + "test": "export $(cat ./qa.env | egrep -v '#|^$' | xargs) && node --no-warnings --experimental-vm-modules ./node_modules/jest/bin/jest.js", + "test:file": "export $(cat ./qa.env | egrep -v '#|^$' | xargs) && node --no-warnings --experimental-vm-modules ./node_modules/jest/bin/jest.js" }, "keywords": [], "author": "RazzNBlue", diff --git a/src/app.js b/src/app.js index 5f728e3..11e3e25 100644 --- a/src/app.js +++ b/src/app.js @@ -1,11 +1,10 @@ -import express from 'express'; -import axios from 'axios'; -import dotenv from 'dotenv'; +import express from "express"; +import axios from "axios"; +import dotenv from "dotenv"; dotenv.config(); -import LOGGER from './utils/logger.js'; -import MiddlewareManager from './managers/MiddlewareManager.js'; - +import LOGGER from "./utils/logger.js"; +import MiddlewareManager from "./managers/MiddlewareManager.js"; const middlewareManager = new MiddlewareManager(); @@ -18,9 +17,9 @@ middlewareManager.setMiddleware(app); app.listen(PORT || 8080, () => { LOGGER.info(`JSR-API Listening on port ${PORT}`); -// Ping App every 10 minutes + // Ping App every 10 minutes setInterval(async () => { const res = await axios.get(`${baseUrl}/health`); console.log(`App Ping - ${baseUrl}. Status: ${res.data.message}`); }, 600000); -}) \ No newline at end of file +}); diff --git a/src/config/db.js b/src/config/db.js index a246d37..0771db6 100644 --- a/src/config/db.js +++ b/src/config/db.js @@ -6,7 +6,7 @@ dotenv.config(); import LOGGER from "../utils/logger.js"; import Constants from "../constants/dbConstants.js"; -const {CORE_DB, JSR_DB, JSRF_DB, BRC_DB} = Constants; +const {CORE_DB} = Constants; const buildMongoUri = () => { const user = process.env.MONGO_USER; @@ -33,7 +33,7 @@ const buildMongoUri = () => { const client = new MongoClient(buildMongoUri()); /* Database Connections */ -export const performCoreAdminAction = async (action, username) => { +export const performAdminAction = async (action, username) => { try { await client.connect(); return await action(client, CORE_DB, "Admin", username); @@ -45,73 +45,13 @@ export const performCoreAdminAction = async (action, username) => { } }; -export const performCoreAction = async (action, collection, id, qps) => { +export const performDBAction = async (action, dbName, collection, id, qps) => { try { await client.connect(); const queryActions = [getSortQuery(qps), getLimitSize(qps)]; return await action( client, - CORE_DB, - collection, - id, - getQueryObject(qps), - queryActions - ); - } catch (err) { - console.error(err); - return err; - } finally { - await client.close(); - } -}; - -export const performJSRAction = async (action, collection, id, qps) => { - try { - await client.connect(); - const queryActions = [getSortQuery(qps), getLimitSize(qps)]; - return await action( - client, - JSR_DB, - collection, - id, - getQueryObject(qps), - queryActions - ); - } catch (err) { - console.error(err); - return err; - } finally { - await client.close(); - } -}; - -export const performJSRFAction = async (action, collection, id, qps) => { - try { - await client.connect(); - const queryActions = [getSortQuery(qps), getLimitSize(qps)]; - return await action( - client, - JSRF_DB, - collection, - id, - getQueryObject(qps), - queryActions - ); - } catch (err) { - console.error(err); - return err; - } finally { - await client.close(); - } -}; - -export const performBRCAction = async (action, collection, id, qps) => { - try { - await client.connect(); - const queryActions = [getSortQuery(qps), getLimitSize(qps)]; - return await action( - client, - BRC_DB, + dbName, collection, id, getQueryObject(qps), diff --git a/src/config/dbActions.js b/src/config/dbActions.js index d462194..9a92442 100644 --- a/src/config/dbActions.js +++ b/src/config/dbActions.js @@ -5,5 +5,6 @@ export const Actions = { fetchAll: async (client, dbName, collectionName, id, qps, sortValue) => { return await client.db(dbName).collection(collectionName).find({}).sort(sortValue).toArray() }, fetchWithQuery: async (client, dbName, collectionName, id, qps, queryActions) => { return await client.db(dbName).collection(collectionName).find(qps).sort(queryActions[0]).limit(queryActions[1]).toArray() }, fetchById: async (client, dbName, collectionName, id) => { return await client.db(dbName).collection(collectionName).findOne({ _id: new ObjectId(id) }) }, - fetchAdmin: async (client, dbName, collectionName, username) => { return await client.db(dbName).collection(collectionName).findOne({ username: username }) } + fetchAdmin: async (client, dbName, collectionName, username) => { return await client.db(dbName).collection(collectionName).findOne({ username: username }) }, + fetchRandom: async (client, dbName, collectionName) => { return await client.db(dbName).collection(collectionName).aggregate([{ $sample: { size: 1 } }]).toArray(); } } diff --git a/src/constants/dbConstants.js b/src/constants/dbConstants.js index 7d9ad8b..c849335 100644 --- a/src/constants/dbConstants.js +++ b/src/constants/dbConstants.js @@ -6,6 +6,11 @@ const Constants = { JSR_DB: process.env.JSR_DB, JSRF_DB: process.env.JSRF_DB, BRC_DB: process.env.BRC_DB, + gameMap: { + jsr: process.env.JSR_DB, + jsrf: process.env.JSRF_DB, + brc: process.env.BRC_DB, + }, }; export default Constants; diff --git a/src/controllers/artistController.js b/src/controllers/artistController.js index 0129a33..3e33fa5 100644 --- a/src/controllers/artistController.js +++ b/src/controllers/artistController.js @@ -1,60 +1,103 @@ -import { performBRCAction, performCoreAction, performJSRAction, performJSRFAction } from "../config/db.js"; -import { Actions } from "../config/dbActions.js"; -import { ObjectId } from "mongodb"; +import {ObjectId} from "mongodb"; +import Constants from "../constants/dbConstants.js"; +import {Actions} from "../config/dbActions.js"; +import {performDBAction} from "../config/db.js"; +import LOGGER from "../utils/logger.js"; - -const Artist = 'Artist'; -const Song = 'Song'; +const Artist = "Artist"; +const Song = "Song"; +const {CORE_DB, JSR_DB, JSRF_DB, BRC_DB} = Constants; export const getArtists = async (req, res) => { try { const artists = await fetchArtists(req); if (artists) { return res.send(artists.length === 1 ? artists[0] : artists); - } + } res.status(404).send(); - } catch(err) { - res.status(500).send(`Could not fetch ALL Artists due to error: \n${err}`); + } catch (err) { + LOGGER.error(`Could not fetch ALL Artists`, err); + res.status(500).send(`Could not fetch ALL Artists due to error`, err); } -} +}; export const getArtistById = async (req, res) => { try { - const artist = await performCoreAction(Actions.fetchById, Artist, req?.params?.id); + const artist = await performDBAction( + Actions.fetchById, + CORE_DB, + Artist, + req?.params?.id + ); if (artist) { return res.send(artist); } - res.status(404).send(`Artist Resource could not be found at requested location`); - } catch(err) { - res.status(500).send(`Could not fetch Artist with ID: ${req.params.id} \n${err}`); + res + .status(404) + .send(`Artist Resource could not be found at requested location`); + } catch (err) { + LOGGER.error(`Could not fetch Artist by Id ${req?.params?.id}`, err); + res + .status(500) + .send(`Could not fetch Artist with ID: ${req.params.id}`, err); } -} +}; export const getSongsByArtist = async (req, res) => { try { const artistId = req?.params?.id; if (!artistId) { - return res.status(400).send('Invalid ArtistId'); + return res.status(400).send("Invalid ArtistId"); } res.send(await fetchSongsByArtistId(artistId)); - } catch(err) { - res.status(500).send(`Could not fetch Songs by Artist with ID: ${req.params.id} \n${err}`); + } catch (err) { + LOGGER.error(`Could not fetch Songs By Artist ${req?.params?.id}`, err); + res + .status(500) + .send(`Could not fetch Songs by Artist with ID: ${req.params.id}`, err); } -} - +}; export const fetchArtists = async (req) => { if (req?.query) { - return await performCoreAction(Actions.fetchWithQuery, Artist, null, req?.query); + return await performDBAction( + Actions.fetchWithQuery, + CORE_DB, + Artist, + null, + req?.query + ); } - return await performCoreAction(Actions.fetchAll, Artist, null); -} + return await performDBAction(Actions.fetchAll, CORE_DB, Artist, null); +}; export const fetchSongsByArtistId = async (artistId) => { const songs = []; - const jsrSongs = await performJSRAction(Actions.fetchWithQuery, Song, null, { artistId: new ObjectId(artistId) }); - const jsrfSongs = await performJSRFAction(Actions.fetchWithQuery, Song, null, { artistId: new ObjectId(artistId) }); - const brcSongs = await performBRCAction(Actions.fetchWithQuery, Song, null, { artistId: new ObjectId(artistId) }); + const jsrSongs = await performDBAction( + Actions.fetchWithQuery, + JSR_DB, + Song, + null, + { + artistId: new ObjectId(artistId), + } + ); + const jsrfSongs = await performDBAction( + Actions.fetchWithQuery, + JSRF_DB, + Song, + null, + {artistId: new ObjectId(artistId)} + ); + const brcSongs = await performDBAction( + Actions.fetchWithQuery, + BRC_DB, + Song, + null, + { + artistId: new ObjectId(artistId), + } + ); if (jsrSongs && jsrSongs.length > 0) { songs.push(jsrSongs); } @@ -65,4 +108,4 @@ export const fetchSongsByArtistId = async (artistId) => { songs.push(brcSongs); } return songs.flat(1); -} \ No newline at end of file +}; diff --git a/src/controllers/characterController.js b/src/controllers/characterController.js index 5d6fda0..ae0b18f 100644 --- a/src/controllers/characterController.js +++ b/src/controllers/characterController.js @@ -1,130 +1,140 @@ -import {all} from "axios"; -import { - performJSRAction, - performJSRFAction, - performBRCAction, -} from "../config/db.js"; +import Constants from "../constants/dbConstants.js"; import {Actions} from "../config/dbActions.js"; -import LOGGER from "../utils/logger.js"; +import {performDBAction} from "../config/db.js"; import {sortObjects} from "../utils/utility.js"; +import LOGGER from "../utils/logger.js"; const Character = "Character"; +const {JSR_DB, JSRF_DB, BRC_DB, gameMap} = Constants; export const getAllCharacters = async (req, res) => { try { const sortByValue = req?.query?.sortBy ? req?.query?.sortBy : undefined; const sortOrder = req?.query?.orderBy ? req?.query?.orderBy : "asc"; - const jsrCharacters = await fetchJSRCharacters(req); - const jsrfCharacters = await fetchJSRFCharacters(req); - const brcCharacters = await fetchBRCCharacters(req); + const characters = await fetchCharacters(req, "ALL"); if (sortByValue) { - const characters = [ - ...jsrCharacters, - ...jsrfCharacters, - ...brcCharacters, - ]; return res.send(characters.sort(sortObjects(sortByValue, sortOrder))); } - res.send([...jsrCharacters, ...jsrfCharacters, ...brcCharacters]); + res.send(characters); } catch (err) { - LOGGER.error(`Could not fetch ALL Characters \n${err}`); + LOGGER.error(`Could not fetch ALL Characters`, err); + res.status(500).json({message: "Failed to fetch ALL characters", err: err}); } }; export const getRandomCharacter = async (req, res) => { try { - const jsrCharacters = await fetchJSRCharacters(req); - const jsrfCharacters = await fetchJSRFCharacters(req); - const brcCharacters = await fetchBRCCharacters(req); - - const allCharacters = [ - ...jsrCharacters, - ...jsrfCharacters, - ...brcCharacters, - ]; - - const randomCharacter = - allCharacters[Math.floor(Math.random() * allCharacters.length)]; - - res.json(randomCharacter); + const games = [JSR_DB, JSRF_DB, BRC_DB]; + const userSelectedGame = req?.query?.game; + let game = + gameMap[userSelectedGame] || + games[Math.floor(Math.random() * games.length)]; + const randomCharacter = await fetchRandomCharacter(req, game); + res.json(randomCharacter[0]); } catch (err) { - LOGGER.error(`Could not fetch random character \n${err}`); + LOGGER.error(`Could not fetch random character`, err); res.status(500).json({error: "Failed to fetch random character"}); } }; export const getJSRCharacters = async (req, res) => { try { - res.send(await fetchJSRCharacters(req)); + res.send(await fetchCharacters(req, JSR_DB)); } catch (err) { - LOGGER.error(`Could not fetch JSR Characters \n${err}`); + LOGGER.error(`Could not fetch JSR Characters`, err); + res.status(500).json({error: "Failed to fetch JSR characters"}); } }; export const getJSRFCharacters = async (req, res) => { try { - res.send(await fetchJSRFCharacters(req)); + res.send(await fetchCharacters(req, JSRF_DB)); } catch (err) { - LOGGER.error(`Could not fetch JSRF Characters \n${err}`); + LOGGER.error(`Could not fetch JSRF Characters`, err); + res.status(500).json({error: "Failed to fetch JSRF characters"}); } }; export const getBRCCharacters = async (req, res) => { try { - res.send(await fetchBRCCharacters(req)); + res.send(await fetchCharacters(req, BRC_DB)); } catch (err) { - LOGGER.error(`Could not fetch BRC Characters \n${err}`); + LOGGER.error(`Could not fetch BRC Characters`, err); + res.status(500).json({error: "Failed to fetch BRC characters"}); } }; export const getJSRCharacterById = async (req, res) => { try { const id = req?.params?.id; - res.send(await performJSRAction(Actions.fetchById, Character, id)); + if (!id) { + return res.status(400).json({error: "Missing character ID"}); + } + res.send(await performDBAction(Actions.fetchById, JSR_DB, Character, id)); } catch (err) { - LOGGER.error(`Could not fetch JSR Character With ID: ${id} \n${err}`); + LOGGER.error(`Could not fetch JSR Character With ID: ${id}`, err); + res + .status(500) + .json({error: `Failed to fetch JSR character with ID ${id}`}); } }; export const getJSRFCharacterById = async (req, res) => { try { const id = req?.params?.id; - res.send(await performJSRFAction(Actions.fetchById, Character, id)); + if (!id) { + return res.status(400).json({error: "Missing character ID"}); + } + res.send(await performDBAction(Actions.fetchById, JSRF_DB, Character, id)); } catch (err) { - LOGGER.error(`Could not fetch JSRF Character With ID: ${id} \n${err}`); + LOGGER.error(`Could not fetch JSRF Character With ID: ${id}`, err); + res + .status(500) + .json({error: `Failed to fetch JSRF character with ID ${id}`}); } }; export const getBRCCharacterById = async (req, res) => { try { const id = req?.params?.id; - res.send(await performBRCAction(Actions.fetchById, Character, id)); + if (!id) { + return res.status(400).json({error: "Missing character ID"}); + } + res.send(await performDBAction(Actions.fetchById, BRC_DB, Character, id)); } catch (err) { - LOGGER.error(`Could not fetch BRC Character With ID: ${id} \n${err}`); + LOGGER.error(`Could not fetch BRC Character With ID: ${id}`, err); + res + .status(500) + .json({error: `Failed to fetch BRC character with ID ${id}`}); } }; -export const fetchJSRCharacters = async (req) => { - return await performJSRAction( - Actions.fetchWithQuery, - Character, - null, - req?.query - ); -}; +export const fetchCharacters = async (req, dbName) => { + if (dbName === "ALL") { + const jsrCharacters = await fetchCharacters(req, JSR_DB); + const jsrfCharacters = await fetchCharacters(req, JSRF_DB); + const brcCharacters = await fetchCharacters(req, BRC_DB); + const allCharacters = [ + ...jsrCharacters, + ...jsrfCharacters, + ...brcCharacters, + ]; + return allCharacters; + } -export const fetchJSRFCharacters = async (req) => { - return await performJSRFAction( + return await performDBAction( Actions.fetchWithQuery, + dbName, Character, null, req?.query ); }; -export const fetchBRCCharacters = async (req) => { - return await performBRCAction( - Actions.fetchWithQuery, +export const fetchRandomCharacter = async (req, dbName) => { + return await performDBAction( + Actions.fetchRandom, + dbName, Character, null, req?.query diff --git a/src/controllers/collectibleController.js b/src/controllers/collectibleController.js index 7ff003d..d63653f 100644 --- a/src/controllers/collectibleController.js +++ b/src/controllers/collectibleController.js @@ -1,50 +1,71 @@ -import {performBRCAction} from "../config/db.js"; +import Constants from "../constants/dbConstants.js"; import {Actions} from "../config/dbActions.js"; -import LOGGER from "../utils/logger.js"; +import {performDBAction} from "../config/db.js"; import {sortObjects} from "../utils/utility.js"; +import LOGGER from "../utils/logger.js"; const Collectible = "Collectible"; +const {BRC_DB} = Constants; -export const getAllCollectibles = async (req, res) => { +export const getCollectibles = async (req, res) => { try { const sortByValue = req?.query?.sortBy ? req?.query?.sortBy : undefined; const sortOrder = req?.query?.orderBy ? req?.query?.orderBy : "asc"; - const brcCollectibles = await fetchBRCCollectibles(req); + const allCollectibles = await fetchCollectibles(req); if (sortByValue) { - const collectibles = [...brcCollectibles]; + const collectibles = [...allCollectibles]; return res.send(collectibles.sort(sortObjects(sortByValue, sortOrder))); } - res.send([...brcCollectibles]); + res.send([...allCollectibles]); } catch (err) { - LOGGER.error(`Could not fetch ALL Collectibles \n${err}`); + LOGGER.error(`Could not fetch ALL Collectibles`, err); + res + .status(500) + .json({message: "Failed to fetch ALL Collectibles", err: err}); } }; -export const getBRCCollectibles = async (req, res) => { +export const getRandomCollectible = async (req, res) => { try { - res.send(await fetchBRCTags(req)); + const randomCollectible = await fetchRandomCollectible(req, BRC_DB); + res.json(randomCollectible[0]); } catch (err) { - LOGGER.error(`Could not fetch BRC Collectible \n${err}`); + LOGGER.error(`Could not fetch random collectible`, err); + res.status(500).json({error: "Failed to fetch random collectible"}); } }; -export const getBRCCollectibleById = async (req, res) => { +export const getCollectibleById = async (req, res) => { try { - const tagId = req?.params?.id; - res.send(await performBRCAction(Actions.fetchById, Collectible, tagId)); + const id = req?.params?.id; + res.send(await performDBAction(Actions.fetchById, BRC_DB, Collectible, id)); } catch (err) { - LOGGER.error(`Could not fetch BRC Collectible With ID: ${tagId} \n${err}`); + LOGGER.error(`Could not fetch Collectible With ID: ${tagId}`, err); + res + .status(500) + .json({message: `Failed to fetch Collectible by Id ${id}`, err: err}); } }; -export const fetchBRCCollectibles = async (req) => { +export const fetchCollectibles = async (req) => { if (req?.query) { - return await performBRCAction( + return await performDBAction( Actions.fetchWithQuery, + BRC_DB, Collectible, null, req?.query ); } - return await performBRCAction(Actions.fetchAll, Collectible, null); + return await performDBAction(Actions.fetchAll, BRC_DB, Collectible, null); +}; + +export const fetchRandomCollectible = async (req, dbName) => { + return await performDBAction( + Actions.fetchRandom, + dbName, + Collectible, + null, + req?.query + ); }; diff --git a/src/controllers/gameController.js b/src/controllers/gameController.js index 81e4259..b7a29b8 100644 --- a/src/controllers/gameController.js +++ b/src/controllers/gameController.js @@ -1,30 +1,36 @@ -import { performCoreAction } from "../config/db.js"; -import { Actions } from "../config/dbActions.js"; +import Constants from "../constants/dbConstants.js"; +import {Actions} from "../config/dbActions.js"; +import {performDBAction} from "../config/db.js"; - -const Game = 'Game'; +const Game = "Game"; +const {CORE_DB} = Constants; export const getAllGames = async (req, res) => { try { res.send(await fetchGames(req?.query)); - } catch(err) { - res.status(500).send(err); + } catch (err) { + res.status(500).send("Error fetching ALL Games", err); } -} +}; export const getGameById = async (req, res) => { try { res.send(await fetchGameById(req?.params?.id)); - } catch(err) { - res.send(err); + } catch (err) { + res.send(`Error fetching game by ID ${req?.params?.id}`, err); } -} - +}; export const fetchGames = async (query) => { - return await performCoreAction(Actions.fetchWithQuery, Game, null, query); -} + return await performDBAction( + Actions.fetchWithQuery, + CORE_DB, + Game, + null, + query + ); +}; export const fetchGameById = async (id) => { - return await performCoreAction(Actions.fetchById, Game, id) -} \ No newline at end of file + return await performDBAction(Actions.fetchById, CORE_DB, Game, id); +}; diff --git a/src/controllers/graffitiTagController.js b/src/controllers/graffitiTagController.js index c5de182..112cc94 100644 --- a/src/controllers/graffitiTagController.js +++ b/src/controllers/graffitiTagController.js @@ -1,15 +1,16 @@ -import { performJSRAction, performJSRFAction } from "../config/db.js"; -import { Actions } from "../config/dbActions.js"; +import Constants from "../constants/dbConstants.js"; +import {Actions} from "../config/dbActions.js"; +import {performDBAction} from "../config/db.js"; +import {sortObjects} from "../utils/utility.js"; import LOGGER from "../utils/logger.js"; -import { sortObjects } from "../utils/utility.js"; - -const GraffitiTag = 'GraffitiTag'; +const GraffitiTag = "GraffitiTag"; +const {JSR_DB, JSRF_DB} = Constants; export const getAllGraffitiTags = async (req, res) => { try { const sortByValue = req?.query?.sortBy ? req?.query?.sortBy : undefined; - const sortOrder = req?.query?.orderBy ? req?.query?.orderBy : 'asc'; + const sortOrder = req?.query?.orderBy ? req?.query?.orderBy : "asc"; const jsrTags = await fetchJSRTags(req); const jsrfTags = await fetchJSRFTags(req); if (sortByValue) { @@ -17,55 +18,86 @@ export const getAllGraffitiTags = async (req, res) => { return res.send(tags.sort(sortObjects(sortByValue, sortOrder))); } res.send([...jsrTags, ...jsrfTags]); - } catch(err) { - LOGGER.error(`Could not fetch ALL GraffitiTags \n${err}`); + } catch (err) { + LOGGER.error(`Could not fetch ALL GraffitiTags`, err); + res + .status(500) + .json({message: "Failed to fetch ALL GraffitiTags", err: err}); } -} +}; export const getJSRGraffitiTags = async (req, res) => { try { res.send(await fetchJSRTags(req)); - } catch(err) { - LOGGER.error(`Could not fetch JSR GraffitiTags \n${err}`); + } catch (err) { + LOGGER.error(`Could not fetch JSR GraffitiTags`, err); + res + .status(500) + .json({message: "Failed to fetch JSR GraffitiTags", err: err}); } -} +}; export const getJSRFGraffitiTags = async (req, res) => { try { res.send(await fetchJSRFTags(req)); - } catch(err) { - LOGGER.error(`Could not fetch JSRF GraffitiTags \n${err}`) + } catch (err) { + LOGGER.error(`Could not fetch JSRF GraffitiTags`, err); + res + .status(500) + .json({message: "Failed to fetch JSRF GraffitiTags", err: err}); } -} +}; export const getJSRGraffitiTagById = async (req, res) => { try { const tagId = req?.params?.id; - res.send(await performJSRAction(Actions.fetchById, GraffitiTag, tagId)); - } catch(err) { - LOGGER.error(`Could not fetch JSR GraffitiTag With ID: ${tagId} \n${err}`); + res.send( + await performDBAction(Actions.fetchById, JSR_DB, GraffitiTag, tagId) + ); + } catch (err) { + LOGGER.error(`Could not fetch JSR GraffitiTag With ID: ${tagId}`, err); + res + .status(500) + .json({message: "Failed to fetch JSR GraffitiTag By ID", err: err}); } -} +}; export const getJSRFGraffitiTagById = async (req, res) => { try { const tagId = req?.params?.id; - res.send(await performJSRFAction(Actions.fetchById, GraffitiTag, tagId)); - } catch(err) { - LOGGER.error(`Could not fetch JSRF GraffitiTag With ID: ${tagId} \n${err}`); + res.send( + await performDBAction(Actions.fetchById, JSRF_DB, GraffitiTag, tagId) + ); + } catch (err) { + LOGGER.error(`Could not fetch JSRF GraffitiTag With ID: ${tagId}`, err); + res + .status(500) + .json({message: "Failed to fetch JSRF GraffitiTag By ID", err: err}); } -} +}; export const fetchJSRTags = async (req) => { if (req?.query) { - return await performJSRAction(Actions.fetchWithQuery, GraffitiTag, null, req?.query); + return await performDBAction( + Actions.fetchWithQuery, + JSR_DB, + GraffitiTag, + null, + req?.query + ); } - return await performJSRAction(Actions.fetchAll, GraffitiTag, null); -} + return await performDBAction(Actions.fetchAll, JSR_DB, GraffitiTag, null); +}; export const fetchJSRFTags = async (req) => { if (req?.query) { - return await performJSRFAction(Actions.fetchWithQuery, GraffitiTag, null, req?.query); + return await performDBAction( + Actions.fetchWithQuery, + JSRF_DB, + GraffitiTag, + null, + req?.query + ); } - return await performJSRFAction(Actions.fetchAll, GraffitiTag, null); -} \ No newline at end of file + return await performDBAction(Actions.fetchAll, JSRF_DB, GraffitiTag, null); +}; diff --git a/src/controllers/indexController.js b/src/controllers/indexController.js index 843219c..95c4d01 100644 --- a/src/controllers/indexController.js +++ b/src/controllers/indexController.js @@ -1,15 +1,14 @@ -import { fileURLToPath } from 'url'; -import path, { dirname } from 'path'; -import dotenv from 'dotenv'; +import {fileURLToPath} from "url"; +import path, {dirname} from "path"; +import dotenv from "dotenv"; dotenv.config(); - const __dirname = dirname(fileURLToPath(import.meta.url)); export const renderHome = (req, res) => { - res.sendFile(path.join(__dirname, '..', 'public', 'index.html')); -} + res.sendFile(path.join(__dirname, "..", "public", "index.html")); +}; export const renderDocs = (req, res) => { - res.sendFile(path.join(__dirname, '..', 'public', 'docs.html')) -} \ No newline at end of file + res.sendFile(path.join(__dirname, "..", "public", "docs.html")); +}; diff --git a/src/controllers/locationController.js b/src/controllers/locationController.js index 929fe9c..7330728 100644 --- a/src/controllers/locationController.js +++ b/src/controllers/locationController.js @@ -1,80 +1,103 @@ -import { - performJSRAction, - performJSRFAction, - performBRCAction, -} from "../config/db.js"; +import Constants from "../constants/dbConstants.js"; import {Actions} from "../config/dbActions.js"; -import LOGGER from "../utils/logger.js"; +import {performDBAction} from "../config/db.js"; import {sortObjects} from "../utils/utility.js"; +import LOGGER from "../utils/logger.js"; const Location = "Location"; const Level = "Level"; +const {JSR_DB, JSRF_DB, BRC_DB, gameMap} = Constants; export const getLocations = async (req, res) => { try { const sortByValue = req?.query?.sortBy ? req?.query?.sortBy : undefined; const sortOrder = req?.query?.orderBy ? req?.query?.orderBy : "asc"; - const jsrLocations = await fetchJSRLocations(req); - const jsrfLocations = await fetchJSRFLocations(req); - const brcLocations = await fetchBRCLocations(req); + const locations = await fetchLocations(req, "ALL"); if (sortByValue) { - const locations = [...jsrLocations, ...jsrfLocations, ...brcLocations]; return res.send(locations.sort(sortObjects(sortByValue, sortOrder))); } - res.send([...jsrLocations, ...jsrfLocations, ...brcLocations]); + res.send(locations); + } catch (err) { + LOGGER.error(`Could not fetch ALL Locations`, err); + res.status(500).json({message: "Failed to fetch ALL locations", err: err}); + } +}; + +export const getRandomLocation = async (req, res) => { + try { + const games = [JSR_DB, JSRF_DB, BRC_DB]; + const userSelectedGame = req?.query?.game; + let game = + gameMap[userSelectedGame] || + games[Math.floor(Math.random() * games.length)]; + const randomLocation = await fetchRandomLocation(req, game); + res.json(randomLocation[0]); } catch (err) { - LOGGER.error(`Could not fetch ALL Locations \n${err}`); + LOGGER.error(`Could not fetch random location`, err); + res.status(500).json({error: "Failed to fetch random location"}); } }; export const getJSRLocations = async (req, res) => { try { - res.send(await fetchJSRLocations(req)); + res.send(await fetchLocations(req, JSR_DB)); } catch (err) { - LOGGER.error(`Could not fetch JSR Locations \n${err}`); + LOGGER.error(`Could not fetch JSR Locations`, err); + res.status(500).json({message: "Failed to fetch JSR locations", err: err}); } }; export const getJSRLocationById = async (req, res) => { try { const id = req?.params?.id; - res.send(await performJSRAction(Actions.fetchById, Location, id)); + res.send(await performDBAction(Actions.fetchById, JSR_DB, Location, id)); } catch (err) { - LOGGER.error(`Could not fetch JSR Location With ID: ${id} \n${err}`); + LOGGER.error(`Could not fetch JSR Location With ID: ${id}`, err); + res + .status(500) + .json({message: `Failed to fetch JSR location By ID ${id}`, err: err}); } }; export const getJSRFLocations = async (req, res) => { try { - res.send(await fetchJSRFLocations(req)); + res.send(await fetchLocations(req, JSRF_DB)); } catch (err) { - LOGGER.error(`Could not fetch JSRF Locations \n${err}`); + LOGGER.error(`Could not fetch JSRF Locations`, err); + res.status(500).json({message: "Failed to fetch JSRF locations", err: err}); } }; export const getJSRFLocationById = async (req, res) => { try { const id = req?.params?.id; - res.send(await performJSRFAction(Actions.fetchById, Location, id)); + res.send(await performDBAction(Actions.fetchById, JSRF_DB, Location, id)); } catch (err) { - LOGGER.error(`Could not fetch JSRF Location With ID: ${id} \n${err}`); + LOGGER.error(`Could not fetch JSRF Location With ID: ${id}`, err); + res + .status(500) + .json({message: `Failed to fetch JSRF location By ID ${id}`, err: err}); } }; export const getBRCLocations = async (req, res) => { try { - res.send(await fetchBRCLocations(req)); + res.send(await fetchLocations(req, BRC_DB)); } catch (err) { - LOGGER.error(`Could not fetch BRC Locations \n${err}`); + LOGGER.error(`Could not fetch BRC Locations`, err); + res.status(500).json({message: "Failed to fetch BRC locations", err: err}); } }; export const getBRCLocationById = async (req, res) => { try { const id = req?.params?.id; - res.send(await performBRCAction(Actions.fetchById, Location, id)); + res.send(await performDBAction(Actions.fetchById, BRC_DB, Location, id)); } catch (err) { - LOGGER.error(`Could not fetch BRC Location With ID: ${id} \n${err}`); + LOGGER.error(`Could not fetch BRC Location With ID: ${id}`, err); + res + .status(500) + .json({message: `Failed to fetch BRC location By ID ${id}`, err: err}); } }; @@ -82,63 +105,60 @@ export const getLevels = async (req, res) => { try { res.send(await fetchJSRLevels(req)); } catch (err) { - LOGGER.error(`Could not fetch JSR Levels \n${err}`); + LOGGER.error(`Could not fetch JSR Levels`, err); + res.status(500).json({message: "Failed to fetch JSR levels", err: err}); } }; export const getLevelById = async (req, res) => { try { const id = req?.params?.id; - res.send(await performJSRAction(Actions.fetchById, Level, id)); + res.send(await performDBAction(Actions.fetchById, JSR_DB, Level, id)); } catch (err) { - LOGGER.error(`Could not fetch JSR Level with ID ${id} \n${err}`); + LOGGER.error(`Could not fetch JSR Level with ID ${id}`, err); + res + .status(500) + .json({message: `Failed to fetch JSR level By ID ${id}`, err: err}); } }; export const fetchJSRLevels = async (req) => { if (req?.query) { - return await performJSRAction( + return await performDBAction( Actions.fetchWithQuery, + JSR_DB, Level, null, req?.query ); } - return await performJSRAction(Actions.fetchAll, Level, null); + return await performDBAction(Actions.fetchAll, JSR_DB, Level, null); }; -export const fetchJSRLocations = async (req) => { - if (req?.query) { - return await performJSRAction( - Actions.fetchWithQuery, - Location, - null, - req?.query - ); +export const fetchLocations = async (req, dbName) => { + if (dbName === "ALL") { + const jsrLocations = await fetchLocations(req, JSR_DB); + const jsrfLocations = await fetchLocations(req, JSRF_DB); + const brcLocations = await fetchLocations(req, BRC_DB); + const allLocations = [...jsrLocations, ...jsrfLocations, ...brcLocations]; + return allLocations; } - return await performJSRAction(Actions.fetchAll, Location, null); -}; -export const fetchJSRFLocations = async (req) => { - if (req?.query) { - return await performJSRFAction( - Actions.fetchWithQuery, - Location, - null, - req?.query - ); - } - return await performJSRFAction(Actions.fetchAll, Location, null); + return await performDBAction( + Actions.fetchWithQuery, + dbName, + Location, + null, + req?.query + ); }; -export const fetchBRCLocations = async (req) => { - if (req?.query) { - return await performBRCAction( - Actions.fetchWithQuery, - Location, - null, - req?.query - ); - } - return await performBRCAction(Actions.fetchAll, Location, null); +export const fetchRandomLocation = async (req, dbName) => { + return await performDBAction( + Actions.fetchRandom, + dbName, + Location, + null, + req?.query + ); }; diff --git a/src/controllers/songController.js b/src/controllers/songController.js index c0f1c6a..a2a2cbf 100644 --- a/src/controllers/songController.js +++ b/src/controllers/songController.js @@ -1,107 +1,164 @@ -import { performJSRAction, performJSRFAction, performBRCAction } from "../config/db.js"; -import { Actions } from "../config/dbActions.js"; -import { sortObjects } from "../utils/utility.js"; +import Constants from "../constants/dbConstants.js"; +import {Actions} from "../config/dbActions.js"; +import {performDBAction} from "../config/db.js"; +import {sortObjects} from "../utils/utility.js"; +import LOGGER from "../utils/logger.js"; - -const Song = 'Song'; +const Song = "Song"; +const {JSR_DB, JSRF_DB, BRC_DB, gameMap} = Constants; export const getSongs = async (req, res) => { try { const sortByValue = req?.query?.sortBy ? req?.query?.sortBy : undefined; - const sortOrder = req?.query?.orderBy ? req?.query?.orderBy : 'asc'; - const jsrSongs = await fetchJSRSongs(req); - const jsrfSongs = await fetchJSRFSongs(req); - const brcSongs = await fetchBRCSongs(req); + const sortOrder = req?.query?.orderBy ? req?.query?.orderBy : "asc"; + const songs = await fetchSongs(req, "ALL"); if (sortByValue) { - const songs = [...jsrSongs, ...jsrfSongs, ...brcSongs]; return res.send(songs.sort(sortObjects(sortByValue, sortOrder))); } - res.send([...jsrSongs, ...jsrfSongs, ...brcSongs]); - } catch(err) { - res.status(500).send(`Could not fetch ALL SONGS due to error: \n${err}`); + res.send(songs); + } catch (err) { + LOGGER.error(`Could not fetch ALL Songs`, err); + res.status(500).send(`Could not fetch ALL SONGS due to error:`, err); + } +}; + +export const getRandomSong = async (req, res) => { + try { + const games = [JSR_DB, JSRF_DB, BRC_DB]; + const userSelectedGame = req?.query?.game; + let game = + gameMap[userSelectedGame] || + games[Math.floor(Math.random() * games.length)]; + const randomSong = await fetchRandomSong(req, game); + res.json(randomSong[0]); + } catch (err) { + LOGGER.error(`Could not fetch random song`, err); + res.status(500).json({error: "Failed to fetch random song"}); } -} +}; export const getJSRSongs = async (req, res) => { try { - const jsrSongs = await fetchJSRSongs(req); + const jsrSongs = await fetchSongs(req, JSR_DB); if (jsrSongs) { return res.send(jsrSongs); } res.status(404).send(); - } catch(err) { - res.status(500).send(`Could not fetch JSR SONGS \n${err}`); + } catch (err) { + LOGGER.error(`Could not fetch JSR Songs`, err); + res.status(500).send(`Could not fetch JSR SONGS`, err); } -} +}; export const getJSRSongById = async (req, res) => { try { - const jsrSong = await performJSRAction(Actions.fetchById, Song, id); + const id = req?.params?.id; + const jsrSong = await performDBAction(Actions.fetchById, JSR_DB, Song, id); if (jsrSong) { return res.send(jsrSong); } - res.status(404).send(`JSR Song Resource could not be found at requested location`); - } catch(err) { - res.status(500).send(`Could not fetch JSR SONG with ID: ${req.params.id} \n${err}`); + res + .status(404) + .send(`JSR Song Resource could not be found at requested location`); + } catch (err) { + LOGGER.error(`Could not fetch JSR Song by ID ${req?.params?.id}`, err); + res + .status(500) + .send(`Could not fetch JSR SONG with ID: ${req.params.id}`, err); } -} +}; export const getJSRFSongs = async (req, res) => { try { - const jsrfSongs = await fetchJSRFSongs(req); + const jsrfSongs = await fetchSongs(req, JSRF_DB); if (jsrfSongs) { return res.send(jsrfSongs); } res.status(404).send(); - } catch(err) { - res.status(500).send(`Could not fetch JSRF Songs \n${err}`); + } catch (err) { + LOGGER.error(`Could not fetch JSRF Songs`, err); + res.status(500).send(`Could not fetch JSRF Songs`, err); } -} +}; export const getJSRFSongById = async (req, res) => { try { - const jsrfSong = await performJSRFAction(Actions.fetchById, Song, id); + const id = req?.params?.id; + const jsrfSong = await performDBAction( + Actions.fetchById, + JSRF_DB, + Song, + id + ); if (jsrfSong) { return res.send(jsrfSong); } - res.status(404).send(`JSRF Song Resource could not be found at requested location`); - } catch(err) { - res.status(500).send(`Could not fetch JSRF SONG with ID: ${req.params.id} \n${err}`); + res + .status(404) + .send(`JSRF Song Resource could not be found at requested location`); + } catch (err) { + LOGGER.error(`Could not fetch JSRF Song by ID`, err); + res + .status(500) + .send(`Could not fetch JSRF SONG with ID: ${req.params.id}`, err); } -} +}; export const getBRCSongs = async (req, res) => { try { - const brcSongs = await fetchBRCSongs(req); + const brcSongs = await fetchSongs(req, BRC_DB); if (brcSongs) { return res.send(brcSongs); } res.status(404).send(); - } catch(err) { - res.status(500).send(`Could not fetch BRC Songs \n${err}`); + } catch (err) { + LOGGER.error(`Could not fetch BRC Songs`, err); + res.status(500).send(`Could not fetch BRC Songs`, err); } -} +}; export const getBRCSongById = async (req, res) => { try { - const brcSong = await performBRCAction(Actions.fetchById, Song, id); + const id = req?.params?.id; + const brcSong = await performDBAction(Actions.fetchById, BRC_DB, Song, id); if (brcSong) { return res.send(brcSong); } - res.status(404).send(`BRC Song Resource could not be found at requested location`); - } catch(err) { - res.status(500).send(`Could not fetch BRC SONG with ID: ${req.params.id} \n${err}`); + res + .status(404) + .send(`BRC Song Resource could not be found at requested location`); + } catch (err) { + LOGGER.error(`Could not fetch BRC Song By Id`, err); + res + .status(500) + .send(`Could not fetch BRC Song with ID: ${req.params.id}`, err); + } +}; + +export const fetchSongs = async (req, dbName) => { + if (dbName === "ALL") { + const jsrSongs = await fetchSongs(req, JSR_DB); + const jsrfSongs = await fetchSongs(req, JSRF_DB); + const brcSongs = await fetchSongs(req, BRC_DB); + const songs = [...jsrSongs, ...jsrfSongs, ...brcSongs]; + return songs; } -} -export const fetchJSRSongs = async (req) => { - return await performJSRAction(Actions.fetchWithQuery, Song, null, req?.query); -} - -export const fetchJSRFSongs = async (req) => { - return await performJSRFAction(Actions.fetchWithQuery, Song, null, req?.query); -} + return await performDBAction( + Actions.fetchWithQuery, + dbName, + Song, + null, + req?.query + ); +}; -export const fetchBRCSongs = async (req) => { - return await performBRCAction(Actions.fetchWithQuery, Song, null, req?.query); -} \ No newline at end of file +export const fetchRandomSong = async (req, dbName) => { + return await performDBAction( + Actions.fetchRandom, + dbName, + Song, + null, + req?.query + ); +}; diff --git a/src/docs/CONTRIBUTE.md b/src/docs/CONTRIBUTE.md index ab0de78..f9a21fc 100644 --- a/src/docs/CONTRIBUTE.md +++ b/src/docs/CONTRIBUTE.md @@ -25,7 +25,14 @@ Src - `Utils`: A list of helper functions and services such as winston logging and swagger doc configs Test + - `Data`: Contains test data used for application unit tests - `Helper`: Contains a list of fuctions to use the `mongodb-memory-server` in integration tests. - Every other file contains unit and integration tests relating to a specific resource. Before submitting your changes, make sure to run `npm run test` to verify all tests pass successfully. + +How to run tests: + - To run ALL Tests: `npm run test` + - To test individual files + - `npm run test:file -- test/locations.test.js` + diff --git a/src/docs/DEV_SETUP.md b/src/docs/DEV_SETUP.md index 86879d7..a770f6c 100644 --- a/src/docs/DEV_SETUP.md +++ b/src/docs/DEV_SETUP.md @@ -13,9 +13,10 @@ This page will guide you on setting up a development environment for this projec ```sh git clone git@github.com:Jet-Set-Radio-API/JetSetRadio-API.git ``` + 2. Install Dependencies ```sh - npm install + nvm use && npm install ``` 3. [Create](https://account.mongodb.com/account/login) a local MongoDB Database or in Atlas @@ -42,15 +43,16 @@ This page will guide you on setting up a development environment for this projec # MONGO DATABASES (names do not matter) JSR_DB= JSRF_DB= + BRC_DB CORE_DB= ``` - The databases section in the env file are names of the databases. For development purposes it does not matter what these names are just as long as you can distinguish one from the other and you know which one is Core, JSR, or JSRF. + The databases section in the env file are names of the databases. For development purposes it does not matter what these names are just as long as you can distinguish one from the other and you know which one is which. 6. Run the project ```sh npm run qa ``` -7. (Optional) - To populate your local database with production data, hit the /pipe route in your browser or using Postman. The logs should indicate if the piping was successful. +7. (Optional) - To populate your local database with production data, hit the /pipe route in your browser or using Postman. The logs will indicate if the piping was successful. ```sh http://localhost:3005/pipe ``` diff --git a/src/managers/HealthCheckManager.js b/src/managers/HealthCheckManager.js index 3caab9c..a11876f 100644 --- a/src/managers/HealthCheckManager.js +++ b/src/managers/HealthCheckManager.js @@ -5,11 +5,11 @@ class HealthCheckManager { return { uptimeInSeconds: process.uptime(), responseTime: process.hrtime(), - message: 'OK', + message: "OK", timestamp: process.hrtime(), - nodeVersion: process.version + nodeVersion: process.version, }; } } -export default HealthCheckManager; \ No newline at end of file +export default HealthCheckManager; diff --git a/src/managers/MiddlewareManager.js b/src/managers/MiddlewareManager.js index 13db649..8b08edc 100644 --- a/src/managers/MiddlewareManager.js +++ b/src/managers/MiddlewareManager.js @@ -21,7 +21,7 @@ import {renderHome, renderDocs} from "../controllers/indexController.js"; import {listCollections} from "../config/db.js"; import LOGGER from "../utils/logger.js"; import {Actions} from "../config/dbActions.js"; -import {performCoreAdminAction} from "../config/db.js"; +import {performAdminAction} from "../config/db.js"; const cache = new MemoryCache.Cache(); const __dirname = dirname(fileURLToPath(import.meta.url)); @@ -97,7 +97,6 @@ const filterPipeRoutes = async (req, endpoints) => { const filteredEndpoints = []; for (const endpoint of endpoints) { const model = endpoint.split("/")[3].replace("-", ""); - console.log("processing model: ", model, " and endpoint: ", endpoint); if (coreCollections.includes(model)) { filteredEndpoints.push(endpoint); } @@ -167,7 +166,7 @@ const cacheMiddleware = (req, res, next) => { const clearCache = async (req, res) => { const username = req?.body?.username; const password = req?.body?.password; - const adminUser = await performCoreAdminAction(Actions.fetchAdmin, username); + const adminUser = await performAdminAction(Actions.fetchAdmin, username); if (!adminUser) { LOGGER.error("Admin User Not Found"); return res.status(400).send(); diff --git a/src/public/docs.html b/src/public/docs.html index 6b29bc7..97998a0 100644 --- a/src/public/docs.html +++ b/src/public/docs.html @@ -114,6 +114,10 @@
Endpoints:
/characters ==> Returns all Characters/characters/random ==> Returns a Random Character From any Game/characters/random?game=jsr ==> Returns a Random JSR Character/characters/random?game=jsrf ==> Returns a Random JSRF Character/characters/random?game=brc ==> Returns a Random BRC Character/characters/jsr ==> Returns all Jet Set Radio Characters/characters/jsr/:id ==> Returns a single JSR Character by ID/characters/jsrf ==> Returns all Jet Set Radio Future CharactersEndpoints:
/locations ==> Returns all Locations/locations/random ==> Returns a Random Location From any Game/locations/random?game=jsr ==> Returns a Random JSR Location/locations/random?game=jsrf ==> Returns a Random JSRF Location/locations/random?game=brc ==> Returns a Random BRC Location/locations/jsr ==> Returns all Jet Set Radio Locations/locations/jsr/:id ==> Returns a single JSR Location by ID/locations/jsrf ==> Returns all Jet Set Radio Future Locations/graffiti-tags/jsr/:id ==> Returns a single JSR Graffiti-Tag by ID/graffiti-tags/jsrf ==> Returns all Jet Set Radio Future Graffiti-Tags/graffiti-tags/jsrf/:id ==> Returns a single JSRF Graffiti-Tag by ID/graffiti-tags/jsr?size=XL ==> Returns all JSR XL Graffiti-Tags/graffiti-tags?tagName=POISON JAM ==> Returns Graffiti-Tags with tagNameExample Request:
https://jetsetradio-api.onrender.com/v1/api/graffiti-tags?sortBy=size&orderBy=desc
@@ -183,6 +193,10 @@ Endpoints:
/songs ==> Returns all Songs/songs/random ==> Returns a Random Song From any Game/songs/random?game=jsr ==> Returns a Random JSR Song/songs/random?game=jsrf ==> Returns a Random JSRF Song/songs/random?game=brc ==> Returns a Random BRC Song/songs/jsr ==> Returns all Jet Set Radio Songs/songs/jsr/:id ==> Returns a single JSR Song by ID/songs/jsrf ==> Returns all Jet Set Radio Future Songs/artists ==> Returns all Artists/artists/:id ==> Returns a single Artist by ID/artists/:id/songs ==> Returns all Songs by Artist/artists/:id/songs ==> Returns all Songs by A Single ArtistExample Request:
https://jetsetradio-api.onrender.com/v1/api/artists/643865b5af5362b86b844d75
@@ -217,7 +231,10 @@ Endpoints:
/collectibles ==> Returns all Collectibles/collectibles/random ==> Returns a Random Collectible/collectibles/:id ==> Returns a single Collectible by ID/collectibles?type=CD ==> Returns all CD Collectibles/collectibles?character.name=Shine ==> Returns Shine's CollectiblesExample Request:
https://jetsetradio-api.onrender.com/v1/api/collectibles?sortBy=name&limit=5
diff --git a/src/public/js/examples/collectibleExample.js b/src/public/js/examples/collectibleExample.js
index 409f996..fb2032d 100644
--- a/src/public/js/examples/collectibleExample.js
+++ b/src/public/js/examples/collectibleExample.js
@@ -3,14 +3,13 @@ const collectibleResource = {
gallery: [
{
name: "collectible",
- image:
- "https://static.wikia.nocookie.net/bomb-rush-cyberfunk/images/6/68/PyramidIslandMusic4.jpg/revision/latest/scale-to-width-down/1000?cb=20240809021710",
+ image: "https://static.wikia.nocookie.net/bomb-rush-cyberfunk...",
},
],
name: "AGUA",
key: "AGUA-CD",
description:
- "on a ledge near the gate that Rise guards in the beginning of the area. Face the gate after getting into the area from the ferry, it is on the ledge to the left of it. In order to get this CD, you need to get past said gate, then navigate until you're one level above the main floor. Then you need to drop down to the ledge from there.",
+ "on a ledge near the gate that Rise guards in the beginning of the area.",
type: "CD",
unlockedByDefault: false,
gameId: "68b8d3cff2538137a7c2fc2e",
diff --git a/src/routes/collectibleRouter.js b/src/routes/collectibleRouter.js
index fdc2832..95b04e4 100644
--- a/src/routes/collectibleRouter.js
+++ b/src/routes/collectibleRouter.js
@@ -1,12 +1,14 @@
import express from "express";
import {
- getAllCollectibles,
- getBRCCollectibleById,
+ getCollectibles,
+ getRandomCollectible,
+ getCollectibleById,
} from "../controllers/collectibleController.js";
const collectibles = express.Router();
-collectibles.get("/", async (req, res) => /* #swagger.tags = ['Collectibles'] */ await getAllCollectibles(req, res));
-collectibles.get("/:id", async (req, res) => /* #swagger.tags = ['Collectibles'] */ await getBRCCollectibleById(req, res));
+collectibles.get("/", async (req, res) => /* #swagger.tags = ['Collectibles'] */ await getCollectibles(req, res));
+collectibles.get("/random", async (req, res) => /* #swagger.tags = ['Collectibles'] */ await getRandomCollectible(req, res));
+collectibles.get("/:id", async (req, res) => /* #swagger.tags = ['Collectibles'] */ await getCollectibleById(req, res));
export default collectibles;
diff --git a/src/routes/locationRouter.js b/src/routes/locationRouter.js
index 407d998..6749aa2 100644
--- a/src/routes/locationRouter.js
+++ b/src/routes/locationRouter.js
@@ -1,6 +1,7 @@
import express from 'express';
import {
getLocations,
+ getRandomLocation,
getJSRLocations,
getJSRFLocations,
getJSRLocationById,
@@ -12,6 +13,7 @@ import {
export const locations = express.Router();
locations.get('/', async (req, res) => /* #swagger.tags = ['Locations'] */ await getLocations(req, res));
+locations.get('/random', async (req, res) => /* #swagger.tags = ['Locations'] */ await getRandomLocation(req, res));
locations.get('/jsr', async (req, res) => /* #swagger.tags = ['Locations'] */ await getJSRLocations(req, res));
locations.get('/jsr/:id', async (req, res) => /* #swagger.tags = ['Locations'] */ await getJSRLocationById(req, res));
locations.get('/jsrf', async (req, res) => /* #swagger.tags = ['Locations'] */ await getJSRFLocations(req, res));
diff --git a/src/routes/songRouter.js b/src/routes/songRouter.js
index 75d13b4..1b39d02 100644
--- a/src/routes/songRouter.js
+++ b/src/routes/songRouter.js
@@ -1,11 +1,12 @@
import express from 'express';
-import { getSongs, getJSRSongs, getJSRSongById, getJSRFSongs, getJSRFSongById, getBRCSongs, getBRCSongById } from '../controllers/songController.js';
+import { getSongs, getRandomSong, getJSRSongs, getJSRSongById, getJSRFSongs, getJSRFSongById, getBRCSongs, getBRCSongById } from '../controllers/songController.js';
const songs = express.Router();
songs.get('/', async (req, res) => /* #swagger.tags = ['Songs'] */ await getSongs(req, res));
+songs.get('/random', async (req, res) => /* #swagger.tags = ['Songs'] */ await getRandomSong(req, res));
songs.get('/jsr', async (req, res) => /* #swagger.tags = ['Songs'] */ await getJSRSongs(req, res));
songs.get('/jsr/:id', async (req, res) => /* #swagger.tags = ['Songs'] */ await getJSRSongById(req, res));
songs.get('/jsrf', async (req, res) => /* #swagger.tags = ['Songs'] */ await getJSRFSongs(req, res));
diff --git a/src/utils/logger.js b/src/utils/logger.js
index 2b20dfb..83818b3 100644
--- a/src/utils/logger.js
+++ b/src/utils/logger.js
@@ -1,8 +1,7 @@
-import { createLogger, transports, format } from 'winston';
-import dotenv from 'dotenv';
+import {createLogger, transports, format} from "winston";
+import dotenv from "dotenv";
dotenv.config();
-
const colors = {
debug: "magenta",
info: "cyan",
@@ -17,13 +16,13 @@ const LOGGER = createLogger({
format: format.combine(
format.colorize({
all: true,
- colors: colors
+ colors: colors,
}),
- format.timestamp(),
+ format.timestamp(),
format.simple()
),
- })
- ]
-})
+ }),
+ ],
+});
-export default LOGGER;
\ No newline at end of file
+export default LOGGER;
diff --git a/src/utils/swagger-docs.json b/src/utils/swagger-docs.json
index 4f96a3b..03cf9ff 100644
--- a/src/utils/swagger-docs.json
+++ b/src/utils/swagger-docs.json
@@ -2,7 +2,7 @@
"swagger": "2.0",
"info": {
"title": "JetSetRadio-API",
- "version": "1.1.0",
+ "version": "1.1.1",
"description": "Providing data for all things JSR and JSRF!"
},
"host": "localhost:9005",
@@ -37,11 +37,16 @@
"description": "Artist Data from JSR and JSRF"
}
],
- "schemes": ["https", "http"],
+ "schemes": [
+ "https",
+ "http"
+ ],
"paths": {
"/games/": {
"get": {
- "tags": ["Games"],
+ "tags": [
+ "Games"
+ ],
"description": "",
"responses": {
"200": {
@@ -55,7 +60,9 @@
},
"/games/{id}": {
"get": {
- "tags": ["Games"],
+ "tags": [
+ "Games"
+ ],
"description": "",
"parameters": [
{
@@ -74,7 +81,25 @@
},
"/songs/": {
"get": {
- "tags": ["Songs"],
+ "tags": [
+ "Songs"
+ ],
+ "description": "",
+ "responses": {
+ "200": {
+ "description": "OK"
+ },
+ "500": {
+ "description": "Internal Server Error"
+ }
+ }
+ }
+ },
+ "/songs/random": {
+ "get": {
+ "tags": [
+ "Songs"
+ ],
"description": "",
"responses": {
"200": {
@@ -88,7 +113,9 @@
},
"/songs/jsr": {
"get": {
- "tags": ["Songs"],
+ "tags": [
+ "Songs"
+ ],
"description": "",
"responses": {
"200": {
@@ -105,7 +132,9 @@
},
"/songs/jsr/{id}": {
"get": {
- "tags": ["Songs"],
+ "tags": [
+ "Songs"
+ ],
"description": "",
"parameters": [
{
@@ -130,7 +159,9 @@
},
"/songs/jsrf": {
"get": {
- "tags": ["Songs"],
+ "tags": [
+ "Songs"
+ ],
"description": "",
"responses": {
"200": {
@@ -147,7 +178,9 @@
},
"/songs/jsrf/{id}": {
"get": {
- "tags": ["Songs"],
+ "tags": [
+ "Songs"
+ ],
"description": "",
"parameters": [
{
@@ -172,7 +205,9 @@
},
"/songs/brc": {
"get": {
- "tags": ["Songs"],
+ "tags": [
+ "Songs"
+ ],
"description": "",
"responses": {
"200": {
@@ -189,7 +224,9 @@
},
"/songs/brc/{id}": {
"get": {
- "tags": ["Songs"],
+ "tags": [
+ "Songs"
+ ],
"description": "",
"parameters": [
{
@@ -214,7 +251,9 @@
},
"/artists/": {
"get": {
- "tags": ["Artists"],
+ "tags": [
+ "Artists"
+ ],
"description": "",
"responses": {
"200": {
@@ -231,7 +270,9 @@
},
"/artists/{id}": {
"get": {
- "tags": ["Artists"],
+ "tags": [
+ "Artists"
+ ],
"description": "",
"parameters": [
{
@@ -256,7 +297,9 @@
},
"/artists/{id}/songs": {
"get": {
- "tags": ["Artists"],
+ "tags": [
+ "Artists"
+ ],
"description": "",
"parameters": [
{
@@ -281,29 +324,41 @@
},
"/graffiti-tags/": {
"get": {
- "tags": ["GraffitiTags"],
+ "tags": [
+ "GraffitiTags"
+ ],
"description": "",
"responses": {
"200": {
"description": "OK"
+ },
+ "500": {
+ "description": "Internal Server Error"
}
}
}
},
"/graffiti-tags/jsr": {
"get": {
- "tags": ["GraffitiTags"],
+ "tags": [
+ "GraffitiTags"
+ ],
"description": "",
"responses": {
"200": {
"description": "OK"
+ },
+ "500": {
+ "description": "Internal Server Error"
}
}
}
},
"/graffiti-tags/jsr/{id}": {
"get": {
- "tags": ["GraffitiTags"],
+ "tags": [
+ "GraffitiTags"
+ ],
"description": "",
"parameters": [
{
@@ -316,24 +371,34 @@
"responses": {
"200": {
"description": "OK"
+ },
+ "500": {
+ "description": "Internal Server Error"
}
}
}
},
"/graffiti-tags/jsrf": {
"get": {
- "tags": ["GraffitiTags"],
+ "tags": [
+ "GraffitiTags"
+ ],
"description": "",
"responses": {
"200": {
"description": "OK"
+ },
+ "500": {
+ "description": "Internal Server Error"
}
}
}
},
"/graffiti-tags/jsrf/{id}": {
"get": {
- "tags": ["GraffitiTags"],
+ "tags": [
+ "GraffitiTags"
+ ],
"description": "",
"parameters": [
{
@@ -346,24 +411,34 @@
"responses": {
"200": {
"description": "OK"
+ },
+ "500": {
+ "description": "Internal Server Error"
}
}
}
},
"/characters/": {
"get": {
- "tags": ["Characters"],
+ "tags": [
+ "Characters"
+ ],
"description": "",
"responses": {
"200": {
"description": "OK"
+ },
+ "500": {
+ "description": "Internal Server Error"
}
}
}
},
"/characters/random": {
"get": {
- "tags": ["Characters"],
+ "tags": [
+ "Characters"
+ ],
"description": "",
"responses": {
"200": {
@@ -377,18 +452,25 @@
},
"/characters/jsr": {
"get": {
- "tags": ["Characters"],
+ "tags": [
+ "Characters"
+ ],
"description": "",
"responses": {
"200": {
"description": "OK"
+ },
+ "500": {
+ "description": "Internal Server Error"
}
}
}
},
"/characters/jsr/{id}": {
"get": {
- "tags": ["Characters"],
+ "tags": [
+ "Characters"
+ ],
"description": "",
"parameters": [
{
@@ -401,24 +483,37 @@
"responses": {
"200": {
"description": "OK"
+ },
+ "400": {
+ "description": "Bad Request"
+ },
+ "500": {
+ "description": "Internal Server Error"
}
}
}
},
"/characters/jsrf": {
"get": {
- "tags": ["Characters"],
+ "tags": [
+ "Characters"
+ ],
"description": "",
"responses": {
"200": {
"description": "OK"
+ },
+ "500": {
+ "description": "Internal Server Error"
}
}
}
},
"/characters/jsrf/{id}": {
"get": {
- "tags": ["Characters"],
+ "tags": [
+ "Characters"
+ ],
"description": "",
"parameters": [
{
@@ -431,24 +526,37 @@
"responses": {
"200": {
"description": "OK"
+ },
+ "400": {
+ "description": "Bad Request"
+ },
+ "500": {
+ "description": "Internal Server Error"
}
}
}
},
"/characters/brc": {
"get": {
- "tags": ["Characters"],
+ "tags": [
+ "Characters"
+ ],
"description": "",
"responses": {
"200": {
"description": "OK"
+ },
+ "500": {
+ "description": "Internal Server Error"
}
}
}
},
"/characters/brc/{id}": {
"get": {
- "tags": ["Characters"],
+ "tags": [
+ "Characters"
+ ],
"description": "",
"parameters": [
{
@@ -461,35 +569,69 @@
"responses": {
"200": {
"description": "OK"
+ },
+ "400": {
+ "description": "Bad Request"
+ },
+ "500": {
+ "description": "Internal Server Error"
}
}
}
},
"/locations/": {
"get": {
- "tags": ["Locations"],
+ "tags": [
+ "Locations"
+ ],
"description": "",
"responses": {
"200": {
"description": "OK"
+ },
+ "500": {
+ "description": "Internal Server Error"
+ }
+ }
+ }
+ },
+ "/locations/random": {
+ "get": {
+ "tags": [
+ "Locations"
+ ],
+ "description": "",
+ "responses": {
+ "200": {
+ "description": "OK"
+ },
+ "500": {
+ "description": "Internal Server Error"
}
}
}
},
"/locations/jsr": {
"get": {
- "tags": ["Locations"],
+ "tags": [
+ "Locations"
+ ],
"description": "",
"responses": {
"200": {
"description": "OK"
+ },
+ "500": {
+ "description": "Internal Server Error"
}
}
}
},
"/locations/jsr/{id}": {
"get": {
- "tags": ["Locations"],
+ "tags": [
+ "Locations"
+ ],
"description": "",
"parameters": [
{
@@ -502,24 +644,34 @@
"responses": {
"200": {
"description": "OK"
+ },
+ "500": {
+ "description": "Internal Server Error"
}
}
}
},
"/locations/jsrf": {
"get": {
- "tags": ["Locations"],
+ "tags": [
+ "Locations"
+ ],
"description": "",
"responses": {
"200": {
"description": "OK"
+ },
+ "500": {
+ "description": "Internal Server Error"
}
}
}
},
"/locations/jsrf/{id}": {
"get": {
- "tags": ["Locations"],
+ "tags": [
+ "Locations"
+ ],
"description": "",
"parameters": [
{
@@ -532,24 +684,34 @@
"responses": {
"200": {
"description": "OK"
+ },
+ "500": {
+ "description": "Internal Server Error"
}
}
}
},
"/locations/brc": {
"get": {
- "tags": ["Locations"],
+ "tags": [
+ "Locations"
+ ],
"description": "",
"responses": {
"200": {
"description": "OK"
+ },
+ "500": {
+ "description": "Internal Server Error"
}
}
}
},
"/locations/brc/{id}": {
"get": {
- "tags": ["Locations"],
+ "tags": [
+ "Locations"
+ ],
"description": "",
"parameters": [
{
@@ -562,24 +724,34 @@
"responses": {
"200": {
"description": "OK"
+ },
+ "500": {
+ "description": "Internal Server Error"
}
}
}
},
"/levels/": {
"get": {
- "tags": ["Levels"],
+ "tags": [
+ "Levels"
+ ],
"description": "",
"responses": {
"200": {
"description": "OK"
+ },
+ "500": {
+ "description": "Internal Server Error"
}
}
}
},
"/levels/{id}": {
"get": {
- "tags": ["Levels"],
+ "tags": [
+ "Levels"
+ ],
"description": "",
"parameters": [
{
@@ -592,24 +764,50 @@
"responses": {
"200": {
"description": "OK"
+ },
+ "500": {
+ "description": "Internal Server Error"
}
}
}
},
"/collectibles/": {
"get": {
- "tags": ["Collectibles"],
+ "tags": [
+ "Collectibles"
+ ],
+ "description": "",
+ "responses": {
+ "200": {
+ "description": "OK"
+ },
+ "500": {
+ "description": "Internal Server Error"
+ }
+ }
+ }
+ },
+ "/collectibles/random": {
+ "get": {
+ "tags": [
+ "Collectibles"
+ ],
"description": "",
"responses": {
"200": {
"description": "OK"
+ },
+ "500": {
+ "description": "Internal Server Error"
}
}
}
},
"/collectibles/{id}": {
"get": {
- "tags": ["Collectibles"],
+ "tags": [
+ "Collectibles"
+ ],
"description": "",
"parameters": [
{
@@ -622,9 +820,12 @@
"responses": {
"200": {
"description": "OK"
+ },
+ "500": {
+ "description": "Internal Server Error"
}
}
}
}
}
-}
+}
\ No newline at end of file
diff --git a/test/artists.test.js b/test/artists.test.js
index 81ef221..22db829 100644
--- a/test/artists.test.js
+++ b/test/artists.test.js
@@ -1,47 +1,51 @@
-import {jest} from '@jest/globals';
-import axios from 'axios';
-import dotenv from 'dotenv';
+import {jest} from "@jest/globals";
+import axios from "axios";
+import dotenv from "dotenv";
dotenv.config();
-import { connect, disconnect } from './helper/mongodbMemoryTest.js';
-import { fetchArtists, fetchSongsByArtistId } from '../src/controllers/artistController.js';
+import {connect, disconnect} from "./helper/mongodbMemoryTest.js";
+import {isValidJson} from "./helper/util.js";
+import {createArtist} from "./data/artists.js";
+import {
+ fetchArtists,
+ fetchSongsByArtistId,
+} from "../src/controllers/artistController.js";
const baseUrl = `${process.env.BASE_URL}/v1/api`;
-const createMock = mockObj => axios.get = jest.fn().mockResolvedValue({ __esModule: true, data: mockObj })
-
-
-describe('Artists Routes', () => {
+const createMock = (mockObj) =>
+ (axios.get = jest.fn().mockResolvedValue({__esModule: true, data: mockObj}));
+describe("Artists Routes", () => {
beforeAll(connect);
afterAll(disconnect);
afterEach(() => jest.clearAllMocks());
/* API/Integration Tests */
- test('GET /artists', async () => {
+ test("GET /artists", async () => {
const artists = await fetchArtists();
expect(Array.isArray(artists)).toBe(true);
expect(isValidJson(artists)).toBe(true);
- expect(artists[0]).toHaveProperty('_id');
- expect(artists[0]).toHaveProperty('gameIds');
- expect(artists[0]).toHaveProperty('name');
- expect(artists[0]).toHaveProperty('createdAt');
- expect(artists[0]).toHaveProperty('updatedAt');
+ expect(artists[0]).toHaveProperty("_id");
+ expect(artists[0]).toHaveProperty("gameIds");
+ expect(artists[0]).toHaveProperty("name");
+ expect(artists[0]).toHaveProperty("createdAt");
+ expect(artists[0]).toHaveProperty("updatedAt");
- const artist = artists.find(artist => artist.name === 'Guitar Vader');
+ const artist = artists.find((artist) => artist.name === "Guitar Vader");
if (artist && artist._id) {
const songsByArtist = await fetchSongsByArtistId(artist._id);
expect(Array.isArray(songsByArtist)).toBe(true);
expect(isValidJson(songsByArtist)).toBe(true);
- expect(songsByArtist[0]).toHaveProperty('artistId');
+ expect(songsByArtist[0]).toHaveProperty("artistId");
expect(songsByArtist[0].artistId).toStrictEqual(artist._id);
- expect(songsByArtist[0]).toHaveProperty('audioLink');
- expect(songsByArtist[0]).toHaveProperty('gameId');
- expect(songsByArtist[0]).toHaveProperty('name');
+ expect(songsByArtist[0]).toHaveProperty("audioLink");
+ expect(songsByArtist[0]).toHaveProperty("gameId");
+ expect(songsByArtist[0]).toHaveProperty("name");
}
- })
+ });
/* Unit/Mock Tests */
- test('GET /artists/:id', async () => {
+ test("GET /artists/:id", async () => {
const testId = "642f778224b4bca91d5a70d2";
createMock(createArtist(testId));
@@ -51,28 +55,5 @@ describe('Artists Routes', () => {
expect(result.data.gameIds).toHaveLength(2);
expect(result.data.name).toBe("Guitar Vader");
expect(axios.get).toHaveBeenCalledTimes(1);
- })
-
-
- /* Helper Objects */
- const createArtist = (testId) => { return {
- "_id" : testId,
- "gameIds" : [
- "642f773924b4bca91d5a6c57",
- "642f773924b4bca91d5a6c54"
- ],
- "name" : "Guitar Vader",
- "createdAt" : "2023-04-07T01:53:06.263Z",
- "updatedAt" : "2023-04-07T04:25:48.363Z"
- }}
-
- const isValidJson = (text) => {
- try {
- JSON.parse(JSON.stringify(text));
- return true;
- } catch {
- return false;
- }
- }
-
-})
\ No newline at end of file
+ });
+});
diff --git a/test/characters.test.js b/test/characters.test.js
index 0bb441d..e592d8a 100644
--- a/test/characters.test.js
+++ b/test/characters.test.js
@@ -1,73 +1,80 @@
-import {jest} from '@jest/globals';
-import axios from 'axios';
-import dotenv from 'dotenv';
+import {jest} from "@jest/globals";
+import axios from "axios";
+import dotenv from "dotenv";
dotenv.config();
-import { connect, disconnect } from './helper/mongodbMemoryTest.js';
-import { fetchJSRCharacters, fetchJSRFCharacters } from '../src/controllers/characterController.js';
-import { sortObjects } from '../src/utils/utility.js';
+import {connect, disconnect} from "./helper/mongodbMemoryTest.js";
+import {isValidJson} from "./helper/util.js";
+import {
+ createBrcCharacter,
+ createJsrCharacter,
+ createJsrfCharacter,
+} from "./data/characters.js";
+import {fetchCharacters} from "../src/controllers/characterController.js";
+import {sortObjects} from "../src/utils/utility.js";
+import Constants from "../src/constants/dbConstants.js";
const baseUrl = `${process.env.BASE_URL}/v1/api`;
-const createMock = mockObj => axios.get = jest.fn().mockResolvedValue({ __esModule: true, data: mockObj })
-
-
-describe('Character Routes', () => {
+const createMock = (mockObj) =>
+ (axios.get = jest.fn().mockResolvedValue({__esModule: true, data: mockObj}));
+const {JSR_DB, JSRF_DB, BRC_DB} = Constants;
+const req = {query: {}};
+describe("Character Routes", () => {
beforeAll(connect);
afterAll(disconnect);
afterEach(() => jest.clearAllMocks());
/* API/Integration Tests */
- test('GET /characters', async () => {
- const jsrCharacters = await fetchJSRCharacters();
- const jsrfCharacters = await fetchJSRFCharacters();
- const characters = [...jsrCharacters, ...jsrfCharacters];
- expect(Array.isArray(jsrCharacters)).toBe(true);
- expect(Array.isArray(jsrfCharacters)).toBe(true);
+ test("GET /characters", async () => {
+ const characters = await fetchCharacters(req, "ALL");
expect(Array.isArray(characters)).toBe(true);
expect(isValidJson(characters)).toBe(true);
- expect(jsrCharacters[0]).toHaveProperty('name')
- expect(jsrCharacters[0]).toHaveProperty('descriptions')
- expect(jsrCharacters[0]).toHaveProperty('heroImage')
- expect(jsrCharacters[0]).toHaveProperty('wikiPage')
- })
+ expect(characters[0]).toHaveProperty("name");
+ expect(characters[0]).toHaveProperty("descriptions");
+ expect(characters[0]).toHaveProperty("heroImage");
+ expect(characters[0]).toHaveProperty("wikiPage");
+ });
- test('GET /characters?sortBy=name&orderBy=desc', async () => {
- const req = {query: { sortBy: 'name', orderBy: 'desc' }};
+ test("GET /characters?sortBy=name&orderBy=desc", async () => {
+ const req = {query: {sortBy: "name", orderBy: "desc"}};
const sortByValue = req?.query?.sortBy ? req?.query?.sortBy : undefined;
- const sortOrder = req?.query?.orderBy ? req?.query?.orderBy : 'asc';
- const jsrCharacters = await fetchJSRCharacters();
- const jsrfCharacters = await fetchJSRFCharacters();
+ const sortOrder = req?.query?.orderBy ? req?.query?.orderBy : "asc";
- let characters = [];
+ let characters = await fetchCharacters(req, "ALL");
if (sortByValue) {
- const allCharacters = [...jsrCharacters, ...jsrfCharacters];
- characters = allCharacters.sort(sortObjects(sortByValue, sortOrder));
- } else {
- characters = [...jsrCharacters, ...jsrfCharacters];
+ characters = characters.sort(sortObjects(sortByValue, sortOrder));
}
expect(isValidJson(characters)).toBe(true);
- })
-
- test('GET /characters/jsr?limit=5', async () => {
- const req = {query: { limit: '5' }};
- const jsrCharacters = await fetchJSRCharacters(req);
- expect(Array.isArray(jsrCharacters)).toBe(true);
- expect(isValidJson(jsrCharacters)).toBe(true);
- expect(jsrCharacters).toHaveLength(5);
- })
-
- test('GET /characters/jsrf?limit=15', async () => {
- const req = {query: { limit: '15' }};
- const jsrCharacters = await fetchJSRCharacters(req);
- expect(Array.isArray(jsrCharacters)).toBe(true);
- expect(isValidJson(jsrCharacters)).toBe(true);
- expect(jsrCharacters).toHaveLength(15);
- })
+ });
+
+ test("GET /characters/jsr?limit=5", async () => {
+ const req = {query: {limit: "5"}};
+ const characters = await fetchCharacters(req, JSR_DB);
+ expect(Array.isArray(characters)).toBe(true);
+ expect(isValidJson(characters)).toBe(true);
+ expect(characters).toHaveLength(5);
+ });
+
+ test("GET /characters/jsrf?limit=15", async () => {
+ const req = {query: {limit: "15"}};
+ const characters = await fetchCharacters(req, JSRF_DB);
+ expect(Array.isArray(characters)).toBe(true);
+ expect(isValidJson(characters)).toBe(true);
+ expect(characters).toHaveLength(15);
+ });
+
+ test("GET /characters/brc?limit=15", async () => {
+ const req = {query: {limit: "10"}};
+ const characters = await fetchCharacters(req, BRC_DB);
+ expect(Array.isArray(characters)).toBe(true);
+ expect(isValidJson(characters)).toBe(true);
+ expect(characters).toHaveLength(10);
+ });
/* Unit/Mock Tests */
- test('GET /characters/jsr/:id', async () => {
+ test("GET /characters/jsr/:id", async () => {
const testId = "643c6663092314f05f4da407";
createMock(createJsrCharacter(testId));
@@ -75,12 +82,14 @@ describe('Character Routes', () => {
expect(isValidJson(result.data)).toBe(true);
expect(result.data.name).toBe("Slate");
expect(result.data.age).toBe("19");
- expect(result.data.wikiPage).toBe("https://jetsetradio.fandom.com/wiki/Slate");
+ expect(result.data.wikiPage).toBe(
+ "https://jetsetradio.fandom.com/wiki/Slate"
+ );
expect(result.data.gallery).toHaveLength(4);
expect(axios.get).toHaveBeenCalledTimes(1);
- })
+ });
- test('GET /characters/jsrf/:id', async () => {
+ test("GET /characters/jsrf/:id", async () => {
const testId = "643c6664092314f05f4da461";
createMock(createJsrfCharacter(testId));
@@ -89,122 +98,33 @@ describe('Character Routes', () => {
expect(result.data.name).toBe("Love Shockers");
expect(result.data.descriptions).toHaveLength(9);
expect(result.data.gallery).toHaveLength(5);
- expect(result.data.wikiPage).toBe("https://jetsetradio.fandom.com/wiki/Love_Shockers");
- expect(result.data.heroImage).toBe("https://static.wikia.nocookie.net/jetsetradio/images/a/aa/Loveshockers.png/revision/latest?cb=20211011173518");
- expect(result.data.stats).toHaveProperty('stamina');
+ expect(result.data.wikiPage).toBe(
+ "https://jetsetradio.fandom.com/wiki/Love_Shockers"
+ );
+ expect(result.data.heroImage).toBe(
+ "https://static.wikia.nocookie.net/jetsetradio/images/a/aa/Loveshockers.png/revision/latest?cb=20211011173518"
+ );
+ expect(result.data.stats).toHaveProperty("stamina");
expect(axios.get).toHaveBeenCalledTimes(1);
- })
-
-
- /* Helper Objects */
- const createJsrCharacter = (testId) => { return {
- "_id" : testId,
- "aliases" : [],
- "descriptions" : [
- "Slate is a very distinguishable character. His most prominent features are his tall stature (the official Jet Set Radio Guidebook states that he is 6 feet tall) and oblong nose (resembling the famous graffiti character Kilroy). He is virtually bald, save for what look like three brown braids that stick straight up from the very top of his head. He wears an orange jacket with a high collar that masks the lower half of his face, and goggles on his forehead. He has gray pants with lighter stripes, matching striped gloves, and blue skates with yellow wheels.",
- "Slate's small graffiti. Illegible, warped-looking yellow shapes with purple borders.",
- "Slate's large graffiti. Blue letters in perspective, with a lighter blue glow around them.",
- "Slate's extra-large graffiti. Black and white stylized letters.",
- "Slate's only appearance in this racing game is on an unlockable sticker, obtained from freezing an opponent with the ice powerup from at least 100 meters away."
- ],
- "graffitiTrademarks" : [],
- "gallery" : [
- {
- "photo" : "https://static.wikia.nocookie.net/jetsetradio/images/d/d8/GarageSlate.jpg/revision/latest/scale-to-width-down/185?cb=20160330170129",
- "description" : "Slate leaning against the pinball table in the Garage in Jet Set Radio HD."
- },
- {
- "photo" : "https://static.wikia.nocookie.net/jetsetradio/images/e/ed/StickerSlate.png/revision/latest/scale-to-width-down/185?cb=20160330170216",
- "description" : "Slate on a sticker in Sonic & All-Stars Racing Transformed."
- },
- {
- "photo" : "https://static.wikia.nocookie.net/jetsetradio/images/0/0e/Slate_logo_jsr.gif/revision/latest?cb=20211011165424",
- "description" : "Slate's logo in Jet Set Radio."
- },
- {
- "photo" : "https://static.wikia.nocookie.net/jetsetradio/images/8/87/Jsr_gba_manual5.jpg/revision/latest/scale-to-width-down/185?cb=20211011172103",
- "description" : "Slate's description for Jet Set Radio GBA."
- }
- ],
- "name" : "Slate",
- "gender" : "Male",
- "age" : "19",
- "height" : "185 cm (6 ft 0.8 in)",
- "trait" : "Aloof",
- "likes" : "Puzzles",
- "debut" : "Rival Challenge (JSR)",
- "heroImage" : "https://static.wikia.nocookie.net/jetsetradio/images/9/97/Slate.png/revision/latest?cb=20220104203110",
- "wikiPage" : "https://jetsetradio.fandom.com/wiki/Slate",
- "gameId" : "64285b7918c8a0231136dc5a",
- "createdAt" : "2023-04-16T21:19:32.652Z",
- "updatedAt" : "2023-04-16T21:19:34.870Z",
- "stats" : {
- "power" : "18",
- "technique" : "10",
- "graffiti" : "14"
- }
-}}
-
- const createJsrfCharacter = (testId) => {return {
- "_id" : testId,
- "aliases" : [],
- "descriptions" : [
- "In Jet Set Radio, the Love Shockers appear without warning in the Shibuya-Cho district (which belongs to the GG's) and begin taking it over, prompting the GG's to rush around and retake their territory from the group. On their defeat in the first chapter of Jet Set Radio, one of the downed Love Shockers holds out a badge for the Golden Rhinos.",
- "In Jet Set Radio Future, they first appear in chapter 5 as part of Death Ball at the future site of the Rokkaku Expo Stadium in the third round. Like the Doom Riders and The Immortals, the Love Shockers will speak to the GGs during the first couple moments of the round. The GGs end up winning against them, but not long before the Rokkaku Police enforcement barge into the stadium, thus halting the round and sending everyone into panic, supposedly making the Love Shockers run off to Hikage street.",
- "Anytime during or after the pursuit of Clutch in chapter 7, the player can go back to Hikage street, which activates a Flag Battle. This Flag Battle is entirely optional and isn't downright required to be won in order to unlock their gang as long as the player has completed all Street Challenges and collected all Graffiti Souls prior to proceeding to chapter 5.",
- "Love Shocker's small graffiti where it says \"Love Shock\".",
- "Love Shocker's large graffiti.",
- "Love Shocker's extra large graffiti. It features a broken heart with an arrow between it.",
- "Successfully complete all Shibuya-Cho Story levels with a \"Jet\" rank to unlock the Love Shockers gang.",
- "The Love Shockers can be unlocked as a playable character by earning a Jet ranking on all of the Test Runs on Hikage Street.",
- "In Sonic & Sega All-Stars Racing, a Love Shocker makes a cameo appearance in all of the Jet Set Radio Future stages. They are shown on the sides and cheering as they watch the race."
- ],
- "gallery" : [
- {
- "photo" : "https://static.wikia.nocookie.net/jetsetradio/images/c/ce/Love_Shocker_Faces.png/revision/latest/scale-to-width-down/185?cb=20200512180522",
- "description" : "Textures used for the Love Shockers' heads and scarves."
- },
- {
- "photo" : "https://static.wikia.nocookie.net/jetsetradio/images/c/ca/Love_Shocker_Clothes.png/revision/latest/scale-to-width-down/185?cb=20200512180602",
- "description" : "Textures used for the Love Shockers' clothing."
- },
- {
- "photo" : "https://static.wikia.nocookie.net/jetsetradio/images/d/dd/Love_shockers_the_rude_awakening_art.png/revision/latest/scale-to-width-down/152?cb=20211010201427",
- "description" : "Art from the Jet Set Radio Documentary: The Rude Awakening."
- },
- {
- "photo" : "https://static.wikia.nocookie.net/jetsetradio/images/a/a4/Love_shockers_logo_jsr.gif/revision/latest?cb=20211011173049",
- "description" : "Love Shockers logo in Jet Set Radio."
- },
- {
- "photo" : "https://static.wikia.nocookie.net/jetsetradio/images/e/e6/Love_shockers_logo_jsrf.png/revision/latest/scale-to-width-down/185?cb=20211012124811",
- "description" : "Love Shockers logo in Jet Set Radio Future."
- }
- ],
- "name" : "Love Shockers",
- "heroImage" : "https://static.wikia.nocookie.net/jetsetradio/images/a/aa/Loveshockers.png/revision/latest?cb=20211011173518",
- "wikiPage" : "https://jetsetradio.fandom.com/wiki/Love_Shockers",
- "gameId" : "64285b7918c8a0231136dc5d",
- "createdAt" : "2023-04-16T21:19:33.440Z",
- "updatedAt" : "2023-04-16T21:19:39.630Z",
- "stats" : {
- "stamina" : "7",
- "gStamina" : "7",
- "sprayCans" : "30",
- "graffiti" : "16",
- "acceleration" : "22",
- "cornering" : "25",
- "grind" : "16"
- }
-}}
-
- const isValidJson = (text) => {
- try {
- JSON.parse(JSON.stringify(text));
- return true;
- } catch {
- return false;
- }
- }
+ });
-})
\ No newline at end of file
+ test("GET /characters/brc/:id", async () => {
+ const testId = "643c6664092314f05f4da461";
+ createMock(createBrcCharacter(testId));
+
+ const result = await axios.get(`${baseUrl}/characters/brc/${testId}`);
+ expect(isValidJson(result.data)).toBe(true);
+ expect(result.data.name).toBe("Coil");
+ expect(result.data.description).toBe(
+ "Coil is first found under the tower near the Mataan taxi stop. Speak with him and then match his movements with Housedance. He will then leave but promises to contact you again later."
+ );
+ expect(result.data.gallery).toHaveLength(1);
+ expect(result.data.defaultMovestyle).toBe("BMX");
+ expect(result.data.unlockedByDefault).toBe(false);
+ expect(result.data.isPlayable).toBe(true);
+ expect(result.data.wikiPage).toBe(
+ "https://bomb-rush-cyberfunk.fandom.com/wiki/Coil"
+ );
+ expect(axios.get).toHaveBeenCalledTimes(1);
+ });
+});
diff --git a/test/collectibles.test.js b/test/collectibles.test.js
new file mode 100644
index 0000000..2430bfb
--- /dev/null
+++ b/test/collectibles.test.js
@@ -0,0 +1,62 @@
+import {jest} from "@jest/globals";
+import axios from "axios";
+import dotenv from "dotenv";
+dotenv.config();
+
+import {connect, disconnect} from "./helper/mongodbMemoryTest.js";
+import {isValidJson} from "./helper/util.js";
+import {fetchCollectibles} from "../src/controllers/collectibleController.js";
+import {sortObjects} from "../src/utils/utility.js";
+import {createBrcCollectible} from "./data/collectibles.js";
+
+const baseUrl = `${process.env.BASE_URL}/v1/api`;
+const createMock = (mockObj) =>
+ (axios.get = jest.fn().mockResolvedValue({__esModule: true, data: mockObj}));
+const req = {query: {}};
+
+describe("Location Routes", () => {
+ beforeAll(connect);
+ afterAll(disconnect);
+ afterEach(() => jest.clearAllMocks());
+
+ /* API/Integration Tests */
+
+ test("GET /collectibles", async () => {
+ const collectibles = await fetchCollectibles(req, "ALL");
+ expect(Array.isArray(collectibles)).toBe(true);
+ expect(isValidJson(collectibles)).toBe(true);
+
+ expect(collectibles[0]).toHaveProperty("name");
+ expect(collectibles[0]).toHaveProperty("description");
+ expect(collectibles[0]).toHaveProperty("key");
+ expect(collectibles[0]).toHaveProperty("character");
+ expect(collectibles[0]).toHaveProperty("type");
+ expect(collectibles[0]).toHaveProperty("unlockedByDefault");
+ expect(collectibles[0]).toHaveProperty("gallery");
+ expect(collectibles[0]).toHaveProperty("gameId");
+ });
+
+ test("GET /collectibles?sortBy=name&orderBy=desc", async () => {
+ const req = {query: {sortBy: "name", orderBy: "desc"}};
+ const sortByValue = req?.query?.sortBy ? req?.query?.sortBy : undefined;
+ const sortOrder = req?.query?.orderBy ? req?.query?.orderBy : "asc";
+ let collectibles = await fetchCollectibles(req, "ALL");
+ if (sortByValue) {
+ collectibles = collectibles.sort(sortObjects(sortByValue, sortOrder));
+ }
+ expect(isValidJson(collectibles)).toBe(true);
+ });
+
+ /* Unit/Mock Tests */
+ test("GET /collectibles/:id", async () => {
+ const testId = "68bb2c34813c6317bbd07cab";
+ createMock(createBrcCollectible(testId));
+
+ const result = await axios.get(`${baseUrl}/collectibles/${testId}`);
+ expect(isValidJson(result.data)).toBe(true);
+ expect(result.data.name).toBe("Spring");
+ expect(result.data.key).toBe("Spring-Red");
+ expect(result.data.type).toBe("Outfit");
+ expect(axios.get).toHaveBeenCalledTimes(1);
+ });
+});
diff --git a/test/data/artists.js b/test/data/artists.js
new file mode 100644
index 0000000..85455d7
--- /dev/null
+++ b/test/data/artists.js
@@ -0,0 +1,9 @@
+export const createArtist = (testId) => {
+ return {
+ _id: testId,
+ gameIds: ["642f773924b4bca91d5a6c57", "642f773924b4bca91d5a6c54"],
+ name: "Guitar Vader",
+ createdAt: "2023-04-07T01:53:06.263Z",
+ updatedAt: "2023-04-07T04:25:48.363Z",
+ };
+};
diff --git a/test/data/characters.js b/test/data/characters.js
new file mode 100644
index 0000000..ec8db35
--- /dev/null
+++ b/test/data/characters.js
@@ -0,0 +1,143 @@
+export const createJsrCharacter = (testId) => {
+ return {
+ _id: testId,
+ aliases: [],
+ descriptions: [
+ "Slate is a very distinguishable character. His most prominent features are his tall stature (the official Jet Set Radio Guidebook states that he is 6 feet tall) and oblong nose (resembling the famous graffiti character Kilroy). He is virtually bald, save for what look like three brown braids that stick straight up from the very top of his head. He wears an orange jacket with a high collar that masks the lower half of his face, and goggles on his forehead. He has gray pants with lighter stripes, matching striped gloves, and blue skates with yellow wheels.",
+ "Slate's small graffiti. Illegible, warped-looking yellow shapes with purple borders.",
+ "Slate's large graffiti. Blue letters in perspective, with a lighter blue glow around them.",
+ "Slate's extra-large graffiti. Black and white stylized letters.",
+ "Slate's only appearance in this racing game is on an unlockable sticker, obtained from freezing an opponent with the ice powerup from at least 100 meters away.",
+ ],
+ graffitiTrademarks: [],
+ gallery: [
+ {
+ photo:
+ "https://static.wikia.nocookie.net/jetsetradio/images/d/d8/GarageSlate.jpg/revision/latest/scale-to-width-down/185?cb=20160330170129",
+ description:
+ "Slate leaning against the pinball table in the Garage in Jet Set Radio HD.",
+ },
+ {
+ photo:
+ "https://static.wikia.nocookie.net/jetsetradio/images/e/ed/StickerSlate.png/revision/latest/scale-to-width-down/185?cb=20160330170216",
+ description:
+ "Slate on a sticker in Sonic & All-Stars Racing Transformed.",
+ },
+ {
+ photo:
+ "https://static.wikia.nocookie.net/jetsetradio/images/0/0e/Slate_logo_jsr.gif/revision/latest?cb=20211011165424",
+ description: "Slate's logo in Jet Set Radio.",
+ },
+ {
+ photo:
+ "https://static.wikia.nocookie.net/jetsetradio/images/8/87/Jsr_gba_manual5.jpg/revision/latest/scale-to-width-down/185?cb=20211011172103",
+ description: "Slate's description for Jet Set Radio GBA.",
+ },
+ ],
+ name: "Slate",
+ gender: "Male",
+ age: "19",
+ height: "185 cm (6 ft 0.8 in)",
+ trait: "Aloof",
+ likes: "Puzzles",
+ debut: "Rival Challenge (JSR)",
+ heroImage:
+ "https://static.wikia.nocookie.net/jetsetradio/images/9/97/Slate.png/revision/latest?cb=20220104203110",
+ wikiPage: "https://jetsetradio.fandom.com/wiki/Slate",
+ gameId: "64285b7918c8a0231136dc5a",
+ createdAt: "2023-04-16T21:19:32.652Z",
+ updatedAt: "2023-04-16T21:19:34.870Z",
+ stats: {
+ power: "18",
+ technique: "10",
+ graffiti: "14",
+ },
+ };
+};
+
+export const createJsrfCharacter = (testId) => {
+ return {
+ _id: testId,
+ aliases: [],
+ descriptions: [
+ "In Jet Set Radio, the Love Shockers appear without warning in the Shibuya-Cho district (which belongs to the GG's) and begin taking it over, prompting the GG's to rush around and retake their territory from the group. On their defeat in the first chapter of Jet Set Radio, one of the downed Love Shockers holds out a badge for the Golden Rhinos.",
+ "In Jet Set Radio Future, they first appear in chapter 5 as part of Death Ball at the future site of the Rokkaku Expo Stadium in the third round. Like the Doom Riders and The Immortals, the Love Shockers will speak to the GGs during the first couple moments of the round. The GGs end up winning against them, but not long before the Rokkaku Police enforcement barge into the stadium, thus halting the round and sending everyone into panic, supposedly making the Love Shockers run off to Hikage street.",
+ "Anytime during or after the pursuit of Clutch in chapter 7, the player can go back to Hikage street, which activates a Flag Battle. This Flag Battle is entirely optional and isn't downright required to be won in order to unlock their gang as long as the player has completed all Street Challenges and collected all Graffiti Souls prior to proceeding to chapter 5.",
+ 'Love Shocker\'s small graffiti where it says "Love Shock".',
+ "Love Shocker's large graffiti.",
+ "Love Shocker's extra large graffiti. It features a broken heart with an arrow between it.",
+ 'Successfully complete all Shibuya-Cho Story levels with a "Jet" rank to unlock the Love Shockers gang.',
+ "The Love Shockers can be unlocked as a playable character by earning a Jet ranking on all of the Test Runs on Hikage Street.",
+ "In Sonic & Sega All-Stars Racing, a Love Shocker makes a cameo appearance in all of the Jet Set Radio Future stages. They are shown on the sides and cheering as they watch the race.",
+ ],
+ gallery: [
+ {
+ photo:
+ "https://static.wikia.nocookie.net/jetsetradio/images/c/ce/Love_Shocker_Faces.png/revision/latest/scale-to-width-down/185?cb=20200512180522",
+ description: "Textures used for the Love Shockers' heads and scarves.",
+ },
+ {
+ photo:
+ "https://static.wikia.nocookie.net/jetsetradio/images/c/ca/Love_Shocker_Clothes.png/revision/latest/scale-to-width-down/185?cb=20200512180602",
+ description: "Textures used for the Love Shockers' clothing.",
+ },
+ {
+ photo:
+ "https://static.wikia.nocookie.net/jetsetradio/images/d/dd/Love_shockers_the_rude_awakening_art.png/revision/latest/scale-to-width-down/152?cb=20211010201427",
+ description:
+ "Art from the Jet Set Radio Documentary: The Rude Awakening.",
+ },
+ {
+ photo:
+ "https://static.wikia.nocookie.net/jetsetradio/images/a/a4/Love_shockers_logo_jsr.gif/revision/latest?cb=20211011173049",
+ description: "Love Shockers logo in Jet Set Radio.",
+ },
+ {
+ photo:
+ "https://static.wikia.nocookie.net/jetsetradio/images/e/e6/Love_shockers_logo_jsrf.png/revision/latest/scale-to-width-down/185?cb=20211012124811",
+ description: "Love Shockers logo in Jet Set Radio Future.",
+ },
+ ],
+ name: "Love Shockers",
+ heroImage:
+ "https://static.wikia.nocookie.net/jetsetradio/images/a/aa/Loveshockers.png/revision/latest?cb=20211011173518",
+ wikiPage: "https://jetsetradio.fandom.com/wiki/Love_Shockers",
+ gameId: "64285b7918c8a0231136dc5d",
+ createdAt: "2023-04-16T21:19:33.440Z",
+ updatedAt: "2023-04-16T21:19:39.630Z",
+ stats: {
+ stamina: "7",
+ gStamina: "7",
+ sprayCans: "30",
+ graffiti: "16",
+ acceleration: "22",
+ cornering: "25",
+ grind: "16",
+ },
+ };
+};
+
+export const createBrcCharacter = (testId) => {
+ return {
+ _id: testId,
+ name: "Coil",
+ description:
+ "Coil is first found under the tower near the Mataan taxi stop. Speak with him and then match his movements with Housedance. He will then leave but promises to contact you again later.",
+ wikiPage: "https://bomb-rush-cyberfunk.fandom.com/wiki/Coil",
+ crew: "Bomb Rush Crew",
+ defaultMovestyle: "BMX",
+ unlockedByDefault: false,
+ unlockedByDLC: false,
+ unlockLocation: "68b8db6ccec5d6b573cf62b5",
+ unlockTrigger: "Beat Coil in a race around Mataan.",
+ isPlayable: true,
+ gallery: [
+ {
+ photo:
+ "https://static.wikia.nocookie.net/bomb-rush-cyberfunk/images/2/2f/Coil_Press_Kit_Render.png/revision/latest?cb=20240623205504",
+ description: "hero",
+ },
+ ],
+ gameId: "68b8db67cec5d6b573cf624c",
+ };
+};
diff --git a/test/data/collectibles.js b/test/data/collectibles.js
new file mode 100644
index 0000000..26a5475
--- /dev/null
+++ b/test/data/collectibles.js
@@ -0,0 +1,22 @@
+export const createBrcCollectible = (testId, type) => {
+ return {
+ _id: testId,
+ name: "Spring",
+ key: "Spring-Red",
+ description: "",
+ type: "Outfit",
+ character: {
+ name: "Red",
+ id: "68b8db67cec5d6b573cf6255",
+ },
+ unlockedByDefault: true,
+ gallery: [
+ {
+ name: "asset",
+ image:
+ "https://static.wikia.nocookie.net/bomb-rush-cyberfunk/images/a/a3/Red_Spring_Ref.png/revision/latest/scale-to-width-down/1000?cb=20240331024105",
+ },
+ ],
+ gameId: "68b8db67cec5d6b573cf624c",
+ };
+};
diff --git a/test/data/games.js b/test/data/games.js
new file mode 100644
index 0000000..30ef3a0
--- /dev/null
+++ b/test/data/games.js
@@ -0,0 +1,64 @@
+export const createGame = (testId) => {
+ return {
+ _id: testId,
+ aliases: [],
+ developers: ["Smilebit"],
+ publishers: ["Sega"],
+ platforms: ["Xbox"],
+ releaseDates: [
+ {country: "JP", date: "February 22, 2002"},
+ {country: "US", date: "February 25, 2002"},
+ {country: "EU", date: "March 24, 2002"},
+ ],
+ assets: [
+ {
+ country: "US",
+ images: {
+ frontCover:
+ "https://storage.googleapis.com/jetsetradio-api-core/images/games/jsrf/us/front-cover.webp",
+ backCover:
+ "https://storage.googleapis.com/jetsetradio-api-core/images/games/jsrf/us/back-cover.webp",
+ disc: "https://storage.googleapis.com/jetsetradio-api-core/images/games/jsrf/us/disc.webp",
+ },
+ },
+ {
+ country: "JP",
+ images: {
+ frontCover:
+ "https://storage.googleapis.com/jetsetradio-api-core/images/games/jsrf/jp/front-cover.webp",
+ backCover:
+ "https://storage.googleapis.com/jetsetradio-api-core/images/games/jsrf/jp/back-cover.webp",
+ disc: "https://storage.googleapis.com/jetsetradio-api-core/images/games/jsrf/jp/disc.webp",
+ },
+ },
+ {
+ country: "EU",
+ images: {
+ frontCover:
+ "https://storage.googleapis.com/jetsetradio-api-core/images/games/jsrf/eu/front-cover.webp",
+ backCover:
+ "https://storage.googleapis.com/jetsetradio-api-core/images/games/jsrf/eu/back-cover.webp",
+ disc: "https://storage.googleapis.com/jetsetradio-api-core/images/games/jsrf/eu/disc.webp",
+ },
+ },
+ {
+ country: "DE",
+ images: {
+ frontCover:
+ "https://storage.googleapis.com/jetsetradio-api-core/images/games/jsrf/de/front-cover.webp",
+ backCover:
+ "https://storage.googleapis.com/jetsetradio-api-core/images/games/jsrf/de/back-cover.webp",
+ disc: "https://storage.googleapis.com/jetsetradio-api-core/images/games/jsrf/de/disc.webp",
+ },
+ },
+ ],
+ name: "Jet Set Radio Future",
+ intro:
+ "Jet Set Radio Future (JSRF) is the sequel to Jet Set Radio (also known as Jet Grind Radio in America), and a sort of re-imagining of that game.",
+ description:
+ "Jet Set Radio Future is set in Tokyo-To, Japan, in 2024, where a street gang known as the GG's is fighting for control over the streets against rival gangs, as well as the large and powerful corporation known as the Rokkaku Group, led by Gouji Rokkaku. The Rokkaku Group is attempting to seize control of Tokyo by force and convert it into a totalitarian police state.",
+ genre: "Platform/Sports",
+ createdAt: "2023-04-07T01:51:53.511Z",
+ updatedAt: "2023-04-07T01:51:53.511Z",
+ };
+};
diff --git a/test/data/graffitiTags.js b/test/data/graffitiTags.js
new file mode 100644
index 0000000..3311fc6
--- /dev/null
+++ b/test/data/graffitiTags.js
@@ -0,0 +1,42 @@
+export const createJsrTag = (testId) => {
+ return {
+ _id: testId,
+ number: "0018",
+ tagName: "SARU",
+ tagSubName: "SARU - A Head of the Crowd",
+ size: "XL",
+ gameId: "642f773924b4bca91d5a6c54",
+ imageUrl:
+ "https://storage.googleapis.com/greg-kennedy.com/jsr/U_GRF.0018.png",
+ createdAt: "2023-04-07T01:52:11.751Z",
+ updatedAt: "2023-04-07T01:52:11.751Z",
+ };
+};
+
+export const createJsrfTag = (testId) => {
+ return {
+ _id: testId,
+ number: "No. 025",
+ tagName: "Poison Jam",
+ size: "SS",
+ wikiImageUrl:
+ "https://static.wikia.nocookie.net/jetsetradio/images/e/e8/No._025.png/revision/latest/scale-to-width-down/80?cb=20170220220832",
+ imageUrl:
+ "https://storage.googleapis.com/jetsetradio-api/jsrf/graffiti-tags/025.png",
+ gameId: "64285b7918c8a0231136dc5d",
+ createdAt: "2023-04-08T04:16:16.114Z",
+ updatedAt: "2023-04-24T04:23:33.147Z",
+ graffitiSoulLocation:
+ "Test Runs (Unlock Poison Jam as a playable character)",
+ locations: [
+ {
+ name: "Tokyo Underground Sewage Facility",
+ id: "6445cb9dd85986264951f134",
+ },
+ {
+ name: "Bottom Point of Sewage Facility",
+ id: "6445cb9ed85986264951f13b",
+ },
+ ],
+ };
+};
diff --git a/test/data/locations.js b/test/data/locations.js
new file mode 100644
index 0000000..a16fa0f
--- /dev/null
+++ b/test/data/locations.js
@@ -0,0 +1,256 @@
+export const createJsrLevel = (testId) => {
+ return {
+ _id: testId,
+ name: "Noise Reduction",
+ description:
+ "This is a tag level. You need to tag each member of the Noise Tanks 10 times in order to beat this level.",
+ location: {
+ name: "Benten-cho",
+ id: "6445cb96d85986264951f0ae",
+ },
+ bossLevel: true,
+ chapter: "1",
+ createdAt: "2023-04-24T00:21:46.134Z",
+ updatedAt: "2023-04-24T00:21:46.134Z",
+ };
+};
+
+export const createJsrLocation = (testId) => {
+ return {
+ _id: testId,
+ levels: [],
+ subLocations: [
+ "Subway",
+ "Train Line",
+ "Chinatown",
+ "Highway Zero",
+ "Geckijomae",
+ "Business District",
+ ],
+ unlockableCharacters: [
+ {
+ name: "Noise Tanks",
+ id: "643c71bb8cabe0dcede86902",
+ },
+ {
+ name: "Mew",
+ id: "643c71a58cabe0dcede868e5",
+ },
+ {
+ name: "Cube",
+ id: "643c71c08cabe0dcede86917",
+ },
+ {
+ name: "Yo-Yo",
+ id: "643c71c38cabe0dcede86933",
+ },
+ ],
+ name: "Benten-cho",
+ description:
+ "Benten-cho is the home turf of the Noise Tanks, a rival tech-savvy gang of the GG's.",
+ gameId: "64285b7918c8a0231136dc5a",
+ secretCharacter: {
+ name: "Noise Tanks",
+ id: "643c71bb8cabe0dcede86902",
+ },
+ createdAt: "2023-04-24T00:21:43.098Z",
+ updatedAt: "2023-04-24T00:21:43.098Z",
+ };
+};
+
+export const createJsrfLocation = (testId) => {
+ return {
+ _id: testId,
+ unlockableCharacters: [
+ {
+ name: "Beat",
+ id: "643c71a48cabe0dcede868de",
+ },
+ {
+ name: "Doom Riders",
+ id: "643c719b8cabe0dcede868c6",
+ },
+ ],
+ adjacentLocations: [
+ {
+ name: "The Garage",
+ id: "6445cb9bd85986264951f10f",
+ },
+ {
+ name: "Shibuya Terminal",
+ id: "6445cb9cd85986264951f11a",
+ },
+ ],
+ name: "Dogenzaka Hill",
+ description:
+ "Dogenzaka Hill is the first level in the game after completing the initial tutorial at the Garage.",
+ gameId: "64285b7918c8a0231136dc5d",
+ hasMixtape: true,
+ imageUrl:
+ "https://storage.googleapis.com/jetsetradio-api/jsrf/locations/dogenzaka-hill.webp",
+ secretCharacter: {
+ name: "Doom Riders",
+ id: "643c719b8cabe0dcede868c6",
+ },
+ createdAt: "2023-04-24T00:21:48.297Z",
+ updatedAt: "2023-04-24T00:21:53.824Z",
+ };
+};
+
+export const createBRCLocation = (testId) => {
+ return {
+ _id: testId,
+ name: "Versum Hill",
+ description:
+ "One of New Amsterdam’s five boroughs; rooftop and alley routes.",
+ unlockableCharacters: [{name: "Rave"}, {name: "Frank"}, {name: "Rietveld"}],
+ adjacentLocations: [
+ {
+ name: "Millennium Square",
+ image:
+ "https://static.wikia.nocookie.net/bomb-rush-cyberfunk/images/d/dd/VersumHillToMilleniumSquare.jpg/revision/latest/scale-to-width-down/1000?cb=20240811221634",
+ unlockTrigger: "",
+ },
+ {
+ name: "Hideout",
+ image:
+ "https://static.wikia.nocookie.net/bomb-rush-cyberfunk/images/8/80/VersumHillToHideout.jpg/revision/latest/scale-to-width-down/1000?cb=20240811221626",
+ unlockTrigger: "",
+ },
+ ],
+ cypherSpots: [
+ {
+ location: "In the below-ground path at the bottom of the main area.",
+ image:
+ "https://static.wikia.nocookie.net/bomb-rush-cyberfunk/images/f/fb/CypherSpotVersumHill1.png/revision/latest/scale-to-width-down/1000?cb=20240427040816",
+ },
+ {
+ location:
+ "In one of the corners of the main area, specifically opposite of the stairs leading up to the basketball court.",
+ image:
+ "https://static.wikia.nocookie.net/bomb-rush-cyberfunk/images/a/a3/CypherSpotVersumHill2.png/revision/latest/scale-to-width-down/1000?cb=20240427040852",
+ },
+ {
+ location:
+ "On a slightly raised section of the ground near the borough's taxi stop.",
+ image:
+ "https://static.wikia.nocookie.net/bomb-rush-cyberfunk/images/b/bd/CypherSpotVersumHill3.png/revision/latest/scale-to-width-down/1000?cb=20240427040923",
+ },
+ ],
+ imageUrl:
+ "https://static.wikia.nocookie.net/bomb-rush-cyberfunk/images/1/12/VersumHill1.jpg/revision/latest/scale-to-width-down/1000?cb=20240811205909",
+ collectablesCount: "22",
+ maxRep: "282",
+ graffitiCollectables: [
+ "Vom'b",
+ "Zona Leste",
+ "Pico Pow",
+ "JD Vila Formosa",
+ "Messenger Mural",
+ "Buttercup",
+ "Gamex UPA ABL",
+ "Headphones On Helmet On",
+ ],
+ cds: [
+ "Precious Thing",
+ "Operator",
+ "GET ENUF",
+ "Bounce Upon A Time",
+ "Next To Me",
+ ],
+ outfits: [
+ {
+ character: "Red",
+ season: "Autumn",
+ },
+ {
+ character: "Red",
+ season: "Winter",
+ },
+ {
+ character: "Rave",
+ season: "Autumn",
+ },
+ ],
+ moveStyles: [
+ "Laser Accuracy",
+ "Death Boogie",
+ "Sylk",
+ "Taiga",
+ "Just Swell",
+ "Strawberry Missiles",
+ ],
+ mapCard:
+ "The map card for Versum Hill is found between two billboards, over the street that leads into Millennium Square. You can either use the billboards to wallride to it, or otherwise just jump off the rail near it. This is only available after beating the Franks.",
+ taxiStop:
+ "Versum Hill's taxi stop is located in the main section of this area. Facing the stairs leading up to the basketball court, the stop is on the raised section to the left of this, past a set of shorter stairs.",
+ outhouses: [
+ {
+ location:
+ "In a corner of the below-ground path, in the main area of the borough.",
+ image:
+ "https://static.wikia.nocookie.net/bomb-rush-cyberfunk/images/d/d8/OuthouseVersumHill1.png/revision/latest/scale-to-width-down/1000?cb=20240426215910",
+ },
+ {
+ location:
+ "At the back of the main area, between the stairs leading to the borough's taxi stop and the stairs leading to the basketball court.",
+ image:
+ "https://static.wikia.nocookie.net/bomb-rush-cyberfunk/images/f/f8/OuthouseVersumHill2.png/revision/latest/scale-to-width-down/1000?cb=20240426220048",
+ },
+ {
+ location:
+ "In the underground bazaar. When entering the bazaar, this outhouse is along the right side.",
+ image:
+ "https://static.wikia.nocookie.net/bomb-rush-cyberfunk/images/1/1e/OuthouseVersumHill3.png/revision/latest/scale-to-width-down/1000?cb=20240426220138",
+ },
+ ],
+ roboPosts: [
+ {
+ location:
+ "The first booth can be found along the main road between the entrances to the hideout and Millennium Square. This has 3 mascots, and the prize is the Buttercup graffiti design.",
+ image:
+ "https://static.wikia.nocookie.net/bomb-rush-cyberfunk/images/0/05/RoboPostVersumHill1.png/revision/latest/scale-to-width-down/1000?cb=20240725223942",
+ },
+ {
+ location:
+ "The second booth is in the main area of Versum Hill, right near the taxi stop for this area. This has 4 mascots, scattered across this area, and the prize is the Bounce Upon A Time CD.",
+ image:
+ "https://static.wikia.nocookie.net/bomb-rush-cyberfunk/images/6/6b/RoboPostVersumHill2.png/revision/latest/scale-to-width-down/1000?cb=20240725224019",
+ },
+ {
+ location:
+ "The last booth is near the basketball court at the top of the hill. This has 6 mascots, all along this area, and the prize is the Operator CD.",
+ image:
+ "https://static.wikia.nocookie.net/bomb-rush-cyberfunk/images/e/e1/RoboPostVersumHill3.png/revision/latest/scale-to-width-down/1000?cb=20240725224437",
+ },
+ ],
+ isBorough: true,
+ poloLocations: [
+ {
+ location:
+ "A giant Polo can be found sitting on one of the buildings near the entrance back to the hideout.",
+ image:
+ "https://static.wikia.nocookie.net/bomb-rush-cyberfunk/images/c/c0/PoloVersumHill1.png/revision/latest/scale-to-width-down/1000?cb=20240429182207",
+ },
+ {
+ location:
+ "Another Polo can be found behind some glass, further down the road from the big Polo. You have to use your boostpack to break through and get close enough for the photo.",
+ image:
+ "https://static.wikia.nocookie.net/bomb-rush-cyberfunk/images/6/6e/PoloVersumHill2.png/revision/latest/scale-to-width-down/1000?cb=20240429182538",
+ },
+ {
+ location:
+ "The last Polo in Versum Hill is in the underground bazaar. Towards the back of the bazaar, near the giant snake statues, there is an opening near the floor. Slide into it to reach a hidden room that contains the Polo.",
+ image:
+ "https://static.wikia.nocookie.net/bomb-rush-cyberfunk/images/4/40/PoloVersumHill3_1.png/revision/latest/scale-to-width-down/1000?cb=20240429182609",
+ },
+ ],
+ gallery: [
+ "https://static.wikia.nocookie.net/bomb-rush-cyberfunk/images/9/93/KeyArtLogo.png/revision/latest/scale-to-width-down/185?cb=20240522230040",
+ "https://static.wikia.nocookie.net/bomb-rush-cyberfunk/images/c/c1/Versum_hill_grind.jpg/revision/latest/scale-to-width-down/185?cb=20240712161124",
+ "https://static.wikia.nocookie.net/bomb-rush-cyberfunk/images/9/91/VersumHillMapMenu.jpg/revision/latest/scale-to-width-down/185?cb=20240811224028",
+ "https://static.wikia.nocookie.net/bomb-rush-cyberfunk/images/2/2c/VersumHill2.jpg/revision/latest/scale-to-width-down/185?cb=20240811212605",
+ "https://static.wikia.nocookie.net/bomb-rush-cyberfunk/images/d/da/VersumHill3.jpg/revision/latest/scale-to-width-down/185?cb=20240811212611",
+ ],
+ };
+};
diff --git a/test/data/songs.js b/test/data/songs.js
new file mode 100644
index 0000000..add12f8
--- /dev/null
+++ b/test/data/songs.js
@@ -0,0 +1,43 @@
+export const createJsrSong = (testId) => {
+ return {
+ _id: testId,
+ artistId: "642f97e8ce2153e34bcd3774",
+ audioLink:
+ "https://jetsetradio.live/radio/stations/classic/Deavid Soul - Dunny Boy Williamson Show.mp3",
+ createdAt: "2023-04-07T04:27:54.124Z",
+ gameId: "642f773924b4bca91d5a6c54",
+ name: "Dunny Boy Williamson Show",
+ shortName: "Dunny Boy Williamson Show",
+ updatedAt: "2023-04-07T04:27:54.124Z",
+ };
+};
+
+export const createJsrfSong = (testId) => {
+ return {
+ _id: testId,
+ chapters: ["8", "9", "sewers"],
+ name: "The Scrappy (The Latch Brothers Remix)",
+ shortName: "The Scrappy",
+ gameId: "642f773924b4bca91d5a6c57",
+ artistId: "642f778024b4bca91d5a70b5",
+ audioLink:
+ "https://jetsetradio.live/radio/stations/future/BS 2000 - The Scrappy (The Latch Brothers Remix).mp3",
+ createdAt: "2023-04-07T04:28:08.839Z",
+ updatedAt: "2023-04-07T04:28:08.839Z",
+ };
+};
+
+export const createBrcSong = (testId) => {
+ return {
+ _id: testId,
+ name: "Big City Life",
+ key: "Big City Life-kidkanevilV",
+ audioLink:
+ "https://drive.google.com/file/d/1Y3TnluLo2k62acc3K6R21isTyTdqptOM/view?usp=drive_link",
+ trackNumber: 1,
+ length: "2:59",
+ gameId: "68b8db67cec5d6b573cf624c",
+ artistId: "68b8dba6cec5d6b573cf66ce",
+ featuredArtistId: "68b8dba6cec5d6b573cf66ce",
+ };
+};
diff --git a/test/games.test.js b/test/games.test.js
index 237db33..7d90c19 100644
--- a/test/games.test.js
+++ b/test/games.test.js
@@ -1,35 +1,36 @@
-import {jest} from '@jest/globals';
-import axios from 'axios';
-import dotenv from 'dotenv';
+import {jest} from "@jest/globals";
+import axios from "axios";
+import dotenv from "dotenv";
dotenv.config();
-import { connect, disconnect } from './helper/mongodbMemoryTest.js';
-import { fetchGames } from '../src/controllers/gameController.js';
+import {connect, disconnect} from "./helper/mongodbMemoryTest.js";
+import {isValidJson} from "./helper/util.js";
+import {fetchGames} from "../src/controllers/gameController.js";
+import {createGame} from "./data/games.js";
const baseUrl = `${process.env.BASE_URL}/v1/api`;
-const createMock = mockObj => axios.get = jest.fn().mockResolvedValue({ __esModule: true, data: mockObj })
-
-
-describe('Games Routes', () => {
+const createMock = (mockObj) =>
+ (axios.get = jest.fn().mockResolvedValue({__esModule: true, data: mockObj}));
+describe("Games Routes", () => {
beforeAll(connect);
afterAll(disconnect);
afterEach(() => jest.clearAllMocks());
/* API/Integration Tests */
- test('GET /games', async () => {
+ test("GET /games", async () => {
const games = await fetchGames();
expect(Array.isArray(games)).toBe(true);
expect(isValidJson(games)).toBe(true);
- expect(games[0]).toHaveProperty('name');
- expect(games[0]).toHaveProperty('publishers');
- expect(games[0]).toHaveProperty('developers');
- expect(games[0]).toHaveProperty('platforms');
- expect(games[0]).toHaveProperty('releaseDates');
- })
+ expect(games[0]).toHaveProperty("name");
+ expect(games[0]).toHaveProperty("publishers");
+ expect(games[0]).toHaveProperty("developers");
+ expect(games[0]).toHaveProperty("platforms");
+ expect(games[0]).toHaveProperty("releaseDates");
+ });
/* Unit/Mock Tests */
- test('GET /games/:id', async () => {
+ test("GET /games/:id", async () => {
const testId = "642f773924b4bca91d5a6c57";
createMock(createGame(testId));
@@ -41,62 +42,5 @@ describe('Games Routes', () => {
expect(result.data.releaseDates).toHaveLength(3);
expect(result.data.assets).toHaveLength(4);
expect(axios.get).toHaveBeenCalledTimes(1);
- })
-
-
- /* Helper Objects */
- const createGame = (testId) => { return {
- "_id" : testId,
- "aliases" : [],
- "developers" : ["Smilebit"],
- "publishers" : ["Sega"],
- "platforms" : ["Xbox"],
- "releaseDates" : [
- { "country" : "JP", "date" : "February 22, 2002" },
- { "country" : "US", "date" : "February 25, 2002" },
- { "country" : "EU", "date" : "March 24, 2002" }
- ],
- "assets" : [
- { "country" : "US", "images" : {
- "frontCover" : "https://storage.googleapis.com/jetsetradio-api-core/images/games/jsrf/us/front-cover.webp",
- "backCover" : "https://storage.googleapis.com/jetsetradio-api-core/images/games/jsrf/us/back-cover.webp",
- "disc" : "https://storage.googleapis.com/jetsetradio-api-core/images/games/jsrf/us/disc.webp"
- }
- },
- { "country" : "JP", "images" : {
- "frontCover" : "https://storage.googleapis.com/jetsetradio-api-core/images/games/jsrf/jp/front-cover.webp",
- "backCover" : "https://storage.googleapis.com/jetsetradio-api-core/images/games/jsrf/jp/back-cover.webp",
- "disc" : "https://storage.googleapis.com/jetsetradio-api-core/images/games/jsrf/jp/disc.webp"
- }
- },
- { "country" : "EU", "images" : {
- "frontCover" : "https://storage.googleapis.com/jetsetradio-api-core/images/games/jsrf/eu/front-cover.webp",
- "backCover" : "https://storage.googleapis.com/jetsetradio-api-core/images/games/jsrf/eu/back-cover.webp",
- "disc" : "https://storage.googleapis.com/jetsetradio-api-core/images/games/jsrf/eu/disc.webp"
- }
- },
- { "country" : "DE", "images" : {
- "frontCover" : "https://storage.googleapis.com/jetsetradio-api-core/images/games/jsrf/de/front-cover.webp",
- "backCover" : "https://storage.googleapis.com/jetsetradio-api-core/images/games/jsrf/de/back-cover.webp",
- "disc" : "https://storage.googleapis.com/jetsetradio-api-core/images/games/jsrf/de/disc.webp"
- }
- }
- ],
- "name" : "Jet Set Radio Future",
- "intro" : "Jet Set Radio Future (JSRF) is the sequel to Jet Set Radio (also known as Jet Grind Radio in America), and a sort of re-imagining of that game.",
- "description" : "Jet Set Radio Future is set in Tokyo-To, Japan, in 2024, where a street gang known as the GG's is fighting for control over the streets against rival gangs, as well as the large and powerful corporation known as the Rokkaku Group, led by Gouji Rokkaku. The Rokkaku Group is attempting to seize control of Tokyo by force and convert it into a totalitarian police state.",
- "genre" : "Platform/Sports",
- "createdAt" : "2023-04-07T01:51:53.511Z",
- "updatedAt" : "2023-04-07T01:51:53.511Z"
- }}
-
- const isValidJson = (text) => {
- try {
- JSON.parse(JSON.stringify(text));
- return true;
- } catch {
- return false;
- }
- }
-
-})
\ No newline at end of file
+ });
+});
diff --git a/test/graffitiTags.test.js b/test/graffitiTags.test.js
index 7ce2d52..b89b860 100644
--- a/test/graffitiTags.test.js
+++ b/test/graffitiTags.test.js
@@ -1,24 +1,28 @@
-import {jest} from '@jest/globals';
-import axios from 'axios';
-import dotenv from 'dotenv';
+import {jest} from "@jest/globals";
+import axios from "axios";
+import dotenv from "dotenv";
dotenv.config();
-import { connect, disconnect } from './helper/mongodbMemoryTest.js';
-import { fetchJSRTags, fetchJSRFTags } from '../src/controllers/graffitiTagController.js';
-import { sortObjects } from '../src/utils/utility.js';
+import {connect, disconnect} from "./helper/mongodbMemoryTest.js";
+import {isValidJson} from "./helper/util.js";
+import {
+ fetchJSRTags,
+ fetchJSRFTags,
+} from "../src/controllers/graffitiTagController.js";
+import {createJsrTag, createJsrfTag} from "./data/graffititags.js";
+import {sortObjects} from "../src/utils/utility.js";
const baseUrl = `${process.env.BASE_URL}/v1/api`;
-const createMock = mockObj => axios.get = jest.fn().mockResolvedValue({ __esModule: true, data: mockObj })
-
-
-describe('GraffitiTag Routes', () => {
+const createMock = (mockObj) =>
+ (axios.get = jest.fn().mockResolvedValue({__esModule: true, data: mockObj}));
+describe("GraffitiTag Routes", () => {
beforeAll(connect);
afterAll(disconnect);
afterEach(() => jest.clearAllMocks());
/* API/Integration Tests */
- test('GET /graffiti-tags', async () => {
+ test("GET /graffiti-tags", async () => {
const jsrTags = await fetchJSRTags();
const jsrfTags = await fetchJSRFTags();
const tags = [...jsrTags, ...jsrfTags];
@@ -27,20 +31,20 @@ describe('GraffitiTag Routes', () => {
expect(Array.isArray(tags)).toBe(true);
expect(isValidJson(tags)).toBe(true);
- expect(jsrTags[0]).toHaveProperty('number');
- expect(jsrTags[0]).toHaveProperty('tagName');
- expect(jsrTags[0]).toHaveProperty('tagSubName');
- expect(jsrTags[0]).toHaveProperty('imageUrl');
- expect(jsrfTags[9]).toHaveProperty('tagName');
- expect(jsrfTags[9]).toHaveProperty('gameId');
- expect(jsrfTags[9]).toHaveProperty('imageUrl');
- expect(jsrfTags[9]).toHaveProperty('wikiImageUrl');
- })
-
- test('GET /graffiti-tags?sortBy=name&orderBy=desc', async () => {
- const req = {query: { sortBy: 'name', orderBy: 'desc' }};
+ expect(jsrTags[0]).toHaveProperty("number");
+ expect(jsrTags[0]).toHaveProperty("tagName");
+ expect(jsrTags[0]).toHaveProperty("tagSubName");
+ expect(jsrTags[0]).toHaveProperty("imageUrl");
+ expect(jsrfTags[9]).toHaveProperty("tagName");
+ expect(jsrfTags[9]).toHaveProperty("gameId");
+ expect(jsrfTags[9]).toHaveProperty("imageUrl");
+ expect(jsrfTags[9]).toHaveProperty("wikiImageUrl");
+ });
+
+ test("GET /graffiti-tags?sortBy=name&orderBy=desc", async () => {
+ const req = {query: {sortBy: "name", orderBy: "desc"}};
const sortByValue = req?.query?.sortBy ? req?.query?.sortBy : undefined;
- const sortOrder = req?.query?.orderBy ? req?.query?.orderBy : 'asc';
+ const sortOrder = req?.query?.orderBy ? req?.query?.orderBy : "asc";
const jsrTags = await fetchJSRTags();
const jsrfTags = await fetchJSRFTags();
@@ -52,26 +56,26 @@ describe('GraffitiTag Routes', () => {
tags = [...jsrTags, ...jsrfTags];
}
expect(isValidJson(tags)).toBe(true);
- })
+ });
- test('GET /graffiti-tags/jsr?limit=5', async () => {
- const req = {query: { limit: '5' }};
+ test("GET /graffiti-tags/jsr?limit=5", async () => {
+ const req = {query: {limit: "5"}};
const tags = await fetchJSRTags(req);
expect(Array.isArray(tags)).toBe(true);
expect(isValidJson(tags)).toBe(true);
expect(tags).toHaveLength(5);
- })
+ });
- test('GET /graffiti-tags/jsrf?limit=15', async () => {
- const req = {query: { limit: '15' }};
+ test("GET /graffiti-tags/jsrf?limit=15", async () => {
+ const req = {query: {limit: "15"}};
const tags = await fetchJSRFTags(req);
expect(Array.isArray(tags)).toBe(true);
expect(isValidJson(tags)).toBe(true);
expect(tags).toHaveLength(15);
- })
+ });
/* Unit/Mock Tests */
- test('GET /graffiti-tags/jsr/:id', async () => {
+ test("GET /graffiti-tags/jsr/:id", async () => {
const testId = "642f774b24b4bca91d5a6c99";
createMock(createJsrTag(testId));
@@ -79,11 +83,13 @@ describe('GraffitiTag Routes', () => {
expect(isValidJson(result.data)).toBe(true);
expect(result.data.tagSubName).toBe("SARU - A Head of the Crowd");
expect(result.data.size).toBe("XL");
- expect(result.data.imageUrl).toBe("https://storage.googleapis.com/greg-kennedy.com/jsr/U_GRF.0018.png");
+ expect(result.data.imageUrl).toBe(
+ "https://storage.googleapis.com/greg-kennedy.com/jsr/U_GRF.0018.png"
+ );
expect(axios.get).toHaveBeenCalledTimes(1);
- })
+ });
- test('GET /graffiti-tags/jsrf/:id', async () => {
+ test("GET /graffiti-tags/jsrf/:id", async () => {
const testId = "642f776624b4bca91d5a6f4b";
createMock(createJsrfTag(testId));
@@ -92,57 +98,14 @@ describe('GraffitiTag Routes', () => {
expect(result.data.number).toBe("No. 025");
expect(result.data.tagName).toBe("Poison Jam");
expect(result.data.size).toBe("SS");
- expect(result.data.imageUrl).toBe("https://storage.googleapis.com/jetsetradio-api/jsrf/graffiti-tags/025.png");
- expect(result.data.locations[0].name).toBe('Tokyo Underground Sewage Facility');
- expect(result.data.locations[0].id).toBe('6445cb9dd85986264951f134');
+ expect(result.data.imageUrl).toBe(
+ "https://storage.googleapis.com/jetsetradio-api/jsrf/graffiti-tags/025.png"
+ );
+ expect(result.data.locations[0].name).toBe(
+ "Tokyo Underground Sewage Facility"
+ );
+ expect(result.data.locations[0].id).toBe("6445cb9dd85986264951f134");
expect(result.data.locations).toHaveLength(2);
expect(axios.get).toHaveBeenCalledTimes(1);
- })
-
-
- /* Helper Objects */
- const createJsrTag = (testId) => { return {
- "_id" : testId,
- "number" : "0018",
- "tagName" : "SARU",
- "tagSubName" : "SARU - A Head of the Crowd",
- "size" : "XL",
- "gameId" : "642f773924b4bca91d5a6c54",
- "imageUrl" : "https://storage.googleapis.com/greg-kennedy.com/jsr/U_GRF.0018.png",
- "createdAt" : "2023-04-07T01:52:11.751Z",
- "updatedAt" : "2023-04-07T01:52:11.751Z"
-}}
-
- const createJsrfTag = (testId) => {return {
- "_id" : "6430ea8f11948a6df41bcf22",
- "number" : "No. 025",
- "tagName" : "Poison Jam",
- "size" : "SS",
- "wikiImageUrl" : "https://static.wikia.nocookie.net/jetsetradio/images/e/e8/No._025.png/revision/latest/scale-to-width-down/80?cb=20170220220832",
- "imageUrl" : "https://storage.googleapis.com/jetsetradio-api/jsrf/graffiti-tags/025.png",
- "gameId" : "64285b7918c8a0231136dc5d",
- "createdAt" : "2023-04-08T04:16:16.114Z",
- "updatedAt" : "2023-04-24T04:23:33.147Z",
- "graffitiSoulLocation" : "Test Runs (Unlock Poison Jam as a playable character)",
- "locations" : [
- {
- "name" : "Tokyo Underground Sewage Facility",
- "id" : "6445cb9dd85986264951f134"
- },
- {
- "name" : "Bottom Point of Sewage Facility",
- "id" : "6445cb9ed85986264951f13b"
- }
- ]
-}}
-
- const isValidJson = (text) => {
- try {
- JSON.parse(JSON.stringify(text));
- return true;
- } catch {
- return false;
- }
- }
-
-})
\ No newline at end of file
+ });
+});
diff --git a/test/helper/mongodbMemoryTest.js b/test/helper/mongodbMemoryTest.js
index e8b46fb..19c9bfb 100644
--- a/test/helper/mongodbMemoryTest.js
+++ b/test/helper/mongodbMemoryTest.js
@@ -1,5 +1,5 @@
-import { MongoMemoryServer } from "mongodb-memory-server";
-import { MongoClient } from "mongodb";
+import {MongoMemoryServer} from "mongodb-memory-server";
+import {MongoClient} from "mongodb";
let mongoDB;
let client;
@@ -9,9 +9,9 @@ export const connect = async () => {
const uri = mongoDB.getUri();
client = new MongoClient(uri);
await client.connect();
-}
+};
export const disconnect = async () => {
await client.close();
await mongoDB.stop();
-}
+};
diff --git a/test/helper/util.js b/test/helper/util.js
new file mode 100644
index 0000000..14331f4
--- /dev/null
+++ b/test/helper/util.js
@@ -0,0 +1,8 @@
+export const isValidJson = (text) => {
+ try {
+ JSON.parse(JSON.stringify(text));
+ return true;
+ } catch {
+ return false;
+ }
+};
diff --git a/test/locations.test.js b/test/locations.test.js
index f2e4c0b..983cc43 100644
--- a/test/locations.test.js
+++ b/test/locations.test.js
@@ -1,57 +1,66 @@
-import {jest} from '@jest/globals';
-import axios from 'axios';
-import dotenv from 'dotenv';
+import {jest} from "@jest/globals";
+import axios from "axios";
+import dotenv from "dotenv";
dotenv.config();
-import { connect, disconnect } from './helper/mongodbMemoryTest.js';
-import { fetchJSRLocations, fetchJSRFLocations, fetchJSRLevels } from '../src/controllers/locationController.js';
-import { sortObjects } from '../src/utils/utility.js';
+import {connect, disconnect} from "./helper/mongodbMemoryTest.js";
+import {isValidJson} from "./helper/util.js";
+import {
+ createJsrLevel,
+ createJsrLocation,
+ createJsrfLocation,
+ createBRCLocation,
+} from "./data/locations.js";
+import {
+ fetchLocations,
+ fetchJSRLevels,
+} from "../src/controllers/locationController.js";
+import {sortObjects} from "../src/utils/utility.js";
+import Constants from "../src/constants/dbConstants.js";
const baseUrl = `${process.env.BASE_URL}/v1/api`;
-const createMock = mockObj => axios.get = jest.fn().mockResolvedValue({ __esModule: true, data: mockObj })
-
-
-describe('Location Routes', () => {
+const createMock = (mockObj) =>
+ (axios.get = jest.fn().mockResolvedValue({__esModule: true, data: mockObj}));
+const {JSR_DB, JSRF_DB, BRC_DB} = Constants;
+const req = {query: {}};
+describe("Location Routes", () => {
beforeAll(connect);
afterAll(disconnect);
afterEach(() => jest.clearAllMocks());
/* API/Integration Tests */
- test('GET /locations', async () => {
- const jsrLocations = await fetchJSRLocations();
- const jsrfLocations = await fetchJSRFLocations();
- const locations = [...jsrLocations, ...jsrfLocations];
- expect(Array.isArray(jsrLocations)).toBe(true);
- expect(Array.isArray(jsrfLocations)).toBe(true);
+
+ test("GET /locations", async () => {
+ const locations = await fetchLocations(req, "ALL");
expect(Array.isArray(locations)).toBe(true);
expect(isValidJson(locations)).toBe(true);
- expect(jsrLocations[0]).toHaveProperty('name')
- expect(jsrLocations[0]).toHaveProperty('description')
- expect(jsrLocations[0]).toHaveProperty('subLocations')
- expect(jsrLocations[0]).toHaveProperty('unlockableCharacters')
- expect(jsrLocations[0]).toHaveProperty('secretCharacter')
- })
+ expect(locations[0]).toHaveProperty("name");
+ expect(locations[0]).toHaveProperty("description");
+ expect(locations[0]).toHaveProperty("subLocations");
+ expect(locations[0]).toHaveProperty("unlockableCharacters");
+ expect(locations[0]).toHaveProperty("secretCharacter");
+ });
- test('GET /levels', async () => {
- const levels = await fetchJSRLevels();
+ test("GET /levels", async () => {
+ const levels = await fetchJSRLevels(req);
expect(Array.isArray(levels)).toBe(true);
expect(isValidJson(levels)).toBe(true);
- expect(levels[0]).toHaveProperty('name')
- expect(levels[0]).toHaveProperty('description')
- expect(levels[0]).toHaveProperty('location')
- expect(levels[0]).toHaveProperty('bossLevel')
- expect(levels[0]).toHaveProperty('chapter')
- })
+ expect(levels[0]).toHaveProperty("name");
+ expect(levels[0]).toHaveProperty("description");
+ expect(levels[0]).toHaveProperty("location");
+ expect(levels[0]).toHaveProperty("bossLevel");
+ expect(levels[0]).toHaveProperty("chapter");
+ });
- test('GET /locations?sortBy=name&orderBy=desc', async () => {
- const req = {query: { sortBy: 'name', orderBy: 'desc' }};
+ test("GET /locations?sortBy=name&orderBy=desc", async () => {
+ const req = {query: {sortBy: "name", orderBy: "desc"}};
const sortByValue = req?.query?.sortBy ? req?.query?.sortBy : undefined;
- const sortOrder = req?.query?.orderBy ? req?.query?.orderBy : 'asc';
- const jsrLocations = await fetchJSRLocations();
- const jsrfLocations = await fetchJSRFLocations();
+ const sortOrder = req?.query?.orderBy ? req?.query?.orderBy : "asc";
+ const jsrLocations = await fetchLocations(req, JSR_DB);
+ const jsrfLocations = await fetchLocations(req, JSRF_DB);
let locations = [];
if (sortByValue) {
@@ -61,26 +70,42 @@ describe('Location Routes', () => {
tags = [...jsrLocations, ...jsrfLocations];
}
expect(isValidJson(locations)).toBe(true);
- })
+ });
- test('GET /locations/jsr?limit=2', async () => {
- const req = {query: { limit: '2' }};
- const locations = await fetchJSRLocations(req);
+ test("GET /locations/jsr?limit=2", async () => {
+ const req = {query: {limit: "2"}};
+ const locations = await fetchLocations(req, JSR_DB);
expect(Array.isArray(locations)).toBe(true);
expect(isValidJson(locations)).toBe(true);
expect(locations).toHaveLength(2);
- })
+ });
- test('GET /locations/jsrf?limit=15', async () => {
- const req = {query: { limit: '15' }};
- const locations = await fetchJSRFLocations(req);
+ test("GET /locations/jsrf?limit=15", async () => {
+ const req = {query: {limit: "15"}};
+ const locations = await fetchLocations(req, JSRF_DB);
expect(Array.isArray(locations)).toBe(true);
expect(isValidJson(locations)).toBe(true);
expect(locations).toHaveLength(15);
- })
+ });
+
+ test("GET /locations/brc?limit=3", async () => {
+ const req = {query: {limit: "3"}};
+ const locations = await fetchLocations(req, BRC_DB);
+ expect(Array.isArray(locations)).toBe(true);
+ expect(isValidJson(locations)).toBe(true);
+ const location = locations[0];
+ expect(location).toHaveProperty("name");
+ expect(location).toHaveProperty("adjacentLocations");
+ expect(location).toHaveProperty("cypherSpots");
+ expect(location).toHaveProperty("collectablesCount");
+ expect(location).toHaveProperty("maxRep");
+ expect(location).toHaveProperty("mapCard");
+ expect(location).toHaveProperty("taxiStop");
+ expect(locations).toHaveLength(3);
+ });
/* Unit/Mock Tests */
- test('GET /locations/jsr/:id', async () => {
+ test("GET /locations/jsr/:id", async () => {
const testId = "6445cb96d85986264951f0ae";
createMock(createJsrLocation(testId));
@@ -88,12 +113,14 @@ describe('Location Routes', () => {
expect(isValidJson(result.data)).toBe(true);
expect(result.data.name).toBe("Benten-cho");
expect(result.data.gameId).toBe("64285b7918c8a0231136dc5a");
- expect(result.data.description).toBe("Benten-cho is the home turf of the Noise Tanks, a rival tech-savvy gang of the GG's.");
+ expect(result.data.description).toBe(
+ "Benten-cho is the home turf of the Noise Tanks, a rival tech-savvy gang of the GG's."
+ );
expect(result.data.unlockableCharacters).toHaveLength(4);
expect(axios.get).toHaveBeenCalledTimes(1);
- })
+ });
- test('GET /levels/:id', async () => {
+ test("GET /levels/:id", async () => {
const testId = "6445cb9ad85986264951f0eb";
createMock(createJsrLevel(testId));
@@ -103,122 +130,49 @@ describe('Location Routes', () => {
expect(result.data.location.name).toBe("Benten-cho");
expect(result.data.location.id).toBe("6445cb96d85986264951f0ae");
expect(axios.get).toHaveBeenCalledTimes(1);
- })
+ });
- test('GET /locations/jsrf/:id', async () => {
+ test("GET /locations/jsrf/:id", async () => {
const testId = "6445cb9cd85986264951f113";
createMock(createJsrfLocation(testId));
const result = await axios.get(`${baseUrl}/locations/jsrf/${testId}`);
expect(isValidJson(result.data)).toBe(true);
expect(result.data.name).toBe("Dogenzaka Hill");
- expect(result.data.description).toBe("Dogenzaka Hill is the first level in the game after completing the initial tutorial at the Garage.");
+ expect(result.data.description).toBe(
+ "Dogenzaka Hill is the first level in the game after completing the initial tutorial at the Garage."
+ );
expect(result.data.unlockableCharacters).toHaveLength(2);
expect(result.data.adjacentLocations).toHaveLength(2);
expect(result.data.hasMixtape).toBe(true);
- expect(result.data.imageUrl).toBe("https://storage.googleapis.com/jetsetradio-api/jsrf/locations/dogenzaka-hill.webp");
- expect(result.data.secretCharacter.name).toBe('Doom Riders');
- expect(result.data.secretCharacter.id).toBe('643c719b8cabe0dcede868c6');
+ expect(result.data.imageUrl).toBe(
+ "https://storage.googleapis.com/jetsetradio-api/jsrf/locations/dogenzaka-hill.webp"
+ );
+ expect(result.data.secretCharacter.name).toBe("Doom Riders");
+ expect(result.data.secretCharacter.id).toBe("643c719b8cabe0dcede868c6");
+ expect(axios.get).toHaveBeenCalledTimes(1);
+ });
+
+ test("GET /locations/brc/:id", async () => {
+ const testId = "68bb2c34813c6317bbd07cab";
+ createMock(createBRCLocation(testId));
+
+ const result = await axios.get(`${baseUrl}/locations/brc/${testId}`);
+ expect(isValidJson(result.data)).toBe(true);
+ expect(result.data.name).toBe("Versum Hill");
+ expect(result.data.description).toBe(
+ "One of New Amsterdam’s five boroughs; rooftop and alley routes."
+ );
+ expect(result.data.unlockableCharacters).toHaveLength(3);
+ expect(result.data.adjacentLocations).toHaveLength(2);
+ expect(result.data.mapCard).toBe(
+ "The map card for Versum Hill is found between two billboards, over the street that leads into Millennium Square. You can either use the billboards to wallride to it, or otherwise just jump off the rail near it. This is only available after beating the Franks."
+ );
+ expect(result.data.imageUrl).toBe(
+ "https://static.wikia.nocookie.net/bomb-rush-cyberfunk/images/1/12/VersumHill1.jpg/revision/latest/scale-to-width-down/1000?cb=20240811205909"
+ );
+ expect(result.data.collectablesCount).toBe("22");
+ expect(result.data.maxRep).toBe("282");
expect(axios.get).toHaveBeenCalledTimes(1);
- })
-
-})
-
-/* Helpers */
-const createJsrLocation = (testId) => { return {
- "_id" : testId,
- "levels" : [],
- "subLocations" : [
- "Subway",
- "Train Line",
- "Chinatown",
- "Highway Zero",
- "Geckijomae",
- "Business District"
- ],
- "unlockableCharacters" : [
- {
- "name" : "Noise Tanks",
- "id" : "643c71bb8cabe0dcede86902"
- },
- {
- "name" : "Mew",
- "id" : "643c71a58cabe0dcede868e5"
- },
- {
- "name" : "Cube",
- "id" : "643c71c08cabe0dcede86917"
- },
- {
- "name" : "Yo-Yo",
- "id" : "643c71c38cabe0dcede86933"
- }
- ],
- "name" : "Benten-cho",
- "description" : "Benten-cho is the home turf of the Noise Tanks, a rival tech-savvy gang of the GG's.",
- "gameId" : "64285b7918c8a0231136dc5a",
- "secretCharacter" : {
- "name" : "Noise Tanks",
- "id" : "643c71bb8cabe0dcede86902"
- },
- "createdAt" : "2023-04-24T00:21:43.098Z",
- "updatedAt" : "2023-04-24T00:21:43.098Z"
-}}
-
-const createJsrLevel = (testId) => { return {
- "_id" : testId,
- "name" : "Noise Reduction",
- "description" : "This is a tag level. You need to tag each member of the Noise Tanks 10 times in order to beat this level.",
- "location" : {
- "name" : "Benten-cho",
- "id" : "6445cb96d85986264951f0ae"
- },
- "bossLevel" : true,
- "chapter" : "1",
- "createdAt" : "2023-04-24T00:21:46.134Z",
- "updatedAt" : "2023-04-24T00:21:46.134Z"
-}}
-
-const createJsrfLocation = (testId) => { return {
- "_id" : testId,
- "unlockableCharacters" : [
- {
- "name" : "Beat",
- "id" : "643c71a48cabe0dcede868de"
- },
- {
- "name" : "Doom Riders",
- "id" : "643c719b8cabe0dcede868c6"
- }
- ],
- "adjacentLocations" : [
- {
- "name" : "The Garage",
- "id" : "6445cb9bd85986264951f10f"
- },
- {
- "name" : "Shibuya Terminal",
- "id" : "6445cb9cd85986264951f11a"
- }
- ],
- "name" : "Dogenzaka Hill",
- "description" : "Dogenzaka Hill is the first level in the game after completing the initial tutorial at the Garage.",
- "gameId" : "64285b7918c8a0231136dc5d",
- "hasMixtape" : true,
- "imageUrl" : "https://storage.googleapis.com/jetsetradio-api/jsrf/locations/dogenzaka-hill.webp",
- "secretCharacter" : {
- "name" : "Doom Riders",
- "id" : "643c719b8cabe0dcede868c6"
- },
- "createdAt" : "2023-04-24T00:21:48.297Z",
- "updatedAt" : "2023-04-24T00:21:53.824Z"
-}}
-
-const isValidJson = (text) => {
- try {
- JSON.parse(JSON.stringify(text));
- return true;
- } catch {
- return false;
- }
-}
\ No newline at end of file
+ });
+});
diff --git a/test/songs.test.js b/test/songs.test.js
index 10ebbf0..9ace703 100644
--- a/test/songs.test.js
+++ b/test/songs.test.js
@@ -1,73 +1,74 @@
-import {jest} from '@jest/globals';
-import axios from 'axios';
-import dotenv from 'dotenv';
+import {jest} from "@jest/globals";
+import axios from "axios";
+import dotenv from "dotenv";
dotenv.config();
-import { connect, disconnect } from './helper/mongodbMemoryTest.js';
-import { fetchJSRSongs, fetchJSRFSongs } from '../src/controllers/songController.js';
-import { sortObjects } from '../src/utils/utility.js';
-
+import {connect, disconnect} from "./helper/mongodbMemoryTest.js";
+import {fetchSongs} from "../src/controllers/songController.js";
+import {sortObjects} from "../src/utils/utility.js";
+import {isValidJson} from "./helper/util.js";
+import Constants from "../src/constants/dbConstants.js";
+import {createJsrSong, createJsrfSong, createBrcSong} from "./data/songs.js";
const baseUrl = `${process.env.BASE_URL}/v1/api`;
-const createMock = mockObj => axios.get = jest.fn().mockResolvedValue({ __esModule: true, data: mockObj })
-
-describe('Songs Routes', () => {
+const createMock = (mockObj) =>
+ (axios.get = jest.fn().mockResolvedValue({__esModule: true, data: mockObj}));
+const {JSR_DB, JSRF_DB, BRC_DB} = Constants;
+const req = {query: {}};
+describe("Songs Routes", () => {
beforeAll(connect);
afterAll(disconnect);
afterEach(() => jest.clearAllMocks());
/* API/Integration Tests */
- test('GET /songs', async () => {
- const jsrSongs = await fetchJSRSongs();
- const jsrfSongs = await fetchJSRFSongs();
- const songs = [...jsrSongs, ...jsrSongs];
- expect(Array.isArray(jsrSongs)).toBe(true);
- expect(Array.isArray(jsrfSongs)).toBe(true);
+ test("GET /songs", async () => {
+ const songs = await fetchSongs(req, "ALL");
expect(Array.isArray(songs)).toBe(true);
expect(isValidJson(songs[0])).toBe(true);
- expect(songs[0]).toHaveProperty('artistId');
- expect(songs[0]).toHaveProperty('audioLink');
- expect(songs[0]).toHaveProperty('gameId');
- expect(songs[0]).toHaveProperty('name');
- expect(songs[0]).toHaveProperty('shortName');
- })
-
- test('GET /songs?sortBy=name&orderBy=desc', async () => {
- const req = {query: { sortBy: 'name', orderBy: 'desc' }};
+ expect(songs[0]).toHaveProperty("artistId");
+ expect(songs[0]).toHaveProperty("audioLink");
+ expect(songs[0]).toHaveProperty("gameId");
+ expect(songs[0]).toHaveProperty("name");
+ });
+
+ test("GET /songs?sortBy=name&orderBy=desc", async () => {
+ const req = {query: {sortBy: "name", orderBy: "desc"}};
const sortByValue = req?.query?.sortBy ? req?.query?.sortBy : undefined;
- const sortOrder = req?.query?.orderBy ? req?.query?.orderBy : 'asc';
- const jsrSongs = await fetchJSRSongs();
- const jsrfSongs = await fetchJSRFSongs();
-
- let songs = [];
+ const sortOrder = req?.query?.orderBy ? req?.query?.orderBy : "asc";
+ let songs = await fetchSongs(req, "ALL");
if (sortByValue) {
- const allSongs = [...jsrSongs, ...jsrfSongs];
- songs = allSongs.sort(sortObjects(sortByValue, sortOrder));
- } else {
- songs = [...jsrSongs, ...jsrfSongs];
+ songs = songs.sort(sortObjects(sortByValue, sortOrder));
}
expect(isValidJson(songs)).toBe(true);
- })
+ });
- test('GET /songs/jsr?limit=5', async () => {
- const req = {query: { limit: '5' }};
- const songs = await fetchJSRSongs(req);
+ test("GET /songs/jsr?limit=5", async () => {
+ const req = {query: {limit: "5"}};
+ const songs = await fetchSongs(req, JSR_DB);
expect(Array.isArray(songs)).toBe(true);
expect(isValidJson(songs)).toBe(true);
expect(songs).toHaveLength(5);
- })
+ });
- test('GET /songs/jsrf?limit=15', async () => {
- const req = {query: { limit: '15' }};
- const songs = await fetchJSRFSongs(req);
+ test("GET /songs/jsrf?limit=15", async () => {
+ const req = {query: {limit: "15"}};
+ const songs = await fetchSongs(req, JSRF_DB);
expect(Array.isArray(songs)).toBe(true);
expect(isValidJson(songs)).toBe(true);
expect(songs).toHaveLength(15);
- })
+ });
+
+ test("GET /songs/brc?limit=8", async () => {
+ const req = {query: {limit: "8"}};
+ const songs = await fetchSongs(req, JSRF_DB);
+ expect(Array.isArray(songs)).toBe(true);
+ expect(isValidJson(songs)).toBe(true);
+ expect(songs).toHaveLength(8);
+ });
/* Unit/Mock Tests */
- test('GET /songs/jsr/:id', async () => {
+ test("GET /songs/jsr/:id", async () => {
const testId = "642f9bca54abd26ec59b87cd";
createMock(createJsrSong(testId));
@@ -75,11 +76,13 @@ describe('Songs Routes', () => {
expect(isValidJson(result.data)).toBe(true);
expect(result.data.name).toBe("Dunny Boy Williamson Show");
expect(result.data.gameId).toBe("642f773924b4bca91d5a6c54");
- expect(result.data.audioLink).toBe("https://jetsetradio.live/radio/stations/classic/Deavid Soul - Dunny Boy Williamson Show.mp3");
+ expect(result.data.audioLink).toBe(
+ "https://jetsetradio.live/radio/stations/classic/Deavid Soul - Dunny Boy Williamson Show.mp3"
+ );
expect(axios.get).toHaveBeenCalledTimes(1);
- })
+ });
- test('GET /songs/jsrf/:id', async () => {
+ test("GET /songs/jsrf/:id", async () => {
const testId = "642f9bd854abd26ec59b885e";
createMock(createJsrfSong(testId));
@@ -89,44 +92,20 @@ describe('Songs Routes', () => {
expect(result.data.chapters).toStrictEqual(["8", "9", "sewers"]);
expect(result.data.gameId).toBe("642f773924b4bca91d5a6c57");
expect(axios.get).toHaveBeenCalledTimes(1);
- })
+ });
+ test("GET /songs/brc/:id", async () => {
+ const testId = "68bb2eb8c94ffcab6d8bf6f9";
+ createMock(createBrcSong(testId));
- /* Helper Objects */
- const createJsrSong = (testId) => { return {
- "_id": testId,
- "artistId": "642f97e8ce2153e34bcd3774",
- "audioLink": "https://jetsetradio.live/radio/stations/classic/Deavid Soul - Dunny Boy Williamson Show.mp3",
- "createdAt": "2023-04-07T04:27:54.124Z",
- "gameId": "642f773924b4bca91d5a6c54",
- "name": "Dunny Boy Williamson Show",
- "shortName": "Dunny Boy Williamson Show",
- "updatedAt": "2023-04-07T04:27:54.124Z"
- }}
-
- const createJsrfSong = (testId) => {return {
- "_id" : testId,
- "chapters" : [
- "8",
- "9",
- "sewers"
- ],
- "name" : "The Scrappy (The Latch Brothers Remix)",
- "shortName" : "The Scrappy",
- "gameId" : "642f773924b4bca91d5a6c57",
- "artistId" : "642f778024b4bca91d5a70b5",
- "audioLink" : "https://jetsetradio.live/radio/stations/future/BS 2000 - The Scrappy (The Latch Brothers Remix).mp3",
- "createdAt" : "2023-04-07T04:28:08.839Z",
- "updatedAt" : "2023-04-07T04:28:08.839Z"
- }}
-
- const isValidJson = (text) => {
- try {
- JSON.parse(JSON.stringify(text));
- return true;
- } catch {
- return false;
- }
- }
-
-})
\ No newline at end of file
+ const result = await axios.get(`${baseUrl}/songs/brc/${testId}`);
+ expect(isValidJson(result.data)).toBe(true);
+ expect(result.data.name).toBe("Big City Life");
+ expect(result.data.key).toBe("Big City Life-kidkanevilV");
+ expect(result.data.audioLink).toBe(
+ "https://drive.google.com/file/d/1Y3TnluLo2k62acc3K6R21isTyTdqptOM/view?usp=drive_link"
+ );
+ expect(result.data.gameId).toBe("68b8db67cec5d6b573cf624c");
+ expect(axios.get).toHaveBeenCalledTimes(1);
+ });
+});