nip/tests/test_decentralized.nim

397 lines
12 KiB
Nim

## tests/test_decentralized.nim
## Tests for Decentralized Architecture
import std/[unittest, tables, times, asyncdispatch, options, strutils]
import ../src/nimpak/[decentralized, merkle_tree, nippel_types]
import ../src/nimpak/utils/resultutils
suite "Decentralized Architecture":
# ==========================================================================
# Test: Peer-to-Peer Discovery
# ==========================================================================
test "Discovery manager creation":
let manager = newDiscoveryManager(mDNS)
check manager.protocol == mDNS
check manager.localServices.len == 0
check manager.discoveredServices.len == 0
check manager.announceInterval == 30
check not manager.isRunning
test "Service announcement":
let manager = newDiscoveryManager(mDNS)
let announcement = ServiceAnnouncement(
name: "test-nippel",
kind: NippelService,
port: 8080,
utcpAddress: "utcp://localhost/nippel/test-nippel",
metadata: initTable[string, string](),
ttl: 300
)
proc testAnnounce() {.async.} =
let result = await manager.announceService(announcement)
check result.isOk
check manager.localServices.hasKey("test-nippel")
check manager.localServices["test-nippel"].kind == NippelService
waitFor testAnnounce()
test "Service discovery":
let manager = newDiscoveryManager(mDNS)
# Add a discovered service manually for testing
let service = DiscoveredService(
name: "remote-nippel",
kind: NippelService,
host: "192.168.1.100",
port: 8080,
protocol: mDNS,
utcpAddress: "utcp://192.168.1.100/nippel/remote-nippel",
metadata: initTable[string, string](),
discoveredAt: now(),
lastSeen: now()
)
manager.discoveredServices["remote-nippel"] = service
proc testDiscover() {.async.} =
let result = await manager.discoverServices(NippelService)
check result.isOk
let services = result.value
check services.len == 1
check services[0].name == "remote-nippel"
waitFor testDiscover()
test "Find service by name":
let manager = newDiscoveryManager(mDNS)
let service = DiscoveredService(
name: "test-service",
kind: CASService,
host: "localhost",
port: 9000,
protocol: mDNS,
utcpAddress: "utcp://localhost/cas/test-service",
metadata: initTable[string, string](),
discoveredAt: now(),
lastSeen: now()
)
manager.discoveredServices["test-service"] = service
let found = manager.findService("test-service")
check found.isSome
check found.get().kind == CASService
let notFound = manager.findService("nonexistent")
check notFound.isNone
test "Update service last seen":
let manager = newDiscoveryManager(mDNS)
let service = DiscoveredService(
name: "test-service",
kind: NippelService,
host: "localhost",
port: 8080,
protocol: mDNS,
utcpAddress: "utcp://localhost/nippel/test-service",
metadata: initTable[string, string](),
discoveredAt: now() - 100.seconds,
lastSeen: now() - 100.seconds
)
manager.discoveredServices["test-service"] = service
let oldLastSeen = manager.discoveredServices["test-service"].lastSeen
manager.updateServiceLastSeen("test-service")
let newLastSeen = manager.discoveredServices["test-service"].lastSeen
check newLastSeen > oldLastSeen
test "Remove stale services":
let manager = newDiscoveryManager(mDNS)
# Add a fresh service
let freshService = DiscoveredService(
name: "fresh-service",
kind: NippelService,
host: "localhost",
port: 8080,
protocol: mDNS,
utcpAddress: "utcp://localhost/nippel/fresh-service",
metadata: initTable[string, string](),
discoveredAt: now(),
lastSeen: now()
)
# Add a stale service
let staleService = DiscoveredService(
name: "stale-service",
kind: NippelService,
host: "localhost",
port: 8081,
protocol: mDNS,
utcpAddress: "utcp://localhost/nippel/stale-service",
metadata: initTable[string, string](),
discoveredAt: now() - 400.seconds,
lastSeen: now() - 400.seconds
)
manager.discoveredServices["fresh-service"] = freshService
manager.discoveredServices["stale-service"] = staleService
check manager.discoveredServices.len == 2
manager.removeStaleServices(300) # Remove services older than 5 minutes
check manager.discoveredServices.len == 1
check manager.discoveredServices.hasKey("fresh-service")
check not manager.discoveredServices.hasKey("stale-service")
# ==========================================================================
# Test: Distributed UTCP Addressing
# ==========================================================================
test "Parse distributed UTCP address":
let result = parseDistributedUTCPAddress("utcp://example.com:8080/nippel/test")
check result.isOk
let address = result.value
check address.scheme == "utcp"
check address.host == "example.com"
check address.port == 8080
check address.resource == "/nippel/test"
test "Parse UTCP address without port":
let result = parseDistributedUTCPAddress("utcp://localhost/nexter/container1")
check result.isOk
let address = result.value
check address.scheme == "utcp"
check address.host == "localhost"
check address.port == 8080 # Default port
check address.resource == "/nexter/container1"
test "Parse invalid UTCP address":
let result = parseDistributedUTCPAddress("http://example.com/test")
check result.isErr
check result.error.contains("must start with utcp://")
test "Format distributed UTCP address":
let address = DistributedUTCPAddress(
scheme: "utcp",
host: "example.com",
port: 9000,
resource: "/nippel/test",
query: initTable[string, string]()
)
let formatted = formatDistributedUTCPAddress(address)
check formatted == "utcp://example.com:9000/nippel/test"
test "Format UTCP address with default port":
let address = DistributedUTCPAddress(
scheme: "utcp",
host: "localhost",
port: 8080,
resource: "/cas/store",
query: initTable[string, string]()
)
let formatted = formatDistributedUTCPAddress(address)
check formatted == "utcp://localhost/cas/store"
test "UTCP router creation":
let router = newUTCPRouter("localhost", 8080)
check router.localAddress.host == "localhost"
check router.localAddress.port == 8080
check router.routes.len == 0
check router.neighbors.len == 0
test "Add route to router":
let router = newUTCPRouter("localhost", 8080)
let result = router.addRoute(
"utcp://remote.host:9000/nippel/test",
"192.168.1.100",
metric = 5
)
check result.isOk
check router.routes.len == 1
test "Find route in router":
let router = newUTCPRouter("localhost", 8080)
discard router.addRoute(
"utcp://remote.host:9000/nippel/test",
"192.168.1.100",
metric = 5
)
let found = router.findRoute("utcp://remote.host:9000/nippel/test")
check found.isSome
check found.get().nextHop == "192.168.1.100"
check found.get().metric == 5
let notFound = router.findRoute("utcp://nonexistent/test")
check notFound.isNone
test "Add neighbor to router":
let router = newUTCPRouter("localhost", 8080)
router.addNeighbor("192.168.1.100")
router.addNeighbor("192.168.1.101")
check router.neighbors.len == 2
check "192.168.1.100" in router.neighbors
check "192.168.1.101" in router.neighbors
# Adding same neighbor again shouldn't duplicate
router.addNeighbor("192.168.1.100")
check router.neighbors.len == 2
# ==========================================================================
# Test: Merkle Tree Synchronization
# ==========================================================================
test "Sync manager creation":
let manager = newSyncManager()
check manager.activeSessions.len == 0
check manager.syncInterval == 60
test "Start sync session":
let manager = newSyncManager()
# Create a simple merkle tree for testing
let tree = MerkleTree(
root: MerkleNode(
hash: "test-root-hash",
path: "/",
isLeaf: false,
children: @[],
size: 0
),
hashAlgorithm: "xxh3",
nodeCount: 1,
leafCount: 0
)
proc testSync() {.async.} =
let result = await manager.startSync(tree, "remote.host:8080")
check result.isOk
let sessionId = result.value
check sessionId.len > 0
check manager.activeSessions.hasKey(sessionId)
waitFor testSync()
test "Get sync status":
let manager = newSyncManager()
let tree = MerkleTree(
root: MerkleNode(
hash: "test-root-hash",
path: "/",
isLeaf: false,
children: @[], size: 0
),
hashAlgorithm: "xxh3", nodeCount: 1, leafCount: 0
)
proc testStatus() {.async.} =
let result = await manager.startSync(tree, "remote.host:8080")
check result.isOk
let sessionId = result.value
let status = manager.getSyncStatus(sessionId)
check status.isSome
check status.get().status == Syncing
check status.get().remoteAddress == "remote.host:8080"
waitFor testStatus()
test "Cancel sync session":
let manager = newSyncManager()
let tree = MerkleTree(
root: MerkleNode(
hash: "test-root-hash",
path: "/",
isLeaf: false,
children: @[], size: 0
),
hashAlgorithm: "xxh3", nodeCount: 1, leafCount: 0
)
proc testCancel() {.async.} =
let result = await manager.startSync(tree, "remote.host:8080")
check result.isOk
let sessionId = result.value
check manager.activeSessions.hasKey(sessionId)
let cancelResult = manager.cancelSync(sessionId)
check cancelResult.isOk
check not manager.activeSessions.hasKey(sessionId)
waitFor testCancel()
# ==========================================================================
# Test: High-Level Operations
# ==========================================================================
test "Build decentralized cluster":
proc testCluster() {.async.} =
let result = await buildDecentralizedCluster(
@["nippel1", "nippel2"],
@["nexter1"]
)
check result.isOk
let manager = result.value
check manager.localServices.len == 3
check manager.localServices.hasKey("nippel1")
check manager.localServices.hasKey("nippel2")
check manager.localServices.hasKey("nexter1")
waitFor testCluster()
# ==========================================================================
# Test: String Conversions
# ==========================================================================
test "DiscoveryProtocol string conversion":
check $mDNS == "mDNS"
check $DNSSD == "DNS-SD"
check $DHT == "DHT"
check $Gossip == "Gossip"
test "ServiceKind string conversion":
check $NippelService == "Nippel"
check $NexterService == "Nexter"
check $CASService == "CAS"
check $MerkleService == "Merkle"
test "SyncStatus string conversion":
check $InSync == "InSync"
check $OutOfSync == "OutOfSync"
check $Syncing == "Syncing"
check $SyncFailed == "SyncFailed"
test "DiffAction string conversion":
check $Add == "Add"
check $Remove == "Remove"
check $Update == "Update"
check $Conflict == "Conflict"
echo "✓ All Decentralized Architecture tests completed"