diff --git a/server/.github/workflows/tests.yml b/server/.github/workflows/tests.yml index 9a5baa635..0030cf98b 100644 --- a/server/.github/workflows/tests.yml +++ b/server/.github/workflows/tests.yml @@ -6,9 +6,9 @@ env: KTBSV_LOG_LEVEL: "info" KTBSV_CAPTCHA_SECRET_KEY: "unused" KTBSV_SESSION_SECRET: "unused" - KTBSV_FLO_API_URL: "unused" - KTBSV_EAG_API_URL: "unused" - KTBSV_ARC_API_URL: "unused" + KTBSV_FLO_API_URL: "https://flo.example.com" + KTBSV_EAG_API_URL: "https://eag.example.com" + KTBSV_ARC_API_URL: "https://arc.example.com" CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} on: diff --git a/server/src/lib/score-import/import-types/common/api-kai/iidx/converter.test.ts b/server/src/lib/score-import/import-types/common/api-kai/iidx/converter.test.ts new file mode 100644 index 000000000..6faf88579 --- /dev/null +++ b/server/src/lib/score-import/import-types/common/api-kai/iidx/converter.test.ts @@ -0,0 +1,99 @@ +import t from "tap"; +import { CloseAllConnections } from "../../../../../../test-utils/close-connections"; +import ResetDBState from "../../../../../../test-utils/reset-db-state"; +import { + GetKTDataJSON, + Testing511Song, + Testing511SPA, +} from "../../../../../../test-utils/test-data"; +import CreateLogCtx from "../../../../../logger/logger"; +import { ConvertAPIKaiIIDX } from "./converter"; +import deepmerge from "deepmerge"; +import db from "../../../../../../external/mongo/db"; + +const logger = CreateLogCtx(__filename); + +const iidxScore = GetKTDataJSON("./api-kai/iidx-score.json"); + +t.test("#ConvertAPIKaiIIDX", (t) => { + t.beforeEach(ResetDBState); + + t.test("Should return a dryScore on valid input.", async (t) => { + const res = await ConvertAPIKaiIIDX(iidxScore, { service: "FLO" }, "api/flo-iidx", logger); + + t.hasStrict(res, { + song: Testing511Song, + chart: Testing511SPA, + dryScore: { + comment: null, + game: "iidx", + importType: "api/flo-iidx", + timeAchieved: 1604171450000, + service: "FLO", + scoreData: { + grade: "MAX-", + // percent: 99.87277353689568 floating point, + score: 1570, + lamp: "HARD CLEAR", + hitData: {}, + hitMeta: { + fast: null, + slow: null, + bp: 24, + }, + }, + scoreMeta: {}, + }, + } as any); + + t.end(); + }); + + t.test("Should throw KTDataNotFound on unknown chart", (t) => { + t.rejects( + () => + ConvertAPIKaiIIDX( + deepmerge(iidxScore, { music_id: 0 }), + { service: "FLO" }, + "api/flo-iidx", + logger + ), + { + message: /Could not find chart with songID 0 \(SP ANOTHER - Version 26\)/u, + } as any + ); + + t.end(); + }); + + t.test("Should throw InvalidScoreFailure on invalid score", (t) => { + t.rejects( + () => + ConvertAPIKaiIIDX( + deepmerge(iidxScore, { music_id: "foo" }), + { service: "FLO" }, + "api/flo-iidx", + logger + ), + { + message: /Error: music_id.*Expected a positive integer.* received foo \[string\]/iu, + } as any + ); + + t.end(); + }); + + t.test("Should throw InternalFailure on song-chart desync", async (t) => { + await db.songs.iidx.remove({}); + + t.rejects(() => ConvertAPIKaiIIDX(iidxScore, { service: "FLO" }, "api/flo-iidx", logger), { + message: /Song-Chart desync/u, + } as any); + + t.end(); + }); + + t.end(); +}); + +t.teardown(CloseAllConnections); diff --git a/server/src/lib/score-import/import-types/common/api-kai/iidx/converter.ts b/server/src/lib/score-import/import-types/common/api-kai/iidx/converter.ts index 32b74415f..5a352b951 100644 --- a/server/src/lib/score-import/import-types/common/api-kai/iidx/converter.ts +++ b/server/src/lib/score-import/import-types/common/api-kai/iidx/converter.ts @@ -24,8 +24,8 @@ const PR_KaiIIDXScore = { lamp: p.isBoundedInteger(0, 7), ex_score: p.isPositiveInteger, miss_count: p.or(p.isPositiveInteger, p.is(-1)), - fast_count: p.isPositiveInteger, - slow_count: p.isPositiveInteger, + fast_count: p.nullable(p.isPositiveInteger), + slow_count: p.nullable(p.isPositiveInteger), timestamp: "string", }; diff --git a/server/src/lib/score-import/import-types/common/api-kai/iidx/parser.test.ts b/server/src/lib/score-import/import-types/common/api-kai/iidx/parser.test.ts new file mode 100644 index 000000000..cf2e0eb70 --- /dev/null +++ b/server/src/lib/score-import/import-types/common/api-kai/iidx/parser.test.ts @@ -0,0 +1,89 @@ +import t from "tap"; +import { CloseAllConnections } from "../../../../../../test-utils/close-connections"; +import { MockJSONFetch } from "../../../../../../test-utils/mock-fetch"; +import ResetDBState from "../../../../../../test-utils/reset-db-state"; +import CreateLogCtx from "../../../../../logger/logger"; +import { ParseKaiIIDX } from "./parser"; + +const fakeAuth = { + userID: 1, + refreshToken: "foo", + service: "FLO" as const, + token: "bar", +}; + +const logger = CreateLogCtx(__filename); + +t.test("#ParseKaiIIDX", (t) => { + t.beforeEach(ResetDBState); + + t.test("Should iterate over the IIDX FLO API asynchronously.", async (t) => { + const mockFloAPI = MockJSONFetch({ + "https://flo.example.com/api/iidx/v2/play_history": { + _links: { + _next: "https://flo.example.com/api/iidx/v2/play_history?page=2", + }, + _items: [1, 2, 3, 4], + }, + "https://flo.example.com/api/iidx/v2/play_history?page=2": { + _links: { + _next: null, + }, + _items: [5, 6], + }, + }); + + const res = ParseKaiIIDX("FLO", fakeAuth, logger, mockFloAPI); + + t.equal(res.game, "iidx"); + t.equal(res.classHandler, null); + t.strictSame(res.context, { service: "FLO" }); + + const iter = []; + + for await (const el of res.iterable) { + iter.push(el); + } + + t.strictSame(iter, [1, 2, 3, 4, 5, 6]); + + t.end(); + }); + + t.test("Should iterate over the IIDX EAG API asynchronously.", async (t) => { + const mockEagAPI = MockJSONFetch({ + "https://eag.example.com/api/iidx/v2/play_history": { + _links: { + _next: "https://eag.example.com/api/iidx/v2/play_history?page=2", + }, + _items: [1, 2, 3, 4], + }, + "https://eag.example.com/api/iidx/v2/play_history?page=2": { + _links: { + _next: null, + }, + _items: [5, 6], + }, + }); + + const res = ParseKaiIIDX("EAG", fakeAuth, logger, mockEagAPI); + + t.equal(res.game, "iidx"); + t.equal(res.classHandler, null); + t.strictSame(res.context, { service: "EAG" }); + + const iter = []; + + for await (const el of res.iterable) { + iter.push(el); + } + + t.strictSame(iter, [1, 2, 3, 4, 5, 6]); + + t.end(); + }); + + t.end(); +}); + +t.teardown(CloseAllConnections); diff --git a/server/src/lib/score-import/import-types/common/api-kai/iidx/parser.ts b/server/src/lib/score-import/import-types/common/api-kai/iidx/parser.ts index 534f431d9..34bf8b73c 100644 --- a/server/src/lib/score-import/import-types/common/api-kai/iidx/parser.ts +++ b/server/src/lib/score-import/import-types/common/api-kai/iidx/parser.ts @@ -2,7 +2,6 @@ import { KtLogger } from "../../../../../logger/logger"; import nodeFetch from "../../../../../../utils/fetch"; import { KaiAuthDocument } from "kamaitachi-common"; import { KaiContext } from "../types"; -import { ConvertAPIKaiIIDX } from "./converter"; import { TraverseKaiAPI } from "../traverse-api"; import { ParserFunctionReturnsAsync } from "../../types"; import { FLO_API_URL, EAG_API_URL } from "../../../../../env/env"; diff --git a/server/src/lib/score-import/import-types/common/api-kai/sdvx/converter.test.ts b/server/src/lib/score-import/import-types/common/api-kai/sdvx/converter.test.ts new file mode 100644 index 000000000..16e88f391 --- /dev/null +++ b/server/src/lib/score-import/import-types/common/api-kai/sdvx/converter.test.ts @@ -0,0 +1,133 @@ +import t from "tap"; +import { CloseAllConnections } from "../../../../../../test-utils/close-connections"; +import ResetDBState from "../../../../../../test-utils/reset-db-state"; +import { + GetKTDataJSON, + TestingAlbidaADV, + TestingSDVXAlbidaSong, +} from "../../../../../../test-utils/test-data"; +import CreateLogCtx from "../../../../../logger/logger"; +import { ConvertAPIKaiSDVX, ConvertDifficulty, ConvertVersion, ResolveKaiLamp } from "./converter"; +import deepmerge from "deepmerge"; +import db from "../../../../../../external/mongo/db"; + +const logger = CreateLogCtx(__filename); + +const sdvxScore = GetKTDataJSON("./api-kai/sdvx-score.json"); + +t.test("#ConvertAPIKaiSDVX", (t) => { + t.beforeEach(ResetDBState); + + t.test("Should return a dryScore on valid input.", async (t) => { + const res = await ConvertAPIKaiSDVX(sdvxScore, { service: "FLO" }, "api/flo-sdvx", logger); + + t.hasStrict(res, { + song: TestingSDVXAlbidaSong, + chart: TestingAlbidaADV, + dryScore: { + comment: null, + game: "sdvx", + importType: "api/flo-sdvx", + timeAchieved: 1598792891000, + service: "FLO", + scoreData: { + grade: "AA", + // percent: 93.10699, floating point + score: 9310699, + lamp: "EXCESSIVE CLEAR", + hitData: {}, + hitMeta: { + fast: 70, + slow: 42, + gauge: 90, + maxCombo: 179, + }, + }, + scoreMeta: {}, + }, + } as any); + + t.end(); + }); + + t.test("Should throw KTDataNotFound on unknown chart", (t) => { + t.rejects( + () => + ConvertAPIKaiSDVX( + deepmerge(sdvxScore, { music_id: 0 }), + { service: "FLO" }, + "api/flo-sdvx", + logger + ), + { + message: /Could not find chart with songID 0 \(ADV - Version vivid\)/u, + } as any + ); + + t.end(); + }); + + t.test("Should throw InvalidScoreFailure on invalid score", (t) => { + t.rejects( + () => + ConvertAPIKaiSDVX( + deepmerge(sdvxScore, { music_id: "foo" }), + { service: "FLO" }, + "api/flo-sdvx", + logger + ), + { + message: /Error: music_id.*Expected a positive integer.* received foo \[string\]/iu, + } as any + ); + + t.end(); + }); + + t.test("Should throw InternalFailure on song-chart desync", async (t) => { + await db.songs.sdvx.remove({}); + + t.rejects(() => ConvertAPIKaiSDVX(sdvxScore, { service: "FLO" }, "api/flo-sdvx", logger), { + message: /Song-Chart desync/u, + } as any); + + t.end(); + }); + + t.end(); +}); + +t.test("#ConvertDifficulty", (t) => { + t.equal(ConvertDifficulty(0), "NOV"); + t.equal(ConvertDifficulty(1), "ADV"); + t.equal(ConvertDifficulty(2), "EXH"); + t.equal(ConvertDifficulty(3), "ANY_INF"); + t.equal(ConvertDifficulty(4), "MXM"); + t.throws(() => ConvertDifficulty(5)); + + t.end(); +}); + +t.test("#ConvertVersion", (t) => { + t.equal(ConvertVersion(0), "booth"); + t.equal(ConvertVersion(1), "inf"); + t.equal(ConvertVersion(2), "gw"); + t.equal(ConvertVersion(3), "heaven"); + t.equal(ConvertVersion(4), "vivid"); + t.throws(() => ConvertVersion(5)); + + t.end(); +}); + +t.test("#ResolveKaiLamp", (t) => { + t.equal(ResolveKaiLamp(0), "FAILED"); + t.equal(ResolveKaiLamp(1), "CLEAR"); + t.equal(ResolveKaiLamp(2), "EXCESSIVE CLEAR"); + t.equal(ResolveKaiLamp(3), "ULTIMATE CHAIN"); + t.equal(ResolveKaiLamp(4), "PERFECT ULTIMATE CHAIN"); + t.throws(() => ConvertDifficulty(5)); + + t.end(); +}); + +t.teardown(CloseAllConnections); diff --git a/server/src/lib/score-import/import-types/common/api-kai/sdvx/converter.ts b/server/src/lib/score-import/import-types/common/api-kai/sdvx/converter.ts index 60ba9e36d..8fe04584b 100644 --- a/server/src/lib/score-import/import-types/common/api-kai/sdvx/converter.ts +++ b/server/src/lib/score-import/import-types/common/api-kai/sdvx/converter.ts @@ -48,16 +48,13 @@ export const ConvertAPIKaiSDVX: ConverterFunction = async ( const score = data as KaiSDVXScore; const difficulty = ConvertDifficulty(score.music_difficulty); + const version = ConvertVersion(score.played_version); - const chart = await FindSDVXChartOnInGameIDVersion( - score.music_id, - difficulty, - score.played_version.toString() - ); + const chart = await FindSDVXChartOnInGameIDVersion(score.music_id, difficulty, version); if (!chart) { throw new KTDataNotFoundFailure( - `Could not find chart with songID ${score.music_id} (${difficulty} - Version ${score.played_version})`, + `Could not find chart with songID ${score.music_id} (${difficulty} - Version ${version})`, importType, data, context @@ -102,7 +99,7 @@ export const ConvertAPIKaiSDVX: ConverterFunction = async ( return { song, chart, dryScore }; }; -function ConvertDifficulty(diff: number) { +export function ConvertDifficulty(diff: number) { switch (diff) { case 0: return "NOV"; @@ -119,7 +116,24 @@ function ConvertDifficulty(diff: number) { throw new InvalidScoreFailure(`Invalid difficulty of ${diff} - Could not convert.`); } -function ResolveKaiLamp(clear: number): Lamps["sdvx:Single"] { +export function ConvertVersion(ver: number) { + switch (ver) { + case 0: + return "booth"; + case 1: + return "inf"; + case 2: + return "gw"; + case 3: + return "heaven"; + case 4: + return "vivid"; + } + + throw new InvalidScoreFailure(`Unknown Game Version ${ver}.`); +} + +export function ResolveKaiLamp(clear: number): Lamps["sdvx:Single"] { switch (clear) { case 0: return "FAILED"; diff --git a/server/src/lib/score-import/import-types/common/api-kai/sdvx/parser.test.ts b/server/src/lib/score-import/import-types/common/api-kai/sdvx/parser.test.ts new file mode 100644 index 000000000..228f1705a --- /dev/null +++ b/server/src/lib/score-import/import-types/common/api-kai/sdvx/parser.test.ts @@ -0,0 +1,89 @@ +import t from "tap"; +import { CloseAllConnections } from "../../../../../../test-utils/close-connections"; +import { MockJSONFetch } from "../../../../../../test-utils/mock-fetch"; +import ResetDBState from "../../../../../../test-utils/reset-db-state"; +import CreateLogCtx from "../../../../../logger/logger"; +import { ParseKaiSDVX } from "./parser"; + +const fakeAuth = { + userID: 1, + refreshToken: "foo", + service: "FLO" as const, + token: "bar", +}; + +const logger = CreateLogCtx(__filename); + +t.test("#ParseKaiSDVX", (t) => { + t.beforeEach(ResetDBState); + + t.test("Should iterate over the SDVX FLO API asynchronously.", async (t) => { + const mockFloAPI = MockJSONFetch({ + "https://flo.example.com/api/sdvx/v1/play_history": { + _links: { + _next: "https://flo.example.com/api/sdvx/v1/play_history?page=2", + }, + _items: [1, 2, 3, 4], + }, + "https://flo.example.com/api/sdvx/v1/play_history?page=2": { + _links: { + _next: null, + }, + _items: [5, 6], + }, + }); + + const res = ParseKaiSDVX("FLO", fakeAuth, logger, mockFloAPI); + + t.equal(res.game, "sdvx"); + t.equal(res.classHandler, null); + t.strictSame(res.context, { service: "FLO" }); + + const iter = []; + + for await (const el of res.iterable) { + iter.push(el); + } + + t.strictSame(iter, [1, 2, 3, 4, 5, 6]); + + t.end(); + }); + + t.test("Should iterate over the SDVX EAG API asynchronously.", async (t) => { + const mockEagAPI = MockJSONFetch({ + "https://eag.example.com/api/sdvx/v1/play_history": { + _links: { + _next: "https://eag.example.com/api/sdvx/v1/play_history?page=2", + }, + _items: [1, 2, 3, 4], + }, + "https://eag.example.com/api/sdvx/v1/play_history?page=2": { + _links: { + _next: null, + }, + _items: [5, 6], + }, + }); + + const res = ParseKaiSDVX("EAG", fakeAuth, logger, mockEagAPI); + + t.equal(res.game, "sdvx"); + t.equal(res.classHandler, null); + t.strictSame(res.context, { service: "EAG" }); + + const iter = []; + + for await (const el of res.iterable) { + iter.push(el); + } + + t.strictSame(iter, [1, 2, 3, 4, 5, 6]); + + t.end(); + }); + + t.end(); +}); + +t.teardown(CloseAllConnections); diff --git a/server/src/lib/score-import/import-types/common/api-kai/sdvx/parser.ts b/server/src/lib/score-import/import-types/common/api-kai/sdvx/parser.ts index cfcfb3ec1..470bb2214 100644 --- a/server/src/lib/score-import/import-types/common/api-kai/sdvx/parser.ts +++ b/server/src/lib/score-import/import-types/common/api-kai/sdvx/parser.ts @@ -2,7 +2,6 @@ import { KtLogger } from "../../../../../logger/logger"; import nodeFetch from "../../../../../../utils/fetch"; import { KaiAuthDocument } from "kamaitachi-common"; import { KaiContext } from "../types"; -import { ConvertAPIKaiSDVX } from "./converter"; import { TraverseKaiAPI } from "../traverse-api"; import { ParserFunctionReturnsAsync } from "../../types"; import { EAG_API_URL, FLO_API_URL } from "../../../../../env/env"; @@ -21,6 +20,6 @@ export function ParseKaiSDVX( service, }, classHandler: null, - game: "iidx", + game: "sdvx", }; } diff --git a/server/src/lib/score-import/import-types/common/api-kai/traverse-api.test.ts b/server/src/lib/score-import/import-types/common/api-kai/traverse-api.test.ts new file mode 100644 index 000000000..4b15477ec --- /dev/null +++ b/server/src/lib/score-import/import-types/common/api-kai/traverse-api.test.ts @@ -0,0 +1,173 @@ +import t from "tap"; +import { CloseAllConnections } from "../../../../../test-utils/close-connections"; +import { MockJSONFetch } from "../../../../../test-utils/mock-fetch"; +import ResetDBState from "../../../../../test-utils/reset-db-state"; +import CreateLogCtx from "../../../../logger/logger"; +import ScoreImportFatalError from "../../../framework/score-importing/score-import-error"; +import { TraverseKaiAPI } from "./traverse-api"; + +const logger = CreateLogCtx(__filename); + +const fakeAuth = { + userID: 1, + refreshToken: "foo", + service: "FLO" as const, + token: "bar", +}; + +/** + * Async Generator To Array + */ +async function agta(ag: AsyncGenerator) { + const a = []; + for await (const el of ag) { + a.push(el); + } + + return a; +} + +t.test("#TraverseKaiAPI", (t) => { + t.beforeEach(ResetDBState); + + t.test("Should traverse the API", async (t) => { + const mockKaiAPI = MockJSONFetch({ + "http://url.com/sub": { + _links: { + _next: "http://url.com/sub?page=2", + }, + _items: [1, 2, 3, 4], + }, + "http://url.com/sub?page=2": { + _links: { + _next: null, + }, + _items: [5, 6], + }, + }); + + const res = TraverseKaiAPI("http://url.com", "/sub", fakeAuth, logger, mockKaiAPI); + + const elements = await agta(res); + + t.strictSame(elements, [1, 2, 3, 4, 5, 6]); + + t.end(); + }); + + t.test("Should throw on attempted SSRF", (t) => { + const mockKaiAPI = MockJSONFetch({ + "http://url.com/sub": { + _links: { + _next: "http://evil.com/sub?page=2", + }, + _items: [1, 2, 3, 4], + }, + "http://url.com/sub?page=2": { + _links: { + _next: null, + }, + _items: [5, 6], + }, + }); + const res = TraverseKaiAPI("http://url.com", "/sub", fakeAuth, logger, mockKaiAPI); + + t.rejects( + () => agta(res), + new ScoreImportFatalError(500, `http://url.com returned invalid data.`) + ); + + t.end(); + }); + + t.test("Should throw on invalid response JSON", (t) => { + const mockKaiAPI = () => ({ json: null } as any); + const res = TraverseKaiAPI("http://url.com", "/sub", fakeAuth, logger, mockKaiAPI); + + t.rejects(() => agta(res)); + + t.end(); + }); + + t.test("Should throw on request failure", (t) => { + const mockKaiAPI = () => { + throw new Error("Fake Request timeout..."); + }; + const res = TraverseKaiAPI("http://url.com", "/sub", fakeAuth, logger, mockKaiAPI); + + t.rejects(() => agta(res)); + + t.end(); + }); + + t.test("Should throw on invalid _links", (t) => { + const mockKaiAPI = MockJSONFetch({ + "http://url.com/sub": { + _links: null, + }, + }); + + const res = TraverseKaiAPI("http://url.com", "/sub", fakeAuth, logger, mockKaiAPI); + + t.rejects(() => agta(res)); + + const mockKaiAPI2 = MockJSONFetch({ + "http://url.com/sub": { + _links: "foo", + }, + }); + + const res2 = TraverseKaiAPI("http://url.com", "/sub", fakeAuth, logger, mockKaiAPI2); + + t.rejects(() => agta(res2)); + + t.end(); + }); + + t.test("Should throw on invalid _links._next", (t) => { + const mockKaiAPI = MockJSONFetch({ + "http://url.com/sub": { + _links: {}, + }, + }); + + const res = TraverseKaiAPI("http://url.com", "/sub", fakeAuth, logger, mockKaiAPI); + + t.rejects(() => agta(res)); + + const mockKaiAPI2 = MockJSONFetch({ + "http://url.com/sub": { + _links: { + _next: {}, + }, + }, + }); + + const res2 = TraverseKaiAPI("http://url.com", "/sub", fakeAuth, logger, mockKaiAPI2); + + t.rejects(() => agta(res2)); + + t.end(); + }); + + t.test("Should throw on invalid _items", (t) => { + const mockKaiAPI = MockJSONFetch({ + "http://url.com/sub": { + _links: { + _next: null, + }, + _items: {}, + }, + }); + + const res = TraverseKaiAPI("http://url.com", "/sub", fakeAuth, logger, mockKaiAPI); + + t.rejects(() => agta(res)); + + t.end(); + }); + + t.end(); +}); + +t.teardown(CloseAllConnections); diff --git a/server/src/server/router/api/v1/auth/auth.test.ts b/server/src/server/router/api/v1/auth/auth.test.ts index 6985f29ac..fb4e869b3 100644 --- a/server/src/server/router/api/v1/auth/auth.test.ts +++ b/server/src/server/router/api/v1/auth/auth.test.ts @@ -2,7 +2,7 @@ import { CreateInviteCode, AddNewInvite, ReinstateInvite, ValidateCaptcha } from import t from "tap"; import db from "../../../../../external/mongo/db"; import ResetDBState from "../../../../../test-utils/reset-db-state"; -import { MockFetch } from "../../../../../test-utils/mock-fetch"; +import { MockBasicFetch } from "../../../../../test-utils/mock-fetch"; import { CloseAllConnections } from "../../../../../test-utils/close-connections"; t.test("#ReinstateInvite", (t) => { @@ -61,13 +61,13 @@ t.test("#AddNewInvite", (t) => { t.test("#ValidateCaptcha", async (t) => { t.equal( - await ValidateCaptcha("200", "bar", MockFetch({ status: 200 })), + await ValidateCaptcha("200", "bar", MockBasicFetch({ status: 200 })), true, "Validates captcha when status return is 200" ); t.equal( - await ValidateCaptcha("400", "bar", MockFetch({ status: 400 })), + await ValidateCaptcha("400", "bar", MockBasicFetch({ status: 400 })), false, "Invalidates captcha when status return is not 200" ); diff --git a/server/src/test-utils/mock-fetch.ts b/server/src/test-utils/mock-fetch.ts index 241441ca5..71498c338 100644 --- a/server/src/test-utils/mock-fetch.ts +++ b/server/src/test-utils/mock-fetch.ts @@ -1,8 +1,27 @@ +// We know! these are mock functions. +/* eslint-disable require-await */ import { Response } from "node-fetch"; import { NodeFetch } from "../utils/fetch"; -export function MockFetch(data: Partial) { - // We know! this is a mock function. - // eslint-disable-next-line require-await +/** + * Creates a basic Fetch function used for statusCode checks. + */ +export function MockBasicFetch(data: Partial) { return ((async () => data) as unknown) as NodeFetch; } + +/** + * Creates a fake "fetch" function which takes a url and returns + * a fake JSON -> data function at that key. + */ +export function MockJSONFetch(urlDataMap: Record) { + return (async (url: string) => { + if (urlDataMap[url]) { + return { + json: async () => urlDataMap[url], + }; + } + + throw new Error(`Unexpected url ${url} - No Data Present?`); + }) as NodeFetch; +} diff --git a/server/src/test-utils/test-data.ts b/server/src/test-utils/test-data.ts index 62f650c8b..745581942 100644 --- a/server/src/test-utils/test-data.ts +++ b/server/src/test-utils/test-data.ts @@ -46,6 +46,14 @@ export const Testing511Song = GetKTDataJSON( "./kamaitachi/iidx-511-song.json" ) as SongDocument<"iidx">; +export const TestingAlbidaADV = GetKTDataJSON( + "./kamaitachi/sdvx-albida-chart.json" +) as ChartDocument<"sdvx:Single">; + +export const TestingSDVXAlbidaSong = GetKTDataJSON( + "./kamaitachi/sdvx-albida-song.json" +) as SongDocument<"sdvx">; + export const TestingDoraChart = GetKTDataJSON( "./kamaitachi/gitadora-ithinkabtyou.json" ) as ChartDocument<"gitadora:Dora">; diff --git a/server/src/test-utils/test-data/api-kai/iidx-score.json b/server/src/test-utils/test-data/api-kai/iidx-score.json index f47c46b26..9358c4f24 100644 --- a/server/src/test-utils/test-data/api-kai/iidx-score.json +++ b/server/src/test-utils/test-data/api-kai/iidx-score.json @@ -7,7 +7,7 @@ "iidx_id": 35247879, "version_played": 26, "lamp": 5, - "ex_score": 2570, + "ex_score": 1570, "grade": "AA", "miss_count": 24, "fast_count": null, diff --git a/server/src/test-utils/test-data/api-kai/sdvx-score.json b/server/src/test-utils/test-data/api-kai/sdvx-score.json new file mode 100644 index 000000000..e1d34d033 --- /dev/null +++ b/server/src/test-utils/test-data/api-kai/sdvx-score.json @@ -0,0 +1,18 @@ +{ + "sdvx_id": 32157055, + "music_id": 1, + "music_difficulty": 1, + "played_version": 4, + "clear_type": 2, + "max_chain": 179, + "score": 9310699, + "critical": 1754, + "near": 112, + "error": 78, + "early": 70, + "late": 42, + "gauge_type": 0, + "gauge_rate": 90, + "timestamp": "2020-08-30T13:08:11Z", + "_id": 127108 +} \ No newline at end of file diff --git a/server/src/test-utils/test-data/kamaitachi/sdvx-albida-chart.json b/server/src/test-utils/test-data/kamaitachi/sdvx-albida-chart.json new file mode 100644 index 000000000..8f835bfbb --- /dev/null +++ b/server/src/test-utils/test-data/kamaitachi/sdvx-albida-chart.json @@ -0,0 +1,25 @@ +{ + "rgcID": null, + "chartID": "5088a4d0e1ee9d0cc2f625934306e45b1a60699b", + "difficulty": "ADV", + "songID": 1, + "playtype": "Single", + "levelNum": 10, + "level": "10", + "flags": { + "IN BASE GAME": true, + "OMNIMIX": false, + "N-1": false + }, + "data": { + "inGameID": 1 + }, + "isPrimary": true, + "versions": [ + "booth", + "inf", + "gw", + "heaven", + "vivid" + ] +} \ No newline at end of file diff --git a/server/src/test-utils/test-data/kamaitachi/sdvx-albida-song.json b/server/src/test-utils/test-data/kamaitachi/sdvx-albida-song.json new file mode 100644 index 000000000..57f9ca611 --- /dev/null +++ b/server/src/test-utils/test-data/kamaitachi/sdvx-albida-song.json @@ -0,0 +1,14 @@ +{ + "title": "ALBIDA Powerless Mix", + "artist": "無力P", + "id": 1, + "firstVersion": "booth", + "alt-titles": [], + "search-titles": [ + "albida_muryoku", + "アルビダパワーレスミックス" + ], + "data": { + "uscEquiv": null + } +} \ No newline at end of file