Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion bindings/dart/test/lua_runner_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -271,7 +271,10 @@ void main() {
);
try {
db.createElement('Configuration', {'label': 'Config'});
db.createElement('Collection', {'label': 'Item 1', 'value_int': [1, 2, 3]});
db.createElement('Collection', {
'label': 'Item 1',
'value_int': [1, 2, 3]
});

final lua = LuaRunner(db);
try {
Expand Down
3 changes: 3 additions & 0 deletions tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,15 @@ include(GoogleTest)
add_executable(psr_database_tests
test_database_create.cpp
test_database_delete.cpp
test_database_errors.cpp
test_database_lifecycle.cpp
test_database_read.cpp
test_database_relations.cpp
test_database_update.cpp
test_element.cpp
test_lua_runner.cpp
test_migrations.cpp
test_row_result.cpp
test_schema_validator.cpp
)

Expand Down
325 changes: 325 additions & 0 deletions tests/test_database_errors.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,325 @@
#include "test_utils.h"

#include <gtest/gtest.h>
#include <psr/database.h>
#include <psr/element.h>

// ============================================================================
// No schema loaded error tests
// ============================================================================

TEST(DatabaseErrors, CreateElementNoSchema) {
psr::Database db(":memory:", {.console_level = psr::LogLevel::off});

psr::Element element;
element.set("label", std::string("Test"));

EXPECT_THROW(db.create_element("Configuration", element), std::runtime_error);
}

TEST(DatabaseErrors, CreateElementCollectionNotFound) {
auto db = psr::Database::from_schema(":memory:", VALID_SCHEMA("basic.sql"), {.console_level = psr::LogLevel::off});

psr::Element element;
element.set("label", std::string("Test"));

EXPECT_THROW(db.create_element("NonexistentCollection", element), std::runtime_error);
}

TEST(DatabaseErrors, CreateElementEmptyElement) {
auto db = psr::Database::from_schema(":memory:", VALID_SCHEMA("basic.sql"), {.console_level = psr::LogLevel::off});

psr::Element element; // Empty element with no scalars

EXPECT_THROW(db.create_element("Configuration", element), std::runtime_error);
}

TEST(DatabaseErrors, CreateElementEmptyArray) {
auto db =
psr::Database::from_schema(":memory:", VALID_SCHEMA("collections.sql"), {.console_level = psr::LogLevel::off});

// Create required Configuration first
psr::Element config;
config.set("label", std::string("Test Config"));
db.create_element("Configuration", config);

// Try to create element with empty array
psr::Element element;
element.set("label", std::string("Item 1")).set("value_int", std::vector<int64_t>{});

EXPECT_THROW(db.create_element("Collection", element), std::runtime_error);
}

// ============================================================================
// Update error tests
// ============================================================================

TEST(DatabaseErrors, UpdateElementNoSchema) {
psr::Database db(":memory:", {.console_level = psr::LogLevel::off});

psr::Element element;
element.set("label", std::string("Test"));

EXPECT_THROW(db.update_element("Configuration", 1, element), std::runtime_error);
}

TEST(DatabaseErrors, UpdateElementCollectionNotFound) {
auto db = psr::Database::from_schema(":memory:", VALID_SCHEMA("basic.sql"), {.console_level = psr::LogLevel::off});

psr::Element element;
element.set("label", std::string("Test"));

EXPECT_THROW(db.update_element("NonexistentCollection", 1, element), std::runtime_error);
}

TEST(DatabaseErrors, UpdateElementEmptyElement) {
auto db = psr::Database::from_schema(":memory:", VALID_SCHEMA("basic.sql"), {.console_level = psr::LogLevel::off});

// Create an element first
psr::Element original;
original.set("label", std::string("Test"));
int64_t id = db.create_element("Configuration", original);

// Try to update with empty element
psr::Element empty_element;

EXPECT_THROW(db.update_element("Configuration", id, empty_element), std::runtime_error);
}

// ============================================================================
// Delete error tests
// ============================================================================

TEST(DatabaseErrors, DeleteElementNoSchema) {
psr::Database db(":memory:", {.console_level = psr::LogLevel::off});

EXPECT_THROW(db.delete_element_by_id("Configuration", 1), std::runtime_error);
}

TEST(DatabaseErrors, DeleteElementCollectionNotFound) {
auto db = psr::Database::from_schema(":memory:", VALID_SCHEMA("basic.sql"), {.console_level = psr::LogLevel::off});

EXPECT_THROW(db.delete_element_by_id("NonexistentCollection", 1), std::runtime_error);
}

// ============================================================================
// Read scalar error tests (no schema)
// ============================================================================

TEST(DatabaseErrors, ReadScalarIntegersNoSchema) {
psr::Database db(":memory:", {.console_level = psr::LogLevel::off});

// Without schema, executing SQL directly will fail due to missing table
EXPECT_THROW(db.read_scalar_integers("Configuration", "integer_attribute"), std::runtime_error);
}

TEST(DatabaseErrors, ReadScalarDoublesNoSchema) {
psr::Database db(":memory:", {.console_level = psr::LogLevel::off});

EXPECT_THROW(db.read_scalar_doubles("Configuration", "float_attribute"), std::runtime_error);
}

TEST(DatabaseErrors, ReadScalarStringsNoSchema) {
psr::Database db(":memory:", {.console_level = psr::LogLevel::off});

EXPECT_THROW(db.read_scalar_strings("Configuration", "label"), std::runtime_error);
}

// ============================================================================
// Read vector error tests
// Note: read_vector_* methods without schema cause segfault (null pointer dereference)
// because impl_->schema->find_vector_table() is called without null check.
// These tests are skipped until the library adds proper null checks.
// ============================================================================

TEST(DatabaseErrors, ReadVectorIntegersCollectionNotFound) {
auto db =
psr::Database::from_schema(":memory:", VALID_SCHEMA("collections.sql"), {.console_level = psr::LogLevel::off});

// Create required Configuration
psr::Element config;
config.set("label", std::string("Config"));
db.create_element("Configuration", config);

EXPECT_THROW(db.read_vector_integers("NonexistentCollection", "value_int"), std::exception);
}

TEST(DatabaseErrors, ReadVectorDoublesCollectionNotFound) {
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("NonexistentCollection", "value_float"), std::exception);
}

// ============================================================================
// Read set error tests
// Note: read_set_* methods without schema cause segfault (null pointer dereference)
// because impl_->schema->find_set_table() is called without null check.
// These tests are skipped until the library adds proper null checks.
// ============================================================================

TEST(DatabaseErrors, ReadSetStringsCollectionNotFound) {
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_strings("NonexistentCollection", "tag"), std::exception);
}

// ============================================================================
// GetAttributeType error tests
// Note: get_attribute_type without schema causes segfault (null pointer dereference)
// because impl_->schema is dereferenced without null check.
// ============================================================================

TEST(DatabaseErrors, GetAttributeTypeCollectionNotFound) {
auto db = psr::Database::from_schema(":memory:", VALID_SCHEMA("basic.sql"), {.console_level = psr::LogLevel::off});

EXPECT_THROW(db.get_attribute_type("NonexistentCollection", "label"), std::runtime_error);
}

TEST(DatabaseErrors, GetAttributeTypeAttributeNotFound) {
auto db = psr::Database::from_schema(":memory:", VALID_SCHEMA("basic.sql"), {.console_level = psr::LogLevel::off});

EXPECT_THROW(db.get_attribute_type("Configuration", "nonexistent_attribute"), std::runtime_error);
}

// ============================================================================
// Relation error tests
// ============================================================================

TEST(DatabaseErrors, SetScalarRelationNoSchema) {
psr::Database db(":memory:", {.console_level = psr::LogLevel::off});

EXPECT_THROW(db.set_scalar_relation("Child", "parent_id", "Child 1", "Parent 1"), std::runtime_error);
}

TEST(DatabaseErrors, SetScalarRelationCollectionNotFound) {
auto db =
psr::Database::from_schema(":memory:", VALID_SCHEMA("relations.sql"), {.console_level = psr::LogLevel::off});

EXPECT_THROW(db.set_scalar_relation("NonexistentCollection", "parent_id", "Child 1", "Parent 1"),
std::runtime_error);
}

TEST(DatabaseErrors, SetScalarRelationNotForeignKey) {
auto db =
psr::Database::from_schema(":memory:", VALID_SCHEMA("relations.sql"), {.console_level = psr::LogLevel::off});

// 'label' is not a foreign key
EXPECT_THROW(db.set_scalar_relation("Child", "label", "Child 1", "Parent 1"), std::runtime_error);
}

TEST(DatabaseErrors, SetScalarRelationTargetNotFound) {
auto db =
psr::Database::from_schema(":memory:", VALID_SCHEMA("relations.sql"), {.console_level = psr::LogLevel::off});

// Create parent and child
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);

// Try to set relation to nonexistent parent
EXPECT_THROW(db.set_scalar_relation("Child", "parent_id", "Child 1", "Nonexistent Parent"), std::runtime_error);
}

TEST(DatabaseErrors, ReadScalarRelationNoSchema) {
psr::Database db(":memory:", {.console_level = psr::LogLevel::off});

EXPECT_THROW(db.read_scalar_relation("Child", "parent_id"), std::runtime_error);
}

TEST(DatabaseErrors, ReadScalarRelationCollectionNotFound) {
auto db =
psr::Database::from_schema(":memory:", VALID_SCHEMA("relations.sql"), {.console_level = psr::LogLevel::off});

EXPECT_THROW(db.read_scalar_relation("NonexistentCollection", "parent_id"), std::runtime_error);
}

TEST(DatabaseErrors, ReadScalarRelationNotForeignKey) {
auto db =
psr::Database::from_schema(":memory:", VALID_SCHEMA("relations.sql"), {.console_level = psr::LogLevel::off});

// 'label' is not a foreign key
EXPECT_THROW(db.read_scalar_relation("Child", "label"), std::runtime_error);
}

// ============================================================================
// Update scalar error tests
// ============================================================================

TEST(DatabaseErrors, UpdateScalarIntegerNoSchema) {
psr::Database db(":memory:", {.console_level = psr::LogLevel::off});

EXPECT_THROW(db.update_scalar_integer("Configuration", "integer_attribute", 1, 42), std::exception);
}

TEST(DatabaseErrors, UpdateScalarDoubleNoSchema) {
psr::Database db(":memory:", {.console_level = psr::LogLevel::off});

EXPECT_THROW(db.update_scalar_double("Configuration", "float_attribute", 1, 3.14), std::exception);
}

TEST(DatabaseErrors, UpdateScalarStringNoSchema) {
psr::Database db(":memory:", {.console_level = psr::LogLevel::off});

EXPECT_THROW(db.update_scalar_string("Configuration", "label", 1, "new value"), std::exception);
}

// ============================================================================
// Update vector error tests
// Note: update_vector_* methods without schema cause segfault (null pointer dereference)
// because impl_->schema->find_vector_table() is called without null check.
// These tests use a loaded schema and test collection-not-found instead.
// ============================================================================

TEST(DatabaseErrors, UpdateVectorIntegersCollectionNotFound) {
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.update_vector_integers("NonexistentCollection", "value_int", 1, {1, 2, 3}), std::exception);
}

TEST(DatabaseErrors, UpdateVectorDoublesCollectionNotFound) {
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.update_vector_doubles("NonexistentCollection", "value_float", 1, {1.5, 2.5}), std::exception);
}

// ============================================================================
// Update set error tests
// Note: update_set_* methods without schema cause segfault (null pointer dereference)
// because impl_->schema->find_set_table() is called without null check.
// These tests use a loaded schema and test collection-not-found instead.
// ============================================================================

TEST(DatabaseErrors, UpdateSetStringsCollectionNotFound) {
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.update_set_strings("NonexistentCollection", "tag", 1, {"a", "b"}), std::exception);
}
Loading
Loading