fix(api): update backend project validation (#54218)

Co-authored-by: Sem Bauke <semboot699@gmail.com>
This commit is contained in:
Shaun Hamilton
2024-03-28 11:54:29 +02:00
committed by GitHub
parent 3c8b60d4f9
commit c333a74e77
3 changed files with 58 additions and 6 deletions
+27 -1
View File
@@ -362,7 +362,33 @@ describe('challengeRoutes', () => {
expect(response.body).toStrictEqual(
isValidChallengeCompletionErrorMsg
);
expect(response.statusCode).toBe(400);
expect(response.statusCode).toBe(403);
});
it('POST rejects backendProject requests without URL githubLinks', async () => {
const response = await superPost('/project-completed').send({
id: id1,
challengeType: challengeTypes.backEndProject,
// Solution is allowed to be localhost for backEndProject
solution: 'http://localhost:3000'
});
expect(response.body).toStrictEqual(
isValidChallengeCompletionErrorMsg
);
expect(response.statusCode).toBe(403);
const response_2 = await superPost('/project-completed').send({
id: id1,
challengeType: challengeTypes.backEndProject,
solution: 'http://localhost:3000',
githubLink: 'not-a-valid-url'
});
expect(response_2.body).toStrictEqual(
isValidChallengeCompletionErrorMsg
);
expect(response_2.statusCode).toBe(403);
});
it('POST rejects CodeRoad/CodeAlly projects when the user has not completed the required challenges', async () => {
+21
View File
@@ -2,6 +2,7 @@ import { type FastifyPluginCallbackTypebox } from '@fastify/type-provider-typebo
import jwt from 'jsonwebtoken';
import { uniqBy } from 'lodash';
import { CompletedExam, ExamResults } from '@prisma/client';
import isURL from 'validator/lib/isURL';
import { challengeTypes } from '../../../shared/config/challenge-types';
import { schemas } from '../schemas';
@@ -192,9 +193,28 @@ export const challengeRoutes: FastifyPluginCallbackTypebox = (
}
},
async (req, reply) => {
// TODO: considering validation is determined by `challengeType`, it should not come from the client
// Determine `challengeType` by `id`
const { id: projectId, challengeType, solution, githubLink } = req.body;
const userId = req.user?.id;
// If `backEndProject`:
// - `solution` needs to exist, but does not have to be valid URL
// - `githubLink` needs to exist and be valid URL
if (challengeType === challengeTypes.backEndProject) {
if (!solution || !isURL(githubLink + '')) {
return void reply.code(403).send({
type: 'error',
message: 'That does not appear to be a valid challenge submission.'
});
}
} else if (solution && !isURL(solution + '')) {
return void reply.code(403).send({
type: 'error',
message: 'That does not appear to be a valid challenge submission.'
});
}
try {
const user = await fastify.prisma.user.findUniqueOrThrow({
where: { id: userId },
@@ -221,6 +241,7 @@ export const challengeRoutes: FastifyPluginCallbackTypebox = (
const oldChallenge = user.completedChallenges?.find(
({ id }) => id === projectId
);
const updatedChallenge = {
challengeType,
solution,
+10 -5
View File
@@ -526,8 +526,8 @@ export const schemas = {
body: Type.Object({
id: Type.String({ format: 'objectid', maxLength: 24, minLength: 24 }),
challengeType: Type.Optional(Type.Number()),
solution: Type.String({ format: 'url', maxLength: 1024 }),
// TODO(Post-MVP): require format: 'url' for githubLink
// The solution must be a valid URL only if it is a `backEndProject`.
solution: Type.String({ maxLength: 1024 }),
githubLink: Type.Optional(Type.String())
}),
response: {
@@ -551,9 +551,14 @@ export const schemas = {
}),
403: Type.Object({
type: Type.Literal('error'),
message: Type.Literal(
'You have to complete the project before you can submit a URL.'
)
message: Type.Union([
Type.Literal(
'You have to complete the project before you can submit a URL.'
),
Type.Literal(
'That does not appear to be a valid challenge submission.'
)
])
}),
500: Type.Object({
message: Type.Literal(