summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.editorconfig8
-rw-r--r--.gitlab-ci.yml20
-rw-r--r--Cargo.lock158
-rw-r--r--crates/arti/Cargo.toml2
-rw-r--r--crates/arti/tests/README.md40
-rw-r--r--crates/arti/tests/cli_tests.rs42
-rw-r--r--crates/arti/tests/testcases/hsc-feature-missing/hsc.in/hsc.toml11
-rw-r--r--crates/arti/tests/testcases/hsc-feature-missing/hsc.in/local/keystore/client/mnyizjj7m3hpcr7i5afph3zt7maa65johyu2ruis6z7cmnjmaj3h6tad/ks_hsc_desc_enc.x25519_private7
-rw-r--r--crates/arti/tests/testcases/hsc-feature-missing/hsc.in/local/state/circuit_timeouts.json5
-rw-r--r--crates/arti/tests/testcases/hsc-feature-missing/hsc.in/local/state/guards.json14
-rw-r--r--crates/arti/tests/testcases/hsc-feature-missing/hsc.in/local/state/state.lock0
-rw-r--r--crates/arti/tests/testcases/hsc-feature-missing/hsc.md9
-rw-r--r--crates/arti/tests/testcases/hsc/conf/hsc.toml11
-rw-r--r--crates/arti/tests/testcases/hsc/get-key-error.stderr1
-rw-r--r--crates/arti/tests/testcases/hsc/get-key-error.stdout0
-rw-r--r--crates/arti/tests/testcases/hsc/get-key-error.toml3
-rw-r--r--crates/arti/tests/testcases/hsc/help.stderr0
-rw-r--r--crates/arti/tests/testcases/hsc/help.stdout18
-rw-r--r--crates/arti/tests/testcases/hsc/help.toml3
-rw-r--r--crates/arti/tests/testcases/hsc/hsc.in/hsc.toml11
-rw-r--r--crates/arti/tests/testcases/hsc/hsc.in/local/keystore/client/mnyizjj7m3hpcr7i5afph3zt7maa65johyu2ruis6z7cmnjmaj3h6tad/ks_hsc_desc_enc.x25519_private7
-rw-r--r--crates/arti/tests/testcases/hsc/hsc.in/local/state/circuit_timeouts.json5
-rw-r--r--crates/arti/tests/testcases/hsc/hsc.in/local/state/guards.json14
-rw-r--r--crates/arti/tests/testcases/hsc/hsc.in/local/state/state.lock0
-rw-r--r--crates/arti/tests/testcases/hsc/hsc.md12
-rw-r--r--crates/arti/tests/testcases/hss-feature-missing/hss.in/hss.toml13
-rw-r--r--crates/arti/tests/testcases/hss-feature-missing/hss.in/local/keystore/hss/acutus-cepa/ks_hs_id.ed25519_expanded_private8
-rw-r--r--crates/arti/tests/testcases/hss-feature-missing/hss.md9
-rw-r--r--crates/arti/tests/testcases/hss/help.stderr0
-rw-r--r--crates/arti/tests/testcases/hss/help.stdout18
-rw-r--r--crates/arti/tests/testcases/hss/help.toml3
-rw-r--r--crates/arti/tests/testcases/hss/hss.in/hss.toml17
-rw-r--r--crates/arti/tests/testcases/hss/hss.in/local/keystore/hss/acutus-cepa/ks_hs_id.ed25519_expanded_private8
-rw-r--r--crates/arti/tests/testcases/hss/hss.md14
34 files changed, 491 insertions, 0 deletions
diff --git a/.editorconfig b/.editorconfig
index 555fe3fd5..6256aeae6 100644
--- a/.editorconfig
+++ b/.editorconfig
@@ -20,3 +20,11 @@ insert_final_newline = unset
trim_trailing_whitespace = unset
indent_style = unset
indent_size = unset
+
+[crates/arti/tests/testcases/**/local/state/*]
+charset = unset
+end_of_line = unset
+insert_final_newline = unset
+trim_trailing_whitespace = unset
+indent_style = unset
+indent_size = unset
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 2471554e0..beef4ee1c 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -458,6 +458,10 @@ every-crate:
- maint/common/apt-install python3-toml python-is-python3
- ./maint/every-crate
+# TODO: we should rename this to matrix_check for clarity,
+# and introduce a new matrix_test script that runs cargo test.
+# That way, we could use matrix_test to run the tests gated behind
+# specific feature combinations, such as the cli_tests.
matrix-test:
stage: test
image: $RECENT_RUST_IMAGE
@@ -472,12 +476,28 @@ matrix-test-cfg:
script:
- ./maint/matrix_test_cfg
+# TODO: consider removing this in favor of cli-test
cli-help:
stage: test
image: $RECENT_RUST_IMAGE
script:
- ./maint/check-cli-help
+# TODO: this should be folded in one of the other test jobs.
+#
+# Since this is testing an additional combination of features,
+# ideally it would be handled by the matrix_test script,
+# but matrix_test runs cargo check, and we would like to cargo *test*.
+cli-test:
+ stage: test
+ image: $RECENT_RUST_IMAGE
+ script:
+ # The rust-latest job runs the CLI tests with all features enabled.
+ # This job runs the CLI tests with various feature combinations that aren't
+ # covered by the other tests
+ - cargo test --verbose --target x86_64-unknown-linux-gnu -p arti cli_tests
+ - cargo test --verbose --target x86_64-unknown-linux-gnu -p arti --features experimental cli_tests
+
coverage-aggregated:
rules:
- if: $CI_PIPELINE_SOURCE == "schedule"
diff --git a/Cargo.lock b/Cargo.lock
index c3420328d..db8e293a8 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -220,6 +220,7 @@ dependencies = [
"serde_json",
"signal-hook",
"signal-hook-async-std",
+ "tempfile",
"thiserror",
"time",
"tokio",
@@ -236,6 +237,7 @@ dependencies = [
"tracing-appender",
"tracing-journald",
"tracing-subscriber",
+ "trycmd",
"visibility",
"winapi",
]
@@ -708,6 +710,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0"
[[package]]
+name = "automod"
+version = "1.0.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "edf3ee19dbc0a46d740f6f0926bde8c50f02bdbc7b536842da28f6ac56513a8b"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.68",
+]
+
+[[package]]
name = "axum"
version = "0.6.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1035,6 +1048,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8"
[[package]]
+name = "content_inspector"
+version = "0.2.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b7bda66e858c683005a53a9a60c69a4aca7eeaa45d124526e389f7aec8e62f38"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
name = "convert_case"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1084,6 +1106,25 @@ dependencies = [
]
[[package]]
+name = "crossbeam-deque"
+version = "0.8.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d"
+dependencies = [
+ "crossbeam-epoch",
+ "crossbeam-utils",
+]
+
+[[package]]
+name = "crossbeam-epoch"
+version = "0.9.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e"
+dependencies = [
+ "crossbeam-utils",
+]
+
+[[package]]
name = "crossbeam-queue"
version = "0.3.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1467,6 +1508,12 @@ dependencies = [
]
[[package]]
+name = "dunce"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "56ce8c6da7551ec6c462cbaf3bfbc75131ebbfa1c944aeaa9dab51ca1c5f0c3b"
+
+[[package]]
name = "dyn-clone"
version = "1.0.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2084,6 +2131,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253"
[[package]]
+name = "glob"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b"
+
+[[package]]
name = "glob-match"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2909,6 +2962,12 @@ dependencies = [
]
[[package]]
+name = "normalize-line-endings"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "61807f77802ff30975e01f4f071c8ba10c022052f98b3294119f3e615d13e5be"
+
+[[package]]
name = "notify"
version = "6.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -3153,6 +3212,16 @@ dependencies = [
]
[[package]]
+name = "os_pipe"
+version = "1.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "29d73ba8daf8fac13b0501d1abeddcfe21ba7401ada61a819144b6c2a4f32209"
+dependencies = [
+ "libc",
+ "windows-sys 0.52.0",
+]
+
+[[package]]
name = "overload"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -3589,6 +3658,26 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "60a357793950651c4ed0f3f52338f53b2f809f32d83a07f72909fa13e4c6c1e3"
[[package]]
+name = "rayon"
+version = "1.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa"
+dependencies = [
+ "either",
+ "rayon-core",
+]
+
+[[package]]
+name = "rayon-core"
+version = "1.12.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2"
+dependencies = [
+ "crossbeam-deque",
+ "crossbeam-utils",
+]
+
+[[package]]
name = "redox_syscall"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -4220,6 +4309,12 @@ dependencies = [
]
[[package]]
+name = "shlex"
+version = "1.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
+
+[[package]]
name = "signal-hook"
version = "0.3.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -4274,6 +4369,12 @@ dependencies = [
]
[[package]]
+name = "similar"
+version = "2.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fa42c91313f1d05da9b26f267f931cf178d4aba455b4c4622dd7355eb80c6640"
+
+[[package]]
name = "simple_asn1"
version = "0.6.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -4316,6 +4417,37 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67"
[[package]]
+name = "snapbox"
+version = "0.6.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0d656960fa127e80ade23c321d8c573bb9ba462c3a69e62ede635fc180ffc6cc"
+dependencies = [
+ "anstream",
+ "anstyle",
+ "content_inspector",
+ "dunce",
+ "filetime",
+ "libc",
+ "normalize-line-endings",
+ "os_pipe",
+ "similar",
+ "snapbox-macros",
+ "tempfile",
+ "wait-timeout",
+ "walkdir",
+ "windows-sys 0.52.0",
+]
+
+[[package]]
+name = "snapbox-macros"
+version = "0.3.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b1f4c14672714436c09254801c934b203196a51182a5107fb76591c7cc56424d"
+dependencies = [
+ "anstream",
+]
+
+[[package]]
name = "socket2"
version = "0.4.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -5942,6 +6074,23 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b"
[[package]]
+name = "trycmd"
+version = "0.15.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "59709bd8eccada6a3fded26d22a7f2dcee406c18d3bd7ad2605ca3eeb8f6f6ec"
+dependencies = [
+ "automod",
+ "glob",
+ "humantime",
+ "humantime-serde",
+ "rayon",
+ "serde",
+ "shlex",
+ "snapbox",
+ "toml_edit 0.22.14",
+]
+
+[[package]]
name = "typed-index-collections"
version = "3.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -6103,6 +6252,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d"
[[package]]
+name = "wait-timeout"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9f200f5b12eb75f8c1ed65abd4b2db8a6e1b138a20de009dacee265a2498f3f6"
+dependencies = [
+ "libc",
+]
+
+[[package]]
name = "waker-fn"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
diff --git a/crates/arti/Cargo.toml b/crates/arti/Cargo.toml
index 7b4e9de9d..8c54376dc 100644
--- a/crates/arti/Cargo.toml
+++ b/crates/arti/Cargo.toml
@@ -140,6 +140,8 @@ derive_more = "0.99.3"
itertools = "0.13.0"
regex = { version = "1", default-features = false, features = ["std"] }
serde_json = "1.0.50"
+tempfile = "3"
+trycmd = "0.15.4"
[target.'cfg(windows)'.dependencies]
winapi = { version = "0.3.8", features = ["winerror"] }
diff --git a/crates/arti/tests/README.md b/crates/arti/tests/README.md
new file mode 100644
index 000000000..2259c4d28
--- /dev/null
+++ b/crates/arti/tests/README.md
@@ -0,0 +1,40 @@
+# Arti CLI tests
+
+This is a [`trycmd`]-based test suite for the arti CLI.
+
+Each testable subcommand has a corresponding directory in `testdata`:
+```
+tests
+├── cli_tests.rs # The test runner
+├── README.md
+└── testcases
+ ├── hsc
+ │   ├── help.stderr # expected stderr for the "help" test
+ │   ├── help.stdout # expected stdout for the "help" test
+ │   ├── help.toml # hsc "help" test
+ │   ├── hsc.in # test inputs and CWD
+ │   └── hsc.md # multiple hsc tests
+ ├── hss # hss subcommand tests
+ │   ├── hss.in # test inputs and CWD
+ │   └── hss.md # multiple hss tests
+ └── ...
+```
+
+Each feature-dependent subcommand has a corresponding `<subcmd>-feature-missing`
+test case, which tests that we output a hint about recompiling arti with the
+necessary features (the feature-dependent tests are currently all skipped,
+because we don't yet print helpful messages in such cases. See #1487).
+
+The tests can be written as [`*.trycmd`/`*.md` files], or in [`toml` format].
+The `*md`-based tests can double as documentation, so they are often preferable
+over the `toml` ones.
+
+See the [`trycmd`] docs for more information.
+
+### Debugging
+
+You can pass `-F trycmd/debug` to `cargo test` to debug `trycmd`'s behavior.`
+
+[`trycmd`]: https://docs.rs/trycmd/latest/trycmd/
+[`*.trycmd`/`*.md` files]: https://docs.rs/trycmd/latest/trycmd/#trycmd
+[`toml` format]: https://docs.rs/trycmd/latest/trycmd/#toml
diff --git a/crates/arti/tests/cli_tests.rs b/crates/arti/tests/cli_tests.rs
new file mode 100644
index 000000000..e473d2fad
--- /dev/null
+++ b/crates/arti/tests/cli_tests.rs
@@ -0,0 +1,42 @@
+#![doc = include_str!("../README.md")]
+
+#[test]
+fn cli_tests() {
+ let t = trycmd::TestCases::new();
+ let dir = tempfile::TempDir::new().unwrap();
+ t.env("HOME", dir.path().to_str().unwrap());
+
+ cfg_if::cfg_if! {
+ if #[cfg(feature = "onion-service-service")] {
+ t.case("tests/testcases/hss/*.toml");
+ t.case("tests/testcases/hss/*.md");
+ } else {
+ // This is not yet implemented, see #1487
+ t.skip("tests/testcases/hss-feature-missing/*.toml");
+ t.skip("tests/testcases/hss-feature-missing/*.md");
+ }
+ }
+
+ cfg_if::cfg_if! {
+ if #[cfg(all(feature = "onion-service-client", feature = "experimental-api", feature = "keymgr"))] {
+ // TODO: Skipped because running creating multiple TorClient at the same time
+ // is not supported.
+ //
+ // Re-enable these when #1497 is closed.
+ t.skip("tests/testcases/hsc/*.toml");
+ t.skip("tests/testcases/hsc/*.md");
+ } else {
+ // This is not yet implemented, see #1487
+ t.skip("tests/testcases/hsc-feature-missing/*.toml");
+ t.skip("tests/testcases/hsc-feature-missing/*.md");
+ }
+ }
+
+ t.case("README.md");
+
+ // Run the tests.
+ //
+ // Note: the TestCases must be dropped *before* the tempdir
+ // (otherwise HOME will get cleaned up before the tests have had a chance to run!)
+ drop(t);
+}
diff --git a/crates/arti/tests/testcases/hsc-feature-missing/hsc.in/hsc.toml b/crates/arti/tests/testcases/hsc-feature-missing/hsc.in/hsc.toml
new file mode 100644
index 000000000..9f9c701f1
--- /dev/null
+++ b/crates/arti/tests/testcases/hsc-feature-missing/hsc.in/hsc.toml
@@ -0,0 +1,11 @@
+[application]
+allow_running_as_root = true
+
+[storage]
+state_dir = "./local"
+
+[storage.permissions]
+dangerously_trust_everyone = true
+
+[logging]
+console = "warn"
diff --git a/crates/arti/tests/testcases/hsc-feature-missing/hsc.in/local/keystore/client/mnyizjj7m3hpcr7i5afph3zt7maa65johyu2ruis6z7cmnjmaj3h6tad/ks_hsc_desc_enc.x25519_private b/crates/arti/tests/testcases/hsc-feature-missing/hsc.in/local/keystore/client/mnyizjj7m3hpcr7i5afph3zt7maa65johyu2ruis6z7cmnjmaj3h6tad/ks_hsc_desc_enc.x25519_private
new file mode 100644
index 000000000..3089776c9
--- /dev/null
+++ b/crates/arti/tests/testcases/hsc-feature-missing/hsc.in/local/keystore/client/mnyizjj7m3hpcr7i5afph3zt7maa65johyu2ruis6z7cmnjmaj3h6tad/ks_hsc_desc_enc.x25519_private
@@ -0,0 +1,7 @@
+-----BEGIN OPENSSH PRIVATE KEY-----
+b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAQgAAABp4MjU1MT
+lAc3BlYy50b3Jwcm9qZWN0Lm9yZwAAACCNrKwy3SXHTOiSGMXywSI3ouBHfF3bKdyyx5LI
+Os0dWwAAAHj5tlsh+bZbIQAAABp4MjU1MTlAc3BlYy50b3Jwcm9qZWN0Lm9yZwAAACCNrK
+wy3SXHTOiSGMXywSI3ouBHfF3bKdyyx5LIOs0dWwAAACAO5qh4FRo41KE2Q7D5VACR8Rnu
+3zdBIVgjwDVRX7ByegAAAAABAgMEBQY=
+-----END OPENSSH PRIVATE KEY-----
diff --git a/crates/arti/tests/testcases/hsc-feature-missing/hsc.in/local/state/circuit_timeouts.json b/crates/arti/tests/testcases/hsc-feature-missing/hsc.in/local/state/circuit_timeouts.json
new file mode 100644
index 000000000..404293a15
--- /dev/null
+++ b/crates/arti/tests/testcases/hsc-feature-missing/hsc.in/local/state/circuit_timeouts.json
@@ -0,0 +1,5 @@
+{
+ "version": 1,
+ "histogram": [],
+ "current_timeout": 60000
+} \ No newline at end of file
diff --git a/crates/arti/tests/testcases/hsc-feature-missing/hsc.in/local/state/guards.json b/crates/arti/tests/testcases/hsc-feature-missing/hsc.in/local/state/guards.json
new file mode 100644
index 000000000..e3af2c5ef
--- /dev/null
+++ b/crates/arti/tests/testcases/hsc-feature-missing/hsc.in/local/state/guards.json
@@ -0,0 +1,14 @@
+{
+ "default": {
+ "guards": [],
+ "confirmed": []
+ },
+ "restricted": {
+ "guards": [],
+ "confirmed": []
+ },
+ "bridges": {
+ "guards": [],
+ "confirmed": []
+ }
+} \ No newline at end of file
diff --git a/crates/arti/tests/testcases/hsc-feature-missing/hsc.in/local/state/state.lock b/crates/arti/tests/testcases/hsc-feature-missing/hsc.in/local/state/state.lock
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/crates/arti/tests/testcases/hsc-feature-missing/hsc.in/local/state/state.lock
diff --git a/crates/arti/tests/testcases/hsc-feature-missing/hsc.md b/crates/arti/tests/testcases/hsc-feature-missing/hsc.md
new file mode 100644
index 000000000..c9651ab9c
--- /dev/null
+++ b/crates/arti/tests/testcases/hsc-feature-missing/hsc.md
@@ -0,0 +1,9 @@
+# `arti hsc`
+
+`arti hsc` is only supported in builds that have the `onion-service-client`,
+`keymgr`, and `experimental-api` features enabled:
+
+```console
+$ arti hsc --help
+error: unrecognized subcommand 'hsc' (hint: recompile with the onion-service-client, keymgr, experimental-api features)
+```
diff --git a/crates/arti/tests/testcases/hsc/conf/hsc.toml b/crates/arti/tests/testcases/hsc/conf/hsc.toml
new file mode 100644
index 000000000..f9c2cf63d
--- /dev/null
+++ b/crates/arti/tests/testcases/hsc/conf/hsc.toml
@@ -0,0 +1,11 @@
+[application]
+allow_running_as_root = true
+
+[storage]
+state_dir = "./tests/testcases/hsc/hsc.in/local"
+
+[storage.permissions]
+dangerously_trust_everyone = true
+
+[logging]
+console = "warn"
diff --git a/crates/arti/tests/testcases/hsc/get-key-error.stderr b/crates/arti/tests/testcases/hsc/get-key-error.stderr
new file mode 100644
index 000000000..f8a38cd46
--- /dev/null
+++ b/crates/arti/tests/testcases/hsc/get-key-error.stderr
@@ -0,0 +1 @@
+[..]error: Service discovery key not found. Rerun with --generate=if-needed to generate a new service discovery keypair
diff --git a/crates/arti/tests/testcases/hsc/get-key-error.stdout b/crates/arti/tests/testcases/hsc/get-key-error.stdout
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/crates/arti/tests/testcases/hsc/get-key-error.stdout
diff --git a/crates/arti/tests/testcases/hsc/get-key-error.toml b/crates/arti/tests/testcases/hsc/get-key-error.toml
new file mode 100644
index 000000000..1be0f4f9e
--- /dev/null
+++ b/crates/arti/tests/testcases/hsc/get-key-error.toml
@@ -0,0 +1,3 @@
+bin.name = "arti"
+args = "-c ./tests/testcases/hsc/conf/hsc.toml hsc get-key --key-type=service-discovery --onion-name fpqqmiwzqiv63jczrshh4qcmlxw6gujcai3arobq23wikt7hk7ojadid.onion --generate=no --output -"
+status.code = 127
diff --git a/crates/arti/tests/testcases/hsc/help.stderr b/crates/arti/tests/testcases/hsc/help.stderr
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/crates/arti/tests/testcases/hsc/help.stderr
diff --git a/crates/arti/tests/testcases/hsc/help.stdout b/crates/arti/tests/testcases/hsc/help.stdout
new file mode 100644
index 000000000..adab2965f
--- /dev/null
+++ b/crates/arti/tests/testcases/hsc/help.stdout
@@ -0,0 +1,18 @@
+Run state management commands for an Arti hidden service client
+
+Usage: arti hsc [OPTIONS] <COMMAND>
+
+Commands:
+ get-key Prepare a service discovery key for connecting to a service running in restricted
+ discovery mode
+ help Print this message or the help of the given subcommand(s)
+
+Options:
+ -c, --config <FILE> Specify which config file(s) to read. Defaults to
+ [File("[..]"),
+ Dir("[..]")]
+ -o <KEY=VALUE> Override config file parameters, using TOML-like syntax.
+ -l, --log-level <LEVEL> Override the log level (usually one of 'trace', 'debug',
+ 'info', 'warn', 'error').
+ --disable-fs-permission-checks Don't check permissions on the files we use.
+ -h, --help Print help
diff --git a/crates/arti/tests/testcases/hsc/help.toml b/crates/arti/tests/testcases/hsc/help.toml
new file mode 100644
index 000000000..424f3cf1c
--- /dev/null
+++ b/crates/arti/tests/testcases/hsc/help.toml
@@ -0,0 +1,3 @@
+bin.name = "arti"
+args = "hsc --help"
+status.code = 0
diff --git a/crates/arti/tests/testcases/hsc/hsc.in/hsc.toml b/crates/arti/tests/testcases/hsc/hsc.in/hsc.toml
new file mode 100644
index 000000000..9f9c701f1
--- /dev/null
+++ b/crates/arti/tests/testcases/hsc/hsc.in/hsc.toml
@@ -0,0 +1,11 @@
+[application]
+allow_running_as_root = true
+
+[storage]
+state_dir = "./local"
+
+[storage.permissions]
+dangerously_trust_everyone = true
+
+[logging]
+console = "warn"
diff --git a/crates/arti/tests/testcases/hsc/hsc.in/local/keystore/client/mnyizjj7m3hpcr7i5afph3zt7maa65johyu2ruis6z7cmnjmaj3h6tad/ks_hsc_desc_enc.x25519_private b/crates/arti/tests/testcases/hsc/hsc.in/local/keystore/client/mnyizjj7m3hpcr7i5afph3zt7maa65johyu2ruis6z7cmnjmaj3h6tad/ks_hsc_desc_enc.x25519_private
new file mode 100644
index 000000000..3089776c9
--- /dev/null
+++ b/crates/arti/tests/testcases/hsc/hsc.in/local/keystore/client/mnyizjj7m3hpcr7i5afph3zt7maa65johyu2ruis6z7cmnjmaj3h6tad/ks_hsc_desc_enc.x25519_private
@@ -0,0 +1,7 @@
+-----BEGIN OPENSSH PRIVATE KEY-----
+b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAQgAAABp4MjU1MT
+lAc3BlYy50b3Jwcm9qZWN0Lm9yZwAAACCNrKwy3SXHTOiSGMXywSI3ouBHfF3bKdyyx5LI
+Os0dWwAAAHj5tlsh+bZbIQAAABp4MjU1MTlAc3BlYy50b3Jwcm9qZWN0Lm9yZwAAACCNrK
+wy3SXHTOiSGMXywSI3ouBHfF3bKdyyx5LIOs0dWwAAACAO5qh4FRo41KE2Q7D5VACR8Rnu
+3zdBIVgjwDVRX7ByegAAAAABAgMEBQY=
+-----END OPENSSH PRIVATE KEY-----
diff --git a/crates/arti/tests/testcases/hsc/hsc.in/local/state/circuit_timeouts.json b/crates/arti/tests/testcases/hsc/hsc.in/local/state/circuit_timeouts.json
new file mode 100644
index 000000000..404293a15
--- /dev/null
+++ b/crates/arti/tests/testcases/hsc/hsc.in/local/state/circuit_timeouts.json
@@ -0,0 +1,5 @@
+{
+ "version": 1,
+ "histogram": [],
+ "current_timeout": 60000
+} \ No newline at end of file
diff --git a/crates/arti/tests/testcases/hsc/hsc.in/local/state/guards.json b/crates/arti/tests/testcases/hsc/hsc.in/local/state/guards.json
new file mode 100644
index 000000000..e3af2c5ef
--- /dev/null
+++ b/crates/arti/tests/testcases/hsc/hsc.in/local/state/guards.json
@@ -0,0 +1,14 @@
+{
+ "default": {
+ "guards": [],
+ "confirmed": []
+ },
+ "restricted": {
+ "guards": [],
+ "confirmed": []
+ },
+ "bridges": {
+ "guards": [],
+ "confirmed": []
+ }
+} \ No newline at end of file
diff --git a/crates/arti/tests/testcases/hsc/hsc.in/local/state/state.lock b/crates/arti/tests/testcases/hsc/hsc.in/local/state/state.lock
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/crates/arti/tests/testcases/hsc/hsc.in/local/state/state.lock
diff --git a/crates/arti/tests/testcases/hsc/hsc.md b/crates/arti/tests/testcases/hsc/hsc.md
new file mode 100644
index 000000000..0e25a480d
--- /dev/null
+++ b/crates/arti/tests/testcases/hsc/hsc.md
@@ -0,0 +1,12 @@
+# `arti hsc`
+
+## The `get-key` subcommand
+
+Prepare a service discovery key:
+
+```console
+$ arti -c hsc.toml hsc get-key --key-type=service-discovery
+> --onion-name mnyizjj7m3hpcr7i5afph3zt7maa65johyu2ruis6z7cmnjmaj3h6tad.onion
+> --generate=no --output -
+descriptor:x25519:RWWKYMW5EXDUZ2ESDDC7FQJCG6ROAR34LXNSTXFSY6JMQOWNDVNQ
+```
diff --git a/crates/arti/tests/testcases/hss-feature-missing/hss.in/hss.toml b/crates/arti/tests/testcases/hss-feature-missing/hss.in/hss.toml
new file mode 100644
index 000000000..a6a0a482b
--- /dev/null
+++ b/crates/arti/tests/testcases/hss-feature-missing/hss.in/hss.toml
@@ -0,0 +1,13 @@
+[application]
+allow_running_as_root = true
+
+[storage]
+state_dir = "./local"
+
+[storage.permissions]
+dangerously_trust_everyone = true
+
+[logging]
+console = "warn"
+
+[onion_services."acutus-cepa"]
diff --git a/crates/arti/tests/testcases/hss-feature-missing/hss.in/local/keystore/hss/acutus-cepa/ks_hs_id.ed25519_expanded_private b/crates/arti/tests/testcases/hss-feature-missing/hss.in/local/keystore/hss/acutus-cepa/ks_hs_id.ed25519_expanded_private
new file mode 100644
index 000000000..e7ca44495
--- /dev/null
+++ b/crates/arti/tests/testcases/hss-feature-missing/hss.in/local/keystore/hss/acutus-cepa/ks_hs_id.ed25519_expanded_private
@@ -0,0 +1,8 @@
+-----BEGIN OPENSSH PRIVATE KEY-----
+b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAATAAAACRlZDI1NT
+E5LWV4cGFuZGVkQHNwZWMudG9ycHJvamVjdC5vcmcAAAAgY3CMpT9mzvFH6OgK8+8z+wAP
+dS4+KajREvZ+JjUsAnYAAACgIP5WeyD+VnsAAAAkZWQyNTUxOS1leHBhbmRlZEBzcGVjLn
+RvcnByb2plY3Qub3JnAAAAIGNwjKU/Zs7xR+joCvPvM/sAD3UuPimo0RL2fiY1LAJ2AAAA
+QA/QV+74kAKl8QDDu8iB8hfbuDFuYDIQUE+tDVV7uYcH80JBy1/sjWq4bMwVe1ZyhjRs4h
+wJ3R9Q5vTT0dVwxV8AAAAAAQIDBA==
+-----END OPENSSH PRIVATE KEY-----
diff --git a/crates/arti/tests/testcases/hss-feature-missing/hss.md b/crates/arti/tests/testcases/hss-feature-missing/hss.md
new file mode 100644
index 000000000..a2d8ce142
--- /dev/null
+++ b/crates/arti/tests/testcases/hss-feature-missing/hss.md
@@ -0,0 +1,9 @@
+# `arti hss`
+
+`arti hss` is only supported in builds that have the `onion-service-service`
+feature enabled:
+
+```console
+$ arti -c hss.toml hss --nickname acutus-cepa onion-name
+error: unrecognized subcommand 'hss' (hint: recompile with the onion-service-service feature)
+```
diff --git a/crates/arti/tests/testcases/hss/help.stderr b/crates/arti/tests/testcases/hss/help.stderr
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/crates/arti/tests/testcases/hss/help.stderr
diff --git a/crates/arti/tests/testcases/hss/help.stdout b/crates/arti/tests/testcases/hss/help.stdout
new file mode 100644
index 000000000..929a283b8
--- /dev/null
+++ b/crates/arti/tests/testcases/hss/help.stdout
@@ -0,0 +1,18 @@
+Run state management commands for an Arti hidden service
+
+Usage: arti hss [OPTIONS] --nickname <HS_NICKNAME> <COMMAND>
+
+Commands:
+ onion-name Print the .onion address of a hidden service
+ help Print this message or the help of the given subcommand(s)
+
+Options:
+ -c, --config <FILE> Specify which config file(s) to read. Defaults to
+ [File("[..]"),
+ Dir("[..]")]
+ -n, --nickname <HS_NICKNAME> The nickname of the service
+ -o <KEY=VALUE> Override config file parameters, using TOML-like syntax.
+ -l, --log-level <LEVEL> Override the log level (usually one of 'trace', 'debug',
+ 'info', 'warn', 'error').
+ --disable-fs-permission-checks Don't check permissions on the files we use.
+ -h, --help Print help
diff --git a/crates/arti/tests/testcases/hss/help.toml b/crates/arti/tests/testcases/hss/help.toml
new file mode 100644
index 000000000..8265de5cf
--- /dev/null
+++ b/crates/arti/tests/testcases/hss/help.toml
@@ -0,0 +1,3 @@
+bin.name = "arti"
+args = "hss --help"
+status.code = 0
diff --git a/crates/arti/tests/testcases/hss/hss.in/hss.toml b/crates/arti/tests/testcases/hss/hss.in/hss.toml
new file mode 100644
index 000000000..12f98bc80
--- /dev/null
+++ b/crates/arti/tests/testcases/hss/hss.in/hss.toml
@@ -0,0 +1,17 @@
+[application]
+allow_running_as_root = true
+
+[storage]
+state_dir = "./local"
+
+[storage.permissions]
+dangerously_trust_everyone = true
+
+[logging]
+console = "warn"
+
+[onion_services."acutus-cepa"]
+proxy_ports = [
+ # Forward port 80 on the service to localhost:10080.
+ ["80", "127.0.0.1:10080"],
+]
diff --git a/crates/arti/tests/testcases/hss/hss.in/local/keystore/hss/acutus-cepa/ks_hs_id.ed25519_expanded_private b/crates/arti/tests/testcases/hss/hss.in/local/keystore/hss/acutus-cepa/ks_hs_id.ed25519_expanded_private
new file mode 100644
index 000000000..e7ca44495
--- /dev/null
+++ b/crates/arti/tests/testcases/hss/hss.in/local/keystore/hss/acutus-cepa/ks_hs_id.ed25519_expanded_private
@@ -0,0 +1,8 @@
+-----BEGIN OPENSSH PRIVATE KEY-----
+b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAATAAAACRlZDI1NT
+E5LWV4cGFuZGVkQHNwZWMudG9ycHJvamVjdC5vcmcAAAAgY3CMpT9mzvFH6OgK8+8z+wAP
+dS4+KajREvZ+JjUsAnYAAACgIP5WeyD+VnsAAAAkZWQyNTUxOS1leHBhbmRlZEBzcGVjLn
+RvcnByb2plY3Qub3JnAAAAIGNwjKU/Zs7xR+joCvPvM/sAD3UuPimo0RL2fiY1LAJ2AAAA
+QA/QV+74kAKl8QDDu8iB8hfbuDFuYDIQUE+tDVV7uYcH80JBy1/sjWq4bMwVe1ZyhjRs4h
+wJ3R9Q5vTT0dVwxV8AAAAAAQIDBA==
+-----END OPENSSH PRIVATE KEY-----
diff --git a/crates/arti/tests/testcases/hss/hss.md b/crates/arti/tests/testcases/hss/hss.md
new file mode 100644
index 000000000..da4977896
--- /dev/null
+++ b/crates/arti/tests/testcases/hss/hss.md
@@ -0,0 +1,14 @@
+# `arti hss`
+
+## The `onion-name` subcommand
+
+Print the `.onion` address of a hidden service:
+
+```console
+$ arti -c hss.toml hss --nickname acutus-cepa onion-name
+mnyizjj7m3hpcr7i5afph3zt7maa65johyu2ruis6z7cmnjmaj3h6tad.onion
+
+$ arti -c hss.toml hss --nickname flamingo onion-name
+Service flamingo is not configured
+
+```