From 6b4874f33ce92e14f102b6bf216ca5ea207fe780 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9mi=20Cocula?= Date: Sun, 30 Sep 2018 11:14:28 +0200 Subject: [PATCH] archetype code for rest api integration tests --- airsonic-main/pom.xml | 10 + .../controller/SubsonicRESTController.java | 4 +- .../org/airsonic/player/dao/PlayerDao.java | 6 +- .../player/dao/PlayerDaoPlayQueueFactory.java | 12 + .../player/service/JukeboxJavaService.java | 73 ++--- .../service/JukeboxLegacySubsonicService.java | 5 +- .../player/service/TranscodingService.java | 2 + .../service/jukebox/AudioPlayerFactory.java | 14 + .../service/jukebox/JavaPlayerFactory.java | 17 ++ .../org/airsonic/player/TestCaseUtils.java | 9 + .../player/api/AirsonicRestApiIntTest.java | 59 ++++ ...AbstractAirsonicRestApiJukeboxIntTest.java | 279 ++++++++++++++++++ .../AirsonicRestApiJukeboxIntTest.java | 38 +++ .../AirsonicRestApiJukeboxLegacyIntTest.java | 62 ++++ .../service/TranscodingServiceIntTest.java | 41 +++ .../src/test/resources/application.properties | 13 + pom.xml | 2 + 17 files changed, 599 insertions(+), 47 deletions(-) create mode 100644 airsonic-main/src/main/java/org/airsonic/player/dao/PlayerDaoPlayQueueFactory.java create mode 100644 airsonic-main/src/main/java/org/airsonic/player/service/jukebox/AudioPlayerFactory.java create mode 100644 airsonic-main/src/main/java/org/airsonic/player/service/jukebox/JavaPlayerFactory.java create mode 100644 airsonic-main/src/test/java/org/airsonic/player/api/AirsonicRestApiIntTest.java create mode 100644 airsonic-main/src/test/java/org/airsonic/player/api/jukebox/AbstractAirsonicRestApiJukeboxIntTest.java create mode 100644 airsonic-main/src/test/java/org/airsonic/player/api/jukebox/AirsonicRestApiJukeboxIntTest.java create mode 100644 airsonic-main/src/test/java/org/airsonic/player/api/jukebox/AirsonicRestApiJukeboxLegacyIntTest.java create mode 100644 airsonic-main/src/test/java/org/airsonic/player/service/TranscodingServiceIntTest.java create mode 100644 airsonic-main/src/test/resources/application.properties diff --git a/airsonic-main/pom.xml b/airsonic-main/pom.xml index 7204c4fd..7a33e986 100755 --- a/airsonic-main/pom.xml +++ b/airsonic-main/pom.xml @@ -86,6 +86,16 @@ org.springframework.boot spring-boot-starter-security + + org.springframework.boot + spring-boot-starter-test + test + + + org.springframework.security + spring-security-test + test + org.springframework.security spring-security-ldap diff --git a/airsonic-main/src/main/java/org/airsonic/player/controller/SubsonicRESTController.java b/airsonic-main/src/main/java/org/airsonic/player/controller/SubsonicRESTController.java index 73ab02aa..ee193430 100644 --- a/airsonic-main/src/main/java/org/airsonic/player/controller/SubsonicRESTController.java +++ b/airsonic-main/src/main/java/org/airsonic/player/controller/SubsonicRESTController.java @@ -1274,7 +1274,7 @@ public class SubsonicRESTController { child.setSuffix(suffix); child.setContentType(StringUtil.getMimeType(suffix)); child.setIsVideo(mediaFile.isVideo()); - child.setPath(getRelativePath(mediaFile)); + child.setPath(getRelativePath(mediaFile, settingsService)); org.airsonic.player.domain.Bookmark bookmark = bookmarkCache.get(new BookmarkKey(username, mediaFile.getId())); if (bookmark != null) { @@ -1329,7 +1329,7 @@ public class SubsonicRESTController { return null; } - private String getRelativePath(MediaFile musicFile) { + public static String getRelativePath(MediaFile musicFile, SettingsService settingsService) { String filePath = musicFile.getPath(); diff --git a/airsonic-main/src/main/java/org/airsonic/player/dao/PlayerDao.java b/airsonic-main/src/main/java/org/airsonic/player/dao/PlayerDao.java index 17ad5943..2b59e321 100644 --- a/airsonic-main/src/main/java/org/airsonic/player/dao/PlayerDao.java +++ b/airsonic-main/src/main/java/org/airsonic/player/dao/PlayerDao.java @@ -22,6 +22,7 @@ package org.airsonic.player.dao; import org.airsonic.player.domain.*; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.jdbc.core.RowMapper; import org.springframework.stereotype.Repository; import org.springframework.transaction.annotation.Transactional; @@ -43,6 +44,9 @@ public class PlayerDao extends AbstractDao { "last_seen, cover_art_scheme, transcode_scheme, dynamic_ip, technology, client_id, mixer"; private static final String QUERY_COLUMNS = "id, " + INSERT_COLUMNS; + @Autowired + private PlayerDaoPlayQueueFactory playerDaoPlayQueueFactory; + private PlayerRowMapper rowMapper = new PlayerRowMapper(); private Map playlists = Collections.synchronizedMap(new HashMap()); @@ -166,7 +170,7 @@ public class PlayerDao extends AbstractDao { private void addPlaylist(Player player) { PlayQueue playQueue = playlists.get(player.getId()); if (playQueue == null) { - playQueue = new PlayQueue(); + playQueue = playerDaoPlayQueueFactory.createPlayQueue(); playlists.put(player.getId(), playQueue); } player.setPlayQueue(playQueue); diff --git a/airsonic-main/src/main/java/org/airsonic/player/dao/PlayerDaoPlayQueueFactory.java b/airsonic-main/src/main/java/org/airsonic/player/dao/PlayerDaoPlayQueueFactory.java new file mode 100644 index 00000000..ceb65087 --- /dev/null +++ b/airsonic-main/src/main/java/org/airsonic/player/dao/PlayerDaoPlayQueueFactory.java @@ -0,0 +1,12 @@ +package org.airsonic.player.dao; + +import org.airsonic.player.domain.PlayQueue; +import org.springframework.stereotype.Component; + +@Component +public class PlayerDaoPlayQueueFactory { + + public PlayQueue createPlayQueue() { + return new PlayQueue(); + } +} diff --git a/airsonic-main/src/main/java/org/airsonic/player/service/JukeboxJavaService.java b/airsonic-main/src/main/java/org/airsonic/player/service/JukeboxJavaService.java index 59668a0a..908dc43c 100644 --- a/airsonic-main/src/main/java/org/airsonic/player/service/JukeboxJavaService.java +++ b/airsonic-main/src/main/java/org/airsonic/player/service/JukeboxJavaService.java @@ -1,9 +1,9 @@ package org.airsonic.player.service; -import com.github.biconou.AudioPlayer.JavaPlayer; -import com.github.biconou.AudioPlayer.api.*; +import com.github.biconou.AudioPlayer.api.PlayList; +import com.github.biconou.AudioPlayer.api.PlayerListener; import org.airsonic.player.domain.*; -import org.airsonic.player.domain.Player; +import org.airsonic.player.service.jukebox.JavaPlayerFactory; import org.airsonic.player.util.FileUtil; import org.apache.commons.lang.StringUtils; import org.slf4j.LoggerFactory; @@ -19,23 +19,25 @@ import java.util.Map; /** - * @author R??mi Cocula + * @author Rémi Cocula */ @Service public class JukeboxJavaService { private static final org.slf4j.Logger log = LoggerFactory.getLogger(JukeboxJavaService.class); + private static final float DEFAULT_GAIN = 0.75f; + @Autowired private AudioScrobblerService audioScrobblerService; @Autowired private StatusService statusService; @Autowired - private SettingsService settingsService; - @Autowired private SecurityService securityService; @Autowired private MediaFileService mediaFileService; + @Autowired + private JavaPlayerFactory javaPlayerFactory; private TransferStatus status; @@ -45,31 +47,32 @@ public class JukeboxJavaService { /** * Finds the corresponding active audio player for a given airsonic player. + * If no player exists we create one. * The JukeboxJavaService references all active audio players in a map indexed by airsonic player id. * * @param airsonicPlayer a given airsonic player. - * @return the corresponding active audio player of null if none exists. + * @return the corresponding active audio player. */ private com.github.biconou.AudioPlayer.api.Player retrieveAudioPlayerForAirsonicPlayer(Player airsonicPlayer) { com.github.biconou.AudioPlayer.api.Player foundPlayer = activeAudioPlayers.get(airsonicPlayer.getId()); if (foundPlayer == null) { synchronized (activeAudioPlayers) { - foundPlayer = initAudioPlayer(airsonicPlayer); - if (foundPlayer == null) { + com.github.biconou.AudioPlayer.api.Player newPlayer = initAudioPlayer(airsonicPlayer); + if (newPlayer == null) { throw new RuntimeException("Did not initialized a player"); - } else { - activeAudioPlayers.put(airsonicPlayer.getId(), foundPlayer); - String mixer = airsonicPlayer.getJavaJukeboxMixer(); - if (StringUtils.isBlank(mixer)) { - mixer = DEFAULT_MIXER_ENTRY_KEY; - } - List playersForMixer = activeAudioPlayersPerMixer.get(mixer); - if (playersForMixer == null) { - playersForMixer = new ArrayList<>(); - activeAudioPlayersPerMixer.put(mixer, playersForMixer); - } - playersForMixer.add(foundPlayer); } + activeAudioPlayers.put(airsonicPlayer.getId(), newPlayer); + String mixer = airsonicPlayer.getJavaJukeboxMixer(); + if (StringUtils.isBlank(mixer)) { + mixer = DEFAULT_MIXER_ENTRY_KEY; + } + List playersForMixer = activeAudioPlayersPerMixer.get(mixer); + if (playersForMixer == null) { + playersForMixer = new ArrayList<>(); + activeAudioPlayersPerMixer.put(mixer, playersForMixer); + } + playersForMixer.add(newPlayer); + foundPlayer = newPlayer; } } return foundPlayer; @@ -88,11 +91,12 @@ public class JukeboxJavaService { if (StringUtils.isNotBlank(airsonicPlayer.getJavaJukeboxMixer())) { log.info("use mixer : {}", airsonicPlayer.getJavaJukeboxMixer()); - audioPlayer = new JavaPlayer(airsonicPlayer.getJavaJukeboxMixer()); + audioPlayer = javaPlayerFactory.createJavaPlayer(airsonicPlayer.getJavaJukeboxMixer()); } else { log.info("use default mixer"); - audioPlayer = new JavaPlayer(); + audioPlayer = javaPlayerFactory.createJavaPlayer(); } + audioPlayer.setGain(DEFAULT_GAIN); if (audioPlayer != null) { audioPlayer.registerListener(new PlayerListener() { @Override @@ -159,10 +163,7 @@ public class JukeboxJavaService { throw new RuntimeException("The player " + airsonicPlayer.getName() + " is not a java jukebox player"); } com.github.biconou.AudioPlayer.api.Player audioPlayer = retrieveAudioPlayerForAirsonicPlayer(airsonicPlayer); - if (audioPlayer != null) { - return audioPlayer.getGain(); - } - return 0.5f; + return audioPlayer.getGain(); } public void setGain(final Player airsonicPlayer, final float gain) { @@ -271,20 +272,8 @@ public class JukeboxJavaService { } } - public void start(Player airsonicPlayer) throws Exception { - log.debug("begin start jukebox : player = id:{};name:{}", airsonicPlayer.getId(), airsonicPlayer.getName()); - - com.github.biconou.AudioPlayer.api.Player audioPlayer = retrieveAudioPlayerForAirsonicPlayer(airsonicPlayer); - - // Control user authorizations - User user = securityService.getUserByName(airsonicPlayer.getUsername()); - if (!user.isJukeboxRole()) { - log.warn("{} is not authorized for jukebox playback.", user.getUsername()); - return; - } - - log.debug("PlayQueue.Status is {}", airsonicPlayer.getPlayQueue().getStatus()); - audioPlayer.play(); + public void start(Player airsonicPlayer) { + play(airsonicPlayer); } public void stop(Player airsonicPlayer) throws Exception { @@ -332,7 +321,6 @@ public class JukeboxJavaService { } } - public void setAudioScrobblerService(AudioScrobblerService audioScrobblerService) { this.audioScrobblerService = audioScrobblerService; } @@ -342,7 +330,6 @@ public class JukeboxJavaService { } public void setSettingsService(SettingsService settingsService) { - this.settingsService = settingsService; } public void setSecurityService(SecurityService securityService) { diff --git a/airsonic-main/src/main/java/org/airsonic/player/service/JukeboxLegacySubsonicService.java b/airsonic-main/src/main/java/org/airsonic/player/service/JukeboxLegacySubsonicService.java index 4205bf43..939d07c5 100644 --- a/airsonic-main/src/main/java/org/airsonic/player/service/JukeboxLegacySubsonicService.java +++ b/airsonic-main/src/main/java/org/airsonic/player/service/JukeboxLegacySubsonicService.java @@ -21,6 +21,7 @@ package org.airsonic.player.service; import org.airsonic.player.domain.*; import org.airsonic.player.service.jukebox.AudioPlayer; +import org.airsonic.player.service.jukebox.AudioPlayerFactory; import org.airsonic.player.util.FileUtil; import org.apache.commons.io.IOUtils; import org.slf4j.Logger; @@ -52,6 +53,8 @@ public class JukeboxLegacySubsonicService implements AudioPlayer.Listener { private SecurityService securityService; @Autowired private MediaFileService mediaFileService; + @Autowired + private AudioPlayerFactory audioPlayerFactory; private AudioPlayer audioPlayer; private Player player; @@ -111,7 +114,7 @@ public class JukeboxLegacySubsonicService implements AudioPlayer.Listener { String command = settingsService.getJukeboxCommand(); parameters.setTranscoding(new Transcoding(null, null, null, null, command, null, null, false)); in = transcodingService.getTranscodedInputStream(parameters); - audioPlayer = new AudioPlayer(in, this); + audioPlayer = audioPlayerFactory.createAudioPlayer(in, this); audioPlayer.setGain(gain); audioPlayer.play(); onSongStart(file); diff --git a/airsonic-main/src/main/java/org/airsonic/player/service/TranscodingService.java b/airsonic-main/src/main/java/org/airsonic/player/service/TranscodingService.java index 21499ee8..9176b281 100644 --- a/airsonic-main/src/main/java/org/airsonic/player/service/TranscodingService.java +++ b/airsonic-main/src/main/java/org/airsonic/player/service/TranscodingService.java @@ -32,6 +32,7 @@ import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service; import java.io.File; @@ -61,6 +62,7 @@ public class TranscodingService { @Autowired private SettingsService settingsService; @Autowired + @Lazy // used to deal with circular dependencies between PlayerService and TranscodingService private PlayerService playerService; /** diff --git a/airsonic-main/src/main/java/org/airsonic/player/service/jukebox/AudioPlayerFactory.java b/airsonic-main/src/main/java/org/airsonic/player/service/jukebox/AudioPlayerFactory.java new file mode 100644 index 00000000..a7253011 --- /dev/null +++ b/airsonic-main/src/main/java/org/airsonic/player/service/jukebox/AudioPlayerFactory.java @@ -0,0 +1,14 @@ +package org.airsonic.player.service.jukebox; + +import org.airsonic.player.service.JukeboxLegacySubsonicService; +import org.springframework.stereotype.Component; + +import java.io.InputStream; + +@Component +public class AudioPlayerFactory { + + public AudioPlayer createAudioPlayer(InputStream in, JukeboxLegacySubsonicService jukeboxLegacySubsonicService) throws Exception { + return new AudioPlayer(in, jukeboxLegacySubsonicService); + } +} diff --git a/airsonic-main/src/main/java/org/airsonic/player/service/jukebox/JavaPlayerFactory.java b/airsonic-main/src/main/java/org/airsonic/player/service/jukebox/JavaPlayerFactory.java new file mode 100644 index 00000000..7ed6b2ed --- /dev/null +++ b/airsonic-main/src/main/java/org/airsonic/player/service/jukebox/JavaPlayerFactory.java @@ -0,0 +1,17 @@ +package org.airsonic.player.service.jukebox; + +import com.github.biconou.AudioPlayer.JavaPlayer; +import com.github.biconou.AudioPlayer.api.Player; +import org.springframework.stereotype.Component; + +@Component +public class JavaPlayerFactory { + + public Player createJavaPlayer() { + return new JavaPlayer(); + } + + public Player createJavaPlayer(String mixerName) { + return new JavaPlayer(mixerName); + } +} diff --git a/airsonic-main/src/test/java/org/airsonic/player/TestCaseUtils.java b/airsonic-main/src/test/java/org/airsonic/player/TestCaseUtils.java index 12b102a0..50c8b5d0 100644 --- a/airsonic-main/src/test/java/org/airsonic/player/TestCaseUtils.java +++ b/airsonic-main/src/test/java/org/airsonic/player/TestCaseUtils.java @@ -1,5 +1,6 @@ package org.airsonic.player; +import org.airsonic.player.controller.JAXBWriter; import org.airsonic.player.dao.DaoHelper; import org.airsonic.player.service.MediaScannerService; import org.apache.commons.io.FileUtils; @@ -37,6 +38,13 @@ public class TestCaseUtils { return airsonicHomeDirForTest.getAbsolutePath(); } + /** + * + * @return current REST api version. + */ + public static String restApiVersion() { + return new JAXBWriter().getRestProtocolVersion(); + } /** * Cleans the AIRSONIC_HOME directory used for tests. @@ -106,6 +114,7 @@ public class TestCaseUtils { * Scans the music library * @param mediaScannerService */ public static void execScan(MediaScannerService mediaScannerService) { + // TODO create a synchronous scan mediaScannerService.scanLibrary(); while (mediaScannerService.isScanning()) { diff --git a/airsonic-main/src/test/java/org/airsonic/player/api/AirsonicRestApiIntTest.java b/airsonic-main/src/test/java/org/airsonic/player/api/AirsonicRestApiIntTest.java new file mode 100644 index 00000000..0dce645d --- /dev/null +++ b/airsonic-main/src/test/java/org/airsonic/player/api/AirsonicRestApiIntTest.java @@ -0,0 +1,59 @@ +package org.airsonic.player.api; + +import org.airsonic.player.TestCaseUtils; +import org.airsonic.player.util.HomeRule; +import org.junit.BeforeClass; +import org.junit.ClassRule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.http.MediaType; +import org.springframework.test.context.junit4.SpringRunner; +import org.springframework.test.web.servlet.MockMvc; + +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + + +@RunWith(SpringRunner.class) +@SpringBootTest +@AutoConfigureMockMvc +public class AirsonicRestApiIntTest { + + public static final String CLIENT_NAME = "airsonic"; + public static final String AIRSONIC_USER = "admin"; + public static final String AIRSONIC_PASSWORD = "admin"; + public static final String EXPECTED_FORMAT = "json"; + + private static String AIRSONIC_API_VERSION; + + @Autowired + private MockMvc mvc; + + @ClassRule + public static final HomeRule classRule = new HomeRule(); // sets airsonic.home to a temporary dir + + @BeforeClass + public static void setupClass() { + AIRSONIC_API_VERSION = TestCaseUtils.restApiVersion(); + } + + @Test + public void pingTest() throws Exception { + mvc.perform(get("/rest/ping") + .param("v", AIRSONIC_API_VERSION) + .param("c", CLIENT_NAME) + .param("u", AIRSONIC_USER) + .param("p", AIRSONIC_PASSWORD) + .param("f", EXPECTED_FORMAT) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.subsonic-response.status").value("ok")) + .andExpect(jsonPath("$.subsonic-response.version").value(AIRSONIC_API_VERSION)) + .andDo(print()); + } +} 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 new file mode 100644 index 00000000..0195fedd --- /dev/null +++ b/airsonic-main/src/test/java/org/airsonic/player/api/jukebox/AbstractAirsonicRestApiJukeboxIntTest.java @@ -0,0 +1,279 @@ +package org.airsonic.player.api.jukebox; + +import org.airsonic.player.TestCaseUtils; +import org.airsonic.player.dao.*; +import org.airsonic.player.domain.*; +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.junit.*; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.context.TestConfiguration; +import org.springframework.context.annotation.Bean; +import org.springframework.http.MediaType; +import org.springframework.security.test.context.support.WithMockUser; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.context.junit4.SpringRunner; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.ResultActions; +import org.springframework.test.web.servlet.ResultMatcher; + +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.TimeZone; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.*; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@RunWith(SpringRunner.class) +@SpringBootTest +@AutoConfigureMockMvc +@DirtiesContext(classMode = DirtiesContext.ClassMode.BEFORE_EACH_TEST_METHOD) +public abstract class AbstractAirsonicRestApiJukeboxIntTest { + + @ClassRule + public static final HomeRule classRule = new HomeRule(); // sets airsonic.home to a temporary dir + + @TestConfiguration + static class Config { + private static class SpiedPlayerDaoPlayQueueFactory extends PlayerDaoPlayQueueFactory { + @Override + public PlayQueue createPlayQueue() { + return spy(super.createPlayQueue()); + } + } + + @Bean + public PlayerDaoPlayQueueFactory playerDaoPlayQueueFactory() { + return new SpiedPlayerDaoPlayQueueFactory(); + } + } + + protected static final String CLIENT_NAME = "airsonic"; + protected static final String JUKEBOX_PLAYER_NAME = CLIENT_NAME + "-jukebox"; + private static final String EXPECTED_FORMAT = "json"; + private static String AIRSONIC_API_VERSION; + + private static boolean dataBasePopulated; + private static DaoHelper staticDaoHelper; + + @Autowired + protected PlayerService playerService; + @Autowired + private MockMvc mvc; + @Autowired + private MusicFolderDao musicFolderDao; + @Autowired + private SettingsService settingsService; + @Autowired + private MediaScannerService mediaScannerService; + @Autowired + private PlayerDao playerDao; + @Autowired + private MediaFileDao mediaFileDao; + @Autowired + private DaoHelper daoHelper; + @Autowired + private AlbumDao albumDao; + @Autowired + private ArtistDao artistDao; + + private Player testJukeboxPlayer; + + @BeforeClass + public static void setupClass() { + AIRSONIC_API_VERSION = TestCaseUtils.restApiVersion(); + dataBasePopulated = false; + } + + @AfterClass + public static void cleanDataBase() { + staticDaoHelper.getJdbcTemplate().execute("DROP SCHEMA PUBLIC CASCADE"); + staticDaoHelper = null; + dataBasePopulated = false; + } + + /** + * Populate test datas in the database only once. + * + *
    + *
  • Creates 2 music folder
  • + *
  • Scans the music folders
  • + *
  • Creates a test jukebox player
  • + *
+ */ + private void populateDatabase() { + if (!dataBasePopulated) { + staticDaoHelper = daoHelper; + + assertThat(musicFolderDao.getAllMusicFolders().size()).isEqualTo(1); + MusicFolderTestData.getTestMusicFolders().forEach(musicFolderDao::createMusicFolder); + settingsService.clearMusicFolderCache(); + + TestCaseUtils.execScan(mediaScannerService); + + assertThat(playerDao.getAllPlayers().size()).isEqualTo(0); + createTestPlayer(); + assertThat(playerDao.getAllPlayers().size()).isEqualTo(1); + + dataBasePopulated = true; + } + } + + @Before + public void setup() throws Exception { + populateDatabase(); + + testJukeboxPlayer = findTestJukeboxPlayer(); + assertThat(testJukeboxPlayer).isNotNull(); + reset(testJukeboxPlayer.getPlayQueue()); + testJukeboxPlayer.getPlayQueue().clear(); + assertThat(testJukeboxPlayer.getPlayQueue().size()).isEqualTo(0); + testJukeboxPlayer.getPlayQueue().addFiles(true, + mediaFileDao.getSongsForAlbum("_DIR_ Ravel", "Complete Piano Works")); + assertThat(testJukeboxPlayer.getPlayQueue().size()).isEqualTo(2); + } + + protected abstract void createTestPlayer(); + + private Player findTestJukeboxPlayer() { + return playerDao.getAllPlayers().stream().filter(player -> player.getName().equals(JUKEBOX_PLAYER_NAME)) + .findFirst().orElseThrow(() -> new RuntimeException("No player found in database")); + } + + private String convertDateToString(Date date) { + SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.000'Z'"); + formatter.setTimeZone(TimeZone.getTimeZone("UTC")); + return formatter.format(date); + } + + private ResultMatcher playListItem1isCorrect() { + MediaFile mediaFile = testJukeboxPlayer.getPlayQueue().getFile(0); + MediaFile parent = mediaFileDao.getMediaFile(mediaFile.getParentPath()); + Album album = albumDao.getAlbum(mediaFile.getArtist(), mediaFile.getAlbumName()); + Artist artist = artistDao.getArtist(mediaFile.getArtist()); + assertThat(album).isNotNull(); + return result -> { + jsonPath("$.subsonic-response.jukeboxPlaylist.entry[0].id").value(mediaFile.getId()).match(result); + jsonPath("$.subsonic-response.jukeboxPlaylist.entry[0].parent").value(parent.getId()).match(result); + jsonPath("$.subsonic-response.jukeboxPlaylist.entry[0].isDir").value(false).match(result); + jsonPath("$.subsonic-response.jukeboxPlaylist.entry[0].title").value("01 - Gaspard de la Nuit - i. Ondine").match(result); + jsonPath("$.subsonic-response.jukeboxPlaylist.entry[0].album").value("Complete Piano Works").match(result); + jsonPath("$.subsonic-response.jukeboxPlaylist.entry[0].artist").value("_DIR_ Ravel").match(result); + jsonPath("$.subsonic-response.jukeboxPlaylist.entry[0].coverArt").value(parent.getId()).match(result); + jsonPath("$.subsonic-response.jukeboxPlaylist.entry[0].size").value(45138).match(result); + jsonPath("$.subsonic-response.jukeboxPlaylist.entry[0].contentType").value("audio/mpeg").match(result); + jsonPath("$.subsonic-response.jukeboxPlaylist.entry[0].suffix").value("mp3").match(result); + jsonPath("$.subsonic-response.jukeboxPlaylist.entry[0].duration").value(2).match(result); + jsonPath("$.subsonic-response.jukeboxPlaylist.entry[0].bitRate").value(128).match(result); + jsonPath("$.subsonic-response.jukeboxPlaylist.entry[0].path").value("_DIR_ Ravel/_DIR_ Ravel - Complete Piano Works/01 - Gaspard de la Nuit - i. Ondine.mp3").match(result); + jsonPath("$.subsonic-response.jukeboxPlaylist.entry[0].isVideo").value(false).match(result); + jsonPath("$.subsonic-response.jukeboxPlaylist.entry[0].playCount").isNumber().match(result); + jsonPath("$.subsonic-response.jukeboxPlaylist.entry[0].created").value(convertDateToString(mediaFile.getCreated())).match(result); + jsonPath("$.subsonic-response.jukeboxPlaylist.entry[0].albumId").value(album.getId()).match(result); + jsonPath("$.subsonic-response.jukeboxPlaylist.entry[0].artistId").value(artist.getId()).match(result); + jsonPath("$.subsonic-response.jukeboxPlaylist.entry[0].type").value("music").match(result); + }; + } + + @Test + @WithMockUser(username = "admin") + public void jukeboxStartActionTest() throws Exception { + // Given + + // When and Then + performStartAction(); + performStatusAction("true"); + performGetAction() + .andExpect(jsonPath("$.subsonic-response.jukeboxPlaylist.currentIndex").value("0")) + .andExpect(jsonPath("$.subsonic-response.jukeboxPlaylist.playing").value("true")) + .andExpect(jsonPath("$.subsonic-response.jukeboxPlaylist.gain").value("0.75")) + .andExpect(jsonPath("$.subsonic-response.jukeboxPlaylist.position").value("0")) + .andExpect(jsonPath("$.subsonic-response.jukeboxPlaylist.entry").isArray()) + .andExpect(jsonPath("$.subsonic-response.jukeboxPlaylist.entry.length()").value(2)) + .andExpect(playListItem1isCorrect()) + .andDo(print()); + + verify(testJukeboxPlayer.getPlayQueue(), times(2)).setStatus(PlayQueue.Status.PLAYING); + assertThat(testJukeboxPlayer.getPlayQueue().getIndex()).isEqualTo(0); + assertThat(testJukeboxPlayer.getPlayQueue().getStatus()).isEqualTo(PlayQueue.Status.PLAYING); + } + + @Test + @WithMockUser(username = "admin") + public void jukeboxStopActionTest() throws Exception { + // Given + + // When and Then + performStartAction(); + performStatusAction("true"); + performStopAction(); + performStatusAction("false"); + + verify(testJukeboxPlayer.getPlayQueue(), times(2)).setStatus(PlayQueue.Status.PLAYING); + verify(testJukeboxPlayer.getPlayQueue(), times(1)).setStatus(PlayQueue.Status.STOPPED); + assertThat(testJukeboxPlayer.getPlayQueue().getIndex()).isEqualTo(0); + assertThat(testJukeboxPlayer.getPlayQueue().getStatus()).isEqualTo(PlayQueue.Status.STOPPED); + } + + private void performStatusAction(String expectedPlayingValue) throws Exception { + mvc.perform(get("/rest/jukeboxControl.view") + .param("v", AIRSONIC_API_VERSION) + .param("c", CLIENT_NAME) + .param("f", EXPECTED_FORMAT) + .param("action", "status") + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.subsonic-response.status").value("ok")) + .andExpect(jsonPath("$.subsonic-response.jukeboxStatus.currentIndex").value("0")) + .andExpect(jsonPath("$.subsonic-response.jukeboxStatus.playing").value(expectedPlayingValue)) + .andExpect(jsonPath("$.subsonic-response.jukeboxStatus.position").value("0")); + } + + private ResultActions performGetAction() throws Exception { + return mvc.perform(get("/rest/jukeboxControl.view") + .param("v", AIRSONIC_API_VERSION) + .param("c", CLIENT_NAME) + .param("f", EXPECTED_FORMAT) + .param("action", "get") + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.subsonic-response.status").value("ok")); + } + + private void performStopAction() throws Exception { + mvc.perform(get("/rest/jukeboxControl.view") + .param("v", AIRSONIC_API_VERSION) + .param("c", CLIENT_NAME) + .param("f", EXPECTED_FORMAT) + .param("action", "stop") + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.subsonic-response.status").value("ok")) + .andExpect(jsonPath("$.subsonic-response.jukeboxStatus.currentIndex").value("0")) + .andExpect(jsonPath("$.subsonic-response.jukeboxStatus.playing").value("false")) + .andExpect(jsonPath("$.subsonic-response.jukeboxStatus.position").value("0")); + } + + private void performStartAction() throws Exception { + mvc.perform(get("/rest/jukeboxControl.view") + .param("v", AIRSONIC_API_VERSION) + .param("c", CLIENT_NAME) + .param("f", EXPECTED_FORMAT) + .param("action", "start") + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.subsonic-response.status").value("ok")) + .andExpect(jsonPath("$.subsonic-response.jukeboxStatus.currentIndex").value("0")) + .andExpect(jsonPath("$.subsonic-response.jukeboxStatus.playing").value("true")) + .andExpect(jsonPath("$.subsonic-response.jukeboxStatus.position").value("0")); + } +} diff --git a/airsonic-main/src/test/java/org/airsonic/player/api/jukebox/AirsonicRestApiJukeboxIntTest.java b/airsonic-main/src/test/java/org/airsonic/player/api/jukebox/AirsonicRestApiJukeboxIntTest.java new file mode 100644 index 00000000..f0c61a0b --- /dev/null +++ b/airsonic-main/src/test/java/org/airsonic/player/api/jukebox/AirsonicRestApiJukeboxIntTest.java @@ -0,0 +1,38 @@ +package org.airsonic.player.api.jukebox; + +import com.github.biconou.AudioPlayer.JavaPlayer; +import org.airsonic.player.domain.Player; +import org.airsonic.player.domain.PlayerTechnology; +import org.airsonic.player.service.jukebox.JavaPlayerFactory; +import org.junit.Before; +import org.springframework.boot.test.mock.mockito.MockBean; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class AirsonicRestApiJukeboxIntTest extends AbstractAirsonicRestApiJukeboxIntTest { + + @MockBean + protected JavaPlayerFactory javaPlayerFactory; + + @Before + @Override + public void setup() throws Exception { + super.setup(); + JavaPlayer mockJavaPlayer = mock(JavaPlayer.class); + when(mockJavaPlayer.getPlayingInfos()).thenReturn( () -> 0 ); + when(mockJavaPlayer.getGain()).thenReturn(0.75f); + when(javaPlayerFactory.createJavaPlayer()).thenReturn(mockJavaPlayer); + } + + @Override + protected void createTestPlayer() { + Player jukeBoxPlayer = new Player(); + jukeBoxPlayer.setName(JUKEBOX_PLAYER_NAME); + jukeBoxPlayer.setUsername("admin"); + jukeBoxPlayer.setClientId(CLIENT_NAME + "-jukebox"); + jukeBoxPlayer.setTechnology(PlayerTechnology.JAVA_JUKEBOX); + playerService.createPlayer(jukeBoxPlayer); + } + +} \ No newline at end of file diff --git a/airsonic-main/src/test/java/org/airsonic/player/api/jukebox/AirsonicRestApiJukeboxLegacyIntTest.java b/airsonic-main/src/test/java/org/airsonic/player/api/jukebox/AirsonicRestApiJukeboxLegacyIntTest.java new file mode 100644 index 00000000..2cdee852 --- /dev/null +++ b/airsonic-main/src/test/java/org/airsonic/player/api/jukebox/AirsonicRestApiJukeboxLegacyIntTest.java @@ -0,0 +1,62 @@ +package org.airsonic.player.api.jukebox; + +import org.airsonic.player.domain.Player; +import org.airsonic.player.domain.PlayerTechnology; +import org.airsonic.player.service.TranscodingService; +import org.airsonic.player.service.jukebox.AudioPlayer; +import org.airsonic.player.service.jukebox.AudioPlayerFactory; +import org.junit.Before; +import org.junit.Test; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.boot.test.mock.mockito.SpyBean; +import org.springframework.security.test.context.support.WithMockUser; + +import static org.mockito.Matchers.any; +import static org.mockito.Mockito.*; + +public class AirsonicRestApiJukeboxLegacyIntTest extends AirsonicRestApiJukeboxIntTest { + + @SpyBean + private TranscodingService transcodingService; + @MockBean + protected AudioPlayerFactory audioPlayerFactory; + + private AudioPlayer mockAudioPlayer; + + @Before + @Override + public void setup() throws Exception { + super.setup(); + mockAudioPlayer = mock(AudioPlayer.class); + when(audioPlayerFactory.createAudioPlayer(any(), any())).thenReturn(mockAudioPlayer); + doReturn(null).when(transcodingService).getTranscodedInputStream(any()); + } + + @Override + protected final void createTestPlayer() { + Player jukeBoxPlayer = new Player(); + jukeBoxPlayer.setName(JUKEBOX_PLAYER_NAME); + jukeBoxPlayer.setUsername("admin"); + jukeBoxPlayer.setClientId(CLIENT_NAME + "-jukebox"); + jukeBoxPlayer.setTechnology(PlayerTechnology.JUKEBOX); + playerService.createPlayer(jukeBoxPlayer); + } + + @Test + @WithMockUser(username = "admin") + @Override + public void jukeboxStartActionTest() throws Exception { + super.jukeboxStartActionTest(); + verify(mockAudioPlayer).play(); + } + + @Test + @WithMockUser(username = "admin") + @Override + public void jukeboxStopActionTest() throws Exception { + super.jukeboxStopActionTest(); + verify(mockAudioPlayer).play(); + verify(mockAudioPlayer).pause(); + } + +} diff --git a/airsonic-main/src/test/java/org/airsonic/player/service/TranscodingServiceIntTest.java b/airsonic-main/src/test/java/org/airsonic/player/service/TranscodingServiceIntTest.java new file mode 100644 index 00000000..1c9293b4 --- /dev/null +++ b/airsonic-main/src/test/java/org/airsonic/player/service/TranscodingServiceIntTest.java @@ -0,0 +1,41 @@ +package org.airsonic.player.service; + +import org.airsonic.player.domain.Transcoding; +import org.airsonic.player.util.HomeRule; +import org.junit.ClassRule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.mock.mockito.SpyBean; +import org.springframework.test.context.junit4.SpringRunner; +import static org.mockito.Mockito.*; + +@RunWith(SpringRunner.class) +@SpringBootTest +public class TranscodingServiceIntTest { + + @Autowired + private TranscodingService transcodingService; + @SpyBean + private PlayerService playerService; + + @ClassRule + public static final HomeRule classRule = new HomeRule(); // sets airsonic.home to a temporary dir + + @Test + public void createTranscodingTest() { + // Given + Transcoding transcoding = new Transcoding(null, + "test-transcoding", + "mp3", + "wav", + "step1", + "step2", + "step3", + true); + + transcodingService.createTranscoding(transcoding); + verify(playerService).getAllPlayers(); + } +} diff --git a/airsonic-main/src/test/resources/application.properties b/airsonic-main/src/test/resources/application.properties new file mode 100644 index 00000000..29c1c5dd --- /dev/null +++ b/airsonic-main/src/test/resources/application.properties @@ -0,0 +1,13 @@ +spring.mvc.view.prefix: /WEB-INF/jsp/ +spring.mvc.view.suffix: .jsp +server.error.includeStacktrace: ALWAYS +logging.level.root=WARN +logging.level.org.airsonic=INFO +logging.level.liquibase=INFO +logging.pattern.console=%clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){faint} %clr(%5p){green} %clr(---){faint} %clr(%-40.40logger{32}){blue} %clr(:){faint} %m%n%wEx +logging.pattern.file=%d{yyyy-MM-dd HH:mm:ss.SSS} %5p --- %-40.40logger{32} : %m%n%wEx +DatabaseConfigType=embed +DatabaseConfigEmbedDriver=org.hsqldb.jdbcDriver +DatabaseConfigEmbedUrl=jdbc:hsqldb:mem:airsonic +DatabaseConfigEmbedUsername=sa +DatabaseConfigEmbedPassword= \ No newline at end of file diff --git a/pom.xml b/pom.xml index be66c6d3..b63145f6 100644 --- a/pom.xml +++ b/pom.xml @@ -255,6 +255,8 @@ org.springframework:* org.springframework.security:* org.springframework.boot:* + org.assertj:* + org.hamcrest:* org.apache.tomcat.embed:tomcat-embed-core* org.apache.tomcat:tomcat-annotations-api:*