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#80/visualizar conteudo #30

Merged
merged 9 commits into from
Sep 2, 2024
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,
);
});
});
});
Loading