Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
-- AlterTable
ALTER TABLE "Exam_Student" ALTER COLUMN "score" DROP NOT NULL;
2 changes: 1 addition & 1 deletion prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ model Sub_Topic {
}

model Exam_Student {
score Float
score Float?
exam_id Int
student_id Int
teacher_id Int
Expand Down
15 changes: 12 additions & 3 deletions prisma/seeds/seeds_Exam.ts
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,14 @@ export async function seed_exams(prisma: PrismaClient) {
if (maxScore <= 1) return 0;
return Math.floor(Math.random() * (maxScore - 1)) + 1; // 1..(max-1)
}

function getCorrectProbability(difficulty: string): number {
switch(difficulty.toLowerCase()){

Copilot AI Dec 13, 2025

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's a missing space after the switch keyword. The correct syntax is switch (difficulty.toLowerCase()) with a space between switch and the opening parenthesis, following standard JavaScript/TypeScript formatting conventions.

Suggested change
switch(difficulty.toLowerCase()){
switch (difficulty.toLowerCase()) {

Copilot uses AI. Check for mistakes.
case 'facil': return 0.95;
case 'medio': return 0.75;
case 'dificil': return 0.6;
default: return 0.7;
}
}

// ----------------------------
// 🔥 CREAR RESPUESTAS
Expand All @@ -158,8 +165,10 @@ export async function seed_exams(prisma: PrismaClient) {
where: { exam_id: eq.exam_id }
});

for (const es of examStudents) {
const isCorrect = Math.random() < 0.8; // 80% correctas
for (const student of students) {
const exam = createdExams.find(e => e.id === eq.exam_id)!;
const prob = getCorrectProbability(exam.difficulty);
const isCorrect = Math.random() < prob;

let answerText: string = "";
let score = 0;
Expand Down
8 changes: 0 additions & 8 deletions src/modules/exam_related/exam/dto/generated-exam.dto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,6 @@ export class GenerateExamDto {
@IsInt()
subject_id: number;

@Type(() => Number)
@IsInt()
teacher_id: number;

@Type(() => Number)
@IsInt()
head_teacher_id: number;

@IsArray()
@ValidateNested({ each: true })
@Type(() => QuestionDistributionDto)
Expand Down
246 changes: 243 additions & 3 deletions src/modules/exam_related/exam/exam.service.spec.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,258 @@
import { Test, TestingModule } from '@nestjs/testing';
import { ExamService } from './exam.service';
import { PrismaService } from 'src/prisma/prisma.service';
import { BadRequestException, NotFoundException } from '@nestjs/common';

describe('ExamService', () => {
let service: ExamService;
let prismaMock: any;

beforeEach(async () => {
prismaMock = {
exam: {
create: jest.fn(),
findUnique: jest.fn(),
findMany: jest.fn(),
update: jest.fn(),
delete: jest.fn(),
},
exam_Question: {
createMany: jest.fn(),
findMany: jest.fn(),
},
question: {
findMany: jest.fn(),
},
$transaction: jest.fn(),
};

const module: TestingModule = await Test.createTestingModule({
providers: [ExamService],
providers: [
ExamService,
{
provide: PrismaService,
useValue: prismaMock,
},
],
}).compile();

service = module.get<ExamService>(ExamService);
});

it('should be defined', () => {
expect(service).toBeDefined();
// ============================================================
// 🧪 CREATE
// ============================================================

it('create debe crear un examen y asociar preguntas', async () => {
prismaMock.$transaction.mockImplementation(async (cb) =>
cb({
exam: {
create: jest.fn().mockResolvedValue({ id: 1 }),
findUnique: jest.fn().mockResolvedValue({
id: 1,
exam_questions: [],
}),
},
exam_Question: {
createMany: jest.fn(),
},
}),
);

const result = await service.create(
{
name: 'Parcial',
status: 'Borrador',
difficulty: 'Media',
subject_id: 1,
teacher_id: 2,
parameters_id: 3,
head_teacher_id: 4,
},
[10, 11],
);

expect(result?.id).toBe(1);
});

// ============================================================
// 🧪 GENERATED
// ============================================================
it('generated debe insertar una nueva combinación válida', async () => {
prismaMock.question.findMany.mockResolvedValue([
{ id: 1, type: 'Multiple', subject_id: 1 },
{ id: 2, type: 'Multiple', subject_id: 1 },
]);

prismaMock.exam_Question.findMany.mockResolvedValue([
{ question_id: 1 }, // combinación previa distinta
]);

prismaMock.exam_Question.createMany.mockResolvedValue({ count: 1 });

const result = await service.generated({
exam_id: 1,
subject_id: 1,
questionDistribution: [
{ type: 'Multiple', amount: 1 },
],
});

expect(prismaMock.exam_Question.createMany).toHaveBeenCalledWith({
data: [{ exam_id: 1, question_id: expect.any(Number) }],
});

expect(result.questions_added).toBe(1);
});



Comment on lines +108 to +109

Copilot AI Dec 13, 2025

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The test is missing proper spacing after the closing brace. There should be a blank line after the closing brace of this test for better readability and consistency with other tests in the file.

Suggested change

Copilot uses AI. Check for mistakes.
Comment on lines +108 to +109

Copilot AI Dec 13, 2025

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are extra blank lines (109) that serve no purpose and reduce code cleanliness. Consider removing these empty lines to improve code readability.

Suggested change

Copilot uses AI. Check for mistakes.
it('generated debe lanzar error si no hay suficientes preguntas', async () => {
prismaMock.question.findMany.mockResolvedValue([]);

await expect(
service.generated({
exam_id: 1,
subject_id: 1,
questionDistribution: [
{ type: 'Multiple', amount: 2 },
],
}),
).rejects.toThrow(NotFoundException);
});

it('generated debe lanzar error si no existen combinaciones', async () => {
prismaMock.question.findMany.mockResolvedValue([
{ id: 1, type: 'Multiple', subject_id: 1 },
]);

prismaMock.exam_Question.findMany.mockResolvedValue([
{ question_id: 1 },
]);

await expect(
service.generated({
exam_id: 1,
subject_id: 1,
questionDistribution: [
{ type: 'Multiple', amount: 1 },
],
}),
).rejects.toThrow(BadRequestException);
});
// ============================================================

Copilot AI Dec 13, 2025

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's an extra blank line here that's unnecessary. Consider removing it for consistency with the rest of the codebase.

Copilot uses AI. Check for mistakes.
// 🧪 Task1
// ============================================================
it('listGeneratedExamsBySubject debe retornar exámenes generados por asignatura', async () => {
prismaMock.exam.findMany.mockResolvedValue([
{
id: 10,
name: 'Parcial Matemática',
status: 'GENERADO',
difficulty: 'MEDIA',
subject_id: 3,
teacher: {
id: 5,
specialty: 'Álgebra',
user: {
id_us: 20,
name: 'Ana López',
},
},
parameters: {
id: 7,
},
},
]);

const result = await service.listGeneratedExamsBySubject(3);

expect(prismaMock.exam.findMany).toHaveBeenCalledWith({
where: {
subject_id: 3,
},
select: {
id: true,
name: true,
status: true,
difficulty: true,
subject_id: true,
teacher: {
select: {
id: true,
specialty: true,
user: {
select: {
id_us: true,
name: true,
},
},
},
},
parameters: {
select: {
id: true,
},
},
},
orderBy: {
id: 'desc',
},
});

expect(result).toHaveLength(1);
expect(result[0].name).toBe('Parcial Matemática');
});


Copilot AI Dec 13, 2025

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's an extra blank line here that's unnecessary. Consider removing it for consistency with the rest of the codebase.

Suggested change

Copilot uses AI. Check for mistakes.
// ============================================================
// 🧪 CRUD
// ============================================================

it('findAll debe retornar examenes con relaciones', async () => {
prismaMock.exam.findMany.mockResolvedValue([{ id: 1 }]);

const result = await service.findAll();

expect(prismaMock.exam.findMany).toHaveBeenCalled();
expect(result).toEqual([{ id: 1 }]);
});

it('findOne debe retornar un examen', async () => {
prismaMock.exam.findUnique.mockResolvedValue({ id: 1 });

const result = await service.findOne(1);

expect(prismaMock.exam.findUnique).toHaveBeenCalledWith({
where: { id: 1 },
include: expect.any(Object),
});

expect(result).toEqual({ id: 1 });
});

it('update debe actualizar el examen', async () => {
prismaMock.exam.update.mockResolvedValue({ id: 1, name: 'Actualizado' });

const result = await service.update(1, { name: 'Actualizado' });

expect(prismaMock.exam.update).toHaveBeenCalledWith({
where: { id: 1 },
data: { name: 'Actualizado' },
});

expect(result.name).toBe('Actualizado');
});

it('remove debe eliminar el examen', async () => {
prismaMock.exam.delete.mockResolvedValue({ id: 1 });

const result = await service.remove(1);

expect(prismaMock.exam.delete).toHaveBeenCalledWith({
where: { id: 1 },
});

expect(result.id).toBe(1);
});
Comment on lines +46 to 257

Copilot AI Dec 13, 2025

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All test descriptions in this file are written in Spanish (e.g., "debe crear un examen y asociar preguntas", "debe insertar una nueva combinación válida", "debe lanzar error si no hay suficientes preguntas"). These should be written in English to maintain consistency with the codebase conventions.

Copilot uses AI. Check for mistakes.
});
Loading