org.ambraproject.wombat.service.remote.SolrSearchApiTest.java Source code

Java tutorial

Introduction

Here is the source code for org.ambraproject.wombat.service.remote.SolrSearchApiTest.java

Source

/*
 * Copyright (c) 2017 Public Library of Science
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 */

package org.ambraproject.wombat.service.remote;

import com.google.common.base.Joiner;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.SetMultimap;
import org.ambraproject.wombat.config.TestSpringConfiguration;
import org.ambraproject.wombat.config.site.Site;
import org.ambraproject.wombat.config.site.SiteSet;
import org.ambraproject.wombat.util.MockSiteUtil;
import org.apache.http.NameValuePair;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.support.AnnotationConfigContextLoader;
import org.springframework.test.context.testng.AbstractTestNGSpringContextTests;
import org.testng.annotations.Test;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertNotNull;
import static org.testng.Assert.assertTrue;
import static org.testng.Assert.fail;

@ContextConfiguration(loader = AnnotationConfigContextLoader.class, classes = TestSpringConfiguration.class)
public class SolrSearchApiTest extends AbstractTestNGSpringContextTests {

    @Autowired
    private SiteSet siteSet;

    private static List<NameValuePair> buildCommonParams(String query, boolean useDisMax, int start, int rows,
            SolrSearchApi.SearchCriterion sortOrder, boolean forHomePage) {
        return ArticleSearchQuery.builder().setQuery(query).setSimple(useDisMax).setStart(start).setRows(rows)
                .setSortOrder(sortOrder).build().buildParameters();
    }

    @Test
    public void testBuildCommonParams() {
        // query string not null
        List<NameValuePair> actual = buildCommonParams("foo", true, 0, 10,
                SolrSearchApiImpl.SolrSortOrder.MOST_CITED, true);
        SetMultimap<String, String> actualMap = convertToMap(actual);
        assertCommonParams(actualMap, 2);
        assertSingle(actualMap.get("rows"), "10");

        // if start == 0 no param should be present.
        assertEquals(actualMap.get("start").size(), 0);
        assertSingle(actualMap.get("sort"), "alm_scopusCiteCount desc,id desc");
        assertSingle(actualMap.get("defType"), "dismax");
        assertSingle(actualMap.get("q"), "foo");

        // empty query string
        actual = buildCommonParams("", false, 0, 10, SolrSearchApiImpl.SolrSortOrder.MOST_CITED, true);
        actualMap = convertToMap(actual);
        assertCommonParams(actualMap, 2);
        assertEquals(actualMap.get("defType").size(), 0);
        assertSingle(actualMap.get("q"), "*:*");

        // not home page
        actual = buildCommonParams("", false, 0, 10, SolrSearchApiImpl.SolrSortOrder.RELEVANCE, false);
        actualMap = convertToMap(actual);
        assertCommonParams(actualMap, 2);
        assertSingle(actualMap.get("sort"), "score desc,publication_date desc,id desc");

    }

    private List<NameValuePair> buildFacetParams(String facetField, String query, boolean useDisMax) {
        return ArticleSearchQuery.builder().setFacet(facetField).setQuery(query).setSimple(useDisMax).build()
                .buildParameters();
    }

    @Test
    public void testFacetParams() {
        // query string not null
        List<NameValuePair> actual = buildFacetParams("journal", "foo", true);
        SetMultimap<String, String> actualMap = convertToMap(actual);
        assertFacetParams(actualMap, 2);
        assertSingle(actualMap.get("facet.field"), "journal");
        assertSingle(actualMap.get("defType"), "dismax");
        assertSingle(actualMap.get("q"), "foo");

        // empty query string
        actual = buildFacetParams("journal", "", false);
        actualMap = convertToMap(actual);
        assertFacetParams(actualMap, 2);
        assertSingle(actualMap.get("facet.field"), "journal");
        assertEquals(actualMap.get("defType").size(), 0);
        assertSingle(actualMap.get("q"), "*:*");
    }

    private static void setQueryFilters(List<NameValuePair> params, List<String> journalKeys,
            List<String> articleTypes, List<String> subjects, SolrSearchApi.SearchCriterion dateRange) {
        ArticleSearchQuery.builder().setJournalKeys(journalKeys).setArticleTypes(articleTypes).setSubjects(subjects)
                .setDateRange(dateRange).build().setQueryFilters(params);
    }

    @Test
    public void testSetQueryFilters() {
        List<NameValuePair> actual = new ArrayList<>();
        List<String> articleTypes = new ArrayList<>();
        List<String> subjects = new ArrayList<>();

        // Multiple journals
        setQueryFilters(actual, Arrays.asList("foo", "bar", "blaz"), articleTypes, subjects,
                SolrSearchApiImpl.SolrEnumeratedDateRange.ALL_TIME);
        SetMultimap<String, String> actualMap = convertToMap(actual);
        assertJournals(actualMap.get("fq"), "foo", "bar", "blaz");

        // date range
        actual = new ArrayList<>();
        setQueryFilters(actual, Collections.singletonList("foo"), articleTypes, subjects,
                SolrSearchApiImpl.SolrEnumeratedDateRange.LAST_3_MONTHS);
        actualMap = convertToMap(actual);
        assertEquals(actualMap.get("fq").size(), 2);
        for (String s : actualMap.get("fq")) {
            if (!s.contains("publication_date:") && !s.contains("journal_key:")) {
                fail(s);
            }
        }

        // null date range
        actual = new ArrayList<>();
        setQueryFilters(new ArrayList<NameValuePair>(), Collections.singletonList("foo"), articleTypes, subjects,
                null);
        actualMap = convertToMap(actual);
        Set<String> fq = actualMap.get("fq");
        for (String s : fq) {
            if (s.startsWith("publication_date:")) {
                fail(s);
            }
        }
    }

    @Test
    public void testSetQueryFilters_ExplicitDateRange() throws IOException {
        List<NameValuePair> actual = new ArrayList<>();
        SolrSearchApiImpl.SolrExplicitDateRange edr = new SolrSearchApiImpl.SolrExplicitDateRange("test",
                "2011-01-01", "2015-06-01");

        setQueryFilters(actual, Collections.singletonList("foo"), new ArrayList<String>(), new ArrayList<String>(),
                edr);
        SetMultimap<String, String> actualMap = convertToMap(actual);
        assertPubDate(actualMap.get("fq"));
        assertJournals(actualMap.get("fq"), "foo");
    }

    @Test
    public void testSetQueryFilters_IncludeArticleTypes() throws IOException {
        List<NameValuePair> actual = new ArrayList<>();
        SolrSearchApiImpl.SolrExplicitDateRange edr = new SolrSearchApiImpl.SolrExplicitDateRange("test",
                "2011-01-01", "2015-06-01");
        ArrayList<String> articleTypes = new ArrayList<>();
        articleTypes.add("Research Article");
        setQueryFilters(actual, Collections.singletonList("foo"), articleTypes, new ArrayList<String>(), edr);
        SetMultimap<String, String> actualMap = convertToMap(actual);
        assertPubDate(actualMap.get("fq"));
        assertJournals(actualMap.get("fq"), "foo");
        assertArticleTypes(actualMap.get("fq"));
    }

    @Test
    public void testSetQueryFilters_IncludeSubjects() throws IOException {
        List<NameValuePair> actual = new ArrayList<>();
        SolrSearchApiImpl.SolrExplicitDateRange edr = new SolrSearchApiImpl.SolrExplicitDateRange("test",
                "2011-01-01", "2015-06-01");
        ArrayList<String> articleTypes = new ArrayList<>();
        articleTypes.add("Research Article");
        setQueryFilters(actual, Collections.singletonList("foo"), articleTypes,
                Arrays.asList("Skull", "Head", "Teeth"), edr);
        SetMultimap<String, String> actualMap = convertToMap(actual);
        assertPubDate(actualMap.get("fq"));
        assertJournals(actualMap.get("fq"), "foo");
        assertArticleTypes(actualMap.get("fq"));
        assertSubjects(actualMap.get("fq"), "\"Skull\"", "\"Head\"", "\"Teeth\"");
    }

    private static class SearchApiForAddArticleLinksTest extends SolrSearchApiImpl {

        @Override
        protected void initializeEIssnToJournalKeyMap(SiteSet siteSet, Site currentSite) throws IOException {
            ImmutableMap.Builder<String, String> builder = new ImmutableMap.Builder<>();
            builder.put("123", "journal1Key").put("456", "journal2Key").put("789", "collectionJournalKey");
            eIssnToJournalKey = builder.build();
        }
    }

    @Test
    public void testAddArticleLinks() throws IOException {
        SolrSearchApi solrSearchApiForTest = new SearchApiForAddArticleLinksTest();
        Map<String, List<Map>> searchResults = new HashMap<>();
        List<Map> docs = new ArrayList<>(1);
        Map doc = new HashMap();
        List<String> crossPubbedJournals = new ArrayList<>(1);
        crossPubbedJournals.add("journal1Key");
        doc.put("id", "12345");
        doc.put("eissn", "123");
        docs.add(doc);
        searchResults.put("docs", docs);
        MockHttpServletRequest request = new MockHttpServletRequest();
        request.setContextPath("someContextPath");
        Site site = MockSiteUtil.getByUniqueJournalKey(siteSet, "journal2Key");

        Map<?, ?> actual = solrSearchApiForTest.addArticleLinks(searchResults, request, site, siteSet);
        List<Map> actualDocs = (List) actual.get("docs");
        assertEquals(actualDocs.size(), 1);
        Map actualDoc = (Map) actualDocs.get(0);
        assertEquals(actualDoc.get("id"), "12345");
        assertTrue(actualDoc.get("link").toString().endsWith("someContextPath/site1/article?id=12345"));
    }

    @Test
    public void testBuildSubjectClause() {
        assertEquals(ArticleSearchQuery.buildSubjectClause(Arrays.asList("foo")), "subject:\"foo\"");
        assertEquals(ArticleSearchQuery.buildSubjectClause(Arrays.asList("foo", "2nd subject")),
                "subject:\"foo\" AND subject:\"2nd subject\"");
    }

    @Test
    public void testBuildAuthorClause() {
        assertEquals(ArticleSearchQuery.buildAuthorClause(Arrays.asList("author1")), "author:\"author1\"");
        assertEquals(ArticleSearchQuery.buildAuthorClause(Arrays.asList("author1", "author2")),
                "author:\"author1\" AND author:\"author2\"");
    }

    /**
     * Converts a list of params to a multimap from key to value(s).
     */
    private SetMultimap<String, String> convertToMap(List<NameValuePair> nameValuePairs) {
        SetMultimap<String, String> map = HashMultimap.create();
        for (NameValuePair pair : nameValuePairs) {
            map.put(pair.getName(), pair.getValue());
        }
        return map;
    }

    /**
     * Tests for parameters expected to be the same across all tests.
     */
    private void assertCommonParams(SetMultimap<String, String> actual, int expectedNumFqParams) {
        assertSingle(actual.get("wt"), "json");
        Set<String> fq = actual.get("fq");

        // We check two of the fq params here; the caller should check the others.
        Joiner joiner = Joiner.on(",");
        assertEquals(fq.size(), expectedNumFqParams, joiner.join(fq));
        assertTrue(fq.contains("doc_type:full"));
        assertTrue(fq.contains("!article_type_facet:\"Issue Image\""));
        assertSingle(actual.get("hl"), "false");
        assertSingle(actual.get("facet"), "false");
    }

    /**
     * Tests for parameters expected to be the same across all tests.
     */
    private void assertFacetParams(SetMultimap<String, String> actual, int expectedNumFqParams) {
        assertSingle(actual.get("wt"), "json");
        assertSingle(actual.get("json.nl"), "map");
        Set<String> fq = actual.get("fq");

        // We check two of the fq params here; the caller should check the others.
        Joiner joiner = Joiner.on(",");
        assertEquals(fq.size(), expectedNumFqParams, joiner.join(fq));
        assertTrue(fq.contains("doc_type:full"));
        assertTrue(fq.contains("!article_type_facet:\"Issue Image\""));
        assertSingle(actual.get("hl"), "false");
        assertSingle(actual.get("facet"), "true");
    }

    /**
     * Asserts that the given set (all values of a single parameter) has exactly one entry with the expected value.
     */
    private void assertSingle(Set<String> multimapSet, String expected) {
        assertEquals(multimapSet.size(), 1);
        assertTrue(multimapSet.contains(expected));
    }

    // Regex used to validate the publication_date fq param.  This is not very strict, since we only
    // look for anything that appears to resemble a date range, without actually parsing the dates.

    private static final String TIMESTAMP_RE = "\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}\\:\\d{2}(\\.\\d+)?Z";

    private static final Pattern PUB_DATE_RE = Pattern
            .compile(String.format("publication_date:\\[%s TO %s\\]", TIMESTAMP_RE, TIMESTAMP_RE));

    private static final Pattern ARTICLE_TYPE_RE = Pattern.compile(String.format("article_type_facet:\"[A-z ]+\""));

    private void assertPubDate(Set<String> actualFq) {
        String pubDate = null;
        for (String s : actualFq) {
            if (s.startsWith("publication_date:")) {
                pubDate = s;
                break;
            }
        }
        assertNotNull(pubDate);
        Matcher matcher = PUB_DATE_RE.matcher(pubDate);
        assertTrue(matcher.matches());
    }

    private void assertJournals(Set<String> actualFq, String... expectedJournals) {
        String journals = null;
        for (String s : actualFq) {
            if (s.startsWith("journal_key:")) {
                journals = s;
                break;
            }
        }
        assertNotNull(journals);

        // For multiple journals, the expected format of the param is
        // "journal_key:PLoSBiology OR journal_key:PLoSONE"
        String[] parts = journals.split(" OR ");
        assertEquals(parts.length, expectedJournals.length);
        Set<String> actualJournals = new HashSet<>();
        for (String part : parts) {
            actualJournals.add(part.substring("journal_key:".length()));
        }
        for (String expected : expectedJournals) {
            assertTrue(actualJournals.contains(expected));
        }
    }

    private void assertArticleTypes(Set<String> actualFq) {
        String articleType = null;
        for (String s : actualFq) {
            if (s.startsWith("article_type_facet:")) {
                articleType = s;
                break;
            }
        }
        assertNotNull(articleType);
        Matcher matcher = ARTICLE_TYPE_RE.matcher(articleType);
        assertTrue(matcher.matches());
    }

    private void assertSubjects(Set<String> actualFq, String... expectedSubjects) {
        String subjects = null;
        for (String s : actualFq) {
            if (s.startsWith("subject:")) {
                subjects = s;
                break;
            }
        }

        assertNotNull(subjects);
        // For multiple subjects, the expected format of the param is
        // "subject:Teeth AND subject:Head"
        String[] parts = subjects.split(" AND ");
        assertEquals(parts.length, expectedSubjects.length);
        Set<String> actualSubjects = new HashSet<>();
        for (String part : parts) {
            actualSubjects.add(part.substring("subject:".length()));
        }
        for (String expected : expectedSubjects) {
            assertTrue(actualSubjects.contains(expected));
        }
    }
}