/* * (c) Copyright 2016 Micro Focus or one of its affiliates. * * Licensed under the MIT License (the "License"); you may not use this file * except in compliance with the License. * * The only warranties for products and services of Micro Focus and its affiliates * and licensors ("Micro Focus") are as may be set forth in the express warranty * statements accompanying such products and services. Nothing herein should be * construed as constituting an additional warranty. Micro Focus shall not be * liable for technical or editorial errors or omissions contained herein. The * information contained herein is subject to change without notice. */ package com.hp.autonomy.frontend.find.core.savedsearches.query; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.hp.autonomy.frontend.find.core.savedsearches.ConceptClusterPhrase; import com.hp.autonomy.frontend.find.core.savedsearches.EmbeddableIndex; import com.hp.autonomy.frontend.find.core.savedsearches.UserEntity; import com.hp.autonomy.frontend.find.core.test.AbstractFindIT; import com.hp.autonomy.frontend.find.core.test.MvcIntegrationTestUtils; import org.apache.commons.io.IOUtils; import org.hamcrest.BaseMatcher; import org.hamcrest.Description; import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.core.io.Resource; import org.springframework.http.MediaType; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.test.jdbc.JdbcTestUtils; import org.springframework.test.web.servlet.MvcResult; import org.springframework.test.web.servlet.ResultActions; import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder; import javax.annotation.PostConstruct; import javax.sql.DataSource; import java.time.ZonedDateTime; import java.time.chrono.ChronoZonedDateTime; import java.util.Collections; import java.util.HashSet; import java.util.Set; import static com.hp.autonomy.frontend.find.core.savedsearches.query.SavedQueryController.NEW_RESULTS_PATH; import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.CoreMatchers.not; import static org.hamcrest.CoreMatchers.nullValue; import static org.hamcrest.Matchers.*; import static org.hamcrest.collection.IsEmptyCollection.empty; import static org.hamcrest.core.Is.isA; import static org.junit.Assert.*; import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.authentication; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; @SuppressWarnings("SpringJavaAutowiringInspection") public abstract class AbstractSavedQueryIT extends AbstractFindIT { private static final TypeReference<Set<SavedQuery>> LIST_TYPE_REFERENCE = new TypeReference<Set<SavedQuery>>() { }; private static final String TITLE = "Any old saved search"; private static final String PRIMARY_PHRASE = "manhattan"; private static final String OTHER_PHRASE = "mid-town"; private static final Integer MIN_SCORE = 88; @Autowired private ObjectMapper objectMapper; @Autowired private DataSource dataSource; @Autowired private MvcIntegrationTestUtils integrationTestUtils; @Value("classpath:save-query-request.json") private Resource saveQueryRequestResource; private JdbcTemplate jdbcTemplate; @PostConstruct public void initialise() { jdbcTemplate = new JdbcTemplate(dataSource); } @Test public void create() throws Exception { createSavedQuery(getBaseSavedQuery()) .andExpect(status().isOk()) .andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8)) .andExpect(jsonPath("$.id", not(nullValue()))) .andExpect(jsonPath("$.title", equalTo(TITLE))) .andExpect(jsonPath("$.minScore", equalTo(MIN_SCORE))) .andExpect(jsonPath("$.conceptClusterPhrases", hasSize(2))) .andExpect(jsonPath("$.conceptClusterPhrases[*].phrase", containsInAnyOrder(PRIMARY_PHRASE, OTHER_PHRASE))) .andExpect(jsonPath("$.conceptClusterPhrases[?(@.phrase=='" + PRIMARY_PHRASE + "')].primary", contains(true))) .andExpect(jsonPath("$.conceptClusterPhrases[?(@.phrase== '" + PRIMARY_PHRASE + "')].clusterId", contains(0))) .andExpect(jsonPath("$.conceptClusterPhrases[?(@.phrase== '" + OTHER_PHRASE + "')].primary", contains(false))) .andExpect(jsonPath("$.conceptClusterPhrases[?(@.phrase=='" + OTHER_PHRASE + "')].clusterId", contains(0))); } @Test public void update() throws Exception { final SavedQuery createdEntity = createAndParseSavedQuery(getBaseSavedQuery()); final String updatedPhrase = "jersey"; final Set<ConceptClusterPhrase> conceptClusterPhrases = new HashSet<>(); conceptClusterPhrases.add(new ConceptClusterPhrase(updatedPhrase, true, 1)); final Integer updatedMinScore = 99; final SavedQuery updatedQuery = new SavedQuery.Builder() .setMinScore(updatedMinScore) .setConceptClusterPhrases(conceptClusterPhrases) .build(); final MockHttpServletRequestBuilder requestBuilder = put(SavedQueryController.PATH + '/' + createdEntity.getId()) .with(authentication(biAuth())) .content(objectMapper.writeValueAsString(updatedQuery)) .contentType(MediaType.APPLICATION_JSON); mockMvc.perform(requestBuilder) .andExpect(status().isOk()) .andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8)) .andExpect(jsonPath("$.id", is(createdEntity.getId().intValue()))) .andExpect(jsonPath("$.minScore", equalTo(updatedMinScore))) .andExpect(jsonPath("$.conceptClusterPhrases", hasSize(1))) .andExpect(jsonPath("$.conceptClusterPhrases[*].phrase", contains(updatedPhrase))) .andExpect(jsonPath("$.conceptClusterPhrases[?(@.phrase=='" + updatedPhrase + "')].primary", contains(true))) .andExpect(jsonPath("$.conceptClusterPhrases[?(@.phrase== '" + updatedPhrase + "')].clusterId", contains(1))); } @Test public void createAndFetch() throws Exception { final byte[] requestBytes = IOUtils.toByteArray(saveQueryRequestResource.getInputStream()); final MockHttpServletRequestBuilder requestBuilder = post(SavedQueryController.PATH + '/') .content(requestBytes) .contentType(MediaType.APPLICATION_JSON) .with(authentication(biAuth())); final MvcResult createResult = mockMvc.perform(requestBuilder) .andReturn(); final JsonNode responseTree = objectMapper.readTree(createResult.getResponse().getContentAsString()); final int id = responseTree.get("id").asInt(); listSavedQueries() .andExpect(status().isOk()) .andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8)) .andExpect(jsonPath("$[0].id", is(id))) .andExpect(jsonPath("$[0].title", is("\u30e2\u30f3\u30ad\u30fc"))) .andExpect(jsonPath("$[0].minDate", new ZonedDateTimeMatcher("2017-05-17T15:51:20Z"))) .andExpect(jsonPath("$[0].maxDate", new ZonedDateTimeMatcher("2017-05-17T15:51:40Z"))) .andExpect(jsonPath("$[0].conceptClusterPhrases", hasSize(3))) .andExpect(jsonPath("$[0].conceptClusterPhrases[*].phrase", containsInAnyOrder("characters", "faces", "animals"))) .andExpect(jsonPath("$[0].conceptClusterPhrases[?(@.phrase=='characters')].primary", contains(true))) .andExpect(jsonPath("$[0].conceptClusterPhrases[?(@.phrase=='characters')].clusterId", contains(1))) .andExpect(jsonPath("$[0].conceptClusterPhrases[?(@.phrase=='faces')].primary", contains(false))) .andExpect(jsonPath("$[0].conceptClusterPhrases[?(@.phrase=='faces')].clusterId", contains(1))) .andExpect(jsonPath("$[0].conceptClusterPhrases[?(@.phrase=='animals')].primary", contains(true))) .andExpect(jsonPath("$[0].conceptClusterPhrases[?(@.phrase=='animals')].clusterId", contains(2))) .andExpect(jsonPath("$[0].parametricValues", hasSize(1))) .andExpect(jsonPath("$[0].parametricValues[0].field", is("CATEGORY"))) .andExpect(jsonPath("$[0].parametricValues[0].value", is("COMPUTING"))) .andExpect(jsonPath("$[0].numericRangeRestrictions", hasSize(1))) .andExpect(jsonPath("$[0].numericRangeRestrictions[0].field", is("SOME_NUMBER"))) .andExpect(jsonPath("$[0].numericRangeRestrictions[0].min", is(123.5))) .andExpect(jsonPath("$[0].numericRangeRestrictions[0].max", is(124.5))) .andExpect(jsonPath("$[0].dateRangeRestrictions", hasSize(1))) .andExpect(jsonPath("$[0].dateRangeRestrictions[0].field", is("SOME_DATE"))) .andExpect(jsonPath("$[0].dateRangeRestrictions[0].min", new ZonedDateTimeMatcher("2017-05-17T15:51:20Z"))) .andExpect(jsonPath("$[0].dateRangeRestrictions[0].max", new ZonedDateTimeMatcher("2017-05-17T15:51:40Z"))) .andExpect(jsonPath("$[0].indexes", hasSize(2))) .andExpect(jsonPath("$[0].indexes[*].name", containsInAnyOrder("English Wikipedia", "\u65e5\u672c\u8a9e Wikipedia"))) .andExpect(jsonPath("$[0].indexes[?(@.name=='English Wikipedia')].domain", contains("MY_DOMAIN"))) .andExpect(jsonPath("$[0].indexes[?(@.name=='\u65e5\u672c\u8a9e Wikipedia')].domain", contains("MY_DOMAIN"))); } @Test public void deleteById() throws Exception { final SavedQuery createdEntity = createAndParseSavedQuery(getBaseSavedQuery()); final MockHttpServletRequestBuilder requestBuilder = delete(SavedQueryController.PATH + '/' + createdEntity.getId()) .contentType(MediaType.APPLICATION_JSON) .with(authentication(biAuth())); mockMvc.perform(requestBuilder).andExpect(status().isOk()); final Set<SavedQuery> queries = listAndParseSavedQueries(); assertThat(queries, is(empty())); } @Test public void getAllReturnsNothing() throws Exception { assertThat(listAndParseSavedQueries(), is(empty())); } @Test public void basicUserNotAuthorised() throws Exception { mockMvc.perform(get(SavedQueryController.PATH).with(authentication(userAuth()))) .andExpect(status().is(403)); } @Test public void checkTimeAuditDataInsertedUpdated() throws Exception { final SavedQuery inputSavedQuery = new SavedQuery.Builder() .setTitle("title") .setMinScore(0) .build(); final SavedQuery savedQuery = createAndParseSavedQuery(inputSavedQuery); assertNotNull(savedQuery.getId()); assertThat(savedQuery.getDateCreated(), isA(ZonedDateTime.class)); assertThat(savedQuery.getDateModified(), isA(ZonedDateTime.class)); assertTrue(savedQuery.getDateCreated().isEqual(savedQuery.getDateModified())); // Safe to assume completed in an hour // TODO: mock out the datetime service used by spring auditing to check this properly assertTrue(savedQuery.getDateCreated().plusHours(1).isAfter(ZonedDateTime.now())); savedQuery.setConceptClusterPhrases(Collections.singleton(new ConceptClusterPhrase("*", true, -1))); final SavedQuery savedQueryUpdate = new SavedQuery.Builder() .setId(savedQuery.getId()) .setTitle("new title") .setMinScore(0) .build(); final SavedQuery updatedSavedQuery = updateAndParseSavedQuery(savedQueryUpdate); assertThat(updatedSavedQuery.getDateCreated(), isA(ZonedDateTime.class)); assertThat(updatedSavedQuery.getDateModified(), isA(ZonedDateTime.class)); assertTrue(updatedSavedQuery.getDateModified().isAfter(savedQuery.getDateCreated())); } @Test public void checkUserNotDuplicated() throws Exception { final SavedQuery savedQuery1 = new SavedQuery.Builder() .setTitle("title1") .setMinScore(0) .build(); final SavedQuery savedQuery2 = new SavedQuery.Builder() .setTitle("title2") .setMinScore(0) .build(); createSavedQuery(savedQuery1); createSavedQuery(savedQuery2); final int userRows = JdbcTestUtils.countRowsInTable(jdbcTemplate, "find." + UserEntity.Table.NAME); assertThat(userRows, is(1)); } @Test public void checkForNewQueryResults() throws Exception { final Set<EmbeddableIndex> indexes = Collections.singleton(integrationTestUtils.getEmbeddableIndex()); final SavedQuery saveRequest1 = new SavedQuery.Builder() .setTitle("title1") .setMinScore(0) .setIndexes(indexes) .setConceptClusterPhrases(Collections.singleton(new ConceptClusterPhrase("*", true, -1))) .build(); final SavedQuery saveRequest2 = new SavedQuery.Builder() .setDateDocsLastFetched(ZonedDateTime.now()) .setTitle("title2") .setMinScore(0) .setIndexes(indexes) .setConceptClusterPhrases(Collections.singleton(new ConceptClusterPhrase("*", true, -1))) .build(); final SavedQuery savedQuery1 = createAndParseSavedQuery(saveRequest1); final long id1 = savedQuery1.getId(); final SavedQuery savedQuery2 = createAndParseSavedQuery(saveRequest2); final long id2 = savedQuery2.getId(); mockMvc.perform(get(SavedQueryController.PATH + NEW_RESULTS_PATH + '/' + id1).with(authentication(biAuth()))) .andExpect(status().isOk()) .andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8)) .andExpect(jsonPath("$", greaterThan(0))); mockMvc.perform(get(SavedQueryController.PATH + NEW_RESULTS_PATH + '/' + id2).with(authentication(biAuth()))) .andExpect(status().isOk()) .andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8)) .andExpect(jsonPath("$", is(0))); // likely to work though not foolproof } private ResultActions createSavedQuery(final SavedQuery savedQuery) throws Exception { final MockHttpServletRequestBuilder requestBuilder = post(SavedQueryController.PATH + '/') .content(objectMapper.writeValueAsString(savedQuery)) .contentType(MediaType.APPLICATION_JSON) .with(authentication(biAuth())); return mockMvc.perform(requestBuilder); } private SavedQuery createAndParseSavedQuery(final SavedQuery savedQuery) throws Exception { final MvcResult mvcResult = createSavedQuery(savedQuery).andReturn(); final String response = mvcResult.getResponse().getContentAsString(); return objectMapper.readValue(response, SavedQuery.class); } private SavedQuery updateAndParseSavedQuery(final SavedQuery update) throws Exception { final MockHttpServletRequestBuilder requestBuilder = put(SavedQueryController.PATH + '/' + update.getId()) .content(objectMapper.writeValueAsString(update)) .contentType(MediaType.APPLICATION_JSON) .with(authentication(biAuth())); final MvcResult mvcResult = mockMvc.perform(requestBuilder).andReturn(); final String response = mvcResult.getResponse().getContentAsString(); return objectMapper.readValue(response, SavedQuery.class); } private ResultActions listSavedQueries() throws Exception { return mockMvc.perform(get(SavedQueryController.PATH).with(authentication(biAuth()))); } private Set<SavedQuery> listAndParseSavedQueries() throws Exception { final MvcResult listResult = listSavedQueries() .andExpect(status().isOk()) .andReturn(); return objectMapper.readValue(listResult.getResponse().getContentAsString(), LIST_TYPE_REFERENCE); } private Set<ConceptClusterPhrase> getBaseConceptClusterPhrases() { final Set<ConceptClusterPhrase> conceptClusterPhrases = new HashSet<>(); final ConceptClusterPhrase manhattanClusterPhraseOne = new ConceptClusterPhrase(PRIMARY_PHRASE, true, 0); final ConceptClusterPhrase manhattanClusterPhraseTwo = new ConceptClusterPhrase(OTHER_PHRASE, false, 0); conceptClusterPhrases.add(manhattanClusterPhraseOne); conceptClusterPhrases.add(manhattanClusterPhraseTwo); return conceptClusterPhrases; } private SavedQuery getBaseSavedQuery() { return new SavedQuery.Builder() .setTitle(TITLE) .setMinScore(MIN_SCORE) .setConceptClusterPhrases(getBaseConceptClusterPhrases()) .build(); } private static class ZonedDateTimeMatcher extends BaseMatcher<ChronoZonedDateTime<?>> { private final ChronoZonedDateTime<?> expectation; private ZonedDateTimeMatcher(final CharSequence expectation) { this.expectation = ZonedDateTime.parse(expectation); } @Override public boolean matches(final Object o) { return o instanceof CharSequence && ZonedDateTime.parse((CharSequence) o).isEqual(expectation); } @Override public void describeTo(final Description description) { description.appendText(expectation.toString()); } } }