From 5dbabc256107115a36258dfdeb3ebe5d1fbf197d Mon Sep 17 00:00:00 2001 From: raphasampaio Date: Sun, 18 Jan 2026 15:15:19 -0300 Subject: [PATCH 1/2] update --- tests/test_c_api_database_lifecycle.cpp | 220 ++++++++++++++++++++++++ tests/test_database_errors.cpp | 144 ++++++++++++++++ tests/test_element.cpp | 134 +++++++++++++++ tests/test_lua_runner.cpp | 121 +++++++++++++ tests/test_schema_validator.cpp | 133 ++++++++++++++ 5 files changed, 752 insertions(+) diff --git a/tests/test_c_api_database_lifecycle.cpp b/tests/test_c_api_database_lifecycle.cpp index e61b246..efddf8d 100644 --- a/tests/test_c_api_database_lifecycle.cpp +++ b/tests/test_c_api_database_lifecycle.cpp @@ -419,3 +419,223 @@ TEST_F(TempFileFixture, ReadScalarRelationValid) { psr_free_string_array(values, count); psr_database_close(db); } + +// ============================================================================ +// Additional error handling tests +// ============================================================================ + +TEST_F(TempFileFixture, CreateElementInNonExistentCollection) { + auto options = psr_database_options_default(); + options.console_level = PSR_LOG_OFF; + auto db = psr_database_from_schema(":memory:", VALID_SCHEMA("basic.sql").c_str(), &options); + ASSERT_NE(db, nullptr); + + // Try to create element in non-existent collection - should fail + auto element = psr_element_create(); + psr_element_set_string(element, "label", "Test"); + auto id = psr_database_create_element(db, "NonexistentCollection", element); + psr_element_destroy(element); + + EXPECT_EQ(id, -1); + + psr_database_close(db); +} + +TEST_F(TempFileFixture, OpenReadOnlyNonExistentPath) { + auto options = psr_database_options_default(); + options.console_level = PSR_LOG_OFF; + options.read_only = 1; + + // Try to open non-existent file as read-only + auto db = psr_database_open("nonexistent_path_12345.db", &options); + + // Should fail because file doesn't exist and we can't create in read-only mode + EXPECT_EQ(db, nullptr); +} + +TEST_F(TempFileFixture, FromSchemaValidPath) { + auto options = psr_database_options_default(); + options.console_level = PSR_LOG_OFF; + auto db = psr_database_from_schema(":memory:", VALID_SCHEMA("basic.sql").c_str(), &options); + + ASSERT_NE(db, nullptr); + EXPECT_EQ(psr_database_is_healthy(db), 1); + + psr_database_close(db); +} + +// ============================================================================ +// Element ID operations +// ============================================================================ + +TEST_F(TempFileFixture, ReadElementIdsNullDb) { + int64_t* ids = nullptr; + size_t count = 0; + auto err = psr_database_read_element_ids(nullptr, "Collection", &ids, &count); + EXPECT_EQ(err, PSR_ERROR_INVALID_ARGUMENT); +} + +TEST_F(TempFileFixture, ReadElementIdsNullCollection) { + auto options = psr_database_options_default(); + options.console_level = PSR_LOG_OFF; + auto db = psr_database_from_schema(":memory:", VALID_SCHEMA("collections.sql").c_str(), &options); + ASSERT_NE(db, nullptr); + + int64_t* ids = nullptr; + size_t count = 0; + auto err = psr_database_read_element_ids(db, nullptr, &ids, &count); + EXPECT_EQ(err, PSR_ERROR_INVALID_ARGUMENT); + + psr_database_close(db); +} + +TEST_F(TempFileFixture, ReadElementIdsNullOutput) { + auto options = psr_database_options_default(); + options.console_level = PSR_LOG_OFF; + auto db = psr_database_from_schema(":memory:", VALID_SCHEMA("collections.sql").c_str(), &options); + ASSERT_NE(db, nullptr); + + size_t count = 0; + auto err = psr_database_read_element_ids(db, "Collection", nullptr, &count); + EXPECT_EQ(err, PSR_ERROR_INVALID_ARGUMENT); + + int64_t* ids = nullptr; + err = psr_database_read_element_ids(db, "Collection", &ids, nullptr); + EXPECT_EQ(err, PSR_ERROR_INVALID_ARGUMENT); + + psr_database_close(db); +} + +TEST_F(TempFileFixture, ReadElementIdsValid) { + auto options = psr_database_options_default(); + options.console_level = PSR_LOG_OFF; + auto db = psr_database_from_schema(":memory:", VALID_SCHEMA("collections.sql").c_str(), &options); + ASSERT_NE(db, nullptr); + + // Create Configuration first + auto config = psr_element_create(); + psr_element_set_string(config, "label", "Config"); + psr_database_create_element(db, "Configuration", config); + psr_element_destroy(config); + + // Create some elements + for (int i = 1; i <= 3; ++i) { + auto element = psr_element_create(); + psr_element_set_string(element, "label", ("Item " + std::to_string(i)).c_str()); + psr_database_create_element(db, "Collection", element); + psr_element_destroy(element); + } + + // Read element IDs + int64_t* ids = nullptr; + size_t count = 0; + auto err = psr_database_read_element_ids(db, "Collection", &ids, &count); + EXPECT_EQ(err, PSR_OK); + EXPECT_EQ(count, 3); + + if (ids != nullptr) { + free(ids); + } + + psr_database_close(db); +} + +// ============================================================================ +// Delete element tests +// ============================================================================ + +TEST_F(TempFileFixture, DeleteElementNullDb) { + auto err = psr_database_delete_element_by_id(nullptr, "Collection", 1); + EXPECT_EQ(err, PSR_ERROR_INVALID_ARGUMENT); +} + +TEST_F(TempFileFixture, DeleteElementNullCollection) { + auto options = psr_database_options_default(); + options.console_level = PSR_LOG_OFF; + auto db = psr_database_from_schema(":memory:", VALID_SCHEMA("collections.sql").c_str(), &options); + ASSERT_NE(db, nullptr); + + auto err = psr_database_delete_element_by_id(db, nullptr, 1); + EXPECT_EQ(err, PSR_ERROR_INVALID_ARGUMENT); + + psr_database_close(db); +} + +TEST_F(TempFileFixture, DeleteElementValid) { + auto options = psr_database_options_default(); + options.console_level = PSR_LOG_OFF; + auto db = psr_database_from_schema(":memory:", VALID_SCHEMA("collections.sql").c_str(), &options); + ASSERT_NE(db, nullptr); + + // Create Configuration first + auto config = psr_element_create(); + psr_element_set_string(config, "label", "Config"); + psr_database_create_element(db, "Configuration", config); + psr_element_destroy(config); + + // Create element + auto element = psr_element_create(); + psr_element_set_string(element, "label", "Item 1"); + int64_t id = psr_database_create_element(db, "Collection", element); + psr_element_destroy(element); + + EXPECT_GT(id, 0); + + // Delete element + auto err = psr_database_delete_element_by_id(db, "Collection", id); + EXPECT_EQ(err, PSR_OK); + + // Verify element is deleted + int64_t* ids = nullptr; + size_t count = 0; + psr_database_read_element_ids(db, "Collection", &ids, &count); + EXPECT_EQ(count, 0); + + if (ids != nullptr) { + free(ids); + } + + psr_database_close(db); +} + +// ============================================================================ +// Update element tests +// ============================================================================ + +TEST_F(TempFileFixture, UpdateElementNullDb) { + auto element = psr_element_create(); + psr_element_set_string(element, "label", "New Label"); + + auto err = psr_database_update_element(nullptr, "Collection", 1, element); + EXPECT_EQ(err, PSR_ERROR_INVALID_ARGUMENT); + + psr_element_destroy(element); +} + +TEST_F(TempFileFixture, UpdateElementNullCollection) { + auto options = psr_database_options_default(); + options.console_level = PSR_LOG_OFF; + auto db = psr_database_from_schema(":memory:", VALID_SCHEMA("collections.sql").c_str(), &options); + ASSERT_NE(db, nullptr); + + auto element = psr_element_create(); + psr_element_set_string(element, "label", "New Label"); + + auto err = psr_database_update_element(db, nullptr, 1, element); + EXPECT_EQ(err, PSR_ERROR_INVALID_ARGUMENT); + + psr_element_destroy(element); + psr_database_close(db); +} + +TEST_F(TempFileFixture, UpdateElementNullElement) { + auto options = psr_database_options_default(); + options.console_level = PSR_LOG_OFF; + auto db = psr_database_from_schema(":memory:", VALID_SCHEMA("collections.sql").c_str(), &options); + ASSERT_NE(db, nullptr); + + auto err = psr_database_update_element(db, "Collection", 1, nullptr); + EXPECT_EQ(err, PSR_ERROR_INVALID_ARGUMENT); + + psr_database_close(db); +} diff --git a/tests/test_database_errors.cpp b/tests/test_database_errors.cpp index 7d6db96..5f40fb3 100644 --- a/tests/test_database_errors.cpp +++ b/tests/test_database_errors.cpp @@ -323,3 +323,147 @@ TEST(DatabaseErrors, UpdateSetStringsCollectionNotFound) { EXPECT_THROW(db.update_set_strings("NonexistentCollection", "tag", 1, {"a", "b"}), std::exception); } + +// ============================================================================ +// Read scalar with non-existent attribute tests +// ============================================================================ + +TEST(DatabaseErrors, ReadScalarIntegersAttributeNotFound) { + auto db = psr::Database::from_schema(":memory:", VALID_SCHEMA("basic.sql"), {.console_level = psr::LogLevel::off}); + + psr::Element e; + e.set("label", std::string("Test")); + db.create_element("Configuration", e); + + // Reading non-existent column throws because SQL is invalid + EXPECT_THROW(db.read_scalar_integers("Configuration", "nonexistent_attribute"), std::runtime_error); +} + +TEST(DatabaseErrors, ReadScalarDoublesAttributeNotFound) { + auto db = psr::Database::from_schema(":memory:", VALID_SCHEMA("basic.sql"), {.console_level = psr::LogLevel::off}); + + psr::Element e; + e.set("label", std::string("Test")); + db.create_element("Configuration", e); + + EXPECT_THROW(db.read_scalar_doubles("Configuration", "nonexistent_attribute"), std::runtime_error); +} + +TEST(DatabaseErrors, ReadScalarStringsAttributeNotFound) { + auto db = psr::Database::from_schema(":memory:", VALID_SCHEMA("basic.sql"), {.console_level = psr::LogLevel::off}); + + psr::Element e; + e.set("label", std::string("Test")); + db.create_element("Configuration", e); + + EXPECT_THROW(db.read_scalar_strings("Configuration", "nonexistent_attribute"), std::runtime_error); +} + +// ============================================================================ +// Read vector with non-existent attribute tests +// ============================================================================ + +TEST(DatabaseErrors, ReadVectorIntegersAttributeNotFound) { + auto db = + psr::Database::from_schema(":memory:", VALID_SCHEMA("collections.sql"), {.console_level = psr::LogLevel::off}); + + psr::Element config; + config.set("label", std::string("Config")); + db.create_element("Configuration", config); + + EXPECT_THROW(db.read_vector_integers("Collection", "nonexistent_attribute"), std::exception); +} + +TEST(DatabaseErrors, ReadVectorDoublesAttributeNotFound) { + auto db = + psr::Database::from_schema(":memory:", VALID_SCHEMA("collections.sql"), {.console_level = psr::LogLevel::off}); + + psr::Element config; + config.set("label", std::string("Config")); + db.create_element("Configuration", config); + + EXPECT_THROW(db.read_vector_doubles("Collection", "nonexistent_attribute"), std::exception); +} + +TEST(DatabaseErrors, ReadVectorStringsAttributeNotFound) { + auto db = + psr::Database::from_schema(":memory:", VALID_SCHEMA("collections.sql"), {.console_level = psr::LogLevel::off}); + + psr::Element config; + config.set("label", std::string("Config")); + db.create_element("Configuration", config); + + EXPECT_THROW(db.read_vector_strings("Collection", "nonexistent_attribute"), std::exception); +} + +// ============================================================================ +// Read set with non-existent attribute tests +// ============================================================================ + +TEST(DatabaseErrors, ReadSetIntegersAttributeNotFound) { + auto db = + psr::Database::from_schema(":memory:", VALID_SCHEMA("collections.sql"), {.console_level = psr::LogLevel::off}); + + psr::Element config; + config.set("label", std::string("Config")); + db.create_element("Configuration", config); + + EXPECT_THROW(db.read_set_integers("Collection", "nonexistent_attribute"), std::exception); +} + +TEST(DatabaseErrors, ReadSetDoublesAttributeNotFound) { + auto db = + psr::Database::from_schema(":memory:", VALID_SCHEMA("collections.sql"), {.console_level = psr::LogLevel::off}); + + psr::Element config; + config.set("label", std::string("Config")); + db.create_element("Configuration", config); + + EXPECT_THROW(db.read_set_doubles("Collection", "nonexistent_attribute"), std::exception); +} + +// ============================================================================ +// Schema file error tests +// ============================================================================ + +TEST(DatabaseErrors, ApplySchemaEmptyPath) { + EXPECT_THROW(psr::Database::from_schema(":memory:", "", {.console_level = psr::LogLevel::off}), std::runtime_error); +} + +TEST(DatabaseErrors, ApplySchemaFileNotFound) { + EXPECT_THROW(psr::Database::from_schema(":memory:", "nonexistent/path/schema.sql", {.console_level = psr::LogLevel::off}), + std::runtime_error); +} + +// ============================================================================ +// Update scalar with collection not found +// ============================================================================ + +TEST(DatabaseErrors, UpdateScalarIntegerCollectionNotFound) { + auto db = psr::Database::from_schema(":memory:", VALID_SCHEMA("basic.sql"), {.console_level = psr::LogLevel::off}); + + EXPECT_THROW(db.update_scalar_integer("NonexistentCollection", "value", 1, 42), std::runtime_error); +} + +TEST(DatabaseErrors, UpdateScalarDoubleCollectionNotFound) { + auto db = psr::Database::from_schema(":memory:", VALID_SCHEMA("basic.sql"), {.console_level = psr::LogLevel::off}); + + EXPECT_THROW(db.update_scalar_double("NonexistentCollection", "value", 1, 3.14), std::runtime_error); +} + +TEST(DatabaseErrors, UpdateScalarStringCollectionNotFound) { + auto db = psr::Database::from_schema(":memory:", VALID_SCHEMA("basic.sql"), {.console_level = psr::LogLevel::off}); + + EXPECT_THROW(db.update_scalar_string("NonexistentCollection", "value", 1, "test"), std::runtime_error); +} + +// ============================================================================ +// Read element IDs errors +// ============================================================================ + +TEST(DatabaseErrors, ReadElementIdsNoSchema) { + psr::Database db(":memory:", {.console_level = psr::LogLevel::off}); + + // Without schema, executing SQL will fail due to missing table + EXPECT_THROW(db.read_element_ids("Configuration"), std::runtime_error); +} diff --git a/tests/test_element.cpp b/tests/test_element.cpp index b428f7b..e5cfd7a 100644 --- a/tests/test_element.cpp +++ b/tests/test_element.cpp @@ -135,3 +135,137 @@ TEST(Element, ToStringEmpty) { EXPECT_EQ(str.find("scalars:"), std::string::npos); EXPECT_EQ(str.find("vectors:"), std::string::npos); } + +// ============================================================================ +// to_string() formatting edge cases +// ============================================================================ + +TEST(Element, ToStringWithSpecialCharacters) { + psr::Element element; + element.set("label", std::string("Test \"with\" special\nchars")); + + std::string str = element.to_string(); + + EXPECT_NE(str.find("Element {"), std::string::npos); + EXPECT_NE(str.find("scalars:"), std::string::npos); + EXPECT_NE(str.find("label:"), std::string::npos); +} + +TEST(Element, ToStringWithEmptyString) { + psr::Element element; + element.set("empty_value", std::string("")); + + std::string str = element.to_string(); + + EXPECT_NE(str.find("empty_value:"), std::string::npos); + EXPECT_NE(str.find("\"\""), std::string::npos); +} + +TEST(Element, ToStringWithLargeArray) { + psr::Element element; + std::vector large_array; + for (int i = 0; i < 100; ++i) { + large_array.push_back(i); + } + element.set("large_array", large_array); + + std::string str = element.to_string(); + + EXPECT_NE(str.find("Element {"), std::string::npos); + EXPECT_NE(str.find("arrays:"), std::string::npos); + EXPECT_NE(str.find("large_array:"), std::string::npos); + // Verify first and last elements are in the string + EXPECT_NE(str.find("0"), std::string::npos); + EXPECT_NE(str.find("99"), std::string::npos); +} + +TEST(Element, ToStringWithNullValue) { + psr::Element element; + element.set_null("nullable_field"); + + std::string str = element.to_string(); + + EXPECT_NE(str.find("nullable_field:"), std::string::npos); + EXPECT_NE(str.find("null"), std::string::npos); +} + +// ============================================================================ +// Element builder edge cases +// ============================================================================ + +TEST(Element, SetOverwriteWithDifferentType) { + psr::Element element; + element.set("value", int64_t{42}); + + // Overwrite with different type (double) + element.set("value", 3.14); + + EXPECT_EQ(element.scalars().size(), 1); + EXPECT_EQ(std::get(element.scalars().at("value")), 3.14); +} + +TEST(Element, ClearAndReuse) { + psr::Element element; + element.set("label", std::string("Original")).set("data", std::vector{1.0, 2.0}); + + EXPECT_TRUE(element.has_scalars()); + EXPECT_TRUE(element.has_arrays()); + + element.clear(); + + EXPECT_FALSE(element.has_scalars()); + EXPECT_FALSE(element.has_arrays()); + + // Reuse after clear + element.set("new_label", std::string("Reused")).set("new_data", std::vector{3, 4, 5}); + + EXPECT_TRUE(element.has_scalars()); + EXPECT_TRUE(element.has_arrays()); + EXPECT_EQ(std::get(element.scalars().at("new_label")), "Reused"); + EXPECT_EQ(element.arrays().at("new_data").size(), 3); +} + +TEST(Element, SetMultipleSameNameArrays) { + psr::Element element; + element.set("values", std::vector{1, 2, 3}); + + // Overwrite with different array + element.set("values", std::vector{10, 20}); + + EXPECT_EQ(element.arrays().size(), 1); + EXPECT_EQ(element.arrays().at("values").size(), 2); + EXPECT_EQ(std::get(element.arrays().at("values")[0]), 10); +} + +TEST(Element, SetMixedScalarsAndArrays) { + psr::Element element; + element.set("label", std::string("Test")) + .set("int_value", int64_t{42}) + .set("double_value", 3.14) + .set_null("null_value") + .set("int_array", std::vector{1, 2, 3}) + .set("double_array", std::vector{1.1, 2.2}) + .set("string_array", std::vector{"a", "b", "c"}); + + EXPECT_EQ(element.scalars().size(), 4); + EXPECT_EQ(element.arrays().size(), 3); + + std::string str = element.to_string(); + EXPECT_NE(str.find("scalars:"), std::string::npos); + EXPECT_NE(str.find("arrays:"), std::string::npos); +} + +TEST(Element, ToStringWithAllTypes) { + psr::Element element; + element.set("text", std::string("hello")) + .set("integer", int64_t{123}) + .set("real", 45.67) + .set_null("empty"); + + std::string str = element.to_string(); + + EXPECT_NE(str.find("\"hello\""), std::string::npos); + EXPECT_NE(str.find("123"), std::string::npos); + EXPECT_NE(str.find("45.67"), std::string::npos); + EXPECT_NE(str.find("null"), std::string::npos); +} diff --git a/tests/test_lua_runner.cpp b/tests/test_lua_runner.cpp index 64c5d8c..22d1ad7 100644 --- a/tests/test_lua_runner.cpp +++ b/tests/test_lua_runner.cpp @@ -876,3 +876,124 @@ TEST_F(LuaRunnerTest, ReadVectorDoublesByIdFromLua) { )"; lua.run(script); } + +// ============================================================================ +// Additional LuaRunner error handling tests +// ============================================================================ + +TEST_F(LuaRunnerTest, CreateElementMissingLabel) { + auto db = psr::Database::from_schema(":memory:", collections_schema); + db.create_element("Configuration", psr::Element().set("label", "Config")); + + psr::LuaRunner lua(db); + + // Attempting to create element without required label should fail + EXPECT_THROW({ lua.run(R"(db:create_element("Collection", { some_integer = 42 }))"); }, std::runtime_error); +} + +TEST_F(LuaRunnerTest, ReadNonExistentAttribute) { + auto db = psr::Database::from_schema(":memory:", collections_schema); + db.create_element("Configuration", psr::Element().set("label", "Config")); + db.create_element("Collection", psr::Element().set("label", "Item 1")); + + psr::LuaRunner lua(db); + + EXPECT_THROW({ lua.run(R"(local x = db:read_scalar_strings("Collection", "nonexistent"))"); }, std::runtime_error); +} + +TEST_F(LuaRunnerTest, UpdateElementNonExistentCollection) { + auto db = psr::Database::from_schema(":memory:", collections_schema); + db.create_element("Configuration", psr::Element().set("label", "Config")); + + psr::LuaRunner lua(db); + + EXPECT_THROW({ lua.run(R"(db:update_element("NonexistentCollection", 1, { label = "Test" }))"); }, + std::runtime_error); +} + +TEST_F(LuaRunnerTest, DeleteFromNonExistentCollection) { + auto db = psr::Database::from_schema(":memory:", collections_schema); + db.create_element("Configuration", psr::Element().set("label", "Config")); + + psr::LuaRunner lua(db); + + EXPECT_THROW({ lua.run(R"(db:delete_element_by_id("NonexistentCollection", 1))"); }, std::runtime_error); +} + +TEST_F(LuaRunnerTest, MultipleOperationsPartialFailure) { + auto db = psr::Database::from_schema(":memory:", collections_schema); + db.create_element("Configuration", psr::Element().set("label", "Config")); + + psr::LuaRunner lua(db); + + // First operation succeeds, second should fail + EXPECT_THROW( + { + lua.run(R"( + db:create_element("Collection", { label = "Item 1" }) + db:create_element("NonexistentCollection", { label = "Bad" }) + )"); + }, + std::runtime_error); + + // Verify first element was created before failure + auto labels = db.read_scalar_strings("Collection", "label"); + EXPECT_EQ(labels.size(), 1); + EXPECT_EQ(labels[0], "Item 1"); +} + +TEST_F(LuaRunnerTest, LuaTypeCoercionInteger) { + auto db = psr::Database::from_schema(":memory:", collections_schema); + psr::LuaRunner lua(db); + + lua.run(R"( + db:create_element("Configuration", { label = "Config" }) + db:create_element("Collection", { + label = "Item 1", + some_integer = 42.0 -- Lua number that happens to be whole + }) + )"); + + auto integers = db.read_scalar_integers("Collection", "some_integer"); + EXPECT_EQ(integers.size(), 1); + EXPECT_EQ(integers[0], 42); +} + +TEST_F(LuaRunnerTest, ReadElementIdsFromNonExistentCollection) { + auto db = psr::Database::from_schema(":memory:", collections_schema); + db.create_element("Configuration", psr::Element().set("label", "Config")); + + psr::LuaRunner lua(db); + + EXPECT_THROW({ lua.run(R"(local ids = db:read_element_ids("NonexistentCollection"))"); }, std::runtime_error); +} + +TEST_F(LuaRunnerTest, CreateElementWithSpecialCharactersInLabel) { + auto db = psr::Database::from_schema(":memory:", collections_schema); + psr::LuaRunner lua(db); + + lua.run(R"( + db:create_element("Configuration", { label = "Config" }) + db:create_element("Collection", { label = "Test's \"special\" chars: <>&" }) + )"); + + auto labels = db.read_scalar_strings("Collection", "label"); + EXPECT_EQ(labels.size(), 1); + EXPECT_EQ(labels[0], "Test's \"special\" chars: <>&"); +} + +TEST_F(LuaRunnerTest, LuaScriptWithUnicodeCharacters) { + auto db = psr::Database::from_schema(":memory:", collections_schema); + psr::LuaRunner lua(db); + + lua.run(R"( + db:create_element("Configuration", { label = "配置" }) + db:create_element("Collection", { label = "项目 αβγ 🎉" }) + )"); + + auto config_labels = db.read_scalar_strings("Configuration", "label"); + auto collection_labels = db.read_scalar_strings("Collection", "label"); + + EXPECT_EQ(config_labels.size(), 1); + EXPECT_EQ(collection_labels.size(), 1); +} diff --git a/tests/test_schema_validator.cpp b/tests/test_schema_validator.cpp index 02b3fb3..61a4ef9 100644 --- a/tests/test_schema_validator.cpp +++ b/tests/test_schema_validator.cpp @@ -164,3 +164,136 @@ TEST_F(SchemaValidatorFixture, GetAttributeTypeSetText) { EXPECT_EQ(type.structure, psr::AttributeStructure::Set); EXPECT_EQ(type.data_type, psr::AttributeDataType::Text); } + +// ============================================================================ +// Additional attribute type tests +// ============================================================================ + +TEST_F(SchemaValidatorFixture, GetAttributeTypeVectorReal) { + auto db = psr::Database::from_schema(":memory:", VALID_SCHEMA("collections.sql"), opts); + + auto type = db.get_attribute_type("Collection", "value_float"); + + EXPECT_EQ(type.structure, psr::AttributeStructure::Vector); + EXPECT_EQ(type.data_type, psr::AttributeDataType::Real); +} + +TEST_F(SchemaValidatorFixture, GetAttributeTypeForeignKeyAsInteger) { + auto db = psr::Database::from_schema(":memory:", VALID_SCHEMA("relations.sql"), opts); + + // parent_id is a foreign key but stored as INTEGER + auto type = db.get_attribute_type("Child", "parent_id"); + + EXPECT_EQ(type.structure, psr::AttributeStructure::Scalar); + EXPECT_EQ(type.data_type, psr::AttributeDataType::Integer); +} + +TEST_F(SchemaValidatorFixture, GetAttributeTypeSelfReference) { + auto db = psr::Database::from_schema(":memory:", VALID_SCHEMA("relations.sql"), opts); + + // sibling_id is a self-referencing foreign key + auto type = db.get_attribute_type("Child", "sibling_id"); + + EXPECT_EQ(type.structure, psr::AttributeStructure::Scalar); + EXPECT_EQ(type.data_type, psr::AttributeDataType::Integer); +} + +// ============================================================================ +// Schema loading and validation edge cases +// ============================================================================ + +TEST_F(SchemaValidatorFixture, CollectionWithOptionalScalars) { + auto db = psr::Database::from_schema(":memory:", VALID_SCHEMA("collections.sql"), opts); + + // Create element with only label (other scalars are optional) + psr::Element config; + config.set("label", std::string("Config")); + db.create_element("Configuration", config); + + psr::Element e; + e.set("label", std::string("Item 1")); + int64_t id = db.create_element("Collection", e); + + EXPECT_GT(id, 0); + + // Read back - optional scalars should be null + auto integers = db.read_scalar_integers("Collection", "some_integer"); + EXPECT_TRUE(integers.empty()); // NULL values are skipped +} + +TEST_F(SchemaValidatorFixture, RelationsSchemaWithVectorFK) { + auto db = psr::Database::from_schema(":memory:", VALID_SCHEMA("relations.sql"), opts); + + // Verify the schema loaded successfully with vector FK table + auto type = db.get_attribute_type("Child", "label"); + EXPECT_EQ(type.data_type, psr::AttributeDataType::Text); +} + +// ============================================================================ +// Type validation edge cases with create_element +// ============================================================================ + +TEST_F(SchemaValidatorFixture, CreateElementWithDefaultValue) { + auto db = psr::Database::from_schema(":memory:", VALID_SCHEMA("basic.sql"), opts); + + // The basic.sql schema has integer_attribute with DEFAULT 6 + psr::Element e; + e.set("label", std::string("Test")); + int64_t id = db.create_element("Configuration", e); + + // Read back the default value + auto val = db.read_scalar_integers_by_id("Configuration", "integer_attribute", id); + EXPECT_TRUE(val.has_value()); + EXPECT_EQ(*val, 6); +} + +TEST_F(SchemaValidatorFixture, CreateElementWithNullableColumn) { + auto db = psr::Database::from_schema(":memory:", VALID_SCHEMA("basic.sql"), opts); + + // float_attribute is nullable (no NOT NULL constraint) + psr::Element e; + e.set("label", std::string("Test")).set_null("float_attribute"); + int64_t id = db.create_element("Configuration", e); + + auto val = db.read_scalar_doubles_by_id("Configuration", "float_attribute", id); + EXPECT_FALSE(val.has_value()); // NULL +} + +// ============================================================================ +// Multiple collections with different structures +// ============================================================================ + +TEST_F(SchemaValidatorFixture, ReadFromMultipleCollections) { + auto db = psr::Database::from_schema(":memory:", VALID_SCHEMA("relations.sql"), opts); + + // Create elements in different collections + psr::Element parent; + parent.set("label", std::string("Parent 1")); + db.create_element("Parent", parent); + + psr::Element child; + child.set("label", std::string("Child 1")); + db.create_element("Child", child); + + // Verify both can be read + auto parent_labels = db.read_scalar_strings("Parent", "label"); + auto child_labels = db.read_scalar_strings("Child", "label"); + + EXPECT_EQ(parent_labels.size(), 1); + EXPECT_EQ(child_labels.size(), 1); + EXPECT_EQ(parent_labels[0], "Parent 1"); + EXPECT_EQ(child_labels[0], "Child 1"); +} + +// ============================================================================ +// get_attribute_type error cases +// ============================================================================ + +TEST_F(SchemaValidatorFixture, GetAttributeTypeIdColumn) { + auto db = psr::Database::from_schema(":memory:", VALID_SCHEMA("basic.sql"), opts); + + // 'id' column should exist and be INTEGER + auto type = db.get_attribute_type("Configuration", "id"); + EXPECT_EQ(type.structure, psr::AttributeStructure::Scalar); + EXPECT_EQ(type.data_type, psr::AttributeDataType::Integer); +} From 7de25b09f84b2847680588193c64810076703919 Mon Sep 17 00:00:00 2001 From: raphasampaio Date: Sun, 18 Jan 2026 15:15:34 -0300 Subject: [PATCH 2/2] update --- tests/test_database_errors.cpp | 5 +++-- tests/test_element.cpp | 5 +---- tests/test_lua_runner.cpp | 4 ++-- 3 files changed, 6 insertions(+), 8 deletions(-) diff --git a/tests/test_database_errors.cpp b/tests/test_database_errors.cpp index 5f40fb3..05b402d 100644 --- a/tests/test_database_errors.cpp +++ b/tests/test_database_errors.cpp @@ -431,8 +431,9 @@ TEST(DatabaseErrors, ApplySchemaEmptyPath) { } TEST(DatabaseErrors, ApplySchemaFileNotFound) { - EXPECT_THROW(psr::Database::from_schema(":memory:", "nonexistent/path/schema.sql", {.console_level = psr::LogLevel::off}), - std::runtime_error); + EXPECT_THROW( + psr::Database::from_schema(":memory:", "nonexistent/path/schema.sql", {.console_level = psr::LogLevel::off}), + std::runtime_error); } // ============================================================================ diff --git a/tests/test_element.cpp b/tests/test_element.cpp index e5cfd7a..e0058e4 100644 --- a/tests/test_element.cpp +++ b/tests/test_element.cpp @@ -257,10 +257,7 @@ TEST(Element, SetMixedScalarsAndArrays) { TEST(Element, ToStringWithAllTypes) { psr::Element element; - element.set("text", std::string("hello")) - .set("integer", int64_t{123}) - .set("real", 45.67) - .set_null("empty"); + element.set("text", std::string("hello")).set("integer", int64_t{123}).set("real", 45.67).set_null("empty"); std::string str = element.to_string(); diff --git a/tests/test_lua_runner.cpp b/tests/test_lua_runner.cpp index 22d1ad7..3f4c7c9 100644 --- a/tests/test_lua_runner.cpp +++ b/tests/test_lua_runner.cpp @@ -907,8 +907,8 @@ TEST_F(LuaRunnerTest, UpdateElementNonExistentCollection) { psr::LuaRunner lua(db); - EXPECT_THROW({ lua.run(R"(db:update_element("NonexistentCollection", 1, { label = "Test" }))"); }, - std::runtime_error); + EXPECT_THROW( + { lua.run(R"(db:update_element("NonexistentCollection", 1, { label = "Test" }))"); }, std::runtime_error); } TEST_F(LuaRunnerTest, DeleteFromNonExistentCollection) {