277 lines
7.1 KiB
Nim
Raw Normal View History

2025-02-11 10:54:58 +01:00
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
2025-02-13 10:49:50 +01:00
import ../../../codexcrawler/state
2025-02-12 14:48:57 +01:00
import ../mocks/mockstate
2025-02-13 10:49:50 +01:00
import ../mocks/mockclock
2025-02-11 10:54:58 +01:00
import ../helpers
suite "Nodestore":
let
dsPath = getTempDir() / "testds"
nodestoreName = "nodestore"
2025-02-11 12:43:55 +01:00
var
2025-02-11 10:54:58 +01:00
ds: TypedDatastore
state: MockState
2025-02-13 10:49:50 +01:00
clock: MockClock
2025-02-11 10:54:58 +01:00
store: NodeStore
setup:
ds = createTypedDatastore(dsPath).tryGet()
state = createMockState()
2025-02-13 10:49:50 +01:00
clock = createMockClock()
2025-02-11 10:54:58 +01:00
2025-02-13 10:49:50 +01:00
store = NodeStore.new(state, ds, clock)
2025-02-11 12:42:20 +01:00
(await store.start()).tryGet()
2025-02-11 10:54:58 +01:00
teardown:
2025-02-11 12:42:20 +01:00
(await store.stop()).tryGet()
2025-02-11 10:54:58 +01:00
(await ds.close()).tryGet()
2025-02-11 12:42:20 +01:00
state.checkAllUnsubscribed()
2025-02-11 10:54:58 +01:00
removeDir(dsPath)
2025-06-02 15:30:12 +02:00
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")
2025-02-11 10:54:58 +01:00
test "nodeEntry encoding":
let entry =
NodeEntry(id: genNid(), lastVisit: 123.uint64, firstInactive: 234.uint64)
2025-02-11 10:54:58 +01:00
let
bytes = entry.encode()
decoded = NodeEntry.decode(bytes).tryGet()
check:
entry.id == decoded.id
entry.lastVisit == decoded.lastVisit
entry.firstInactive == decoded.firstInactive
2025-02-11 10:54:58 +01:00
test "nodesFound event should store nodes":
2025-02-11 12:43:55 +01:00
let
2025-02-11 10:54:58 +01:00
nid = genNid()
expectedKey = Key.init(nodestoreName / $nid).tryGet()
await fireNodeFoundEvent(@[nid])
2025-02-11 10:54:58 +01:00
check:
(await ds.has(expectedKey)).tryGet()
2025-02-11 12:43:55 +01:00
2025-02-11 10:54:58 +01:00
let entry = (await get[NodeEntry](ds, expectedKey)).tryGet()
check:
entry.id == nid
test "nodesFound event should fire newNodesDiscovered":
var newNodes = newSeq[Nid]()
2025-06-02 16:16:41 +02:00
proc onNewNodes(
nids: seq[Nid]
): Future[?!void] {.async: (raises: [CancelledError]).} =
2025-02-11 10:54:58 +01:00
newNodes = nids
return success()
2025-02-11 12:43:55 +01:00
let
2025-02-11 10:54:58 +01:00
sub = state.events.newNodesDiscovered.subscribe(onNewNodes)
nid = genNid()
await fireNodeFoundEvent(@[nid])
2025-02-11 10:54:58 +01:00
check:
newNodes == @[nid]
await state.events.newNodesDiscovered.unsubscribe(sub)
2025-02-11 12:43:55 +01:00
2025-02-11 10:54:58 +01:00
test "nodesFound event should not fire newNodesDiscovered for previously seen nodes":
2025-02-11 12:43:55 +01:00
let nid = genNid()
2025-02-11 10:54:58 +01:00
# Make nid known first. Then subscribe.
await fireNodeFoundEvent(@[nid])
2025-02-11 10:54:58 +01:00
var
newNodes = newSeq[Nid]()
count = 0
2025-06-02 16:16:41 +02:00
proc onNewNodes(
nids: seq[Nid]
): Future[?!void] {.async: (raises: [CancelledError]).} =
2025-02-11 10:54:58 +01:00
newNodes = nids
inc count
return success()
2025-02-11 12:43:55 +01:00
let sub = state.events.newNodesDiscovered.subscribe(onNewNodes)
2025-02-11 10:54:58 +01:00
# Firing the event again should not trigger newNodesDiscovered for nid
await fireNodeFoundEvent(@[nid])
2025-02-11 10:54:58 +01:00
check:
newNodes.len == 0
count == 0
await state.events.newNodesDiscovered.unsubscribe(sub)
test "iterateAll yields all known nids":
2025-02-11 12:43:55 +01:00
let
2025-02-11 10:54:58 +01:00
nid1 = genNid()
nid2 = genNid()
nid3 = genNid()
2025-02-11 12:43:55 +01:00
await fireNodeFoundEvent(@[nid1, nid2, nid3])
2025-02-11 10:54:58 +01:00
var iterNodes = newSeq[Nid]()
2025-02-11 12:42:20 +01:00
proc onNode(entry: NodeEntry): Future[?!void] {.async: (raises: []), gcsafe.} =
iterNodes.add(entry.id)
2025-02-11 10:54:58 +01:00
return success()
2025-02-11 12:42:20 +01:00
(await store.iterateAll(onNode)).tryGet()
2025-02-11 10:54:58 +01:00
check:
nid1 in iterNodes
nid2 in iterNodes
nid3 in iterNodes
2025-02-13 10:49:50 +01:00
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]()
2025-06-02 16:16:41 +02:00
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)
2025-02-13 10:49:50 +01:00
test "dhtNodeCheck event should update lastVisit":
let
nid = genNid()
expectedKey = Key.init(nodestoreName / $nid).tryGet()
clock.setNow = 123456789.uint64
await fireNodeFoundEvent(@[nid])
2025-02-13 10:49:50 +01:00
let originalEntry = (await get[NodeEntry](ds, expectedKey)).tryGet()
check:
originalEntry.lastVisit == 0
await fireCheckEvent(nid, true)
2025-02-13 10:49:50 +01:00
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]()
2025-06-02 16:16:41 +02:00
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)