Skip to content

Commit 28e1126

Browse files
bernhaaardjb-cc
authored andcommitted
feat(backend): add request state param to count endpoint (SCRUM-XXX) (#202)
1 parent e8605e6 commit 28e1126

File tree

8 files changed

+160
-101
lines changed

8 files changed

+160
-101
lines changed

backend/OpenAPI.json

Lines changed: 27 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1617,42 +1617,57 @@
16171617
]
16181618
}
16191619
},
1620-
"/supervision-requests/pending-count/{userId}": {
1620+
"/supervision-requests/count/{userId}": {
16211621
"get": {
1622-
"description": "Returns the count of pending supervision requests for a specific user. Requesting a student gets their outgoing requests, requesting a supervisor gets their incoming requests.",
1623-
"operationId": "SupervisionRequestsController_getPendingRequestCountForUser",
1622+
"description": "Returns the count of supervision requests for a specific user filtered by state. Requesting a student gets their outgoing requests, requesting a supervisor gets their incoming requests.",
1623+
"operationId": "SupervisionRequestsController_getRequestCountForUser",
16241624
"parameters": [
16251625
{
16261626
"name": "userId",
16271627
"required": true,
16281628
"in": "path",
1629-
"description": "ID of the user to get pending request count for",
1629+
"description": "ID of the user to get request count for",
16301630
"schema": {
16311631
"format": "uuid",
16321632
"example": "123e4567-e89b-12d3-a456-426614174000",
16331633
"type": "string"
16341634
}
1635+
},
1636+
{
1637+
"name": "request_state",
1638+
"required": true,
1639+
"in": "query",
1640+
"description": "The request state to count",
1641+
"schema": {
1642+
"enum": [
1643+
"PENDING",
1644+
"ACCEPTED",
1645+
"REJECTED",
1646+
"WITHDRAWN"
1647+
],
1648+
"type": "string"
1649+
}
16351650
}
16361651
],
16371652
"responses": {
16381653
"200": {
1639-
"description": "Returns the count of pending supervision requests",
1654+
"description": "Returns the count of supervision requests",
16401655
"content": {
16411656
"application/json": {
16421657
"schema": {
1643-
"$ref": "#/components/schemas/PendingRequestCountEntity"
1658+
"$ref": "#/components/schemas/SupervisionRequestCountEntity"
16441659
}
16451660
}
16461661
}
16471662
},
16481663
"400": {
1649-
"description": "Bad request - Invalid UUID or admin user requested"
1664+
"description": "Bad request - Invalid UUID, admin user requested, or missing request state"
16501665
},
16511666
"404": {
16521667
"description": "User not found"
16531668
}
16541669
},
1655-
"summary": "Get pending supervision request count for a user",
1670+
"summary": "Get supervision request count for a user by state",
16561671
"tags": [
16571672
"supervision-requests"
16581673
]
@@ -2877,18 +2892,18 @@
28772892
"request_state"
28782893
]
28792894
},
2880-
"PendingRequestCountEntity": {
2895+
"SupervisionRequestCountEntity": {
28812896
"type": "object",
28822897
"properties": {
2883-
"pending_count": {
2898+
"request_count": {
28842899
"type": "number",
2885-
"description": "Number of pending supervision requests for the user",
2900+
"description": "Number of supervision requests for the user with the specified state",
28862901
"example": 5,
28872902
"minimum": 0
28882903
}
28892904
},
28902905
"required": [
2891-
"pending_count"
2906+
"request_count"
28922907
]
28932908
}
28942909
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import { ApiProperty } from '@nestjs/swagger';
2+
import { IsEnum, IsNotEmpty } from 'class-validator';
3+
import { RequestState } from '@prisma/client';
4+
5+
export class CountQueryDto {
6+
@ApiProperty({
7+
description: 'The request state to count',
8+
enum: RequestState,
9+
example: RequestState.PENDING,
10+
})
11+
@IsEnum(RequestState, { message: 'Must be a valid request state' })
12+
@IsNotEmpty({ message: 'Request state is required' })
13+
request_state: RequestState;
14+
}

backend/src/modules/requests/supervision/entities/pending-request-count.entity.ts

Lines changed: 0 additions & 10 deletions
This file was deleted.
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import { ApiProperty } from '@nestjs/swagger';
2+
3+
export class SupervisionRequestCountEntity {
4+
@ApiProperty({
5+
description: 'Number of supervision requests for the user with the specified state',
6+
example: 5,
7+
minimum: 0,
8+
})
9+
request_count: number;
10+
}

backend/src/modules/requests/supervision/supervision-requests.controller.spec.ts

Lines changed: 42 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { RequestState, Role, User } from '@prisma/client';
55
import { CreateSupervisionRequestDto } from './dto/create-supervision-request.dto';
66
import { UpdateSupervisionRequestDto } from './dto/update-supervision-request.dto';
77
import { SupervisionRequestQueryDto } from './dto/supervision-request-query.dto';
8-
import { PendingRequestCountEntity } from './entities/pending-request-count.entity';
8+
import { SupervisionRequestCountEntity } from './entities/supervision-request-count.entity';
99
import { AdminSupervisionRequestException } from '../../../common/exceptions/custom-exceptions/admin-supervision-request.exception';
1010
import { BadRequestException, ForbiddenException, NotFoundException } from '@nestjs/common';
1111
import { StudentAlreadyHasAnAcceptedSupervisionRequestException } from '../../../common/exceptions/custom-exceptions/multiple-supervision-acceptances.exception';
@@ -19,7 +19,7 @@ describe('SupervisionRequestsController', () => {
1919
findAllRequests: jest.fn(),
2020
findRequestById: jest.fn(),
2121
updateRequestState: jest.fn(),
22-
countPendingRequestsForUser: jest.fn(),
22+
countRequestsForUser: jest.fn(),
2323
};
2424

2525
// Sample test data with proper UUIDs
@@ -381,98 +381,104 @@ describe('SupervisionRequestsController', () => {
381381
});
382382
});
383383

384-
describe('getPendingRequestCountForUser', () => {
384+
describe('getRequestCountForUser', () => {
385385
it('should return pending request count for a student user', async () => {
386386
// Arrange
387-
const expectedResponse: PendingRequestCountEntity = { pending_count: 3 };
388-
mockSupervisionRequestsService.countPendingRequestsForUser.mockResolvedValue(
389-
expectedResponse,
390-
);
387+
const expectedResponse: SupervisionRequestCountEntity = { request_count: 3 };
388+
const queryParams = { request_state: RequestState.PENDING };
389+
mockSupervisionRequestsService.countRequestsForUser.mockResolvedValue(expectedResponse);
391390

392391
// Act
393-
const result = await controller.getPendingRequestCountForUser(STUDENT_USER_UUID);
392+
const result = await controller.getRequestCountForUser(STUDENT_USER_UUID, queryParams);
394393

395394
// Assert
396395
expect(result).toEqual(expectedResponse);
397-
expect(mockSupervisionRequestsService.countPendingRequestsForUser).toHaveBeenCalledWith(
396+
expect(mockSupervisionRequestsService.countRequestsForUser).toHaveBeenCalledWith(
398397
STUDENT_USER_UUID,
398+
RequestState.PENDING,
399399
);
400400
});
401401

402-
it('should return pending request count for a supervisor user', async () => {
402+
it('should return accepted request count for a supervisor user', async () => {
403403
// Arrange
404-
const expectedResponse: PendingRequestCountEntity = { pending_count: 5 };
405-
mockSupervisionRequestsService.countPendingRequestsForUser.mockResolvedValue(
406-
expectedResponse,
407-
);
404+
const expectedResponse: SupervisionRequestCountEntity = { request_count: 5 };
405+
const queryParams = { request_state: RequestState.ACCEPTED };
406+
mockSupervisionRequestsService.countRequestsForUser.mockResolvedValue(expectedResponse);
408407

409408
// Act
410-
const result = await controller.getPendingRequestCountForUser(SUPERVISOR_USER_UUID);
409+
const result = await controller.getRequestCountForUser(SUPERVISOR_USER_UUID, queryParams);
411410

412411
// Assert
413412
expect(result).toEqual(expectedResponse);
414-
expect(mockSupervisionRequestsService.countPendingRequestsForUser).toHaveBeenCalledWith(
413+
expect(mockSupervisionRequestsService.countRequestsForUser).toHaveBeenCalledWith(
415414
SUPERVISOR_USER_UUID,
415+
RequestState.ACCEPTED,
416416
);
417417
});
418418

419-
it('should return 0 pending requests when user has none', async () => {
419+
it('should return 0 requests when user has none', async () => {
420420
// Arrange
421-
const expectedResponse: PendingRequestCountEntity = { pending_count: 0 };
422-
mockSupervisionRequestsService.countPendingRequestsForUser.mockResolvedValue(
423-
expectedResponse,
424-
);
421+
const expectedResponse: SupervisionRequestCountEntity = { request_count: 0 };
422+
const queryParams = { request_state: RequestState.REJECTED };
423+
mockSupervisionRequestsService.countRequestsForUser.mockResolvedValue(expectedResponse);
425424

426425
// Act
427-
const result = await controller.getPendingRequestCountForUser(STUDENT_USER_UUID);
426+
const result = await controller.getRequestCountForUser(STUDENT_USER_UUID, queryParams);
428427

429428
// Assert
430429
expect(result).toEqual(expectedResponse);
431-
expect(mockSupervisionRequestsService.countPendingRequestsForUser).toHaveBeenCalledWith(
430+
expect(mockSupervisionRequestsService.countRequestsForUser).toHaveBeenCalledWith(
432431
STUDENT_USER_UUID,
432+
RequestState.REJECTED,
433433
);
434434
});
435435

436436
it('should pass through NotFoundException when user is not found', async () => {
437437
// Arrange
438438
const nonExistentUserId = 'non-existent-user-id';
439+
const queryParams = { request_state: RequestState.PENDING };
439440
const expectedError = new NotFoundException(`User with ID ${nonExistentUserId} not found`);
440-
mockSupervisionRequestsService.countPendingRequestsForUser.mockRejectedValue(expectedError);
441+
mockSupervisionRequestsService.countRequestsForUser.mockRejectedValue(expectedError);
441442

442443
// Act & Assert
443-
await expect(controller.getPendingRequestCountForUser(nonExistentUserId)).rejects.toThrow(
444-
expectedError,
445-
);
446-
expect(mockSupervisionRequestsService.countPendingRequestsForUser).toHaveBeenCalledWith(
444+
await expect(
445+
controller.getRequestCountForUser(nonExistentUserId, queryParams),
446+
).rejects.toThrow(expectedError);
447+
expect(mockSupervisionRequestsService.countRequestsForUser).toHaveBeenCalledWith(
447448
nonExistentUserId,
449+
RequestState.PENDING,
448450
);
449451
});
450452

451453
it('should pass through AdminSupervisionRequestException when admin user is requested', async () => {
452454
// Arrange
455+
const queryParams = { request_state: RequestState.PENDING };
453456
const expectedError = new AdminSupervisionRequestException();
454-
mockSupervisionRequestsService.countPendingRequestsForUser.mockRejectedValue(expectedError);
457+
mockSupervisionRequestsService.countRequestsForUser.mockRejectedValue(expectedError);
455458

456459
// Act & Assert
457-
await expect(controller.getPendingRequestCountForUser(ADMIN_USER_UUID)).rejects.toThrow(
460+
await expect(controller.getRequestCountForUser(ADMIN_USER_UUID, queryParams)).rejects.toThrow(
458461
expectedError,
459462
);
460-
expect(mockSupervisionRequestsService.countPendingRequestsForUser).toHaveBeenCalledWith(
463+
expect(mockSupervisionRequestsService.countRequestsForUser).toHaveBeenCalledWith(
461464
ADMIN_USER_UUID,
465+
RequestState.PENDING,
462466
);
463467
});
464468

465469
it('should pass through any service errors', async () => {
466470
// Arrange
471+
const queryParams = { request_state: RequestState.WITHDRAWN };
467472
const serviceError = new Error('Database connection failed');
468-
mockSupervisionRequestsService.countPendingRequestsForUser.mockRejectedValue(serviceError);
473+
mockSupervisionRequestsService.countRequestsForUser.mockRejectedValue(serviceError);
469474

470475
// Act & Assert
471-
await expect(controller.getPendingRequestCountForUser(STUDENT_USER_UUID)).rejects.toThrow(
472-
'Database connection failed',
473-
);
474-
expect(mockSupervisionRequestsService.countPendingRequestsForUser).toHaveBeenCalledWith(
476+
await expect(
477+
controller.getRequestCountForUser(STUDENT_USER_UUID, queryParams),
478+
).rejects.toThrow('Database connection failed');
479+
expect(mockSupervisionRequestsService.countRequestsForUser).toHaveBeenCalledWith(
475480
STUDENT_USER_UUID,
481+
RequestState.WITHDRAWN,
476482
);
477483
});
478484
});

backend/src/modules/requests/supervision/supervision-requests.controller.ts

Lines changed: 19 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,10 @@ import { SupervisionRequestsService } from './supervision-requests.service';
44
import { CreateSupervisionRequestDto } from './dto/create-supervision-request.dto';
55
import { UpdateSupervisionRequestDto } from './dto/update-supervision-request.dto';
66
import { SupervisionRequestQueryDto } from './dto/supervision-request-query.dto';
7+
import { CountQueryDto } from './dto/count-query.dto';
78
import { SupervisionRequestEntity } from './entities/supervision-request.entity';
89
import { SupervisionRequestWithUsersEntity } from './entities/supervision-request-with-users.entity';
9-
import { PendingRequestCountEntity } from './entities/pending-request-count.entity';
10+
import { SupervisionRequestCountEntity } from './entities/supervision-request-count.entity';
1011
import { CurrentUser } from '../../../common/decorators/current-user.decorator';
1112
import { RequestState, User } from '@prisma/client';
1213

@@ -137,35 +138,42 @@ export class SupervisionRequestsController {
137138
);
138139
}
139140

140-
@Get('pending-count/:userId')
141+
@Get('count/:userId')
141142
@ApiOperation({
142-
summary: 'Get pending supervision request count for a user',
143+
summary: 'Get supervision request count for a user by state',
143144
description:
144-
'Returns the count of pending supervision requests for a specific user. Requesting a student gets their outgoing requests, requesting a supervisor gets their incoming requests.',
145+
'Returns the count of supervision requests for a specific user filtered by state. Requesting a student gets their outgoing requests, requesting a supervisor gets their incoming requests.',
145146
})
146147
@ApiParam({
147148
name: 'userId',
148-
description: 'ID of the user to get pending request count for',
149+
description: 'ID of the user to get request count for',
149150
type: String,
150151
format: 'uuid',
151152
example: '123e4567-e89b-12d3-a456-426614174000',
152153
})
154+
@ApiQuery({
155+
name: 'request_state',
156+
required: true,
157+
enum: RequestState,
158+
description: 'The request state to count',
159+
})
153160
@ApiResponse({
154161
status: 200,
155-
description: 'Returns the count of pending supervision requests',
156-
type: PendingRequestCountEntity,
162+
description: 'Returns the count of supervision requests',
163+
type: SupervisionRequestCountEntity,
157164
})
158165
@ApiResponse({
159166
status: 400,
160-
description: 'Bad request - Invalid UUID or admin user requested',
167+
description: 'Bad request - Invalid UUID, admin user requested, or missing request state',
161168
})
162169
@ApiResponse({
163170
status: 404,
164171
description: 'User not found',
165172
})
166-
async getPendingRequestCountForUser(
173+
async getRequestCountForUser(
167174
@Param('userId', ParseUUIDPipe) userId: string,
168-
): Promise<PendingRequestCountEntity> {
169-
return this.supervisionRequestsService.countPendingRequestsForUser(userId);
175+
@Query() queryParams: CountQueryDto,
176+
): Promise<SupervisionRequestCountEntity> {
177+
return this.supervisionRequestsService.countRequestsForUser(userId, queryParams.request_state);
170178
}
171179
}

0 commit comments

Comments
 (0)