Skip to content

Commit

Permalink
Merge pull request #30 from fga-eps-mds/feat#80/visualizar-conteudo
Browse files Browse the repository at this point in the history
  • Loading branch information
paulohgontijoo authored Sep 2, 2024
2 parents 8b6949c + 8f3098e commit 8536641
Show file tree
Hide file tree
Showing 6 changed files with 201 additions and 32 deletions.
2 changes: 2 additions & 0 deletions src/users/interface/user.interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ export interface User extends Document {
verificationToken?: string;
isVerified?: boolean;
role?: UserRole;
points?: mongoose.Types.ObjectId[];
journeys?: mongoose.Types.ObjectId[];
subscribedJourneys?: mongoose.Types.ObjectId[];
completedTrails?: mongoose.Types.ObjectId[];
}
3 changes: 2 additions & 1 deletion src/users/interface/user.schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,11 @@ export const UserSchema = new mongoose.Schema(
enum: Object.values(UserRole),
default: UserRole.ALUNO,
},
journeys: [{ type: mongoose.Schema.Types.ObjectId, ref: 'Journey' }],
points: [{ type: mongoose.Schema.Types.ObjectId, ref: 'Point' }],
subscribedJourneys: [
{ type: mongoose.Schema.Types.ObjectId, ref: 'Journey' },
],
completedTrails: [{ type: mongoose.Schema.Types.ObjectId, ref: 'Trail' }],
},
{ timestamps: true, collection: 'users' },
);
Expand Down
25 changes: 21 additions & 4 deletions src/users/users.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,13 +60,14 @@ export class UsersController {
async getUsers() {
return await this.usersService.getUsers();
}
@Patch(':id/add-journey')
async addJourneyToUser(

@Patch(':id/add-point')
async addPointToUser(
@Param('id') id: string,
@Body() body: { journeyId: string },
@Body() body: { pointId: string },
) {
try {
return await this.usersService.addJourneyToUser(id, body.journeyId);
return await this.usersService.addPointToUser(id, body.pointId);
} catch (error) {
throw error;
}
Expand All @@ -90,6 +91,22 @@ export class UsersController {
return this.usersService.unsubscribeJourney(userId, journeyId);
}

@UseGuards(JwtAuthGuard)
@Post(':userId/complete/:trailId')
async completeTrail(
@Param('userId') userId: string,
@Param('trailId') trailId: string,
) {
return this.usersService.completeTrail(userId, trailId);
}

@Get(':userId/completedTrails')
async getCompletedTrails(
@Param('userId') userId: string,
): Promise<Types.ObjectId[]> {
return await this.usersService.getCompletedTrails(userId);
}

@Get('/:id')
async getUserById(@Param('id') id: string) {
try {
Expand Down
51 changes: 51 additions & 0 deletions src/users/users.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,24 @@ export class UsersService {
return user;
}

async addPointToUser(userId: string, pointId: string): Promise<User> {
const user = await this.userModel.findById(userId).exec();
if (!user) {
throw new NotFoundException(`User with ID ${userId} not found`);
}

const objectId = new Types.ObjectId(pointId);

if (!user.points) {
user.points = [];
}

if (!user.points.includes(objectId)) {
user.points.push(objectId);
}

return user.save();
}
async addJourneyToUser(userId: string, journeyId: string): Promise<User> {
const user = await this.userModel.findById(userId).exec();
if (!user) {
Expand All @@ -98,6 +116,39 @@ export class UsersService {

return user.save();
}

async getCompletedTrails(userId: string): Promise<Types.ObjectId[]> {
const user = await this.userModel.findById(userId).exec();
if (!user) {
throw new NotFoundException(`User with ID ${userId} not found`);
}

return user.completedTrails;
}

async completeTrail(userId: string, trailId: string): Promise<User> {
const user = await this.userModel.findById(userId).exec();
if (!user) {
throw new NotFoundException(`User with ID ${userId} not found`);
}

const objectId = new Types.ObjectId(trailId);

const isTrailCompleted = user.completedTrails.some((completedTrailId) =>
completedTrailId.equals(objectId),
);

if (isTrailCompleted) {
throw new ConflictException(
`User already completed trail with ID ${trailId}`,
);
}

user.completedTrails.push(objectId);

return user.save();
}

async deleteUserById(_id: string): Promise<void> {
const result = await this.userModel.deleteOne({ _id }).exec();
if (result.deletedCount === 0) {
Expand Down
76 changes: 51 additions & 25 deletions test/user.controller.spec.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import { Test, TestingModule } from '@nestjs/testing';
import { UsersController } from 'src/users/users.controller';
import { UsersService } from 'src/users/users.service';
import { NotFoundException } from '@nestjs/common';
import { UserRole } from 'src/users/dtos/user-role.enum';
import { CreateUserDto } from 'src/users/dtos/create-user.dto';
import { UpdateRoleDto } from 'src/users/dtos/update-role.dto';
import { Test, TestingModule } from '@nestjs/testing';
import { AuthService } from 'src/auth/auth.service'; // Import AuthService
import { JwtService } from '@nestjs/jwt'; // Import JwtService
import { JwtService } from '@nestjs/jwt';
import { AuthService } from 'src/auth/auth.service';

describe('UsersController', () => {
let controller: UsersController;
Expand All @@ -22,16 +23,16 @@ describe('UsersController', () => {
verifyUser: jest.fn().mockResolvedValue(mockUser),
getSubscribedJourneys: jest.fn().mockResolvedValue([]),
getUsers: jest.fn().mockResolvedValue([mockUser]),
addJourneyToUser: jest.fn().mockResolvedValue(mockUser),
addPointToUser: jest.fn().mockResolvedValue(mockUser),
subscribeJourney: jest.fn().mockResolvedValue(mockUser),
unsubscribeJourney: jest.fn().mockResolvedValue(mockUser),
getUserById: jest.fn().mockResolvedValue(mockUser),
deleteUserById: jest.fn().mockResolvedValue(undefined),
updateUserRole: jest.fn().mockResolvedValue(mockUser),
};

const mockAuthService = {}; // Mock any methods you use from AuthService
const mockJwtService = {}; // Mock any methods you use from JwtService
const mockAuthService = {};
const mockJwtService = {};

beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
Expand All @@ -44,7 +45,6 @@ describe('UsersController', () => {
}).compile();

controller = module.get<UsersController>(UsersController);

});

it('should be defined', () => {
Expand All @@ -56,7 +56,7 @@ describe('UsersController', () => {
name: 'New User',
email: '[email protected]',
password: 'password123',
username: ''
username: '',
};
await expect(controller.createUser(createUserDto)).resolves.toEqual({
message: 'User created successfully. Please verify your email.',
Expand All @@ -79,42 +79,56 @@ describe('UsersController', () => {
await expect(controller.getUsers()).resolves.toEqual([mockUser]);
});

it('should add a journey to a user', async () => {
it('should add a point to a user', async () => {
const userId = 'mockUserId';
const journeyId = 'mockJourneyId';
const body = { journeyId };
await expect(controller.addJourneyToUser(userId, body)).resolves.toEqual(
mockUser,
);
});

it('should subscribe a journey for a user', async () => {
const userId = 'mockUserId';
const journeyId = 'mockJourneyId';
const pointId = 'mockPointId';
await expect(
controller.subscribeJourney(userId, journeyId),
controller.addPointToUser(userId, { pointId }),
).resolves.toEqual(mockUser);
});

it('should unsubscribe a journey for a user', async () => {
it('should handle error when adding a point to a user', async () => {
const userId = 'mockUserId';
const journeyId = 'mockJourneyId';
const pointId = 'mockPointId';
mockUserService.addPointToUser.mockRejectedValueOnce(
new NotFoundException('User not found'),
);
await expect(
controller.unsubscribeJourney(userId, journeyId),
).resolves.toEqual(mockUser);
controller.addPointToUser(userId, { pointId }),
).rejects.toThrow(NotFoundException);
});

it('should get a user by ID', async () => {
const userId = 'mockUserId';
await expect(controller.getUserById(userId)).resolves.toEqual(mockUser);
});

it('should handle error when getting a user by ID', async () => {
const userId = 'mockUserId';
mockUserService.getUserById.mockRejectedValueOnce(
new NotFoundException('User not found'),
);
await expect(controller.getUserById(userId)).rejects.toThrow(
NotFoundException,
);
});

it('should delete a user by ID', async () => {
const userId = 'mockUserId';
await expect(controller.deleteUserById(userId)).resolves.toBeUndefined();
});

it('should update user role', async () => {
it('should handle error when deleting a user by ID', async () => {
const userId = 'mockUserId';
mockUserService.deleteUserById.mockRejectedValueOnce(
new NotFoundException('User not found'),
);
await expect(controller.deleteUserById(userId)).rejects.toThrow(
NotFoundException,
);
});

it('should update a user role', async () => {
const userId = 'mockUserId';
const updateRoleDto: UpdateRoleDto = { role: UserRole.ADMIN };
await expect(
Expand All @@ -124,4 +138,16 @@ describe('UsersController', () => {
user: mockUser,
});
});

it('should handle error when updating a user role', async () => {
const userId = 'mockUserId';
const updateRoleDto: UpdateRoleDto = { role: UserRole.ADMIN };
mockUserService.updateUserRole.mockRejectedValueOnce(
new NotFoundException('User not found'),
);
await expect(
controller.updateUserRole(userId, updateRoleDto),
).rejects.toThrow(NotFoundException);
});
});

76 changes: 74 additions & 2 deletions test/user.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { Model, Types } from 'mongoose';
import { User } from '../src/users/interface/user.interface';
import { UserRole } from '../src/users/dtos/user-role.enum';
import { UpdateRoleDto } from '../src/users/dtos/update-role.dto';
import { NotFoundException } from '@nestjs/common';
import { ConflictException, NotFoundException } from '@nestjs/common';

describe('UsersService', () => {
let service: UsersService;
Expand All @@ -22,7 +22,8 @@ describe('UsersService', () => {
verificationToken: 'mockToken',
isVerified: false,
subscribedJourneys: [new Types.ObjectId(), new Types.ObjectId()],
save: jest.fn().mockResolvedValue(this), // Mock da instância
completedTrails: [new Types.ObjectId(), new Types.ObjectId()],
save: jest.fn().mockResolvedValue(this),
};

const mockUserList = [
Expand Down Expand Up @@ -261,4 +262,75 @@ describe('UsersService', () => {
NotFoundException,
);
});

describe('UsersService - Trail Management', () => {
it('should get completed trails for a user', async () => {
jest.spyOn(model, 'findById').mockReturnValueOnce({
exec: jest.fn().mockResolvedValue(mockUser),
} as any);

const result = await service.getCompletedTrails('mockId');
expect(result).toEqual(mockUser.completedTrails);
});

it('should throw NotFoundException if user is not found when getting completed trails', async () => {
jest.spyOn(model, 'findById').mockReturnValueOnce({
exec: jest.fn().mockResolvedValue(null),
} as any);

await expect(service.getCompletedTrails('invalidId')).rejects.toThrow(
NotFoundException,
);
});

it('should mark a trail as completed for a user', async () => {
const trailId = new Types.ObjectId().toHexString();

jest.spyOn(model, 'findById').mockReturnValueOnce({
exec: jest.fn().mockResolvedValue(mockUser),
} as any);

const userWithCompletedTrail = {
...mockUser,
completedTrails: [new Types.ObjectId(trailId)],
};

jest
.spyOn(mockUser, 'save')
.mockResolvedValue(userWithCompletedTrail as any);

const result = await service.completeTrail('mockId', trailId);
expect(result.completedTrails).toContainEqual(
new Types.ObjectId(trailId),
);
expect(mockUser.save).toHaveBeenCalled();
});

it('should throw NotFoundException if user is not found when marking a trail as completed', async () => {
jest.spyOn(model, 'findById').mockReturnValueOnce({
exec: jest.fn().mockResolvedValue(null),
} as any);

await expect(
service.completeTrail('invalidId', new Types.ObjectId().toHexString()),
).rejects.toThrow(NotFoundException);
});

it('should throw ConflictException if trail is already completed by the user', async () => {
const trailId = new Types.ObjectId().toHexString();

const userWithCompletedTrail = {
...mockUser,
completedTrails: [new Types.ObjectId(trailId)],
};

jest.spyOn(model, 'findById').mockReturnValueOnce({
exec: jest.fn().mockResolvedValue(userWithCompletedTrail),
} as any);

await expect(service.completeTrail('mockId', trailId)).rejects.toThrow(
ConflictException,
);
});
});
});

0 comments on commit 8536641

Please sign in to comment.