diff --git a/jest.config.js b/jest.config.js
new file mode 100644
index 0000000..5114fe6
--- /dev/null
+++ b/jest.config.js
@@ -0,0 +1,4 @@
+export default {
+ testEnvironment: "node",
+ transform: {},
+};
diff --git a/package-lock.json b/package-lock.json
index 812dc9c..8e3c3d3 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "jetsetradio-api",
- "version": "1.1.5",
+ "version": "1.1.6",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "jetsetradio-api",
- "version": "1.1.5",
+ "version": "1.1.6",
"license": "Apache 2.0",
"dependencies": {
"axios": "^1.11.0",
@@ -2697,9 +2697,9 @@
"license": "MIT"
},
"node_modules/axios": {
- "version": "1.11.0",
- "resolved": "https://registry.npmjs.org/axios/-/axios-1.11.0.tgz",
- "integrity": "sha512-1Lx3WLFQWm3ooKDYZD1eXmoGO9fxYQjrycfHFC8P0sCfQVXyROp0p9PFWBehewBOdCwHc+f/b8I0fMto5eSfwA==",
+ "version": "1.12.1",
+ "resolved": "https://registry.npmjs.org/axios/-/axios-1.12.1.tgz",
+ "integrity": "sha512-Kn4kbSXpkFHCGE6rBFNwIv0GQs4AvDT80jlveJDKFxjbTYMUeB4QtsdPCv6H8Cm19Je7IU6VFtRl2zWZI0rudQ==",
"license": "MIT",
"dependencies": {
"follow-redirects": "^1.15.6",
diff --git a/package.json b/package.json
index 5438b11..c4ccbda 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "jetsetradio-api",
- "version": "1.1.5",
+ "version": "1.1.6",
"description": "A Data Provider relating to the JSR/JSRF universe",
"type": "module",
"main": "src/app.js",
diff --git a/src/controllers/artistController.js b/src/controllers/artistController.js
index 3e33fa5..25483a4 100644
--- a/src/controllers/artistController.js
+++ b/src/controllers/artistController.js
@@ -3,6 +3,7 @@ import Constants from "../constants/dbConstants.js";
import {Actions} from "../config/dbActions.js";
import {performDBAction} from "../config/db.js";
import LOGGER from "../utils/logger.js";
+import {fetchRandom} from "./utilController.js";
const Artist = "Artist";
const Song = "Song";
@@ -21,6 +22,15 @@ export const getArtists = async (req, res) => {
}
};
+export const getRandomArtist = async (req, res) => {
+ try {
+ res.send(await fetchRandom(req, Artist, "N/A"));
+ } catch (err) {
+ LOGGER.error(`Could not fetch random Artist`, err);
+ res.status(500).json({error: "Failed to fetch random Artist"});
+ }
+};
+
export const getArtistById = async (req, res) => {
try {
const artist = await performDBAction(
diff --git a/src/controllers/graffitiSoulController.js b/src/controllers/graffitiSoulController.js
new file mode 100644
index 0000000..7d1af1a
--- /dev/null
+++ b/src/controllers/graffitiSoulController.js
@@ -0,0 +1,113 @@
+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 {fetchRandom} from "./utilController.js";
+
+const GraffitiSoul = "GraffitiSoul";
+const {JSR_DB, JSRF_DB} = Constants;
+
+export const getAllGraffitiSouls = async (req, res) => {
+ try {
+ const sortByValue = req?.query?.sortBy ? req?.query?.sortBy : undefined;
+ const sortOrder = req?.query?.orderBy ? req?.query?.orderBy : "asc";
+ const jsrSouls = await fetchJSRSouls(req);
+ const jsrfSouls = await fetchJSRFSouls(req);
+ if (sortByValue) {
+ const Souls = [...jsrSouls, ...jsrfSouls];
+ return res.send(Souls.sort(sortObjects(sortByValue, sortOrder)));
+ }
+ res.send([...jsrSouls, ...jsrfSouls]);
+ } catch (err) {
+ LOGGER.error(`Could not fetch ALL GraffitiSouls`, err);
+ res
+ .status(500)
+ .json({message: "Failed to fetch ALL GraffitiSouls", err: err});
+ }
+};
+
+export const getRandomGraffitiSoul = async (req, res) => {
+ try {
+ res.send(await fetchRandom(req, GraffitiSoul));
+ } catch (err) {
+ LOGGER.error(`Could not fetch random GraffitiSoul`, err);
+ res.status(500).json({error: "Failed to fetch random GraffitiSoul"});
+ }
+};
+
+export const getJSRGraffitiSouls = async (req, res) => {
+ try {
+ res.send(await fetchJSRSouls(req));
+ } catch (err) {
+ LOGGER.error(`Could not fetch JSR GraffitiSouls`, err);
+ res
+ .status(500)
+ .json({message: "Failed to fetch JSR GraffitiSouls", err: err});
+ }
+};
+
+export const getJSRFGraffitiSouls = async (req, res) => {
+ try {
+ res.send(await fetchJSRFSouls(req));
+ } catch (err) {
+ LOGGER.error(`Could not fetch JSRF GraffitiSouls`, err);
+ res
+ .status(500)
+ .json({message: "Failed to fetch JSRF GraffitiSouls", err: err});
+ }
+};
+
+export const getJSRGraffitiSoulById = async (req, res) => {
+ try {
+ const soulId = req?.params?.id;
+ res.send(
+ await performDBAction(Actions.fetchById, JSR_DB, GraffitiSoul, soulId)
+ );
+ } catch (err) {
+ LOGGER.error(`Could not fetch JSR GraffitiSoul With ID: ${soulId}`, err);
+ res
+ .status(500)
+ .json({message: "Failed to fetch JSR GraffitiSoul By ID", err: err});
+ }
+};
+
+export const getJSRFGraffitiSoulById = async (req, res) => {
+ try {
+ const soulId = req?.params?.id;
+ res.send(
+ await performDBAction(Actions.fetchById, JSRF_DB, GraffitiSoul, soulId)
+ );
+ } catch (err) {
+ LOGGER.error(`Could not fetch JSRF GraffitiSoul With ID: ${soulId}`, err);
+ res
+ .status(500)
+ .json({message: "Failed to fetch JSRF GraffitiSoul By ID", err: err});
+ }
+};
+
+export const fetchJSRSouls = async (req) => {
+ if (req?.query) {
+ return await performDBAction(
+ Actions.fetchWithQuery,
+ JSR_DB,
+ GraffitiSoul,
+ null,
+ req?.query
+ );
+ }
+ return await performDBAction(Actions.fetchAll, JSR_DB, GraffitiSoul, null);
+};
+
+export const fetchJSRFSouls = async (req) => {
+ if (req?.query) {
+ return await performDBAction(
+ Actions.fetchWithQuery,
+ JSRF_DB,
+ GraffitiSoul,
+ null,
+ req?.query
+ );
+ }
+ return await performDBAction(Actions.fetchAll, JSRF_DB, GraffitiSoul, null);
+};
diff --git a/src/controllers/utilController.js b/src/controllers/utilController.js
index efbedf9..ec22384 100644
--- a/src/controllers/utilController.js
+++ b/src/controllers/utilController.js
@@ -3,19 +3,19 @@ import {Actions} from "../config/dbActions.js";
import Constants from "../constants/dbConstants.js";
import LOGGER from "../utils/logger.js";
-const {JSR_DB, JSRF_DB, BRC_DB, gameMap} = Constants;
+const {JSR_DB, JSRF_DB, BRC_DB, gameMap, CORE_DB} = Constants;
/* Helper Functions to support all other Controllers */
export const fetchRandom = async (req, resource, game) => {
try {
- const games =
+ const games = game === 'N/A' ? [CORE_DB] :
resource === "Audio" ? [JSR_DB, JSRF_DB] : [JSR_DB, JSRF_DB, BRC_DB];
const selectedGame = req?.query?.game;
const count = Number(req?.query?.count);
const safeCount = Number.isFinite(count) && count > 0 ? count : 1;
/* if a game is provided */
- if (game || selectedGame) {
+ if ((game && game !== 'N/A') || selectedGame) {
const dbName = game || gameMap[selectedGame];
return await performDBAction(
Actions.fetchRandom,
diff --git a/src/managers/MiddlewareManager.js b/src/managers/MiddlewareManager.js
index 59fcc1f..119f4c1 100644
--- a/src/managers/MiddlewareManager.js
+++ b/src/managers/MiddlewareManager.js
@@ -164,7 +164,7 @@ const cacheMiddleware = (req, res, next) => {
}
res.sendResponse = res.send;
res.send = (body) => {
- cache.put(cacheKey, body, 3600000); // 1 hour cache time, restart the service to bypass
+ cache.put(cacheKey, body, 3600000); // 1 hour cache time, restart the service to bypass or run /cache/clear
res.sendResponse(body);
};
LOGGER.info(`Cache missed for url ${req.url}`);
diff --git a/src/public/docs.html b/src/public/docs.html
index 95530a0..bd8fac4 100644
--- a/src/public/docs.html
+++ b/src/public/docs.html
@@ -41,6 +41,7 @@
The following Resources will be added in a future release of JSRAPI.
If you would like to contribute in adding any of these resources, see the Contributing Docs.
Collectibles
A Collectible is a resource describing a location or place from a specific Game. The only supported game currently is Bomb Rush Cyberfunk.
@@ -260,7 +283,7 @@
Sorting
Limiting
-
All API routes support the limit parameter. You can use this to return a limited number of results instead of the entire response.
+
All API routes(with the exception of /random) support the limit parameter. You can use this to return a limited number of results instead of the entire response.
Example:
https://jetsetradio-api.onrender.com/v1/api/locations/jsrf?limit=5
diff --git a/src/public/js/apiTable.js b/src/public/js/apiTable.js
index 7475479..13d6d77 100644
--- a/src/public/js/apiTable.js
+++ b/src/public/js/apiTable.js
@@ -56,6 +56,12 @@ export function createApiTable() {
endpoint: "/v1/api/graffitiTags?size=L",
description: "Returns all Large Graffiti Tags from any game",
},
+ {
+ endpoint:
+ "/v1/api/graffiti-souls/jsrf?locationId=64c95601b33c6b029d936802",
+ description:
+ "Returns all Graffiti-Souls from The Skyscraper District Location",
+ },
{
endpoint: "/v1/api/collectibles?type=Outfit",
description: "Returns all Outfit collectibles from Bomb Rush Cyberfunk",
diff --git a/src/public/js/docs.js b/src/public/js/docs.js
index 2f130b7..2808381 100644
--- a/src/public/js/docs.js
+++ b/src/public/js/docs.js
@@ -3,6 +3,7 @@ import characterResource from "./examples/characterExample.js";
import locationResource from "./examples/locationExample.js";
import levelResource from "./examples/levelExample.js";
import graffitiTagResource from "./examples/graffitiTagExample.js";
+import graffitiSoulResource from "./examples/graffitiSoulExample.js";
import songResource from "./examples/songExample.js";
import artistResource from "./examples/artistExample.js";
import collectibleResource from "./examples/collectibleExample.js";
@@ -14,6 +15,7 @@ const resources = [
{selector: "#location-response", data: locationResource},
{selector: "#level-response", data: levelResource},
{selector: "#graffiti-tag-response", data: graffitiTagResource},
+ {selector: "#graffiti-soul-response", data: graffitiSoulResource},
{selector: "#song-response", data: songResource},
{selector: "#artist-response", data: artistResource},
{selector: "#collectible-response", data: collectibleResource},
diff --git a/src/public/js/examples/graffitiSoulExample.js b/src/public/js/examples/graffitiSoulExample.js
new file mode 100644
index 0000000..cddff38
--- /dev/null
+++ b/src/public/js/examples/graffitiSoulExample.js
@@ -0,0 +1,13 @@
+const graffitiSoulResource = {
+ gameName: "JSRF",
+ gameId: "64285b7918c8a0231136dc5d",
+ category: "XL",
+ number: "No. 131 - Megaro",
+ size: "XL",
+ graffitiTagId: "64c955f2b33c6b029d93675a",
+ locationName: "Hikage Street",
+ locationId: "64c95601b33c6b029d9367fc",
+ description: "Complete the Street Challenges grind combo challenge.",
+};
+
+export default graffitiSoulResource;
diff --git a/src/routes/artistRouter.js b/src/routes/artistRouter.js
index d16eb2d..b51fb6c 100644
--- a/src/routes/artistRouter.js
+++ b/src/routes/artistRouter.js
@@ -1,11 +1,12 @@
import express from 'express';
-import { getArtists, getArtistById, getSongsByArtist } from '../controllers/artistController.js';
+import { getArtists, getRandomArtist, getArtistById, getSongsByArtist } from '../controllers/artistController.js';
const artists = express.Router();
artists.get('/', async (req, res) => /* #swagger.tags = ['Artists'] */ await getArtists(req, res));
+artists.get('/random', async (req, res) => /* #swagger.tags = ['Artists'] */ await getRandomArtist(req, res));
artists.get('/:id', async (req, res) => /* #swagger.tags = ['Artists'] */ await getArtistById(req, res));
artists.get('/:id/songs', async (req, res) => /* #swagger.tags = ['Artists'] */ await getSongsByArtist(req, res));
diff --git a/src/routes/graffitiSoulRouter.js b/src/routes/graffitiSoulRouter.js
new file mode 100644
index 0000000..5e49aa7
--- /dev/null
+++ b/src/routes/graffitiSoulRouter.js
@@ -0,0 +1,53 @@
+import express from "express";
+import {
+ getAllGraffitiSouls,
+ getRandomGraffitiSoul,
+ getJSRGraffitiSouls,
+ getJSRGraffitiSoulById,
+ getJSRFGraffitiSouls,
+ getJSRFGraffitiSoulById,
+} from "../controllers/graffitiSoulController.js";
+
+const graffitiSouls = express.Router();
+
+graffitiSouls.get(
+ "/",
+ async (req, res) =>
+ /* #swagger.tags = ['GraffitiSouls'] */ await getAllGraffitiSouls(req, res)
+);
+graffitiSouls.get(
+ "/random",
+ async (req, res) =>
+ /* #swagger.tags = ['GraffitiSouls'] */ await getRandomGraffitiSoul(
+ req,
+ res
+ )
+);
+graffitiSouls.get(
+ "/jsr",
+ async (req, res) =>
+ /* #swagger.tags = ['GraffitiSouls'] */ await getJSRGraffitiSouls(req, res)
+);
+graffitiSouls.get(
+ "/jsr/:id",
+ async (req, res) =>
+ /* #swagger.tags = ['GraffitiSouls'] */ await getJSRGraffitiSoulById(
+ req,
+ res
+ )
+);
+graffitiSouls.get(
+ "/jsrf",
+ async (req, res) =>
+ /* #swagger.tags = ['GraffitiSouls'] */ await getJSRFGraffitiSouls(req, res)
+);
+graffitiSouls.get(
+ "/jsrf/:id",
+ async (req, res) =>
+ /* #swagger.tags = ['GraffitiSouls'] */ await getJSRFGraffitiSoulById(
+ req,
+ res
+ )
+);
+
+export default graffitiSouls;
diff --git a/src/routes/router.js b/src/routes/router.js
index 790c2ce..3570db1 100644
--- a/src/routes/router.js
+++ b/src/routes/router.js
@@ -2,6 +2,7 @@ import express from "express";
import games from "./gameRouter.js";
import graffitiTags from "./graffitiTagRouter.js";
+import graffitiSouls from "./graffitiSoulRouter.js";
import songs from "./songRouter.js";
import artists from "./artistRouter.js";
import characters from "./characterRouter.js";
@@ -18,6 +19,7 @@ router.use("/games", games);
router.use("/songs", songs);
router.use("/artists", artists);
router.use("/graffiti-tags", graffitiTags);
+router.use("/graffiti-souls", graffitiSouls);
router.use("/characters", characters);
router.use("/locations", locations);
router.use("/levels", levels);
diff --git a/src/utils/swagger-docs.json b/src/utils/swagger-docs.json
index dd86a79..7d70091 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.5",
+ "version": "1.1.6",
"description": "Providing data for all things Jet Set Radio, Future, and Bomb Rush Cyberfunk!"
},
"host": "localhost:9005",
@@ -28,6 +28,10 @@
"name": "GraffitiTags",
"description": "All Graffiti-Points from JSR and JSRF"
},
+ {
+ "name": "GraffitiSouls",
+ "description": "All Graffiti-Souls from JSR and JSRF"
+ },
{
"name": "Songs",
"description": "Soundtrack Data from JSR/JSRF/BRC"
@@ -276,6 +280,22 @@
}
}
},
+ "/artists/random": {
+ "get": {
+ "tags": [
+ "Artists"
+ ],
+ "description": "",
+ "responses": {
+ "200": {
+ "description": "OK"
+ },
+ "500": {
+ "description": "Internal Server Error"
+ }
+ }
+ }
+ },
"/artists/{id}": {
"get": {
"tags": [
@@ -426,6 +446,118 @@
}
}
},
+ "/graffiti-souls/": {
+ "get": {
+ "tags": [
+ "GraffitiSouls"
+ ],
+ "description": "",
+ "responses": {
+ "200": {
+ "description": "OK"
+ },
+ "500": {
+ "description": "Internal Server Error"
+ }
+ }
+ }
+ },
+ "/graffiti-souls/random": {
+ "get": {
+ "tags": [
+ "GraffitiSouls"
+ ],
+ "description": "",
+ "responses": {
+ "200": {
+ "description": "OK"
+ },
+ "500": {
+ "description": "Internal Server Error"
+ }
+ }
+ }
+ },
+ "/graffiti-souls/jsr": {
+ "get": {
+ "tags": [
+ "GraffitiSouls"
+ ],
+ "description": "",
+ "responses": {
+ "200": {
+ "description": "OK"
+ },
+ "500": {
+ "description": "Internal Server Error"
+ }
+ }
+ }
+ },
+ "/graffiti-souls/jsr/{id}": {
+ "get": {
+ "tags": [
+ "GraffitiSouls"
+ ],
+ "description": "",
+ "parameters": [
+ {
+ "name": "id",
+ "in": "path",
+ "required": true,
+ "type": "string"
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "OK"
+ },
+ "500": {
+ "description": "Internal Server Error"
+ }
+ }
+ }
+ },
+ "/graffiti-souls/jsrf": {
+ "get": {
+ "tags": [
+ "GraffitiSouls"
+ ],
+ "description": "",
+ "responses": {
+ "200": {
+ "description": "OK"
+ },
+ "500": {
+ "description": "Internal Server Error"
+ }
+ }
+ }
+ },
+ "/graffiti-souls/jsrf/{id}": {
+ "get": {
+ "tags": [
+ "GraffitiSouls"
+ ],
+ "description": "",
+ "parameters": [
+ {
+ "name": "id",
+ "in": "path",
+ "required": true,
+ "type": "string"
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "OK"
+ },
+ "500": {
+ "description": "Internal Server Error"
+ }
+ }
+ }
+ },
"/characters/": {
"get": {
"tags": [
diff --git a/src/utils/swagger.js b/src/utils/swagger.js
index 51aad18..016ac23 100644
--- a/src/utils/swagger.js
+++ b/src/utils/swagger.js
@@ -33,6 +33,10 @@ const doc = {
name: "GraffitiTags",
description: "All Graffiti-Points from JSR and JSRF",
},
+ {
+ name: "GraffitiSouls",
+ description: "All Graffiti-Souls from JSR and JSRF",
+ },
{
name: "Songs",
description: "Soundtrack Data from JSR/JSRF/BRC",
diff --git a/test/data/graffitiSouls.js b/test/data/graffitiSouls.js
new file mode 100644
index 0000000..7a91b0c
--- /dev/null
+++ b/test/data/graffitiSouls.js
@@ -0,0 +1,28 @@
+export const createJsrSoul = (testId) => {
+ return {
+ _id: testId,
+ gameName: "JSR",
+ gameId: "64285b7918c8a0231136dc5a",
+ number: "No. 2 - Pop!",
+ size: "Small",
+ graffitiTagId: "",
+ locationName: "Shibuya-cho",
+ locationId: "",
+ description: "Automatically unlocks after completing Gum's tutorial.",
+ };
+};
+
+export const createJsrfSoul = (testId) => {
+ return {
+ _id: testId,
+ gameName: "JSRF",
+ gameId: "64285b7918c8a0231136dc5d",
+ number: "No. 131 - Megaro",
+ size: "XL",
+ graffitiTagId: "64c955f2b33c6b029d93675a",
+ locationName: "Hikage Street",
+ locationId: "64c95601b33c6b029d9367fc",
+ description:
+ "Complete the Street Challenges grind combo challenge. The soul is located in the eastern overpass, just jump between the first gap between the two walkways to reach this soul.",
+ };
+};
diff --git a/test/graffitiSouls.test.js b/test/graffitiSouls.test.js
new file mode 100644
index 0000000..769a8a4
--- /dev/null
+++ b/test/graffitiSouls.test.js
@@ -0,0 +1,103 @@
+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 {
+ fetchJSRSouls,
+ fetchJSRFSouls,
+} from "../src/controllers/graffitiSoulController.js";
+import {createJsrSoul, createJsrfSoul} from "./data/graffitiSouls.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("GraffitiSoul Routes", () => {
+ beforeAll(connect);
+ afterAll(disconnect);
+ afterEach(() => jest.clearAllMocks());
+
+ /* API/Integration Tests */
+ test("GET /graffiti-souls", async () => {
+ const jsrSouls = await fetchJSRFSouls();
+ const jsrfSouls = await fetchJSRFSouls();
+ const souls = [...jsrSouls, ...jsrfSouls];
+ expect(Array.isArray(jsrSouls)).toBe(true);
+ expect(Array.isArray(jsrfSouls)).toBe(true);
+ expect(Array.isArray(souls)).toBe(true);
+ expect(isValidJson(souls)).toBe(true);
+
+ expect(jsrSouls[0]).toHaveProperty("number");
+ expect(jsrSouls[0]).toHaveProperty("locationId");
+ expect(jsrSouls[0]).toHaveProperty("gameId");
+ expect(jsrfSouls[9]).toHaveProperty("gameName");
+ expect(jsrfSouls[9]).toHaveProperty("locationName");
+ expect(jsrfSouls[9]).toHaveProperty("size");
+ expect(jsrfSouls[9]).toHaveProperty("graffitiTagId");
+ });
+
+ test("GET /graffiti-souls?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 jsrSouls = await fetchJSRSouls();
+ const jsrfSouls = await fetchJSRFSouls();
+
+ let souls = [];
+ if (sortByValue) {
+ const allSouls = [...jsrSouls, ...jsrfSouls];
+ souls = allSouls.sort(sortObjects(sortByValue, sortOrder));
+ } else {
+ souls = [...jsrSouls, ...jsrfSouls];
+ }
+ expect(isValidJson(souls)).toBe(true);
+ });
+
+ test("GET /graffiti-souls/jsr?limit=7", async () => {
+ const req = {query: {limit: "7"}};
+ const souls = await fetchJSRSouls(req);
+ expect(Array.isArray(souls)).toBe(true);
+ expect(isValidJson(souls)).toBe(true);
+ expect(souls).toHaveLength(7);
+ });
+
+ test("GET /graffiti-souls/jsrf?limit=15", async () => {
+ const req = {query: {limit: "15"}};
+ const souls = await fetchJSRFSouls(req);
+ expect(Array.isArray(souls)).toBe(true);
+ expect(isValidJson(souls)).toBe(true);
+ expect(souls).toHaveLength(15);
+ });
+
+ /* Unit/Mock Tests */
+ test("GET /graffiti-souls/jsr/:id", async () => {
+ const testId = "642f774b24b4bca91d5a6c99";
+ createMock(createJsrSoul(testId));
+
+ const result = await axios.get(`${baseUrl}/graffiti-soul/jsr/${testId}`);
+ expect(isValidJson(result.data)).toBe(true);
+ expect(result.data.number).toBe("No. 2 - Pop!");
+ expect(result.data.locationName).toBe("Shibuya-cho");
+ expect(result.data.description).toBe(
+ "Automatically unlocks after completing Gum's tutorial."
+ );
+ expect(axios.get).toHaveBeenCalledTimes(1);
+ });
+
+ test("GET /graffiti-souls/jsrf/:id", async () => {
+ const testId = "642f776624b4bca91d5a6f4b";
+ createMock(createJsrfSoul(testId));
+
+ const result = await axios.get(`${baseUrl}/graffiti-souls/jsrf/${testId}`);
+ expect(isValidJson(result.data)).toBe(true);
+ expect(result.data.number).toBe("No. 131 - Megaro");
+ expect(result.data.size).toBe("XL");
+ expect(result.data.locationName).toBe("Hikage Street");
+ expect(result.data.gameId).toBe("64285b7918c8a0231136dc5d");
+ expect(axios.get).toHaveBeenCalledTimes(1);
+ });
+});