diff --git a/src/modules/simulado/dtos/update-disponibilidade.dto.input.ts b/src/modules/simulado/dtos/update-disponibilidade.dto.input.ts new file mode 100644 index 0000000..72a4f54 --- /dev/null +++ b/src/modules/simulado/dtos/update-disponibilidade.dto.input.ts @@ -0,0 +1,17 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { Type } from 'class-transformer'; +import { IsDate, IsOptional } from 'class-validator'; + +export class UpdateDisponibilidadeDTO { + @ApiProperty({ required: false, nullable: true, type: Date }) + @IsOptional() + @IsDate() + @Type(() => Date) + disponivelDe?: Date | null; + + @ApiProperty({ required: false, nullable: true, type: Date }) + @IsOptional() + @IsDate() + @Type(() => Date) + disponivelAte?: Date | null; +} diff --git a/src/modules/simulado/helpers/availability.spec.ts b/src/modules/simulado/helpers/availability.spec.ts new file mode 100644 index 0000000..ef09f08 --- /dev/null +++ b/src/modules/simulado/helpers/availability.spec.ts @@ -0,0 +1,118 @@ +import { getAvailabilityStatus, isSimuladoAvailable } from './availability'; + +// Datas fixas de referência (UTC) para determinismo — sem depender do relógio real. +const DE = new Date('2026-01-10T00:00:00.000Z'); +const ATE = new Date('2026-01-20T00:00:00.000Z'); +const ANTES = new Date('2026-01-05T00:00:00.000Z'); +const DENTRO = new Date('2026-01-15T00:00:00.000Z'); +const DEPOIS = new Date('2026-01-25T00:00:00.000Z'); + +describe('isSimuladoAvailable', () => { + it('false quando bloqueado, independente da janela', () => { + expect(isSimuladoAvailable({ bloqueado: true }, DENTRO)).toBe(false); + expect( + isSimuladoAvailable( + { bloqueado: true, disponivelDe: DE, disponivelAte: ATE }, + DENTRO, + ), + ).toBe(false); + }); + + it('true sem janela (null/null) quando desbloqueado', () => { + expect( + isSimuladoAvailable( + { bloqueado: false, disponivelDe: null, disponivelAte: null }, + DENTRO, + ), + ).toBe(true); + }); + + it('true quando desbloqueado, sem janela', () => { + expect(isSimuladoAvailable({ bloqueado: false }, DENTRO)).toBe(true); + }); + + it('só de: false antes, true no limite e depois de disponivelDe', () => { + const s = { + bloqueado: false, + disponivelDe: DE, + disponivelAte: null, + } as any; + expect(isSimuladoAvailable(s, ANTES)).toBe(false); + expect(isSimuladoAvailable(s, DE)).toBe(true); // limite inclusivo + expect(isSimuladoAvailable(s, DEPOIS)).toBe(true); + }); + + it('só até: true antes e no limite, false depois de disponivelAte', () => { + const s = { + bloqueado: false, + disponivelDe: null, + disponivelAte: ATE, + } as any; + expect(isSimuladoAvailable(s, ANTES)).toBe(true); + expect(isSimuladoAvailable(s, ATE)).toBe(true); // limite inclusivo + expect(isSimuladoAvailable(s, DEPOIS)).toBe(false); + }); + + it('de + até: false antes, true dentro, false depois', () => { + const s = { bloqueado: false, disponivelDe: DE, disponivelAte: ATE }; + expect(isSimuladoAvailable(s, ANTES)).toBe(false); + expect(isSimuladoAvailable(s, DENTRO)).toBe(true); + expect(isSimuladoAvailable(s, DEPOIS)).toBe(false); + }); +}); + +describe('getAvailabilityStatus', () => { + it("'bloqueado' quando bloqueado, independente da janela", () => { + expect(getAvailabilityStatus({ bloqueado: true }, DENTRO)).toBe( + 'bloqueado', + ); + expect( + getAvailabilityStatus( + { bloqueado: true, disponivelDe: DE, disponivelAte: ATE }, + ANTES, + ), + ).toBe('bloqueado'); + }); + + it("'disponivel' sem janela quando desbloqueado", () => { + expect( + getAvailabilityStatus( + { bloqueado: false, disponivelDe: null, disponivelAte: null }, + DENTRO, + ), + ).toBe('disponivel'); + }); + + it("'antes_da_janela' quando now < disponivelDe", () => { + expect( + getAvailabilityStatus( + { bloqueado: false, disponivelDe: DE, disponivelAte: ATE }, + ANTES, + ), + ).toBe('antes_da_janela'); + }); + + it("'disponivel' quando dentro da janela", () => { + expect( + getAvailabilityStatus( + { bloqueado: false, disponivelDe: DE, disponivelAte: ATE }, + DENTRO, + ), + ).toBe('disponivel'); + }); + + it("'depois_da_janela' quando now > disponivelAte", () => { + expect( + getAvailabilityStatus( + { bloqueado: false, disponivelDe: DE, disponivelAte: ATE }, + DEPOIS, + ), + ).toBe('depois_da_janela'); + }); + + it('limites inclusivos: disponivel exatamente em disponivelDe e disponivelAte', () => { + const s = { bloqueado: false, disponivelDe: DE, disponivelAte: ATE }; + expect(getAvailabilityStatus(s, DE)).toBe('disponivel'); + expect(getAvailabilityStatus(s, ATE)).toBe('disponivel'); + }); +}); diff --git a/src/modules/simulado/helpers/availability.ts b/src/modules/simulado/helpers/availability.ts new file mode 100644 index 0000000..6f63a23 --- /dev/null +++ b/src/modules/simulado/helpers/availability.ts @@ -0,0 +1,37 @@ +import { Simulado } from '../schemas/simulado.schema'; + +/** + * Fonte única de verdade sobre "esse simulado está disponível agora?". + * Combina o gate existente `bloqueado` com a janela temporal opcional + * (`disponivelDe` / `disponivelAte`). Funções puras, sem acesso a banco. + */ +export type AvailabilityStatus = + | 'disponivel' // bloqueado=false + dentro da janela (ou sem janela) + | 'bloqueado' // questões pendentes + | 'antes_da_janela' // bloqueado=false, mas now < disponivelDe + | 'depois_da_janela'; // bloqueado=false, mas now > disponivelAte + +type AvailabilityInput = Pick< + Simulado, + 'bloqueado' | 'disponivelDe' | 'disponivelAte' +>; + +export function isSimuladoAvailable( + s: AvailabilityInput, + now: Date = new Date(), +): boolean { + if (s.bloqueado) return false; + if (s.disponivelDe && now < s.disponivelDe) return false; + if (s.disponivelAte && now > s.disponivelAte) return false; + return true; +} + +export function getAvailabilityStatus( + s: AvailabilityInput, + now: Date = new Date(), +): AvailabilityStatus { + if (s.bloqueado) return 'bloqueado'; + if (s.disponivelDe && now < s.disponivelDe) return 'antes_da_janela'; + if (s.disponivelAte && now > s.disponivelAte) return 'depois_da_janela'; + return 'disponivel'; +} diff --git a/src/modules/simulado/schemas/simulado.schema.ts b/src/modules/simulado/schemas/simulado.schema.ts index 85caa7a..428578a 100644 --- a/src/modules/simulado/schemas/simulado.schema.ts +++ b/src/modules/simulado/schemas/simulado.schema.ts @@ -46,6 +46,23 @@ export class Simulado extends BaseSchema { @Prop({ required: false, default: null }) @ApiProperty() cursinhoId?: string | null; + + @Prop({ required: false, default: null }) + @ApiProperty({ required: false, nullable: true }) + disponivelDe?: Date | null; + + @Prop({ required: false, default: null }) + @ApiProperty({ required: false, nullable: true }) + disponivelAte?: Date | null; } export const SimuladoSchema = SchemaFactory.createForClass(Simulado); + +// Índice composto para o filtro de availability do getAvailable (Card 02). +// autoIndex (default true no MongooseModule) cria automaticamente no boot. +SimuladoSchema.index({ + categoria: 1, + bloqueado: 1, + disponivelDe: 1, + disponivelAte: 1, +}); diff --git a/src/modules/simulado/simulado.controller.ts b/src/modules/simulado/simulado.controller.ts index b4bfd9d..e83a953 100644 --- a/src/modules/simulado/simulado.controller.ts +++ b/src/modules/simulado/simulado.controller.ts @@ -5,6 +5,7 @@ import { Get, HttpCode, Param, + Patch, Post, Query, } from '@nestjs/common'; @@ -14,6 +15,7 @@ import { GetAllDtoOutput } from 'src/shared/dtos/get-all.dto.output'; import { AnswerSimuladoDto } from './dtos/answer-simulado.dto.input'; import { AvailableSimuladoDTOoutput } from './dtos/available-simulado.dto.output'; import { SimuladoAnswerDTOOutput } from './dtos/simulado-answer.dto.output'; +import { UpdateDisponibilidadeDTO } from './dtos/update-disponibilidade.dto.input'; import { Simulado } from './schemas/simulado.schema'; import { SimuladoService } from './simulado.service'; @@ -52,6 +54,20 @@ export class SimuladoController { return this.service.answer(answer); } + @Patch(':id/disponibilidade') + @ApiResponse({ + status: 200, + description: 'atualiza a janela de disponibilidade do simulado', + type: Simulado, + isArray: false, + }) + public async updateDisponibilidade( + @Param('id') id: string, + @Body() dto: UpdateDisponibilidadeDTO, + ): Promise { + return await this.service.updateDisponibilidade(id, dto); + } + @Get('available') public async getAvailable( @Query('tipo') tipo: string, diff --git a/src/modules/simulado/simulado.repository.spec.ts b/src/modules/simulado/simulado.repository.spec.ts index cf6d7ea..69adca1 100644 --- a/src/modules/simulado/simulado.repository.spec.ts +++ b/src/modules/simulado/simulado.repository.spec.ts @@ -14,3 +14,79 @@ describe('SimuladoRepository.countByCategoria', () => { }); }); }); + +describe('SimuladoRepository.updateDisponibilidade', () => { + it('faz $set apenas com os campos fornecidos', async () => { + const updateOne = jest.fn().mockResolvedValue({ acknowledged: true }); + const repo = new SimuladoRepository({ updateOne } as any); + + const de = new Date('2026-01-10T00:00:00.000Z'); + await repo.updateDisponibilidade('sim-1', { disponivelDe: de }); + + expect(updateOne).toHaveBeenCalledWith( + { _id: 'sim-1' }, + { $set: { disponivelDe: de } }, + ); + }); + + it('propaga null explícito no $set (para limpar)', async () => { + const updateOne = jest.fn().mockResolvedValue({ acknowledged: true }); + const repo = new SimuladoRepository({ updateOne } as any); + + await repo.updateDisponibilidade('sim-1', { + disponivelDe: null, + disponivelAte: null, + }); + + expect(updateOne).toHaveBeenCalledWith( + { _id: 'sim-1' }, + { $set: { disponivelDe: null, disponivelAte: null } }, + ); + }); +}); + +describe('SimuladoRepository.getAvailable', () => { + it('filtra por categoria, desbloqueado e dentro da janela temporal', async () => { + const select = jest.fn().mockResolvedValue([{ _id: 's1', nome: 'S1' }]); + const find = jest.fn().mockReturnValue({ select }); + const repo = new SimuladoRepository({ find } as any); + + const result = await repo.getAvailable('cat-1'); + + expect(result).toEqual([{ _id: 's1', nome: 'S1' }]); + const filtro = find.mock.calls[0][0]; + expect(filtro.categoria).toBe('cat-1'); + expect(filtro.bloqueado).toBe(false); + // janela: disponivelDe null OU <= agora ; disponivelAte null OU >= agora + expect(filtro.$and).toHaveLength(2); + expect(filtro.$and[0].$or[0]).toEqual({ disponivelDe: null }); + expect(filtro.$and[0].$or[1].disponivelDe.$lte).toBeInstanceOf(Date); + expect(filtro.$and[1].$or[0]).toEqual({ disponivelAte: null }); + expect(filtro.$and[1].$or[1].disponivelAte.$gte).toBeInstanceOf(Date); + expect(select).toHaveBeenCalledWith(['nome', '_id']); + }); +}); + +describe('SimuladoRepository.getAvailabilityById', () => { + it('lê só os campos de disponibilidade, sem populate', async () => { + const exec = jest.fn().mockResolvedValue({ + bloqueado: false, + disponivelDe: null, + disponivelAte: null, + }); + const lean = jest.fn().mockReturnValue({ exec }); + const select = jest.fn().mockReturnValue({ lean }); + const findById = jest.fn().mockReturnValue({ select }); + const repo = new SimuladoRepository({ findById } as any); + + const result = await repo.getAvailabilityById('sim-1'); + + expect(findById).toHaveBeenCalledWith('sim-1'); + expect(select).toHaveBeenCalledWith('bloqueado disponivelDe disponivelAte'); + expect(result).toEqual({ + bloqueado: false, + disponivelDe: null, + disponivelAte: null, + }); + }); +}); diff --git a/src/modules/simulado/simulado.repository.ts b/src/modules/simulado/simulado.repository.ts index 326c40a..9f6b1f9 100644 --- a/src/modules/simulado/simulado.repository.ts +++ b/src/modules/simulado/simulado.repository.ts @@ -80,12 +80,34 @@ export class SimuladoRepository extends BaseRepository { } async getAvailable(categoriaId: string) { + const now = new Date(); return await this.model - .find() - .where({ categoria: categoriaId, bloqueado: false }) + .find({ + categoria: categoriaId, + bloqueado: false, + $and: [ + { $or: [{ disponivelDe: null }, { disponivelDe: { $lte: now } }] }, + { $or: [{ disponivelAte: null }, { disponivelAte: { $gte: now } }] }, + ], + }) .select(['nome', '_id']); } + async updateDisponibilidade( + id: string, + fields: { disponivelDe?: Date | null; disponivelAte?: Date | null }, + ) { + await this.model.updateOne({ _id: id }, { $set: fields }); + } + + async getAvailabilityById(id: string): Promise { + return await this.model + .findById(id) + .select('bloqueado disponivelDe disponivelAte') + .lean() + .exec(); + } + public removeDuplicatedSimulados( simuladosToCheck: Simulado[], simuladosToCompare: Simulado[], diff --git a/src/modules/simulado/simulado.service.spec.ts b/src/modules/simulado/simulado.service.spec.ts new file mode 100644 index 0000000..afcba82 --- /dev/null +++ b/src/modules/simulado/simulado.service.spec.ts @@ -0,0 +1,169 @@ +import { + BadRequestException, + HttpException, + HttpStatus, + NotFoundException, +} from '@nestjs/common'; +import { SimuladoService } from './simulado.service'; + +function makeService(simuladoRepo: Record) { + const service = new SimuladoService( + simuladoRepo as any, // simuladoRepository + {} as any, // questoesRepository + {} as any, // categoriaRepository + {} as any, // historicoRepository + {} as any, // materiaRepository + {} as any, // queueProducer + ); + return service; +} + +const DE = new Date('2026-01-10T00:00:00.000Z'); +const ATE = new Date('2026-01-20T00:00:00.000Z'); + +describe('SimuladoService.getToAnswer (gate de disponibilidade)', () => { + it('retorna null quando o simulado não existe', async () => { + const service = makeService({ + getAvailabilityById: jest.fn().mockResolvedValue(null), + }); + await expect(service.getToAnswer('x')).resolves.toBeNull(); + }); + + it('lança 403 com status "bloqueado" quando bloqueado', async () => { + const service = makeService({ + getAvailabilityById: jest.fn().mockResolvedValue({ bloqueado: true }), + }); + await expect(service.getToAnswer('x')).rejects.toBeInstanceOf( + HttpException, + ); + try { + await service.getToAnswer('x'); + } catch (e) { + expect((e as HttpException).getStatus()).toBe(HttpStatus.FORBIDDEN); + expect((e as HttpException).getResponse()).toMatchObject({ + status: 'bloqueado', + }); + } + }); + + it('lança 403 com status "antes_da_janela" quando antes da janela', async () => { + const service = makeService({ + getAvailabilityById: jest.fn().mockResolvedValue({ + bloqueado: false, + disponivelDe: new Date(Date.now() + 60 * 60 * 1000), + disponivelAte: null, + }), + }); + try { + await service.getToAnswer('x'); + fail('deveria ter lançado'); + } catch (e) { + expect((e as HttpException).getResponse()).toMatchObject({ + status: 'antes_da_janela', + }); + } + }); + + it('lança 403 com status "depois_da_janela" quando depois da janela', async () => { + const service = makeService({ + getAvailabilityById: jest.fn().mockResolvedValue({ + bloqueado: false, + disponivelDe: null, + disponivelAte: new Date(Date.now() - 60 * 60 * 1000), + }), + }); + try { + await service.getToAnswer('x'); + fail('deveria ter lançado'); + } catch (e) { + expect((e as HttpException).getResponse()).toMatchObject({ + status: 'depois_da_janela', + }); + } + }); + + it('retorna o simulado montado quando disponível (null/null)', async () => { + const simulado = { + _id: 's1', + nome: 'S', + descricao: 'd', + bloqueado: false, + disponivelDe: null, + disponivelAte: null, + categoria: { _id: 'c1', duracao: 60 }, + questoes: [], + } as any; + const service = makeService({ + getAvailabilityById: jest.fn().mockResolvedValue({ + bloqueado: false, + disponivelDe: null, + disponivelAte: null, + }), + getById: jest.fn().mockResolvedValue(simulado), + }); + const result = await service.getToAnswer('s1'); + expect(result).toMatchObject({ _id: 's1', nome: 'S', duracao: 60 }); + }); +}); + +describe('SimuladoService.updateDisponibilidade', () => { + it('lança NotFound quando o simulado não existe', async () => { + const service = makeService({ + getById: jest.fn().mockResolvedValue(null), + }); + await expect( + service.updateDisponibilidade('x', { disponivelDe: DE }), + ).rejects.toBeInstanceOf(NotFoundException); + }); + + it('lança 400 quando disponivelDe >= disponivelAte (ambos no dto)', async () => { + const service = makeService({ + getById: jest.fn().mockResolvedValue({ _id: 'x' }), + updateDisponibilidade: jest.fn(), + }); + await expect( + service.updateDisponibilidade('x', { + disponivelDe: ATE, + disponivelAte: DE, + }), + ).rejects.toBeInstanceOf(BadRequestException); + }); + + it('lança 400 ao patchar só disponivelDe se o disponivelAte existente ficar invertido', async () => { + const service = makeService({ + getById: jest.fn().mockResolvedValue({ _id: 'x', disponivelAte: DE }), + updateDisponibilidade: jest.fn(), + }); + await expect( + service.updateDisponibilidade('x', { disponivelDe: ATE }), + ).rejects.toBeInstanceOf(BadRequestException); + }); + + it('persiste só o campo enviado (omitido = inalterado)', async () => { + const updateDisponibilidade = jest.fn().mockResolvedValue(undefined); + const getById = jest + .fn() + .mockResolvedValue({ _id: 'x', disponivelDe: null, disponivelAte: null }); + const service = makeService({ getById, updateDisponibilidade }); + + await service.updateDisponibilidade('x', { disponivelAte: ATE }); + + expect(updateDisponibilidade).toHaveBeenCalledWith('x', { + disponivelAte: ATE, + }); + }); + + it('propaga null explícito para limpar', async () => { + const updateDisponibilidade = jest.fn().mockResolvedValue(undefined); + const getById = jest + .fn() + .mockResolvedValue({ _id: 'x', disponivelDe: DE, disponivelAte: ATE }); + const service = makeService({ getById, updateDisponibilidade }); + + await service.updateDisponibilidade('x', { disponivelDe: null }); + + expect(updateDisponibilidade).toHaveBeenCalledWith('x', { + disponivelDe: null, + }); + }); +}); diff --git a/src/modules/simulado/simulado.service.ts b/src/modules/simulado/simulado.service.ts index 6dd104e..951d114 100644 --- a/src/modules/simulado/simulado.service.ts +++ b/src/modules/simulado/simulado.service.ts @@ -1,11 +1,16 @@ -import { Injectable } from '@nestjs/common'; +import { + BadRequestException, + HttpException, + HttpStatus, + Injectable, + NotFoundException, +} from '@nestjs/common'; import { ClientSession } from 'mongoose'; import { GetAllInput } from 'src/shared/base/interfaces/get-all.input'; import { GetAllOutput } from 'src/shared/base/interfaces/get-all.output'; import { QueueProducer } from 'src/shared/modules/queue/queue.producer'; import { HistoricoRepository } from '../historico/historico.repository'; import { HistoricoStatus } from '../historico/enums/historico-status.enum'; -import { Historico } from '../historico/historico.schema'; import { AproveitamentoHistorico, MateriaAproveitamento, @@ -17,6 +22,11 @@ import { QuestaoRepository } from '../questao/questao.repository'; import { Questao } from '../questao/questao.schema'; import { CategoriaRepository } from '../categoria/categoria.repository'; import { atingiuQuantidade } from './helpers/bloqueado'; +import { + getAvailabilityStatus, + isSimuladoAvailable, +} from './helpers/availability'; +import { UpdateDisponibilidadeDTO } from './dtos/update-disponibilidade.dto.input'; import { AnswerSimuladoDto } from './dtos/answer-simulado.dto.input'; import { AvailableSimuladoDTOoutput } from './dtos/available-simulado.dto.output'; import { SimuladoAnswerDTOOutput } from './dtos/simulado-answer.dto.output'; @@ -50,6 +60,22 @@ export class SimuladoService { public async getToAnswer( simuladoId: string, ): Promise { + const simulado = + await this.simuladoRepository.getAvailabilityById(simuladoId); + if (!simulado) return null; + + // Gate rodado ANTES do try/catch: o try engole erros e retorna null, + // então o 403 precisa ser lançado fora dele para propagar. + if (!isSimuladoAvailable(simulado)) { + throw new HttpException( + { + message: 'Simulado fora da janela de disponibilidade', + status: getAvailabilityStatus(simulado), + }, + HttpStatus.FORBIDDEN, + ); + } + try { return await this.GetSimulado(simuladoId); } catch (error) { @@ -57,6 +83,41 @@ export class SimuladoService { } } + public async updateDisponibilidade( + id: string, + dto: UpdateDisponibilidadeDTO, + ): Promise { + const simulado = await this.simuladoRepository.getById(id); + if (!simulado) throw new NotFoundException(); + + // Valida contra os valores FINAIS (existente mesclado com o dto), + // não só quando ambos vêm no payload — evita janela invertida + // criada ao patchar um campo só. + const finalDe = + dto.disponivelDe !== undefined ? dto.disponivelDe : simulado.disponivelDe; + const finalAte = + dto.disponivelAte !== undefined + ? dto.disponivelAte + : simulado.disponivelAte; + if (finalDe && finalAte && finalDe >= finalAte) { + throw new BadRequestException( + 'disponivelDe deve ser anterior a disponivelAte', + ); + } + + // PATCH parcial: só grava as chaves presentes no dto. + // Omitido = inalterado; null explícito = limpa. + const fields: { disponivelDe?: Date | null; disponivelAte?: Date | null } = + {}; + if (dto.disponivelDe !== undefined) fields.disponivelDe = dto.disponivelDe; + if (dto.disponivelAte !== undefined) { + fields.disponivelAte = dto.disponivelAte; + } + + await this.simuladoRepository.updateDisponibilidade(id, fields); + return this.simuladoRepository.getById(id); + } + public async addQuestionSimulados( simulados: Simulado[], question: Questao, @@ -132,20 +193,24 @@ export class SimuladoService { const historico = await this.historicoRepository.getById(histId); if (!historico?.rawRespostas) { - await this.historicoRepository.updateStatus(histId, HistoricoStatus.Failed); + await this.historicoRepository.updateStatus( + histId, + HistoricoStatus.Failed, + ); return; } - const simuladoId = (historico.simulado as any)?._id?.toString() - ?? historico.simulado.toString(); + const simuladoId = + (historico.simulado as any)?._id?.toString() ?? + historico.simulado.toString(); const simulado = await this.simuladoRepository.answer(simuladoId); const ano = ( await this.questoesRepository.getById(simulado.questoes[0]._id) ).prova.ano; - const respostasAproveitamento: RespostaAproveitamento[] = simulado.questoes.map( - (questao) => { + const respostasAproveitamento: RespostaAproveitamento[] = + simulado.questoes.map((questao) => { const resposta = historico.rawRespostas!.find( (r: any) => r.questao === questao._id.toString(), ); @@ -156,11 +221,14 @@ export class SimuladoService { materia: questao.materia, frente: questao.frente1, }; - }, - ); + }); - const aproveitamento = await this.criaAproveitamento(respostasAproveitamento); - await this.questoesRepository.updateQuestionAnswered(respostasAproveitamento); + const aproveitamento = await this.criaAproveitamento( + respostasAproveitamento, + ); + await this.questoesRepository.updateQuestionAnswered( + respostasAproveitamento, + ); await this.historicoRepository.completeProcessing(histId, { ano, @@ -173,7 +241,10 @@ export class SimuladoService { aproveitamento, }); } catch (err) { - await this.historicoRepository.updateStatus(histId, HistoricoStatus.Failed); + await this.historicoRepository.updateStatus( + histId, + HistoricoStatus.Failed, + ); throw err; } }