Skip to content

Commit

Permalink
[frontend/backend] add tests + improve code
Browse files Browse the repository at this point in the history
  • Loading branch information
MarineLeM committed Sep 13, 2024
1 parent af7f817 commit 9aabf65
Show file tree
Hide file tree
Showing 10 changed files with 315 additions and 15 deletions.
169 changes: 169 additions & 0 deletions openbas-api/src/test/java/io/openbas/rest/ReportApiTest.java
Original file line number Diff line number Diff line change
@@ -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<Report> 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()));
}
}
}
131 changes: 131 additions & 0 deletions openbas-api/src/test/java/io/openbas/service/ReportServiceTest.java
Original file line number Diff line number Diff line change
@@ -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<Report> 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<Report> 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());
}
}



















2 changes: 1 addition & 1 deletion openbas-front/src/actions/reports/report-helper.d.ts
Original file line number Diff line number Diff line change
@@ -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[];
Expand Down
Original file line number Diff line number Diff line change
@@ -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';
Expand Down Expand Up @@ -52,13 +52,13 @@ const ReportPopover: React.FC<Props> = ({
handleSubmit={submitDelete}
text={t('Do you want to delete this report ?')}
/>
<EditDialog
<Dialog
title={t('Update the report')}
open={openEdit}
handleClose={handleCloseEdit}
>
{renderReportForm(submitUpdate, handleCloseEdit, report)}
</EditDialog>
</Dialog>
</>
);
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ const Reports: React.FC<Props> = ({ reports, navigateToReportPage }) => {
&& <ReportPopover
report={report}
actions={['Delete', 'Update']}
/>
/>
}
>
<ListItemButton onClick={() => navigateToReportPage(report.report_id)}>
Expand Down
Original file line number Diff line number Diff line change
@@ -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';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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';
Expand Down Expand Up @@ -151,7 +151,7 @@ const ExerciseReport: React.FC = () => {
teamsMap={teamsMap}
teams={teams}
isReport
/>
/>
}
{displayModule(ReportInformationType.EXERCISE_DETAILS) && <ExerciseDistribution exerciseId={exerciseId}/>}
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down
Loading

0 comments on commit 9aabf65

Please sign in to comment.