test(cli): move tests/ out of tools/, separate marmot vs dm
`tools/` is for libraries (arti-build); shell-based interop harnesses
that drive the `amy` binary belong with the CLI module. New layout:
cli/tests/
├── lib.sh # shared logging + result tracking
├── headless/helpers.sh # shared amy_a / amy_json / assertions
├── marmot/ # MLS group-messaging interop (vs whitenoise-rs)
│ ├── marmot-interop.sh
│ ├── marmot-interop-headless.sh
│ ├── setup.sh
│ ├── tests-create.sh
│ ├── tests-manage.sh
│ ├── tests-extras.sh
│ └── patches/
└── dm/ # NIP-17 DM interop (amy ↔ amy)
├── dm-interop-headless.sh
├── setup.sh
└── tests-dm.sh
Per-suite changes:
- Each suite owns its own setup.sh; previously the Marmot setup was at
headless/setup.sh and the DM setup at headless/setup-dm.sh, which
implied shared infra they don't actually share.
- `cli/tests/lib.sh` and `cli/tests/headless/helpers.sh` are the only
shared bits across suites.
- The DM harness still reuses Marmot's `start_local_relay` /
`stop_local_relay` (sourced via `../marmot/setup.sh`) — same relay
binary, no duplication.
- All sourcing paths updated; REPO_ROOT now climbs three levels (was
two when the harness lived under `tools/`); patches/ is now a sibling
of marmot/setup.sh rather than under marmot/headless/.
- `.gitignore` updated to cover both suites' state dirs.
cli/ROADMAP.md and cli/tests/README.md updated to point at the new paths.
No behavioural change — every test runs the same code; only the file
layout and source paths moved.
This commit is contained in:
+5
-5
@@ -95,11 +95,11 @@ move anything, re-audit — you're probably duplicating logic.
|
|||||||
8. **Distribution** — Homebrew + Scoop + `.deb` in the same release
|
8. **Distribution** — Homebrew + Scoop + `.deb` in the same release
|
||||||
pipeline as desktop. Plan: `cli/plans/2026-04-21-cli-distribution.md`.
|
pipeline as desktop. Plan: `cli/plans/2026-04-21-cli-distribution.md`.
|
||||||
9. **Test suite** — end-to-end against a local relay. Marmot interop is
|
9. **Test suite** — end-to-end against a local relay. Marmot interop is
|
||||||
covered by `tools/marmot-interop/marmot-interop-headless.sh`; NIP-17
|
covered by `cli/tests/marmot/marmot-interop-headless.sh`; NIP-17 DM
|
||||||
DM interop between two `amy` clients is covered by
|
interop between two `amy` clients is covered by
|
||||||
`tools/marmot-interop/dm-interop-headless.sh` (text + file + strict
|
`cli/tests/dm/dm-interop-headless.sh` (text + file + strict 10050 +
|
||||||
10050 + fallback + cursor-advance). Neither runs in CI yet (both
|
fallback + cursor-advance). Neither runs in CI yet (both need Rust +
|
||||||
need Rust + ~3 min cold relay build).
|
~3 min cold relay build).
|
||||||
10. **Everything else in the matrix.**
|
10. **Everything else in the matrix.**
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|||||||
@@ -0,0 +1,3 @@
|
|||||||
|
marmot/state/
|
||||||
|
marmot/state-headless/
|
||||||
|
dm/state-dm-headless/
|
||||||
@@ -1,22 +1,45 @@
|
|||||||
# Interop Test Harnesses
|
# amy CLI test harnesses
|
||||||
|
|
||||||
Two flavours of the Marmot harness, same scenarios:
|
Shell-based end-to-end harnesses that drive the `amy` CLI binary against a
|
||||||
|
loopback `nostr-rs-relay`. Layout:
|
||||||
|
|
||||||
- **`marmot-interop.sh`** — interactive. Drives B/C via `wn` and **prompts the
|
```
|
||||||
human** to perform each Amethyst-side step in the mobile UI (Identity A).
|
cli/tests/
|
||||||
Use this for final UI verification.
|
├── lib.sh # shared logging, results, assertions
|
||||||
- **`marmot-interop-headless.sh`** — zero prompts. Drives A via the `amy` CLI
|
├── headless/ # shared bits used by every harness
|
||||||
(`./gradlew :cli:installDist`) and B/C via `wn`. Runs every scenario
|
│ └── helpers.sh
|
||||||
end-to-end and exits with a pass/fail summary. Use this for CI and for
|
├── marmot/ # Marmot / MLS group-messaging interop
|
||||||
iterating on the Nostr/Marmot plumbing without needing to touch a phone.
|
│ ├── marmot-interop.sh # interactive — prompts Amethyst Android UI
|
||||||
|
│ ├── marmot-interop-headless.sh # zero-prompt
|
||||||
|
│ ├── setup.sh # preflight + wn + relay + identities
|
||||||
|
│ ├── tests-create.sh # tests 01–05
|
||||||
|
│ ├── tests-manage.sh # tests 06–08, 11
|
||||||
|
│ ├── tests-extras.sh # tests 09, 10, 12, 13
|
||||||
|
│ └── patches/ # whitenoise-rs harness patches
|
||||||
|
└── dm/ # NIP-17 DM interop (amy ↔ amy)
|
||||||
|
├── dm-interop-headless.sh
|
||||||
|
├── setup.sh # preflight + identities
|
||||||
|
└── tests-dm.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
The Marmot harnesses come in two flavours, same scenarios:
|
||||||
|
|
||||||
|
- **`marmot/marmot-interop.sh`** — interactive. Drives B/C via `wn` and
|
||||||
|
**prompts the human** to perform each Amethyst-side step in the mobile UI
|
||||||
|
(Identity A). Use this for final UI verification.
|
||||||
|
- **`marmot/marmot-interop-headless.sh`** — zero prompts. Drives A via the
|
||||||
|
`amy` CLI (`./gradlew :cli:installDist`) and B/C via `wn`. Runs every
|
||||||
|
scenario end-to-end and exits with a pass/fail summary. Use this for CI
|
||||||
|
and for iterating on the Nostr/Marmot plumbing without needing to touch a
|
||||||
|
phone.
|
||||||
|
|
||||||
A third, slimmer harness covers the NIP-17 DM surface:
|
A third, slimmer harness covers the NIP-17 DM surface:
|
||||||
|
|
||||||
- **`dm-interop-headless.sh`** — two `amy` processes (Identity A and
|
- **`dm/dm-interop-headless.sh`** — two `amy` processes (Identity A and
|
||||||
Identity D) exchange NIP-17 DMs through the loopback nostr-rs-relay.
|
Identity D) exchange NIP-17 DMs through the loopback nostr-rs-relay.
|
||||||
No whitenoise-rs required — only `amy` and the relay binary (which
|
No whitenoise-rs required — only `amy` and the relay binary (which
|
||||||
is shared with the Marmot harness's checkout at
|
is shared with the Marmot harness's checkout at
|
||||||
`state-headless/nostr-rs-relay/`).
|
`marmot/state-headless/nostr-rs-relay/`).
|
||||||
|
|
||||||
Both Marmot harnesses validate Amethyst against **whitenoise-rs**
|
Both Marmot harnesses validate Amethyst against **whitenoise-rs**
|
||||||
(https://github.com/marmot-protocol/whitenoise-rs), the reference Rust
|
(https://github.com/marmot-protocol/whitenoise-rs), the reference Rust
|
||||||
@@ -42,7 +65,7 @@ the end of the run.
|
|||||||
| 12 | Offline catch-up / replay | – |
|
| 12 | Offline catch-up / replay | – |
|
||||||
| 13 | KeyPackage rotation | – |
|
| 13 | KeyPackage rotation | – |
|
||||||
|
|
||||||
### DM (amy ↔ amy, NIP-17) — `dm-interop-headless.sh`
|
### DM (amy ↔ amy, NIP-17) — `dm/dm-interop-headless.sh`
|
||||||
|
|
||||||
| # | Test |
|
| # | Test |
|
||||||
|---|---|
|
|---|---|
|
||||||
@@ -11,7 +11,8 @@
|
|||||||
set -uo pipefail
|
set -uo pipefail
|
||||||
|
|
||||||
SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
|
SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
REPO_ROOT="$(cd -- "$SCRIPT_DIR/../.." && pwd)"
|
REPO_ROOT="$(cd -- "$SCRIPT_DIR/../../.." && pwd)"
|
||||||
|
TESTS_DIR="$(cd -- "$SCRIPT_DIR/.." && pwd)"
|
||||||
STATE_DIR="$SCRIPT_DIR/state-dm-headless"
|
STATE_DIR="$SCRIPT_DIR/state-dm-headless"
|
||||||
LOG_DIR="$STATE_DIR/logs"
|
LOG_DIR="$STATE_DIR/logs"
|
||||||
A_DIR="$STATE_DIR/A"
|
A_DIR="$STATE_DIR/A"
|
||||||
@@ -26,7 +27,7 @@ AMY_BIN="$REPO_ROOT/cli/build/install/amy/bin/amy"
|
|||||||
# Share the nostr-rs-relay checkout with the Marmot harness to avoid
|
# Share the nostr-rs-relay checkout with the Marmot harness to avoid
|
||||||
# rebuilding it twice. Override RELAY_REPO / RELAY_DATA if you want full
|
# rebuilding it twice. Override RELAY_REPO / RELAY_DATA if you want full
|
||||||
# isolation between runs.
|
# isolation between runs.
|
||||||
RELAY_REPO="${RELAY_REPO:-$SCRIPT_DIR/state-headless/nostr-rs-relay}"
|
RELAY_REPO="${RELAY_REPO:-$TESTS_DIR/marmot/state-headless/nostr-rs-relay}"
|
||||||
RELAY_BIN="$RELAY_REPO/target/release/nostr-rs-relay"
|
RELAY_BIN="$RELAY_REPO/target/release/nostr-rs-relay"
|
||||||
RELAY_DATA="$STATE_DIR/relay"
|
RELAY_DATA="$STATE_DIR/relay"
|
||||||
RELAY_PORT="${RELAY_PORT:-8090}"
|
RELAY_PORT="${RELAY_PORT:-8090}"
|
||||||
@@ -54,22 +55,23 @@ mkdir -p "$STATE_DIR" "$LOG_DIR" "$A_DIR" "$D_DIR"
|
|||||||
: >"$LOG_FILE"
|
: >"$LOG_FILE"
|
||||||
: >"$RESULTS_FILE"
|
: >"$RESULTS_FILE"
|
||||||
|
|
||||||
# Reuse the logging + result helpers from the Marmot harness. lib.sh
|
# Reuse the logging + result helpers shared between every harness.
|
||||||
# also declares wn-specific helpers; they're harmless when unused.
|
# lib.sh declares wn-specific helpers too — harmless when unused.
|
||||||
# shellcheck source=lib.sh
|
# shellcheck source=../lib.sh
|
||||||
source "$SCRIPT_DIR/lib.sh"
|
source "$TESTS_DIR/lib.sh"
|
||||||
|
|
||||||
# Reuse start_local_relay / stop_local_relay from setup.sh. preflight()
|
# Reuse start_local_relay / stop_local_relay from the Marmot harness's
|
||||||
# there also builds whitenoise-rs, which we don't need — setup-dm.sh
|
# setup.sh — the relay lifecycle is identical. preflight() there also
|
||||||
|
# builds whitenoise-rs, which we don't need; setup.sh in this dir
|
||||||
# defines a slimmer preflight_dm().
|
# defines a slimmer preflight_dm().
|
||||||
# shellcheck source=headless/setup.sh
|
# shellcheck source=../marmot/setup.sh
|
||||||
source "$SCRIPT_DIR/headless/setup.sh"
|
source "$TESTS_DIR/marmot/setup.sh"
|
||||||
# shellcheck source=headless/setup-dm.sh
|
# shellcheck source=setup.sh
|
||||||
source "$SCRIPT_DIR/headless/setup-dm.sh"
|
source "$SCRIPT_DIR/setup.sh"
|
||||||
# shellcheck source=headless/helpers.sh
|
# shellcheck source=../headless/helpers.sh
|
||||||
source "$SCRIPT_DIR/headless/helpers.sh"
|
source "$TESTS_DIR/headless/helpers.sh"
|
||||||
# shellcheck source=headless/tests-dm.sh
|
# shellcheck source=tests-dm.sh
|
||||||
source "$SCRIPT_DIR/headless/tests-dm.sh"
|
source "$SCRIPT_DIR/tests-dm.sh"
|
||||||
|
|
||||||
cleanup() {
|
cleanup() {
|
||||||
local rc=$?
|
local rc=$?
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
# shellcheck shell=bash
|
# shellcheck shell=bash
|
||||||
#
|
#
|
||||||
# headless/setup-dm.sh — amy-only preflight + identity bootstrap for the
|
# setup.sh — amy-only preflight + identity bootstrap for the
|
||||||
# NIP-17 DM interop harness. Much slimmer than the Marmot setup:
|
# NIP-17 DM interop harness. Much slimmer than the Marmot setup:
|
||||||
#
|
#
|
||||||
# - Builds `amy` (same retry-on-503 logic as setup.sh).
|
# - Builds `amy` (same retry-on-503 logic as setup.sh).
|
||||||
@@ -11,7 +11,7 @@
|
|||||||
# recipient-inbox routing has something to resolve to.
|
# recipient-inbox routing has something to resolve to.
|
||||||
#
|
#
|
||||||
# The heavy `start_local_relay` / `stop_local_relay` helpers live in
|
# The heavy `start_local_relay` / `stop_local_relay` helpers live in
|
||||||
# headless/setup.sh and are sourced by the top-level harness.
|
# the Marmot harness's setup.sh and are sourced by the top-level harness.
|
||||||
|
|
||||||
# --- preflight (amy + relay only, no wn / Marmot patches) -------------------
|
# --- preflight (amy + relay only, no wn / Marmot patches) -------------------
|
||||||
preflight_dm() {
|
preflight_dm() {
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
# shellcheck shell=bash
|
# shellcheck shell=bash
|
||||||
#
|
#
|
||||||
# headless/tests-dm.sh — NIP-17 DM interop tests for two `amy` clients.
|
# tests-dm.sh — NIP-17 DM interop tests for two `amy` clients.
|
||||||
#
|
#
|
||||||
# Identity A (sender) and Identity D (recipient) each live in their own
|
# Identity A (sender) and Identity D (recipient) each live in their own
|
||||||
# --data-dir and share one loopback nostr-rs-relay. Tests cover:
|
# --data-dir and share one loopback nostr-rs-relay. Tests cover:
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
# shellcheck shell=bash
|
# shellcheck shell=bash
|
||||||
#
|
#
|
||||||
# headless/helpers.sh — thin wrappers that keep the per-test code tight.
|
# helpers.sh — thin wrappers that keep the per-test code tight.
|
||||||
|
|
||||||
# --- amy wrapper -------------------------------------------------------------
|
# --- amy wrapper -------------------------------------------------------------
|
||||||
amy_a() { "$AMY_BIN" --data-dir "$A_DIR" "$@"; }
|
amy_a() { "$AMY_BIN" --data-dir "$A_DIR" "$@"; }
|
||||||
+14
-13
@@ -14,7 +14,8 @@
|
|||||||
set -uo pipefail
|
set -uo pipefail
|
||||||
|
|
||||||
SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
|
SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
REPO_ROOT="$(cd -- "$SCRIPT_DIR/../.." && pwd)"
|
REPO_ROOT="$(cd -- "$SCRIPT_DIR/../../.." && pwd)"
|
||||||
|
TESTS_DIR="$(cd -- "$SCRIPT_DIR/.." && pwd)"
|
||||||
STATE_DIR="$SCRIPT_DIR/state-headless"
|
STATE_DIR="$SCRIPT_DIR/state-headless"
|
||||||
LOG_DIR="$STATE_DIR/logs"
|
LOG_DIR="$STATE_DIR/logs"
|
||||||
A_DIR="$STATE_DIR/A"
|
A_DIR="$STATE_DIR/A"
|
||||||
@@ -66,19 +67,19 @@ mkdir -p "$STATE_DIR" "$LOG_DIR" "$A_DIR" "$B_DIR/logs" "$C_DIR/logs"
|
|||||||
: >"$RESULTS_FILE"
|
: >"$RESULTS_FILE"
|
||||||
|
|
||||||
# Reuse colours / logging / dump_daemon_diagnostics from the interactive harness.
|
# Reuse colours / logging / dump_daemon_diagnostics from the interactive harness.
|
||||||
# shellcheck source=lib.sh
|
# shellcheck source=../lib.sh
|
||||||
source "$SCRIPT_DIR/lib.sh"
|
source "$TESTS_DIR/lib.sh"
|
||||||
|
|
||||||
# shellcheck source=headless/setup.sh
|
# shellcheck source=setup.sh
|
||||||
source "$SCRIPT_DIR/headless/setup.sh"
|
source "$SCRIPT_DIR/setup.sh"
|
||||||
# shellcheck source=headless/helpers.sh
|
# shellcheck source=../headless/helpers.sh
|
||||||
source "$SCRIPT_DIR/headless/helpers.sh"
|
source "$TESTS_DIR/headless/helpers.sh"
|
||||||
# shellcheck source=headless/tests-create.sh
|
# shellcheck source=tests-create.sh
|
||||||
source "$SCRIPT_DIR/headless/tests-create.sh"
|
source "$SCRIPT_DIR/tests-create.sh"
|
||||||
# shellcheck source=headless/tests-manage.sh
|
# shellcheck source=tests-manage.sh
|
||||||
source "$SCRIPT_DIR/headless/tests-manage.sh"
|
source "$SCRIPT_DIR/tests-manage.sh"
|
||||||
# shellcheck source=headless/tests-extras.sh
|
# shellcheck source=tests-extras.sh
|
||||||
source "$SCRIPT_DIR/headless/tests-extras.sh"
|
source "$SCRIPT_DIR/tests-extras.sh"
|
||||||
|
|
||||||
# Make sure Ctrl+C / SIGTERM / SIGHUP all run the full cleanup path —
|
# Make sure Ctrl+C / SIGTERM / SIGHUP all run the full cleanup path —
|
||||||
# otherwise wnd is nohup'd and keeps running after the script dies,
|
# otherwise wnd is nohup'd and keeps running after the script dies,
|
||||||
@@ -10,6 +10,7 @@
|
|||||||
set -uo pipefail
|
set -uo pipefail
|
||||||
|
|
||||||
SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
|
SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
TESTS_DIR="$(cd -- "$SCRIPT_DIR/.." && pwd)"
|
||||||
STATE_DIR="$SCRIPT_DIR/state"
|
STATE_DIR="$SCRIPT_DIR/state"
|
||||||
LOG_DIR="$STATE_DIR/logs"
|
LOG_DIR="$STATE_DIR/logs"
|
||||||
B_DIR="$STATE_DIR/B"
|
B_DIR="$STATE_DIR/B"
|
||||||
@@ -75,8 +76,8 @@ mkdir -p "$STATE_DIR" "$LOG_DIR" "$B_DIR/logs" "$C_DIR/logs"
|
|||||||
: >"$LOG_FILE"
|
: >"$LOG_FILE"
|
||||||
: >"$RESULTS_FILE"
|
: >"$RESULTS_FILE"
|
||||||
|
|
||||||
# shellcheck source=lib.sh
|
# shellcheck source=../lib.sh
|
||||||
source "$SCRIPT_DIR/lib.sh"
|
source "$TESTS_DIR/lib.sh"
|
||||||
|
|
||||||
# --- preflight ---------------------------------------------------------------
|
# --- preflight ---------------------------------------------------------------
|
||||||
preflight() {
|
preflight() {
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
# shellcheck shell=bash
|
# shellcheck shell=bash
|
||||||
#
|
#
|
||||||
# headless/setup.sh — preflight + daemon lifecycle + identity bootstrap.
|
# setup.sh — preflight + daemon lifecycle + identity bootstrap.
|
||||||
# Sourced from marmot-interop-headless.sh.
|
# Sourced from marmot-interop-headless.sh.
|
||||||
|
|
||||||
# --- preflight ---------------------------------------------------------------
|
# --- preflight ---------------------------------------------------------------
|
||||||
@@ -90,7 +90,7 @@ preflight() {
|
|||||||
if [[ ! -f "$marker" ]]; then
|
if [[ ! -f "$marker" ]]; then
|
||||||
step "patching whitenoise-rs: $name"
|
step "patching whitenoise-rs: $name"
|
||||||
if ( cd "$WN_REPO" && patch -p1 --forward --reject-file=- \
|
if ( cd "$WN_REPO" && patch -p1 --forward --reject-file=- \
|
||||||
<"$SCRIPT_DIR/headless/patches/$name" >>"$LOG_FILE" 2>&1 ); then
|
<"$SCRIPT_DIR/patches/$name" >>"$LOG_FILE" 2>&1 ); then
|
||||||
touch "$marker"
|
touch "$marker"
|
||||||
# Invalidate the previous build so the patched source is picked up.
|
# Invalidate the previous build so the patched source is picked up.
|
||||||
rm -f "$WN_BIN" "$WND_BIN"
|
rm -f "$WN_BIN" "$WND_BIN"
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
# shellcheck shell=bash
|
# shellcheck shell=bash
|
||||||
#
|
#
|
||||||
# headless/tests-create.sh — tests 01..05.
|
# tests-create.sh — tests 01..05.
|
||||||
# Focus: KeyPackage discovery, group creation, invites, initial messages.
|
# Focus: KeyPackage discovery, group creation, invites, initial messages.
|
||||||
|
|
||||||
test_01_keypackage_discovery() {
|
test_01_keypackage_discovery() {
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
# shellcheck shell=bash
|
# shellcheck shell=bash
|
||||||
#
|
#
|
||||||
# headless/tests-extras.sh — tests 09, 10, 12, 13.
|
# tests-extras.sh — tests 09, 10, 12, 13.
|
||||||
# Focus: reactions/replies, concurrent commits, offline catchup, KP rotation.
|
# Focus: reactions/replies, concurrent commits, offline catchup, KP rotation.
|
||||||
|
|
||||||
test_09_reply_react_unreact() {
|
test_09_reply_react_unreact() {
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
# shellcheck shell=bash
|
# shellcheck shell=bash
|
||||||
#
|
#
|
||||||
# headless/tests-manage.sh — tests 06, 07, 08, 11.
|
# tests-manage.sh — tests 06, 07, 08, 11.
|
||||||
# Focus: removal, metadata rename, admin promote/demote, leave.
|
# Focus: removal, metadata rename, admin promote/demote, leave.
|
||||||
|
|
||||||
test_06_member_removal() {
|
test_06_member_removal() {
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
state/
|
|
||||||
state-headless/
|
|
||||||
Reference in New Issue
Block a user