Files
product-vamp/tests/test_portal_artwork.cpp
T
khodowany 9fd5abd075
ci / checks (push) Successful in 1h16m10s
Rhythm Selection & Cleanup
2026-09-26 02:50:22 -05:00

139 lines
6.3 KiB
C++

#include <boost/test/unit_test.hpp>
#include <array>
#include <chrono>
#include <filesystem>
#include <fstream>
#include <future>
#include <memory>
#include <string>
#include <boost/asio/co_spawn.hpp>
#include <boost/asio/io_context.hpp>
#include <boost/asio/use_future.hpp>
#include <boost/json/object.hpp>
#include <openssl/sha.h>
#include <vamp_pico/http_client.hpp>
#include <vamp_zero/portal.hpp>
namespace {
struct artwork_fixture {
std::filesystem::path root{std::filesystem::temp_directory_path() / ("vamp-title-test-" + std::to_string(std::chrono::steady_clock::now().time_since_epoch().count()))};
std::string bytes = std::string(vamp_protocol::title_image_size, static_cast<std::char_t>(0xa3));
std::string identifier{};
artwork_fixture() {
bytes.replace(0U, 8U, "VPTI\x28\x01\x40\x00", 8U);
auto digest = std::array<std::uchar_t, SHA256_DIGEST_LENGTH>{};
SHA256(reinterpret_cast<std::uchar_t const *>(bytes.data()), bytes.size(), digest.data());
for (auto byte : digest) {
identifier.push_back("0123456789abcdef"[byte >> 4U]);
identifier.push_back("0123456789abcdef"[byte & 15U]);
}
std::filesystem::create_directories(root / "pico/titles");
write(bytes);
}
~artwork_fixture() {
auto error = std::error_code{};
std::filesystem::remove_all(root, error);
}
auto path() const -> std::filesystem::path {
return root / "pico/titles" / (identifier + ".mask");
}
auto write(std::string const &value) -> void {
auto stream = std::ofstream{path(), std::ios::binary};
stream.write(value.data(), static_cast<std::streamsize>(value.size()));
}
auto config() const -> vamp_zero::config {
auto value = vamp_zero::config{};
value.runtime.assets_path = root.string();
return value;
}
auto state() const -> vamp_zero::app_state {
auto catalog = std::make_shared<vamp_zero::catalog>();
catalog->performance.parts.push_back({.id = "part", .title = "Title", .part = "Acoustic", .total_pages = 10000U, .title_image = identifier});
auto value = vamp_zero::app_state{};
value.catalog = catalog;
value.mode = vamp_zero::app_mode::PERFORMANCE_SCORE;
value.active_part_id = "part";
value.page_index = 9999U;
return value;
}
};
auto call(ligo::service_router const &router, std::string method, std::string route, boost::json::value payload = {})
-> std::expected<ligo::service_response, modulus::error> {
auto io = boost::asio::io_context{};
auto future = boost::asio::co_spawn(io, router.call_response({.method = std::move(method), .route = std::move(route)}, std::move(payload)), boost::asio::use_future);
io.run();
return future.get();
}
}
BOOST_FIXTURE_TEST_CASE(portal_title_route_serves_registered_binary_and_remote_identity, artwork_fixture) {
auto control = vamp_zero::control_state{config(), state()};
auto router = vamp_zero::make_router(control);
auto status = call(router, "GET", vamp_protocol::remote_display_path);
BOOST_REQUIRE(status);
auto remote = vamp_pico::remote_display_state{};
BOOST_REQUIRE(vamp_pico::parse_remote_display_body(status->body, remote));
BOOST_TEST(std::string{remote.title_image.data()} == identifier);
BOOST_TEST(remote.page == 10000U);
BOOST_TEST(remote.pages == 10000U);
auto response = call(router, "POST", vamp_protocol::remote_title_path, boost::json::object{{"id", identifier}});
BOOST_REQUIRE(response);
BOOST_TEST(response->content_type == "application/octet-stream");
BOOST_TEST(response->body == bytes);
auto validated = vamp_zero::validated_title_path(root.string(), identifier);
BOOST_REQUIRE(validated);
BOOST_TEST(*validated == path().string());
BOOST_CHECK(!call(router, "GET", vamp_protocol::remote_title_path, boost::json::object{{"id", identifier}}));
}
BOOST_FIXTURE_TEST_CASE(portal_title_route_rejects_arbitrary_unregistered_and_corrupt_assets, artwork_fixture) {
auto control = vamp_zero::control_state{config(), state()};
auto router = vamp_zero::make_router(control);
for (auto const &id : {std::string{"../title"}, std::string(64U, 'f'), std::string(64U, 'A'), identifier + "a"}) {
BOOST_CHECK(!call(router, "POST", vamp_protocol::remote_title_path, boost::json::object{{"id", id}}));
}
BOOST_CHECK(!call(router, "POST", vamp_protocol::remote_title_path, boost::json::object{{"id", identifier}, {"path", path().string()}}));
BOOST_CHECK(!call(router, "POST", vamp_protocol::remote_title_path, boost::json::object{{"id", 1}}));
BOOST_CHECK(!call(router, "POST", vamp_protocol::remote_title_path));
auto empty = vamp_zero::control_state{config(), {}};
BOOST_CHECK(!call(vamp_zero::make_router(empty), "POST", vamp_protocol::remote_title_path, boost::json::object{{"id", identifier}}));
for (auto const &corrupt : {bytes.substr(1U), bytes + "x", std::string(bytes.size(), 'x')}) {
write(corrupt);
BOOST_CHECK(!vamp_zero::validated_title_path(root.string(), identifier));
BOOST_CHECK(!call(router, "POST", vamp_protocol::remote_title_path, boost::json::object{{"id", identifier}}));
}
auto modified = bytes;
modified.back() ^= 1;
write(modified);
BOOST_CHECK(!vamp_zero::validated_title_path(root.string(), identifier));
BOOST_CHECK(!call(router, "POST", vamp_protocol::remote_title_path, boost::json::object{{"id", identifier}}));
}
BOOST_FIXTURE_TEST_CASE(portal_title_validation_rejects_symlink_outside_title_directory, artwork_fixture) {
auto other = root / "other.mask";
std::filesystem::rename(path(), other);
std::filesystem::create_symlink(other, path());
BOOST_CHECK(!vamp_zero::validated_title_path(root.string(), identifier));
}
BOOST_FIXTURE_TEST_CASE(portal_uses_and_serves_only_the_packaged_petaluma_font, artwork_fixture) {
std::filesystem::create_directories(root / "fonts/petaluma");
std::filesystem::copy_file(std::filesystem::path{VAMP_TEST_SOURCE_DIR} / "package/assets/fonts/petaluma/PetalumaScript.otf", root / "fonts/petaluma/PetalumaScript.otf");
auto control = vamp_zero::control_state{config(), {}};
auto router = vamp_zero::make_router(control);
auto html = call(router, "GET", "/");
BOOST_REQUIRE(html);
BOOST_CHECK(html->body.find("font-family:PetalumaScript") != std::string::npos);
auto font = call(router, "GET", "/assets/PetalumaScript.otf");
BOOST_REQUIRE(font);
BOOST_TEST(font->content_type == "font/otf");
BOOST_TEST(font->body.substr(0U, 4U) == "OTTO");
BOOST_CHECK(!call(router, "GET", "/assets/other.otf"));
}