Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 0 additions & 20 deletions .github/workflows/enforce-develop-pr.yml

This file was deleted.

20 changes: 0 additions & 20 deletions .github/workflows/enforce-staging-pr.yml

This file was deleted.

9 changes: 9 additions & 0 deletions .vscode/tasks.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,15 @@
"group": "test",
"detail": "Runs all Behave tests and saves JUnit results to the host machine"
},
{
"label": "Docker: Run Smoke Tests",
"type": "shell",
"command": "docker run --rm --env-file .env -v ${workspaceFolder}/reports:/app/reports behave-test test/features -t @smoke --junit --junit-directory /app/reports",
"dependsOn": ["Docker: Build Behave Image"],
"problemMatcher": [],
"group": "test",
"detail": "Runs only Behave tests tagged with @smoke and saves JUnit results to the host machine"
},
{
"label": "Docker: Run Finnhub REST API Test",
"type": "shell",
Expand Down
26 changes: 24 additions & 2 deletions test/features/ankreth_rpc_api_test.feature
Original file line number Diff line number Diff line change
@@ -1,10 +1,32 @@
Feature: Ethereum RPC Block Number Retrieval

@smoke
Scenario: Validate latest block number response
Given I set the Ethereum RPC endpoint
When I request the latest block number
Then the response status should be 200
Then the response status should be "200"
Then I should get a valid hex response
And the block number should be a positive integer
And the block number should match expected range
And no error should occur during the request
And no error should occur during the request

Scenario: Validate block number retrieval with invalid endpoint
Given I set an invalid Ethereum RPC endpoint
When I request the latest block number
Then the response status should not be "200"
And I should receive an error message indicating the failure
And no valid block number should be returned

Scenario: Validate block number retrieval with malformed response
Given I set the Ethereum RPC endpoint with a malformed response
When I request the latest block number
Then the response status should not be "200"
And I should receive an error message indicating the malformed response
And no valid block number should be returned

Scenario: Validate block number retrieval with network timeout
Given I set the Ethereum RPC endpoint with a network timeout
When I request the latest block number
Then the response status should not be "200"
And I should receive an error message indicating a network timeout
And no valid block number should be returned
20 changes: 20 additions & 0 deletions test/features/bitstamp_websocket_api_test.feature
Original file line number Diff line number Diff line change
@@ -1,7 +1,27 @@
Feature: Bitstamp WebSocket BTC price feed

@smoke
Scenario: Receive real-time BTC/USD trade price
Given the WebSocket connection to Bitstamp is established
When I subscribe to BTC/USD trades
Then I should receive a trade event
And the price should be a valid number

Scenario: Receive real-time BTC/USD order book updates
Given the WebSocket connection to Bitstamp is established
When I subscribe to BTC/USD order book updates
Then I should receive an order book update event
And the order book should contain valid bids and asks

Scenario: Handle WebSocket reconnection
Given the WebSocket connection to Bitstamp is established
When the WebSocket connection is lost
And I wait for reconnection
Then the WebSocket connection should be re-established
And I should still receive BTC/USD trade events

Scenario: Handle invalid messages
Given the WebSocket connection to Bitstamp is established
When I receive an invalid message format
Then I should handle the error gracefully
And no unhandled exceptions should occur
13 changes: 12 additions & 1 deletion test/features/environment.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,18 @@
import os
import glob
from behave import fixture, use_fixture

def before_all(context):
pass
reports_dir = os.path.join(os.getcwd(), "reports")
if os.path.exists(reports_dir):
files = glob.glob(os.path.join(reports_dir, "*"))
for f in files:
try:
os.remove(f)
print(f"🗑️ Removed report file: {os.path.basename(f)}")
except Exception as e:
print(f"⚠️ Could not remove {f}: {e}")
print("📂 Reports directory cleared.")

def after_all(context):
pass
Expand Down
22 changes: 21 additions & 1 deletion test/features/finnhub_rest_api_test.feature
Original file line number Diff line number Diff line change
@@ -1,8 +1,28 @@
Feature: Get stock quote from Finnhub API

@smoke
Scenario: Successful stock quote fetch for AAPL
Given I have a valid API key
When I request a stock quote for "AAPL"
Then the response status should be 200
Then the response status should be "200"
And the current price should be a positive number
And the response should contain required quote fields

Scenario: Unsuccessful stock quote fetch for invalid symbol
Given I have a valid API key
When I request a stock quote for "INVALID"
Then the response status should be "200"

Scenario: Successful stock quote fetch for MSFT
Given I have a valid API key
When I request a stock quote for "MSFT"
Then the response status should be "200"
And the current price should be a positive number
And the response should contain required quote fields

Scenario: Successful stock quote fetch for GOOGL
Given I have a valid API key
When I request a stock quote for "GOOGL"
Then the response status should be "200"
And the current price should be a positive number
And the response should contain required quote fields
26 changes: 25 additions & 1 deletion test/features/rickandmorty_graphql_api_test.feature
Original file line number Diff line number Diff line change
@@ -1,9 +1,33 @@
Feature: Rick and Morty GraphQL character lookup

@smoke
Scenario: Query character by ID and validate fields
Given I prepare a GraphQL query for character ID 1
When I send the GraphQL request
Then the response status should be 200
Then the response status should be "200"
Then the response should return character "Rick Sanchez"
And the character should belong to the "Human" species
And the response should contain a valid ID and status

Scenario: Query character by name and validate fields
Given I prepare a GraphQL query for character name "Morty Smith"
When I send the GraphQL request
Then the response status should be "200"
Then the response should return character "Morty Smith"
And the character should belong to the "Human" species
And the response should contain a valid ID and status

Scenario: Query character by status and validate fields
Given I prepare a GraphQL query for character status "Alive"
When I send the GraphQL request
Then the response status should be "200"
Then the response should return characters with status "Alive"
And the response should contain valid IDs and statuses

Scenario: Query character by species and validate fields
Given I prepare a GraphQL query for character species "Alien"
When I send the GraphQL request
Then the response status should be "200"
Then the response should return characters of species "Alien"
And each character should have a valid ID and status
And the response should not contain any characters of species "Human"
117 changes: 89 additions & 28 deletions test/features/steps/ankreth_rpc_api_test_steps.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,45 +3,106 @@
from hamcrest import assert_that, greater_than, starts_with
import sys
import os

# Add project root to sys.path for imports
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '../../')))
from utils.ankr_assertions import assert_is_hex, assert_is_integer

API_KEY = os.getenv('ANKRETH_API_KEY')
RPC_URL = f"https://rpc.ankr.com/eth/{API_KEY}"

@given("I set the Ethereum RPC endpoint")
def step_given_rpc(context):

@given('I set the Ethereum RPC endpoint')
def step_set_valid_endpoint(context):
context.url = RPC_URL
context.payload = {


@given('I set an invalid Ethereum RPC endpoint')
def step_set_invalid_endpoint(context):
context.url = "https://invalid-ethereum-endpoint.com"


@given('I set the Ethereum RPC endpoint with a malformed response')
def step_set_malformed_endpoint(context):
context.url = "http://localhost:9999/malformed" # Replace with mock URL if needed


@given('I set the Ethereum RPC endpoint with a network timeout')
def step_set_timeout_endpoint(context):
context.url = "http://10.255.255.1" # Non-routable IP to simulate timeout


@when('I request the latest block number')
def step_request_latest_block_number(context):
payload = {
"jsonrpc": "2.0",
"method": "eth_blockNumber",
"params": [],
"id": 1
}

@when("I request the latest block number")
def step_when_request_block(context):
try:
context.response = requests.post(context.url, json=context.payload)
data = context.response.json()
context.raw_hex = data.get("result")
context.block_number = int(context.raw_hex, 16)
context.exception = None
except Exception as e:
context.exception = e

@then("I should get a valid hex response")
def step_then_valid_hex(context):
assert_is_hex(context.raw_hex)

@then("the block number should be a positive integer")
def step_then_positive_int(context):
assert_is_integer(context.block_number)

@then("the block number should match expected range")
def step_then_expected_range(context):
assert_that(context.block_number, greater_than(1000000))

@then("no error should occur during the request")
def step_then_no_error(context):
assert context.exception is None, f"Unexpected exception: {context.exception}"
context.response = requests.post(context.url, json=payload, timeout=5)
context.result = context.response.json()
context.error_message = None
except requests.exceptions.RequestException as e:
context.response = None
context.result = None
context.error_message = str(e)


@then('the response status should be "200"')
def step_check_status_200(context):
assert context.response is not None
assert_that(context.response.status_code, 200)


@then('the response status should not be "200"')
def step_check_status_not_200(context):
if context.response:
assert_that(context.response.status_code, is_not(200))
else:
assert context.error_message is not None


@then('I should get a valid hex response')
def step_check_valid_hex(context):
result = context.result
assert result is not None
hex_value = result.get('result')
assert_is_hex(hex_value)


@then('the block number should be a positive integer')
def step_check_positive_integer(context):
block_hex = context.result.get('result')
block_number = int(block_hex, 16)
context.block_number = block_number
assert_is_integer(block_number)
assert_that(block_number, greater_than(0))


@then('the block number should match expected range')
def step_check_expected_range(context):
assert 10_000_000 <= context.block_number <= 30_000_000


@then('no error should occur during the request')
def step_check_no_error(context):
assert context.result is not None
assert 'error' not in context.result


@then('I should receive an error message indicating the failure')
@then('I should receive an error message indicating the malformed response')
@then('I should receive an error message indicating a network timeout')
def step_check_error_message(context):
assert context.result is None or 'error' in context.result or context.error_message is not None


@then('no valid block number should be returned')
def step_no_valid_block_returned(context):
if context.result:
assert 'result' not in context.result or context.result['result'] in (None, '')
else:
assert context.response is None or context.response.status_code != 200
Loading