# Tests for smartbotic-database

# yyjson (v1.10+) — Phase A swaps nlohmann::json::parse for parse_to_nlohmann
# at hot paths in wal.cpp, snapshot.cpp, history_store.cpp, etc. Any test
# target that compiles those files now also needs json_parse.cpp linked and
# yyjson linked. Resolve once at file scope so every test target below can
# use ${yyjson_LIBRARIES} / ${yyjson_INCLUDE_DIRS}.
if(NOT yyjson_FOUND)
    find_package(PkgConfig REQUIRED)
    pkg_check_modules(yyjson REQUIRED yyjson)
endif()

# LMDB (v2.0 storage substrate) — resolve once at file scope, mirroring the
# yyjson pattern. liblmdb does not ship pkg-config on Debian 13, so use
# find_path/find_library. Each test target that pulls in storage/ sources
# adds ${LMDB_INCLUDE_DIR} / ${LMDB_LIBRARY} to its link/include flags.
if(NOT LMDB_INCLUDE_DIR OR NOT LMDB_LIBRARY)
    find_path(LMDB_INCLUDE_DIR lmdb.h)
    find_library(LMDB_LIBRARY NAMES lmdb)
    if(NOT LMDB_INCLUDE_DIR OR NOT LMDB_LIBRARY)
        message(FATAL_ERROR "LMDB not found. Install liblmdb-dev.")
    endif()
endif()

# Vector storage integration test
# Compiles memory_store.cpp directly — no dependency on the full service executable.
set(TEST_VECTOR_SOURCES
    test_vector_storage.cpp
    ${CMAKE_CURRENT_SOURCE_DIR}/../service/src/memory_store.cpp
    ${CMAKE_CURRENT_SOURCE_DIR}/../service/src/config/collection_config_manager.cpp
    ${CMAKE_CURRENT_SOURCE_DIR}/../service/src/persistence/history_store.cpp
    ${CMAKE_CURRENT_SOURCE_DIR}/../service/src/persistence/wal.cpp
    ${CMAKE_CURRENT_SOURCE_DIR}/../service/src/json_parse.cpp
    ${CMAKE_CURRENT_SOURCE_DIR}/../service/src/doc_binary.cpp
)

add_executable(test_vector_storage ${TEST_VECTOR_SOURCES})

target_include_directories(test_vector_storage PRIVATE
    ${CMAKE_CURRENT_SOURCE_DIR}/../service/src
)

# nlohmann/json
if(TARGET nlohmann_json::nlohmann_json)
    target_link_libraries(test_vector_storage PRIVATE nlohmann_json::nlohmann_json)
else()
    target_include_directories(test_vector_storage PRIVATE ${NLOHMANN_JSON_INCLUDE_DIRS})
endif()

# spdlog
if(TARGET spdlog::spdlog)
    target_link_libraries(test_vector_storage PRIVATE spdlog::spdlog)
else()
    target_link_libraries(test_vector_storage PRIVATE ${SPDLOG_LIBRARIES})
    target_include_directories(test_vector_storage PRIVATE ${SPDLOG_INCLUDE_DIRS})
endif()

# Threads (required by MemoryStore background threads)
find_package(Threads REQUIRED)
target_link_libraries(test_vector_storage PRIVATE Threads::Threads)

# yyjson (wal.cpp uses parse_to_nlohmann from v1.10+)
target_link_libraries(test_vector_storage PRIVATE ${yyjson_LIBRARIES})
target_include_directories(test_vector_storage PRIVATE ${yyjson_INCLUDE_DIRS})

# View projection unit test
set(TEST_VIEWS_SOURCES
    test_views.cpp
    ${CMAKE_CURRENT_SOURCE_DIR}/../service/src/views/projection.cpp
)

add_executable(test_views ${TEST_VIEWS_SOURCES})

target_include_directories(test_views PRIVATE
    ${CMAKE_CURRENT_SOURCE_DIR}/../service/src
)

# nlohmann/json
if(TARGET nlohmann_json::nlohmann_json)
    target_link_libraries(test_views PRIVATE nlohmann_json::nlohmann_json)
else()
    target_include_directories(test_views PRIVATE ${NLOHMANN_JSON_INCLUDE_DIRS})
endif()

# Per-collection timestamp precision test
set(TEST_TIMESTAMP_PRECISION_SOURCES
    test_timestamp_precision.cpp
    ${CMAKE_CURRENT_SOURCE_DIR}/../service/src/memory_store.cpp
    ${CMAKE_CURRENT_SOURCE_DIR}/../service/src/config/collection_config_manager.cpp
    ${CMAKE_CURRENT_SOURCE_DIR}/../service/src/persistence/history_store.cpp
    ${CMAKE_CURRENT_SOURCE_DIR}/../service/src/persistence/wal.cpp
    ${CMAKE_CURRENT_SOURCE_DIR}/../service/src/json_parse.cpp
    ${CMAKE_CURRENT_SOURCE_DIR}/../service/src/doc_binary.cpp
)

add_executable(test_timestamp_precision ${TEST_TIMESTAMP_PRECISION_SOURCES})

target_include_directories(test_timestamp_precision PRIVATE
    ${CMAKE_CURRENT_SOURCE_DIR}/../service/src
)

if(TARGET nlohmann_json::nlohmann_json)
    target_link_libraries(test_timestamp_precision PRIVATE nlohmann_json::nlohmann_json)
else()
    target_include_directories(test_timestamp_precision PRIVATE ${NLOHMANN_JSON_INCLUDE_DIRS})
endif()

if(TARGET spdlog::spdlog)
    target_link_libraries(test_timestamp_precision PRIVATE spdlog::spdlog)
else()
    target_link_libraries(test_timestamp_precision PRIVATE ${SPDLOG_LIBRARIES})
    target_include_directories(test_timestamp_precision PRIVATE ${SPDLOG_INCLUDE_DIRS})
endif()

find_package(Threads REQUIRED)
target_link_libraries(test_timestamp_precision PRIVATE Threads::Threads)

# yyjson (wal.cpp uses parse_to_nlohmann from v1.10+)
target_link_libraries(test_timestamp_precision PRIVATE ${yyjson_LIBRARIES})
target_include_directories(test_timestamp_precision PRIVATE ${yyjson_INCLUDE_DIRS})

# Snapshot durability + fallback integration test
# Exercises SnapshotManager directly; pulls in memory_store.cpp, snapshot.cpp,
# and wal.cpp (for the crc32 helpers used by snapshot.cpp).
set(TEST_SNAPSHOT_DURABILITY_SOURCES
    test_snapshot_durability.cpp
    ${CMAKE_CURRENT_SOURCE_DIR}/../service/src/memory_store.cpp
    ${CMAKE_CURRENT_SOURCE_DIR}/../service/src/config/collection_config_manager.cpp
    ${CMAKE_CURRENT_SOURCE_DIR}/../service/src/persistence/snapshot.cpp
    ${CMAKE_CURRENT_SOURCE_DIR}/../service/src/persistence/wal.cpp
    ${CMAKE_CURRENT_SOURCE_DIR}/../service/src/persistence/history_store.cpp
    ${CMAKE_CURRENT_SOURCE_DIR}/../service/src/json_parse.cpp
    ${CMAKE_CURRENT_SOURCE_DIR}/../service/src/doc_binary.cpp
)

add_executable(test_snapshot_durability ${TEST_SNAPSHOT_DURABILITY_SOURCES})

target_include_directories(test_snapshot_durability PRIVATE
    ${CMAKE_CURRENT_SOURCE_DIR}/../service/src
)

if(TARGET nlohmann_json::nlohmann_json)
    target_link_libraries(test_snapshot_durability PRIVATE nlohmann_json::nlohmann_json)
else()
    target_include_directories(test_snapshot_durability PRIVATE ${NLOHMANN_JSON_INCLUDE_DIRS})
endif()

if(TARGET spdlog::spdlog)
    target_link_libraries(test_snapshot_durability PRIVATE spdlog::spdlog)
else()
    target_link_libraries(test_snapshot_durability PRIVATE ${SPDLOG_LIBRARIES})
    target_include_directories(test_snapshot_durability PRIVATE ${SPDLOG_INCLUDE_DIRS})
endif()

find_package(Threads REQUIRED)
target_link_libraries(test_snapshot_durability PRIVATE Threads::Threads)

# yyjson (wal.cpp/snapshot.cpp use parse_to_nlohmann from v1.10+)
target_link_libraries(test_snapshot_durability PRIVATE ${yyjson_LIBRARIES})
target_include_directories(test_snapshot_durability PRIVATE ${yyjson_INCLUDE_DIRS})

# LZ4 — snapshot.cpp #includes <lz4.h> unless DISABLE_LZ4 is defined, so link it.
find_package(PkgConfig QUIET)
if(PKG_CONFIG_FOUND)
    pkg_check_modules(TEST_LZ4 QUIET liblz4)
endif()
if(TEST_LZ4_FOUND)
    target_link_libraries(test_snapshot_durability PRIVATE ${TEST_LZ4_LIBRARIES})
    target_include_directories(test_snapshot_durability PRIVATE ${TEST_LZ4_INCLUDE_DIRS})
else()
    # Fallback: assume liblz4 is installed at a standard location
    target_link_libraries(test_snapshot_durability PRIVATE lz4)
endif()

# Config drop-in loader test (v1.7.0 T1 → T12)
# Exercises loadLayeredConfig() in isolation.
set(TEST_CONFIG_DROPINS_SOURCES
    test_config_dropins.cpp
    ${CMAKE_CURRENT_SOURCE_DIR}/../service/src/config/config_loader.cpp
)

add_executable(test_config_dropins ${TEST_CONFIG_DROPINS_SOURCES})

target_include_directories(test_config_dropins PRIVATE
    ${CMAKE_CURRENT_SOURCE_DIR}/../service/src
)

if(TARGET nlohmann_json::nlohmann_json)
    target_link_libraries(test_config_dropins PRIVATE nlohmann_json::nlohmann_json)
else()
    target_include_directories(test_config_dropins PRIVATE ${NLOHMANN_JSON_INCLUDE_DIRS})
endif()

if(TARGET spdlog::spdlog)
    target_link_libraries(test_config_dropins PRIVATE spdlog::spdlog)
else()
    target_link_libraries(test_config_dropins PRIVATE ${SPDLOG_LIBRARIES})
    target_include_directories(test_config_dropins PRIVATE ${SPDLOG_INCLUDE_DIRS})
endif()

# Eviction test (v1.7.0 T4+T5+T6 → T12)
# Exercises chunked eviction, per-collection priorities, and the hot-write
# floor directly through MemoryStore. Mirrors the link deps of test_timestamp_precision.
set(TEST_EVICTION_SOURCES
    test_eviction.cpp
    ${CMAKE_CURRENT_SOURCE_DIR}/../service/src/memory_store.cpp
    ${CMAKE_CURRENT_SOURCE_DIR}/../service/src/config/collection_config_manager.cpp
    ${CMAKE_CURRENT_SOURCE_DIR}/../service/src/persistence/history_store.cpp
    ${CMAKE_CURRENT_SOURCE_DIR}/../service/src/persistence/wal.cpp
    ${CMAKE_CURRENT_SOURCE_DIR}/../service/src/json_parse.cpp
    ${CMAKE_CURRENT_SOURCE_DIR}/../service/src/doc_binary.cpp
)

add_executable(test_eviction ${TEST_EVICTION_SOURCES})

target_include_directories(test_eviction PRIVATE
    ${CMAKE_CURRENT_SOURCE_DIR}/../service/src
)

if(TARGET nlohmann_json::nlohmann_json)
    target_link_libraries(test_eviction PRIVATE nlohmann_json::nlohmann_json)
else()
    target_include_directories(test_eviction PRIVATE ${NLOHMANN_JSON_INCLUDE_DIRS})
endif()

if(TARGET spdlog::spdlog)
    target_link_libraries(test_eviction PRIVATE spdlog::spdlog)
else()
    target_link_libraries(test_eviction PRIVATE ${SPDLOG_LIBRARIES})
    target_include_directories(test_eviction PRIVATE ${SPDLOG_INCLUDE_DIRS})
endif()

find_package(Threads REQUIRED)
target_link_libraries(test_eviction PRIVATE Threads::Threads)

# yyjson (wal.cpp uses parse_to_nlohmann from v1.10+)
target_link_libraries(test_eviction PRIVATE ${yyjson_LIBRARIES})
target_include_directories(test_eviction PRIVATE ${yyjson_INCLUDE_DIRS})

# yyjson → nlohmann bridge helper test (v1.10 Phase A T2)
# Exercises parse_to_nlohmann() in isolation against nlohmann::json::parse
# for a corpus of production-shaped docs. yyjson is configured at the
# service/ scope; re-resolve it here so the test target can find it
# independently of the service build.
if(NOT yyjson_FOUND)
    find_package(PkgConfig REQUIRED)
    pkg_check_modules(yyjson REQUIRED yyjson)
endif()

add_executable(test_json_parse
    test_json_parse.cpp
    ${CMAKE_CURRENT_SOURCE_DIR}/../service/src/json_parse.cpp
)

target_include_directories(test_json_parse PRIVATE
    ${CMAKE_CURRENT_SOURCE_DIR}/../service/src
    ${yyjson_INCLUDE_DIRS}
)

if(TARGET nlohmann_json::nlohmann_json)
    target_link_libraries(test_json_parse PRIVATE nlohmann_json::nlohmann_json)
else()
    target_include_directories(test_json_parse PRIVATE ${NLOHMANN_JSON_INCLUDE_DIRS})
endif()

target_link_libraries(test_json_parse PRIVATE ${yyjson_LIBRARIES})

# Binary doc encoder/decoder test (v1.11 Phase B T2)
# Exercises doc_binary::Doc, encode, decode, get_field, to_json_text against
# nlohmann::json round-trips. yyjson resolution is shared at file scope above.
add_executable(test_doc_binary
    test_doc_binary.cpp
    ${CMAKE_CURRENT_SOURCE_DIR}/../service/src/doc_binary.cpp
)

target_include_directories(test_doc_binary PRIVATE
    ${CMAKE_CURRENT_SOURCE_DIR}/../service/src
    ${yyjson_INCLUDE_DIRS}
)

if(TARGET nlohmann_json::nlohmann_json)
    target_link_libraries(test_doc_binary PRIVATE nlohmann_json::nlohmann_json)
else()
    target_include_directories(test_doc_binary PRIVATE ${NLOHMANN_JSON_INCLUDE_DIRS})
endif()

target_link_libraries(test_doc_binary PRIVATE ${yyjson_LIBRARIES})

# Parse-time micro-benchmark (v1.10 Phase A T8) — manual bench, NOT
# registered with CTest. Run as ./build/tests/bench_json_parse <corpus>.
add_executable(bench_json_parse
    bench_json_parse.cpp
    ${CMAKE_CURRENT_SOURCE_DIR}/../service/src/json_parse.cpp
)

target_include_directories(bench_json_parse PRIVATE
    ${CMAKE_CURRENT_SOURCE_DIR}/../service/src
    ${yyjson_INCLUDE_DIRS}
)

if(TARGET nlohmann_json::nlohmann_json)
    target_link_libraries(bench_json_parse PRIVATE nlohmann_json::nlohmann_json)
else()
    target_include_directories(bench_json_parse PRIVATE ${NLOHMANN_JSON_INCLUDE_DIRS})
endif()

target_link_libraries(bench_json_parse PRIVATE ${yyjson_LIBRARIES})

# RSS bench (v1.11 Phase B T8) — manual bench, NOT registered with CTest.
# Run as ./build/tests/bench_doc_memory [N].
add_executable(bench_doc_memory
    bench_doc_memory.cpp
    ${CMAKE_CURRENT_SOURCE_DIR}/../service/src/doc_binary.cpp
)

target_include_directories(bench_doc_memory PRIVATE
    ${CMAKE_CURRENT_SOURCE_DIR}/../service/src
    ${yyjson_INCLUDE_DIRS}
)

if(TARGET nlohmann_json::nlohmann_json)
    target_link_libraries(bench_doc_memory PRIVATE nlohmann_json::nlohmann_json)
else()
    target_include_directories(bench_doc_memory PRIVATE ${NLOHMANN_JSON_INCLUDE_DIRS})
endif()

target_link_libraries(bench_doc_memory PRIVATE ${yyjson_LIBRARIES})

# LMDB environment unit test (v2.0 storage Phase C Stage 1, Tasks 1.2 / 1.3).
# Compiles the storage/ sources directly — no dependency on the service
# executable.
add_executable(test_lmdb_env
    test_lmdb_env.cpp
    ${CMAKE_CURRENT_SOURCE_DIR}/../service/src/storage/lmdb_env.cpp
    ${CMAKE_CURRENT_SOURCE_DIR}/../service/src/storage/lmdb_txn.cpp
    ${CMAKE_CURRENT_SOURCE_DIR}/../service/src/storage/lmdb_dbi.cpp
)

target_include_directories(test_lmdb_env PRIVATE
    ${CMAKE_CURRENT_SOURCE_DIR}/../service/src
    ${LMDB_INCLUDE_DIR}
)

target_link_libraries(test_lmdb_env PRIVATE ${LMDB_LIBRARY})

# DocumentStore + LmdbDocumentStore unit test (v2.0 storage Phase C Stage 2).
# Same shape as test_lmdb_env: links the storage/ sources and a couple of
# helpers (doc_binary, json_parse) directly. No dependency on the full
# service executable.
add_executable(test_document_store
    test_document_store.cpp
    ${CMAKE_CURRENT_SOURCE_DIR}/../service/src/storage/lmdb_env.cpp
    ${CMAKE_CURRENT_SOURCE_DIR}/../service/src/storage/lmdb_txn.cpp
    ${CMAKE_CURRENT_SOURCE_DIR}/../service/src/storage/lmdb_dbi.cpp
    ${CMAKE_CURRENT_SOURCE_DIR}/../service/src/storage/document_store_lmdb.cpp
    ${CMAKE_CURRENT_SOURCE_DIR}/../service/src/relations/relation_index.cpp
    ${CMAKE_CURRENT_SOURCE_DIR}/../service/src/storage/secondary_index.cpp
    ${CMAKE_CURRENT_SOURCE_DIR}/../service/src/storage/subdb_identity.cpp
    ${CMAKE_CURRENT_SOURCE_DIR}/../service/src/json_parse.cpp
    ${CMAKE_CURRENT_SOURCE_DIR}/../service/src/doc_binary.cpp
)

target_include_directories(test_document_store PRIVATE
    ${CMAKE_CURRENT_SOURCE_DIR}/../service/src
    ${LMDB_INCLUDE_DIR}
    ${yyjson_INCLUDE_DIRS}
)

target_link_libraries(test_document_store PRIVATE ${LMDB_LIBRARY})
target_link_libraries(test_document_store PRIVATE ${yyjson_LIBRARIES})

if(TARGET nlohmann_json::nlohmann_json)
    target_link_libraries(test_document_store PRIVATE nlohmann_json::nlohmann_json)
else()
    target_include_directories(test_document_store PRIVATE ${NLOHMANN_JSON_INCLUDE_DIRS})
endif()

# v1.x → v2.0 migration tool unit test (v2.0 storage Phase C Stage 3).
# Pulls in both the v1.x persistence stack (snapshot.cpp, wal.cpp, history_store.cpp,
# memory_store.cpp) and the v2.0 storage stack (LMDB primitives + DocumentStore +
# migrate_v1_to_v2). Each side is independent of the other at link time except
# for the migration .cpp itself, which is the bridge.
add_executable(test_migrate_v1_to_v2
    test_migrate_v1_to_v2.cpp
    ${CMAKE_CURRENT_SOURCE_DIR}/../service/src/memory_store.cpp
    ${CMAKE_CURRENT_SOURCE_DIR}/../service/src/config/collection_config_manager.cpp
    ${CMAKE_CURRENT_SOURCE_DIR}/../service/src/persistence/snapshot.cpp
    ${CMAKE_CURRENT_SOURCE_DIR}/../service/src/persistence/wal.cpp
    ${CMAKE_CURRENT_SOURCE_DIR}/../service/src/persistence/history_store.cpp
    ${CMAKE_CURRENT_SOURCE_DIR}/../service/src/json_parse.cpp
    ${CMAKE_CURRENT_SOURCE_DIR}/../service/src/doc_binary.cpp
    ${CMAKE_CURRENT_SOURCE_DIR}/../service/src/storage/lmdb_env.cpp
    ${CMAKE_CURRENT_SOURCE_DIR}/../service/src/storage/lmdb_txn.cpp
    ${CMAKE_CURRENT_SOURCE_DIR}/../service/src/storage/lmdb_dbi.cpp
    ${CMAKE_CURRENT_SOURCE_DIR}/../service/src/storage/document_store_lmdb.cpp
    ${CMAKE_CURRENT_SOURCE_DIR}/../service/src/relations/relation_index.cpp
    ${CMAKE_CURRENT_SOURCE_DIR}/../service/src/storage/secondary_index.cpp
    ${CMAKE_CURRENT_SOURCE_DIR}/../service/src/storage/subdb_identity.cpp
    ${CMAKE_CURRENT_SOURCE_DIR}/../service/src/storage/migrate_v1_to_v2.cpp
)

target_include_directories(test_migrate_v1_to_v2 PRIVATE
    ${CMAKE_CURRENT_SOURCE_DIR}/../service/src
    ${LMDB_INCLUDE_DIR}
    ${yyjson_INCLUDE_DIRS}
)

target_link_libraries(test_migrate_v1_to_v2 PRIVATE ${LMDB_LIBRARY})
target_link_libraries(test_migrate_v1_to_v2 PRIVATE ${yyjson_LIBRARIES})

if(TARGET nlohmann_json::nlohmann_json)
    target_link_libraries(test_migrate_v1_to_v2 PRIVATE nlohmann_json::nlohmann_json)
else()
    target_include_directories(test_migrate_v1_to_v2 PRIVATE ${NLOHMANN_JSON_INCLUDE_DIRS})
endif()

if(TARGET spdlog::spdlog)
    target_link_libraries(test_migrate_v1_to_v2 PRIVATE spdlog::spdlog)
else()
    target_link_libraries(test_migrate_v1_to_v2 PRIVATE ${SPDLOG_LIBRARIES})
    target_include_directories(test_migrate_v1_to_v2 PRIVATE ${SPDLOG_INCLUDE_DIRS})
endif()

find_package(Threads REQUIRED)
target_link_libraries(test_migrate_v1_to_v2 PRIVATE Threads::Threads)

# LZ4 — snapshot.cpp uses LZ4 compression
if(PKG_CONFIG_FOUND)
    pkg_check_modules(MIGRATE_LZ4 QUIET liblz4)
endif()
if(MIGRATE_LZ4_FOUND)
    target_link_libraries(test_migrate_v1_to_v2 PRIVATE ${MIGRATE_LZ4_LIBRARIES})
    target_include_directories(test_migrate_v1_to_v2 PRIVATE ${MIGRATE_LZ4_INCLUDE_DIRS})
else()
    target_link_libraries(test_migrate_v1_to_v2 PRIVATE lz4)
endif()

# v2.0 Stage 4 — dual-write mirror unit + integration test.
# Links the MemoryStore + LMDB substrate the same way test_migrate_v1_to_v2 does,
# but skips persistence/snapshot/wal since the mirror helper does not touch them.
add_executable(test_dual_write_mirror
    test_dual_write_mirror.cpp
    ${CMAKE_CURRENT_SOURCE_DIR}/../service/src/memory_store.cpp
    ${CMAKE_CURRENT_SOURCE_DIR}/../service/src/config/collection_config_manager.cpp
    ${CMAKE_CURRENT_SOURCE_DIR}/../service/src/persistence/history_store.cpp
    ${CMAKE_CURRENT_SOURCE_DIR}/../service/src/persistence/wal.cpp
    ${CMAKE_CURRENT_SOURCE_DIR}/../service/src/json_parse.cpp
    ${CMAKE_CURRENT_SOURCE_DIR}/../service/src/doc_binary.cpp
    ${CMAKE_CURRENT_SOURCE_DIR}/../service/src/storage/lmdb_env.cpp
    ${CMAKE_CURRENT_SOURCE_DIR}/../service/src/storage/lmdb_txn.cpp
    ${CMAKE_CURRENT_SOURCE_DIR}/../service/src/storage/lmdb_dbi.cpp
    ${CMAKE_CURRENT_SOURCE_DIR}/../service/src/storage/document_store_lmdb.cpp
    ${CMAKE_CURRENT_SOURCE_DIR}/../service/src/relations/relation_index.cpp
    ${CMAKE_CURRENT_SOURCE_DIR}/../service/src/storage/secondary_index.cpp
    ${CMAKE_CURRENT_SOURCE_DIR}/../service/src/storage/subdb_identity.cpp
)

target_include_directories(test_dual_write_mirror PRIVATE
    ${CMAKE_CURRENT_SOURCE_DIR}/../service/src
    ${LMDB_INCLUDE_DIR}
    ${yyjson_INCLUDE_DIRS}
)

target_link_libraries(test_dual_write_mirror PRIVATE ${LMDB_LIBRARY})
target_link_libraries(test_dual_write_mirror PRIVATE ${yyjson_LIBRARIES})

if(TARGET nlohmann_json::nlohmann_json)
    target_link_libraries(test_dual_write_mirror PRIVATE nlohmann_json::nlohmann_json)
else()
    target_include_directories(test_dual_write_mirror PRIVATE ${NLOHMANN_JSON_INCLUDE_DIRS})
endif()

if(TARGET spdlog::spdlog)
    target_link_libraries(test_dual_write_mirror PRIVATE spdlog::spdlog)
else()
    target_link_libraries(test_dual_write_mirror PRIVATE ${SPDLOG_LIBRARIES})
    target_include_directories(test_dual_write_mirror PRIVATE ${SPDLOG_INCLUDE_DIRS})
endif()

find_package(Threads REQUIRED)
target_link_libraries(test_dual_write_mirror PRIVATE Threads::Threads)

# Five test files assert via assert(), which CMAKE_BUILD_TYPE=Release compiles
# out via -DNDEBUG. That silently reduced 104 assertions across test_views,
# test_eviction, test_snapshot_durability, test_timestamp_precision and
# test_config_dropins to no-ops: the suites ran, printed PASS, and checked
# nothing. Strip NDEBUG from test targets so assertions are live in every
# build type. Production targets are unaffected.
foreach(_t
    test_vector_storage test_views test_timestamp_precision
    test_snapshot_durability test_config_dropins test_eviction
    test_json_parse test_doc_binary test_lmdb_env test_document_store
    test_migrate_v1_to_v2 test_dual_write_mirror)
  target_compile_options(${_t} PRIVATE -UNDEBUG)
endforeach()

# Register with CTest
enable_testing()
add_test(NAME vector_storage COMMAND test_vector_storage)
add_test(NAME views COMMAND test_views)
add_test(NAME timestamp_precision COMMAND test_timestamp_precision)
add_test(NAME snapshot_durability COMMAND test_snapshot_durability)
add_test(NAME config_dropins COMMAND test_config_dropins)
add_test(NAME eviction COMMAND test_eviction)
add_test(NAME json_parse COMMAND test_json_parse)
add_test(NAME doc_binary COMMAND test_doc_binary)
add_test(NAME lmdb_env COMMAND test_lmdb_env)
add_test(NAME document_store COMMAND test_document_store)
add_test(NAME migrate_v1_to_v2 COMMAND test_migrate_v1_to_v2)
add_test(NAME dual_write_mirror COMMAND test_dual_write_mirror)

# v2.3 — project addressing (header-only parser; no link deps).
add_executable(test_project_addressing test_project_addressing.cpp)
target_include_directories(test_project_addressing PRIVATE
    ${CMAKE_CURRENT_SOURCE_DIR}/../service/src
)
add_test(NAME project_addressing COMMAND test_project_addressing)

# v2.3 Stage F — project CRUD placeholders (header-only filesystem helpers;
# no link deps beyond std::filesystem). Exercises listProjects /
# createProject / dropProject against a tmp data dir. Stage B replaces the
# placeholder with a project-registry-backed implementation; this test
# will be retargeted then.
add_executable(test_project_crud test_project_crud.cpp)
target_include_directories(test_project_crud PRIVATE
    ${CMAKE_CURRENT_SOURCE_DIR}/../service/src
)
add_test(NAME project_crud COMMAND test_project_crud)

# v2.4.4 — sub-db identity sentinel. Regression cover for the misbound-MDB_dbi
# incident in which writes for one collection landed in another's sub-db.
add_executable(test_subdb_identity
    test_subdb_identity.cpp
    ${CMAKE_CURRENT_SOURCE_DIR}/../service/src/storage/lmdb_env.cpp
    ${CMAKE_CURRENT_SOURCE_DIR}/../service/src/storage/lmdb_txn.cpp
    ${CMAKE_CURRENT_SOURCE_DIR}/../service/src/storage/lmdb_dbi.cpp
    ${CMAKE_CURRENT_SOURCE_DIR}/../service/src/storage/subdb_identity.cpp
    ${CMAKE_CURRENT_SOURCE_DIR}/../service/src/storage/document_store_lmdb.cpp
    ${CMAKE_CURRENT_SOURCE_DIR}/../service/src/relations/relation_index.cpp
    ${CMAKE_CURRENT_SOURCE_DIR}/../service/src/storage/secondary_index.cpp
    ${CMAKE_CURRENT_SOURCE_DIR}/../service/src/json_parse.cpp
    ${CMAKE_CURRENT_SOURCE_DIR}/../service/src/doc_binary.cpp
)

target_include_directories(test_subdb_identity PRIVATE
    ${CMAKE_CURRENT_SOURCE_DIR}/../service/src
    ${LMDB_INCLUDE_DIR}
    ${yyjson_INCLUDE_DIRS}
)

target_link_libraries(test_subdb_identity PRIVATE ${LMDB_LIBRARY})
target_link_libraries(test_subdb_identity PRIVATE ${yyjson_LIBRARIES})

if(TARGET nlohmann_json::nlohmann_json)
    target_link_libraries(test_subdb_identity PRIVATE nlohmann_json::nlohmann_json)
else()
    target_include_directories(test_subdb_identity PRIVATE ${NLOHMANN_JSON_INCLUDE_DIRS})
endif()

add_test(NAME test_subdb_identity COMMAND test_subdb_identity)

# v2.11.0 T2 — relation reverse index (parent id -> child ids, DUPSORT).
# Only the sources relation_index_{add,remove,child_count,children} actually
# need: the LMDB store itself plus its direct dependencies. No relations/
# relation_manager.cpp here - the storage layer is deliberately free of that
# dependency (see relations/relation_index.hpp).
add_executable(test_relation_index
    test_relation_index.cpp
    ${CMAKE_CURRENT_SOURCE_DIR}/../service/src/storage/lmdb_env.cpp
    ${CMAKE_CURRENT_SOURCE_DIR}/../service/src/storage/lmdb_txn.cpp
    ${CMAKE_CURRENT_SOURCE_DIR}/../service/src/storage/lmdb_dbi.cpp
    ${CMAKE_CURRENT_SOURCE_DIR}/../service/src/storage/subdb_identity.cpp
    ${CMAKE_CURRENT_SOURCE_DIR}/../service/src/storage/document_store_lmdb.cpp
    ${CMAKE_CURRENT_SOURCE_DIR}/../service/src/storage/secondary_index.cpp
    ${CMAKE_CURRENT_SOURCE_DIR}/../service/src/relations/relation_index.cpp
    ${CMAKE_CURRENT_SOURCE_DIR}/../service/src/json_parse.cpp
    ${CMAKE_CURRENT_SOURCE_DIR}/../service/src/doc_binary.cpp
)

target_include_directories(test_relation_index PRIVATE
    ${CMAKE_CURRENT_SOURCE_DIR}/../service/src
    ${LMDB_INCLUDE_DIR}
    ${yyjson_INCLUDE_DIRS}
)

target_link_libraries(test_relation_index PRIVATE ${LMDB_LIBRARY})
target_link_libraries(test_relation_index PRIVATE ${yyjson_LIBRARIES})

if(TARGET nlohmann_json::nlohmann_json)
    target_link_libraries(test_relation_index PRIVATE nlohmann_json::nlohmann_json)
else()
    target_include_directories(test_relation_index PRIVATE ${NLOHMANN_JSON_INCLUDE_DIRS})
endif()

add_test(NAME test_relation_index COMMAND test_relation_index)

# v2.11.0 T3/T4 — relation reverse-index maintenance on child writes (T3),
# plus restrict/no_action delete enforcement (T4). T4 needs the declaration
# registry (RelationManager, backed by a MemoryStore) alongside the LMDB
# reverse index, so unlike test_relation_index this target does pull in
# relations/relation_manager.cpp and memory_store.cpp and their dependencies.
add_executable(test_relation_enforcement
    test_relation_enforcement.cpp
    ${CMAKE_CURRENT_SOURCE_DIR}/../service/src/storage/lmdb_env.cpp
    ${CMAKE_CURRENT_SOURCE_DIR}/../service/src/storage/lmdb_txn.cpp
    ${CMAKE_CURRENT_SOURCE_DIR}/../service/src/storage/lmdb_dbi.cpp
    ${CMAKE_CURRENT_SOURCE_DIR}/../service/src/storage/subdb_identity.cpp
    ${CMAKE_CURRENT_SOURCE_DIR}/../service/src/storage/document_store_lmdb.cpp
    ${CMAKE_CURRENT_SOURCE_DIR}/../service/src/storage/secondary_index.cpp
    ${CMAKE_CURRENT_SOURCE_DIR}/../service/src/relations/relation_index.cpp
    ${CMAKE_CURRENT_SOURCE_DIR}/../service/src/relations/relation_manager.cpp
    ${CMAKE_CURRENT_SOURCE_DIR}/../service/src/relations/relation_enforcement.cpp
    ${CMAKE_CURRENT_SOURCE_DIR}/../service/src/memory_store.cpp
    ${CMAKE_CURRENT_SOURCE_DIR}/../service/src/views/view_manager.cpp
    ${CMAKE_CURRENT_SOURCE_DIR}/../service/src/views/projection.cpp
    ${CMAKE_CURRENT_SOURCE_DIR}/../service/src/config/collection_config_manager.cpp
    ${CMAKE_CURRENT_SOURCE_DIR}/../service/src/persistence/history_store.cpp
    ${CMAKE_CURRENT_SOURCE_DIR}/../service/src/persistence/wal.cpp
    ${CMAKE_CURRENT_SOURCE_DIR}/../service/src/json_parse.cpp
    ${CMAKE_CURRENT_SOURCE_DIR}/../service/src/doc_binary.cpp
)

target_include_directories(test_relation_enforcement PRIVATE
    ${CMAKE_CURRENT_SOURCE_DIR}/../service/src
    ${LMDB_INCLUDE_DIR}
    ${yyjson_INCLUDE_DIRS}
)

target_link_libraries(test_relation_enforcement PRIVATE ${LMDB_LIBRARY})
target_link_libraries(test_relation_enforcement PRIVATE ${yyjson_LIBRARIES})

if(TARGET nlohmann_json::nlohmann_json)
    target_link_libraries(test_relation_enforcement PRIVATE nlohmann_json::nlohmann_json)
else()
    target_include_directories(test_relation_enforcement PRIVATE ${NLOHMANN_JSON_INCLUDE_DIRS})
endif()

if(TARGET spdlog::spdlog)
    target_link_libraries(test_relation_enforcement PRIVATE spdlog::spdlog)
else()
    target_link_libraries(test_relation_enforcement PRIVATE ${SPDLOG_LIBRARIES})
    target_include_directories(test_relation_enforcement PRIVATE ${SPDLOG_INCLUDE_DIRS})
endif()
find_package(Threads REQUIRED)
target_link_libraries(test_relation_enforcement PRIVATE Threads::Threads)

add_test(NAME test_relation_enforcement COMMAND test_relation_enforcement)

# v2.9.0 — secondary index key encoding. Pins the one property that matters:
# an index key comparison must mean the same thing as the scan's comparison,
# because two paths answering one question that disagree return wrong data
# rather than an error.
add_executable(test_secondary_index_keys
    test_secondary_index_keys.cpp
    ${CMAKE_CURRENT_SOURCE_DIR}/../service/src/storage/secondary_index.cpp
    ${CMAKE_CURRENT_SOURCE_DIR}/../service/src/doc_binary.cpp
)

target_include_directories(test_secondary_index_keys PRIVATE
    ${CMAKE_CURRENT_SOURCE_DIR}/../service/src
    ${yyjson_INCLUDE_DIRS}
)

target_link_libraries(test_secondary_index_keys PRIVATE ${yyjson_LIBRARIES})

if(TARGET nlohmann_json::nlohmann_json)
    target_link_libraries(test_secondary_index_keys PRIVATE nlohmann_json::nlohmann_json)
else()
    target_include_directories(test_secondary_index_keys PRIVATE ${NLOHMANN_JSON_INCLUDE_DIRS})
endif()

add_test(NAME test_secondary_index_keys COMMAND test_secondary_index_keys)

# v2.6.0 — project-scoped file storage. Covers cross-project id isolation and
# the per-project deduplicated flag (global dedup would otherwise be a
# cross-project existence oracle).
add_executable(test_file_project_scope
    test_file_project_scope.cpp
    ${CMAKE_CURRENT_SOURCE_DIR}/../service/src/files/file_manager.cpp
)

target_include_directories(test_file_project_scope PRIVATE
    ${CMAKE_CURRENT_SOURCE_DIR}/../service/src
)

find_package(OpenSSL REQUIRED)
target_link_libraries(test_file_project_scope PRIVATE OpenSSL::SSL OpenSSL::Crypto)

if(TARGET nlohmann_json::nlohmann_json)
    target_link_libraries(test_file_project_scope PRIVATE nlohmann_json::nlohmann_json)
else()
    target_include_directories(test_file_project_scope PRIVATE ${NLOHMANN_JSON_INCLUDE_DIRS})
endif()

if(TARGET spdlog::spdlog)
    target_link_libraries(test_file_project_scope PRIVATE spdlog::spdlog)
else()
    target_link_libraries(test_file_project_scope PRIVATE ${SPDLOG_LIBRARIES})
    target_include_directories(test_file_project_scope PRIVATE ${SPDLOG_INCLUDE_DIRS})
endif()

find_package(Threads REQUIRED)
target_link_libraries(test_file_project_scope PRIVATE Threads::Threads)

add_test(NAME test_file_project_scope COMMAND test_file_project_scope)

# v2.8.0 — access policy engine. The critical assertions are the safety ones:
# security off allows everything (so upgrades are inert), enabling is refused
# without an admin policy, the last admin cannot be removed, and audit mode
# evaluates honestly but allows.
add_executable(test_policy_manager
    test_policy_manager.cpp
    ${CMAKE_CURRENT_SOURCE_DIR}/../service/src/security/policy_manager.cpp
    ${CMAKE_CURRENT_SOURCE_DIR}/../service/src/memory_store.cpp
    ${CMAKE_CURRENT_SOURCE_DIR}/../service/src/config/collection_config_manager.cpp
    ${CMAKE_CURRENT_SOURCE_DIR}/../service/src/persistence/history_store.cpp
    ${CMAKE_CURRENT_SOURCE_DIR}/../service/src/persistence/wal.cpp
    ${CMAKE_CURRENT_SOURCE_DIR}/../service/src/json_parse.cpp
    ${CMAKE_CURRENT_SOURCE_DIR}/../service/src/doc_binary.cpp
)

target_include_directories(test_policy_manager PRIVATE
    ${CMAKE_CURRENT_SOURCE_DIR}/../service/src
    ${yyjson_INCLUDE_DIRS}
)

target_link_libraries(test_policy_manager PRIVATE ${yyjson_LIBRARIES})

if(TARGET nlohmann_json::nlohmann_json)
    target_link_libraries(test_policy_manager PRIVATE nlohmann_json::nlohmann_json)
else()
    target_include_directories(test_policy_manager PRIVATE ${NLOHMANN_JSON_INCLUDE_DIRS})
endif()

if(TARGET spdlog::spdlog)
    target_link_libraries(test_policy_manager PRIVATE spdlog::spdlog)
else()
    target_link_libraries(test_policy_manager PRIVATE ${SPDLOG_LIBRARIES})
    target_include_directories(test_policy_manager PRIVATE ${SPDLOG_INCLUDE_DIRS})
endif()

find_package(Threads REQUIRED)
target_link_libraries(test_policy_manager PRIVATE Threads::Threads)

add_test(NAME test_policy_manager COMMAND test_policy_manager)

# v2.8.0 — document TTL across write paths. Pins that update() preserves an
# existing expiry: it used to wipe it, so any TTL'd document that was ever
# updated became permanent.
add_executable(test_document_ttl
    test_document_ttl.cpp
    ${CMAKE_CURRENT_SOURCE_DIR}/../service/src/memory_store.cpp
    ${CMAKE_CURRENT_SOURCE_DIR}/../service/src/config/collection_config_manager.cpp
    ${CMAKE_CURRENT_SOURCE_DIR}/../service/src/persistence/history_store.cpp
    ${CMAKE_CURRENT_SOURCE_DIR}/../service/src/persistence/wal.cpp
    ${CMAKE_CURRENT_SOURCE_DIR}/../service/src/json_parse.cpp
    ${CMAKE_CURRENT_SOURCE_DIR}/../service/src/doc_binary.cpp
)
target_include_directories(test_document_ttl PRIVATE
    ${CMAKE_CURRENT_SOURCE_DIR}/../service/src
    ${yyjson_INCLUDE_DIRS}
)
target_link_libraries(test_document_ttl PRIVATE ${yyjson_LIBRARIES})
if(TARGET nlohmann_json::nlohmann_json)
    target_link_libraries(test_document_ttl PRIVATE nlohmann_json::nlohmann_json)
else()
    target_include_directories(test_document_ttl PRIVATE ${NLOHMANN_JSON_INCLUDE_DIRS})
endif()
if(TARGET spdlog::spdlog)
    target_link_libraries(test_document_ttl PRIVATE spdlog::spdlog)
else()
    target_link_libraries(test_document_ttl PRIVATE ${SPDLOG_LIBRARIES})
    target_include_directories(test_document_ttl PRIVATE ${SPDLOG_INCLUDE_DIRS})
endif()
find_package(Threads REQUIRED)
target_link_libraries(test_document_ttl PRIVATE Threads::Threads)
add_test(NAME test_document_ttl COMMAND test_document_ttl)

# v2.8.0 — ViewManager persistence. test_views only exercises applyProjection()
# in-process, so nothing covered loadFromStore's paging: a bare Query stops at
# 100 views and silently loses the rest.
add_executable(test_view_manager_paging
    test_view_manager_paging.cpp
    ${CMAKE_CURRENT_SOURCE_DIR}/../service/src/views/view_manager.cpp
    ${CMAKE_CURRENT_SOURCE_DIR}/../service/src/views/projection.cpp
    ${CMAKE_CURRENT_SOURCE_DIR}/../service/src/memory_store.cpp
    ${CMAKE_CURRENT_SOURCE_DIR}/../service/src/config/collection_config_manager.cpp
    ${CMAKE_CURRENT_SOURCE_DIR}/../service/src/persistence/history_store.cpp
    ${CMAKE_CURRENT_SOURCE_DIR}/../service/src/persistence/wal.cpp
    ${CMAKE_CURRENT_SOURCE_DIR}/../service/src/json_parse.cpp
    ${CMAKE_CURRENT_SOURCE_DIR}/../service/src/doc_binary.cpp
)
target_include_directories(test_view_manager_paging PRIVATE
    ${CMAKE_CURRENT_SOURCE_DIR}/../service/src
    ${yyjson_INCLUDE_DIRS}
)
target_link_libraries(test_view_manager_paging PRIVATE ${yyjson_LIBRARIES})
if(TARGET nlohmann_json::nlohmann_json)
    target_link_libraries(test_view_manager_paging PRIVATE nlohmann_json::nlohmann_json)
else()
    target_include_directories(test_view_manager_paging PRIVATE ${NLOHMANN_JSON_INCLUDE_DIRS})
endif()
if(TARGET spdlog::spdlog)
    target_link_libraries(test_view_manager_paging PRIVATE spdlog::spdlog)
else()
    target_link_libraries(test_view_manager_paging PRIVATE ${SPDLOG_LIBRARIES})
    target_include_directories(test_view_manager_paging PRIVATE ${SPDLOG_INCLUDE_DIRS})
endif()
find_package(Threads REQUIRED)
target_link_libraries(test_view_manager_paging PRIVATE Threads::Threads)
add_test(NAME test_view_manager_paging COMMAND test_view_manager_paging)

# v2.11.0 T1 — RelationManager: the declaration registry for referential
# integrity relations. Same paging trap as ViewManager/PolicyManager/
# CollectionConfigManager — loadFromStore() must page explicitly.
add_executable(test_relation_manager
    test_relation_manager.cpp
    ${CMAKE_CURRENT_SOURCE_DIR}/../service/src/relations/relation_manager.cpp
    ${CMAKE_CURRENT_SOURCE_DIR}/../service/src/views/view_manager.cpp
    ${CMAKE_CURRENT_SOURCE_DIR}/../service/src/views/projection.cpp
    ${CMAKE_CURRENT_SOURCE_DIR}/../service/src/memory_store.cpp
    ${CMAKE_CURRENT_SOURCE_DIR}/../service/src/config/collection_config_manager.cpp
    ${CMAKE_CURRENT_SOURCE_DIR}/../service/src/persistence/history_store.cpp
    ${CMAKE_CURRENT_SOURCE_DIR}/../service/src/persistence/wal.cpp
    ${CMAKE_CURRENT_SOURCE_DIR}/../service/src/json_parse.cpp
    ${CMAKE_CURRENT_SOURCE_DIR}/../service/src/doc_binary.cpp
)
target_include_directories(test_relation_manager PRIVATE
    ${CMAKE_CURRENT_SOURCE_DIR}/../service/src
    ${yyjson_INCLUDE_DIRS}
)
target_link_libraries(test_relation_manager PRIVATE ${yyjson_LIBRARIES})
if(TARGET nlohmann_json::nlohmann_json)
    target_link_libraries(test_relation_manager PRIVATE nlohmann_json::nlohmann_json)
else()
    target_include_directories(test_relation_manager PRIVATE ${NLOHMANN_JSON_INCLUDE_DIRS})
endif()
if(TARGET spdlog::spdlog)
    target_link_libraries(test_relation_manager PRIVATE spdlog::spdlog)
else()
    target_link_libraries(test_relation_manager PRIVATE ${SPDLOG_LIBRARIES})
    target_include_directories(test_relation_manager PRIVATE ${SPDLOG_INCLUDE_DIRS})
endif()
target_link_libraries(test_relation_manager PRIVATE Threads::Threads)
add_test(NAME test_relation_manager COMMAND test_relation_manager)
