From f627aa8a45582633cc5cfa97b3bfbb9726e5b149 Mon Sep 17 00:00:00 2001 From: DaviMatheus Date: Thu, 15 Aug 2024 19:49:12 -0300 Subject: [PATCH 01/11] [feat]#75-creat_journey_user_ref --- src/app.module.ts | 2 + src/journey/dtos/create-journey.dto.ts | 13 +++ src/journey/journey.controller.ts | 57 +++++++++++++ src/journey/journey.module.ts | 16 ++++ src/journey/journey.schema.ts | 11 ++- src/journey/journey.service.ts | 112 +++++++++++++++++++++++++ 6 files changed, 209 insertions(+), 2 deletions(-) create mode 100644 src/journey/dtos/create-journey.dto.ts create mode 100644 src/journey/journey.controller.ts create mode 100644 src/journey/journey.module.ts create mode 100644 src/journey/journey.service.ts diff --git a/src/app.module.ts b/src/app.module.ts index d18ec88..19407c9 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -4,6 +4,7 @@ import { MongooseModule } from '@nestjs/mongoose'; import { HttpModule } from '@nestjs/axios'; import { ContentModule } from './content/content.module'; import * as Joi from 'joi'; +import { JourneyModule } from './journey/journey.module'; @Module({ imports: [ @@ -23,6 +24,7 @@ import * as Joi from 'joi'; }), HttpModule, ContentModule, + JourneyModule, ], controllers: [], providers: [], diff --git a/src/journey/dtos/create-journey.dto.ts b/src/journey/dtos/create-journey.dto.ts new file mode 100644 index 0000000..d4c76d1 --- /dev/null +++ b/src/journey/dtos/create-journey.dto.ts @@ -0,0 +1,13 @@ +import { IsString, IsOptional, IsMongoId } from 'class-validator'; + +export class CreateJourneyDto { + @IsString() + title: string; + + @IsString() + description: string; + + @IsOptional() + @IsMongoId() + user?: string; +} diff --git a/src/journey/journey.controller.ts b/src/journey/journey.controller.ts new file mode 100644 index 0000000..1ffb1e8 --- /dev/null +++ b/src/journey/journey.controller.ts @@ -0,0 +1,57 @@ +import { + Controller, + Get, + Post, + Body, + Param, + Put, + Req, + UnauthorizedException, + Delete, +} from '@nestjs/common'; +import { JourneyService } from './journey.service'; +import { Request } from 'express'; +import { CreateJourneyDto } from './dtos/create-journey.dto'; + +@Controller('journeys') +export class JourneyController { + constructor(private readonly journeyService: JourneyService) {} + + @Post() + async create( + @Body() createJourneyDto: CreateJourneyDto, + @Req() req: Request, + ) { + const authHeader = req.headers.authorization as string; + const token = authHeader?.split(' ')[1]; + + if (!token) { + throw new UnauthorizedException('Token not found'); + } + + return this.journeyService.create(createJourneyDto, token); + } + + @Get() + async findAll() { + return this.journeyService.findAll(); + } + + @Get(':id') + async findById(@Param('id') id: string) { + return this.journeyService.findById(id); + } + + @Put(':id') + async update( + @Param('id') id: string, + @Body() updateJourneyDto: CreateJourneyDto, + ) { + return this.journeyService.update(id, updateJourneyDto); + } + + @Delete(':id') + async delete(@Param('id') id: string) { + return this.journeyService.delete(id); + } +} diff --git a/src/journey/journey.module.ts b/src/journey/journey.module.ts new file mode 100644 index 0000000..65d9891 --- /dev/null +++ b/src/journey/journey.module.ts @@ -0,0 +1,16 @@ +import { Module } from '@nestjs/common'; +import { MongooseModule } from '@nestjs/mongoose'; +import { JourneySchema } from './journey.schema'; +import { JourneyService } from './journey.service'; +import { HttpModule } from '@nestjs/axios'; +import { JourneyController } from './journey.controller'; + +@Module({ + imports: [ + HttpModule, + MongooseModule.forFeature([{ name: 'Journey', schema: JourneySchema }]), + ], + providers: [JourneyService], + controllers: [JourneyController], +}) +export class JourneyModule {} diff --git a/src/journey/journey.schema.ts b/src/journey/journey.schema.ts index 96a823d..f74fc0e 100644 --- a/src/journey/journey.schema.ts +++ b/src/journey/journey.schema.ts @@ -2,8 +2,15 @@ import * as mongoose from 'mongoose'; export const JourneySchema = new mongoose.Schema( { - name: { type: String, required: true }, - description: { type: String }, + title: { type: String, required: true }, + description: { type: String, required: true }, + user: { type: mongoose.Schema.Types.ObjectId, ref: 'User', required: true }, }, { timestamps: true, collection: 'journeys' }, ); + +export interface Journey extends mongoose.Document { + title: string; + description: string; + user: mongoose.Schema.Types.ObjectId; +} diff --git a/src/journey/journey.service.ts b/src/journey/journey.service.ts new file mode 100644 index 0000000..1e6349a --- /dev/null +++ b/src/journey/journey.service.ts @@ -0,0 +1,112 @@ +import { HttpService } from '@nestjs/axios'; +import { firstValueFrom } from 'rxjs'; +import { + Injectable, + Logger, + NotFoundException, + UnauthorizedException, +} from '@nestjs/common'; +import { InjectModel } from '@nestjs/mongoose'; +import { Model } from 'mongoose'; +import { Journey } from './journey.schema'; +import { CreateJourneyDto } from './dtos/create-journey.dto'; + +@Injectable() +export class JourneyService { + private readonly logger = new Logger(JourneyService.name); + + constructor( + @InjectModel('Journey') private readonly journeyModel: Model, + private readonly httpService: HttpService, + ) {} + + async create( + createJourneyDto: CreateJourneyDto, + token: string, + ): Promise { + const userId = await this.validateTokenAndGetUserId(token); + + this.logger.log(`User ID from token: ${userId}`); + + if (!userId) { + throw new UnauthorizedException('Invalid token'); + } + + const newJourney = new this.journeyModel({ + ...createJourneyDto, + user: userId, + }); + const savedJourney = await newJourney.save(); + + // Atualizar o usuário com o novo ID de jornada + await this.addJourneyToUser(userId, savedJourney._id.toString()); + + return savedJourney; + } + + async validateTokenAndGetUserId(token: string): Promise { + try { + this.logger.log(`Validating token: ${token}`); + const response = await firstValueFrom( + this.httpService.get(`${process.env.AUTH_SERVICE_URL}/validate-token`, { + headers: { Authorization: `Bearer ${token}` }, + }), + ); + this.logger.log( + `Token validation response: ${JSON.stringify(response.data)}`, + ); + return response.data.userPayload?.id || null; + } catch (err) { + this.logger.error(`Token validation failed: ${err.message}`); + return null; + } + } + + async addJourneyToUser(userId: string, journeyId: string): Promise { + try { + await firstValueFrom( + this.httpService.patch( + `${process.env.USER_SERVICE_URL}/${userId}/add-journey`, + { journeyId }, + ), + ); + this.logger.log(`Added journey ${journeyId} to user ${userId}`); + } catch (err) { + this.logger.error(`Failed to add journey to user: ${err.message}`); + throw new NotFoundException('Failed to update user with new journey'); + } + } + + async findAll(): Promise { + return this.journeyModel.find().exec(); + } + + async findById(id: string): Promise { + const journey = await this.journeyModel.findById(id).exec(); + if (!journey) { + throw new NotFoundException(`Journey with ID ${id} not found`); + } + return journey; + } + + async update( + id: string, + updateJourneyDto: CreateJourneyDto, + ): Promise { + const journey = await this.journeyModel + .findByIdAndUpdate(id, updateJourneyDto, { new: true }) + .exec(); + if (!journey) { + throw new NotFoundException(`Journey with ID ${id} not found`); + } + return journey; + } + + async delete(id: string): Promise { + const journey = await this.journeyModel.findByIdAndDelete(id).exec(); + if (!journey) { + throw new NotFoundException(`Journey with ID ${id} not found`); + } + return journey; + } +} From 57e124ff16d9b4ae3630af99c8c805adbb98afff Mon Sep 17 00:00:00 2001 From: DaviMatheus Date: Thu, 15 Aug 2024 20:16:13 -0300 Subject: [PATCH 02/11] [feat]#75-creat_trail_and_add_to_journey --- src/app.module.ts | 2 + src/journey/journey.controller.ts | 9 ++++ src/journey/journey.module.ts | 2 + src/journey/journey.schema.ts | 2 + src/journey/journey.service.ts | 22 +++++++++- src/trail/trail.controller.ts | 56 +++++++++++++++++++++++++ src/trail/trail.module.ts | 17 ++++++++ src/trail/trail.schema.ts | 12 +++++- src/trail/trail.service.ts | 69 +++++++++++++++++++++++++++++++ 9 files changed, 188 insertions(+), 3 deletions(-) create mode 100644 src/trail/trail.controller.ts create mode 100644 src/trail/trail.module.ts create mode 100644 src/trail/trail.service.ts diff --git a/src/app.module.ts b/src/app.module.ts index 19407c9..cecbe6f 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -5,6 +5,7 @@ import { HttpModule } from '@nestjs/axios'; import { ContentModule } from './content/content.module'; import * as Joi from 'joi'; import { JourneyModule } from './journey/journey.module'; +import { TrailModule } from './trail/trail.module'; @Module({ imports: [ @@ -25,6 +26,7 @@ import { JourneyModule } from './journey/journey.module'; HttpModule, ContentModule, JourneyModule, + TrailModule, ], controllers: [], providers: [], diff --git a/src/journey/journey.controller.ts b/src/journey/journey.controller.ts index 1ffb1e8..5bace59 100644 --- a/src/journey/journey.controller.ts +++ b/src/journey/journey.controller.ts @@ -8,6 +8,7 @@ import { Req, UnauthorizedException, Delete, + Patch, } from '@nestjs/common'; import { JourneyService } from './journey.service'; import { Request } from 'express'; @@ -54,4 +55,12 @@ export class JourneyController { async delete(@Param('id') id: string) { return this.journeyService.delete(id); } + + @Patch(':id/add-trail') + async addTrailToJourney( + @Param('id') id: string, + @Body() body: { trailId: string }, + ) { + return this.journeyService.addTrailToJourney(id, body.trailId); + } } diff --git a/src/journey/journey.module.ts b/src/journey/journey.module.ts index 65d9891..6ff26db 100644 --- a/src/journey/journey.module.ts +++ b/src/journey/journey.module.ts @@ -12,5 +12,7 @@ import { JourneyController } from './journey.controller'; ], providers: [JourneyService], controllers: [JourneyController], + exports: [MongooseModule, JourneyService], + }) export class JourneyModule {} diff --git a/src/journey/journey.schema.ts b/src/journey/journey.schema.ts index f74fc0e..6567a62 100644 --- a/src/journey/journey.schema.ts +++ b/src/journey/journey.schema.ts @@ -5,6 +5,7 @@ export const JourneySchema = new mongoose.Schema( title: { type: String, required: true }, description: { type: String, required: true }, user: { type: mongoose.Schema.Types.ObjectId, ref: 'User', required: true }, + trails: [{ type: mongoose.Schema.Types.ObjectId, ref: 'Trail' }], }, { timestamps: true, collection: 'journeys' }, ); @@ -13,4 +14,5 @@ export interface Journey extends mongoose.Document { title: string; description: string; user: mongoose.Schema.Types.ObjectId; + trails?: mongoose.Types.ObjectId[]; } diff --git a/src/journey/journey.service.ts b/src/journey/journey.service.ts index 1e6349a..2e6b450 100644 --- a/src/journey/journey.service.ts +++ b/src/journey/journey.service.ts @@ -7,7 +7,7 @@ import { UnauthorizedException, } from '@nestjs/common'; import { InjectModel } from '@nestjs/mongoose'; -import { Model } from 'mongoose'; +import { Model, Types } from 'mongoose'; import { Journey } from './journey.schema'; import { CreateJourneyDto } from './dtos/create-journey.dto'; @@ -109,4 +109,24 @@ export class JourneyService { } return journey; } + + async addTrailToJourney( + journeyId: string, + trailId: string, + ): Promise { + const journey = await this.journeyModel.findById(journeyId).exec(); + if (!journey) { + throw new NotFoundException(`Journey with ID ${journeyId} not found`); + } + + const objectId = new Types.ObjectId(trailId); + + if (!journey.trails) { + journey.trails = []; + } + + journey.trails.push(objectId); + + return journey.save(); + } } diff --git a/src/trail/trail.controller.ts b/src/trail/trail.controller.ts new file mode 100644 index 0000000..b3e3013 --- /dev/null +++ b/src/trail/trail.controller.ts @@ -0,0 +1,56 @@ +import { + Controller, + Post, + Get, + Put, + Delete, + Param, + Body, + NotFoundException, + Headers, +} from '@nestjs/common'; +import { TrailService } from './trail.service'; + +@Controller('trails') +export class TrailController { + constructor(private readonly trailService: TrailService) {} + + @Post() + async createTrail( + @Body() body: { name: string; description?: string }, + @Headers('journey-id') journeyId: string, + ) { + if (!journeyId) { + throw new NotFoundException('Journey ID not provided in header'); + } + return this.trailService.createTrail( + body.name, + body.description, + journeyId, + ); + } + + @Get(':id') + async getTrailById(@Param('id') id: string) { + return this.trailService.findTrailById(id); + } + + @Get() + async getAllTrails() { + return this.trailService.findAllTrails(); + } + + @Put(':id') + async updateTrail( + @Param('id') id: string, + @Body() updateData: Partial<{ name: string; description?: string }>, + ) { + return this.trailService.updateTrail(id, updateData); + } + + @Delete(':id') + async deleteTrail(@Param('id') id: string) { + await this.trailService.deleteTrail(id); + return { message: 'Trail deleted successfully' }; + } +} diff --git a/src/trail/trail.module.ts b/src/trail/trail.module.ts new file mode 100644 index 0000000..d047c0b --- /dev/null +++ b/src/trail/trail.module.ts @@ -0,0 +1,17 @@ +import { Module } from '@nestjs/common'; +import { MongooseModule } from '@nestjs/mongoose'; +import { TrailSchema } from './trail.schema'; +import { TrailService } from './trail.service'; +import { JourneyModule } from '../journey/journey.module'; +import { TrailController } from './trail.controller'; +import { JourneyService } from 'src/journey/journey.service'; + +@Module({ + imports: [ + MongooseModule.forFeature([{ name: 'Trail', schema: TrailSchema }]), + JourneyModule, + ], + providers: [TrailService], + controllers: [TrailController], +}) +export class TrailModule {} diff --git a/src/trail/trail.schema.ts b/src/trail/trail.schema.ts index 691fd54..2481f8a 100644 --- a/src/trail/trail.schema.ts +++ b/src/trail/trail.schema.ts @@ -3,8 +3,16 @@ import * as mongoose from 'mongoose'; export const TrailSchema = new mongoose.Schema( { name: { type: String, required: true }, - description: { type: String }, - journey: { type: mongoose.Schema.Types.ObjectId, ref: 'Journey' }, + journey: { + type: mongoose.Schema.Types.ObjectId, + ref: 'Journey', + required: true, + }, }, { timestamps: true, collection: 'trails' }, ); + +export interface Trail extends mongoose.Document { + name: string; + journey: mongoose.Schema.Types.ObjectId; +} diff --git a/src/trail/trail.service.ts b/src/trail/trail.service.ts new file mode 100644 index 0000000..4f9363b --- /dev/null +++ b/src/trail/trail.service.ts @@ -0,0 +1,69 @@ +import { Injectable, NotFoundException } from '@nestjs/common'; +import { InjectModel } from '@nestjs/mongoose'; +import { Model } from 'mongoose'; +import { Trail } from './trail.schema'; +import { Journey } from 'src/journey/journey.schema'; +import { JourneyService } from 'src/journey/journey.service'; + +@Injectable() +export class TrailService { + constructor( + @InjectModel('Trail') private readonly trailModel: Model, + @InjectModel('Journey') private readonly journeyModel: Model, + private readonly journeyService: JourneyService, + + ) {} + + async createTrail( + name: string, + description: string, + journeyId: string, + ): Promise { + const journeyExists = await this.journeyModel.findById(journeyId).exec(); + if (!journeyExists) { + throw new NotFoundException(`Journey with ID ${journeyId} not found`); + } + + const newTrail = new this.trailModel({ + name, + description, + journey: journeyId, + }); + + await this.journeyService.addTrailToJourney( + journeyId, + newTrail._id.toString(), + ); + + return newTrail.save(); + } + + async findTrailById(id: string): Promise { + const trail = await this.trailModel.findById(id).exec(); + if (!trail) { + throw new NotFoundException(`Trail with ID ${id} not found`); + } + return trail; + } + + async findAllTrails(): Promise { + return this.trailModel.find().exec(); + } + + async updateTrail(id: string, updateData: Partial): Promise { + const trail = await this.trailModel + .findByIdAndUpdate(id, updateData, { new: true }) + .exec(); + if (!trail) { + throw new NotFoundException(`Trail with ID ${id} not found`); + } + return trail; + } + + async deleteTrail(id: string): Promise { + const result = await this.trailModel.findByIdAndDelete(id).exec(); + if (!result) { + throw new NotFoundException(`Trail with ID ${id} not found`); + } + } +} From bece1bf4738fdf32e4306c741b12762f1624a912 Mon Sep 17 00:00:00 2001 From: DaviMatheus Date: Thu, 15 Aug 2024 21:08:37 -0300 Subject: [PATCH 03/11] [feat]#75-creat_trail_and_add_to_journey Co-authored-by: Fernandes Natanael --- src/content/content.controller.ts | 60 +++++++++---------- src/content/content.module.ts | 7 ++- src/content/content.schema.ts | 12 ++-- src/content/content.service.ts | 83 ++++++++++---------------- src/content/dtos/create-content.dto.ts | 18 ++---- src/journey/journey.service.ts | 1 - src/trail/trail.module.ts | 6 +- src/trail/trail.schema.ts | 10 ++-- src/trail/trail.service.ts | 20 ++++++- 9 files changed, 102 insertions(+), 115 deletions(-) diff --git a/src/content/content.controller.ts b/src/content/content.controller.ts index 85dbc92..e834d1c 100644 --- a/src/content/content.controller.ts +++ b/src/content/content.controller.ts @@ -1,57 +1,53 @@ import { Controller, - Get, Post, - Body, - Param, - Put, - Req, - UnauthorizedException, + Get, + Patch, Delete, + Param, + Body, + Headers, + NotFoundException, } from '@nestjs/common'; import { ContentService } from './content.service'; -import { CreateContentDto } from './dtos/create-content.dto'; -import { Request } from 'express'; +import { Content } from './content.schema'; @Controller('contents') export class ContentController { constructor(private readonly contentService: ContentService) {} @Post() - async create( - @Body() createContentDto: CreateContentDto, - @Req() req: Request, - ) { - const authHeader = req.headers.authorization as string; - const token = authHeader?.split(' ')[1]; - - if (!token) { - throw new UnauthorizedException('Token not found'); + async createContent( + @Body('title') title: string, + @Body('body') body: string, + @Headers('trailId') trailId: string, + ): Promise { + if (!title || !body || !trailId) { + throw new NotFoundException('Title, body, and trailId are required'); } - - return this.contentService.create(createContentDto, token); + return this.contentService.createContent(title, body, trailId); } - @Get() - async findAll() { - return this.contentService.findAll(); + @Get(':id') + async findContentById(@Param('id') id: string): Promise { + return this.contentService.findContentById(id); } - @Get(':id') - async findById(@Param('id') id: string) { - return this.contentService.findById(id); + @Get() + async findAllContents(): Promise { + return this.contentService.findAllContents(); } - @Put(':id') - async update( + @Patch(':id') + async updateContent( @Param('id') id: string, - @Body() updateContentDto: CreateContentDto, - ) { - return this.contentService.update(id, updateContentDto); + @Body() updateData: Partial, + ): Promise { + return this.contentService.updateContent(id, updateData); } @Delete(':id') - async delete(@Param('id') id: string) { - return this.contentService.delete(id); + async deleteContent(@Param('id') id: string): Promise { + return this.contentService.deleteContent(id); } } diff --git a/src/content/content.module.ts b/src/content/content.module.ts index 8234079..a975293 100644 --- a/src/content/content.module.ts +++ b/src/content/content.module.ts @@ -1,16 +1,17 @@ -import { Module } from '@nestjs/common'; +import { Module, forwardRef } from '@nestjs/common'; import { MongooseModule } from '@nestjs/mongoose'; import { ContentSchema } from './content.schema'; import { ContentService } from './content.service'; import { ContentController } from './content.controller'; -import { HttpModule } from '@nestjs/axios'; +import { TrailModule } from '../trail/trail.module'; @Module({ imports: [ - HttpModule, MongooseModule.forFeature([{ name: 'Content', schema: ContentSchema }]), + forwardRef(() => TrailModule), ], providers: [ContentService], controllers: [ContentController], + exports: [ContentService], }) export class ContentModule {} diff --git a/src/content/content.schema.ts b/src/content/content.schema.ts index 1febda7..7e12f3c 100644 --- a/src/content/content.schema.ts +++ b/src/content/content.schema.ts @@ -4,9 +4,11 @@ export const ContentSchema = new mongoose.Schema( { title: { type: String, required: true }, body: { type: String, required: true }, - user: { type: mongoose.Schema.Types.ObjectId, ref: 'User', required: true }, - trail: { type: mongoose.Schema.Types.ObjectId, ref: 'Trail' }, - journey: { type: mongoose.Schema.Types.ObjectId, ref: 'Journey' }, + trail: { + type: mongoose.Schema.Types.ObjectId, + ref: 'Trail', + required: true, + }, }, { timestamps: true, collection: 'contents' }, ); @@ -14,7 +16,5 @@ export const ContentSchema = new mongoose.Schema( export interface Content extends mongoose.Document { title: string; body: string; - user: mongoose.Schema.Types.ObjectId; - trail?: mongoose.Schema.Types.ObjectId; - journey?: mongoose.Schema.Types.ObjectId; + trail: mongoose.Schema.Types.ObjectId; } diff --git a/src/content/content.service.ts b/src/content/content.service.ts index b508a59..27fcbad 100644 --- a/src/content/content.service.ts +++ b/src/content/content.service.ts @@ -1,67 +1,43 @@ -import { - Injectable, - Logger, - NotFoundException, - UnauthorizedException, -} from '@nestjs/common'; +import { Injectable, NotFoundException } from '@nestjs/common'; import { InjectModel } from '@nestjs/mongoose'; import { Model } from 'mongoose'; import { Content } from './content.schema'; -import { CreateContentDto } from './dtos/create-content.dto'; -import { HttpService } from '@nestjs/axios'; -import { firstValueFrom } from 'rxjs'; +import { Trail } from '../trail/trail.schema'; +import { TrailService } from '../trail/trail.service'; @Injectable() export class ContentService { - private readonly logger = new Logger(ContentService.name); - constructor( @InjectModel('Content') private readonly contentModel: Model, - private readonly httpService: HttpService, + @InjectModel('Trail') private readonly trailModel: Model, + private readonly trailService: TrailService, ) {} - async create( - createContentDto: CreateContentDto, - token: string, + async createContent( + title: string, + body: string, + trailId: string, ): Promise { - const userId = await this.validateTokenAndGetUserId(token); - - this.logger.log(`User ID from token: ${userId}`); - - if (!userId) { - throw new UnauthorizedException('Invalid token'); + const trailExists = await this.trailModel.findById(trailId).exec(); + if (!trailExists) { + throw new NotFoundException(`Trail with ID ${trailId} not found`); } const newContent = new this.contentModel({ - ...createContentDto, - user: userId, + title, + body, + trail: trailId, }); - return newContent.save(); - } - async validateTokenAndGetUserId(token: string): Promise { - try { - this.logger.log(`Validating token: ${token}`); - const response = await firstValueFrom( - this.httpService.get(`${process.env.AUTH_SERVICE_URL}/validate-token`, { - headers: { Authorization: `Bearer ${token}` }, - }), - ); - this.logger.log( - `Token validation response: ${JSON.stringify(response.data)}`, - ); - return response.data.userPayload?.id || null; - } catch (err) { - this.logger.error(`Token validation failed: ${err.message}`); - return null; - } - } + await this.trailService.addContentToTrail( + trailId, + newContent._id.toString(), + ); - async findAll(): Promise { - return this.contentModel.find().exec(); + return newContent.save(); } - async findById(id: string): Promise { + async findContentById(id: string): Promise { const content = await this.contentModel.findById(id).exec(); if (!content) { throw new NotFoundException(`Content with ID ${id} not found`); @@ -69,12 +45,16 @@ export class ContentService { return content; } - async update( + async findAllContents(): Promise { + return this.contentModel.find().exec(); + } + + async updateContent( id: string, - updateContentDto: CreateContentDto, + updateData: Partial, ): Promise { const content = await this.contentModel - .findByIdAndUpdate(id, updateContentDto, { new: true }) + .findByIdAndUpdate(id, updateData, { new: true }) .exec(); if (!content) { throw new NotFoundException(`Content with ID ${id} not found`); @@ -82,11 +62,10 @@ export class ContentService { return content; } - async delete(id: string): Promise { - const content = await this.contentModel.findByIdAndDelete(id).exec(); - if (!content) { + async deleteContent(id: string): Promise { + const result = await this.contentModel.findByIdAndDelete(id).exec(); + if (!result) { throw new NotFoundException(`Content with ID ${id} not found`); } - return content; } } diff --git a/src/content/dtos/create-content.dto.ts b/src/content/dtos/create-content.dto.ts index ad13aa4..2023e20 100644 --- a/src/content/dtos/create-content.dto.ts +++ b/src/content/dtos/create-content.dto.ts @@ -1,21 +1,15 @@ -import { IsString, IsOptional, IsMongoId } from 'class-validator'; +import { IsString, IsNotEmpty, IsMongoId } from 'class-validator'; export class CreateContentDto { @IsString() + @IsNotEmpty() title: string; @IsString() + @IsNotEmpty() body: string; - @IsOptional() @IsMongoId() - user?: string; - - @IsOptional() - @IsMongoId() - trail?: string; - - @IsOptional() - @IsMongoId() - journey?: string; -} + @IsNotEmpty() + trail: string; +} \ No newline at end of file diff --git a/src/journey/journey.service.ts b/src/journey/journey.service.ts index 2e6b450..c37ad3e 100644 --- a/src/journey/journey.service.ts +++ b/src/journey/journey.service.ts @@ -38,7 +38,6 @@ export class JourneyService { }); const savedJourney = await newJourney.save(); - // Atualizar o usuário com o novo ID de jornada await this.addJourneyToUser(userId, savedJourney._id.toString()); return savedJourney; diff --git a/src/trail/trail.module.ts b/src/trail/trail.module.ts index d047c0b..7673cc7 100644 --- a/src/trail/trail.module.ts +++ b/src/trail/trail.module.ts @@ -1,17 +1,19 @@ -import { Module } from '@nestjs/common'; +import { Module, forwardRef } from '@nestjs/common'; import { MongooseModule } from '@nestjs/mongoose'; import { TrailSchema } from './trail.schema'; import { TrailService } from './trail.service'; import { JourneyModule } from '../journey/journey.module'; import { TrailController } from './trail.controller'; -import { JourneyService } from 'src/journey/journey.service'; +import { ContentModule } from '../content/content.module'; @Module({ imports: [ MongooseModule.forFeature([{ name: 'Trail', schema: TrailSchema }]), JourneyModule, + forwardRef(() => ContentModule), ], providers: [TrailService], controllers: [TrailController], + exports: [MongooseModule, TrailService], }) export class TrailModule {} diff --git a/src/trail/trail.schema.ts b/src/trail/trail.schema.ts index 2481f8a..2f51797 100644 --- a/src/trail/trail.schema.ts +++ b/src/trail/trail.schema.ts @@ -3,11 +3,8 @@ import * as mongoose from 'mongoose'; export const TrailSchema = new mongoose.Schema( { name: { type: String, required: true }, - journey: { - type: mongoose.Schema.Types.ObjectId, - ref: 'Journey', - required: true, - }, + journey: { type: mongoose.Schema.Types.ObjectId, ref: 'Journey' }, + contents: [{ type: mongoose.Schema.Types.ObjectId, ref: 'Content' }], }, { timestamps: true, collection: 'trails' }, ); @@ -15,4 +12,5 @@ export const TrailSchema = new mongoose.Schema( export interface Trail extends mongoose.Document { name: string; journey: mongoose.Schema.Types.ObjectId; -} + contents: mongoose.Types.ObjectId[]; +} \ No newline at end of file diff --git a/src/trail/trail.service.ts b/src/trail/trail.service.ts index 4f9363b..33b62f0 100644 --- a/src/trail/trail.service.ts +++ b/src/trail/trail.service.ts @@ -1,6 +1,6 @@ import { Injectable, NotFoundException } from '@nestjs/common'; import { InjectModel } from '@nestjs/mongoose'; -import { Model } from 'mongoose'; +import { Model, Types } from 'mongoose'; import { Trail } from './trail.schema'; import { Journey } from 'src/journey/journey.schema'; import { JourneyService } from 'src/journey/journey.service'; @@ -38,6 +38,24 @@ export class TrailService { return newTrail.save(); } + async addContentToTrail(trailId: string, contentId: string): Promise { + const trail = await this.trailModel.findById(trailId).exec(); + if (!trail) { + throw new NotFoundException(`Trail with ID ${trailId} not found`); + } + + const objectId = new Types.ObjectId(contentId); + + if (!trail.contents) { + trail.contents = []; + } + + trail.contents.push(objectId); + + return trail.save(); + } + + async findTrailById(id: string): Promise { const trail = await this.trailModel.findById(id).exec(); if (!trail) { From 259d775e07605eb76834fb636f7784cc386b950b Mon Sep 17 00:00:00 2001 From: DaviMatheus Date: Thu, 15 Aug 2024 21:25:39 -0300 Subject: [PATCH 04/11] [feat]#75-fixing-body-details --- src/content/content.controller.ts | 13 +++++++------ src/content/content.module.ts | 4 ++-- src/content/content.schema.ts | 4 ++-- src/content/content.service.ts | 4 ++-- src/content/dtos/create-content.dto.ts | 6 +++--- src/journey/journey.module.ts | 1 - src/journey/journey.schema.ts | 2 +- src/journey/journey.service.ts | 2 +- src/trail/trail.controller.ts | 17 ++++++----------- src/trail/trail.module.ts | 2 +- src/trail/trail.schema.ts | 4 ++-- src/trail/trail.service.ts | 9 +-------- 12 files changed, 28 insertions(+), 40 deletions(-) diff --git a/src/content/content.controller.ts b/src/content/content.controller.ts index e834d1c..edfb8e8 100644 --- a/src/content/content.controller.ts +++ b/src/content/content.controller.ts @@ -18,14 +18,15 @@ export class ContentController { @Post() async createContent( - @Body('title') title: string, - @Body('body') body: string, - @Headers('trailId') trailId: string, + @Body() body: { title: string; content: string; trailId: string }, ): Promise { - if (!title || !body || !trailId) { - throw new NotFoundException('Title, body, and trailId are required'); + const { title, content, trailId } = body; + + if (!title || !content || !trailId) { + throw new NotFoundException('Title, content, and trailId are required'); } - return this.contentService.createContent(title, body, trailId); + + return this.contentService.createContent(title, content, trailId); } @Get(':id') diff --git a/src/content/content.module.ts b/src/content/content.module.ts index a975293..158edef 100644 --- a/src/content/content.module.ts +++ b/src/content/content.module.ts @@ -8,10 +8,10 @@ import { TrailModule } from '../trail/trail.module'; @Module({ imports: [ MongooseModule.forFeature([{ name: 'Content', schema: ContentSchema }]), - forwardRef(() => TrailModule), + forwardRef(() => TrailModule), ], providers: [ContentService], controllers: [ContentController], - exports: [ContentService], + exports: [ContentService], }) export class ContentModule {} diff --git a/src/content/content.schema.ts b/src/content/content.schema.ts index 7e12f3c..49265f4 100644 --- a/src/content/content.schema.ts +++ b/src/content/content.schema.ts @@ -3,7 +3,7 @@ import * as mongoose from 'mongoose'; export const ContentSchema = new mongoose.Schema( { title: { type: String, required: true }, - body: { type: String, required: true }, + content: { type: String, required: true }, trail: { type: mongoose.Schema.Types.ObjectId, ref: 'Trail', @@ -15,6 +15,6 @@ export const ContentSchema = new mongoose.Schema( export interface Content extends mongoose.Document { title: string; - body: string; + content: string; trail: mongoose.Schema.Types.ObjectId; } diff --git a/src/content/content.service.ts b/src/content/content.service.ts index 27fcbad..ed9aea4 100644 --- a/src/content/content.service.ts +++ b/src/content/content.service.ts @@ -15,7 +15,7 @@ export class ContentService { async createContent( title: string, - body: string, + content: string, trailId: string, ): Promise { const trailExists = await this.trailModel.findById(trailId).exec(); @@ -25,7 +25,7 @@ export class ContentService { const newContent = new this.contentModel({ title, - body, + content, trail: trailId, }); diff --git a/src/content/dtos/create-content.dto.ts b/src/content/dtos/create-content.dto.ts index 2023e20..fe9111f 100644 --- a/src/content/dtos/create-content.dto.ts +++ b/src/content/dtos/create-content.dto.ts @@ -7,9 +7,9 @@ export class CreateContentDto { @IsString() @IsNotEmpty() - body: string; + content: string; @IsMongoId() @IsNotEmpty() - trail: string; -} \ No newline at end of file + trail: string; +} diff --git a/src/journey/journey.module.ts b/src/journey/journey.module.ts index 6ff26db..b8802f2 100644 --- a/src/journey/journey.module.ts +++ b/src/journey/journey.module.ts @@ -13,6 +13,5 @@ import { JourneyController } from './journey.controller'; providers: [JourneyService], controllers: [JourneyController], exports: [MongooseModule, JourneyService], - }) export class JourneyModule {} diff --git a/src/journey/journey.schema.ts b/src/journey/journey.schema.ts index 6567a62..049ed56 100644 --- a/src/journey/journey.schema.ts +++ b/src/journey/journey.schema.ts @@ -14,5 +14,5 @@ export interface Journey extends mongoose.Document { title: string; description: string; user: mongoose.Schema.Types.ObjectId; - trails?: mongoose.Types.ObjectId[]; + trails?: mongoose.Types.ObjectId[]; } diff --git a/src/journey/journey.service.ts b/src/journey/journey.service.ts index c37ad3e..1bfedb8 100644 --- a/src/journey/journey.service.ts +++ b/src/journey/journey.service.ts @@ -108,7 +108,7 @@ export class JourneyService { } return journey; } - + async addTrailToJourney( journeyId: string, trailId: string, diff --git a/src/trail/trail.controller.ts b/src/trail/trail.controller.ts index b3e3013..c4ce493 100644 --- a/src/trail/trail.controller.ts +++ b/src/trail/trail.controller.ts @@ -7,7 +7,6 @@ import { Param, Body, NotFoundException, - Headers, } from '@nestjs/common'; import { TrailService } from './trail.service'; @@ -16,18 +15,14 @@ export class TrailController { constructor(private readonly trailService: TrailService) {} @Post() - async createTrail( - @Body() body: { name: string; description?: string }, - @Headers('journey-id') journeyId: string, - ) { + async createTrail(@Body() body: { name: string; journeyId: string }) { + const { name, journeyId } = body; + if (!journeyId) { - throw new NotFoundException('Journey ID not provided in header'); + throw new NotFoundException('Journey ID not provided in body'); } - return this.trailService.createTrail( - body.name, - body.description, - journeyId, - ); + + return this.trailService.createTrail(name, journeyId); } @Get(':id') diff --git a/src/trail/trail.module.ts b/src/trail/trail.module.ts index 7673cc7..a302088 100644 --- a/src/trail/trail.module.ts +++ b/src/trail/trail.module.ts @@ -10,7 +10,7 @@ import { ContentModule } from '../content/content.module'; imports: [ MongooseModule.forFeature([{ name: 'Trail', schema: TrailSchema }]), JourneyModule, - forwardRef(() => ContentModule), + forwardRef(() => ContentModule), ], providers: [TrailService], controllers: [TrailController], diff --git a/src/trail/trail.schema.ts b/src/trail/trail.schema.ts index 2f51797..d64961c 100644 --- a/src/trail/trail.schema.ts +++ b/src/trail/trail.schema.ts @@ -4,7 +4,7 @@ export const TrailSchema = new mongoose.Schema( { name: { type: String, required: true }, journey: { type: mongoose.Schema.Types.ObjectId, ref: 'Journey' }, - contents: [{ type: mongoose.Schema.Types.ObjectId, ref: 'Content' }], + contents: [{ type: mongoose.Schema.Types.ObjectId, ref: 'Content' }], }, { timestamps: true, collection: 'trails' }, ); @@ -13,4 +13,4 @@ export interface Trail extends mongoose.Document { name: string; journey: mongoose.Schema.Types.ObjectId; contents: mongoose.Types.ObjectId[]; -} \ No newline at end of file +} diff --git a/src/trail/trail.service.ts b/src/trail/trail.service.ts index 33b62f0..c571651 100644 --- a/src/trail/trail.service.ts +++ b/src/trail/trail.service.ts @@ -11,14 +11,9 @@ export class TrailService { @InjectModel('Trail') private readonly trailModel: Model, @InjectModel('Journey') private readonly journeyModel: Model, private readonly journeyService: JourneyService, - ) {} - async createTrail( - name: string, - description: string, - journeyId: string, - ): Promise { + async createTrail(name: string, journeyId: string): Promise { const journeyExists = await this.journeyModel.findById(journeyId).exec(); if (!journeyExists) { throw new NotFoundException(`Journey with ID ${journeyId} not found`); @@ -26,7 +21,6 @@ export class TrailService { const newTrail = new this.trailModel({ name, - description, journey: journeyId, }); @@ -55,7 +49,6 @@ export class TrailService { return trail.save(); } - async findTrailById(id: string): Promise { const trail = await this.trailModel.findById(id).exec(); if (!trail) { From 68f099f706d8eb2d6552826cc38ff2da8118af84 Mon Sep 17 00:00:00 2001 From: Natanael Filho Date: Sun, 18 Aug 2024 17:51:27 -0300 Subject: [PATCH 05/11] feat(#75): add new endpoint --- .env.dev | 3 +++ src/journey/journey.controller.ts | 7 +++++++ src/journey/journey.service.ts | 4 ++++ src/main.ts | 7 +------ 4 files changed, 15 insertions(+), 6 deletions(-) diff --git a/.env.dev b/.env.dev index e69de29..68e40c3 100644 --- a/.env.dev +++ b/.env.dev @@ -0,0 +1,3 @@ +MONGODB_URI= +USER_SERVICE_URL= +AUTH_SERVICE_URL= \ No newline at end of file diff --git a/src/journey/journey.controller.ts b/src/journey/journey.controller.ts index 5bace59..03c13d1 100644 --- a/src/journey/journey.controller.ts +++ b/src/journey/journey.controller.ts @@ -37,6 +37,13 @@ export class JourneyController { async findAll() { return this.journeyService.findAll(); } + + @Get('user/:id') + async findByUser( + @Param('id') userId: string, + ) { + return this.journeyService.findByUserId(userId); + } @Get(':id') async findById(@Param('id') id: string) { diff --git a/src/journey/journey.service.ts b/src/journey/journey.service.ts index 1bfedb8..ca26a4a 100644 --- a/src/journey/journey.service.ts +++ b/src/journey/journey.service.ts @@ -80,6 +80,10 @@ export class JourneyService { return this.journeyModel.find().exec(); } + async findByUserId(userId: string): Promise { + return this.journeyModel.find({ user: userId }).exec(); + } + async findById(id: string): Promise { const journey = await this.journeyModel.findById(id).exec(); if (!journey) { diff --git a/src/main.ts b/src/main.ts index 14ba579..49335f2 100644 --- a/src/main.ts +++ b/src/main.ts @@ -9,12 +9,7 @@ const logger = new Logger('Main'); async function bootstrap() { const app = await NestFactory.create(AppModule); app.useGlobalPipes(new ValidationPipe()); - app.enableCors({ - origin: ['http://localhost:4000'], - credentials: true, - methods: ['GET', 'POST', 'PATCH', 'PUT', 'DELETE'], - allowedHeaders: ['Content-Type', 'Authorization'], - }); + app.enableCors(); await app.listen(configService.get('PORT')); logger.log(`Application listening on port ${configService.get('PORT')}`); } From 5f8b7acc196500f11ac6918e11f576a30437085d Mon Sep 17 00:00:00 2001 From: joaobisi Date: Sun, 18 Aug 2024 18:50:58 -0300 Subject: [PATCH 06/11] feat(#75): Criando rotas para adicionar e remover conteudo de trilha --- src/trail/trail.controller.ts | 21 +++++++++++++++++++++ src/trail/trail.service.ts | 11 +++++++++++ 2 files changed, 32 insertions(+) diff --git a/src/trail/trail.controller.ts b/src/trail/trail.controller.ts index c4ce493..29d77b9 100644 --- a/src/trail/trail.controller.ts +++ b/src/trail/trail.controller.ts @@ -43,6 +43,27 @@ export class TrailController { return this.trailService.updateTrail(id, updateData); } + @Put(':id/addContent') + async addContentToTrail( + @Param('id') trailId: string, + @Body() body: { contentId: string }, + ) { + const { contentId } = body; + if (!contentId) { + throw new NotFoundException('Content ID not provided in body'); + } + return this.trailService.addContentToTrail(trailId, contentId); + } + + @Put(':id/removeContent') + async removeContentFromTrail( + @Param('id') trailId: string, + @Body() body: { contentId: string } + ) { + const { contentId } = body; + return this.trailService.removeContentFromTrail(trailId, contentId); + } + @Delete(':id') async deleteTrail(@Param('id') id: string) { await this.trailService.deleteTrail(id); diff --git a/src/trail/trail.service.ts b/src/trail/trail.service.ts index c571651..a84dbee 100644 --- a/src/trail/trail.service.ts +++ b/src/trail/trail.service.ts @@ -49,6 +49,17 @@ export class TrailService { return trail.save(); } + async removeContentFromTrail(trailId: string, contentId: string): Promise { + const trail = await this.trailModel.findById(trailId).exec(); + if (!trail) { + throw new NotFoundException(`Trail with ID ${trailId} not found`); + } + + trail.contents = trail.contents.filter(content => !content.equals(contentId)); + + return trail.save(); +} + async findTrailById(id: string): Promise { const trail = await this.trailModel.findById(id).exec(); if (!trail) { From ed8648a1e34cdbc12e59677555b97b89f5767bf7 Mon Sep 17 00:00:00 2001 From: DaviMatheus Date: Sun, 18 Aug 2024 19:48:11 -0300 Subject: [PATCH 07/11] [feat]#75-fixing-sonar_and_adding_tests --- jest.config.ts | 16 ++- src/journey/journey.controller.ts | 6 +- test/app.e2e-spec.ts | 24 ----- test/content.controller.spec.ts | 91 ----------------- test/content.service.spec.ts | 95 ------------------ test/journey.service.spec.ts | 159 ++++++++++++++++++++++++++++++ 6 files changed, 167 insertions(+), 224 deletions(-) delete mode 100644 test/app.e2e-spec.ts delete mode 100644 test/content.controller.spec.ts delete mode 100644 test/content.service.spec.ts create mode 100644 test/journey.service.spec.ts diff --git a/jest.config.ts b/jest.config.ts index 2a725e5..97cef7c 100644 --- a/jest.config.ts +++ b/jest.config.ts @@ -1,17 +1,9 @@ import type { Config } from 'jest'; import * as dotenv from 'dotenv'; -dotenv.config(); - +dotenv.config(); const config: Config = { - moduleFileExtensions: ['js', 'json', 'ts'], - rootDir: './test', - testRegex: '.*\\.spec\\.ts$', - transform: { - '^.+\\.(t|j)s$': 'ts-jest', - }, - collectCoverageFrom: ['**/*.(t|j)s'], - coverageDirectory: '../coverage', + preset: 'ts-jest', testEnvironment: 'node', reporters: [ 'default', @@ -23,6 +15,10 @@ const config: Config = { }, ], ], + moduleNameMapper: { + '^src/(.*)$': '/src/$1', + }, + setupFiles: ['dotenv/config'], }; export default config; diff --git a/src/journey/journey.controller.ts b/src/journey/journey.controller.ts index 03c13d1..c551523 100644 --- a/src/journey/journey.controller.ts +++ b/src/journey/journey.controller.ts @@ -37,11 +37,9 @@ export class JourneyController { async findAll() { return this.journeyService.findAll(); } - + @Get('user/:id') - async findByUser( - @Param('id') userId: string, - ) { + async findByUser(@Param('id') userId: string) { return this.journeyService.findByUserId(userId); } diff --git a/test/app.e2e-spec.ts b/test/app.e2e-spec.ts deleted file mode 100644 index 50cda62..0000000 --- a/test/app.e2e-spec.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { Test, TestingModule } from '@nestjs/testing'; -import { INestApplication } from '@nestjs/common'; -import * as request from 'supertest'; -import { AppModule } from './../src/app.module'; - -describe('AppController (e2e)', () => { - let app: INestApplication; - - beforeEach(async () => { - const moduleFixture: TestingModule = await Test.createTestingModule({ - imports: [AppModule], - }).compile(); - - app = moduleFixture.createNestApplication(); - await app.init(); - }); - - it('/ (GET)', () => { - return request(app.getHttpServer()) - .get('/') - .expect(200) - .expect('Hello World!'); - }); -}); diff --git a/test/content.controller.spec.ts b/test/content.controller.spec.ts deleted file mode 100644 index c9692e8..0000000 --- a/test/content.controller.spec.ts +++ /dev/null @@ -1,91 +0,0 @@ -import { Test, TestingModule } from '@nestjs/testing'; -import { ContentController } from '../src/content/content.controller'; -import { ContentService } from '../src/content/content.service'; -import { CreateContentDto } from '../src/content/dtos/create-content.dto'; -import { UnauthorizedException } from '@nestjs/common'; - -describe('ContentController', () => { - let controller: ContentController; - let service: ContentService; - - beforeEach(async () => { - const module: TestingModule = await Test.createTestingModule({ - controllers: [ContentController], - providers: [ - { - provide: ContentService, - useValue: { - create: jest.fn(), - findAll: jest.fn(), - findById: jest.fn(), - update: jest.fn(), - }, - }, - ], - }).compile(); - - controller = module.get(ContentController); - service = module.get(ContentService); - }); - - it('should be defined', () => { - expect(controller).toBeDefined(); - }); - - describe('create', () => { - it('should throw UnauthorizedException if no token is provided', async () => { - const dto: CreateContentDto = { title: 'Test', body: 'Test body' }; - const req = { headers: { authorization: '' } } as any; - - await expect(controller.create(dto, req)).rejects.toThrow( - UnauthorizedException, - ); - }); - - it('should call service.create with correct parameters', async () => { - const dto: CreateContentDto = { title: 'Test', body: 'Test body' }; - const req = { headers: { authorization: 'Bearer token' } } as any; - const result = { ...dto, id: '1' }; - - jest.spyOn(service, 'create').mockResolvedValue(result as any); - - expect(await controller.create(dto, req)).toBe(result); - expect(service.create).toHaveBeenCalledWith(dto, 'token'); - }); - }); - - describe('findAll', () => { - it('should return an array of contents', async () => { - const result = [{ id: '1', title: 'Test', body: 'Test body' }]; - - jest.spyOn(service, 'findAll').mockResolvedValue(result as any); - - expect(await controller.findAll()).toBe(result); - }); - }); - - describe('findById', () => { - it('should return a single content', async () => { - const result = { id: '1', title: 'Test', body: 'Test body' }; - - jest.spyOn(service, 'findById').mockResolvedValue(result as any); - - expect(await controller.findById('1')).toBe(result); - }); - }); - - describe('update', () => { - it('should call service.update with correct parameters', async () => { - const dto: CreateContentDto = { - title: 'Updated Test', - body: 'Updated body', - }; - const result = { ...dto, id: '1' }; - - jest.spyOn(service, 'update').mockResolvedValue(result as any); - - expect(await controller.update('1', dto)).toBe(result); - expect(service.update).toHaveBeenCalledWith('1', dto); - }); - }); -}); diff --git a/test/content.service.spec.ts b/test/content.service.spec.ts deleted file mode 100644 index 983af14..0000000 --- a/test/content.service.spec.ts +++ /dev/null @@ -1,95 +0,0 @@ -import { Test, TestingModule } from '@nestjs/testing'; -import { ContentService } from '../src/content/content.service'; -import { getModelToken } from '@nestjs/mongoose'; -import { HttpService } from '@nestjs/axios'; -import { of } from 'rxjs'; -import { CreateContentDto } from '../src/content/dtos/create-content.dto'; -import { Content } from '../src/content/content.schema'; -import { Model } from 'mongoose'; -import { NotFoundException, UnauthorizedException } from '@nestjs/common'; - -describe('ContentService', () => { - let service: ContentService; - let model: Model; - - const mockContent = { - _id: 'mockId', - title: 'Mock Title', - body: 'Mock Body', - user: 'mockUserId', - trail: 'mockTrailId', - journey: 'mockJourneyId', - }; - - const mockContentDto: CreateContentDto = { - title: 'Mock Title', - body: 'Mock Body', - user: 'mockUserId', - trail: 'mockTrailId', - journey: 'mockJourneyId', - }; - - const mockContentModel = { - create: jest.fn().mockResolvedValue(mockContent), - findById: jest.fn().mockReturnValue({ - exec: jest.fn().mockResolvedValue(mockContent), - }), - }; - - const mockHttpService = { - post: jest.fn().mockReturnValue(of({ data: { success: true } })), - }; - - beforeEach(async () => { - const module: TestingModule = await Test.createTestingModule({ - providers: [ - ContentService, - { provide: getModelToken('Content'), useValue: mockContentModel }, - { provide: HttpService, useValue: mockHttpService }, - ], - }).compile(); - - service = module.get(ContentService); - model = module.get>(getModelToken('Content')); - }); - - it('should be defined', () => { - expect(service).toBeDefined(); - }); - - // it('should create a new content', async () => { - // const result = await service.create(mockContentDto, 'mockToken'); - // expect(result).toEqual(mockContent); - // expect(model.create).toHaveBeenCalledWith(mockContentDto); - // }); - - // it('should call the external API when creating content', async () => { - // await service.create(mockContentDto, 'mockToken'); - // expect(httpService.post).toHaveBeenCalledWith( - // 'external-api-url', - // mockContentDto, - // { headers: { Authorization: 'Bearer mockToken' } }, - // ); - // }); - - it('should throw NotFoundException if content is not found', async () => { - jest.spyOn(model, 'findById').mockReturnValueOnce({ - exec: jest.fn().mockResolvedValue(null), - } as any); - - await expect(service.findById('invalidId')).rejects.toThrow( - NotFoundException, - ); - }); - - it('should return the content if found', async () => { - const result = await service.findById('mockId'); - expect(result).toEqual(mockContent); - }); - - it('should throw UnauthorizedException if user is not authorized', async () => { - await expect(service.create(mockContentDto, '')).rejects.toThrow( - UnauthorizedException, - ); - }); -}); diff --git a/test/journey.service.spec.ts b/test/journey.service.spec.ts new file mode 100644 index 0000000..0f9856c --- /dev/null +++ b/test/journey.service.spec.ts @@ -0,0 +1,159 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { JourneyService } from '../src/journey/journey.service'; +import { getModelToken } from '@nestjs/mongoose'; +import { HttpService } from '@nestjs/axios'; +import { Model, Types } from 'mongoose'; +import { Journey } from '../src/journey/journey.schema'; +import { CreateJourneyDto } from '../src/journey/dtos/create-journey.dto'; +import { UnauthorizedException, NotFoundException, Logger } from '@nestjs/common'; +import { of, throwError } from 'rxjs'; + +describe('JourneyService', () => { + let service: JourneyService; + let model: Model; + let httpService: HttpService; + let logger: Logger; + + const mockJourney = { + _id: 'mockId', + title: 'Mock Journey', + description: 'Mock Description', + user: 'mockUserId', + save: jest.fn().mockResolvedValue(this), // Mock da instância + trails: [], + }; + + const mockJourneyList = [ + { ...mockJourney, _id: 'mockId1' }, + { ...mockJourney, _id: 'mockId2' }, + ]; + + const mockCreateJourneyDto: CreateJourneyDto = { + title: 'New Journey', + description: 'New Journey Description', + }; + + const mockJourneyModel = { + create: jest.fn().mockResolvedValue(mockJourney), + findById: jest.fn().mockReturnValue({ + exec: jest.fn().mockResolvedValue(mockJourney), + }), + find: jest.fn().mockReturnValue({ + exec: jest.fn().mockResolvedValue(mockJourneyList), + }), + findByIdAndUpdate: jest.fn().mockReturnValue({ + exec: jest.fn().mockResolvedValue(mockJourney), + }), + findByIdAndDelete: jest.fn().mockReturnValue({ + exec: jest.fn().mockResolvedValue(mockJourney), + }), + new: jest.fn(() => mockJourney), + }; + + const mockHttpService = { + get: jest.fn(), + patch: jest.fn().mockResolvedValue({}), + }; + + const mockLogger = { + log: jest.fn(), + error: jest.fn(), + }; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + providers: [ + JourneyService, + { provide: getModelToken('Journey'), useValue: mockJourneyModel }, + { provide: HttpService, useValue: mockHttpService }, + { provide: Logger, useValue: mockLogger }, + ], + }).compile(); + + service = module.get(JourneyService); + model = module.get>(getModelToken('Journey')); + httpService = module.get(HttpService); + logger = module.get(Logger); + }); + + it('should be defined', () => { + expect(service).toBeDefined(); + }); + + it('should throw UnauthorizedException if token is invalid', async () => { + jest.spyOn(mockHttpService, 'get').mockReturnValueOnce(throwError(new Error('Invalid token'))); + + await expect(service.create(mockCreateJourneyDto, 'invalidToken')).rejects.toThrow(UnauthorizedException); + }); + + it('should throw NotFoundException if journey is not found', async () => { + jest.spyOn(model, 'findById').mockReturnValueOnce({ + exec: jest.fn().mockResolvedValue(null), + } as any); + + await expect(service.findById('invalidId')).rejects.toThrow(NotFoundException); + }); + + it('should return all journeys', async () => { + const result = await service.findAll(); + expect(result).toEqual(mockJourneyList); + }); + + it('should return journeys by user ID', async () => { + const result = await service.findByUserId('mockUserId'); + expect(result).toEqual(mockJourneyList); + }); + + it('should update a journey', async () => { + const updatedJourneyDto: CreateJourneyDto = { + title: 'Updated Title', + description: 'Updated Description', + }; + + const result = await service.update('mockId', updatedJourneyDto); + expect(result).toEqual(mockJourney); + expect(model.findByIdAndUpdate).toHaveBeenCalledWith('mockId', updatedJourneyDto, { new: true }); + }); + + + it('should delete a journey', async () => { + const result = await service.delete('mockId'); + expect(result).toEqual(mockJourney); + expect(model.findByIdAndDelete).toHaveBeenCalledWith('mockId'); + }); + + it('should throw NotFoundException if journey is not found when adding a trail', async () => { + jest.spyOn(model, 'findById').mockReturnValueOnce({ + exec: jest.fn().mockResolvedValue(null), + } as any); + + await expect(service.addTrailToJourney('invalidId', 'mockTrailId')).rejects.toThrow(NotFoundException); + }); + + it('should return user id when token is valid', async () => { + const token = 'validToken'; + const mockResponse = { data: { userPayload: { id: 'userId123' } } }; + + jest.spyOn(mockHttpService, 'get').mockReturnValueOnce(of(mockResponse)); + + const result = await service.validateTokenAndGetUserId(token); + + expect(result).toBe('userId123'); + // Remove the logger log check if not required + }); + + it('should return null when token is invalid', async () => { + const token = 'invalidToken'; + const mockError = new Error('Token invalid'); + + jest.spyOn(mockHttpService, 'get').mockReturnValueOnce(throwError(mockError)); + + const result = await service.validateTokenAndGetUserId(token); + + expect(result).toBeNull(); + // Remove the logger error check if not required + }); + + + +}); \ No newline at end of file From 473632664af3ebbb5123ce613f40fddcb92e9ff1 Mon Sep 17 00:00:00 2001 From: DaviMatheus Date: Sun, 18 Aug 2024 20:28:03 -0300 Subject: [PATCH 08/11] [feat]#75-adding_journey_tests --- test/journey.controller.spec.ts | 135 ++++++++++++++++++++++++++++++++ 1 file changed, 135 insertions(+) create mode 100644 test/journey.controller.spec.ts diff --git a/test/journey.controller.spec.ts b/test/journey.controller.spec.ts new file mode 100644 index 0000000..5f05fde --- /dev/null +++ b/test/journey.controller.spec.ts @@ -0,0 +1,135 @@ +import { Test, TestingModule } from '@nestjs/testing'; + +import { UnauthorizedException } from '@nestjs/common'; +import { Request } from 'express'; +import { JourneyService } from 'src/journey/journey.service'; +import { JourneyController } from 'src/journey/journey.controller'; +import { CreateJourneyDto } from 'src/journey/dtos/create-journey.dto'; + +describe('JourneyController', () => { + let controller: JourneyController; + let service: JourneyService; + + const mockJourneyService = { + create: jest.fn(), + findAll: jest.fn(), + findByUserId: jest.fn(), + findById: jest.fn(), + update: jest.fn(), + delete: jest.fn(), + addTrailToJourney: jest.fn(), + }; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + controllers: [JourneyController], + providers: [ + { + provide: JourneyService, + useValue: mockJourneyService, + }, + ], + }).compile(); + + controller = module.get(JourneyController); + service = module.get(JourneyService); + }); + + it('should be defined', () => { + expect(controller).toBeDefined(); + }); + + describe('create', () => { + it('should create a journey', async () => { + const createJourneyDto: CreateJourneyDto = { + title: '', + description: '' + }; + const token = 'test-token'; + const req = { headers: { authorization: `Bearer ${token}` } } as Request; + + mockJourneyService.create.mockResolvedValue('some-value'); + + const result = await controller.create(createJourneyDto, req); + expect(result).toEqual('some-value'); + expect(mockJourneyService.create).toHaveBeenCalledWith( + createJourneyDto, + token, + ); + }); + + it('should throw UnauthorizedException if token is not provided', async () => { + const createJourneyDto: CreateJourneyDto = { + title: '', + description: '' + }; + const req = { headers: {} } as Request; + + await expect(controller.create(createJourneyDto, req)).rejects.toThrow(UnauthorizedException); + }); + }); + + describe('findAll', () => { + it('should return all journeys', async () => { + mockJourneyService.findAll.mockResolvedValue(['journey1', 'journey2']); + + const result = await controller.findAll(); + expect(result).toEqual(['journey1', 'journey2']); + }); + }); + + describe('findByUser', () => { + it('should return journeys by user id', async () => { + const userId = 'user-id'; + mockJourneyService.findByUserId.mockResolvedValue(['journey1']); + + const result = await controller.findByUser(userId); + expect(result).toEqual(['journey1']); + }); + }); + + describe('findById', () => { + it('should return a journey by id', async () => { + const id = 'journey-id'; + mockJourneyService.findById.mockResolvedValue('journey'); + + const result = await controller.findById(id); + expect(result).toEqual('journey'); + }); + }); + + describe('update', () => { + it('should update a journey', async () => { + const id = 'journey-id'; + const updateJourneyDto: CreateJourneyDto = { + title: '', + description: '' + }; + mockJourneyService.update.mockResolvedValue('updated-journey'); + + const result = await controller.update(id, updateJourneyDto); + expect(result).toEqual('updated-journey'); + }); + }); + + describe('delete', () => { + it('should delete a journey', async () => { + const id = 'journey-id'; + mockJourneyService.delete.mockResolvedValue('deleted'); + + const result = await controller.delete(id); + expect(result).toEqual('deleted'); + }); + }); + + describe('addTrailToJourney', () => { + it('should add a trail to a journey', async () => { + const id = 'journey-id'; + const trailId = 'trail-id'; + mockJourneyService.addTrailToJourney.mockResolvedValue('trail-added'); + + const result = await controller.addTrailToJourney(id, { trailId }); + expect(result).toEqual('trail-added'); + }); + }); +}); From 7819ee2602b438cd0f8a2794b82ff0a1551322ad Mon Sep 17 00:00:00 2001 From: DaviMatheus Date: Sun, 18 Aug 2024 20:28:29 -0300 Subject: [PATCH 09/11] [feat]#75-adding_trail_tests --- test/trail.controller.spec.ts | 128 ++++++++++++++++++++++++++++ test/trail.service.spec.ts | 151 ++++++++++++++++++++++++++++++++++ 2 files changed, 279 insertions(+) create mode 100644 test/trail.controller.spec.ts create mode 100644 test/trail.service.spec.ts diff --git a/test/trail.controller.spec.ts b/test/trail.controller.spec.ts new file mode 100644 index 0000000..fec5c2b --- /dev/null +++ b/test/trail.controller.spec.ts @@ -0,0 +1,128 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { NotFoundException } from '@nestjs/common'; +import { TrailController } from 'src/trail/trail.controller'; +import { TrailService } from 'src/trail/trail.service'; + +describe('TrailController', () => { + let controller: TrailController; + let service: TrailService; + + const mockTrailService = { + createTrail: jest.fn(), + findTrailById: jest.fn(), + findAllTrails: jest.fn(), + updateTrail: jest.fn(), + addContentToTrail: jest.fn(), + removeContentFromTrail: jest.fn(), + deleteTrail: jest.fn(), + }; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + controllers: [TrailController], + providers: [ + { + provide: TrailService, + useValue: mockTrailService, + }, + ], + }).compile(); + + controller = module.get(TrailController); + service = module.get(TrailService); + }); + + it('should be defined', () => { + expect(controller).toBeDefined(); + }); + + describe('createTrail', () => { + it('should create a trail', async () => { + const trailData = { name: 'Test Trail', journeyId: 'journey-id' }; + mockTrailService.createTrail.mockResolvedValue('trail-created'); + + const result = await controller.createTrail(trailData); + expect(result).toEqual('trail-created'); + expect(mockTrailService.createTrail).toHaveBeenCalledWith( + trailData.name, + trailData.journeyId, + ); + }); + + it('should throw NotFoundException if journeyId is not provided', async () => { + const trailData = { name: 'Test Trail', journeyId: '' }; + + await expect(controller.createTrail(trailData)).rejects.toThrow(NotFoundException); + }); + }); + + describe('getTrailById', () => { + it('should return a trail by id', async () => { + const trailId = 'trail-id'; + mockTrailService.findTrailById.mockResolvedValue('trail'); + + const result = await controller.getTrailById(trailId); + expect(result).toEqual('trail'); + }); + }); + + describe('getAllTrails', () => { + it('should return all trails', async () => { + mockTrailService.findAllTrails.mockResolvedValue(['trail1', 'trail2']); + + const result = await controller.getAllTrails(); + expect(result).toEqual(['trail1', 'trail2']); + }); + }); + + describe('updateTrail', () => { + it('should update a trail', async () => { + const trailId = 'trail-id'; + const updateData = { name: 'Updated Trail' }; + mockTrailService.updateTrail.mockResolvedValue('updated-trail'); + + const result = await controller.updateTrail(trailId, updateData); + expect(result).toEqual('updated-trail'); + }); + }); + + describe('addContentToTrail', () => { + it('should add content to a trail', async () => { + const trailId = 'trail-id'; + const contentData = { contentId: 'content-id' }; + mockTrailService.addContentToTrail.mockResolvedValue('content-added'); + + const result = await controller.addContentToTrail(trailId, contentData); + expect(result).toEqual('content-added'); + }); + + it('should throw NotFoundException if contentId is not provided', async () => { + const trailId = 'trail-id'; + const contentData = { contentId: '' }; + + await expect(controller.addContentToTrail(trailId, contentData)).rejects.toThrow(NotFoundException); + }); + }); + + describe('removeContentFromTrail', () => { + it('should remove content from a trail', async () => { + const trailId = 'trail-id'; + const contentData = { contentId: 'content-id' }; + mockTrailService.removeContentFromTrail.mockResolvedValue('content-removed'); + + const result = await controller.removeContentFromTrail(trailId, contentData); + expect(result).toEqual('content-removed'); + }); + }); + + describe('deleteTrail', () => { + it('should delete a trail', async () => { + const trailId = 'trail-id'; + mockTrailService.deleteTrail.mockResolvedValue(null); + + const result = await controller.deleteTrail(trailId); + expect(result).toEqual({ message: 'Trail deleted successfully' }); + expect(mockTrailService.deleteTrail).toHaveBeenCalledWith(trailId); + }); + }); +}); diff --git a/test/trail.service.spec.ts b/test/trail.service.spec.ts new file mode 100644 index 0000000..5e9a442 --- /dev/null +++ b/test/trail.service.spec.ts @@ -0,0 +1,151 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { getModelToken } from '@nestjs/mongoose'; +import { Model, Types } from 'mongoose'; +import { NotFoundException } from '@nestjs/common'; +import { JourneyService } from 'src/journey/journey.service'; +import { Journey } from 'src/journey/journey.schema'; +import { TrailService } from 'src/trail/trail.service'; +import { Trail } from 'src/trail/trail.schema'; + +describe('TrailService', () => { + let service: TrailService; + let trailModel: Model; + let journeyModel: Model; + let journeyService: JourneyService; + + const mockTrail = { + _id: 'mockTrailId', + name: 'Mock Trail', + journey: 'mockJourneyId', + contents: [], + save: jest.fn().mockResolvedValue(this), + }; + + const mockJourney = { + _id: 'mockJourneyId', + name: 'Mock Journey', + trails: [], + }; + + const mockTrailModel = { + create: jest.fn().mockResolvedValue(mockTrail), + findById: jest.fn().mockReturnValue({ + exec: jest.fn().mockResolvedValue(mockTrail), + }), + find: jest.fn().mockReturnValue({ + exec: jest.fn().mockResolvedValue([mockTrail]), + }), + findByIdAndUpdate: jest.fn().mockReturnValue({ + exec: jest.fn().mockResolvedValue(mockTrail), + }), + findByIdAndDelete: jest.fn().mockReturnValue({ + exec: jest.fn().mockResolvedValue(mockTrail), + }), + }; + + const mockJourneyModel = { + findById: jest.fn().mockReturnValue({ + exec: jest.fn().mockResolvedValue(mockJourney), + }), + }; + + const mockJourneyService = { + addTrailToJourney: jest.fn(), + }; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + providers: [ + TrailService, + { provide: getModelToken('Trail'), useValue: mockTrailModel }, + { provide: getModelToken('Journey'), useValue: mockJourneyModel }, + { provide: JourneyService, useValue: mockJourneyService }, + ], + }).compile(); + + service = module.get(TrailService); + trailModel = module.get>(getModelToken('Trail')); + journeyModel = module.get>(getModelToken('Journey')); + journeyService = module.get(JourneyService); + }); + + it('should be defined', () => { + expect(service).toBeDefined(); + }); + + + it('should throw NotFoundException if journey is not found when creating a trail', async () => { + jest.spyOn(journeyModel, 'findById').mockReturnValueOnce({ + exec: jest.fn().mockResolvedValue(null), + } as any); + + await expect(service.createTrail('New Trail', 'invalidJourneyId')).rejects.toThrow(NotFoundException); + }); + + + it('should throw NotFoundException if trail is not found when adding content', async () => { + jest.spyOn(trailModel, 'findById').mockReturnValueOnce({ + exec: jest.fn().mockResolvedValue(null), + } as any); + + await expect(service.addContentToTrail('invalidTrailId', 'mockContentId')).rejects.toThrow(NotFoundException); + }); + + + + it('should throw NotFoundException if trail is not found when removing content', async () => { + jest.spyOn(trailModel, 'findById').mockReturnValueOnce({ + exec: jest.fn().mockResolvedValue(null), + } as any); + + await expect(service.removeContentFromTrail('invalidTrailId', 'mockContentId')).rejects.toThrow(NotFoundException); + }); + + it('should find a trail by ID', async () => { + const result = await service.findTrailById('mockTrailId'); + expect(result).toEqual(mockTrail); + expect(trailModel.findById).toHaveBeenCalledWith('mockTrailId'); + }); + + it('should throw NotFoundException if trail is not found when finding by ID', async () => { + jest.spyOn(trailModel, 'findById').mockReturnValueOnce({ + exec: jest.fn().mockResolvedValue(null), + } as any); + + await expect(service.findTrailById('invalidTrailId')).rejects.toThrow(NotFoundException); + }); + + it('should return all trails', async () => { + const result = await service.findAllTrails(); + expect(result).toEqual([mockTrail]); + expect(trailModel.find).toHaveBeenCalled(); + }); + + it('should update a trail', async () => { + const updateData = { name: 'Updated Trail' }; + const result = await service.updateTrail('mockTrailId', updateData); + expect(result).toEqual(mockTrail); + expect(trailModel.findByIdAndUpdate).toHaveBeenCalledWith('mockTrailId', updateData, { new: true }); + }); + + it('should throw NotFoundException if trail is not found when updating', async () => { + jest.spyOn(trailModel, 'findByIdAndUpdate').mockReturnValueOnce({ + exec: jest.fn().mockResolvedValue(null), + } as any); + + await expect( + service.updateTrail('invalidTrailId', { name: 'Updated Trail' }), + ).rejects.toThrow(NotFoundException); + }); + + + + it('should throw NotFoundException if trail is not found when deleting', async () => { + jest.spyOn(trailModel, 'findByIdAndDelete').mockReturnValueOnce({ + exec: jest.fn().mockResolvedValue(null), + } as any); + + await expect(service.deleteTrail('invalidTrailId')).rejects.toThrow(NotFoundException); + }); +}); + From 8b9593964ac859ffec540897a90214224f94d90f Mon Sep 17 00:00:00 2001 From: DaviMatheus Date: Sun, 18 Aug 2024 20:42:50 -0300 Subject: [PATCH 10/11] [feat]#75-adding_content_tests --- test/content.controller.spec.ts | 167 +++++++++++++++++++++++++++++ test/content.service.spec.ts | 183 ++++++++++++++++++++++++++++++++ 2 files changed, 350 insertions(+) create mode 100644 test/content.controller.spec.ts create mode 100644 test/content.service.spec.ts diff --git a/test/content.controller.spec.ts b/test/content.controller.spec.ts new file mode 100644 index 0000000..7442a3f --- /dev/null +++ b/test/content.controller.spec.ts @@ -0,0 +1,167 @@ +import { Test, TestingModule } from '@nestjs/testing'; + +import { NotFoundException } from '@nestjs/common'; +import { ContentController } from 'src/content/content.controller'; +import { ContentService } from 'src/content/content.service'; +import { Content } from 'src/content/content.schema'; + + +describe('ContentController', () => { + let controller: ContentController; + let service: ContentService; + + const mockContentService = { + createContent: jest.fn(), + findContentById: jest.fn(), + findAllContents: jest.fn(), + updateContent: jest.fn(), + deleteContent: jest.fn(), + }; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + controllers: [ContentController], + providers: [ + { + provide: ContentService, + useValue: mockContentService, + }, + ], + }).compile(); + + controller = module.get(ContentController); + service = module.get(ContentService); + }); + + it('should be defined', () => { + expect(controller).toBeDefined(); + }); + + describe('createContent', () => { + it('should create content', async () => { + const contentDto = { + title: 'Test Title', + content: 'Test Content', + trailId: 'trail-id', + }; + const content = { + _id: 'content-id', + ...contentDto, + } as unknown as Content; + + mockContentService.createContent.mockResolvedValue(content); + + const result = await controller.createContent(contentDto); + expect(result).toEqual(content); + expect(mockContentService.createContent).toHaveBeenCalledWith( + contentDto.title, + contentDto.content, + contentDto.trailId, + ); + }); + + it('should throw NotFoundException if required fields are missing', async () => { + const contentDto = { title: '', content: '', trailId: '' }; + + await expect(controller.createContent(contentDto)).rejects.toThrow( + NotFoundException, + ); + }); + }); + + describe('findContentById', () => { + it('should return content by id', async () => { + const content = { + _id: 'content-id', + title: 'Test Title', + content: 'Test Content', + } as Content; + + mockContentService.findContentById.mockResolvedValue(content); + + const result = await controller.findContentById('content-id'); + expect(result).toEqual(content); + expect(mockContentService.findContentById).toHaveBeenCalledWith( + 'content-id', + ); + }); + + it('should throw NotFoundException if content is not found', async () => { + mockContentService.findContentById.mockRejectedValue( + new NotFoundException(), + ); + + await expect(controller.findContentById('invalid-id')).rejects.toThrow( + NotFoundException, + ); + }); + }); + + describe('findAllContents', () => { + it('should return all contents', async () => { + const contents = [ + { _id: 'content-id-1', title: 'Title 1', content: 'Content 1' }, + { _id: 'content-id-2', title: 'Title 2', content: 'Content 2' }, + ] as Content[]; + + mockContentService.findAllContents.mockResolvedValue(contents); + + const result = await controller.findAllContents(); + expect(result).toEqual(contents); + expect(mockContentService.findAllContents).toHaveBeenCalled(); + }); + }); + + describe('updateContent', () => { + it('should update content and return the updated content', async () => { + const content = { + _id: 'content-id', + title: 'Updated Title', + content: 'Updated Content', + } as Content; + + mockContentService.updateContent.mockResolvedValue(content); + + const updateData = { title: 'Updated Title', content: 'Updated Content' }; + const result = await controller.updateContent('content-id', updateData); + expect(result).toEqual(content); + expect(mockContentService.updateContent).toHaveBeenCalledWith( + 'content-id', + updateData, + ); + }); + + it('should throw NotFoundException if content is not found', async () => { + mockContentService.updateContent.mockRejectedValue( + new NotFoundException(), + ); + + await expect(controller.updateContent('invalid-id', {})).rejects.toThrow( + NotFoundException, + ); + }); + }); + + describe('deleteContent', () => { + it('should delete content', async () => { + mockContentService.deleteContent.mockResolvedValue(undefined); + + await expect( + controller.deleteContent('content-id'), + ).resolves.not.toThrow(); + expect(mockContentService.deleteContent).toHaveBeenCalledWith( + 'content-id', + ); + }); + + it('should throw NotFoundException if content is not found', async () => { + mockContentService.deleteContent.mockRejectedValue( + new NotFoundException(), + ); + + await expect(controller.deleteContent('invalid-id')).rejects.toThrow( + NotFoundException, + ); + }); + }); +}); \ No newline at end of file diff --git a/test/content.service.spec.ts b/test/content.service.spec.ts new file mode 100644 index 0000000..e7d6408 --- /dev/null +++ b/test/content.service.spec.ts @@ -0,0 +1,183 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { getModelToken } from '@nestjs/mongoose'; +import { NotFoundException } from '@nestjs/common'; +import { Model } from 'mongoose'; +import { ContentService } from 'src/content/content.service'; +import { Content } from 'src/content/content.schema'; +import { Trail } from 'src/trail/trail.schema'; +import { TrailService } from 'src/trail/trail.service'; + +describe('ContentService', () => { + let service: ContentService; + let contentModel: Model; + let trailModel: Model; + let trailService: TrailService; + + const mockContentModel = { + findById: jest.fn(), + findByIdAndUpdate: jest.fn(), + findByIdAndDelete: jest.fn(), + find: jest.fn(), + exec: jest.fn(), + save: jest.fn(), + create: jest.fn().mockImplementation((dto) => dto), + }; + + const mockTrailModel = { + findById: jest.fn(), + }; + + const mockTrailService = { + addContentToTrail: jest.fn(), + }; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + providers: [ + ContentService, + { + provide: getModelToken('Content'), + useValue: mockContentModel, + }, + { + provide: getModelToken('Trail'), + useValue: mockTrailModel, + }, + { + provide: TrailService, + useValue: mockTrailService, + }, + ], + }).compile(); + + service = module.get(ContentService); + contentModel = module.get>(getModelToken('Content')); + trailModel = module.get>(getModelToken('Trail')); + trailService = module.get(TrailService); + }); + + it('should be defined', () => { + expect(service).toBeDefined(); + }); + + describe('createContent', () => { + it('should throw NotFoundException if trail does not exist', async () => { + const contentDto = { + title: 'Test Title', + content: 'Test Content', + trailId: 'invalid-trail-id', + }; + + mockTrailModel.findById.mockReturnValueOnce({ + exec: jest.fn().mockResolvedValue(null), + }); + + await expect( + service.createContent( + contentDto.title, + contentDto.content, + contentDto.trailId, + ), + ).rejects.toThrow(NotFoundException); + }); + }); + + describe('findContentById', () => { + it('should return content by id', async () => { + const content = { + _id: 'content-id', + title: 'Test Title', + content: 'Test Content', + }; + mockContentModel.findById.mockReturnValueOnce({ + exec: jest.fn().mockResolvedValue(content), + }); + + const result = await service.findContentById('content-id'); + expect(result).toEqual(content); + expect(mockContentModel.findById).toHaveBeenCalledWith('content-id'); + }); + + it('should throw NotFoundException if content does not exist', async () => { + mockContentModel.findById.mockReturnValueOnce({ + exec: jest.fn().mockResolvedValue(null), + }); + + await expect( + service.findContentById('invalid-content-id'), + ).rejects.toThrow(NotFoundException); + }); + }); + + describe('findAllContents', () => { + it('should return all contents', async () => { + const contents = [ + { _id: 'content-id', title: 'Test Title', content: 'Test Content' }, + ]; + mockContentModel.find.mockReturnValueOnce({ + exec: jest.fn().mockResolvedValue(contents), + }); + + const result = await service.findAllContents(); + expect(result).toEqual(contents); + expect(mockContentModel.find).toHaveBeenCalled(); + }); + }); + + describe('updateContent', () => { + it('should update content and return the updated content', async () => { + const content = { + _id: 'content-id', + title: 'Updated Title', + content: 'Updated Content', + }; + mockContentModel.findByIdAndUpdate.mockReturnValueOnce({ + exec: jest.fn().mockResolvedValue(content), + }); + + const result = await service.updateContent('content-id', { + title: 'Updated Title', + content: 'Updated Content', + }); + expect(result).toEqual(content); + expect(mockContentModel.findByIdAndUpdate).toHaveBeenCalledWith( + 'content-id', + { title: 'Updated Title', content: 'Updated Content' }, + { new: true }, + ); + }); + + it('should throw NotFoundException if content does not exist', async () => { + mockContentModel.findByIdAndUpdate.mockReturnValueOnce({ + exec: jest.fn().mockResolvedValue(null), + }); + + await expect( + service.updateContent('invalid-content-id', { title: 'Updated Title' }), + ).rejects.toThrow(NotFoundException); + }); + }); + + describe('deleteContent', () => { + it('should delete content', async () => { + mockContentModel.findByIdAndDelete.mockReturnValueOnce({ + exec: jest.fn().mockResolvedValue({}), + }); + + await service.deleteContent('content-id'); + expect(mockContentModel.findByIdAndDelete).toHaveBeenCalledWith( + 'content-id', + ); + }); + + it('should throw NotFoundException if content does not exist', async () => { + mockContentModel.findByIdAndDelete.mockReturnValueOnce({ + exec: jest.fn().mockResolvedValue(null), + }); + + await expect(service.deleteContent('invalid-content-id')).rejects.toThrow( + NotFoundException, + ); + }); + }); +}); From c858f399de0435a43c8482fe34d18599f3d62d1f Mon Sep 17 00:00:00 2001 From: DaviMatheus Date: Sun, 18 Aug 2024 20:46:17 -0300 Subject: [PATCH 11/11] [feat]#75-running-lint --- src/content/content.controller.ts | 1 - src/trail/trail.controller.ts | 4 +-- src/trail/trail.service.ts | 13 ++++++--- test/content.controller.spec.ts | 9 ++---- test/content.service.spec.ts | 9 ------ test/journey.controller.spec.ts | 14 +++++----- test/journey.service.spec.ts | 46 ++++++++++++++++++------------- test/trail.controller.spec.ts | 19 +++++++++---- test/trail.service.spec.ts | 37 ++++++++++++++----------- 9 files changed, 82 insertions(+), 70 deletions(-) diff --git a/src/content/content.controller.ts b/src/content/content.controller.ts index edfb8e8..a5a7c44 100644 --- a/src/content/content.controller.ts +++ b/src/content/content.controller.ts @@ -6,7 +6,6 @@ import { Delete, Param, Body, - Headers, NotFoundException, } from '@nestjs/common'; import { ContentService } from './content.service'; diff --git a/src/trail/trail.controller.ts b/src/trail/trail.controller.ts index 29d77b9..860d142 100644 --- a/src/trail/trail.controller.ts +++ b/src/trail/trail.controller.ts @@ -58,12 +58,12 @@ export class TrailController { @Put(':id/removeContent') async removeContentFromTrail( @Param('id') trailId: string, - @Body() body: { contentId: string } + @Body() body: { contentId: string }, ) { const { contentId } = body; return this.trailService.removeContentFromTrail(trailId, contentId); } - + @Delete(':id') async deleteTrail(@Param('id') id: string) { await this.trailService.deleteTrail(id); diff --git a/src/trail/trail.service.ts b/src/trail/trail.service.ts index a84dbee..d5f2fb2 100644 --- a/src/trail/trail.service.ts +++ b/src/trail/trail.service.ts @@ -49,16 +49,21 @@ export class TrailService { return trail.save(); } - async removeContentFromTrail(trailId: string, contentId: string): Promise { + async removeContentFromTrail( + trailId: string, + contentId: string, + ): Promise { const trail = await this.trailModel.findById(trailId).exec(); if (!trail) { - throw new NotFoundException(`Trail with ID ${trailId} not found`); + throw new NotFoundException(`Trail with ID ${trailId} not found`); } - trail.contents = trail.contents.filter(content => !content.equals(contentId)); + trail.contents = trail.contents.filter( + (content) => !content.equals(contentId), + ); return trail.save(); -} + } async findTrailById(id: string): Promise { const trail = await this.trailModel.findById(id).exec(); diff --git a/test/content.controller.spec.ts b/test/content.controller.spec.ts index 7442a3f..025ed2b 100644 --- a/test/content.controller.spec.ts +++ b/test/content.controller.spec.ts @@ -5,10 +5,8 @@ import { ContentController } from 'src/content/content.controller'; import { ContentService } from 'src/content/content.service'; import { Content } from 'src/content/content.schema'; - describe('ContentController', () => { let controller: ContentController; - let service: ContentService; const mockContentService = { createContent: jest.fn(), @@ -30,7 +28,6 @@ describe('ContentController', () => { }).compile(); controller = module.get(ContentController); - service = module.get(ContentService); }); it('should be defined', () => { @@ -76,7 +73,7 @@ describe('ContentController', () => { title: 'Test Title', content: 'Test Content', } as Content; - + mockContentService.findContentById.mockResolvedValue(content); const result = await controller.findContentById('content-id'); @@ -119,7 +116,7 @@ describe('ContentController', () => { title: 'Updated Title', content: 'Updated Content', } as Content; - + mockContentService.updateContent.mockResolvedValue(content); const updateData = { title: 'Updated Title', content: 'Updated Content' }; @@ -164,4 +161,4 @@ describe('ContentController', () => { ); }); }); -}); \ No newline at end of file +}); diff --git a/test/content.service.spec.ts b/test/content.service.spec.ts index e7d6408..fcee0b6 100644 --- a/test/content.service.spec.ts +++ b/test/content.service.spec.ts @@ -1,17 +1,11 @@ import { Test, TestingModule } from '@nestjs/testing'; import { getModelToken } from '@nestjs/mongoose'; import { NotFoundException } from '@nestjs/common'; -import { Model } from 'mongoose'; import { ContentService } from 'src/content/content.service'; -import { Content } from 'src/content/content.schema'; -import { Trail } from 'src/trail/trail.schema'; import { TrailService } from 'src/trail/trail.service'; describe('ContentService', () => { let service: ContentService; - let contentModel: Model; - let trailModel: Model; - let trailService: TrailService; const mockContentModel = { findById: jest.fn(), @@ -51,9 +45,6 @@ describe('ContentService', () => { }).compile(); service = module.get(ContentService); - contentModel = module.get>(getModelToken('Content')); - trailModel = module.get>(getModelToken('Trail')); - trailService = module.get(TrailService); }); it('should be defined', () => { diff --git a/test/journey.controller.spec.ts b/test/journey.controller.spec.ts index 5f05fde..d149e8c 100644 --- a/test/journey.controller.spec.ts +++ b/test/journey.controller.spec.ts @@ -8,7 +8,6 @@ import { CreateJourneyDto } from 'src/journey/dtos/create-journey.dto'; describe('JourneyController', () => { let controller: JourneyController; - let service: JourneyService; const mockJourneyService = { create: jest.fn(), @@ -32,7 +31,6 @@ describe('JourneyController', () => { }).compile(); controller = module.get(JourneyController); - service = module.get(JourneyService); }); it('should be defined', () => { @@ -43,11 +41,11 @@ describe('JourneyController', () => { it('should create a journey', async () => { const createJourneyDto: CreateJourneyDto = { title: '', - description: '' + description: '', }; const token = 'test-token'; const req = { headers: { authorization: `Bearer ${token}` } } as Request; - + mockJourneyService.create.mockResolvedValue('some-value'); const result = await controller.create(createJourneyDto, req); @@ -61,11 +59,13 @@ describe('JourneyController', () => { it('should throw UnauthorizedException if token is not provided', async () => { const createJourneyDto: CreateJourneyDto = { title: '', - description: '' + description: '', }; const req = { headers: {} } as Request; - await expect(controller.create(createJourneyDto, req)).rejects.toThrow(UnauthorizedException); + await expect(controller.create(createJourneyDto, req)).rejects.toThrow( + UnauthorizedException, + ); }); }); @@ -103,7 +103,7 @@ describe('JourneyController', () => { const id = 'journey-id'; const updateJourneyDto: CreateJourneyDto = { title: '', - description: '' + description: '', }; mockJourneyService.update.mockResolvedValue('updated-journey'); diff --git a/test/journey.service.spec.ts b/test/journey.service.spec.ts index 0f9856c..2f9cb00 100644 --- a/test/journey.service.spec.ts +++ b/test/journey.service.spec.ts @@ -2,17 +2,19 @@ import { Test, TestingModule } from '@nestjs/testing'; import { JourneyService } from '../src/journey/journey.service'; import { getModelToken } from '@nestjs/mongoose'; import { HttpService } from '@nestjs/axios'; -import { Model, Types } from 'mongoose'; +import { Model } from 'mongoose'; import { Journey } from '../src/journey/journey.schema'; import { CreateJourneyDto } from '../src/journey/dtos/create-journey.dto'; -import { UnauthorizedException, NotFoundException, Logger } from '@nestjs/common'; +import { + UnauthorizedException, + NotFoundException, + Logger, +} from '@nestjs/common'; import { of, throwError } from 'rxjs'; describe('JourneyService', () => { let service: JourneyService; let model: Model; - let httpService: HttpService; - let logger: Logger; const mockJourney = { _id: 'mockId', @@ -72,8 +74,6 @@ describe('JourneyService', () => { service = module.get(JourneyService); model = module.get>(getModelToken('Journey')); - httpService = module.get(HttpService); - logger = module.get(Logger); }); it('should be defined', () => { @@ -81,9 +81,13 @@ describe('JourneyService', () => { }); it('should throw UnauthorizedException if token is invalid', async () => { - jest.spyOn(mockHttpService, 'get').mockReturnValueOnce(throwError(new Error('Invalid token'))); + jest + .spyOn(mockHttpService, 'get') + .mockReturnValueOnce(throwError(new Error('Invalid token'))); - await expect(service.create(mockCreateJourneyDto, 'invalidToken')).rejects.toThrow(UnauthorizedException); + await expect( + service.create(mockCreateJourneyDto, 'invalidToken'), + ).rejects.toThrow(UnauthorizedException); }); it('should throw NotFoundException if journey is not found', async () => { @@ -91,7 +95,9 @@ describe('JourneyService', () => { exec: jest.fn().mockResolvedValue(null), } as any); - await expect(service.findById('invalidId')).rejects.toThrow(NotFoundException); + await expect(service.findById('invalidId')).rejects.toThrow( + NotFoundException, + ); }); it('should return all journeys', async () => { @@ -112,10 +118,13 @@ describe('JourneyService', () => { const result = await service.update('mockId', updatedJourneyDto); expect(result).toEqual(mockJourney); - expect(model.findByIdAndUpdate).toHaveBeenCalledWith('mockId', updatedJourneyDto, { new: true }); + expect(model.findByIdAndUpdate).toHaveBeenCalledWith( + 'mockId', + updatedJourneyDto, + { new: true }, + ); }); - it('should delete a journey', async () => { const result = await service.delete('mockId'); expect(result).toEqual(mockJourney); @@ -127,7 +136,9 @@ describe('JourneyService', () => { exec: jest.fn().mockResolvedValue(null), } as any); - await expect(service.addTrailToJourney('invalidId', 'mockTrailId')).rejects.toThrow(NotFoundException); + await expect( + service.addTrailToJourney('invalidId', 'mockTrailId'), + ).rejects.toThrow(NotFoundException); }); it('should return user id when token is valid', async () => { @@ -139,21 +150,18 @@ describe('JourneyService', () => { const result = await service.validateTokenAndGetUserId(token); expect(result).toBe('userId123'); - // Remove the logger log check if not required }); it('should return null when token is invalid', async () => { const token = 'invalidToken'; const mockError = new Error('Token invalid'); - jest.spyOn(mockHttpService, 'get').mockReturnValueOnce(throwError(mockError)); + jest + .spyOn(mockHttpService, 'get') + .mockReturnValueOnce(throwError(mockError)); const result = await service.validateTokenAndGetUserId(token); expect(result).toBeNull(); - // Remove the logger error check if not required }); - - - -}); \ No newline at end of file +}); diff --git a/test/trail.controller.spec.ts b/test/trail.controller.spec.ts index fec5c2b..5297a05 100644 --- a/test/trail.controller.spec.ts +++ b/test/trail.controller.spec.ts @@ -5,7 +5,6 @@ import { TrailService } from 'src/trail/trail.service'; describe('TrailController', () => { let controller: TrailController; - let service: TrailService; const mockTrailService = { createTrail: jest.fn(), @@ -29,7 +28,6 @@ describe('TrailController', () => { }).compile(); controller = module.get(TrailController); - service = module.get(TrailService); }); it('should be defined', () => { @@ -52,7 +50,9 @@ describe('TrailController', () => { it('should throw NotFoundException if journeyId is not provided', async () => { const trailData = { name: 'Test Trail', journeyId: '' }; - await expect(controller.createTrail(trailData)).rejects.toThrow(NotFoundException); + await expect(controller.createTrail(trailData)).rejects.toThrow( + NotFoundException, + ); }); }); @@ -100,7 +100,9 @@ describe('TrailController', () => { const trailId = 'trail-id'; const contentData = { contentId: '' }; - await expect(controller.addContentToTrail(trailId, contentData)).rejects.toThrow(NotFoundException); + await expect( + controller.addContentToTrail(trailId, contentData), + ).rejects.toThrow(NotFoundException); }); }); @@ -108,9 +110,14 @@ describe('TrailController', () => { it('should remove content from a trail', async () => { const trailId = 'trail-id'; const contentData = { contentId: 'content-id' }; - mockTrailService.removeContentFromTrail.mockResolvedValue('content-removed'); + mockTrailService.removeContentFromTrail.mockResolvedValue( + 'content-removed', + ); - const result = await controller.removeContentFromTrail(trailId, contentData); + const result = await controller.removeContentFromTrail( + trailId, + contentData, + ); expect(result).toEqual('content-removed'); }); }); diff --git a/test/trail.service.spec.ts b/test/trail.service.spec.ts index 5e9a442..331acd3 100644 --- a/test/trail.service.spec.ts +++ b/test/trail.service.spec.ts @@ -1,6 +1,6 @@ import { Test, TestingModule } from '@nestjs/testing'; import { getModelToken } from '@nestjs/mongoose'; -import { Model, Types } from 'mongoose'; +import { Model } from 'mongoose'; import { NotFoundException } from '@nestjs/common'; import { JourneyService } from 'src/journey/journey.service'; import { Journey } from 'src/journey/journey.schema'; @@ -11,7 +11,6 @@ describe('TrailService', () => { let service: TrailService; let trailModel: Model; let journeyModel: Model; - let journeyService: JourneyService; const mockTrail = { _id: 'mockTrailId', @@ -66,39 +65,40 @@ describe('TrailService', () => { service = module.get(TrailService); trailModel = module.get>(getModelToken('Trail')); journeyModel = module.get>(getModelToken('Journey')); - journeyService = module.get(JourneyService); }); it('should be defined', () => { expect(service).toBeDefined(); }); - it('should throw NotFoundException if journey is not found when creating a trail', async () => { jest.spyOn(journeyModel, 'findById').mockReturnValueOnce({ exec: jest.fn().mockResolvedValue(null), } as any); - await expect(service.createTrail('New Trail', 'invalidJourneyId')).rejects.toThrow(NotFoundException); + await expect( + service.createTrail('New Trail', 'invalidJourneyId'), + ).rejects.toThrow(NotFoundException); }); - it('should throw NotFoundException if trail is not found when adding content', async () => { jest.spyOn(trailModel, 'findById').mockReturnValueOnce({ exec: jest.fn().mockResolvedValue(null), } as any); - await expect(service.addContentToTrail('invalidTrailId', 'mockContentId')).rejects.toThrow(NotFoundException); + await expect( + service.addContentToTrail('invalidTrailId', 'mockContentId'), + ).rejects.toThrow(NotFoundException); }); - - it('should throw NotFoundException if trail is not found when removing content', async () => { jest.spyOn(trailModel, 'findById').mockReturnValueOnce({ exec: jest.fn().mockResolvedValue(null), } as any); - await expect(service.removeContentFromTrail('invalidTrailId', 'mockContentId')).rejects.toThrow(NotFoundException); + await expect( + service.removeContentFromTrail('invalidTrailId', 'mockContentId'), + ).rejects.toThrow(NotFoundException); }); it('should find a trail by ID', async () => { @@ -112,7 +112,9 @@ describe('TrailService', () => { exec: jest.fn().mockResolvedValue(null), } as any); - await expect(service.findTrailById('invalidTrailId')).rejects.toThrow(NotFoundException); + await expect(service.findTrailById('invalidTrailId')).rejects.toThrow( + NotFoundException, + ); }); it('should return all trails', async () => { @@ -125,7 +127,11 @@ describe('TrailService', () => { const updateData = { name: 'Updated Trail' }; const result = await service.updateTrail('mockTrailId', updateData); expect(result).toEqual(mockTrail); - expect(trailModel.findByIdAndUpdate).toHaveBeenCalledWith('mockTrailId', updateData, { new: true }); + expect(trailModel.findByIdAndUpdate).toHaveBeenCalledWith( + 'mockTrailId', + updateData, + { new: true }, + ); }); it('should throw NotFoundException if trail is not found when updating', async () => { @@ -138,14 +144,13 @@ describe('TrailService', () => { ).rejects.toThrow(NotFoundException); }); - - it('should throw NotFoundException if trail is not found when deleting', async () => { jest.spyOn(trailModel, 'findByIdAndDelete').mockReturnValueOnce({ exec: jest.fn().mockResolvedValue(null), } as any); - await expect(service.deleteTrail('invalidTrailId')).rejects.toThrow(NotFoundException); + await expect(service.deleteTrail('invalidTrailId')).rejects.toThrow( + NotFoundException, + ); }); }); -