libchat/nim-bindings/tests/test_all_endpoints.nim

259 lines
9.0 KiB
Nim
Raw Normal View History

# Comprehensive test for all FFI procs declared in bindings.nim.
#
# Design intent: By importing `bindings` directly and calling every importc
# proc at least once, the linker is forced to include ALL symbol references.
# This prevents link-time optimizations from stripping unused symbols and
# catches both link-time crashes (missing symbols) and runtime crashes
# (wrong ABI, segfaults on use).
import bindings
# ---------------------------------------------------------------------------
# Assertion helper
# ---------------------------------------------------------------------------
proc check(cond: bool, msg: string) =
if not cond:
echo "FAIL: ", msg
quit(1)
echo "OK: ", msg
# ---------------------------------------------------------------------------
# Section 1: Helper proc coverage
# ---------------------------------------------------------------------------
proc testHelperProcs() =
echo "\n--- testHelperProcs ---"
# toSlice(string) — non-empty and empty branches
let s = "hello"
let sl = toSlice(s)
check(sl.len == 5, "toSlice(string): correct len")
check(sl.`ptr` != nil, "toSlice(non-empty string): non-nil ptr")
let emptySl = toSlice("")
check(emptySl.len == 0, "toSlice(empty string): len == 0")
check(emptySl.`ptr` == nil, "toSlice(empty string): ptr == nil")
# toSlice(seq[byte]) — non-empty and empty branches
let b: seq[byte] = @[0x61'u8, 0x62'u8, 0x63'u8]
let bSl = toSlice(b)
check(bSl.len == 3, "toSlice(seq[byte]): correct len")
check(bSl.`ptr` != nil, "toSlice(non-empty seq[byte]): non-nil ptr")
let emptyBSl = toSlice(newSeq[byte](0))
check(emptyBSl.len == 0, "toSlice(empty seq[byte]): len == 0")
check(emptyBSl.`ptr` == nil, "toSlice(empty seq[byte]): ptr == nil")
# toReprCString(string) and $(ReprCString) round-trip
let name = "testname"
let rcs = toReprCString(name)
check(rcs.len == csize_t(name.len), "toReprCString: correct len")
check(rcs.cap == 0, "toReprCString: cap == 0 (prevents Rust dealloc of Nim memory)")
check(rcs.`ptr` != nil, "toReprCString: non-nil ptr")
check($rcs == name, "$(ReprCString): round-trips to original string")
let emptyRcs = toReprCString("")
check(emptyRcs.len == 0, "toReprCString(empty): len == 0")
check($emptyRcs == "", "$(empty ReprCString): returns empty string")
# toBytes(string)
let bs = toBytes("abc")
check(bs.len == 3, "toBytes: correct length")
check(bs[0] == 0x61'u8, "toBytes: correct first byte")
let emptyBs = toBytes("")
check(emptyBs.len == 0, "toBytes(empty): empty seq")
# ---------------------------------------------------------------------------
# Section 2: create_context / installation_name / destroy_context
# ---------------------------------------------------------------------------
proc testContextLifecycle() =
echo "\n--- testContextLifecycle ---"
let ctx = create_context(toReprCString("lifecycle-test"))
check(ctx != nil, "create_context: returns non-nil handle")
let iname = installation_name(ctx)
defer: destroy_string(iname)
let inameStr = $iname
check(inameStr.len > 0, "installation_name: returns non-empty name")
echo " installation name: ", inameStr
destroy_context(ctx)
echo " destroy_context: no crash"
# ---------------------------------------------------------------------------
# Section 3: Full two-party conversation flow
# ---------------------------------------------------------------------------
# Exercises: create_intro_bundle, create_new_private_convo, handle_payload,
# send_content, and all four destroy_* procs.
# VecPayload helpers ([], len, items) are also exercised here.
proc testFullConversationFlow() =
echo "\n--- testFullConversationFlow ---"
let aliceCtx = create_context(toReprCString("alice"))
check(aliceCtx != nil, "Alice: create_context non-nil")
let bobCtx = create_context(toReprCString("bob"))
check(bobCtx != nil, "Bob: create_context non-nil")
# --- create_intro_bundle ---
var bobIntroRes = create_intro_bundle(bobCtx)
check(bobIntroRes.error_code == ErrNone,
"create_intro_bundle: error_code == ErrNone")
check(bobIntroRes.intro_bytes.len > 0,
"create_intro_bundle: intro_bytes non-empty")
# toSeq(VecUint8)
let introBytes = toSeq(bobIntroRes.intro_bytes)
check(introBytes.len > 0, "toSeq(VecUint8): produces non-empty seq")
# destroy_intro_result
destroy_intro_result(bobIntroRes)
echo " destroy_intro_result: no crash"
# --- create_new_private_convo ---
var convoRes = create_new_private_convo(
aliceCtx,
toSlice(introBytes),
toSlice("Hello, Bob!")
)
check(convoRes.error_code == ErrNone,
"create_new_private_convo: error_code == ErrNone")
let aliceConvoId = $convoRes.convo_id
check(aliceConvoId.len > 0, "create_new_private_convo: convo_id non-empty")
echo " Alice-Bob convo_id: ", aliceConvoId
# len(VecPayload)
let numPayloads = len(convoRes.payloads)
check(numPayloads > 0, "len(VecPayload): > 0 payloads in new convo")
# [](VecPayload, int): subscript access
let firstPayload = convoRes.payloads[0]
check(firstPayload.data.len > 0, "VecPayload[0].data: non-empty")
check(firstPayload.address.len > 0, "VecPayload[0].address: non-empty")
echo " first payload address: ", $firstPayload.address
# items(VecPayload): collect bytes before destroy
var payloadDatas: seq[seq[byte]] = @[]
var iterCount = 0
for p in convoRes.payloads:
payloadDatas.add(toSeq(p.data))
inc iterCount
check(iterCount == numPayloads,
"items(VecPayload): iterator yields all payloads")
# destroy_convo_result
destroy_convo_result(convoRes)
echo " destroy_convo_result: no crash"
# --- handle_payload ---
var bobSawContent = false
var bobConvoId = ""
for pData in payloadDatas:
var hp = handle_payload(bobCtx, toSlice(pData))
check(hp.error_code == ErrNone, "handle_payload: error_code == ErrNone")
let content = toSeq(hp.content)
if content.len > 0:
bobConvoId = $hp.convo_id
check(bobConvoId.len > 0,
"handle_payload: convo_id non-empty when content present")
if not bobSawContent:
check(hp.is_new_convo,
"handle_payload: is_new_convo == true on first contact")
bobSawContent = true
echo " Bob received content in convo: ", bobConvoId
destroy_handle_payload_result(hp)
check(bobSawContent, "handle_payload: Bob received Alice's opening message")
echo " destroy_handle_payload_result: no crash"
# --- send_content ---
var sendRes = send_content(
aliceCtx,
toReprCString(aliceConvoId),
toSlice("How are you, Bob?")
)
check(sendRes.error_code == ErrNone,
"send_content: error_code == ErrNone for valid convo_id")
check(len(sendRes.payloads) > 0,
"send_content: returns at least one payload")
var sendPayloadDatas: seq[seq[byte]] = @[]
for p in sendRes.payloads:
sendPayloadDatas.add(toSeq(p.data))
# destroy_send_content_result
destroy_send_content_result(sendRes)
echo " destroy_send_content_result: no crash"
# Bob handles follow-up payloads
for pData in sendPayloadDatas:
var hp2 = handle_payload(bobCtx, toSlice(pData))
check(hp2.error_code == ErrNone,
"handle_payload: Bob handles send_content payload without error")
destroy_handle_payload_result(hp2)
destroy_context(aliceCtx)
destroy_context(bobCtx)
echo " both contexts destroyed: no crash"
# ---------------------------------------------------------------------------
# Section 4: Error-case coverage
# ---------------------------------------------------------------------------
# Exercises destroy_* on error results (empty/null Vecs) to confirm they
# do not crash.
proc testErrorCases() =
echo "\n--- testErrorCases ---"
let ctx = create_context(toReprCString("error-tester"))
check(ctx != nil, "error-tester: create_context non-nil")
# send_content with a nonexistent convo_id must fail
var badSend = send_content(
ctx,
toReprCString("00000000-0000-0000-0000-nonexistent"),
toSlice("payload")
)
check(badSend.error_code != ErrNone,
"send_content(bad convo_id): error_code != ErrNone")
echo " send_content(bad convo_id) error_code: ", badSend.error_code
# Destroy error result to confirm destroy handles empty VecPayload
destroy_send_content_result(badSend)
echo " destroy_send_content_result(error result): no crash"
# create_new_private_convo with garbage bytes must fail with ErrBadIntro
let badIntro: seq[byte] = @[0xDE'u8, 0xAD'u8, 0xBE'u8, 0xEF'u8]
var badConvo = create_new_private_convo(
ctx,
toSlice(badIntro),
toSlice("content")
)
check(badConvo.error_code == ErrBadIntro,
"create_new_private_convo(bad intro): error_code == ErrBadIntro")
destroy_convo_result(badConvo)
echo " destroy_convo_result(error result): no crash"
destroy_context(ctx)
# ---------------------------------------------------------------------------
# Entry point
# ---------------------------------------------------------------------------
when isMainModule:
echo "=== test_all_endpoints: begin ==="
testHelperProcs()
testContextLifecycle()
testFullConversationFlow()
testErrorCases()
echo "\n=== ALL TESTS PASSED ==="