diff --git a/bindings/dart/test/create_test.dart b/bindings/dart/test/create_test.dart index 8636c7b..1a4d830 100644 --- a/bindings/dart/test/create_test.dart +++ b/bindings/dart/test/create_test.dart @@ -230,4 +230,161 @@ void main() { } }); }); + + // Edge case tests + + group('Create Single Element Vector', () { + test('creates element with single element vector', () { + final db = Database.fromSchema( + ':memory:', + path.join(testsPath, 'schemas', 'valid', 'collections.sql'), + ); + try { + db.createElement('Configuration', {'label': 'Test Config'}); + final id = db.createElement('Collection', { + 'label': 'Item 1', + 'value_int': [42], + }); + expect(id, greaterThan(0)); + + final result = db.readVectorIntegersById('Collection', 'value_int', 1); + expect(result.length, equals(1)); + expect(result[0], equals(42)); + } finally { + db.close(); + } + }); + }); + + group('Create Large Vector', () { + test('creates element with 100+ element vector', () { + final db = Database.fromSchema( + ':memory:', + path.join(testsPath, 'schemas', 'valid', 'collections.sql'), + ); + try { + db.createElement('Configuration', {'label': 'Test Config'}); + + final largeVector = List.generate(150, (i) => i + 1); + final id = db.createElement('Collection', { + 'label': 'Item 1', + 'value_int': largeVector, + }); + expect(id, greaterThan(0)); + + final result = db.readVectorIntegersById('Collection', 'value_int', 1); + expect(result.length, equals(150)); + expect(result, equals(largeVector)); + } finally { + db.close(); + } + }); + }); + + group('Create Invalid Collection', () { + test('throws on nonexistent collection', () { + final db = Database.fromSchema( + ':memory:', + path.join(testsPath, 'schemas', 'valid', 'basic.sql'), + ); + try { + expect( + () => db.createElement('NonexistentCollection', {'label': 'Test'}), + throwsA(isA()), + ); + } finally { + db.close(); + } + }); + }); + + group('Create With Only Required Label', () { + test('creates element with only required label', () { + final db = Database.fromSchema( + ':memory:', + path.join(testsPath, 'schemas', 'valid', 'collections.sql'), + ); + try { + db.createElement('Configuration', {'label': 'Test Config'}); + + // Create with only required label, no optional attributes + final id = db.createElement('Collection', {'label': 'Minimal Item'}); + expect(id, greaterThan(0)); + + final labels = db.readScalarStrings('Collection', 'label'); + expect(labels.length, equals(1)); + expect(labels[0], equals('Minimal Item')); + } finally { + db.close(); + } + }); + }); + + group('Create With Multiple Sets', () { + test('creates element with set containing multiple values', () { + final db = Database.fromSchema( + ':memory:', + path.join(testsPath, 'schemas', 'valid', 'collections.sql'), + ); + try { + db.createElement('Configuration', {'label': 'Test Config'}); + + final id = db.createElement('Collection', { + 'label': 'Item 1', + 'tag': ['a', 'b', 'c', 'd', 'e'], + }); + expect(id, greaterThan(0)); + + final result = db.readSetStringsById('Collection', 'tag', 1); + expect(result.length, equals(5)); + expect(result..sort(), equals(['a', 'b', 'c', 'd', 'e'])); + } finally { + db.close(); + } + }); + }); + + group('Create Duplicate Label Rejected', () { + test('throws on duplicate label', () { + final db = Database.fromSchema( + ':memory:', + path.join(testsPath, 'schemas', 'valid', 'basic.sql'), + ); + try { + db.createElement('Configuration', {'label': 'Config 1'}); + + // Trying to create with same label should fail + expect( + () => db.createElement('Configuration', {'label': 'Config 1'}), + throwsA(isA()), + ); + } finally { + db.close(); + } + }); + }); + + group('Create With Float Vector', () { + test('creates element with float vector', () { + final db = Database.fromSchema( + ':memory:', + path.join(testsPath, 'schemas', 'valid', 'collections.sql'), + ); + try { + db.createElement('Configuration', {'label': 'Test Config'}); + + final id = db.createElement('Collection', { + 'label': 'Item 1', + 'value_float': [1.1, 2.2, 3.3], + }); + expect(id, greaterThan(0)); + + final result = db.readVectorDoublesById('Collection', 'value_float', 1); + expect(result.length, equals(3)); + expect(result, equals([1.1, 2.2, 3.3])); + } finally { + db.close(); + } + }); + }); } diff --git a/bindings/dart/test/lua_runner_test.dart b/bindings/dart/test/lua_runner_test.dart index fb36dec..2ef1052 100644 --- a/bindings/dart/test/lua_runner_test.dart +++ b/bindings/dart/test/lua_runner_test.dart @@ -116,4 +116,205 @@ void main() { } }); }); + + // Error handling tests + + group('LuaRunner Undefined Variable', () { + test('throws on undefined variable access', () { + final db = Database.fromSchema( + ':memory:', + path.join(testsPath, 'schemas', 'valid', 'collections.sql'), + ); + try { + final lua = LuaRunner(db); + try { + expect( + () => lua.run('print(undefined_variable.field)'), + throwsA(isA()), + ); + } finally { + lua.dispose(); + } + } finally { + db.close(); + } + }); + }); + + group('LuaRunner Create Invalid Collection', () { + test('throws on nonexistent collection in script', () { + final db = Database.fromSchema( + ':memory:', + path.join(testsPath, 'schemas', 'valid', 'collections.sql'), + ); + try { + final lua = LuaRunner(db); + try { + lua.run('db:create_element("Configuration", { label = "Test Config" })'); + expect( + () => lua.run('db:create_element("NonexistentCollection", { label = "Item" })'), + throwsA(isA()), + ); + } finally { + lua.dispose(); + } + } finally { + db.close(); + } + }); + }); + + group('LuaRunner Empty Script', () { + test('runs empty script successfully', () { + final db = Database.fromSchema( + ':memory:', + path.join(testsPath, 'schemas', 'valid', 'collections.sql'), + ); + try { + final lua = LuaRunner(db); + try { + // Empty script should succeed without error + lua.run(''); + // If we get here, the test passed + expect(true, isTrue); + } finally { + lua.dispose(); + } + } finally { + db.close(); + } + }); + }); + + group('LuaRunner Comment Only Script', () { + test('runs comment-only script successfully', () { + final db = Database.fromSchema( + ':memory:', + path.join(testsPath, 'schemas', 'valid', 'collections.sql'), + ); + try { + final lua = LuaRunner(db); + try { + // Comment-only script should succeed + lua.run('-- this is just a comment'); + expect(true, isTrue); + } finally { + lua.dispose(); + } + } finally { + db.close(); + } + }); + }); + + group('LuaRunner Read Integers', () { + test('reads scalar integers in Lua script', () { + final db = Database.fromSchema( + ':memory:', + path.join(testsPath, 'schemas', 'valid', 'collections.sql'), + ); + try { + db.createElement('Configuration', {'label': 'Config'}); + db.createElement('Collection', {'label': 'Item 1', 'some_integer': 100}); + db.createElement('Collection', {'label': 'Item 2', 'some_integer': 200}); + + final lua = LuaRunner(db); + try { + lua.run(''' + local ints = db:read_scalar_integers("Collection", "some_integer") + assert(#ints == 2, "Expected 2 integers") + assert(ints[1] == 100, "First integer mismatch") + assert(ints[2] == 200, "Second integer mismatch") + '''); + } finally { + lua.dispose(); + } + } finally { + db.close(); + } + }); + }); + + group('LuaRunner Read Doubles', () { + test('reads scalar doubles in Lua script', () { + final db = Database.fromSchema( + ':memory:', + path.join(testsPath, 'schemas', 'valid', 'collections.sql'), + ); + try { + db.createElement('Configuration', {'label': 'Config'}); + db.createElement('Collection', {'label': 'Item 1', 'some_float': 1.5}); + db.createElement('Collection', {'label': 'Item 2', 'some_float': 2.5}); + + final lua = LuaRunner(db); + try { + lua.run(''' + local floats = db:read_scalar_doubles("Collection", "some_float") + assert(#floats == 2, "Expected 2 doubles") + assert(floats[1] == 1.5, "First double mismatch") + assert(floats[2] == 2.5, "Second double mismatch") + '''); + } finally { + lua.dispose(); + } + } finally { + db.close(); + } + }); + }); + + group('LuaRunner Read Vectors', () { + test('reads vector integers in Lua script', () { + final db = Database.fromSchema( + ':memory:', + path.join(testsPath, 'schemas', 'valid', 'collections.sql'), + ); + try { + db.createElement('Configuration', {'label': 'Config'}); + db.createElement('Collection', {'label': 'Item 1', 'value_int': [1, 2, 3]}); + + final lua = LuaRunner(db); + try { + lua.run(''' + local vectors = db:read_vector_integers("Collection", "value_int") + assert(#vectors == 1, "Expected 1 vector") + assert(#vectors[1] == 3, "Expected 3 elements in vector") + assert(vectors[1][1] == 1, "First element mismatch") + assert(vectors[1][2] == 2, "Second element mismatch") + assert(vectors[1][3] == 3, "Third element mismatch") + '''); + } finally { + lua.dispose(); + } + } finally { + db.close(); + } + }); + }); + + group('LuaRunner Create With Vector', () { + test('creates element with vector in Lua script', () { + final db = Database.fromSchema( + ':memory:', + path.join(testsPath, 'schemas', 'valid', 'collections.sql'), + ); + try { + final lua = LuaRunner(db); + try { + lua.run(''' + db:create_element("Configuration", { label = "Config" }) + db:create_element("Collection", { label = "Item 1", value_int = {10, 20, 30} }) + '''); + + final result = db.readVectorIntegers('Collection', 'value_int'); + expect(result.length, equals(1)); + expect(result[0], equals([10, 20, 30])); + } finally { + lua.dispose(); + } + } finally { + db.close(); + } + }); + }); } diff --git a/bindings/dart/test/read_test.dart b/bindings/dart/test/read_test.dart index 1b17f0b..c00317c 100644 --- a/bindings/dart/test/read_test.dart +++ b/bindings/dart/test/read_test.dart @@ -714,4 +714,240 @@ void main() { } }); }); + + // Error handling tests + + group('Read Invalid Collection', () { + test('throws on nonexistent collection for strings', () { + final db = Database.fromSchema( + ':memory:', + path.join(testsPath, 'schemas', 'valid', 'basic.sql'), + ); + try { + expect( + () => db.readScalarStrings('NonexistentCollection', 'label'), + throwsA(isA()), + ); + } finally { + db.close(); + } + }); + + test('throws on nonexistent collection for integers', () { + final db = Database.fromSchema( + ':memory:', + path.join(testsPath, 'schemas', 'valid', 'basic.sql'), + ); + try { + expect( + () => db.readScalarIntegers('NonexistentCollection', 'value'), + throwsA(isA()), + ); + } finally { + db.close(); + } + }); + + test('throws on nonexistent collection for doubles', () { + final db = Database.fromSchema( + ':memory:', + path.join(testsPath, 'schemas', 'valid', 'basic.sql'), + ); + try { + expect( + () => db.readScalarDoubles('NonexistentCollection', 'value'), + throwsA(isA()), + ); + } finally { + db.close(); + } + }); + }); + + group('Read Invalid Attribute', () { + test('throws on nonexistent attribute for strings', () { + final db = Database.fromSchema( + ':memory:', + path.join(testsPath, 'schemas', 'valid', 'basic.sql'), + ); + try { + expect( + () => db.readScalarStrings('Configuration', 'nonexistent_attr'), + throwsA(isA()), + ); + } finally { + db.close(); + } + }); + + test('throws on nonexistent attribute for integers', () { + final db = Database.fromSchema( + ':memory:', + path.join(testsPath, 'schemas', 'valid', 'basic.sql'), + ); + try { + expect( + () => db.readScalarIntegers('Configuration', 'nonexistent_attr'), + throwsA(isA()), + ); + } finally { + db.close(); + } + }); + + test('throws on nonexistent attribute for doubles', () { + final db = Database.fromSchema( + ':memory:', + path.join(testsPath, 'schemas', 'valid', 'basic.sql'), + ); + try { + expect( + () => db.readScalarDoubles('Configuration', 'nonexistent_attr'), + throwsA(isA()), + ); + } finally { + db.close(); + } + }); + }); + + group('Read By ID Not Found', () { + test('returns null for nonexistent integer ID', () { + final db = Database.fromSchema( + ':memory:', + path.join(testsPath, 'schemas', 'valid', 'basic.sql'), + ); + try { + db.createElement('Configuration', { + 'label': 'Config 1', + 'integer_attribute': 42, + }); + + expect(db.readScalarIntegerById('Configuration', 'integer_attribute', 999), isNull); + } finally { + db.close(); + } + }); + + test('returns null for nonexistent double ID', () { + final db = Database.fromSchema( + ':memory:', + path.join(testsPath, 'schemas', 'valid', 'basic.sql'), + ); + try { + db.createElement('Configuration', { + 'label': 'Config 1', + 'float_attribute': 3.14, + }); + + expect(db.readScalarDoubleById('Configuration', 'float_attribute', 999), isNull); + } finally { + db.close(); + } + }); + + test('returns null for nonexistent string ID', () { + final db = Database.fromSchema( + ':memory:', + path.join(testsPath, 'schemas', 'valid', 'basic.sql'), + ); + try { + db.createElement('Configuration', { + 'label': 'Config 1', + 'string_attribute': 'hello', + }); + + expect(db.readScalarStringById('Configuration', 'string_attribute', 999), isNull); + } finally { + db.close(); + } + }); + }); + + group('Read Vector Invalid Collection', () { + test('throws on nonexistent collection for vector integers', () { + final db = Database.fromSchema( + ':memory:', + path.join(testsPath, 'schemas', 'valid', 'collections.sql'), + ); + try { + db.createElement('Configuration', {'label': 'Test Config'}); + expect( + () => db.readVectorIntegers('NonexistentCollection', 'value_int'), + throwsA(isA()), + ); + } finally { + db.close(); + } + }); + + test('throws on nonexistent collection for vector doubles', () { + final db = Database.fromSchema( + ':memory:', + path.join(testsPath, 'schemas', 'valid', 'collections.sql'), + ); + try { + db.createElement('Configuration', {'label': 'Test Config'}); + expect( + () => db.readVectorDoubles('NonexistentCollection', 'value_float'), + throwsA(isA()), + ); + } finally { + db.close(); + } + }); + }); + + group('Read Set Invalid Collection', () { + test('throws on nonexistent collection for set strings', () { + final db = Database.fromSchema( + ':memory:', + path.join(testsPath, 'schemas', 'valid', 'collections.sql'), + ); + try { + db.createElement('Configuration', {'label': 'Test Config'}); + expect( + () => db.readSetStrings('NonexistentCollection', 'tag'), + throwsA(isA()), + ); + } finally { + db.close(); + } + }); + }); + + group('Read Element IDs Invalid Collection', () { + test('throws on nonexistent collection', () { + final db = Database.fromSchema( + ':memory:', + path.join(testsPath, 'schemas', 'valid', 'basic.sql'), + ); + try { + expect( + () => db.readElementIds('NonexistentCollection'), + throwsA(isA()), + ); + } finally { + db.close(); + } + }); + }); + + group('Read Scalar Relation Invalid Collection', () { + test('throws on nonexistent collection', () { + final db = Database.fromSchema( + ':memory:', + path.join(testsPath, 'schemas', 'valid', 'relations.sql'), + ); + try { + db.createElement('Configuration', {'label': 'Test Config'}); + expect( + () => db.readScalarRelation('NonexistentCollection', 'parent_id'), + throwsA(isA()), + ); + } finally { + db.close(); + } + }); + }); } diff --git a/bindings/dart/test/update_test.dart b/bindings/dart/test/update_test.dart index 9631958..0743765 100644 --- a/bindings/dart/test/update_test.dart +++ b/bindings/dart/test/update_test.dart @@ -155,4 +155,123 @@ void main() { } }); }); + + // Error handling tests + + group('Update Invalid Collection', () { + test('throws on nonexistent collection', () { + final db = Database.fromSchema( + ':memory:', + path.join(testsPath, 'schemas', 'valid', 'basic.sql'), + ); + try { + db.createElement('Configuration', {'label': 'Config 1', 'integer_attribute': 100}); + expect( + () => db.updateElement('NonexistentCollection', 1, {'integer_attribute': 999}), + throwsA(isA()), + ); + } finally { + db.close(); + } + }); + }); + + group('Update Invalid Element ID', () { + test('does not throw for nonexistent ID', () { + final db = Database.fromSchema( + ':memory:', + path.join(testsPath, 'schemas', 'valid', 'basic.sql'), + ); + try { + db.createElement('Configuration', {'label': 'Config 1', 'integer_attribute': 100}); + + // Update element that doesn't exist - should not throw + db.updateElement('Configuration', 999, {'integer_attribute': 500}); + + // Verify original element unchanged + final value = db.readScalarIntegerById('Configuration', 'integer_attribute', 1); + expect(value, equals(100)); + } finally { + db.close(); + } + }); + }); + + group('Update Invalid Attribute', () { + test('throws on nonexistent attribute', () { + final db = Database.fromSchema( + ':memory:', + path.join(testsPath, 'schemas', 'valid', 'basic.sql'), + ); + try { + db.createElement('Configuration', {'label': 'Config 1', 'integer_attribute': 100}); + expect( + () => db.updateElement('Configuration', 1, {'nonexistent_attribute': 999}), + throwsA(isA()), + ); + } finally { + db.close(); + } + }); + }); + + group('Update String Attribute', () { + test('updates string attribute correctly', () { + final db = Database.fromSchema( + ':memory:', + path.join(testsPath, 'schemas', 'valid', 'basic.sql'), + ); + try { + db.createElement('Configuration', {'label': 'Config 1', 'string_attribute': 'original'}); + + db.updateElement('Configuration', 1, {'string_attribute': 'updated'}); + + final value = db.readScalarStringById('Configuration', 'string_attribute', 1); + expect(value, equals('updated')); + } finally { + db.close(); + } + }); + }); + + group('Update Float Attribute', () { + test('updates float attribute correctly', () { + final db = Database.fromSchema( + ':memory:', + path.join(testsPath, 'schemas', 'valid', 'basic.sql'), + ); + try { + db.createElement('Configuration', {'label': 'Config 1', 'float_attribute': 1.5}); + + db.updateElement('Configuration', 1, {'float_attribute': 99.99}); + + final value = db.readScalarDoubleById('Configuration', 'float_attribute', 1); + expect(value, equals(99.99)); + } finally { + db.close(); + } + }); + }); + + group('Update Multiple Elements Sequentially', () { + test('updates multiple elements independently', () { + final db = Database.fromSchema( + ':memory:', + path.join(testsPath, 'schemas', 'valid', 'basic.sql'), + ); + try { + db.createElement('Configuration', {'label': 'Config 1', 'integer_attribute': 100}); + db.createElement('Configuration', {'label': 'Config 2', 'integer_attribute': 200}); + + // Update both elements + db.updateElement('Configuration', 1, {'integer_attribute': 111}); + db.updateElement('Configuration', 2, {'integer_attribute': 222}); + + expect(db.readScalarIntegerById('Configuration', 'integer_attribute', 1), equals(111)); + expect(db.readScalarIntegerById('Configuration', 'integer_attribute', 2), equals(222)); + } finally { + db.close(); + } + }); + }); } diff --git a/bindings/julia/test/test_create.jl b/bindings/julia/test/test_create.jl index 0bdde86..5992c2e 100644 --- a/bindings/julia/test/test_create.jl +++ b/bindings/julia/test/test_create.jl @@ -114,4 +114,107 @@ end PSRDatabase.close!(db) end +# Edge case tests + +@testset "Create Single Element Vector" begin + path_schema = joinpath(tests_path(), "schemas", "valid", "collections.sql") + db = PSRDatabase.from_schema(":memory:", path_schema) + + PSRDatabase.create_element!(db, "Configuration"; label = "Test Config") + + # Create element with single element vector + PSRDatabase.create_element!(db, "Collection"; label = "Item 1", value_int = [42]) + + result = PSRDatabase.read_vector_integers_by_id(db, "Collection", "value_int", Int64(1)) + @test length(result) == 1 + @test result[1] == 42 + + PSRDatabase.close!(db) +end + +@testset "Create Large Vector" begin + path_schema = joinpath(tests_path(), "schemas", "valid", "collections.sql") + db = PSRDatabase.from_schema(":memory:", path_schema) + + PSRDatabase.create_element!(db, "Configuration"; label = "Test Config") + + # Create element with 100+ element vector + large_vector = collect(1:150) + PSRDatabase.create_element!(db, "Collection"; label = "Item 1", value_int = large_vector) + + result = PSRDatabase.read_vector_integers_by_id(db, "Collection", "value_int", Int64(1)) + @test length(result) == 150 + @test result == large_vector + + PSRDatabase.close!(db) +end + +@testset "Create Invalid Collection" begin + path_schema = joinpath(tests_path(), "schemas", "valid", "basic.sql") + db = PSRDatabase.from_schema(":memory:", path_schema) + + @test_throws PSRDatabase.DatabaseException PSRDatabase.create_element!(db, "NonexistentCollection"; label = "Test") + + PSRDatabase.close!(db) +end + +@testset "Create With Only Required Label" begin + path_schema = joinpath(tests_path(), "schemas", "valid", "collections.sql") + db = PSRDatabase.from_schema(":memory:", path_schema) + + PSRDatabase.create_element!(db, "Configuration"; label = "Test Config") + + # Create with only required label, no optional attributes + PSRDatabase.create_element!(db, "Collection"; label = "Minimal Item") + + labels = PSRDatabase.read_scalar_strings(db, "Collection", "label") + @test length(labels) == 1 + @test labels[1] == "Minimal Item" + + PSRDatabase.close!(db) +end + +@testset "Create With Multiple Sets" begin + path_schema = joinpath(tests_path(), "schemas", "valid", "collections.sql") + db = PSRDatabase.from_schema(":memory:", path_schema) + + PSRDatabase.create_element!(db, "Configuration"; label = "Test Config") + + # Create element with set attribute containing multiple values + PSRDatabase.create_element!(db, "Collection"; label = "Item 1", tag = ["a", "b", "c", "d", "e"]) + + result = PSRDatabase.read_set_strings_by_id(db, "Collection", "tag", Int64(1)) + @test length(result) == 5 + @test sort(result) == ["a", "b", "c", "d", "e"] + + PSRDatabase.close!(db) +end + +@testset "Create Duplicate Label Rejected" begin + path_schema = joinpath(tests_path(), "schemas", "valid", "basic.sql") + db = PSRDatabase.from_schema(":memory:", path_schema) + + PSRDatabase.create_element!(db, "Configuration"; label = "Config 1") + + # Trying to create with same label should fail + @test_throws PSRDatabase.DatabaseException PSRDatabase.create_element!(db, "Configuration"; label = "Config 1") + + PSRDatabase.close!(db) +end + +@testset "Create With Float Vector" begin + path_schema = joinpath(tests_path(), "schemas", "valid", "collections.sql") + db = PSRDatabase.from_schema(":memory:", path_schema) + + PSRDatabase.create_element!(db, "Configuration"; label = "Test Config") + + PSRDatabase.create_element!(db, "Collection"; label = "Item 1", value_float = [1.1, 2.2, 3.3]) + + result = PSRDatabase.read_vector_doubles_by_id(db, "Collection", "value_float", Int64(1)) + @test length(result) == 3 + @test result == [1.1, 2.2, 3.3] + + PSRDatabase.close!(db) +end + end diff --git a/bindings/julia/test/test_lua_runner.jl b/bindings/julia/test/test_lua_runner.jl index 1468095..8c85882 100644 --- a/bindings/julia/test/test_lua_runner.jl +++ b/bindings/julia/test/test_lua_runner.jl @@ -86,4 +86,160 @@ end PSRDatabase.close!(db) end +# Error handling tests + +@testset "LuaRunner Undefined Variable" begin + path_schema = joinpath(tests_path(), "schemas", "valid", "collections.sql") + db = PSRDatabase.from_schema(":memory:", path_schema) + + lua = PSRDatabase.LuaRunner(db) + + # Script that references undefined variable + @test_throws PSRDatabase.DatabaseException PSRDatabase.run!(lua, "print(undefined_variable.field)") + + PSRDatabase.close!(lua) + PSRDatabase.close!(db) +end + +@testset "LuaRunner Create Invalid Collection" begin + path_schema = joinpath(tests_path(), "schemas", "valid", "collections.sql") + db = PSRDatabase.from_schema(":memory:", path_schema) + + lua = PSRDatabase.LuaRunner(db) + + PSRDatabase.run!(lua, """db:create_element("Configuration", { label = "Test Config" })""") + + # Script that creates element in nonexistent collection + @test_throws PSRDatabase.DatabaseException PSRDatabase.run!( + lua, + """db:create_element("NonexistentCollection", { label = "Item" })""", + ) + + PSRDatabase.close!(lua) + PSRDatabase.close!(db) +end + +@testset "LuaRunner Empty Script" begin + path_schema = joinpath(tests_path(), "schemas", "valid", "collections.sql") + db = PSRDatabase.from_schema(":memory:", path_schema) + + lua = PSRDatabase.LuaRunner(db) + + # Empty script should succeed without error + PSRDatabase.run!(lua, "") + @test true # If we get here, the empty script ran without error + + PSRDatabase.close!(lua) + PSRDatabase.close!(db) +end + +@testset "LuaRunner Comment Only Script" begin + path_schema = joinpath(tests_path(), "schemas", "valid", "collections.sql") + db = PSRDatabase.from_schema(":memory:", path_schema) + + lua = PSRDatabase.LuaRunner(db) + + # Comment-only script should succeed + PSRDatabase.run!(lua, "-- this is just a comment") + @test true + + PSRDatabase.close!(lua) + PSRDatabase.close!(db) +end + +@testset "LuaRunner Read Integers" begin + path_schema = joinpath(tests_path(), "schemas", "valid", "collections.sql") + db = PSRDatabase.from_schema(":memory:", path_schema) + + PSRDatabase.create_element!(db, "Configuration"; label = "Config") + PSRDatabase.create_element!(db, "Collection"; label = "Item 1", some_integer = 100) + PSRDatabase.create_element!(db, "Collection"; label = "Item 2", some_integer = 200) + + lua = PSRDatabase.LuaRunner(db) + + PSRDatabase.run!( + lua, + """ + local ints = db:read_scalar_integers("Collection", "some_integer") + assert(#ints == 2, "Expected 2 integers") + assert(ints[1] == 100, "First integer mismatch") + assert(ints[2] == 200, "Second integer mismatch") +""", + ) + + PSRDatabase.close!(lua) + PSRDatabase.close!(db) +end + +@testset "LuaRunner Read Doubles" begin + path_schema = joinpath(tests_path(), "schemas", "valid", "collections.sql") + db = PSRDatabase.from_schema(":memory:", path_schema) + + PSRDatabase.create_element!(db, "Configuration"; label = "Config") + PSRDatabase.create_element!(db, "Collection"; label = "Item 1", some_float = 1.5) + PSRDatabase.create_element!(db, "Collection"; label = "Item 2", some_float = 2.5) + + lua = PSRDatabase.LuaRunner(db) + + PSRDatabase.run!( + lua, + """ + local floats = db:read_scalar_doubles("Collection", "some_float") + assert(#floats == 2, "Expected 2 doubles") + assert(floats[1] == 1.5, "First double mismatch") + assert(floats[2] == 2.5, "Second double mismatch") +""", + ) + + PSRDatabase.close!(lua) + PSRDatabase.close!(db) +end + +@testset "LuaRunner Read Vectors" begin + path_schema = joinpath(tests_path(), "schemas", "valid", "collections.sql") + db = PSRDatabase.from_schema(":memory:", path_schema) + + PSRDatabase.create_element!(db, "Configuration"; label = "Config") + PSRDatabase.create_element!(db, "Collection"; label = "Item 1", value_int = [1, 2, 3]) + + lua = PSRDatabase.LuaRunner(db) + + PSRDatabase.run!( + lua, + """ + local vectors = db:read_vector_integers("Collection", "value_int") + assert(#vectors == 1, "Expected 1 vector") + assert(#vectors[1] == 3, "Expected 3 elements in vector") + assert(vectors[1][1] == 1, "First element mismatch") + assert(vectors[1][2] == 2, "Second element mismatch") + assert(vectors[1][3] == 3, "Third element mismatch") +""", + ) + + PSRDatabase.close!(lua) + PSRDatabase.close!(db) +end + +@testset "LuaRunner Create With Vector" begin + path_schema = joinpath(tests_path(), "schemas", "valid", "collections.sql") + db = PSRDatabase.from_schema(":memory:", path_schema) + + lua = PSRDatabase.LuaRunner(db) + + PSRDatabase.run!( + lua, + """ + db:create_element("Configuration", { label = "Config" }) + db:create_element("Collection", { label = "Item 1", value_int = {10, 20, 30} }) +""", + ) + + result = PSRDatabase.read_vector_integers(db, "Collection", "value_int") + @test length(result) == 1 + @test result[1] == [10, 20, 30] + + PSRDatabase.close!(lua) + PSRDatabase.close!(db) +end + end diff --git a/bindings/julia/test/test_read.jl b/bindings/julia/test/test_read.jl index dd5161a..fe92174 100644 --- a/bindings/julia/test/test_read.jl +++ b/bindings/julia/test/test_read.jl @@ -431,4 +431,97 @@ end PSRDatabase.close!(db) end +# Error handling tests + +@testset "Read Invalid Collection" begin + path_schema = joinpath(tests_path(), "schemas", "valid", "basic.sql") + db = PSRDatabase.from_schema(":memory:", path_schema) + + @test_throws PSRDatabase.DatabaseException PSRDatabase.read_scalar_strings(db, "NonexistentCollection", "label") + @test_throws PSRDatabase.DatabaseException PSRDatabase.read_scalar_integers(db, "NonexistentCollection", "value") + @test_throws PSRDatabase.DatabaseException PSRDatabase.read_scalar_doubles(db, "NonexistentCollection", "value") + + PSRDatabase.close!(db) +end + +@testset "Read Invalid Attribute" begin + path_schema = joinpath(tests_path(), "schemas", "valid", "basic.sql") + db = PSRDatabase.from_schema(":memory:", path_schema) + + @test_throws PSRDatabase.DatabaseException PSRDatabase.read_scalar_strings(db, "Configuration", "nonexistent_attr") + @test_throws PSRDatabase.DatabaseException PSRDatabase.read_scalar_integers(db, "Configuration", "nonexistent_attr") + @test_throws PSRDatabase.DatabaseException PSRDatabase.read_scalar_doubles(db, "Configuration", "nonexistent_attr") + + PSRDatabase.close!(db) +end + +@testset "Read By ID Not Found" begin + path_schema = joinpath(tests_path(), "schemas", "valid", "basic.sql") + db = PSRDatabase.from_schema(":memory:", path_schema) + + PSRDatabase.create_element!(db, "Configuration"; label = "Config 1", integer_attribute = 42) + + # Read by ID that doesn't exist returns nothing + @test PSRDatabase.read_scalar_integers_by_id(db, "Configuration", "integer_attribute", 999) === nothing + @test PSRDatabase.read_scalar_doubles_by_id(db, "Configuration", "float_attribute", 999) === nothing + @test PSRDatabase.read_scalar_strings_by_id(db, "Configuration", "string_attribute", 999) === nothing + + PSRDatabase.close!(db) +end + +@testset "Read Vector Invalid Collection" begin + path_schema = joinpath(tests_path(), "schemas", "valid", "collections.sql") + db = PSRDatabase.from_schema(":memory:", path_schema) + + PSRDatabase.create_element!(db, "Configuration"; label = "Test Config") + + @test_throws PSRDatabase.DatabaseException PSRDatabase.read_vector_integers( + db, + "NonexistentCollection", + "value_int", + ) + @test_throws PSRDatabase.DatabaseException PSRDatabase.read_vector_doubles( + db, + "NonexistentCollection", + "value_float", + ) + + PSRDatabase.close!(db) +end + +@testset "Read Set Invalid Collection" begin + path_schema = joinpath(tests_path(), "schemas", "valid", "collections.sql") + db = PSRDatabase.from_schema(":memory:", path_schema) + + PSRDatabase.create_element!(db, "Configuration"; label = "Test Config") + + @test_throws PSRDatabase.DatabaseException PSRDatabase.read_set_strings(db, "NonexistentCollection", "tag") + + PSRDatabase.close!(db) +end + +@testset "Read Element IDs Invalid Collection" begin + path_schema = joinpath(tests_path(), "schemas", "valid", "basic.sql") + db = PSRDatabase.from_schema(":memory:", path_schema) + + @test_throws PSRDatabase.DatabaseException PSRDatabase.read_element_ids(db, "NonexistentCollection") + + PSRDatabase.close!(db) +end + +@testset "Read Scalar Relation Invalid Collection" begin + path_schema = joinpath(tests_path(), "schemas", "valid", "relations.sql") + db = PSRDatabase.from_schema(":memory:", path_schema) + + PSRDatabase.create_element!(db, "Configuration"; label = "Test Config") + + @test_throws PSRDatabase.DatabaseException PSRDatabase.read_scalar_relation( + db, + "NonexistentCollection", + "parent_id", + ) + + PSRDatabase.close!(db) +end + end diff --git a/bindings/julia/test/test_update.jl b/bindings/julia/test/test_update.jl index bc240a3..5add682 100644 --- a/bindings/julia/test/test_update.jl +++ b/bindings/julia/test/test_update.jl @@ -123,4 +123,99 @@ end PSRDatabase.close!(db) end +# Error handling tests + +@testset "Update Invalid Collection" begin + path_schema = joinpath(tests_path(), "schemas", "valid", "basic.sql") + db = PSRDatabase.from_schema(":memory:", path_schema) + + PSRDatabase.create_element!(db, "Configuration"; label = "Config 1", integer_attribute = 100) + + @test_throws PSRDatabase.DatabaseException PSRDatabase.update_element!( + db, + "NonexistentCollection", + Int64(1); + integer_attribute = 999, + ) + + PSRDatabase.close!(db) +end + +@testset "Update Invalid Element ID" begin + path_schema = joinpath(tests_path(), "schemas", "valid", "basic.sql") + db = PSRDatabase.from_schema(":memory:", path_schema) + + PSRDatabase.create_element!(db, "Configuration"; label = "Config 1", integer_attribute = 100) + + # Update element that doesn't exist - should not throw but also should not affect anything + PSRDatabase.update_element!(db, "Configuration", Int64(999); integer_attribute = 500) + + # Verify original element unchanged + value = PSRDatabase.read_scalar_integers_by_id(db, "Configuration", "integer_attribute", Int64(1)) + @test value == 100 + + PSRDatabase.close!(db) +end + +@testset "Update Invalid Attribute" begin + path_schema = joinpath(tests_path(), "schemas", "valid", "basic.sql") + db = PSRDatabase.from_schema(":memory:", path_schema) + + PSRDatabase.create_element!(db, "Configuration"; label = "Config 1", integer_attribute = 100) + + @test_throws PSRDatabase.DatabaseException PSRDatabase.update_element!( + db, + "Configuration", + Int64(1); + nonexistent_attribute = 999, + ) + + PSRDatabase.close!(db) +end + +@testset "Update String Attribute" begin + path_schema = joinpath(tests_path(), "schemas", "valid", "basic.sql") + db = PSRDatabase.from_schema(":memory:", path_schema) + + PSRDatabase.create_element!(db, "Configuration"; label = "Config 1", string_attribute = "original") + + PSRDatabase.update_element!(db, "Configuration", Int64(1); string_attribute = "updated") + + value = PSRDatabase.read_scalar_strings_by_id(db, "Configuration", "string_attribute", Int64(1)) + @test value == "updated" + + PSRDatabase.close!(db) +end + +@testset "Update Float Attribute" begin + path_schema = joinpath(tests_path(), "schemas", "valid", "basic.sql") + db = PSRDatabase.from_schema(":memory:", path_schema) + + PSRDatabase.create_element!(db, "Configuration"; label = "Config 1", float_attribute = 1.5) + + PSRDatabase.update_element!(db, "Configuration", Int64(1); float_attribute = 99.99) + + value = PSRDatabase.read_scalar_doubles_by_id(db, "Configuration", "float_attribute", Int64(1)) + @test value == 99.99 + + PSRDatabase.close!(db) +end + +@testset "Update Multiple Elements Sequentially" begin + path_schema = joinpath(tests_path(), "schemas", "valid", "basic.sql") + db = PSRDatabase.from_schema(":memory:", path_schema) + + PSRDatabase.create_element!(db, "Configuration"; label = "Config 1", integer_attribute = 100) + PSRDatabase.create_element!(db, "Configuration"; label = "Config 2", integer_attribute = 200) + + # Update both elements + PSRDatabase.update_element!(db, "Configuration", Int64(1); integer_attribute = 111) + PSRDatabase.update_element!(db, "Configuration", Int64(2); integer_attribute = 222) + + @test PSRDatabase.read_scalar_integers_by_id(db, "Configuration", "integer_attribute", Int64(1)) == 111 + @test PSRDatabase.read_scalar_integers_by_id(db, "Configuration", "integer_attribute", Int64(2)) == 222 + + PSRDatabase.close!(db) +end + end diff --git a/docs/rules.md b/docs/rules.md index dbddb1a..e133d7a 100644 --- a/docs/rules.md +++ b/docs/rules.md @@ -194,7 +194,3 @@ by the function `PSRDatabase.set_migrations_folder` and after that you can creat ### Running migrations To run migrations you need to use the function `PSRDatabase.apply_migrations!`. There are various versions of this function, each one tailored to make something easier for the user. - -### Testing migrations - -It is very important to test if the migrations of a certain model are working as expected, so the user can be sure that the database schema is updated correctly. To test migrations you need to use the function `PSRDatabase.test_migrations()`. It is highly advised that each model has one of these functions in their test suite to make sure that the migrations are working as expected. \ No newline at end of file diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index a3303b0..b50a620 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -9,7 +9,6 @@ add_executable(psr_database_tests test_database_update.cpp test_element.cpp test_lua_runner.cpp - test_migrations/test_migrations.cpp test_schema_validator.cpp ) diff --git a/tests/test_migrations/chaining_migrations/1/down.sql b/tests/schemas/migrations/1/down.sql similarity index 100% rename from tests/test_migrations/chaining_migrations/1/down.sql rename to tests/schemas/migrations/1/down.sql diff --git a/tests/test_migrations/chaining_migrations/1/up.sql b/tests/schemas/migrations/1/up.sql similarity index 100% rename from tests/test_migrations/chaining_migrations/1/up.sql rename to tests/schemas/migrations/1/up.sql diff --git a/tests/test_migrations/chaining_migrations/2/down.sql b/tests/schemas/migrations/2/down.sql similarity index 100% rename from tests/test_migrations/chaining_migrations/2/down.sql rename to tests/schemas/migrations/2/down.sql diff --git a/tests/test_migrations/chaining_migrations/2/up.sql b/tests/schemas/migrations/2/up.sql similarity index 100% rename from tests/test_migrations/chaining_migrations/2/up.sql rename to tests/schemas/migrations/2/up.sql diff --git a/tests/test_migrations/chaining_migrations/3/down.sql b/tests/schemas/migrations/3/down.sql similarity index 100% rename from tests/test_migrations/chaining_migrations/3/down.sql rename to tests/schemas/migrations/3/down.sql diff --git a/tests/test_migrations/chaining_migrations/3/up.sql b/tests/schemas/migrations/3/up.sql similarity index 100% rename from tests/test_migrations/chaining_migrations/3/up.sql rename to tests/schemas/migrations/3/up.sql diff --git a/tests/test_c_api_database_lifecycle.cpp b/tests/test_c_api_database_lifecycle.cpp index c820a5d..e61b246 100644 --- a/tests/test_c_api_database_lifecycle.cpp +++ b/tests/test_c_api_database_lifecycle.cpp @@ -1,3 +1,5 @@ +#include "test_utils.h" + #include #include #include @@ -175,3 +177,245 @@ TEST_F(TempFileFixture, OpenReadOnly) { psr_database_close(db); } + +// ============================================================================ +// Current version tests +// ============================================================================ + +TEST_F(TempFileFixture, CurrentVersionNullDb) { + auto version = psr_database_current_version(nullptr); + EXPECT_EQ(version, -1); +} + +TEST_F(TempFileFixture, CurrentVersionValid) { + auto options = psr_database_options_default(); + options.console_level = PSR_LOG_OFF; + auto db = psr_database_open(":memory:", &options); + ASSERT_NE(db, nullptr); + + auto version = psr_database_current_version(db); + EXPECT_EQ(version, 0); + + psr_database_close(db); +} + +// ============================================================================ +// From schema error tests +// ============================================================================ + +TEST_F(TempFileFixture, FromSchemaNullDbPath) { + auto options = psr_database_options_default(); + options.console_level = PSR_LOG_OFF; + auto db = psr_database_from_schema(nullptr, "schema.sql", &options); + EXPECT_EQ(db, nullptr); +} + +TEST_F(TempFileFixture, FromSchemaNullSchemaPath) { + auto options = psr_database_options_default(); + options.console_level = PSR_LOG_OFF; + auto db = psr_database_from_schema(":memory:", nullptr, &options); + EXPECT_EQ(db, nullptr); +} + +TEST_F(TempFileFixture, FromSchemaInvalidPath) { + auto options = psr_database_options_default(); + options.console_level = PSR_LOG_OFF; + auto db = psr_database_from_schema(":memory:", "nonexistent/path/schema.sql", &options); + EXPECT_EQ(db, nullptr); +} + +// ============================================================================ +// From migrations tests +// ============================================================================ + +TEST_F(TempFileFixture, FromMigrationsNullDbPath) { + auto options = psr_database_options_default(); + options.console_level = PSR_LOG_OFF; + auto db = psr_database_from_migrations(nullptr, "migrations/", &options); + EXPECT_EQ(db, nullptr); +} + +TEST_F(TempFileFixture, FromMigrationsNullMigrationsPath) { + auto options = psr_database_options_default(); + options.console_level = PSR_LOG_OFF; + auto db = psr_database_from_migrations(":memory:", nullptr, &options); + EXPECT_EQ(db, nullptr); +} + +TEST_F(TempFileFixture, FromMigrationsInvalidPath) { + auto options = psr_database_options_default(); + options.console_level = PSR_LOG_OFF; + auto db = psr_database_from_migrations(":memory:", "nonexistent/migrations/", &options); + // Invalid migrations path results in database with version 0 (no migrations applied) + ASSERT_NE(db, nullptr); + EXPECT_EQ(psr_database_current_version(db), 0); + psr_database_close(db); +} + +// ============================================================================ +// Relation operation tests +// ============================================================================ + +TEST_F(TempFileFixture, SetScalarRelationNullDb) { + auto err = psr_database_set_scalar_relation(nullptr, "Child", "parent_id", "Child 1", "Parent 1"); + EXPECT_EQ(err, PSR_ERROR_INVALID_ARGUMENT); +} + +TEST_F(TempFileFixture, SetScalarRelationNullCollection) { + auto options = psr_database_options_default(); + options.console_level = PSR_LOG_OFF; + auto db = psr_database_from_schema(":memory:", VALID_SCHEMA("relations.sql").c_str(), &options); + ASSERT_NE(db, nullptr); + + auto err = psr_database_set_scalar_relation(db, nullptr, "parent_id", "Child 1", "Parent 1"); + EXPECT_EQ(err, PSR_ERROR_INVALID_ARGUMENT); + + psr_database_close(db); +} + +TEST_F(TempFileFixture, SetScalarRelationNullAttribute) { + auto options = psr_database_options_default(); + options.console_level = PSR_LOG_OFF; + auto db = psr_database_from_schema(":memory:", VALID_SCHEMA("relations.sql").c_str(), &options); + ASSERT_NE(db, nullptr); + + auto err = psr_database_set_scalar_relation(db, "Child", nullptr, "Child 1", "Parent 1"); + EXPECT_EQ(err, PSR_ERROR_INVALID_ARGUMENT); + + psr_database_close(db); +} + +TEST_F(TempFileFixture, SetScalarRelationNullFromLabel) { + auto options = psr_database_options_default(); + options.console_level = PSR_LOG_OFF; + auto db = psr_database_from_schema(":memory:", VALID_SCHEMA("relations.sql").c_str(), &options); + ASSERT_NE(db, nullptr); + + auto err = psr_database_set_scalar_relation(db, "Child", "parent_id", nullptr, "Parent 1"); + EXPECT_EQ(err, PSR_ERROR_INVALID_ARGUMENT); + + psr_database_close(db); +} + +TEST_F(TempFileFixture, SetScalarRelationNullToLabel) { + auto options = psr_database_options_default(); + options.console_level = PSR_LOG_OFF; + auto db = psr_database_from_schema(":memory:", VALID_SCHEMA("relations.sql").c_str(), &options); + ASSERT_NE(db, nullptr); + + auto err = psr_database_set_scalar_relation(db, "Child", "parent_id", "Child 1", nullptr); + EXPECT_EQ(err, PSR_ERROR_INVALID_ARGUMENT); + + psr_database_close(db); +} + +TEST_F(TempFileFixture, SetScalarRelationValid) { + auto options = psr_database_options_default(); + options.console_level = PSR_LOG_OFF; + auto db = psr_database_from_schema(":memory:", VALID_SCHEMA("relations.sql").c_str(), &options); + ASSERT_NE(db, nullptr); + + // Create parent + auto parent = psr_element_create(); + psr_element_set_string(parent, "label", "Parent 1"); + psr_database_create_element(db, "Parent", parent); + psr_element_destroy(parent); + + // Create child + auto child = psr_element_create(); + psr_element_set_string(child, "label", "Child 1"); + psr_database_create_element(db, "Child", child); + psr_element_destroy(child); + + // Set relation + auto err = psr_database_set_scalar_relation(db, "Child", "parent_id", "Child 1", "Parent 1"); + EXPECT_EQ(err, PSR_OK); + + psr_database_close(db); +} + +TEST_F(TempFileFixture, ReadScalarRelationNullDb) { + char** values = nullptr; + size_t count = 0; + auto err = psr_database_read_scalar_relation(nullptr, "Child", "parent_id", &values, &count); + EXPECT_EQ(err, PSR_ERROR_INVALID_ARGUMENT); +} + +TEST_F(TempFileFixture, ReadScalarRelationNullCollection) { + auto options = psr_database_options_default(); + options.console_level = PSR_LOG_OFF; + auto db = psr_database_from_schema(":memory:", VALID_SCHEMA("relations.sql").c_str(), &options); + ASSERT_NE(db, nullptr); + + char** values = nullptr; + size_t count = 0; + auto err = psr_database_read_scalar_relation(db, nullptr, "parent_id", &values, &count); + EXPECT_EQ(err, PSR_ERROR_INVALID_ARGUMENT); + + psr_database_close(db); +} + +TEST_F(TempFileFixture, ReadScalarRelationNullAttribute) { + auto options = psr_database_options_default(); + options.console_level = PSR_LOG_OFF; + auto db = psr_database_from_schema(":memory:", VALID_SCHEMA("relations.sql").c_str(), &options); + ASSERT_NE(db, nullptr); + + char** values = nullptr; + size_t count = 0; + auto err = psr_database_read_scalar_relation(db, "Child", nullptr, &values, &count); + EXPECT_EQ(err, PSR_ERROR_INVALID_ARGUMENT); + + psr_database_close(db); +} + +TEST_F(TempFileFixture, ReadScalarRelationNullOutput) { + auto options = psr_database_options_default(); + options.console_level = PSR_LOG_OFF; + auto db = psr_database_from_schema(":memory:", VALID_SCHEMA("relations.sql").c_str(), &options); + ASSERT_NE(db, nullptr); + + size_t count = 0; + auto err = psr_database_read_scalar_relation(db, "Child", "parent_id", nullptr, &count); + EXPECT_EQ(err, PSR_ERROR_INVALID_ARGUMENT); + + char** values = nullptr; + err = psr_database_read_scalar_relation(db, "Child", "parent_id", &values, nullptr); + EXPECT_EQ(err, PSR_ERROR_INVALID_ARGUMENT); + + psr_database_close(db); +} + +TEST_F(TempFileFixture, ReadScalarRelationValid) { + auto options = psr_database_options_default(); + options.console_level = PSR_LOG_OFF; + auto db = psr_database_from_schema(":memory:", VALID_SCHEMA("relations.sql").c_str(), &options); + ASSERT_NE(db, nullptr); + + // Create parent + auto parent = psr_element_create(); + psr_element_set_string(parent, "label", "Parent 1"); + psr_database_create_element(db, "Parent", parent); + psr_element_destroy(parent); + + // Create child + auto child = psr_element_create(); + psr_element_set_string(child, "label", "Child 1"); + psr_database_create_element(db, "Child", child); + psr_element_destroy(child); + + // Set relation + auto err = psr_database_set_scalar_relation(db, "Child", "parent_id", "Child 1", "Parent 1"); + EXPECT_EQ(err, PSR_OK); + + // Read relation + char** values = nullptr; + size_t count = 0; + err = psr_database_read_scalar_relation(db, "Child", "parent_id", &values, &count); + EXPECT_EQ(err, PSR_OK); + EXPECT_EQ(count, 1); + EXPECT_STREQ(values[0], "Parent 1"); + + psr_free_string_array(values, count); + psr_database_close(db); +} diff --git a/tests/test_c_api_database_read.cpp b/tests/test_c_api_database_read.cpp index 01b198e..30aabed 100644 --- a/tests/test_c_api_database_read.cpp +++ b/tests/test_c_api_database_read.cpp @@ -951,3 +951,585 @@ TEST(DatabaseCApi, GetAttributeTypeInvalidArgument) { psr_database_close(db); } + +// ============================================================================ +// Read scalar null pointer tests +// ============================================================================ + +TEST(DatabaseCApi, ReadScalarIntegersNullDb) { + int64_t* values = nullptr; + size_t count = 0; + auto err = psr_database_read_scalar_integers(nullptr, "Configuration", "integer_attribute", &values, &count); + EXPECT_EQ(err, PSR_ERROR_INVALID_ARGUMENT); +} + +TEST(DatabaseCApi, ReadScalarIntegersNullCollection) { + 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); + + int64_t* values = nullptr; + size_t count = 0; + auto err = psr_database_read_scalar_integers(db, nullptr, "integer_attribute", &values, &count); + EXPECT_EQ(err, PSR_ERROR_INVALID_ARGUMENT); + + psr_database_close(db); +} + +TEST(DatabaseCApi, ReadScalarIntegersNullAttribute) { + 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); + + int64_t* values = nullptr; + size_t count = 0; + auto err = psr_database_read_scalar_integers(db, "Configuration", nullptr, &values, &count); + EXPECT_EQ(err, PSR_ERROR_INVALID_ARGUMENT); + + psr_database_close(db); +} + +TEST(DatabaseCApi, ReadScalarIntegersNullOutput) { + 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); + + size_t count = 0; + auto err = psr_database_read_scalar_integers(db, "Configuration", "integer_attribute", nullptr, &count); + EXPECT_EQ(err, PSR_ERROR_INVALID_ARGUMENT); + + int64_t* values = nullptr; + err = psr_database_read_scalar_integers(db, "Configuration", "integer_attribute", &values, nullptr); + EXPECT_EQ(err, PSR_ERROR_INVALID_ARGUMENT); + + psr_database_close(db); +} + +TEST(DatabaseCApi, ReadScalarDoublesNullDb) { + double* values = nullptr; + size_t count = 0; + auto err = psr_database_read_scalar_doubles(nullptr, "Configuration", "float_attribute", &values, &count); + EXPECT_EQ(err, PSR_ERROR_INVALID_ARGUMENT); +} + +TEST(DatabaseCApi, ReadScalarDoublesNullCollection) { + 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); + + double* values = nullptr; + size_t count = 0; + auto err = psr_database_read_scalar_doubles(db, nullptr, "float_attribute", &values, &count); + EXPECT_EQ(err, PSR_ERROR_INVALID_ARGUMENT); + + psr_database_close(db); +} + +TEST(DatabaseCApi, ReadScalarDoublesNullOutput) { + 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); + + size_t count = 0; + auto err = psr_database_read_scalar_doubles(db, "Configuration", "float_attribute", nullptr, &count); + EXPECT_EQ(err, PSR_ERROR_INVALID_ARGUMENT); + + double* values = nullptr; + err = psr_database_read_scalar_doubles(db, "Configuration", "float_attribute", &values, nullptr); + EXPECT_EQ(err, PSR_ERROR_INVALID_ARGUMENT); + + psr_database_close(db); +} + +TEST(DatabaseCApi, ReadScalarStringsNullDb) { + char** values = nullptr; + size_t count = 0; + auto err = psr_database_read_scalar_strings(nullptr, "Configuration", "string_attribute", &values, &count); + EXPECT_EQ(err, PSR_ERROR_INVALID_ARGUMENT); +} + +TEST(DatabaseCApi, ReadScalarStringsNullCollection) { + 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); + + char** values = nullptr; + size_t count = 0; + auto err = psr_database_read_scalar_strings(db, nullptr, "string_attribute", &values, &count); + EXPECT_EQ(err, PSR_ERROR_INVALID_ARGUMENT); + + psr_database_close(db); +} + +TEST(DatabaseCApi, ReadScalarStringsNullOutput) { + 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); + + size_t count = 0; + auto err = psr_database_read_scalar_strings(db, "Configuration", "string_attribute", nullptr, &count); + EXPECT_EQ(err, PSR_ERROR_INVALID_ARGUMENT); + + char** values = nullptr; + err = psr_database_read_scalar_strings(db, "Configuration", "string_attribute", &values, nullptr); + EXPECT_EQ(err, PSR_ERROR_INVALID_ARGUMENT); + + psr_database_close(db); +} + +// ============================================================================ +// Read scalar by ID null pointer tests +// ============================================================================ + +TEST(DatabaseCApi, ReadScalarIntegersByIdNullDb) { + int64_t value; + int has_value; + auto err = + psr_database_read_scalar_integers_by_id(nullptr, "Configuration", "integer_attribute", 1, &value, &has_value); + EXPECT_EQ(err, PSR_ERROR_INVALID_ARGUMENT); +} + +TEST(DatabaseCApi, ReadScalarIntegersByIdNullCollection) { + 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); + + int64_t value; + int has_value; + auto err = psr_database_read_scalar_integers_by_id(db, nullptr, "integer_attribute", 1, &value, &has_value); + EXPECT_EQ(err, PSR_ERROR_INVALID_ARGUMENT); + + psr_database_close(db); +} + +TEST(DatabaseCApi, ReadScalarIntegersByIdNullOutput) { + 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); + + int has_value; + auto err = + psr_database_read_scalar_integers_by_id(db, "Configuration", "integer_attribute", 1, nullptr, &has_value); + EXPECT_EQ(err, PSR_ERROR_INVALID_ARGUMENT); + + int64_t value; + err = psr_database_read_scalar_integers_by_id(db, "Configuration", "integer_attribute", 1, &value, nullptr); + EXPECT_EQ(err, PSR_ERROR_INVALID_ARGUMENT); + + psr_database_close(db); +} + +TEST(DatabaseCApi, ReadScalarDoublesByIdNullDb) { + double value; + int has_value; + auto err = + psr_database_read_scalar_doubles_by_id(nullptr, "Configuration", "float_attribute", 1, &value, &has_value); + EXPECT_EQ(err, PSR_ERROR_INVALID_ARGUMENT); +} + +TEST(DatabaseCApi, ReadScalarDoublesByIdNullOutput) { + 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); + + int has_value; + auto err = psr_database_read_scalar_doubles_by_id(db, "Configuration", "float_attribute", 1, nullptr, &has_value); + EXPECT_EQ(err, PSR_ERROR_INVALID_ARGUMENT); + + double value; + err = psr_database_read_scalar_doubles_by_id(db, "Configuration", "float_attribute", 1, &value, nullptr); + EXPECT_EQ(err, PSR_ERROR_INVALID_ARGUMENT); + + psr_database_close(db); +} + +TEST(DatabaseCApi, ReadScalarStringsByIdNullDb) { + char* value = nullptr; + int has_value; + auto err = + psr_database_read_scalar_strings_by_id(nullptr, "Configuration", "string_attribute", 1, &value, &has_value); + EXPECT_EQ(err, PSR_ERROR_INVALID_ARGUMENT); +} + +TEST(DatabaseCApi, ReadScalarStringsByIdNullOutput) { + 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); + + int has_value; + auto err = psr_database_read_scalar_strings_by_id(db, "Configuration", "string_attribute", 1, nullptr, &has_value); + EXPECT_EQ(err, PSR_ERROR_INVALID_ARGUMENT); + + char* value = nullptr; + err = psr_database_read_scalar_strings_by_id(db, "Configuration", "string_attribute", 1, &value, nullptr); + EXPECT_EQ(err, PSR_ERROR_INVALID_ARGUMENT); + + psr_database_close(db); +} + +// ============================================================================ +// Read vector null pointer tests +// ============================================================================ + +TEST(DatabaseCApi, ReadVectorIntegersNullDb) { + int64_t** vectors = nullptr; + size_t* sizes = nullptr; + size_t count = 0; + auto err = psr_database_read_vector_integers(nullptr, "Collection", "value_int", &vectors, &sizes, &count); + EXPECT_EQ(err, PSR_ERROR_INVALID_ARGUMENT); +} + +TEST(DatabaseCApi, ReadVectorIntegersNullCollection) { + 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** vectors = nullptr; + size_t* sizes = nullptr; + size_t count = 0; + auto err = psr_database_read_vector_integers(db, nullptr, "value_int", &vectors, &sizes, &count); + EXPECT_EQ(err, PSR_ERROR_INVALID_ARGUMENT); + + psr_database_close(db); +} + +TEST(DatabaseCApi, ReadVectorIntegersNullOutput) { + 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* sizes = nullptr; + size_t count = 0; + auto err = psr_database_read_vector_integers(db, "Collection", "value_int", nullptr, &sizes, &count); + EXPECT_EQ(err, PSR_ERROR_INVALID_ARGUMENT); + + int64_t** vectors = nullptr; + err = psr_database_read_vector_integers(db, "Collection", "value_int", &vectors, nullptr, &count); + EXPECT_EQ(err, PSR_ERROR_INVALID_ARGUMENT); + + err = psr_database_read_vector_integers(db, "Collection", "value_int", &vectors, &sizes, nullptr); + EXPECT_EQ(err, PSR_ERROR_INVALID_ARGUMENT); + + psr_database_close(db); +} + +TEST(DatabaseCApi, ReadVectorDoublesNullDb) { + double** vectors = nullptr; + size_t* sizes = nullptr; + size_t count = 0; + auto err = psr_database_read_vector_doubles(nullptr, "Collection", "value_float", &vectors, &sizes, &count); + EXPECT_EQ(err, PSR_ERROR_INVALID_ARGUMENT); +} + +TEST(DatabaseCApi, ReadVectorDoublesNullOutput) { + 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* sizes = nullptr; + size_t count = 0; + auto err = psr_database_read_vector_doubles(db, "Collection", "value_float", nullptr, &sizes, &count); + EXPECT_EQ(err, PSR_ERROR_INVALID_ARGUMENT); + + double** vectors = nullptr; + err = psr_database_read_vector_doubles(db, "Collection", "value_float", &vectors, nullptr, &count); + EXPECT_EQ(err, PSR_ERROR_INVALID_ARGUMENT); + + err = psr_database_read_vector_doubles(db, "Collection", "value_float", &vectors, &sizes, nullptr); + EXPECT_EQ(err, PSR_ERROR_INVALID_ARGUMENT); + + psr_database_close(db); +} + +TEST(DatabaseCApi, ReadVectorStringsNullDb) { + char**** vectors = nullptr; + size_t* sizes = nullptr; + size_t count = 0; + auto err = psr_database_read_vector_strings(nullptr, "Collection", "tag", vectors, &sizes, &count); + EXPECT_EQ(err, PSR_ERROR_INVALID_ARGUMENT); +} + +// ============================================================================ +// Read vector by ID null pointer tests +// ============================================================================ + +TEST(DatabaseCApi, ReadVectorIntegersByIdNullDb) { + int64_t* values = nullptr; + size_t count = 0; + auto err = psr_database_read_vector_integers_by_id(nullptr, "Collection", "value_int", 1, &values, &count); + EXPECT_EQ(err, PSR_ERROR_INVALID_ARGUMENT); +} + +TEST(DatabaseCApi, ReadVectorIntegersByIdNullCollection) { + 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* values = nullptr; + size_t count = 0; + auto err = psr_database_read_vector_integers_by_id(db, nullptr, "value_int", 1, &values, &count); + EXPECT_EQ(err, PSR_ERROR_INVALID_ARGUMENT); + + psr_database_close(db); +} + +TEST(DatabaseCApi, ReadVectorIntegersByIdNullOutput) { + 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_vector_integers_by_id(db, "Collection", "value_int", 1, nullptr, &count); + EXPECT_EQ(err, PSR_ERROR_INVALID_ARGUMENT); + + int64_t* values = nullptr; + err = psr_database_read_vector_integers_by_id(db, "Collection", "value_int", 1, &values, nullptr); + EXPECT_EQ(err, PSR_ERROR_INVALID_ARGUMENT); + + psr_database_close(db); +} + +TEST(DatabaseCApi, ReadVectorDoublesByIdNullDb) { + double* values = nullptr; + size_t count = 0; + auto err = psr_database_read_vector_doubles_by_id(nullptr, "Collection", "value_float", 1, &values, &count); + EXPECT_EQ(err, PSR_ERROR_INVALID_ARGUMENT); +} + +TEST(DatabaseCApi, ReadVectorDoublesByIdNullOutput) { + 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_vector_doubles_by_id(db, "Collection", "value_float", 1, nullptr, &count); + EXPECT_EQ(err, PSR_ERROR_INVALID_ARGUMENT); + + double* values = nullptr; + err = psr_database_read_vector_doubles_by_id(db, "Collection", "value_float", 1, &values, nullptr); + EXPECT_EQ(err, PSR_ERROR_INVALID_ARGUMENT); + + psr_database_close(db); +} + +TEST(DatabaseCApi, ReadVectorStringsByIdNullDb) { + char** values = nullptr; + size_t count = 0; + auto err = psr_database_read_vector_strings_by_id(nullptr, "Collection", "tag", 1, &values, &count); + EXPECT_EQ(err, PSR_ERROR_INVALID_ARGUMENT); +} + +// ============================================================================ +// Read set null pointer tests +// ============================================================================ + +TEST(DatabaseCApi, ReadSetIntegersNullDb) { + int64_t** sets = nullptr; + size_t* sizes = nullptr; + size_t count = 0; + auto err = psr_database_read_set_integers(nullptr, "Collection", "tag", &sets, &sizes, &count); + EXPECT_EQ(err, PSR_ERROR_INVALID_ARGUMENT); +} + +TEST(DatabaseCApi, ReadSetIntegersNullCollection) { + 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** sets = nullptr; + size_t* sizes = nullptr; + size_t count = 0; + auto err = psr_database_read_set_integers(db, nullptr, "tag", &sets, &sizes, &count); + EXPECT_EQ(err, PSR_ERROR_INVALID_ARGUMENT); + + psr_database_close(db); +} + +TEST(DatabaseCApi, ReadSetIntegersNullOutput) { + 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* sizes = nullptr; + size_t count = 0; + auto err = psr_database_read_set_integers(db, "Collection", "tag", nullptr, &sizes, &count); + EXPECT_EQ(err, PSR_ERROR_INVALID_ARGUMENT); + + int64_t** sets = nullptr; + err = psr_database_read_set_integers(db, "Collection", "tag", &sets, nullptr, &count); + EXPECT_EQ(err, PSR_ERROR_INVALID_ARGUMENT); + + err = psr_database_read_set_integers(db, "Collection", "tag", &sets, &sizes, nullptr); + EXPECT_EQ(err, PSR_ERROR_INVALID_ARGUMENT); + + psr_database_close(db); +} + +TEST(DatabaseCApi, ReadSetDoublesNullDb) { + double** sets = nullptr; + size_t* sizes = nullptr; + size_t count = 0; + auto err = psr_database_read_set_doubles(nullptr, "Collection", "tag", &sets, &sizes, &count); + EXPECT_EQ(err, PSR_ERROR_INVALID_ARGUMENT); +} + +TEST(DatabaseCApi, ReadSetStringsNullDb) { + char*** sets = nullptr; + size_t* sizes = nullptr; + size_t count = 0; + auto err = psr_database_read_set_strings(nullptr, "Collection", "tag", &sets, &sizes, &count); + EXPECT_EQ(err, PSR_ERROR_INVALID_ARGUMENT); +} + +TEST(DatabaseCApi, ReadSetStringsNullCollection) { + 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); + + char*** sets = nullptr; + size_t* sizes = nullptr; + size_t count = 0; + auto err = psr_database_read_set_strings(db, nullptr, "tag", &sets, &sizes, &count); + EXPECT_EQ(err, PSR_ERROR_INVALID_ARGUMENT); + + psr_database_close(db); +} + +TEST(DatabaseCApi, ReadSetStringsNullOutput) { + 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* sizes = nullptr; + size_t count = 0; + auto err = psr_database_read_set_strings(db, "Collection", "tag", nullptr, &sizes, &count); + EXPECT_EQ(err, PSR_ERROR_INVALID_ARGUMENT); + + char*** sets = nullptr; + err = psr_database_read_set_strings(db, "Collection", "tag", &sets, nullptr, &count); + EXPECT_EQ(err, PSR_ERROR_INVALID_ARGUMENT); + + err = psr_database_read_set_strings(db, "Collection", "tag", &sets, &sizes, nullptr); + EXPECT_EQ(err, PSR_ERROR_INVALID_ARGUMENT); + + psr_database_close(db); +} + +// ============================================================================ +// Read set by ID null pointer tests +// ============================================================================ + +TEST(DatabaseCApi, ReadSetIntegersByIdNullDb) { + int64_t* values = nullptr; + size_t count = 0; + auto err = psr_database_read_set_integers_by_id(nullptr, "Collection", "tag", 1, &values, &count); + EXPECT_EQ(err, PSR_ERROR_INVALID_ARGUMENT); +} + +TEST(DatabaseCApi, ReadSetDoublesByIdNullDb) { + double* values = nullptr; + size_t count = 0; + auto err = psr_database_read_set_doubles_by_id(nullptr, "Collection", "tag", 1, &values, &count); + EXPECT_EQ(err, PSR_ERROR_INVALID_ARGUMENT); +} + +TEST(DatabaseCApi, ReadSetStringsByIdNullDb) { + char** values = nullptr; + size_t count = 0; + auto err = psr_database_read_set_strings_by_id(nullptr, "Collection", "tag", 1, &values, &count); + EXPECT_EQ(err, PSR_ERROR_INVALID_ARGUMENT); +} + +TEST(DatabaseCApi, ReadSetStringsByIdNullCollection) { + 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); + + char** values = nullptr; + size_t count = 0; + auto err = psr_database_read_set_strings_by_id(db, nullptr, "tag", 1, &values, &count); + EXPECT_EQ(err, PSR_ERROR_INVALID_ARGUMENT); + + psr_database_close(db); +} + +TEST(DatabaseCApi, ReadSetStringsByIdNullOutput) { + 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_set_strings_by_id(db, "Collection", "tag", 1, nullptr, &count); + EXPECT_EQ(err, PSR_ERROR_INVALID_ARGUMENT); + + char** values = nullptr; + err = psr_database_read_set_strings_by_id(db, "Collection", "tag", 1, &values, nullptr); + EXPECT_EQ(err, PSR_ERROR_INVALID_ARGUMENT); + + psr_database_close(db); +} + +// ============================================================================ +// Read element IDs null pointer tests +// ============================================================================ + +TEST(DatabaseCApi, ReadElementIdsNullDb) { + int64_t* ids = nullptr; + size_t count = 0; + auto err = psr_database_read_element_ids(nullptr, "Configuration", &ids, &count); + EXPECT_EQ(err, PSR_ERROR_INVALID_ARGUMENT); +} + +TEST(DatabaseCApi, ReadElementIdsNullCollection) { + 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); + + 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(DatabaseCApi, ReadElementIdsNullOutput) { + 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); + + size_t count = 0; + auto err = psr_database_read_element_ids(db, "Configuration", nullptr, &count); + EXPECT_EQ(err, PSR_ERROR_INVALID_ARGUMENT); + + int64_t* ids = nullptr; + err = psr_database_read_element_ids(db, "Configuration", &ids, nullptr); + EXPECT_EQ(err, PSR_ERROR_INVALID_ARGUMENT); + + psr_database_close(db); +} diff --git a/tests/test_c_api_database_update.cpp b/tests/test_c_api_database_update.cpp index ecc63fc..09c6173 100644 --- a/tests/test_c_api_database_update.cpp +++ b/tests/test_c_api_database_update.cpp @@ -430,3 +430,230 @@ TEST(DatabaseCApi, UpdateElementNullArguments) { psr_element_destroy(element); psr_database_close(db); } + +// ============================================================================ +// Update scalar null pointer tests +// ============================================================================ + +TEST(DatabaseCApi, UpdateScalarIntegerNullDb) { + auto err = psr_database_update_scalar_integer(nullptr, "Configuration", "integer_attribute", 1, 42); + EXPECT_EQ(err, PSR_ERROR_INVALID_ARGUMENT); +} + +TEST(DatabaseCApi, UpdateScalarIntegerNullCollection) { + 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); + + auto err = psr_database_update_scalar_integer(db, nullptr, "integer_attribute", 1, 42); + EXPECT_EQ(err, PSR_ERROR_INVALID_ARGUMENT); + + psr_database_close(db); +} + +TEST(DatabaseCApi, UpdateScalarIntegerNullAttribute) { + 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); + + auto err = psr_database_update_scalar_integer(db, "Configuration", nullptr, 1, 42); + EXPECT_EQ(err, PSR_ERROR_INVALID_ARGUMENT); + + psr_database_close(db); +} + +TEST(DatabaseCApi, UpdateScalarDoubleNullDb) { + auto err = psr_database_update_scalar_double(nullptr, "Configuration", "float_attribute", 1, 3.14); + EXPECT_EQ(err, PSR_ERROR_INVALID_ARGUMENT); +} + +TEST(DatabaseCApi, UpdateScalarDoubleNullCollection) { + 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); + + auto err = psr_database_update_scalar_double(db, nullptr, "float_attribute", 1, 3.14); + EXPECT_EQ(err, PSR_ERROR_INVALID_ARGUMENT); + + psr_database_close(db); +} + +TEST(DatabaseCApi, UpdateScalarStringNullDb) { + auto err = psr_database_update_scalar_string(nullptr, "Configuration", "string_attribute", 1, "test"); + EXPECT_EQ(err, PSR_ERROR_INVALID_ARGUMENT); +} + +TEST(DatabaseCApi, UpdateScalarStringNullCollection) { + 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); + + auto err = psr_database_update_scalar_string(db, nullptr, "string_attribute", 1, "test"); + EXPECT_EQ(err, PSR_ERROR_INVALID_ARGUMENT); + + psr_database_close(db); +} + +TEST(DatabaseCApi, UpdateScalarStringNullValue) { + 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); + + auto err = psr_database_update_scalar_string(db, "Configuration", "string_attribute", 1, nullptr); + EXPECT_EQ(err, PSR_ERROR_INVALID_ARGUMENT); + + psr_database_close(db); +} + +// ============================================================================ +// Update vector null pointer tests +// ============================================================================ + +TEST(DatabaseCApi, UpdateVectorIntegersNullDb) { + int64_t values[] = {1, 2, 3}; + auto err = psr_database_update_vector_integers(nullptr, "Collection", "value_int", 1, values, 3); + EXPECT_EQ(err, PSR_ERROR_INVALID_ARGUMENT); +} + +TEST(DatabaseCApi, UpdateVectorIntegersNullCollection) { + 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 values[] = {1, 2, 3}; + auto err = psr_database_update_vector_integers(db, nullptr, "value_int", 1, values, 3); + EXPECT_EQ(err, PSR_ERROR_INVALID_ARGUMENT); + + psr_database_close(db); +} + +TEST(DatabaseCApi, UpdateVectorIntegersNullAttribute) { + 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 values[] = {1, 2, 3}; + auto err = psr_database_update_vector_integers(db, "Collection", nullptr, 1, values, 3); + EXPECT_EQ(err, PSR_ERROR_INVALID_ARGUMENT); + + psr_database_close(db); +} + +TEST(DatabaseCApi, UpdateVectorDoublesNullDb) { + double values[] = {1.0, 2.0, 3.0}; + auto err = psr_database_update_vector_doubles(nullptr, "Collection", "value_float", 1, values, 3); + EXPECT_EQ(err, PSR_ERROR_INVALID_ARGUMENT); +} + +TEST(DatabaseCApi, UpdateVectorDoublesNullCollection) { + 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); + + double values[] = {1.0, 2.0, 3.0}; + auto err = psr_database_update_vector_doubles(db, nullptr, "value_float", 1, values, 3); + EXPECT_EQ(err, PSR_ERROR_INVALID_ARGUMENT); + + psr_database_close(db); +} + +TEST(DatabaseCApi, UpdateVectorStringsNullDb) { + const char* values[] = {"a", "b", "c"}; + auto err = psr_database_update_vector_strings(nullptr, "Collection", "tag", 1, values, 3); + EXPECT_EQ(err, PSR_ERROR_INVALID_ARGUMENT); +} + +TEST(DatabaseCApi, UpdateVectorStringsNullCollection) { + 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); + + const char* values[] = {"a", "b", "c"}; + auto err = psr_database_update_vector_strings(db, nullptr, "tag", 1, values, 3); + EXPECT_EQ(err, PSR_ERROR_INVALID_ARGUMENT); + + psr_database_close(db); +} + +// ============================================================================ +// Update set null pointer tests +// ============================================================================ + +TEST(DatabaseCApi, UpdateSetIntegersNullDb) { + int64_t values[] = {1, 2, 3}; + auto err = psr_database_update_set_integers(nullptr, "Collection", "tag", 1, values, 3); + EXPECT_EQ(err, PSR_ERROR_INVALID_ARGUMENT); +} + +TEST(DatabaseCApi, UpdateSetIntegersNullCollection) { + 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 values[] = {1, 2, 3}; + auto err = psr_database_update_set_integers(db, nullptr, "tag", 1, values, 3); + EXPECT_EQ(err, PSR_ERROR_INVALID_ARGUMENT); + + psr_database_close(db); +} + +TEST(DatabaseCApi, UpdateSetDoublesNullDb) { + double values[] = {1.0, 2.0, 3.0}; + auto err = psr_database_update_set_doubles(nullptr, "Collection", "tag", 1, values, 3); + EXPECT_EQ(err, PSR_ERROR_INVALID_ARGUMENT); +} + +TEST(DatabaseCApi, UpdateSetDoublesNullCollection) { + 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); + + double values[] = {1.0, 2.0, 3.0}; + auto err = psr_database_update_set_doubles(db, nullptr, "tag", 1, values, 3); + EXPECT_EQ(err, PSR_ERROR_INVALID_ARGUMENT); + + psr_database_close(db); +} + +TEST(DatabaseCApi, UpdateSetStringsNullDb) { + const char* values[] = {"a", "b", "c"}; + auto err = psr_database_update_set_strings(nullptr, "Collection", "tag", 1, values, 3); + EXPECT_EQ(err, PSR_ERROR_INVALID_ARGUMENT); +} + +TEST(DatabaseCApi, UpdateSetStringsNullCollection) { + 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); + + const char* values[] = {"a", "b", "c"}; + auto err = psr_database_update_set_strings(db, nullptr, "tag", 1, values, 3); + EXPECT_EQ(err, PSR_ERROR_INVALID_ARGUMENT); + + psr_database_close(db); +} + +TEST(DatabaseCApi, UpdateSetStringsNullAttribute) { + 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); + + const char* values[] = {"a", "b", "c"}; + auto err = psr_database_update_set_strings(db, "Collection", nullptr, 1, values, 3); + EXPECT_EQ(err, PSR_ERROR_INVALID_ARGUMENT); + + psr_database_close(db); +} diff --git a/tests/test_c_api_lua_runner.cpp b/tests/test_c_api_lua_runner.cpp index aeba056..3d18004 100644 --- a/tests/test_c_api_lua_runner.cpp +++ b/tests/test_c_api_lua_runner.cpp @@ -296,3 +296,128 @@ TEST_F(LuaRunnerCApiTest, UpdateElement) { psr_lua_runner_free(lua); psr_database_close(db); } + +// ============================================================================ +// Additional C API LuaRunner error tests +// ============================================================================ + +TEST_F(LuaRunnerCApiTest, EmptyScript) { + auto options = psr_database_options_default(); + options.console_level = PSR_LOG_OFF; + auto db = psr_database_from_schema(":memory:", collections_schema.c_str(), &options); + ASSERT_NE(db, nullptr); + + auto lua = psr_lua_runner_new(db); + ASSERT_NE(lua, nullptr); + + auto result = psr_lua_runner_run(lua, ""); + EXPECT_EQ(result, PSR_OK); + + psr_lua_runner_free(lua); + psr_database_close(db); +} + +TEST_F(LuaRunnerCApiTest, CommentOnlyScript) { + auto options = psr_database_options_default(); + options.console_level = PSR_LOG_OFF; + auto db = psr_database_from_schema(":memory:", collections_schema.c_str(), &options); + ASSERT_NE(db, nullptr); + + auto lua = psr_lua_runner_new(db); + ASSERT_NE(lua, nullptr); + + auto result = psr_lua_runner_run(lua, "-- this is a comment\n-- another comment"); + EXPECT_EQ(result, PSR_OK); + + psr_lua_runner_free(lua); + psr_database_close(db); +} + +TEST_F(LuaRunnerCApiTest, AssertionFailure) { + auto options = psr_database_options_default(); + options.console_level = PSR_LOG_OFF; + auto db = psr_database_from_schema(":memory:", collections_schema.c_str(), &options); + ASSERT_NE(db, nullptr); + + auto lua = psr_lua_runner_new(db); + ASSERT_NE(lua, nullptr); + + auto result = psr_lua_runner_run(lua, "assert(false, 'Test assertion failure')"); + EXPECT_NE(result, PSR_OK); + + auto error = psr_lua_runner_get_error(lua); + EXPECT_NE(error, nullptr); + EXPECT_NE(std::string(error).find("assertion"), std::string::npos); + + psr_lua_runner_free(lua); + psr_database_close(db); +} + +TEST_F(LuaRunnerCApiTest, UndefinedVariableError) { + auto options = psr_database_options_default(); + options.console_level = PSR_LOG_OFF; + auto db = psr_database_from_schema(":memory:", collections_schema.c_str(), &options); + ASSERT_NE(db, nullptr); + + auto lua = psr_lua_runner_new(db); + ASSERT_NE(lua, nullptr); + + auto result = psr_lua_runner_run(lua, "local x = undefined_variable + 1"); + EXPECT_NE(result, PSR_OK); + + auto error = psr_lua_runner_get_error(lua); + EXPECT_NE(error, nullptr); + + psr_lua_runner_free(lua); + psr_database_close(db); +} + +TEST_F(LuaRunnerCApiTest, ErrorClearedAfterSuccessfulRun) { + auto options = psr_database_options_default(); + options.console_level = PSR_LOG_OFF; + auto db = psr_database_from_schema(":memory:", collections_schema.c_str(), &options); + ASSERT_NE(db, nullptr); + + auto lua = psr_lua_runner_new(db); + ASSERT_NE(lua, nullptr); + + // First, run a failing script + auto result = psr_lua_runner_run(lua, "invalid lua syntax !!!"); + EXPECT_NE(result, PSR_OK); + auto error1 = psr_lua_runner_get_error(lua); + EXPECT_NE(error1, nullptr); + + // Now run a successful script + result = psr_lua_runner_run(lua, "local x = 1 + 1"); + EXPECT_EQ(result, PSR_OK); + + psr_lua_runner_free(lua); + psr_database_close(db); +} + +TEST_F(LuaRunnerCApiTest, ReadVectorIntegersFromLua) { + auto options = psr_database_options_default(); + options.console_level = PSR_LOG_OFF; + auto db = psr_database_from_schema(":memory:", collections_schema.c_str(), &options); + ASSERT_NE(db, nullptr); + + auto lua = psr_lua_runner_new(db); + ASSERT_NE(lua, nullptr); + + auto result = psr_lua_runner_run(lua, R"( + db:create_element("Configuration", { label = "Config" }) + db:create_element("Collection", { + label = "Item 1", + value_int = {1, 2, 3} + }) + + local vectors = db:read_vector_integers("Collection", "value_int") + assert(#vectors == 1, "Expected 1 vector") + assert(#vectors[1] == 3, "Expected 3 values") + assert(vectors[1][1] == 1, "Expected first value to be 1") + )"); + EXPECT_EQ(result, PSR_OK); + + psr_lua_runner_free(lua); + psr_database_close(db); +} diff --git a/tests/test_database_create.cpp b/tests/test_database_create.cpp index 4ba3751..94667d7 100644 --- a/tests/test_database_create.cpp +++ b/tests/test_database_create.cpp @@ -128,3 +128,104 @@ TEST(Database, CreateMultipleElements) { auto labels = db.read_scalar_strings("Configuration", "label"); EXPECT_EQ(labels.size(), 2); } + +// ============================================================================ +// Vector/Set edge case tests +// ============================================================================ + +TEST(Database, CreateElementSingleElementVector) { + auto db = + psr::Database::from_schema(":memory:", VALID_SCHEMA("collections.sql"), {.console_level = psr::LogLevel::off}); + + psr::Element config; + config.set("label", std::string("Test Config")); + db.create_element("Configuration", config); + + psr::Element element; + element.set("label", std::string("Item 1")).set("value_int", std::vector{42}); + + int64_t id = db.create_element("Collection", element); + EXPECT_EQ(id, 1); + + auto vectors = db.read_vector_integers("Collection", "value_int"); + EXPECT_EQ(vectors.size(), 1); + EXPECT_EQ(vectors[0], (std::vector{42})); +} + +TEST(Database, CreateElementSingleElementSet) { + auto db = + psr::Database::from_schema(":memory:", VALID_SCHEMA("collections.sql"), {.console_level = psr::LogLevel::off}); + + psr::Element config; + config.set("label", std::string("Test Config")); + db.create_element("Configuration", config); + + psr::Element element; + element.set("label", std::string("Item 1")).set("tag", std::vector{"single_tag"}); + + int64_t id = db.create_element("Collection", element); + EXPECT_EQ(id, 1); + + auto sets = db.read_set_strings("Collection", "tag"); + EXPECT_EQ(sets.size(), 1); + EXPECT_EQ(sets[0], (std::vector{"single_tag"})); +} + +TEST(Database, CreateElementInvalidCollection) { + 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(Database, CreateElementLargeVector) { + auto db = + psr::Database::from_schema(":memory:", VALID_SCHEMA("collections.sql"), {.console_level = psr::LogLevel::off}); + + psr::Element config; + config.set("label", std::string("Test Config")); + db.create_element("Configuration", config); + + // Create a vector with many elements + std::vector large_vec; + for (int i = 0; i < 100; i++) { + large_vec.push_back(i); + } + + psr::Element element; + element.set("label", std::string("Item 1")).set("value_int", large_vec); + + int64_t id = db.create_element("Collection", element); + EXPECT_EQ(id, 1); + + auto vectors = db.read_vector_integers("Collection", "value_int"); + EXPECT_EQ(vectors.size(), 1); + EXPECT_EQ(vectors[0].size(), 100); + EXPECT_EQ(vectors[0][0], 0); + EXPECT_EQ(vectors[0][99], 99); +} + +TEST(Database, CreateElementWithNoOptionalAttributes) { + auto db = + psr::Database::from_schema(":memory:", VALID_SCHEMA("collections.sql"), {.console_level = psr::LogLevel::off}); + + psr::Element config; + config.set("label", std::string("Test Config")); + db.create_element("Configuration", config); + + // Create element with only required label + psr::Element element; + element.set("label", std::string("Item 1")); + + int64_t id = db.create_element("Collection", element); + EXPECT_EQ(id, 1); + + // Verify vector attributes are empty + auto vectors = db.read_vector_integers("Collection", "value_int"); + EXPECT_TRUE(vectors.empty()); + + auto sets = db.read_set_strings("Collection", "tag"); + EXPECT_TRUE(sets.empty()); +} diff --git a/tests/test_database_lifecycle.cpp b/tests/test_database_lifecycle.cpp index 8dea73b..89782a2 100644 --- a/tests/test_database_lifecycle.cpp +++ b/tests/test_database_lifecycle.cpp @@ -1,6 +1,9 @@ #include #include #include +#include +#include +#include namespace fs = std::filesystem; @@ -75,3 +78,226 @@ TEST_F(TempFileFixture, CurrentVersion) { psr::Database db(":memory:", {.console_level = psr::LogLevel::off}); EXPECT_EQ(db.current_version(), 0); } + +// ============================================================================ +// Schema error tests +// ============================================================================ + +TEST_F(TempFileFixture, FromSchemaFileNotFound) { + EXPECT_THROW( + psr::Database::from_schema(":memory:", "nonexistent/path/schema.sql", {.console_level = psr::LogLevel::off}), + std::runtime_error); +} + +TEST_F(TempFileFixture, FromSchemaInvalidPath) { + EXPECT_THROW(psr::Database::from_schema(":memory:", "", {.console_level = psr::LogLevel::off}), std::runtime_error); +} + +TEST_F(TempFileFixture, FromMigrationsInvalidPath) { + // Invalid migrations path results in database with version 0 (no migrations applied) + auto db = + psr::Database::from_migrations(":memory:", "nonexistent/migrations/", {.console_level = psr::LogLevel::off}); + EXPECT_EQ(db.current_version(), 0); +} + +// ============================================================================ +// Migration tests +// ============================================================================ + +class MigrationFixture : public ::testing::Test { +protected: + void SetUp() override { + path = (fs::temp_directory_path() / "psr_test.db").string(); + migrations_path = (fs::path(__FILE__).parent_path() / "schemas" / "migrations").string(); + } + void TearDown() override { + if (fs::exists(path)) + fs::remove(path); + } + std::string path; + std::string migrations_path; +}; + +// ============================================================================ +// Migration class tests +// ============================================================================ + +TEST_F(MigrationFixture, MigrationCreation) { + psr::Migration migration(1, migrations_path + "/1"); + EXPECT_EQ(migration.version(), 1); + EXPECT_FALSE(migration.path().empty()); +} + +TEST_F(MigrationFixture, MigrationUpSqlRead) { + psr::Migration migration(1, migrations_path + "/1"); + std::string sql = migration.up_sql(); + EXPECT_FALSE(sql.empty()); + EXPECT_NE(sql.find("CREATE TABLE Test1"), std::string::npos); +} + +TEST_F(MigrationFixture, MigrationDownSqlRead) { + psr::Migration migration(1, migrations_path + "/1"); + std::string sql = migration.down_sql(); + EXPECT_FALSE(sql.empty()); + EXPECT_NE(sql.find("DROP TABLE"), std::string::npos); +} + +TEST_F(MigrationFixture, MigrationComparison) { + psr::Migration m1(1, migrations_path + "/1"); + psr::Migration m2(2, migrations_path + "/2"); + psr::Migration m3(3, migrations_path + "/3"); + + EXPECT_TRUE(m1 < m2); + EXPECT_TRUE(m2 < m3); + EXPECT_TRUE(m1 < m3); + EXPECT_FALSE(m2 < m1); + + EXPECT_TRUE(m1 == m1); + EXPECT_FALSE(m1 == m2); + EXPECT_TRUE(m1 != m2); +} + +TEST_F(MigrationFixture, MigrationCopy) { + psr::Migration original(2, migrations_path + "/2"); + psr::Migration copy = original; + + EXPECT_EQ(copy.version(), original.version()); + EXPECT_EQ(copy.path(), original.path()); +} + +// ============================================================================ +// Migrations class tests +// ============================================================================ + +TEST_F(MigrationFixture, MigrationsLoad) { + psr::Migrations migrations(migrations_path); + + EXPECT_FALSE(migrations.empty()); + EXPECT_EQ(migrations.count(), 3u); + EXPECT_EQ(migrations.latest_version(), 3); +} + +TEST_F(MigrationFixture, MigrationsOrder) { + psr::Migrations migrations(migrations_path); + auto all = migrations.all(); + + ASSERT_EQ(all.size(), 3u); + EXPECT_EQ(all[0].version(), 1); + EXPECT_EQ(all[1].version(), 2); + EXPECT_EQ(all[2].version(), 3); +} + +TEST_F(MigrationFixture, MigrationsPending) { + psr::Migrations migrations(migrations_path); + + auto pending_from_0 = migrations.pending(0); + EXPECT_EQ(pending_from_0.size(), 3u); + + auto pending_from_1 = migrations.pending(1); + EXPECT_EQ(pending_from_1.size(), 2u); + EXPECT_EQ(pending_from_1[0].version(), 2); + + auto pending_from_2 = migrations.pending(2); + EXPECT_EQ(pending_from_2.size(), 1u); + EXPECT_EQ(pending_from_2[0].version(), 3); + + auto pending_from_3 = migrations.pending(3); + EXPECT_TRUE(pending_from_3.empty()); +} + +TEST_F(MigrationFixture, MigrationsIteration) { + psr::Migrations migrations(migrations_path); + + int64_t expected_version = 1; + for (const auto& migration : migrations) { + EXPECT_EQ(migration.version(), expected_version); + ++expected_version; + } + EXPECT_EQ(expected_version, 4); +} + +TEST_F(MigrationFixture, MigrationsEmptyPath) { + psr::Migrations migrations("non_existent_path"); + EXPECT_TRUE(migrations.empty()); + EXPECT_EQ(migrations.count(), 0u); + EXPECT_EQ(migrations.latest_version(), 0); +} + +// ============================================================================ +// Database migration tests +// ============================================================================ + +TEST_F(MigrationFixture, DatabaseCurrentVersion) { + psr::Database db(":memory:"); + EXPECT_EQ(db.current_version(), 0); +} + +TEST_F(MigrationFixture, DatabaseFromMigrations) { + auto db = psr::Database::from_migrations(path, migrations_path); + + EXPECT_EQ(db.current_version(), 3); + EXPECT_TRUE(db.is_healthy()); +} + +TEST_F(MigrationFixture, MigrationsInvalidDirectory) { + psr::Migrations migrations("nonexistent/path/to/migrations"); + EXPECT_TRUE(migrations.empty()); + EXPECT_EQ(migrations.count(), 0u); + EXPECT_EQ(migrations.latest_version(), 0); +} + +TEST_F(MigrationFixture, MigrationsPendingFromHigherVersion) { + psr::Migrations migrations(migrations_path); + + // If current version is higher than latest migration version + auto pending = migrations.pending(100); + EXPECT_TRUE(pending.empty()); +} + +TEST_F(MigrationFixture, DatabaseFromMigrationsInvalidPath) { + // Invalid migrations path results in database with version 0 (no migrations applied) + auto db = psr::Database::from_migrations(path, "nonexistent/migrations/"); + EXPECT_EQ(db.current_version(), 0); +} + +TEST_F(MigrationFixture, MigrationVersionZero) { + psr::Migrations migrations(migrations_path); + + // Version 0 should return all migrations as pending + auto pending = migrations.pending(0); + EXPECT_EQ(pending.size(), migrations.count()); +} + +TEST_F(MigrationFixture, MigrationsWithPartialApplication) { + // First apply first migration using from_migrations and then check + { + auto db = psr::Database::from_migrations(path, migrations_path); + EXPECT_EQ(db.current_version(), 3); + } + + // Reopen the database and verify it still has version 3 + { + psr::Database db(path); + EXPECT_EQ(db.current_version(), 3); + } +} + +TEST_F(MigrationFixture, MigrationGetByVersion) { + psr::Migrations migrations(migrations_path); + + auto all = migrations.all(); + ASSERT_EQ(all.size(), 3u); + + // Verify each migration has correct version + for (const auto& m : all) { + EXPECT_GE(m.version(), 1); + EXPECT_LE(m.version(), 3); + } +} + +TEST_F(MigrationFixture, DatabaseFromMigrationsMemory) { + auto db = psr::Database::from_migrations(":memory:", migrations_path); + + EXPECT_EQ(db.current_version(), 3); + EXPECT_TRUE(db.is_healthy()); +} diff --git a/tests/test_database_read.cpp b/tests/test_database_read.cpp index d41b933..6799c4a 100644 --- a/tests/test_database_read.cpp +++ b/tests/test_database_read.cpp @@ -536,3 +536,109 @@ TEST(Database, GetAttributeTypeCollectionNotFound) { EXPECT_THROW(db.get_attribute_type("NonexistentCollection", "label"), std::runtime_error); } + +// ============================================================================ +// Invalid collection/attribute error tests +// ============================================================================ + +TEST(Database, ReadScalarIntegersInvalidCollection) { + auto db = psr::Database::from_schema(":memory:", VALID_SCHEMA("basic.sql"), {.console_level = psr::LogLevel::off}); + + EXPECT_THROW(db.read_scalar_integers("NonexistentCollection", "integer_attribute"), std::runtime_error); +} + +TEST(Database, ReadScalarIntegersInvalidAttribute) { + auto db = psr::Database::from_schema(":memory:", VALID_SCHEMA("basic.sql"), {.console_level = psr::LogLevel::off}); + + EXPECT_THROW(db.read_scalar_integers("Configuration", "nonexistent_attribute"), std::runtime_error); +} + +TEST(Database, ReadScalarDoublesInvalidCollection) { + auto db = psr::Database::from_schema(":memory:", VALID_SCHEMA("basic.sql"), {.console_level = psr::LogLevel::off}); + + EXPECT_THROW(db.read_scalar_doubles("NonexistentCollection", "float_attribute"), std::runtime_error); +} + +TEST(Database, ReadScalarStringsInvalidCollection) { + auto db = psr::Database::from_schema(":memory:", VALID_SCHEMA("basic.sql"), {.console_level = psr::LogLevel::off}); + + EXPECT_THROW(db.read_scalar_strings("NonexistentCollection", "string_attribute"), std::runtime_error); +} + +TEST(Database, ReadVectorIntegersInvalidCollection) { + auto db = + psr::Database::from_schema(":memory:", VALID_SCHEMA("collections.sql"), {.console_level = psr::LogLevel::off}); + + psr::Element config; + config.set("label", std::string("Test Config")); + db.create_element("Configuration", config); + + EXPECT_THROW(db.read_vector_integers("NonexistentCollection", "value_int"), std::runtime_error); +} + +TEST(Database, ReadVectorIntegersInvalidAttribute) { + auto db = + psr::Database::from_schema(":memory:", VALID_SCHEMA("collections.sql"), {.console_level = psr::LogLevel::off}); + + psr::Element config; + config.set("label", std::string("Test Config")); + db.create_element("Configuration", config); + + EXPECT_THROW(db.read_vector_integers("Collection", "nonexistent_attribute"), std::runtime_error); +} + +TEST(Database, ReadSetStringsInvalidCollection) { + auto db = + psr::Database::from_schema(":memory:", VALID_SCHEMA("collections.sql"), {.console_level = psr::LogLevel::off}); + + psr::Element config; + config.set("label", std::string("Test Config")); + db.create_element("Configuration", config); + + EXPECT_THROW(db.read_set_strings("NonexistentCollection", "tag"), std::runtime_error); +} + +TEST(Database, ReadSetStringsInvalidAttribute) { + auto db = + psr::Database::from_schema(":memory:", VALID_SCHEMA("collections.sql"), {.console_level = psr::LogLevel::off}); + + psr::Element config; + config.set("label", std::string("Test Config")); + db.create_element("Configuration", config); + + EXPECT_THROW(db.read_set_strings("Collection", "nonexistent_attribute"), std::runtime_error); +} + +TEST(Database, ReadElementIdsInvalidCollection) { + auto db = psr::Database::from_schema(":memory:", VALID_SCHEMA("basic.sql"), {.console_level = psr::LogLevel::off}); + + EXPECT_THROW(db.read_element_ids("NonexistentCollection"), std::runtime_error); +} + +TEST(Database, ReadScalarIntegerByIdInvalidCollection) { + auto db = psr::Database::from_schema(":memory:", VALID_SCHEMA("basic.sql"), {.console_level = psr::LogLevel::off}); + + EXPECT_THROW(db.read_scalar_integers_by_id("NonexistentCollection", "integer_attribute", 1), std::runtime_error); +} + +TEST(Database, ReadVectorIntegerByIdInvalidCollection) { + auto db = + psr::Database::from_schema(":memory:", VALID_SCHEMA("collections.sql"), {.console_level = psr::LogLevel::off}); + + psr::Element config; + config.set("label", std::string("Test Config")); + db.create_element("Configuration", config); + + EXPECT_THROW(db.read_vector_integers_by_id("NonexistentCollection", "value_int", 1), std::runtime_error); +} + +TEST(Database, ReadSetStringsByIdInvalidCollection) { + auto db = + psr::Database::from_schema(":memory:", VALID_SCHEMA("collections.sql"), {.console_level = psr::LogLevel::off}); + + psr::Element config; + config.set("label", std::string("Test Config")); + db.create_element("Configuration", config); + + EXPECT_THROW(db.read_set_strings_by_id("NonexistentCollection", "tag", 1), std::runtime_error); +} diff --git a/tests/test_database_update.cpp b/tests/test_database_update.cpp index 88cf08a..a64b437 100644 --- a/tests/test_database_update.cpp +++ b/tests/test_database_update.cpp @@ -345,3 +345,132 @@ TEST(Database, UpdateElementIgnoresArrays) { auto vec = db.read_vector_integers_by_id("Collection", "value_int", id); EXPECT_EQ(vec, (std::vector{1, 2, 3})); } + +// ============================================================================ +// Update edge case tests +// ============================================================================ + +TEST(Database, UpdateVectorSingleElement) { + auto db = + psr::Database::from_schema(":memory:", VALID_SCHEMA("collections.sql"), {.console_level = psr::LogLevel::off}); + + psr::Element config; + config.set("label", std::string("Test Config")); + db.create_element("Configuration", config); + + psr::Element e; + e.set("label", std::string("Item 1")).set("value_int", std::vector{1, 2, 3}); + int64_t id = db.create_element("Collection", e); + + // Update to single element vector + db.update_vector_integers("Collection", "value_int", id, {42}); + + auto vec = db.read_vector_integers_by_id("Collection", "value_int", id); + EXPECT_EQ(vec, (std::vector{42})); +} + +TEST(Database, UpdateSetSingleElement) { + auto db = + psr::Database::from_schema(":memory:", VALID_SCHEMA("collections.sql"), {.console_level = psr::LogLevel::off}); + + psr::Element config; + config.set("label", std::string("Test Config")); + db.create_element("Configuration", config); + + psr::Element e; + e.set("label", std::string("Item 1")).set("tag", std::vector{"important", "urgent"}); + int64_t id = db.create_element("Collection", e); + + // Update to single element set + db.update_set_strings("Collection", "tag", id, {"single_tag"}); + + auto set = db.read_set_strings_by_id("Collection", "tag", id); + EXPECT_EQ(set, (std::vector{"single_tag"})); +} + +TEST(Database, UpdateScalarInvalidCollection) { + auto db = psr::Database::from_schema(":memory:", VALID_SCHEMA("basic.sql"), {.console_level = psr::LogLevel::off}); + + EXPECT_THROW(db.update_scalar_integer("NonexistentCollection", "integer_attribute", 1, 42), std::runtime_error); +} + +TEST(Database, UpdateScalarInvalidAttribute) { + auto db = psr::Database::from_schema(":memory:", VALID_SCHEMA("basic.sql"), {.console_level = psr::LogLevel::off}); + + psr::Element e; + e.set("label", std::string("Config 1")).set("integer_attribute", int64_t{42}); + int64_t id = db.create_element("Configuration", e); + + EXPECT_THROW(db.update_scalar_integer("Configuration", "nonexistent_attribute", id, 100), std::runtime_error); +} + +TEST(Database, UpdateVectorInvalidCollection) { + auto db = + psr::Database::from_schema(":memory:", VALID_SCHEMA("collections.sql"), {.console_level = psr::LogLevel::off}); + + psr::Element config; + config.set("label", std::string("Test Config")); + db.create_element("Configuration", config); + + EXPECT_THROW(db.update_vector_integers("NonexistentCollection", "value_int", 1, {1, 2, 3}), std::runtime_error); +} + +TEST(Database, UpdateSetInvalidCollection) { + auto db = + psr::Database::from_schema(":memory:", VALID_SCHEMA("collections.sql"), {.console_level = psr::LogLevel::off}); + + psr::Element config; + config.set("label", std::string("Test Config")); + db.create_element("Configuration", config); + + EXPECT_THROW(db.update_set_strings("NonexistentCollection", "tag", 1, {"tag1"}), std::runtime_error); +} + +TEST(Database, UpdateVectorFromEmptyToNonEmpty) { + auto db = + psr::Database::from_schema(":memory:", VALID_SCHEMA("collections.sql"), {.console_level = psr::LogLevel::off}); + + psr::Element config; + config.set("label", std::string("Test Config")); + db.create_element("Configuration", config); + + // Create element without vector data + psr::Element e; + e.set("label", std::string("Item 1")); + int64_t id = db.create_element("Collection", e); + + // Verify initially empty + auto vec_initial = db.read_vector_integers_by_id("Collection", "value_int", id); + EXPECT_TRUE(vec_initial.empty()); + + // Update to non-empty vector + db.update_vector_integers("Collection", "value_int", id, {1, 2, 3}); + + auto vec = db.read_vector_integers_by_id("Collection", "value_int", id); + EXPECT_EQ(vec, (std::vector{1, 2, 3})); +} + +TEST(Database, UpdateSetFromEmptyToNonEmpty) { + auto db = + psr::Database::from_schema(":memory:", VALID_SCHEMA("collections.sql"), {.console_level = psr::LogLevel::off}); + + psr::Element config; + config.set("label", std::string("Test Config")); + db.create_element("Configuration", config); + + // Create element without set data + psr::Element e; + e.set("label", std::string("Item 1")); + int64_t id = db.create_element("Collection", e); + + // Verify initially empty + auto set_initial = db.read_set_strings_by_id("Collection", "tag", id); + EXPECT_TRUE(set_initial.empty()); + + // Update to non-empty set + db.update_set_strings("Collection", "tag", id, {"important", "urgent"}); + + auto set = db.read_set_strings_by_id("Collection", "tag", id); + std::sort(set.begin(), set.end()); + EXPECT_EQ(set, (std::vector{"important", "urgent"})); +} diff --git a/tests/test_lua_runner.cpp b/tests/test_lua_runner.cpp index fbdbf57..7f69e89 100644 --- a/tests/test_lua_runner.cpp +++ b/tests/test_lua_runner.cpp @@ -649,3 +649,96 @@ TEST_F(LuaRunnerTest, UpdateElementArraysIgnoredFromLua) { auto vec_values = db.read_vector_integers_by_id("Collection", "value_int", 1); EXPECT_EQ(vec_values, (std::vector{1, 2, 3})); } + +// ============================================================================ +// LuaRunner error path tests +// ============================================================================ + +TEST_F(LuaRunnerTest, UndefinedVariableError) { + 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("local x = undefined_variable + 1"); }, std::runtime_error); +} + +TEST_F(LuaRunnerTest, NilFunctionCallError) { + 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("local f = nil; f()"); }, std::runtime_error); +} + +TEST_F(LuaRunnerTest, TableIndexError) { + 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("local t = nil; local x = t.field"); }, std::runtime_error); +} + +TEST_F(LuaRunnerTest, CreateElementInvalidCollection) { + 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:create_element("NonexistentCollection", { label = "Test" }))"); }, std::runtime_error); +} + +TEST_F(LuaRunnerTest, MultipleScriptExecutions) { + auto db = psr::Database::from_schema(":memory:", collections_schema); + psr::LuaRunner lua(db); + + lua.run(R"(db:create_element("Configuration", { label = "Config" }))"); + lua.run(R"(db:create_element("Collection", { label = "Item 1" }))"); + lua.run(R"(db:create_element("Collection", { label = "Item 2" }))"); + lua.run(R"(db:create_element("Collection", { label = "Item 3" }))"); + + auto labels = db.read_scalar_strings("Collection", "label"); + EXPECT_EQ(labels.size(), 3); +} + +TEST_F(LuaRunnerTest, EmptyScriptSucceeds) { + auto db = psr::Database::from_schema(":memory:", collections_schema); + db.create_element("Configuration", psr::Element().set("label", "Config")); + + psr::LuaRunner lua(db); + + // Empty script should succeed + lua.run(""); +} + +TEST_F(LuaRunnerTest, WhitespaceOnlyScriptSucceeds) { + auto db = psr::Database::from_schema(":memory:", collections_schema); + db.create_element("Configuration", psr::Element().set("label", "Config")); + + psr::LuaRunner lua(db); + + // Whitespace only script should succeed + lua.run(" \n\t\n "); +} + +TEST_F(LuaRunnerTest, CommentOnlyScriptSucceeds) { + auto db = psr::Database::from_schema(":memory:", collections_schema); + db.create_element("Configuration", psr::Element().set("label", "Config")); + + psr::LuaRunner lua(db); + + // Comment only script should succeed + lua.run("-- this is a comment\n-- another comment"); +} + +TEST_F(LuaRunnerTest, ReadFromNonExistentCollection) { + 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 x = db:read_scalar_strings("NonexistentCollection", "label"))"); }, std::runtime_error); +} diff --git a/tests/test_migrations/test_migrations.cpp b/tests/test_migrations/test_migrations.cpp deleted file mode 100644 index d2eb959..0000000 --- a/tests/test_migrations/test_migrations.cpp +++ /dev/null @@ -1,147 +0,0 @@ -#include -#include -#include -#include -#include -#include - -namespace fs = std::filesystem; - -namespace database_utils { - -class MigrationFixture : public ::testing::Test { -protected: - void SetUp() override { - path = (fs::temp_directory_path() / "psr_test.db").string(); - migrations_path = (fs::path(__FILE__).parent_path() / "chaining_migrations").string(); - } - void TearDown() override { - if (fs::exists(path)) - fs::remove(path); - } - std::string path; - std::string migrations_path; -}; - -// ============================================================================ -// Migration class tests -// ============================================================================ - -TEST_F(MigrationFixture, MigrationCreation) { - psr::Migration migration(1, migrations_path + "/1"); - EXPECT_EQ(migration.version(), 1); - EXPECT_FALSE(migration.path().empty()); -} - -TEST_F(MigrationFixture, MigrationUpSqlRead) { - psr::Migration migration(1, migrations_path + "/1"); - std::string sql = migration.up_sql(); - EXPECT_FALSE(sql.empty()); - EXPECT_NE(sql.find("CREATE TABLE Test1"), std::string::npos); -} - -TEST_F(MigrationFixture, MigrationDownSqlRead) { - psr::Migration migration(1, migrations_path + "/1"); - std::string sql = migration.down_sql(); - EXPECT_FALSE(sql.empty()); - EXPECT_NE(sql.find("DROP TABLE"), std::string::npos); -} - -TEST_F(MigrationFixture, MigrationComparison) { - psr::Migration m1(1, migrations_path + "/1"); - psr::Migration m2(2, migrations_path + "/2"); - psr::Migration m3(3, migrations_path + "/3"); - - EXPECT_TRUE(m1 < m2); - EXPECT_TRUE(m2 < m3); - EXPECT_TRUE(m1 < m3); - EXPECT_FALSE(m2 < m1); - - EXPECT_TRUE(m1 == m1); - EXPECT_FALSE(m1 == m2); - EXPECT_TRUE(m1 != m2); -} - -TEST_F(MigrationFixture, MigrationCopy) { - psr::Migration original(2, migrations_path + "/2"); - psr::Migration copy = original; - - EXPECT_EQ(copy.version(), original.version()); - EXPECT_EQ(copy.path(), original.path()); -} - -// ============================================================================ -// Migrations class tests -// ============================================================================ - -TEST_F(MigrationFixture, MigrationsLoad) { - psr::Migrations migrations(migrations_path); - - EXPECT_FALSE(migrations.empty()); - EXPECT_EQ(migrations.count(), 3u); - EXPECT_EQ(migrations.latest_version(), 3); -} - -TEST_F(MigrationFixture, MigrationsOrder) { - psr::Migrations migrations(migrations_path); - auto all = migrations.all(); - - ASSERT_EQ(all.size(), 3u); - EXPECT_EQ(all[0].version(), 1); - EXPECT_EQ(all[1].version(), 2); - EXPECT_EQ(all[2].version(), 3); -} - -TEST_F(MigrationFixture, MigrationsPending) { - psr::Migrations migrations(migrations_path); - - auto pending_from_0 = migrations.pending(0); - EXPECT_EQ(pending_from_0.size(), 3u); - - auto pending_from_1 = migrations.pending(1); - EXPECT_EQ(pending_from_1.size(), 2u); - EXPECT_EQ(pending_from_1[0].version(), 2); - - auto pending_from_2 = migrations.pending(2); - EXPECT_EQ(pending_from_2.size(), 1u); - EXPECT_EQ(pending_from_2[0].version(), 3); - - auto pending_from_3 = migrations.pending(3); - EXPECT_TRUE(pending_from_3.empty()); -} - -TEST_F(MigrationFixture, MigrationsIteration) { - psr::Migrations migrations(migrations_path); - - int64_t expected_version = 1; - for (const auto& migration : migrations) { - EXPECT_EQ(migration.version(), expected_version); - ++expected_version; - } - EXPECT_EQ(expected_version, 4); -} - -TEST_F(MigrationFixture, MigrationsEmptyPath) { - psr::Migrations migrations("non_existent_path"); - EXPECT_TRUE(migrations.empty()); - EXPECT_EQ(migrations.count(), 0u); - EXPECT_EQ(migrations.latest_version(), 0); -} - -// ============================================================================ -// Database migration tests -// ============================================================================ - -TEST_F(MigrationFixture, DatabaseCurrentVersion) { - psr::Database db(":memory:"); - EXPECT_EQ(db.current_version(), 0); -} - -TEST_F(MigrationFixture, DatabaseFromMigrations) { - auto db = psr::Database::from_migrations(path, migrations_path); - - EXPECT_EQ(db.current_version(), 3); - EXPECT_TRUE(db.is_healthy()); -} - -} // namespace database_utils