/*
* Licensed to Elasticsearch under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.elasticsearch.search.suggest.term;
import com.carrotsearch.randomizedtesting.generators.RandomStrings;
import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.common.xcontent.json.JsonXContent;
import org.elasticsearch.search.suggest.AbstractSuggestionBuilderTestCase;
import org.elasticsearch.search.suggest.SortBy;
import org.elasticsearch.search.suggest.SuggestBuilder;
import org.elasticsearch.search.suggest.term.TermSuggestionBuilder.StringDistanceImpl;
import org.elasticsearch.search.suggest.term.TermSuggestionBuilder.SuggestMode;
import java.io.IOException;
import java.util.Locale;
import static org.elasticsearch.search.suggest.DirectSpellcheckerSettings.DEFAULT_ACCURACY;
import static org.elasticsearch.search.suggest.DirectSpellcheckerSettings.DEFAULT_MAX_EDITS;
import static org.elasticsearch.search.suggest.DirectSpellcheckerSettings.DEFAULT_MAX_INSPECTIONS;
import static org.elasticsearch.search.suggest.DirectSpellcheckerSettings.DEFAULT_MAX_TERM_FREQ;
import static org.elasticsearch.search.suggest.DirectSpellcheckerSettings.DEFAULT_MIN_DOC_FREQ;
import static org.elasticsearch.search.suggest.DirectSpellcheckerSettings.DEFAULT_MIN_WORD_LENGTH;
import static org.elasticsearch.search.suggest.DirectSpellcheckerSettings.DEFAULT_PREFIX_LENGTH;
import static org.hamcrest.Matchers.containsString;
/**
* Test the {@link TermSuggestionBuilder} class.
*/
public class TermSuggestionBuilderTests extends AbstractSuggestionBuilderTestCase<TermSuggestionBuilder> {
/**
* creates random suggestion builder, renders it to xContent and back to new instance that should be equal to original
*/
@Override
protected TermSuggestionBuilder randomSuggestionBuilder() {
return randomTermSuggestionBuilder();
}
/**
* Creates a random TermSuggestionBuilder
*/
public static TermSuggestionBuilder randomTermSuggestionBuilder() {
TermSuggestionBuilder testBuilder = new TermSuggestionBuilder(randomAlphaOfLengthBetween(2, 20));
setCommonPropertiesOnRandomBuilder(testBuilder);
maybeSet(testBuilder::suggestMode, randomSuggestMode());
maybeSet(testBuilder::accuracy, randomFloat());
maybeSet(testBuilder::sort, randomSort());
maybeSet(testBuilder::stringDistance, randomStringDistance());
maybeSet(testBuilder::maxEdits, randomIntBetween(1, 2));
maybeSet(testBuilder::maxInspections, randomInt(Integer.MAX_VALUE));
maybeSet(testBuilder::maxTermFreq, randomFloat());
maybeSet(testBuilder::prefixLength, randomInt(Integer.MAX_VALUE));
maybeSet(testBuilder::minWordLength, randomInt(Integer.MAX_VALUE));
maybeSet(testBuilder::minDocFreq, randomFloat());
return testBuilder;
}
private static SuggestMode randomSuggestMode() {
final int randomVal = randomIntBetween(0, 2);
switch (randomVal) {
case 0: return SuggestMode.MISSING;
case 1: return SuggestMode.POPULAR;
case 2: return SuggestMode.ALWAYS;
default: throw new IllegalArgumentException("No suggest mode with an ordinal of " + randomVal);
}
}
private static SortBy randomSort() {
int randomVal = randomIntBetween(0, 1);
switch (randomVal) {
case 0: return SortBy.SCORE;
case 1: return SortBy.FREQUENCY;
default: throw new IllegalArgumentException("No sort mode with an ordinal of " + randomVal);
}
}
private static StringDistanceImpl randomStringDistance() {
int randomVal = randomIntBetween(0, 4);
switch (randomVal) {
case 0: return StringDistanceImpl.INTERNAL;
case 1: return StringDistanceImpl.DAMERAU_LEVENSHTEIN;
case 2: return StringDistanceImpl.LEVENSTEIN;
case 3: return StringDistanceImpl.JAROWINKLER;
case 4: return StringDistanceImpl.NGRAM;
default: throw new IllegalArgumentException("No string distance algorithm with an ordinal of " + randomVal);
}
}
@Override
protected void mutateSpecificParameters(TermSuggestionBuilder builder) throws IOException {
switch (randomIntBetween(0, 9)) {
case 0:
builder.suggestMode(randomValueOtherThan(builder.suggestMode(), () -> randomSuggestMode()));
break;
case 1:
builder.accuracy(randomValueOtherThan(builder.accuracy(), () -> randomFloat()));
break;
case 2:
builder.sort(randomValueOtherThan(builder.sort(), () -> randomSort()));
break;
case 3:
builder.stringDistance(randomValueOtherThan(builder.stringDistance(), () -> randomStringDistance()));
break;
case 4:
builder.maxEdits(randomValueOtherThan(builder.maxEdits(), () -> randomIntBetween(1, 2)));
break;
case 5:
builder.maxInspections(randomValueOtherThan(builder.maxInspections(), () -> randomInt(Integer.MAX_VALUE)));
break;
case 6:
builder.maxTermFreq(randomValueOtherThan(builder.maxTermFreq(), () -> randomFloat()));
break;
case 7:
builder.prefixLength(randomValueOtherThan(builder.prefixLength(), () -> randomInt(Integer.MAX_VALUE)));
break;
case 8:
builder.minWordLength(randomValueOtherThan(builder.minWordLength(), () -> randomInt(Integer.MAX_VALUE)));
break;
case 9:
builder.minDocFreq(randomValueOtherThan(builder.minDocFreq(), () -> randomFloat()));
break;
default:
break; // do nothing
}
}
public void testInvalidParameters() throws IOException {
// test missing field name
Exception e = expectThrows(NullPointerException.class, () -> new TermSuggestionBuilder((String) null));
assertEquals("suggestion requires a field name", e.getMessage());
// test empty field name
e = expectThrows(IllegalArgumentException.class, () -> new TermSuggestionBuilder(""));
assertEquals("suggestion field name is empty", e.getMessage());
TermSuggestionBuilder builder = new TermSuggestionBuilder(randomAlphaOfLengthBetween(2, 20));
// test invalid accuracy values
expectThrows(IllegalArgumentException.class, () -> builder.accuracy(-0.5f));
expectThrows(IllegalArgumentException.class, () -> builder.accuracy(1.1f));
// test invalid max edit distance values
expectThrows(IllegalArgumentException.class, () -> builder.maxEdits(0));
expectThrows(IllegalArgumentException.class, () -> builder.maxEdits(-1));
expectThrows(IllegalArgumentException.class, () -> builder.maxEdits(3));
// test invalid max inspections values
expectThrows(IllegalArgumentException.class, () -> builder.maxInspections(-1));
// test invalid max term freq values
expectThrows(IllegalArgumentException.class, () -> builder.maxTermFreq(-0.5f));
expectThrows(IllegalArgumentException.class, () -> builder.maxTermFreq(1.5f));
builder.maxTermFreq(2.0f);
// test invalid min doc freq values
expectThrows(IllegalArgumentException.class, () -> builder.minDocFreq(-0.5f));
expectThrows(IllegalArgumentException.class, () -> builder.minDocFreq(1.5f));
builder.minDocFreq(2.0f);
// test invalid min word length values
expectThrows(IllegalArgumentException.class, () -> builder.minWordLength(0));
expectThrows(IllegalArgumentException.class, () -> builder.minWordLength(-1));
// test invalid prefix length values
expectThrows(IllegalArgumentException.class, () -> builder.prefixLength(-1));
// test invalid size values
expectThrows(IllegalArgumentException.class, () -> builder.size(0));
expectThrows(IllegalArgumentException.class, () -> builder.size(-1));
// null values not allowed for enums
expectThrows(NullPointerException.class, () -> builder.sort(null));
expectThrows(NullPointerException.class, () -> builder.stringDistance(null));
expectThrows(NullPointerException.class, () -> builder.suggestMode(null));
}
public void testDefaultValuesSet() {
TermSuggestionBuilder builder = new TermSuggestionBuilder(randomAlphaOfLengthBetween(2, 20));
assertEquals(DEFAULT_ACCURACY, builder.accuracy(), Float.MIN_VALUE);
assertEquals(DEFAULT_MAX_EDITS, builder.maxEdits());
assertEquals(DEFAULT_MAX_INSPECTIONS, builder.maxInspections());
assertEquals(DEFAULT_MAX_TERM_FREQ, builder.maxTermFreq(), Float.MIN_VALUE);
assertEquals(DEFAULT_MIN_DOC_FREQ, builder.minDocFreq(), Float.MIN_VALUE);
assertEquals(DEFAULT_MIN_WORD_LENGTH, builder.minWordLength());
assertEquals(DEFAULT_PREFIX_LENGTH, builder.prefixLength());
assertEquals(SortBy.SCORE, builder.sort());
assertEquals(StringDistanceImpl.INTERNAL, builder.stringDistance());
assertEquals(SuggestMode.MISSING, builder.suggestMode());
}
public void testMalformedJson() {
final String field = RandomStrings.randomAsciiOfLength(random(), 10).toLowerCase(Locale.ROOT);
String suggest = "{\n" +
" \"bad-payload\" : {\n" +
" \"text\" : \"the amsterdma meetpu\",\n" +
" \"term\" : {\n" +
" \"field\" : { \"" + field + "\" : \"bad-object\" }\n" +
" }\n" +
" }\n" +
"}";
try (XContentParser parser = createParser(JsonXContent.jsonXContent, suggest)) {
final SuggestBuilder suggestBuilder = SuggestBuilder.fromXContent(parser);
fail("Should not have been able to create SuggestBuilder from malformed JSON: " + suggestBuilder);
} catch (Exception e) {
assertThat(e.getMessage(), containsString("parsing failed"));
}
}
}