diff --git a/openbas-api/src/test/java/io/openbas/rest/ReportApiTest.java b/openbas-api/src/test/java/io/openbas/rest/ReportApiTest.java new file mode 100644 index 0000000000..1383cacc78 --- /dev/null +++ b/openbas-api/src/test/java/io/openbas/rest/ReportApiTest.java @@ -0,0 +1,169 @@ +package io.openbas.rest; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.jayway.jsonpath.JsonPath; +import io.openbas.database.model.*; +import io.openbas.rest.exercise.ExerciseService; +import io.openbas.rest.mapper.MapperApi; +import io.openbas.rest.report.ReportApi; +import io.openbas.rest.report.form.ReportInput; +import io.openbas.service.ReportService; +import io.openbas.utils.fixtures.PaginationFixture; +import io.openbas.utils.mockUser.WithMockPlannerUser; +import org.junit.jupiter.api.*; +import org.mockito.Mock; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.http.MediaType; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; + +import java.lang.reflect.Field; +import java.util.List; +import java.util.UUID; + +import static io.openbas.utils.JsonUtils.asJsonString; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.Mockito.*; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@SpringBootTest +@AutoConfigureMockMvc +@TestMethodOrder(MethodOrderer.OrderAnnotation.class) +@TestInstance(PER_CLASS) +public class ReportApiTest { + + private MockMvc mvc; + + @Mock + private ReportService reportService; + @Mock + private ExerciseService exerciseService; + + @Autowired + private ObjectMapper objectMapper; + + private Exercise exercise; + private Report report; + private ReportInput reportInput; + + @BeforeEach + void before() throws IllegalAccessException, NoSuchFieldException { + ReportApi reportApi = new ReportApi(exerciseService, reportService); + Field sessionContextField = MapperApi.class.getSuperclass().getDeclaredField("mapper"); + sessionContextField.setAccessible(true); + sessionContextField.set(reportApi, objectMapper); + mvc = MockMvcBuilders.standaloneSetup(reportApi) + .build(); + + exercise = new Exercise(); + exercise.setName("Exercise name"); + exercise.setId("exercise123"); + report = new Report(); + report.setId(UUID.randomUUID().toString()); + reportInput = new ReportInput(); + reportInput.setName("Report name"); + } + + @Nested + @WithMockPlannerUser + @DisplayName("Reports for exercise") + class ReportsForExercise { + @DisplayName("Create report") + @Test + void createReportForExercise() throws Exception { + // -- PREPARE -- + when(exerciseService.exercise(anyString())).thenReturn(exercise); + when(reportService.updateReport(any(Report.class), any(ReportInput.class))) + .thenReturn(report); + + // -- EXECUTE -- + String response = mvc + .perform(MockMvcRequestBuilders.post("/api/exercises/"+exercise.getId()+"/reports") + .content(asJsonString(reportInput)) + .contentType(MediaType.APPLICATION_JSON) + .accept(MediaType.APPLICATION_JSON)) + .andExpect(status().is2xxSuccessful()) + .andReturn() + .getResponse() + .getContentAsString(); + + // -- ASSERT -- + verify(exerciseService).exercise(exercise.getId()); + assertNotNull(response); + assertEquals(JsonPath.read(response, "$.report_id"), report.getId()); + } + + @DisplayName("Retrieve reports") + @Test + void retrieveReportForExercise() throws Exception { + // PREPARE + List reports = List.of(report); + when(reportService.reportsFromExercise(anyString())).thenReturn(reports); + + // -- EXECUTE -- + String response = mvc + .perform(MockMvcRequestBuilders.get( "/api/exercises/fakeExercisesId123/reports") + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().is2xxSuccessful()) + .andReturn() + .getResponse() + .getContentAsString(); + + // -- ASSERT -- + verify(reportService).reportsFromExercise("fakeExercisesId123"); + assertNotNull(response); + assertEquals(JsonPath.read(response, "$[0].report_id"), report.getId()); + } + + @DisplayName("Update Report") + @Test + void updateReportForExercise() throws Exception { + // -- PREPARE -- + report.setExercise(exercise); + when(reportService.report(any())).thenReturn(report); + when(reportService.updateReport(any(Report.class), any(ReportInput.class))) + .thenReturn(report); + + // -- EXECUTE -- + String response = mvc + .perform(MockMvcRequestBuilders.put("/api/exercises/"+ exercise.getId() +"/reports/"+ report.getId()) + .content(asJsonString(reportInput)) + .contentType(MediaType.APPLICATION_JSON) + .accept(MediaType.APPLICATION_JSON)) + .andExpect(status().is2xxSuccessful()) + .andReturn() + .getResponse() + .getContentAsString(); + + // -- ASSERT -- + report.setName("fake"); + verify(reportService).report(UUID.fromString(report.getId())); + verify(reportService).updateReport(report, reportInput); + assertNotNull(response); + assertEquals(JsonPath.read(response, "$.report_id"), report.getId()); + } + + @DisplayName("Delete Report") + @Test + void deleteReportForExercise() throws Exception { + // -- PREPARE -- + report.setExercise(exercise); + when(reportService.report(any())).thenReturn(report); + + // -- EXECUTE -- + mvc.perform(MockMvcRequestBuilders.delete("/api/exercises/" + exercise.getId() +"/reports/"+ report.getId()) + .contentType(MediaType.APPLICATION_JSON) + .content(asJsonString(PaginationFixture.getDefault().textSearch("").build()))) + .andExpect(status().is2xxSuccessful()); + + // -- ASSERT -- + verify(reportService, times(1)).deleteReport(UUID.fromString(report.getId())); + } + } +} diff --git a/openbas-api/src/test/java/io/openbas/service/ReportServiceTest.java b/openbas-api/src/test/java/io/openbas/service/ReportServiceTest.java new file mode 100644 index 0000000000..795a1cc325 --- /dev/null +++ b/openbas-api/src/test/java/io/openbas/service/ReportServiceTest.java @@ -0,0 +1,131 @@ +package io.openbas.service; + +import io.openbas.database.model.Report; +import io.openbas.database.model.ReportInformation; +import io.openbas.database.model.ReportInformationsType; +import io.openbas.database.repository.ReportRepository; +import io.openbas.rest.report.form.ReportInformationInput; +import io.openbas.rest.report.form.ReportInput; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.boot.test.context.SpringBootTest; + +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +@SpringBootTest +@ExtendWith(MockitoExtension.class) +public class ReportServiceTest { + + @Mock + private ReportRepository reportRepository; + + private ReportService reportService; + + @BeforeEach + void before() { + // Injecting mocks into the controller + reportService = new ReportService(reportRepository); + } + + @DisplayName("Test create a report") + @Test + void createReport() throws Exception { + // -- PREPARE -- + Report report = new Report(); + + ReportInput reportInput = new ReportInput(); + reportInput.setName("test"); + ReportInformationInput reportInformationInput = new ReportInformationInput(); + reportInformationInput.setReportInformationsType(ReportInformationsType.MAIN_INFORMATION); + reportInformationInput.setReportInformationsDisplay(true); + ReportInformationInput reportInformationInput2 = new ReportInformationInput(); + reportInformationInput2.setReportInformationsType(ReportInformationsType.SCORE_DETAILS); + reportInformationInput2.setReportInformationsDisplay(true); + reportInput.setReportInformations(List.of(reportInformationInput, reportInformationInput2)); + + when(reportRepository.save(any(Report.class))).thenReturn(report); + + // -- EXECUTE -- + reportService.updateReport(report, reportInput); + + // -- ASSERT -- + ArgumentCaptor reportCaptor = ArgumentCaptor.forClass(Report.class); + verify(reportRepository).save(reportCaptor.capture()); + Report capturedReport= reportCaptor.getValue(); + assertEquals(reportInput.getName(), capturedReport.getName()); + assertEquals(2, capturedReport.getReportInformations().size()); + ReportInformation reportInformationCaptured = capturedReport.getReportInformations().stream() + .filter(r -> r.getReportInformationsType() == ReportInformationsType.MAIN_INFORMATION) + .findFirst() + .orElse(null); + assert reportInformationCaptured != null; + assertEquals(true, reportInformationCaptured.getReportInformationsDisplay()); + ReportInformation reportInformationCaptured2 = capturedReport.getReportInformations().stream() + .filter(r -> r.getReportInformationsType() == ReportInformationsType.SCORE_DETAILS) + .findFirst() + .orElse(null); + assert reportInformationCaptured2 != null; + assertEquals(true, reportInformationCaptured2.getReportInformationsDisplay()); + } + + @DisplayName("Test update a report") + @Test + void updateReport() throws Exception { + // -- PREPARE -- + Report report = new Report(); + report.setName("test"); + ReportInformation reportInformation = new ReportInformation(); + reportInformation.setReportInformationsType(ReportInformationsType.MAIN_INFORMATION); + reportInformation.setReportInformationsDisplay(false); + report.setReportInformations(List.of(reportInformation)); + + ReportInput reportInput = new ReportInput(); + reportInput.setName("new name test"); + ReportInformationInput reportInformationInput = new ReportInformationInput(); + reportInformationInput.setReportInformationsType(ReportInformationsType.MAIN_INFORMATION); + reportInformationInput.setReportInformationsDisplay(true); + reportInput.setReportInformations(List.of(reportInformationInput)); + + when(reportRepository.save(any(Report.class))).thenReturn(report); + + // -- EXECUTE -- + reportService.updateReport(report, reportInput); + + // -- ASSERT -- + ArgumentCaptor reportCaptor = ArgumentCaptor.forClass(Report.class); + verify(reportRepository).save(reportCaptor.capture()); + Report capturedReport= reportCaptor.getValue(); + assertEquals(reportInput.getName(), capturedReport.getName()); + assertEquals(1, capturedReport.getReportInformations().size()); + assertEquals(true, capturedReport.getReportInformations().getFirst().getReportInformationsDisplay()); + } +} + + + + + + + + + + + + + + + + + + + diff --git a/openbas-front/src/actions/reports/report-helper.d.ts b/openbas-front/src/actions/reports/report-helper.d.ts index 5d5835715b..b4ac857b12 100644 --- a/openbas-front/src/actions/reports/report-helper.d.ts +++ b/openbas-front/src/actions/reports/report-helper.d.ts @@ -1,4 +1,4 @@ -import { Exercise, Report } from '../../utils/api-types'; +import type { Exercise, Report } from '../../utils/api-types'; export interface ReportsHelper { getExerciseReports: (exerciseId: Exercise['exercise_id']) => Report[]; diff --git a/openbas-front/src/admin/components/components/reports/ReportPopover.tsx b/openbas-front/src/admin/components/components/reports/ReportPopover.tsx index 757e78a1d8..41e9d07ec8 100644 --- a/openbas-front/src/admin/components/components/reports/ReportPopover.tsx +++ b/openbas-front/src/admin/components/components/reports/ReportPopover.tsx @@ -1,7 +1,7 @@ import React, { useContext, useState } from 'react'; import ButtonPopover, { VariantButtonPopover } from '../../../../components/common/ButtonPopover'; import DialogDelete from '../../../../components/common/DialogDelete'; -import { default as EditDialog } from '../../../../components/common/Dialog'; +import Dialog from '../../../../components/common/Dialog'; import type { Report, ReportInput } from '../../../../utils/api-types'; import { useFormatter } from '../../../../components/i18n'; import { ReportContext } from '../../common/Context'; @@ -52,13 +52,13 @@ const ReportPopover: React.FC = ({ handleSubmit={submitDelete} text={t('Do you want to delete this report ?')} /> - {renderReportForm(submitUpdate, handleCloseEdit, report)} - + ); }; diff --git a/openbas-front/src/admin/components/components/reports/Reports.tsx b/openbas-front/src/admin/components/components/reports/Reports.tsx index 41dc57b706..31d6af7a66 100644 --- a/openbas-front/src/admin/components/components/reports/Reports.tsx +++ b/openbas-front/src/admin/components/components/reports/Reports.tsx @@ -36,7 +36,7 @@ const Reports: React.FC = ({ reports, navigateToReportPage }) => { && + /> } > navigateToReportPage(report.report_id)}> diff --git a/openbas-front/src/admin/components/simulations/simulation/ExerciseMainInformation.tsx b/openbas-front/src/admin/components/simulations/simulation/ExerciseMainInformation.tsx index 44c0377679..49a8832e42 100644 --- a/openbas-front/src/admin/components/simulations/simulation/ExerciseMainInformation.tsx +++ b/openbas-front/src/admin/components/simulations/simulation/ExerciseMainInformation.tsx @@ -1,7 +1,7 @@ import React from 'react'; import * as R from 'ramda'; import { Chip, Grid, Paper, Typography } from '@mui/material'; -import { Exercise, type KillChainPhase } from '../../../../utils/api-types'; +import type { Exercise, KillChainPhase } from '../../../../utils/api-types'; import ExpandableMarkdown from '../../../../components/ExpandableMarkdown'; import ItemSeverity from '../../../../components/ItemSeverity'; import ItemCategory from '../../../../components/ItemCategory'; diff --git a/openbas-front/src/admin/components/simulations/simulation/reports/ExerciseReport.tsx b/openbas-front/src/admin/components/simulations/simulation/reports/ExerciseReport.tsx index b87ca220b0..66969dd4c0 100644 --- a/openbas-front/src/admin/components/simulations/simulation/reports/ExerciseReport.tsx +++ b/openbas-front/src/admin/components/simulations/simulation/reports/ExerciseReport.tsx @@ -6,7 +6,7 @@ import { Button, Paper, Typography } from '@mui/material'; import useDataLoader from '../../../../../utils/hooks/useDataLoader'; import { useHelper } from '../../../../../store'; import { fetchReport, updateReportForExercise } from '../../../../../actions/reports/report-actions'; -import { ReportsHelper } from '../../../../../actions/reports/report-helper'; +import type { ReportsHelper } from '../../../../../actions/reports/report-helper'; import type { Exercise, ExpectationResultsByType, Report, ReportInformation, ReportInput } from '../../../../../utils/api-types'; import { useAppDispatch } from '../../../../../utils/hooks'; import Loader from '../../../../../components/Loader'; @@ -17,7 +17,7 @@ import type { ExercisesHelper } from '../../../../../actions/exercises/exercise- import { fetchExerciseExpectationResult, fetchLessonsAnswers, fetchLessonsCategories, fetchLessonsQuestions } from '../../../../../actions/exercises/exercise-action'; import ExerciseDistribution from '../overview/ExerciseDistribution'; import LessonsCategories from '../../../lessons/exercises/LessonsCategories'; -import { TeamsHelper } from '../../../../../actions/teams/team-helper'; +import type { TeamsHelper } from '../../../../../actions/teams/team-helper'; import ExerciseMainInformation from '../ExerciseMainInformation'; import { fetchExercise } from '../../../../../actions/Exercise'; import ReportInformationType from './ReportInformationType'; @@ -151,7 +151,7 @@ const ExerciseReport: React.FC = () => { teamsMap={teamsMap} teams={teams} isReport - /> + /> } {displayModule(ReportInformationType.EXERCISE_DETAILS) && } diff --git a/openbas-front/src/admin/components/simulations/simulation/reports/ExerciseReportForm.tsx b/openbas-front/src/admin/components/simulations/simulation/reports/ExerciseReportForm.tsx index f9b6a1f34c..06472f9ce7 100644 --- a/openbas-front/src/admin/components/simulations/simulation/reports/ExerciseReportForm.tsx +++ b/openbas-front/src/admin/components/simulations/simulation/reports/ExerciseReportForm.tsx @@ -5,7 +5,7 @@ import { SubmitHandler, useForm } from 'react-hook-form'; import { Button, Checkbox, FormControlLabel, TextField, Typography } from '@mui/material'; import { zodResolver } from '@hookform/resolvers/zod'; -import { ReportInput, ReportInformationInput, Report } from '../../../../../utils/api-types'; +import type { ReportInput, ReportInformationInput, Report } from '../../../../../utils/api-types'; import { zodImplement } from '../../../../../utils/Zod'; import { useFormatter } from '../../../../../components/i18n'; import ReportInformationType from './ReportInformationType'; diff --git a/openbas-front/src/admin/components/simulations/simulation/reports/ExerciseReports.tsx b/openbas-front/src/admin/components/simulations/simulation/reports/ExerciseReports.tsx index 8961e779dc..be8f87ec95 100644 --- a/openbas-front/src/admin/components/simulations/simulation/reports/ExerciseReports.tsx +++ b/openbas-front/src/admin/components/simulations/simulation/reports/ExerciseReports.tsx @@ -6,12 +6,12 @@ import useDataLoader from '../../../../../utils/hooks/useDataLoader'; import { useHelper } from '../../../../../store'; import { useAppDispatch } from '../../../../../utils/hooks'; import { addReportForExercise, deleteReportForExercise, fetchReportsForExercise, updateReportForExercise } from '../../../../../actions/reports/report-actions'; -import { ReportsHelper } from '../../../../../actions/reports/report-helper'; +import type { ReportsHelper } from '../../../../../actions/reports/report-helper'; import Reports from '../../../components/reports/Reports'; import { PermissionsContext, ReportContextType, ReportContext } from '../../../common/Context'; import type { Report, ReportInput } from '../../../../../utils/api-types'; import ExerciseReportForm from './ExerciseReportForm'; -import { default as CreateReportDialog } from '../../../../../components/common/Dialog'; +import Dialog from '../../../../../components/common/Dialog'; import { useFormatter } from '../../../../../components/i18n'; interface ReportListProps { @@ -68,7 +68,7 @@ const ExerciseReports: React.FC = ({ exerciseId, exerciseName } > - = ({ exerciseId, exerciseName } handleCancel={handleCloseCreate} initialValues={{ report_name: exerciseName } as Report} /> - + )} diff --git a/openbas-front/src/utils/Localization.js b/openbas-front/src/utils/Localization.js index 2a6db56884..10b84e4f13 100644 --- a/openbas-front/src/utils/Localization.js +++ b/openbas-front/src/utils/Localization.js @@ -769,7 +769,7 @@ const i18n = { 'General information': 'Informations générales', 'There is no report for this simulation yet': 'Il n\'y a pas encore de rapport pour cette simulation', 'Do you want to delete this report ?': 'Voulez-vous supprimer ce rapport ?', - 'Player surveys' : 'Enquête auprès des joueurs', + 'Player surveys': 'Enquête auprès des joueurs', 'Score details': 'Details des scores', 'Exercise details': 'Détails de l\'exercise', 'Simulation definition statistics': 'Statistiques de la définition de la simulation',