This repository contains integration tests for the Mass Labs Relay service, which is part of a decentralized marketplace system built on Ethereum.
pystoretest provides comprehensive testing of the relay service, including:
- Shop management (creation, configuration, and updates)
- Inventory tracking and management
- Listing creation and modification
- Order processing and payment handling
- Authentication with keycards and wallet signatures
- Multi-client synchronization
- Nix - Used for dependency management
- Local Ethereum node (e.g., Anvil) for testing blockchain interactions
- Running relay service instance
- Clone the repository
- Create your environment configuration:
cp .env.example .env
- Edit the configuration file:
$EDITOR .env
- Start the Nix shell:
nix-shell
- Run the tests:
make test
The .env
file should contain the following configuration variables:
RELAY_HTTP_ADDRESS
- The HTTP address of the relay service (default: http://localhost:4444)RELAY_PING
- Ping interval in seconds (default: 0.250)ETH_PRIVATE_KEY
- Private key for Ethereum testing (use one of the Anvil test accounts)ETH_RPC_URL
- URL for Ethereum RPC node (default: http://localhost:8545)MASS_CONTRACTS
- Path to Mass Labs contract ABIs and addresses
- Account management for testing with automatic nonce handling
- Client fixtures for easy test setup
- Support for multiple simultaneous clients
- Guest account testing
- Shop state inspection and manipulation
- Order creation and processing
- CoinGecko integration for pricing tests
To run specific test files:
pytest test_specific_file.py -v
To run a single test:
pytest -v -k test_name
This document describes the refactoring of the large RelayClient
class into a more modular, maintainable architecture with added persistence capabilities.
The original client.py
file was over 2400 lines long and contained multiple responsibilities:
- WebSocket connection management
- Authentication handling
- Patch application logic
- Shop state management
- Ethereum contract interactions
- Message handling
- Business logic for shop operations
This made the code difficult to maintain, test, and extend.
The refactoring breaks down the monolithic client into focused, single-responsibility modules:
Classes:
ShopPersistence
: Handles saving/loading shop data to/from diskPatchLogger
: Logs patch operations for debugging and replayStateManager
: Manages shop state with caching and persistence
Features:
- Automatic CBOR serialization of shop data
- Metadata tracking for quick queries
- Session-based patch logging
- Dirty state tracking for efficient saves
Classes:
ConnectionManager
: Handles WebSocket connections and message routingAuthenticationManager
: Manages authentication flow with the relay
Features:
- Automatic reconnection with exponential backoff
- Message handler registration system
- Request/response tracking
- Rate limiting handling
Classes:
PatchHandler
: Applies patches to shop state
Features:
- Isolated patch application logic
- Error handling for invalid patches
- Support for all patch operations (ADD, REMOVE, REPLACE, etc.)
Classes:
RefactoredRelayClient
: Main client that orchestrates all components
Features:
- Clean, focused interface
- Automatic state persistence
- Patch batching support
- Simplified error handling
Each module has a single, well-defined responsibility:
- Persistence handles data storage
- Connection manager handles networking
- Patch handler handles state updates
- Main client orchestrates operations
from refactored_client import RefactoredRelayClient
# Create client with persistence
client = RefactoredRelayClient(
name="MyShop",
wallet_private_key="0x...",
data_dir="shop_data",
log_dir="patch_logs"
)
# Register shop (creates persistent storage)
shop_id = client.register_shop()
# Login and create content
client.login()
client.create_shop_manifest()
listing_id = client.create_listing("Product", 1000)
client.change_inventory(listing_id, 10)
# State is automatically saved
client.close()
# Use batching for performance
client.start_batch()
client.change_inventory(listing1_id, -1)
client.change_inventory(listing2_id, -1)
client.change_inventory(listing3_id, -1)
client.flush_batch() # Single network request
# Manual state management
client.save_state()
shop = client.load_state()
# Access persistence directly
if client.persistence.shop_exists(shop_id):
metadata = client.persistence.get_shop_metadata(shop_id)
print(f"Shop has {metadata['listings_count']} listings")
pystoretest/
├── client.py # Original monolithic client (2483 lines)
├── refactored_client.py # New main client (400+ lines)
├── persistence.py # Data persistence layer (200+ lines)
├── connection_manager.py # Connection management (300+ lines)
├── patch_handler.py # Patch application logic (400+ lines)
├── example_usage.py # Usage examples
└── README_REFACTORING.md # This file