+100
-12
@@ -209,13 +209,12 @@ void expect_down_stem_connected(vamp_zero::score_layout_item const& head, vamp_z
|
||||
auto test_database_config(std::filesystem::path const& path) -> vamp_zero::database_config {
|
||||
return {
|
||||
.path = path.string(),
|
||||
.migrations_path = (std::filesystem::path{VAMP_TEST_SOURCE_DIR} / "package" / "migrations").string(),
|
||||
};
|
||||
}
|
||||
|
||||
auto run_migrate(vamp_zero::database_config config) -> std::expected<void, modulus::error> {
|
||||
auto run_ensure_database(vamp_zero::database_config config) -> std::expected<void, modulus::error> {
|
||||
auto io = boost::asio::io_context{};
|
||||
auto future = boost::asio::co_spawn(io, vamp_zero::async_migrate_database(io.get_executor(), std::move(config)), boost::asio::use_future);
|
||||
auto future = boost::asio::co_spawn(io, vamp_zero::async_ensure_database(io.get_executor(), std::move(config)), boost::asio::use_future);
|
||||
io.run();
|
||||
return future.get();
|
||||
}
|
||||
@@ -264,9 +263,14 @@ auto integer_value(noto::sqlite_value const& value) -> std::int64_t {
|
||||
return integer == nullptr ? 0 : *integer;
|
||||
}
|
||||
|
||||
auto has_value(noto::query_result const& result, std::size_t column, std::string_view expected) -> std::bool_t {
|
||||
return std::any_of(result.rows.begin(), result.rows.end(), [&](auto const& row) {
|
||||
return row.size() > column && text_value(row[column]) == expected;
|
||||
});
|
||||
}
|
||||
|
||||
auto score_asset_json(std::string title, std::string key = "C") -> std::string {
|
||||
return std::string{R"json({
|
||||
"version": 1,
|
||||
"metadata": {
|
||||
"title": ")json"} + title + R"json(",
|
||||
"composer": "Composer",
|
||||
@@ -318,7 +322,6 @@ void write_text(std::filesystem::path const& path, std::string const& text) {
|
||||
BOOST_AUTO_TEST_CASE(score_parser_preserves_notation_preamble) {
|
||||
auto parsed = vamp_zero::parse_score_document(R"json(
|
||||
{
|
||||
"version": 1,
|
||||
"metadata": {
|
||||
"title": "Standard Tune",
|
||||
"composer": "Composer",
|
||||
@@ -467,7 +470,7 @@ BOOST_AUTO_TEST_CASE(tied_rests_hidden_and_slash_notes_do_not_emit_accidentals)
|
||||
|
||||
BOOST_AUTO_TEST_CASE(pico_remote_display_parser_accepts_crlf_payloads) {
|
||||
auto state = vamp_pico::remote_display_state{};
|
||||
BOOST_REQUIRE(vamp_pico::parse_remote_display_body("version=1\r\nactive=1\r\ntitle=Autumn Leaves\r\npart=Vocal\r\nmode=score\r\npage=2\r\npages=11\r\n", state));
|
||||
BOOST_REQUIRE(vamp_pico::parse_remote_display_body("active=1\r\ntitle=Autumn Leaves\r\npart=Vocal\r\nmode=score\r\npage=2\r\npages=11\r\n", state));
|
||||
|
||||
BOOST_TEST(state.active);
|
||||
BOOST_TEST(std::strcmp(state.title, "Autumn Leaves") == 0);
|
||||
@@ -477,19 +480,17 @@ BOOST_AUTO_TEST_CASE(pico_remote_display_parser_accepts_crlf_payloads) {
|
||||
BOOST_TEST(state.pages == 11U);
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(pico_remote_display_parser_rejects_missing_version_and_bad_numbers) {
|
||||
BOOST_AUTO_TEST_CASE(pico_remote_display_parser_rejects_bad_numbers_and_preserves_state) {
|
||||
auto state = vamp_pico::remote_display_state{};
|
||||
std::snprintf(state.title, sizeof(state.title), "Keep");
|
||||
|
||||
BOOST_TEST(!vamp_pico::parse_remote_display_body("title=Autumn Leaves\npage=1\npages=2\n", state));
|
||||
BOOST_TEST(std::strcmp(state.title, "Keep") == 0);
|
||||
BOOST_TEST(!vamp_pico::parse_remote_display_body("version=1\npage=1x\npages=2\n", state));
|
||||
BOOST_TEST(!vamp_pico::parse_remote_display_body("page=1x\npages=2\n", state));
|
||||
BOOST_TEST(std::strcmp(state.title, "Keep") == 0);
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(pico_remote_display_parser_truncates_long_fields_safely) {
|
||||
auto state = vamp_pico::remote_display_state{};
|
||||
auto body = std::string{"version=1\ntitle="} + std::string(140U, 'A') + "\npart=" + std::string(80U, 'B') + "\nmode=performance-score";
|
||||
auto body = std::string{"title="} + std::string(140U, 'A') + "\npart=" + std::string(80U, 'B') + "\nmode=performance-score";
|
||||
|
||||
BOOST_REQUIRE(vamp_pico::parse_remote_display_body(body, state));
|
||||
BOOST_TEST(std::strlen(state.title) == sizeof(state.title) - 1U);
|
||||
@@ -497,6 +498,93 @@ BOOST_AUTO_TEST_CASE(pico_remote_display_parser_truncates_long_fields_safely) {
|
||||
BOOST_TEST(std::strcmp(state.mode, "performance-score") == 0);
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(database_ensure_creates_current_schema_without_version_bookkeeping) {
|
||||
auto root = std::filesystem::temp_directory_path()
|
||||
/ ("product-vamp-db-schema-test-" + std::to_string(std::chrono::steady_clock::now().time_since_epoch().count()));
|
||||
auto database_path = root / "vamp.db";
|
||||
std::filesystem::remove_all(root);
|
||||
|
||||
auto config = test_database_config(database_path);
|
||||
BOOST_REQUIRE(run_ensure_database(config));
|
||||
BOOST_REQUIRE(run_ensure_database(config));
|
||||
|
||||
auto tables = query_database(database_path, "SELECT name FROM sqlite_schema WHERE type = 'table' ORDER BY name");
|
||||
BOOST_REQUIRE(tables);
|
||||
BOOST_TEST(has_value(*tables, 0U, "songs"));
|
||||
BOOST_TEST(has_value(*tables, 0U, "practice_exercises"));
|
||||
BOOST_TEST(has_value(*tables, 0U, "page_state"));
|
||||
BOOST_TEST(!has_value(*tables, 0U, "_noto_schema_migrations"));
|
||||
|
||||
auto version = query_database(database_path, "PRAGMA user_version");
|
||||
BOOST_REQUIRE(version);
|
||||
BOOST_REQUIRE_EQUAL(version->rows.size(), 1U);
|
||||
BOOST_TEST(integer_value(version->rows[0][0]) == 0);
|
||||
|
||||
std::filesystem::remove_all(root);
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(database_ensure_updates_legacy_schema_in_place) {
|
||||
auto root = std::filesystem::temp_directory_path()
|
||||
/ ("product-vamp-db-legacy-test-" + std::to_string(std::chrono::steady_clock::now().time_since_epoch().count()));
|
||||
auto database_path = root / "vamp.db";
|
||||
std::filesystem::remove_all(root);
|
||||
std::filesystem::create_directories(root);
|
||||
|
||||
BOOST_REQUIRE(query_database(database_path, R"sql(
|
||||
CREATE TABLE songs (
|
||||
id TEXT PRIMARY KEY NOT NULL CHECK(length(trim(id)) BETWEEN 1 AND 120),
|
||||
title TEXT NOT NULL CHECK(length(trim(title)) BETWEEN 1 AND 200),
|
||||
composer TEXT NOT NULL DEFAULT '',
|
||||
source_json TEXT NOT NULL CHECK(json_valid(source_json)),
|
||||
created_at_ms INTEGER NOT NULL CHECK(created_at_ms >= 0),
|
||||
updated_at_ms INTEGER NOT NULL CHECK(updated_at_ms >= created_at_ms)
|
||||
) STRICT
|
||||
)sql"));
|
||||
BOOST_REQUIRE(query_database(database_path, R"sql(
|
||||
CREATE TABLE practice_exercises (
|
||||
id TEXT PRIMARY KEY NOT NULL CHECK(length(trim(id)) BETWEEN 1 AND 160),
|
||||
title TEXT NOT NULL CHECK(length(trim(title)) BETWEEN 1 AND 200),
|
||||
composer TEXT NOT NULL DEFAULT '',
|
||||
key_signature TEXT NOT NULL DEFAULT 'C',
|
||||
source_json TEXT NOT NULL CHECK(json_valid(source_json)),
|
||||
source_path TEXT NOT NULL DEFAULT '',
|
||||
created_at_ms INTEGER NOT NULL CHECK(created_at_ms >= 0),
|
||||
updated_at_ms INTEGER NOT NULL CHECK(updated_at_ms >= created_at_ms)
|
||||
) STRICT
|
||||
)sql"));
|
||||
BOOST_REQUIRE(query_database(database_path, "CREATE TABLE _noto_schema_migrations(id TEXT PRIMARY KEY NOT NULL) STRICT"));
|
||||
BOOST_REQUIRE(query_database(database_path, "PRAGMA user_version = 5"));
|
||||
|
||||
auto config = test_database_config(database_path);
|
||||
BOOST_REQUIRE(run_ensure_database(config));
|
||||
|
||||
auto song_columns = query_database(database_path, "PRAGMA table_info(songs)");
|
||||
BOOST_REQUIRE(song_columns);
|
||||
BOOST_TEST(has_value(*song_columns, 1U, "part"));
|
||||
BOOST_TEST(has_value(*song_columns, 1U, "source_path"));
|
||||
BOOST_TEST(has_value(*song_columns, 1U, "key_signature"));
|
||||
BOOST_TEST(has_value(*song_columns, 1U, "catalog_managed"));
|
||||
|
||||
auto practice_columns = query_database(database_path, "PRAGMA table_info(practice_exercises)");
|
||||
BOOST_REQUIRE(practice_columns);
|
||||
BOOST_TEST(has_value(*practice_columns, 1U, "practice_type_id"));
|
||||
BOOST_TEST(has_value(*practice_columns, 1U, "practice_type_title"));
|
||||
BOOST_TEST(has_value(*practice_columns, 1U, "lesson_group_id"));
|
||||
BOOST_TEST(has_value(*practice_columns, 1U, "lesson_group_title"));
|
||||
BOOST_TEST(has_value(*practice_columns, 1U, "catalog_managed"));
|
||||
|
||||
auto tables = query_database(database_path, "SELECT name FROM sqlite_schema WHERE type = 'table'");
|
||||
BOOST_REQUIRE(tables);
|
||||
BOOST_TEST(!has_value(*tables, 0U, "_noto_schema_migrations"));
|
||||
|
||||
auto version = query_database(database_path, "PRAGMA user_version");
|
||||
BOOST_REQUIRE(version);
|
||||
BOOST_REQUIRE_EQUAL(version->rows.size(), 1U);
|
||||
BOOST_TEST(integer_value(version->rows[0][0]) == 0);
|
||||
|
||||
std::filesystem::remove_all(root);
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(database_import_upserts_catalog_rows_and_keeps_unmanaged_rows) {
|
||||
auto root = std::filesystem::temp_directory_path()
|
||||
/ ("product-vamp-db-test-" + std::to_string(std::chrono::steady_clock::now().time_since_epoch().count()));
|
||||
@@ -507,7 +595,7 @@ BOOST_AUTO_TEST_CASE(database_import_upserts_catalog_rows_and_keeps_unmanaged_ro
|
||||
write_text(song_path, score_asset_json("Catalog Tune"));
|
||||
|
||||
auto config = test_database_config(database_path);
|
||||
BOOST_REQUIRE(run_migrate(config));
|
||||
BOOST_REQUIRE(run_ensure_database(config));
|
||||
BOOST_REQUIRE(run_import(config, assets));
|
||||
|
||||
auto first = query_database(database_path, "SELECT title, created_at_ms, updated_at_ms, catalog_managed FROM songs WHERE id = 'tune'");
|
||||
|
||||
Reference in New Issue
Block a user