Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: only 1 coach is allowed per 1-player-tournament team #157

Merged
merged 3 commits into from
Oct 17, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .mocharc.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,4 @@ spec: 'tests/**/*.test.ts'

watch-extensions: ts

timeout: 10000
timeout: 30000
15 changes: 13 additions & 2 deletions src/controllers/admin/tournaments/updateTournament.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import Joi from 'joi';
import { Request, Response, NextFunction } from 'express';
import { hasPermission } from '../../../middlewares/authentication';
import { validateBody } from '../../../middlewares/validation';
import { notFound, success } from '../../../utils/responses';
import { conflict, notFound, success } from '../../../utils/responses';
import { Error, Permission } from '../../../types';
import { fetchTournaments, updateTournament } from '../../../operations/tournament';
import { addCasterToTournament, removeAllCastersFromTournament } from '../../../operations/caster';
Expand Down Expand Up @@ -30,16 +30,27 @@ export default [
// Controller
async (request: Request, response: Response, next: NextFunction) => {
try {
if (!(await fetchTournaments()).some((tournament) => tournament.id === request.params.tournamentId)) {
const allTournaments = await fetchTournaments();
if (!allTournaments.some((tournament) => tournament.id === request.params.tournamentId)) {
return notFound(response, Error.NotFound);
}

if (
request.body.name &&
allTournaments.some(
(tournament) => tournament.name === request.body.name && tournament.id !== request.params.tournamentId,
)
) {
return conflict(response, Error.TournamentNameAlreadyExists);
}

if (request.body.casters && request.body.casters.length > 0) {
await removeAllCastersFromTournament(request.params.tournamentId as string);

for (const casterName of request.body.casters) {
await addCasterToTournament(request.params.tournamentId as string, casterName);
}
request.body.casters = undefined;
}

const result = await updateTournament(request.params.tournamentId as string, request.body);
Expand Down
4 changes: 4 additions & 0 deletions src/controllers/teams/acceptRequest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,10 @@ export default [
return forbidden(response, Error.TeamFull);
}

if (team.coaches.length >= Math.min(tournament.playersPerTeam, 2) && askingUser.type === UserType.coach) {
return forbidden(response, Error.TeamMaxCoachReached);
}

await joinTeam(team.id, askingUser, askingUser.type);

const updatedTeam = await fetchTeam(team.id);
Expand Down
3 changes: 0 additions & 3 deletions src/controllers/teams/createTeamRequest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,9 +47,6 @@ export default [

return success(response, filterUser(updatedUser));
} catch (error) {
// This may happen when max coach amount is reached already
if (error.code === 'API_COACH_MAX_TEAM') return forbidden(response, ResponseError.TeamMaxCoachReached);

return next(error);
}
},
Expand Down
11 changes: 1 addition & 10 deletions src/operations/team.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,10 @@ import {
PrimitiveTeamWithPartialTournament,
} from '../types';
import nanoid from '../utils/nanoid';
import { countCoaches, formatUser, userInclusions } from './user';
import { formatUser, userInclusions } from './user';
import { setupDiscordTeam } from '../utils/discord';
import { fetchTournament } from './tournament';

const teamMaxCoachCount = 2;

const teamInclusions = {
users: {
include: userInclusions,
Expand Down Expand Up @@ -205,13 +203,6 @@ export const updateTeam = async (teamId: string, name: string): Promise<Team> =>
};

export const askJoinTeam = async (teamId: string, userId: string, userType: UserType) => {
// We check the amount of coaches at that point
const teamCoachCount = await countCoaches(teamId);
if (userType === UserType.coach && teamCoachCount >= teamMaxCoachCount)
throw Object.assign(new Error('Query cannot be executed: max count of coach reached already'), {
code: 'API_COACH_MAX_TEAM',
});

// Then we create the join request when it is alright
const updatedUser = await database.user.update({
data: {
Expand Down
2 changes: 1 addition & 1 deletion src/operations/tournament.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ export const updateTournament = (
): PrismaPromise<PrimitiveTournament> =>
database.tournament.update({
where: { id },
data: { ...data, casters: undefined },
data: { ...data },
});

export const updateTournamentsPosition = (tournaments: { id: string; position: number }[]) =>
Expand Down
1 change: 1 addition & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -343,6 +343,7 @@ export const enum Error {
TeamAlreadyExists = "Le nom de l'équipe existe déjà",
PlaceAlreadyAttributed = 'Cette place est déjà attribuée',
DiscordAccountAlreadyUsed = 'Ce compte discord est déjà lié à un compte',
TournamentNameAlreadyExists = 'Un tournoi a déjà ce nom',

// 410
// indicates that access to the target resource is no longer available at the server.
Expand Down
6 changes: 1 addition & 5 deletions tests/admin/carts/refund.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,7 @@ import * as userOperations from '../../../src/operations/user';
import { sandbox } from '../../setup';
import { generateToken } from '../../../src/utils/users';

// eslint-disable-next-line func-names
describe('POST /admin/carts/:cartId/refund', function () {
// Setup is slow
this.timeout(30000);

describe('POST /admin/carts/:cartId/refund', () => {
let user: User;
let admin: User;
let adminToken: string;
Expand Down
12 changes: 10 additions & 2 deletions tests/admin/tournaments/updateTournament.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ describe('PATCH /admin/tournaments/{tournamentId}', () => {
let tournament: Tournament;

const validBody = {
name: 'test',
name: 'anothertestname',
maxPlayers: 100,
playersPerTeam: 5,
cashprize: 100,
Expand All @@ -39,7 +39,7 @@ describe('PATCH /admin/tournaments/{tournamentId}', () => {
nonAdminUser = await createFakeUser();
adminToken = generateToken(admin);

tournament = await createFakeTournament({ id: 'test', name: 'Dragibus', maxTeams: 1, playersPerTeam: 1 });
tournament = await createFakeTournament({ id: 'test', name: 'test', playersPerTeam: 1, maxTeams: 1 });
});

it('should error as the user is not authenticated', () =>
Expand Down Expand Up @@ -75,6 +75,14 @@ describe('PATCH /admin/tournaments/{tournamentId}', () => {
.expect(404, { error: Error.NotFound });
});

it('should fail as a tournament already has this name', async () => {
await request(app)
.patch(`/admin/tournaments/${tournament.id}`)
.send({ name: 'Pokémon' })
.set('Authorization', `Bearer ${adminToken}`)
.expect(409, { error: Error.TournamentNameAlreadyExists });
});

it('should successfully update the tournament', async () => {
await request(app)
.patch(`/admin/tournaments/${tournament.id}`)
Expand Down
6 changes: 1 addition & 5 deletions tests/admin/users/forcePay.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,7 @@ import * as teamOperations from '../../../src/operations/team';
import * as userOperations from '../../../src/operations/user';
import * as tournamentOperations from '../../../src/operations/tournament';

// eslint-disable-next-line func-names
describe('POST /admin/users/:userId/force-pay', function () {
// The setup is slow
this.timeout(30000);

describe('POST /admin/users/:userId/force-pay', () => {
let user: User;
let admin: User;
let adminToken: string;
Expand Down
6 changes: 1 addition & 5 deletions tests/etupay/callback.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,11 +77,7 @@ const createCartAndPayload = async (
};
};

// eslint-disable-next-line func-names
describe('POST /etupay/callback', function () {
// The setup is slow
this.timeout(30000);

describe('POST /etupay/callback', () => {
let cart: Cart;
let paidPayload: string;
let refusedPayload: string;
Expand Down
69 changes: 57 additions & 12 deletions tests/teams/acceptRequest.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,20 @@ import { Error, Team, User, UserType } from '../../src/types';
import { createFakeUser, createFakeTeam } from '../utils';
import { generateToken } from '../../src/utils/users';
import { getCaptain } from '../../src/utils/teams';
import { fetchUser } from '../../src/operations/user';

// eslint-disable-next-line func-names
describe('POST /teams/current/join-requests/:userId', function () {
// Setup is slow
this.timeout(30000);

describe('POST /teams/current/join-requests/:userId', () => {
let user: User;
let user2: User;
let team: Team;
let captain: User;
let token: string;
let fullTeam: Team;
let fullCaptain: User;
let fullToken: string;
let onePlayerTeam: Team;
let onePlayerTeamCaptain: User;
let onePlayerTeamToken: string;

before(async () => {
const tournament = await tournamentOperations.fetchTournament('lol');
Expand All @@ -30,6 +33,9 @@ describe('POST /teams/current/join-requests/:userId', function () {
user2 = await createFakeUser({ paid: true, type: UserType.player });
await teamOperations.askJoinTeam(team.id, user.id, UserType.player);
await teamOperations.askJoinTeam(team.id, user2.id, UserType.player);
fullTeam = await createFakeTeam({ members: tournament.playersPerTeam, paid: true });
fullCaptain = getCaptain(fullTeam);
fullToken = generateToken(fullCaptain);
// Fill the tournament
// Store the promises
const promises = [];
Expand All @@ -42,6 +48,10 @@ describe('POST /teams/current/join-requests/:userId', function () {

captain = getCaptain(team);
token = generateToken(captain);

onePlayerTeam = await createFakeTeam({ tournament: 'pokemon' });
onePlayerTeamCaptain = getCaptain(onePlayerTeam);
onePlayerTeamToken = generateToken(onePlayerTeamCaptain);
});

after(async () => {
Expand Down Expand Up @@ -101,11 +111,7 @@ describe('POST /teams/current/join-requests/:userId', function () {
});

it('should fail as the team is full', async () => {
const fullTeam = await createFakeTeam({ members: 5 });
const otherUser = await createFakeUser();

const fullCaptain = getCaptain(fullTeam);
const fullToken = generateToken(fullCaptain);
await teamOperations.askJoinTeam(fullTeam.id, otherUser.id, UserType.player);

await request(app)
Expand All @@ -115,17 +121,56 @@ describe('POST /teams/current/join-requests/:userId', function () {
});

it('should succeed to join a full team as a coach', async () => {
const fullTeam = await createFakeTeam({ members: 5 });
const otherUser = await createFakeUser();

const fullCaptain = getCaptain(fullTeam);
const fullToken = generateToken(fullCaptain);
await teamOperations.askJoinTeam(fullTeam.id, otherUser.id, UserType.coach);

await request(app)
.post(`/teams/current/join-requests/${otherUser.id}`)
.set('Authorization', `Bearer ${fullToken}`)
.expect(200);

const databaseUser = await fetchUser(otherUser.id);
expect(databaseUser.teamId).to.be.equal(fullTeam.id);
});

it('should fail to join the team as a coach because there are already 2 coaches', async () => {
// There is only one coach for the moment
const coach = await createFakeUser();
await teamOperations.joinTeam(fullTeam.id, coach, UserType.coach);
const willNotJoinCoach = await createFakeUser();
await teamOperations.askJoinTeam(fullTeam.id, willNotJoinCoach.id, UserType.coach);
await request(app)
.post(`/teams/current/join-requests/${willNotJoinCoach.id}`)
.set('Authorization', `Bearer ${fullToken}`)
.expect(403, { error: Error.TeamMaxCoachReached });

const databaseCoach = await fetchUser(willNotJoinCoach.id);
expect(databaseCoach.teamId).to.be.null;
});

it('should successfully join the 1-player team as a coach', async () => {
const coach = await createFakeUser();
await teamOperations.askJoinTeam(onePlayerTeam.id, coach.id, UserType.coach);
await request(app)
.post(`/teams/current/join-requests/${coach.id}`)
.set('Authorization', `Bearer ${onePlayerTeamToken}`)
.expect(200);

const databaseCoach = await fetchUser(coach.id);
expect(databaseCoach.teamId).to.be.equal(onePlayerTeam.id);
});

it('should fail to join the 1-player team as a coach because there is already a coach', async () => {
const coach = await createFakeUser();
await teamOperations.askJoinTeam(onePlayerTeam.id, coach.id, UserType.coach);
await request(app)
.post(`/teams/current/join-requests/${coach.id}`)
.set('Authorization', `Bearer ${onePlayerTeamToken}`)
.expect(403, { error: Error.TeamMaxCoachReached });

const databaseCoach = await fetchUser(coach.id);
expect(databaseCoach.teamId).to.be.null;
});

it('should successfully join the team and not lock the team as it is not full', async () => {
Expand Down
33 changes: 0 additions & 33 deletions tests/teams/createTeamRequest.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,39 +54,6 @@ describe('POST /teams/:teamId/join-requests', () => {
.expect(403, { error: Error.AlreadyInTeam });
});

it('should fail because coach limit is already reached (coach team members)', async () => {
const otherTeam = await createFakeTeam({ members: 2 });
const [user1, user2] = otherTeam.players;
await updateAdminUser(user1.id, { type: UserType.coach });
await updateAdminUser(user2.id, { type: UserType.coach });

const otherCoach = await createFakeUser();
const otherToken = generateToken(otherCoach);

return request(app)
.post(`/teams/${otherTeam.id}/join-requests`)
.send({ userType: UserType.coach })
.set('Authorization', `Bearer ${otherToken}`)
.expect(403, { error: Error.TeamMaxCoachReached });
});

it('should fail because coach limit is already reached (coach team member requests)', async () => {
const otherTeam = await createFakeTeam();
const [user1] = otherTeam.players;
const user2 = await createFakeUser();
await updateAdminUser(user1.id, { type: UserType.coach });
await teamOperations.askJoinTeam(otherTeam.id, user2.id, UserType.coach);

const otherCoach = await createFakeUser();
const otherToken = generateToken(otherCoach);

return request(app)
.post(`/teams/${otherTeam.id}/join-requests`)
.send({ userType: UserType.coach })
.set('Authorization', `Bearer ${otherToken}`)
.expect(403, { error: Error.TeamMaxCoachReached });
});

it('should fail because the user is a spectator', async () => {
const spectator = await createFakeUser({ type: UserType.spectator });
const spectatorToken = generateToken(spectator);
Expand Down
6 changes: 1 addition & 5 deletions tests/teams/deleteTeam.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,7 @@ import { createFakeTeam, createFakeUser } from '../utils';
import { generateToken } from '../../src/utils/users';
import { getCaptain } from '../../src/utils/teams';

// eslint-disable-next-line func-names
describe('DELETE /teams/current', function () {
// Setup is slow
this.timeout(30000);

describe('DELETE /teams/current', () => {
let captain: User;
let team: Team;
let lockedTeam: Team;
Expand Down
Loading