/*******************************************************************************
* Copyright (c) 2012, 2016 IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* IBM Corporation - initial API and implementation
*******************************************************************************/
package org.eclipse.orion.server.tests.search;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.FileLocator;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.Path;
import org.eclipse.core.runtime.URIUtil;
import org.eclipse.orion.internal.server.core.metastore.SimpleMetaStore;
import org.eclipse.orion.server.tests.ServerTestsActivator;
import org.eclipse.orion.server.tests.servlets.files.FileSystemTest;
import org.eclipse.orion.server.tests.servlets.xfer.TransferTest;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.junit.Assert;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import org.xml.sax.SAXException;
import com.meterware.httpunit.GetMethodWebRequest;
import com.meterware.httpunit.HttpUnitOptions;
import com.meterware.httpunit.PostMethodWebRequest;
import com.meterware.httpunit.PutMethodWebRequest;
import com.meterware.httpunit.WebConversation;
import com.meterware.httpunit.WebRequest;
import com.meterware.httpunit.WebResponse;
/**
* Tests for the search servlet.
*/
public class SearchTest extends FileSystemTest {
private static final String SEARCH_LOCATION = toAbsoluteURI("filesearch?q=");
@BeforeClass
public static void setupWorkspace() {
initializeWorkspaceLocation();
}
/**
* Asserts that a search result contains no matches.
*/
private void assertNoMatch(JSONObject searchResult) throws JSONException {
JSONObject response = searchResult.getJSONObject("response");
assertEquals(0, response.getInt("numFound"));
}
/**
* Asserts that a search result has exactly one match. Returns the matching document.
*/
private JSONObject assertOneMatch(JSONObject searchResult, String file) throws JSONException {
JSONObject response = searchResult.getJSONObject("response");
assertEquals(1, response.getInt("numFound"));
JSONArray docs = response.getJSONArray("docs");
JSONObject single = docs.getJSONObject(0);
assertEquals(file, single.getString("Name"));
return single;
}
/**
* Setup a project with some files that we can use for search tests.
* @throws CoreException
* @throws IOException
* @throws SAXException
*/
private void createTestData() throws Exception {
//create a directory to upload to
String directoryPath = "sample/directory/path" + System.currentTimeMillis();
createDirectory(directoryPath);
//start the import
URL entry = ServerTestsActivator.getContext().getBundle().getEntry("testData/searchTest/files.zip");
File source = new File(FileLocator.toFileURL(entry).getPath());
long length = source.length();
IPath path = new Path("/xfer/import").append(getTestBaseResourceURILocation()).append(directoryPath);
PostMethodWebRequest request = new PostMethodWebRequest(URIUtil.fromString(SERVER_LOCATION + path.toString()).toString());
request.setHeaderField("X-Xfer-Content-Length", Long.toString(length));
setAuthentication(request);
WebResponse postResponse = webConversation.getResponse(request);
assertEquals(HttpURLConnection.HTTP_OK, postResponse.getResponseCode());
String location = postResponse.getHeaderField("Location");
assertNotNull(location);
doImport(source, length, location);
}
/**
* Copied from {@link TransferTest}.
*/
private void doImport(File source, long length, String location) throws FileNotFoundException, IOException, SAXException {
//repeat putting chunks until done
byte[] chunk = new byte[64 * 1024];
InputStream in = new BufferedInputStream(new FileInputStream(source));
int chunkSize = 0;
int totalTransferred = 0;
while ((chunkSize = in.read(chunk, 0, chunk.length)) > 0) {
PutMethodWebRequest put = new PutMethodWebRequest(toAbsoluteURI(location), new ByteArrayInputStream(chunk, 0, chunkSize), "application/zip");
put.setHeaderField("Content-Range", "bytes " + totalTransferred + "-" + (totalTransferred + chunkSize - 1) + "/" + length);
put.setHeaderField("Content-Length", "" + length);
put.setHeaderField("Content-Type", "application/zip");
setAuthentication(put);
totalTransferred += chunkSize;
WebResponse putResponse = webConversation.getResponse(put);
if (totalTransferred == length) {
assertEquals(201, putResponse.getResponseCode());
} else {
assertEquals(308, putResponse.getResponseCode());
String range = putResponse.getHeaderField("Range");
assertEquals("bytes 0-" + (totalTransferred - 1), range);
}
}
in.close();
}
private JSONObject doSearch(String query) throws Exception {
WebRequest request = new GetMethodWebRequest(SEARCH_LOCATION + query);
setAuthentication(request);
WebResponse response = webConversation.getResponse(request);
assertEquals(HttpURLConnection.HTTP_OK, response.getResponseCode());
return new JSONObject(response.getText());
}
@Before
public void setUp() throws Exception {
HttpUnitOptions.setDefaultCharacterSet("UTF-8");
webConversation = new WebConversation();
webConversation.setExceptionsThrownOnErrorStatus(false);
setUpAuthorization();
createWorkspace(SimpleMetaStore.DEFAULT_WORKSPACE_NAME);
createTestProject(testName.getMethodName());
createTestData();
}
/**
* @see https://bugs.eclipse.org/bugs/show_bug.cgi?id=505737
* @throws Exception
*/
@Test
public void testUnknownTerm() throws Exception {
try {
JSONObject result = doSearch("Unknown:'hello'");
assertNoMatch(result);
} catch(NullPointerException npe) {
Assert.fail("No NPE should have been thrown");
}
}
/**
* @see https://bugs.eclipse.org/bugs/show_bug.cgi?id=505737
* @throws Exception
*/
@Test
public void testUnknownTermMixedTail() throws Exception {
try {
JSONObject result = doSearch("Name:'Foo'+NameLower:'foo'+Unknown:'hello'");
assertNoMatch(result);
} catch(NullPointerException npe) {
Assert.fail("No NPE should have been thrown");
}
}
/**
* @see https://bugs.eclipse.org/bugs/show_bug.cgi?id=505737
* @throws Exception
*/
@Test
public void testUnknownTermMixedMid() throws Exception {
try {
JSONObject result = doSearch("Name:'Foo'+Unknown:'hello'+NameLower:'foo'");
assertNoMatch(result);
} catch(NullPointerException npe) {
Assert.fail("No NPE should have been thrown");
}
}
/**
* Tests finding search results on a part of a word.
*/
@Test
public void testPartialWord() throws Exception {
JSONObject searchResult = doSearch("*ErrorMess");
JSONObject match = assertOneMatch(searchResult, "script.js");
//query with location
String location = match.getString("Location");
searchResult = doSearch("*ErrorMess+Location:" + location);
assertOneMatch(searchResult, "script.js");
}
/**
* Tests that we don't concatenate over punctuation boundaries.
* @throws Exception
*/
@Test
public void testWordConcatenation() throws Exception {
JSONObject searchResult = doSearch("jsondata.Children");
assertOneMatch(searchResult, "script.js");
searchResult = doSearch("jsondataChildren");
assertNoMatch(searchResult);
searchResult = doSearch("functionparentLocation");
assertNoMatch(searchResult);
searchResult = doSearch("function(parentLocation");
assertOneMatch(searchResult, "script.js");
}
/**
* Tests a malicious search that attempts to inject HTML.
* @throws Exception
*/
@Test
public void testHTMLTag() throws Exception {
JSONObject searchResult = doSearch("<img src=\"http://i.imgur.com/3dLMJ.jpg\" alt=\"Mark Macdonald\" title=\"Mark Macdonald\">");
assertNoMatch(searchResult);
}
/**
* Tests searching for special characters.
*/
@Test
public void testSearchNonAlphaNumeric() throws Exception {
JSONObject searchResult = doSearch("amber&sand");
assertOneMatch(searchResult, "a.txt");
searchResult = doSearch("per%25cent");
assertOneMatch(searchResult, "a.txt");
searchResult = doSearch("car%5Eat");
assertOneMatch(searchResult, "a.txt");
searchResult = doSearch("do%24%24ar");
assertOneMatch(searchResult, "a.txt");
searchResult = doSearch("has%23");
assertOneMatch(searchResult, "a.txt");
searchResult = doSearch("at%40sign");
assertOneMatch(searchResult, "a.txt");
searchResult = doSearch("equals%3Dcharacter");
assertOneMatch(searchResult, "page.html");
}
/**
* Tests searching for a sequence of words in a particular order
* @throws Exception
*/
@Test
public void testPhraseSearch() throws Exception {
//these same words appear in a.txt in a different order
JSONObject searchResult = doSearch("%22Eclipse Public License%22");
assertOneMatch(searchResult, "script.js");
}
/**
* Tests searching in a project whose name starts with square bracket.
* This is a regression test for bug 417124.
*/
@Test
public void testSearchInProjectWithURLName() throws Exception {
final String projectName = "[breakme]";
createTestProject(projectName);
createFile("smaug.txt", "Chiefest and Greatest of Calamities");
JSONObject searchResult = doSearch("Calamities");
assertOneMatch(searchResult, "smaug.txt");
}
/**
* Tests searching for a sequence of words in a particular order
* @throws Exception
*/
@Test
public void testPhraseDifferentCase() throws Exception {
//these same words appear in a.txt in a different order
JSONObject searchResult = doSearch("%22ECLIPSE public LiCeNse%22");
assertOneMatch(searchResult, "script.js");
}
/**
* Tests finding search results on a whole single word.
*/
@Test
public void testSingleWord() throws Exception {
//simple word in html tag
JSONObject searchResult = doSearch("monkey");
assertOneMatch(searchResult, "page.html");
//word in quotes
searchResult = doSearch("mammoth");
assertOneMatch(searchResult, "a.txt");
//word in quotes with Name
searchResult = doSearch("mammoth+Name:*.txt");
assertOneMatch(searchResult, "a.txt");
//compound word
searchResult = doSearch("Will-o'-the-wisp");
assertOneMatch(searchResult, "a.txt");
//should not match
searchResult = doSearch("Will-o'-the-whisk");
assertNoMatch(searchResult);
}
/**
* Tests finding search results in a directory with double byte character set folder name.
*/
@Test
public void testPathWithDBCS() throws Exception {
//simple word
JSONObject searchResult = doSearch("badger");
assertOneMatch(searchResult, "dbcs-folder.txt");
//wildcard
searchResult = doSearch("*badg?r");
JSONObject match = assertOneMatch(searchResult, "dbcs-folder.txt");
//query with location
String location = match.getString("Location");
searchResult = doSearch("badger+Location:" + location);
match = assertOneMatch(searchResult, "dbcs-folder.txt");
}
/**
* Tests finding search results with both the file and directory with double byte characters in the name.
*/
@Test
public void testFileWithDBCS() throws Exception {
//simple word
JSONObject searchResult = doSearch("gazelle");
assertOneMatch(searchResult, "\u65e5\u672c\u8a9e.txt");
//wildcard
searchResult = doSearch("*ga?elle");
JSONObject match = assertOneMatch(searchResult, "\u65e5\u672c\u8a9e.txt");
//query with location
String location = match.getString("Location");
searchResult = doSearch("gazelle+Location:" + location);
match = assertOneMatch(searchResult, "\u65e5\u672c\u8a9e.txt");
}
/**
* Tests finding search results of double byte characters.
* Commented out due to failure. See bug 409766 for details.
*/
public void testSearchDBCS() throws Exception {
//simple word
JSONObject searchResult = doSearch("\u4e56\u4e56");
assertOneMatch(searchResult, "DBCS.txt");
//wildcard
searchResult = doSearch("\u65e5?\u8a9e");
JSONObject match = assertOneMatch(searchResult, "DBCS.txt");
//query with location
String location = match.getString("Location");
searchResult = doSearch("\u4e56+Location:" + location);
match = assertOneMatch(searchResult, "DBCS.txt");
}
/**
* Tests finding search results in a file and directory with whitespace in the name.
*/
@Test
public void testPathWithSpaces() throws Exception {
//simple word
JSONObject searchResult = doSearch("oryx");
assertOneMatch(searchResult, "file with spaces.txt");
//wildcard
searchResult = doSearch("*lem?r");
JSONObject match = assertOneMatch(searchResult, "file with spaces.txt");
//query with location
String location = match.getString("Location");
searchResult = doSearch("oryx+Location:" + location);
match = assertOneMatch(searchResult, "file with spaces.txt");
//same query with wildcard on location
location = match.getString("Location");
location = location.substring(0, location.length() - "file%20with%20spaces.txt".length());
searchResult = doSearch("oryx+Location:" + location + '*');
match = assertOneMatch(searchResult, "file with spaces.txt");
}
/**
* Tests simply searching for file names.
*/
@Test
public void testFilenameSearch() throws Exception {
//search for file name starting with page
JSONObject searchResult = doSearch("NameLower:page*");
assertOneMatch(searchResult, "page.html");
//search for file name starting with page
searchResult = doSearch("Name:page*");
assertOneMatch(searchResult, "page.html");
//search for file name starting with page
searchResult = doSearch("NameLower:*.html");
assertOneMatch(searchResult, "page.html");
//search for file name starting with page
searchResult = doSearch("Name:*.html");
assertOneMatch(searchResult, "page.html");
//search for file name with *
searchResult = doSearch("NameLower:p*.html");
assertOneMatch(searchResult, "page.html");
//search for file name with *
searchResult = doSearch("Name:p*.html");
assertOneMatch(searchResult, "page.html");
//search for file name with ?
searchResult = doSearch("NameLower:pag?.html");
assertOneMatch(searchResult, "page.html");
//search for file name with ?
searchResult = doSearch("Name:pag?.html");
assertOneMatch(searchResult, "page.html");
// search for dbcs filenames
searchResult = doSearch("NameLower:\u65e5\u672c*");
assertOneMatch(searchResult, "\u65e5\u672c\u8a9e.txt");
// search for dbcs filenames
searchResult = doSearch("Name:\u65e5\u672c*");
assertOneMatch(searchResult, "\u65e5\u672c\u8a9e.txt");
// search upper case filename
searchResult = doSearch("NameLower:dbcs.txt");
assertOneMatch(searchResult, "DBCS.txt");
// search with lucene escaped dash
searchResult = doSearch("Name:dbcs\\-*");
assertOneMatch(searchResult, "dbcs-folder.txt");
}
/**
* Tests searching with regular expression search option.
*/
@Test
public void testRegExSearch() throws Exception {
// search with regex
JSONObject searchResult = doSearch("(error|warn)+RegEx:true");
assertOneMatch(searchResult, "script.js");
// search with regex
searchResult = doSearch("[Ee]rr+RegEx:true");
assertOneMatch(searchResult, "script.js");
}
/**
* Tests searching with case sensitivity search option.
*/
@Test
public void testCaseSensitive() throws Exception {
// search with not case sensitive, the default
JSONObject searchResult = doSearch("error");
assertOneMatch(searchResult, "script.js");
// search with not case sensitive, the default
searchResult = doSearch("ERROR");
assertOneMatch(searchResult, "script.js");
// search with case sensitive
searchResult = doSearch("error+CaseSensitive:true");
assertOneMatch(searchResult, "script.js");
// search with case sensitive
searchResult = doSearch("ERROR+CaseSensitive:true");
assertNoMatch(searchResult);
// search with case sensitive
searchResult = doSearch("error+CaseSensitive:true+NameLower:Script.js");
assertOneMatch(searchResult, "script.js");
// search with case sensitive
searchResult = doSearch("error+CaseSensitive:true+Name:*.js");
assertOneMatch(searchResult, "script.js");
// search with case sensitive
searchResult = doSearch("error+CaseSensitive:true+Name:script.js");
assertOneMatch(searchResult, "script.js");
}
}