// Copyright (c) 2017-2024, Mudita Sp. z.o.o. All rights reserved. // For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md #include #include #include #include #include #include #include #include #include #include #include #include using namespace db::multimedia_files; const std::vector artists = {{""}, {"Just an artist"}, {"Mega artist"}, {"Super artist"}}; constexpr auto song1 = "Super song"; constexpr auto song2 = "Mega song"; constexpr auto song3 = "Just a song"; constexpr auto album1 = "Super album"; constexpr auto album2 = "Mega album"; constexpr auto album3 = "Just an album"; const std::vector albums = {{.artist = artists[0], .title = {}}, {.artist = artists[0], .title = album1}, {.artist = artists[1], .title = {}}, {.artist = artists[1], .title = album1}, {.artist = artists[1], .title = album2}, {.artist = artists[2], .title = album1}, {.artist = artists[2], .title = album3}, {.artist = artists[3], .title = album2}}; const std::vector records = { {Record{DB_ID_NONE}, .fileInfo = {.path = "user/audio/file1.mp3", .mediaType = "audio/mp3", .size = 100}, .tags = {.title = song1, .album = albums[3], .comment = "", .genre = "", .year = 2011, .track = 1}, .audioProperties = {.songLength = 300, .bitrate = 320, .sampleRate = 44100, .channels = 1}}, {Record{DB_ID_NONE}, .fileInfo = {.path = "user/audio/file2.mp3", .mediaType = "audio/mp3", .size = 100}, .tags = {.title = song2, .album = albums[3], .comment = "", .genre = "", .year = 2011, .track = 1}, .audioProperties = {.songLength = 300, .bitrate = 320, .sampleRate = 44100, .channels = 1}}, {Record{DB_ID_NONE}, .fileInfo = {.path = "user/audio/file3.mp3", .mediaType = "audio/mp3", .size = 100}, .tags = {.title = song2, .album = albums[4], .comment = "", .genre = "", .year = 2011, .track = 1}, .audioProperties = {.songLength = 300, .bitrate = 320, .sampleRate = 44100, .channels = 1}}, {Record{DB_ID_NONE}, .fileInfo = {.path = "user/audio/file4.mp3", .mediaType = "audio/mp3", .size = 100}, .tags = {.title = song2, .album = albums[5], .comment = "", .genre = "", .year = 2011, .track = 1}, .audioProperties = {.songLength = 300, .bitrate = 320, .sampleRate = 44100, .channels = 1}}, {Record{DB_ID_NONE}, .fileInfo = {.path = "user/audio/file5.mp3", .mediaType = "audio/mp3", .size = 100}, .tags = {.title = song3, .album = albums[0], .comment = "", .genre = "", .year = 2011, .track = 1}, .audioProperties = {.songLength = 300, .bitrate = 320, .sampleRate = 44100, .channels = 1}}, {Record{DB_ID_NONE}, .fileInfo = {.path = "user/audio/file6.mp3", .mediaType = "audio/mp3", .size = 100}, .tags = {.title = song3, .album = albums[7], .comment = "", .genre = "", .year = 2011, .track = 1}, .audioProperties = {.songLength = 300, .bitrate = 320, .sampleRate = 44100, .channels = 1}}, {Record{DB_ID_NONE}, .fileInfo = {.path = "user/audio/file7.mp3", .mediaType = "audio/mp3", .size = 100}, .tags = {.title = song3, .album = albums[5], .comment = "", .genre = "", .year = 2011, .track = 1}, .audioProperties = {.songLength = 300, .bitrate = 320, .sampleRate = 44100, .channels = 1}}, {Record{DB_ID_NONE}, .fileInfo = {.path = "user/file1.mp3", .mediaType = "audio/mp3", .size = 100}, .tags = {.title = song3, .album = albums[2], .comment = "", .genre = "", .year = 2011, .track = 1}, .audioProperties = {.songLength = 300, .bitrate = 320, .sampleRate = 44100, .channels = 1}}, {Record{DB_ID_NONE}, .fileInfo = {.path = "user/file2.mp3", .mediaType = "audio/mp3", .size = 100}, .tags = {.title = song3, .album = albums[1], .comment = "", .genre = "", .year = 2011, .track = 1}, .audioProperties = {.songLength = 300, .bitrate = 320, .sampleRate = 44100, .channels = 1}}, {Record{DB_ID_NONE}, .fileInfo = {.path = "user/file3.mp3", .mediaType = "audio/mp3", .size = 100}, .tags = {.title = song3, .album = albums[6], .comment = "", .genre = "", .year = 2011, .track = 1}, .audioProperties = {.songLength = 300, .bitrate = 320, .sampleRate = 44100, .channels = 1}}}; TEST_CASE("Multimedia DB tests") { db::tests::DatabaseUnderTest db{"multimedia.db", db::tests::getScriptsPath()}; MultimediaFilesRecordInterface multimediaFilesRecordInterface(&db.get()); constexpr auto PageSize = 8; SECTION("Table") { SECTION("TableRow") { auto record = TableRow{}; REQUIRE(!record.isValid()); record.ID = 1; record.fileInfo.path = "audio"; REQUIRE(record.isValid()); } SECTION("Empty") { REQUIRE(db.get().files.count() == 0); REQUIRE(db.get().files.getLimitOffset(0, PageSize).empty()); } SECTION("Add, get and remove") { const auto path = "audio/user"; TableRow record; record.fileInfo.path = path; REQUIRE(!record.isValid()); REQUIRE(db.get().files.add(record)); REQUIRE(db.get().files.count() == 1); auto result = db.get().files.getById(1); REQUIRE(result.isValid()); SECTION("Remove by ID") { REQUIRE(db.get().files.removeById(1)); REQUIRE(db.get().files.count() == 0); auto result = db.get().files.getById(1); REQUIRE(!result.isValid()); } SECTION("Remove by field") { REQUIRE(db.get().files.removeByField(TableFields::path, path)); REQUIRE(db.get().files.count() == 0); auto result = db.get().files.getById(1); REQUIRE(!result.isValid()); } } for (const auto &record : records) { REQUIRE(db.get().files.add(record)); } SECTION("Remove all") { REQUIRE(db.get().files.count() == records.size()); REQUIRE(db.get().files.removeAll()); REQUIRE(db.get().files.count() == 0); } SECTION("Add for existing path") { auto resultPre = db.get().files.getById(2); resultPre.fileInfo.mediaType = "bla bla"; REQUIRE(db.get().files.add(resultPre)); auto resultPost = db.get().files.getById(2); REQUIRE((resultPre.ID == resultPost.ID && resultPre.fileInfo.mediaType == resultPost.fileInfo.mediaType)); } SECTION("Update") { auto resultPre = db.get().files.getById(2); resultPre.fileInfo.mediaType = "bla bla"; REQUIRE(db.get().files.update(resultPre)); auto resultPost = db.get().files.getById(2); REQUIRE((resultPre.ID == resultPost.ID && resultPre.fileInfo.mediaType == resultPost.fileInfo.mediaType)); } SECTION("Add or Update") { REQUIRE(db.get().files.removeAll()); REQUIRE(db.get().files.count() == 0); for (const auto &record : records) { REQUIRE(db.get().files.addOrUpdate(record)); } REQUIRE(db.get().files.count() == records.size()); auto oldPath = records[1].fileInfo.path; auto resultPre = db.get().files.getByPath(oldPath); const auto referenceID = resultPre.ID; resultPre.ID = 10; SECTION("No changes") { REQUIRE(db.get().files.addOrUpdate(resultPre)); REQUIRE(db.get().files.count() == records.size()); auto resultPost = db.get().files.getByPath(oldPath); REQUIRE(resultPost.isValid()); REQUIRE((resultPost.ID == referenceID && resultPre.fileInfo.path == resultPost.fileInfo.path)); } SECTION("change path") { auto newPath = "user/newPath"; resultPre.fileInfo.path = newPath; REQUIRE(db.get().files.addOrUpdate(resultPre, oldPath)); REQUIRE(db.get().files.count() == records.size()); auto notExistingEntry = db.get().files.getByPath(oldPath); REQUIRE(!notExistingEntry.isValid()); auto resultPost = db.get().files.getByPath(newPath); REQUIRE(resultPost.isValid()); REQUIRE((resultPost.ID == referenceID && resultPre.fileInfo.path == resultPost.fileInfo.path)); } SECTION("change any field") { resultPre.fileInfo.mediaType = "bla bla"; REQUIRE(db.get().files.addOrUpdate(resultPre)); auto resultPost = db.get().files.getByPath(oldPath); REQUIRE(resultPost.isValid()); REQUIRE((resultPost.ID == referenceID && resultPre.fileInfo.path == resultPost.fileInfo.path && resultPre.fileInfo.mediaType == resultPost.fileInfo.mediaType)); } } SECTION("getLimitOffset") { auto size = records.size(); REQUIRE(db.get().files.getLimitOffset(0, size - 3).size() == size - 3); REQUIRE(db.get().files.getLimitOffset(size - 3, size).size() == 3); } SECTION("getLimitOffsetByField") { auto size = records.size(); REQUIRE(db.get().files.getLimitOffsetByField(0, size, TableFields::artist, artists[1].c_str()).size() == 4); } SECTION("Artists") { REQUIRE(db.get().files.countArtists() == artists.size()); auto artistsList = db.get().files.getArtistsLimitOffset(0, artists.size()); REQUIRE(artistsList.size() == artists.size()); for (size_t i = 0; i < artists.size(); i++) { REQUIRE(artistsList[i] == artists[i]); } SECTION("getLimitOffset") { const auto size = artists.size(); constexpr auto reminder = 2; const auto pageSize = size - reminder; REQUIRE(db.get().files.getLimitOffset(0, pageSize).size() == pageSize); REQUIRE(db.get().files.getLimitOffset(pageSize, pageSize).size() == reminder); } } const auto numberOfAlbums = albums.size(); SECTION("Albums") { REQUIRE(db.get().files.countAlbums() == numberOfAlbums); auto albumsList = db.get().files.getAlbumsLimitOffset(0, numberOfAlbums); REQUIRE(albumsList.size() == numberOfAlbums); for (const auto &album : albumsList) { auto it = std::find_if(albums.begin(), albums.end(), [album](const Album &albumRef) { return album.artist == albumRef.artist && album.title == albumRef.title; }); REQUIRE(it != albums.end()); } SECTION("getLimitOffset") { const auto size = numberOfAlbums; constexpr auto reminder = 2; const auto pageSize = size - reminder; REQUIRE(db.get().files.getAlbumsLimitOffset(0, pageSize).size() == pageSize); REQUIRE(db.get().files.getAlbumsLimitOffset(pageSize, pageSize).size() == reminder); } } SECTION("Get songs for artist") { for (const auto &artist : artists) { size_t size = std::count_if(records.begin(), records.end(), [artist](const TableRow &record) { return record.tags.album.artist == artist; }); REQUIRE(db.get().files.count(artist) == size); auto artistsList = db.get().files.getLimitOffset(artist, 0, records.size()); REQUIRE(artistsList.size() == size); for (size_t i = 0; i < artistsList.size(); i++) { REQUIRE(artistsList[i].tags.album.artist == artist); } } } SECTION("Get songs from album") { for (const auto &album : albums) { size_t size = std::count_if(records.begin(), records.end(), [album](const TableRow &record) { return record.tags.album.artist == album.artist && record.tags.album.title == album.title; }); REQUIRE(db.get().files.count(album) == size); auto artistsList = db.get().files.getLimitOffset(album, 0, records.size()); REQUIRE(artistsList.size() == size); for (size_t i = 0; i < artistsList.size(); i++) { REQUIRE((artistsList[i].tags.album.artist == album.artist && artistsList[i].tags.album.title == album.title)); } } } } SECTION("Queries") { auto addQuery = [&](const MultimediaFilesRecord &record) { auto query = std::make_shared(record); auto ret = multimediaFilesRecordInterface.runQuery(query); auto result = dynamic_cast(ret.get()); REQUIRE(result != nullptr); REQUIRE(result->getResult()); }; auto addOrUpdateQuery = [&](const MultimediaFilesRecord &record, std::string oldPath = "") { auto query = std::make_shared(record, oldPath); auto ret = multimediaFilesRecordInterface.runQuery(query); auto result = dynamic_cast(ret.get()); REQUIRE(result != nullptr); REQUIRE(result->getResult()); }; auto getQuery = [&](uint32_t id) { auto query = std::make_shared(id); auto ret = multimediaFilesRecordInterface.runQuery(query); auto result = dynamic_cast(ret.get()); REQUIRE(result != nullptr); return result->getResult(); }; auto getByPathQuery = [&](std::string path) { auto query = std::make_shared(path); auto ret = multimediaFilesRecordInterface.runQuery(query); auto result = dynamic_cast(ret.get()); REQUIRE(result != nullptr); return result->getResult(); }; auto getLimitedQuery = [&](const uint32_t offset, const uint32_t limit) { auto query = std::make_shared(offset, limit); auto ret = multimediaFilesRecordInterface.runQuery(query); auto result = dynamic_cast(ret.get()); REQUIRE(result != nullptr); auto record = result->getResult(); return record; }; auto getLimitedByPathsQuery = [&](const std::vector &paths, const uint32_t offset, const uint32_t limit) { auto query = std::make_shared(paths, offset, limit); auto ret = multimediaFilesRecordInterface.runQuery(query); auto result = dynamic_cast(ret.get()); REQUIRE(result != nullptr); auto record = result->getResult(); return record; }; auto updateQuery = [&](const MultimediaFilesRecord &record) { const auto query = std::make_shared(record); const auto ret = multimediaFilesRecordInterface.runQuery(query); const auto result = dynamic_cast(ret.get()); REQUIRE(result != nullptr); REQUIRE(result->getResult()); }; auto getCountQuery = [&]() { const auto query = std::make_shared(); const auto ret = multimediaFilesRecordInterface.runQuery(query); const auto result = dynamic_cast(ret.get()); REQUIRE(result != nullptr); return result->getCount(); }; auto removeQuery = [&](uint32_t id) { const auto query = std::make_shared(id); const auto ret = multimediaFilesRecordInterface.runQuery(query); const auto result = dynamic_cast(ret.get()); REQUIRE(result != nullptr); REQUIRE(result->getResult()); }; auto removeByPathQuery = [&](const std::string &path) { const auto query = std::make_shared(path); const auto ret = multimediaFilesRecordInterface.runQuery(query); const auto result = dynamic_cast(ret.get()); REQUIRE(result != nullptr); REQUIRE(result->getResult()); }; auto removeAllQuery = [&]() { const auto query = std::make_shared(); const auto ret = multimediaFilesRecordInterface.runQuery(query); const auto result = dynamic_cast(ret.get()); REQUIRE(result != nullptr); REQUIRE(result->getResult()); }; auto getCountArtistsQuery = [&]() { const auto query = std::make_shared(); const auto ret = multimediaFilesRecordInterface.runQuery(query); const auto result = dynamic_cast(ret.get()); REQUIRE(result != nullptr); return result->getCount(); }; auto getArtistsLimitedQuery = [&](const uint32_t offset, const uint32_t limit) { auto query = std::make_shared(offset, limit); auto ret = multimediaFilesRecordInterface.runQuery(query); auto result = dynamic_cast(ret.get()); REQUIRE(result != nullptr); auto record = result->getResult(); return record; }; auto getCountAlbumsQuery = [&]() { const auto query = std::make_shared(); const auto ret = multimediaFilesRecordInterface.runQuery(query); const auto result = dynamic_cast(ret.get()); REQUIRE(result != nullptr); return result->getCount(); }; auto getAlbumsLimitedQuery = [&](const uint32_t offset, const uint32_t limit) { auto query = std::make_shared(offset, limit); auto ret = multimediaFilesRecordInterface.runQuery(query); auto result = dynamic_cast(ret.get()); REQUIRE(result != nullptr); auto record = result->getResult(); return record; }; auto getLimitedQueryForArtist = [&](const Artist &artist, const uint32_t offset, const uint32_t limit) { auto query = std::make_shared(artist, offset, limit); auto ret = multimediaFilesRecordInterface.runQuery(query); auto result = dynamic_cast(ret.get()); REQUIRE(result != nullptr); auto record = result->getResult(); return record; }; auto getLimitedQueryForAlbum = [&](const Album &album, const uint32_t offset, const uint32_t limit) { auto query = std::make_shared(album, offset, limit); auto ret = multimediaFilesRecordInterface.runQuery(query); auto result = dynamic_cast(ret.get()); REQUIRE(result != nullptr); auto record = result->getResult(); return record; }; auto getCountQueryForArtist = [&](const Artist &artist) { const auto query = std::make_shared(artist); const auto ret = multimediaFilesRecordInterface.runQuery(query); const auto result = dynamic_cast(ret.get()); REQUIRE(result != nullptr); return result->getCount(); }; auto getCountQueryForAlbum = [&](const Album &album) { const auto query = std::make_shared(album); const auto ret = multimediaFilesRecordInterface.runQuery(query); const auto result = dynamic_cast(ret.get()); REQUIRE(result != nullptr); return result->getCount(); }; auto getOffsetQuery = [&](const std::string &folderPath, const std::string &recordPath, SortingBy sorting) { auto query = std::make_shared(folderPath, recordPath, sorting); auto ret = multimediaFilesRecordInterface.runQuery(query); auto result = dynamic_cast(ret.get()); REQUIRE(result != nullptr); auto record = result->getResult(); return record; }; SECTION("add, get, remove query") { const auto path = "audio/user"; MultimediaFilesRecord record; record.fileInfo.path = path; REQUIRE(!record.isValid()); addQuery(record); REQUIRE(getCountQuery() == 1); auto result = getQuery(1); REQUIRE(result.isValid()); SECTION("Remove by ID") { removeQuery(1); REQUIRE(getCountQuery() == 0); auto result = getQuery(1); REQUIRE(!result.isValid()); } SECTION("Remove by Path") { removeByPathQuery(path); REQUIRE(getCountQuery() == 0); auto result = getQuery(1); REQUIRE(!result.isValid()); } } for (const auto &record : records) { addQuery(record); } SECTION("Remove all") { REQUIRE(getCountQuery() == records.size()); removeAllQuery(); REQUIRE(getCountQuery() == 0); } SECTION("Add for existing path") { auto resultPre = getQuery(2); resultPre.fileInfo.mediaType = "bla bla"; addQuery(resultPre); auto resultPost = getQuery(2); REQUIRE((resultPre.ID == resultPost.ID && resultPre.fileInfo.mediaType == resultPost.fileInfo.mediaType)); } SECTION("Update") { auto resultPre = getQuery(2); resultPre.fileInfo.mediaType = "bla bla"; updateQuery(resultPre); auto resultPost = getQuery(2); REQUIRE((resultPre.ID == resultPost.ID && resultPre.fileInfo.mediaType == resultPost.fileInfo.mediaType)); } SECTION("Add or Update") { removeAllQuery(); REQUIRE(getCountQuery() == 0); for (const auto &record : records) { addOrUpdateQuery(record); } REQUIRE(getCountQuery() == records.size()); auto oldPath = records[1].fileInfo.path; auto resultPre = getByPathQuery(oldPath); const auto referenceID = resultPre.ID; resultPre.ID = 10; SECTION("No changes") { addOrUpdateQuery(resultPre); REQUIRE(getCountQuery() == records.size()); auto resultPost = getByPathQuery(oldPath); REQUIRE(resultPost.isValid()); REQUIRE((resultPost.ID == referenceID && resultPre.fileInfo.path == resultPost.fileInfo.path)); } SECTION("change path") { auto newPath = "user/newPath"; resultPre.fileInfo.path = newPath; addOrUpdateQuery(resultPre, oldPath); REQUIRE(getCountQuery() == records.size()); auto notExistingEntry = getByPathQuery(oldPath); REQUIRE(!notExistingEntry.isValid()); auto resultPost = getByPathQuery(newPath); REQUIRE(resultPost.isValid()); REQUIRE((resultPost.ID == referenceID && resultPre.fileInfo.path == resultPost.fileInfo.path)); } SECTION("change any field") { resultPre.fileInfo.mediaType = "bla bla"; addOrUpdateQuery(resultPre); auto resultPost = getByPathQuery(oldPath); REQUIRE(resultPost.isValid()); REQUIRE((resultPost.ID == referenceID && resultPre.fileInfo.path == resultPost.fileInfo.path && resultPre.fileInfo.mediaType == resultPost.fileInfo.mediaType)); } } SECTION("getLimitOffset") { auto size = records.size(); REQUIRE(getLimitedQuery(0, size - 3).size() == size - 3); REQUIRE(getLimitedQuery(size - 3, size).size() == 3); } SECTION("getLimitOffsetByPaths") { MultimediaFilesRecord entry; const auto user_count = records.size(); const auto user_audio_count = user_count - 3; REQUIRE(getLimitedByPathsQuery({"user/"}, 0, UINT32_MAX).size() == user_count); REQUIRE(getLimitedByPathsQuery({"user/audio/"}, 0, UINT32_MAX).size() == user_audio_count); REQUIRE(getLimitedByPathsQuery({"user/audio1/"}, 0, UINT32_MAX).size() == 0); REQUIRE(getLimitedByPathsQuery({"user/audio2"}, 0, UINT32_MAX).size() == 0); REQUIRE(getLimitedByPathsQuery({"abc/"}, 0, UINT32_MAX).size() == 0); entry.fileInfo.path = "assets/audio/file30.mp3"; addQuery(entry); entry.fileInfo.path = "assets/audio/file31.mp3"; addQuery(entry); REQUIRE(getLimitedByPathsQuery({"user/audio", "assets/audio"}, 0, UINT32_MAX).size() == user_audio_count + 2); REQUIRE(getLimitedByPathsQuery({"user/audio", "assets/audio", "non/existing/path"}, 0, UINT32_MAX).size() == user_audio_count + 2); entry.fileInfo.path = "assets/audio/app/file32.mp3"; addQuery(entry); REQUIRE(getLimitedByPathsQuery({"user/audio", "assets/audio/app"}, 0, UINT32_MAX).size() == user_audio_count + 1); } SECTION("Artists") { REQUIRE(getCountArtistsQuery() == artists.size()); auto artistsList = getArtistsLimitedQuery(0, artists.size()); REQUIRE(artistsList.size() == artists.size()); for (size_t i = 0; i < artists.size(); i++) { REQUIRE(artistsList[i] == artists[i]); } SECTION("getLimitOffset") { const auto size = artists.size(); constexpr auto reminder = 2; const auto pageSize = size - reminder; REQUIRE(getArtistsLimitedQuery(0, pageSize).size() == pageSize); REQUIRE(getArtistsLimitedQuery(pageSize, pageSize).size() == reminder); } } const auto numberOfAlbums = albums.size(); SECTION("Albums") { REQUIRE(getCountAlbumsQuery() == numberOfAlbums); auto albumsList = getAlbumsLimitedQuery(0, numberOfAlbums); REQUIRE(albumsList.size() == numberOfAlbums); for (const auto &album : albumsList) { auto it = std::find_if(albums.begin(), albums.end(), [album](const Album &albumRef) { return album.artist == albumRef.artist && album.title == albumRef.title; }); REQUIRE(it != albums.end()); } SECTION("getLimitOffset") { const auto size = numberOfAlbums; constexpr auto reminder = 2; const auto pageSize = size - reminder; REQUIRE(db.get().files.getAlbumsLimitOffset(0, pageSize).size() == pageSize); REQUIRE(db.get().files.getAlbumsLimitOffset(pageSize, pageSize).size() == reminder); } } SECTION("Get songs for artist") { for (const auto &artist : artists) { size_t size = std::count_if(records.begin(), records.end(), [artist](const TableRow &record) { return record.tags.album.artist == artist; }); REQUIRE(getCountQueryForArtist(artist) == size); auto artistsList = getLimitedQueryForArtist(artist, 0, records.size()); REQUIRE(artistsList.size() == size); for (size_t i = 0; i < artistsList.size(); i++) { REQUIRE(artistsList[i].tags.album.artist == artist); } } } SECTION("Get songs from album") { for (const auto &album : albums) { size_t size = std::count_if(records.begin(), records.end(), [album](const TableRow &record) { return record.tags.album.artist == album.artist && record.tags.album.title == album.title; }); REQUIRE(getCountQueryForAlbum(album) == size); auto artistsList = getLimitedQueryForAlbum(album, 0, records.size()); REQUIRE(artistsList.size() == size); for (size_t i = 0; i < artistsList.size(); i++) { REQUIRE((artistsList[i].tags.album.artist == album.artist && artistsList[i].tags.album.title == album.title)); } } } SECTION("Get offset by path") { REQUIRE(getOffsetQuery("user/audio", "user/audio/file1.mp3", SortingBy::TrackIdAscending).offset == 1); REQUIRE(getOffsetQuery("user/audio", "user/audio/file4.mp3", SortingBy::TrackIdAscending).offset == 4); REQUIRE(getOffsetQuery("user", "user/audio/file7.mp3", SortingBy::TrackIdAscending).offset == 7); REQUIRE(getOffsetQuery("user", "user/file3.mp3", SortingBy::TrackIdAscending).offset == 10); } } }