mirror of
https://github.com/fabro-sh/fabro.git
synced 2026-09-10 22:43:37 +00:00
Replace trycmd tests with fabro-test crate and insta snapshots
Adopt uv's testing pattern: a shared `fabro-test` crate with TestContext and fabro_snapshot! macro, plus one test file per subcommand under tests/it/cmd/. This replaces the trycmd-based tests which were hard to read and didn't compose well with programmatic assertions. - Create lib/crates/fabro-test with TestContext, run_and_format, apply_filters, INSTA_FILTERS, and test_context!/fabro_snapshot! macros - Add 42 snapshot tests across 16 subcommand files - Delete trycmd.rs and all tests/cmd/ trycmd files - Remove trycmd dependency, add fabro-test dev-dependency Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
e2d1ad2144
commit
d4a91eb29d
72 changed files with 1395 additions and 514 deletions
139
Cargo.lock
generated
139
Cargo.lock
generated
|
|
@ -310,17 +310,6 @@ version = "1.5.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
|
||||
|
||||
[[package]]
|
||||
name = "automod"
|
||||
version = "1.0.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e8b5778837666541195063243828c5b6139221b47dc4ec3ba81738e532469ab1"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.117",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "aws-lc-rs"
|
||||
version = "1.16.1"
|
||||
|
|
@ -729,15 +718,6 @@ version = "0.9.6"
|
|||
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.10.0"
|
||||
|
|
@ -1493,6 +1473,7 @@ dependencies = [
|
|||
"fabro-server",
|
||||
"fabro-store",
|
||||
"fabro-telemetry",
|
||||
"fabro-test",
|
||||
"fabro-types",
|
||||
"fabro-util",
|
||||
"fabro-validate",
|
||||
|
|
@ -1526,7 +1507,6 @@ dependencies = [
|
|||
"tracing",
|
||||
"tracing-appender",
|
||||
"tracing-subscriber",
|
||||
"trycmd",
|
||||
"ulid",
|
||||
"walkdir",
|
||||
"x509-parser",
|
||||
|
|
@ -1926,6 +1906,16 @@ dependencies = [
|
|||
"uuid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fabro-test"
|
||||
version = "0.176.2"
|
||||
dependencies = [
|
||||
"assert_cmd",
|
||||
"insta",
|
||||
"regex",
|
||||
"tempfile",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fabro-tracker"
|
||||
version = "0.176.2"
|
||||
|
|
@ -2776,16 +2766,6 @@ version = "2.3.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "135b12329e5e3ce057a9f972339ea52bc954fe1e9358ef27f95e89716fbc5424"
|
||||
|
||||
[[package]]
|
||||
name = "humantime-serde"
|
||||
version = "1.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "57a3db5ea5923d99402c94e9feb261dc5ee9b4efa158b0315f788cf549cc200c"
|
||||
dependencies = [
|
||||
"humantime",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hyper"
|
||||
version = "1.8.1"
|
||||
|
|
@ -3086,6 +3066,7 @@ checksum = "e82db8c87c7f1ccecb34ce0c24399b8a73081427f3c7c50a5d597925356115e4"
|
|||
dependencies = [
|
||||
"console 0.15.11",
|
||||
"once_cell",
|
||||
"regex",
|
||||
"similar",
|
||||
"tempfile",
|
||||
]
|
||||
|
|
@ -4134,16 +4115,6 @@ dependencies = [
|
|||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "os_pipe"
|
||||
version = "1.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7d8fae84b431384b68627d0f9b3b1245fcf9f46f6c0e3dc902e9dce64edd1967"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ouroboros"
|
||||
version = "0.18.5"
|
||||
|
|
@ -4693,26 +4664,6 @@ dependencies = [
|
|||
"rand_core 0.9.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rayon"
|
||||
version = "1.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "368f01d005bf8fd9b1206fb6fa653e6c4a81ceb1466406b81792d87c5677a58f"
|
||||
dependencies = [
|
||||
"either",
|
||||
"rayon-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rayon-core"
|
||||
version = "1.13.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91"
|
||||
dependencies = [
|
||||
"crossbeam-deque",
|
||||
"crossbeam-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "redox_syscall"
|
||||
version = "0.5.18"
|
||||
|
|
@ -5641,37 +5592,6 @@ dependencies = [
|
|||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "snapbox"
|
||||
version = "0.6.24"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6c1abc378119f77310836665f8523018532cf7e3faeb3b10b01da5a7321bf8e1"
|
||||
dependencies = [
|
||||
"anstream",
|
||||
"anstyle",
|
||||
"content_inspector",
|
||||
"dunce",
|
||||
"filetime",
|
||||
"libc",
|
||||
"normalize-line-endings",
|
||||
"os_pipe",
|
||||
"similar",
|
||||
"snapbox-macros",
|
||||
"tempfile",
|
||||
"wait-timeout",
|
||||
"walkdir",
|
||||
"windows-sys 0.60.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "snapbox-macros"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3b750c344002d7cc69afb9da00ebd9b5c0f8ac2eb7d115d9d45d5b5f47718d74"
|
||||
dependencies = [
|
||||
"anstream",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "socket2"
|
||||
version = "0.6.2"
|
||||
|
|
@ -6341,7 +6261,7 @@ dependencies = [
|
|||
"serde",
|
||||
"serde_spanned 0.6.9",
|
||||
"toml_datetime 0.6.11",
|
||||
"toml_edit 0.22.27",
|
||||
"toml_edit",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -6391,21 +6311,6 @@ dependencies = [
|
|||
"winnow 0.7.14",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_edit"
|
||||
version = "0.23.10+spec-1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "84c8b9f757e028cee9fa244aea147aab2a9ec09d5325a9b01e0a49730c2b5269"
|
||||
dependencies = [
|
||||
"indexmap 2.13.0",
|
||||
"serde_core",
|
||||
"serde_spanned 1.0.4",
|
||||
"toml_datetime 0.7.5+spec-1.1.0",
|
||||
"toml_parser",
|
||||
"toml_writer",
|
||||
"winnow 0.7.14",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_parser"
|
||||
version = "1.0.9+spec-1.1.0"
|
||||
|
|
@ -6553,24 +6458,6 @@ version = "0.2.5"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b"
|
||||
|
||||
[[package]]
|
||||
name = "trycmd"
|
||||
version = "0.15.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a81ea3136ddc88e19c2cc2eb3176b72abee4e831367cd8949f2a88ac5497e64e"
|
||||
dependencies = [
|
||||
"anstream",
|
||||
"automod",
|
||||
"glob",
|
||||
"humantime",
|
||||
"humantime-serde",
|
||||
"rayon",
|
||||
"serde",
|
||||
"shlex",
|
||||
"snapbox",
|
||||
"toml_edit 0.23.10+spec-1.0.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tungstenite"
|
||||
version = "0.26.2"
|
||||
|
|
|
|||
|
|
@ -55,6 +55,7 @@ hmac = "0.12"
|
|||
sha2 = "0.10"
|
||||
hex = "0.4"
|
||||
insta = "1"
|
||||
fabro-test = { path = "lib/crates/fabro-test" }
|
||||
tokio-tungstenite = { version = "0.26", features = ["rustls-tls-webpki-roots"] }
|
||||
futures-util = "0.3"
|
||||
openssh = "0.11"
|
||||
|
|
|
|||
|
|
@ -99,4 +99,4 @@ predicates = "3"
|
|||
serde_json.workspace = true
|
||||
tempfile = "3"
|
||||
httpmock = "0.8"
|
||||
trycmd = "0.15"
|
||||
fabro-test = { workspace = true }
|
||||
|
|
|
|||
|
|
@ -1,9 +0,0 @@
|
|||
```console
|
||||
$ fabro config --help
|
||||
[..]
|
||||
...
|
||||
|
||||
$ fabro config show --help
|
||||
[..]
|
||||
...
|
||||
```
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
```console
|
||||
$ fabro cp --help
|
||||
[..]
|
||||
...
|
||||
|
||||
```
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
...
|
||||
[..]Fabro Doctor[..]
|
||||
...
|
||||
|
|
@ -1,10 +0,0 @@
|
|||
bin.name = "fabro"
|
||||
args = ["doctor", "--dry-run"]
|
||||
|
||||
[env]
|
||||
inherit = false
|
||||
|
||||
[env.add]
|
||||
PATH = "/usr/local/bin:/opt/homebrew/bin:/usr/bin:/bin:/usr/sbin:/sbin"
|
||||
HOME = "/tmp/fabro-trycmd-nonexistent"
|
||||
ANTHROPIC_API_KEY = "sk-test-dummy"
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
```console
|
||||
$ fabro doctor --help
|
||||
[..]
|
||||
...
|
||||
|
||||
```
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
...
|
||||
[..]invalid value[..]
|
||||
...
|
||||
|
|
@ -1,7 +0,0 @@
|
|||
bin.name = "fabro"
|
||||
args = ["exec", "--permissions", "bogus", "test prompt"]
|
||||
status = "failed"
|
||||
stderr-to-stdout = true
|
||||
|
||||
[env]
|
||||
inherit = false
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
...
|
||||
[..]Usage:[..]
|
||||
...
|
||||
|
|
@ -1,7 +0,0 @@
|
|||
bin.name = "fabro"
|
||||
args = ["exec"]
|
||||
status = "failed"
|
||||
stderr-to-stdout = true
|
||||
|
||||
[env]
|
||||
inherit = false
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
```console
|
||||
$ fabro init --help
|
||||
[..]
|
||||
...
|
||||
|
||||
```
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
```console
|
||||
$ fabro install --help
|
||||
[..]
|
||||
...
|
||||
|
||||
```
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
...
|
||||
[..]expected key=value[..]
|
||||
...
|
||||
|
|
@ -1,7 +0,0 @@
|
|||
bin.name = "fabro"
|
||||
args = ["llm", "prompt", "-o", "bad_option", "hello"]
|
||||
status = "failed"
|
||||
stderr-to-stdout = true
|
||||
|
||||
[env]
|
||||
inherit = false
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
...
|
||||
[..]no prompt provided[..]
|
||||
...
|
||||
|
|
@ -1,7 +0,0 @@
|
|||
bin.name = "fabro"
|
||||
args = ["llm", "prompt"]
|
||||
status = "failed"
|
||||
stderr-to-stdout = true
|
||||
|
||||
[env]
|
||||
inherit = false
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
...
|
||||
[..]--schema must be valid JSON[..]
|
||||
...
|
||||
|
|
@ -1,7 +0,0 @@
|
|||
bin.name = "fabro"
|
||||
args = ["llm", "prompt", "--no-stream", "-m", "test-model", "--schema", "not json", "hello"]
|
||||
status = "failed"
|
||||
stderr-to-stdout = true
|
||||
|
||||
[env]
|
||||
inherit = false
|
||||
|
|
@ -1,25 +0,0 @@
|
|||
```console
|
||||
$ fabro model
|
||||
[0m [0m[0m[0m[1mMODEL [0m [0m[0m [0m[0m[0m[1mPROVIDER [0m [0m[0m [0m[0m[0m[1mALIASES [0m [0m[0m [0m[0m[0m[1mCONTEXT[0m [0m[0m [0m[0m[0m[1m COST[0m [0m[0m [0m[0m[0m[1m SPEED[0m [0m
|
||||
[0m[0m [0m[0m[0mclaude-opus-4-6 [0m [0m[0m [0m[0m[0manthropic[0m [0m[0m [0m[0m[0mopus, claude-opus [0m [0m[0m [0m[0m[0m 1m[0m [0m[0m [0m[0m[0m $15.0 / $75.0[0m [0m[0m [0m[0m[0m 25 tok/s[0m [0m
|
||||
[0m[0m [0m[0m[0mclaude-sonnet-4-5 [0m [0m[0m [0m[0m[0manthropic[0m [0m[0m [0m[0m[0m [0m [0m[0m [0m[0m[0m 200k[0m [0m[0m [0m[0m[0m $3.0 / $15.0[0m [0m[0m [0m[0m[0m 50 tok/s[0m [0m
|
||||
[0m[0m [0m[0m[0mclaude-sonnet-4-6 [0m [0m[0m [0m[0m[0manthropic[0m [0m[0m [0m[0m[0msonnet, claude-sonnet [0m [0m[0m [0m[0m[0m 200k[0m [0m[0m [0m[0m[0m $3.0 / $15.0[0m [0m[0m [0m[0m[0m 50 tok/s[0m [0m
|
||||
[0m[0m [0m[0m[0mclaude-haiku-4-5 [0m [0m[0m [0m[0m[0manthropic[0m [0m[0m [0m[0m[0mhaiku, claude-haiku [0m [0m[0m [0m[0m[0m 200k[0m [0m[0m [0m[0m[0m $0.8 / $4.0[0m [0m[0m [0m[0m[0m 100 tok/s[0m [0m
|
||||
[0m[0m [0m[0m[0mgpt-5.2 [0m [0m[0m [0m[0m[0mopenai [0m [0m[0m [0m[0m[0mgpt5 [0m [0m[0m [0m[0m[0m 1m[0m [0m[0m [0m[0m[0m $1.8 / $14.0[0m [0m[0m [0m[0m[0m 65 tok/s[0m [0m
|
||||
[0m[0m [0m[0m[0mgpt-5-mini [0m [0m[0m [0m[0m[0mopenai [0m [0m[0m [0m[0m[0mgpt5-mini [0m [0m[0m [0m[0m[0m 1m[0m [0m[0m [0m[0m[0m $0.2 / $2.0[0m [0m[0m [0m[0m[0m 70 tok/s[0m [0m
|
||||
[0m[0m [0m[0m[0mgpt-5.2-codex [0m [0m[0m [0m[0m[0mopenai [0m [0m[0m [0m[0m[0m [0m [0m[0m [0m[0m[0m 1m[0m [0m[0m [0m[0m[0m $1.8 / $14.0[0m [0m[0m [0m[0m[0m 100 tok/s[0m [0m
|
||||
[0m[0m [0m[0m[0mgpt-5.3-codex [0m [0m[0m [0m[0m[0mopenai [0m [0m[0m [0m[0m[0mcodex [0m [0m[0m [0m[0m[0m 1m[0m [0m[0m [0m[0m[0m $1.8 / $14.0[0m [0m[0m [0m[0m[0m 100 tok/s[0m [0m
|
||||
[0m[0m [0m[0m[0mgpt-5.3-codex-spark [0m [0m[0m [0m[0m[0mopenai [0m [0m[0m [0m[0m[0mcodex-spark [0m [0m[0m [0m[0m[0m 131k[0m [0m[0m [0m[0m[0m - / -[0m [0m[0m [0m[0m[0m1000 tok/s[0m [0m
|
||||
[0m[0m [0m[0m[0mgpt-5.4 [0m [0m[0m [0m[0m[0mopenai [0m [0m[0m [0m[0m[0mgpt54, gpt-54 [0m [0m[0m [0m[0m[0m 1m[0m [0m[0m [0m[0m[0m $2.5 / $15.0[0m [0m[0m [0m[0m[0m 70 tok/s[0m [0m
|
||||
[0m[0m [0m[0m[0mgpt-5.4-pro [0m [0m[0m [0m[0m[0mopenai [0m [0m[0m [0m[0m[0mgpt54-pro, gpt-54-pro [0m [0m[0m [0m[0m[0m 1m[0m [0m[0m [0m[0m[0m$30.0 / $180.0[0m [0m[0m [0m[0m[0m 20 tok/s[0m [0m
|
||||
[0m[0m [0m[0m[0mgpt-5.4-mini [0m [0m[0m [0m[0m[0mopenai [0m [0m[0m [0m[0m[0mgpt54-mini, gpt-54-mini[0m [0m[0m [0m[0m[0m 400k[0m [0m[0m [0m[0m[0m $0.8 / $4.5[0m [0m[0m [0m[0m[0m 140 tok/s[0m [0m
|
||||
[0m[0m [0m[0m[0mgemini-3.1-pro-preview [0m [0m[0m [0m[0m[0mgemini [0m [0m[0m [0m[0m[0mgemini-pro [0m [0m[0m [0m[0m[0m 1m[0m [0m[0m [0m[0m[0m $2.0 / $12.0[0m [0m[0m [0m[0m[0m 85 tok/s[0m [0m
|
||||
[0m[0m [0m[0m[0mgemini-3.1-pro-preview-customtools[0m [0m[0m [0m[0m[0mgemini [0m [0m[0m [0m[0m[0mgemini-customtools [0m [0m[0m [0m[0m[0m 1m[0m [0m[0m [0m[0m[0m $2.0 / $12.0[0m [0m[0m [0m[0m[0m 85 tok/s[0m [0m
|
||||
[0m[0m [0m[0m[0mgemini-3-flash-preview [0m [0m[0m [0m[0m[0mgemini [0m [0m[0m [0m[0m[0mgemini-flash [0m [0m[0m [0m[0m[0m 1m[0m [0m[0m [0m[0m[0m $0.5 / $3.0[0m [0m[0m [0m[0m[0m 150 tok/s[0m [0m
|
||||
[0m[0m [0m[0m[0mgemini-3.1-flash-lite-preview [0m [0m[0m [0m[0m[0mgemini [0m [0m[0m [0m[0m[0mgemini-flash-lite [0m [0m[0m [0m[0m[0m 1m[0m [0m[0m [0m[0m[0m $0.2 / $1.5[0m [0m[0m [0m[0m[0m 200 tok/s[0m [0m
|
||||
[0m[0m [0m[0m[0mkimi-k2.5 [0m [0m[0m [0m[0m[0mkimi [0m [0m[0m [0m[0m[0mkimi [0m [0m[0m [0m[0m[0m 262k[0m [0m[0m [0m[0m[0m $0.6 / $3.0[0m [0m[0m [0m[0m[0m 50 tok/s[0m [0m
|
||||
[0m[0m [0m[0m[0mglm-4.7 [0m [0m[0m [0m[0m[0mzai [0m [0m[0m [0m[0m[0mglm, glm4 [0m [0m[0m [0m[0m[0m 203k[0m [0m[0m [0m[0m[0m $0.6 / $2.2[0m [0m[0m [0m[0m[0m 100 tok/s[0m [0m
|
||||
[0m[0m [0m[0m[0mminimax-m2.5 [0m [0m[0m [0m[0m[0mminimax [0m [0m[0m [0m[0m[0mminimax [0m [0m[0m [0m[0m[0m 197k[0m [0m[0m [0m[0m[0m $0.3 / $1.2[0m [0m[0m [0m[0m[0m 45 tok/s[0m [0m
|
||||
[0m[0m [0m[0m[0mmercury-2 [0m [0m[0m [0m[0m[0minception[0m [0m[0m [0m[0m[0mmercury [0m [0m[0m [0m[0m[0m 131k[0m [0m[0m [0m[0m[0m $0.2 / $0.8[0m [0m[0m [0m[0m[0m1000 tok/s[0m [0m
|
||||
[0m
|
||||
```
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
```console
|
||||
$ fabro model --help
|
||||
[..]
|
||||
...
|
||||
|
||||
```
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
```console
|
||||
$ fabro model list --provider anthropic
|
||||
[0m [0m[0m[0m[1mMODEL [0m [0m[0m [0m[0m[0m[1mPROVIDER [0m [0m[0m [0m[0m[0m[1mALIASES [0m [0m[0m [0m[0m[0m[1mCONTEXT[0m [0m[0m [0m[0m[0m[1m COST[0m [0m[0m [0m[0m[0m[1m SPEED[0m [0m
|
||||
[0m[0m [0m[0m[0mclaude-opus-4-6 [0m [0m[0m [0m[0m[0manthropic[0m [0m[0m [0m[0m[0mopus, claude-opus [0m [0m[0m [0m[0m[0m 1m[0m [0m[0m [0m[0m[0m$15.0 / $75.0[0m [0m[0m [0m[0m[0m 25 tok/s[0m [0m
|
||||
[0m[0m [0m[0m[0mclaude-sonnet-4-5[0m [0m[0m [0m[0m[0manthropic[0m [0m[0m [0m[0m[0m [0m [0m[0m [0m[0m[0m 200k[0m [0m[0m [0m[0m[0m $3.0 / $15.0[0m [0m[0m [0m[0m[0m 50 tok/s[0m [0m
|
||||
[0m[0m [0m[0m[0mclaude-sonnet-4-6[0m [0m[0m [0m[0m[0manthropic[0m [0m[0m [0m[0m[0msonnet, claude-sonnet[0m [0m[0m [0m[0m[0m 200k[0m [0m[0m [0m[0m[0m $3.0 / $15.0[0m [0m[0m [0m[0m[0m 50 tok/s[0m [0m
|
||||
[0m[0m [0m[0m[0mclaude-haiku-4-5 [0m [0m[0m [0m[0m[0manthropic[0m [0m[0m [0m[0m[0mhaiku, claude-haiku [0m [0m[0m [0m[0m[0m 200k[0m [0m[0m [0m[0m[0m $0.8 / $4.0[0m [0m[0m [0m[0m[0m100 tok/s[0m [0m
|
||||
[0m
|
||||
```
|
||||
|
|
@ -1,8 +0,0 @@
|
|||
```console
|
||||
$ fabro model list --query codex
|
||||
[0m [0m[0m[0m[1mMODEL [0m [0m[0m [0m[0m[0m[1mPROVIDER[0m [0m[0m [0m[0m[0m[1mALIASES [0m [0m[0m [0m[0m[0m[1mCONTEXT[0m [0m[0m [0m[0m[0m[1m COST[0m [0m[0m [0m[0m[0m[1m SPEED[0m [0m
|
||||
[0m[0m [0m[0m[0mgpt-5.2-codex [0m [0m[0m [0m[0m[0mopenai [0m [0m[0m [0m[0m[0m [0m [0m[0m [0m[0m[0m 1m[0m [0m[0m [0m[0m[0m$1.8 / $14.0[0m [0m[0m [0m[0m[0m 100 tok/s[0m [0m
|
||||
[0m[0m [0m[0m[0mgpt-5.3-codex [0m [0m[0m [0m[0m[0mopenai [0m [0m[0m [0m[0m[0mcodex [0m [0m[0m [0m[0m[0m 1m[0m [0m[0m [0m[0m[0m$1.8 / $14.0[0m [0m[0m [0m[0m[0m 100 tok/s[0m [0m
|
||||
[0m[0m [0m[0m[0mgpt-5.3-codex-spark[0m [0m[0m [0m[0m[0mopenai [0m [0m[0m [0m[0m[0mcodex-spark[0m [0m[0m [0m[0m[0m 131k[0m [0m[0m [0m[0m[0m - / -[0m [0m[0m [0m[0m[0m1000 tok/s[0m [0m
|
||||
[0m
|
||||
```
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
```console
|
||||
$ fabro model list --query OPUS
|
||||
[0m [0m[0m[0m[1mMODEL [0m [0m[0m [0m[0m[0m[1mPROVIDER [0m [0m[0m [0m[0m[0m[1mALIASES [0m [0m[0m [0m[0m[0m[1mCONTEXT[0m [0m[0m [0m[0m[0m[1m COST[0m [0m[0m [0m[0m[0m[1m SPEED[0m [0m
|
||||
[0m[0m [0m[0m[0mclaude-opus-4-6[0m [0m[0m [0m[0m[0manthropic[0m [0m[0m [0m[0m[0mopus, claude-opus[0m [0m[0m [0m[0m[0m 1m[0m [0m[0m [0m[0m[0m$15.0 / $75.0[0m [0m[0m [0m[0m[0m25 tok/s[0m [0m
|
||||
[0m
|
||||
```
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
```console
|
||||
$ fabro model list --query opus
|
||||
[0m [0m[0m[0m[1mMODEL [0m [0m[0m [0m[0m[0m[1mPROVIDER [0m [0m[0m [0m[0m[0m[1mALIASES [0m [0m[0m [0m[0m[0m[1mCONTEXT[0m [0m[0m [0m[0m[0m[1m COST[0m [0m[0m [0m[0m[0m[1m SPEED[0m [0m
|
||||
[0m[0m [0m[0m[0mclaude-opus-4-6[0m [0m[0m [0m[0m[0manthropic[0m [0m[0m [0m[0m[0mopus, claude-opus[0m [0m[0m [0m[0m[0m 1m[0m [0m[0m [0m[0m[0m$15.0 / $75.0[0m [0m[0m [0m[0m[0m25 tok/s[0m [0m
|
||||
[0m
|
||||
```
|
||||
|
|
@ -1,25 +0,0 @@
|
|||
```console
|
||||
$ fabro model list
|
||||
[0m [0m[0m[0m[1mMODEL [0m [0m[0m [0m[0m[0m[1mPROVIDER [0m [0m[0m [0m[0m[0m[1mALIASES [0m [0m[0m [0m[0m[0m[1mCONTEXT[0m [0m[0m [0m[0m[0m[1m COST[0m [0m[0m [0m[0m[0m[1m SPEED[0m [0m
|
||||
[0m[0m [0m[0m[0mclaude-opus-4-6 [0m [0m[0m [0m[0m[0manthropic[0m [0m[0m [0m[0m[0mopus, claude-opus [0m [0m[0m [0m[0m[0m 1m[0m [0m[0m [0m[0m[0m $15.0 / $75.0[0m [0m[0m [0m[0m[0m 25 tok/s[0m [0m
|
||||
[0m[0m [0m[0m[0mclaude-sonnet-4-5 [0m [0m[0m [0m[0m[0manthropic[0m [0m[0m [0m[0m[0m [0m [0m[0m [0m[0m[0m 200k[0m [0m[0m [0m[0m[0m $3.0 / $15.0[0m [0m[0m [0m[0m[0m 50 tok/s[0m [0m
|
||||
[0m[0m [0m[0m[0mclaude-sonnet-4-6 [0m [0m[0m [0m[0m[0manthropic[0m [0m[0m [0m[0m[0msonnet, claude-sonnet [0m [0m[0m [0m[0m[0m 200k[0m [0m[0m [0m[0m[0m $3.0 / $15.0[0m [0m[0m [0m[0m[0m 50 tok/s[0m [0m
|
||||
[0m[0m [0m[0m[0mclaude-haiku-4-5 [0m [0m[0m [0m[0m[0manthropic[0m [0m[0m [0m[0m[0mhaiku, claude-haiku [0m [0m[0m [0m[0m[0m 200k[0m [0m[0m [0m[0m[0m $0.8 / $4.0[0m [0m[0m [0m[0m[0m 100 tok/s[0m [0m
|
||||
[0m[0m [0m[0m[0mgpt-5.2 [0m [0m[0m [0m[0m[0mopenai [0m [0m[0m [0m[0m[0mgpt5 [0m [0m[0m [0m[0m[0m 1m[0m [0m[0m [0m[0m[0m $1.8 / $14.0[0m [0m[0m [0m[0m[0m 65 tok/s[0m [0m
|
||||
[0m[0m [0m[0m[0mgpt-5-mini [0m [0m[0m [0m[0m[0mopenai [0m [0m[0m [0m[0m[0mgpt5-mini [0m [0m[0m [0m[0m[0m 1m[0m [0m[0m [0m[0m[0m $0.2 / $2.0[0m [0m[0m [0m[0m[0m 70 tok/s[0m [0m
|
||||
[0m[0m [0m[0m[0mgpt-5.2-codex [0m [0m[0m [0m[0m[0mopenai [0m [0m[0m [0m[0m[0m [0m [0m[0m [0m[0m[0m 1m[0m [0m[0m [0m[0m[0m $1.8 / $14.0[0m [0m[0m [0m[0m[0m 100 tok/s[0m [0m
|
||||
[0m[0m [0m[0m[0mgpt-5.3-codex [0m [0m[0m [0m[0m[0mopenai [0m [0m[0m [0m[0m[0mcodex [0m [0m[0m [0m[0m[0m 1m[0m [0m[0m [0m[0m[0m $1.8 / $14.0[0m [0m[0m [0m[0m[0m 100 tok/s[0m [0m
|
||||
[0m[0m [0m[0m[0mgpt-5.3-codex-spark [0m [0m[0m [0m[0m[0mopenai [0m [0m[0m [0m[0m[0mcodex-spark [0m [0m[0m [0m[0m[0m 131k[0m [0m[0m [0m[0m[0m - / -[0m [0m[0m [0m[0m[0m1000 tok/s[0m [0m
|
||||
[0m[0m [0m[0m[0mgpt-5.4 [0m [0m[0m [0m[0m[0mopenai [0m [0m[0m [0m[0m[0mgpt54, gpt-54 [0m [0m[0m [0m[0m[0m 1m[0m [0m[0m [0m[0m[0m $2.5 / $15.0[0m [0m[0m [0m[0m[0m 70 tok/s[0m [0m
|
||||
[0m[0m [0m[0m[0mgpt-5.4-pro [0m [0m[0m [0m[0m[0mopenai [0m [0m[0m [0m[0m[0mgpt54-pro, gpt-54-pro [0m [0m[0m [0m[0m[0m 1m[0m [0m[0m [0m[0m[0m$30.0 / $180.0[0m [0m[0m [0m[0m[0m 20 tok/s[0m [0m
|
||||
[0m[0m [0m[0m[0mgpt-5.4-mini [0m [0m[0m [0m[0m[0mopenai [0m [0m[0m [0m[0m[0mgpt54-mini, gpt-54-mini[0m [0m[0m [0m[0m[0m 400k[0m [0m[0m [0m[0m[0m $0.8 / $4.5[0m [0m[0m [0m[0m[0m 140 tok/s[0m [0m
|
||||
[0m[0m [0m[0m[0mgemini-3.1-pro-preview [0m [0m[0m [0m[0m[0mgemini [0m [0m[0m [0m[0m[0mgemini-pro [0m [0m[0m [0m[0m[0m 1m[0m [0m[0m [0m[0m[0m $2.0 / $12.0[0m [0m[0m [0m[0m[0m 85 tok/s[0m [0m
|
||||
[0m[0m [0m[0m[0mgemini-3.1-pro-preview-customtools[0m [0m[0m [0m[0m[0mgemini [0m [0m[0m [0m[0m[0mgemini-customtools [0m [0m[0m [0m[0m[0m 1m[0m [0m[0m [0m[0m[0m $2.0 / $12.0[0m [0m[0m [0m[0m[0m 85 tok/s[0m [0m
|
||||
[0m[0m [0m[0m[0mgemini-3-flash-preview [0m [0m[0m [0m[0m[0mgemini [0m [0m[0m [0m[0m[0mgemini-flash [0m [0m[0m [0m[0m[0m 1m[0m [0m[0m [0m[0m[0m $0.5 / $3.0[0m [0m[0m [0m[0m[0m 150 tok/s[0m [0m
|
||||
[0m[0m [0m[0m[0mgemini-3.1-flash-lite-preview [0m [0m[0m [0m[0m[0mgemini [0m [0m[0m [0m[0m[0mgemini-flash-lite [0m [0m[0m [0m[0m[0m 1m[0m [0m[0m [0m[0m[0m $0.2 / $1.5[0m [0m[0m [0m[0m[0m 200 tok/s[0m [0m
|
||||
[0m[0m [0m[0m[0mkimi-k2.5 [0m [0m[0m [0m[0m[0mkimi [0m [0m[0m [0m[0m[0mkimi [0m [0m[0m [0m[0m[0m 262k[0m [0m[0m [0m[0m[0m $0.6 / $3.0[0m [0m[0m [0m[0m[0m 50 tok/s[0m [0m
|
||||
[0m[0m [0m[0m[0mglm-4.7 [0m [0m[0m [0m[0m[0mzai [0m [0m[0m [0m[0m[0mglm, glm4 [0m [0m[0m [0m[0m[0m 203k[0m [0m[0m [0m[0m[0m $0.6 / $2.2[0m [0m[0m [0m[0m[0m 100 tok/s[0m [0m
|
||||
[0m[0m [0m[0m[0mminimax-m2.5 [0m [0m[0m [0m[0m[0mminimax [0m [0m[0m [0m[0m[0mminimax [0m [0m[0m [0m[0m[0m 197k[0m [0m[0m [0m[0m[0m $0.3 / $1.2[0m [0m[0m [0m[0m[0m 45 tok/s[0m [0m
|
||||
[0m[0m [0m[0m[0mmercury-2 [0m [0m[0m [0m[0m[0minception[0m [0m[0m [0m[0m[0mmercury [0m [0m[0m [0m[0m[0m 131k[0m [0m[0m [0m[0m[0m $0.2 / $0.8[0m [0m[0m [0m[0m[0m1000 tok/s[0m [0m
|
||||
[0m
|
||||
```
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
```console
|
||||
$ fabro pr --help
|
||||
[..]
|
||||
...
|
||||
|
||||
```
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
```console
|
||||
$ fabro preview --help
|
||||
[..]
|
||||
...
|
||||
|
||||
```
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
```console
|
||||
$ fabro repo --help
|
||||
[..]
|
||||
...
|
||||
|
||||
```
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
bin.name = "fabro"
|
||||
args = ["run", "--dry-run", "--auto-approve", "../../../test/branching.fabro"]
|
||||
|
||||
[env]
|
||||
inherit = false
|
||||
|
||||
[fs]
|
||||
sandbox = false
|
||||
cwd = "../../.."
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
bin.name = "fabro"
|
||||
args = ["run", "--dry-run", "--auto-approve", "../../../test/conditions.fabro"]
|
||||
|
||||
[env]
|
||||
inherit = false
|
||||
|
||||
[fs]
|
||||
sandbox = false
|
||||
cwd = "../../.."
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
bin.name = "fabro"
|
||||
args = ["run", "--dry-run", "--auto-approve", "../../../test/legacy_tool.fabro"]
|
||||
|
||||
[env]
|
||||
inherit = false
|
||||
|
||||
[fs]
|
||||
sandbox = false
|
||||
cwd = "../../.."
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
bin.name = "fabro"
|
||||
args = ["run", "--dry-run", "--auto-approve", "../../../test/parallel.fabro"]
|
||||
|
||||
[env]
|
||||
inherit = false
|
||||
|
||||
[fs]
|
||||
sandbox = false
|
||||
cwd = "../../.."
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
bin.name = "fabro"
|
||||
args = ["run", "--dry-run", "--auto-approve", "../../../test/simple.fabro"]
|
||||
|
||||
[env]
|
||||
inherit = false
|
||||
|
||||
[fs]
|
||||
sandbox = false
|
||||
cwd = "../../.."
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
bin.name = "fabro"
|
||||
args = ["run", "--dry-run", "--auto-approve", "../../../test/styled.fabro"]
|
||||
|
||||
[env]
|
||||
inherit = false
|
||||
|
||||
[fs]
|
||||
sandbox = false
|
||||
cwd = "../../.."
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
```console
|
||||
$ fabro run --help
|
||||
[..]
|
||||
...
|
||||
|
||||
```
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
```console
|
||||
$ fabro ssh --help
|
||||
[..]
|
||||
...
|
||||
|
||||
```
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
```console
|
||||
$ fabro system --help
|
||||
[..]
|
||||
...
|
||||
|
||||
```
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
...
|
||||
[..]unexpected argument[..]
|
||||
...
|
||||
|
|
@ -1,7 +0,0 @@
|
|||
bin.name = "fabro"
|
||||
args = ["--no-dotenv", "doctor"]
|
||||
status = "failed"
|
||||
stderr-to-stdout = true
|
||||
|
||||
[env]
|
||||
inherit = false
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
```console
|
||||
$ fabro --version
|
||||
fabro [..]
|
||||
|
||||
```
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
bin.name = "fabro"
|
||||
args = ["validate", "../../../test/branching.fabro"]
|
||||
|
||||
[env]
|
||||
inherit = false
|
||||
|
||||
[fs]
|
||||
sandbox = false
|
||||
cwd = "../../.."
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
bin.name = "fabro"
|
||||
args = ["validate", "../../../test/conditions.fabro"]
|
||||
|
||||
[env]
|
||||
inherit = false
|
||||
|
||||
[fs]
|
||||
sandbox = false
|
||||
cwd = "../../.."
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
```console
|
||||
$ fabro validate --help
|
||||
[..]
|
||||
...
|
||||
|
||||
```
|
||||
|
|
@ -1,10 +0,0 @@
|
|||
bin.name = "fabro"
|
||||
args = ["validate", "../../../test/invalid.fabro"]
|
||||
status = "failed"
|
||||
|
||||
[env]
|
||||
inherit = false
|
||||
|
||||
[fs]
|
||||
sandbox = false
|
||||
cwd = "../../.."
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
bin.name = "fabro"
|
||||
args = ["validate", "../../../test/legacy_tool.fabro"]
|
||||
|
||||
[env]
|
||||
inherit = false
|
||||
|
||||
[fs]
|
||||
sandbox = false
|
||||
cwd = "../../.."
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
bin.name = "fabro"
|
||||
args = ["validate", "../../../test/parallel.fabro"]
|
||||
|
||||
[env]
|
||||
inherit = false
|
||||
|
||||
[fs]
|
||||
sandbox = false
|
||||
cwd = "../../.."
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
bin.name = "fabro"
|
||||
args = ["validate", "../../../test/simple.fabro"]
|
||||
|
||||
[env]
|
||||
inherit = false
|
||||
|
||||
[fs]
|
||||
sandbox = false
|
||||
cwd = "../../.."
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
bin.name = "fabro"
|
||||
args = ["validate", "../../../test/styled.fabro"]
|
||||
|
||||
[env]
|
||||
inherit = false
|
||||
|
||||
[fs]
|
||||
sandbox = false
|
||||
cwd = "../../.."
|
||||
56
lib/crates/fabro-cli/tests/it/cmd/config.rs
Normal file
56
lib/crates/fabro-cli/tests/it/cmd/config.rs
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
use fabro_test::{fabro_snapshot, test_context};
|
||||
|
||||
#[test]
|
||||
fn help() {
|
||||
let context = test_context!();
|
||||
let mut cmd = context.config();
|
||||
cmd.arg("--help");
|
||||
fabro_snapshot!(context.filters(), cmd, @"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
Inspect merged configuration
|
||||
|
||||
Usage: fabro config [OPTIONS] <COMMAND>
|
||||
|
||||
Commands:
|
||||
show Print the merged FabroSettings as YAML
|
||||
help Print this message or the help of the given subcommand(s)
|
||||
|
||||
Options:
|
||||
--debug Enable DEBUG-level logging (default is INFO) [env: FABRO_DEBUG=]
|
||||
--no-upgrade-check Disable automatic upgrade check [env: FABRO_NO_UPGRADE_CHECK=true]
|
||||
--quiet Suppress non-essential output [env: FABRO_QUIET=]
|
||||
--verbose Enable verbose output [env: FABRO_VERBOSE=]
|
||||
--storage-dir <STORAGE_DIR> Storage directory (default: ~/.fabro) [env: FABRO_STORAGE_DIR=[STORAGE_DIR]]
|
||||
-h, --help Print help
|
||||
----- stderr -----
|
||||
");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn show_help() {
|
||||
let context = test_context!();
|
||||
let mut cmd = context.config();
|
||||
cmd.args(["show", "--help"]);
|
||||
fabro_snapshot!(context.filters(), cmd, @"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
Print the merged FabroSettings as YAML
|
||||
|
||||
Usage: fabro config show [OPTIONS] [WORKFLOW]
|
||||
|
||||
Arguments:
|
||||
[WORKFLOW] Optional workflow name, .fabro path, or .toml run config to overlay
|
||||
|
||||
Options:
|
||||
--debug Enable DEBUG-level logging (default is INFO) [env: FABRO_DEBUG=]
|
||||
--no-upgrade-check Disable automatic upgrade check [env: FABRO_NO_UPGRADE_CHECK=true]
|
||||
--quiet Suppress non-essential output [env: FABRO_QUIET=]
|
||||
--verbose Enable verbose output [env: FABRO_VERBOSE=]
|
||||
--storage-dir <STORAGE_DIR> Storage directory (default: ~/.fabro) [env: FABRO_STORAGE_DIR=[STORAGE_DIR]]
|
||||
-h, --help Print help
|
||||
----- stderr -----
|
||||
");
|
||||
}
|
||||
30
lib/crates/fabro-cli/tests/it/cmd/cp.rs
Normal file
30
lib/crates/fabro-cli/tests/it/cmd/cp.rs
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
use fabro_test::{fabro_snapshot, test_context};
|
||||
|
||||
#[test]
|
||||
fn help() {
|
||||
let context = test_context!();
|
||||
let mut cmd = context.cp();
|
||||
cmd.arg("--help");
|
||||
fabro_snapshot!(context.filters(), cmd, @"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
Copy files to/from a run's sandbox
|
||||
|
||||
Usage: fabro cp [OPTIONS] <SRC> <DST>
|
||||
|
||||
Arguments:
|
||||
<SRC> Source: <run-id>:<path> or local path
|
||||
<DST> Destination: <run-id>:<path> or local path
|
||||
|
||||
Options:
|
||||
--debug Enable DEBUG-level logging (default is INFO) [env: FABRO_DEBUG=]
|
||||
-r, --recursive Recurse into directories
|
||||
--no-upgrade-check Disable automatic upgrade check [env: FABRO_NO_UPGRADE_CHECK=true]
|
||||
--quiet Suppress non-essential output [env: FABRO_QUIET=]
|
||||
--verbose Enable verbose output [env: FABRO_VERBOSE=]
|
||||
--storage-dir <STORAGE_DIR> Storage directory (default: ~/.fabro) [env: FABRO_STORAGE_DIR=[STORAGE_DIR]]
|
||||
-h, --help Print help
|
||||
----- stderr -----
|
||||
");
|
||||
}
|
||||
62
lib/crates/fabro-cli/tests/it/cmd/doctor.rs
Normal file
62
lib/crates/fabro-cli/tests/it/cmd/doctor.rs
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
use fabro_test::{fabro_snapshot, test_context};
|
||||
|
||||
#[test]
|
||||
fn help() {
|
||||
let context = test_context!();
|
||||
let mut cmd = context.doctor();
|
||||
cmd.arg("--help");
|
||||
fabro_snapshot!(context.filters(), cmd, @"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
Check environment and integration health
|
||||
|
||||
Usage: fabro doctor [OPTIONS]
|
||||
|
||||
Options:
|
||||
--debug Enable DEBUG-level logging (default is INFO) [env: FABRO_DEBUG=]
|
||||
-v, --verbose Show detailed information for each check
|
||||
--dry-run Skip live service probes (LLM, sandbox, API, web, Brave Search)
|
||||
--no-upgrade-check Disable automatic upgrade check [env: FABRO_NO_UPGRADE_CHECK=true]
|
||||
--quiet Suppress non-essential output [env: FABRO_QUIET=]
|
||||
--storage-dir <STORAGE_DIR> Storage directory (default: ~/.fabro) [env: FABRO_STORAGE_DIR=[STORAGE_DIR]]
|
||||
-h, --help Print help
|
||||
----- stderr -----
|
||||
");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dry_run_flag() {
|
||||
let context = test_context!();
|
||||
let mut cmd = context.doctor();
|
||||
cmd.arg("--dry-run");
|
||||
cmd.env(
|
||||
"PATH",
|
||||
"/usr/local/bin:/opt/homebrew/bin:/usr/bin:/bin:/usr/sbin:/sbin",
|
||||
);
|
||||
cmd.env("ANTHROPIC_API_KEY", "sk-test-dummy");
|
||||
fabro_snapshot!(context.filters(), cmd, @"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
Fabro Doctor
|
||||
|
||||
Required
|
||||
[!] Configuration (no user config file found)
|
||||
[✓] LLM providers (1 configured)
|
||||
[!] GitHub App (not configured)
|
||||
|
||||
Optional
|
||||
[!] Cloud sandbox (no sandbox configured)
|
||||
[!] Brave Search (not configured)
|
||||
|
||||
Found issues in 4 categories.
|
||||
|
||||
Warnings:
|
||||
• Configuration — Create ~/.fabro/user.toml
|
||||
• GitHub App — Configure GitHub App in server.toml and set env vars to enable GitHub integration
|
||||
• Cloud sandbox — Set DAYTONA_API_KEY to enable cloud sandbox execution
|
||||
• Brave Search — Set BRAVE_SEARCH_API_KEY to enable web search
|
||||
----- stderr -----
|
||||
");
|
||||
}
|
||||
35
lib/crates/fabro-cli/tests/it/cmd/exec.rs
Normal file
35
lib/crates/fabro-cli/tests/it/cmd/exec.rs
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
use fabro_test::{fabro_snapshot, test_context};
|
||||
|
||||
#[test]
|
||||
fn invalid_permissions() {
|
||||
let context = test_context!();
|
||||
let mut cmd = context.exec_cmd();
|
||||
cmd.args(["--permissions", "bogus", "test prompt"]);
|
||||
fabro_snapshot!(context.filters(), cmd, @"
|
||||
success: false
|
||||
exit_code: 2
|
||||
----- stdout -----
|
||||
----- stderr -----
|
||||
error: invalid value 'bogus' for '--permissions <PERMISSIONS>'
|
||||
[possible values: read-only, read-write, full]
|
||||
|
||||
For more information, try '--help'.
|
||||
");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn no_prompt() {
|
||||
let context = test_context!();
|
||||
fabro_snapshot!(context.filters(), context.exec_cmd(), @"
|
||||
success: false
|
||||
exit_code: 2
|
||||
----- stdout -----
|
||||
----- stderr -----
|
||||
error: the following required arguments were not provided:
|
||||
<PROMPT>
|
||||
|
||||
Usage: fabro exec --no-upgrade-check --storage-dir <STORAGE_DIR> <PROMPT>
|
||||
|
||||
For more information, try '--help'.
|
||||
");
|
||||
}
|
||||
25
lib/crates/fabro-cli/tests/it/cmd/init.rs
Normal file
25
lib/crates/fabro-cli/tests/it/cmd/init.rs
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
use fabro_test::{fabro_snapshot, test_context};
|
||||
|
||||
#[test]
|
||||
fn help() {
|
||||
let context = test_context!();
|
||||
let mut cmd = context.init_cmd();
|
||||
cmd.arg("--help");
|
||||
fabro_snapshot!(context.filters(), cmd, @"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
Initialize a new project (deprecated: use `repo init`)
|
||||
|
||||
Usage: fabro init [OPTIONS]
|
||||
|
||||
Options:
|
||||
--debug Enable DEBUG-level logging (default is INFO) [env: FABRO_DEBUG=]
|
||||
--no-upgrade-check Disable automatic upgrade check [env: FABRO_NO_UPGRADE_CHECK=true]
|
||||
--quiet Suppress non-essential output [env: FABRO_QUIET=]
|
||||
--verbose Enable verbose output [env: FABRO_VERBOSE=]
|
||||
--storage-dir <STORAGE_DIR> Storage directory (default: ~/.fabro) [env: FABRO_STORAGE_DIR=[STORAGE_DIR]]
|
||||
-h, --help Print help
|
||||
----- stderr -----
|
||||
");
|
||||
}
|
||||
26
lib/crates/fabro-cli/tests/it/cmd/install.rs
Normal file
26
lib/crates/fabro-cli/tests/it/cmd/install.rs
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
use fabro_test::{fabro_snapshot, test_context};
|
||||
|
||||
#[test]
|
||||
fn help() {
|
||||
let context = test_context!();
|
||||
let mut cmd = context.install();
|
||||
cmd.arg("--help");
|
||||
fabro_snapshot!(context.filters(), cmd, @"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
Set up the Fabro environment (LLMs, certs, GitHub)
|
||||
|
||||
Usage: fabro install [OPTIONS]
|
||||
|
||||
Options:
|
||||
--debug Enable DEBUG-level logging (default is INFO) [env: FABRO_DEBUG=]
|
||||
--web-url <WEB_URL> Base URL for the web UI (used for OAuth callback URLs) [default: http://localhost:5173]
|
||||
--no-upgrade-check Disable automatic upgrade check [env: FABRO_NO_UPGRADE_CHECK=true]
|
||||
--quiet Suppress non-essential output [env: FABRO_QUIET=]
|
||||
--verbose Enable verbose output [env: FABRO_VERBOSE=]
|
||||
--storage-dir <STORAGE_DIR> Storage directory (default: ~/.fabro) [env: FABRO_STORAGE_DIR=[STORAGE_DIR]]
|
||||
-h, --help Print help
|
||||
----- stderr -----
|
||||
");
|
||||
}
|
||||
55
lib/crates/fabro-cli/tests/it/cmd/llm.rs
Normal file
55
lib/crates/fabro-cli/tests/it/cmd/llm.rs
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
use fabro_test::{fabro_snapshot, test_context};
|
||||
|
||||
#[test]
|
||||
fn prompt_bad_option() {
|
||||
let context = test_context!();
|
||||
let mut cmd = context.llm();
|
||||
cmd.args(["prompt", "-o", "bad_option", "hello"]);
|
||||
fabro_snapshot!(context.filters(), cmd, @"
|
||||
success: false
|
||||
exit_code: 2
|
||||
----- stdout -----
|
||||
----- stderr -----
|
||||
error: invalid value 'bad_option' for '--option <OPTION>': expected key=value, got bad_option
|
||||
|
||||
For more information, try '--help'.
|
||||
");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn prompt_no_text() {
|
||||
let context = test_context!();
|
||||
let mut cmd = context.llm();
|
||||
cmd.arg("prompt");
|
||||
fabro_snapshot!(context.filters(), cmd, @"
|
||||
success: false
|
||||
exit_code: 1
|
||||
----- stdout -----
|
||||
----- stderr -----
|
||||
error: Error: no prompt provided. Pass a prompt as an argument or pipe text via stdin.
|
||||
");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn prompt_schema_invalid() {
|
||||
let context = test_context!();
|
||||
let mut cmd = context.llm();
|
||||
cmd.args([
|
||||
"prompt",
|
||||
"--no-stream",
|
||||
"-m",
|
||||
"test-model",
|
||||
"--schema",
|
||||
"not json",
|
||||
"hello",
|
||||
]);
|
||||
fabro_snapshot!(context.filters(), cmd, @"
|
||||
success: false
|
||||
exit_code: 1
|
||||
----- stdout -----
|
||||
----- stderr -----
|
||||
Using model: test-model
|
||||
error: --schema must be valid JSON
|
||||
> expected ident at line 1 column 2
|
||||
");
|
||||
}
|
||||
17
lib/crates/fabro-cli/tests/it/cmd/mod.rs
Normal file
17
lib/crates/fabro-cli/tests/it/cmd/mod.rs
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
mod config;
|
||||
mod cp;
|
||||
mod doctor;
|
||||
mod exec;
|
||||
mod init;
|
||||
mod install;
|
||||
mod llm;
|
||||
mod model;
|
||||
mod pr;
|
||||
mod preview;
|
||||
mod repo;
|
||||
mod run;
|
||||
mod secret;
|
||||
mod ssh;
|
||||
mod system;
|
||||
mod top_level;
|
||||
mod validate;
|
||||
161
lib/crates/fabro-cli/tests/it/cmd/model.rs
Normal file
161
lib/crates/fabro-cli/tests/it/cmd/model.rs
Normal file
|
|
@ -0,0 +1,161 @@
|
|||
use fabro_test::{fabro_snapshot, test_context};
|
||||
|
||||
#[test]
|
||||
fn help() {
|
||||
let context = test_context!();
|
||||
let mut cmd = context.model();
|
||||
cmd.arg("--help");
|
||||
fabro_snapshot!(context.filters(), cmd, @"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
List and test LLM models
|
||||
|
||||
Usage: fabro model [OPTIONS] [COMMAND]
|
||||
|
||||
Commands:
|
||||
list List available models
|
||||
test Test model availability by sending a simple prompt
|
||||
help Print this message or the help of the given subcommand(s)
|
||||
|
||||
Options:
|
||||
--debug Enable DEBUG-level logging (default is INFO) [env: FABRO_DEBUG=]
|
||||
--no-upgrade-check Disable automatic upgrade check [env: FABRO_NO_UPGRADE_CHECK=true]
|
||||
--quiet Suppress non-essential output [env: FABRO_QUIET=]
|
||||
--verbose Enable verbose output [env: FABRO_VERBOSE=]
|
||||
--storage-dir <STORAGE_DIR> Storage directory (default: ~/.fabro) [env: FABRO_STORAGE_DIR=[STORAGE_DIR]]
|
||||
-h, --help Print help
|
||||
----- stderr -----
|
||||
");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bare() {
|
||||
let context = test_context!();
|
||||
fabro_snapshot!(context.filters(), context.model(), @"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
[0m [0m[0m[0m[1mMODEL [0m [0m[0m [0m[0m[0m[1mPROVIDER [0m [0m[0m [0m[0m[0m[1mALIASES [0m [0m[0m [0m[0m[0m[1mCONTEXT[0m [0m[0m [0m[0m[0m[1m COST[0m [0m[0m [0m[0m[0m[1m SPEED[0m [0m
|
||||
[0m[0m [0m[0m[0mclaude-opus-4-6 [0m [0m[0m [0m[0m[0manthropic[0m [0m[0m [0m[0m[0mopus, claude-opus [0m [0m[0m [0m[0m[0m 1m[0m [0m[0m [0m[0m[0m $15.0 / $75.0[0m [0m[0m [0m[0m[0m 25 tok/s[0m [0m
|
||||
[0m[0m [0m[0m[0mclaude-sonnet-4-5 [0m [0m[0m [0m[0m[0manthropic[0m [0m[0m [0m[0m[0m [0m [0m[0m [0m[0m[0m 200k[0m [0m[0m [0m[0m[0m $3.0 / $15.0[0m [0m[0m [0m[0m[0m 50 tok/s[0m [0m
|
||||
[0m[0m [0m[0m[0mclaude-sonnet-4-6 [0m [0m[0m [0m[0m[0manthropic[0m [0m[0m [0m[0m[0msonnet, claude-sonnet [0m [0m[0m [0m[0m[0m 200k[0m [0m[0m [0m[0m[0m $3.0 / $15.0[0m [0m[0m [0m[0m[0m 50 tok/s[0m [0m
|
||||
[0m[0m [0m[0m[0mclaude-haiku-4-5 [0m [0m[0m [0m[0m[0manthropic[0m [0m[0m [0m[0m[0mhaiku, claude-haiku [0m [0m[0m [0m[0m[0m 200k[0m [0m[0m [0m[0m[0m $0.8 / $4.0[0m [0m[0m [0m[0m[0m 100 tok/s[0m [0m
|
||||
[0m[0m [0m[0m[0mgpt-5.2 [0m [0m[0m [0m[0m[0mopenai [0m [0m[0m [0m[0m[0mgpt5 [0m [0m[0m [0m[0m[0m 1m[0m [0m[0m [0m[0m[0m $1.8 / $14.0[0m [0m[0m [0m[0m[0m 65 tok/s[0m [0m
|
||||
[0m[0m [0m[0m[0mgpt-5-mini [0m [0m[0m [0m[0m[0mopenai [0m [0m[0m [0m[0m[0mgpt5-mini [0m [0m[0m [0m[0m[0m 1m[0m [0m[0m [0m[0m[0m $0.2 / $2.0[0m [0m[0m [0m[0m[0m 70 tok/s[0m [0m
|
||||
[0m[0m [0m[0m[0mgpt-5.2-codex [0m [0m[0m [0m[0m[0mopenai [0m [0m[0m [0m[0m[0m [0m [0m[0m [0m[0m[0m 1m[0m [0m[0m [0m[0m[0m $1.8 / $14.0[0m [0m[0m [0m[0m[0m 100 tok/s[0m [0m
|
||||
[0m[0m [0m[0m[0mgpt-5.3-codex [0m [0m[0m [0m[0m[0mopenai [0m [0m[0m [0m[0m[0mcodex [0m [0m[0m [0m[0m[0m 1m[0m [0m[0m [0m[0m[0m $1.8 / $14.0[0m [0m[0m [0m[0m[0m 100 tok/s[0m [0m
|
||||
[0m[0m [0m[0m[0mgpt-5.3-codex-spark [0m [0m[0m [0m[0m[0mopenai [0m [0m[0m [0m[0m[0mcodex-spark [0m [0m[0m [0m[0m[0m 131k[0m [0m[0m [0m[0m[0m - / -[0m [0m[0m [0m[0m[0m1000 tok/s[0m [0m
|
||||
[0m[0m [0m[0m[0mgpt-5.4 [0m [0m[0m [0m[0m[0mopenai [0m [0m[0m [0m[0m[0mgpt54, gpt-54 [0m [0m[0m [0m[0m[0m 1m[0m [0m[0m [0m[0m[0m $2.5 / $15.0[0m [0m[0m [0m[0m[0m 70 tok/s[0m [0m
|
||||
[0m[0m [0m[0m[0mgpt-5.4-pro [0m [0m[0m [0m[0m[0mopenai [0m [0m[0m [0m[0m[0mgpt54-pro, gpt-54-pro [0m [0m[0m [0m[0m[0m 1m[0m [0m[0m [0m[0m[0m$30.0 / $180.0[0m [0m[0m [0m[0m[0m 20 tok/s[0m [0m
|
||||
[0m[0m [0m[0m[0mgpt-5.4-mini [0m [0m[0m [0m[0m[0mopenai [0m [0m[0m [0m[0m[0mgpt54-mini, gpt-54-mini[0m [0m[0m [0m[0m[0m 400k[0m [0m[0m [0m[0m[0m $0.8 / $4.5[0m [0m[0m [0m[0m[0m 140 tok/s[0m [0m
|
||||
[0m[0m [0m[0m[0mgemini-3.1-pro-preview [0m [0m[0m [0m[0m[0mgemini [0m [0m[0m [0m[0m[0mgemini-pro [0m [0m[0m [0m[0m[0m 1m[0m [0m[0m [0m[0m[0m $2.0 / $12.0[0m [0m[0m [0m[0m[0m 85 tok/s[0m [0m
|
||||
[0m[0m [0m[0m[0mgemini-3.1-pro-preview-customtools[0m [0m[0m [0m[0m[0mgemini [0m [0m[0m [0m[0m[0mgemini-customtools [0m [0m[0m [0m[0m[0m 1m[0m [0m[0m [0m[0m[0m $2.0 / $12.0[0m [0m[0m [0m[0m[0m 85 tok/s[0m [0m
|
||||
[0m[0m [0m[0m[0mgemini-3-flash-preview [0m [0m[0m [0m[0m[0mgemini [0m [0m[0m [0m[0m[0mgemini-flash [0m [0m[0m [0m[0m[0m 1m[0m [0m[0m [0m[0m[0m $0.5 / $3.0[0m [0m[0m [0m[0m[0m 150 tok/s[0m [0m
|
||||
[0m[0m [0m[0m[0mgemini-3.1-flash-lite-preview [0m [0m[0m [0m[0m[0mgemini [0m [0m[0m [0m[0m[0mgemini-flash-lite [0m [0m[0m [0m[0m[0m 1m[0m [0m[0m [0m[0m[0m $0.2 / $1.5[0m [0m[0m [0m[0m[0m 200 tok/s[0m [0m
|
||||
[0m[0m [0m[0m[0mkimi-k2.5 [0m [0m[0m [0m[0m[0mkimi [0m [0m[0m [0m[0m[0mkimi [0m [0m[0m [0m[0m[0m 262k[0m [0m[0m [0m[0m[0m $0.6 / $3.0[0m [0m[0m [0m[0m[0m 50 tok/s[0m [0m
|
||||
[0m[0m [0m[0m[0mglm-4.7 [0m [0m[0m [0m[0m[0mzai [0m [0m[0m [0m[0m[0mglm, glm4 [0m [0m[0m [0m[0m[0m 203k[0m [0m[0m [0m[0m[0m $0.6 / $2.2[0m [0m[0m [0m[0m[0m 100 tok/s[0m [0m
|
||||
[0m[0m [0m[0m[0mminimax-m2.5 [0m [0m[0m [0m[0m[0mminimax [0m [0m[0m [0m[0m[0mminimax [0m [0m[0m [0m[0m[0m 197k[0m [0m[0m [0m[0m[0m $0.3 / $1.2[0m [0m[0m [0m[0m[0m 45 tok/s[0m [0m
|
||||
[0m[0m [0m[0m[0mmercury-2 [0m [0m[0m [0m[0m[0minception[0m [0m[0m [0m[0m[0mmercury [0m [0m[0m [0m[0m[0m 131k[0m [0m[0m [0m[0m[0m $0.2 / $0.8[0m [0m[0m [0m[0m[0m1000 tok/s[0m [0m
|
||||
[0m----- stderr -----
|
||||
");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn list() {
|
||||
let context = test_context!();
|
||||
let mut cmd = context.model();
|
||||
cmd.arg("list");
|
||||
fabro_snapshot!(context.filters(), cmd, @"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
[0m [0m[0m[0m[1mMODEL [0m [0m[0m [0m[0m[0m[1mPROVIDER [0m [0m[0m [0m[0m[0m[1mALIASES [0m [0m[0m [0m[0m[0m[1mCONTEXT[0m [0m[0m [0m[0m[0m[1m COST[0m [0m[0m [0m[0m[0m[1m SPEED[0m [0m
|
||||
[0m[0m [0m[0m[0mclaude-opus-4-6 [0m [0m[0m [0m[0m[0manthropic[0m [0m[0m [0m[0m[0mopus, claude-opus [0m [0m[0m [0m[0m[0m 1m[0m [0m[0m [0m[0m[0m $15.0 / $75.0[0m [0m[0m [0m[0m[0m 25 tok/s[0m [0m
|
||||
[0m[0m [0m[0m[0mclaude-sonnet-4-5 [0m [0m[0m [0m[0m[0manthropic[0m [0m[0m [0m[0m[0m [0m [0m[0m [0m[0m[0m 200k[0m [0m[0m [0m[0m[0m $3.0 / $15.0[0m [0m[0m [0m[0m[0m 50 tok/s[0m [0m
|
||||
[0m[0m [0m[0m[0mclaude-sonnet-4-6 [0m [0m[0m [0m[0m[0manthropic[0m [0m[0m [0m[0m[0msonnet, claude-sonnet [0m [0m[0m [0m[0m[0m 200k[0m [0m[0m [0m[0m[0m $3.0 / $15.0[0m [0m[0m [0m[0m[0m 50 tok/s[0m [0m
|
||||
[0m[0m [0m[0m[0mclaude-haiku-4-5 [0m [0m[0m [0m[0m[0manthropic[0m [0m[0m [0m[0m[0mhaiku, claude-haiku [0m [0m[0m [0m[0m[0m 200k[0m [0m[0m [0m[0m[0m $0.8 / $4.0[0m [0m[0m [0m[0m[0m 100 tok/s[0m [0m
|
||||
[0m[0m [0m[0m[0mgpt-5.2 [0m [0m[0m [0m[0m[0mopenai [0m [0m[0m [0m[0m[0mgpt5 [0m [0m[0m [0m[0m[0m 1m[0m [0m[0m [0m[0m[0m $1.8 / $14.0[0m [0m[0m [0m[0m[0m 65 tok/s[0m [0m
|
||||
[0m[0m [0m[0m[0mgpt-5-mini [0m [0m[0m [0m[0m[0mopenai [0m [0m[0m [0m[0m[0mgpt5-mini [0m [0m[0m [0m[0m[0m 1m[0m [0m[0m [0m[0m[0m $0.2 / $2.0[0m [0m[0m [0m[0m[0m 70 tok/s[0m [0m
|
||||
[0m[0m [0m[0m[0mgpt-5.2-codex [0m [0m[0m [0m[0m[0mopenai [0m [0m[0m [0m[0m[0m [0m [0m[0m [0m[0m[0m 1m[0m [0m[0m [0m[0m[0m $1.8 / $14.0[0m [0m[0m [0m[0m[0m 100 tok/s[0m [0m
|
||||
[0m[0m [0m[0m[0mgpt-5.3-codex [0m [0m[0m [0m[0m[0mopenai [0m [0m[0m [0m[0m[0mcodex [0m [0m[0m [0m[0m[0m 1m[0m [0m[0m [0m[0m[0m $1.8 / $14.0[0m [0m[0m [0m[0m[0m 100 tok/s[0m [0m
|
||||
[0m[0m [0m[0m[0mgpt-5.3-codex-spark [0m [0m[0m [0m[0m[0mopenai [0m [0m[0m [0m[0m[0mcodex-spark [0m [0m[0m [0m[0m[0m 131k[0m [0m[0m [0m[0m[0m - / -[0m [0m[0m [0m[0m[0m1000 tok/s[0m [0m
|
||||
[0m[0m [0m[0m[0mgpt-5.4 [0m [0m[0m [0m[0m[0mopenai [0m [0m[0m [0m[0m[0mgpt54, gpt-54 [0m [0m[0m [0m[0m[0m 1m[0m [0m[0m [0m[0m[0m $2.5 / $15.0[0m [0m[0m [0m[0m[0m 70 tok/s[0m [0m
|
||||
[0m[0m [0m[0m[0mgpt-5.4-pro [0m [0m[0m [0m[0m[0mopenai [0m [0m[0m [0m[0m[0mgpt54-pro, gpt-54-pro [0m [0m[0m [0m[0m[0m 1m[0m [0m[0m [0m[0m[0m$30.0 / $180.0[0m [0m[0m [0m[0m[0m 20 tok/s[0m [0m
|
||||
[0m[0m [0m[0m[0mgpt-5.4-mini [0m [0m[0m [0m[0m[0mopenai [0m [0m[0m [0m[0m[0mgpt54-mini, gpt-54-mini[0m [0m[0m [0m[0m[0m 400k[0m [0m[0m [0m[0m[0m $0.8 / $4.5[0m [0m[0m [0m[0m[0m 140 tok/s[0m [0m
|
||||
[0m[0m [0m[0m[0mgemini-3.1-pro-preview [0m [0m[0m [0m[0m[0mgemini [0m [0m[0m [0m[0m[0mgemini-pro [0m [0m[0m [0m[0m[0m 1m[0m [0m[0m [0m[0m[0m $2.0 / $12.0[0m [0m[0m [0m[0m[0m 85 tok/s[0m [0m
|
||||
[0m[0m [0m[0m[0mgemini-3.1-pro-preview-customtools[0m [0m[0m [0m[0m[0mgemini [0m [0m[0m [0m[0m[0mgemini-customtools [0m [0m[0m [0m[0m[0m 1m[0m [0m[0m [0m[0m[0m $2.0 / $12.0[0m [0m[0m [0m[0m[0m 85 tok/s[0m [0m
|
||||
[0m[0m [0m[0m[0mgemini-3-flash-preview [0m [0m[0m [0m[0m[0mgemini [0m [0m[0m [0m[0m[0mgemini-flash [0m [0m[0m [0m[0m[0m 1m[0m [0m[0m [0m[0m[0m $0.5 / $3.0[0m [0m[0m [0m[0m[0m 150 tok/s[0m [0m
|
||||
[0m[0m [0m[0m[0mgemini-3.1-flash-lite-preview [0m [0m[0m [0m[0m[0mgemini [0m [0m[0m [0m[0m[0mgemini-flash-lite [0m [0m[0m [0m[0m[0m 1m[0m [0m[0m [0m[0m[0m $0.2 / $1.5[0m [0m[0m [0m[0m[0m 200 tok/s[0m [0m
|
||||
[0m[0m [0m[0m[0mkimi-k2.5 [0m [0m[0m [0m[0m[0mkimi [0m [0m[0m [0m[0m[0mkimi [0m [0m[0m [0m[0m[0m 262k[0m [0m[0m [0m[0m[0m $0.6 / $3.0[0m [0m[0m [0m[0m[0m 50 tok/s[0m [0m
|
||||
[0m[0m [0m[0m[0mglm-4.7 [0m [0m[0m [0m[0m[0mzai [0m [0m[0m [0m[0m[0mglm, glm4 [0m [0m[0m [0m[0m[0m 203k[0m [0m[0m [0m[0m[0m $0.6 / $2.2[0m [0m[0m [0m[0m[0m 100 tok/s[0m [0m
|
||||
[0m[0m [0m[0m[0mminimax-m2.5 [0m [0m[0m [0m[0m[0mminimax [0m [0m[0m [0m[0m[0mminimax [0m [0m[0m [0m[0m[0m 197k[0m [0m[0m [0m[0m[0m $0.3 / $1.2[0m [0m[0m [0m[0m[0m 45 tok/s[0m [0m
|
||||
[0m[0m [0m[0m[0mmercury-2 [0m [0m[0m [0m[0m[0minception[0m [0m[0m [0m[0m[0mmercury [0m [0m[0m [0m[0m[0m 131k[0m [0m[0m [0m[0m[0m $0.2 / $0.8[0m [0m[0m [0m[0m[0m1000 tok/s[0m [0m
|
||||
[0m----- stderr -----
|
||||
");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn list_provider() {
|
||||
let context = test_context!();
|
||||
let mut cmd = context.model();
|
||||
cmd.args(["list", "--provider", "anthropic"]);
|
||||
fabro_snapshot!(context.filters(), cmd, @"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
[0m [0m[0m[0m[1mMODEL [0m [0m[0m [0m[0m[0m[1mPROVIDER [0m [0m[0m [0m[0m[0m[1mALIASES [0m [0m[0m [0m[0m[0m[1mCONTEXT[0m [0m[0m [0m[0m[0m[1m COST[0m [0m[0m [0m[0m[0m[1m SPEED[0m [0m
|
||||
[0m[0m [0m[0m[0mclaude-opus-4-6 [0m [0m[0m [0m[0m[0manthropic[0m [0m[0m [0m[0m[0mopus, claude-opus [0m [0m[0m [0m[0m[0m 1m[0m [0m[0m [0m[0m[0m$15.0 / $75.0[0m [0m[0m [0m[0m[0m 25 tok/s[0m [0m
|
||||
[0m[0m [0m[0m[0mclaude-sonnet-4-5[0m [0m[0m [0m[0m[0manthropic[0m [0m[0m [0m[0m[0m [0m [0m[0m [0m[0m[0m 200k[0m [0m[0m [0m[0m[0m $3.0 / $15.0[0m [0m[0m [0m[0m[0m 50 tok/s[0m [0m
|
||||
[0m[0m [0m[0m[0mclaude-sonnet-4-6[0m [0m[0m [0m[0m[0manthropic[0m [0m[0m [0m[0m[0msonnet, claude-sonnet[0m [0m[0m [0m[0m[0m 200k[0m [0m[0m [0m[0m[0m $3.0 / $15.0[0m [0m[0m [0m[0m[0m 50 tok/s[0m [0m
|
||||
[0m[0m [0m[0m[0mclaude-haiku-4-5 [0m [0m[0m [0m[0m[0manthropic[0m [0m[0m [0m[0m[0mhaiku, claude-haiku [0m [0m[0m [0m[0m[0m 200k[0m [0m[0m [0m[0m[0m $0.8 / $4.0[0m [0m[0m [0m[0m[0m100 tok/s[0m [0m
|
||||
[0m----- stderr -----
|
||||
");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn list_query() {
|
||||
let context = test_context!();
|
||||
let mut cmd = context.model();
|
||||
cmd.args(["list", "--query", "opus"]);
|
||||
fabro_snapshot!(context.filters(), cmd, @"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
[0m [0m[0m[0m[1mMODEL [0m [0m[0m [0m[0m[0m[1mPROVIDER [0m [0m[0m [0m[0m[0m[1mALIASES [0m [0m[0m [0m[0m[0m[1mCONTEXT[0m [0m[0m [0m[0m[0m[1m COST[0m [0m[0m [0m[0m[0m[1m SPEED[0m [0m
|
||||
[0m[0m [0m[0m[0mclaude-opus-4-6[0m [0m[0m [0m[0m[0manthropic[0m [0m[0m [0m[0m[0mopus, claude-opus[0m [0m[0m [0m[0m[0m 1m[0m [0m[0m [0m[0m[0m$15.0 / $75.0[0m [0m[0m [0m[0m[0m25 tok/s[0m [0m
|
||||
[0m----- stderr -----
|
||||
");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn list_query_aliases() {
|
||||
let context = test_context!();
|
||||
let mut cmd = context.model();
|
||||
cmd.args(["list", "--query", "codex"]);
|
||||
fabro_snapshot!(context.filters(), cmd, @"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
[0m [0m[0m[0m[1mMODEL [0m [0m[0m [0m[0m[0m[1mPROVIDER[0m [0m[0m [0m[0m[0m[1mALIASES [0m [0m[0m [0m[0m[0m[1mCONTEXT[0m [0m[0m [0m[0m[0m[1m COST[0m [0m[0m [0m[0m[0m[1m SPEED[0m [0m
|
||||
[0m[0m [0m[0m[0mgpt-5.2-codex [0m [0m[0m [0m[0m[0mopenai [0m [0m[0m [0m[0m[0m [0m [0m[0m [0m[0m[0m 1m[0m [0m[0m [0m[0m[0m$1.8 / $14.0[0m [0m[0m [0m[0m[0m 100 tok/s[0m [0m
|
||||
[0m[0m [0m[0m[0mgpt-5.3-codex [0m [0m[0m [0m[0m[0mopenai [0m [0m[0m [0m[0m[0mcodex [0m [0m[0m [0m[0m[0m 1m[0m [0m[0m [0m[0m[0m$1.8 / $14.0[0m [0m[0m [0m[0m[0m 100 tok/s[0m [0m
|
||||
[0m[0m [0m[0m[0mgpt-5.3-codex-spark[0m [0m[0m [0m[0m[0mopenai [0m [0m[0m [0m[0m[0mcodex-spark[0m [0m[0m [0m[0m[0m 131k[0m [0m[0m [0m[0m[0m - / -[0m [0m[0m [0m[0m[0m1000 tok/s[0m [0m
|
||||
[0m----- stderr -----
|
||||
");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn list_query_case_insensitive() {
|
||||
let context = test_context!();
|
||||
let mut cmd = context.model();
|
||||
cmd.args(["list", "--query", "OPUS"]);
|
||||
fabro_snapshot!(context.filters(), cmd, @"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
[0m [0m[0m[0m[1mMODEL [0m [0m[0m [0m[0m[0m[1mPROVIDER [0m [0m[0m [0m[0m[0m[1mALIASES [0m [0m[0m [0m[0m[0m[1mCONTEXT[0m [0m[0m [0m[0m[0m[1m COST[0m [0m[0m [0m[0m[0m[1m SPEED[0m [0m
|
||||
[0m[0m [0m[0m[0mclaude-opus-4-6[0m [0m[0m [0m[0m[0manthropic[0m [0m[0m [0m[0m[0mopus, claude-opus[0m [0m[0m [0m[0m[0m 1m[0m [0m[0m [0m[0m[0m$15.0 / $75.0[0m [0m[0m [0m[0m[0m25 tok/s[0m [0m
|
||||
[0m----- stderr -----
|
||||
");
|
||||
}
|
||||
33
lib/crates/fabro-cli/tests/it/cmd/pr.rs
Normal file
33
lib/crates/fabro-cli/tests/it/cmd/pr.rs
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
use fabro_test::{fabro_snapshot, test_context};
|
||||
|
||||
#[test]
|
||||
fn help() {
|
||||
let context = test_context!();
|
||||
let mut cmd = context.pr();
|
||||
cmd.arg("--help");
|
||||
fabro_snapshot!(context.filters(), cmd, @"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
Pull request operations
|
||||
|
||||
Usage: fabro pr [OPTIONS] <COMMAND>
|
||||
|
||||
Commands:
|
||||
create Create a pull request from a completed run
|
||||
list List pull requests from workflow runs
|
||||
view View pull request details
|
||||
merge Merge a pull request
|
||||
close Close a pull request
|
||||
help Print this message or the help of the given subcommand(s)
|
||||
|
||||
Options:
|
||||
--debug Enable DEBUG-level logging (default is INFO) [env: FABRO_DEBUG=]
|
||||
--no-upgrade-check Disable automatic upgrade check [env: FABRO_NO_UPGRADE_CHECK=true]
|
||||
--quiet Suppress non-essential output [env: FABRO_QUIET=]
|
||||
--verbose Enable verbose output [env: FABRO_VERBOSE=]
|
||||
--storage-dir <STORAGE_DIR> Storage directory (default: ~/.fabro) [env: FABRO_STORAGE_DIR=[STORAGE_DIR]]
|
||||
-h, --help Print help
|
||||
----- stderr -----
|
||||
");
|
||||
}
|
||||
32
lib/crates/fabro-cli/tests/it/cmd/preview.rs
Normal file
32
lib/crates/fabro-cli/tests/it/cmd/preview.rs
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
use fabro_test::{fabro_snapshot, test_context};
|
||||
|
||||
#[test]
|
||||
fn help() {
|
||||
let context = test_context!();
|
||||
let mut cmd = context.preview();
|
||||
cmd.arg("--help");
|
||||
fabro_snapshot!(context.filters(), cmd, @"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
Get a preview URL for a port on a run's sandbox
|
||||
|
||||
Usage: fabro preview [OPTIONS] <RUN> <PORT>
|
||||
|
||||
Arguments:
|
||||
<RUN> Run ID or prefix
|
||||
<PORT> Port number
|
||||
|
||||
Options:
|
||||
--debug Enable DEBUG-level logging (default is INFO) [env: FABRO_DEBUG=]
|
||||
--signed Generate a signed URL (embeds auth token, no headers needed)
|
||||
--no-upgrade-check Disable automatic upgrade check [env: FABRO_NO_UPGRADE_CHECK=true]
|
||||
--ttl <TTL> Signed URL expiry in seconds (default 3600, requires --signed) [default: 3600]
|
||||
--open Open URL in browser (implies --signed)
|
||||
--quiet Suppress non-essential output [env: FABRO_QUIET=]
|
||||
--verbose Enable verbose output [env: FABRO_VERBOSE=]
|
||||
--storage-dir <STORAGE_DIR> Storage directory (default: ~/.fabro) [env: FABRO_STORAGE_DIR=[STORAGE_DIR]]
|
||||
-h, --help Print help
|
||||
----- stderr -----
|
||||
");
|
||||
}
|
||||
30
lib/crates/fabro-cli/tests/it/cmd/repo.rs
Normal file
30
lib/crates/fabro-cli/tests/it/cmd/repo.rs
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
use fabro_test::{fabro_snapshot, test_context};
|
||||
|
||||
#[test]
|
||||
fn help() {
|
||||
let context = test_context!();
|
||||
let mut cmd = context.repo();
|
||||
cmd.arg("--help");
|
||||
fabro_snapshot!(context.filters(), cmd, @"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
Repository commands
|
||||
|
||||
Usage: fabro repo [OPTIONS] <COMMAND>
|
||||
|
||||
Commands:
|
||||
init Initialize a new project
|
||||
deinit Remove fabro.toml and fabro/ directory
|
||||
help Print this message or the help of the given subcommand(s)
|
||||
|
||||
Options:
|
||||
--debug Enable DEBUG-level logging (default is INFO) [env: FABRO_DEBUG=]
|
||||
--no-upgrade-check Disable automatic upgrade check [env: FABRO_NO_UPGRADE_CHECK=true]
|
||||
--quiet Suppress non-essential output [env: FABRO_QUIET=]
|
||||
--verbose Enable verbose output [env: FABRO_VERBOSE=]
|
||||
--storage-dir <STORAGE_DIR> Storage directory (default: ~/.fabro) [env: FABRO_STORAGE_DIR=[STORAGE_DIR]]
|
||||
-h, --help Print help
|
||||
----- stderr -----
|
||||
");
|
||||
}
|
||||
236
lib/crates/fabro-cli/tests/it/cmd/run.rs
Normal file
236
lib/crates/fabro-cli/tests/it/cmd/run.rs
Normal file
|
|
@ -0,0 +1,236 @@
|
|||
use fabro_test::{fabro_snapshot, test_context};
|
||||
|
||||
fn fixture(name: &str) -> std::path::PathBuf {
|
||||
std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join(format!("../../../test/{name}"))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn help() {
|
||||
let context = test_context!();
|
||||
let mut cmd = context.run_cmd();
|
||||
cmd.arg("--help");
|
||||
fabro_snapshot!(context.filters(), cmd, @"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
Launch a workflow run
|
||||
|
||||
Usage: fabro run [OPTIONS] <WORKFLOW>
|
||||
|
||||
Arguments:
|
||||
<WORKFLOW> Path to a .fabro workflow file or .toml task config
|
||||
|
||||
Options:
|
||||
--debug Enable DEBUG-level logging (default is INFO) [env: FABRO_DEBUG=]
|
||||
--dry-run Execute with simulated LLM backend
|
||||
--auto-approve Auto-approve all human gates
|
||||
--no-upgrade-check Disable automatic upgrade check [env: FABRO_NO_UPGRADE_CHECK=true]
|
||||
--goal <GOAL> Override the workflow goal (exposed as $goal in prompts)
|
||||
--quiet Suppress non-essential output [env: FABRO_QUIET=]
|
||||
--goal-file <GOAL_FILE> Read the workflow goal from a file
|
||||
--model <MODEL> Override default LLM model
|
||||
--storage-dir <STORAGE_DIR> Storage directory (default: ~/.fabro) [env: FABRO_STORAGE_DIR=[STORAGE_DIR]]
|
||||
--provider <PROVIDER> Override default LLM provider
|
||||
-v, --verbose Enable verbose output
|
||||
--sandbox <SANDBOX> Sandbox for agent tools [possible values: local, docker, daytona, ssh]
|
||||
--label <KEY=VALUE> Attach a label to this run (repeatable, format: KEY=VALUE)
|
||||
--no-retro Skip retro generation after the run
|
||||
--preserve-sandbox Keep the sandbox alive after the run finishes (for debugging)
|
||||
-d, --detach Run the workflow in the background and print the run ID
|
||||
-h, --help Print help
|
||||
----- stderr -----
|
||||
");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dry_run_simple() {
|
||||
let context = test_context!();
|
||||
let mut cmd = context.run_cmd();
|
||||
cmd.args(["--dry-run", "--auto-approve"]);
|
||||
cmd.arg(fixture("simple.fabro"));
|
||||
fabro_snapshot!(context.filters(), cmd, @"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
----- stderr -----
|
||||
Workflow: Simple (4 nodes, 3 edges)
|
||||
Graph: ../../../test/simple.fabro
|
||||
Goal: Run tests and report results
|
||||
|
||||
Sandbox: local (ready in [TIME])
|
||||
✓ Start 0ms
|
||||
✓ Run Tests 0ms
|
||||
✓ Report 0ms
|
||||
✓ Exit 0ms
|
||||
|
||||
=== Run Result ===
|
||||
Run: [ULID]
|
||||
Status: SUCCESS
|
||||
Duration: 0 seconds
|
||||
Run: [STORAGE_DIR]/runs/20260330-dry-run-[ULID]
|
||||
|
||||
=== Output ===
|
||||
[Simulated] Response for stage: report
|
||||
");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dry_run_branching() {
|
||||
let context = test_context!();
|
||||
let mut cmd = context.run_cmd();
|
||||
cmd.args(["--dry-run", "--auto-approve"]);
|
||||
cmd.arg(fixture("branching.fabro"));
|
||||
fabro_snapshot!(context.filters(), cmd, @"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
----- stderr -----
|
||||
Workflow: Branch (6 nodes, 6 edges)
|
||||
Graph: ../../../test/branching.fabro
|
||||
Goal: Implement and validate a feature
|
||||
|
||||
warning [node: implement]: Node 'implement' has goal_gate=true but no retry_target or fallback_retry_target (goal_gate_has_retry)
|
||||
Sandbox: local (ready in [TIME])
|
||||
✓ Start 0ms
|
||||
✓ Plan 0ms
|
||||
✓ Implement 0ms
|
||||
✓ Validate 0ms
|
||||
✓ Tests passing? 0ms
|
||||
✓ Exit 0ms
|
||||
|
||||
=== Run Result ===
|
||||
Run: [ULID]
|
||||
Status: SUCCESS
|
||||
Duration: 0 seconds
|
||||
Run: [STORAGE_DIR]/runs/20260330-dry-run-[ULID]
|
||||
|
||||
=== Output ===
|
||||
[Simulated] Response for stage: validate
|
||||
");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dry_run_conditions() {
|
||||
let context = test_context!();
|
||||
let mut cmd = context.run_cmd();
|
||||
cmd.args(["--dry-run", "--auto-approve"]);
|
||||
cmd.arg(fixture("conditions.fabro"));
|
||||
fabro_snapshot!(context.filters(), cmd, @"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
----- stderr -----
|
||||
Workflow: Conditions (5 nodes, 5 edges)
|
||||
Graph: ../../../test/conditions.fabro
|
||||
Goal: Test condition evaluation with OR and parentheses
|
||||
|
||||
Sandbox: local (ready in [TIME])
|
||||
✓ start 0ms
|
||||
✓ Decide 0ms
|
||||
✓ Path B 0ms
|
||||
✓ exit 0ms
|
||||
|
||||
=== Run Result ===
|
||||
Run: [ULID]
|
||||
Status: SUCCESS
|
||||
Duration: 0 seconds
|
||||
Run: [STORAGE_DIR]/runs/20260330-dry-run-[ULID]
|
||||
|
||||
=== Output ===
|
||||
[Simulated] Response for stage: path_b
|
||||
");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dry_run_parallel() {
|
||||
let context = test_context!();
|
||||
let mut cmd = context.run_cmd();
|
||||
cmd.args(["--dry-run", "--auto-approve"]);
|
||||
cmd.arg(fixture("parallel.fabro"));
|
||||
fabro_snapshot!(context.filters(), cmd, @"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
----- stderr -----
|
||||
Workflow: Parallel (7 nodes, 7 edges)
|
||||
Graph: ../../../test/parallel.fabro
|
||||
Goal: Test parallel and fan-in execution
|
||||
|
||||
Sandbox: local (ready in [TIME])
|
||||
✓ start 0ms
|
||||
✓ Fork Work 0ms
|
||||
✓ Merge Results 0ms
|
||||
✓ Review 0ms
|
||||
✓ exit 0ms
|
||||
|
||||
=== Run Result ===
|
||||
Run: [ULID]
|
||||
Status: SUCCESS
|
||||
Duration: 0 seconds
|
||||
Run: [STORAGE_DIR]/runs/20260330-dry-run-[ULID]
|
||||
|
||||
=== Output ===
|
||||
[Simulated] Response for stage: review
|
||||
");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dry_run_styled() {
|
||||
let context = test_context!();
|
||||
let mut cmd = context.run_cmd();
|
||||
cmd.args(["--dry-run", "--auto-approve"]);
|
||||
cmd.arg(fixture("styled.fabro"));
|
||||
fabro_snapshot!(context.filters(), cmd, @"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
----- stderr -----
|
||||
Workflow: Styled (5 nodes, 4 edges)
|
||||
Graph: ../../../test/styled.fabro
|
||||
Goal: Build a styled pipeline
|
||||
|
||||
Sandbox: local (ready in [TIME])
|
||||
✓ start 0ms
|
||||
✓ Plan 0ms
|
||||
✓ Implement 0ms
|
||||
✓ Critical Review 0ms
|
||||
✓ exit 0ms
|
||||
|
||||
=== Run Result ===
|
||||
Run: [ULID]
|
||||
Status: SUCCESS
|
||||
Duration: 0 seconds
|
||||
Run: [STORAGE_DIR]/runs/20260330-dry-run-[ULID]
|
||||
|
||||
=== Output ===
|
||||
[Simulated] Response for stage: critical_review
|
||||
");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dry_run_legacy_tool() {
|
||||
let context = test_context!();
|
||||
let mut cmd = context.run_cmd();
|
||||
cmd.args(["--dry-run", "--auto-approve"]);
|
||||
cmd.arg(fixture("legacy_tool.fabro"));
|
||||
fabro_snapshot!(context.filters(), cmd, @"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
----- stderr -----
|
||||
Workflow: LegacyTool (3 nodes, 2 edges)
|
||||
Graph: ../../../test/legacy_tool.fabro
|
||||
Goal: Verify backwards compatibility with old tool naming
|
||||
|
||||
Sandbox: local (ready in [TIME])
|
||||
✓ Start 0ms
|
||||
✓ Echo 0ms
|
||||
✓ Exit 0ms
|
||||
|
||||
=== Run Result ===
|
||||
Run: [ULID]
|
||||
Status: SUCCESS
|
||||
Duration: 0 seconds
|
||||
Run: [STORAGE_DIR]/runs/20260330-dry-run-[ULID]
|
||||
");
|
||||
}
|
||||
32
lib/crates/fabro-cli/tests/it/cmd/secret.rs
Normal file
32
lib/crates/fabro-cli/tests/it/cmd/secret.rs
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
use fabro_test::{fabro_snapshot, test_context};
|
||||
|
||||
#[test]
|
||||
fn help() {
|
||||
let context = test_context!();
|
||||
let mut cmd = context.secret();
|
||||
cmd.arg("--help");
|
||||
fabro_snapshot!(context.filters(), cmd, @"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
Manage secrets in ~/.fabro/.env
|
||||
|
||||
Usage: fabro secret [OPTIONS] <COMMAND>
|
||||
|
||||
Commands:
|
||||
get Get a secret value
|
||||
list List secret names
|
||||
rm Remove a secret
|
||||
set Set a secret value
|
||||
help Print this message or the help of the given subcommand(s)
|
||||
|
||||
Options:
|
||||
--debug Enable DEBUG-level logging (default is INFO) [env: FABRO_DEBUG=]
|
||||
--no-upgrade-check Disable automatic upgrade check [env: FABRO_NO_UPGRADE_CHECK=true]
|
||||
--quiet Suppress non-essential output [env: FABRO_QUIET=]
|
||||
--verbose Enable verbose output [env: FABRO_VERBOSE=]
|
||||
--storage-dir <STORAGE_DIR> Storage directory (default: ~/.fabro) [env: FABRO_STORAGE_DIR=[STORAGE_DIR]]
|
||||
-h, --help Print help
|
||||
----- stderr -----
|
||||
");
|
||||
}
|
||||
30
lib/crates/fabro-cli/tests/it/cmd/ssh.rs
Normal file
30
lib/crates/fabro-cli/tests/it/cmd/ssh.rs
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
use fabro_test::{fabro_snapshot, test_context};
|
||||
|
||||
#[test]
|
||||
fn help() {
|
||||
let context = test_context!();
|
||||
let mut cmd = context.ssh();
|
||||
cmd.arg("--help");
|
||||
fabro_snapshot!(context.filters(), cmd, @"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
SSH into a run's Daytona sandbox
|
||||
|
||||
Usage: fabro ssh [OPTIONS] <RUN>
|
||||
|
||||
Arguments:
|
||||
<RUN> Run ID or prefix
|
||||
|
||||
Options:
|
||||
--debug Enable DEBUG-level logging (default is INFO) [env: FABRO_DEBUG=]
|
||||
--ttl <TTL> SSH access expiry in minutes (default 60) [default: 60]
|
||||
--no-upgrade-check Disable automatic upgrade check [env: FABRO_NO_UPGRADE_CHECK=true]
|
||||
--print Print the SSH command instead of connecting
|
||||
--quiet Suppress non-essential output [env: FABRO_QUIET=]
|
||||
--verbose Enable verbose output [env: FABRO_VERBOSE=]
|
||||
--storage-dir <STORAGE_DIR> Storage directory (default: ~/.fabro) [env: FABRO_STORAGE_DIR=[STORAGE_DIR]]
|
||||
-h, --help Print help
|
||||
----- stderr -----
|
||||
");
|
||||
}
|
||||
30
lib/crates/fabro-cli/tests/it/cmd/system.rs
Normal file
30
lib/crates/fabro-cli/tests/it/cmd/system.rs
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
use fabro_test::{fabro_snapshot, test_context};
|
||||
|
||||
#[test]
|
||||
fn help() {
|
||||
let context = test_context!();
|
||||
let mut cmd = context.system();
|
||||
cmd.arg("--help");
|
||||
fabro_snapshot!(context.filters(), cmd, @"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
System maintenance commands
|
||||
|
||||
Usage: fabro system [OPTIONS] <COMMAND>
|
||||
|
||||
Commands:
|
||||
prune Delete old workflow runs
|
||||
df Show disk usage
|
||||
help Print this message or the help of the given subcommand(s)
|
||||
|
||||
Options:
|
||||
--debug Enable DEBUG-level logging (default is INFO) [env: FABRO_DEBUG=]
|
||||
--no-upgrade-check Disable automatic upgrade check [env: FABRO_NO_UPGRADE_CHECK=true]
|
||||
--quiet Suppress non-essential output [env: FABRO_QUIET=]
|
||||
--verbose Enable verbose output [env: FABRO_VERBOSE=]
|
||||
--storage-dir <STORAGE_DIR> Storage directory (default: ~/.fabro) [env: FABRO_STORAGE_DIR=[STORAGE_DIR]]
|
||||
-h, --help Print help
|
||||
----- stderr -----
|
||||
");
|
||||
}
|
||||
33
lib/crates/fabro-cli/tests/it/cmd/top_level.rs
Normal file
33
lib/crates/fabro-cli/tests/it/cmd/top_level.rs
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
use fabro_test::{fabro_snapshot, test_context};
|
||||
|
||||
#[test]
|
||||
fn version() {
|
||||
let context = test_context!();
|
||||
let mut cmd = context.command();
|
||||
cmd.arg("--version");
|
||||
fabro_snapshot!(context.filters(), cmd, @"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
fabro [VERSION] (c6ff6c9 2026-03-30)
|
||||
----- stderr -----
|
||||
");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn no_dotenv_flag() {
|
||||
let context = test_context!();
|
||||
let mut cmd = context.command();
|
||||
cmd.args(["--no-dotenv", "doctor"]);
|
||||
fabro_snapshot!(context.filters(), cmd, @"
|
||||
success: false
|
||||
exit_code: 2
|
||||
----- stdout -----
|
||||
----- stderr -----
|
||||
error: unexpected argument '--no-dotenv' found
|
||||
|
||||
Usage: fabro [OPTIONS] <COMMAND>
|
||||
|
||||
For more information, try '--help'.
|
||||
");
|
||||
}
|
||||
147
lib/crates/fabro-cli/tests/it/cmd/validate.rs
Normal file
147
lib/crates/fabro-cli/tests/it/cmd/validate.rs
Normal file
|
|
@ -0,0 +1,147 @@
|
|||
use fabro_test::{fabro_snapshot, test_context};
|
||||
|
||||
fn fixture(name: &str) -> std::path::PathBuf {
|
||||
std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join(format!("../../../test/{name}"))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn help() {
|
||||
let context = test_context!();
|
||||
let mut cmd = context.validate();
|
||||
cmd.arg("--help");
|
||||
fabro_snapshot!(context.filters(), cmd, @"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
Validate a workflow
|
||||
|
||||
Usage: fabro validate [OPTIONS] <WORKFLOW>
|
||||
|
||||
Arguments:
|
||||
<WORKFLOW> Path to the .fabro workflow file
|
||||
|
||||
Options:
|
||||
--debug Enable DEBUG-level logging (default is INFO) [env: FABRO_DEBUG=]
|
||||
--no-upgrade-check Disable automatic upgrade check [env: FABRO_NO_UPGRADE_CHECK=true]
|
||||
--quiet Suppress non-essential output [env: FABRO_QUIET=]
|
||||
--verbose Enable verbose output [env: FABRO_VERBOSE=]
|
||||
--storage-dir <STORAGE_DIR> Storage directory (default: ~/.fabro) [env: FABRO_STORAGE_DIR=[STORAGE_DIR]]
|
||||
-h, --help Print help
|
||||
----- stderr -----
|
||||
");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn simple() {
|
||||
let context = test_context!();
|
||||
let mut cmd = context.validate();
|
||||
cmd.arg(fixture("simple.fabro"));
|
||||
fabro_snapshot!(context.filters(), cmd, @"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
----- stderr -----
|
||||
Workflow: Simple (4 nodes, 3 edges)
|
||||
Graph: ../../../test/simple.fabro
|
||||
Validation: OK
|
||||
");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn branching() {
|
||||
let context = test_context!();
|
||||
let mut cmd = context.validate();
|
||||
cmd.arg(fixture("branching.fabro"));
|
||||
fabro_snapshot!(context.filters(), cmd, @"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
----- stderr -----
|
||||
Workflow: Branch (6 nodes, 6 edges)
|
||||
Graph: ../../../test/branching.fabro
|
||||
warning [node: implement]: Node 'implement' has goal_gate=true but no retry_target or fallback_retry_target (goal_gate_has_retry)
|
||||
Validation: OK
|
||||
");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn conditions() {
|
||||
let context = test_context!();
|
||||
let mut cmd = context.validate();
|
||||
cmd.arg(fixture("conditions.fabro"));
|
||||
fabro_snapshot!(context.filters(), cmd, @"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
----- stderr -----
|
||||
Workflow: Conditions (5 nodes, 5 edges)
|
||||
Graph: ../../../test/conditions.fabro
|
||||
Validation: OK
|
||||
");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parallel() {
|
||||
let context = test_context!();
|
||||
let mut cmd = context.validate();
|
||||
cmd.arg(fixture("parallel.fabro"));
|
||||
fabro_snapshot!(context.filters(), cmd, @"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
----- stderr -----
|
||||
Workflow: Parallel (7 nodes, 7 edges)
|
||||
Graph: ../../../test/parallel.fabro
|
||||
Validation: OK
|
||||
");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn styled() {
|
||||
let context = test_context!();
|
||||
let mut cmd = context.validate();
|
||||
cmd.arg(fixture("styled.fabro"));
|
||||
fabro_snapshot!(context.filters(), cmd, @"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
----- stderr -----
|
||||
Workflow: Styled (5 nodes, 4 edges)
|
||||
Graph: ../../../test/styled.fabro
|
||||
Validation: OK
|
||||
");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn legacy_tool() {
|
||||
let context = test_context!();
|
||||
let mut cmd = context.validate();
|
||||
cmd.arg(fixture("legacy_tool.fabro"));
|
||||
fabro_snapshot!(context.filters(), cmd, @"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
----- stderr -----
|
||||
Workflow: LegacyTool (3 nodes, 2 edges)
|
||||
Graph: ../../../test/legacy_tool.fabro
|
||||
Validation: OK
|
||||
");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn invalid() {
|
||||
let context = test_context!();
|
||||
let mut cmd = context.validate();
|
||||
cmd.arg(fixture("invalid.fabro"));
|
||||
fabro_snapshot!(context.filters(), cmd, @"
|
||||
success: false
|
||||
exit_code: 1
|
||||
----- stdout -----
|
||||
----- stderr -----
|
||||
Workflow: Invalid (2 nodes, 1 edges)
|
||||
Graph: ../../../test/invalid.fabro
|
||||
error: Pipeline must have exactly one start node (shape=Mdiamond or id start/Start) (start_node)
|
||||
error [node: exit]: Exit node 'exit' has 1 outgoing edge(s) but must have none (exit_no_outgoing)
|
||||
error: Validation failed
|
||||
");
|
||||
}
|
||||
|
|
@ -1,3 +1,3 @@
|
|||
mod cli;
|
||||
mod cmd;
|
||||
mod scenario;
|
||||
mod trycmd;
|
||||
|
|
|
|||
|
|
@ -1,30 +0,0 @@
|
|||
macro_rules! trycmd_subcommand {
|
||||
($name:ident, $dir:expr) => {
|
||||
#[test]
|
||||
fn $name() {
|
||||
let home = tempfile::tempdir().unwrap();
|
||||
let home = home.path().display().to_string();
|
||||
trycmd::TestCases::new()
|
||||
.env("HOME", home)
|
||||
.case(concat!("tests/cmd/", $dir, "/*.trycmd"))
|
||||
.case(concat!("tests/cmd/", $dir, "/*.toml"));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
trycmd_subcommand!(cli_cp, "cp");
|
||||
trycmd_subcommand!(cli_config, "config");
|
||||
trycmd_subcommand!(cli_doctor, "doctor");
|
||||
trycmd_subcommand!(cli_exec, "exec");
|
||||
trycmd_subcommand!(cli_init, "init");
|
||||
trycmd_subcommand!(cli_install, "install");
|
||||
trycmd_subcommand!(cli_llm, "llm");
|
||||
trycmd_subcommand!(cli_model, "model");
|
||||
trycmd_subcommand!(cli_pr, "pr");
|
||||
trycmd_subcommand!(cli_repo, "repo");
|
||||
trycmd_subcommand!(cli_preview, "preview");
|
||||
trycmd_subcommand!(cli_run, "run");
|
||||
trycmd_subcommand!(cli_ssh, "ssh");
|
||||
trycmd_subcommand!(cli_system, "system");
|
||||
trycmd_subcommand!(cli_top_level, "top-level");
|
||||
trycmd_subcommand!(cli_validate, "validate");
|
||||
19
lib/crates/fabro-test/Cargo.toml
Normal file
19
lib/crates/fabro-test/Cargo.toml
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
[package]
|
||||
name = "fabro-test"
|
||||
edition.workspace = true
|
||||
version.workspace = true
|
||||
publish = false
|
||||
license.workspace = true
|
||||
description = "Test utilities for fabro integration tests"
|
||||
|
||||
[lib]
|
||||
doctest = false
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[dependencies]
|
||||
assert_cmd = "2"
|
||||
insta = { workspace = true, features = ["filters"] }
|
||||
regex = { workspace = true }
|
||||
tempfile = "3"
|
||||
290
lib/crates/fabro-test/src/lib.rs
Normal file
290
lib/crates/fabro-test/src/lib.rs
Normal file
|
|
@ -0,0 +1,290 @@
|
|||
use std::path::PathBuf;
|
||||
use std::process::Output;
|
||||
|
||||
use assert_cmd::Command;
|
||||
use regex::Regex;
|
||||
|
||||
/// Static filters applied to every snapshot.
|
||||
static INSTA_FILTERS: &[(&str, &str)] = &[
|
||||
(r"fabro \d+\.\d+\.\d+", "fabro [VERSION]"),
|
||||
(r"\b[0-9A-HJKMNP-TV-Z]{26}\b", "[ULID]"),
|
||||
(r"in \d+(\.\d+)?(ms|s)", "in [TIME]"),
|
||||
(r"\\([\w\d])", "/$1"),
|
||||
];
|
||||
|
||||
/// An isolated test context for running fabro CLI commands.
|
||||
///
|
||||
/// Creates temporary directories for home, storage, and working directory,
|
||||
/// and provides methods to build commands with proper isolation env vars.
|
||||
pub struct TestContext {
|
||||
pub temp_dir: PathBuf,
|
||||
pub home_dir: PathBuf,
|
||||
pub storage_dir: PathBuf,
|
||||
fabro_bin: PathBuf,
|
||||
filters: Vec<(String, String)>,
|
||||
_root: tempfile::TempDir,
|
||||
}
|
||||
|
||||
impl TestContext {
|
||||
/// Create a new isolated test context.
|
||||
///
|
||||
/// `fabro_bin` should be the path to the compiled `fabro` binary,
|
||||
/// typically obtained via `env!("CARGO_BIN_EXE_fabro")`.
|
||||
pub fn new(fabro_bin: PathBuf) -> Self {
|
||||
let root = tempfile::tempdir().expect("failed to create temp dir");
|
||||
let root_path = root.path().to_path_buf();
|
||||
|
||||
let temp_dir = root_path.join("temp");
|
||||
let home_dir = root_path.join("home");
|
||||
let storage_dir = root_path.join("storage");
|
||||
|
||||
std::fs::create_dir_all(&temp_dir).expect("failed to create temp_dir");
|
||||
std::fs::create_dir_all(&home_dir).expect("failed to create home_dir");
|
||||
std::fs::create_dir_all(&storage_dir).expect("failed to create storage_dir");
|
||||
|
||||
let filters = vec![
|
||||
(
|
||||
regex::escape(temp_dir.to_str().unwrap()),
|
||||
"[TEMP_DIR]".to_string(),
|
||||
),
|
||||
(
|
||||
regex::escape(home_dir.to_str().unwrap()),
|
||||
"[HOME_DIR]".to_string(),
|
||||
),
|
||||
(
|
||||
regex::escape(storage_dir.to_str().unwrap()),
|
||||
"[STORAGE_DIR]".to_string(),
|
||||
),
|
||||
];
|
||||
|
||||
Self {
|
||||
temp_dir,
|
||||
home_dir,
|
||||
storage_dir,
|
||||
fabro_bin,
|
||||
filters,
|
||||
_root: root,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the combined static + context-specific filters.
|
||||
pub fn filters(&self) -> Vec<(String, String)> {
|
||||
let mut filters: Vec<(String, String)> = INSTA_FILTERS
|
||||
.iter()
|
||||
.map(|(pat, rep)| ((*pat).to_string(), (*rep).to_string()))
|
||||
.collect();
|
||||
filters.extend(self.filters.clone());
|
||||
filters
|
||||
}
|
||||
|
||||
/// Build a base `Command` with all isolation env vars set.
|
||||
pub fn command(&self) -> Command {
|
||||
let mut cmd = Command::new(&self.fabro_bin);
|
||||
cmd.env("NO_COLOR", "1");
|
||||
cmd.env("HOME", &self.home_dir);
|
||||
cmd.env("FABRO_NO_UPGRADE_CHECK", "true");
|
||||
cmd.env("FABRO_STORAGE_DIR", &self.storage_dir);
|
||||
cmd
|
||||
}
|
||||
|
||||
/// Build a `validate` subcommand.
|
||||
pub fn validate(&self) -> Command {
|
||||
let mut cmd = self.command();
|
||||
cmd.arg("validate");
|
||||
cmd
|
||||
}
|
||||
|
||||
/// Build a `run` subcommand.
|
||||
pub fn run_cmd(&self) -> Command {
|
||||
let mut cmd = self.command();
|
||||
cmd.arg("run");
|
||||
cmd
|
||||
}
|
||||
|
||||
/// Build a `ps` subcommand.
|
||||
pub fn ps(&self) -> Command {
|
||||
let mut cmd = self.command();
|
||||
cmd.arg("ps");
|
||||
cmd
|
||||
}
|
||||
|
||||
/// Build a `model` subcommand.
|
||||
pub fn model(&self) -> Command {
|
||||
let mut cmd = self.command();
|
||||
cmd.arg("model");
|
||||
cmd
|
||||
}
|
||||
|
||||
/// Build a `secret` subcommand.
|
||||
pub fn secret(&self) -> Command {
|
||||
let mut cmd = self.command();
|
||||
cmd.arg("secret");
|
||||
cmd
|
||||
}
|
||||
|
||||
/// Build a `doctor` subcommand.
|
||||
pub fn doctor(&self) -> Command {
|
||||
let mut cmd = self.command();
|
||||
cmd.arg("doctor");
|
||||
cmd
|
||||
}
|
||||
|
||||
/// Build a `exec` subcommand.
|
||||
pub fn exec_cmd(&self) -> Command {
|
||||
let mut cmd = self.command();
|
||||
cmd.arg("exec");
|
||||
cmd
|
||||
}
|
||||
|
||||
/// Build a `llm` subcommand.
|
||||
pub fn llm(&self) -> Command {
|
||||
let mut cmd = self.command();
|
||||
cmd.arg("llm");
|
||||
cmd
|
||||
}
|
||||
|
||||
/// Build a `config` subcommand.
|
||||
pub fn config(&self) -> Command {
|
||||
let mut cmd = self.command();
|
||||
cmd.arg("config");
|
||||
cmd
|
||||
}
|
||||
|
||||
/// Build a `cp` subcommand.
|
||||
pub fn cp(&self) -> Command {
|
||||
let mut cmd = self.command();
|
||||
cmd.arg("cp");
|
||||
cmd
|
||||
}
|
||||
|
||||
/// Build an `init` subcommand.
|
||||
pub fn init_cmd(&self) -> Command {
|
||||
let mut cmd = self.command();
|
||||
cmd.arg("init");
|
||||
cmd
|
||||
}
|
||||
|
||||
/// Build an `install` subcommand.
|
||||
pub fn install(&self) -> Command {
|
||||
let mut cmd = self.command();
|
||||
cmd.arg("install");
|
||||
cmd
|
||||
}
|
||||
|
||||
/// Build a `pr` subcommand.
|
||||
pub fn pr(&self) -> Command {
|
||||
let mut cmd = self.command();
|
||||
cmd.arg("pr");
|
||||
cmd
|
||||
}
|
||||
|
||||
/// Build a `preview` subcommand.
|
||||
pub fn preview(&self) -> Command {
|
||||
let mut cmd = self.command();
|
||||
cmd.arg("preview");
|
||||
cmd
|
||||
}
|
||||
|
||||
/// Build a `repo` subcommand.
|
||||
pub fn repo(&self) -> Command {
|
||||
let mut cmd = self.command();
|
||||
cmd.arg("repo");
|
||||
cmd
|
||||
}
|
||||
|
||||
/// Build an `ssh` subcommand.
|
||||
pub fn ssh(&self) -> Command {
|
||||
let mut cmd = self.command();
|
||||
cmd.arg("ssh");
|
||||
cmd
|
||||
}
|
||||
|
||||
/// Build a `system` subcommand.
|
||||
pub fn system(&self) -> Command {
|
||||
let mut cmd = self.command();
|
||||
cmd.arg("system");
|
||||
cmd
|
||||
}
|
||||
}
|
||||
|
||||
/// Execute a command and format the output for snapshot testing.
|
||||
///
|
||||
/// Returns the formatted string and the raw `Output`.
|
||||
/// Prints unfiltered output to stderr for debugging failed tests.
|
||||
pub fn run_and_format(cmd: &mut Command, filters: &[(String, String)]) -> (String, Output) {
|
||||
let output = cmd.output().expect("failed to execute command");
|
||||
|
||||
let stdout = String::from_utf8_lossy(&output.stdout).to_string();
|
||||
let stderr = String::from_utf8_lossy(&output.stderr).to_string();
|
||||
|
||||
// Print unfiltered output for debugging
|
||||
#[allow(clippy::print_stderr)]
|
||||
{
|
||||
eprint!("{stdout}");
|
||||
eprint!("{stderr}");
|
||||
}
|
||||
|
||||
let filtered_stdout = apply_filters(&stdout, filters);
|
||||
let filtered_stderr = apply_filters(&stderr, filters);
|
||||
|
||||
let formatted = format!(
|
||||
"success: {success}\nexit_code: {code}\n----- stdout -----\n{stdout}----- stderr -----\n{stderr}",
|
||||
success = output.status.success(),
|
||||
code = output.status.code().unwrap_or(-1),
|
||||
stdout = filtered_stdout,
|
||||
stderr = filtered_stderr,
|
||||
);
|
||||
|
||||
(formatted, output)
|
||||
}
|
||||
|
||||
/// Apply regex-based filters to a snapshot string.
|
||||
pub fn apply_filters(snapshot: &str, filters: &[(String, String)]) -> String {
|
||||
let mut result = snapshot.to_string();
|
||||
for (pattern, replacement) in filters {
|
||||
if let Ok(re) = Regex::new(pattern) {
|
||||
result = re.replace_all(&result, replacement.as_str()).to_string();
|
||||
}
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
/// Create a `TestContext` using the `fabro` binary built by cargo.
|
||||
#[macro_export]
|
||||
macro_rules! test_context {
|
||||
() => {
|
||||
$crate::TestContext::new(std::path::PathBuf::from(env!("CARGO_BIN_EXE_fabro")))
|
||||
};
|
||||
}
|
||||
|
||||
/// Snapshot test macro that runs a command and compares output using insta.
|
||||
///
|
||||
/// Usage:
|
||||
/// ```ignore
|
||||
/// fabro_snapshot!(context.filters(), context.validate().arg("--help"), @"...");
|
||||
/// ```
|
||||
#[macro_export]
|
||||
macro_rules! fabro_snapshot {
|
||||
($spawnable:expr, @$snapshot:literal) => {{
|
||||
let filters: Vec<(String, String)> = $crate::TestContext::default_filters();
|
||||
let mut cmd = $spawnable;
|
||||
let (snapshot, _output) = $crate::run_and_format(&mut cmd, &filters);
|
||||
insta::assert_snapshot!(snapshot, @$snapshot);
|
||||
}};
|
||||
($filters:expr, $spawnable:expr, @$snapshot:literal) => {{
|
||||
let filters: Vec<(String, String)> = $filters;
|
||||
let mut cmd = $spawnable;
|
||||
let (snapshot, _output) = $crate::run_and_format(&mut cmd, &filters);
|
||||
insta::assert_snapshot!(snapshot, @$snapshot);
|
||||
}};
|
||||
}
|
||||
|
||||
impl TestContext {
|
||||
/// Returns just the static default filters (no context-specific paths).
|
||||
pub fn default_filters() -> Vec<(String, String)> {
|
||||
INSTA_FILTERS
|
||||
.iter()
|
||||
.map(|(pat, rep)| ((*pat).to_string(), (*rep).to_string()))
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue