/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF 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.apache.zeppelin.search;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Mockito.*;
import static org.apache.zeppelin.search.LuceneSearch.formatId;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import org.apache.zeppelin.interpreter.InterpreterSetting;
import org.apache.zeppelin.notebook.Note;
import org.apache.zeppelin.notebook.NoteInterpreterLoader;
import org.apache.zeppelin.notebook.Paragraph;
import org.apache.zeppelin.notebook.repo.NotebookRepo;
import org.junit.After;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import com.google.common.base.Splitter;
import com.google.common.collect.ImmutableList;
public class LuceneSearchTest {
private static NoteInterpreterLoader replLoaderMock;
private static NotebookRepo notebookRepoMock;
private SearchService notebookIndex;
@BeforeClass
public static void beforeStartUp() {
notebookRepoMock = mock(NotebookRepo.class);
replLoaderMock = mock(NoteInterpreterLoader.class);
when(replLoaderMock.getInterpreterSettings())
.thenReturn(ImmutableList.<InterpreterSetting>of());
}
@Before
public void startUp() {
notebookIndex = new LuceneSearch();
}
@After
public void shutDown() {
notebookIndex.close();
}
@Test public void canIndexNotebook() {
//give
Note note1 = newNoteWithParapgraph("Notebook1", "test");
Note note2 = newNoteWithParapgraph("Notebook2", "not test");
List<Note> notebook = Arrays.asList(note1, note2);
//when
notebookIndex.addIndexDocs(notebook);
}
@Test public void canIndexAndQuery() {
//given
Note note1 = newNoteWithParapgraph("Notebook1", "test");
Note note2 = newNoteWithParapgraphs("Notebook2", "not test", "not test at all");
notebookIndex.addIndexDocs(Arrays.asList(note1, note2));
//when
List<Map<String, String>> results = notebookIndex.query("all");
//then
assertThat(results).isNotEmpty();
assertThat(results.size()).isEqualTo(1);
assertThat(results.get(0))
.containsEntry("id", formatId(note2.getId(), note2.getLastParagraph()));
}
@Test public void canIndexAndQueryByNotebookName() {
//given
Note note1 = newNoteWithParapgraph("Notebook1", "test");
Note note2 = newNoteWithParapgraphs("Notebook2", "not test", "not test at all");
notebookIndex.addIndexDocs(Arrays.asList(note1, note2));
//when
List<Map<String, String>> results = notebookIndex.query("Notebook1");
//then
assertThat(results).isNotEmpty();
assertThat(results.size()).isEqualTo(1);
assertThat(results.get(0)).containsEntry("id", note1.getId());
}
@Test public void indexKeyContract() throws IOException {
//give
Note note1 = newNoteWithParapgraph("Notebook1", "test");
//when
notebookIndex.addIndexDoc(note1);
//then
String id = resultForQuery("test").get(0).get(LuceneSearch.ID_FIELD);
assertThat(Splitter.on("/").split(id)) //key structure <noteId>/paragraph/<paragraphId>
.containsAllOf(note1.getId(), LuceneSearch.PARAGRAPH, note1.getLastParagraph().getId());
}
@Test //(expected=IllegalStateException.class)
public void canNotSearchBeforeIndexing() {
//given NO notebookIndex.index() was called
//when
List<Map<String, String>> result = notebookIndex.query("anything");
//then
assertThat(result).isEmpty();
//assert logs were printed
//"ERROR org.apache.zeppelin.search.SearchService:97 - Failed to open index dir RAMDirectory"
}
@Test public void canIndexAndReIndex() throws IOException {
//given
Note note1 = newNoteWithParapgraph("Notebook1", "test");
Note note2 = newNoteWithParapgraphs("Notebook2", "not test", "not test at all");
notebookIndex.addIndexDocs(Arrays.asList(note1, note2));
//when
Paragraph p2 = note2.getLastParagraph();
p2.setText("test indeed");
notebookIndex.updateIndexDoc(note2);
//then
List<Map<String, String>> results = notebookIndex.query("all");
assertThat(results).isEmpty();
results = notebookIndex.query("indeed");
assertThat(results).isNotEmpty();
}
@Test public void canDeleteNull() throws IOException {
//give
// looks like a bug in web UI: it tries to delete a note twice (after it has just been deleted)
//when
notebookIndex.deleteIndexDocs(null);
}
@Test public void canDeleteFromIndex() throws IOException {
//given
Note note1 = newNoteWithParapgraph("Notebook1", "test");
Note note2 = newNoteWithParapgraphs("Notebook2", "not test", "not test at all");
notebookIndex.addIndexDocs(Arrays.asList(note1, note2));
assertThat(resultForQuery("Notebook2")).isNotEmpty();
//when
notebookIndex.deleteIndexDocs(note2);
//then
assertThat(notebookIndex.query("all")).isEmpty();
assertThat(resultForQuery("Notebook2")).isEmpty();
List<Map<String, String>> results = resultForQuery("test");
assertThat(results).isNotEmpty();
assertThat(results.size()).isEqualTo(1);
}
@Test public void indexParagraphUpdatedOnNoteSave() throws IOException {
//given: total 2 notebooks, 3 paragraphs
Note note1 = newNoteWithParapgraph("Notebook1", "test");
Note note2 = newNoteWithParapgraphs("Notebook2", "not test", "not test at all");
notebookIndex.addIndexDocs(Arrays.asList(note1, note2));
assertThat(resultForQuery("test").size()).isEqualTo(3);
//when
Paragraph p1 = note1.getLastParagraph();
p1.setText("no no no");
note1.persist();
//then
assertThat(resultForQuery("Notebook1").size()).isEqualTo(1);
List<Map<String, String>> results = resultForQuery("test");
assertThat(results).isNotEmpty();
assertThat(results.size()).isEqualTo(2);
//does not include Notebook1's paragraph any more
for (Map<String, String> result: results) {
assertThat(result.get("id").startsWith(note1.getId())).isFalse();;
}
}
@Test public void indexNoteNameUpdatedOnNoteSave() throws IOException {
//given: total 2 notebooks, 3 paragraphs
Note note1 = newNoteWithParapgraph("Notebook1", "test");
Note note2 = newNoteWithParapgraphs("Notebook2", "not test", "not test at all");
notebookIndex.addIndexDocs(Arrays.asList(note1, note2));
assertThat(resultForQuery("test").size()).isEqualTo(3);
//when
note1.setName("NotebookN");
note1.persist();
//then
assertThat(resultForQuery("Notebook1")).isEmpty();
assertThat(resultForQuery("NotebookN")).isNotEmpty();
assertThat(resultForQuery("NotebookN").size()).isEqualTo(1);
}
private List<Map<String, String>> resultForQuery(String q) {
return notebookIndex.query(q);
}
/**
* Creates a new Note \w given name,
* adds a new paragraph \w given text
*
* @param noteName name of the note
* @param parText text of the paragraph
* @return Note
*/
private Note newNoteWithParapgraph(String noteName, String parText) {
Note note1 = newNote(noteName);
addParagraphWithText(note1, parText);
return note1;
}
/**
* Creates a new Note \w given name,
* adds N paragraphs \w given texts
*/
private Note newNoteWithParapgraphs(String noteName, String... parTexts) {
Note note1 = newNote(noteName);
for (String parText : parTexts) {
addParagraphWithText(note1, parText);
}
return note1;
}
private Paragraph addParagraphWithText(Note note, String text) {
Paragraph p = note.addParagraph();
p.setText(text);
return p;
}
private Note newNote(String name) {
Note note = new Note(notebookRepoMock, replLoaderMock, null, notebookIndex);
note.setName(name);
return note;
}
}