From 39e6170427961d7a04185b1d72a446284902883e Mon Sep 17 00:00:00 2001 From: AJaccP <112334043+AJaccP@users.noreply.github.com> Date: Sat, 22 Jun 2024 22:46:39 +0530 Subject: [PATCH] Add submissions service find by UUID method (#17) * Added submissions service findByUuid method * linting * bug fix * fix failing tests * linting * small challenge deletion fix * Add factory tweak --------- Co-authored-by: Matthew M-B --- .../services/challenges-service.ts | 31 ++++- .../services/participants-service.ts | 38 +++++- .../services/submissions-service.ts | 110 +++++++++++++++--- .../factories/accessible-challenge-factory.ts | 4 +- .../services/challenges-service.test.ts | 37 ++++++ .../services/participants-service.test.ts | 42 +++++++ .../services/reviews-service.test.ts | 16 ++- .../services/submissions-service.test.ts | 52 +++++++++ 8 files changed, 300 insertions(+), 30 deletions(-) diff --git a/app/challenges-platform/services/challenges-service.ts b/app/challenges-platform/services/challenges-service.ts index 1b9accb..f702638 100644 --- a/app/challenges-platform/services/challenges-service.ts +++ b/app/challenges-platform/services/challenges-service.ts @@ -18,12 +18,39 @@ export const findByUuid = async ( .from(challenges) .where(eq(challenges.uuid, id)); - const transformer = challengesPlatform.findTransformer(result[0].type); - const challenge = transformer.newChallenge(result[0]); + if (result.length === 0) { + return Err(new Error("Challenge not found")); + } + + const challenge = await convert(result[0]); + + return Ok(challenge); +}; + +export const findById = async ( + id: number, +): Promise> => { + const result = await db + .select() + .from(challenges) + .where(eq(challenges.id, id)); + + if (result.length === 0) { + return Err(new Error("Challenge not found")); + } + + const challenge = await convert(result[0]); return Ok(challenge); }; +export const convert = async (result: any): Promise => { + const transformer = challengesPlatform.findTransformer(result.type); + const challenge = transformer.newChallenge(result); + + return challenge; +}; + export const create = async ({ title, body, diff --git a/app/challenges-platform/services/participants-service.ts b/app/challenges-platform/services/participants-service.ts index 2da5bb6..07dbe2f 100644 --- a/app/challenges-platform/services/participants-service.ts +++ b/app/challenges-platform/services/participants-service.ts @@ -17,13 +17,39 @@ export const findByUuid = async ( .from(participants) .where(eq(participants.uuid, id)); - const challenge = new Participant({ - id: result[0].id, - uuid: result[0].uuid, - email: result[0].email, - }); + if (result.length === 0) { + return Err(new Error("Participant not found")); + } + + const participant = await convert(result[0]); + + return Ok(participant); +}; + +export const findById = async ( + id: number, +): Promise> => { + const result = await db + .select() + .from(participants) + .where(eq(participants.id, id)); + + if (result.length === 0) { + return Err(new Error("Participant not found")); + } - return Ok(challenge); + const participant = await convert(result[0]); + + return Ok(participant); +}; + +export const convert = async (result: any): Promise => { + const participant = new Participant({ + id: result.id, + uuid: result.uuid, + email: result.email, + }); + return participant; }; export const findByEmail = async ( diff --git a/app/challenges-platform/services/submissions-service.ts b/app/challenges-platform/services/submissions-service.ts index 5f8fd4e..6fbbf02 100644 --- a/app/challenges-platform/services/submissions-service.ts +++ b/app/challenges-platform/services/submissions-service.ts @@ -1,31 +1,70 @@ +import { eq } from "drizzle-orm"; import { Ok, Err, Result } from "ts-results"; import { db } from "../../../db"; import { submissions } from "../../../db/schema"; import { uuid } from "../../../app/common"; -import { Submission } from "../models"; -import { ChallengesService, ParticipantsService } from "../services"; +import { Challenge, Participant, Submission } from "../models"; +import { + AccessibleChallengesService, + ChallengesService, + ParticipantsService, +} from "../services"; import { challengesPlatform } from ".."; -export const create = async ( - challengeId: string, - participantId: string, +export const findByUuid = async ( + id: string, type: string = "base", - metadata?: any, ): Promise> => { - const challengeResult = await ChallengesService.findByUuid(challengeId); + if (!uuid.isValid(id)) { + return Err(new Error("Invalid UUID")); + } + + const result = await db + .select() + .from(submissions) + .where(eq(submissions.uuid, id)); + + if (result.length === 0) { + return Err(new Error("Submission not found")); + } + + const record = result[0]; + if (!record.challengeId || !record.participantId) { + return Err(new Error("Invalid submission")); + } + + const challengeResult = await ChallengesService.findById(record.challengeId); if (!challengeResult.ok) { return Err(new Error("Failed to find challenge")); } - if (challengeResult.val.deleted === true) { - return Err(new Error("Challenge is deleted")); - } - const participantResult = await ParticipantsService.findByUuid(participantId); + + const participantResult = await ParticipantsService.findById( + record.participantId, + ); if (!participantResult.ok) { return Err(new Error("Failed to find participant")); } - // TODO: Validate that the participant is allowed to submit this challenge (available challenges) - // throw new Error("Participant is not allowed to submit this challenge") if not allowed (use participant-service) + const transformer = challengesPlatform.findTransformer(type); + const submission = transformer.newSubmission( + result[0], + challengeResult.val, + participantResult.val, + ); + + return Ok(submission); +}; + +export const create = async ( + challengeId: string, + participantId: string, + type: string = "base", + metadata?: any, +): Promise> => { + const result = await beforeCreate(challengeId, participantId); + if (!result.ok) return result; + + const [challenge, participant] = result.val; // TODO: switch on submission.challenge.evaluation // if submission.challenge.evaluation is MANUAL, than save it to the database @@ -45,19 +84,54 @@ export const create = async ( const result = await db .insert(submissions) .values({ - uuid: id, - challengeId: challengeResult.val.id, - participantId: participantResult.val.id, + uuid: id.toString(), + challengeId: challenge.id, + participantId: participant.id, }) .returning(); const submission = transformer.newSubmission( result[0], - challengeResult.val, - participantResult.val, + challenge, + participant, ); return Ok(submission); } catch (e) { return Err(new Error("Failed to create submission")); } }; + +//private +const beforeCreate = async ( + challengeId: string, + participantId: string, +): Promise> => { + const challengeResult = await ChallengesService.findByUuid(challengeId); + if (!challengeResult.ok) { + return Err(new Error("Failed to find challenge")); + } else if (challengeResult.val.deleted === true) { + return Err(new Error("Challenge is deleted")); + } + + const participantResult = await ParticipantsService.findByUuid(participantId); + if (!participantResult.ok) { + return Err(new Error("Failed to find participant")); + } + + const countResult = await AccessibleChallengesService.count( + challengeResult.val, + participantResult.val, + ); + if (!countResult.ok) { + return Err(new Error("Failed to count accessible challenges")); + } + + const count = countResult.val; + if (count === 0) { + return Err( + new Error("Participant is not allowed to submit this challenge"), + ); + } + + return Ok([challengeResult.val, participantResult.val]); +}; diff --git a/test/challenges-platform/factories/accessible-challenge-factory.ts b/test/challenges-platform/factories/accessible-challenge-factory.ts index 80b1b0e..c048c9b 100644 --- a/test/challenges-platform/factories/accessible-challenge-factory.ts +++ b/test/challenges-platform/factories/accessible-challenge-factory.ts @@ -13,7 +13,7 @@ export const accessibleChallengeFactory = async ({ }: { challenge?: Challenge; participant?: Participant; -} = {}): Promise => { +} = {}): Promise<[Challenge, Participant]> => { const c = challenge || (await challengeFactory()); const p = participant || (await participantFactory()); @@ -24,4 +24,6 @@ export const accessibleChallengeFactory = async ({ participantId: p.id, }) .returning(); + + return [c, p]; }; diff --git a/test/challenges-platform/services/challenges-service.test.ts b/test/challenges-platform/services/challenges-service.test.ts index aa47990..9e15a09 100644 --- a/test/challenges-platform/services/challenges-service.test.ts +++ b/test/challenges-platform/services/challenges-service.test.ts @@ -2,6 +2,8 @@ import { ChallengesService } from "../../../app/challenges-platform"; import { challengeFactory } from "../factories/challenge-factory"; import { CustomTransformer, CustomChallenge } from "../custom-transformer"; import { challengesPlatform } from "../../../app/challenges-platform"; +import { desc } from "drizzle-orm"; +import { uuid } from "../../../app/common"; describe("ChallengesService", () => { describe("findByUuid", () => { @@ -26,6 +28,41 @@ describe("ChallengesService", () => { expect(result.val.points).toBe(100); }); }); + + describe("when there is no record", () => { + it("returns an error", async () => { + const testUuid = uuid.create(); + const result = await ChallengesService.findByUuid(testUuid); + + expect(result.err).toBe(true); + expect(result.val.toString()).toBe("Error: Challenge not found"); + }); + }); + }); + + describe("findById", () => { + describe("when there is an existing record", () => { + it("returns the challenge", async () => { + const challenge = await challengeFactory(); + + const result = await ChallengesService.findById(challenge.id); + + if (!result.ok) fail("Expected result to be Ok"); + expect(result.val.title).toBe("Test Challenge"); + expect(result.val.body).toBe("This is a test challenge"); + expect(result.val.points).toBe(100); + }); + }); + + describe("when there is no record", () => { + it("returns an error", async () => { + const testId = -1; + const result = await ChallengesService.findById(testId); + + expect(result.err).toBe(true); + expect(result.val.toString()).toBe("Error: Challenge not found"); + }); + }); }); describe("create", () => { diff --git a/test/challenges-platform/services/participants-service.test.ts b/test/challenges-platform/services/participants-service.test.ts index 9121ff7..6bbdd58 100644 --- a/test/challenges-platform/services/participants-service.test.ts +++ b/test/challenges-platform/services/participants-service.test.ts @@ -1,5 +1,6 @@ import { ParticipantsService } from "../../../app/challenges-platform"; import { participantFactory } from "../factories/participant-factory"; +import { uuid } from "../../../app/common"; describe("ParticipantsService", () => { describe("create", () => { @@ -16,6 +17,15 @@ describe("ParticipantsService", () => { }); describe("findByUuid", () => { + describe("when the id is invalid", () => { + it("returns an error", async () => { + const result = await ParticipantsService.findByUuid("invalid-id"); + + expect(result.err).toBe(true); + expect(result.val.toString()).toBe("Error: Invalid UUID"); + }); + }); + describe("when there is an existing record", () => { it("returns the participant", async () => { const participant = await participantFactory(); @@ -25,6 +35,38 @@ describe("ParticipantsService", () => { expect(result.val.email).toBe(participant.email); }); }); + + describe("when there is no record", () => { + it("returns an error", async () => { + const testUuid = uuid.create(); + const result = await ParticipantsService.findByUuid(testUuid); + + expect(result.err).toBe(true); + expect(result.val.toString()).toBe("Error: Participant not found"); + }); + }); + }); + + describe("findById", () => { + describe("when there is an existing record", () => { + it("returns the participant", async () => { + const participant = await participantFactory(); + const result = await ParticipantsService.findById(participant.id); + + if (!result.ok) fail("Expected result to be Ok"); + expect(result.val.email).toBe(participant.email); + }); + }); + + describe("when there is no record", () => { + it("returns an error", async () => { + const testId = -1; + const result = await ParticipantsService.findById(testId); + + expect(result.err).toBe(true); + expect(result.val.toString()).toBe("Error: Participant not found"); + }); + }); }); describe("findByEmail", () => { diff --git a/test/challenges-platform/services/reviews-service.test.ts b/test/challenges-platform/services/reviews-service.test.ts index 5a91989..f891820 100644 --- a/test/challenges-platform/services/reviews-service.test.ts +++ b/test/challenges-platform/services/reviews-service.test.ts @@ -2,13 +2,20 @@ import { ReviewsService } from "../../../app/challenges-platform"; import { Status } from "../../../app/challenges-platform/models"; import { uuid } from "../../../app/common"; import { reviewFactory } from "../factories/review-factory"; +import { challengeFactory } from "../factories/challenge-factory"; +import { participantFactory } from "../factories/participant-factory"; import { submissionFactory } from "../factories/submission-factory"; +import { accessibleChallengeFactory } from "../factories/accessible-challenge-factory"; describe("ReviewsService", () => { describe("create", () => { describe("when submission exists", () => { it("succesfully creates a review", async () => { - const submission = await submissionFactory(); + const [challenge, participant] = await accessibleChallengeFactory(); + const submission = await submissionFactory({ + challenge: challenge, + participant: participant, + }); const body = "Nice work"; const result = await ReviewsService.create( @@ -40,7 +47,11 @@ describe("ReviewsService", () => { describe("when review exists", () => { it("returns the review", async () => { const body = "Nice work"; - const submission = await submissionFactory(); + const [challenge, participant] = await accessibleChallengeFactory(); + const submission = await submissionFactory({ + challenge: challenge, + participant: participant, + }); const review = await reviewFactory({ submission: submission, body: body, @@ -72,6 +83,5 @@ describe("ReviewsService", () => { expect(result.val.toString()).toBe("Error: Review not found"); }); }); - // implement test for when there is an existing record, need factory(?) + create method }); }); diff --git a/test/challenges-platform/services/submissions-service.test.ts b/test/challenges-platform/services/submissions-service.test.ts index a17a1ba..6c6132f 100644 --- a/test/challenges-platform/services/submissions-service.test.ts +++ b/test/challenges-platform/services/submissions-service.test.ts @@ -4,14 +4,64 @@ import { } from "../../../app/challenges-platform"; import { challengeFactory } from "../factories/challenge-factory"; import { participantFactory } from "../factories/participant-factory"; +import { accessibleChallengeFactory } from "../factories/accessible-challenge-factory"; +import { uuid } from "../../../app/common"; describe("SubmissionService", () => { + describe("findByUuid", () => { + describe("when the id is invalid", () => { + it("returns an error", async () => { + const result = await SubmissionService.findByUuid("invalid-id"); + + expect(result.err).toBe(true); + expect(result.val.toString()).toBe("Error: Invalid UUID"); + }); + }); + describe("when there is an existing record", () => { + it("returns the submission", async () => { + const challenge = await challengeFactory(); + const participant = await participantFactory(); + + const insert = await accessibleChallengeFactory({ + challenge, + participant, + }); + + const submission = await SubmissionService.create( + challenge.uuid, + participant.uuid, + ); + if (!submission.ok) fail("Expected submission to be Ok"); + + const result = await SubmissionService.findByUuid(submission.val.uuid); + + if (!result.ok) fail("Expected result to be Ok"); + expect(result.val.challenge.id).toBe(challenge.id); + expect(result.val.participant.id).toBe(participant.id); + }); + }); + describe("when there is no record", () => { + it("returns an error", async () => { + const testUuid = uuid.create(); + const result = await SubmissionService.findByUuid(testUuid); + + expect(result.err).toBe(true); + expect(result.val.toString()).toBe("Error: Submission not found"); + }); + }); + }); + describe("create", () => { describe("when challenge and participant exist", () => { it("succesfully creates a submission", async () => { const challenge = await challengeFactory(); const participant = await participantFactory(); + const insert = await accessibleChallengeFactory({ + challenge, + participant, + }); + const result = await SubmissionService.create( challenge.uuid, participant.uuid, @@ -35,6 +85,7 @@ describe("SubmissionService", () => { expect(result.val.toString()).toBe("Error: Failed to find challenge"); }); }); + describe("when challenge is deleted", () => { it("returns an error", async () => { const factory = await challengeFactory(); @@ -54,6 +105,7 @@ describe("SubmissionService", () => { expect(result.val.toString()).toBe("Error: Challenge is deleted"); }); }); + describe("when participant does not exist", () => { it("returns an error", async () => { const challenge = await challengeFactory();