fix(cli): rename global --name to --account to free --name for subcommands
The marmot harness surfaced the bug on first run: amy stripped `marmot group create --name "Interop-02"` thinking the global account selector ran into the group's display-name flag. Result: amy resolved the "Interop-02" account, found no identity.json, and errored — no group ever got created. Renames the global account-selector flag to `--account`. The per- subcommand `--name` flags (`marmot group create --name "Demo"`, `profile edit --name "Alice"`, `marmot await group --name X`) are untouched — they're free of the global parser now that it doesn't claim the same name. Sweep: - `Main.kt`: GlobalFlag.NAME → ACCOUNT, long "--account". - `Config.kt`: DataDir.resolve param renamed nameFlag → accountFlag; every error message points the user at --account. - `UseCommand.kt`: error hint says `amy --account NAME init`. - All test wrappers + direct $AMY_BIN calls under cli/tests/ swap the global `--name X` for `--account X` (subcommand --name kept exactly where it appeared). - README + DEVELOPMENT updated.
This commit is contained in:
+6
-6
@@ -201,11 +201,11 @@ Amy-specific layer still needs its own coverage:
|
|||||||
|
|
||||||
| Layer | Test approach |
|
| Layer | Test approach |
|
||||||
|---|---|
|
|---|---|
|
||||||
| Argument parsing (`Args`, flag forms, `--name=…` vs `--name …`) | Plain JVM unit tests in `cli/src/test/kotlin/`. |
|
| Argument parsing (`Args`, flag forms, `--account=…` vs `--account …`) | Plain JVM unit tests in `cli/src/test/kotlin/`. |
|
||||||
| Error / exit-code contract (bad args → 2, await timeout → 124, runtime → 1) | Table-driven tests invoking `main(argv)` with captured stdout/stderr. |
|
| Error / exit-code contract (bad args → 2, await timeout → 124, runtime → 1) | Table-driven tests invoking `main(argv)` with captured stdout/stderr. |
|
||||||
| JSON output shape (each command's keys and types under `--json`) | Snapshot tests: run a command with `--json` against a throwaway `$HOME` (`HOME=$(mktemp -d) amy --name X …`), assert the JSON matches a golden file. The default text render has no shape contract and shouldn't be snapshotted. |
|
| JSON output shape (each command's keys and types under `--json`) | Snapshot tests: run a command with `--json` against a throwaway `$HOME` (`HOME=$(mktemp -d) amy --account X …`), assert the JSON matches a golden file. The default text render has no shape contract and shouldn't be snapshotted. |
|
||||||
| File layout on disk (`identity.json`, `events-store/…`, `marmot/groups/*.mls`, `marmot/keypackages.bundle`) | Structural assertions after a command sequence. |
|
| File layout on disk (`identity.json`, `events-store/…`, `marmot/groups/*.mls`, `marmot/keypackages.bundle`) | Structural assertions after a command sequence. |
|
||||||
| Round-trip between two accounts on a local relay | End-to-end shell harnesses under `cli/tests/`. Each harness spins up a local `nostr-rs-relay` and a fresh `$HOME=$STATE_DIR` so amy sees a virgin `~/.amy/`, then bootstraps multiple identities (`--name A`, `--name D`, etc.) sharing the same `~/.amy/shared/events-store/` and drives a scenario through them (+ `wn` for Marmot interop against whitenoise-rs). Today there are two suites: `cli/tests/marmot/` (MLS scenarios vs whitenoise-rs) and `cli/tests/dm/` (NIP-17 DM round-trips between two `amy` accounts). |
|
| Round-trip between two accounts on a local relay | End-to-end shell harnesses under `cli/tests/`. Each harness spins up a local `nostr-rs-relay` and a fresh `$HOME=$STATE_DIR` so amy sees a virgin `~/.amy/`, then bootstraps multiple identities (`--account A`, `--account D`, etc.) sharing the same `~/.amy/shared/events-store/` and drives a scenario through them (+ `wn` for Marmot interop against whitenoise-rs). Today there are two suites: `cli/tests/marmot/` (MLS scenarios vs whitenoise-rs) and `cli/tests/dm/` (NIP-17 DM round-trips between two `amy` accounts). |
|
||||||
| Interop with other clients | Covered by `cli/tests/marmot/marmot-interop-headless.sh` (drives Amy against whitenoise-rs `wn`/`wnd`). Add new scenarios there or start a new sibling under `cli/tests/`. |
|
| Interop with other clients | Covered by `cli/tests/marmot/marmot-interop-headless.sh` (drives Amy against whitenoise-rs `wn`/`wnd`). Add new scenarios there or start a new sibling under `cli/tests/`. |
|
||||||
|
|
||||||
**What not to test here:** event signing, filter assembly, MLS
|
**What not to test here:** event signing, filter assembly, MLS
|
||||||
@@ -224,12 +224,12 @@ At the byte-banging level, a minimal round-trip looks like:
|
|||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
export HOME=$(mktemp -d) # virgin ~/.amy/ for the duration of this script
|
export HOME=$(mktemp -d) # virgin ~/.amy/ for the duration of this script
|
||||||
|
|
||||||
amy --name alice create
|
amy --account alice create
|
||||||
amy --name bob create
|
amy --account bob create
|
||||||
|
|
||||||
# ... the scenario under test ...
|
# ... the scenario under test ...
|
||||||
|
|
||||||
amy --name bob marmot await message "$GID" --match "hello" --timeout 60
|
amy --account bob marmot await message "$GID" --match "hello" --timeout 60
|
||||||
```
|
```
|
||||||
|
|
||||||
If an Amethyst scenario cannot be scripted through Amy yet, that's
|
If an Amethyst scenario cannot be scripted through Amy yet, that's
|
||||||
|
|||||||
+15
-15
@@ -32,7 +32,7 @@ What every caller — user, script, agent, CI — can rely on:
|
|||||||
- **`--json` is the machine contract. One line. One object.** Stable
|
- **`--json` is the machine contract. One line. One object.** Stable
|
||||||
snake_case keys. Pipe it into `jq`, parse it from Python, hand it
|
snake_case keys. Pipe it into `jq`, parse it from Python, hand it
|
||||||
to an agent. Pass `--json` anywhere before the subcommand:
|
to an agent. Pass `--json` anywhere before the subcommand:
|
||||||
`amy --json whoami`, `amy --name alice --json marmot group list`.
|
`amy --json whoami`, `amy --account alice --json marmot group list`.
|
||||||
- **stderr is for humans.** Progress, warnings, per-relay ACK traces.
|
- **stderr is for humans.** Progress, warnings, per-relay ACK traces.
|
||||||
Safe to discard. Errors land here too: `error: <code>: <detail>` by
|
Safe to discard. Errors land here too: `error: <code>: <detail>` by
|
||||||
default, or JSON `{"error":"…","detail":"…"}` under `--json`.
|
default, or JSON `{"error":"…","detail":"…"}` under `--json`.
|
||||||
@@ -47,7 +47,7 @@ What every caller — user, script, agent, CI — can rely on:
|
|||||||
observed Nostr event lands in `~/.amy/shared/events-store/` (one
|
observed Nostr event lands in `~/.amy/shared/events-store/` (one
|
||||||
cache for every account on the machine). Delete to reset; copy to
|
cache for every account on the machine). Delete to reset; copy to
|
||||||
move. Tests isolate by overriding `$HOME` for the amy subprocess
|
move. Tests isolate by overriding `$HOME` for the amy subprocess
|
||||||
(`HOME=/tmp/run.123 amy --name alice …`) — same convention `git`,
|
(`HOME=/tmp/run.123 amy --account alice …`) — same convention `git`,
|
||||||
`gpg`, and `npm` use.
|
`gpg`, and `npm` use.
|
||||||
|
|
||||||
Only the `--json` shape and the exit codes are public API. The default
|
Only the `--json` shape and the exit codes are public API. The default
|
||||||
@@ -153,19 +153,19 @@ The `installDist` tree under `cli/build/install/amy/` is self-contained
|
|||||||
# 1. Create the alice account (~/.amy/alice/) with a full Amethyst-style
|
# 1. Create the alice account (~/.amy/alice/) with a full Amethyst-style
|
||||||
# bootstrap: keypair, default NIP-65 / inbox / key-package relays,
|
# bootstrap: keypair, default NIP-65 / inbox / key-package relays,
|
||||||
# nine bootstrap events.
|
# nine bootstrap events.
|
||||||
amy --name alice create
|
amy --account alice create
|
||||||
|
|
||||||
# 2. Publish a fresh MLS KeyPackage so others can invite you.
|
# 2. Publish a fresh MLS KeyPackage so others can invite you.
|
||||||
amy --name alice marmot key-package publish
|
amy --account alice marmot key-package publish
|
||||||
|
|
||||||
# 3. Create a group, invite someone, send a message.
|
# 3. Create a group, invite someone, send a message.
|
||||||
amy --name alice marmot group create --name "Test Group"
|
amy --account alice marmot group create --name "Test Group"
|
||||||
amy --name alice marmot group add <GID> npub1...bob
|
amy --account alice marmot group add <GID> npub1...bob
|
||||||
amy --name alice marmot message send <GID> "hello"
|
amy --account alice marmot message send <GID> "hello"
|
||||||
|
|
||||||
# 4. On the receiving side, in ~/.amy/bob/.
|
# 4. On the receiving side, in ~/.amy/bob/.
|
||||||
amy --name bob marmot await group --name "Test Group" --timeout 60
|
amy --account bob marmot await group --name "Test Group" --timeout 60
|
||||||
amy --name bob marmot message list <GID>
|
amy --account bob marmot message list <GID>
|
||||||
```
|
```
|
||||||
|
|
||||||
With one account you can drop the flag (auto-pick); with several, pin
|
With one account you can drop the flag (auto-pick); with several, pin
|
||||||
@@ -174,7 +174,7 @@ with `jq` by adding `--json` to switch stdout from human text to a
|
|||||||
parseable single-line JSON object:
|
parseable single-line JSON object:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
GID=$(amy --name alice --json marmot group create --name "Test" | jq -r .group_id)
|
GID=$(amy --account alice --json marmot group create --name "Test" | jq -r .group_id)
|
||||||
```
|
```
|
||||||
|
|
||||||
For an interop-test script template, see
|
For an interop-test script template, see
|
||||||
@@ -235,7 +235,7 @@ itself crashed".
|
|||||||
|
|
||||||
### Global flags
|
### Global flags
|
||||||
|
|
||||||
- `--name ACCOUNT` — pick which account under `~/.amy/<account>/` to
|
- `--account ACCOUNT` — pick which account under `~/.amy/<account>/` to
|
||||||
operate on. Required only when more than one account exists; with a
|
operate on. Required only when more than one account exists; with a
|
||||||
single account amy auto-picks. Override with `amy use NAME` to
|
single account amy auto-picks. Override with `amy use NAME` to
|
||||||
pin one as the default. ACCOUNT must match `[a-zA-Z0-9_-]{1,64}`.
|
pin one as the default. ACCOUNT must match `[a-zA-Z0-9_-]{1,64}`.
|
||||||
@@ -292,7 +292,7 @@ events.
|
|||||||
│ ├── addressable/… # one slot per (kind, pubkey, d-tag) for kind:30000-39999
|
│ ├── addressable/… # one slot per (kind, pubkey, d-tag) for kind:30000-39999
|
||||||
│ ├── idx/ # hardlink indexes (kind / author / owner / tag / fts / expires_at)
|
│ ├── idx/ # hardlink indexes (kind / author / owner / tag / fts / expires_at)
|
||||||
│ └── tombstones/ # NIP-09 / NIP-62 enforcement
|
│ └── tombstones/ # NIP-09 / NIP-62 enforcement
|
||||||
├── alice/ # one dir per account (e.g. created by `amy --name alice init`)
|
├── alice/ # one dir per account (e.g. created by `amy --account alice init`)
|
||||||
│ ├── identity.json # nsec/npub/hex — the account
|
│ ├── identity.json # nsec/npub/hex — the account
|
||||||
│ ├── state.json # sync cursors (giftWrapSince, groupSince)
|
│ ├── state.json # sync cursors (giftWrapSince, groupSince)
|
||||||
│ ├── aliases.json # local name → npub map (init writes a self-entry)
|
│ ├── aliases.json # local name → npub map (init writes a self-entry)
|
||||||
@@ -321,10 +321,10 @@ those events to upstream relays. There is no `relays.json`.
|
|||||||
## Troubleshooting
|
## Troubleshooting
|
||||||
|
|
||||||
- **`no identity`** — run `init`, `create`, or `login` first, or pass a
|
- **`no identity`** — run `init`, `create`, or `login` first, or pass a
|
||||||
different `--name`.
|
different `--account`.
|
||||||
- **`no account at ~/.amy`** — there's no account dir yet; create one
|
- **`no account at ~/.amy`** — there's no account dir yet; create one
|
||||||
with `amy --name <name> init`.
|
with `amy --account <name> init`.
|
||||||
- **`multiple accounts in ~/.amy (...)`** — disambiguate with `--name`
|
- **`multiple accounts in ~/.amy (...)`** — disambiguate with `--account`
|
||||||
on the next call, or pin a default via `amy use NAME`.
|
on the next call, or pin a default via `amy use NAME`.
|
||||||
- **`not_member`** — the group GID is unknown to this account. Run
|
- **`not_member`** — the group GID is unknown to this account. Run
|
||||||
`marmot group list` to confirm, or `marmot await group --name …` to
|
`marmot group list` to confirm, or `marmot await group --name …` to
|
||||||
|
|||||||
@@ -239,7 +239,7 @@ class DataDir(
|
|||||||
|
|
||||||
fun validateName(name: String): String {
|
fun validateName(name: String): String {
|
||||||
require(NAME_REGEX.matches(name)) {
|
require(NAME_REGEX.matches(name)) {
|
||||||
"--name must match [a-zA-Z0-9_-]{1,64} (got '$name')"
|
"--account must match [a-zA-Z0-9_-]{1,64} (got '$name')"
|
||||||
}
|
}
|
||||||
require(name !in RESERVED_NAMES) {
|
require(name !in RESERVED_NAMES) {
|
||||||
"'$name' is reserved (cannot be used as an account name)"
|
"'$name' is reserved (cannot be used as an account name)"
|
||||||
@@ -251,17 +251,17 @@ class DataDir(
|
|||||||
* Build a [DataDir] from the parsed CLI flags.
|
* Build a [DataDir] from the parsed CLI flags.
|
||||||
*
|
*
|
||||||
* Resolution order:
|
* Resolution order:
|
||||||
* 1. `--name X` if provided.
|
* 1. `--account X` if provided.
|
||||||
* 2. `<root>/current` marker (set by `amy use X`).
|
* 2. `<root>/current` marker (set by `amy use X`).
|
||||||
* 3. Sole subdirectory of `<root>` other than `shared/`.
|
* 3. Sole subdirectory of `<root>` other than `shared/`.
|
||||||
* 4. Error — caller must disambiguate via `--name` or `amy use`.
|
* 4. Error — caller must disambiguate via `--account` or `amy use`.
|
||||||
*/
|
*/
|
||||||
fun resolve(
|
fun resolve(
|
||||||
nameFlag: String?,
|
accountFlag: String?,
|
||||||
secrets: SecretStore,
|
secrets: SecretStore,
|
||||||
): DataDir {
|
): DataDir {
|
||||||
val rootBase = DEFAULT_ROOT
|
val rootBase = DEFAULT_ROOT
|
||||||
val name = if (nameFlag != null) validateName(nameFlag) else pickAccount(rootBase)
|
val name = if (accountFlag != null) validateName(accountFlag) else pickAccount(rootBase)
|
||||||
val accountRoot = File(rootBase, name).absoluteFile
|
val accountRoot = File(rootBase, name).absoluteFile
|
||||||
val sharedEvents = File(rootBase, "$SHARED_DIR_NAME/events-store").absoluteFile
|
val sharedEvents = File(rootBase, "$SHARED_DIR_NAME/events-store").absoluteFile
|
||||||
return DataDir(
|
return DataDir(
|
||||||
@@ -284,11 +284,11 @@ class DataDir(
|
|||||||
if (current.isFile) {
|
if (current.isFile) {
|
||||||
val pinned = current.readText().trim()
|
val pinned = current.readText().trim()
|
||||||
require(pinned.isNotEmpty()) {
|
require(pinned.isNotEmpty()) {
|
||||||
"${current.absolutePath} is empty; rewrite with `amy use <name>` or pass --name"
|
"${current.absolutePath} is empty; rewrite with `amy use <name>` or pass --account"
|
||||||
}
|
}
|
||||||
require(File(rootBase, pinned).isDirectory) {
|
require(File(rootBase, pinned).isDirectory) {
|
||||||
"${current.absolutePath} pins '$pinned' but ${File(rootBase, pinned).absolutePath} doesn't exist; " +
|
"${current.absolutePath} pins '$pinned' but ${File(rootBase, pinned).absolutePath} doesn't exist; " +
|
||||||
"rewrite with `amy use <name>` or pass --name"
|
"rewrite with `amy use <name>` or pass --account"
|
||||||
}
|
}
|
||||||
return pinned
|
return pinned
|
||||||
}
|
}
|
||||||
@@ -296,7 +296,7 @@ class DataDir(
|
|||||||
return when (accounts.size) {
|
return when (accounts.size) {
|
||||||
0 -> {
|
0 -> {
|
||||||
throw IllegalArgumentException(
|
throw IllegalArgumentException(
|
||||||
"no account at ${rootBase.absolutePath}; create one with `amy --name <name> init`",
|
"no account at ${rootBase.absolutePath}; create one with `amy --account <name> init`",
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -307,7 +307,7 @@ class DataDir(
|
|||||||
else -> {
|
else -> {
|
||||||
throw IllegalArgumentException(
|
throw IllegalArgumentException(
|
||||||
"multiple accounts in ${rootBase.absolutePath} (${accounts.joinToString(", ")}); " +
|
"multiple accounts in ${rootBase.absolutePath} (${accounts.joinToString(", ")}); " +
|
||||||
"pick one with --name <name> or `amy use <name>`",
|
"pick one with --account <name> or `amy use <name>`",
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -82,7 +82,7 @@ private suspend fun dispatch(argv: Array<String>): Int {
|
|||||||
// Pull global flags out of argv before subcommand parsing so subcommands see
|
// Pull global flags out of argv before subcommand parsing so subcommands see
|
||||||
// only their own args.
|
// only their own args.
|
||||||
val filteredArgs = mutableListOf<String>()
|
val filteredArgs = mutableListOf<String>()
|
||||||
var nameFlag: String? = null
|
var accountFlag: String? = null
|
||||||
var secretBackendFlag: String? = null
|
var secretBackendFlag: String? = null
|
||||||
var passphraseFileFlag: String? = null
|
var passphraseFileFlag: String? = null
|
||||||
var i = 0
|
var i = 0
|
||||||
@@ -90,7 +90,7 @@ private suspend fun dispatch(argv: Array<String>): Int {
|
|||||||
val a = argv[i]
|
val a = argv[i]
|
||||||
val (matched, consumed) = extractGlobalFlag(a, argv, i)
|
val (matched, consumed) = extractGlobalFlag(a, argv, i)
|
||||||
when (matched) {
|
when (matched) {
|
||||||
GlobalFlag.NAME -> nameFlag = consumed.value
|
GlobalFlag.ACCOUNT -> accountFlag = consumed.value
|
||||||
GlobalFlag.SECRET_BACKEND -> secretBackendFlag = consumed.value
|
GlobalFlag.SECRET_BACKEND -> secretBackendFlag = consumed.value
|
||||||
GlobalFlag.PASSPHRASE_FILE -> passphraseFileFlag = consumed.value
|
GlobalFlag.PASSPHRASE_FILE -> passphraseFileFlag = consumed.value
|
||||||
GlobalFlag.JSON -> Output.mode = Output.Mode.JSON
|
GlobalFlag.JSON -> Output.mode = Output.Mode.JSON
|
||||||
@@ -116,7 +116,7 @@ private suspend fun dispatch(argv: Array<String>): Int {
|
|||||||
}
|
}
|
||||||
|
|
||||||
val secrets = SecretStore.from(backendFlag = secretBackendFlag, passphraseFile = passphraseFileFlag)
|
val secrets = SecretStore.from(backendFlag = secretBackendFlag, passphraseFile = passphraseFileFlag)
|
||||||
val dataDir = DataDir.resolve(nameFlag = nameFlag, secrets = secrets)
|
val dataDir = DataDir.resolve(accountFlag = accountFlag, secrets = secrets)
|
||||||
|
|
||||||
return when (head) {
|
return when (head) {
|
||||||
"init" -> {
|
"init" -> {
|
||||||
@@ -210,7 +210,7 @@ private enum class GlobalFlag(
|
|||||||
val long: String,
|
val long: String,
|
||||||
val takesValue: Boolean = true,
|
val takesValue: Boolean = true,
|
||||||
) {
|
) {
|
||||||
NAME("--name"),
|
ACCOUNT("--account"),
|
||||||
SECRET_BACKEND("--secret-backend"),
|
SECRET_BACKEND("--secret-backend"),
|
||||||
PASSPHRASE_FILE("--passphrase-file"),
|
PASSPHRASE_FILE("--passphrase-file"),
|
||||||
JSON("--json", takesValue = false),
|
JSON("--json", takesValue = false),
|
||||||
@@ -253,7 +253,7 @@ private fun printUsage() {
|
|||||||
|amy — Amethyst command-line interface
|
|amy — Amethyst command-line interface
|
||||||
|
|
|
|
||||||
|Usage:
|
|Usage:
|
||||||
| amy [--name ACCOUNT]
|
| amy [--account ACCOUNT]
|
||||||
| [--secret-backend auto|keychain|ncryptsec|plaintext]
|
| [--secret-backend auto|keychain|ncryptsec|plaintext]
|
||||||
| [--passphrase-file PATH]
|
| [--passphrase-file PATH]
|
||||||
| [--json]
|
| [--json]
|
||||||
@@ -267,13 +267,13 @@ private fun printUsage() {
|
|||||||
| [a-zA-Z0-9_-]{1,64} (no spaces, no slashes).
|
| [a-zA-Z0-9_-]{1,64} (no spaces, no slashes).
|
||||||
|
|
|
|
||||||
| Resolution order:
|
| Resolution order:
|
||||||
| 1. --name X if given.
|
| 1. --account X if given.
|
||||||
| 2. ~/.amy/current marker (set by `amy use X`).
|
| 2. ~/.amy/current marker (set by `amy use X`).
|
||||||
| 3. Sole subdirectory of ~/.amy/ other than shared/.
|
| 3. Sole subdirectory of ~/.amy/ other than shared/.
|
||||||
| 4. Error — disambiguate with --name or `amy use`.
|
| 4. Error — disambiguate with --account or `amy use`.
|
||||||
|
|
|
|
||||||
| Test harnesses isolate by overriding ${'$'}HOME for the amy
|
| Test harnesses isolate by overriding ${'$'}HOME for the amy
|
||||||
| subprocess (`HOME=/tmp/run.123 amy --name alice ...`).
|
| subprocess (`HOME=/tmp/run.123 amy --account alice ...`).
|
||||||
|
|
|
|
||||||
| use NAME pin NAME as the active account
|
| use NAME pin NAME as the active account
|
||||||
| use --clear remove the pin
|
| use --clear remove the pin
|
||||||
|
|||||||
@@ -74,7 +74,7 @@ object UseCommand {
|
|||||||
if (!accountDir.isDirectory) {
|
if (!accountDir.isDirectory) {
|
||||||
return Output.error(
|
return Output.error(
|
||||||
"no_account",
|
"no_account",
|
||||||
"${accountDir.absolutePath} doesn't exist; create it with `amy --name $name init`",
|
"${accountDir.absolutePath} doesn't exist; create it with `amy --account $name init`",
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
SecureFileIO.secureMkdirs(rootBase)
|
SecureFileIO.secureMkdirs(rootBase)
|
||||||
|
|||||||
Vendored
+2
-2
@@ -83,7 +83,7 @@ source "$TESTS_DIR/headless/helpers.sh"
|
|||||||
# shellcheck source=../dm/setup.sh
|
# shellcheck source=../dm/setup.sh
|
||||||
source "$TESTS_DIR/dm/setup.sh"
|
source "$TESTS_DIR/dm/setup.sh"
|
||||||
|
|
||||||
amy_b() { HOME="$STATE_DIR" "$AMY_BIN" --name B --secret-backend plaintext --json "$@"; }
|
amy_b() { HOME="$STATE_DIR" "$AMY_BIN" --account B --secret-backend plaintext --json "$@"; }
|
||||||
|
|
||||||
# Helper: B-side amy_json. The dm headless's helpers.sh hardcodes A_DIR
|
# Helper: B-side amy_json. The dm headless's helpers.sh hardcodes A_DIR
|
||||||
# in amy_a; we need a parallel for B without re-sourcing.
|
# in amy_a; we need a parallel for B without re-sourcing.
|
||||||
@@ -251,7 +251,7 @@ fi
|
|||||||
# ----------------------------------------------------------------------
|
# ----------------------------------------------------------------------
|
||||||
banner "T7 — store maintenance verbs (sweep/scrub/compact) on an empty store"
|
banner "T7 — store maintenance verbs (sweep/scrub/compact) on an empty store"
|
||||||
TMP_HOME=$(mktemp -d)
|
TMP_HOME=$(mktemp -d)
|
||||||
T7_AMY=(${AMY_BIN} --secret-backend plaintext --name throwaway --json store)
|
T7_AMY=(${AMY_BIN} --secret-backend plaintext --account throwaway --json store)
|
||||||
T7_STAT=$(HOME="$TMP_HOME" "${T7_AMY[@]}" stat)
|
T7_STAT=$(HOME="$TMP_HOME" "${T7_AMY[@]}" stat)
|
||||||
T7_SWEEP=$(HOME="$TMP_HOME" "${T7_AMY[@]}" sweep-expired)
|
T7_SWEEP=$(HOME="$TMP_HOME" "${T7_AMY[@]}" sweep-expired)
|
||||||
T7_SCRUB=$(HOME="$TMP_HOME" "${T7_AMY[@]}" scrub)
|
T7_SCRUB=$(HOME="$TMP_HOME" "${T7_AMY[@]}" scrub)
|
||||||
|
|||||||
@@ -78,15 +78,15 @@ preflight_dm() {
|
|||||||
# `--secret-backend=plaintext` keeps these throwaway interop runs headless —
|
# `--secret-backend=plaintext` keeps these throwaway interop runs headless —
|
||||||
# the default `auto` would try the OS keychain (not available in CI) and then
|
# 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.
|
# ask for a NIP-49 passphrase. Plaintext still writes 0600-owner-only.
|
||||||
amy_a() { HOME="$STATE_DIR" "$AMY_BIN" --name A --secret-backend plaintext --json "$@"; }
|
amy_a() { HOME="$STATE_DIR" "$AMY_BIN" --account A --secret-backend plaintext --json "$@"; }
|
||||||
amy_d() { HOME="$STATE_DIR" "$AMY_BIN" --name D --secret-backend plaintext --json "$@"; }
|
amy_d() { HOME="$STATE_DIR" "$AMY_BIN" --account D --secret-backend plaintext --json "$@"; }
|
||||||
|
|
||||||
# --- identity bootstrap ------------------------------------------------------
|
# --- identity bootstrap ------------------------------------------------------
|
||||||
ensure_identity_for() {
|
ensure_identity_for() {
|
||||||
local who="$1"
|
local who="$1"
|
||||||
step "initialising Identity $who (amy at \$HOME=$STATE_DIR --name $who)"
|
step "initialising Identity $who (amy at \$HOME=$STATE_DIR --account $who)"
|
||||||
local out
|
local out
|
||||||
out=$(HOME="$STATE_DIR" "$AMY_BIN" --name "$who" --secret-backend plaintext --json init) || {
|
out=$(HOME="$STATE_DIR" "$AMY_BIN" --account "$who" --secret-backend plaintext --json init) || {
|
||||||
fail_msg "amy init failed for $who: $out"; exit 1
|
fail_msg "amy init failed for $who: $out"; exit 1
|
||||||
}
|
}
|
||||||
local npub hex
|
local npub hex
|
||||||
|
|||||||
@@ -13,14 +13,14 @@
|
|||||||
# dm-06 cursor advance (subsequent no-flag `dm list` is empty)
|
# dm-06 cursor advance (subsequent no-flag `dm list` is empty)
|
||||||
|
|
||||||
# Wrap amy_json around either account so the per-test code stays tight.
|
# Wrap amy_json around either account so the per-test code stays tight.
|
||||||
# Both share $HOME=$STATE_DIR (set by the harness); --name picks the
|
# Both share $HOME=$STATE_DIR (set by the harness); --account picks the
|
||||||
# account inside it. `--json` opts into amy's machine-readable contract;
|
# account inside it. `--json` opts into amy's machine-readable contract;
|
||||||
# assertions below parse with jq.
|
# assertions below parse with jq.
|
||||||
amy_json_for() {
|
amy_json_for() {
|
||||||
local account="$1"; shift
|
local account="$1"; shift
|
||||||
local out
|
local out
|
||||||
if ! out=$(HOME="$STATE_DIR" "$AMY_BIN" --name "$account" --secret-backend plaintext --json "$@" 2>>"$LOG_FILE"); then
|
if ! out=$(HOME="$STATE_DIR" "$AMY_BIN" --account "$account" --secret-backend plaintext --json "$@" 2>>"$LOG_FILE"); then
|
||||||
fail_msg "amy --name $account $*: exit $? (see $LOG_FILE)"
|
fail_msg "amy --account $account $*: exit $? (see $LOG_FILE)"
|
||||||
printf '%s\n' "$out" >>"$LOG_FILE"
|
printf '%s\n' "$out" >>"$LOG_FILE"
|
||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
@@ -96,7 +96,7 @@ test_03_dm_send_rejects_no_inbox() {
|
|||||||
# test's main STATE_DIR with a third account.
|
# test's main STATE_DIR with a third account.
|
||||||
local ghost_home; ghost_home=$(mktemp -d "${STATE_DIR}/ghost-home.XXXXXX")
|
local ghost_home; ghost_home=$(mktemp -d "${STATE_DIR}/ghost-home.XXXXXX")
|
||||||
local ghost_out ghost_npub
|
local ghost_out ghost_npub
|
||||||
ghost_out=$(HOME="$ghost_home" "$AMY_BIN" --name ghost --secret-backend plaintext --json init) || {
|
ghost_out=$(HOME="$ghost_home" "$AMY_BIN" --account ghost --secret-backend plaintext --json init) || {
|
||||||
record_result "$id" fail "ghost init failed"; rm -rf "$ghost_home"; return
|
record_result "$id" fail "ghost init failed"; rm -rf "$ghost_home"; return
|
||||||
}
|
}
|
||||||
ghost_npub=$(printf '%s' "$ghost_out" | jq -r '.npub')
|
ghost_npub=$(printf '%s' "$ghost_out" | jq -r '.npub')
|
||||||
@@ -104,7 +104,7 @@ test_03_dm_send_rejects_no_inbox() {
|
|||||||
|
|
||||||
# A sends without --allow-fallback; amy should refuse.
|
# A sends without --allow-fallback; amy should refuse.
|
||||||
local raw rc
|
local raw rc
|
||||||
raw=$(HOME="$STATE_DIR" "$AMY_BIN" --name A --secret-backend plaintext --json dm send "$ghost_npub" "should be rejected" 2>&1)
|
raw=$(HOME="$STATE_DIR" "$AMY_BIN" --account A --secret-backend plaintext --json dm send "$ghost_npub" "should be rejected" 2>&1)
|
||||||
rc=$?
|
rc=$?
|
||||||
rm -rf "$ghost_home"
|
rm -rf "$ghost_home"
|
||||||
if [[ "$rc" -ne 0 ]] && printf '%s' "$raw" | grep -q '"error":"no_dm_relays"'; then
|
if [[ "$rc" -ne 0 ]] && printf '%s' "$raw" | grep -q '"error":"no_dm_relays"'; then
|
||||||
@@ -126,7 +126,7 @@ test_04_dm_send_allow_fallback() {
|
|||||||
# publish should succeed even though the ghost has no 10050.
|
# publish should succeed even though the ghost has no 10050.
|
||||||
local ghost_home; ghost_home=$(mktemp -d "${STATE_DIR}/ghost-home.XXXXXX")
|
local ghost_home; ghost_home=$(mktemp -d "${STATE_DIR}/ghost-home.XXXXXX")
|
||||||
local ghost_out ghost_npub
|
local ghost_out ghost_npub
|
||||||
ghost_out=$(HOME="$ghost_home" "$AMY_BIN" --name ghost --secret-backend plaintext --json init) || {
|
ghost_out=$(HOME="$ghost_home" "$AMY_BIN" --account ghost --secret-backend plaintext --json init) || {
|
||||||
record_result "$id" fail "ghost init failed"; rm -rf "$ghost_home"; return
|
record_result "$id" fail "ghost init failed"; rm -rf "$ghost_home"; return
|
||||||
}
|
}
|
||||||
ghost_npub=$(printf '%s' "$ghost_out" | jq -r '.npub')
|
ghost_npub=$(printf '%s' "$ghost_out" | jq -r '.npub')
|
||||||
|
|||||||
@@ -14,7 +14,7 @@
|
|||||||
#
|
#
|
||||||
# `--json` opts into amy's machine-readable output (the harness parses it
|
# `--json` opts into amy's machine-readable output (the harness parses it
|
||||||
# with jq); the default human-text output is for terminal use.
|
# with jq); the default human-text output is for terminal use.
|
||||||
amy_a() { HOME="$STATE_DIR" "$AMY_BIN" --name alice --secret-backend plaintext --json "$@"; }
|
amy_a() { HOME="$STATE_DIR" "$AMY_BIN" --account A --secret-backend plaintext --json "$@"; }
|
||||||
|
|
||||||
# Run amy, log stderr, surface JSON on stdout, remember last result.
|
# Run amy, log stderr, surface JSON on stdout, remember last result.
|
||||||
amy_json() {
|
amy_json() {
|
||||||
|
|||||||
@@ -271,7 +271,7 @@ test_14_wn_removes_a() {
|
|||||||
local deadline=$(( $(date +%s) + 120 )) removed=0
|
local deadline=$(( $(date +%s) + 120 )) removed=0
|
||||||
while [[ $(date +%s) -lt $deadline ]]; do
|
while [[ $(date +%s) -lt $deadline ]]; do
|
||||||
local show rc
|
local show rc
|
||||||
show=$(HOME="$STATE_DIR" "$AMY_BIN" --name A --secret-backend plaintext --json \
|
show=$(HOME="$STATE_DIR" "$AMY_BIN" --account A --secret-backend plaintext --json \
|
||||||
marmot group show "$a_gid" 2>&1)
|
marmot group show "$a_gid" 2>&1)
|
||||||
rc=$?
|
rc=$?
|
||||||
printf '%s\n' "$show" >>"$LOG_FILE"
|
printf '%s\n' "$show" >>"$LOG_FILE"
|
||||||
|
|||||||
Reference in New Issue
Block a user