mirror of
https://github.com/logos-storage/nim-ethers.git
synced 2026-01-04 14:43:09 +00:00
WIP commit for websockets transport error tests
This commit is contained in:
parent
68e36768d0
commit
f480431ccc
38
testmodule/providers/jsonrpc/mocks/mockWebSocketServer2.nim
Normal file
38
testmodule/providers/jsonrpc/mocks/mockWebSocketServer2.nim
Normal file
@ -0,0 +1,38 @@
|
||||
import std/tables
|
||||
import std/strutils
|
||||
import std/uri
|
||||
# pkg/chronos,
|
||||
import pkg/chronicles
|
||||
# pkg/chronos/apps/http/httpserver,
|
||||
import pkg/websock/websock
|
||||
import pkg/websock/tests/helpers
|
||||
import pkg/httputils
|
||||
import pkg/asynctest/chronos/unittest
|
||||
# json_rpc/clients/websocketclient,
|
||||
# json_rpc/[client, server],
|
||||
# json_serialization
|
||||
|
||||
import pkg/stew/byteutils
|
||||
import pkg/ethers
|
||||
|
||||
const address = initTAddress("127.0.0.1:8888")
|
||||
|
||||
proc handle(request: HttpRequest) {.async.} =
|
||||
check request.uri.path == WSPath
|
||||
|
||||
let server = WSServer.new(protos = ["proto"])
|
||||
let ws = await server.handleRequest(request)
|
||||
let servRes = await ws.recvMsg()
|
||||
|
||||
check string.fromBytes(servRes) == testString
|
||||
await ws.waitForClose()
|
||||
|
||||
|
||||
proc run() {.async.} =
|
||||
|
||||
let server = createServer(
|
||||
address = address,
|
||||
handler = handle,
|
||||
flags = {ReuseAddr})
|
||||
|
||||
let provider = JsonRpcProvider.new("ws://" & $address)
|
||||
@ -12,40 +12,38 @@ import pkg/ethers/providers/jsonrpc
|
||||
import pkg/ethers/providers/jsonrpc/errors
|
||||
import pkg/ethers/erc20
|
||||
import pkg/json_rpc/clients/httpclient
|
||||
import pkg/websock/websock
|
||||
import ./mocks/mockHttpServer
|
||||
import ./mocks/mockWebSocketServer
|
||||
import ../../examples
|
||||
import ../../hardhat
|
||||
|
||||
suite "JSON RPC errors":
|
||||
|
||||
test "converts JSON RPC error to Nim error":
|
||||
let error = %*{ "message": "some error" }
|
||||
let error = %*{"message": "some error"}
|
||||
check JsonRpcProviderError.new(error).msg == "some error"
|
||||
|
||||
test "converts error data to bytes":
|
||||
let error = %*{
|
||||
"message": "VM Exception: reverted with 'some error'",
|
||||
"data": "0xabcd"
|
||||
}
|
||||
let error =
|
||||
%*{"message": "VM Exception: reverted with 'some error'", "data": "0xabcd"}
|
||||
check JsonRpcProviderError.new(error).data == some @[0xab'u8, 0xcd'u8]
|
||||
|
||||
test "converts nested error data to bytes":
|
||||
let error = %*{
|
||||
"message": "VM Exception: reverted with 'some error'",
|
||||
"data": {
|
||||
let error =
|
||||
%*{
|
||||
"message": "VM Exception: reverted with 'some error'",
|
||||
"data": "0xabcd"
|
||||
"data":
|
||||
{"message": "VM Exception: reverted with 'some error'", "data": "0xabcd"},
|
||||
}
|
||||
}
|
||||
check JsonRpcProviderError.new(error).data == some @[0xab'u8, 0xcd'u8]
|
||||
|
||||
type
|
||||
TestToken = ref object of Erc20Token
|
||||
type TestToken = ref object of Erc20Token
|
||||
|
||||
method mint(token: TestToken, holder: Address, amount: UInt256): Confirmable {.base, contract.}
|
||||
method mint(
|
||||
token: TestToken, holder: Address, amount: UInt256
|
||||
): Confirmable {.base, contract.}
|
||||
|
||||
suite "Network errors - HTTP":
|
||||
|
||||
var provider: JsonRpcProvider
|
||||
var mockServer: MockHttpServer
|
||||
var token: TestToken
|
||||
@ -72,20 +70,29 @@ suite "Network errors - HTTP":
|
||||
# mockServer.registerRpcResponse("eth_subscribe", response) # TODO: handle
|
||||
# eth_subscribe for websockets
|
||||
|
||||
proc testCustomResponse(errorName: string, responseHttpCode: HttpCode, responseText: string, errorType: type CatchableError) =
|
||||
let response = proc(request: HttpRequestRef): Future[HttpResponseRef] {.async: (raises: [CancelledError]).} =
|
||||
proc testCustomResponse(
|
||||
errorName: string,
|
||||
responseHttpCode: HttpCode,
|
||||
responseText: string,
|
||||
errorType: type CatchableError,
|
||||
) =
|
||||
let response = proc(
|
||||
request: HttpRequestRef
|
||||
): Future[HttpResponseRef] {.async: (raises: [CancelledError]).} =
|
||||
try:
|
||||
return await request.respond(responseHttpCode, responseText)
|
||||
except HttpWriteError as exc:
|
||||
return defaultResponse(exc)
|
||||
|
||||
let testNamePrefix = errorName & " error response is converted to " & errorType.name & " for "
|
||||
let testNamePrefix =
|
||||
errorName & " error response is converted to " & errorType.name & " for "
|
||||
test testNamePrefix & "sending a manual RPC method request":
|
||||
registerRpcMethods(response)
|
||||
expect errorType:
|
||||
discard await provider.send("eth_accounts")
|
||||
|
||||
test testNamePrefix & "calling a provider method that converts errors when calling a generated RPC request":
|
||||
test testNamePrefix &
|
||||
"calling a provider method that converts errors when calling a generated RPC request":
|
||||
registerRpcMethods(response)
|
||||
expect errorType:
|
||||
discard await provider.listAccounts()
|
||||
@ -100,8 +107,9 @@ suite "Network errors - HTTP":
|
||||
expect errorType:
|
||||
token = TestToken.new(token.address, provider.getSigner())
|
||||
discard await token.mint(
|
||||
Address.example, 100.u256,
|
||||
TransactionOverrides(gasLimit: 100.u256.some, chainId: 1.u256.some)
|
||||
Address.example,
|
||||
100.u256,
|
||||
TransactionOverrides(gasLimit: 100.u256.some, chainId: 1.u256.some),
|
||||
)
|
||||
|
||||
test testNamePrefix & "sending a manual transaction":
|
||||
@ -113,7 +121,8 @@ suite "Network errors - HTTP":
|
||||
test testNamePrefix & "sending a raw transaction":
|
||||
registerRpcMethods(response)
|
||||
expect errorType:
|
||||
const pk_with_funds = "0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d"
|
||||
const pk_with_funds =
|
||||
"0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d"
|
||||
let wallet = !Wallet.new(pk_with_funds)
|
||||
let tx = Transaction(
|
||||
to: wallet.address,
|
||||
@ -128,14 +137,17 @@ suite "Network errors - HTTP":
|
||||
test testNamePrefix & "subscribing to blocks":
|
||||
registerRpcMethods(response)
|
||||
expect errorType:
|
||||
let emptyHandler = proc(blckResult: ?!Block) = discard
|
||||
let emptyHandler = proc(blckResult: ?!Block) =
|
||||
discard
|
||||
discard await provider.subscribe(emptyHandler)
|
||||
|
||||
test testNamePrefix & "subscribing to logs":
|
||||
registerRpcMethods(response)
|
||||
expect errorType:
|
||||
let filter = EventFilter(address: Address.example, topics: @[array[32, byte].example])
|
||||
let emptyHandler = proc(log: ?!Log) = discard
|
||||
let filter =
|
||||
EventFilter(address: Address.example, topics: @[array[32, byte].example])
|
||||
let emptyHandler = proc(log: ?!Log) =
|
||||
discard
|
||||
discard await provider.subscribe(filter, emptyHandler)
|
||||
|
||||
testCustomResponse("429", Http429, "Too many requests", HttpRequestLimitError)
|
||||
@ -146,7 +158,9 @@ suite "Network errors - HTTP":
|
||||
privateAccess(JsonRpcProvider)
|
||||
privateAccess(RpcHttpClient)
|
||||
|
||||
let responseTimeout = proc(request: HttpRequestRef): Future[HttpResponseRef] {.async: (raises: [CancelledError]).} =
|
||||
let responseTimeout = proc(
|
||||
request: HttpRequestRef
|
||||
): Future[HttpResponseRef] {.async: (raises: [CancelledError]).} =
|
||||
try:
|
||||
await sleepAsync(5.minutes)
|
||||
return await request.respond(Http200, "OK")
|
||||
@ -186,3 +200,227 @@ suite "Network errors - HTTP":
|
||||
# We don't need to recreate each and every possible exception condition, as
|
||||
# they are all wrapped up in RpcPostError and converted to RpcNetworkError.
|
||||
# The tests above cover this conversion.
|
||||
|
||||
# suite "Network errors - WebSocket":
|
||||
|
||||
# var provider: JsonRpcProvider
|
||||
# var mockWsServer: MockWebSocketServer
|
||||
# var token: TestToken
|
||||
|
||||
# setup:
|
||||
# mockWsServer = MockWebSocketServer.init(initTAddress("127.0.0.1:0"))
|
||||
# await mockWsServer.start()
|
||||
# # Get the actual bound address
|
||||
# let actualAddress = mockWsServer.localAddress()
|
||||
# provider = JsonRpcProvider.new("ws://" & $actualAddress & "/ws")
|
||||
|
||||
# let deployment = readDeployment()
|
||||
# token = TestToken.new(!deployment.address(TestToken), provider)
|
||||
|
||||
# teardown:
|
||||
# await provider.close()
|
||||
# await mockWsServer.stop()
|
||||
|
||||
# proc registerRpcMethods(behavior: WebSocketBehavior) =
|
||||
# mockWsServer.registerRpcBehavior("eth_accounts", behavior)
|
||||
# mockWsServer.registerRpcBehavior("eth_call", behavior)
|
||||
# mockWsServer.registerRpcBehavior("eth_sendTransaction", behavior)
|
||||
# mockWsServer.registerRpcBehavior("eth_sendRawTransaction", behavior)
|
||||
# mockWsServer.registerRpcBehavior("eth_newBlockFilter", behavior)
|
||||
# mockWsServer.registerRpcBehavior("eth_newFilter", behavior)
|
||||
# mockWsServer.registerRpcBehavior("eth_subscribe", behavior)
|
||||
|
||||
# proc testCustomBehavior(errorName: string, behavior: WebSocketBehavior, errorType: type CatchableError) =
|
||||
# let testNamePrefix = errorName & " behavior is converted to " & errorType.name & " for "
|
||||
|
||||
# test testNamePrefix & "sending a manual RPC method request":
|
||||
# registerRpcMethods(behavior)
|
||||
# expect errorType:
|
||||
# discard await provider.send("eth_accounts")
|
||||
|
||||
# test testNamePrefix & "calling a provider method that converts errors":
|
||||
# registerRpcMethods(behavior)
|
||||
# expect errorType:
|
||||
# discard await provider.listAccounts()
|
||||
|
||||
# test testNamePrefix & "calling a view method of a contract":
|
||||
# registerRpcMethods(behavior)
|
||||
# expect errorType:
|
||||
# discard await token.balanceOf(Address.example)
|
||||
|
||||
# test testNamePrefix & "calling a contract method that executes a transaction":
|
||||
# registerRpcMethods(behavior)
|
||||
# expect errorType:
|
||||
# token = TestToken.new(token.address, provider.getSigner())
|
||||
# discard await token.mint(
|
||||
# Address.example, 100.u256,
|
||||
# TransactionOverrides(gasLimit: 100.u256.some, chainId: 1.u256.some)
|
||||
# )
|
||||
|
||||
# test testNamePrefix & "sending a manual transaction":
|
||||
# registerRpcMethods(behavior)
|
||||
# expect errorType:
|
||||
# let tx = Transaction.example
|
||||
# discard await provider.getSigner().sendTransaction(tx)
|
||||
|
||||
# test testNamePrefix & "sending a raw transaction":
|
||||
# registerRpcMethods(behavior)
|
||||
# expect errorType:
|
||||
# const pk_with_funds = "0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d"
|
||||
# let wallet = !Wallet.new(pk_with_funds)
|
||||
# let tx = Transaction(
|
||||
# to: wallet.address,
|
||||
# nonce: some 0.u256,
|
||||
# chainId: some 31337.u256,
|
||||
# gasPrice: some 1_000_000_000.u256,
|
||||
# gasLimit: some 21_000.u256,
|
||||
# )
|
||||
# let signedTx = await wallet.signTransaction(tx)
|
||||
# discard await provider.sendTransaction(signedTx)
|
||||
|
||||
# test testNamePrefix & "subscribing to blocks":
|
||||
# registerRpcMethods(behavior)
|
||||
# expect errorType:
|
||||
# let emptyHandler = proc(blckResult: ?!Block) = discard
|
||||
# discard await provider.subscribe(emptyHandler)
|
||||
|
||||
# test testNamePrefix & "subscribing to logs":
|
||||
# registerRpcMethods(behavior)
|
||||
# expect errorType:
|
||||
# let filter = EventFilter(address: Address.example, topics: @[array[32, byte].example])
|
||||
# let emptyHandler = proc(log: ?!Log) = discard
|
||||
# discard await provider.subscribe(filter, emptyHandler)
|
||||
|
||||
# # WebSocket close codes equivalent to HTTP status codes
|
||||
# testCustomBehavior(
|
||||
# "Policy violation close (rate limit)",
|
||||
# createBehavior(CloseWithCode, StatusPolicyError, "Policy violation - rate limited"),
|
||||
# WebSocketPolicyError
|
||||
# )
|
||||
|
||||
# testCustomBehavior(
|
||||
# "Server error close",
|
||||
# createBehavior(CloseWithCode, StatusUnexpectedError, "Internal server error"),
|
||||
# JsonRpcProviderError
|
||||
# )
|
||||
|
||||
# # testCustomBehavior(
|
||||
# # "Service unavailable close",
|
||||
# # createBehavior(CloseWithCode, StatusCodes.TryAgainLater, "Try again later"),
|
||||
# # WebSocketServiceUnavailableError
|
||||
# # )
|
||||
|
||||
# testCustomBehavior(
|
||||
# "Abrupt disconnect",
|
||||
# createBehavior(AbruptClose),
|
||||
# RpcNetworkError
|
||||
# )
|
||||
|
||||
# test "raises RpcNetworkError when WebSocket connection times out":
|
||||
# registerRpcMethods(createBehavior(Timeout, delay = 5.minutes))
|
||||
|
||||
# # Set a short timeout on the WebSocket client
|
||||
# privateAccess(JsonRpcProvider)
|
||||
# privateAccess(RpcWebSocketClient)
|
||||
|
||||
# let rpcClient = await provider.client
|
||||
# let client = RpcWebSocketClient(rpcClient)
|
||||
# # Note: Actual timeout setting depends on nim-websock implementation
|
||||
# # This may need to be adjusted based on available APIs
|
||||
|
||||
# expect RpcNetworkError:
|
||||
# discard await provider.send("eth_accounts").wait(1.seconds)
|
||||
|
||||
# test "raises RpcNetworkError when WebSocket connection is closed unexpectedly":
|
||||
# # Start a request, then close the server
|
||||
# let sendFuture = provider.send("eth_accounts")
|
||||
# await sleepAsync(10.millis)
|
||||
# await mockWsServer.stop()
|
||||
|
||||
# expect RpcNetworkError:
|
||||
# discard await sendFuture
|
||||
|
||||
# test "raises RpcNetworkError when WebSocket connection fails to establish":
|
||||
# # Stop the server first
|
||||
# await mockWsServer.stop()
|
||||
|
||||
# expect RpcNetworkError:
|
||||
# let deadProvider = JsonRpcProvider.new("ws://127.0.0.1:9999/ws")
|
||||
# discard await deadProvider.send("eth_accounts")
|
||||
|
||||
# test "handles WebSocket protocol errors gracefully":
|
||||
# registerRpcMethods(createBehavior(InvalidFrame))
|
||||
|
||||
# expect JsonRpcProviderError: # or whatever error nim-json-rpc maps protocol errors to
|
||||
# discard await provider.send("eth_accounts")
|
||||
|
||||
# test "handles oversized WebSocket messages":
|
||||
# registerRpcMethods(createBehavior(MessageTooBig))
|
||||
|
||||
# expect RpcNetworkError: # Large message handling depends on client limits
|
||||
# discard await provider.send("eth_accounts")
|
||||
|
||||
# test "raises timeout error on slow WebSocket handshake":
|
||||
# # Create a server that delays the WebSocket upgrade
|
||||
# let slowServer = MockWebSocketServer.init(initTAddress("127.0.0.1:0"))
|
||||
# # This would need custom implementation to delay handshake
|
||||
|
||||
# expect WebSocketTimeoutError:
|
||||
# let slowProvider = JsonRpcProvider.new("ws://127.0.0.1:9998/ws")
|
||||
# discard await slowProvider.send("eth_accounts").wait(100.millis)
|
||||
|
||||
# test "handles connection drops during message exchange":
|
||||
# # Register normal behavior initially
|
||||
# registerRpcMethods(createBehavior(Normal))
|
||||
|
||||
# # Start multiple requests
|
||||
# let futures = @[
|
||||
# provider.send("eth_accounts"),
|
||||
# provider.send("eth_call"),
|
||||
# provider.send("eth_newBlockFilter")
|
||||
# ]
|
||||
|
||||
# # Close connections after a short delay
|
||||
# await sleepAsync(5.millis)
|
||||
# for conn in mockWsServer.connections:
|
||||
# await conn.close(StatusCodes.AbnormalClosure, "Abnormal closure")
|
||||
|
||||
# # All should fail with network error
|
||||
# for future in futures:
|
||||
# expect RpcNetworkError:
|
||||
# discard await future
|
||||
|
||||
# test "recovers from temporary WebSocket disconnections":
|
||||
# # This test would verify client reconnection logic if implemented
|
||||
# # Initial connection works
|
||||
# registerRpcMethods(createBehavior(Normal))
|
||||
# let result1 = await provider.send("eth_accounts")
|
||||
# check result1.isOk
|
||||
|
||||
# # Simulate connection drop
|
||||
# for conn in mockWsServer.connections:
|
||||
# await conn.close(StatusCodes.GoingAway, "Going away")
|
||||
|
||||
# # Depending on provider implementation, this might auto-reconnect
|
||||
# # or need manual reconnection
|
||||
# expect RpcNetworkError:
|
||||
# discard await provider.send("eth_accounts")
|
||||
|
||||
# test "handles WebSocket ping/pong timeouts":
|
||||
# # This would test the ping/pong mechanism if the client supports it
|
||||
# registerRpcMethods(createBehavior(Normal))
|
||||
|
||||
# # Mock a scenario where server doesn't respond to pings
|
||||
# for conn in mockWsServer.connections:
|
||||
# # Disable pong responses (if we had access to this)
|
||||
# conn.onPing = nil
|
||||
|
||||
# # This test would need to trigger ping timeout
|
||||
# # The exact implementation depends on the websocket client capabilities
|
||||
|
||||
# test "handles WebSocket close frame with invalid payload":
|
||||
# # Test handling of malformed close frames
|
||||
# registerRpcMethods(createBehavior(CloseWithCode, StatusCodes.ProtocolError, ""))
|
||||
|
||||
# expect JsonRpcProviderError:
|
||||
# discard await provider.send("eth_accounts")
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user