test_translate_song.cxx 7.6 KB
Newer Older
1 2 3 4 5
/*
 * Unit tests for playlist_check_translate_song().
 */

#include "config.h"
6
#include "playlist/PlaylistSong.hxx"
7
#include "DetachedSong.hxx"
8 9
#include "SongLoader.hxx"
#include "client/Client.hxx"
10
#include "tag/Builder.hxx"
11 12 13 14 15
#include "tag/Tag.hxx"
#include "util/Domain.hxx"
#include "fs/AllocatedPath.hxx"
#include "ls.hxx"
#include "Log.hxx"
Max Kellermann's avatar
Max Kellermann committed
16
#include "db/DatabaseSong.hxx"
17
#include "storage/plugins/LocalStorage.hxx"
18
#include "Mapper.hxx"
19
#include "util/ChronoUtil.hxx"
20 21 22 23 24 25 26 27 28 29

#include <cppunit/TestFixture.h>
#include <cppunit/extensions/TestFactoryRegistry.h>
#include <cppunit/ui/text/TestRunner.h>
#include <cppunit/extensions/HelperMacros.h>

#include <string.h>
#include <stdio.h>

void
Max Kellermann's avatar
Max Kellermann committed
30
Log(const Domain &domain, gcc_unused LogLevel level, const char *msg) noexcept
31 32 33 34 35
{
	fprintf(stderr, "[%s] %s\n", domain.GetName(), msg);
}

bool
36
uri_supported_scheme(const char *uri) noexcept
37
{
38
	return strncmp(uri, "http://", 7) == 0;
39 40
}

41
static constexpr auto music_directory = PATH_LITERAL("/music");
42
static Storage *storage;
43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110

static void
BuildTag(gcc_unused TagBuilder &tag)
{
}

template<typename... Args>
static void
BuildTag(TagBuilder &tag, TagType type, const char *value, Args&&... args)
{
	tag.AddItem(type, value);
	BuildTag(tag, std::forward<Args>(args)...);
}

template<typename... Args>
static Tag
MakeTag(Args&&... args)
{
	TagBuilder tag;
	BuildTag(tag, std::forward<Args>(args)...);
	return tag.Commit();
}

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";

111
DetachedSong
112
DatabaseDetachSong(gcc_unused const Database &db,
113
		   gcc_unused const Storage *_storage,
114
		   const char *uri)
115 116
{
	if (strcmp(uri, uri2) == 0)
117
		return DetachedSong(uri, MakeTag2a());
118

119
	throw std::runtime_error("No such song");
120 121 122
}

bool
123
DetachedSong::LoadFile(Path path)
124
{
125
	if (path.ToUTF8() == uri1) {
126 127 128 129 130 131 132
		SetTag(MakeTag1a());
		return true;
	}

	return false;
}

133
const Database *
134
Client::GetDatabase() const noexcept
135 136 137 138
{
	return reinterpret_cast<const Database *>(this);
}

139
const Storage *
140
Client::GetStorage() const noexcept
141
{
142
	return ::storage;
143 144
}

145 146
void
Client::AllowFile(gcc_unused Path path_fs) const
147
{
148
	/* always fail, so a SongLoader with a non-nullptr
149 150
	   Client pointer will be regarded "insecure", while one with
	   client==nullptr will allow all files */
151
	throw std::runtime_error("foo");
152 153
}

154 155 156
static std::string
ToString(const Tag &tag)
{
157
	std::string result;
158

159 160 161 162 163
	if (!tag.duration.IsNegative()) {
		char buffer[64];
		sprintf(buffer, "%d", tag.duration.ToMS());
		result.append(buffer);
	}
164

165
	for (const auto &item : tag) {
166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182
		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('|');

	char buffer[64];

183 184 185
	if (!IsNegative(song.GetLastModified())) {
		sprintf(buffer, "%lu",
			(unsigned long)std::chrono::system_clock::to_time_t(song.GetLastModified()));
186 187 188 189 190
		result.append(buffer);
	}

	result.push_back('|');

191 192
	if (song.GetStartTime().IsPositive()) {
		sprintf(buffer, "%u", song.GetStartTime().ToMS());
193 194 195 196 197
		result.append(buffer);
	}

	result.push_back('-');

198 199
	if (song.GetEndTime().IsPositive()) {
		sprintf(buffer, "%u", song.GetEndTime().ToMS());
200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219
		result.append(buffer);
	}

	result.push_back('|');

	result.append(ToString(song.GetTag()));

	return result;
}

class TranslateSongTest : public CppUnit::TestFixture {
	CPPUNIT_TEST_SUITE(TranslateSongTest);
	CPPUNIT_TEST(TestAbsoluteURI);
	CPPUNIT_TEST(TestInsecure);
	CPPUNIT_TEST(TestSecure);
	CPPUNIT_TEST(TestInDatabase);
	CPPUNIT_TEST(TestRelative);
	CPPUNIT_TEST_SUITE_END();

	void TestAbsoluteURI() {
220 221
		DetachedSong song1("http://example.com/foo.ogg");
		auto se = ToString(song1);
222
		const SongLoader loader(nullptr, nullptr);
223 224
		CPPUNIT_ASSERT(playlist_check_translate_song(song1, "/ignored",
							     loader));
225
		CPPUNIT_ASSERT_EQUAL(se, ToString(song1));
226 227 228 229
	}

	void TestInsecure() {
		/* illegal because secure=false */
230
		DetachedSong song1 (uri1);
231
		const SongLoader loader(*reinterpret_cast<const Client *>(1));
232 233
		CPPUNIT_ASSERT(!playlist_check_translate_song(song1, nullptr,
							      loader));
234 235 236
	}

	void TestSecure() {
237
		DetachedSong song1(uri1, MakeTag1b());
238 239
		auto s1 = ToString(song1);
		auto se = ToString(DetachedSong(uri1, MakeTag1c()));
240

241
		const SongLoader loader(nullptr, nullptr);
242 243
		CPPUNIT_ASSERT(playlist_check_translate_song(song1, "/ignored",
							     loader));
244
		CPPUNIT_ASSERT_EQUAL(se, ToString(song1));
245 246 247
	}

	void TestInDatabase() {
248
		const SongLoader loader(reinterpret_cast<const Database *>(1),
249
					storage);
250

251
		DetachedSong song1("doesntexist");
252 253
		CPPUNIT_ASSERT(!playlist_check_translate_song(song1, nullptr,
							      loader));
254

255 256
		DetachedSong song2(uri2, MakeTag2b());
		auto s1 = ToString(song2);
257
		auto se = ToString(DetachedSong(uri2, MakeTag2c()));
258 259
		CPPUNIT_ASSERT(playlist_check_translate_song(song2, nullptr,
							     loader));
260 261
		CPPUNIT_ASSERT_EQUAL(se, ToString(song2));

262 263
		DetachedSong song3("/music/foo/bar.ogg", MakeTag2b());
		s1 = ToString(song3);
264
		se = ToString(DetachedSong(uri2, MakeTag2c()));
265 266
		CPPUNIT_ASSERT(playlist_check_translate_song(song3, nullptr,
							     loader));
267
		CPPUNIT_ASSERT_EQUAL(se, ToString(song3));
268 269 270
	}

	void TestRelative() {
271
		const Database &db = *reinterpret_cast<const Database *>(1);
272
		const SongLoader secure_loader(&db, storage);
273
		const SongLoader insecure_loader(*reinterpret_cast<const Client *>(1),
274
						 &db, storage);
275

276
		/* map to music_directory */
277
		DetachedSong song1("bar.ogg", MakeTag2b());
278 279
		auto s1 = ToString(song1);
		auto se = ToString(DetachedSong(uri2, MakeTag2c()));
280 281
		CPPUNIT_ASSERT(playlist_check_translate_song(song1, "/music/foo",
							     insecure_loader));
282
		CPPUNIT_ASSERT_EQUAL(se, ToString(song1));
283 284

		/* illegal because secure=false */
285
		DetachedSong song2("bar.ogg", MakeTag2b());
286 287
		CPPUNIT_ASSERT(!playlist_check_translate_song(song1, "/foo",
							      insecure_loader));
288 289

		/* legal because secure=true */
290 291
		DetachedSong song3("bar.ogg", MakeTag1b());
		s1 = ToString(song3);
292
		se = ToString(DetachedSong(uri1, MakeTag1c()));
293 294
		CPPUNIT_ASSERT(playlist_check_translate_song(song3, "/foo",
							     secure_loader));
295
		CPPUNIT_ASSERT_EQUAL(se, ToString(song3));
296 297

		/* relative to http:// */
298 299
		DetachedSong song4("bar.ogg", MakeTag2a());
		s1 = ToString(song4);
300
		se = ToString(DetachedSong("http://example.com/foo/bar.ogg", MakeTag2a()));
301 302
		CPPUNIT_ASSERT(playlist_check_translate_song(song4, "http://example.com/foo",
							     insecure_loader));
303
		CPPUNIT_ASSERT_EQUAL(se, ToString(song4));
304 305 306 307 308 309 310 311
	}
};

CPPUNIT_TEST_SUITE_REGISTRATION(TranslateSongTest);

int
main(gcc_unused int argc, gcc_unused char **argv)
{
312 313
	storage = CreateLocalStorage(Path::FromFS(music_directory));

314 315 316 317 318
	CppUnit::TextUi::TestRunner runner;
	auto &registry = CppUnit::TestFactoryRegistry::getRegistry();
	runner.addTest(registry.makeTest());
	return runner.run() ? EXIT_SUCCESS : EXIT_FAILURE;
}