diff --git a/testing/insert.test b/testing/insert.test index 5a37fd692..ab520b052 100755 --- a/testing/insert.test +++ b/testing/insert.test @@ -15,4 +15,149 @@ do_execsql_test_on_specific_db {:memory:} must-be-int-insert { } {1 2 3 -4} \ No newline at end of file +4} + +do_execsql_test strict-basic-creation { + CREATE TABLE test1(id INTEGER, name TEXT, price REAL) STRICT; + INSERT INTO test1 VALUES(1, 'item1', 10.5); + SELECT * FROM test1; +} {1|item1|10.5} + +do_execsql_test_any_error strict-require-datatype { + CREATE TABLE test2(id INTEGER, name) STRICT; +} + +do_execsql_test_any_error strict-valid-datatypes { + CREATE TABLE test2(id INTEGER, value DATETIME) STRICT; +} + +do_execsql_test_any_error strict-type-enforcement { + CREATE TABLE test3(id INTEGER, name TEXT, price REAL) STRICT; + INSERT INTO test3 VALUES(1, 'item1', 'not-a-number'); +} + +do_execsql_test strict-type-coercion { + CREATE TABLE test4(id INTEGER, name TEXT, price REAL) STRICT; + INSERT INTO test4 VALUES(1, 'item1', '10.5'); + SELECT typeof(price), price FROM test4; +} {real|10.5} + +do_execsql_test strict-any-flexibility { + CREATE TABLE test5(id INTEGER, data ANY) STRICT; + INSERT INTO test5 VALUES(1, 100); + INSERT INTO test5 VALUES(2, 'text'); + INSERT INTO test5 VALUES(3, 3.14); + SELECT id, typeof(data) FROM test5 ORDER BY id; +} {1|integer +2|text +3|real} + +do_execsql_test strict-any-preservation { + CREATE TABLE test6(id INTEGER, code ANY) STRICT; + INSERT INTO test6 VALUES(1, '000123'); + SELECT typeof(code), code FROM test6; +} {text|000123} + +do_execsql_test_any_error strict-int-vs-integer-pk { + CREATE TABLE test8(id INT PRIMARY KEY, name TEXT) STRICT + INSERT INTO test8 VALUES(NULL, 'test'); +} + +do_execsql_test strict-integer-pk-behavior { + CREATE TABLE test9(id INTEGER PRIMARY KEY, name TEXT) STRICT; + INSERT INTO test9 VALUES(NULL, 'test'); + SELECT id, name FROM test9; +} {1|test} + + +do_execsql_test strict-mixed-inserts { + CREATE TABLE test11( + id INTEGER PRIMARY KEY, + name TEXT, + price REAL, + quantity INT, + tags ANY + ) STRICT; + + INSERT INTO test11 VALUES(1, 'item1', 10.5, 5, 'tag1'); + INSERT INTO test11 VALUES(2, 'item2', 20.75, 10, 42); + + SELECT id, name, price, quantity, typeof(tags) FROM test11 ORDER BY id; +} {1|item1|10.5|5|text +2|item2|20.75|10|integer} + +do_execsql_test strict-update-basic { + CREATE TABLE test1(id INTEGER, name TEXT, price REAL) STRICT; + INSERT INTO test1 VALUES(1, 'item1', 10.5); + UPDATE test1 SET price = 15.75 WHERE id = 1; + SELECT * FROM test1; +} {1|item1|15.75} + +do_execsql_test_any_error strict-update-type-enforcement { + CREATE TABLE test2(id INTEGER, name TEXT, price REAL) STRICT; + INSERT INTO test2 VALUES(1, 'item1', 10.5); + UPDATE test2 SET price = 'not-a-number' WHERE id = 1; +} + +do_execsql_test strict-update-type-coercion { + CREATE TABLE test3(id INTEGER, name TEXT, price REAL) STRICT; + INSERT INTO test3 VALUES(1, 'item1', 10.5); + UPDATE test3 SET price = '15.75' WHERE id = 1; + SELECT id, typeof(price), price FROM test3; +} {1|real|15.75} + +do_execsql_test strict-update-any-flexibility { + CREATE TABLE test4(id INTEGER, data ANY) STRICT; + INSERT INTO test4 VALUES(1, 100); + UPDATE test4 SET data = 'text' WHERE id = 1; + INSERT INTO test4 VALUES(2, 'original'); + UPDATE test4 SET data = 3.14 WHERE id = 2; + SELECT id, typeof(data), data FROM test4 ORDER BY id; +} {1|text|text +2|real|3.14} + +do_execsql_test strict-update-any-preservation { + CREATE TABLE test5(id INTEGER, code ANY) STRICT; + INSERT INTO test5 VALUES(1, 'text'); + UPDATE test5 SET code = '000123' WHERE id = 1; + SELECT typeof(code), code FROM test5; +} {text|000123} + +do_execsql_test_any_error strict-update-not-null-constraint { + CREATE TABLE test7(id INTEGER, name TEXT NOT NULL) STRICT; + INSERT INTO test7 VALUES(1, 'name'); + UPDATE test7 SET name = NULL WHERE id = 1; +} + +# Uncomment following test case when unique constraint is added +#do_execsql_test_any_error strict-update-pk-constraint { +# CREATE TABLE test8(id INTEGER PRIMARY KEY, name TEXT) STRICT; +# INSERT INTO test8 VALUES(1, 'name1'); +# INSERT INTO test8 VALUES(2, 'name2'); +# UPDATE test8 SET id = 2 WHERE id = 1; +#} + +do_execsql_test strict-update-multiple-columns { + CREATE TABLE test9(id INTEGER, name TEXT, price REAL, quantity INT) STRICT; + INSERT INTO test9 VALUES(1, 'item1', 10.5, 5); + UPDATE test9 SET name = 'updated', price = 20.75, quantity = 10 WHERE id = 1; + SELECT * FROM test9; +} {1|updated|20.75|10} + +do_execsql_test strict-update-where-clause { + CREATE TABLE test10(id INTEGER, category TEXT, price REAL) STRICT; + INSERT INTO test10 VALUES(1, 'A', 10); + INSERT INTO test10 VALUES(2, 'A', 20); + INSERT INTO test10 VALUES(3, 'B', 30); + UPDATE test10 SET price = price * 2 WHERE category = 'A'; + SELECT id, price FROM test10 ORDER BY id; +} {1|20.0 +2|40.0 +3|30.0} + +do_execsql_test strict-update-expression { + CREATE TABLE test11(id INTEGER, name TEXT, price REAL, discount REAL) STRICT; + INSERT INTO test11 VALUES(1, 'item1', 100, 0.1); + UPDATE test11 SET price = price - (price * discount); + SELECT id, price FROM test11; +} {1|90.0} diff --git a/testing/tester.tcl b/testing/tester.tcl index 735c91aae..4bb3ab2ef 100644 --- a/testing/tester.tcl +++ b/testing/tester.tcl @@ -97,3 +97,114 @@ proc do_execsql_test_tolerance {test_name sql_statements expected_outputs tolera } } } +# This procedure passes the test if the output contains error messages +proc run_test_expecting_any_error {sqlite_exec db_name sql} { + # Execute the SQL command and capture output + set command [list $sqlite_exec $db_name $sql] + + # Use catch to handle both successful and error cases + catch {exec {*}$command} result options + + # Check if the output contains error indicators (×, error, syntax error, etc.) + if {[regexp {(error|ERROR|Error|×|syntax error|failed)} $result]} { + # Error found in output - test passed + puts "Test PASSED: Got expected error" + return 1 + } + + # No error indicators in output + puts "Test FAILED: '$sql'" + puts "Expected an error but command output didn't indicate any error: '$result'" + exit 1 +} + +# This procedure passes if error matches a specific pattern +proc run_test_expecting_error {sqlite_exec db_name sql expected_error_pattern} { + # Execute the SQL command and capture output + set command [list $sqlite_exec $db_name $sql] + + # Capture output whether command succeeds or fails + catch {exec {*}$command} result options + + # Check if the output contains error indicators first + if {![regexp {(error|ERROR|Error|×|syntax error|failed)} $result]} { + puts "Test FAILED: '$sql'" + puts "Expected an error matching '$expected_error_pattern'" + puts "But command output didn't indicate any error: '$result'" + exit 1 + } + + # Now check if the error message matches the expected pattern + if {![regexp $expected_error_pattern $result]} { + puts "Test FAILED: '$sql'" + puts "Error occurred but didn't match expected pattern." + puts "Output was: '$result'" + puts "Expected pattern: '$expected_error_pattern'" + exit 1 + } + + # If we get here, the test passed - got expected error matching pattern + return 1 +} + +# This version accepts exact error text, ignoring formatting +proc run_test_expecting_error_content {sqlite_exec db_name sql expected_error_text} { + # Execute the SQL command and capture output + set command [list $sqlite_exec $db_name $sql] + + # Capture output whether command succeeds or fails + catch {exec {*}$command} result options + + # Check if the output contains error indicators first + if {![regexp {(error|ERROR|Error|×|syntax error|failed)} $result]} { + puts "Test FAILED: '$sql'" + puts "Expected an error with text: '$expected_error_text'" + puts "But command output didn't indicate any error: '$result'" + exit 1 + } + + # Normalize both the actual and expected error messages + # Remove all whitespace, newlines, and special characters for comparison + set normalized_actual [regsub -all {[[:space:]]|[[:punct:]]} $result ""] + set normalized_expected [regsub -all {[[:space:]]|[[:punct:]]} $expected_error_text ""] + + # Convert to lowercase for case-insensitive comparison + set normalized_actual [string tolower $normalized_actual] + set normalized_expected [string tolower $normalized_expected] + + # Check if the normalized strings contain the same text + if {[string first $normalized_expected $normalized_actual] == -1} { + puts "Test FAILED: '$sql'" + puts "Error occurred but content didn't match." + puts "Output was: '$result'" + puts "Expected text: '$expected_error_text'" + exit 1 + } + + # If we get here, the test passed - got error with expected content + return 1 +} + +proc do_execsql_test_error {test_name sql_statements expected_error_pattern} { + foreach db $::test_dbs { + puts [format "(%s) %s Running error test: %s" $db [string repeat " " [expr {40 - [string length $db]}]] $test_name] + set combined_sql [string trim $sql_statements] + run_test_expecting_error $::sqlite_exec $db $combined_sql $expected_error_pattern + } +} + +proc do_execsql_test_error_content {test_name sql_statements expected_error_text} { + foreach db $::test_dbs { + puts [format "(%s) %s Running error content test: %s" $db [string repeat " " [expr {40 - [string length $db]}]] $test_name] + set combined_sql [string trim $sql_statements] + run_test_expecting_error_content $::sqlite_exec $db $combined_sql $expected_error_text + } +} + +proc do_execsql_test_any_error {test_name sql_statements} { + foreach db $::test_dbs { + puts [format "(%s) %s Running any-error test: %s" $db [string repeat " " [expr {40 - [string length $db]}]] $test_name] + set combined_sql [string trim $sql_statements] + run_test_expecting_any_error $::sqlite_exec $db $combined_sql + } +}