UI
This commit is contained in:
@@ -0,0 +1,134 @@
|
||||
#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/control.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->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(control_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(control_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(control_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(control_has_no_browser_remote_or_catalog_dump, artwork_fixture) {
|
||||
auto control = vamp_zero::control_state{config(), {}};
|
||||
auto router = vamp_zero::make_router(control);
|
||||
BOOST_CHECK(!call(router, "GET", "/"));
|
||||
BOOST_CHECK(!call(router, "GET", "/api/status"));
|
||||
BOOST_CHECK(!call(router, "GET", "/assets/PetalumaScript.otf"));
|
||||
BOOST_CHECK(!call(router, "GET", "/assets/other.otf"));
|
||||
BOOST_REQUIRE(call(router, "GET", "/api/health"));
|
||||
BOOST_REQUIRE(call(router, "GET", vamp_protocol::remote_display_path));
|
||||
}
|
||||
Reference in New Issue
Block a user