logos-delivery/tests/persistency/test_sds_persistency.nim
NagyZoltanPeter fbdff090b1
persistency: follow nim-sds 0.3.0 snapshot persistence contract
nim-sds 0.3.0 replaced the ~14 fine-grained per-row Persistence callbacks
with a 5-proc snapshot model (saveChannelMeta / updateHistory / loadChannel
/ dropChannel / setRetrievalHint), all returning Future[Result[...]].

Rewrite waku/persistency/sds_persistency.nim accordingly:
- ChannelMeta is stored as one blob per channel; the message log as
  append/evict rows. Categories collapse from 7 to 2 (sds.meta, sds.log).
- Blob transform uses nim-sds' own codecs: snapshot_codec (schema-versioned
  protobuf) for ChannelMeta, the SDS wire codec for SdsMessage log rows. The
  generic payload_codec/BlobCodec path is retired (removed payload_codec.nim
  and test_blob_codec.nim).
- setRetrievalHint is a deliberate no-op: persisted hints are never read back
  (loadChannel/ChannelMeta carry none; hints are supplied live via the
  onRetrievalHint provider). The closure stays because the field is required.
- Fix the module import spelling (srcDir="sds" => bare module paths), which
  the previous adapter got wrong and never compiled against the locked deps.

Add tests/persistency/test_sds_persistency.nim (round-trip, empty-load,
evict, drop) replacing test_blob_codec in test_all. Full persistency suite
passes 74/74 under both refc and ORC.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-02 20:01:53 +02:00

156 lines
5.1 KiB
Nim

{.used.}
## Behavioural tests for the SDS Persistence adapter (nim-sds 0.3.0 snapshot
## model). Importing `sds_persistency` also compile-checks the real adapter.
##
## Writes go through the fire-and-forget Job path (the Future resolves when
## the op is queued, not applied — Persistency v1), so every read-back polls
## until the row appears/disappears.
import std/[options, os, times]
import chronos, results
import testutils/unittests
import waku/persistency/persistency
import waku/persistency/keys
import waku/persistency/sds_persistency
proc tmpRoot(label: string): string =
let p = getTempDir() / ("sds_persistency_test_" & label & "_" & $epochTime().int)
removeDir(p)
p
proc pollExists(
t: Job, category: string, k: Key, timeoutMs = 1000
): Future[bool] {.async.} =
let deadline = epochTime() + (timeoutMs.float / 1000.0)
while epochTime() < deadline:
let r = await t.exists(category, k)
if r.isOk and r.get():
return true
await sleepAsync(chronos.milliseconds(2))
return false
proc pollGone(
t: Job, category: string, k: Key, timeoutMs = 1000
): Future[bool] {.async.} =
let deadline = epochTime() + (timeoutMs.float / 1000.0)
while epochTime() < deadline:
let r = await t.exists(category, k)
if r.isOk and not r.get():
return true
await sleepAsync(chronos.milliseconds(2))
return false
proc mkMsg(channelId: SdsChannelID, msgId: SdsMessageID, lamport: int64): SdsMessage =
SdsMessage.init(
messageId = msgId,
lamportTimestamp = lamport,
causalHistory = @[],
channelId = channelId,
content = @[byte(1), byte(2)],
bloomFilter = @[],
)
suite "SDS persistency adapter (0.3.0 snapshot model)":
asyncTest "saveChannelMeta + updateHistory round-trip via loadChannel":
let root = tmpRoot("roundtrip")
defer:
removeDir(root)
let p = Persistency.instance(root).get()
defer:
Persistency.reset()
let job = p.openJob("sds").get()
let persistence = newSdsPersistence(job)
let channelId = "chan-1".SdsChannelID
var meta = ChannelMeta.init()
meta.lamportTimestamp = 42
check (await persistence.saveChannelMeta(channelId, meta)).isOk
check (await job.pollExists(CatMeta, toKey(channelId)))
# append out of (lamport) order on purpose; loadChannel must sort.
var upd = HistoryUpdate.init()
upd.append = @[mkMsg(channelId, "m2", 2), mkMsg(channelId, "m1", 1)]
check (await persistence.updateHistory(channelId, upd)).isOk
check (await job.pollExists(CatLog, key(channelId, "m2")))
let data = (await persistence.loadChannel(channelId)).valueOr:
check false
return
check data.meta.lamportTimestamp == 42
check data.messageHistory.len == 2
check data.messageHistory[0].messageId == "m1"
check data.messageHistory[1].messageId == "m2"
asyncTest "loadChannel on a fresh channel returns empty ChannelData":
let root = tmpRoot("empty")
defer:
removeDir(root)
let p = Persistency.instance(root).get()
defer:
Persistency.reset()
let job = p.openJob("sds").get()
let persistence = newSdsPersistence(job)
let data = (await persistence.loadChannel("nope".SdsChannelID)).valueOr:
check false
return
check data.meta.lamportTimestamp == 0
check data.messageHistory.len == 0
asyncTest "updateHistory evict removes a log row":
let root = tmpRoot("evict")
defer:
removeDir(root)
let p = Persistency.instance(root).get()
defer:
Persistency.reset()
let job = p.openJob("sds").get()
let persistence = newSdsPersistence(job)
let channelId = "c".SdsChannelID
var upd = HistoryUpdate.init()
upd.append = @[mkMsg(channelId, "a", 1), mkMsg(channelId, "b", 2)]
check (await persistence.updateHistory(channelId, upd)).isOk
check (await job.pollExists(CatLog, key(channelId, "b")))
var ev = HistoryUpdate.init()
ev.evict = @["a".SdsMessageID]
check (await persistence.updateHistory(channelId, ev)).isOk
check (await job.pollGone(CatLog, key(channelId, "a")))
let data = (await persistence.loadChannel(channelId)).valueOr:
check false
return
check data.messageHistory.len == 1
check data.messageHistory[0].messageId == "b"
asyncTest "dropChannel wipes meta and log":
let root = tmpRoot("drop")
defer:
removeDir(root)
let p = Persistency.instance(root).get()
defer:
Persistency.reset()
let job = p.openJob("sds").get()
let persistence = newSdsPersistence(job)
let channelId = "d".SdsChannelID
var meta = ChannelMeta.init()
meta.lamportTimestamp = 7
check (await persistence.saveChannelMeta(channelId, meta)).isOk
var upd = HistoryUpdate.init()
upd.append = @[mkMsg(channelId, "x", 1)]
check (await persistence.updateHistory(channelId, upd)).isOk
check (await job.pollExists(CatMeta, toKey(channelId)))
check (await job.pollExists(CatLog, key(channelId, "x")))
check (await persistence.dropChannel(channelId)).isOk
check (await job.pollGone(CatMeta, toKey(channelId)))
let data = (await persistence.loadChannel(channelId)).valueOr:
check false
return
check data.meta.lamportTimestamp == 0
check data.messageHistory.len == 0