Parser tests

This commit is contained in:
zkldi
2021-05-12 01:06:21 +01:00
parent e7c4a0ad41
commit b62e7ccbeb
6 changed files with 267 additions and 17 deletions
-1
View File
@@ -48,7 +48,6 @@
"csv-parse": "^4.15.4",
"deepmerge": "^4.2.2",
"dotenv": "^8.2.0",
"escape-string-regexp": "^1.0.5",
"express": "^4.17.1",
"express-async-errors": "^3.1.1",
"express-session": "^1.17.1",
+1 -2
View File
@@ -21,7 +21,6 @@ specifiers:
csv-parse: ^4.15.4
deepmerge: ^4.2.2
dotenv: ^8.2.0
escape-string-regexp: ^1.0.5
eslint: ^7.25.0
eslint-config-prettier: ^8.3.0
eslint-plugin-import: ^2.22.1
@@ -57,7 +56,6 @@ dependencies:
csv-parse: 4.15.4
deepmerge: 4.2.2
dotenv: 8.2.0
escape-string-regexp: 1.0.5
express: 4.17.1
express-async-errors: 3.1.1_express@4.17.1
express-session: 1.17.1
@@ -1588,6 +1586,7 @@ packages:
/escape-string-regexp/1.0.5:
resolution: {integrity: sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=}
engines: {node: '>=0.8.0'}
dev: true
/escape-string-regexp/2.0.0:
resolution: {integrity: sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==}
@@ -90,15 +90,15 @@ function ConvertFn(c: any): ScoreDocument | null {
}
if (base.game === "iidx") {
// @ts-expect-error fuck you
// @ts-expect-error nope
if (base.scoreData.hitMeta.gauge > 200) {
// @ts-expect-error fuck you
// @ts-expect-error nope
base.scoreData.hitMeta.gauge = null;
}
// @ts-expect-error fuck you
// @ts-expect-error nope
if (base.scoreData.hitMeta.gaugeHistory) {
// @ts-expect-error fuck you
// @ts-expect-error nope
base.scoreData.hitMeta.gaugeHistory = base.scoreData.hitMeta.gaugeHistory.map((e) =>
e > 200 ? null : e
);
+14
View File
@@ -0,0 +1,14 @@
// https://github.com/sindresorhus/escape-string-regexp/blob/main/index.js
// the developer of this has migrated everything to Force ES6 style modules,
// which really really messes with a lot of the ecosystem.
// shim.
export default function escapeStringRegexp(string: string) {
if (typeof string !== "string") {
throw new TypeError("Expected a string");
}
// Escape characters with special meaning either inside or outside character sets.
// Use a simple backslash escape when it's always valid, and a `\xnn` escape when the simpler form would be disallowed by Unicode patterns' stricter grammar.
return string.replace(/[|\\{}()[\]^$+*?.]/gu, "\\$&").replace(/-/gu, "\\x2d");
}
@@ -3,6 +3,9 @@ import { CloseMongoConnection } from "../../../../db/db";
import CreateLogCtx from "../../../../logger";
import ScoreImportFatalError from "../../../framework/score-importing/score-import-error";
import ParserFn from "./parser";
import { BatchManual, BatchManualScore } from "./types";
import escapeRegex from "../../../../core/escape-string-regexp";
import deepmerge from "deepmerge";
const fileify = (obj: any) =>
({
@@ -10,8 +13,37 @@ const fileify = (obj: any) =>
filename: "fileify.json",
} as Express.Multer.File);
const mockErr = (...msg: string[]) =>
(({
statusCode: 400,
message: new RegExp(msg.map((e) => `${escapeRegex(e)}.*`).join(""), "u"),
name: "Error",
} as unknown) as ScoreImportFatalError);
const logger = CreateLogCtx("parser.test.ts");
const baseBatchManual = {
body: [],
head: { service: "foo", game: "iidx" },
};
const baseBatchManualScore = {
score: 1000,
lamp: "HARD CLEAR",
matchType: "songID",
identifier: "123",
playtype: "SP",
difficulty: "ANOTHER",
};
function dm(sc: any) {
return deepmerge(
baseBatchManual,
{ body: [deepmerge(baseBatchManualScore, sc)] },
{ arrayMerge: (r, c) => c }
);
}
t.test("#ParserFn", (t) => {
t.test("Non-Object", (t) => {
t.throws(
@@ -84,7 +116,7 @@ t.test("#ParserFn", (t) => {
() => ParserFn(fileify({ body: [], head: { service: "1", game: "iidx" } }), {}, logger),
new ScoreImportFatalError(
400,
"Invalid BATCH-MANUAL (head.service | Expected a string with length between 3 and 15. | Received 1)"
"Invalid BATCH-MANUAL: head.service | Expected a string with length between 3 and 15. | Received 1 [string]."
),
"Should throw an error."
);
@@ -93,7 +125,7 @@ t.test("#ParserFn", (t) => {
() => ParserFn(fileify({ body: [], head: { service: 1, game: "iidx" } }), {}, logger),
new ScoreImportFatalError(
400,
"Invalid BATCH-MANUAL (head.service | Expected a string with length between 3 and 15. | Received 1)"
"Invalid BATCH-MANUAL: head.service | Expected a string with length between 3 and 15. | Received 1 [number]."
),
"Should throw an error."
);
@@ -122,6 +154,199 @@ t.test("#ParserFn", (t) => {
t.end();
});
t.test("Valid BATCH-MANUAL", (t) => {
let res = ParserFn(
fileify({
body: [
{
score: 1000,
lamp: "HARD CLEAR",
matchType: "songID",
identifier: "123",
playtype: "SP",
difficulty: "ANOTHER",
},
{
score: 1000,
lamp: "HARD CLEAR",
matchType: "kamaitachiSongID",
identifier: "123",
playtype: "DP",
difficulty: "HYPER",
},
{
score: 1000,
lamp: "HARD CLEAR",
matchType: "songTitle",
identifier: "5.1.1.",
},
{
score: 1000,
lamp: "HARD CLEAR",
matchType: "title",
identifier: "5.1.1.",
},
],
head: { service: "foo", game: "iidx" },
} as BatchManual),
{},
logger
);
t.hasStrict(res, {
game: "iidx",
context: {
service: "foo",
game: "iidx",
version: null,
},
iterable: [],
// eslint-disable-next-line @typescript-eslint/no-explicit-any
} as any);
t.end();
});
t.test("Invalid BATCH-MANUAL", (t) => {
t.test("Invalid Lamp For Game", (t) => {
let fn = () =>
ParserFn(
fileify({
body: [
{
score: 1000,
lamp: "ALL JUSTICE", // not an iidx lamp
matchType: "songID",
identifier: "123",
playtype: "SP",
difficulty: "ANOTHER",
},
],
head: { service: "foo", game: "iidx" },
}),
{},
logger
);
t.throws(
fn,
new ScoreImportFatalError(
400,
"Invalid BATCH-MANUAL: body[0].lamp | Expected any of NO PLAY, FAILED, ASSIST CLEAR, EASY CLEAR, CLEAR, HARD CLEAR, EX HARD CLEAR, FULL COMBO. | Received ALL JUSTICE [string]."
)
);
t.end();
});
t.test("Non-numeric score", (t) => {
let fn = () => ParserFn(fileify(dm({ score: "123" })), {}, logger);
t.throws(
fn,
new ScoreImportFatalError(
400,
"Invalid BATCH-MANUAL: body[0].score | Expected number. | Received 123 [string]."
)
);
t.end();
});
t.test("Invalid timeAchieved", (t) => {
let fn = () => ParserFn(fileify(dm({ timeAchieved: "string" })), {}, logger);
t.throws(
fn,
new ScoreImportFatalError(
400,
"Invalid BATCH-MANUAL: body[0].timeAchieved | Expected a number greater than 1 Trillion - did you pass unix seconds instead of miliseconds? | Received string [string]."
)
);
let fn2 = () =>
ParserFn(fileify(dm({ timeAchieved: 1_620_768_609_637 / 1000 })), {}, logger);
t.throws(
fn2,
new ScoreImportFatalError(
400,
"Invalid BATCH-MANUAL: body[0].timeAchieved | Expected a number greater than 1 Trillion - did you pass unix seconds instead of miliseconds? | Received 1620768609.637 [number]."
),
"Should throw if timeAchieved is less than 10_000_000_000."
);
t.end();
});
t.test("Invalid Playtype", (t) => {
// this is not a valid playtype for IIDX
let fn = () => ParserFn(fileify(dm({ playtype: "Single" })), {}, logger);
t.throws(
fn,
new ScoreImportFatalError(
400,
"Invalid BATCH-MANUAL: body[0].playtype | Expected any of SP, DP. | Received Single [string]."
)
);
t.end();
});
t.test("Invalid Identifier", (t) => {
// this is not a valid playtype for IIDX
let fn = () => ParserFn(fileify(dm({ identifier: null })), {}, logger);
t.throws(fn, mockErr("body[0].identifier | Expected string", "Received null [null]"));
t.end();
});
t.test("Invalid MatchType", (t) => {
let fn = () => ParserFn(fileify(dm({ matchType: "Invalid_MatchType" })), {}, logger);
t.throws(
fn,
mockErr(
"body[0].matchType | Expected any of",
"Received Invalid_MatchType [string]"
)
);
t.end();
});
t.test("Invalid HitData", (t) => {
let fn = () => ParserFn(fileify(dm({ hitData: { not_key: 123 } })), {}, logger);
t.throws(fn, mockErr("body[0].hitData | Invalid Key not_key"));
let fn2 = () => ParserFn(fileify(dm({ hitData: { pgreat: "123" } })), {}, logger);
t.throws(
fn2,
mockErr("body[0].hitData | Key pgreat had an invalid value of 123 [string]")
);
t.end();
});
t.test("Invalid HitMeta", (t) => {
let fn = () => ParserFn(fileify(dm({ hitMeta: { not_key: 123 } })), {}, logger);
t.throws(fn, mockErr("body[0].hitMeta | Unexpected"));
let fn2 = () => ParserFn(fileify(dm({ hitMeta: { bp: -1 } })), {}, logger);
t.throws(fn2, mockErr("body[0].hitMeta.bp | Expected a positive integer"));
t.end();
});
t.end();
});
t.end();
});
@@ -16,9 +16,9 @@ import deepmerge from "deepmerge";
const optNull = (v: ValidSchemaValue) => p.optional(p.nullable(v));
const BaseValidHitMeta = {
fast: p.isPositiveInteger,
slow: p.isPositiveInteger,
maxCombo: p.isPositiveInteger,
fast: optNull(p.isPositiveInteger),
slow: optNull(p.isPositiveInteger),
maxCombo: optNull(p.isPositiveInteger),
};
const PR_HitMeta = (game: Game): PrudenceSchema => {
@@ -73,7 +73,11 @@ const PR_BatchManualScore = (game: Game): PrudenceSchema => ({
difficulty: "*?string", // this is checked in converting instead
// september 9th 2001 - this saves people not
// reading the documentation.
timeAchieved: optNull(p.gt(1_000_000_000_000)),
timeAchieved: optNull(
(self) =>
(typeof self === "number" && self > 1_000_000_000_000) ||
"Expected a number greater than 1 Trillion - did you pass unix seconds instead of miliseconds?"
),
hitData: optNull((self) => {
if (typeof self !== "object" || self === null) {
return "Not a valid object.";
@@ -81,13 +85,15 @@ const PR_BatchManualScore = (game: Game): PrudenceSchema => ({
for (const key in self) {
if (!validHitData[game].includes(key)) {
return `Invalid Key ${key}. Expected any of ${validHitData[game].toString()}`;
return `Invalid Key ${key}. Expected any of ${validHitData[game].join(", ")}`;
}
// @ts-expect-error shush
let v = self[key];
if (!Number.isSafeInteger(v)) {
return `Key ${key} had an invalid value of ${v}.`;
if (!Number.isSafeInteger(v) || v < 0) {
return `Key ${key} had an invalid value of ${v} [${
v === null ? "null" : typeof v
}]`;
}
}
@@ -172,7 +178,14 @@ function ParseBatchManual(
if (err) {
throw new ScoreImportFatalError(
400,
`Invalid BATCH-MANUAL (${err.keychain} | ${err.message} | Received ${err.userVal})`
// ouch prettier
`Invalid BATCH-MANUAL: ${err.keychain} | ${err.message}${
typeof err.userVal === "object" && err.userVal !== null
? ""
: ` | Received ${err.userVal} [${
err.userVal === null ? "null" : typeof err.userVal
}]`
}.`
);
}