Skip to content

final tests implemented#48

Open
KAGmay05 wants to merge 2 commits into
mainfrom
feat/testing_3
Open

final tests implemented#48
KAGmay05 wants to merge 2 commits into
mainfrom
feat/testing_3

Conversation

@KAGmay05

Copy link
Copy Markdown
Collaborator

No description provided.

Copilot AI review requested due to automatic review settings December 13, 2025 20:21

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

This PR implements comprehensive unit tests for the exam-related services (ExamStudentService, ExamQuestionService, and ExamService) and makes the score field optional in the Exam_Student model to allow exam assignments without immediate scoring. The changes also include cleanup of unused DTO fields and improvements to seed data generation.

  • Added comprehensive unit test suites covering CRUD operations and special functions for all three exam services
  • Made the score field optional in Exam_Student model to support exam assignment workflow
  • Removed unused teacher_id and head_teacher_id fields from GenerateExamDto
  • Enhanced seed data with difficulty-based probability function for more realistic test data
  • Cleaned up trailing whitespace in service files

Reviewed changes

Copilot reviewed 9 out of 9 changed files in this pull request and generated 9 comments.

Show a summary per file
File Description
src/modules/exam_related/exam_student/exam_student.service.spec.ts Added comprehensive unit tests for ExamStudentService covering create, findAll, findOne, update, and remove operations with transaction mocking
src/modules/exam_related/exam_student/dto/create-exam_student.dto.ts Made score field optional with @IsOptional() decorator to align with schema changes
src/modules/exam_related/exam_question/exam_question.service.ts Removed trailing whitespace for code cleanliness
src/modules/exam_related/exam_question/exam_question.service.spec.ts Added comprehensive unit tests including CRUD operations and the listMostUsedQuestions feature
src/modules/exam_related/exam/exam.service.spec.ts Added extensive unit tests covering exam creation, generation logic with edge cases, and the listGeneratedExamsBySubject feature
src/modules/exam_related/exam/dto/generated-exam.dto.ts Removed unused teacher_id and head_teacher_id fields that weren't used by the generation logic
prisma/seeds/seeds_Exam.ts Added getCorrectProbability function to vary answer correctness based on exam difficulty level
prisma/schema.prisma Changed score field in Exam_Student model from required to optional (nullable)
prisma/migrations/20251213195700_score_field_optional_in_exam_student_model/migration.sql Database migration to make the score column nullable in Exam_Student table

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

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 +46 to 257
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);
});



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);
});
// ============================================================
// 🧪 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');
});


// ============================================================
// 🧪 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);
});

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.
}),
).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.
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.
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.
// 🧪 CREATE
// =====================================================

it('create debe lanzar error si el examen no existe', async () => {

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 description is in Spanish ("debe lanzar error si el examen no existe") which is inconsistent with the codebase convention that appears to use English for code. Test descriptions should use English to maintain consistency with other test files and make the codebase more accessible to international developers.

Suggested change
it('create debe lanzar error si el examen no existe', async () => {
it('should throw an error if the exam does not exist', async () => {

Copilot uses AI. Check for mistakes.
Comment on lines +60 to 176
it('create debe asignar el examen y cambiar estado a "Asignado"', async () => {
prismaMock.$transaction.mockImplementation(async (cb) =>
cb({
exam: {
findUnique: jest.fn().mockResolvedValue({ id: 1 }),
update: jest.fn().mockResolvedValue({}),
},
exam_Student: {
create: jest.fn().mockResolvedValue({
exam_id: 1,
student_id: 2,
teacher_id: 3,
}),
},
}),
);

const result = await service.create({
exam_id: 1,
student_id: 2,
teacher_id: 3,
});

expect(result.exam_id).toBe(1);
expect(result.student_id).toBe(2);
});

// =====================================================
// 🧪 FIND ALL
// =====================================================

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

const result = await service.findAll();

expect(prismaMock.exam_Student.findMany).toHaveBeenCalledWith({
include: {
exam: true,
student: true,
teacher: true,
reevaluations: true,
},
});

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

// =====================================================
// 🧪 FIND ONE
// =====================================================

it('findOne debe buscar por clave compuesta', async () => {
prismaMock.exam_Student.findUnique.mockResolvedValue({ id: 1 });

const result = await service.findOne(1, 2);

expect(prismaMock.exam_Student.findUnique).toHaveBeenCalledWith({
where: {
exam_id_student_id: {
exam_id: 1,
student_id: 2,
},
},
include: {
exam: true,
student: true,
teacher: true,
reevaluations: true,
},
});

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

// =====================================================
// 🧪 UPDATE
// =====================================================

it('update debe modificar exam_student', async () => {
prismaMock.exam_Student.update.mockResolvedValue({ id: 1, score: 9 });

const result = await service.update(1, 2, { score: 9 });

expect(prismaMock.exam_Student.update).toHaveBeenCalledWith({
where: {
exam_id_student_id: {
exam_id: 1,
student_id: 2,
},
},
data: { score: 9 },
});

expect(result.score).toBe(9);
});

// =====================================================
// 🧪 REMOVE
// =====================================================

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

const result = await service.remove(1, 2);

expect(prismaMock.exam_Student.delete).toHaveBeenCalledWith({
where: {
exam_id_student_id: {
exam_id: 1,
student_id: 2,
},
},
});

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

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 asignar el examen", "debe retornar examenes asignados", "debe buscar por clave compuesta"). These should be written in English to maintain consistency with the codebase conventions, as the rest of the code (variable names, comments, etc.) is in English.

Copilot uses AI. Check for mistakes.
Comment on lines +41 to 195
it('create debe crear una relación exam_question', async () => {
prismaMock.exam_Question.create.mockResolvedValue({
exam_id: 1,
question_id: 2,
});

const result = await service.create({
exam_id: 1,
question_id: 2,
});

expect(prismaMock.exam_Question.create).toHaveBeenCalledWith({
data: {
exam_id: 1,
question_id: 2,
},
});

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

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

const result = await service.findAll();

expect(prismaMock.exam_Question.findMany).toHaveBeenCalledWith({
include: {
exam: true,
question: true,
},
});

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

it('findOne debe buscar por clave compuesta', async () => {
prismaMock.exam_Question.findUnique.mockResolvedValue({ id: 1 });

const result = await service.findOne(1, 2);

expect(prismaMock.exam_Question.findUnique).toHaveBeenCalledWith({
where: {
exam_id_question_id: {
exam_id: 1,
question_id: 2,
},
},
include: {
exam: true,
question: true,
},
});

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

it('update debe actualizar la relación', async () => {
prismaMock.exam_Question.update.mockResolvedValue({ id: 1 });

const result = await service.update(1, 2, {});

expect(prismaMock.exam_Question.update).toHaveBeenCalledWith({
where: {
exam_id_question_id: {
exam_id: 1,
question_id: 2,
},
},
data: {},
});

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

it('remove debe eliminar la relación', async () => {
prismaMock.exam_Question.delete.mockResolvedValue({ id: 1 });

const result = await service.remove(1, 2);

expect(prismaMock.exam_Question.delete).toHaveBeenCalledWith({
where: {
exam_id_question_id: {
exam_id: 1,
question_id: 2,
},
},
});

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

// ======================================================
// 🧪 TASK 3 — listMostUsedQuestions
// ======================================================

it('listMostUsedQuestions debe retornar preguntas ordenadas por uso', async () => {
prismaMock.exam_Question.groupBy.mockResolvedValue([
{ question_id: 1, _count: { question_id: 3 } },
{ question_id: 2, _count: { question_id: 1 } },
]);

prismaMock.question.findUnique
.mockResolvedValueOnce({
id: 1,
question_text: 'Pregunta 1',
difficulty: 'MEDIA',
subject: { name: 'Matemática' },
sub_topic: {
name: 'Álgebra',
topic: { name: 'Ecuaciones' },
},
})
.mockResolvedValueOnce({
id: 2,
question_text: 'Pregunta 2',
difficulty: 'BAJA',
subject: { name: 'Física' },
sub_topic: {
name: 'Cinemática',
topic: { name: 'Movimiento' },
},
});

const result = await service.listMostUsedQuestions();

expect(prismaMock.exam_Question.groupBy).toHaveBeenCalled();

expect(prismaMock.question.findUnique).toHaveBeenCalledTimes(2);

expect(result).toEqual([
{
usage_count: 3,
id: 1,
question_text: 'Pregunta 1',
difficulty: 'MEDIA',
subject: { name: 'Matemática' },
sub_topic: {
name: 'Álgebra',
topic: { name: 'Ecuaciones' },
},
},
{
usage_count: 1,
id: 2,
question_text: 'Pregunta 2',
difficulty: 'BAJA',
subject: { name: 'Física' },
sub_topic: {
name: 'Cinemática',
topic: { name: 'Movimiento' },
},
},
]);
});

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 una relación exam_question", "debe retornar relaciones con exam y question"). These should be written in English to maintain consistency with the codebase conventions.

Copilot uses AI. Check for mistakes.
}

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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants