Add extracted tools: CitrineOS, OpenOCPP, ShapeShifter
- CitrineOS core extracted (CSMS OCPP 2.0.1) - OpenOCPP extracted (firmware OCPP 1.6J/2.0.1) - ShapeShifter library installed (pip install -e) - ShapeShifter specification extracted - EVerest extracted TODO updated with progress
This commit is contained in:
40
tools/EVerest-main/lib/everest/sqlite/tests/CMakeLists.txt
Normal file
40
tools/EVerest-main/lib/everest/sqlite/tests/CMakeLists.txt
Normal file
@@ -0,0 +1,40 @@
|
||||
set(TEST_TARGET_NAME ${PROJECT_NAME}_tests)
|
||||
set(GTEST_LIBRARIES GTest::gmock_main GTest::gtest_main)
|
||||
|
||||
add_executable(${TEST_TARGET_NAME})
|
||||
|
||||
target_sources(${TEST_TARGET_NAME} PRIVATE
|
||||
test_connection.cpp
|
||||
test_database_schema_updater.cpp
|
||||
test_sqlite_statement.cpp
|
||||
)
|
||||
|
||||
target_include_directories(${TEST_TARGET_NAME} PRIVATE
|
||||
"${PROJECT_SOURCE_DIR}/include"
|
||||
)
|
||||
|
||||
target_link_libraries(${TEST_TARGET_NAME} PRIVATE
|
||||
everest::sqlite
|
||||
${GTEST_LIBRARIES}
|
||||
)
|
||||
|
||||
add_test(${TEST_TARGET_NAME} ${TEST_TARGET_NAME})
|
||||
|
||||
if (EVEREST_SQLITE_BUILD_TESTING AND NOT DISABLE_EDM)
|
||||
evc_include(CodeCoverage)
|
||||
append_coverage_compiler_flags_to_target(everest_sqlite)
|
||||
|
||||
setup_target_for_coverage_gcovr_html(
|
||||
NAME ${PROJECT_NAME}_gcovr_coverage
|
||||
EXECUTABLE ctest
|
||||
DEPENDENCIES ${TEST_TARGET_NAME}
|
||||
EXCLUDE "tests/*"
|
||||
)
|
||||
|
||||
setup_target_for_coverage_gcovr_xml(
|
||||
NAME ${PROJECT_NAME}_gcovr_coverage_xml
|
||||
EXECUTABLE ctest
|
||||
DEPENDENCIES ${TEST_TARGET_NAME}
|
||||
EXCLUDE "tests/*"
|
||||
)
|
||||
endif()
|
||||
@@ -0,0 +1,50 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2020 - 2025 Pionix GmbH and Contributors to EVerest
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <everest/database/sqlite/connection.hpp>
|
||||
#include <gmock/gmock.h>
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
using namespace std::string_literals;
|
||||
|
||||
namespace everest::db::sqlite {
|
||||
|
||||
class DatabaseTestingUtils : public ::testing::Test {
|
||||
|
||||
protected:
|
||||
std::unique_ptr<ConnectionInterface> database;
|
||||
|
||||
public:
|
||||
DatabaseTestingUtils() : database(std::make_unique<Connection>("file::memory:?cache=shared")) {
|
||||
EXPECT_TRUE(this->database->open_connection());
|
||||
}
|
||||
|
||||
void ExpectUserVersion(uint32_t expected_version) {
|
||||
auto statement = this->database->new_statement("PRAGMA user_version");
|
||||
|
||||
EXPECT_EQ(statement->step(), SQLITE_ROW);
|
||||
EXPECT_EQ(statement->column_int(0), expected_version);
|
||||
}
|
||||
|
||||
void SetUserVersion(uint32_t user_version) {
|
||||
EXPECT_TRUE(this->database->execute_statement("PRAGMA user_version = "s + std::to_string(user_version)));
|
||||
}
|
||||
|
||||
bool DoesTableExist(std::string_view table) {
|
||||
const std::string statement = "SELECT name FROM sqlite_master WHERE type='table' AND name=@table_name";
|
||||
|
||||
std::unique_ptr<StatementInterface> table_exists_statement = this->database->new_statement(statement);
|
||||
table_exists_statement->bind_text("@table_name", std::string(table), SQLiteString::Transient);
|
||||
const int status = table_exists_statement->step();
|
||||
const int number_of_rows = table_exists_statement->get_number_of_rows();
|
||||
return status != SQLITE_ERROR && number_of_rows == 1;
|
||||
}
|
||||
|
||||
bool DoesColumnExist(std::string_view table, std::string_view column) {
|
||||
return this->database->execute_statement("SELECT "s + column.data() + " FROM " + table.data() + " LIMIT 1;");
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace everest::db::sqlite
|
||||
@@ -0,0 +1,76 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2020 - 2025 Pionix GmbH and Contributors to EVerest
|
||||
|
||||
#include <everest/database/sqlite/connection.hpp>
|
||||
#include <everest/database/sqlite/statement.hpp>
|
||||
#include <gtest/gtest.h>
|
||||
#include <sqlite3.h>
|
||||
|
||||
#include <filesystem>
|
||||
#include <string>
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
namespace everest::db::sqlite {
|
||||
|
||||
class ConnectionWalTest : public ::testing::Test {
|
||||
protected:
|
||||
fs::path db_path;
|
||||
|
||||
void SetUp() override {
|
||||
const auto unique = "everest_sqlite_conn_test_" + std::to_string(reinterpret_cast<std::uintptr_t>(this)) + "_" +
|
||||
std::to_string(::testing::UnitTest::GetInstance()->random_seed()) + ".db";
|
||||
db_path = fs::temp_directory_path() / unique;
|
||||
std::error_code ec;
|
||||
fs::remove(db_path, ec);
|
||||
fs::remove(db_path.string() + "-wal", ec);
|
||||
fs::remove(db_path.string() + "-shm", ec);
|
||||
}
|
||||
|
||||
void TearDown() override {
|
||||
std::error_code ec;
|
||||
fs::remove(db_path, ec);
|
||||
fs::remove(db_path.string() + "-wal", ec);
|
||||
fs::remove(db_path.string() + "-shm", ec);
|
||||
}
|
||||
};
|
||||
|
||||
TEST_F(ConnectionWalTest, FileDatabaseUsesWalJournalMode) {
|
||||
Connection db(db_path);
|
||||
ASSERT_TRUE(db.open_connection());
|
||||
|
||||
{
|
||||
auto stmt = db.new_statement("PRAGMA journal_mode");
|
||||
ASSERT_EQ(stmt->step(), SQLITE_ROW);
|
||||
EXPECT_EQ(stmt->column_text(0), "wal");
|
||||
}
|
||||
|
||||
db.close_connection();
|
||||
}
|
||||
|
||||
TEST_F(ConnectionWalTest, InMemoryDatabaseOpensWithoutWalFailure) {
|
||||
Connection db(fs::path("file::memory:?cache=shared"));
|
||||
ASSERT_TRUE(db.open_connection());
|
||||
db.close_connection();
|
||||
}
|
||||
|
||||
TEST_F(ConnectionWalTest, ExistingRollbackDatabaseMigratesToWal) {
|
||||
sqlite3* raw = nullptr;
|
||||
ASSERT_EQ(sqlite3_open_v2(db_path.c_str(), &raw, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, nullptr), SQLITE_OK);
|
||||
ASSERT_EQ(sqlite3_exec(raw, "PRAGMA journal_mode=DELETE; CREATE TABLE t(id INTEGER);", nullptr, nullptr, nullptr),
|
||||
SQLITE_OK);
|
||||
sqlite3_close(raw);
|
||||
|
||||
Connection db(db_path);
|
||||
ASSERT_TRUE(db.open_connection());
|
||||
|
||||
{
|
||||
auto stmt = db.new_statement("PRAGMA journal_mode");
|
||||
ASSERT_EQ(stmt->step(), SQLITE_ROW);
|
||||
EXPECT_EQ(stmt->column_text(0), "wal");
|
||||
}
|
||||
|
||||
db.close_connection();
|
||||
}
|
||||
|
||||
} // namespace everest::db::sqlite
|
||||
@@ -0,0 +1,313 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2020 - 2025 Pionix GmbH and Contributors to EVerest
|
||||
|
||||
#include "database_testing_utils.hpp"
|
||||
#include <everest/database/sqlite/schema_updater.hpp>
|
||||
#include <fstream>
|
||||
|
||||
namespace everest::db::sqlite {
|
||||
|
||||
struct MigrationFile {
|
||||
std::string_view name;
|
||||
std::string_view content;
|
||||
};
|
||||
|
||||
static constexpr MigrationFile migration_file_up_1_valid{
|
||||
"1_up-initial.sql",
|
||||
"PRAGMA foreign_keys = ON; CREATE TABLE TEST_TABLE1(FIELD1 TEXT PRIMARY KEY NOT NULL, FIELD2 INT NOT NULL);"};
|
||||
|
||||
static constexpr MigrationFile migration_file_up_1_valid_empty_name{
|
||||
"1_up.sql",
|
||||
"PRAGMA foreign_keys = ON; CREATE TABLE TEST_TABLE1(FIELD1 TEXT PRIMARY KEY NOT NULL, FIELD2 INT NOT NULL);"};
|
||||
|
||||
static constexpr MigrationFile migration_file_up_1_invalid{
|
||||
"1_up-initial.sql", "PRAGMA foreign_keys = ON; CREATE TABLE <invalid> TEST_TABLE1(FIELD1 TEXT PRIMARY KEY NOT "
|
||||
"NULL, FIELD2 INT NOT NULL);"};
|
||||
|
||||
static constexpr MigrationFile migration_file_up_2_valid{
|
||||
"2_up-add_table.sql", "CREATE TABLE TEST_TABLE2(FIELD1 TEXT PRIMARY KEY NOT NULL, FIELD2 INT NOT NULL);"};
|
||||
|
||||
static constexpr MigrationFile migration_file_down_2_valid{"2_down-drop_table.sql", "DROP TABLE TEST_TABLE2;"};
|
||||
|
||||
static constexpr MigrationFile migration_file_up_3_valid{
|
||||
"3_up-add_table.sql", "CREATE TABLE TEST_TABLE3(FIELD1 TEXT PRIMARY KEY NOT NULL, FIELD2 INT NOT NULL);"};
|
||||
|
||||
static constexpr MigrationFile migration_file_down_3_valid{"3_down-drop_table.sql", "DROP TABLE TEST_TABLE3;"};
|
||||
|
||||
static constexpr MigrationFile migration_file_up_4_valid{
|
||||
"4_up-add_table.sql", "CREATE TABLE TEST_TABLE4(FIELD1 TEXT PRIMARY KEY NOT NULL, FIELD2 INT NOT NULL);"};
|
||||
|
||||
static constexpr MigrationFile migration_file_down_4_valid{"4_down-drop_table.sql", "DROP TABLE TEST_TABLE4;"};
|
||||
|
||||
static constexpr std::string_view table1{"TEST_TABLE1"};
|
||||
static constexpr std::string_view table2{"TEST_TABLE2"};
|
||||
static constexpr std::string_view table3{"TEST_TABLE3"};
|
||||
static constexpr std::string_view table4{"TEST_TABLE3"};
|
||||
|
||||
class DatabaseSchemaUpdaterTest : public DatabaseTestingUtils {
|
||||
|
||||
protected:
|
||||
std::filesystem::path migration_files_path;
|
||||
|
||||
public:
|
||||
DatabaseSchemaUpdaterTest() :
|
||||
DatabaseTestingUtils(),
|
||||
migration_files_path(std::filesystem::temp_directory_path() / "database_schema_test" / "core_migrations") {
|
||||
std::filesystem::create_directories(migration_files_path);
|
||||
EXPECT_TRUE(this->database->open_connection());
|
||||
}
|
||||
|
||||
~DatabaseSchemaUpdaterTest() {
|
||||
std::filesystem::remove_all(migration_files_path);
|
||||
}
|
||||
|
||||
void WriteMigrationFile(const MigrationFile& file) {
|
||||
std::ofstream stream{this->migration_files_path / file.name};
|
||||
stream << file.content;
|
||||
}
|
||||
};
|
||||
|
||||
TEST_F(DatabaseSchemaUpdaterTest, FolderDoesNotExist) {
|
||||
SchemaUpdater updater{this->database.get()};
|
||||
EXPECT_FALSE(updater.apply_migration_files(this->migration_files_path / "invalid", 1));
|
||||
}
|
||||
|
||||
TEST_F(DatabaseSchemaUpdaterTest, TargetVersionInvalid) {
|
||||
SchemaUpdater updater{this->database.get()};
|
||||
EXPECT_FALSE(updater.apply_migration_files(this->migration_files_path, 0));
|
||||
}
|
||||
|
||||
TEST_F(DatabaseSchemaUpdaterTest, ApplyInitialMigrationFile) {
|
||||
|
||||
this->WriteMigrationFile(migration_file_up_1_valid);
|
||||
|
||||
SchemaUpdater updater{this->database.get()};
|
||||
|
||||
EXPECT_TRUE(updater.apply_migration_files(this->migration_files_path, 1));
|
||||
|
||||
this->ExpectUserVersion(1);
|
||||
EXPECT_TRUE(this->DoesTableExist(table1));
|
||||
}
|
||||
|
||||
TEST_F(DatabaseSchemaUpdaterTest, ApplyInitialMigrationFileEmptyName) {
|
||||
|
||||
this->WriteMigrationFile(migration_file_up_1_valid_empty_name);
|
||||
|
||||
SchemaUpdater updater{this->database.get()};
|
||||
|
||||
EXPECT_TRUE(updater.apply_migration_files(this->migration_files_path, 1));
|
||||
|
||||
this->ExpectUserVersion(1);
|
||||
EXPECT_TRUE(this->DoesTableExist(table1));
|
||||
}
|
||||
|
||||
TEST_F(DatabaseSchemaUpdaterTest, ApplyInitialMigrationFileAlreadyUpToDate) {
|
||||
|
||||
this->WriteMigrationFile(migration_file_up_1_valid);
|
||||
this->SetUserVersion(1);
|
||||
|
||||
SchemaUpdater updater{this->database.get()};
|
||||
|
||||
EXPECT_TRUE(updater.apply_migration_files(this->migration_files_path, 1));
|
||||
|
||||
this->ExpectUserVersion(1);
|
||||
EXPECT_FALSE(this->DoesTableExist(table1)); // Database was not changed
|
||||
}
|
||||
|
||||
TEST_F(DatabaseSchemaUpdaterTest, ApplyInitialMigrationFileVersionToHigh) {
|
||||
|
||||
this->WriteMigrationFile(migration_file_up_1_valid);
|
||||
this->SetUserVersion(2);
|
||||
|
||||
SchemaUpdater updater{this->database.get()};
|
||||
|
||||
EXPECT_FALSE(updater.apply_migration_files(this->migration_files_path, 1));
|
||||
|
||||
this->ExpectUserVersion(2);
|
||||
EXPECT_FALSE(this->DoesTableExist(table1)); // Database was not changed
|
||||
}
|
||||
|
||||
TEST_F(DatabaseSchemaUpdaterTest, ApplyInvalidInitialMigrationFile) {
|
||||
|
||||
this->WriteMigrationFile(migration_file_up_1_invalid);
|
||||
|
||||
SchemaUpdater updater{this->database.get()};
|
||||
|
||||
EXPECT_FALSE(updater.apply_migration_files(this->migration_files_path, 1));
|
||||
|
||||
this->ExpectUserVersion(0);
|
||||
EXPECT_FALSE(this->DoesTableExist(table1)); // Database was not changed
|
||||
}
|
||||
|
||||
TEST_F(DatabaseSchemaUpdaterTest, MissingInitialMigrationFile) {
|
||||
|
||||
this->WriteMigrationFile(migration_file_up_2_valid);
|
||||
|
||||
SchemaUpdater updater{this->database.get()};
|
||||
|
||||
EXPECT_FALSE(updater.apply_migration_files(this->migration_files_path, 1));
|
||||
|
||||
this->ExpectUserVersion(0);
|
||||
EXPECT_FALSE(this->DoesTableExist(table1));
|
||||
}
|
||||
|
||||
TEST_F(DatabaseSchemaUpdaterTest, SequenceNotValidUnevenNrOfFiles) {
|
||||
|
||||
this->WriteMigrationFile(migration_file_up_1_valid);
|
||||
this->WriteMigrationFile(migration_file_up_2_valid);
|
||||
this->WriteMigrationFile(migration_file_up_3_valid);
|
||||
this->WriteMigrationFile(migration_file_down_3_valid);
|
||||
|
||||
SchemaUpdater updater{this->database.get()};
|
||||
|
||||
EXPECT_FALSE(updater.apply_migration_files(this->migration_files_path, 1));
|
||||
EXPECT_FALSE(updater.apply_migration_files(this->migration_files_path, 2));
|
||||
EXPECT_FALSE(updater.apply_migration_files(this->migration_files_path, 3));
|
||||
|
||||
this->ExpectUserVersion(0);
|
||||
EXPECT_FALSE(this->DoesTableExist(table1)); // Database was not changed
|
||||
}
|
||||
|
||||
TEST_F(DatabaseSchemaUpdaterTest, SequenceNotValidNotEnoughFiles) {
|
||||
|
||||
this->WriteMigrationFile(migration_file_up_1_valid);
|
||||
this->WriteMigrationFile(migration_file_up_2_valid);
|
||||
this->WriteMigrationFile(migration_file_down_2_valid);
|
||||
|
||||
SchemaUpdater updater{this->database.get()};
|
||||
|
||||
EXPECT_FALSE(updater.apply_migration_files(this->migration_files_path, 3));
|
||||
|
||||
this->ExpectUserVersion(0);
|
||||
EXPECT_FALSE(this->DoesTableExist(table1)); // Database was not changed
|
||||
}
|
||||
|
||||
TEST_F(DatabaseSchemaUpdaterTest, SequenceNotValidMissingDownFile) {
|
||||
|
||||
this->WriteMigrationFile(migration_file_up_1_valid);
|
||||
this->WriteMigrationFile(migration_file_up_2_valid);
|
||||
this->WriteMigrationFile(migration_file_up_3_valid);
|
||||
this->WriteMigrationFile(migration_file_up_4_valid);
|
||||
this->WriteMigrationFile(migration_file_down_3_valid);
|
||||
|
||||
SchemaUpdater updater{this->database.get()};
|
||||
|
||||
EXPECT_FALSE(updater.apply_migration_files(this->migration_files_path, 1));
|
||||
EXPECT_FALSE(updater.apply_migration_files(this->migration_files_path, 2));
|
||||
EXPECT_FALSE(updater.apply_migration_files(this->migration_files_path, 3));
|
||||
|
||||
this->ExpectUserVersion(0);
|
||||
EXPECT_FALSE(this->DoesTableExist(table1)); // Database was not changed
|
||||
}
|
||||
|
||||
TEST_F(DatabaseSchemaUpdaterTest, SequenceNotValidMissingUpFile) {
|
||||
|
||||
this->WriteMigrationFile(migration_file_up_1_valid);
|
||||
this->WriteMigrationFile(migration_file_up_2_valid);
|
||||
this->WriteMigrationFile(migration_file_down_2_valid);
|
||||
this->WriteMigrationFile(migration_file_down_3_valid);
|
||||
this->WriteMigrationFile(migration_file_down_4_valid);
|
||||
|
||||
SchemaUpdater updater{this->database.get()};
|
||||
|
||||
EXPECT_FALSE(updater.apply_migration_files(this->migration_files_path, 1));
|
||||
EXPECT_FALSE(updater.apply_migration_files(this->migration_files_path, 2));
|
||||
EXPECT_FALSE(updater.apply_migration_files(this->migration_files_path, 3));
|
||||
|
||||
this->ExpectUserVersion(0);
|
||||
EXPECT_FALSE(this->DoesTableExist(table1)); // Database was not changed
|
||||
}
|
||||
|
||||
TEST_F(DatabaseSchemaUpdaterTest, ApplyMultipleMigrationFilesStepByStep) {
|
||||
|
||||
this->WriteMigrationFile(migration_file_up_1_valid);
|
||||
this->WriteMigrationFile(migration_file_up_2_valid);
|
||||
this->WriteMigrationFile(migration_file_up_3_valid);
|
||||
this->WriteMigrationFile(migration_file_down_2_valid);
|
||||
this->WriteMigrationFile(migration_file_down_3_valid);
|
||||
|
||||
SchemaUpdater updater{this->database.get()};
|
||||
|
||||
EXPECT_TRUE(updater.apply_migration_files(this->migration_files_path, 1));
|
||||
this->ExpectUserVersion(1);
|
||||
EXPECT_TRUE(this->DoesTableExist(table1));
|
||||
EXPECT_FALSE(this->DoesTableExist(table2));
|
||||
EXPECT_FALSE(this->DoesTableExist(table3));
|
||||
|
||||
EXPECT_TRUE(updater.apply_migration_files(this->migration_files_path, 2));
|
||||
this->ExpectUserVersion(2);
|
||||
EXPECT_TRUE(this->DoesTableExist(table1));
|
||||
EXPECT_TRUE(this->DoesTableExist(table2));
|
||||
EXPECT_FALSE(this->DoesTableExist(table3));
|
||||
|
||||
EXPECT_TRUE(updater.apply_migration_files(this->migration_files_path, 3));
|
||||
this->ExpectUserVersion(3);
|
||||
EXPECT_TRUE(this->DoesTableExist(table1));
|
||||
EXPECT_TRUE(this->DoesTableExist(table2));
|
||||
EXPECT_TRUE(this->DoesTableExist(table3));
|
||||
|
||||
EXPECT_TRUE(updater.apply_migration_files(this->migration_files_path, 2));
|
||||
this->ExpectUserVersion(2);
|
||||
EXPECT_TRUE(this->DoesTableExist(table1));
|
||||
EXPECT_TRUE(this->DoesTableExist(table2));
|
||||
EXPECT_FALSE(this->DoesTableExist(table3));
|
||||
|
||||
EXPECT_TRUE(updater.apply_migration_files(this->migration_files_path, 1));
|
||||
this->ExpectUserVersion(1);
|
||||
EXPECT_TRUE(this->DoesTableExist(table1));
|
||||
EXPECT_FALSE(this->DoesTableExist(table2));
|
||||
EXPECT_FALSE(this->DoesTableExist(table3));
|
||||
}
|
||||
|
||||
TEST_F(DatabaseSchemaUpdaterTest, ApplyMultipleMigrationFilesAtOnce) {
|
||||
|
||||
this->WriteMigrationFile(migration_file_up_1_valid);
|
||||
this->WriteMigrationFile(migration_file_up_2_valid);
|
||||
this->WriteMigrationFile(migration_file_up_3_valid);
|
||||
this->WriteMigrationFile(migration_file_down_2_valid);
|
||||
this->WriteMigrationFile(migration_file_down_3_valid);
|
||||
|
||||
SchemaUpdater updater{this->database.get()};
|
||||
|
||||
EXPECT_TRUE(updater.apply_migration_files(this->migration_files_path, 3));
|
||||
this->ExpectUserVersion(3);
|
||||
EXPECT_TRUE(this->DoesTableExist(table1));
|
||||
EXPECT_TRUE(this->DoesTableExist(table2));
|
||||
EXPECT_TRUE(this->DoesTableExist(table3));
|
||||
|
||||
EXPECT_TRUE(updater.apply_migration_files(this->migration_files_path, 1));
|
||||
this->ExpectUserVersion(1);
|
||||
EXPECT_TRUE(this->DoesTableExist(table1));
|
||||
EXPECT_FALSE(this->DoesTableExist(table2));
|
||||
EXPECT_FALSE(this->DoesTableExist(table3));
|
||||
}
|
||||
|
||||
TEST_F(DatabaseSchemaUpdaterTest, ApplyMultipleMigrationFilesAtOnceWithFailure) {
|
||||
|
||||
this->WriteMigrationFile(migration_file_up_1_valid);
|
||||
this->WriteMigrationFile(migration_file_up_2_valid);
|
||||
this->WriteMigrationFile(migration_file_up_3_valid);
|
||||
this->WriteMigrationFile(migration_file_down_2_valid);
|
||||
this->WriteMigrationFile(migration_file_down_3_valid);
|
||||
|
||||
SchemaUpdater updater{this->database.get()};
|
||||
|
||||
EXPECT_TRUE(updater.apply_migration_files(this->migration_files_path, 1));
|
||||
this->ExpectUserVersion(1);
|
||||
EXPECT_TRUE(this->DoesTableExist(table1));
|
||||
EXPECT_FALSE(this->DoesTableExist(table2));
|
||||
|
||||
EXPECT_TRUE(this->database->execute_statement(migration_file_up_2_valid.content.data()));
|
||||
|
||||
EXPECT_TRUE(this->DoesTableExist(table2));
|
||||
|
||||
EXPECT_FALSE(updater.apply_migration_files(this->migration_files_path, 3));
|
||||
|
||||
EXPECT_TRUE(this->DoesTableExist(table1));
|
||||
EXPECT_TRUE(this->DoesTableExist(table2));
|
||||
EXPECT_FALSE(this->DoesTableExist(table3));
|
||||
|
||||
this->ExpectUserVersion(1);
|
||||
}
|
||||
|
||||
} // namespace everest::db::sqlite
|
||||
@@ -0,0 +1,199 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2020 - 2025 Pionix GmbH and Contributors to EVerest
|
||||
|
||||
#include <everest/database/exceptions.hpp>
|
||||
#include <everest/database/sqlite/connection.hpp>
|
||||
#include <everest/database/sqlite/statement.hpp>
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <filesystem>
|
||||
#include <string>
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
namespace everest::db::sqlite {
|
||||
|
||||
class SQLiteStatementTest : public ::testing::Test {
|
||||
protected:
|
||||
std::unique_ptr<Connection> db;
|
||||
|
||||
void SetUp() override {
|
||||
fs::path db_path = "file::memory:?cache=shared";
|
||||
db = std::make_unique<Connection>(db_path);
|
||||
ASSERT_TRUE(db->open_connection());
|
||||
|
||||
ASSERT_TRUE(db->execute_statement(
|
||||
"CREATE TABLE test_table (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT, value INTEGER, score REAL);"));
|
||||
}
|
||||
|
||||
void TearDown() override {
|
||||
db->close_connection();
|
||||
}
|
||||
};
|
||||
|
||||
TEST_F(SQLiteStatementTest, InsertAndQueryRow) {
|
||||
auto insert_stmt = db->new_statement("INSERT INTO test_table (name, value, score) VALUES (:name, :value, :score);");
|
||||
|
||||
insert_stmt->bind_text(":name", "test_name", SQLiteString::Transient);
|
||||
insert_stmt->bind_int(":value", 42);
|
||||
insert_stmt->bind_double(":score", 98.6);
|
||||
|
||||
ASSERT_EQ(insert_stmt->step(), SQLITE_DONE);
|
||||
|
||||
auto select_stmt = db->new_statement("SELECT name, value, score FROM test_table WHERE id = 1;");
|
||||
ASSERT_EQ(select_stmt->step(), SQLITE_ROW);
|
||||
|
||||
EXPECT_EQ(select_stmt->column_text(0), "test_name");
|
||||
EXPECT_EQ(select_stmt->column_int(1), 42);
|
||||
EXPECT_DOUBLE_EQ(select_stmt->column_double(2), 98.6);
|
||||
}
|
||||
|
||||
TEST_F(SQLiteStatementTest, NullBindingAndOptionalResult) {
|
||||
auto insert_stmt = db->new_statement("INSERT INTO test_table (name, value, score) VALUES (?, ?, ?);");
|
||||
insert_stmt->bind_null(1);
|
||||
insert_stmt->bind_int(2, 100);
|
||||
insert_stmt->bind_null(3);
|
||||
ASSERT_EQ(insert_stmt->step(), SQLITE_DONE);
|
||||
|
||||
auto select_stmt = db->new_statement("SELECT name, score FROM test_table WHERE id = 1;");
|
||||
ASSERT_EQ(select_stmt->step(), SQLITE_ROW);
|
||||
|
||||
EXPECT_FALSE(select_stmt->column_text_nullable(0).has_value());
|
||||
EXPECT_FALSE(select_stmt->column_text_nullable(1).has_value());
|
||||
}
|
||||
|
||||
TEST_F(SQLiteStatementTest, InvalidParameterThrows) {
|
||||
auto stmt = db->new_statement("SELECT * FROM test_table WHERE name = :name;");
|
||||
EXPECT_THROW(stmt->bind_int(":invalid", 1), std::out_of_range);
|
||||
}
|
||||
|
||||
TEST_F(SQLiteStatementTest, ResetAndReuseStatement) {
|
||||
auto insert_stmt = db->new_statement("INSERT INTO test_table (name, value, score) VALUES (?, ?, ?);");
|
||||
|
||||
insert_stmt->bind_text(1, "row1");
|
||||
insert_stmt->bind_int(2, 1);
|
||||
insert_stmt->bind_double(3, 1.1);
|
||||
ASSERT_EQ(insert_stmt->step(), SQLITE_DONE);
|
||||
|
||||
ASSERT_EQ(insert_stmt->reset(), SQLITE_OK);
|
||||
|
||||
insert_stmt->bind_text(1, "row2");
|
||||
insert_stmt->bind_int(2, 2);
|
||||
insert_stmt->bind_double(3, 2.2);
|
||||
ASSERT_EQ(insert_stmt->step(), SQLITE_DONE);
|
||||
|
||||
auto select_stmt = db->new_statement("SELECT COUNT(*) FROM test_table;");
|
||||
ASSERT_EQ(select_stmt->step(), SQLITE_ROW);
|
||||
EXPECT_EQ(select_stmt->column_int(0), 2);
|
||||
}
|
||||
|
||||
TEST_F(SQLiteStatementTest, BindByNameAndIndexConsistency) {
|
||||
auto stmt_by_index = db->new_statement("INSERT INTO test_table (name, value, score) VALUES (?, ?, ?);");
|
||||
auto stmt_by_name =
|
||||
db->new_statement("INSERT INTO test_table (name, value, score) VALUES (:name, :value, :score);");
|
||||
|
||||
stmt_by_index->bind_text(1, "index_row");
|
||||
stmt_by_index->bind_int(2, 123);
|
||||
stmt_by_index->bind_double(3, 1.23);
|
||||
ASSERT_EQ(stmt_by_index->step(), SQLITE_DONE);
|
||||
|
||||
stmt_by_name->bind_text(":name", "name_row");
|
||||
stmt_by_name->bind_int(":value", 321);
|
||||
stmt_by_name->bind_double(":score", 3.21);
|
||||
ASSERT_EQ(stmt_by_name->step(), SQLITE_DONE);
|
||||
|
||||
auto select_stmt = db->new_statement("SELECT COUNT(*) FROM test_table;");
|
||||
ASSERT_EQ(select_stmt->step(), SQLITE_ROW);
|
||||
EXPECT_EQ(select_stmt->column_int(0), 2);
|
||||
}
|
||||
|
||||
TEST_F(SQLiteStatementTest, BindInt64AndReadBack) {
|
||||
int64_t large_value = 9223372036854775807LL; // max int64
|
||||
|
||||
auto insert_stmt = db->new_statement("INSERT INTO test_table (name, value, score) VALUES (?, ?, ?);");
|
||||
insert_stmt->bind_text(1, "int64_test");
|
||||
insert_stmt->bind_int64(2, large_value);
|
||||
insert_stmt->bind_double(3, 0.0);
|
||||
ASSERT_EQ(insert_stmt->step(), SQLITE_DONE);
|
||||
|
||||
auto select_stmt = db->new_statement("SELECT value FROM test_table WHERE name = 'int64_test';");
|
||||
ASSERT_EQ(select_stmt->step(), SQLITE_ROW);
|
||||
EXPECT_EQ(select_stmt->column_int64(0), large_value);
|
||||
}
|
||||
|
||||
TEST_F(SQLiteStatementTest, BindInt64NamedParameter) {
|
||||
int64_t big_value = 1234567890123456789LL;
|
||||
|
||||
auto insert_stmt = db->new_statement("INSERT INTO test_table (name, value, score) VALUES (:name, :value, :score);");
|
||||
|
||||
insert_stmt->bind_text(":name", "int64_named", SQLiteString::Transient);
|
||||
insert_stmt->bind_int64(":value", big_value);
|
||||
insert_stmt->bind_double(":score", 42.42);
|
||||
|
||||
ASSERT_EQ(insert_stmt->step(), SQLITE_DONE);
|
||||
|
||||
auto select_stmt = db->new_statement("SELECT value FROM test_table WHERE name = 'int64_named';");
|
||||
ASSERT_EQ(select_stmt->step(), SQLITE_ROW);
|
||||
EXPECT_EQ(select_stmt->column_int64(0), big_value);
|
||||
}
|
||||
|
||||
TEST_F(SQLiteStatementTest, StatementDestructorFinalizesStatement) {
|
||||
{
|
||||
auto stmt = db->new_statement("INSERT INTO test_table (name, value, score) VALUES ('temp', 0, 0.0);");
|
||||
ASSERT_EQ(stmt->step(), SQLITE_DONE);
|
||||
// stmt goes out of scope here
|
||||
}
|
||||
|
||||
auto select_stmt = db->new_statement("SELECT COUNT(*) FROM test_table WHERE name = 'temp';");
|
||||
ASSERT_EQ(select_stmt->step(), SQLITE_ROW);
|
||||
EXPECT_EQ(select_stmt->column_int(0), 1);
|
||||
}
|
||||
|
||||
TEST_F(SQLiteStatementTest, GetNumberOfColumnsInRow) {
|
||||
auto insert_stmt = db->new_statement("INSERT INTO test_table (name, value, score) VALUES ('check_cols', 1, 1.0);");
|
||||
ASSERT_EQ(insert_stmt->step(), SQLITE_DONE);
|
||||
|
||||
auto select_stmt = db->new_statement("SELECT id, name, value, score FROM test_table WHERE name = 'check_cols';");
|
||||
ASSERT_EQ(select_stmt->step(), SQLITE_ROW);
|
||||
EXPECT_EQ(select_stmt->get_number_of_rows(), 4);
|
||||
}
|
||||
|
||||
TEST_F(SQLiteStatementTest, ColumnTypeChecks) {
|
||||
auto insert_stmt =
|
||||
db->new_statement("INSERT INTO test_table (name, value, score) VALUES ('type_test', 999, 9.99);");
|
||||
ASSERT_EQ(insert_stmt->step(), SQLITE_DONE);
|
||||
|
||||
auto select_stmt = db->new_statement("SELECT name, value, score FROM test_table WHERE name = 'type_test';");
|
||||
ASSERT_EQ(select_stmt->step(), SQLITE_ROW);
|
||||
|
||||
EXPECT_EQ(select_stmt->column_type(0), SQLITE_TEXT);
|
||||
EXPECT_EQ(select_stmt->column_type(1), SQLITE_INTEGER);
|
||||
EXPECT_EQ(select_stmt->column_type(2), SQLITE_FLOAT);
|
||||
}
|
||||
|
||||
TEST_F(SQLiteStatementTest, BindNullByName) {
|
||||
auto insert_stmt = db->new_statement("INSERT INTO test_table (name, value, score) VALUES (:name, :value, :score);");
|
||||
insert_stmt->bind_null(":name");
|
||||
insert_stmt->bind_int(":value", 5);
|
||||
insert_stmt->bind_null(":score");
|
||||
ASSERT_EQ(insert_stmt->step(), SQLITE_DONE);
|
||||
|
||||
auto select_stmt = db->new_statement("SELECT name, score FROM test_table WHERE value = 5;");
|
||||
ASSERT_EQ(select_stmt->step(), SQLITE_ROW);
|
||||
|
||||
EXPECT_FALSE(select_stmt->column_text_nullable(0).has_value());
|
||||
EXPECT_FALSE(select_stmt->column_text_nullable(1).has_value());
|
||||
}
|
||||
|
||||
TEST_F(SQLiteStatementTest, ResetPreservesPreparedStatement) {
|
||||
auto stmt = db->new_statement("INSERT INTO test_table (name, value, score) VALUES ('x', 1, 1.0);");
|
||||
ASSERT_EQ(stmt->step(), SQLITE_DONE);
|
||||
ASSERT_EQ(stmt->reset(), SQLITE_OK);
|
||||
|
||||
stmt->bind_text(1, "x"); // optional rebind test
|
||||
stmt->bind_int(2, 2);
|
||||
stmt->bind_double(3, 2.0);
|
||||
ASSERT_EQ(stmt->step(), SQLITE_DONE);
|
||||
}
|
||||
|
||||
} // namespace everest::db::sqlite
|
||||
Reference in New Issue
Block a user