2025-06-02 16:16:41 +02:00

277 lines
7.1 KiB
Nim

import std/os
import pkg/chronos
import pkg/questionable/results
import pkg/asynctest/chronos/unittest
import pkg/datastore/typedds
import ../../../codexcrawler/components/nodestore
import ../../../codexcrawler/utils/datastoreutils
import ../../../codexcrawler/utils/asyncdataevent
import ../../../codexcrawler/types
import ../../../codexcrawler/state
import ../mocks/mockstate
import ../mocks/mockclock
import ../helpers
suite "Nodestore":
let
dsPath = getTempDir() / "testds"
nodestoreName = "nodestore"
var
ds: TypedDatastore
state: MockState
clock: MockClock
store: NodeStore
setup:
ds = createTypedDatastore(dsPath).tryGet()
state = createMockState()
clock = createMockClock()
store = NodeStore.new(state, ds, clock)
(await store.start()).tryGet()
teardown:
(await store.stop()).tryGet()
(await ds.close()).tryGet()
state.checkAllUnsubscribed()
removeDir(dsPath)
proc fireNodeFoundEvent(nids: seq[Nid]) {.async: (raises: []).} =
try:
(await state.events.nodesFound.fire(nids)).tryGet()
except CatchableError:
raiseAssert("CatchableError in fireNodeFoundEvent")
proc fireCheckEvent(nid: Nid, isOk: bool) {.async: (raises: []).} =
try:
(await state.events.dhtNodeCheck.fire(DhtNodeCheckEventData(id: nid, isOk: isOk))).tryGet()
except CatchableError:
raiseAssert("CatchableError in fireCheckEvent")
test "nodeEntry encoding":
let entry =
NodeEntry(id: genNid(), lastVisit: 123.uint64, firstInactive: 234.uint64)
let
bytes = entry.encode()
decoded = NodeEntry.decode(bytes).tryGet()
check:
entry.id == decoded.id
entry.lastVisit == decoded.lastVisit
entry.firstInactive == decoded.firstInactive
test "nodesFound event should store nodes":
let
nid = genNid()
expectedKey = Key.init(nodestoreName / $nid).tryGet()
await fireNodeFoundEvent(@[nid])
check:
(await ds.has(expectedKey)).tryGet()
let entry = (await get[NodeEntry](ds, expectedKey)).tryGet()
check:
entry.id == nid
test "nodesFound event should fire newNodesDiscovered":
var newNodes = newSeq[Nid]()
proc onNewNodes(
nids: seq[Nid]
): Future[?!void] {.async: (raises: [CancelledError]).} =
newNodes = nids
return success()
let
sub = state.events.newNodesDiscovered.subscribe(onNewNodes)
nid = genNid()
await fireNodeFoundEvent(@[nid])
check:
newNodes == @[nid]
await state.events.newNodesDiscovered.unsubscribe(sub)
test "nodesFound event should not fire newNodesDiscovered for previously seen nodes":
let nid = genNid()
# Make nid known first. Then subscribe.
await fireNodeFoundEvent(@[nid])
var
newNodes = newSeq[Nid]()
count = 0
proc onNewNodes(
nids: seq[Nid]
): Future[?!void] {.async: (raises: [CancelledError]).} =
newNodes = nids
inc count
return success()
let sub = state.events.newNodesDiscovered.subscribe(onNewNodes)
# Firing the event again should not trigger newNodesDiscovered for nid
await fireNodeFoundEvent(@[nid])
check:
newNodes.len == 0
count == 0
await state.events.newNodesDiscovered.unsubscribe(sub)
test "iterateAll yields all known nids":
let
nid1 = genNid()
nid2 = genNid()
nid3 = genNid()
await fireNodeFoundEvent(@[nid1, nid2, nid3])
var iterNodes = newSeq[Nid]()
proc onNode(entry: NodeEntry): Future[?!void] {.async: (raises: []), gcsafe.} =
iterNodes.add(entry.id)
return success()
(await store.iterateAll(onNode)).tryGet()
check:
nid1 in iterNodes
nid2 in iterNodes
nid3 in iterNodes
test "iterateAll yields no uninitialized entries":
let
nid1 = genNid()
nid2 = genNid()
nid3 = genNid()
await fireNodeFoundEvent(@[nid1, nid2, nid3])
var iterNodes = newSeq[Nid]()
proc onNode(entry: NodeEntry): Future[?!void] {.async: (raises: []), gcsafe.} =
iterNodes.add(entry.id)
return success()
(await store.iterateAll(onNode)).tryGet()
for nid in iterNodes:
check:
$nid != "0"
test "deleteEntries deletes entries":
let
nid1 = genNid()
nid2 = genNid()
nid3 = genNid()
await fireNodeFoundEvent(@[nid1, nid2, nid3])
(await store.deleteEntries(@[nid1, nid2])).tryGet()
var iterNodes = newSeq[Nid]()
proc onNode(entry: NodeEntry): Future[?!void] {.async: (raises: []), gcsafe.} =
iterNodes.add(entry.id)
return success()
(await store.iterateAll(onNode)).tryGet()
check:
nid1 notin iterNodes
nid2 notin iterNodes
nid3 in iterNodes
test "deleteEntries fires nodesDeleted event":
var deletedNodes = newSeq[Nid]()
proc onDeleted(
nids: seq[Nid]
): Future[?!void] {.async: (raises: [CancelledError]).} =
deletedNodes = nids
return success()
let
sub = state.events.nodesDeleted.subscribe(onDeleted)
nid1 = genNid()
nid2 = genNid()
nid3 = genNid()
await fireNodeFoundEvent(@[nid1, nid2, nid3])
(await store.deleteEntries(@[nid1, nid2])).tryGet()
check:
nid1 in deletedNodes
nid2 in deletedNodes
nid3 notin deletedNodes
await state.events.nodesDeleted.unsubscribe(sub)
test "dhtNodeCheck event should update lastVisit":
let
nid = genNid()
expectedKey = Key.init(nodestoreName / $nid).tryGet()
clock.setNow = 123456789.uint64
await fireNodeFoundEvent(@[nid])
let originalEntry = (await get[NodeEntry](ds, expectedKey)).tryGet()
check:
originalEntry.lastVisit == 0
await fireCheckEvent(nid, true)
let updatedEntry = (await get[NodeEntry](ds, expectedKey)).tryGet()
check:
clock.setNow == updatedEntry.lastVisit
test "failed dhtNodeCheck event should set firstInactive":
let
nid = genNid()
expectedKey = Key.init(nodestoreName / $nid).tryGet()
clock.setNow = 345345.uint64
await fireNodeFoundEvent(@[nid])
await fireCheckEvent(nid, false)
let updatedEntry = (await get[NodeEntry](ds, expectedKey)).tryGet()
check:
clock.setNow == updatedEntry.firstInactive
test "successful dhtNodeCheck event should clear firstInactive":
let
nid = genNid()
expectedKey = Key.init(nodestoreName / $nid).tryGet()
clock.setNow = 456456.uint64
await fireNodeFoundEvent(@[nid])
await fireCheckEvent(nid, false)
await fireCheckEvent(nid, true)
let updatedEntry = (await get[NodeEntry](ds, expectedKey)).tryGet()
check:
updatedEntry.firstInactive == 0
test "dhtNodeCheck event for non-existing node should fire nodesDeleted":
var deletedNodes = newSeq[Nid]()
proc onDeleted(
nids: seq[Nid]
): Future[?!void] {.async: (raises: [CancelledError]).} =
deletedNodes = nids
return success()
let
sub = state.events.nodesDeleted.subscribe(onDeleted)
nid = genNid()
# We don't fire nodeFound first. So the store doesn't know it exists.
await fireCheckEvent(nid, true)
check:
nid in deletedNodes
await state.events.nodesDeleted.unsubscribe(sub)