Reference

Testing

Testing Guide

This document describes how to run tests for the eIOU Docker node.

Table of Contents

  1. Quick Start
  2. Test Types
  3. Unit Test Inventory
  4. Integration Test Inventory
  5. Running Unit Tests
  6. Running Integration Tests
  7. Test Structure
  8. Writing New Tests
  9. Prerequisites
  10. Troubleshooting
  11. Continuous Integration

Quick Start

cd eiou-docker/files

# Install dependencies (first time)
composer install

# Run all tests
composer test

# Run with verbose output (shows test names)
composer test-verbose

# Debug mode (stops on first failure)
composer test-debug

Test Types

Unit Tests (PHPUnit)

Unit tests validate individual PHP classes and methods in isolation.

  • Location: tests/Unit/
  • Framework: PHPUnit 11
  • Total: 5000+ tests, 15000+ assertions across 280+ files in 18 test categories (Api, Cli, Core, Database, Events, Exceptions, Formatters, Gui, Payback, Processors, Repositories, Sandbox, Schemas, Security, Services, Startup, Utils, Validators)

Integration Tests (Shell)

Integration tests validate the complete system behavior using Docker containers.

  • Location: tests/testfiles/ (per-test scripts) and tests/ (runners and benchmarks)
  • Runner: ./run-all-tests.sh
  • Coverage: contact lifecycle, transactions, P2P routing (fast and best-fee modes), sync & chain-integrity, REST API, CLI, backups, identity / wallet setup, process lifecycle, networking, security
  • Inventory: see Integration Test Inventory below for the per-file table

Topologies:

  • http4 / https4 / tor4 — 4-node linear chain (standard transaction and routing tests)
  • collisions — 12-node mesh topology with randomized fees and dead-end nodes (best-fee routing, path selection, deadlock prevention, cascade cancel)

Unit Test Inventory

The per-file tables below are a curated overview, not a mechanical dump of every test file. The repo’s actual count is the authoritative number — composer test -- --list-tests | wc -l for tests, the PHPUnit summary line for assertions. The inventory may lag behind when new test files are added; the headings (Security, Utils, …) always reflect every directory under tests/Unit/.

Security Tests (tests/Unit/Security/)

Test File Tests Coverage
BIP39Test.php 22 Mnemonic generation (12/24 words), validation, seed derivation, key pair generation, auth code derivation
KeyEncryptionTest.php 9 AES-256-GCM encryption availability, info, secure clear, error handling
PayloadEncryptionTest.php 17 ECDH + AES-256-GCM E2E encryption: round-trip, tampering detection, wrong-key rejection, cross-node simulation, encrypt-then-sign workflow, secp256k1 compatibility
MessageSignatureTest.php 13 Signature signing/verification: SHA-256 signing pinned (verifies under SHA-256, fails under SHA-1), SHA-1 verify fallback for legacy, version-2 downgrade pin (sha256Only), message-version parsing, field-commitment stability across amount forms and field-boundary non-confusability, tamper/wrong-key/garbage rejection
E2eAllMessagesTest.php 13 End-to-end encryption applied across all message types (type-indistinguishable on the wire)
E2eContactDescriptionTest.php 10 Contact-request description handling under the E2E and signed-content rules
MariaDbEncryptionTest.php 5 MariaDB transparent data encryption (TDE) key derivation and at-rest coverage
VolumeEncryptionTest.php 13 Optional volume-passphrase encryption of the master key (Argon2id + AES-256-GCM)
LowFindingsTest.php 26 Regression coverage for low-severity hardening findings from the security audits
TorKeyDerivationTest.php 10 Ed25519 key derivation, .onion address generation, deterministic keys

Utils Tests (tests/Unit/Utils/)

Test File Tests Coverage
InputValidatorTest.php 40+ All 18 validation methods: amount, currency, address, txid, contact name, fee percent, credit limit, public key, signature, memo, etc.
SecurityTest.php 30 XSS prevention (htmlEncode, jsEncode), input sanitization, password hashing/verification, CSRF tokens, email/URL/IP validation, filename sanitization, timing-safe comparison
AddressValidatorTest.php 20 HTTP/HTTPS/Tor address detection, transport type identification, address categorization
LoggerTest.php 30 Unified Logger facade: interface compliance, singleton, all log levels, logException, context passing, DebugService bridging, exception isolation, sensitive data masking
SecureLoggerTest.php 18 SecureLogger backend: sensitive data masking (passwords, authcodes, API keys, emails, credit cards, SSN, mnemonics), log levels, file rotation
AdaptivePollerTest.php 17 Polling interval calculation, state management, reset, force interval bounds clamping
SecureSeedphraseDisplayTest.php 30+ Secure file display, availability check, TTL, cleanup

API Tests (tests/Unit/Api/)

Test File Tests Coverage
ApiControllerTest.php 41 API endpoint routing, authentication, error handling, all wallet/contacts/system endpoints

Core Tests (tests/Unit/Core/)

Test File Tests Coverage
ErrorCodesTest.php 20 HTTP status mapping, error titles, code validation, constant verification
ConstantsTest.php 43 Application constants validation, hash algorithms, transport modes, status codes
ApplicationTest.php 26 Singleton pattern, service delegation, path getters, CLI mode
DatabaseContextTest.php 29 Config management, DB credentials, initialization state
ErrorHandlerTest.php 30 Error/exception handling, responses, request ID management
UserContextTest.php 46 User data, addresses, wallet validation, config defaults
WalletTest.php 19 Seed extraction, config defaults, hostname validation

Exceptions Tests (tests/Unit/Exceptions/)

Test File Tests Coverage
ServiceExceptionTest.php 41 Service exception hierarchy (ServiceException, FatalServiceException, RecoverableServiceException, ValidationServiceException)

Database Tests (tests/Unit/Database/)

Test File Tests Coverage
DatabaseSchemaTest.php 67 Schema validation for all 14 tables, column types, constraints, indexes
DatabaseSetupTest.php 15+ Migration execution, column migrations, idempotency
P2pSenderRepositoryTest.php 20+ Multi-path upstream sender tracking for RP2P forwarding
CapacityReservationRepositoryTest.php 12 Capacity reservation CRUD, total reserved queries, release/commit by hash, TTL cleanup
RouteCancellationRepositoryTest.php 8 Route cancellation audit trail, acknowledgment, hash queries, TTL cleanup
PdoConnectionTest.php 10+ Connection creation, DSN format, PDO options

Processors Tests (tests/Unit/Processors/)

Test File Tests Coverage
AbstractMessageProcessorTest.php 30+ Base processor, signal handling, lockfile management, shutdown
CleanupMessageProcessorTest.php 15+ Cleanup message handling, log intervals, polling config
ContactStatusProcessorTest.php 35+ Ping/pong, address priority (Tor > HTTPS > HTTP), chain validation
P2pMessageProcessorTest.php 20+ P2P message queue processing, fast polling config
TransactionMessageProcessorTest.php 20+ Transaction processing, lockfile paths

Repositories Tests (tests/Unit/Repositories/)

Test File Tests Coverage
PaymentRequestRepositoryTest.php 16 createRequest, getByRequestId, getPendingIncoming, getAllIncoming/getAllOutgoing (with limit), updateStatus (with extra fields), countPendingIncoming (including query-failure → 0)
TransactionRepositoryTest.php 24 Status/type constants, previous-txid filtering, refund-cap and archive queries, composed-insert failure handling, the auto-accept proof gate excluding contact rows (hasCompletedTransactionBetween), and the claimCompletion terminal-state guard (no resurrecting cancelled/rejected/expired/failed rows)
AbstractRepositoryTest.php 30+ Base CRUD operations, column validation, transactions, JSON decoding
AddressRepositoryTest.php 25+ Address management, lookups, pubkey hashing, transport types
ApiKeyRepositoryTest.php 25+ API key CRUD, permission checks, rate limit logging
BalanceRepositoryTest.php 30+ Balance operations, sent/received tracking, currency grouping
ContactRepositoryTest.php 40+ Contact management, status transitions, lookups
DeadLetterQueueRepositoryTest.php 25+ DLQ operations, status transitions, statistics
DebugRepositoryTest.php 20+ Debug logging, pruning, log levels
DeliveryMetricsRepositoryTest.php 25+ Delivery metrics tracking, aggregation, cleanup
HeldTransactionRepositoryTest.php 44 Held transaction lifecycle, sync status, retry management
MessageDeliveryRepositoryTest.php 38 Message delivery tracking, retry queue, statistics
P2pRepositoryTest.php 48 P2P request management, status updates, statistics
RateLimiterRepositoryTest.php 30 Rate limiting operations, blocking, cleanup
Rp2pRepositoryTest.php 25+ RP2P request management, queries, cleanup
TransactionChainRepositoryTest.php 20+ Chain integrity verification, gap detection
TransactionContactRepositoryTest.php 15+ Contact transaction queries, balance calculation
TransactionRecoveryRepositoryTest.php 20+ Recovery operations, stuck transactions, claiming
TransactionStatisticsRepositoryTest.php 20+ Transaction statistics, daily counts, type grouping

Services Tests (tests/Unit/Services/)

Test File Tests Coverage
TransactionServiceTest.php 4 Txid generation algorithm, SHA-256 hashing, determinism
ContactServiceTest.php 4 Contact status constants, name length limits, default settings, online status
ApiAuthServiceTest.php 14 HMAC-SHA256 signature generation, string-to-sign building, request header parsing, client IP detection
RateLimiterServiceTest.php 7 Rate limiting logic, client IP detection (Cloudflare, X-Forwarded-For), test mode bypass
BalanceServiceTest.php 23 Contact balance conversion, user total balance, contact balance retrieval, batch balance operations, currency conversion, edge cases
DatabaseLockingServiceTest.php 40 MySQL advisory locks (GET_LOCK/RELEASE_LOCK/IS_FREE_LOCK), lock acquisition/release, timeout handling, lock name sanitization, held locks tracking
ChainOperationsServiceTest.php 16 Chain integrity verification, previous txid lookup, chain repair coordination, sync service injection, exception handling
ChainVerificationServiceTest.php 16 Chain verification logic, gap detection, conflict resolution
HeldTransactionServiceTest.php 35 Transaction hold/resume lifecycle, sync status tracking, previous txid updates, statistics, event handling, chain integrity checks, P2P expiry-aware resume (status + timestamp), sync timeout vs P2P expiration invariant
TransactionRecoveryServiceTest.php 28 Stuck transaction recovery, manual resolution (retry/cancel/complete), recovery statistics, exception handling
TransactionValidationServiceTest.php 35 Transaction validation logic, required fields, amount validation
BackupServiceTest.php 22 formatBytes utility, getNextScheduledBackup date logic, boundary conditions
ApiKeyServiceTest.php 44 CLI API key management, permission validation
CleanupServiceTest.php 23 Expired message processing, cleanup scheduling
ContactStatusServiceTest.php 27 Ping/pong handling, contact status updates
MessageDeliveryServiceTest.php 55 Message delivery with retries, dead letter queue
WalletServiceTest.php 22 Wallet key operations, key detection
CliServiceTest.php 25+ CLI command handling, output formatting
DebugServiceTest.php 15+ Debug context, error logging setup
MessageServiceTest.php 25+ Message processing, validation, routing
P2pServiceTest.php 63 P2P routing logic, fund availability via capacity reservations, matching, fee calculation
Rp2pServiceTest.php 46 RP2P relay logic, fee calculation, two-phase relay selection, race condition coverage
RouteCancellationServiceTest.php 18 Route cancellation for unselected candidates, partial route_cancel (multi-route safe acknowledge), full cancel (P2P cancel + reservation release + downstream propagation), hop budget (geometric distribution), capacity reservation release
PaymentRequestServiceTest.php 27 Full lifecycle: create (amount/currency validation, contact lookup, address resolution, delivery failure non-fatal, tor address priority), approve (sendEiou integration, txid extraction, status update, response message), decline, cancel, handleIncomingRequest (idempotent, contact name lookup, invalid currency skip), handleIncomingResponse (approved with txid, declined), getAllForDisplay, countPendingIncoming, #[PluginCallable] attribute presence on create. Note: 8 create tests skip when bcmath extension is not installed on the host (they run inside Docker).
SendOperationServiceTest.php 20+ Send operations with locking, message delivery
ServiceContainerTest.php 20+ Singleton pattern, dependency management, lazy loading
SyncServiceTest.php 55 Synchronization operations, contact/transaction sync, signature verification (standard and contact transactions), tamper/wrong-key rejection, signed-message reconstruction round-trips including the plaintext version-2 case (signed-without-commitment verifies, signed-with-commitment fails reconstruction), chain-conflict resolution, and re-sign-on-restore
TransactionProcessingServiceTest.php 27 Transaction processing, claiming, P2P handling, and the per-message fault isolation / poison-message quarantine path (repeated-failure quarantine flags needs_manual_review rather than silently dropping)
UpdateCheckServiceTest.php 32 Version comparison (isNewerVersion), prerelease ordering, v-prefix handling, getStatus structure, cache-miss behavior, markdownToHtml (headings, lists, bold, italic, inline code, code blocks, links, XSS escaping, horizontal rules, paragraphs, mixed content, list type switching, unclosed code blocks), shouldShowWhatsNew (fresh install seeding, upgrade detection, post-dismissal), dismissWhatsNew (file structure), getReleaseNotes (graceful failure, v-prefix stripping, response structure). Note: filesystem-dependent tests (shouldShowWhatsNew, dismissWhatsNew) skip outside Docker when /etc/eiou/config is not writable; getReleaseNotes structure test skips when GitHub is unreachable.
WalletOutboundServiceTest.php 16 Sandbox bridge send(recipient, amount, currency, description?) — argument-order matches the eiou send CLI. Caller-id requirement enforcement, argument validation (currency shape, amount positive/decimal, recipient pattern, description cap), happy-path sendEiou integration, null-txid case (P2P queued route), downstream refusal rewrap, exception rewrap, PluginCallerAware contract, #[PluginCallable] attribute presence, caller-id clearable between calls. Note: description is the free-form operator-facing text (the [description] arg on eiou send), distinct from the internal transactions.memo routing-hash field.
TransactionRefundServiceTest.php 17 Shared refund core refund(txid, initiator, amount?) and getRefundSummary(txid). Malformed and unknown txid rejection, received-only gate (a sent-only txid is refused so funds cannot be pushed outward), settled-status gate, no-sender-address rejection; a no-amount call returns the full original to the sender, a sub-cent amount keeps full precision, an explicit partial sends just that amount, a second partial under the cap is allowed, the amount defaults to the remainder when prior refunds exist, an amount over the cap and a non-positive amount are both rejected, and an already-fully-refunded original is refused; plus getRefundSummary reporting partial state, fully-refunded state, and not-refundable for a non-received txid, and a downstream refusal that throws.
WalletRefundServiceTest.php 7 Plugin-gateway bridge over the refund core. Caller-id requirement (a gateway bypass is refused and never reaches the core), delegation that tags the calling plugin id as the initiator, optional partial-amount passthrough (explicit amount forwarded to core; empty string normalised to null for back-compat with legacy single-arg callers; null defaults to the full remainder), the PluginCallerAware contract, and the #[PluginCallable] attribute carrying the distinct wallet_refund_return_to_sender permission.
ContainerLifecycleServiceTest.php 14 Sandbox bridge that records desired sidecar state to /var/lib/eiou/plugin-sidecars-desired.json. Caller-id requirement, service-name validation (rejects leading dash, shell metachar, spaces, over-64-char), stopSidecar / startSidecar state-file writes, idempotent overwrites, per-plugin namespace separation (one plugin can’t address another’s services), 0644 permissions for operator orchestration, #[PluginCallable] attribute presence, PluginCallerAware contract.
PluginsTabPanelRegistryTest.php 15 Sub-panel registry that backs the host-owned Plugins tab. register validation (missing plugin_id / invalid shape / empty label / non-callable render / non-int order all rejected; sensible defaults for missing icon and order), last-write-wins on plugin_id collision, all() stable sort by order asc then plugin_id asc, renderPanel (returns closure output, swallows throws + returns empty rather than propagating, coerces non-string returns to empty, returns empty on unknown id), isEmpty reflects registration state (drives the empty-state UX in the Plugins tab).

Services Lookup Tests (tests/Unit/Services/Lookup/)

Test File Tests Coverage
ContactLookupServiceTest.php 25 Read-only contact facade for sandboxed plugins. Projection contract (narrow {name, http, https, tor, pubkey_hash} shape — extra repository columns like status, pubkey, contact_id must NOT leak), getByPubkeyHash (null on missing, empty-input short-circuit, lowercase/trim normalization, null-preservation for missing transports), getByName (strict-match-or-null semantics — null on no match AND on ambiguous match, whitespace trim, short-circuit on empty), getOnlineStatus (returns the online_status field, null on missing contact, empty/whitespace short-circuit, case-normalised), listAccepted (default limit/offset, MAX_PAGE_LIMIT cap, negative-bounds clamp, per-row projection), listPending (projects pending-request rows, allows null name on incoming-before-accept), #[PluginCallable] attribute presence on all five methods, permission-tier annotations (getByPubkeyHash / getByName / getOnlineStatus carry no permission; listAccepted gates on contact_address_book_enumerate; listPending gates on contact_pending_enumerate).
BalanceLookupServiceTest.php 14 Read-only balance facade gated on wallet_balance_read. getUserBalance currency-specific path ({currency, balance} row with {whole, frac, minor_units, display} projection, empty/whitespace short-circuit, zero-row for currencies with no history), currency-list path (per-row projection, empty list on null repo result, drops malformed rows), negative-balance encoding (whole<0 with non-negative frac, minor_units carries lossless signed integer), getUserBalanceContact (multi-currency projection, null/empty repo handling, empty hash short-circuit, case normalisation, drops malformed rows), permission-key annotations on both methods.
PluginLookupServiceTest.php 16 Self-introspection + cross-plugin inventory surface (getOwnPermissions, getOwnManifest, listEnabledPluginIds). PluginCallerAware contract (refuses without gateway-injected caller id on all three methods), per-plugin row resolution from PluginLoader::listAllPlugins, manifest projection allow-list (deliberately omits host-injected fields like gateway tokens, system-user names, runtime status flags), cross-plugin isolation regression (setCallingPluginId switches the row scope; no method arg can override it), listEnabledPluginIds self-exclusion regression (caller’s own row never appears in the result, only enabled plugins returned), #[PluginCallable] attribute presence + per-method permission annotations (getOwnPermissions / getOwnManifest carry no permission; listEnabledPluginIds gates on plugin_inventory_read).
TransactionStatisticsLookupServiceTest.php 8 Aggregate-statistics facade gated on transaction_history_aggregate. Happy-path projection (count + {whole, frac, minor_units, display} for total), optional currency parameter, bounds (zero-window / reversed-window short-circuit before repo, oversized window clamps to MAX_PERIOD_SECONDS, empty currency string short-circuits), defensive handling when repository returns a malformed shape, permission-key annotation.
ContactCreditLookupServiceTest.php 11 Per-contact credit-state facade gated on contact_credit_read. Currency-specific path (projected row, null on no match, empty/whitespace short-circuit), all-currencies path (list projection, empty list on no rows, drops malformed rows), empty pubkey_hash short-circuits for both paths, case normalisation, permission-key annotation.
PaymentRequestLookupServiceTest.php 9 Payment-request lookup facade. getByRequestId ungated (per-id, demand-driven) — projection drops sensitive requester_address / signed_message_content / id, null on missing, empty short-circuit. listPendingIncoming / listOutgoing gated on payment_request_enumerate — per-row projection, MAX_PAGE_LIMIT cap, negative-bound clamp, default limit of 50. Permission annotations verified per method.
PaybackMethodLookupServiceTest.php 12 Capability-discovery facade for payback-rail plugins. PluginCallerAware contract (both methods refuse without caller id), type-scoping (plugin only sees rows whose type is in its own manifest’s payback_method_types — plugin with no declared types gets [] without hitting the repository), sensitive-field omission regression (encrypted_fields / fields_version / fields_json never appear in the projection), currency filter passthrough, empty hash / empty currency short-circuits, permission-key annotations (payback_method_read_own and payback_method_read_contact).

Services Proxies Tests (tests/Unit/Services/Proxies/)

Test File Tests Coverage
SyncServiceProxyTest.php 15+ Lazy proxy pattern, deferred initialization

Services Utilities Tests (tests/Unit/Services/Utilities/)

Test File Tests Coverage
CurrencyUtilityServiceTest.php 27 Cents/dollars conversion, currency formatting, fee calculations, minimum fee floor, rounding, large amounts, unknown currency exceptions
TimeUtilityServiceTest.php 11 Microtime conversion, expiration checking, TTL calculations, timestamp precision
GeneralUtilityServiceTest.php 15+ Address truncation, string manipulation
TransportUtilityServiceTest.php 25+ Transport detection, address types, jitter function
UtilityServiceContainerTest.php 15+ Lazy loading container, utility caching
ValidationUtilityServiceTest.php 20+ Request validation, signature verification, funds calculation

Service Wrappers Tests (tests/Unit/Services/)

Test File Tests Coverage
ServiceWrappersTest.php 10+ Output wrapper function, message handling

Startup Tests (tests/Unit/Startup/)

Test File Tests Coverage
ConfigCheckTest.php 15+ Userconfig validation, public key detection
MessageCheckTest.php 15+ Database prerequisite checks, PDO availability

CLI Tests (tests/Unit/Cli/)

Test File Tests Coverage
CliJsonResponseTest.php 24 RFC 9457 compliant JSON responses, success/error structure, validation errors, pagination, table formatting, transaction responses
CliOutputManagerTest.php 20 Singleton pattern, JSON mode flag parsing, cleanArgv argument filtering, command parsing, fluent interface

Events Tests (tests/Unit/Events/)

Test File Tests Coverage
EventDispatcherTest.php 20 Singleton pattern, event subscription/unsubscription, listener invocation order, exception handling, listener management
SyncEventsTest.php 18 Sync event constants verification, naming convention compliance, string type validation, reflection-based constant enumeration

Formatters Tests (tests/Unit/Formatters/)

Test File Tests Coverage
TransactionFormatterTest.php 14 Amount conversion (cents to dollars), transaction history formatting, counterparty detection, contact formatting

GUI Tests (tests/Unit/Gui/)

Test File Tests Coverage
FunctionsTest.php 20+ Router, view data initialization, XSS prevention, action routing
Includes/SessionTest.php 40+ Authentication, CSRF tokens, flash messages, session timeout

GUI Helpers Tests (tests/Unit/Gui/Helpers/)

Test File Tests Coverage
ContactDataBuilderTest.php 20 Contact data building, address type handling, primary address priority (Tor > HTTPS > HTTP), JSON encoding, HTML-safe output, status handling, Unicode support
MessageHelperTest.php 62 Message parsing, formatting, HTML encoding
ViewHelperTest.php 54 View rendering helpers, template processing

GUI Controllers Tests (tests/Unit/Gui/Controllers/)

Test File Tests Coverage
ContactControllerTest.php 25+ Contact CRUD actions, CSRF verification, validation
SettingsControllerTest.php 25+ Settings management, input validation, JSON export
TransactionControllerTest.php 20+ Transaction actions, recipient handling

Schema Tests (tests/Unit/Schemas/)

Test File Tests Coverage
OutputSchemaTest.php 25+ Debug/logging output for all message types

Validator Tests (tests/Unit/Validators/)

Test File Tests Coverage
PaybackMethodTypeValidatorTest.php 30+ Payback method type catalog: required-keys check, sibling-field references in show_when, currency-code references; field-by-field validation (text, mod-97 IBAN, ABA routing checksum, free-text custom rails); plugin-registered rail types.
Checksum/ varies Checksum-validator helpers (IBAN mod-97, ABA routing) consumed by PaybackMethodTypeValidator.

Schema/Payload Tests (tests/Unit/Schemas/Payloads/)

Test File Tests Coverage
BasePayloadTest.php 53 ensureRequiredFields validation, sanitizeString, sanitizeNumber type handling, validate empty check, edge cases
ContactPayloadTest.php 26 Contact creation/received/updated/rejection/pending/mutually-accepted payloads, filterAddresses, senderAddresses in creation payload, JSON encoding
ContactStatusPayloadTest.php 53 Ping/pong payloads, status responses
MessagePayloadTest.php 20 Contact inquiry/accepted/unknown payloads, transaction status/sync responses, P2P status inquiry/response
P2pPayloadTest.php 50+ P2P request payloads, validation
Rp2pPayloadTest.php 54 Return P2P payloads, relay routing
TransactionPayloadTest.php 77 Transaction payloads, all transaction types
UtilPayloadTest.php 77 Utility/error payloads, acknowledgments

Integration Test Inventory

The tables below are a curated overview of the shell-based integration test suite under tests/testfiles/. The repo’s actual count is the authoritative number — ls tests/testfiles/*.sh | wc -l for files and the run-all-tests.sh summary line for individual test counts. The inventory may lag behind when new test files are added; the headings always reflect the current categorization.

Contact Lifecycle (tests/testfiles/)

Test File Coverage
addContactsTest.sh Contact addition workflow between containers — request, accept, status transitions
mutualContactTest.sh Mutual-contact request auto-accept feature (both sides issue add simultaneously)
contactListTest.sh Contact list storage, ordering, and per-contact metadata verification
contactNameTest.sh Multi-part contact names and duplicate-name disambiguation across the API/CLI

Transactions & Balances (tests/testfiles/)

Test File Coverage
transactionTestSuite.sh Consolidated transaction tests: history, inquiry response, contact-tx type, chain reorder under cancellations, held-tx for invalid previous_txid, self-send prevention
transactionRecoveryTest.sh Atomic transaction claiming and crash-recovery mechanisms
balanceTest.sh Balance queries and verification across all containers in the topology
sendMessageTest.sh Message sending between connected contacts
sendAllPeersTest.sh Transaction sending to all connected peers in the network
negativeFinancialTest.sh Negative / error paths for financial operations (over-credit, double-spend, malformed amounts)

P2P Routing (tests/testfiles/)

Test File Coverage
routingTest.sh Multi-hop message routing and relay-fee calculation
bestFeeRoutingTest.sh Best-fee P2P route selection: single-node, 4-line, 12-collision topologies; fast-vs-best timing, path analysis with randomized fees, dead-end cascade cancel
routeCancellationTest.sh Route-cancellation service wiring, capacity-reservation table, hop-budget distribution (constant under EIOU_HOP_BUDGET_RANDOMIZED=false, variance when randomized), reservation create/release, originator downstream cancel via broadcastFullCancelForHash, multi-route safety with full_cancel
cascadeCancelTest.sh Cascade cancel / expire for dead-end P2P routes
maxLevelCancelTest.sh Nodes at the P2P max-level boundary immediately cancel and notify the originator

Sync & Chain Integrity (tests/testfiles/)

Test File Coverage
syncTestSuite.sh Consolidated sync: basic sync command, transaction-chain recovery, signature-validation stop, multi-cycle resilience, cancelled-tx handling, NULL previous_txid edge cases
chainDropTestSuite.sh Tx-drop agreement protocol for resolving mutual chain gaps (propose / accept / reject; auto-propose; balance-guard)
chunkedSyncTest.sh Chunked transaction-sync protocol behavior at large chain lengths

REST API & Payment Requests (tests/testfiles/)

Test File Coverage
apiEndpointsTest.sh REST API endpoints — happy-path coverage across the v1 surface
apiInputValidationTest.sh Missing-required-field 400s, invalid types, boundary values, special-character / encoding safety
paymentRequestTest.sh Full payment-request lifecycle via REST: list, create, outgoing-pending, incoming-delivery (async-safe), decline, cancel, invalid-create, full approve flow (request → poll → approve → sendEiou → balance verify), approve-with-payer-note (description shape "payment: <req desc> | <payer note>"), over-long payer-note rejection. Permission-gated: wallet:read for list/create/decline/cancel, wallet:send for approve

CLI (tests/testfiles/)

Test File Coverage
cliCommandsTest.sh CLI commands produce correct output in both regular and JSON modes

Backup (tests/testfiles/)

Test File Coverage
backupTestSuite.sh Encrypted backup create / list / verify / restore / status; auto-backup toggle; retention per prefix

Identity & Wallet Setup (tests/testfiles/)

Test File Coverage
nodeIdentityTest.sh EIOU_NAME, EIOU_HOST, EIOU_PORT environment-variable handling and userconfig effects
hostnameTest.sh Hostname configuration in userconfig.json matches expected values
seedphraseTestSuite.sh Seedphrase generate / restore, secure display, authcode restoration, restore + EIOU_HOST hostname application
sslCertificateTest.sh SSL certificate generation and HTTPS functionality

Process & Lifecycle (tests/testfiles/)

Test File Coverage
gracefulShutdownTest.sh Graceful shutdown handling for eIOU Docker containers (processors stop cleanly, lockfiles cleared)
sigTermTest.sh SIGTERM graceful shutdown via docker stop
processorLockfileTest.sh Processor lockfile fix preventing random restart loops
performanceBaseline.sh Performance baseline: transaction processing time, batch throughput, API response times, DB query performance

Networking & Messaging (tests/testfiles/)

Test File Coverage
messageDeliveryTest.sh MessageDeliveryService and Dead Letter Queue functionality
parallelBroadcastTest.sh curl_multi parallel-broadcast functionality in P2P and transport layers
pingTestSuite.sh Contact-status ping feature, online detection, response time, chain-head exchange
curlErrorHandlingTest.sh HTTP-client error handling and timeout behavior

Tor (tests/testfiles/)

Test File Coverage
torTestSuite.sh Consolidated TOR: address verification, restart verification, key-file permissions, rapid-restart resilience

Code Quality & Static Checks (tests/testfiles/)

Test File Coverage
circularDependencyCheck.sh Static analysis: parses PHP service files for constructor & setter-injection deps, builds a dependency graph, DFS-detects cycles
serviceExceptionTest.sh ServiceException hierarchy and error handling
serviceInterfaceTest.sh Verifies all services properly implement their declared interfaces

Security (tests/testfiles/)

Test File Coverage
securityTestSuite.sh SQL-injection protection on API endpoints, XSS payload handling and sanitization, authentication-header manipulation, rate-limit enforcement

Benchmarks (tests/)

Standalone benchmark scripts in tests/ (not run by ./run-all-tests.sh):

Test File Coverage
benchmark-routing.sh Routing throughput and latency measurements
benchmark-bestfee.sh Best-fee mode latency vs fast mode and route-fan-out cost

Running Unit Tests

Configuration Files

  • tests/phpunit.xml.dist - PHPUnit 11 configuration template (tracked in git)
  • tests/phpunit.xml - Local configuration override (in .gitignore)

The .dist file is the tracked template. Copy it to phpunit.xml for local customization:

cp tests/phpunit.xml.dist tests/phpunit.xml

Composer Commands

cd eiou-docker/files

# Run all tests
composer test

# Verbose output with readable test names
composer test-verbose

# Debug mode - stops on first failure
composer test-debug

# With coverage report (requires Xdebug/PCOV)
composer test-coverage

# Pass custom PHPUnit flags
composer test -- --filter=BIP39
composer test -- --stop-on-failure -v
composer test -- --testdox --group=security

Using Docker (No Local PHP Required)

The repository ships a runner image for this: tests/docker/phpunit.Dockerfile. Use it rather than an ad-hoc php:8.3-cli container. It matches the CI runner’s PHP version (8.2) and carries the extensions the suite needs, including bcmath and intl, which composer.json declares as hard requirements and which the stock image does not ship.

cd eiou-docker
docker build -f tests/docker/phpunit.Dockerfile -t eiou-phpunit .

Run the suite as an unprivileged user, with the tree copied into the container rather than bind-mounted:

docker run --rm -v "$PWD:/src:ro" eiou-phpunit bash -c '
  set -e
  mkdir -p /app && tar -C /src --exclude=.git --exclude=files/vendor -cf - . | tar -C /app -xf -
  cd /app/files && composer install --no-interaction --prefer-dist -q
  useradd -m tester && chmod +x /app/scripts/ci/run-phpunit.sh && chown -R tester /app
  su tester -c "cd /app && scripts/ci/run-phpunit.sh /tmp/junit.xml"
'

Both of those details matter:

  • Non-root. Several tests assert on permission behaviour and silently invert under uid 0, because chmod cannot make a directory unwritable for root. A root run reports passes for failure paths that never actually failed.
  • Copy, not bind-mount. Composer has to write files/vendor, and the copy keeps the container’s vendor/ and chown -R off your working tree.

scripts/ci/run-phpunit.sh is the same entry point both workflows call, so this reproduces CI exactly rather than approximately: it adds the completeness guard that catches a suite dying mid-run while still exiting 0, and it writes JUnit XML to the path you give it.

To narrow the run, pass a filter through the script by replacing its line in the command above. The script forwards extra arguments to its test listing as well as to the run, so the completeness guard narrows to match:

  su tester -c "cd /app && scripts/ci/run-phpunit.sh /tmp/junit.xml --filter=BIP39"

For output formats that suppress PHPUnit’s progress characters, such as --testdox, call PHPUnit directly instead. The guard counts those characters, so routing --testdox through the script makes it report an incomplete run:

  su tester -c "cd /app/files && vendor/bin/phpunit --configuration ../tests/phpunit.xml.dist --testdox"

A handful of tests still skip in this image: it has no debian-tor user and no /etc/eiou or /var/log/eiou. Those paths exist only on a real node, so see Running the suite inside an eiou node to close that gap.

Running the suite inside an eiou node (closes most env-gated skips)

A handful of unit tests guard on environment state that only exists inside an eiou container — php-bcmath loaded, /app/eiou/ populated, /etc/eiou/config/ present and writable, the encryption-key file layout. Outside that environment they markTestSkipped() and this is normal, expected behaviour for host-side runs. To exercise that coverage:

# 1) Start a node (adds bcmath, /app/eiou, /etc/eiou/config, network)
docker compose up -d --build

# 2) Run the wrapper. Three modes; "all" runs them in sequence.
tests/run-unit-tests-docker.sh all          # node + fresh + fresh-key
tests/run-unit-tests-docker.sh node         # against running eiou-node only
tests/run-unit-tests-docker.sh fresh        # empty /etc/eiou/config (first-boot path)
tests/run-unit-tests-docker.sh fresh-key    # stub .master.key.enc for the volume-encryption presence path

# Forwarded args go to phpunit:
tests/run-unit-tests-docker.sh node --filter=ApplicationTest

Each mode covers a different class of skip:

Mode What it adds Tests it unlocks
node bcmath + DB + populated /etc/eiou/config ApplicationTest (27), bcmath gates (~14), UserContextTest config tests (3), UpdateCheckServiceTest config tests (~16), the InputValidator-routed currency checks
fresh empty /etc/eiou/config + clean /dev/shm tmpfs VolumeEncryptionTest / MariaDbEncryptionTest / KeyEncryptionTest first-boot bootstrap paths (~9)
fresh-key stub .master.key.enc present in tmpfs config VolumeEncryptionTest::testInitThrowsWhenEncryptedKeyExistsWithoutPassphrase (the encrypted-key-present branch of the same suite)

Skips you’ll still see — and why they’re fine

These never run on a plain host and shouldn’t be treated as failures:

  • bcmath gates (InputValidatorTest, ContactValidatorTest, CurrencyUtilityServiceTest, SettlementPrecisionServiceTest, PaymentRequestServiceTest, ContactDecisionServiceTest, ContactManagementServiceTest, TransactionFormatterTest) — skip unless bcmath is loaded. Production and CI both have it.
  • Docker-environment gates (ApplicationTest × 27, UserContextTest × 3, UpdateCheckServiceTest × ~16) — skip when /app/eiou/ or /etc/eiou/config/ aren’t present/writable.
  • Encryption presence/absence (VolumeEncryptionTest, MariaDbEncryptionTest, KeyEncryptionTest) — split by intent: some assert the first-install path (file absent), others the active-session path (file present). Any single environment will skip one of the two halves.
  • PHP build features (PayloadEncryptionTest × 2, E2eAllMessagesTest, E2eContactDescriptionTest) — skip if the linked OpenSSL lacks secp256k1 or PHP lacks openssl_pkey_derive / hash_hkdf. Production builds have both.
  • Maintenance lockfile (MaintenanceCheckTest) — only skips when /tmp/eiou_maintenance.lock is present (loading the script during a maintenance window would call exit()).
  • Network (UpdateCheckServiceTest::testGetReleaseNotesReturnsExpectedKeysWhenAvailable) — needs to reach github.com.

The wrapper above + a real network connection covers everything except the maintenance-lockfile guard. If you want a single number to track, tests/run-unit-tests-docker.sh all should bottom out at ≤ 5 skips total — anything more means a new environment guard slipped in.

Running Specific Tests

# Run a single test file
composer test -- tests/Unit/Utils/InputValidatorTest.php

# Run tests matching a pattern
composer test -- --filter=testValidateAmount

# Run a specific test class
composer test -- --filter=BIP39Test

# Run tests in a directory
composer test -- tests/Unit/Security/

Running Integration Tests

cd eiou-docker/tests

# Run all integration tests against 4-node topology
./run-all-tests.sh http4

# Run specific test suite
./run-all-tests.sh http4 transactions

# View available test suites
./run-all-tests.sh --help

Integration Test Environment Variables

All buildfiles pass these env vars to containers (with test defaults):

Variable Test Default Purpose
EIOU_CONTACT_STATUS_ENABLED true Enable/disable contact status pinging
EIOU_TOR_FORCE_FAST true Force fast mode for Tor routes
EIOU_DEFAULT_TRANSPORT_MODE http Transport mode (http/https/tor). Tests use http to avoid Tor’s force-fast overriding best-fee mode
EIOU_HOP_BUDGET_RANDOMIZED false Disable hop budget randomization for deterministic routing depth assertions

Override from the parent shell:

EIOU_HOP_BUDGET_RANDOMIZED=true ./run-all-tests.sh http4

Test Structure

tests/
├── bootstrap.php              # PHPUnit bootstrap - sets up test environment:
│   │                          #   - EIOU_TEST_MODE constant (true during tests)
│   │                          #   - PSR-4 namespace Eiou\Tests\ autoloading
│   │                          #   - Mocked output() function for suppressing logs
├── phpunit.xml.dist           # PHPUnit configuration template (tracked in git)
├── run-all-tests.sh           # Integration test runner
├── Unit/                      # PHPUnit unit tests
│   ├── Cli/
│   │   ├── CliJsonResponseTest.php
│   │   └── CliOutputManagerTest.php
│   ├── Core/
│   │   ├── ConstantsTest.php
│   │   └── ErrorCodesTest.php
│   ├── Database/
│   │   ├── CapacityReservationRepositoryTest.php
│   │   ├── DatabaseSchemaTest.php
│   │   ├── P2pSenderRepositoryTest.php
│   │   └── RouteCancellationRepositoryTest.php
│   ├── Events/
│   │   ├── EventDispatcherTest.php
│   │   └── SyncEventsTest.php
│   ├── Exceptions/
│   │   └── ServiceExceptionTest.php
│   ├── Formatters/
│   │   └── TransactionFormatterTest.php
│   ├── Gui/
│   │   ├── Controllers/
│   │   │   ├── ContactControllerTest.php
│   │   │   ├── SettingsControllerTest.php
│   │   │   └── TransactionControllerTest.php
│   │   └── Helpers/
│   │       ├── ContactDataBuilderTest.php
│   │       ├── MessageHelperTest.php
│   │       └── ViewHelperTest.php
│   ├── Repositories/
│   │   ├── AbstractRepositoryTest.php
│   │   ├── AddressRepositoryTest.php
│   │   ├── ApiKeyRepositoryTest.php
│   │   ├── BalanceRepositoryTest.php
│   │   ├── ContactRepositoryTest.php
│   │   ├── DeadLetterQueueRepositoryTest.php
│   │   ├── DebugRepositoryTest.php
│   │   ├── DeliveryMetricsRepositoryTest.php
│   │   ├── HeldTransactionRepositoryTest.php
│   │   ├── MessageDeliveryRepositoryTest.php
│   │   ├── P2pRepositoryTest.php
│   │   ├── RateLimiterRepositoryTest.php
│   │   ├── Rp2pRepositoryTest.php
│   │   ├── TransactionChainRepositoryTest.php
│   │   ├── TransactionContactRepositoryTest.php
│   │   ├── TransactionRecoveryRepositoryTest.php
│   │   ├── TransactionRepositoryTest.php
│   │   └── TransactionStatisticsRepositoryTest.php
│   ├── Schemas/
│   │   └── Payloads/
│   │       ├── BasePayloadTest.php
│   │       ├── ContactPayloadTest.php
│   │       ├── ContactStatusPayloadTest.php
│   │       ├── MessagePayloadTest.php
│   │       ├── P2pPayloadTest.php
│   │       ├── Rp2pPayloadTest.php
│   │       ├── TransactionPayloadTest.php
│   │       └── UtilPayloadTest.php
│   ├── Security/
│   │   ├── BIP39Test.php
│   │   ├── KeyEncryptionTest.php
│   │   ├── MessageSignatureTest.php
│   │   ├── PayloadEncryptionTest.php
│   │   └── TorKeyDerivationTest.php
│   ├── Services/
│   │   ├── ApiAuthServiceTest.php
│   │   ├── ApiKeyServiceTest.php
│   │   ├── BackupServiceTest.php
│   │   ├── BalanceServiceTest.php
│   │   ├── ChainOperationsServiceTest.php
│   │   ├── ChainVerificationServiceTest.php
│   │   ├── CleanupServiceTest.php
│   │   ├── CliServiceTest.php
│   │   ├── ContactServiceTest.php
│   │   ├── ContactStatusServiceTest.php
│   │   ├── DatabaseLockingServiceTest.php
│   │   ├── DebugServiceTest.php
│   │   ├── HeldTransactionServiceTest.php
│   │   ├── MessageDeliveryServiceTest.php
│   │   ├── MessageServiceTest.php
│   │   ├── P2pServiceTest.php
│   │   ├── RateLimiterServiceTest.php
│   │   ├── RouteCancellationServiceTest.php
│   │   ├── Rp2pServiceTest.php
│   │   ├── SendOperationServiceTest.php
│   │   ├── ServiceContainerTest.php
│   │   ├── SyncServiceTest.php
│   │   ├── TransactionProcessingServiceTest.php
│   │   ├── TransactionRecoveryServiceTest.php
│   │   ├── TransactionServiceTest.php
│   │   ├── TransactionValidationServiceTest.php
│   │   ├── WalletServiceTest.php
│   │   ├── Proxies/
│   │   │   └── SyncServiceProxyTest.php
│   │   └── Utilities/
│   │       ├── CurrencyUtilityServiceTest.php
│   │       └── TimeUtilityServiceTest.php
│   ├── Utils/
│   │   ├── AddressValidatorTest.php
│   │   ├── AdaptivePollerTest.php
│   │   ├── InputValidatorTest.php
│   │   ├── LoggerTest.php
│   │   ├── SecureLoggerTest.php
│   │   └── SecurityTest.php
│   └── Validators/
│       ├── Checksum/
│       └── PaybackMethodTypeValidatorTest.php
└── ...                        # Integration test scripts (see tests/README.md)

Writing New Tests

Unit Test Template

<?php
/**
 * Unit Tests for YourClass
 */

namespace Eiou\Tests\Utils;

use PHPUnit\Framework\TestCase;
use PHPUnit\Framework\Attributes\CoversClass;
use Eiou\Utils\YourClass;

#[CoversClass(YourClass::class)]
class YourClassTest extends TestCase
{
    /**
     * Test description of what is being tested
     */
    public function testMethodNameWithScenario(): void
    {
        // Arrange
        $input = 'test-input';

        // Act
        $result = YourClass::doSomething($input);

        // Assert
        $this->assertEquals('expected', $result);
    }

    /**
     * Test error handling
     */
    public function testMethodThrowsOnInvalidInput(): void
    {
        $this->expectException(\InvalidArgumentException::class);
        $this->expectExceptionMessage('Expected error message');

        YourClass::doSomething('invalid');
    }
}

Test Naming Conventions

  • Test classes: {ClassName}Test.php
  • Test methods: test{MethodName}{Scenario}
    • testValidateAmountWithPositiveValue
    • testValidateAmountWithNegativeValue
    • testValidateAmountThrowsOnInvalidInput
  • Use descriptive names that explain what is being tested

Common Assertions

// Equality
$this->assertEquals($expected, $actual);
$this->assertSame($expected, $actual);  // Strict type comparison
$this->assertNotEquals($a, $b);

// Boolean
$this->assertTrue($value);
$this->assertFalse($value);

// Types
$this->assertIsString($value);
$this->assertIsArray($value);
$this->assertIsInt($value);
$this->assertIsBool($value);

// Strings
$this->assertStringContainsString('needle', $haystack);
$this->assertStringStartsWith('prefix', $string);
$this->assertMatchesRegularExpression('/pattern/', $string);

// Arrays
$this->assertArrayHasKey('key', $array);
$this->assertCount(3, $array);
$this->assertContains($value, $array);

// Null
$this->assertNull($value);
$this->assertNotNull($value);

// Exceptions
$this->expectException(ExceptionClass::class);
$this->expectExceptionMessage('message');

Prerequisites

Local PHP Setup

Ubuntu/Debian:

sudo apt-get install php8.3-cli php8.3-xml php8.3-mbstring php8.3-sodium \
    php8.3-bcmath php8.3-intl

bcmath and intl are required — composer.json declares ext-bcmath and ext-intl, so composer install fails without them.

macOS (Homebrew):

brew install php  # Includes all required extensions

Windows: Ensure these extensions are enabled in php.ini:

  • extension=dom
  • extension=mbstring
  • extension=xml
  • extension=sodium
  • extension=intl

intl is the one most often missed: the official Windows builds ship php_intl.dll but leave it commented out, so composer install fails on ext-intl until you uncomment the line above. bcmath needs no entry, it is compiled into the official Windows binaries; confirm with php -m | findstr /i "bcmath intl", which must list both.

Required Extensions

Extension Required For
bcmath Every amount/fee/credit path (declared as ext-bcmath)
intl Diacritic folding beyond the built-in table, in payment QR text (declared as ext-intl)
dom PHPUnit XML parsing
mbstring String handling
sodium TorKeyDerivation tests
openssl KeyEncryption, PayloadEncryption, BIP39 tests

bcmath and intl are declared in composer.json, so Composer refuses to install without them. The rest are checked at test time.

Troubleshooting

“Composer autoloader not found”

cd files
composer install

“ext-dom is missing”

# Ubuntu/Debian
sudo apt-get install php8.3-xml

# macOS
brew reinstall php

“Class not found” errors

cd files
composer dump-autoload

Tests fail with “Sodium extension required”

# Ubuntu/Debian
sudo apt-get install php8.3-sodium

# macOS (usually included)
brew reinstall php

Docker permission errors

# Add user to docker group
sudo usermod -aG docker $USER
# Log out and back in

See detailed error output

# Show full error details
composer test-debug

# Or with PHPUnit flags
composer test -- --stop-on-failure -v

Continuous Integration

Unit tests run automatically on:

  • Pull request creation
  • Push to feature branches
  • Merge to main branch

Requirement: All tests must pass before merging PRs.