From 8bdd94fdecd33fee883458ff943ab0fc399259f8 Mon Sep 17 00:00:00 2001 From: replydev Date: Mon, 14 Jul 2025 18:40:24 +0200 Subject: [PATCH 1/6] build: add assert_cmd as a dev dependency --- Cargo.lock | 72 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ Cargo.toml | 3 +++ 2 files changed, 75 insertions(+) diff --git a/Cargo.lock b/Cargo.lock index 534f79e..d7bb8be 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -129,6 +129,22 @@ version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" +[[package]] +name = "assert_cmd" +version = "2.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bd389a4b2970a01282ee455294913c0a43724daedcd1a24c3eb0ec1c1320b66" +dependencies = [ + "anstyle", + "bstr", + "doc-comment", + "libc", + "predicates", + "predicates-core", + "predicates-tree", + "wait-timeout", +] + [[package]] name = "autocfg" version = "1.4.0" @@ -213,6 +229,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "234113d19d0d7d613b40e86fb654acf958910802bcceab913a4f9e7cda03b1a4" dependencies = [ "memchr", + "regex-automata", "serde", ] @@ -427,6 +444,7 @@ name = "cotp" version = "1.9.6" dependencies = [ "aes-gcm", + "assert_cmd", "base64 0.22.1", "chacha20poly1305", "clap", @@ -630,6 +648,12 @@ dependencies = [ "syn", ] +[[package]] +name = "difflib" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6184e33543162437515c2e2b48714794e37845ec9851711914eec9d308f6ebe8" + [[package]] name = "digest" version = "0.10.7" @@ -682,6 +706,12 @@ dependencies = [ "libloading", ] +[[package]] +name = "doc-comment" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" + [[package]] name = "document-features" version = "0.2.11" @@ -1409,6 +1439,33 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" +[[package]] +name = "predicates" +version = "3.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5d19ee57562043d37e82899fade9a22ebab7be9cef5026b07fda9cdd4293573" +dependencies = [ + "anstyle", + "difflib", + "predicates-core", +] + +[[package]] +name = "predicates-core" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "727e462b119fe9c93fd0eb1429a5f7647394014cf3c04ab2c0350eeb09095ffa" + +[[package]] +name = "predicates-tree" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72dd2d6d381dfb73a193c7fca536518d7caee39fc8503f74e7dc0be0531b425c" +dependencies = [ + "predicates-core", + "termtree", +] + [[package]] name = "proc-macro2" version = "1.0.95" @@ -1814,6 +1871,12 @@ dependencies = [ "syn", ] +[[package]] +name = "termtree" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f50febec83f5ee1df3015341d8bd429f2d1cc62bcba7ea2076759d315084683" + [[package]] name = "thiserror" version = "2.0.12" @@ -2008,6 +2071,15 @@ version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" +[[package]] +name = "wait-timeout" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ac3b126d3914f9849036f826e054cbabdc8519970b8998ddaf3b5bd3c65f11" +dependencies = [ + "libc", +] + [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" diff --git a/Cargo.toml b/Cargo.toml index 0119a20..32ddab7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -55,3 +55,6 @@ color-eyre = "0.6.5" enum_dispatch = "0.3.13" derive_builder = "0.20.1" globset = "0.4.16" + +[dev-dependencies] +assert_cmd = "2.0.17" From 689c7e23929f35305671094ad6fdfd5a1560e97f Mon Sep 17 00:00:00 2001 From: replydev Date: Mon, 14 Jul 2025 18:56:01 +0200 Subject: [PATCH 2/6] test: add first integration test --- Cargo.lock | 31 +++++++++++++++++++++++++++++++ Cargo.toml | 1 + tests/cli_integration_tests.rs | 16 ++++++++++++++++ 3 files changed, 48 insertions(+) create mode 100644 tests/cli_integration_tests.rs diff --git a/Cargo.lock b/Cargo.lock index d7bb8be..e7ce508 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -460,6 +460,7 @@ dependencies = [ "hex", "hmac", "md-5", + "predicates", "qrcode", "ratatui", "rpassword", @@ -771,6 +772,15 @@ dependencies = [ "once_cell", ] +[[package]] +name = "float-cmp" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b09cf3155332e944990140d967ff5eceb70df778b34f77d8075db46e4704e6d8" +dependencies = [ + "num-traits", +] + [[package]] name = "fnv" version = "1.0.7" @@ -1247,6 +1257,12 @@ dependencies = [ "memoffset", ] +[[package]] +name = "normalize-line-endings" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61807f77802ff30975e01f4f071c8ba10c022052f98b3294119f3e615d13e5be" + [[package]] name = "num-conv" version = "0.1.0" @@ -1447,7 +1463,10 @@ checksum = "a5d19ee57562043d37e82899fade9a22ebab7be9cef5026b07fda9cdd4293573" dependencies = [ "anstyle", "difflib", + "float-cmp", + "normalize-line-endings", "predicates-core", + "regex", ] [[package]] @@ -1550,6 +1569,18 @@ dependencies = [ "thiserror", ] +[[package]] +name = "regex" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + [[package]] name = "regex-automata" version = "0.4.9" diff --git a/Cargo.toml b/Cargo.toml index 32ddab7..4397672 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -58,3 +58,4 @@ globset = "0.4.16" [dev-dependencies] assert_cmd = "2.0.17" +predicates = "3.1.3" diff --git a/tests/cli_integration_tests.rs b/tests/cli_integration_tests.rs new file mode 100644 index 0000000..04bc29f --- /dev/null +++ b/tests/cli_integration_tests.rs @@ -0,0 +1,16 @@ +use assert_cmd::Command; +use predicates::str::is_match; + +#[test] +fn test_version_subcommand() { + // Arrange / Act + let assertion = Command::cargo_bin("cotp") + .unwrap() + .arg("--version") + .assert(); + + // Assert + assertion + .success() + .stdout(is_match("^cotp \\d+\\.\\d+\\.\\d+-DEBUG-.+").unwrap()); +} From ea2b91f72d9aec2e5b6e475ebe5a0c90dffe0563 Mon Sep 17 00:00:00 2001 From: replydev Date: Mon, 14 Jul 2025 19:03:13 +0200 Subject: [PATCH 3/6] test: add help integration test --- tests/cli_integration_tests.rs | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/tests/cli_integration_tests.rs b/tests/cli_integration_tests.rs index 04bc29f..dfcbaf6 100644 --- a/tests/cli_integration_tests.rs +++ b/tests/cli_integration_tests.rs @@ -1,5 +1,5 @@ use assert_cmd::Command; -use predicates::str::is_match; +use predicates::str::{is_empty, is_match, starts_with}; #[test] fn test_version_subcommand() { @@ -12,5 +12,22 @@ fn test_version_subcommand() { // Assert assertion .success() - .stdout(is_match("^cotp \\d+\\.\\d+\\.\\d+-DEBUG-.+").unwrap()); + .stdout(is_match("^cotp \\d+\\.\\d+\\.\\d+-DEBUG-.+").unwrap()) + .stderr(is_empty()); +} + +#[test] +fn test_help_subcommand() { + // Arrange / Act + let assertion = Command::cargo_bin("cotp").unwrap().arg("--help").assert(); + + // Assert + assertion + .success() + .stdout(starts_with( + "Trustworthy, encrypted, command-line TOTP/HOTP authenticator app with import functionality. + +Usage: cotp [OPTIONS] [COMMAND]", + )) + .stderr(is_empty()); } From 91107c8cc15353203407e765c8893ade5905809a Mon Sep 17 00:00:00 2001 From: replydev Date: Mon, 14 Jul 2025 19:26:40 +0200 Subject: [PATCH 4/6] test: add subcommand integration tests --- Cargo.lock | 115 ++++++++++++++++++ Cargo.toml | 1 + .../cli_integration_test/empty_database | 1 + tests/add_integration_tests.rs | 28 +++++ 4 files changed, 145 insertions(+) create mode 100644 test_samples/cli_integration_test/empty_database create mode 100644 tests/add_integration_tests.rs diff --git a/Cargo.lock b/Cargo.lock index e7ce508..afb9b07 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -145,6 +145,21 @@ dependencies = [ "wait-timeout", ] +[[package]] +name = "assert_fs" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a652f6cb1f516886fcfee5e7a5c078b9ade62cfcb889524efe5a64d682dd27a9" +dependencies = [ + "anstyle", + "doc-comment", + "globwalk", + "predicates", + "predicates-core", + "predicates-tree", + "tempfile", +] + [[package]] name = "autocfg" version = "1.4.0" @@ -445,6 +460,7 @@ version = "1.9.6" dependencies = [ "aes-gcm", "assert_cmd", + "assert_fs", "base64 0.22.1", "chacha20poly1305", "clap", @@ -484,6 +500,31 @@ dependencies = [ "libc", ] +[[package]] +name = "crossbeam-deque" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" +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-utils" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" + [[package]] name = "crossterm" version = "0.28.1" @@ -772,6 +813,12 @@ dependencies = [ "once_cell", ] +[[package]] +name = "fastrand" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" + [[package]] name = "float-cmp" version = "0.10.0" @@ -874,6 +921,17 @@ dependencies = [ "regex-syntax", ] +[[package]] +name = "globwalk" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf760ebf69878d9fd8f110c89703d90ce35095324d1f1edcb595c63945ee757" +dependencies = [ + "bitflags 2.9.1", + "ignore", + "walkdir", +] + [[package]] name = "hashbrown" version = "0.15.3" @@ -1028,6 +1086,22 @@ dependencies = [ "icu_properties", ] +[[package]] +name = "ignore" +version = "0.4.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d89fd380afde86567dfba715db065673989d6253f42b88179abd3eae47bda4b" +dependencies = [ + "crossbeam-deque", + "globset", + "log", + "memchr", + "regex-automata", + "same-file", + "walkdir", + "winapi-util", +] + [[package]] name = "image" version = "0.25.6" @@ -1683,6 +1757,15 @@ dependencies = [ "cipher", ] +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + [[package]] name = "scoped-tls" version = "1.0.1" @@ -1902,6 +1985,19 @@ dependencies = [ "syn", ] +[[package]] +name = "tempfile" +version = "3.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8a64e3985349f2441a1a9ef0b853f869006c3855f2cda6862a94d26ebb9d6a1" +dependencies = [ + "fastrand", + "getrandom 0.3.3", + "once_cell", + "rustix 1.0.7", + "windows-sys 0.59.0", +] + [[package]] name = "termtree" version = "0.5.1" @@ -2111,6 +2207,16 @@ dependencies = [ "libc", ] +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" @@ -2227,6 +2333,15 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" +[[package]] +name = "winapi-util" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" +dependencies = [ + "windows-sys 0.59.0", +] + [[package]] name = "winapi-wsapoll" version = "0.1.2" diff --git a/Cargo.toml b/Cargo.toml index 4397672..fbf6863 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -58,4 +58,5 @@ globset = "0.4.16" [dev-dependencies] assert_cmd = "2.0.17" +assert_fs = "1.1.3" predicates = "3.1.3" diff --git a/test_samples/cli_integration_test/empty_database b/test_samples/cli_integration_test/empty_database new file mode 100644 index 0000000..56c8680 --- /dev/null +++ b/test_samples/cli_integration_test/empty_database @@ -0,0 +1 @@ +{"version":1,"nonce":"MRFGJOdHjt9wrSbqlWHrwmMGENmjjCMh","salt":"eQkRyDUTaaWNVy/+S/CXIw==","cipher":"tBtvz2AOp7XPUJGSLkm+Ss+exKFjd0eA1UWd58nM/I45P7VmdK6/28c9vw=="} \ No newline at end of file diff --git a/tests/add_integration_tests.rs b/tests/add_integration_tests.rs new file mode 100644 index 0000000..8e68083 --- /dev/null +++ b/tests/add_integration_tests.rs @@ -0,0 +1,28 @@ +use assert_cmd::Command; +use predicates::{ + ord::eq, + str::{is_empty, is_match, starts_with}, +}; + +#[test] +fn add_without_label_should_fail() { + // Arrange / Act + let assertion = Command::cargo_bin("cotp") + .unwrap() + .arg("--database-path") + .arg("test_samples/cli_integration_test/empty_database") + .arg("add") + .assert(); + + // Assert + assertion.failure().code(2).stdout(is_empty()).stderr(eq( + "error: the following required arguments were not provided: + --otpuri + --label