From ea713ae9941fef25f266e4bd55b50cd09c8a3474 Mon Sep 17 00:00:00 2001 From: Kelsi Date: Wed, 6 May 2026 07:47:58 -0700 Subject: [PATCH] test(sql): add escape unit tests 5 tests covering: doubled single quotes (King's Land case), backslash escaping, ordinary text passthrough, control characters (NUL drop, CR/LF/tab/Ctrl-Z escape sequences), and combined escapes. Locks in the recent escape expansion that fixed the multi-line INSERT bug. --- tests/CMakeLists.txt | 20 +++++++++++++++++ tests/test_sql_escape.cpp | 45 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 65 insertions(+) create mode 100644 tests/test_sql_escape.cpp diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 7e53b4f1..a9fc9b77 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -414,6 +414,26 @@ target_link_libraries(test_open_formats PRIVATE catch2_main) add_test(NAME open_formats COMMAND test_open_formats) register_test_target(test_open_formats) +# ── test_sql_escape ────────────────────────────────────────── +add_executable(test_sql_escape + test_sql_escape.cpp + ${CMAKE_SOURCE_DIR}/tools/editor/sql_exporter.cpp + ${CMAKE_SOURCE_DIR}/tools/editor/npc_spawner.cpp + ${CMAKE_SOURCE_DIR}/tools/editor/quest_editor.cpp + ${CMAKE_SOURCE_DIR}/src/core/logger.cpp +) +target_include_directories(test_sql_escape PRIVATE + ${TEST_INCLUDE_DIRS} + ${CMAKE_SOURCE_DIR}/tools/editor +) +target_include_directories(test_sql_escape SYSTEM PRIVATE + ${TEST_SYSTEM_INCLUDE_DIRS} + ${CMAKE_SOURCE_DIR}/extern/nlohmann +) +target_link_libraries(test_sql_escape PRIVATE catch2_main) +add_test(NAME sql_escape COMMAND test_sql_escape) +register_test_target(test_sql_escape) + # ── ASAN / UBSan for test targets ──────────────────────────── if(WOWEE_ENABLE_ASAN AND NOT MSVC) foreach(_t IN LISTS ALL_TEST_TARGETS) diff --git a/tests/test_sql_escape.cpp b/tests/test_sql_escape.cpp new file mode 100644 index 00000000..f2f3ac3b --- /dev/null +++ b/tests/test_sql_escape.cpp @@ -0,0 +1,45 @@ +// Tests for SQLExporter::escape — ensures user-provided strings can't +// produce malformed SQL when emitted into INSERT statements. +#include +#include "sql_exporter.hpp" + +using namespace wowee::editor; + +TEST_CASE("SQLExporter::escape doubles single quotes", "[sql]") { + REQUIRE(SQLExporter::escape("King's Land") == "King''s Land"); + REQUIRE(SQLExporter::escape("''''") == "''''''''"); +} + +TEST_CASE("SQLExporter::escape escapes backslashes", "[sql]") { + REQUIRE(SQLExporter::escape("path\\to\\file") == "path\\\\to\\\\file"); +} + +TEST_CASE("SQLExporter::escape passes through ordinary text unchanged", "[sql]") { + REQUIRE(SQLExporter::escape("Hello, world!") == "Hello, world!"); + REQUIRE(SQLExporter::escape("") == ""); + REQUIRE(SQLExporter::escape("Some-Name_123") == "Some-Name_123"); +} + +TEST_CASE("SQLExporter::escape handles control characters", "[sql]") { + // NUL is dropped (some clients don't respect length-prefixed strings) + std::string withNul("a", 1); + withNul += '\0'; + withNul += 'b'; + REQUIRE(SQLExporter::escape(withNul) == "ab"); + + // Newlines/CR/tab become escape sequences so each INSERT stays on one line + REQUIRE(SQLExporter::escape("a\nb") == "a\\nb"); + REQUIRE(SQLExporter::escape("a\rb") == "a\\rb"); + REQUIRE(SQLExporter::escape("a\tb") == "a\\tb"); + + // Ctrl-Z (historical MySQL string terminator on Windows) + std::string withCtrlZ; + withCtrlZ += 'a'; + withCtrlZ += static_cast(26); + withCtrlZ += 'b'; + REQUIRE(SQLExporter::escape(withCtrlZ) == "a\\Zb"); +} + +TEST_CASE("SQLExporter::escape combines escapes correctly", "[sql]") { + REQUIRE(SQLExporter::escape("O'Brien\\path") == "O''Brien\\\\path"); +}