Skip to content

Commit

Permalink
added: unit tests for /user/student/mystats endpoint.
Browse files Browse the repository at this point in the history
  • Loading branch information
mmoehabb committed Dec 19, 2024
1 parent 5afb23c commit 0d388d0
Show file tree
Hide file tree
Showing 5 changed files with 71 additions and 30 deletions.
7 changes: 2 additions & 5 deletions packages/atlas/src/user.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,11 +94,8 @@ export class User extends Base {
return await this.get({ route: `/api/v1/user/student/stats/${student}` });
}

/**
* used by studens to retrieve their learning stats
*/
async findMyStats(): Promise<IUser.FindMyStatsApiResponse> {
return await this.get(`/api/v1/user/student/mystats`);
async findPublicStudentStats(): Promise<IUser.FindPublicStudentStatsApiResponse> {
return await this.get({ route: `/api/v1/user/student/stats/public` });
}

async findTutorActivityScores(
Expand Down
2 changes: 1 addition & 1 deletion packages/types/src/user.ts
Original file line number Diff line number Diff line change
Expand Up @@ -206,7 +206,7 @@ export type FindStudentStatsApiResponse = {
};
};

export type FindMyStatsApiResponse = {
export type FindPublicStudentStatsApiResponse = {
/**
* Tutors that the student interacted with.
*/
Expand Down
34 changes: 12 additions & 22 deletions services/server/src/handlers/user.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,6 @@ import { sendBackgroundMessage } from "@/workers";
import { WorkerMessageType } from "@/workers/messages";
import { isValidPassword } from "@litespace/sol/verification";
import { selectTutorRuleEvents } from "@/lib/events";
import { FindMyStatsApiResponse, Gender } from "@litespace/types/dist/esm/user";
import { isTutor, isTutorManager } from "@litespace/auth/dist/authorization";

const createUserPayload = zod.object({
Expand Down Expand Up @@ -415,7 +414,7 @@ async function findOnboardedTutors(req: Request, res: Response) {
// online state, and notice.
const user = req.user;
const userGender =
isUser(user) && user.gender ? (user.gender as Gender) : undefined;
isUser(user) && user.gender ? (user.gender as IUser.Gender) : undefined;

const filtered = query.search
? tutors.filter((tutor) => {
Expand Down Expand Up @@ -653,7 +652,7 @@ async function findStudentStats(
res.status(200).json(response);
}

async function findMyStats(
async function findPublicStudentStats(
req: Request,
res: Response,
next: NextFunction
Expand All @@ -666,45 +665,36 @@ async function findMyStats(
const studentData = await users.findById(id);
if (!studentData) return next(notfound.student());

const nowdate = dayjs.utc().toISOString();
const now = dayjs.utc().toISOString();

const tutorCount = await lessons.countCounterpartMembers({
user: id,
ratified: true,
canceled: true,
canceled: false,
});

const completedLessonCount = await lessons.countLessons({
users: [id],
ratified: true,
canceled: false,
before: nowdate
before: now
});

const totalMinutes = await lessons.sumDuration({
const upcomingLessonCount = await lessons.countLessons({
users: [id],
ratified: true,
canceled: true,
before: nowdate
});

const canceledMinutes = await lessons.sumDuration({
users: [id],
ratified: false,
canceled: true,
before: nowdate
canceled: false,
after: now,
});

const totalLearningTime = totalMinutes - canceledMinutes;

const upcomingLessonCount = await lessons.countLessons({
const totalLearningTime = await lessons.sumDuration({
users: [id],
ratified: true,
canceled: false,
after: nowdate,
before: now
});

const response: FindMyStatsApiResponse = {
const response: IUser.FindPublicStudentStatsApiResponse = {
tutorCount,
completedLessonCount,
totalLearningTime,
Expand Down Expand Up @@ -759,5 +749,5 @@ export default {
findTutorActivityScores: safeRequest(findTutorActivityScores),
findTutorsForMediaProvider: safeRequest(findTutorsForMediaProvider),
findStudentStats: safeRequest(findStudentStats),
findMyStats: safeRequest(findMyStats),
findPublicStudentStats: safeRequest(findPublicStudentStats),
};
2 changes: 1 addition & 1 deletion services/server/src/routes/user.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@ export default function router(context: ApiContext) {
router.get("/tutor/list/onboarded", user.findOnboardedTutors);
router.get("/tutor/stats/:tutor", user.findTutorStats);
router.get("/tutor/activity/:tutor", user.findTutorActivityScores);
router.get("/student/stats/public", user.findPublicStudentStats);
router.get("/student/stats/:student", user.findStudentStats);
router.get("/student/mystats", user.findMyStats);

return router;
}
56 changes: 55 additions & 1 deletion services/server/tests/api/user.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { cache } from "@/lib/cache";
import { tutors, users } from "@litespace/models";
import { Role } from "@litespace/types/dist/esm/user";
import { first } from "lodash";
import { notfound } from "@/lib/error";
import { forbidden, notfound } from "@/lib/error";

describe("/api/v1/user/", () => {
beforeEach(async () => {
Expand Down Expand Up @@ -334,4 +334,58 @@ describe("/api/v1/user/", () => {
expect(res).to.be.deep.eq(notfound.tutor());
});
});

describe("GET /api/v1/user/student/stats/public", () => {
beforeEach(async () => {
await flush();
});

it("should retrieve student stats by current logged-in user id.", async () => {
const studentApi = await Api.forStudent();
const student = await studentApi.findCurrentUser();

const rule1 = await db.rule({
userId: student.user.id,
start: dayjs.utc().subtract(2, "days").toISOString(),
})
const rule2 = await db.rule({
userId: student.user.id,
start: dayjs.utc().add(2, "days").toISOString(),
})
const rule3 = await db.rule({
userId: student.user.id,
start: dayjs.utc().add(3, "days").toISOString(),
})

const lesson1 = await db.lesson({
student: student.user.id,
rule: rule1.id,
start: rule1.start,
});

await db.lesson({
student: student.user.id,
rule: rule2.id,
});

await db.lesson({
student: student.user.id,
rule: rule3.id,
canceled: true,
});

const res = await studentApi.atlas.user.findPublicStudentStats();

expect(res.tutorCount).to.eq(2);
expect(res.totalLearningTime).to.eq(lesson1.lesson.duration);
expect(res.upcomingLessonCount).to.eq(1);
expect(res.completedLessonCount).to.eq(1);
});

it("should respond with forbidden if the user is not a student.", async () => {
const tutorApi = await Api.forTutor();
const res = await safe(async () => tutorApi.atlas.user.findPublicStudentStats());
expect(res).to.deep.eq(forbidden())
});
});
});

0 comments on commit 0d388d0

Please sign in to comment.