diff --git a/airsonic-main/src/test/java/org/airsonic/player/api/jukebox/AbstractAirsonicRestApiJukeboxIntTest.java b/airsonic-main/src/test/java/org/airsonic/player/api/jukebox/AbstractAirsonicRestApiJukeboxIntTest.java index c56e9a0b..f49d66a9 100644 --- a/airsonic-main/src/test/java/org/airsonic/player/api/jukebox/AbstractAirsonicRestApiJukeboxIntTest.java +++ b/airsonic-main/src/test/java/org/airsonic/player/api/jukebox/AbstractAirsonicRestApiJukeboxIntTest.java @@ -7,6 +7,7 @@ import org.airsonic.player.service.MediaScannerService; import org.airsonic.player.service.PlayerService; import org.airsonic.player.service.SettingsService; import org.airsonic.player.util.HomeRule; +import org.airsonic.player.util.MusicFolderTestData; import org.airsonic.player.util.StringUtil; import org.junit.*; import org.junit.runner.RunWith; diff --git a/airsonic-main/src/test/java/org/airsonic/player/service/MediaScannerServiceTestCase.java b/airsonic-main/src/test/java/org/airsonic/player/service/MediaScannerServiceTestCase.java index 86ffdfae..f7c895f6 100644 --- a/airsonic-main/src/test/java/org/airsonic/player/service/MediaScannerServiceTestCase.java +++ b/airsonic-main/src/test/java/org/airsonic/player/service/MediaScannerServiceTestCase.java @@ -18,6 +18,7 @@ import org.airsonic.player.TestCaseUtils; import org.airsonic.player.domain.Album; import org.airsonic.player.domain.Artist; import org.airsonic.player.util.HomeRule; +import org.airsonic.player.util.MusicFolderTestData; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.io.Resource; import org.springframework.core.io.ResourceLoader; diff --git a/airsonic-main/src/test/java/org/airsonic/player/service/search/AbstractAirsonicHomeTest.java b/airsonic-main/src/test/java/org/airsonic/player/service/search/AbstractAirsonicHomeTest.java new file mode 100644 index 00000000..0681f799 --- /dev/null +++ b/airsonic-main/src/test/java/org/airsonic/player/service/search/AbstractAirsonicHomeTest.java @@ -0,0 +1,138 @@ +package org.airsonic.player.service.search; + +import java.util.Map; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.function.Function; + +import org.airsonic.player.TestCaseUtils; +import org.airsonic.player.dao.DaoHelper; +import org.airsonic.player.dao.MusicFolderDao; +import org.airsonic.player.service.MediaScannerService; +import org.airsonic.player.service.SettingsService; +import org.airsonic.player.util.HomeRule; +import org.airsonic.player.util.MusicFolderTestData; +import org.junit.ClassRule; +import org.junit.Rule; +import org.junit.rules.TemporaryFolder; +import org.junit.runner.Description; +import org.junit.runners.model.Statement; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.rules.SpringClassRule; +import org.springframework.test.context.junit4.rules.SpringMethodRule; + +@ContextConfiguration(locations = { + "/applicationContext-service.xml", + "/applicationContext-cache.xml", + "/applicationContext-testdb.xml"}) +@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_CLASS) +@Component +/** + * Abstract class for scanning MusicFolder. + */ +public abstract class AbstractAirsonicHomeTest implements AirsonicHomeTest { + + @ClassRule + public static final SpringClassRule classRule = new SpringClassRule() { + HomeRule homeRule = new HomeRule(); + + @Override + public Statement apply(Statement base, Description description) { + Statement spring = super.apply(base, description); + return homeRule.apply(spring, description); + } + }; + + /* + * Currently, Maven is executing test classes in series, + * so this class can hold the state. + * When executing in parallel, subclasses should override this. + */ + private static AtomicBoolean dataBasePopulated = new AtomicBoolean(); + + // Above. + private static AtomicBoolean dataBaseReady = new AtomicBoolean(); + + protected final static Function resolveBaseMediaPath = (childPath) -> { + return MusicFolderTestData.resolveBaseMediaPath().concat(childPath); + }; + + @Autowired + protected DaoHelper daoHelper; + + @Autowired + protected MediaScannerService mediaScannerService; + + @Autowired + protected MusicFolderDao musicFolderDao; + + @Autowired + protected SettingsService settingsService; + + @Rule + public final SpringMethodRule springMethodRule = new SpringMethodRule(); + + @Rule + public TemporaryFolder temporaryFolder = new TemporaryFolder(); + + @Override + public AtomicBoolean dataBasePopulated() { + return dataBasePopulated; + } + + @Override + public AtomicBoolean dataBaseReady() { + return dataBaseReady; + } + + @Override + public final void populateDatabaseOnlyOnce() { + if (!dataBasePopulated().get()) { + dataBasePopulated().set(true); + getMusicFolders().forEach(musicFolderDao::createMusicFolder); + settingsService.clearMusicFolderCache(); + try { + // Await time to avoid scan failure. + for (int i = 0; i < 10; i++) { + Thread.sleep(100); + } + } catch (InterruptedException e) { + e.printStackTrace(); + } + TestCaseUtils.execScan(mediaScannerService); + System.out.println("--- Report of records count per table ---"); + Map records = TestCaseUtils.recordsInAllTables(daoHelper); + records.keySet().stream().filter(s -> + s.equals("MEDIA_FILE") + | s.equals("ARTIST") + | s.equals("MUSIC_FOLDER") + | s.equals("ALBUM")) + .forEach(tableName -> + System.out.println("\t" + tableName + " : " + records.get(tableName).toString())); + System.out.println("--- *********************** ---"); + try { + // Await for Lucene to finish writing(asynchronous). + for (int i = 0; i < 5; i++) { + Thread.sleep(100); + } + } catch (InterruptedException e) { + e.printStackTrace(); + } + dataBaseReady().set(true); + } else { + while (!dataBaseReady().get()) { + try { + // The subsequent test method waits while reading DB data. + for (int i = 0; i < 10; i++) { + Thread.sleep(100); + } + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + } + } + +} \ No newline at end of file diff --git a/airsonic-main/src/test/java/org/airsonic/player/service/search/AirsonicHomeTest.java b/airsonic-main/src/test/java/org/airsonic/player/service/search/AirsonicHomeTest.java new file mode 100644 index 00000000..b30116ef --- /dev/null +++ b/airsonic-main/src/test/java/org/airsonic/player/service/search/AirsonicHomeTest.java @@ -0,0 +1,45 @@ +package org.airsonic.player.service.search; + +import java.util.List; +import java.util.concurrent.atomic.AtomicBoolean; + +import org.airsonic.player.domain.MusicFolder; +import org.airsonic.player.util.MusicFolderTestData; + +/** + * Test case interface for scanning MusicFolder. + */ +public interface AirsonicHomeTest { + + /** + * MusicFolder used by test class. + * + * @return MusicFolder used by test class + */ + default List getMusicFolders() { + return MusicFolderTestData.getTestMusicFolders(); + }; + + /** + * Whether the data input has been completed. + * + * @return Static AtomicBoolean indicating whether the data injection has been + * completed + */ + abstract AtomicBoolean dataBasePopulated(); + + /** + * Whether the data input has been completed. + * + * @return Static AtomicBoolean indicating whether the data injection has been + * completed + */ + abstract AtomicBoolean dataBaseReady(); + + /** + * Populate the database only once. + * It is called in the @Before granted method. + */ + void populateDatabaseOnlyOnce(); + +} diff --git a/airsonic-main/src/test/java/org/airsonic/player/service/search/AnalyzerFactoryTestCase.java b/airsonic-main/src/test/java/org/airsonic/player/service/search/AnalyzerFactoryTestCase.java index e9f23390..e9680023 100644 --- a/airsonic-main/src/test/java/org/airsonic/player/service/search/AnalyzerFactoryTestCase.java +++ b/airsonic-main/src/test/java/org/airsonic/player/service/search/AnalyzerFactoryTestCase.java @@ -1,6 +1,7 @@ package org.airsonic.player.service.search; +import static java.util.Arrays.asList; import static org.junit.Assert.assertEquals; import java.io.IOException; @@ -412,7 +413,7 @@ public class AnalyzerFactoryTestCase { * Case using test resource name */ - // title + // Semicolon, comma and hyphen. String query = "Bach: Goldberg Variations, BWV 988 - Aria"; List terms = toTermString(query); assertEquals(6, terms.size()); @@ -423,7 +424,7 @@ public class AnalyzerFactoryTestCase { assertEquals("988", terms.get(4)); assertEquals("aria", terms.get(5)); - // artist + // Underscores around words, ascii and semicolon. query = "_ID3_ARTIST_ Céline Frisch: Café Zimmermann"; terms = toTermString(query); assertEquals(5, terms.size()); @@ -433,6 +434,72 @@ public class AnalyzerFactoryTestCase { assertEquals("cafe", terms.get(3)); assertEquals("zimmermann", terms.get(4)); + // Underscores around words and slashes. + query = "_ID3_ARTIST_ Sarah Walker/Nash Ensemble"; + terms = toTermString(query); + assertEquals(5, terms.size()); + assertEquals("id3_artist", terms.get(0)); + assertEquals("sarah", terms.get(1)); + assertEquals("walker", terms.get(2)); + assertEquals("nash", terms.get(3)); + assertEquals("ensemble", terms.get(4)); + + // Space + assertEquals(asList("abc", "def"), toTermString(" ABC DEF ")); + assertEquals(asList("abc1", "def"), toTermString(" ABC1 DEF ")); + + // trim and delimiter + assertEquals(asList("abc", "def"), toTermString("+ABC+DEF+")); + assertEquals(asList("abc", "def"), toTermString("|ABC|DEF|")); + assertEquals(asList("abc", "def"), toTermString("!ABC!DEF!")); + assertEquals(asList("abc", "def"), toTermString("(ABC(DEF(")); + assertEquals(asList("abc", "def"), toTermString(")ABC)DEF)")); + assertEquals(asList("abc", "def"), toTermString("{ABC{DEF{")); + assertEquals(asList("abc", "def"), toTermString("}ABC}DEF}")); + assertEquals(asList("abc", "def"), toTermString("[ABC[DEF[")); + assertEquals(asList("abc", "def"), toTermString("]ABC]DEF]")); + assertEquals(asList("abc", "def"), toTermString("^ABC^DEF^")); + assertEquals(asList("abc", "def"), toTermString("\\ABC\\DEF\\")); + assertEquals(asList("abc", "def"), toTermString("\"ABC\"DEF\"")); + assertEquals(asList("abc", "def"), toTermString("~ABC~DEF~")); + assertEquals(asList("abc", "def"), toTermString("*ABC*DEF*")); + assertEquals(asList("abc", "def"), toTermString("?ABC?DEF?")); + assertEquals(asList("abc", "def"), toTermString(":ABC:DEF:")); + assertEquals(asList("abc", "def"), toTermString("-ABC-DEF-")); + assertEquals(asList("abc", "def"), toTermString("/ABC/DEF/")); + assertEquals(asList("abc", "def"), toTermString("_ABC_DEF_")); + assertEquals(asList("abc", "def"), toTermString(",ABC,DEF,")); + assertEquals(asList("abc.def"), toTermString(".ABC.DEF.")); + assertEquals(asList("abc&def"), toTermString("&ABC&DEF&")); + assertEquals(asList("abc@def"), toTermString("@ABC@DEF@")); + assertEquals(asList("abc'def"), toTermString("'ABC'DEF'")); + + // trim and delimiter and number + assertEquals(asList("abc1", "def"), toTermString("+ABC1+DEF+")); + assertEquals(asList("abc1", "def"), toTermString("|ABC1|DEF|")); + assertEquals(asList("abc1", "def"), toTermString("!ABC1!DEF!")); + assertEquals(asList("abc1", "def"), toTermString("(ABC1(DEF(")); + assertEquals(asList("abc1", "def"), toTermString(")ABC1)DEF)")); + assertEquals(asList("abc1", "def"), toTermString("{ABC1{DEF{")); + assertEquals(asList("abc1", "def"), toTermString("}ABC1}DEF}")); + assertEquals(asList("abc1", "def"), toTermString("[ABC1[DEF[")); + assertEquals(asList("abc1", "def"), toTermString("]ABC1]DEF]")); + assertEquals(asList("abc1", "def"), toTermString("^ABC1^DEF^")); + assertEquals(asList("abc1", "def"), toTermString("\\ABC1\\DEF\\")); + assertEquals(asList("abc1", "def"), toTermString("\"ABC1\"DEF\"")); + assertEquals(asList("abc1", "def"), toTermString("~ABC1~DEF~")); + assertEquals(asList("abc1", "def"), toTermString("*ABC1*DEF*")); + assertEquals(asList("abc1", "def"), toTermString("?ABC1?DEF?")); + assertEquals(asList("abc1", "def"), toTermString(":ABC1:DEF:")); + assertEquals(asList("abc1,def"), toTermString(",ABC1,DEF,")); + assertEquals(asList("abc1-def"), toTermString("-ABC1-DEF-")); + assertEquals(asList("abc1/def"), toTermString("/ABC1/DEF/")); + assertEquals(asList("abc1_def"), toTermString("_ABC1_DEF_")); + assertEquals(asList("abc1.def"), toTermString(".ABC1.DEF.")); + assertEquals(asList("abc1", "def"), toTermString("&ABC1&DEF&")); + assertEquals(asList("abc1", "def"), toTermString("@ABC1@DEF@")); + assertEquals(asList("abc1", "def"), toTermString("'ABC1'DEF'")); + } /** diff --git a/airsonic-main/src/test/java/org/airsonic/player/service/search/SearchServiceSpecialGenreTestCase.java b/airsonic-main/src/test/java/org/airsonic/player/service/search/SearchServiceSpecialGenreTestCase.java new file mode 100644 index 00000000..e4e09b6c --- /dev/null +++ b/airsonic-main/src/test/java/org/airsonic/player/service/search/SearchServiceSpecialGenreTestCase.java @@ -0,0 +1,216 @@ + +package org.airsonic.player.service.search; + +import static org.springframework.util.ObjectUtils.isEmpty; + +import java.io.File; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + +import org.airsonic.player.domain.MediaFile; +import org.airsonic.player.domain.MusicFolder; +import org.airsonic.player.domain.RandomSearchCriteria; +import org.airsonic.player.service.SearchService; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; + +import com.google.common.base.Function; + +/* + * Tests to prove what kind of strings/chars can be used in the genre field. + */ +public class SearchServiceSpecialGenreTestCase extends AbstractAirsonicHomeTest { + + private List musicFolders; + + @Autowired + private SearchService searchService; + + @Override + public List getMusicFolders() { + if (isEmpty(musicFolders)) { + musicFolders = new ArrayList<>(); + File musicDir = new File(resolveBaseMediaPath.apply("Search/SpecialGenre")); + musicFolders.add(new MusicFolder(1, musicDir, "accessible", true, new Date())); + } + return musicFolders; + } + + @Before + public void setup() throws Exception { + populateDatabaseOnlyOnce(); + } + + /* + * There are 19 files + * in src/test/resources/MEDIAS/Search/SpecialGenre/ARTIST1/ALBUM_A. + * In FILE01 to FILE16, Special strings for Lucene syntax are stored + * as tag values ​​of Genre. + * + * Legacy can not search all these genres. + * (Strictly speaking, the genre field is not created at index creation.) + */ + @Test + public void testQueryEscapeRequires() { + + List folders = getMusicFolders(); + + Function simpleStringCriteria = s -> + new RandomSearchCriteria(Integer.MAX_VALUE, // count + s, // genre, + null, // fromYear + null, // toYear + folders // musicFolders + ); + + List songs = searchService.getRandomSongs(simpleStringCriteria.apply("+")); + Assert.assertEquals(0, songs.size()); + + songs = searchService.getRandomSongs(simpleStringCriteria.apply("-")); + Assert.assertEquals(0, songs.size()); + + songs = searchService.getRandomSongs(simpleStringCriteria.apply("&&")); + Assert.assertEquals(0, songs.size()); + + songs = searchService.getRandomSongs(simpleStringCriteria.apply("+")); + Assert.assertEquals(0, songs.size()); + + songs = searchService.getRandomSongs(simpleStringCriteria.apply("||")); + Assert.assertEquals(0, songs.size()); + + songs = searchService.getRandomSongs(simpleStringCriteria.apply(" ("));// space & bracket + Assert.assertEquals(0, songs.size()); + + songs = searchService.getRandomSongs(simpleStringCriteria.apply(")")); + Assert.assertEquals(0, songs.size()); + + songs = searchService.getRandomSongs(simpleStringCriteria.apply("{}")); + Assert.assertEquals(0, songs.size()); + + songs = searchService.getRandomSongs(simpleStringCriteria.apply("[]")); + Assert.assertEquals(0, songs.size()); + + songs = searchService.getRandomSongs(simpleStringCriteria.apply("^")); + Assert.assertEquals(0, songs.size()); + + songs = searchService.getRandomSongs(simpleStringCriteria.apply("\"")); + Assert.assertEquals(0, songs.size()); + + songs = searchService.getRandomSongs(simpleStringCriteria.apply("~")); + Assert.assertEquals(0, songs.size()); + + songs = searchService.getRandomSongs(simpleStringCriteria.apply("*")); + Assert.assertEquals(0, songs.size()); + + songs = searchService.getRandomSongs(simpleStringCriteria.apply("?")); + Assert.assertEquals(0, songs.size()); + + songs = searchService.getRandomSongs(simpleStringCriteria.apply(":")); + Assert.assertEquals(0, songs.size()); + + songs = searchService.getRandomSongs(simpleStringCriteria.apply("\\")); + Assert.assertEquals(0, songs.size()); + + songs = searchService.getRandomSongs(simpleStringCriteria.apply("/")); + Assert.assertEquals(0, songs.size()); + + } + + /* + * Jaudiotagger applies special treatment to bracket (FILE17). + * + */ + @Test + public void testBrackets() { + + List folders = getMusicFolders(); + + RandomSearchCriteria criteria = new RandomSearchCriteria(Integer.MAX_VALUE, // count + "-(GENRE)-", // genre, + null, // fromYear + null, // toYear + folders // musicFolders + ); + + List songs = searchService.getRandomSongs(criteria); + Assert.assertEquals(0, songs.size()); + + criteria = new RandomSearchCriteria(Integer.MAX_VALUE, // count + " genre", // genre, + null, // fromYear + null, // toYear + folders // musicFolders + ); + + songs = searchService.getRandomSongs(criteria); + Assert.assertEquals(1, songs.size()); + Assert.assertEquals("Consistency with Tag Parser 1", songs.get(0).getTitle()); + Assert.assertEquals("-GENRE -", songs.get(0).getGenre()); + + } + + /* + * Jaudiotagger applies special treatment to numeric. (FILE18) + */ + @Test + public void testNumericMapping() { + + List folders = getMusicFolders(); + + RandomSearchCriteria criteria = new RandomSearchCriteria(Integer.MAX_VALUE, // count + "Rock", // genre, + null, // fromYear + null, // toYear + folders // musicFolders + ); + + List songs = searchService.getRandomSongs(criteria); + Assert.assertEquals(1, songs.size()); + Assert.assertEquals("Numeric mapping specification of genre 1", songs.get(0).getTitle()); + + // The value registered in the file is 17 + Assert.assertEquals("Rock", songs.get(0).getGenre()); + + } + + /* + * Other special strings. (FILE19) + * + * {'“『【【】】[︴○◎@ $〒→+]FULL-WIDTHCæsar's + * + * Legacy stores with Analyze, + * so searchable characters are different. + * + */ + @Test + public void testOthers() { + + List folders = getMusicFolders(); + + RandomSearchCriteria criteria = new RandomSearchCriteria(Integer.MAX_VALUE, // count + "{'“『【【】】[︴○◎@ $〒→+]FULL-WIDTHCæsar's", // genre, + null, // fromYear + null, // toYear + folders // musicFolders + ); + + List songs = searchService.getRandomSongs(criteria); + Assert.assertEquals(0, songs.size()); + + criteria = new RandomSearchCriteria(Integer.MAX_VALUE, // count + "widthcaesar", // genre, + null, // fromYear + null, // toYear + folders // musicFolders + ); + + songs = searchService.getRandomSongs(criteria); + Assert.assertEquals(1, songs.size()); + Assert.assertEquals("Other special strings 1", songs.get(0).getTitle()); + Assert.assertEquals("{'“『【【】】[︴○◎@ $〒→+]FULL-WIDTHCæsar's", songs.get(0).getGenre()); + + } +} diff --git a/airsonic-main/src/test/java/org/airsonic/player/service/search/SearchServiceSpecialPathTestCase.java b/airsonic-main/src/test/java/org/airsonic/player/service/search/SearchServiceSpecialPathTestCase.java new file mode 100644 index 00000000..132323cd --- /dev/null +++ b/airsonic-main/src/test/java/org/airsonic/player/service/search/SearchServiceSpecialPathTestCase.java @@ -0,0 +1,90 @@ + +package org.airsonic.player.service.search; + +import java.io.File; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; +import java.util.stream.Collectors; + +import org.airsonic.player.domain.MediaFile; +import org.airsonic.player.domain.MusicFolder; +import org.airsonic.player.service.SearchService; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; +import static org.springframework.util.ObjectUtils.isEmpty; + +/* + * Test cases related to #1139. + * Confirming whether shuffle search can be performed correctly in MusicFolder containing special strings. + * + * (Since the query of getRandomAlbums consists of folder paths only, + * this verification is easy to perform.) + * + * This test case is a FalsePattern for search, + * but there may be problems with the data flow prior to creating the search index. + */ +public class SearchServiceSpecialPathTestCase extends AbstractAirsonicHomeTest { + + private List musicFolders; + + @Autowired + private SearchService searchService; + + @Override + public List getMusicFolders() { + if (isEmpty(musicFolders)) { + musicFolders = new ArrayList<>(); + + File musicDir = new File(resolveBaseMediaPath.apply("Search/SpecialPath/accessible")); + musicFolders.add(new MusicFolder(1, musicDir, "accessible", true, new Date())); + + File music2Dir = new File(resolveBaseMediaPath.apply("Search/SpecialPath/accessible's")); + musicFolders.add(new MusicFolder(2, music2Dir, "accessible's", true, new Date())); + + File music3Dir = new File(resolveBaseMediaPath.apply("Search/SpecialPath/accessible+s")); + musicFolders.add(new MusicFolder(3, music3Dir, "accessible+s", true, new Date())); + } + return musicFolders; + } + + @Before + public void setup() throws Exception { + populateDatabaseOnlyOnce(); + } + + @Test + public void testSpecialCharactersInDirName() { + + List folders = getMusicFolders(); + + // ALL Songs + List randomAlbums = searchService.getRandomAlbums(Integer.MAX_VALUE, folders); + Assert.assertEquals("ALL Albums ", 3, randomAlbums.size()); + + // dir - accessible + List folder01 = folders.stream() + .filter(m -> "accessible".equals(m.getName())) + .collect(Collectors.toList()); + randomAlbums = searchService.getRandomAlbums(Integer.MAX_VALUE, folder01); + Assert.assertEquals("Albums in \"accessible\" ", 3, randomAlbums.size()); + + // dir - accessible's + List folder02 = folders.stream() + .filter(m -> "accessible's".equals(m.getName())) + .collect(Collectors.toList()); + randomAlbums = searchService.getRandomAlbums(Integer.MAX_VALUE, folder02); + Assert.assertEquals("Albums in \"accessible's\" ", 0, randomAlbums.size()); + + // dir - accessible+s + List folder03 = folders.stream() + .filter(m -> "accessible+s".equals(m.getName())) + .collect(Collectors.toList()); + randomAlbums = searchService.getRandomAlbums(Integer.MAX_VALUE, folder03); + Assert.assertEquals("Albums in \"accessible+s\" ", 1, folder03.size()); + + } + +} diff --git a/airsonic-main/src/test/java/org/airsonic/player/service/search/SearchServiceStartWithStopwardsTestCase.java b/airsonic-main/src/test/java/org/airsonic/player/service/search/SearchServiceStartWithStopwardsTestCase.java new file mode 100644 index 00000000..58854333 --- /dev/null +++ b/airsonic-main/src/test/java/org/airsonic/player/service/search/SearchServiceStartWithStopwardsTestCase.java @@ -0,0 +1,67 @@ + +package org.airsonic.player.service.search; + +import java.io.File; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + +import org.airsonic.player.domain.MusicFolder; +import org.airsonic.player.domain.SearchCriteria; +import org.airsonic.player.domain.SearchResult; +import org.airsonic.player.service.SearchService; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; +import static org.springframework.util.ObjectUtils.isEmpty; + +/* + * Test cases related to #1142. + * The filter is not properly applied when analyzing the query, + * + * In the process of hardening the Analyzer implementation, + * this problem is solved side by side. + */ +public class SearchServiceStartWithStopwardsTestCase extends AbstractAirsonicHomeTest { + + private List musicFolders; + + @Autowired + private SearchService searchService; + + @Override + public List getMusicFolders() { + if (isEmpty(musicFolders)) { + musicFolders = new ArrayList<>(); + File musicDir = new File(resolveBaseMediaPath.apply("Search/StartWithStopwards")); + musicFolders.add(new MusicFolder(1, musicDir, "accessible", true, new Date())); + } + return musicFolders; + } + + @Before + public void setup() throws Exception { + populateDatabaseOnlyOnce(); + } + + @Test + public void testStartWithStopwards() { + + List folders = getMusicFolders(); + + final SearchCriteria criteria = new SearchCriteria(); + criteria.setCount(Integer.MAX_VALUE); + criteria.setOffset(0); + + criteria.setQuery("will"); + SearchResult result = searchService.search(criteria, folders, IndexType.ARTIST_ID3); + Assert.assertEquals("Williams hit by \"will\" ", 1, result.getTotalHits()); + + criteria.setQuery("the"); + result = searchService.search(criteria, folders, IndexType.SONG); + Assert.assertEquals("Theater hit by \"the\" ", 1, result.getTotalHits()); + + } + +} diff --git a/airsonic-main/src/test/java/org/airsonic/player/service/search/SearchServiceTestCase.java b/airsonic-main/src/test/java/org/airsonic/player/service/search/SearchServiceTestCase.java index e2871753..9d2691b9 100644 --- a/airsonic-main/src/test/java/org/airsonic/player/service/search/SearchServiceTestCase.java +++ b/airsonic-main/src/test/java/org/airsonic/player/service/search/SearchServiceTestCase.java @@ -5,19 +5,13 @@ import com.codahale.metrics.ConsoleReporter; import com.codahale.metrics.MetricRegistry; import com.codahale.metrics.Timer; -import java.io.File; import java.util.Arrays; import java.util.List; -import java.util.Map; import java.util.Random; import java.util.concurrent.TimeUnit; -import org.airsonic.player.TestCaseUtils; import org.airsonic.player.dao.AlbumDao; -import org.airsonic.player.dao.DaoHelper; -import org.airsonic.player.dao.MediaFileDao; import org.airsonic.player.dao.MusicFolderDao; -import org.airsonic.player.dao.MusicFolderTestData; import org.airsonic.player.domain.Album; import org.airsonic.player.domain.Artist; import org.airsonic.player.domain.MediaFile; @@ -27,150 +21,50 @@ import org.airsonic.player.domain.ParamSearchResult; import org.airsonic.player.domain.RandomSearchCriteria; import org.airsonic.player.domain.SearchCriteria; import org.airsonic.player.domain.SearchResult; -import org.airsonic.player.service.MediaScannerService; import org.airsonic.player.service.SearchService; -import org.airsonic.player.service.SettingsService; import org.airsonic.player.service.search.IndexType; -import org.airsonic.player.util.HomeRule; import org.junit.Assert; import org.junit.Before; -import org.junit.ClassRule; -import org.junit.Rule; import org.junit.Test; -import org.junit.rules.TemporaryFolder; -import org.junit.runner.Description; -import org.junit.runners.model.Statement; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.core.io.ResourceLoader; -import org.springframework.test.annotation.DirtiesContext; -import org.springframework.test.context.ContextConfiguration; -import org.springframework.test.context.junit4.rules.SpringClassRule; -import org.springframework.test.context.junit4.rules.SpringMethodRule; import org.subsonic.restapi.ArtistID3; -@ContextConfiguration( - locations = { - "/applicationContext-service.xml", - "/applicationContext-cache.xml", - "/applicationContext-testdb.xml", - "/applicationContext-mockSonos.xml" }) -@DirtiesContext( - classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD) -public class SearchServiceTestCase { - - @ClassRule - public static final SpringClassRule classRule = new SpringClassRule() { - HomeRule homeRule = new HomeRule(); - - @Override - public Statement apply(Statement base, Description description) { - Statement spring = super.apply(base, description); - return homeRule.apply(spring, description); - } - }; - - @Rule - public final SpringMethodRule springMethodRule = new SpringMethodRule(); - - private final MetricRegistry metrics = new MetricRegistry(); +public class SearchServiceTestCase extends AbstractAirsonicHomeTest { @Autowired - private MediaScannerService mediaScannerService; + private AlbumDao albumDao; - @Autowired - private MediaFileDao mediaFileDao; + private final MetricRegistry metrics = new MetricRegistry(); @Autowired private MusicFolderDao musicFolderDao; - @Autowired - private DaoHelper daoHelper; - - @Autowired - private AlbumDao albumDao; - @Autowired private SearchService searchService; - @Autowired - private SettingsService settingsService; - - @Rule - public TemporaryFolder temporaryFolder = new TemporaryFolder(); - - @Autowired - ResourceLoader resourceLoader; - @Before public void setup() throws Exception { - populateDatabase(); + populateDatabaseOnlyOnce(); } - private static boolean dataBasePopulated; - - private int count = 1; - - /* - * Cases susceptible to SerchService refactoring and version upgrades. - * It is not exhaustive. - */ - private synchronized void populateDatabase() { + @Test + public void testSearchTypical() { /* - * It seems that there is a case that does not work well - * if you test immediately after initialization in 1 method. - * It may be improved later. + * A simple test that is expected to easily detect API syntax differences when updating lucene. + * Complete route coverage and data coverage in this case alone are not conscious. */ - try { - Thread.sleep(300 * count++); - } catch (InterruptedException e) { - e.printStackTrace(); - } - - if (!dataBasePopulated) { - - MusicFolderTestData.getTestMusicFolders().forEach(musicFolderDao::createMusicFolder); - settingsService.clearMusicFolderCache(); - TestCaseUtils.execScan(mediaScannerService); - System.out.println("--- Report of records count per table ---"); - Map records = TestCaseUtils.recordsInAllTables(daoHelper); - records.keySet().stream().filter(s -> s.equals("MEDIA_FILE") // 20 - | s.equals("ARTIST") // 5 - | s.equals("MUSIC_FOLDER")// 3 - | s.equals("ALBUM"))// 5 - .forEach(tableName -> System.out - .println("\t" + tableName + " : " + records.get(tableName).toString())); - // Music Folder Music must have 3 children - List listeMusicChildren = mediaFileDao.getChildrenOf( - new File(MusicFolderTestData.resolveMusicFolderPath()).getPath()); - Assert.assertEquals(3, listeMusicChildren.size()); - // Music Folder Music2 must have 1 children - List listeMusic2Children = mediaFileDao.getChildrenOf( - new File(MusicFolderTestData.resolveMusic2FolderPath()).getPath()); - Assert.assertEquals(1, listeMusic2Children.size()); - System.out.println("--- *********************** ---"); - dataBasePopulated = true; - } - } - - @Test - public void testSearchTypical() { - /* - * A simple test that is expected to easily detect API syntax differences when updating lucene. - * Complete route coverage and data coverage in this case alone are not conscious. - */ + List allMusicFolders = musicFolderDao.getAllMusicFolders(); + Assert.assertEquals(3, allMusicFolders.size()); + + // *** testSearch() *** - List allMusicFolders = musicFolderDao.getAllMusicFolders(); - Assert.assertEquals(3, allMusicFolders.size()); - - // *** testSearch() *** - - String query = "Sarah Walker"; - final SearchCriteria searchCriteria = new SearchCriteria(); - searchCriteria.setQuery(query); - searchCriteria.setCount(Integer.MAX_VALUE); - searchCriteria.setOffset(0); + String query = "Sarah Walker"; + final SearchCriteria searchCriteria = new SearchCriteria(); + searchCriteria.setQuery(query); + searchCriteria.setCount(Integer.MAX_VALUE); + searchCriteria.setOffset(0); /* * _ID3_ALBUMARTIST_ Sarah Walker/Nash Ensemble @@ -319,16 +213,30 @@ public class SearchServiceTestCase { Assert.assertEquals("(24) Specify music as 'genre', and randomly acquire songs.", 0, allRandomSongs.size()); + /* + * Genre including blank. + * Regardless of the Lucene version, It should be 2. + */ + randomSearchCriteria = new RandomSearchCriteria(Integer.MAX_VALUE, // count + "Baroque Instrumental", // genre, + null, // fromYear + null, // toYear + allMusicFolders // musicFolders + ); + allRandomSongs = searchService.getRandomSongs(randomSearchCriteria); + Assert.assertEquals("(25) Search by specifying genres including spaces and hyphens.", 2, + allRandomSongs.size()); + // *** testGetRandomAlbums() *** /* * Acquisition of maximum number(5). */ List allAlbums = albumDao.getAlphabeticalAlbums(0, 0, true, true, allMusicFolders); - Assert.assertEquals("(25) Get all albums with Dao.", 5, allAlbums.size()); + Assert.assertEquals("(26) Get all albums with Dao.", 5, allAlbums.size()); List allRandomAlbums = searchService.getRandomAlbums(Integer.MAX_VALUE, allMusicFolders); - Assert.assertEquals("(26) Specify Integer.MAX_VALUE as the upper limit," + Assert.assertEquals("(27) Specify Integer.MAX_VALUE as the upper limit," + "and randomly acquire albums(file struct).", 5, allRandomAlbums.size()); /* @@ -337,7 +245,7 @@ public class SearchServiceTestCase { List allRandomAlbumsId3 = searchService.getRandomAlbumsId3(Integer.MAX_VALUE, allMusicFolders); Assert.assertEquals( - "(27) Specify Integer.MAX_VALUE as the upper limit, and randomly acquire albums(ID3).", + "(28) Specify Integer.MAX_VALUE as the upper limit, and randomly acquire albums(ID3).", 5, allRandomAlbumsId3.size()); /* @@ -346,13 +254,13 @@ public class SearchServiceTestCase { query = "ID 3 ARTIST"; searchCriteria.setQuery(query); result = searchService.search(searchCriteria, allMusicFolders, IndexType.ARTIST_ID3); - Assert.assertEquals("(28) Specify '" + query + "', total Hits is", 4, + Assert.assertEquals("(29) Specify '" + query + "', total Hits is", 4, result.getTotalHits()); - Assert.assertEquals("(29) Specify '" + query + "', and get an artists. Artist SIZE is ", 4, + Assert.assertEquals("(30) Specify '" + query + "', and get an artists. Artist SIZE is ", 4, result.getArtists().size()); - Assert.assertEquals("(30) Specify '" + query + "', and get a artists. Album SIZE is ", 0, + Assert.assertEquals("(31) Specify '" + query + "', and get a artists. Album SIZE is ", 0, result.getAlbums().size()); - Assert.assertEquals("(31) Specify '" + query + "', and get a artists. MediaFile SIZE is ", + Assert.assertEquals("(32) Specify '" + query + "', and get a artists. MediaFile SIZE is ", 0, result.getMediaFiles().size()); /* @@ -362,7 +270,7 @@ public class SearchServiceTestCase { */ long count = result.getArtists().stream() .filter(a -> a.getName().startsWith("_ID3_ARTIST_")).count(); - Assert.assertEquals("(32) Artist whose name contains \\\"_ID3_ARTIST_\\\" is 3 records.", + Assert.assertEquals("(33) Artist whose name contains \\\"_ID3_ARTIST_\\\" is 3 records.", 3L, count); /* @@ -373,7 +281,7 @@ public class SearchServiceTestCase { */ count = result.getArtists().stream() .filter(a -> a.getName().startsWith("_ID3_ALBUMARTIST_")).count(); - Assert.assertEquals("(33) Artist whose name is \"_ID3_ARTIST_\" is 1 records.", 1L, count); + Assert.assertEquals("(34) Artist whose name is \"_ID3_ARTIST_\" is 1 records.", 1L, count); /* * Below is a simple loop test. diff --git a/airsonic-main/src/test/java/org/airsonic/player/dao/MusicFolderTestData.java b/airsonic-main/src/test/java/org/airsonic/player/util/MusicFolderTestData.java similarity index 97% rename from airsonic-main/src/test/java/org/airsonic/player/dao/MusicFolderTestData.java rename to airsonic-main/src/test/java/org/airsonic/player/util/MusicFolderTestData.java index b2c03870..44b719a9 100644 --- a/airsonic-main/src/test/java/org/airsonic/player/dao/MusicFolderTestData.java +++ b/airsonic-main/src/test/java/org/airsonic/player/util/MusicFolderTestData.java @@ -1,4 +1,4 @@ -package org.airsonic.player.dao; +package org.airsonic.player.util; import org.airsonic.player.domain.MusicFolder; @@ -38,4 +38,5 @@ public class MusicFolderTestData { liste.add(musicFolder2); return liste; } + } diff --git a/airsonic-main/src/test/resources/MEDIAS/Search/SpecialGenre/ARTIST1/ALBUM_A/FILE01.mp3 b/airsonic-main/src/test/resources/MEDIAS/Search/SpecialGenre/ARTIST1/ALBUM_A/FILE01.mp3 new file mode 100644 index 00000000..9469db28 Binary files /dev/null and b/airsonic-main/src/test/resources/MEDIAS/Search/SpecialGenre/ARTIST1/ALBUM_A/FILE01.mp3 differ diff --git a/airsonic-main/src/test/resources/MEDIAS/Search/SpecialGenre/ARTIST1/ALBUM_A/FILE02.mp3 b/airsonic-main/src/test/resources/MEDIAS/Search/SpecialGenre/ARTIST1/ALBUM_A/FILE02.mp3 new file mode 100644 index 00000000..d1a10e1d Binary files /dev/null and b/airsonic-main/src/test/resources/MEDIAS/Search/SpecialGenre/ARTIST1/ALBUM_A/FILE02.mp3 differ diff --git a/airsonic-main/src/test/resources/MEDIAS/Search/SpecialGenre/ARTIST1/ALBUM_A/FILE03.mp3 b/airsonic-main/src/test/resources/MEDIAS/Search/SpecialGenre/ARTIST1/ALBUM_A/FILE03.mp3 new file mode 100644 index 00000000..9c484854 Binary files /dev/null and b/airsonic-main/src/test/resources/MEDIAS/Search/SpecialGenre/ARTIST1/ALBUM_A/FILE03.mp3 differ diff --git a/airsonic-main/src/test/resources/MEDIAS/Search/SpecialGenre/ARTIST1/ALBUM_A/FILE04.mp3 b/airsonic-main/src/test/resources/MEDIAS/Search/SpecialGenre/ARTIST1/ALBUM_A/FILE04.mp3 new file mode 100644 index 00000000..1d1c4a6b Binary files /dev/null and b/airsonic-main/src/test/resources/MEDIAS/Search/SpecialGenre/ARTIST1/ALBUM_A/FILE04.mp3 differ diff --git a/airsonic-main/src/test/resources/MEDIAS/Search/SpecialGenre/ARTIST1/ALBUM_A/FILE05.mp3 b/airsonic-main/src/test/resources/MEDIAS/Search/SpecialGenre/ARTIST1/ALBUM_A/FILE05.mp3 new file mode 100644 index 00000000..215fdfed Binary files /dev/null and b/airsonic-main/src/test/resources/MEDIAS/Search/SpecialGenre/ARTIST1/ALBUM_A/FILE05.mp3 differ diff --git a/airsonic-main/src/test/resources/MEDIAS/Search/SpecialGenre/ARTIST1/ALBUM_A/FILE06.mp3 b/airsonic-main/src/test/resources/MEDIAS/Search/SpecialGenre/ARTIST1/ALBUM_A/FILE06.mp3 new file mode 100644 index 00000000..3bc7c2a4 Binary files /dev/null and b/airsonic-main/src/test/resources/MEDIAS/Search/SpecialGenre/ARTIST1/ALBUM_A/FILE06.mp3 differ diff --git a/airsonic-main/src/test/resources/MEDIAS/Search/SpecialGenre/ARTIST1/ALBUM_A/FILE07.mp3 b/airsonic-main/src/test/resources/MEDIAS/Search/SpecialGenre/ARTIST1/ALBUM_A/FILE07.mp3 new file mode 100644 index 00000000..14061b09 Binary files /dev/null and b/airsonic-main/src/test/resources/MEDIAS/Search/SpecialGenre/ARTIST1/ALBUM_A/FILE07.mp3 differ diff --git a/airsonic-main/src/test/resources/MEDIAS/Search/SpecialGenre/ARTIST1/ALBUM_A/FILE08.mp3 b/airsonic-main/src/test/resources/MEDIAS/Search/SpecialGenre/ARTIST1/ALBUM_A/FILE08.mp3 new file mode 100644 index 00000000..59491eb0 Binary files /dev/null and b/airsonic-main/src/test/resources/MEDIAS/Search/SpecialGenre/ARTIST1/ALBUM_A/FILE08.mp3 differ diff --git a/airsonic-main/src/test/resources/MEDIAS/Search/SpecialGenre/ARTIST1/ALBUM_A/FILE09.mp3 b/airsonic-main/src/test/resources/MEDIAS/Search/SpecialGenre/ARTIST1/ALBUM_A/FILE09.mp3 new file mode 100644 index 00000000..be5ce889 Binary files /dev/null and b/airsonic-main/src/test/resources/MEDIAS/Search/SpecialGenre/ARTIST1/ALBUM_A/FILE09.mp3 differ diff --git a/airsonic-main/src/test/resources/MEDIAS/Search/SpecialGenre/ARTIST1/ALBUM_A/FILE10.mp3 b/airsonic-main/src/test/resources/MEDIAS/Search/SpecialGenre/ARTIST1/ALBUM_A/FILE10.mp3 new file mode 100644 index 00000000..0a922377 Binary files /dev/null and b/airsonic-main/src/test/resources/MEDIAS/Search/SpecialGenre/ARTIST1/ALBUM_A/FILE10.mp3 differ diff --git a/airsonic-main/src/test/resources/MEDIAS/Search/SpecialGenre/ARTIST1/ALBUM_A/FILE11.mp3 b/airsonic-main/src/test/resources/MEDIAS/Search/SpecialGenre/ARTIST1/ALBUM_A/FILE11.mp3 new file mode 100644 index 00000000..c3cbdd30 Binary files /dev/null and b/airsonic-main/src/test/resources/MEDIAS/Search/SpecialGenre/ARTIST1/ALBUM_A/FILE11.mp3 differ diff --git a/airsonic-main/src/test/resources/MEDIAS/Search/SpecialGenre/ARTIST1/ALBUM_A/FILE12.mp3 b/airsonic-main/src/test/resources/MEDIAS/Search/SpecialGenre/ARTIST1/ALBUM_A/FILE12.mp3 new file mode 100644 index 00000000..61f23c06 Binary files /dev/null and b/airsonic-main/src/test/resources/MEDIAS/Search/SpecialGenre/ARTIST1/ALBUM_A/FILE12.mp3 differ diff --git a/airsonic-main/src/test/resources/MEDIAS/Search/SpecialGenre/ARTIST1/ALBUM_A/FILE13.mp3 b/airsonic-main/src/test/resources/MEDIAS/Search/SpecialGenre/ARTIST1/ALBUM_A/FILE13.mp3 new file mode 100644 index 00000000..8ed545ba Binary files /dev/null and b/airsonic-main/src/test/resources/MEDIAS/Search/SpecialGenre/ARTIST1/ALBUM_A/FILE13.mp3 differ diff --git a/airsonic-main/src/test/resources/MEDIAS/Search/SpecialGenre/ARTIST1/ALBUM_A/FILE14.mp3 b/airsonic-main/src/test/resources/MEDIAS/Search/SpecialGenre/ARTIST1/ALBUM_A/FILE14.mp3 new file mode 100644 index 00000000..21842e20 Binary files /dev/null and b/airsonic-main/src/test/resources/MEDIAS/Search/SpecialGenre/ARTIST1/ALBUM_A/FILE14.mp3 differ diff --git a/airsonic-main/src/test/resources/MEDIAS/Search/SpecialGenre/ARTIST1/ALBUM_A/FILE15.mp3 b/airsonic-main/src/test/resources/MEDIAS/Search/SpecialGenre/ARTIST1/ALBUM_A/FILE15.mp3 new file mode 100644 index 00000000..e4bce4e1 Binary files /dev/null and b/airsonic-main/src/test/resources/MEDIAS/Search/SpecialGenre/ARTIST1/ALBUM_A/FILE15.mp3 differ diff --git a/airsonic-main/src/test/resources/MEDIAS/Search/SpecialGenre/ARTIST1/ALBUM_A/FILE16.mp3 b/airsonic-main/src/test/resources/MEDIAS/Search/SpecialGenre/ARTIST1/ALBUM_A/FILE16.mp3 new file mode 100644 index 00000000..ecb72046 Binary files /dev/null and b/airsonic-main/src/test/resources/MEDIAS/Search/SpecialGenre/ARTIST1/ALBUM_A/FILE16.mp3 differ diff --git a/airsonic-main/src/test/resources/MEDIAS/Search/SpecialGenre/ARTIST1/ALBUM_A/FILE17.mp3 b/airsonic-main/src/test/resources/MEDIAS/Search/SpecialGenre/ARTIST1/ALBUM_A/FILE17.mp3 new file mode 100644 index 00000000..19eba156 Binary files /dev/null and b/airsonic-main/src/test/resources/MEDIAS/Search/SpecialGenre/ARTIST1/ALBUM_A/FILE17.mp3 differ diff --git a/airsonic-main/src/test/resources/MEDIAS/Search/SpecialGenre/ARTIST1/ALBUM_A/FILE18.mp3 b/airsonic-main/src/test/resources/MEDIAS/Search/SpecialGenre/ARTIST1/ALBUM_A/FILE18.mp3 new file mode 100644 index 00000000..2af3e041 Binary files /dev/null and b/airsonic-main/src/test/resources/MEDIAS/Search/SpecialGenre/ARTIST1/ALBUM_A/FILE18.mp3 differ diff --git a/airsonic-main/src/test/resources/MEDIAS/Search/SpecialGenre/ARTIST1/ALBUM_A/FILE19.mp3 b/airsonic-main/src/test/resources/MEDIAS/Search/SpecialGenre/ARTIST1/ALBUM_A/FILE19.mp3 new file mode 100644 index 00000000..83fe706a Binary files /dev/null and b/airsonic-main/src/test/resources/MEDIAS/Search/SpecialGenre/ARTIST1/ALBUM_A/FILE19.mp3 differ diff --git a/airsonic-main/src/test/resources/MEDIAS/Search/SpecialPath/accessible's/ARTIST3/ALBUM_C/FILE03.mp3 b/airsonic-main/src/test/resources/MEDIAS/Search/SpecialPath/accessible's/ARTIST3/ALBUM_C/FILE03.mp3 new file mode 100644 index 00000000..cdd78422 Binary files /dev/null and b/airsonic-main/src/test/resources/MEDIAS/Search/SpecialPath/accessible's/ARTIST3/ALBUM_C/FILE03.mp3 differ diff --git a/airsonic-main/src/test/resources/MEDIAS/Search/SpecialPath/accessible+s/ARTIST2/ALBUM_B/FILE02.mp3 b/airsonic-main/src/test/resources/MEDIAS/Search/SpecialPath/accessible+s/ARTIST2/ALBUM_B/FILE02.mp3 new file mode 100644 index 00000000..cdd78422 Binary files /dev/null and b/airsonic-main/src/test/resources/MEDIAS/Search/SpecialPath/accessible+s/ARTIST2/ALBUM_B/FILE02.mp3 differ diff --git a/airsonic-main/src/test/resources/MEDIAS/Search/SpecialPath/accessible/ARTIST1/ALBUM_A/FILE01.mp3 b/airsonic-main/src/test/resources/MEDIAS/Search/SpecialPath/accessible/ARTIST1/ALBUM_A/FILE01.mp3 new file mode 100644 index 00000000..cdd78422 Binary files /dev/null and b/airsonic-main/src/test/resources/MEDIAS/Search/SpecialPath/accessible/ARTIST1/ALBUM_A/FILE01.mp3 differ diff --git a/airsonic-main/src/test/resources/MEDIAS/Search/StartWithStopwards/ARTIST1/ALBUM_A/FILE01.mp3 b/airsonic-main/src/test/resources/MEDIAS/Search/StartWithStopwards/ARTIST1/ALBUM_A/FILE01.mp3 new file mode 100644 index 00000000..693e44ef Binary files /dev/null and b/airsonic-main/src/test/resources/MEDIAS/Search/StartWithStopwards/ARTIST1/ALBUM_A/FILE01.mp3 differ