diff --git a/server/src/api/ir/barbatos/barbatos.test.ts b/server/src/api/ir/barbatos/barbatos.test.ts index 069e5e76c..009598b90 100644 --- a/server/src/api/ir/barbatos/barbatos.test.ts +++ b/server/src/api/ir/barbatos/barbatos.test.ts @@ -35,9 +35,13 @@ t.test("POST /api/ir/barbatos/score/submit", async (t) => { }); t.test("Should reject an invalid body", async (t) => { - let res = await mockApi.post("/api/ir/barbatos/import").set("Cookie", cookie).send({}); + let res = await mockApi + .post("/api/ir/barbatos/score/submit") + .set("Cookie", cookie) + .send({}); - t.equal(res.body.success, false, "Should not be successful"); + t.equal(res.body.success, false, "Should not be successful."); + t.equal(res.status, 400, "Should return 400."); t.end(); }); diff --git a/server/src/api/ir/fervidex/fervidex.test.ts b/server/src/api/ir/fervidex/fervidex.test.ts new file mode 100644 index 000000000..bd3aab98f --- /dev/null +++ b/server/src/api/ir/fervidex/fervidex.test.ts @@ -0,0 +1,127 @@ +import t from "tap"; +import db from "../../../db/db"; +import { CloseAllConnections } from "../../../test-utils/close-connections"; +import { CreateFakeAuthCookie } from "../../../test-utils/fake-session"; +import mockApi from "../../../test-utils/mock-api"; +import ResetDBState from "../../../test-utils/reset-db-state"; +import { GetKTDataJSON } from "../../../test-utils/test-data"; + +async function TestSoftwareModels(cookie: string[]) { + t.test("Should reject invalid X-Software-Models", async (t) => { + let res = await mockApi + .post("/api/ir/fervidex/score/submit") + .set("Cookie", cookie) + // rootage + .set("X-Software-Model", "LDJ:J:B:A:2019090200") + .send(GetKTDataJSON("./fervidex/base.json")); + + t.equal(res.body.success, false, "Should reject rootage clients"); + + res = await mockApi + .post("/api/ir/fervidex/score/submit") + .set("Cookie", cookie) + // rootage old + .set("X-Software-Model", "LDJ:J:B:A:2019100700") + .send(GetKTDataJSON("./fervidex/base.json")); + + t.equal(res.body.success, false, "Should reject rootage clients"); + + res = await mockApi + .post("/api/ir/fervidex/score/submit") + .set("Cookie", cookie) + // cannonballers + .set("X-Software-Model", "LDJ:J:B:A:2018091900") + .send(GetKTDataJSON("./fervidex/base.json")); + + t.equal(res.body.success, false, "Should reject cannonballers clients"); + + res = await mockApi + .post("/api/ir/fervidex/score/submit") + .set("Cookie", cookie) + .set("X-Software-Model", "LDJ:J:B:A:NONSENSE") + .send(GetKTDataJSON("./fervidex/base.json")); + + t.equal(res.body.success, false, "Should reject nonsense versions"); + + res = await mockApi + .post("/api/ir/fervidex/score/submit") + .set("Cookie", cookie) + // 2DXtra + .set("X-Software-Model", "LDJ:J:B:X:2020092900") + .send(GetKTDataJSON("./fervidex/base.json")); + + t.equal(res.body.success, false, "Should reject 2DXTra clients"); + + res = await mockApi + .post("/api/ir/fervidex/score/submit") + .set("Cookie", cookie) + // BMS-iidx + .set("X-Software-Model", "LDJ:J:B:Z:2020092900") + .send(GetKTDataJSON("./fervidex/base.json")); + + t.equal(res.body.success, false, "Should reject BMS-iidx clients"); + + t.end(); + }); +} + +t.test("POST /api/ir/fervidex/class/submit", async (t) => { + const cookie = await CreateFakeAuthCookie(mockApi); + + await TestSoftwareModels(cookie); + + t.beforeEach(ResetDBState); + + t.end(); +}); + +t.test("POST /api/ir/fervidex/score/submit", async (t) => { + const cookie = await CreateFakeAuthCookie(mockApi); + + t.beforeEach(ResetDBState); + await TestSoftwareModels(cookie); + + t.test("Should import a valid score", async (t) => { + let res = await mockApi + .post("/api/ir/fervidex/score/submit") + .set("Cookie", cookie) + .set("X-Software-Model", "LDJ:J:B:A:2020092900") + .send(GetKTDataJSON("./fervidex/base.json")); + + t.equal(res.body.success, true, "Should be successful"); + + t.equal(res.body.body.errors.length, 0, "Should have 0 failed scores."); + + let scores = await db.scores.count({ + service: "Fervidex", + }); + + t.equal(scores, 1, "Should import 1 score."); + + t.end(); + }); + + t.test("Should reject an invalid body", async (t) => { + let res = await mockApi + .post("/api/ir/fervidex/score/submit") + .set("Cookie", cookie) + .send({}); + + t.equal(res.body.success, false, "Should not be successful"); + + t.end(); + }); + + t.end(); +}); + +t.test("POST /api/ir/fervidex/profile/submit", async (t) => { + const cookie = await CreateFakeAuthCookie(mockApi); + + t.beforeEach(ResetDBState); + await TestSoftwareModels(cookie); + + t.end(); +}); + +t.teardown(CloseAllConnections); diff --git a/server/src/api/ir/fervidex/fervidex.ts b/server/src/api/ir/fervidex/fervidex.ts index a8daa968a..b0060e866 100644 --- a/server/src/api/ir/fervidex/fervidex.ts +++ b/server/src/api/ir/fervidex/fervidex.ts @@ -11,7 +11,20 @@ const RequireInf2ModelHeader: RequestHandler = async (req, res, next) => { let swModel = req.header("X-Software-Model"); if (!swModel || !swModel.startsWith("P2D:J:B:A")) { - return res.status(400).send(); + return res.status(400).send({ + success: false, + description: "This endpoint is only available for INF2 clients.", + }); + } + + return next(); +}; + +const ValidateModelHeader: RequestHandler = async (req, res, next) => { + let swModel = req.header("X-Software-Model"); + + if (!swModel || swModel.startsWith("LDJ:J:B:X") || swModel.startsWith("LDJ:J:B:Z")) { + return res.status(400).send({ success: false, description: "Invalid X-Software-Model." }); } return next(); @@ -50,7 +63,7 @@ router.post("/profile/submit", RequireLoggedIn, RequireInf2ModelHeader, async (r * * @name /api/ir/fervidex/score/submit */ -router.post("/score/submit", RequireLoggedIn, async (req, res) => { +router.post("/score/submit", RequireLoggedIn, ValidateModelHeader, async (req, res) => { const userDoc = await GetUserWithIDGuaranteed(req.session.ktchi!.userID); let model = req.header("X-Software-Model"); @@ -79,7 +92,7 @@ router.post("/score/submit", RequireLoggedIn, async (req, res) => { * * @name /api/ir/fervidex/class/submit */ -router.post("/class/submit", RequireLoggedIn, async (req, res) => { +router.post("/class/submit", RequireLoggedIn, ValidateModelHeader, async (req, res) => { throw new Error("Unimplemented."); }); diff --git a/server/src/score-import/import-types/ir/fervidex-static/converter.ts b/server/src/score-import/import-types/ir/fervidex-static/converter.ts index 9727d89a5..99b49288f 100644 --- a/server/src/score-import/import-types/ir/fervidex-static/converter.ts +++ b/server/src/score-import/import-types/ir/fervidex-static/converter.ts @@ -44,7 +44,7 @@ export const ConverterIRFervidexStatic: ConverterFunction< throw new InternalFailure(`Song ${chart.songID} (iidx) has no parent song?`); } - const percent = GenericCalculatePercent("iidx", data.ex_score); + const percent = GenericCalculatePercent("iidx", data.ex_score, chart); if (percent > 100) { throw new InvalidScoreFailure( diff --git a/server/src/score-import/import-types/ir/fervidex/converter.ts b/server/src/score-import/import-types/ir/fervidex/converter.ts index f49c8a944..a45236c5c 100644 --- a/server/src/score-import/import-types/ir/fervidex/converter.ts +++ b/server/src/score-import/import-types/ir/fervidex/converter.ts @@ -171,7 +171,7 @@ export const ConverterIRFervidex: ConverterFunction 100) { throw new InvalidScoreFailure( @@ -181,7 +181,7 @@ export const ConverterIRFervidex: ConverterFunction = { game: "iidx", - service: "fervidex", + service: "Fervidex", comment: null, importType: "ir/fervidex", timeAchieved: Date.now(), diff --git a/server/src/test-utils/test-data/fervidex/base.json b/server/src/test-utils/test-data/fervidex/base.json index 7545f03fe..eb91dc855 100644 --- a/server/src/test-utils/test-data/fervidex/base.json +++ b/server/src/test-utils/test-data/fervidex/base.json @@ -1,6 +1,6 @@ { "bad": 0, - "chart": "spn", + "chart": "spa", "clear_type": 1, "combo_break": 6, "dead": {