Skip to content
Merged
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
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,14 @@ and [Semantic Versioning](https://semver.org/).

### Added

- **Docker-based integration test environment.** `tests/cloud-lab/`
brings up a self-contained payment lab via docker-compose: a
cdk-mintd FakeWallet mint, upstream and reseller TollGate containers
(run against a fake-ndsctl shim so all payment/session logic runs
unmodified), and a client container (cdk-cli + nak + pytest) with
smoke-payment, mint-failure, and two-router autopay suites.
([#362](https://github.com/OpenTollGate/tollgate-module-basic-go/pull/362))

- **Captive-portal uhttpd instance on port 2051.** A second uhttpd
section (`config uhttpd portal`) now serves the SPA directly on
`0.0.0.0:2051` / `[::]:2051`, decoupling portal serving from the
Expand Down
34 changes: 34 additions & 0 deletions tests/cloud-lab/Dockerfile.client
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# Dockerfile.client — Test runner container for cloud-lab.
#
# Contains cdk-cli (for Cashu wallet operations), nak (for Nostr event
# signing), and Python+pytest (for test execution).

FROM rust:1.88-bookworm AS rust-builder

# Build cdk-cli for wallet operations
RUN cargo install cdk-cli --locked

FROM golang:1.25-bookworm AS go-builder

# Install nak (Nostr Swiss Army Knife)
RUN go install github.com/fiatjaf/nak@latest

# --- Runtime stage ---
FROM python:3.12-slim-bookworm

# Copy cdk-cli from rust builder
COPY --from=rust-builder /usr/local/cargo/bin/cdk-cli /usr/local/bin/cdk-cli

# Copy nak from go builder
COPY --from=go-builder /go/bin/nak /usr/local/bin/nak

# Install Python test dependencies
RUN pip install --no-cache-dir pytest requests

# Default working directory
WORKDIR /tests

# Install curl for health checks in tests
RUN apt-get update && \
apt-get install -y --no-install-recommends curl && \
rm -rf /var/lib/apt/lists/*
33 changes: 33 additions & 0 deletions tests/cloud-lab/Dockerfile.mint
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# Dockerfile.mint — Builds cdk-mintd with FakeWallet backend for cloud-lab testing.
#
# Uses cargo install from crates.io. The FakeWallet backend automatically
# settles Lightning quotes without a real Lightning node, making it ideal
# for integration testing.
#
# If cdk-mintd is not available on crates.io, build from the cashubtc/cdk
# repo instead:
# RUN git clone https://github.com/cashubtc/cdk.git /cdk && \
# cd /cdk && cargo build --release --bin cdk-mintd --features fakewallet,sqlite

FROM rust:1.88-bookworm AS builder

RUN apt-get update && \
apt-get install -y --no-install-recommends pkg-config libssl-dev protobuf-compiler && \
rm -rf /var/lib/apt/lists/*

# Build cdk-mintd with only the features we need
# --locked ensures reproducible builds from the published Cargo.lock
RUN cargo install cdk-mintd --no-default-features --features fakewallet,sqlite --locked

# --- Runtime stage ---
FROM debian:bookworm-slim

RUN apt-get update && \
apt-get install -y --no-install-recommends libssl3 ca-certificates curl && \
rm -rf /var/lib/apt/lists/*

COPY --from=builder /usr/local/cargo/bin/cdk-mintd /usr/local/bin/cdk-mintd

EXPOSE 8085

CMD ["cdk-mintd"]
41 changes: 41 additions & 0 deletions tests/cloud-lab/Dockerfile.tollgate
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# Dockerfile.tollgate — Builds the TollGate service binary for cloud-lab testing.
#
# The binary runs on x86_64 Linux with a fake ndsctl in PATH instead of
# NoDogSplash. All payment, session, merchant, and mint-health logic runs
# unmodified — only the gate (valve) calls are intercepted.

FROM golang:1.25-bookworm AS builder

WORKDIR /build

# Copy the entire src/ tree (multi-module project with replace directives)
COPY src/ .

# Build the TollGate service binary (package main in src/)
RUN CGO_ENABLED=0 go build -o /tollgate .

# --- Runtime stage ---
FROM debian:bookworm-slim

RUN apt-get update && \
apt-get install -y --no-install-recommends ca-certificates curl iproute2 && \
rm -rf /var/lib/apt/lists/*

# TollGate service binary
COPY --from=builder /tollgate /usr/local/bin/tollgate

# Fake ndsctl that replaces NoDogSplash
COPY tests/cloud-lab/fake-ndsctl.sh /usr/local/bin/ndsctl
RUN chmod +x /usr/local/bin/ndsctl

# Config files are mounted via docker-compose volumes
# /etc/tollgate/config.json
# /etc/tollgate/identities.json
# /etc/tollgate/install.json

# Create the config directory
RUN mkdir -p /etc/tollgate

EXPOSE 2121

CMD ["tollgate"]
126 changes: 126 additions & 0 deletions tests/cloud-lab/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
# TollGate Cloud Lab

Docker-based integration test environment for TollGate. Runs the real
TollGate binary (compiled from source) against a self-hosted Cashu mint
with FakeWallet backend — no physical routers required.

## Architecture

```
┌──────────────────── Docker network: 172.28.0.0/16 ────────────────────┐
│ │
│ ┌────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ tg-mint │◄────│ tg-upstream │◄────│ tg-client │ │
│ │ (cdk-mintd)│ │ (tollgate) │ │ (pytest + │ │
│ │ FakeWallet │ │ port 2121 │ │ cdk-cli + │ │
│ │ port 8085 │ │ │ │ nak) │ │
│ └────────────┘ └──────────────┘ └──────────────┘ │
│ ▲ │
│ │ optional │
│ ┌─────┴───────┐ │
│ │ tg-reseller │ │
│ │ (tollgate) │ │
│ │ port 2121 │ │
│ └─────────────┘ │
└───────────────────────────────────────────────────────────────────────┘
```

- **tg-mint** — Cashu mint ([cdk-mintd](https://github.com/cashubtc/cdk))
with FakeWallet backend. Automatically settles Lightning quotes.
Killable for failure/degraded-mode tests.

- **tg-upstream** — The TollGate Go binary, built from source. Uses a
fake `ndsctl` script instead of NoDogSplash, so all payment/session/
merchant/mint-health logic runs unmodified. Only packet-level gate
control is stubbed.

- **tg-reseller** — Same binary in `reseller_mode: true`. Optional,
started with `--profile two-router`.

- **tg-client** — Test runner with cdk-cli, nak, and pytest.

## Quick Start

```bash
# Build and start the mint + upstream TollGate
docker compose up -d mint upstream

# Wait for health checks to pass
docker compose ps

# Run the smoke tests
docker compose run --rm client

# Run a specific test file
docker compose run --rm client pytest -sv test_smoke_payment.py

# Run the two-router tests (starts reseller too)
docker compose --profile two-router up -d
docker compose run --rm client pytest -sv test_two_router_autopay.py

# Teardown
docker compose down
```

## Test Suite

| File | What it validates |
|---|---|
| `test_smoke_payment.py` | Mint reachable, TollGate reachable, wallet funded, payment returns session event (kind 1022), gate opened, balance endpoint works |
| `test_mint_failure.py` | Kill mint mid-session → TollGate degrades gracefully (no crash) → restart mint → TollGate recovers and accepts payments again |
| `test_two_router_autopay.py` | Two-router chain: reseller processes payment without crashing, both TollGates stay alive |

## What This Tests vs What It Doesn't

### Tested (logic-level)
- Cashu token minting, sending, verification
- Payment event processing (Nostr kind 21000)
- Session event generation (kind 1022)
- Profit-share math
- Mint health tracking and degraded mode
- Config loading and migration
- Two-router autopay payment logic
- Valve timer logic (open/extend/close via fake ndsctl)

### Not tested (needs QEMU/real hardware)
- Packet-level gate control (actual NoDogSplash/iptables)
- WiFi scanning, SSID detection, WPA connection
- DHCP lease assignment
- ARP table MAC resolution
- Upstream TollGate discovery via WiFi probe

## Files

| File | Purpose |
|---|---|
| `docker-compose.yml` | Service definitions, networking, health checks |
| `Dockerfile.tollgate` | Multi-stage Go build → Debian + fake ndsctl |
| `Dockerfile.mint` | cdk-mintd with FakeWallet (cargo install) |
| `Dockerfile.client` | Python + cdk-cli + nak test runner |
| `fake-ndsctl.sh` | Drop-in ndsctl replacement that logs auth/deauth |
| `configs/upstream-config.json` | TollGate config for upstream container |
| `configs/reseller-config.json` | TollGate config for reseller container |
| `configs/*-identities.json` | Nostr identities for signing |
| `configs/install.json` | Minimal install metadata |
| `configs/dhcp.leases` | Fake DHCP lease table (maps client IP → MAC) |
| `conftest.py` | Shared pytest fixtures |
| `test_*.py` | Test suites |

## CI Integration

Add to `.github/workflows/`:

```yaml
cloud-lab-tests:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- working-directory: tests/cloud-lab
run: |
docker compose up -d mint upstream
docker compose run --rm client
docker compose down -v
```

The mint container build (cargo install cdk-mintd) takes ~5-8 minutes
on first run. Docker layer caching makes subsequent runs fast.
2 changes: 2 additions & 0 deletions tests/cloud-lab/configs/dhcp.leases
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
0 02:00:00:00:00:20 172.28.0.20 client *
0 02:00:00:00:00:11 172.28.0.11 reseller *
10 changes: 10 additions & 0 deletions tests/cloud-lab/configs/install.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"config_version": "v0.0.2",
"package_path": "false",
"ip_address_randomized": false,
"install_time": 0,
"download_time": 0,
"release_channel": "stable",
"ensure_default_timestamp": 1719000000,
"installed_version": "0.0.0"
}
66 changes: 66 additions & 0 deletions tests/cloud-lab/configs/reseller-config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
{
"config_version": "v0.0.8",
"log_level": "debug",
"metric": "milliseconds",
"step_size": 60000,
"margin": 0.15,
"show_setup": false,
"reseller_mode": true,
"accepted_mints": [
{
"url": "http://mint:8085",
"min_balance": 0,
"balance_tolerance_percent": 0,
"payout_interval_seconds": 999999,
"min_payout_amount": 999999,
"price_per_step": 2,
"price_unit": "sats",
"purchase_min_steps": 0
}
],
"profit_share": [
{ "factor": 1.0, "identity": "owner" }
],
"upstream_detector": {
"probe_timeout": 5000000000,
"probe_retry_count": 1,
"probe_retry_delay": 1000000000,
"require_valid_signature": false,
"ignore_interfaces": ["lo"],
"only_interfaces": [],
"discovery_timeout": 10000000000
},
"upstream_session_manager": {
"max_price_per_millisecond": 0.01,
"max_price_per_byte": 0.001,
"trust": {
"default_policy": "trust_all",
"allowlist": [],
"blocklist": []
},
"sessions": {
"preferred_session_increments_milliseconds": 60000,
"preferred_session_increments_bytes": 131100000,
"millisecond_renewal_offset": 10000,
"bytes_renewal_offset": 131100000
},
"usage_tracking": {
"data_monitoring_interval": 500000000
}
},
"upstream_wifi": {
"scan_interval_seconds": 300,
"fast_check_seconds": 30,
"lost_threshold": 2,
"hysteresis_db": 12,
"signal_floor": -85,
"blacklist_ttl_minutes": 60,
"emergency_penalty": 20,
"max_consecutive_failures": 3,
"switch_cooldown_minutes": 10,
"startup_grace_seconds": 30,
"post_switch_wait_seconds": 5,
"dhcp_timeout_seconds": 180,
"manual_pause_seconds": 120
}
}
16 changes: 16 additions & 0 deletions tests/cloud-lab/configs/reseller-identities.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"config_version": "v0.0.1",
"owned_identities": [
{
"name": "merchant",
"privatekey": "nsec1fgaau8m9xd7w3a9q7zwu9q3qj6q3qj6q3qj6q3qj6q3qj6q3qj6q"
}
],
"public_identities": [
{
"name": "owner",
"pubkey": "",
"lightning_address": "owner@reseller.example.com"
}
]
}
Loading