From 8394c15a1ad4b1bff2ae933fa34156ec48b1e32a Mon Sep 17 00:00:00 2001 From: NagyZoltanPeter <113987313+NagyZoltanPeter@users.noreply.github.com> Date: Thu, 24 Apr 2025 08:36:30 +0200 Subject: [PATCH] fix: bad HttpCode conversion, add missing lightpush v3 rest api tests (#3389) * Fix bad HttpCode conversion, add missing lightpush v3 rest api tests --- tests/all_tests_waku.nim | 1 + tests/wakunode_rest/test_rest_lightpush.nim | 282 ++++++++++++++++++ .../test_rest_lightpush_legacy.nim | 25 -- waku/waku_api/rest/lightpush/handlers.nim | 26 +- waku/waku_lightpush/common.nim | 6 + 5 files changed, 303 insertions(+), 37 deletions(-) create mode 100644 tests/wakunode_rest/test_rest_lightpush.nim diff --git a/tests/all_tests_waku.nim b/tests/all_tests_waku.nim index f23f4249c..20da29fe2 100644 --- a/tests/all_tests_waku.nim +++ b/tests/all_tests_waku.nim @@ -99,6 +99,7 @@ import ./wakunode_rest/test_rest_relay_serdes, ./wakunode_rest/test_rest_serdes, ./wakunode_rest/test_rest_filter, + ./wakunode_rest/test_rest_lightpush, ./wakunode_rest/test_rest_lightpush_legacy, ./wakunode_rest/test_rest_admin, ./wakunode_rest/test_rest_cors, diff --git a/tests/wakunode_rest/test_rest_lightpush.nim b/tests/wakunode_rest/test_rest_lightpush.nim new file mode 100644 index 000000000..2c4ec0959 --- /dev/null +++ b/tests/wakunode_rest/test_rest_lightpush.nim @@ -0,0 +1,282 @@ +{.used.} + +import + std/sequtils, + stew/byteutils, + stew/shims/net, + testutils/unittests, + presto, + presto/client as presto_client, + libp2p/crypto/crypto + +import + waku/[ + waku_api/message_cache, + waku_core, + waku_node, + node/peer_manager, + waku_lightpush/common, + waku_api/rest/server, + waku_api/rest/client, + waku_api/rest/responses, + waku_api/rest/lightpush/types, + waku_api/rest/lightpush/handlers as lightpush_api, + waku_api/rest/lightpush/client as lightpush_api_client, + waku_relay, + common/rate_limit/setting, + ], + ../testlib/wakucore, + ../testlib/wakunode + +proc testWakuNode(): WakuNode = + let + privkey = generateSecp256k1Key() + bindIp = parseIpAddress("0.0.0.0") + extIp = parseIpAddress("127.0.0.1") + port = Port(0) + + return newTestWakuNode(privkey, bindIp, port, some(extIp), some(port)) + +type RestLightPushTest = object + serviceNode: WakuNode + pushNode: WakuNode + consumerNode: WakuNode + restServer: WakuRestServerRef + restClient: RestClientRef + +proc init( + T: type RestLightPushTest, rateLimit: RateLimitSetting = (0, 0.millis) +): Future[T] {.async.} = + var testSetup = RestLightPushTest() + testSetup.serviceNode = testWakuNode() + testSetup.pushNode = testWakuNode() + testSetup.consumerNode = testWakuNode() + + await allFutures( + testSetup.serviceNode.start(), + testSetup.pushNode.start(), + testSetup.consumerNode.start(), + ) + + await testSetup.consumerNode.mountRelay() + await testSetup.serviceNode.mountRelay() + await testSetup.serviceNode.mountLightPush(rateLimit) + testSetup.pushNode.mountLightPushClient() + + testSetup.serviceNode.peerManager.addServicePeer( + testSetup.consumerNode.peerInfo.toRemotePeerInfo(), WakuRelayCodec + ) + + await testSetup.serviceNode.connectToNodes( + @[testSetup.consumerNode.peerInfo.toRemotePeerInfo()] + ) + + testSetup.pushNode.peerManager.addServicePeer( + testSetup.serviceNode.peerInfo.toRemotePeerInfo(), WakuLightPushCodec + ) + + var restPort = Port(0) + let restAddress = parseIpAddress("127.0.0.1") + testSetup.restServer = WakuRestServerRef.init(restAddress, restPort).tryGet() + restPort = testSetup.restServer.httpServer.address.port + # update with bound port for restClient use + + installLightPushRequestHandler(testSetup.restServer.router, testSetup.pushNode) + + testSetup.restServer.start() + + testSetup.restClient = newRestHttpClient(initTAddress(restAddress, restPort)) + + return testSetup + +proc shutdown(self: RestLightPushTest) {.async.} = + await self.restServer.stop() + await self.restServer.closeWait() + await allFutures( + self.serviceNode.stop(), self.pushNode.stop(), self.consumerNode.stop() + ) + +suite "Waku v2 Rest API - lightpush": + asyncTest "Push message with proof": + let restLightPushTest = await RestLightPushTest.init() + + let message: RelayWakuMessage = fakeWakuMessage( + contentTopic = DefaultContentTopic, + payload = toBytes("TEST-1"), + proof = toBytes("proof-test"), + ) + .toRelayWakuMessage() + + check message.proof.isSome() + + let requestBody = + PushRequest(pubsubTopic: some(DefaultPubsubTopic), message: message) + + let response = + await restLightPushTest.restClient.sendPushRequest(body = requestBody) + + ## Validate that the push request failed because the node is not + ## connected to other node but, doesn't fail because of not properly + ## handling the proof message attribute within the REST request. + check: + response.status == 505 + response.data.statusDesc == some("No peers for topic, skipping publish") + response.data.relayPeerCount == none[uint32]() + + asyncTest "Push message request": + # Given + let restLightPushTest = await RestLightPushTest.init() + + restLightPushTest.consumerNode.subscribe( + (kind: PubsubSub, topic: DefaultPubsubTopic) + ) + restLightPushTest.serviceNode.subscribe( + (kind: PubsubSub, topic: DefaultPubsubTopic) + ) + require: + toSeq(restLightPushTest.serviceNode.wakuRelay.subscribedTopics).len == 1 + + # When + let message: RelayWakuMessage = fakeWakuMessage( + contentTopic = DefaultContentTopic, payload = toBytes("TEST-1") + ) + .toRelayWakuMessage() + + let requestBody = + PushRequest(pubsubTopic: some(DefaultPubsubTopic), message: message) + let response = await restLightPushTest.restClient.sendPushRequest(requestBody) + + echo "response", $response + + # Then + check: + response.status == 200 + response.data.relayPeerCount == some(1.uint32) + + await restLightPushTest.shutdown() + + asyncTest "Push message bad-request": + # Given + let restLightPushTest = await RestLightPushTest.init() + + restLightPushTest.serviceNode.subscribe( + (kind: PubsubSub, topic: DefaultPubsubTopic) + ) + require: + toSeq(restLightPushTest.serviceNode.wakuRelay.subscribedTopics).len == 1 + + # When + let badMessage1: RelayWakuMessage = fakeWakuMessage( + contentTopic = DefaultContentTopic, payload = toBytes("") + ) + .toRelayWakuMessage() + let badRequestBody1 = + PushRequest(pubsubTopic: some(DefaultPubsubTopic), message: badMessage1) + + let badMessage2: RelayWakuMessage = + fakeWakuMessage(contentTopic = "", payload = toBytes("Sthg")).toRelayWakuMessage() + let badRequestBody2 = + PushRequest(pubsubTopic: some(DefaultPubsubTopic), message: badMessage2) + + let badRequestBody3 = + PushRequest(pubsubTopic: none(PubsubTopic), message: badMessage2) + + # var response: RestResponse[PushResponse] + + var response = await restLightPushTest.restClient.sendPushRequest(badRequestBody1) + + # Then + check: + response.status == 400 + response.data.statusDesc.isSome() + response.data.statusDesc.get().startsWith("Invalid push request") + + # when + response = await restLightPushTest.restClient.sendPushRequest(badRequestBody2) + + # Then + check: + response.status == 400 + response.data.statusDesc.isSome() + response.data.statusDesc.get().startsWith("Invalid push request") + + # when + response = await restLightPushTest.restClient.sendPushRequest(badRequestBody3) + + # Then + check: + response.data.statusDesc.isSome() + response.data.statusDesc.get().startsWith("Invalid push request") + + await restLightPushTest.shutdown() + + asyncTest "Request rate limit push message": + # Given + let budgetCap = 3 + let tokenPeriod = 500.millis + let restLightPushTest = await RestLightPushTest.init((budgetCap, tokenPeriod)) + + restLightPushTest.consumerNode.subscribe( + (kind: PubsubSub, topic: DefaultPubsubTopic) + ) + restLightPushTest.serviceNode.subscribe( + (kind: PubsubSub, topic: DefaultPubsubTopic) + ) + require: + toSeq(restLightPushTest.serviceNode.wakuRelay.subscribedTopics).len == 1 + + # When + let pushProc = proc() {.async.} = + let message: RelayWakuMessage = fakeWakuMessage( + contentTopic = DefaultContentTopic, payload = toBytes("TEST-1") + ) + .toRelayWakuMessage() + + let requestBody = + PushRequest(pubsubTopic: some(DefaultPubsubTopic), message: message) + let response = await restLightPushTest.restClient.sendPushRequest(requestBody) + + echo "response", $response + + # Then + check: + response.status == 200 + response.data.relayPeerCount == some(1.uint32) + + let pushRejectedProc = proc() {.async.} = + let message: RelayWakuMessage = fakeWakuMessage( + contentTopic = DefaultContentTopic, payload = toBytes("TEST-1") + ) + .toRelayWakuMessage() + + let requestBody = + PushRequest(pubsubTopic: some(DefaultPubsubTopic), message: message) + let response = await restLightPushTest.restClient.sendPushRequest(requestBody) + + echo "response", $response + + # Then + check: + response.status == 429 + response.data.statusDesc.isSome() # Ensure error status description is present + response.data.statusDesc.get().startsWith( + "Request rejected due to too many requests" + ) # Check specific error message + + await pushProc() + await pushProc() + await pushProc() + await pushRejectedProc() + + await sleepAsync(tokenPeriod) + + for runCnt in 0 ..< 3: + let startTime = Moment.now() + for sendCnt in 0 ..< budgetCap: + await pushProc() + + let endTime = Moment.now() + let elapsed: Duration = (endTime - startTime) + await sleepAsync(tokenPeriod - elapsed + 10.millis) + + await restLightPushTest.shutdown() diff --git a/tests/wakunode_rest/test_rest_lightpush_legacy.nim b/tests/wakunode_rest/test_rest_lightpush_legacy.nim index 61d1de88d..8176aed7a 100644 --- a/tests/wakunode_rest/test_rest_lightpush_legacy.nim +++ b/tests/wakunode_rest/test_rest_lightpush_legacy.nim @@ -274,28 +274,3 @@ suite "Waku v2 Rest API - lightpush": await sleepAsync(tokenPeriod - elapsed + 10.millis) await restLightPushTest.shutdown() - - ## TODO: Re-work this test when lightpush protocol change is done: https://github.com/waku-org/pm/issues/93 - ## This test is similar when no available peer exists for publish. Currently it is returning success, - ## that makes this test not useful. - # asyncTest "Push message request service not available": - # # Given - # let restLightPushTest = await RestLightPushTest.init() - - # # When - # let message : RelayWakuMessage = fakeWakuMessage(contentTopic = DefaultContentTopic, - # payload = toBytes("TEST-1")).toRelayWakuMessage() - - # let requestBody = PushRequest(pubsubTopic: some("NoExistTopic"), - # message: message) - # let response = await restLightPushTest.client.sendPushRequest(requestBody) - - # echo "response", $response - - # # Then - # check: - # response.status == 503 - # $response.contentType == $MIMETYPE_TEXT - # response.data == "Failed to request a message push: Can not publish to any peers" - - # await restLightPushTest.shutdown() diff --git a/waku/waku_api/rest/lightpush/handlers.nim b/waku/waku_api/rest/lightpush/handlers.nim index 601aab74c..cafcd89d2 100644 --- a/waku/waku_api/rest/lightpush/handlers.nim +++ b/waku/waku_api/rest/lightpush/handlers.nim @@ -26,18 +26,15 @@ logScope: const FutTimeoutForPushRequestProcessing* = 5.seconds -const NoPeerNoDiscoError = - RestApiResponse.serviceUnavailable("No suitable service peer & no discovery method") - -const NoPeerNoneFoundError = - RestApiResponse.serviceUnavailable("No suitable service peer & none discovered") +const NoPeerNoDiscoError = "No suitable service peer & no discovery method" +const NoPeerNoneFoundError = "No suitable service peer & none discovered" proc useSelfHostedLightPush(node: WakuNode): bool = return node.wakuLightPush != nil and node.wakuLightPushClient == nil proc convertErrorKindToHttpStatus(statusCode: LightpushStatusCode): HttpCode = ## Lightpush status codes are matching HTTP status codes by design - return HttpCode(statusCode.int32) + return toHttpCode(statusCode.int).get(Http500) proc makeRestResponse(response: WakuLightPushResult): RestApiResponse = var httpStatus: HttpCode = Http200 @@ -72,10 +69,11 @@ proc installLightPushRequestHandler*( debug "post", ROUTE_LIGHTPUSH, contentBody let req: PushRequest = decodeRequestBody[PushRequest](contentBody).valueOr: - return RestApiResponse.badRequest("Invalid push request: " & $error) + return + makeRestResponse(lightpushResultBadRequest("Invalid push request! " & $error)) let msg = req.message.toWakuMessage().valueOr: - return RestApiResponse.badRequest("Invalid message: " & $error) + return makeRestResponse(lightpushResultBadRequest("Invalid message! " & $error)) var toPeer = none(RemotePeerInfo) if useSelfHostedLightPush(node): @@ -83,19 +81,23 @@ proc installLightPushRequestHandler*( else: let aPeer = node.peerManager.selectPeer(WakuLightPushCodec).valueOr: let handler = discHandler.valueOr: - return NoPeerNoDiscoError + return makeRestResponse(lightpushResultServiceUnavailable(NoPeerNoDiscoError)) let peerOp = (await handler()).valueOr: - return RestApiResponse.internalServerError("No value in peerOp: " & $error) + return makeRestResponse( + lightpushResultInternalError("No value in peerOp: " & $error) + ) peerOp.valueOr: - return NoPeerNoneFoundError + return + makeRestResponse(lightpushResultServiceUnavailable(NoPeerNoneFoundError)) toPeer = some(aPeer) let subFut = node.lightpushPublish(req.pubsubTopic, msg, toPeer) if not await subFut.withTimeout(FutTimeoutForPushRequestProcessing): error "Failed to request a message push due to timeout!" - return RestApiResponse.serviceUnavailable("Push request timed out") + return + makeRestResponse(lightpushResultServiceUnavailable("Push request timed out")) return makeRestResponse(subFut.value()) diff --git a/waku/waku_lightpush/common.nim b/waku/waku_lightpush/common.nim index 356ccf8f2..4c2984e8f 100644 --- a/waku/waku_lightpush/common.nim +++ b/waku/waku_lightpush/common.nim @@ -42,6 +42,12 @@ func lightpushSuccessResult*(relayPeerCount: uint32): WakuLightPushResult = func lightpushResultInternalError*(msg: string): WakuLightPushResult = return err((LightpushStatusCode.INTERNAL_SERVER_ERROR, some(msg))) +func lightpushResultBadRequest*(msg: string): WakuLightPushResult = + return err((LightpushStatusCode.BAD_REQUEST, some(msg))) + +func lightpushResultServiceUnavailable*(msg: string): WakuLightPushResult = + return err((LightpushStatusCode.SERVICE_NOT_AVAILABLE, some(msg))) + func lighpushErrorResult*( statusCode: LightpushStatusCode, desc: Option[string] ): WakuLightPushResult =