# Cached key files for test runs — avoids repeated RSA keygen.
# These are stored in the build directory and reused across runs.
set(DSSH_TEST_KEY_ENV
	"DSSH_TEST_ED25519_KEY=${CMAKE_CURRENT_BINARY_DIR}/test_ed25519.pem"
	"DSSH_TEST_RSA_KEY=${CMAKE_CURRENT_BINARY_DIR}/test_rsa.pem")

# Prepend project root to source paths (they're relative to ssh/, not test/)
set(DSSH_TEST_LIB_SOURCES "")
foreach(src ${DEUCESSH_SOURCES})
	list(APPEND DSSH_TEST_LIB_SOURCES "${PROJECT_SOURCE_DIR}/${src}")
endforeach()

# Test-instrumented static library (exposes DSSH_TESTABLE functions)
add_library(deucessh_test_lib STATIC ${DSSH_TEST_LIB_SOURCES})
set_property(TARGET deucessh_test_lib PROPERTY C_STANDARD 17)
set_property(TARGET deucessh_test_lib PROPERTY C_STANDARD_REQUIRED ON)
if(DEUCESSH_CRYPTO_BACKEND STREQUAL "Botan")
	set_property(TARGET deucessh_test_lib PROPERTY CXX_STANDARD 20)
	set_property(TARGET deucessh_test_lib PROPERTY CXX_STANDARD_REQUIRED ON)
endif()
target_compile_definitions(deucessh_test_lib PRIVATE DSSH_TESTING
	$<$<STREQUAL:${DEUCESSH_CRYPTO_BACKEND},OpenSSL>:DSSH_CRYPTO_OPENSSL>
	$<$<STREQUAL:${DEUCESSH_CRYPTO_BACKEND},Botan>:DSSH_CRYPTO_BOTAN>)
target_include_directories(deucessh_test_lib
	PUBLIC ${PROJECT_SOURCE_DIR}
	PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
if(DSSH_THREADS_SHIM_INCLUDE)
	target_include_directories(deucessh_test_lib BEFORE
		PUBLIC ${DSSH_THREADS_SHIM_INCLUDE})
endif()
target_include_directories(deucessh_test_lib SYSTEM
	PRIVATE ${DSSH_CRYPTO_INCLUDE})
target_link_libraries(deucessh_test_lib
	INTERFACE ${DSSH_CRYPTO_LIB_INTERFACE} ${THREADS_LINK_LIB}
		${DSSH_PRIVATE_KEY_FILE_LIB})
target_compile_options(deucessh_test_lib PRIVATE
	${DEUCESSH_COMPILE_OPTIONS}
	$<$<OR:$<C_COMPILER_ID:Clang>,$<C_COMPILER_ID:AppleClang>,$<C_COMPILER_ID:GNU>>:
		-Wno-unused-function>)

# Mock I/O library
add_library(dssh_mock_io STATIC mock_io.c)
set_property(TARGET dssh_mock_io PROPERTY C_STANDARD 17)
set_property(TARGET dssh_mock_io PROPERTY C_STANDARD_REQUIRED ON)
target_include_directories(dssh_mock_io PUBLIC
	${PROJECT_SOURCE_DIR}
	${CMAKE_CURRENT_SOURCE_DIR})
target_link_libraries(dssh_mock_io PRIVATE deucessh_test_lib)

# Library-only test allocator (macro-based, doesn't affect OpenSSL)
add_library(dssh_test_allocator STATIC dssh_test_alloc.c)
set_property(TARGET dssh_test_allocator PROPERTY C_STANDARD 17)
set_property(TARGET dssh_test_allocator PROPERTY C_STANDARD_REQUIRED ON)
target_include_directories(dssh_test_allocator PUBLIC
	${CMAKE_CURRENT_SOURCE_DIR})
# Crypto + C11 threads failure injection library
add_library(dssh_test_ossl STATIC dssh_test_ossl.c)
set_property(TARGET dssh_test_ossl PROPERTY C_STANDARD 17)
set_property(TARGET dssh_test_ossl PROPERTY C_STANDARD_REQUIRED ON)
target_include_directories(dssh_test_ossl PUBLIC
	${CMAKE_CURRENT_SOURCE_DIR})
if(DSSH_THREADS_SHIM_INCLUDE)
	target_include_directories(dssh_test_ossl BEFORE
		PRIVATE ${DSSH_THREADS_SHIM_INCLUDE})
endif()
if(DEUCESSH_CRYPTO_BACKEND STREQUAL "OpenSSL")
	target_link_libraries(dssh_test_ossl PRIVATE OpenSSL::Crypto)
	target_compile_definitions(dssh_test_ossl PRIVATE DSSH_CRYPTO_OPENSSL)
elseif(DEUCESSH_CRYPTO_BACKEND STREQUAL "Botan")
	target_compile_definitions(dssh_test_ossl PRIVATE DSSH_CRYPTO_BOTAN)
endif()

# Link allocator and injection into the test library
# so all test executables get the definitions library code references.
target_link_libraries(deucessh_test_lib INTERFACE
	dssh_test_allocator dssh_test_ossl)

# Mock allocator library (--wrap based, for process-wide injection).
# Apple's Mach-O linker does not support the GNU --wrap option.
if(NOT APPLE)
	add_library(dssh_mock_alloc STATIC mock_alloc.c)
	set_property(TARGET dssh_mock_alloc PROPERTY C_STANDARD 17)
	set_property(TARGET dssh_mock_alloc PROPERTY C_STANDARD_REQUIRED ON)
	target_include_directories(dssh_mock_alloc PUBLIC
		${CMAKE_CURRENT_SOURCE_DIR})

	set(DSSH_WRAP_FLAGS
		-Wl,--wrap=malloc,--wrap=calloc,--wrap=realloc,--wrap=free)
endif()

# Helper function for adding test executables
function(dssh_add_test test_name source_file)
	add_executable(dssh_test_${test_name} ${source_file})
	set_property(TARGET dssh_test_${test_name} PROPERTY C_STANDARD 17)
	set_property(TARGET dssh_test_${test_name} PROPERTY C_STANDARD_REQUIRED ON)
	target_include_directories(dssh_test_${test_name} PRIVATE
		${PROJECT_SOURCE_DIR}
		${CMAKE_CURRENT_SOURCE_DIR})
	target_include_directories(dssh_test_${test_name} SYSTEM
		PRIVATE ${DSSH_CRYPTO_INCLUDE})
	target_link_libraries(dssh_test_${test_name} PRIVATE
		deucessh_test_lib ${DSSH_CRYPTO_LIB_INTERFACE} ${THREADS_LINK_LIB})
	target_compile_options(dssh_test_${test_name} PRIVATE ${DEUCESSH_COMPILE_OPTIONS})
	if(DEUCESSH_CRYPTO_BACKEND STREQUAL "OpenSSL")
		target_compile_definitions(dssh_test_${test_name} PRIVATE DSSH_CRYPTO_OPENSSL)
	elseif(DEUCESSH_CRYPTO_BACKEND STREQUAL "Botan")
		target_compile_definitions(dssh_test_${test_name} PRIVATE DSSH_CRYPTO_BOTAN)
	endif()
endfunction()

# Helper: register one executable × 16 KEX/key/MAC variants as CTest entries.
# Each entry runs the full executable (all tests in one process).
macro(dssh_add_variant_tests test_name executable)
	add_test(NAME ${test_name} COMMAND ${executable})
	set_tests_properties(${test_name} PROPERTIES
		ENVIRONMENT "${DSSH_TEST_KEY_ENV}")
	add_test(NAME ${test_name}_hmac256 COMMAND ${executable})
	set_tests_properties(${test_name}_hmac256 PROPERTIES
		ENVIRONMENT "DSSH_TEST_MAC=hmac256;${DSSH_TEST_KEY_ENV}")
	add_test(NAME ${test_name}_dhgex COMMAND ${executable})
	set_tests_properties(${test_name}_dhgex PROPERTIES
		ENVIRONMENT "DSSH_TEST_KEX=dh-gex;${DSSH_TEST_KEY_ENV}")
	add_test(NAME ${test_name}_rsa COMMAND ${executable})
	set_tests_properties(${test_name}_rsa PROPERTIES
		ENVIRONMENT "DSSH_TEST_KEY=rsa;${DSSH_TEST_KEY_ENV}")
	add_test(NAME ${test_name}_rsa512 COMMAND ${executable})
	set_tests_properties(${test_name}_rsa512 PROPERTIES
		ENVIRONMENT "DSSH_TEST_KEY=rsa512;${DSSH_TEST_KEY_ENV}")
	add_test(NAME ${test_name}_dhgex_rsa COMMAND ${executable})
	set_tests_properties(${test_name}_dhgex_rsa PROPERTIES
		ENVIRONMENT "DSSH_TEST_KEX=dh-gex;DSSH_TEST_KEY=rsa;${DSSH_TEST_KEY_ENV}")
	add_test(NAME ${test_name}_dhgex_rsa512 COMMAND ${executable})
	set_tests_properties(${test_name}_dhgex_rsa512 PROPERTIES
		ENVIRONMENT "DSSH_TEST_KEX=dh-gex;DSSH_TEST_KEY=rsa512;${DSSH_TEST_KEY_ENV}")
	add_test(NAME ${test_name}_sntrup COMMAND ${executable})
	set_tests_properties(${test_name}_sntrup PROPERTIES
		ENVIRONMENT "DSSH_TEST_KEX=sntrup;${DSSH_TEST_KEY_ENV}")
	add_test(NAME ${test_name}_sntrup_rsa COMMAND ${executable})
	set_tests_properties(${test_name}_sntrup_rsa PROPERTIES
		ENVIRONMENT "DSSH_TEST_KEX=sntrup;DSSH_TEST_KEY=rsa;${DSSH_TEST_KEY_ENV}")
	add_test(NAME ${test_name}_sntrup_rsa512 COMMAND ${executable})
	set_tests_properties(${test_name}_sntrup_rsa512 PROPERTIES
		ENVIRONMENT "DSSH_TEST_KEX=sntrup;DSSH_TEST_KEY=rsa512;${DSSH_TEST_KEY_ENV}")
	add_test(NAME ${test_name}_mlkem COMMAND ${executable})
	set_tests_properties(${test_name}_mlkem PROPERTIES
		ENVIRONMENT "DSSH_TEST_KEX=mlkem;${DSSH_TEST_KEY_ENV}")
	add_test(NAME ${test_name}_mlkem_rsa COMMAND ${executable})
	set_tests_properties(${test_name}_mlkem_rsa PROPERTIES
		ENVIRONMENT "DSSH_TEST_KEX=mlkem;DSSH_TEST_KEY=rsa;${DSSH_TEST_KEY_ENV}")
	add_test(NAME ${test_name}_mlkem_rsa512 COMMAND ${executable})
	set_tests_properties(${test_name}_mlkem_rsa512 PROPERTIES
		ENVIRONMENT "DSSH_TEST_KEX=mlkem;DSSH_TEST_KEY=rsa512;${DSSH_TEST_KEY_ENV}")
	add_test(NAME ${test_name}_rsa_hmac256 COMMAND ${executable})
	set_tests_properties(${test_name}_rsa_hmac256 PROPERTIES
		ENVIRONMENT "DSSH_TEST_KEY=rsa;DSSH_TEST_MAC=hmac256;${DSSH_TEST_KEY_ENV}")
	add_test(NAME ${test_name}_dhgex_hmac256 COMMAND ${executable})
	set_tests_properties(${test_name}_dhgex_hmac256 PROPERTIES
		ENVIRONMENT "DSSH_TEST_KEX=dh-gex;DSSH_TEST_MAC=hmac256;${DSSH_TEST_KEY_ENV}")
	add_test(NAME ${test_name}_sntrup_hmac256 COMMAND ${executable})
	set_tests_properties(${test_name}_sntrup_hmac256 PROPERTIES
		ENVIRONMENT "DSSH_TEST_KEX=sntrup;DSSH_TEST_MAC=hmac256;${DSSH_TEST_KEY_ENV}")
endmacro()

# Helper: register one test per name × KEX/key/MAC variants as separate
# CTest entries.  Each entry runs one test in its own process.
# Use for test suites where per-test parallelism matters (e.g. auth
# with expensive sntrup handshakes).
macro(dssh_add_individual_tests executable prefix test_list)
	foreach(t ${${test_list}})
		add_test(NAME ${prefix}_${t}
			COMMAND ${executable} --exact ${t})
		set_tests_properties(${prefix}_${t} PROPERTIES
			ENVIRONMENT "${DSSH_TEST_KEY_ENV}")
		add_test(NAME ${prefix}_${t}_dhgex
			COMMAND ${executable} --exact ${t})
		set_tests_properties(${prefix}_${t}_dhgex PROPERTIES
			ENVIRONMENT "DSSH_TEST_KEX=dh-gex;${DSSH_TEST_KEY_ENV}")
		add_test(NAME ${prefix}_${t}_rsa
			COMMAND ${executable} --exact ${t})
		set_tests_properties(${prefix}_${t}_rsa PROPERTIES
			ENVIRONMENT "DSSH_TEST_KEY=rsa;${DSSH_TEST_KEY_ENV}")
		add_test(NAME ${prefix}_${t}_rsa512
			COMMAND ${executable} --exact ${t})
		set_tests_properties(${prefix}_${t}_rsa512 PROPERTIES
			ENVIRONMENT "DSSH_TEST_KEY=rsa512;${DSSH_TEST_KEY_ENV}")
		add_test(NAME ${prefix}_${t}_dhgex_rsa
			COMMAND ${executable} --exact ${t})
		set_tests_properties(${prefix}_${t}_dhgex_rsa PROPERTIES
			ENVIRONMENT "DSSH_TEST_KEX=dh-gex;DSSH_TEST_KEY=rsa;${DSSH_TEST_KEY_ENV}")
		add_test(NAME ${prefix}_${t}_dhgex_rsa512
			COMMAND ${executable} --exact ${t})
		set_tests_properties(${prefix}_${t}_dhgex_rsa512 PROPERTIES
			ENVIRONMENT "DSSH_TEST_KEX=dh-gex;DSSH_TEST_KEY=rsa512;${DSSH_TEST_KEY_ENV}")
		add_test(NAME ${prefix}_${t}_sntrup
			COMMAND ${executable} --exact ${t})
		set_tests_properties(${prefix}_${t}_sntrup PROPERTIES
			ENVIRONMENT "DSSH_TEST_KEX=sntrup;${DSSH_TEST_KEY_ENV}")
		add_test(NAME ${prefix}_${t}_sntrup_rsa
			COMMAND ${executable} --exact ${t})
		set_tests_properties(${prefix}_${t}_sntrup_rsa PROPERTIES
			ENVIRONMENT "DSSH_TEST_KEX=sntrup;DSSH_TEST_KEY=rsa;${DSSH_TEST_KEY_ENV}")
		add_test(NAME ${prefix}_${t}_sntrup_rsa512
			COMMAND ${executable} --exact ${t})
		set_tests_properties(${prefix}_${t}_sntrup_rsa512 PROPERTIES
			ENVIRONMENT "DSSH_TEST_KEX=sntrup;DSSH_TEST_KEY=rsa512;${DSSH_TEST_KEY_ENV}")
		add_test(NAME ${prefix}_${t}_mlkem
			COMMAND ${executable} --exact ${t})
		set_tests_properties(${prefix}_${t}_mlkem PROPERTIES
			ENVIRONMENT "DSSH_TEST_KEX=mlkem;${DSSH_TEST_KEY_ENV}")
		add_test(NAME ${prefix}_${t}_mlkem_rsa
			COMMAND ${executable} --exact ${t})
		set_tests_properties(${prefix}_${t}_mlkem_rsa PROPERTIES
			ENVIRONMENT "DSSH_TEST_KEX=mlkem;DSSH_TEST_KEY=rsa;${DSSH_TEST_KEY_ENV}")
		add_test(NAME ${prefix}_${t}_mlkem_rsa512
			COMMAND ${executable} --exact ${t})
		set_tests_properties(${prefix}_${t}_mlkem_rsa512 PROPERTIES
			ENVIRONMENT "DSSH_TEST_KEX=mlkem;DSSH_TEST_KEY=rsa512;${DSSH_TEST_KEY_ENV}")
		add_test(NAME ${prefix}_${t}_hmac256
			COMMAND ${executable} --exact ${t})
		set_tests_properties(${prefix}_${t}_hmac256 PROPERTIES
			ENVIRONMENT "DSSH_TEST_MAC=hmac256;${DSSH_TEST_KEY_ENV}")
		add_test(NAME ${prefix}_${t}_rsa_hmac256
			COMMAND ${executable} --exact ${t})
		set_tests_properties(${prefix}_${t}_rsa_hmac256 PROPERTIES
			ENVIRONMENT "DSSH_TEST_KEY=rsa;DSSH_TEST_MAC=hmac256;${DSSH_TEST_KEY_ENV}")
		add_test(NAME ${prefix}_${t}_dhgex_hmac256
			COMMAND ${executable} --exact ${t})
		set_tests_properties(${prefix}_${t}_dhgex_hmac256 PROPERTIES
			ENVIRONMENT "DSSH_TEST_KEX=dh-gex;DSSH_TEST_MAC=hmac256;${DSSH_TEST_KEY_ENV}")
		add_test(NAME ${prefix}_${t}_sntrup_hmac256
			COMMAND ${executable} --exact ${t})
		set_tests_properties(${prefix}_${t}_sntrup_hmac256 PROPERTIES
			ENVIRONMENT "DSSH_TEST_KEX=sntrup;DSSH_TEST_MAC=hmac256;${DSSH_TEST_KEY_ENV}")
	endforeach()
endmacro()

# ================================================================
# Tier 1: Unit tests
# ================================================================

dssh_add_test(chan test_chan.c)
add_test(NAME dssh_unit_chan COMMAND dssh_test_chan)

dssh_add_test(crypto test_crypto.c)
add_test(NAME dssh_unit_crypto COMMAND dssh_test_crypto)

# Algorithm validation tests — reference helpers are backend-conditional
dssh_add_test(algo_enc test_algo_enc.c)
if(DEUCESSH_CRYPTO_BACKEND STREQUAL "OpenSSL")
	target_link_libraries(dssh_test_algo_enc PRIVATE OpenSSL::Crypto)
elseif(DEUCESSH_CRYPTO_BACKEND STREQUAL "Botan")
	target_link_libraries(dssh_test_algo_enc PRIVATE PkgConfig::BOTAN3)
endif()
add_test(NAME dssh_unit_algo_enc COMMAND dssh_test_algo_enc)

dssh_add_test(algo_mac test_algo_mac.c)
if(DEUCESSH_CRYPTO_BACKEND STREQUAL "OpenSSL")
	target_link_libraries(dssh_test_algo_mac PRIVATE OpenSSL::Crypto)
elseif(DEUCESSH_CRYPTO_BACKEND STREQUAL "Botan")
	target_link_libraries(dssh_test_algo_mac PRIVATE PkgConfig::BOTAN3)
endif()
add_test(NAME dssh_unit_algo_mac COMMAND dssh_test_algo_mac)

dssh_add_test(algo_key test_algo_key.c)
if(DEUCESSH_CRYPTO_BACKEND STREQUAL "OpenSSL")
	target_link_libraries(dssh_test_algo_key PRIVATE OpenSSL::Crypto)
elseif(DEUCESSH_CRYPTO_BACKEND STREQUAL "Botan")
	target_link_libraries(dssh_test_algo_key PRIVATE PkgConfig::BOTAN3)
endif()
add_test(NAME dssh_unit_algo_key COMMAND dssh_test_algo_key)
set_tests_properties(dssh_unit_algo_key PROPERTIES COST 20
	ENVIRONMENT "${DSSH_TEST_KEY_ENV}")
add_test(NAME dssh_targeted_dhgex_null_ka
	COMMAND dssh_test_algo_key dhgex_handler_null_ka)
add_test(NAME dssh_targeted_c25519_null_ka
	COMMAND dssh_test_algo_key c25519_handler_null_ka)
add_test(NAME dssh_targeted_sntrup761_null_ka
	COMMAND dssh_test_algo_key sntrup761_handler_null_ka)
add_test(NAME dssh_targeted_mlkem768_null_ka
	COMMAND dssh_test_algo_key mlkem768_handler_null_ka)

# Botan-specific C++ unit tests
if(DEUCESSH_CRYPTO_BACKEND STREQUAL "Botan")
	add_executable(dssh_test_botan_algo_key test_botan_algo_key.cpp)
	set_property(TARGET dssh_test_botan_algo_key PROPERTY CXX_STANDARD 20)
	set_property(TARGET dssh_test_botan_algo_key PROPERTY CXX_STANDARD_REQUIRED ON)
	target_include_directories(dssh_test_botan_algo_key PRIVATE
		${PROJECT_SOURCE_DIR} ${CMAKE_CURRENT_SOURCE_DIR})
	target_include_directories(dssh_test_botan_algo_key SYSTEM
		PRIVATE ${DSSH_CRYPTO_INCLUDE})
	target_link_libraries(dssh_test_botan_algo_key PRIVATE
		deucessh_test_lib PkgConfig::BOTAN3 ${THREADS_LINK_LIB})
	target_compile_options(dssh_test_botan_algo_key PRIVATE ${DEUCESSH_COMPILE_OPTIONS})
	target_compile_definitions(dssh_test_botan_algo_key PRIVATE DSSH_CRYPTO_BOTAN)
	add_test(NAME dssh_unit_botan_algo_key COMMAND dssh_test_botan_algo_key)
	set_tests_properties(dssh_unit_botan_algo_key PROPERTIES COST 20
		ENVIRONMENT "${DSSH_TEST_KEY_ENV}")

	add_executable(dssh_test_botan_transport test_botan_transport.cpp)
	set_property(TARGET dssh_test_botan_transport PROPERTY CXX_STANDARD 20)
	set_property(TARGET dssh_test_botan_transport PROPERTY CXX_STANDARD_REQUIRED ON)
	target_include_directories(dssh_test_botan_transport PRIVATE
		${PROJECT_SOURCE_DIR} ${CMAKE_CURRENT_SOURCE_DIR})
	target_include_directories(dssh_test_botan_transport SYSTEM
		PRIVATE ${DSSH_CRYPTO_INCLUDE})
	target_link_libraries(dssh_test_botan_transport PRIVATE
		deucessh_test_lib PkgConfig::BOTAN3 ${THREADS_LINK_LIB})
	target_compile_options(dssh_test_botan_transport PRIVATE ${DEUCESSH_COMPILE_OPTIONS})
	target_compile_definitions(dssh_test_botan_transport PRIVATE DSSH_CRYPTO_BOTAN)
	add_test(NAME dssh_unit_botan_transport COMMAND dssh_test_botan_transport)
endif()

# ================================================================
# Tier 2: Layer tests (need mock I/O) — one process per variant
# ================================================================

dssh_add_test(transport test_transport.c)
target_link_libraries(dssh_test_transport PRIVATE dssh_mock_io)
dssh_add_variant_tests(dssh_transport dssh_test_transport)

dssh_add_test(auth test_auth.c)
target_link_libraries(dssh_test_auth PRIVATE dssh_mock_io)
set(AUTH_TESTS
	parse/prefix_valid parse/prefix_empty
	parse/prefix_no_service parse/prefix_trunc_service
	parse/prefix_trunc_username parse/prefix_no_method
	parse/prefix_trunc_method
	auth/password/success auth/password/failure
	auth/password/wrong_user auth/password/partial
	auth/password/empty auth/password/long
	auth/server/none_accept auth/server/none_reject
	auth/server/password_accept auth/server/password_reject
	auth/server/publickey_accept auth/server/publickey_reject
	auth/server/no_callbacks auth/server/username_captured
	auth/roundtrip/password auth/roundtrip/password_reject
	auth/roundtrip/publickey auth/roundtrip/get_methods
	auth/roundtrip/kbi auth/roundtrip/multiple_attempts
	auth/banner_delivered auth/banner_no_lang
	auth/server/password_change auth/server/publickey_probe
	auth/server/unknown_method auth/server/unexpected_message
	auth/kbi/zero_prompts auth/kbi/failure_response
	auth/kbi/callback_abort auth/kbi/empty_response
	auth/kbi/unexpected_message auth/kbi/banner_before_info
	auth/kbi/truncated_name_header auth/kbi/truncated_name_data
	auth/kbi/truncated_instr_header auth/kbi/truncated_instr_data
	auth/kbi/truncated_lang_header auth/kbi/truncated_lang_data
	auth/kbi/truncated_num_prompts
	auth/kbi/truncated_prompt_header auth/kbi/truncated_prompt_data
	auth/banner_truncated
	auth/get_methods_none_accepted
	auth/get_methods_fail_trunc auth/get_methods_fail_len
	auth/get_methods_fail_ctrl auth/get_methods_unexpected
	auth/get_methods_del_char auth/get_methods_small_buf
	auth/get_methods_zero_buf
	auth/server/parse_empty auth/server/parse_long_username
	auth/server/parse_no_method auth/server/parse_no_service
	auth/server/parse_not_svc_req
	auth/server/parse_trunc_method auth/server/parse_trunc_service
	auth/server/parse_trunc_user
	auth/server/parse_pw_no_bool auth/server/parse_pw_no_pass
	auth/server/parse_pw_trunc_pw
	auth/server/parse_pw_chg_no_new auth/server/parse_pw_chg_trunc
	auth/server/parse_pk_no_algo auth/server/parse_pk_no_blob
	auth/server/parse_pk_no_has_sig auth/server/parse_pk_no_sig
	auth/server/short_svc_req auth/server/tiny_svc_req
	auth/server/method_4_not_none
	auth/server/method_8_not_password
	auth/server/method_9_not_publickey
	auth/server/kbi_roundtrip
	auth/server/kbi_failure
	auth/server/kbi_multi_round
	auth/server/banner
	auth/server/set_banner_null
	auth/server/kbi_bad_response
	auth/server/kbi_trunc_response
	auth/server/kbi_trunc_lang
	auth/server/kbi_no_callback
	auth/server/kbi_with_banner
	auth/server/no_password_cb auth/server/no_publickey_cb
	auth/server/pk_probe_rejected auth/server/pk_unknown_algo
	auth/server/pk_verify_bad_sig
	auth/server/pk_sig_truncated
	auth/server/pk_sig_trunc_algo auth/server/pk_sig_trunc_blob
	auth/server/pw_change_no_cb auth/server/pw_change_rejected
	auth/server/changepw_null_prompt
	auth/server/changepw_null_prompt_chg
	auth/server/long_algo_name
	auth/server/null_username_out
	auth/server/small_username_buffer
	auth/server_send_fail/svc_accept
	auth/server_send_fail/none_success auth/server_send_fail/none_failure
	auth/server_send_fail/pw_success auth/server_send_fail/pw_failure
	auth/server_send_fail/pw_no_cb auth/server_send_fail/pw_changereq
	auth/server_send_fail/pk_success auth/server_send_fail/pk_ok
	auth/server_send_fail/pk_no_cb auth/server_send_fail/pk_unknown
	auth/server_send_fail/pk_bad_sig auth/server_send_fail/pk_reject_sig
	auth/server_send_fail/pk_probe_rej
	auth/server_send_fail/unknown_meth
	auth/server_send_fail/kbi_success auth/server_send_fail/kbi_failure
	auth/server_send_fail/kbi_info_req auth/server_send_fail/kbi_no_cb
	auth/server_send_fail/chg_success auth/server_send_fail/chg_failure
	auth/server_send_fail/chg_changereq
	auth/pw_changereq_no_cb auth/pw_changereq_loop
	auth/pw_changereq_no_lang
	auth/pw_changereq_trunc_prompt auth/pw_changereq_trunc_lang
	auth/pw_changereq_trunc_lang_data auth/pw_changereq_trunc_data
	auth/pw_unexpected_msg
	auth/service_accept_unexpected
	auth/client/svc_req_send_fail auth/client/pw_changereq_cb_error
	auth/client/pw_changereq_send_fail
	auth/client/pk_no_key auth/client/pk_pubkey_fail
	auth/client/pk_sign_fail auth/client/pk_send_fail
	auth/client/pk_recv_fail auth/client/pk_banner
	auth/client/get_methods_send_fail
	auth/client/kbi_send_fail auth/client/kbi_recv_fail
	direct/wrong_first_msg direct/short_svc_request
	direct/svc_slen_overflow direct/bad_prefix_in_loop
	direct/long_username direct/null_username_out_len
	direct/pw_no_bool direct/pw_no_pw_len direct/pw_overflow
	direct/pw_change_no_new direct/pw_change_overflow
	direct/pk_no_algo_len direct/pk_algo_overflow
	direct/pk_no_hassig direct/pk_no_pk_len direct/pk_pk_overflow
	direct/pk_sig_no_sig_len direct/pk_sig_overflow
	direct/pk_sig_unknown_algo
	direct/pk_verify_alloc
	dclient/wrong_svc_reply dclient/get_methods_unexpected
	dclient/get_methods_trunc_payload
	dclient/get_methods_ctrl_char dclient/get_methods_del
	dclient/changereq_no_cb dclient/changereq_trunc
	dclient/changereq_no_lang
	dclient/changereq_prompt_overflow
	dclient/changereq_lang_overflow
	dclient/get_methods_multi_banner
	dclient/changereq_cb_error
	dclient/pk_no_sign dclient/pk_alloc_fail
	dclient/kbi_alloc_fail dclient/kbi_calloc_chain
	dclient/kbi_resp_alloc
	auth/server/password_disconnect
	auth/server/none_disconnect
	auth/server/pk_probe_disconnect
	auth/server/pk_verify_disconnect
	auth/server/kbi_disconnect
	auth/server/passwd_change_disconnect
	direct/kbi_wrong_resp_type direct/kbi_resp_parse_error
	dclient/pk_no_key
	dclient/pk_banner_in_response dclient/pk_banner_parse_error
	dclient/kbi_resp_overflow dclient/kbi_resp_size_overflow
	direct/kbi_trunc_lang_hdr direct/kbi_trunc_lang_data
	direct/kbi_trunc_submethods direct/kbi_trunc_submethods_data
	auth/server/kbi_trunc_lang_hdr auth/server/kbi_trunc_submethods
	auth/server/kbi_resp_alloc auth/server/kbi_trunc_resp_body
	auth/server/kbi_excess_responses auth/client/kbi_too_many_prompts
)
dssh_add_individual_tests(dssh_test_auth dssh_auth AUTH_TESTS)

dssh_add_test(conn test_conn.c)
target_link_libraries(dssh_test_conn PRIVATE dssh_mock_io)
set(CONN_TESTS
	test_session_start test_session_stop test_session_start_stop
	test_open_exec test_open_exec_command test_open_exec_rejected
	test_open_shell test_open_subsystem
	test_session_write_read test_session_read_write_reverse
	test_session_write_ext test_session_read_ext
	test_session_poll_read test_session_poll_timeout
	test_session_large_write test_session_multiple_writes
	test_subsys_write_read test_subsys_multiple_writes
	test_subsys_poll test_subsys_large_write
	test_send_signal test_signal_after_data test_window_change
	test_session_close_exit_code test_channel_close_subsys
	test_eof_detection test_eof_half_close test_read_nomore_vs_eof
	test_graceful_disconnect
	test_two_channels test_accept_reject test_channel_independence
	test_concurrent_write_read test_terminate_during_io
	test_connection_drop_during_read test_connection_drop_during_write
	test_cleanup_after_drop
	test_session_start_twice test_accept_timeout_zero
	test_accept_timeout_short test_session_poll_timeout_zero
	test_auto_reject_x11 test_auto_reject_forwarded_tcpip
	test_auto_reject_direct_tcpip test_auto_reject_session_server
	test_window_adjust_from_peer test_data_after_eof
	test_truncated_channel_data test_ext_data_after_eof
	test_truncated_ext_data test_channel_request_want_reply
	test_data_exceeds_window
	test_session_poll_write test_session_write_after_eof
	test_session_write_ext_after_eof test_subsys_write_after_close
	test_subsys_poll_write test_subsys_poll_timeout_zero
	test_read_event_empty test_signal_interleave_read
	test_signal_interleave_read_ext test_truncated_channel_open
	test_truncated_channel_request test_demux_short_payload
	test_session_poll_read_empty test_session_write_window_zero
	test_session_write_ext_window_zero test_accept_timeout_negative
	test_setup_exec_rejected_by_callback test_truncated_open_confirmation
	test_open_conf_unknown_channel test_channel_success_no_request
	test_double_eof_close test_data_dlen_exceeds_payload
	test_demux_chan_type_zero test_send_eof_already_sent
	test_send_close_already_sent test_replenish_after_eof
	test_replenish_low_window test_replenish_queued_accounting
	test_zc_consumption_clamped test_window_underflow_to_zero
	test_window_add_overflow
	test_chan_write_not_open test_chan_write_close_recv
	test_subsystem_roundtrip test_accept_zc test_close_during_zc_callback
	test_dispatch_sync_failures test_detach_sync_failures
	test_session_poll_nsec_overflow test_accept_nsec_overflow
	test_demux_data_truncation test_replenish_caps_buf_space
	rx_truncation/default rx_truncation/clips_to_window
	rx_bypass/pre_setup rx_bypass/disengages_post_setup
	early_data/type_lock_subsystem
	poll/terminate_surfaces_data poll/terminate_no_event_bit
	null/chan_read null/chan_write null/chan_poll null/chan_close
	null/chan_send_signal null/chan_send_window_change
	alloc/open_exec_sweep alloc/open_shell_sweep
	alloc/open_subsystem_sweep alloc/server_accept_sweep
	subsys/write_zero
	test_data_after_close test_ext_data_after_close
	test_replenish_after_close test_truncated_window_adjust
	test_poll_deadline_not_reset
	chanreq/parse_valid chanreq/parse_empty_type chanreq/parse_no_data
	chanreq/parse_truncated_type_len chanreq/parse_truncated_type
	chanreq/parse_truncated_want_reply
	zc/open_client zc/cancel null/zc_open
	event/session_set_cb event/session_set_cb_toolate event/chan_set_cb
	event/session_set_max event/session_set_max_toolate event/chan_set_max
	pty/get_pty env/roundtrip
)
dssh_add_individual_tests(dssh_test_conn dssh_conn CONN_TESTS)

# ================================================================
# Tier 1b: Alloc failure tests (need mock allocator + mock I/O)
# ossl_iterate tests are OpenSSL-only (guarded with #ifdef in source)
# The process-wide allocator injection requires the GNU --wrap linker
# option, so these tests are not available with Apple's Mach-O linker.
# ================================================================

if(NOT APPLE)
dssh_add_test(alloc test_alloc.c)
target_link_libraries(dssh_test_alloc PRIVATE dssh_mock_io dssh_mock_alloc)
target_link_options(dssh_test_alloc PRIVATE ${DSSH_WRAP_FLAGS})
add_test(NAME dssh_unit_alloc COMMAND dssh_test_alloc)
set_tests_properties(dssh_unit_alloc PROPERTIES
	ENVIRONMENT "${DSSH_TEST_KEY_ENV}")
add_test(NAME dssh_unit_alloc_dhgex COMMAND dssh_test_alloc)
set_tests_properties(dssh_unit_alloc_dhgex PROPERTIES
	ENVIRONMENT "DSSH_TEST_KEX=dh-gex;${DSSH_TEST_KEY_ENV}")
add_test(NAME dssh_unit_alloc_rsa COMMAND dssh_test_alloc)
set_tests_properties(dssh_unit_alloc_rsa PROPERTIES
	ENVIRONMENT "DSSH_TEST_KEY=rsa;${DSSH_TEST_KEY_ENV}")
add_test(NAME dssh_unit_alloc_dhgex_rsa COMMAND dssh_test_alloc)
set_tests_properties(dssh_unit_alloc_dhgex_rsa PROPERTIES
	ENVIRONMENT "DSSH_TEST_KEX=dh-gex;DSSH_TEST_KEY=rsa;${DSSH_TEST_KEY_ENV}")

add_test(NAME dssh_unit_alloc_sntrup COMMAND dssh_test_alloc)
set_tests_properties(dssh_unit_alloc_sntrup PROPERTIES COST 6
	ENVIRONMENT "DSSH_TEST_KEX=sntrup;${DSSH_TEST_KEY_ENV}")
add_test(NAME dssh_unit_alloc_sntrup_rsa COMMAND dssh_test_alloc)
set_tests_properties(dssh_unit_alloc_sntrup_rsa PROPERTIES COST 7
	ENVIRONMENT "DSSH_TEST_KEX=sntrup;DSSH_TEST_KEY=rsa;${DSSH_TEST_KEY_ENV}")

add_test(NAME dssh_unit_alloc_mlkem COMMAND dssh_test_alloc)
set_tests_properties(dssh_unit_alloc_mlkem PROPERTIES
	ENVIRONMENT "DSSH_TEST_KEX=mlkem;${DSSH_TEST_KEY_ENV}")
add_test(NAME dssh_unit_alloc_mlkem_rsa COMMAND dssh_test_alloc)
set_tests_properties(dssh_unit_alloc_mlkem_rsa PROPERTIES
	ENVIRONMENT "DSSH_TEST_KEX=mlkem;DSSH_TEST_KEY=rsa;${DSSH_TEST_KEY_ENV}")

add_test(NAME dssh_unit_alloc_rsa512 COMMAND dssh_test_alloc)
set_tests_properties(dssh_unit_alloc_rsa512 PROPERTIES
	ENVIRONMENT "DSSH_TEST_KEY=rsa512;${DSSH_TEST_KEY_ENV}")
add_test(NAME dssh_unit_alloc_dhgex_rsa512 COMMAND dssh_test_alloc)
set_tests_properties(dssh_unit_alloc_dhgex_rsa512 PROPERTIES
	ENVIRONMENT "DSSH_TEST_KEX=dh-gex;DSSH_TEST_KEY=rsa512;${DSSH_TEST_KEY_ENV}")

# Targeted coverage tests — run in their own process for clean state
add_test(NAME dssh_targeted_c25519_server_trunc
	COMMAND dssh_test_alloc c25519/server_truncated_init)
set_tests_properties(dssh_targeted_c25519_server_trunc PROPERTIES
	ENVIRONMENT "${DSSH_TEST_KEY_ENV}")
add_test(NAME dssh_targeted_c25519_client_bad_qs
	COMMAND dssh_test_alloc c25519/client_bad_qs_length)
set_tests_properties(dssh_targeted_c25519_client_bad_qs PROPERTIES
	ENVIRONMENT "${DSSH_TEST_KEY_ENV}")
if(DEUCESSH_CRYPTO_BACKEND STREQUAL "OpenSSL")
add_test(NAME dssh_targeted_dhgex_bn_rand_fail
	COMMAND dssh_test_alloc dhgex/server_bn_rand_fail)
set_tests_properties(dssh_targeted_dhgex_bn_rand_fail PROPERTIES
	ENVIRONMENT "DSSH_TEST_KEX=dh-gex;${DSSH_TEST_KEY_ENV}")
endif()
endif()

# ================================================================
# Test algorithm modules (enc/mac with failure injection)
# ================================================================

add_library(dssh_test_algos STATIC test_enc.c test_mac.c)
set_property(TARGET dssh_test_algos PROPERTY C_STANDARD 17)
set_property(TARGET dssh_test_algos PROPERTY C_STANDARD_REQUIRED ON)
target_include_directories(dssh_test_algos PUBLIC
	${PROJECT_SOURCE_DIR}
	${CMAKE_CURRENT_SOURCE_DIR})
target_link_libraries(dssh_test_algos PRIVATE deucessh_test_lib)

# ================================================================
# Tier 2b: Transport error tests (need test enc/mac + mock I/O)
# ================================================================

dssh_add_test(transport_errors test_transport_errors.c)
target_link_libraries(dssh_test_transport_errors PRIVATE
	dssh_mock_io dssh_test_algos)
set(TRANSPORT_ERROR_TESTS
	test_modules/roundtrip
	send/encrypt_error send/mac_error send/mac_overflow_rejected
	send/sensitive_success send/sensitive_gather_success
	send/sensitive_encrypt_error send/sensitive_too_long
	send/channel_slot_drain_fatal send/channel_slot_gather_fatal
	recv/decrypt_error_first_block recv/decrypt_error_remaining
	recv/mac_generate_error recv/mac_mismatch recv/mac_too_large
	newkeys/enc_init_failure newkeys/mac_init_failure
)
foreach(t ${TRANSPORT_ERROR_TESTS})
	add_test(NAME dssh_transport_errors_${t}
		COMMAND dssh_test_transport_errors ${t})
	set_tests_properties(dssh_transport_errors_${t} PROPERTIES
		ENVIRONMENT "${DSSH_TEST_KEY_ENV}")
endforeach()
set_tests_properties(
	dssh_transport_errors_send/channel_slot_drain_fatal
	dssh_transport_errors_send/channel_slot_gather_fatal
	PROPERTIES TIMEOUT 15)

# ================================================================
# Tier 2c: Thread error tests (lock/unlock/condvar failure injection)
# ================================================================

dssh_add_test(thread_errors test_thread_errors.c)
target_link_libraries(dssh_test_thread_errors PRIVATE dssh_mock_io)
dssh_add_variant_tests(dssh_thrd dssh_test_thread_errors)

# ================================================================
# Tier 3a: Self-test (integration) — one process per case and variant
# ================================================================

dssh_add_test(selftest test_selftest.c)
set(SELFTEST_TESTS
	test_self_handshake_curve25519
	test_self_auth_password
	test_self_exec_echo
	test_self_window_adjust_race
	test_self_exec_exit_code
	test_self_shell_echo
	test_self_shell_large_data
	test_self_signal
	test_self_window_change
	test_self_multiple_channels
	test_self_graceful_disconnect
	test_self_eof_half_close
	test_self_rekey_during_data
	test_self_rekey_manual
	test_self_rekey_preserves_channels
	test_self_rekey_inflight_data
	test_self_connection_drop
	test_null_session_api
	test_log_api
	test_set_timeout
	test_set_terminate_cb
	test_open_channel_timeout
	test_setup_recv_timeout
	test_rekey_send_timeout
	test_self_chan_exec_echo
	test_self_chan_shell_pty
	test_self_chan_accept_exec
	test_self_chan_accept_shell_pty
	test_self_chan_event_exit_status
	test_self_chan_read_peek
	test_self_chan_shutwr_then_close
	test_self_chan_close_exit_code
	test_self_chan_close_no_exit_code
	test_self_chan_send_break
	test_self_chan_getters_after_accept
	test_self_chan_accept_env_reject
	test_self_io_callback_contract
)
dssh_add_individual_tests(dssh_test_selftest dssh_self SELFTEST_TESTS)

# ================================================================
# Tier 3b: Asymmetric MAC negotiation (fork-based, separate registries)
# ================================================================

dssh_add_test(asymmetric_mac test_asymmetric_mac.c)
add_test(NAME dssh_asymmetric_mac COMMAND dssh_test_asymmetric_mac)

# ================================================================
# Tier 3c/3d: Optional interop tests
# ================================================================

if(DEUCESSH_TEST_OPENSSH)
	dssh_add_test(openssh_server test_openssh_server.c)
	dssh_add_test(openssh_client test_openssh_client.c)
	add_test(NAME dssh_integration_openssh
		COMMAND ${PROJECT_SOURCE_DIR}/test/test_openssh_interop.sh
			${CMAKE_CURRENT_BINARY_DIR}/dssh_test_openssh_server
			${CMAKE_CURRENT_BINARY_DIR}/dssh_test_openssh_client)
endif()

if(DEUCESSH_TEST_SYNCHRONET)
	dssh_add_test(synchronet test_synchronet.c)
	add_test(NAME dssh_integration_synchronet COMMAND dssh_test_synchronet)
endif()
