## 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"