2014-01-20 21:31:40 +01:00
|
|
|
/*
|
|
|
|
* Unit tests for playlist_check_translate_song().
|
|
|
|
*/
|
|
|
|
|
2019-03-15 20:41:55 +01:00
|
|
|
#include "MakeTag.hxx"
|
2014-01-23 23:30:12 +01:00
|
|
|
#include "playlist/PlaylistSong.hxx"
|
2018-08-02 13:45:43 +02:00
|
|
|
#include "song/DetachedSong.hxx"
|
2014-02-02 14:37:52 +01:00
|
|
|
#include "SongLoader.hxx"
|
2023-11-25 23:06:24 +01:00
|
|
|
#include "client/IClient.hxx"
|
2017-02-08 08:26:58 +01:00
|
|
|
#include "tag/Builder.hxx"
|
2023-03-06 14:25:19 +01:00
|
|
|
#include "tag/Names.hxx"
|
2014-01-20 21:31:40 +01:00
|
|
|
#include "tag/Tag.hxx"
|
|
|
|
#include "util/Domain.hxx"
|
|
|
|
#include "fs/AllocatedPath.hxx"
|
|
|
|
#include "ls.hxx"
|
2014-01-24 16:18:50 +01:00
|
|
|
#include "db/DatabaseSong.hxx"
|
2022-11-12 12:16:56 +01:00
|
|
|
#include "storage/Registry.hxx"
|
2018-01-02 16:11:17 +01:00
|
|
|
#include "storage/StorageInterface.hxx"
|
2014-02-07 19:01:06 +01:00
|
|
|
#include "storage/plugins/LocalStorage.hxx"
|
2014-01-20 21:31:40 +01:00
|
|
|
#include "Mapper.hxx"
|
2019-05-08 15:47:58 +02:00
|
|
|
#include "time/ChronoUtil.hxx"
|
2014-01-20 21:31:40 +01:00
|
|
|
|
2018-10-16 19:01:13 +02:00
|
|
|
#include <gtest/gtest.h>
|
2014-01-20 21:31:40 +01:00
|
|
|
|
|
|
|
#include <string.h>
|
|
|
|
#include <stdio.h>
|
|
|
|
|
|
|
|
bool
|
2017-05-08 14:44:49 +02:00
|
|
|
uri_supported_scheme(const char *uri) noexcept
|
2014-01-20 21:31:40 +01:00
|
|
|
{
|
2017-01-03 13:16:29 +01:00
|
|
|
return strncmp(uri, "http://", 7) == 0;
|
2014-01-20 21:31:40 +01:00
|
|
|
}
|
|
|
|
|
2022-11-12 12:16:56 +01:00
|
|
|
const StoragePlugin *
|
|
|
|
GetStoragePluginByUri(const char *) noexcept
|
|
|
|
{
|
|
|
|
// dummy symbol
|
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
|
2015-06-22 22:12:02 +02:00
|
|
|
static constexpr auto music_directory = PATH_LITERAL("/music");
|
2014-02-07 23:41:06 +01:00
|
|
|
static Storage *storage;
|
2014-01-20 21:31:40 +01:00
|
|
|
|
|
|
|
static Tag
|
|
|
|
MakeTag1a()
|
|
|
|
{
|
|
|
|
return MakeTag(TAG_ARTIST, "artist_a1", TAG_TITLE, "title_a1",
|
|
|
|
TAG_ALBUM, "album_a1");
|
|
|
|
}
|
|
|
|
|
|
|
|
static Tag
|
|
|
|
MakeTag1b()
|
|
|
|
{
|
|
|
|
return MakeTag(TAG_ARTIST, "artist_b1", TAG_TITLE, "title_b1",
|
|
|
|
TAG_COMMENT, "comment_b1");
|
|
|
|
}
|
|
|
|
|
|
|
|
static Tag
|
|
|
|
MakeTag1c()
|
|
|
|
{
|
|
|
|
return MakeTag(TAG_ARTIST, "artist_b1", TAG_TITLE, "title_b1",
|
|
|
|
TAG_COMMENT, "comment_b1", TAG_ALBUM, "album_a1");
|
|
|
|
}
|
|
|
|
|
|
|
|
static Tag
|
|
|
|
MakeTag2a()
|
|
|
|
{
|
|
|
|
return MakeTag(TAG_ARTIST, "artist_a2", TAG_TITLE, "title_a2",
|
|
|
|
TAG_ALBUM, "album_a2");
|
|
|
|
}
|
|
|
|
|
|
|
|
static Tag
|
|
|
|
MakeTag2b()
|
|
|
|
{
|
|
|
|
return MakeTag(TAG_ARTIST, "artist_b2", TAG_TITLE, "title_b2",
|
|
|
|
TAG_COMMENT, "comment_b2");
|
|
|
|
}
|
|
|
|
|
|
|
|
static Tag
|
|
|
|
MakeTag2c()
|
|
|
|
{
|
|
|
|
return MakeTag(TAG_ARTIST, "artist_b2", TAG_TITLE, "title_b2",
|
|
|
|
TAG_COMMENT, "comment_b2", TAG_ALBUM, "album_a2");
|
|
|
|
}
|
|
|
|
|
|
|
|
static const char *uri1 = "/foo/bar.ogg";
|
|
|
|
static const char *uri2 = "foo/bar.ogg";
|
|
|
|
|
2017-02-08 09:58:20 +01:00
|
|
|
DetachedSong
|
2020-03-12 20:56:11 +01:00
|
|
|
DatabaseDetachSong([[maybe_unused]] const Database &db,
|
|
|
|
[[maybe_unused]] const Storage *_storage,
|
2016-03-19 00:13:57 +01:00
|
|
|
const char *uri)
|
2014-01-20 21:31:40 +01:00
|
|
|
{
|
|
|
|
if (strcmp(uri, uri2) == 0)
|
2017-02-08 09:58:20 +01:00
|
|
|
return DetachedSong(uri, MakeTag2a());
|
2014-01-20 21:31:40 +01:00
|
|
|
|
2017-02-08 09:58:20 +01:00
|
|
|
throw std::runtime_error("No such song");
|
2014-01-20 21:31:40 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
bool
|
2019-05-21 23:11:56 +02:00
|
|
|
DetachedSong::LoadFile(Path path)
|
2014-01-20 21:31:40 +01:00
|
|
|
{
|
2015-10-20 12:10:42 +02:00
|
|
|
if (path.ToUTF8() == uri1) {
|
2014-01-20 21:31:40 +01:00
|
|
|
SetTag(MakeTag1a());
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2023-11-25 23:06:24 +01:00
|
|
|
class TestClient final : public IClient {
|
|
|
|
public:
|
|
|
|
// virtual methods from class IClient
|
|
|
|
void AllowFile([[maybe_unused]] Path path_fs) const override {
|
|
|
|
/* always fail, so a SongLoader with a non-nullptr
|
|
|
|
Client pointer will be regarded "insecure", while one with
|
|
|
|
client==nullptr will allow all files */
|
|
|
|
throw std::runtime_error{"foo"};
|
|
|
|
}
|
2014-02-01 00:26:34 +01:00
|
|
|
|
2023-11-25 23:06:24 +01:00
|
|
|
#ifdef ENABLE_DATABASE
|
|
|
|
const Database *GetDatabase() const noexcept override {
|
|
|
|
return reinterpret_cast<const Database *>(this);
|
|
|
|
}
|
2014-02-07 00:29:07 +01:00
|
|
|
|
2024-05-15 14:59:28 +02:00
|
|
|
Storage *GetStorage() const noexcept override {
|
2023-11-25 23:06:24 +01:00
|
|
|
return ::storage;
|
|
|
|
}
|
|
|
|
#endif // ENABLE_DATABASE
|
|
|
|
};
|
2014-02-02 14:37:52 +01:00
|
|
|
|
2014-01-20 21:31:40 +01:00
|
|
|
static std::string
|
|
|
|
ToString(const Tag &tag)
|
|
|
|
{
|
2014-08-29 12:14:27 +02:00
|
|
|
std::string result;
|
2014-01-20 21:31:40 +01:00
|
|
|
|
2018-01-24 13:32:39 +01:00
|
|
|
if (!tag.duration.IsNegative())
|
|
|
|
result.append(std::to_string(tag.duration.ToMS()));
|
2014-01-20 21:31:40 +01:00
|
|
|
|
2014-07-12 17:22:39 +02:00
|
|
|
for (const auto &item : tag) {
|
2014-01-20 21:31:40 +01:00
|
|
|
result.push_back('|');
|
|
|
|
result.append(tag_item_names[item.type]);
|
|
|
|
result.push_back('=');
|
|
|
|
result.append(item.value);
|
|
|
|
}
|
|
|
|
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
static std::string
|
|
|
|
ToString(const DetachedSong &song)
|
|
|
|
{
|
|
|
|
std::string result = song.GetURI();
|
|
|
|
result.push_back('|');
|
|
|
|
|
2018-01-24 13:32:39 +01:00
|
|
|
if (!IsNegative(song.GetLastModified()))
|
|
|
|
result.append(std::to_string(std::chrono::system_clock::to_time_t(song.GetLastModified())));
|
2014-01-20 21:31:40 +01:00
|
|
|
|
|
|
|
result.push_back('|');
|
|
|
|
|
2018-01-24 13:32:39 +01:00
|
|
|
if (song.GetStartTime().IsPositive())
|
|
|
|
result.append(std::to_string(song.GetStartTime().ToMS()));
|
2014-01-20 21:31:40 +01:00
|
|
|
|
|
|
|
result.push_back('-');
|
|
|
|
|
2018-01-24 13:32:39 +01:00
|
|
|
if (song.GetEndTime().IsPositive())
|
|
|
|
result.append(std::to_string(song.GetEndTime().ToMS()));
|
2014-01-20 21:31:40 +01:00
|
|
|
|
|
|
|
result.push_back('|');
|
|
|
|
|
|
|
|
result.append(ToString(song.GetTag()));
|
|
|
|
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2018-10-16 19:01:13 +02:00
|
|
|
class TranslateSongTest : public ::testing::Test {
|
|
|
|
std::unique_ptr<Storage> _storage;
|
2014-01-20 21:31:40 +01:00
|
|
|
|
2018-10-16 19:01:13 +02:00
|
|
|
protected:
|
|
|
|
void SetUp() override {
|
|
|
|
_storage = CreateLocalStorage(Path::FromFS(music_directory));
|
|
|
|
storage = _storage.get();
|
2014-01-20 21:31:40 +01:00
|
|
|
}
|
|
|
|
|
2018-10-16 19:01:13 +02:00
|
|
|
void TearDown() override {
|
|
|
|
_storage.reset();
|
2014-01-20 21:31:40 +01:00
|
|
|
}
|
2018-10-16 19:01:13 +02:00
|
|
|
};
|
2014-01-20 21:31:40 +01:00
|
|
|
|
2018-10-16 19:01:13 +02:00
|
|
|
TEST_F(TranslateSongTest, AbsoluteURI)
|
|
|
|
{
|
|
|
|
DetachedSong song1("http://example.com/foo.ogg");
|
|
|
|
auto se = ToString(song1);
|
|
|
|
const SongLoader loader(nullptr, nullptr);
|
|
|
|
EXPECT_TRUE(playlist_check_translate_song(song1, "/ignored",
|
|
|
|
loader));
|
|
|
|
EXPECT_EQ(se, ToString(song1));
|
|
|
|
}
|
2014-01-20 21:31:40 +01:00
|
|
|
|
2018-10-16 19:01:13 +02:00
|
|
|
TEST_F(TranslateSongTest, Insecure)
|
|
|
|
{
|
|
|
|
/* illegal because secure=false */
|
|
|
|
DetachedSong song1 (uri1);
|
2023-11-25 23:06:24 +01:00
|
|
|
TestClient client;
|
|
|
|
const SongLoader loader{client};
|
2020-03-13 19:58:36 +01:00
|
|
|
EXPECT_FALSE(playlist_check_translate_song(song1, {},
|
2018-10-16 19:01:13 +02:00
|
|
|
loader));
|
|
|
|
}
|
2014-01-20 21:31:40 +01:00
|
|
|
|
2018-10-16 19:01:13 +02:00
|
|
|
TEST_F(TranslateSongTest, Secure)
|
|
|
|
{
|
|
|
|
DetachedSong song1(uri1, MakeTag1b());
|
|
|
|
auto se = ToString(DetachedSong(uri1, MakeTag1c()));
|
|
|
|
|
|
|
|
const SongLoader loader(nullptr, nullptr);
|
|
|
|
EXPECT_TRUE(playlist_check_translate_song(song1, "/ignored",
|
|
|
|
loader));
|
|
|
|
EXPECT_EQ(se, ToString(song1));
|
|
|
|
}
|
2014-01-20 21:31:40 +01:00
|
|
|
|
2018-10-16 19:01:13 +02:00
|
|
|
TEST_F(TranslateSongTest, InDatabase)
|
2014-01-20 21:31:40 +01:00
|
|
|
{
|
2018-10-16 19:01:13 +02:00
|
|
|
const SongLoader loader(reinterpret_cast<const Database *>(1),
|
|
|
|
storage);
|
|
|
|
|
|
|
|
DetachedSong song1("doesntexist");
|
2020-03-13 19:58:36 +01:00
|
|
|
EXPECT_FALSE(playlist_check_translate_song(song1, {},
|
2018-10-16 19:01:13 +02:00
|
|
|
loader));
|
|
|
|
|
|
|
|
DetachedSong song2(uri2, MakeTag2b());
|
|
|
|
auto se = ToString(DetachedSong(uri2, MakeTag2c()));
|
2020-03-13 19:58:36 +01:00
|
|
|
EXPECT_TRUE(playlist_check_translate_song(song2, {},
|
2018-10-16 19:01:13 +02:00
|
|
|
loader));
|
|
|
|
EXPECT_EQ(se, ToString(song2));
|
|
|
|
|
|
|
|
DetachedSong song3("/music/foo/bar.ogg", MakeTag2b());
|
|
|
|
se = ToString(DetachedSong(uri2, MakeTag2c()));
|
2020-03-13 19:58:36 +01:00
|
|
|
EXPECT_TRUE(playlist_check_translate_song(song3, {},
|
2018-10-16 19:01:13 +02:00
|
|
|
loader));
|
|
|
|
EXPECT_EQ(se, ToString(song3));
|
|
|
|
}
|
2014-02-07 23:41:06 +01:00
|
|
|
|
2018-10-16 19:01:13 +02:00
|
|
|
TEST_F(TranslateSongTest, Relative)
|
|
|
|
{
|
|
|
|
const Database &db = *reinterpret_cast<const Database *>(1);
|
|
|
|
const SongLoader secure_loader(&db, storage);
|
2023-11-25 23:06:24 +01:00
|
|
|
|
|
|
|
TestClient client;
|
|
|
|
const SongLoader insecure_loader{client, &db, storage};
|
2018-10-16 19:01:13 +02:00
|
|
|
|
|
|
|
/* map to music_directory */
|
|
|
|
DetachedSong song1("bar.ogg", MakeTag2b());
|
|
|
|
auto se = ToString(DetachedSong(uri2, MakeTag2c()));
|
|
|
|
EXPECT_TRUE(playlist_check_translate_song(song1, "/music/foo",
|
|
|
|
insecure_loader));
|
|
|
|
EXPECT_EQ(se, ToString(song1));
|
|
|
|
|
|
|
|
/* illegal because secure=false */
|
|
|
|
DetachedSong song2("bar.ogg", MakeTag2b());
|
|
|
|
EXPECT_FALSE(playlist_check_translate_song(song1, "/foo",
|
|
|
|
insecure_loader));
|
|
|
|
|
|
|
|
/* legal because secure=true */
|
|
|
|
DetachedSong song3("bar.ogg", MakeTag1b());
|
|
|
|
se = ToString(DetachedSong(uri1, MakeTag1c()));
|
|
|
|
EXPECT_TRUE(playlist_check_translate_song(song3, "/foo",
|
|
|
|
secure_loader));
|
|
|
|
EXPECT_EQ(se, ToString(song3));
|
|
|
|
|
|
|
|
/* relative to http:// */
|
|
|
|
DetachedSong song4("bar.ogg", MakeTag2a());
|
|
|
|
se = ToString(DetachedSong("http://example.com/foo/bar.ogg", MakeTag2a()));
|
|
|
|
EXPECT_TRUE(playlist_check_translate_song(song4, "http://example.com/foo",
|
|
|
|
insecure_loader));
|
|
|
|
EXPECT_EQ(se, ToString(song4));
|
2014-01-20 21:31:40 +01:00
|
|
|
}
|
2019-07-29 09:52:18 +02:00
|
|
|
|
|
|
|
TEST_F(TranslateSongTest, Backslash)
|
|
|
|
{
|
|
|
|
const SongLoader loader(reinterpret_cast<const Database *>(1),
|
|
|
|
storage);
|
|
|
|
|
|
|
|
DetachedSong song1("foo\\bar.ogg", MakeTag2b());
|
|
|
|
#ifdef _WIN32
|
|
|
|
/* on Windows, all backslashes are converted to slashes in
|
|
|
|
relative paths from playlists */
|
|
|
|
auto se = ToString(DetachedSong(uri2, MakeTag2c()));
|
2020-03-13 19:58:36 +01:00
|
|
|
EXPECT_TRUE(playlist_check_translate_song(song1, {},
|
2019-07-29 09:52:18 +02:00
|
|
|
loader));
|
|
|
|
EXPECT_EQ(se, ToString(song1));
|
|
|
|
#else
|
|
|
|
/* backslash only supported on Windows */
|
2020-03-13 19:58:36 +01:00
|
|
|
EXPECT_FALSE(playlist_check_translate_song(song1, {},
|
2019-07-29 09:52:18 +02:00
|
|
|
loader));
|
|
|
|
#endif
|
|
|
|
}
|