You cannot select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
	
	
		
			298 lines
		
	
	
		
			7.1 KiB
		
	
	
	
		
			C++
		
	
			
		
		
	
	
			298 lines
		
	
	
		
			7.1 KiB
		
	
	
	
		
			C++
		
	
/*
 | 
						|
 * Unit tests for playlist_check_translate_song().
 | 
						|
 */
 | 
						|
 | 
						|
#include "MakeTag.hxx"
 | 
						|
#include "playlist/PlaylistSong.hxx"
 | 
						|
#include "song/DetachedSong.hxx"
 | 
						|
#include "SongLoader.hxx"
 | 
						|
#include "client/IClient.hxx"
 | 
						|
#include "tag/Builder.hxx"
 | 
						|
#include "tag/Names.hxx"
 | 
						|
#include "tag/Tag.hxx"
 | 
						|
#include "util/Domain.hxx"
 | 
						|
#include "fs/AllocatedPath.hxx"
 | 
						|
#include "ls.hxx"
 | 
						|
#include "db/Features.hxx" // for ENABLE_DATABASE
 | 
						|
#include "db/DatabaseSong.hxx"
 | 
						|
#include "storage/Registry.hxx"
 | 
						|
#include "storage/StorageInterface.hxx"
 | 
						|
#include "storage/plugins/LocalStorage.hxx"
 | 
						|
#include "Mapper.hxx"
 | 
						|
#include "time/ChronoUtil.hxx"
 | 
						|
 | 
						|
#include <gtest/gtest.h>
 | 
						|
 | 
						|
#include <string.h>
 | 
						|
#include <stdio.h>
 | 
						|
 | 
						|
bool
 | 
						|
uri_supported_scheme(const char *uri) noexcept
 | 
						|
{
 | 
						|
	return strncmp(uri, "http://", 7) == 0;
 | 
						|
}
 | 
						|
 | 
						|
const StoragePlugin *
 | 
						|
GetStoragePluginByUri(const char *) noexcept
 | 
						|
{
 | 
						|
	// dummy symbol
 | 
						|
	return nullptr;
 | 
						|
}
 | 
						|
 | 
						|
static constexpr auto music_directory = PATH_LITERAL("/music");
 | 
						|
static Storage *storage;
 | 
						|
 | 
						|
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";
 | 
						|
 | 
						|
DetachedSong
 | 
						|
DatabaseDetachSong([[maybe_unused]] const Database &db,
 | 
						|
		   [[maybe_unused]] const Storage *_storage,
 | 
						|
		   const char *uri)
 | 
						|
{
 | 
						|
	if (strcmp(uri, uri2) == 0)
 | 
						|
		return DetachedSong(uri, MakeTag2a());
 | 
						|
 | 
						|
	throw std::runtime_error("No such song");
 | 
						|
}
 | 
						|
 | 
						|
bool
 | 
						|
DetachedSong::LoadFile(Path path)
 | 
						|
{
 | 
						|
	if (path.ToUTF8() == uri1) {
 | 
						|
		SetTag(MakeTag1a());
 | 
						|
		return true;
 | 
						|
	}
 | 
						|
 | 
						|
	return false;
 | 
						|
}
 | 
						|
 | 
						|
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"};
 | 
						|
	}
 | 
						|
 | 
						|
#ifdef ENABLE_DATABASE
 | 
						|
	const Database *GetDatabase() const noexcept override {
 | 
						|
		return reinterpret_cast<const Database *>(this);
 | 
						|
	}
 | 
						|
 | 
						|
	Storage *GetStorage() const noexcept override {
 | 
						|
		return ::storage;
 | 
						|
	}
 | 
						|
#endif // ENABLE_DATABASE
 | 
						|
};
 | 
						|
 | 
						|
static std::string
 | 
						|
ToString(const Tag &tag)
 | 
						|
{
 | 
						|
	std::string result;
 | 
						|
 | 
						|
	if (!tag.duration.IsNegative())
 | 
						|
		result.append(std::to_string(tag.duration.ToMS()));
 | 
						|
 | 
						|
	for (const auto &item : tag) {
 | 
						|
		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('|');
 | 
						|
 | 
						|
	if (!IsNegative(song.GetLastModified()))
 | 
						|
		result.append(std::to_string(std::chrono::system_clock::to_time_t(song.GetLastModified())));
 | 
						|
 | 
						|
	result.push_back('|');
 | 
						|
 | 
						|
	if (song.GetStartTime().IsPositive())
 | 
						|
		result.append(std::to_string(song.GetStartTime().ToMS()));
 | 
						|
 | 
						|
	result.push_back('-');
 | 
						|
 | 
						|
	if (song.GetEndTime().IsPositive())
 | 
						|
		result.append(std::to_string(song.GetEndTime().ToMS()));
 | 
						|
 | 
						|
	result.push_back('|');
 | 
						|
 | 
						|
	result.append(ToString(song.GetTag()));
 | 
						|
 | 
						|
	return result;
 | 
						|
}
 | 
						|
 | 
						|
class TranslateSongTest : public ::testing::Test {
 | 
						|
	std::unique_ptr<Storage> _storage;
 | 
						|
 | 
						|
protected:
 | 
						|
	void SetUp() override {
 | 
						|
		_storage = CreateLocalStorage(Path::FromFS(music_directory));
 | 
						|
		storage = _storage.get();
 | 
						|
	}
 | 
						|
 | 
						|
	void TearDown() override {
 | 
						|
		_storage.reset();
 | 
						|
	}
 | 
						|
};
 | 
						|
 | 
						|
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));
 | 
						|
}
 | 
						|
 | 
						|
TEST_F(TranslateSongTest, Insecure)
 | 
						|
{
 | 
						|
	/* illegal because secure=false */
 | 
						|
	DetachedSong song1 (uri1);
 | 
						|
	TestClient client;
 | 
						|
	const SongLoader loader{client};
 | 
						|
	EXPECT_FALSE(playlist_check_translate_song(song1, {},
 | 
						|
						   loader));
 | 
						|
}
 | 
						|
 | 
						|
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));
 | 
						|
}
 | 
						|
 | 
						|
TEST_F(TranslateSongTest, InDatabase)
 | 
						|
{
 | 
						|
	const SongLoader loader(reinterpret_cast<const Database *>(1),
 | 
						|
				storage);
 | 
						|
 | 
						|
	DetachedSong song1("doesntexist");
 | 
						|
	EXPECT_FALSE(playlist_check_translate_song(song1, {},
 | 
						|
						   loader));
 | 
						|
 | 
						|
	DetachedSong song2(uri2, MakeTag2b());
 | 
						|
	auto se = ToString(DetachedSong(uri2, MakeTag2c()));
 | 
						|
	EXPECT_TRUE(playlist_check_translate_song(song2, {},
 | 
						|
						  loader));
 | 
						|
	EXPECT_EQ(se, ToString(song2));
 | 
						|
 | 
						|
	DetachedSong song3("/music/foo/bar.ogg", MakeTag2b());
 | 
						|
	se = ToString(DetachedSong(uri2, MakeTag2c()));
 | 
						|
	EXPECT_TRUE(playlist_check_translate_song(song3, {},
 | 
						|
						  loader));
 | 
						|
	EXPECT_EQ(se, ToString(song3));
 | 
						|
}
 | 
						|
 | 
						|
TEST_F(TranslateSongTest, Relative)
 | 
						|
{
 | 
						|
	const Database &db = *reinterpret_cast<const Database *>(1);
 | 
						|
	const SongLoader secure_loader(&db, storage);
 | 
						|
 | 
						|
	TestClient client;
 | 
						|
	const SongLoader insecure_loader{client, &db, storage};
 | 
						|
 | 
						|
	/* 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));
 | 
						|
}
 | 
						|
 | 
						|
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()));
 | 
						|
	EXPECT_TRUE(playlist_check_translate_song(song1, {},
 | 
						|
						  loader));
 | 
						|
	EXPECT_EQ(se, ToString(song1));
 | 
						|
#else
 | 
						|
	/* backslash only supported on Windows */
 | 
						|
	EXPECT_FALSE(playlist_check_translate_song(song1, {},
 | 
						|
						   loader));
 | 
						|
#endif
 | 
						|
}
 |