Skip to content

Commit

Permalink
[backend/frontend] Add quick filters on scenarios injects & simulatio…
Browse files Browse the repository at this point in the history
…ns injects
  • Loading branch information
RomuDeuxfois committed Sep 3, 2024
1 parent 89fb356 commit b47964c
Show file tree
Hide file tree
Showing 25 changed files with 967 additions and 746 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ public class InjectOutput {
private String id;

@JsonProperty("inject_title")
@NotBlank
private String title;

@JsonProperty("inject_enabled")
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
package io.openbas.rest;

import io.openbas.IntegrationTest;
import io.openbas.database.model.Inject;
import io.openbas.database.model.InjectorContract;
import io.openbas.database.model.Scenario;
import io.openbas.database.repository.InjectRepository;
import io.openbas.database.repository.InjectorContractRepository;
import io.openbas.database.repository.ScenarioRepository;
import io.openbas.utils.fixtures.PaginationFixture;
import io.openbas.utils.mockUser.WithMockAdminUser;
import io.openbas.utils.pagination.SearchPaginationInput;
import io.openbas.utils.pagination.SortField;
import org.junit.jupiter.api.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.test.web.servlet.MockMvc;

import java.util.ArrayList;
import java.util.List;

import static io.openbas.database.model.Filters.FilterOperator.contains;
import static io.openbas.injectors.email.EmailContract.EMAIL_DEFAULT;
import static io.openbas.rest.scenario.ScenarioApi.SCENARIO_URI;
import static io.openbas.utils.JsonUtils.asJsonString;
import static io.openbas.utils.fixtures.InjectFixture.getInjectForEmailContract;
import static io.openbas.utils.fixtures.ScenarioFixture.createDefaultCrisisScenario;
import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

@TestInstance(PER_CLASS)
public class ScenarioInjectApiSearchTest extends IntegrationTest {

@Autowired
private MockMvc mvc;

@Autowired
private InjectRepository injectRepository;
@Autowired
private InjectorContractRepository injectorContractRepository;
@Autowired
private ScenarioRepository scenarioRepository;

private static final List<String> INJECT_IDS = new ArrayList<>();
private static String SCENARIO_ID;
private static String EMAIL_INJECTOR_CONTRACT_ID;

@BeforeAll
void beforeAll() {
InjectorContract injectorContract = this.injectorContractRepository.findById(EMAIL_DEFAULT).orElseThrow();
EMAIL_INJECTOR_CONTRACT_ID = injectorContract.getInjector().getId();

Scenario scenario = createDefaultCrisisScenario();
Scenario scenarioSaved = this.scenarioRepository.save(scenario);
SCENARIO_ID = scenarioSaved.getId();

Inject injectDefaultEmail = getInjectForEmailContract(injectorContract);
injectDefaultEmail.setScenario(scenarioSaved);
injectDefaultEmail.setTitle("Inject default email");
injectDefaultEmail.setDependsDuration(1L);
Inject injectDefaultEmailSaved = this.injectRepository.save(injectDefaultEmail);
INJECT_IDS.add(injectDefaultEmailSaved.getId());

Inject injectDefaultGlobal = getInjectForEmailContract(injectorContract);
injectDefaultGlobal.setScenario(scenarioSaved);
injectDefaultGlobal.setTitle("Inject global email");
Inject injectDefaultGlobalSaved = this.injectRepository.save(injectDefaultGlobal);
INJECT_IDS.add(injectDefaultGlobalSaved.getId());
}

@AfterAll
void afterAll() {
this.injectRepository.deleteAllById(INJECT_IDS);
this.scenarioRepository.deleteById(SCENARIO_ID);
}

@Nested
@WithMockAdminUser
@DisplayName("Retrieving injects")
class RetrievingInjects {
// -- PREPARE --

@Nested
@DisplayName("Searching page of injects")
class SearchingPageOfInjects {

@Test
@DisplayName("Retrieving first page of injects by textsearch")
void given_working_search_input_should_return_a_page_of_injects() throws Exception {
SearchPaginationInput searchPaginationInput = PaginationFixture.getDefault().textSearch("default").build();

mvc.perform(post(SCENARIO_URI + "/" + SCENARIO_ID + "/injects/simple")
.contentType(MediaType.APPLICATION_JSON)
.content(asJsonString(searchPaginationInput)))
.andExpect(status().is2xxSuccessful())
.andExpect(jsonPath("$.numberOfElements").value(1));
}

@Test
@DisplayName("Not retrieving first page of injects by textsearch")
void given_not_working_search_input_should_return_a_page_of_injects() throws Exception {
SearchPaginationInput searchPaginationInput = PaginationFixture.getDefault().textSearch("wrong").build();

mvc.perform(post(SCENARIO_URI + "/" + SCENARIO_ID + "/injects/simple")
.contentType(MediaType.APPLICATION_JSON)
.content(asJsonString(searchPaginationInput)))
.andExpect(status().is2xxSuccessful())
.andExpect(jsonPath("$.numberOfElements").value(0));
}
}

@Nested
@DisplayName("Sorting page of injects")
class SortingPageOfInjects {

@Test
@DisplayName("Sorting page of injects by name")
void given_sorting_input_by_name_should_return_a_page_of_injects_sort_by_name() throws Exception {
SearchPaginationInput searchPaginationInput = PaginationFixture.getDefault()
.sorts(List.of(SortField.builder().property("inject_title").build()))
.build();

mvc.perform(post(SCENARIO_URI + "/" + SCENARIO_ID + "/injects/simple")
.contentType(MediaType.APPLICATION_JSON)
.content(asJsonString(searchPaginationInput)))
.andExpect(status().is2xxSuccessful())
.andExpect(jsonPath("$.content.[0].inject_title").value("Inject default email"))
.andExpect(jsonPath("$.content.[1].inject_title").value("Inject global email"));
}

@Test
@DisplayName("Sorting page of injects by updated at")
void given_sorting_input_by_updated_at_should_return_a_page_of_injects_sort_by_updated_at()
throws Exception {
SearchPaginationInput searchPaginationInput = PaginationFixture.getDefault()
.sorts(List.of(SortField.builder().property("inject_depends_duration").direction("asc").build()))
.build();

mvc.perform(post(SCENARIO_URI + "/" + SCENARIO_ID + "/injects/simple")
.contentType(MediaType.APPLICATION_JSON)
.content(asJsonString(searchPaginationInput)))
.andExpect(status().is2xxSuccessful())
.andExpect(jsonPath("$.content.[0].inject_title").value("Inject global email"))
.andExpect(jsonPath("$.content.[1].inject_title").value("Inject default email"));
}
}

@Nested
@DisplayName("Filtering page of injects")
class FilteringPageOfInjects {

@Test
@DisplayName("Filtering page of injects by name")
void given_filter_input_by_name_should_return_a_page_of_injects_filter_by_name() throws Exception {
SearchPaginationInput searchPaginationInput = PaginationFixture.simpleFilter(
"inject_title", "email", contains
);

mvc.perform(post(SCENARIO_URI + "/" + SCENARIO_ID + "/injects/simple")
.contentType(MediaType.APPLICATION_JSON)
.content(asJsonString(searchPaginationInput)))
.andExpect(status().is2xxSuccessful())
.andExpect(jsonPath("$.numberOfElements").value(2));
}

@Test
@DisplayName("Filtering page of injects by injector contract")
void given_filter_input_by_injector_contract_should_return_a_page_of_injects_filter_by_injector_contract() throws Exception {
SearchPaginationInput searchPaginationInput = PaginationFixture.simpleFilter(
"inject_injector_contract", EMAIL_INJECTOR_CONTRACT_ID, contains
);

mvc.perform(post(SCENARIO_URI + "/" + SCENARIO_ID + "/injects/simple")
.contentType(MediaType.APPLICATION_JSON)
.content(asJsonString(searchPaginationInput)))
.andExpect(status().is2xxSuccessful())
.andExpect(jsonPath("$.numberOfElements").value(2));
}
}

}

}
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.time.Instant;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
Expand All @@ -35,27 +34,6 @@ private SchemaUtils() {
Email.class
);

public static final Class<?>[] BASE_CLASSES = {
byte.class,
short.class,
int.class,
long.class,
float.class,
double.class,
char.class,
boolean.class,
Byte.class,
Short.class,
Integer.class,
Long.class,
Float.class,
Double.class,
Character.class,
Boolean.class,
String.class,
Instant.class,
};

private static final ConcurrentHashMap<Class<?>, List<PropertySchema>> cacheMap = new ConcurrentHashMap<>();

// -- SCHEMA --
Expand Down
33 changes: 23 additions & 10 deletions openbas-front/src/actions/injects/Inject.d.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,4 @@
import type { Inject, InjectExpectation } from '../../utils/api-types';

export type InjectInput = {
inject_injector_contract: { id: string, type: string };
inject_tags: string[];
inject_depends_duration_days: number;
inject_depends_duration_hours: number;
inject_depends_duration_minutes: number;
inject_depends_duration_seconds: number;
};
import type { Inject, InjectExpectation, InjectOutput } from '../../utils/api-types';

export type InjectStore = Omit<Inject, 'inject_tags' | 'inject_content' | 'inject_injector_contract' | 'inject_teams' | 'inject_exercise' | 'inject_scenario'> & {
inject_tags: string[] | undefined;
Expand All @@ -17,11 +8,33 @@ export type InjectStore = Omit<Inject, 'inject_tags' | 'inject_content' | 'injec
// as we don't know the type of the content of a contract we need to put any here
// eslint-disable-next-line @typescript-eslint/no-explicit-any
injector_contract_content_parsed: any
convertedContent: {
label: Record<string, string>
config: {
expose: boolean
}
}
} & Inject['inject_injector_contract']
inject_exercise?: string
inject_scenario?: string
};

export type InjectorContractConvertedContent = {
label: Record<string, string>
config: {
expose: boolean
}
};

export type InjectOutputType = InjectOutput & {
inject_injector_contract: {
// as we don't know the type of the content of a contract we need to put any here
// eslint-disable-next-line @typescript-eslint/no-explicit-any
injector_contract_content_parsed: any
convertedContent: InjectorContractConvertedContent
} & Inject['inject_injector_contract']
};

export type InjectExpectationStore = Omit<InjectExpectation, 'inject_expectation_team', 'inject_expectation_inject'> & {
inject_expectation_team: string | undefined;
inject_expectation_inject: string | undefined;
Expand Down
12 changes: 11 additions & 1 deletion openbas-front/src/actions/injects/inject-action.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Dispatch } from 'redux';
import { getReferential, simpleCall, simplePostCall } from '../../utils/Action';
import type { Exercise, Scenario } from '../../utils/api-types';
import type { Exercise, Scenario, SearchPaginationInput } from '../../utils/api-types';
import * as schema from '../Schema';

export const testInject = (injectId: string) => {
Expand All @@ -21,9 +21,19 @@ export const fetchExerciseInjectsSimple = (exerciseId: Exercise['exercise_id'])
return getReferential(schema.arrayOfInjects, uri)(dispatch);
};

export const searchExerciseInjectsSimple = (exerciseId: Exercise['exercise_id'], input: SearchPaginationInput) => {
const uri = `/api/exercises/${exerciseId}/injects/simple`;
return simplePostCall(uri, input);
};

// -- SCENARIOS --

export const fetchScenarioInjectsSimple = (scenarioId: Scenario['scenario_id']) => (dispatch: Dispatch) => {
const uri = `/api/scenarios/${scenarioId}/injects/simple`;
return getReferential(schema.arrayOfInjects, uri)(dispatch);
};

export const searchScenarioInjectsSimple = (scenarioId: Scenario['scenario_id'], input: SearchPaginationInput) => {
const uri = `/api/scenarios/${scenarioId}/injects/simple`;
return simplePostCall(uri, input);
};
8 changes: 0 additions & 8 deletions openbas-front/src/actions/scenarios/scenario-actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ import type {
ScenarioInput,
ScenarioRecurrenceInput,
ScenarioTeamPlayersEnableInput,
ScenarioUpdateTagsInput,
ScenarioUpdateTeamsInput,
SearchPaginationInput,
Team,
Expand Down Expand Up @@ -78,13 +77,6 @@ export const duplicateScenario = (scenarioId: string) => (dispatch: Dispatch) =>
return postReferential(scenario, uri, null)(dispatch);
};

// -- TAGS --

export const updateScenarioTags = (scenarioId: Scenario['scenario_id'], data: ScenarioUpdateTagsInput) => {
const uri = `${SCENARIO_URI}/${scenarioId}/tags`;
return putReferential(scenario, uri, data);
};

// -- TEAMS --

export const fetchScenarioTeams = (scenarioId: Scenario['scenario_id']) => (dispatch: Dispatch) => {
Expand Down
21 changes: 14 additions & 7 deletions openbas-front/src/admin/components/common/Context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,15 @@ import type {
LessonsSendInput,
Objective,
ObjectiveInput,
SearchPaginationInput,
Team,
TeamCreateInput,
Variable,
VariableInput,
} from '../../../utils/api-types';
import type { UserStore } from '../teams/players/Player';
import type { InjectStore } from '../../../actions/injects/Inject';
import type { InjectOutputType, InjectStore } from '../../../actions/injects/Inject';
import { Page } from '../../../components/common/queryable/Page';

export type PermissionsContextType = {
permissions: { readOnly: boolean, canWrite: boolean, isRunning: boolean }
Expand Down Expand Up @@ -69,8 +71,9 @@ export type TeamContextType = {
};

export type InjectContextType = {
onAddInject: (inject: Inject) => Promise<{ result: string }>,
onUpdateInject: (injectId: Inject['inject_id'], inject: Inject) => Promise<{ result: string }>,
searchInjects: (input: SearchPaginationInput) => Promise<{ data: Page<InjectOutputType> }>,
onAddInject: (inject: Inject) => Promise<{ result: string, entities: { injects: Record<string, InjectStore> } }>,
onUpdateInject: (injectId: Inject['inject_id'], inject: Inject) => Promise<{ result: string, entities: { injects: Record<string, InjectStore> } }>,
onUpdateInjectTrigger?: (injectId: Inject['inject_id']) => void,
onUpdateInjectActivation: (injectId: Inject['inject_id'], injectEnabled: { inject_enabled: boolean }) => Promise<{
result: string,
Expand Down Expand Up @@ -156,11 +159,15 @@ export const TeamContext = createContext<TeamContextType>({
},
});
export const InjectContext = createContext<InjectContextType>({
onAddInject(_inject: Inject): Promise<{ result: string }> {
return Promise.resolve({ result: '' });
searchInjects(_: SearchPaginationInput): Promise<{ data: Page<InjectOutputType> }> {
return new Promise<{ data: Page<InjectOutputType> }>(() => {
});
},
onUpdateInject(_injectId: Inject['inject_id'], _inject: Inject): Promise<{ result: string }> {
return Promise.resolve({ result: '' });
onAddInject(_inject: Inject): Promise<{ result: string, entities: { injects: Record<string, InjectStore> } }> {
return Promise.resolve({ result: '', entities: { injects: {} } });
},
onUpdateInject(_injectId: Inject['inject_id'], _inject: Inject): Promise<{ result: string, entities: { injects: Record<string, InjectStore> } }> {
return Promise.resolve({ result: '', entities: { injects: {} } });
},
onUpdateInjectTrigger(_injectId: Inject['inject_id']): void {
},
Expand Down
Loading

0 comments on commit b47964c

Please sign in to comment.