test user endpoints

This commit is contained in:
zkldi
2021-06-17 01:07:36 +01:00
parent fdd4945369
commit 05692d6476
5 changed files with 363 additions and 22 deletions
@@ -1,15 +1,330 @@
import t from "tap";
import db from "../../../../../../../../../external/mongo/db";
import { CloseAllConnections } from "../../../../../../../../../test-utils/close-connections";
import mockApi from "../../../../../../../../../test-utils/mock-api";
import ResetDBState from "../../../../../../../../../test-utils/resets";
import {
ScoreDocument,
UserGoalDocument,
GoalDocument,
MilestoneDocument,
UserMilestoneDocument,
PBScoreDocument,
} from "tachi-common";
import { Testing511Song, Testing511SPA } from "../../../../../../../../../test-utils/test-data";
t.test("GET /api/v1/users/:userID/games/:game/:playtype/", (t) => {
t.beforeEach(ResetDBState);
t.test("Should return a users statistics for that game.", async (t) => {
const res = await mockApi.get("/api/v1/users/zkldi/games/iidx/SP");
const res = await mockApi.get("/api/v1/users/test_zkldi/games/iidx/SP");
t.strictSame(res.body, {});
t.hasStrict(res.body, {
success: true,
description: "Retrieved user statistics for test_zkldi (iidx SP)",
body: {
gameStats: {
userID: 1,
game: "iidx",
playtype: "SP",
classes: {},
ratings: {},
},
firstScore: null,
mostRecentScore: null,
totalScores: 1,
rankingData: {
ranking: 1,
outOf: 1,
},
},
});
t.end();
});
t.test("Should return a users first score if one exists.", async (t) => {
await db.scores.insert([
{
userID: 1,
timeAchieved: 100,
scoreID: "foo",
game: "iidx",
playtype: "SP",
},
{
userID: 1,
timeAchieved: 200,
scoreID: "bar",
game: "iidx",
playtype: "SP",
},
{
userID: 1,
timeAchieved: 300,
scoreID: "baz",
game: "iidx",
playtype: "SP",
},
] as ScoreDocument[]);
const res = await mockApi.get("/api/v1/users/test_zkldi/games/iidx/SP");
t.hasStrict(res.body, {
success: true,
description: "Retrieved user statistics for test_zkldi (iidx SP)",
body: {
gameStats: {
userID: 1,
game: "iidx",
playtype: "SP",
classes: {},
ratings: {},
},
firstScore: {
timeAchieved: 100,
scoreID: "foo",
},
mostRecentScore: {
timeAchieved: 300,
scoreID: "baz",
},
totalScores: 4,
rankingData: {
ranking: 1,
outOf: 1,
},
},
});
t.end();
});
t.end();
});
t.test("GET /api/v1/users/:userID/games/:game/:playtype/goals", (t) => {
t.beforeEach(ResetDBState);
t.test(
"Should return all of a users goals, and only unachieved goals if the argument is set.",
async (t) => {
await db["user-goals"].insert([
{
goalID: "foo",
userID: 1,
game: "iidx",
playtype: "SP",
achieved: false,
},
{
goalID: "bar",
userID: 1,
game: "iidx",
playtype: "SP",
achieved: true,
},
] as UserGoalDocument[]);
await db.goals.insert([{ goalID: "foo" }, { goalID: "bar" }] as GoalDocument[]);
const res = await mockApi.get("/api/v1/users/1/games/iidx/SP/goals");
t.hasStrict(res.body, {
success: true,
description: "Successfully returned 2 goal(s).",
body: {
userGoals: [
{
goalID: "foo",
achieved: false,
},
{
goalID: "bar",
achieved: true,
},
],
goals: [{ goalID: "bar" }, { goalID: "foo" }],
},
});
const resUnachieved = await mockApi.get(
"/api/v1/users/1/games/iidx/SP/goals?unachieved=true"
);
t.hasStrict(resUnachieved.body, {
success: true,
description: "Successfully returned 1 goal(s).",
body: {
userGoals: [
{
goalID: "foo",
achieved: false,
},
],
goals: [{ goalID: "foo" }],
},
});
t.end();
}
);
t.end();
});
t.test("GET /api/v1/users/:userID/games/:game/:playtype/milestones", (t) => {
t.beforeEach(ResetDBState);
t.test(
"Should return all of a users milestones, and only unachieved milestones if the argument is set.",
async (t) => {
await db["user-milestones"].insert([
{
milestoneID: "foo",
userID: 1,
game: "iidx",
playtype: "SP",
achieved: false,
},
{
milestoneID: "bar",
userID: 1,
game: "iidx",
playtype: "SP",
achieved: true,
},
] as UserMilestoneDocument[]);
await db.milestones.insert([
{ milestoneID: "foo" },
{ milestoneID: "bar" },
] as MilestoneDocument[]);
const res = await mockApi.get("/api/v1/users/1/games/iidx/SP/milestones");
t.hasStrict(res.body, {
success: true,
description: "Successfully returned 2 milestone(s).",
body: {
userMilestones: [
{
milestoneID: "foo",
achieved: false,
},
{
milestoneID: "bar",
achieved: true,
},
],
milestones: [{ milestoneID: "bar" }, { milestoneID: "foo" }],
},
});
const resUnachieved = await mockApi.get(
"/api/v1/users/1/games/iidx/SP/milestones?unachieved=true"
);
t.hasStrict(resUnachieved.body, {
success: true,
description: "Successfully returned 1 milestone(s).",
body: {
userMilestones: [
{
milestoneID: "foo",
achieved: false,
},
],
milestones: [{ milestoneID: "foo" }],
},
});
t.end();
}
);
t.end();
});
t.test("GET /api/v1/users/:userID/games/:game/:playtype/recent-scores", (t) => {
t.beforeEach(ResetDBState);
t.test("Should return a users 100 most recent scores.", async (t) => {
const mockScores: ScoreDocument[] = [];
for (let i = 0; i < 200; i++) {
mockScores.push({
userID: 1,
game: "iidx",
playtype: "SP",
timeAchieved: i * 100,
scoreID: `scoreID_${i}`,
chartID: Testing511SPA.chartID,
songID: Testing511Song.id,
} as ScoreDocument);
}
await db.scores.insert(mockScores);
for (const sc of mockScores) {
delete sc._id; // lol
}
const res = await mockApi.get("/api/v1/users/test_zkldi/games/iidx/SP/recent-scores");
t.hasStrict(res.body, {
success: true,
description: "Retrieved 100 scores.",
body: {
scores: mockScores.slice(100).reverse(),
songs: [Testing511Song],
charts: [Testing511SPA],
},
});
t.end();
});
t.end();
});
t.test("GET /api/v1/users/:userID/games/:game/:playtype/best", (t) => {
t.beforeEach(ResetDBState);
t.test("Should return a users 100 most recent scores.", async (t) => {
const mockPBs: PBScoreDocument[] = [];
for (let i = 0; i < 200; i++) {
mockPBs.push({
userID: 1,
game: "iidx",
playtype: "SP",
isPrimary: true,
chartID: i.toString(), // hack to generate some random chartIDs
songID: Testing511Song.id,
calculatedData: {
ktRating: i,
},
} as PBScoreDocument);
}
await db["personal-bests"].insert(mockPBs);
for (const sc of mockPBs) {
delete sc._id; // lol
}
const res = await mockApi.get("/api/v1/users/test_zkldi/games/iidx/SP/best");
t.hasStrict(res.body, {
success: true,
description: "Retrieved 100 scores.",
body: {
scores: mockPBs.slice(100).reverse(),
songs: [Testing511Song],
charts: [],
},
});
t.end();
});
@@ -4,6 +4,8 @@ import { SYMBOL_TachiData } from "../../../../../../../../../lib/constants/tachi
import { GetRelevantSongsAndCharts } from "../../../../../../../../../utils/db";
import { GetDefaultScoreRatingAlg, GetUsersRanking } from "../../../../../../../../../utils/user";
import { CheckUserPlayedGamePlaytype } from "./middleware";
import { FilterQuery } from "mongodb";
import { UserGoalDocument, UserMilestoneDocument } from "tachi-common";
const router: Router = Router({ mergeParams: true });
@@ -30,6 +32,7 @@ router.get("/", async (req, res) => {
userID: user.id,
game,
playtype,
timeAchieved: { $ne: null },
},
{
sort: {
@@ -42,6 +45,7 @@ router.get("/", async (req, res) => {
userID: user.id,
game,
playtype,
timeAchieved: { $ne: null },
},
{
sort: {
@@ -76,12 +80,17 @@ router.get("/goals", async (req, res) => {
const game = req[SYMBOL_TachiData]!.game!;
const playtype = req[SYMBOL_TachiData]!.playtype!;
const userGoals = await db["user-goals"].find({
const query: FilterQuery<UserGoalDocument> = {
userID: user.id,
game,
playtype,
achieved: req.query.unachieved ? false : undefined,
});
};
if (req.query.unachieved) {
query.achieved = false;
}
const userGoals = await db["user-goals"].find(query);
const goals = await db.goals.find({
goalID: { $in: userGoals.map((e) => e.goalID) },
@@ -89,7 +98,7 @@ router.get("/goals", async (req, res) => {
return res.status(200).json({
success: true,
description: `Successfully returned ${userGoals.length} goals.`,
description: `Successfully returned ${userGoals.length} goal(s).`,
body: {
userGoals,
goals,
@@ -108,12 +117,17 @@ router.get("/milestones", async (req, res) => {
const game = req[SYMBOL_TachiData]!.game!;
const playtype = req[SYMBOL_TachiData]!.playtype!;
const userMilestones = await db["user-milestones"].find({
const query: FilterQuery<UserMilestoneDocument> = {
userID: user.id,
game,
playtype,
achieved: req.query.unachieved ? false : undefined,
});
};
if (req.query.unachieved) {
query.achieved = false;
}
const userMilestones = await db["user-milestones"].find(query);
const milestones = await db.milestones.find({
milestoneID: { $in: userMilestones.map((e) => e.milestoneID) },
@@ -121,7 +135,7 @@ router.get("/milestones", async (req, res) => {
return res.status(200).json({
success: true,
description: `Successfully returned ${userMilestones.length} milestones.`,
description: `Successfully returned ${userMilestones.length} milestone(s).`,
body: {
userMilestones,
milestones,
+1
View File
@@ -79,6 +79,7 @@ interface ExpressJSONErr extends SyntaxError {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const MAIN_ERR_HANDLER: express.ErrorRequestHandler = (err, req, res) => {
logger.info("foo");
if (err instanceof SyntaxError) {
const expErr: ExpressJSONErr = err as ExpressJSONErr;
if (expErr.status === 400 && "body" in expErr) {
@@ -1 +1,9 @@
[]
[
{
"userID": 1,
"game": "iidx",
"playtype": "SP",
"classes": {},
"ratings": {}
}
]
+14 -11
View File
@@ -163,16 +163,19 @@ export async function GetUsersRanking(stats: UserGameStats) {
},
},
{
total: { $sum: 1 },
ranking: {
$sum: {
$cond: {
if: {
// @ts-expect-error garbage...
$gte: [`$ratings.${ratingKey}`, stats.ratings[ratingKey]],
$group: {
_id: null,
outOf: { $sum: 1 },
ranking: {
$sum: {
$cond: {
if: {
// @ts-expect-error garbage...
$gte: [`$ratings.${ratingKey}`, stats.ratings[ratingKey]],
},
then: 1,
else: 0,
},
then: 1,
else: 0,
},
},
},
@@ -180,7 +183,7 @@ export async function GetUsersRanking(stats: UserGameStats) {
]);
return {
ranking: aggRes.ranking,
total: aggRes.total,
ranking: (aggRes[0].ranking + 1) as integer,
outOf: aggRes[0].outOf as integer,
};
}