diff --git a/server/package.json b/server/package.json index bffbb4fee..5d76a216f 100644 --- a/server/package.json +++ b/server/package.json @@ -82,7 +82,7 @@ "rimraf": "3.0.2", "safe-json-stringify": "1.2.0", "seq-logging": "1.1.1", - "tachi-common": "0.5.15", + "tachi-common": "0.6.6", "ts-node": "10.4.0", "tsconfig-paths": "3.12.0", "typescript": "4.5.5", diff --git a/server/pnpm-lock.yaml b/server/pnpm-lock.yaml index d6008c4c7..37d00a907 100644 --- a/server/pnpm-lock.yaml +++ b/server/pnpm-lock.yaml @@ -63,7 +63,7 @@ specifiers: safe-json-stringify: 1.2.0 seq-logging: 1.1.1 supertest: 6.2.2 - tachi-common: 0.5.15 + tachi-common: 0.6.6 tap: 15.1.6 ts-node: 10.4.0 tsconfig-paths: 3.12.0 @@ -108,7 +108,7 @@ dependencies: rimraf: 3.0.2 safe-json-stringify: 1.2.0 seq-logging: 1.1.1 - tachi-common: 0.5.15 + tachi-common: 0.6.6 ts-node: 10.4.0_2615db9039ce432b4abf2fc39ef336ec tsconfig-paths: 3.12.0 typescript: 4.5.5 @@ -1428,6 +1428,13 @@ packages: '@types/node': 17.0.15 dev: true + /@types/mongodb/3.6.18: + resolution: {integrity: sha512-JSVFt9p0rTfZ4EgzXmVHUB3ue00xe3CRbQho8nXfImzEDDM4O7I3po1bwbWl/EIbLENxUreZxqLOc8lvcnLVPA==} + dependencies: + '@types/bson': 4.2.0 + '@types/node': 17.0.15 + dev: false + /@types/mongodb/3.6.20: resolution: {integrity: sha512-WcdpPJCakFzcWWD9juKoZbRtQxKIMYF/JIAM4JrNHrMcnJL6/a2NWjXxW7fo9hxboxxkg+icff8d7+WIEvKgYQ==} dependencies: @@ -5284,18 +5291,10 @@ packages: engines: {node: '>= 0.4'} dev: true - /tachi-common/0.5.15: - resolution: {integrity: sha512-7pBspD/7TduoJu1OdguRRxg5LyWwodhbZi5NU4y6aviJmHRetTwWXksCi4QBc72NbKlwqGwnbGrxdKFKdKQNhQ==} + /tachi-common/0.6.6: + resolution: {integrity: sha512-20kbxnFFa+P8Ba8mHFmoUYyuO/6w08PGcop4b6FS2QL9vpSrMTWdCKssM8WFlBbO0Cp7WDVebZ5plRpFeMoSKQ==} dependencies: - monk: 7.3.4 - transitivePeerDependencies: - - aws4 - - bson-ext - - kerberos - - mongodb-client-encryption - - mongodb-extjson - - snappy - - supports-color + '@types/mongodb': 3.6.18 dev: false /tap-mocha-reporter/5.0.1: diff --git a/server/src/lib/score-import/framework/goals/goals.test.ts b/server/src/lib/score-import/framework/goals/goals.test.ts index 745c8f6f9..65cd72e8f 100644 --- a/server/src/lib/score-import/framework/goals/goals.test.ts +++ b/server/src/lib/score-import/framework/goals/goals.test.ts @@ -133,6 +133,9 @@ t.test("#GetRelevantGoals", (t) => { progressHuman: "NO DATA", timeSet: Date.now(), userID: 1, + from: { + origin: "manual", + }, })) ); }); @@ -195,6 +198,9 @@ t.test("#UpdateGoalsForUser", (t) => { timeAchieved: null, timeSet: 0, userID: 1, + from: { + origin: "manual", + }, }; t.test("Should correctly update goals when user achieves goal.", async (t) => { diff --git a/server/src/lib/score-import/framework/goals/goals.ts b/server/src/lib/score-import/framework/goals/goals.ts index 3ba453b25..003a23863 100644 --- a/server/src/lib/score-import/framework/goals/goals.ts +++ b/server/src/lib/score-import/framework/goals/goals.ts @@ -3,6 +3,7 @@ import { EvaluateGoalForUser } from "lib/achievables/goals"; import db from "external/mongo/db"; import { KtLogger } from "lib/logger/logger"; import { EmitWebhookEvent } from "lib/webhooks/webhooks"; +import { IObjectID } from "monk"; /** * Update a user's progress on all of their set goals. @@ -135,7 +136,7 @@ export async function ProcessGoal( // that haven't changed return nothing instead of // getting to this point. lastInteraction: Date.now(), - }, + } as Partial, }, }, }; diff --git a/server/src/lib/score-import/import-types/common/batch-manual/parser.test.ts b/server/src/lib/score-import/import-types/common/batch-manual/parser.test.ts index a977b8da5..584bc34ad 100644 --- a/server/src/lib/score-import/import-types/common/batch-manual/parser.test.ts +++ b/server/src/lib/score-import/import-types/common/batch-manual/parser.test.ts @@ -3,7 +3,7 @@ import CreateLogCtx from "lib/logger/logger"; import { BatchManual } from "tachi-common"; import t from "tap"; import { EscapeStringRegexp } from "utils/misc"; -import { IIDXDans, WACCA_STAGEUPS } from "lib/constants/classes"; +import { IIDXDans } from "lib/constants/classes"; import ScoreImportFatalError from "../../../framework/score-importing/score-import-error"; import { ParseBatchManualFromObject as ParserFn } from "./parser"; @@ -338,11 +338,24 @@ t.test("#ParserFn", (t) => { t.not(res.classHandler, null); - t.same( - res.classHandler!("iidx", "SP", 1, {}, logger), - { dan: IIDXDans.KAIDEN }, + t.strictSame(res.classHandler!("iidx", "SP", 1, {}, logger), { dan: IIDXDans.KAIDEN }); + + t.end(); + }); + + t.test("With class set to null.", (t) => { + const res = ParserFn( + { + meta: baseBatchManual.meta, + scores: [baseBatchManualScore], + classes: null, + } as BatchManual, + "file/batch-manual", + logger ); + t.equal(res.classHandler, null); + t.end(); }); @@ -506,30 +519,100 @@ t.test("#ParserFn", (t) => { t.test("Invalid class", (t) => { // Out of bounds. (18 is kaiden) - const fn = () => ParserFn( - { - meta: baseBatchManual.meta, - scores: [baseBatchManualScore], - classes: { dan: 19 }, - } as BatchManual, - "file/batch-manual", - logger - ); - t.throws(fn, mockErr("classes.dan | Expected a number between 0 and 18. | Received 19 [number]")); + t.test("Should throw if class is out of bounds.", (t) => { + t.throws( + () => + ParserFn( + { + meta: baseBatchManual.meta, + scores: [baseBatchManualScore], + classes: { dan: 19 }, + } as BatchManual, + "file/batch-manual", + logger + ), + mockErr( + "classes.dan | Expected an integer between 0 and 18. | Received 19 [number]" + ) + ); - // Wrong game. - const fn2 = () => ParserFn( - { - meta: baseBatchManual.meta, - scores: [baseBatchManualScore], - classes: { stageUp: 9 }, - } as BatchManual, - "file/batch-manual", - logger - ); + t.end(); + }); - t.throws(fn2, mockErr("classes | Unexpected properties inside object: stageUp")); + t.test("Should throw if dans for different games are passed.", (t) => { + t.throws( + () => + ParserFn( + { + meta: baseBatchManual.meta, + scores: [baseBatchManualScore], + classes: { stageUp: 9 }, + } as BatchManual, + "file/batch-manual", + logger + ), + mockErr("classes | Unexpected properties inside object: stageUp") + ); + + t.end(); + }); + + t.test("Should throw if dan is a non-integer.", (t) => { + t.throws( + () => + ParserFn( + { + meta: baseBatchManual.meta, + scores: [baseBatchManualScore], + classes: { dan: 9.5 }, + } as BatchManual, + "file/batch-manual", + logger + ), + mockErr( + "classes.dan | Expected an integer between 0 and 18. | Received 9.5 [number]." + ) + ); + + t.end(); + }); + + t.test("Should throw if unknown classes are present.", (t) => { + t.throws( + () => + ParserFn( + { + meta: baseBatchManual.meta, + scores: [baseBatchManualScore], + classes: { dan: 14, unknownDan: 4 }, + } as unknown, + "file/batch-manual", + logger + ), + mockErr("classes | Unexpected properties inside object: unknownDan.") + ); + + // should also throw if classes from a valid game and invalid game + // are passed. + t.throws( + () => + ParserFn( + { + meta: baseBatchManual.meta, + scores: [baseBatchManualScore], + classes: { dan: 14, stageUp: 4 }, + } as unknown, + "file/batch-manual", + logger + ), + mockErr( + "Invalid BATCH-MANUAL: classes | Unexpected properties inside object: stageUp." + ) + ); + + t.end(); + }); t.end(); }); diff --git a/server/src/lib/score-import/import-types/common/batch-manual/parser.ts b/server/src/lib/score-import/import-types/common/batch-manual/parser.ts index 908cd98ae..9efbcdbef 100644 --- a/server/src/lib/score-import/import-types/common/batch-manual/parser.ts +++ b/server/src/lib/score-import/import-types/common/batch-manual/parser.ts @@ -12,13 +12,8 @@ import { Playtypes, } from "tachi-common"; import { FormatPrError } from "utils/prudence"; -import { - IIDXDans, - SDVXDans, - WACCA_STAGEUPS, -} from "lib/constants/classes"; +import { IIDXDans, SDVXDans, WACCA_STAGEUPS } from "lib/constants/classes"; import ScoreImportFatalError from "../../../framework/score-importing/score-import-error"; -import { ScoreClasses } from "../../../framework/user-game-stats/types"; import { ParserFunctionReturns } from "../types"; import { BatchManualContext } from "./types"; @@ -208,18 +203,18 @@ const PR_BatchManualScore = (game: Game, playtype: Playtypes[Game]): PrudenceSch const PR_BatchManualClasses = (game: Game): PrudenceSchema => { switch (game) { - // This can be implemented for any non-static class (i.e. dans). + // This can be implemented for any non-static class (i.e. dans). case "iidx": return { - dan: optNull(p.isBetween(IIDXDans.KYU_7, IIDXDans.KAIDEN)) + dan: optNull(p.isBoundedInteger(IIDXDans.KYU_7, IIDXDans.KAIDEN)), }; case "sdvx": return { - dan: optNull(p.isBetween(SDVXDans.DAN_1, SDVXDans.INF)) + dan: optNull(p.isBoundedInteger(SDVXDans.DAN_1, SDVXDans.INF)), }; case "wacca": return { - stageUp: optNull(p.isBetween(WACCA_STAGEUPS.I, WACCA_STAGEUPS.XIV)) + stageUp: optNull(p.isBoundedInteger(WACCA_STAGEUPS.I, WACCA_STAGEUPS.XIV)), }; default: return {}; @@ -313,7 +308,7 @@ export function ParseBatchManualFromObject( throw new ScoreImportFatalError(400, FormatPrError(err, "Invalid BATCH-MANUAL")); } - const batchManual = object as BatchManual & {classes: ScoreClasses}; + const batchManual = object as BatchManual; return { game, @@ -324,6 +319,8 @@ export function ParseBatchManualFromObject( version: batchManual.meta.version ?? null, }, iterable: batchManual.scores, - classHandler: (..._) => batchManual.classes, + // if classes are provided, use those as a class handler. Otherwise, we + // don't care. + classHandler: batchManual.classes ? () => batchManual.classes! : null, }; } diff --git a/server/src/test-utils/test-data.ts b/server/src/test-utils/test-data.ts index c73c3c047..a24196338 100644 --- a/server/src/test-utils/test-data.ts +++ b/server/src/test-utils/test-data.ts @@ -436,6 +436,9 @@ export const HC511UserGoal: UserGoalDocument = { progressHuman: "NO DATA", timeSet: 0, userID: 1, + from: { + origin: "manual", + }, }; export const TestingIIDXFolderSP10: FolderDocument = { @@ -452,15 +455,12 @@ export const TestingIIDXFolderSP10: FolderDocument = { }; export const TestingIIDXSPMilestone: MilestoneDocument = { - createdBy: 1, criteria: { type: "all", }, desc: "testing milestone", game: "iidx", playtype: "SP", - group: "ExampleGroup", - groupIndex: 1, milestoneID: "example_milestone_id", name: "Example Milestone", milestoneData: [