// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. package com.yahoo.search.rendering; import com.yahoo.component.ComponentId; import com.yahoo.component.chain.Chain; import com.yahoo.container.QrSearchersConfig; import com.yahoo.prelude.Index; import com.yahoo.prelude.IndexFacts; import com.yahoo.prelude.IndexModel; import com.yahoo.prelude.SearchDefinition; import com.yahoo.prelude.fastsearch.FastHit; import com.yahoo.prelude.searcher.JuniperSearcher; import com.yahoo.search.Query; import com.yahoo.search.Result; import com.yahoo.search.Searcher; import com.yahoo.search.result.Coverage; import com.yahoo.search.result.ErrorMessage; import com.yahoo.search.result.Hit; import com.yahoo.search.result.HitGroup; import com.yahoo.search.result.Relevance; import com.yahoo.search.searchchain.Execution; import com.yahoo.search.searchchain.testutil.DocumentSourceSearcher; import com.yahoo.search.statistics.ElapsedTimeTestCase; import com.yahoo.search.statistics.ElapsedTimeTestCase.CreativeTimeSource; import com.yahoo.search.statistics.TimeTracker; import com.yahoo.text.Utf8; import org.junit.jupiter.api.Test; import java.io.ByteArrayOutputStream; import java.util.concurrent.CompletableFuture; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; /** * Test the XML renderer * * @author Steinar Knutsen * @author bratseth */ public class XMLRendererTestCase { @Test void testGetEncoding() { XmlRenderer renderer = new XmlRenderer(); renderer.init(); assertEquals("utf-8", renderer.getEncoding()); } @Test void testGetMimeType() { XmlRenderer renderer = new XmlRenderer(); renderer.init(); assertEquals("text/xml", renderer.getMimeType()); } @Test void testXmlRendering() throws Exception { Query q = new Query("/?query=a"); Result result = new Result(q); result.setCoverage(new Coverage(500, 1, 1)); FastHit h = new FastHit("http://localhost/", .95); h.setField("$a", "Hello, world."); h.setField("b", "foo"); result.hits().add(h); HitGroup g = new HitGroup("usual"); h = new FastHit("http://localhost/1", .90); h.setField("c", "d"); g.add(h); result.hits().add(g); HitGroup gg = new HitGroup("type grouphit"); gg.types().add("grouphit"); gg.setField("e", "f"); result.hits().add(gg); result.hits().addError(ErrorMessage.createInternalServerError("message")); String summary = render(result); String expected = "\n" + "\n" + " Internal server error.\n" + " \n" + " message\n" + " \n" + " \n" + " \n" + " 0.9\n" + " d\n" + " \n" + " \n" + " \n" + " type grouphit\n" + " \n" + " \n" + " 0.95\n" + " foo\n" + " \n" + "\n"; assertEquals(expected, summary); } @Test void testXmlRenderingOfDynamicSummary() throws Exception { String content = "\uFFF9Feeding\uFFFAfeed\uFFFB \u001F\uFFF9documents\uFFFAdocument\uFFFB\u001F into Vespa \uFFF9is\uFFFAbe\u001Eincrement of a set of \u001F\uFFF9documents\uFFFAdocument\uFFFB\u001F fed into Vespa \uFFF9is\u001Efloat in XML when \u001Fdocument\u001F attribute \uFFF9is\uFFFAbe\uFFFB int\u001E"; Result result = createResult("one", content, true); String summary = render(result); String expected = "\n" + "\n" + " \n" + " 1.0\n" + " one\n" + " Feeding documents into Vespa isincrement of a set of documents fed into Vespa float in XML when document attribute is int\n" + " \n" + "\n"; assertEquals(expected, summary); } @Test void testXmlRenderingWithTimeTracking() throws Exception { Query q = new Query("/?query=a&tracelevel=5"); q.getPresentation().setTiming(true); Result result = new Result(q); result.setCoverage(new Coverage(500, 1, 1)); TimeTracker t = new TimeTracker(new Chain(new NoopSearcher("first"), new NoopSearcher("second"), new NoopSearcher("third"))); ElapsedTimeTestCase.doInjectTimeSource(t, new CreativeTimeSource(new long[]{1L, 2L, 3L, 4L, 5L, 6L, 7L})); t.sampleSearch(0, true); t.sampleSearch(1, true); t.sampleSearch(2, true); t.sampleSearch(3, true); t.sampleSearchReturn(2, true, null); t.sampleSearchReturn(1, true, null); t.sampleSearchReturn(0, true, null); result.getElapsedTime().add(t); String summary = render(result); assertEquals("\n")); } private String render(Result result) throws Exception { XmlRenderer renderer = new XmlRenderer(); renderer.init(); ByteArrayOutputStream bs = new ByteArrayOutputStream(); CompletableFuture f = renderer.renderResponse(bs, result, null, null); assertTrue(f.get()); return Utf8.toString(bs.toByteArray()); } private Result createResult(String sdName, String content, boolean bolding) { Chain chain = createSearchChain(sdName, content); Query query = new Query("?query=12"); if ( ! bolding) query = new Query("?query=12&bolding=false"); Execution execution = createExecution(chain); Result result = execution.search(query); execution.fill(result); return result; } /** * Creates a search chain which always returns a result with one hit containing information given in this * * @param sdName the search definition type of the returned hit * @param content the content of the "dynteaser" field of the returned hit */ private Chain createSearchChain(String sdName, String content) { JuniperSearcher searcher = new JuniperSearcher(new ComponentId("test"), new QrSearchersConfig(new QrSearchersConfig.Builder())); DocumentSourceSearcher docsource = new DocumentSourceSearcher(); addResult(new Query("?query=12"), sdName, content, docsource); addResult(new Query("?query=12&bolding=false"), sdName, content, docsource); return new Chain<>(searcher, docsource); } private void addResult(Query query, String sdName, String content, DocumentSourceSearcher docsource) { Result r = new Result(query); FastHit hit = new FastHit(); hit.setId("http://abc.html"); hit.setRelevance(new Relevance(1)); hit.setField(Hit.SDDOCNAME_FIELD, sdName); hit.setField("dynteaser", content); r.hits().add(hit); docsource.addResult(query, r); } private Execution createExecution(Chain chain) { IndexModel indexModel = new IndexModel(createSearchDefinitionOne()); return new Execution(chain, Execution.Context.createContextStub(new IndexFacts(indexModel))); } private SearchDefinition createSearchDefinitionOne() { SearchDefinition one = new SearchDefinition("one"); Index dynteaser = new Index("dynteaser"); dynteaser.setDynamicSummary(true); one.addIndex(dynteaser); Index bigteaser = new Index("bigteaser"); dynteaser.setHighlightSummary(true); one.addIndex(bigteaser); Index otherteaser = new Index("otherteaser"); otherteaser.setDynamicSummary(true); one.addIndex(otherteaser); return one; } public static class NoopSearcher extends Searcher { public NoopSearcher(String name) { super(new ComponentId(name)); } @Override public Result search(Query query, Execution execution) { return execution.search(query); } } }