aboutsummaryrefslogtreecommitdiffstats
path: root/container-search/src/test/java/com/yahoo/search/rendering/XMLRendererTestCase.java
blob: 4a2e45e5b53bd466682dcdad8f6b5e4af4f5c722 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
// 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 =
                "<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n" +
                        "<result total-hit-count=\"0\" coverage-docs=\"500\" coverage-nodes=\"1\" coverage-full=\"false\" coverage=\"0\" results-full=\"0\" results=\"1\">\n" +
                        "  <error code=\"18\">Internal server error.</error>\n" +
                        "  <errordetails>\n" +
                        "    <error error=\"Internal server error.\" code=\"18\">message</error>\n" +
                        "  </errordetails>\n" +
                        "  <group relevancy=\"1.0\">\n" +
                        "    <hit type=\"summary\" relevancy=\"0.9\">\n" +
                        "      <field name=\"relevancy\">0.9</field>\n" +
                        "      <field name=\"c\">d</field>\n" +
                        "    </hit>\n" +
                        "  </group>\n" +
                        "  <hit type=\"grouphit\" relevancy=\"1.0\">\n" +
                        "    <id>type grouphit</id>\n" +
                        "  </hit>\n" +
                        "  <hit type=\"summary\" relevancy=\"0.95\">\n" +
                        "    <field name=\"relevancy\">0.95</field>\n" +
                        "    <field name=\"b\">foo</field>\n" +
                        "  </hit>\n" +
                        "</result>\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 =
                "<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n" +
                        "<result total-hit-count=\"0\">\n" +
                        "  <hit relevancy=\"1.0\">\n" +
                        "    <field name=\"relevancy\">1.0</field>\n" +
                        "    <field name=\"sddocname\">one</field>\n" +
                        "    <field name=\"dynteaser\">Feeding <hi>documents</hi> into Vespa is<sep />increment of a set of <hi>documents</hi> fed into Vespa <sep />float in XML when <hi>document</hi> attribute is int<sep /></field>\n" +
                        "  </hit>\n" +
                        "</result>\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<Searcher>(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("<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n<result total-hit-count=\"0\"",
                summary.substring(0, 67));
        assertTrue(summary.contains("querytime="));
        assertTrue(summary.contains("summaryfetchtime="));
        assertTrue(summary.contains("searchtime="));
        assertTrue(summary.contains("<meta type=\"context\">"));
    }

    private String render(Result result) throws Exception {
        XmlRenderer renderer = new XmlRenderer();
        renderer.init();
        ByteArrayOutputStream bs = new ByteArrayOutputStream();
        CompletableFuture<Boolean> 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<Searcher> 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<Searcher> 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<Searcher> 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);
        }

    }

}