diff --git a/server/src/score-import/framework/user-game-stats/update-ugs.ts b/server/src/score-import/framework/user-game-stats/update-ugs.ts index 15e0cf01b..4da325ff4 100644 --- a/server/src/score-import/framework/user-game-stats/update-ugs.ts +++ b/server/src/score-import/framework/user-game-stats/update-ugs.ts @@ -73,7 +73,7 @@ export async function UpdateUsersGamePlaytypeStats( classes, }; - logger.info(`Created new player gamestats for ${game} (${playtype})`); + logger.info(`Created new gamestats for ${game} (${playtype})`); await db["game-stats"].insert(newStats); } diff --git a/server/src/score-import/import-types/file/solid-state-squad/converter.test.ts b/server/src/score-import/import-types/file/solid-state-squad/converter.test.ts new file mode 100644 index 000000000..4e098447a --- /dev/null +++ b/server/src/score-import/import-types/file/solid-state-squad/converter.test.ts @@ -0,0 +1,225 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +import t from "tap"; +import db, { CloseMongoConnection } from "../../../../db/db"; +import CreateLogCtx from "../../../../logger"; +import ResetDBState from "../../../../test-utils/reset-db-state"; +import { GetKTDataJSON, Testing511Song, Testing511SPA } from "../../../../test-utils/test-data"; +import { ConvertFileS3, ParseDifficulty, ResolveS3Lamp } from "./converter"; +import { S3Score } from "./types"; +import deepmerge from "deepmerge"; + +const logger = CreateLogCtx("converter.test.ts"); + +function cfile(data: S3Score) { + return ConvertFileS3(data, {}, "file/solid-state-squad", logger); +} + +t.test("#ConvertFileS3", (t) => { + t.beforeEach(ResetDBState); + t.beforeEach(() => { + delete BaseS3Score._id; // just incase + }); + + function mfile(merge: Partial) { + return cfile(deepmerge(BaseS3Score, merge)); + } + + const dryScore = { + game: "iidx", + comment: null, + importType: "file/solid-state-squad", + service: "Solid State Squad", + scoreData: { + // percent: 6.36, -- fpa + grade: "F", + score: 100, + lamp: "FULL COMBO", + hitData: { + pgreat: 25, + great: 50, + good: 0, + bad: 0, + poor: 4, + }, + hitMeta: {}, + }, + scoreMeta: {}, + timeAchieved: 1287460462000, + }; + + const BaseS3Score = GetKTDataJSON("./s3/s3score.json"); + + t.test("Should import a valid S3 score", async (t) => { + const res = await cfile(BaseS3Score); + + t.hasStrict( + res, + { + chart: Testing511SPA, + song: Testing511Song, + dryScore, + } as any, + "Should correctly return the song, chart and DryScore." + ); + + t.end(); + }); + + t.test("Should support comments in S3 scores", async (t) => { + const res = await mfile({ comment: "FOO BAR" }); + + t.hasStrict( + res, + { + chart: Testing511SPA, + song: Testing511Song, + dryScore: deepmerge(dryScore, { comment: "FOO BAR" }), + } as any, + "Should correctly return the song, chart and DryScore." + ); + + t.end(); + }); + + t.test("Should find song case-insensitively", async (t) => { + await db.charts.iidx.remove({}); + await db.charts.iidx.insert(GetKTDataJSON("./kamaitachi/ktblack-charts-iidx.json")); + + await db.songs.iidx.remove({}); + await db.songs.iidx.insert(GetKTDataJSON("./kamaitachi/ktblack-songs-iidx.json")); + + const res = await mfile({ songname: "gamBOL", diff: 7 }); + + t.hasStrict( + res, + { + chart: { songID: 7, difficulty: "HYPER", playtype: "SP" }, + song: { title: "GAMBOL" }, + // dryScore, dont care + } as any, + "Should correctly return the song, chart and DryScore." + ); + + t.end(); + }); + + t.test("Should reject invalid styles in S3 scores", (t) => { + t.rejects(mfile({ styles: "3rd,4th,INVALID" }), { + message: /Song has invalid style INVALID/u, + } as any); + + t.end(); + }); + + t.test("Should throw ktdatanf if no song", (t) => { + t.rejects(mfile({ songname: "INVALID SONG TITLE" }), { + message: /Could not find song with title INVALID SONG TITLE/u, + } as any); + + t.end(); + }); + + t.test("Should throw ktdatanf if no song", (t) => { + t.rejects(mfile({ diff: "B" }), { + message: /Could not find chart 5\.1\.1\. \(SP LEGGENDARIA/u, + } as any); + + t.end(); + }); + + t.test("Should throw a skipscore if the song is 5key", (t) => { + t.rejects(mfile({ diff: 5 }), { + message: /5KEY scores are not supported/u, + } as any); + + t.end(); + }); + + t.test("Should throw an invalidscore if the difficulty is invalid", (t) => { + t.rejects(mfile({ diff: "INVALID" as any }), { + message: /Invalid difficulty INVALID/u, + } as any); + + t.end(); + }); + + t.test("Should throw an invalidscore if the hardeasy is invalid", (t) => { + t.rejects(mfile({ mods: { hardeasy: "INVALID" } as any, cleartype: "cleared" }), { + message: /Invalid cleartype of 'cleared' with hardeasy of INVALID/u, + } as any); + + t.end(); + }); + + t.test("Should throw an invalidscore if the cleartype is invalid", (t) => { + t.rejects(mfile({ cleartype: "INVALID" as any }), { + message: /Invalid cleartype of INVALID/u, + } as any); + + t.end(); + }); + + t.test("Should throw an invalidscore if the exscore is greater than MAX", (t) => { + t.rejects(mfile({ exscore: 10000 }), { + message: /Percent was greater than 100%/u, + } as any); + + t.end(); + }); + + t.test("Should throw an invalidscore if the date is invalid.", (t) => { + t.rejects(mfile({ date: "INVALID" }), { + message: /Invalid timestamp of INVALID - could not parse/u, + } as any); + + t.end(); + }); + + t.end(); +}); + +t.test("#ParseDifficulty", (t) => { + t.beforeEach(ResetDBState); + + t.strictSame(ParseDifficulty("L7"), { playtype: "SP", difficulty: "NORMAL" }); + t.strictSame(ParseDifficulty(7), { playtype: "SP", difficulty: "HYPER" }); + t.strictSame(ParseDifficulty("A"), { playtype: "SP", difficulty: "ANOTHER" }); + t.strictSame(ParseDifficulty("B"), { playtype: "SP", difficulty: "LEGGENDARIA" }); + t.strictSame(ParseDifficulty("L14"), { playtype: "DP", difficulty: "NORMAL" }); + t.strictSame(ParseDifficulty(14), { playtype: "DP", difficulty: "HYPER" }); + t.strictSame(ParseDifficulty("A14"), { playtype: "DP", difficulty: "ANOTHER" }); + t.strictSame(ParseDifficulty("B14"), { playtype: "DP", difficulty: "LEGGENDARIA" }); + t.throws(() => ParseDifficulty(5)); + + t.end(); +}); + +t.test("#ResolveS3Lamp", (t) => { + t.beforeEach(ResetDBState); + + const BaseS3Score = GetKTDataJSON("./s3/s3score.json"); + + t.equal(ResolveS3Lamp({ cleartype: "played" } as S3Score, logger), "FAILED"); + t.equal(ResolveS3Lamp({ cleartype: "cleared", mods: {} } as S3Score, logger), "CLEAR"); + t.equal( + ResolveS3Lamp({ cleartype: "cleared", mods: { hardeasy: "E" } } as S3Score, logger), + "EASY CLEAR" + ); + t.equal( + ResolveS3Lamp({ cleartype: "cleared", mods: { hardeasy: "H" } } as S3Score, logger), + "HARD CLEAR" + ); + t.equal(ResolveS3Lamp({ cleartype: "combo" } as S3Score, logger), "FULL COMBO"); + t.equal(ResolveS3Lamp({ cleartype: "comboed" } as S3Score, logger), "FULL COMBO"); + t.equal(ResolveS3Lamp({ cleartype: "perfect" } as S3Score, logger), "FULL COMBO"); + t.equal(ResolveS3Lamp({ cleartype: "perfected" } as S3Score, logger), "FULL COMBO"); + + t.throws(() => ResolveS3Lamp({ cleartype: "invalid" } as any, logger)); + t.throws(() => + ResolveS3Lamp({ cleartype: "cleared", mods: { hardeasy: "invalid" } } as any, logger) + ); + + t.end(); +}); + +t.teardown(CloseMongoConnection); diff --git a/server/src/score-import/import-types/file/solid-state-squad/converter.ts b/server/src/score-import/import-types/file/solid-state-squad/converter.ts index d4cfb154f..f6f87efd9 100644 --- a/server/src/score-import/import-types/file/solid-state-squad/converter.ts +++ b/server/src/score-import/import-types/file/solid-state-squad/converter.ts @@ -13,15 +13,15 @@ import { GetGradeFromPercent, } from "../../../framework/common/score-utils"; -function ParseDifficulty( - diff: string | number +export function ParseDifficulty( + diff: S3Score["diff"] ): { playtype: Playtypes["iidx"]; difficulty: Difficulties["iidx:SP" | "iidx:DP"] } { switch (diff) { case "L7": return { playtype: "SP", difficulty: "NORMAL" }; case 7: return { playtype: "SP", difficulty: "HYPER" }; - case "A7": + case "A": return { playtype: "SP", difficulty: "ANOTHER" }; case "B": return { playtype: "SP", difficulty: "LEGGENDARIA" }; @@ -40,7 +40,7 @@ function ParseDifficulty( } } -function ResolveS3Lamp(data: S3Score, logger: KtLogger): Lamps["iidx:SP" | "iidx:DP"] { +export function ResolveS3Lamp(data: S3Score, logger: KtLogger): Lamps["iidx:SP" | "iidx:DP"] { if (data.cleartype === "played") { return "FAILED"; } else if (data.cleartype === "cleared") { @@ -94,7 +94,7 @@ function ConvertVersion(joinedStyles: string) { const convertedStyle = S3_VERSION_CONV[style]; if (!convertedStyle) { - throw new InvalidScoreFailure(`Song has invalid style ${style}`); + throw new InvalidScoreFailure(`Song has invalid style ${style}.`); } return convertedStyle; diff --git a/server/src/score-import/import-types/file/solid-state-squad/parser.test.ts b/server/src/score-import/import-types/file/solid-state-squad/parser.test.ts index 786e8c25a..4c160494a 100644 --- a/server/src/score-import/import-types/file/solid-state-squad/parser.test.ts +++ b/server/src/score-import/import-types/file/solid-state-squad/parser.test.ts @@ -193,7 +193,7 @@ t.test("#ParseSolidStateXML", (t) => { () => ParseSolidStateXML( MockMulterFile( - GetKTDataBuffer("./s3/billion-laughs.xml"), + GetKTDataBuffer("./s3/danger/billion-laughs.xml"), "billion-laughs.xml" ), {}, @@ -213,7 +213,10 @@ t.test("#ParseSolidStateXML", (t) => { const time = process.hrtime.bigint(); const res = ParseSolidStateXML( - MockMulterFile(GetKTDataBuffer("./s3/specific-blaugh.xml"), "specific-blaugh.xml"), + MockMulterFile( + GetKTDataBuffer("./s3/danger/specific-blaugh.xml"), + "specific-blaugh.xml" + ), {}, logger ); diff --git a/server/src/score-import/import-types/file/solid-state-squad/parser.ts b/server/src/score-import/import-types/file/solid-state-squad/parser.ts index 882dc620c..d60316887 100644 --- a/server/src/score-import/import-types/file/solid-state-squad/parser.ts +++ b/server/src/score-import/import-types/file/solid-state-squad/parser.ts @@ -17,7 +17,7 @@ const PR_SolidState: PrudenceSchema = { song: [ { id: p.isPositiveInteger, - diff: p.isIn("L7", 7, "A", "B", "5", "L14", 14, "A14", "B14"), + diff: p.isIn("L7", 7, "A", "B", 5, "L14", 14, "A14", "B14"), songname: "string", styles: "string", diff --git a/server/src/score-import/import-types/file/solid-state-squad/types.ts b/server/src/score-import/import-types/file/solid-state-squad/types.ts index f7485cb52..810af86ef 100644 --- a/server/src/score-import/import-types/file/solid-state-squad/types.ts +++ b/server/src/score-import/import-types/file/solid-state-squad/types.ts @@ -2,7 +2,7 @@ import { integer } from "kamaitachi-common"; export interface S3Score { id: integer; - diff: "5" | "L7" | 7 | "A" | "B" | "L14" | 14 | "A14" | "B14"; + diff: 5 | "L7" | 7 | "A" | "B" | "L14" | 14 | "A14" | "B14"; songname: string; exscore: integer; styles: string; diff --git a/server/src/test-utils/test-data/s3/s3score.json b/server/src/test-utils/test-data/s3/s3score.json new file mode 100644 index 000000000..c6df00d90 --- /dev/null +++ b/server/src/test-utils/test-data/s3/s3score.json @@ -0,0 +1,17 @@ +{ + "id": 187, + "diff": "A", + "songname": "5.1.1.", + "styles": "3rd", + "exscore": 100, + "scorebreakdown": { + "justgreats": 25, + "greats": 50, + "good": 0, + "bad": 0, + "poor": 4 + }, + "mods": {}, + "cleartype": "perfect", + "date": "2010-10-19 04:54:22" +} \ No newline at end of file