Files
amethyst/cli/tests/headless/helpers.sh
T
Claude 9e41858c8f test(marmot): interop coverage for avatars, edits, deletions and media
Six new harness tests against MDK, all green in one clean run:

  20 avatar-url amy->wn   21 avatar-url wn->amy
  22 edit amy->wn         23 deletion amy->wn
  24 media-v2 amy->wn     25 media-v2 wn->amy

The media pair needed a blob store, so the harness now runs a loopback
Blossom server of its own (`blossom-server.py`, PUT /upload + GET
/<sha256>). It holds nothing but ciphertext — the file key comes from each
group's MLS exporter — so a download that hashes back to the original
bytes is proof both implementations derived the same key. That is the
whole point of tests 24 and 25, and they pass in both directions.

Test 20 sends a URL that is deliberately NOT normalized
(`https://Example.COM:443/a/./avatars/../pic.png`) and asserts both what we
store and what wn reads back. Normalization is the wire format for this
component — a decoder rejects bytes that differ from its own serialization
— so a disagreement here is a group the other side cannot read at all,
not a cosmetic difference.

Tests 22 and 23 assert what the protocol actually says rather than what a
renderer happens to do. For the edit that means a well-formed kind:1009
reaching wn (one `e` tag naming the target, the replacement as its body,
the right author) plus our own reader applying the overlay — MDK's storage
deliberately leaves the original row's body alone and lets the client
compute the chain, so asserting on painted text would be testing its TUI.
For the deletion it means the `deleted` flag on wn's materialized
timeline, which is its user-visible truth.

Two harness bugs surfaced while getting there, both of the kind that make
a failure unreadable rather than wrong. `run.env` — where tests hand each
other group ids — survived the per-run state wipe, so a `--tests` subset
that consumed without re-creating failed on "not a member" for a group id
from a previous run. And `amy_json` read `$?` inside `if ! cmd`, where it
is the status of the negation, so every failure reported "exit 0".

Push (kind 451) has no cross-implementation test here and cannot: the
owner proof is an UNPUBLISHED event handed to a push service, the
reference CLI exposes no command that emits one, and the harness runs no
push service. There is nothing for two implementations to disagree about
on the wire.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016kCuA6tc4JQzHPCDd39GHq
2026-09-09 17:05:13 +00:00

71 lines
2.7 KiB
Bash

# shellcheck shell=bash
#
# helpers.sh — thin wrappers that keep the per-test code tight.
# --- amy wrapper -------------------------------------------------------------
# Tests isolate by overriding $HOME for the amy subprocess; amy reads the
# env var directly (not Java's `user.home`, which JDK 21 pulls from
# /etc/passwd) and treats $HOME/.amy/ as its tree. No --data-dir flag,
# no test-only escape hatch — production code path, fresh every run.
#
# `--secret-backend=plaintext` keeps these throwaway runs headless —
# the default `auto` would try the OS keychain (not available in CI) and
# then ask for a NIP-49 passphrase. Plaintext still writes 0600-owner-only.
#
# `--json` opts into amy's machine-readable output (the harness parses it
# with jq); the default human-text output is for terminal use.
amy_a() { HOME="$STATE_DIR" "$AMY_BIN" --account A --secret-backend plaintext --json "$@"; }
# Run amy, log stderr, surface JSON on stdout, remember last result.
amy_json() {
local out rc
# Capture the status separately: inside `if ! cmd; then`, `$?` is the status
# of the negation (always 0), so the message reported "exit 0" for every
# failure and told a reader nothing about what went wrong.
out=$(amy_a "$@" 2>>"$LOG_FILE"); rc=$?
if [[ $rc -ne 0 ]]; then
fail_msg "amy $*: exit $rc (see $LOG_FILE)"
printf '%s\n' "$out" >>"$LOG_FILE"
return 1
fi
printf '%s' "$out"
}
# Convenience extractors — the CLI emits one JSON object per success so we can
# jq with impunity.
amy_field() {
# usage: amy_field '.group_id' init [args...]
local path="$1"; shift
amy_json "$@" | jq -r "$path"
}
# --- assertion helpers -------------------------------------------------------
# Assert a substring is present in a variable; append a failed result on miss
# and return 1. Positive case just logs.
assert_contains() {
local haystack="$1" needle="$2" test_id="$3" note="${4:-}"
if [[ "$haystack" == *"$needle"* ]]; then
info "assertion hit: $test_id contains \"$needle\""
return 0
fi
fail_msg "$test_id: missing \"$needle\" (${note:-no note})"
info "actual: $haystack"
record_result "$test_id" fail "${note:-missing \"$needle\"}"
return 1
}
# Assert two strings are equal (leniently trimmed).
assert_eq() {
local actual="$1" expected="$2" test_id="$3" note="${4:-}"
if [[ "${actual// /}" == "${expected// /}" ]]; then
info "assertion hit: $test_id \"$actual\" == \"$expected\""
return 0
fi
fail_msg "$test_id: expected \"$expected\", got \"$actual\" (${note:-})"
record_result "$test_id" fail "${note:-mismatch}"
return 1
}
# --- wn-side pollers (delegates to lib.sh) -----------------------------------
# Both exist in lib.sh already; this file only adds headless-specific niceties.