From b4dd55bd05e378d8374c239113a6d4fcadd20eda Mon Sep 17 00:00:00 2001 From: Alexander Cyon Date: Wed, 27 Nov 2024 09:04:30 +0100 Subject: [PATCH 01/33] Add `rules` and `rules-uniffi` crates with implementation of builders of roles and matrices - with UniFFI implementation. --- .gitignore | 3 +- .tarpaulin.toml | 5 +- Cargo.lock | 729 ------- Cargo.toml | 1 + README.md | 6 +- crates/rules-uniffi/Cargo.toml | 9 +- crates/rules-uniffi/build.rs | 3 + crates/rules-uniffi/src/builder.rs | 279 +++ crates/rules-uniffi/src/error_conversion.rs | 25 + crates/rules-uniffi/src/lib.rs | 14 + ...ource_in_role_builder_validation_status.rs | 16 + crates/rules-uniffi/src/models/mod.rs | 3 + crates/rules-uniffi/src/sargon.udl | 2 + .../src/unneeded_when_moved_to_sargon.rs | 67 + crates/rules/Cargo.toml | 3 + crates/rules/src/lib.rs | 18 +- .../abstract_matrix_builder_or_built.rs | 17 + crates/rules/src/matrices/builder/error.rs | 55 + .../src/matrices/builder/matrix_builder.rs | 357 ++++ .../builder/matrix_builder_unit_tests.rs | 1678 +++++++++++++++ crates/rules/src/matrices/builder/mod.rs | 7 + .../matrices/matrix_with_factor_source_ids.rs | 51 + crates/rules/src/matrices/mod.rs | 8 + crates/rules/src/move_to_sargon.rs | 92 + .../roles/abstract_role_builder_or_built.rs | 112 + crates/rules/src/roles/builder/mod.rs | 4 + .../rules/src/roles/builder/roles_builder.rs | 603 ++++++ .../roles/builder/roles_builder_unit_tests.rs | 1825 +++++++++++++++++ crates/rules/src/roles/mod.rs | 7 + .../rules/src/roles/roles_with_factor_ids.rs | 64 + crates/rules/src/rules/error.rs | 8 - crates/rules/src/rules/mod.rs | 3 - rust-toolchain.toml | 2 +- 33 files changed, 5326 insertions(+), 750 deletions(-) delete mode 100644 Cargo.lock create mode 100644 crates/rules-uniffi/build.rs create mode 100644 crates/rules-uniffi/src/builder.rs create mode 100644 crates/rules-uniffi/src/error_conversion.rs create mode 100644 crates/rules-uniffi/src/models/factor_source_in_role_builder_validation_status.rs create mode 100644 crates/rules-uniffi/src/models/mod.rs create mode 100644 crates/rules-uniffi/src/sargon.udl create mode 100644 crates/rules-uniffi/src/unneeded_when_moved_to_sargon.rs create mode 100644 crates/rules/src/matrices/abstract_matrix_builder_or_built.rs create mode 100644 crates/rules/src/matrices/builder/error.rs create mode 100644 crates/rules/src/matrices/builder/matrix_builder.rs create mode 100644 crates/rules/src/matrices/builder/matrix_builder_unit_tests.rs create mode 100644 crates/rules/src/matrices/builder/mod.rs create mode 100644 crates/rules/src/matrices/matrix_with_factor_source_ids.rs create mode 100644 crates/rules/src/matrices/mod.rs create mode 100644 crates/rules/src/move_to_sargon.rs create mode 100644 crates/rules/src/roles/abstract_role_builder_or_built.rs create mode 100644 crates/rules/src/roles/builder/mod.rs create mode 100644 crates/rules/src/roles/builder/roles_builder.rs create mode 100644 crates/rules/src/roles/builder/roles_builder_unit_tests.rs create mode 100644 crates/rules/src/roles/mod.rs create mode 100644 crates/rules/src/roles/roles_with_factor_ids.rs delete mode 100644 crates/rules/src/rules/error.rs delete mode 100644 crates/rules/src/rules/mod.rs diff --git a/.gitignore b/.gitignore index b02f7f66..b1affaa1 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ /target -tarpaulin-report.* \ No newline at end of file +tarpaulin-report.* +Cargo.lock \ No newline at end of file diff --git a/.tarpaulin.toml b/.tarpaulin.toml index a1529758..1a5914e5 100644 --- a/.tarpaulin.toml +++ b/.tarpaulin.toml @@ -1,5 +1,8 @@ [all] -exclude-files = ["src/types/sargon_types.rs", "src/testing/*", "src/samples/*"] +exclude-files = [ + "crates/rules-uniffi/src/unneeded_when_moved_to_sargon.rs", + "crates/rules-uniffi/src/error_conversion.rs", +] verbose = false force-clean = true timeout = "2m" diff --git a/Cargo.lock b/Cargo.lock deleted file mode 100644 index 4898266c..00000000 --- a/Cargo.lock +++ /dev/null @@ -1,729 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -version = 3 - -[[package]] -name = "anstream" -version = "0.6.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b" -dependencies = [ - "anstyle", - "anstyle-parse", - "anstyle-query", - "anstyle-wincon", - "colorchoice", - "is_terminal_polyfill", - "utf8parse", -] - -[[package]] -name = "anstyle" -version = "1.0.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" - -[[package]] -name = "anstyle-parse" -version = "0.2.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9" -dependencies = [ - "utf8parse", -] - -[[package]] -name = "anstyle-query" -version = "1.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c" -dependencies = [ - "windows-sys", -] - -[[package]] -name = "anstyle-wincon" -version = "3.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2109dbce0e72be3ec00bed26e6a7479ca384ad226efdd66db8fa2e3a38c83125" -dependencies = [ - "anstyle", - "windows-sys", -] - -[[package]] -name = "anyhow" -version = "1.0.93" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c95c10ba0b00a02636238b814946408b1322d5ac4760326e6fb8ec956d85775" - -[[package]] -name = "askama" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b79091df18a97caea757e28cd2d5fda49c6cd4bd01ddffd7ff01ace0c0ad2c28" -dependencies = [ - "askama_derive", - "askama_escape", -] - -[[package]] -name = "askama_derive" -version = "0.12.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19fe8d6cb13c4714962c072ea496f3392015f0989b1a2847bb4b2d9effd71d83" -dependencies = [ - "askama_parser", - "basic-toml", - "mime", - "mime_guess", - "proc-macro2", - "quote", - "serde", - "syn", -] - -[[package]] -name = "askama_escape" -version = "0.10.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "619743e34b5ba4e9703bba34deac3427c72507c7159f5fd030aea8cac0cfe341" - -[[package]] -name = "askama_parser" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "acb1161c6b64d1c3d83108213c2a2533a342ac225aabd0bda218278c2ddb00c0" -dependencies = [ - "nom", -] - -[[package]] -name = "autocfg" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" - -[[package]] -name = "basic-toml" -version = "0.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "823388e228f614e9558c6804262db37960ec8821856535f5c3f59913140558f8" -dependencies = [ - "serde", -] - -[[package]] -name = "bincode" -version = "1.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" -dependencies = [ - "serde", -] - -[[package]] -name = "bytes" -version = "1.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ac0150caa2ae65ca5bd83f25c7de183dea78d4d366469f148435e2acfbad0da" - -[[package]] -name = "camino" -version = "1.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b96ec4966b5813e2c0507c1f86115c8c5abaadc3980879c3424042a02fd1ad3" -dependencies = [ - "serde", -] - -[[package]] -name = "cargo-platform" -version = "0.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24b1f0365a6c6bb4020cd05806fd0d33c44d38046b8bd7f0e40814b9763cabfc" -dependencies = [ - "serde", -] - -[[package]] -name = "cargo_metadata" -version = "0.15.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eee4243f1f26fc7a42710e7439c149e2b10b05472f88090acce52632f231a73a" -dependencies = [ - "camino", - "cargo-platform", - "semver", - "serde", - "serde_json", - "thiserror 1.0.69", -] - -[[package]] -name = "clap" -version = "4.5.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb3b4b9e5a7c7514dfa52869339ee98b3156b0bfb4e8a77c4ff4babb64b1604f" -dependencies = [ - "clap_builder", - "clap_derive", -] - -[[package]] -name = "clap_builder" -version = "4.5.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b17a95aa67cc7b5ebd32aa5370189aa0d79069ef1c64ce893bd30fb24bff20ec" -dependencies = [ - "anstream", - "anstyle", - "clap_lex", - "strsim", -] - -[[package]] -name = "clap_derive" -version = "4.5.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ac6a0c7b1a9e9a5186361f67dfa1b88213572f427fb9ab038efb2bd8c582dab" -dependencies = [ - "heck", - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "clap_lex" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "afb84c814227b90d6895e01398aee0d8033c00e7466aca416fb6a8e0eb19d8a7" - -[[package]] -name = "colorchoice" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" - -[[package]] -name = "fs-err" -version = "2.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88a41f105fe1d5b6b34b2055e3dc59bb79b46b48b2040b9e6c7b4b5de097aa41" -dependencies = [ - "autocfg", -] - -[[package]] -name = "glob" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" - -[[package]] -name = "goblin" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b363a30c165f666402fe6a3024d3bec7ebc898f96a4a23bd1c99f8dbf3f4f47" -dependencies = [ - "log", - "plain", - "scroll", -] - -[[package]] -name = "heck" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" - -[[package]] -name = "is_terminal_polyfill" -version = "1.70.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" - -[[package]] -name = "itoa" -version = "1.0.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "540654e97a3f4470a492cd30ff187bc95d89557a903a2bbf112e2fae98104ef2" - -[[package]] -name = "log" -version = "0.4.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" - -[[package]] -name = "memchr" -version = "2.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" - -[[package]] -name = "mime" -version = "0.3.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" - -[[package]] -name = "mime_guess" -version = "2.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7c44f8e672c00fe5308fa235f821cb4198414e1c77935c1ab6948d3fd78550e" -dependencies = [ - "mime", - "unicase", -] - -[[package]] -name = "minimal-lexical" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" - -[[package]] -name = "nom" -version = "7.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" -dependencies = [ - "memchr", - "minimal-lexical", -] - -[[package]] -name = "once_cell" -version = "1.20.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" - -[[package]] -name = "paste" -version = "1.0.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" - -[[package]] -name = "plain" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6" - -[[package]] -name = "proc-macro2" -version = "1.0.92" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0" -dependencies = [ - "unicode-ident", -] - -[[package]] -name = "quote" -version = "1.0.37" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" -dependencies = [ - "proc-macro2", -] - -[[package]] -name = "rules" -version = "0.1.0" -dependencies = [ - "thiserror 2.0.3", -] - -[[package]] -name = "rules-uniffi" -version = "0.1.0" -dependencies = [ - "rules", - "thiserror 2.0.3", - "uniffi", -] - -[[package]] -name = "ryu" -version = "1.0.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" - -[[package]] -name = "scroll" -version = "0.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ab8598aa408498679922eff7fa985c25d58a90771bd6be794434c5277eab1a6" -dependencies = [ - "scroll_derive", -] - -[[package]] -name = "scroll_derive" -version = "0.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f81c2fde025af7e69b1d1420531c8a8811ca898919db177141a85313b1cb932" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "semver" -version = "1.0.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" -dependencies = [ - "serde", -] - -[[package]] -name = "serde" -version = "1.0.215" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6513c1ad0b11a9376da888e3e0baa0077f1aed55c17f50e7b2397136129fb88f" -dependencies = [ - "serde_derive", -] - -[[package]] -name = "serde_derive" -version = "1.0.215" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad1e866f866923f252f05c889987993144fb74e722403468a4ebd70c3cd756c0" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "serde_json" -version = "1.0.133" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7fceb2473b9166b2294ef05efcb65a3db80803f0b03ef86a5fc88a2b85ee377" -dependencies = [ - "itoa", - "memchr", - "ryu", - "serde", -] - -[[package]] -name = "siphasher" -version = "0.3.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" - -[[package]] -name = "smawk" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7c388c1b5e93756d0c740965c41e8822f866621d41acbdf6336a6a168f8840c" - -[[package]] -name = "static_assertions" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" - -[[package]] -name = "strsim" -version = "0.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" - -[[package]] -name = "syn" -version = "2.0.89" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44d46482f1c1c87acd84dea20c1bf5ebff4c757009ed6bf19cfd36fb10e92c4e" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "textwrap" -version = "0.16.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23d434d3f8967a09480fb04132ebe0a3e088c173e6d0ee7897abbdf4eab0f8b9" -dependencies = [ - "smawk", -] - -[[package]] -name = "thiserror" -version = "1.0.69" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" -dependencies = [ - "thiserror-impl 1.0.69", -] - -[[package]] -name = "thiserror" -version = "2.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c006c85c7651b3cf2ada4584faa36773bd07bac24acfb39f3c431b36d7e667aa" -dependencies = [ - "thiserror-impl 2.0.3", -] - -[[package]] -name = "thiserror-impl" -version = "1.0.69" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "thiserror-impl" -version = "2.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f077553d607adc1caf65430528a576c757a71ed73944b66ebb58ef2bbd243568" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "toml" -version = "0.5.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234" -dependencies = [ - "serde", -] - -[[package]] -name = "unicase" -version = "2.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e51b68083f157f853b6379db119d1c1be0e6e4dec98101079dec41f6f5cf6df" - -[[package]] -name = "unicode-ident" -version = "1.0.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83" - -[[package]] -name = "uniffi" -version = "0.27.1" -source = "git+https://github.com/mozilla/uniffi-rs/?rev=6f33088e8100a2ea9586c8c3ecf98ab51d5aba62#6f33088e8100a2ea9586c8c3ecf98ab51d5aba62" -dependencies = [ - "anyhow", - "camino", - "clap", - "uniffi_bindgen", - "uniffi_build", - "uniffi_core", - "uniffi_macros", -] - -[[package]] -name = "uniffi_bindgen" -version = "0.27.1" -source = "git+https://github.com/mozilla/uniffi-rs/?rev=6f33088e8100a2ea9586c8c3ecf98ab51d5aba62#6f33088e8100a2ea9586c8c3ecf98ab51d5aba62" -dependencies = [ - "anyhow", - "askama", - "camino", - "cargo_metadata", - "clap", - "fs-err", - "glob", - "goblin", - "heck", - "once_cell", - "paste", - "serde", - "textwrap", - "toml", - "uniffi_meta", - "uniffi_testing", - "uniffi_udl", -] - -[[package]] -name = "uniffi_build" -version = "0.27.1" -source = "git+https://github.com/mozilla/uniffi-rs/?rev=6f33088e8100a2ea9586c8c3ecf98ab51d5aba62#6f33088e8100a2ea9586c8c3ecf98ab51d5aba62" -dependencies = [ - "anyhow", - "camino", - "uniffi_bindgen", -] - -[[package]] -name = "uniffi_checksum_derive" -version = "0.27.1" -source = "git+https://github.com/mozilla/uniffi-rs/?rev=6f33088e8100a2ea9586c8c3ecf98ab51d5aba62#6f33088e8100a2ea9586c8c3ecf98ab51d5aba62" -dependencies = [ - "quote", - "syn", -] - -[[package]] -name = "uniffi_core" -version = "0.27.1" -source = "git+https://github.com/mozilla/uniffi-rs/?rev=6f33088e8100a2ea9586c8c3ecf98ab51d5aba62#6f33088e8100a2ea9586c8c3ecf98ab51d5aba62" -dependencies = [ - "anyhow", - "bytes", - "camino", - "log", - "once_cell", - "paste", - "static_assertions", -] - -[[package]] -name = "uniffi_macros" -version = "0.27.1" -source = "git+https://github.com/mozilla/uniffi-rs/?rev=6f33088e8100a2ea9586c8c3ecf98ab51d5aba62#6f33088e8100a2ea9586c8c3ecf98ab51d5aba62" -dependencies = [ - "bincode", - "camino", - "fs-err", - "once_cell", - "proc-macro2", - "quote", - "serde", - "syn", - "toml", - "uniffi_meta", -] - -[[package]] -name = "uniffi_meta" -version = "0.27.1" -source = "git+https://github.com/mozilla/uniffi-rs/?rev=6f33088e8100a2ea9586c8c3ecf98ab51d5aba62#6f33088e8100a2ea9586c8c3ecf98ab51d5aba62" -dependencies = [ - "anyhow", - "bytes", - "siphasher", - "uniffi_checksum_derive", -] - -[[package]] -name = "uniffi_testing" -version = "0.27.1" -source = "git+https://github.com/mozilla/uniffi-rs/?rev=6f33088e8100a2ea9586c8c3ecf98ab51d5aba62#6f33088e8100a2ea9586c8c3ecf98ab51d5aba62" -dependencies = [ - "anyhow", - "camino", - "cargo_metadata", - "fs-err", - "once_cell", -] - -[[package]] -name = "uniffi_udl" -version = "0.27.1" -source = "git+https://github.com/mozilla/uniffi-rs/?rev=6f33088e8100a2ea9586c8c3ecf98ab51d5aba62#6f33088e8100a2ea9586c8c3ecf98ab51d5aba62" -dependencies = [ - "anyhow", - "textwrap", - "uniffi_meta", - "uniffi_testing", - "weedle2", -] - -[[package]] -name = "utf8parse" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" - -[[package]] -name = "weedle2" -version = "5.0.0" -source = "git+https://github.com/mozilla/uniffi-rs/?rev=6f33088e8100a2ea9586c8c3ecf98ab51d5aba62#6f33088e8100a2ea9586c8c3ecf98ab51d5aba62" -dependencies = [ - "nom", -] - -[[package]] -name = "windows-sys" -version = "0.59.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" -dependencies = [ - "windows-targets", -] - -[[package]] -name = "windows-targets" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" -dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_gnullvm", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", -] - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" - -[[package]] -name = "windows_i686_gnu" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" - -[[package]] -name = "windows_i686_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" - -[[package]] -name = "windows_i686_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" diff --git a/Cargo.toml b/Cargo.toml index 770dff92..787d49d0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,3 +13,4 @@ debug = true [workspace.dependencies] thiserror = "2.0.3" +sargon = { git = "https://github.com/radixdlt/sargon", branch = "main" } diff --git a/README.md b/README.md index 14adf54b..981343ea 100644 --- a/README.md +++ b/README.md @@ -1 +1,5 @@ -With https://github.com/radixdlt/sargon/pull/254 merged this repo is now archived. +# Rules + +[Implementation of FactorSourceID rules defined here][doc] + +[doc]: https://radixdlt.atlassian.net/wiki/spaces/AT/pages/3758063620/MFA+Rules+for+Factors+and+Security+Shields diff --git a/crates/rules-uniffi/Cargo.toml b/crates/rules-uniffi/Cargo.toml index 2eeb8a5a..89c91eca 100644 --- a/crates/rules-uniffi/Cargo.toml +++ b/crates/rules-uniffi/Cargo.toml @@ -2,18 +2,23 @@ name = "rules-uniffi" version = "0.1.0" edition = "2021" +build = "build.rs" +[lib] +crate-type = ["staticlib", "cdylib", "lib"] [dependencies] +sargon = { workspace = true } rules = { path = "../rules" } +thiserror = { workspace = true } +serde = { version = "1.0.215", features = ["derive"] } +pretty_assertions = "1.4.1" # uniffi = "0.27.1" uniffi = { git = "https://github.com/mozilla/uniffi-rs/", rev = "6f33088e8100a2ea9586c8c3ecf98ab51d5aba62", features = [ "cli", ] } -thiserror = { workspace = true } - [dev-dependencies] # uniffi = "0.27.1" uniffi = { git = "https://github.com/mozilla/uniffi-rs/", rev = "6f33088e8100a2ea9586c8c3ecf98ab51d5aba62", features = [ diff --git a/crates/rules-uniffi/build.rs b/crates/rules-uniffi/build.rs new file mode 100644 index 00000000..0f27335e --- /dev/null +++ b/crates/rules-uniffi/build.rs @@ -0,0 +1,3 @@ +pub fn main() { + uniffi::generate_scaffolding("src/sargon.udl").expect("Should be able to build."); +} diff --git a/crates/rules-uniffi/src/builder.rs b/crates/rules-uniffi/src/builder.rs new file mode 100644 index 00000000..3f86d756 --- /dev/null +++ b/crates/rules-uniffi/src/builder.rs @@ -0,0 +1,279 @@ +#![allow(clippy::new_without_default)] +#![allow(dead_code)] +#![allow(unused_variables)] + +use std::sync::{Arc, RwLock}; + +use sargon::IndexSet; + +use crate::prelude::*; + +#[derive(Debug, uniffi::Object)] +pub struct SecurityShieldBuilder { + wrapped: RwLock>, +} + +#[derive(Debug, PartialEq, Eq, Hash, uniffi::Object)] +#[uniffi::export(Debug, Eq, Hash)] +pub struct SecurityStructureOfFactorSourceIds { + wrapped_matrix: MatrixWithFactorSourceIds, + name: String, +} + +impl SecurityShieldBuilder { + fn with>( + &self, + mut with_non_consumed_builder: impl FnMut(&mut MatrixBuilder) -> Result, + ) -> Result { + let guard = self.wrapped.write(); + + let mut binding = guard.map_err(|_| CommonError::MatrixBuilderRwLockPoisoned)?; + + let Some(builder) = binding.as_mut() else { + return Err(CommonError::AlreadyBuilt); + }; + with_non_consumed_builder(builder).map_err(|e| Into::::into(e)) + } + + fn validation_for_addition_of_factor_source_by_calling( + &self, + factor_sources: Vec>, + call: impl Fn( + &MatrixBuilder, + &IndexSet, + ) -> IndexSet, + ) -> Result>, CommonError> { + let input = &factor_sources + .clone() + .into_iter() + .map(|x| x.inner) + .collect::>(); + self.with(|builder| { + let xs = call(builder, input); + + let xs = xs + .into_iter() + .map(Into::::into) + .map(Arc::new) + .collect(); + + Ok::<_, CommonError>(xs) + }) + } +} + +#[uniffi::export] +impl SecurityShieldBuilder { + #[uniffi::constructor] + pub fn new() -> Arc { + Arc::new(Self { + wrapped: RwLock::new(Some(MatrixBuilder::new())), + }) + } + + /// Adds the factor source to the primary role threshold list. + pub fn add_factor_source_to_primary_threshold( + &self, + factor_source_id: Arc, + ) -> Result<(), CommonError> { + self.with(|builder| builder.add_factor_source_to_primary_threshold(factor_source_id.inner)) + } + + pub fn add_factor_source_to_primary_override( + &self, + factor_source_id: Arc, + ) -> Result<(), CommonError> { + self.with(|builder| builder.add_factor_source_to_primary_override(factor_source_id.inner)) + } + + pub fn remove_factor(&self, factor_source_id: Arc) -> Result<(), CommonError> { + self.with(|builder| builder.remove_factor(&factor_source_id.inner)) + } + + pub fn set_threshold(&self, threshold: u8) -> Result<(), CommonError> { + self.with(|builder| builder.set_threshold(threshold)) + } + + pub fn set_number_of_days_until_auto_confirm( + &self, + number_of_days: u16, + ) -> Result<(), CommonError> { + self.with(|builder| builder.set_number_of_days_until_auto_confirm(number_of_days)) + } + + pub fn add_factor_source_to_recovery_override( + &self, + factor_source_id: Arc, + ) -> Result<(), CommonError> { + self.with(|builder| builder.add_factor_source_to_recovery_override(factor_source_id.inner)) + } + + pub fn add_factor_source_to_confirmation_override( + &self, + factor_source_id: Arc, + ) -> Result<(), CommonError> { + self.with(|builder| { + builder.add_factor_source_to_confirmation_override(factor_source_id.inner) + }) + } + + pub fn validation_for_addition_of_factor_source_of_kind_to_confirmation_override( + &self, + factor_source_kind: FactorSourceKind, + ) -> Result<(), CommonError> { + self.with(|builder| { + builder.validation_for_addition_of_factor_source_of_kind_to_confirmation_override( + factor_source_kind.into(), + ) + }) + } + + pub fn validation_for_addition_of_factor_source_of_kind_to_recovery_override( + &self, + factor_source_kind: FactorSourceKind, + ) -> Result<(), CommonError> { + self.with(|builder| { + builder.validation_for_addition_of_factor_source_of_kind_to_recovery_override( + factor_source_kind.into(), + ) + }) + } + + pub fn validation_for_addition_of_factor_source_of_kind_to_primary_override( + &self, + factor_source_kind: FactorSourceKind, + ) -> Result<(), CommonError> { + self.with(|builder| { + builder.validation_for_addition_of_factor_source_of_kind_to_primary_override( + factor_source_kind.into(), + ) + }) + } + + pub fn validation_for_addition_of_factor_source_of_kind_to_primary_threshold( + &self, + factor_source_kind: FactorSourceKind, + ) -> Result<(), CommonError> { + self.with(|builder| { + builder.validation_for_addition_of_factor_source_of_kind_to_primary_threshold( + factor_source_kind.into(), + ) + }) + } + + pub fn validation_for_addition_of_factor_source_to_primary_threshold_for_each( + &self, + factor_sources: Vec>, + ) -> Result>, CommonError> { + self.validation_for_addition_of_factor_source_by_calling( + factor_sources, + |builder, input| { + builder + .validation_for_addition_of_factor_source_to_primary_threshold_for_each(input) + }, + ) + } + + pub fn validation_for_addition_of_factor_source_to_primary_override_for_each( + &self, + factor_sources: Vec>, + ) -> Result>, CommonError> { + self.validation_for_addition_of_factor_source_by_calling( + factor_sources, + |builder, input| { + builder.validation_for_addition_of_factor_source_to_primary_override_for_each(input) + }, + ) + } + + pub fn validation_for_addition_of_factor_source_to_recovery_override_for_each( + &self, + factor_sources: Vec>, + ) -> Result>, CommonError> { + self.validation_for_addition_of_factor_source_by_calling( + factor_sources, + |builder, input| { + builder + .validation_for_addition_of_factor_source_to_recovery_override_for_each(input) + }, + ) + } + + pub fn validation_for_addition_of_factor_source_to_confirmation_override_for_each( + &self, + factor_sources: Vec>, + ) -> Result>, CommonError> { + self.validation_for_addition_of_factor_source_by_calling( + factor_sources, + |builder, input| { + builder.validation_for_addition_of_factor_source_to_confirmation_override_for_each( + input, + ) + }, + ) + } + + pub fn build( + self: Arc, + name: String, + ) -> Result { + let mut binding = self + .wrapped + .write() + .map_err(|_| CommonError::MatrixBuilderRwLockPoisoned)?; + let builder = binding.take().ok_or(CommonError::AlreadyBuilt)?; + let wrapped_matrix = builder + .build() + .map_err(|e| CommonError::BuildError(format!("{:?}", e)))?; + + let shield = SecurityStructureOfFactorSourceIds { + wrapped_matrix, + name, + }; + + Ok(shield) + } +} + +#[cfg(test)] +mod tests { + + use super::*; + + #[allow(clippy::upper_case_acronyms)] + type SUT = SecurityShieldBuilder; + + #[test] + fn test() { + let sut = SUT::new(); + sut.add_factor_source_to_primary_override(FactorSourceID::sample_arculus()) + .unwrap(); + sut.add_factor_source_to_primary_override(FactorSourceID::sample_arculus_other()) + .unwrap(); + sut.add_factor_source_to_recovery_override(FactorSourceID::sample_ledger()) + .unwrap(); + sut.add_factor_source_to_recovery_override(FactorSourceID::sample_ledger_other()) + .unwrap(); + sut.add_factor_source_to_confirmation_override(FactorSourceID::sample_device()) + .unwrap(); + + sut.remove_factor(FactorSourceID::sample_arculus_other()) + .unwrap(); + sut.remove_factor(FactorSourceID::sample_ledger_other()) + .unwrap(); + + let shield = sut.build("test".to_owned()).unwrap(); + assert_eq!( + shield.wrapped_matrix.primary().override_factors(), + &vec![FactorSourceID::sample_arculus().inner] + ); + assert_eq!( + shield.wrapped_matrix.recovery().override_factors(), + &vec![FactorSourceID::sample_ledger().inner] + ); + assert_eq!( + shield.wrapped_matrix.confirmation().override_factors(), + &vec![FactorSourceID::sample_device().inner] + ); + } +} diff --git a/crates/rules-uniffi/src/error_conversion.rs b/crates/rules-uniffi/src/error_conversion.rs new file mode 100644 index 00000000..52a833ae --- /dev/null +++ b/crates/rules-uniffi/src/error_conversion.rs @@ -0,0 +1,25 @@ +use crate::prelude::*; + +#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error, uniffi::Error)] +pub enum CommonError { + #[error("AlreadyBuilt")] + AlreadyBuilt, + + #[error("Matrix builder RwLock poisoned")] + MatrixBuilderRwLockPoisoned, + + #[error("Build error {0}")] + BuildError(String), +} + +impl From for CommonError { + fn from(val: MatrixBuilderValidation) -> Self { + CommonError::BuildError(format!("{:?}", val)) + } +} + +impl From for CommonError { + fn from(val: RoleBuilderValidation) -> Self { + CommonError::BuildError(format!("{:?}", val)) + } +} diff --git a/crates/rules-uniffi/src/lib.rs b/crates/rules-uniffi/src/lib.rs index e69de29b..777df2fb 100644 --- a/crates/rules-uniffi/src/lib.rs +++ b/crates/rules-uniffi/src/lib.rs @@ -0,0 +1,14 @@ +mod builder; +mod error_conversion; +mod models; +mod unneeded_when_moved_to_sargon; + +pub mod prelude { + pub(crate) use rules::prelude::*; + + pub(crate) use crate::error_conversion::*; + pub(crate) use crate::models::*; + pub(crate) use crate::unneeded_when_moved_to_sargon::*; +} + +uniffi::include_scaffolding!("sargon"); diff --git a/crates/rules-uniffi/src/models/factor_source_in_role_builder_validation_status.rs b/crates/rules-uniffi/src/models/factor_source_in_role_builder_validation_status.rs new file mode 100644 index 00000000..51f1c747 --- /dev/null +++ b/crates/rules-uniffi/src/models/factor_source_in_role_builder_validation_status.rs @@ -0,0 +1,16 @@ +#[derive(Debug, Clone, PartialEq, Eq, Hash, uniffi::Object)] +pub struct FactorSourceValidationStatus { + pub role: sargon::RoleKind, + pub factor_source_id: sargon::FactorSourceID, + pub validation: rules::RoleBuilderMutateResult, +} + +impl From for FactorSourceValidationStatus { + fn from(val: rules::FactorSourceInRoleBuilderValidationStatus) -> Self { + FactorSourceValidationStatus { + role: val.role, + factor_source_id: val.factor_source_id, + validation: val.validation, + } + } +} diff --git a/crates/rules-uniffi/src/models/mod.rs b/crates/rules-uniffi/src/models/mod.rs new file mode 100644 index 00000000..81c8aa5a --- /dev/null +++ b/crates/rules-uniffi/src/models/mod.rs @@ -0,0 +1,3 @@ +mod factor_source_in_role_builder_validation_status; + +pub use factor_source_in_role_builder_validation_status::*; diff --git a/crates/rules-uniffi/src/sargon.udl b/crates/rules-uniffi/src/sargon.udl new file mode 100644 index 00000000..fa3390fb --- /dev/null +++ b/crates/rules-uniffi/src/sargon.udl @@ -0,0 +1,2 @@ + +namespace sargon {}; \ No newline at end of file diff --git a/crates/rules-uniffi/src/unneeded_when_moved_to_sargon.rs b/crates/rules-uniffi/src/unneeded_when_moved_to_sargon.rs new file mode 100644 index 00000000..0ccea71e --- /dev/null +++ b/crates/rules-uniffi/src/unneeded_when_moved_to_sargon.rs @@ -0,0 +1,67 @@ +use std::sync::Arc; + +#[cfg(test)] +use rules::SampleValues; + +#[derive(Debug, Clone, Copy, PartialEq, Eq, uniffi::Enum)] +pub enum FactorSourceKind { + Device, + LedgerHQHardwareWallet, + Passphrase, + OffDeviceMnemonic, + TrustedContact, + SecurityQuestions, + ArculusCard, +} +impl From for sargon::FactorSourceKind { + fn from(value: FactorSourceKind) -> Self { + match value { + FactorSourceKind::Device => sargon::FactorSourceKind::Device, + FactorSourceKind::LedgerHQHardwareWallet => { + sargon::FactorSourceKind::LedgerHQHardwareWallet + } + FactorSourceKind::Passphrase => sargon::FactorSourceKind::Passphrase, + FactorSourceKind::OffDeviceMnemonic => sargon::FactorSourceKind::OffDeviceMnemonic, + FactorSourceKind::TrustedContact => sargon::FactorSourceKind::TrustedContact, + FactorSourceKind::SecurityQuestions => sargon::FactorSourceKind::SecurityQuestions, + FactorSourceKind::ArculusCard => sargon::FactorSourceKind::ArculusCard, + } + } +} + +#[derive(Debug, Clone, PartialEq, Eq, uniffi::Object)] +pub struct FactorSourceID { + pub inner: sargon::FactorSourceID, +} +impl FactorSourceID { + pub fn new(inner: sargon::FactorSourceID) -> Arc { + Arc::new(Self { inner }) + } +} + +#[cfg(test)] +impl FactorSourceID { + pub fn sample_device() -> Arc { + Self::new(sargon::FactorSourceID::sample_device()) + } + + pub fn sample_device_other() -> Arc { + Self::new(sargon::FactorSourceID::sample_device_other()) + } + + pub fn sample_ledger() -> Arc { + Self::new(sargon::FactorSourceID::sample_ledger()) + } + + pub fn sample_ledger_other() -> Arc { + Self::new(sargon::FactorSourceID::sample_ledger_other()) + } + + pub fn sample_arculus() -> Arc { + Self::new(sargon::FactorSourceID::sample_arculus()) + } + + pub fn sample_arculus_other() -> Arc { + Self::new(sargon::FactorSourceID::sample_arculus_other()) + } +} diff --git a/crates/rules/Cargo.toml b/crates/rules/Cargo.toml index 41a1503d..86679a0b 100644 --- a/crates/rules/Cargo.toml +++ b/crates/rules/Cargo.toml @@ -5,3 +5,6 @@ edition = "2021" [dependencies] thiserror = { workspace = true } +sargon = { workspace = true} +serde = { version = "1.0.215", features = ["derive"] } +pretty_assertions = "1.4.1" diff --git a/crates/rules/src/lib.rs b/crates/rules/src/lib.rs index 57fa7b3c..9ba99b16 100644 --- a/crates/rules/src/lib.rs +++ b/crates/rules/src/lib.rs @@ -1,10 +1,20 @@ -mod rules; +mod matrices; +mod move_to_sargon; +mod roles; pub mod prelude { + pub(crate) use sargon::{ + FactorSourceID, FactorSourceIDFromHash, FactorSourceKind, Identifiable, IndexSet, RoleKind, + }; - pub(crate) use crate::rules::*; + #[allow(unused_imports)] + pub use crate::matrices::*; + pub use crate::move_to_sargon::*; + pub use crate::roles::*; - pub(crate) use thiserror::Error as ThisError; + pub(crate) use serde::{Deserialize, Serialize}; + pub(crate) use std::collections::HashSet; + pub(crate) use std::marker::PhantomData; } -pub use prelude::*; +pub use crate::prelude::*; diff --git a/crates/rules/src/matrices/abstract_matrix_builder_or_built.rs b/crates/rules/src/matrices/abstract_matrix_builder_or_built.rs new file mode 100644 index 00000000..265e2021 --- /dev/null +++ b/crates/rules/src/matrices/abstract_matrix_builder_or_built.rs @@ -0,0 +1,17 @@ +use crate::prelude::*; + +#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] +pub struct AbstractMatrixBuilderOrBuilt { + #[serde(skip)] + #[doc(hidden)] + pub(crate) built: PhantomData, + + pub(crate) primary_role: AbstractRoleBuilderOrBuilt, + pub(crate) recovery_role: AbstractRoleBuilderOrBuilt, + pub(crate) confirmation_role: AbstractRoleBuilderOrBuilt, + + pub(crate) number_of_days_until_auto_confirm: u16, +} +impl AbstractMatrixBuilderOrBuilt { + pub const DEFAULT_NUMBER_OF_DAYS_UNTIL_AUTO_CONFIRM: u16 = 14; +} diff --git a/crates/rules/src/matrices/builder/error.rs b/crates/rules/src/matrices/builder/error.rs new file mode 100644 index 00000000..56ad9d8a --- /dev/null +++ b/crates/rules/src/matrices/builder/error.rs @@ -0,0 +1,55 @@ +use crate::prelude::*; + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, thiserror::Error)] +pub enum MatrixRolesInCombinationViolation { + #[error("Basic violation: {0}")] + Basic(#[from] MatrixRolesInCombinationBasicViolation), + + #[error("Forever invalid: {0}")] + ForeverInvalid(#[from] MatrixRolesInCombinationForeverInvalid), + + #[error("Not yet valid: {0}")] + NotYetValid(#[from] MatrixRolesInCombinationNotYetValid), +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, thiserror::Error)] +pub enum MatrixRolesInCombinationBasicViolation { + #[error("The factor source was not found in any role")] + FactorSourceNotFoundInAnyRole, + + #[error("The number of days until auto confirm must be greater than zero")] + NumberOfDaysUntilAutoConfirmMustBeGreaterThanZero, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, thiserror::Error)] +pub enum MatrixRolesInCombinationForeverInvalid { + #[error("Recovery and confirmation factors overlap. No factor may be used in both the recovery and confirmation roles")] + RecoveryAndConfirmationFactorsOverlap, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, thiserror::Error)] +pub enum MatrixRolesInCombinationNotYetValid { + #[error("The single factor used in the primary role must not be used in any other role")] + SingleFactorUsedInPrimaryMustNotBeUsedInAnyOtherRole, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, thiserror::Error)] +pub enum MatrixBuilderValidation { + #[error("Role {role:?} in isolation violation: {violation}")] + RoleInIsolation { + role: RoleKind, + violation: RoleBuilderValidation, + }, + #[error("Roles in combination violation: {0}")] + CombinationViolation(#[from] MatrixRolesInCombinationViolation), +} + +pub(crate) trait IntoMatrixErr { + fn into_matrix_err(self, role: RoleKind) -> Result; +} + +impl IntoMatrixErr for Result { + fn into_matrix_err(self, role: RoleKind) -> Result { + self.map_err(|violation| MatrixBuilderValidation::RoleInIsolation { role, violation }) + } +} diff --git a/crates/rules/src/matrices/builder/matrix_builder.rs b/crates/rules/src/matrices/builder/matrix_builder.rs new file mode 100644 index 00000000..f31702d0 --- /dev/null +++ b/crates/rules/src/matrices/builder/matrix_builder.rs @@ -0,0 +1,357 @@ +#![allow(clippy::new_without_default)] + +use crate::prelude::*; + +pub type MatrixBuilderMutateResult = Result<(), MatrixBuilderValidation>; +pub type MatrixBuilderBuildResult = Result; + +pub type MatrixBuilder = AbstractMatrixBuilderOrBuilt< + FactorSourceID, + MatrixWithFactorSourceIds, + RoleWithFactorSourceIds, +>; + +// ================== +// ===== PUBLIC ===== +// ================== +impl MatrixBuilder { + pub fn new() -> Self { + Self { + built: PhantomData, + primary_role: RoleBuilder::primary(), + recovery_role: RoleBuilder::recovery(), + confirmation_role: RoleBuilder::confirmation(), + number_of_days_until_auto_confirm: Self::DEFAULT_NUMBER_OF_DAYS_UNTIL_AUTO_CONFIRM, + } + } + + pub fn build(self) -> MatrixBuilderBuildResult { + self.validate_combination()?; + + let primary = self + .primary_role + .build() + .into_matrix_err(RoleKind::Primary)?; + let recovery = self + .recovery_role + .build() + .into_matrix_err(RoleKind::Recovery)?; + let confirmation = self + .confirmation_role + .build() + .into_matrix_err(RoleKind::Confirmation)?; + + let built = MatrixWithFactorSourceIds { + built: PhantomData, + primary_role: primary, + recovery_role: recovery, + confirmation_role: confirmation, + number_of_days_until_auto_confirm: self.number_of_days_until_auto_confirm, + }; + Ok(built) + } + + pub fn validation_for_addition_of_factor_source_of_kind_to_primary_threshold( + &self, + factor_source_kind: FactorSourceKind, + ) -> RoleBuilderMutateResult { + self.primary_role + .validation_for_addition_of_factor_source_of_kind_to_list( + factor_source_kind, + FactorListKind::Threshold, + ) + } + + pub fn validation_for_addition_of_factor_source_of_kind_to_primary_override( + &self, + factor_source_kind: FactorSourceKind, + ) -> RoleBuilderMutateResult { + self.primary_role + .validation_for_addition_of_factor_source_of_kind_to_list( + factor_source_kind, + FactorListKind::Override, + ) + } + + pub fn validation_for_addition_of_factor_source_to_primary_threshold_for_each( + &self, + factor_sources: &IndexSet, + ) -> IndexSet { + self.primary_role + .validation_for_addition_of_factor_source_for_each( + FactorListKind::Threshold, + factor_sources, + ) + } + + pub fn validation_for_addition_of_factor_source_to_primary_override_for_each( + &self, + factor_sources: &IndexSet, + ) -> IndexSet { + self.primary_role + .validation_for_addition_of_factor_source_for_each( + FactorListKind::Override, + factor_sources, + ) + } + + pub fn validation_for_addition_of_factor_source_of_kind_to_recovery_override( + &self, + factor_source_kind: FactorSourceKind, + ) -> RoleBuilderMutateResult { + self.recovery_role + .validation_for_addition_of_factor_source_of_kind_to_list( + factor_source_kind, + FactorListKind::Override, + ) + } + + pub fn validation_for_addition_of_factor_source_to_recovery_override_for_each( + &self, + factor_sources: &IndexSet, + ) -> IndexSet { + self.recovery_role + .validation_for_addition_of_factor_source_for_each( + FactorListKind::Override, + factor_sources, + ) + } + + pub fn validation_for_addition_of_factor_source_of_kind_to_confirmation_override( + &self, + factor_source_kind: FactorSourceKind, + ) -> RoleBuilderMutateResult { + self.confirmation_role + .validation_for_addition_of_factor_source_of_kind_to_list( + factor_source_kind, + FactorListKind::Override, + ) + } + + pub fn validation_for_addition_of_factor_source_to_confirmation_override_for_each( + &self, + factor_sources: &IndexSet, + ) -> IndexSet { + self.confirmation_role + .validation_for_addition_of_factor_source_for_each( + FactorListKind::Override, + factor_sources, + ) + } + + pub fn validate_each_role_in_isolation(&self) -> MatrixBuilderMutateResult { + self.primary_role + .validate() + .into_matrix_err(RoleKind::Primary)?; + self.recovery_role + .validate() + .into_matrix_err(RoleKind::Recovery)?; + self.confirmation_role + .validate() + .into_matrix_err(RoleKind::Confirmation)?; + Ok(()) + } + + pub fn validate(&self) -> MatrixBuilderMutateResult { + self.validate_each_role_in_isolation()?; + self.validate_combination()?; + Ok(()) + } + + /// Adds the factor source to the primary role threshold list. + pub fn add_factor_source_to_primary_threshold( + &mut self, + factor_source_id: FactorSourceID, + ) -> MatrixBuilderMutateResult { + self.primary_role + .add_factor_source_to_list(factor_source_id, FactorListKind::Threshold) + .into_matrix_err(RoleKind::Primary)?; + + self.validate_combination() + } + + /// Adds the factor source to the primary role override list. + pub fn add_factor_source_to_primary_override( + &mut self, + factor_source_id: FactorSourceID, + ) -> MatrixBuilderMutateResult { + self.primary_role + .add_factor_source_to_list(factor_source_id, FactorListKind::Override) + .into_matrix_err(RoleKind::Primary)?; + + self.validate_combination() + } + + /// Adds the factor source to the recovery role override list if not already present. + /// + /// Even if `Err(MatrixBuilderValidation::CombinationViolation(MatrixRolesInCombinationViolation::NotYetValid(MatrixRolesInCombinationNotYetValid::SingleFactorUsedInPrimaryMustNotBeUsedInAnyOtherRole))` is thrown, the factor is still added to the recovery role. + /// + /// However, if `Err(MatrixBuilderValidation::Basic(_))` or `Err(MatrixBuilderValidation::ForeverInvalid(_))` is thrown + /// then the factor is not added to the recovery role. + pub fn add_factor_source_to_recovery_override( + &mut self, + factor_source_id: FactorSourceID, + ) -> MatrixBuilderMutateResult { + self.recovery_role + .add_factor_source_to_list(factor_source_id, FactorListKind::Override) + .into_matrix_err(RoleKind::Recovery)?; + + self.validate_combination() + } + + /// Adds the factor source to the confirmation role override list if not already present. + /// + /// Even if `Err(MatrixBuilderValidation::CombinationViolation(MatrixRolesInCombinationViolation::NotYetValid(MatrixRolesInCombinationNotYetValid::SingleFactorUsedInPrimaryMustNotBeUsedInAnyOtherRole))` is thrown, the factor is still added to the recovery role. + /// + /// However, if `Err(MatrixBuilderValidation::Basic(_))` or `Err(MatrixBuilderValidation::ForeverInvalid(_))` is thrown + /// then the factor is not added to the recovery role. + pub fn add_factor_source_to_confirmation_override( + &mut self, + factor_source_id: FactorSourceID, + ) -> MatrixBuilderMutateResult { + self.confirmation_role + .add_factor_source_to_list(factor_source_id, FactorListKind::Override) + .into_matrix_err(RoleKind::Confirmation)?; + + self.validate_combination() + } + + /// Sets the threshold on the primary role builder. + pub fn set_threshold(&mut self, threshold: u8) -> MatrixBuilderMutateResult { + self.primary_role + .set_threshold(threshold) + .into_matrix_err(RoleKind::Primary)?; + + self.validate_combination() + } + + pub fn set_number_of_days_until_auto_confirm( + &mut self, + number_of_days: u16, + ) -> MatrixBuilderMutateResult { + self.number_of_days_until_auto_confirm = number_of_days; + + self.validate_combination() + } + + /// Removes `factor_source_id` from all three roles, if not found in any an error + /// is thrown. + /// + /// # Throws + /// If none of the three role builders contains the factor source id, `Err(BasicViolation::FactorSourceNotFound)` is thrown + pub fn remove_factor( + &mut self, + factor_source_id: &FactorSourceID, + ) -> MatrixBuilderMutateResult { + let mut found = false; + if self + .primary_role + .remove_factor_source(factor_source_id) + .is_ok() + { + found = true; + } + if self + .recovery_role + .remove_factor_source(factor_source_id) + .is_ok() + { + found = true; + } + if self + .confirmation_role + .remove_factor_source(factor_source_id) + .is_ok() + { + found = true; + } + if !found { + MatrixBuilderMutateResult::Err(MatrixBuilderValidation::CombinationViolation( + MatrixRolesInCombinationViolation::Basic( + MatrixRolesInCombinationBasicViolation::FactorSourceNotFoundInAnyRole, + ), + )) + } else { + self.validate_combination() + } + } +} + +// ================== +// ==== PRIVATE ===== +// ================== +impl MatrixBuilder { + fn validate_if_primary_has_single_it_must_not_be_used_by_any_other_role( + &self, + ) -> MatrixBuilderMutateResult { + let primary_has_single_factor = self.primary_role.factors().len() == 1; + if primary_has_single_factor { + let primary_factors = self.primary_role.factors(); + let primary_factor = primary_factors.first().unwrap(); + let recovery_set = HashSet::<_>::from_iter(self.recovery_role.override_factors()); + let confirmation_set = + HashSet::<_>::from_iter(self.confirmation_role.override_factors()); + if recovery_set.contains(primary_factor) || confirmation_set.contains(primary_factor) { + return Err(MatrixBuilderValidation::CombinationViolation( + MatrixRolesInCombinationViolation::NotYetValid(MatrixRolesInCombinationNotYetValid::SingleFactorUsedInPrimaryMustNotBeUsedInAnyOtherRole), + )); + } + } + Ok(()) + } + + fn validate_no_factor_may_be_used_in_both_recovery_and_confirmation( + &self, + ) -> MatrixBuilderMutateResult { + let recovery_set = HashSet::<_>::from_iter(self.recovery_role.override_factors()); + let confirmation_set = HashSet::<_>::from_iter(self.confirmation_role.override_factors()); + let intersection = recovery_set + .intersection(&confirmation_set) + .collect::>(); + if intersection.is_empty() { + Ok(()) + } else { + Err(MatrixBuilderValidation::CombinationViolation( + MatrixRolesInCombinationViolation::ForeverInvalid( + MatrixRolesInCombinationForeverInvalid::RecoveryAndConfirmationFactorsOverlap, + ), + )) + } + } + + fn validate_number_of_days_until_auto_confirm(&self) -> MatrixBuilderMutateResult { + if self.number_of_days_until_auto_confirm == 0 { + return Err(MatrixBuilderValidation::CombinationViolation( + MatrixRolesInCombinationViolation::Basic( + MatrixRolesInCombinationBasicViolation::NumberOfDaysUntilAutoConfirmMustBeGreaterThanZero, + ), + )); + } + Ok(()) + } + + /// Security Shield Rules + /// In addition to the factor/role rules above, the wallet must enforce certain rules for combinations of + /// factors across the three roles. The construction method described in the next section will automatically + /// always follow these rules. A user may however choose to manually add/remove factors from their Shield + /// configuration and so the wallet must evaluate these rules and inform the user when the combination they + /// have chosen cannot be used. The wallet should never allow a user to complete a Shield configuration that + /// violates these rules. + /// + /// 1. If only one factor is used for `Primary`, that factor may not be used for either `Recovery` or `Confirmation` + /// 2. No factor may be used (override) in both `Recovery` and `Confirmation` + /// 3. No factor may be used in both the `Primary` threshold and `Primary` override + /// 4. Number of days until auto confirm is greater than zero + fn validate_combination(&self) -> MatrixBuilderMutateResult { + self.validate_if_primary_has_single_it_must_not_be_used_by_any_other_role()?; + self.validate_no_factor_may_be_used_in_both_recovery_and_confirmation()?; + + // N.B. the third 3: + // "3. No factor may be used in both the `Primary` threshold and `Primary` override" + // is already enforced by the RoleBuilder + + self.validate_number_of_days_until_auto_confirm()?; + + Ok(()) + } +} diff --git a/crates/rules/src/matrices/builder/matrix_builder_unit_tests.rs b/crates/rules/src/matrices/builder/matrix_builder_unit_tests.rs new file mode 100644 index 00000000..2d5e0311 --- /dev/null +++ b/crates/rules/src/matrices/builder/matrix_builder_unit_tests.rs @@ -0,0 +1,1678 @@ +#![cfg(test)] +use crate::prelude::*; + +#[allow(clippy::upper_case_acronyms)] +type SUT = MatrixBuilder; + +fn make() -> SUT { + SUT::new() +} + +#[test] +fn empty_primary_is_err() { + let sut = make(); + let res = sut.build(); + assert_eq!( + res, + MatrixBuilderBuildResult::Err(MatrixBuilderValidation::RoleInIsolation { + role: RoleKind::Primary, + violation: RoleBuilderValidation::NotYetValid( + NotYetValidReason::RoleMustHaveAtLeastOneFactor + ) + }) + ) +} + +#[test] +fn empty_recovery_is_err() { + let mut sut = make(); + sut.add_factor_source_to_primary_override(FactorSourceID::sample_ledger()) + .unwrap(); + let res = sut.build(); + assert_eq!( + res, + MatrixBuilderBuildResult::Err(MatrixBuilderValidation::RoleInIsolation { + role: RoleKind::Recovery, + violation: RoleBuilderValidation::NotYetValid( + NotYetValidReason::RoleMustHaveAtLeastOneFactor + ) + }) + ) +} + +#[test] +fn empty_confirmation_is_err() { + let mut sut = make(); + sut.add_factor_source_to_primary_override(FactorSourceID::sample_ledger()) + .unwrap(); + + sut.add_factor_source_to_recovery_override(FactorSourceID::sample_arculus()) + .unwrap(); + let res = sut.build(); + assert_eq!( + res, + MatrixBuilderBuildResult::Err(MatrixBuilderValidation::RoleInIsolation { + role: RoleKind::Confirmation, + violation: RoleBuilderValidation::NotYetValid( + NotYetValidReason::RoleMustHaveAtLeastOneFactor + ) + }) + ) +} + +#[test] +fn set_number_of_days_cannot_be_zero() { + let mut sut = make(); + + // Primary + sut.add_factor_source_to_primary_threshold(FactorSourceID::sample_device()) + .unwrap(); + + sut.set_threshold(1).unwrap(); + + // Recovery + sut.add_factor_source_to_recovery_override(FactorSourceID::sample_ledger()) + .unwrap(); + + // Confirmation + sut.add_factor_source_to_confirmation_override(FactorSourceID::sample_password()) + .unwrap(); + + sut.number_of_days_until_auto_confirm = 0; // bypass validation + + // Build + let validation = MatrixBuilderValidation::CombinationViolation( + MatrixRolesInCombinationViolation::Basic(MatrixRolesInCombinationBasicViolation::NumberOfDaysUntilAutoConfirmMustBeGreaterThanZero) + ); + assert_eq!(sut.validate(), Err(validation)); + let res = sut.build(); + assert_eq!(res, Err(validation)); +} + +#[test] +fn set_number_of_days_42() { + let mut sut = make(); + + // Primary + sut.add_factor_source_to_primary_threshold(FactorSourceID::sample_device()) + .unwrap(); + + sut.set_threshold(1).unwrap(); + + // Recovery + sut.add_factor_source_to_recovery_override(FactorSourceID::sample_ledger()) + .unwrap(); + + // Confirmation + sut.add_factor_source_to_confirmation_override(FactorSourceID::sample_password()) + .unwrap(); + + sut.set_number_of_days_until_auto_confirm(42).unwrap(); + + // Build + assert!(sut.validate().is_ok()); + let built = sut.build().unwrap(); + pretty_assertions::assert_eq!( + built, + MatrixWithFactorSourceIds::with_roles_and_days( + RoleWithFactorSourceIds::primary_with_factors( + 1, + [FactorSourceID::sample_device(),], + [], + ), + RoleWithFactorSourceIds::recovery_with_factors([FactorSourceID::sample_ledger(),],), + RoleWithFactorSourceIds::confirmation_with_factors([FactorSourceID::sample_password()],), + 42, + ) + ); +} + +#[test] +fn auto_confirm_default() { + assert_eq!(SUT::DEFAULT_NUMBER_OF_DAYS_UNTIL_AUTO_CONFIRM, 14); +} + +#[test] +fn set_number_of_days_if_not_set_uses_default() { + let mut sut = make(); + + // Primary + sut.add_factor_source_to_primary_threshold(FactorSourceID::sample_device()) + .unwrap(); + + sut.set_threshold(1).unwrap(); + + // Recovery + sut.add_factor_source_to_recovery_override(FactorSourceID::sample_ledger()) + .unwrap(); + + // Confirmation + sut.add_factor_source_to_confirmation_override(FactorSourceID::sample_password()) + .unwrap(); + + // Build + assert!(sut.validate().is_ok()); + let built = sut.build().unwrap(); + pretty_assertions::assert_eq!( + built, + MatrixWithFactorSourceIds::with_roles_and_days( + RoleWithFactorSourceIds::primary_with_factors( + 1, + [FactorSourceID::sample_device(),], + [], + ), + RoleWithFactorSourceIds::recovery_with_factors([FactorSourceID::sample_ledger(),],), + RoleWithFactorSourceIds::confirmation_with_factors([FactorSourceID::sample_password()],), + SUT::DEFAULT_NUMBER_OF_DAYS_UNTIL_AUTO_CONFIRM, + ) + ); +} + +#[test] +fn sample_factor_cannot_be_both_in_threshold_and_override() { + let mut sut = make(); + let fs = FactorSourceID::sample_ledger(); + sut.add_factor_source_to_primary_override(fs).unwrap(); + let res = sut.add_factor_source_to_primary_override(fs); + assert!(res.is_err()); +} + +#[test] +fn single_factor_in_primary_threshold_cannot_be_in_recovery() { + let mut sut = make(); + let fs = FactorSourceID::sample_ledger(); + sut.add_factor_source_to_primary_threshold(fs).unwrap(); + sut.set_threshold(1).unwrap(); + + let res = sut.add_factor_source_to_recovery_override(fs); + assert_eq!(res, Err(MatrixBuilderValidation::CombinationViolation( + MatrixRolesInCombinationViolation::NotYetValid(MatrixRolesInCombinationNotYetValid::SingleFactorUsedInPrimaryMustNotBeUsedInAnyOtherRole) + ))); + + sut.add_factor_source_to_primary_threshold(FactorSourceID::sample_arculus()) + .unwrap(); + sut.add_factor_source_to_confirmation_override(FactorSourceID::sample_arculus_other()) + .unwrap(); + let built = sut.build().unwrap(); + pretty_assertions::assert_eq!( + built.primary(), + &RoleWithFactorSourceIds::primary_with_factors( + 1, + [ + FactorSourceID::sample_ledger(), + FactorSourceID::sample_arculus() + ], + [] + ) + ); + pretty_assertions::assert_eq!( + built.recovery(), + &RoleWithFactorSourceIds::recovery_with_factors([FactorSourceID::sample_ledger()]), + ); + + pretty_assertions::assert_eq!( + built.confirmation(), + &RoleWithFactorSourceIds::confirmation_with_factors([ + FactorSourceID::sample_arculus_other() + ]) + ) +} + +#[test] +fn single_factor_in_primary_override_cannot_be_in_recovery() { + let mut sut = make(); + let fs = FactorSourceID::sample_ledger(); + sut.add_factor_source_to_primary_override(fs).unwrap(); + let res = sut.add_factor_source_to_recovery_override(fs); + assert_eq!(res, Err(MatrixBuilderValidation::CombinationViolation( + MatrixRolesInCombinationViolation::NotYetValid(MatrixRolesInCombinationNotYetValid::SingleFactorUsedInPrimaryMustNotBeUsedInAnyOtherRole) + ))); +} + +#[test] +fn single_factor_in_primary_threshold_cannot_be_in_confirmation() { + let mut sut = make(); + let fs = FactorSourceID::sample_ledger(); + sut.add_factor_source_to_primary_threshold(fs).unwrap(); + let res = sut.add_factor_source_to_confirmation_override(fs); + assert_eq!(res, Err(MatrixBuilderValidation::CombinationViolation( + MatrixRolesInCombinationViolation::NotYetValid(MatrixRolesInCombinationNotYetValid::SingleFactorUsedInPrimaryMustNotBeUsedInAnyOtherRole) + ))); +} + +#[test] +fn single_factor_in_primary_override_cannot_be_in_confirmation() { + let mut sut = make(); + let fs = FactorSourceID::sample_ledger(); + sut.add_factor_source_to_primary_override(fs).unwrap(); + let res = sut.add_factor_source_to_confirmation_override(fs); + assert_eq!(res, Err(MatrixBuilderValidation::CombinationViolation( + MatrixRolesInCombinationViolation::NotYetValid(MatrixRolesInCombinationNotYetValid::SingleFactorUsedInPrimaryMustNotBeUsedInAnyOtherRole) + ))); +} + +#[test] +fn add_factor_to_recovery_then_same_to_confirmation_is_err() { + let mut sut = make(); + let fs = FactorSourceID::sample_ledger(); + sut.add_factor_source_to_confirmation_override(fs).unwrap(); + let res = sut.add_factor_source_to_recovery_override(fs); + assert_eq!( + res, + Err(MatrixBuilderValidation::CombinationViolation( + MatrixRolesInCombinationViolation::ForeverInvalid( + MatrixRolesInCombinationForeverInvalid::RecoveryAndConfirmationFactorsOverlap + ) + )) + ); +} + +#[test] +fn add_factor_to_confirmation_then_same_to_override_is_err() { + let mut sut = make(); + let fs = FactorSourceID::sample_ledger(); + sut.add_factor_source_to_recovery_override(fs).unwrap(); + let res = sut.add_factor_source_to_confirmation_override(fs); + assert_eq!( + res, + Err(MatrixBuilderValidation::CombinationViolation( + MatrixRolesInCombinationViolation::ForeverInvalid( + MatrixRolesInCombinationForeverInvalid::RecoveryAndConfirmationFactorsOverlap + ) + )) + ); +} + +#[test] +fn add_factor_to_confirmation_then_same_to_primary_threshold_is_not_yet_valid() { + let mut sut = make(); + let fs = FactorSourceID::sample_ledger(); + sut.add_factor_source_to_recovery_override(fs).unwrap(); + let res = sut.add_factor_source_to_primary_threshold(fs); + assert_eq!(res, Err(MatrixBuilderValidation::CombinationViolation( + MatrixRolesInCombinationViolation::NotYetValid(MatrixRolesInCombinationNotYetValid::SingleFactorUsedInPrimaryMustNotBeUsedInAnyOtherRole) + ))); +} + +mod remove { + use super::*; + + #[test] + fn not_found() { + let mut sut = make(); + let res = sut.remove_factor(&FactorSourceID::sample_device()); + assert_eq!( + res, + Err(MatrixBuilderValidation::CombinationViolation( + MatrixRolesInCombinationViolation::Basic( + MatrixRolesInCombinationBasicViolation::FactorSourceNotFoundInAnyRole + ) + )) + ); + } + + #[test] + fn remove_from_primary_threshold_is_ok() { + let mut sut = make(); + sut.add_factor_source_to_primary_threshold(FactorSourceID::sample_device()) + .unwrap(); + let res = sut.remove_factor(&FactorSourceID::sample_device()); + assert_eq!(res, Ok(())); + } + + #[test] + fn remove_from_primary_override_is_ok() { + let mut sut = make(); + sut.add_factor_source_to_primary_override(FactorSourceID::sample_device()) + .unwrap(); + let res = sut.remove_factor(&FactorSourceID::sample_device()); + assert_eq!(res, Ok(())); + } + + #[test] + fn remove_from_recovery_is_ok() { + let mut sut = make(); + sut.add_factor_source_to_recovery_override(FactorSourceID::sample_device()) + .unwrap(); + let res = sut.remove_factor(&FactorSourceID::sample_device()); + assert_eq!(res, Ok(())); + } + + #[test] + fn remove_from_confirmation_is_ok() { + let mut sut = make(); + sut.add_factor_source_to_confirmation_override(FactorSourceID::sample_device()) + .unwrap(); + let res = sut.remove_factor(&FactorSourceID::sample_device()); + assert_eq!(res, Ok(())); + } +} + +mod validation_for_addition_of_factor_source_for_each { + use super::*; + + mod primary { + + use super::*; + + #[test] + fn empty() { + let sut = make(); + let xs = sut.validation_for_addition_of_factor_source_to_primary_threshold_for_each( + &IndexSet::new(), + ); + assert_eq!(xs, IndexSet::new()); + } + + #[test] + fn device_threshold_3x_first_ok_second_not() { + let mut sut = make(); + let xs = sut.validation_for_addition_of_factor_source_to_primary_threshold_for_each( + &IndexSet::from_iter([ + FactorSourceID::sample_device(), + FactorSourceID::sample_device_other(), + ]), + ); + assert_eq!( + xs.into_iter().collect::>(), + vec![ + FactorSourceInRoleBuilderValidationStatus::ok( + sargon::RoleKind::Primary, + FactorSourceID::sample_device() + ), + FactorSourceInRoleBuilderValidationStatus::ok( + sargon::RoleKind::Primary, + FactorSourceID::sample_device_other(), + ) + ] + ); + + sut.add_factor_source_to_primary_threshold(FactorSourceID::sample_device()) + .unwrap(); + + let xs = sut.validation_for_addition_of_factor_source_to_primary_threshold_for_each( + &IndexSet::from_iter([ + FactorSourceID::sample_device(), + FactorSourceID::sample_device_other(), + ]), + ); + + pretty_assertions::assert_eq!( + xs.into_iter().collect::>(), + vec![ + FactorSourceInRoleBuilderValidationStatus::forever_invalid( + sargon::RoleKind::Primary, + FactorSourceID::sample_device(), + ForeverInvalidReason::FactorSourceAlreadyPresent + ), + FactorSourceInRoleBuilderValidationStatus::forever_invalid( + sargon::RoleKind::Primary, + FactorSourceID::sample_device_other(), + ForeverInvalidReason::PrimaryCannotHaveMultipleDevices + ), + ] + ); + } + + #[test] + fn device_2x_threshold_override_first_ok_second_not() { + let mut sut = make(); + let xs = sut.validation_for_addition_of_factor_source_to_primary_threshold_for_each( + &IndexSet::from_iter([ + FactorSourceID::sample_device(), + FactorSourceID::sample_device_other(), + ]), + ); + assert_eq!( + xs.into_iter().collect::>(), + vec![ + FactorSourceInRoleBuilderValidationStatus::ok( + sargon::RoleKind::Primary, + FactorSourceID::sample_device() + ), + FactorSourceInRoleBuilderValidationStatus::ok( + sargon::RoleKind::Primary, + FactorSourceID::sample_device_other(), + ) + ] + ); + + sut.add_factor_source_to_primary_threshold(FactorSourceID::sample_device()) + .unwrap(); + + let xs = sut.validation_for_addition_of_factor_source_to_primary_override_for_each( + &IndexSet::from_iter([ + FactorSourceID::sample_device(), + FactorSourceID::sample_device_other(), + ]), + ); + + pretty_assertions::assert_eq!( + xs.into_iter().collect::>(), + vec![ + FactorSourceInRoleBuilderValidationStatus::forever_invalid( + sargon::RoleKind::Primary, + FactorSourceID::sample_device(), + ForeverInvalidReason::FactorSourceAlreadyPresent + ), + FactorSourceInRoleBuilderValidationStatus::forever_invalid( + sargon::RoleKind::Primary, + FactorSourceID::sample_device_other(), + ForeverInvalidReason::PrimaryCannotHaveMultipleDevices + ), + ] + ); + } + + #[test] + fn device_threshold_override_2x_first_ok_second_not() { + let mut sut = make(); + let xs = sut.validation_for_addition_of_factor_source_to_primary_threshold_for_each( + &IndexSet::from_iter([ + FactorSourceID::sample_device(), + FactorSourceID::sample_device_other(), + ]), + ); + assert_eq!( + xs.into_iter().collect::>(), + vec![ + FactorSourceInRoleBuilderValidationStatus::ok( + sargon::RoleKind::Primary, + FactorSourceID::sample_device() + ), + FactorSourceInRoleBuilderValidationStatus::ok( + sargon::RoleKind::Primary, + FactorSourceID::sample_device_other(), + ) + ] + ); + + sut.add_factor_source_to_primary_override(FactorSourceID::sample_device()) + .unwrap(); + + let xs = sut.validation_for_addition_of_factor_source_to_primary_override_for_each( + &IndexSet::from_iter([ + FactorSourceID::sample_device(), + FactorSourceID::sample_device_other(), + ]), + ); + + pretty_assertions::assert_eq!( + xs.into_iter().collect::>(), + vec![ + FactorSourceInRoleBuilderValidationStatus::forever_invalid( + sargon::RoleKind::Primary, + FactorSourceID::sample_device(), + ForeverInvalidReason::FactorSourceAlreadyPresent + ), + FactorSourceInRoleBuilderValidationStatus::forever_invalid( + sargon::RoleKind::Primary, + FactorSourceID::sample_device_other(), + ForeverInvalidReason::PrimaryCannotHaveMultipleDevices + ), + ] + ); + } + + #[test] + fn device_2x_override_threshold_first_ok_second_not() { + let mut sut = make(); + let xs = sut.validation_for_addition_of_factor_source_to_primary_override_for_each( + &IndexSet::from_iter([ + FactorSourceID::sample_device(), + FactorSourceID::sample_device_other(), + ]), + ); + assert_eq!( + xs.into_iter().collect::>(), + vec![ + FactorSourceInRoleBuilderValidationStatus::ok( + sargon::RoleKind::Primary, + FactorSourceID::sample_device() + ), + FactorSourceInRoleBuilderValidationStatus::ok( + sargon::RoleKind::Primary, + FactorSourceID::sample_device_other(), + ) + ] + ); + + sut.add_factor_source_to_primary_override(FactorSourceID::sample_device()) + .unwrap(); + + let xs = sut.validation_for_addition_of_factor_source_to_primary_threshold_for_each( + &IndexSet::from_iter([ + FactorSourceID::sample_device(), + FactorSourceID::sample_device_other(), + ]), + ); + + pretty_assertions::assert_eq!( + xs.into_iter().collect::>(), + vec![ + FactorSourceInRoleBuilderValidationStatus::forever_invalid( + sargon::RoleKind::Primary, + FactorSourceID::sample_device(), + ForeverInvalidReason::FactorSourceAlreadyPresent + ), + FactorSourceInRoleBuilderValidationStatus::forever_invalid( + sargon::RoleKind::Primary, + FactorSourceID::sample_device_other(), + ForeverInvalidReason::PrimaryCannotHaveMultipleDevices + ), + ] + ); + } + } + + mod recovery { + use super::*; + + fn role() -> sargon::RoleKind { + sargon::RoleKind::Recovery + } + + #[test] + fn empty() { + let sut = make(); + let xs = sut.validation_for_addition_of_factor_source_to_recovery_override_for_each( + &IndexSet::new(), + ); + assert_eq!(xs, IndexSet::new()); + } + + #[test] + fn supported() { + let sut = make(); + let fsids = vec![ + FactorSourceID::sample_device(), + FactorSourceID::sample_device_other(), + FactorSourceID::sample_ledger(), + FactorSourceID::sample_ledger_other(), + FactorSourceID::sample_arculus(), + FactorSourceID::sample_arculus_other(), + FactorSourceID::sample_passphrase(), + FactorSourceID::sample_passphrase_other(), + FactorSourceID::sample_trusted_contact(), + FactorSourceID::sample_trusted_contact_other(), + ]; + let xs = sut.validation_for_addition_of_factor_source_to_recovery_override_for_each( + &IndexSet::from_iter(fsids.clone()), + ); + pretty_assertions::assert_eq!( + xs.into_iter().collect::>(), + fsids + .into_iter() + .map(|fsid| FactorSourceInRoleBuilderValidationStatus::ok(role(), fsid)) + .collect::>() + ); + } + + #[test] + fn password_and_security_questions_not_supported() { + let sut = make(); + let xs = sut.validation_for_addition_of_factor_source_to_recovery_override_for_each( + &IndexSet::from_iter([ + FactorSourceID::sample_password(), + FactorSourceID::sample_password_other(), + FactorSourceID::sample_security_questions(), + FactorSourceID::sample_security_questions_other(), + ]), + ); + pretty_assertions::assert_eq!( + xs.into_iter().collect::>(), + [ + FactorSourceID::sample_password(), + FactorSourceID::sample_password_other(), + FactorSourceID::sample_security_questions(), + FactorSourceID::sample_security_questions_other(), + ] + .into_iter() + .map( + |fsid| FactorSourceInRoleBuilderValidationStatus::forever_invalid( + role(), + fsid, + if fsid.get_factor_source_kind() == FactorSourceKind::SecurityQuestions { + ForeverInvalidReason::RecoveryRoleSecurityQuestionsNotSupported + } else { + ForeverInvalidReason::RecoveryRolePasswordNotSupported + } + ) + ) + .collect::>() + ); + } + } + + mod confirmation { + use super::*; + + fn role() -> sargon::RoleKind { + sargon::RoleKind::Confirmation + } + + #[test] + fn empty() { + let sut = make(); + let xs = sut + .validation_for_addition_of_factor_source_to_confirmation_override_for_each( + &IndexSet::new(), + ); + assert_eq!(xs, IndexSet::new()); + } + + #[test] + fn supported() { + let sut = make(); + let fsids = vec![ + FactorSourceID::sample_device(), + FactorSourceID::sample_device_other(), + FactorSourceID::sample_ledger(), + FactorSourceID::sample_ledger_other(), + FactorSourceID::sample_arculus(), + FactorSourceID::sample_arculus_other(), + FactorSourceID::sample_security_questions(), + FactorSourceID::sample_security_questions_other(), + FactorSourceID::sample_password(), + FactorSourceID::sample_password_other(), + FactorSourceID::sample_passphrase(), + FactorSourceID::sample_passphrase_other(), + ]; + let xs = sut + .validation_for_addition_of_factor_source_to_confirmation_override_for_each( + &IndexSet::from_iter(fsids.clone()), + ); + pretty_assertions::assert_eq!( + xs.into_iter().collect::>(), + fsids + .into_iter() + .map(|fsid| FactorSourceInRoleBuilderValidationStatus::ok(role(), fsid)) + .collect::>() + ); + } + + #[test] + fn password_and_security_questions_not_supported() { + let sut = make(); + let xs = sut + .validation_for_addition_of_factor_source_to_confirmation_override_for_each( + &IndexSet::from_iter([ + FactorSourceID::sample_trusted_contact(), + FactorSourceID::sample_trusted_contact_other(), + ]), + ); + pretty_assertions::assert_eq!( + xs.into_iter().collect::>(), + [ + FactorSourceID::sample_trusted_contact(), + FactorSourceID::sample_trusted_contact_other(), + ] + .into_iter() + .map( + |fsid| FactorSourceInRoleBuilderValidationStatus::forever_invalid( + role(), + fsid, + ForeverInvalidReason::ConfirmationRoleTrustedContactNotSupported + ) + ) + .collect::>() + ); + } + } +} + +mod validation_of_addition_of_kind { + use super::*; + + mod recovery { + use super::*; + + #[test] + fn validation_for_addition_of_factor_source_of_kind_to_recovery_override_empty() { + let sut = make(); + let test = |kind: FactorSourceKind, should_be_ok: bool| { + let is_ok = sut + .validation_for_addition_of_factor_source_of_kind_to_recovery_override(kind) + .is_ok(); + assert_eq!(is_ok, should_be_ok); + }; + test(FactorSourceKind::Device, true); + test(FactorSourceKind::LedgerHQHardwareWallet, true); + test(FactorSourceKind::ArculusCard, true); + test(FactorSourceKind::SecurityQuestions, false); + test(FactorSourceKind::Passphrase, false); + test(FactorSourceKind::OffDeviceMnemonic, true); + test(FactorSourceKind::TrustedContact, true); + } + + #[test] + fn validation_for_addition_of_factor_source_of_kind_to_recovery_override_single_recovery() { + let mut sut = make(); + sut.add_factor_source_to_recovery_override(FactorSourceID::sample_ledger()) + .unwrap(); + let test = |kind: FactorSourceKind, should_be_ok: bool| { + let is_ok = sut + .validation_for_addition_of_factor_source_of_kind_to_recovery_override(kind) + .is_ok(); + assert_eq!(is_ok, should_be_ok); + }; + test(FactorSourceKind::Device, true); + test(FactorSourceKind::LedgerHQHardwareWallet, true); + test(FactorSourceKind::ArculusCard, true); + test(FactorSourceKind::SecurityQuestions, false); + test(FactorSourceKind::Passphrase, false); + test(FactorSourceKind::OffDeviceMnemonic, true); + test(FactorSourceKind::TrustedContact, true); + } + } + + mod confirmation { + use super::*; + + #[test] + fn validation_for_addition_of_factor_source_of_kind_to_confirmation_override_empty() { + let sut = make(); + let test = |kind: FactorSourceKind, should_be_ok: bool| { + let is_ok = sut + .validation_for_addition_of_factor_source_of_kind_to_confirmation_override(kind) + .is_ok(); + assert_eq!(is_ok, should_be_ok); + }; + test(FactorSourceKind::Device, true); + test(FactorSourceKind::LedgerHQHardwareWallet, true); + test(FactorSourceKind::ArculusCard, true); + test(FactorSourceKind::SecurityQuestions, true); + test(FactorSourceKind::Passphrase, true); + test(FactorSourceKind::OffDeviceMnemonic, true); + test(FactorSourceKind::TrustedContact, false); + } + + #[test] + fn validation_for_addition_of_factor_source_of_kind_to_confirmation_override_single_recovery( + ) { + let mut sut = make(); + sut.add_factor_source_to_confirmation_override(FactorSourceID::sample_ledger()) + .unwrap(); + let test = |kind: FactorSourceKind, should_be_ok: bool| { + let is_ok = sut + .validation_for_addition_of_factor_source_of_kind_to_confirmation_override(kind) + .is_ok(); + assert_eq!(is_ok, should_be_ok); + }; + test(FactorSourceKind::Device, true); + test(FactorSourceKind::LedgerHQHardwareWallet, true); + test(FactorSourceKind::ArculusCard, true); + test(FactorSourceKind::SecurityQuestions, true); + test(FactorSourceKind::Passphrase, true); + test(FactorSourceKind::OffDeviceMnemonic, true); + test(FactorSourceKind::TrustedContact, false); + } + } + + mod primary { + use super::*; + + #[test] + fn ledger_threshold_threshold() { + // ARRANGE + let mut sut = make(); + sut.add_factor_source_to_primary_threshold(FactorSourceID::sample_ledger()) + .unwrap(); + + // ASSERT + let res = sut.validation_for_addition_of_factor_source_of_kind_to_primary_threshold( + FactorSourceKind::LedgerHQHardwareWallet, + ); + assert!(res.is_ok()); + } + + #[test] + fn ledger_threshold_override() { + // ARRANGE + let mut sut = make(); + sut.add_factor_source_to_primary_threshold(FactorSourceID::sample_ledger()) + .unwrap(); + + // ASSERT + let res = sut.validation_for_addition_of_factor_source_of_kind_to_primary_override( + FactorSourceKind::LedgerHQHardwareWallet, + ); + assert!(res.is_ok()); + } + + #[test] + fn ledger_override_override() { + // ARRANGE + let mut sut = make(); + sut.add_factor_source_to_primary_override(FactorSourceID::sample_ledger()) + .unwrap(); + + // ASSERT + let res = sut.validation_for_addition_of_factor_source_of_kind_to_primary_override( + FactorSourceKind::LedgerHQHardwareWallet, + ); + assert!(res.is_ok()); + } + + #[test] + fn ledger_override_threshold() { + // ARRANGE + let mut sut = make(); + sut.add_factor_source_to_primary_override(FactorSourceID::sample_ledger()) + .unwrap(); + + // ASSERT + let res = sut.validation_for_addition_of_factor_source_of_kind_to_primary_threshold( + FactorSourceKind::LedgerHQHardwareWallet, + ); + assert!(res.is_ok()); + } + + #[test] + fn arculus_threshold_threshold() { + // ARRANGE + let mut sut = make(); + sut.add_factor_source_to_primary_threshold(FactorSourceID::sample_arculus()) + .unwrap(); + + // ASSERT + let res = sut.validation_for_addition_of_factor_source_of_kind_to_primary_threshold( + FactorSourceKind::ArculusCard, + ); + assert!(res.is_ok()); + } + + #[test] + fn arculus_threshold_override() { + // ARRANGE + let mut sut = make(); + sut.add_factor_source_to_primary_threshold(FactorSourceID::sample_arculus()) + .unwrap(); + + // ASSERT + let res = sut.validation_for_addition_of_factor_source_of_kind_to_primary_override( + FactorSourceKind::ArculusCard, + ); + assert!(res.is_ok()); + } + + #[test] + fn arculus_override_override() { + // ARRANGE + let mut sut = make(); + sut.add_factor_source_to_primary_override(FactorSourceID::sample_arculus()) + .unwrap(); + + // ASSERT + let res = sut.validation_for_addition_of_factor_source_of_kind_to_primary_override( + FactorSourceKind::ArculusCard, + ); + assert!(res.is_ok()); + } + + #[test] + fn arculus_override_threshold() { + // ARRANGE + let mut sut = make(); + sut.add_factor_source_to_primary_override(FactorSourceID::sample_arculus()) + .unwrap(); + + // ASSERT + let res = sut.validation_for_addition_of_factor_source_of_kind_to_primary_threshold( + FactorSourceKind::ArculusCard, + ); + assert!(res.is_ok()); + } + + #[test] + fn security_questions_not_supported_threshold() { + // ARRANGE + let sut = make(); + + // ASSERT + let res = sut.validation_for_addition_of_factor_source_of_kind_to_primary_threshold( + FactorSourceKind::SecurityQuestions, + ); + assert!(res.is_err()); + } + + #[test] + fn security_questions_not_supported_override() { + // ARRANGE + let sut = make(); + + // ASSERT + let res = sut.validation_for_addition_of_factor_source_of_kind_to_primary_override( + FactorSourceKind::SecurityQuestions, + ); + assert!(res.is_err()); + } + + #[test] + fn trusted_contact_not_supported_threshold() { + // ARRANGE + let sut = make(); + + // ASSERT + let res = sut.validation_for_addition_of_factor_source_of_kind_to_primary_threshold( + FactorSourceKind::TrustedContact, + ); + assert!(res.is_err()); + } + + #[test] + fn trusted_contact_not_supported_override() { + // ARRANGE + let sut = make(); + + // ASSERT + let res = sut.validation_for_addition_of_factor_source_of_kind_to_primary_override( + FactorSourceKind::TrustedContact, + ); + assert!(res.is_err()); + } + + #[test] + fn passphrase_threshold_threshold() { + // ARRANGE + let mut sut = make(); + sut.add_factor_source_to_primary_threshold(FactorSourceID::sample_passphrase()) + .unwrap(); + + // ASSERT + let res = sut.validation_for_addition_of_factor_source_of_kind_to_primary_threshold( + FactorSourceKind::OffDeviceMnemonic, + ); + assert!(res.is_ok()); + } + + #[test] + fn passphrase_threshold_override() { + // ARRANGE + let mut sut = make(); + sut.add_factor_source_to_primary_threshold(FactorSourceID::sample_passphrase()) + .unwrap(); + + // ASSERT + let res = sut.validation_for_addition_of_factor_source_of_kind_to_primary_override( + FactorSourceKind::OffDeviceMnemonic, + ); + assert!(res.is_ok()); + } + + #[test] + fn passphrase_override_override() { + // ARRANGE + let mut sut = make(); + sut.add_factor_source_to_primary_override(FactorSourceID::sample_passphrase()) + .unwrap(); + + // ASSERT + let res = sut.validation_for_addition_of_factor_source_of_kind_to_primary_override( + FactorSourceKind::OffDeviceMnemonic, + ); + assert!(res.is_ok()); + } + + #[test] + fn passphrase_override_threshold() { + // ARRANGE + let mut sut = make(); + sut.add_factor_source_to_primary_override(FactorSourceID::sample_passphrase()) + .unwrap(); + + // ASSERT + let res = sut.validation_for_addition_of_factor_source_of_kind_to_primary_threshold( + FactorSourceKind::OffDeviceMnemonic, + ); + assert!(res.is_ok()); + } + + #[test] + fn thresehold_password_alone_is_err() { + // ARRANGE + let sut = make(); + + // ASSERT + let res = sut.validation_for_addition_of_factor_source_of_kind_to_primary_threshold( + FactorSourceKind::Passphrase, + ); + assert!(res.is_err()); + } + + #[test] + fn thresehold_password_not_alone() { + // ARRANGE + let mut sut = make(); + sut.add_factor_source_to_primary_threshold(FactorSourceID::sample_arculus()) + .unwrap(); + _ = sut.set_threshold(2); + + // ASSERT + let res = sut.validation_for_addition_of_factor_source_of_kind_to_primary_threshold( + FactorSourceKind::Passphrase, + ); + assert!(res.is_ok()); + } + + #[test] + fn device_is_err_for_second_3x_threshold() { + let mut sut = make(); + let res = sut.validation_for_addition_of_factor_source_of_kind_to_primary_threshold( + FactorSourceKind::Device, + ); + assert!(res.is_ok()); + sut.add_factor_source_to_primary_threshold(FactorSourceID::sample_device()) + .unwrap(); + let res = sut.validation_for_addition_of_factor_source_of_kind_to_primary_threshold( + FactorSourceKind::Device, + ); + assert!(res.is_err()); + } + + #[test] + fn device_is_err_for_second_2x_threshold_override() { + let mut sut = make(); + let res = sut.validation_for_addition_of_factor_source_of_kind_to_primary_threshold( + FactorSourceKind::Device, + ); + assert!(res.is_ok()); + sut.add_factor_source_to_primary_threshold(FactorSourceID::sample_device()) + .unwrap(); + let res = sut.validation_for_addition_of_factor_source_of_kind_to_primary_override( + FactorSourceKind::Device, + ); + assert!(res.is_err()); + } + + #[test] + fn device_is_err_for_second_threshold_override_threshold() { + let mut sut = make(); + let res = sut.validation_for_addition_of_factor_source_of_kind_to_primary_threshold( + FactorSourceKind::Device, + ); + assert!(res.is_ok()); + sut.add_factor_source_to_primary_override(FactorSourceID::sample_device()) + .unwrap(); + let res = sut.validation_for_addition_of_factor_source_of_kind_to_primary_threshold( + FactorSourceKind::Device, + ); + assert!(res.is_err()); + } + + #[test] + fn device_is_err_for_second_threshold_override_2x() { + let mut sut = make(); + let res = sut.validation_for_addition_of_factor_source_of_kind_to_primary_threshold( + FactorSourceKind::Device, + ); + assert!(res.is_ok()); + sut.add_factor_source_to_primary_override(FactorSourceID::sample_device()) + .unwrap(); + let res = sut.validation_for_addition_of_factor_source_of_kind_to_primary_override( + FactorSourceKind::Device, + ); + assert!(res.is_err()); + } + + #[test] + fn device_is_err_for_second_3x_override() { + let mut sut = make(); + let res = sut.validation_for_addition_of_factor_source_of_kind_to_primary_override( + FactorSourceKind::Device, + ); + assert!(res.is_ok()); + sut.add_factor_source_to_primary_override(FactorSourceID::sample_device()) + .unwrap(); + let res = sut.validation_for_addition_of_factor_source_of_kind_to_primary_override( + FactorSourceKind::Device, + ); + assert!(res.is_err()); + } + + #[test] + fn device_is_err_for_second_2x_override_threshold() { + let mut sut = make(); + let res = sut.validation_for_addition_of_factor_source_of_kind_to_primary_override( + FactorSourceKind::Device, + ); + assert!(res.is_ok()); + sut.add_factor_source_to_primary_override(FactorSourceID::sample_device()) + .unwrap(); + let res = sut.validation_for_addition_of_factor_source_of_kind_to_primary_threshold( + FactorSourceKind::Device, + ); + assert!(res.is_err()); + } + + #[test] + fn device_is_err_for_second_override_threshold_2x() { + let mut sut = make(); + let res = sut.validation_for_addition_of_factor_source_of_kind_to_primary_override( + FactorSourceKind::Device, + ); + assert!(res.is_ok()); + sut.add_factor_source_to_primary_threshold(FactorSourceID::sample_device()) + .unwrap(); + let res = sut.validation_for_addition_of_factor_source_of_kind_to_primary_threshold( + FactorSourceKind::Device, + ); + assert!(res.is_err()); + } + + #[test] + fn device_is_err_for_second_override_threshold_override() { + let mut sut = make(); + let res = sut.validation_for_addition_of_factor_source_of_kind_to_primary_override( + FactorSourceKind::Device, + ); + assert!(res.is_ok()); + sut.add_factor_source_to_primary_threshold(FactorSourceID::sample_device()) + .unwrap(); + let res = sut.validation_for_addition_of_factor_source_of_kind_to_primary_override( + FactorSourceKind::Device, + ); + assert!(res.is_err()); + } + } +} + +mod shield_configs { + use super::*; + + mod mvp { + + use super::*; + + #[test] + fn config_1_1() { + let mut sut = make(); + + // Primary + sut.add_factor_source_to_primary_threshold(FactorSourceID::sample_device()) + .unwrap(); + sut.add_factor_source_to_primary_threshold(FactorSourceID::sample_ledger()) + .unwrap(); + sut.set_threshold(2).unwrap(); + + // Recovery + sut.add_factor_source_to_recovery_override(FactorSourceID::sample_device()) + .unwrap(); + sut.add_factor_source_to_recovery_override(FactorSourceID::sample_ledger()) + .unwrap(); + + // Confirmation + sut.add_factor_source_to_confirmation_override(FactorSourceID::sample_password()) + .unwrap(); + + // Build + assert!(sut.validate().is_ok()); + let built = sut.build().unwrap(); + pretty_assertions::assert_eq!( + built, + MatrixWithFactorSourceIds::with_roles( + RoleWithFactorSourceIds::primary_with_factors( + 2, + [ + FactorSourceID::sample_device(), + FactorSourceID::sample_ledger() + ], + [], + ), + RoleWithFactorSourceIds::recovery_with_factors([ + FactorSourceID::sample_device(), + FactorSourceID::sample_ledger(), + ],), + RoleWithFactorSourceIds::confirmation_with_factors([ + FactorSourceID::sample_password() + ],), + ) + ); + } + + #[test] + fn config_1_2() { + let mut sut = make(); + + // Primary + sut.add_factor_source_to_primary_threshold(FactorSourceID::sample_ledger()) + .unwrap(); + let res = sut.add_factor_source_to_primary_threshold(FactorSourceID::sample_password()); + + assert_eq!( + res, + Err(MatrixBuilderValidation::RoleInIsolation { role: RoleKind::Primary, violation: RoleBuilderValidation::NotYetValid(NotYetValidReason::PrimaryRoleWithPasswordInThresholdListMustThresholdGreaterThanOne)} + )); + sut.set_threshold(2).unwrap(); + + // Recovery + sut.add_factor_source_to_recovery_override(FactorSourceID::sample_device()) + .unwrap(); + sut.add_factor_source_to_recovery_override(FactorSourceID::sample_ledger()) + .unwrap(); + + // Confirmation + sut.add_factor_source_to_confirmation_override(FactorSourceID::sample_password()) + .unwrap(); + + // Build + assert!(sut.validate().is_ok()); + let built = sut.build().unwrap(); + pretty_assertions::assert_eq!( + built, + MatrixWithFactorSourceIds::with_roles( + RoleWithFactorSourceIds::primary_with_factors( + 2, + [ + FactorSourceID::sample_ledger(), + FactorSourceID::sample_password() + ], + [], + ), + RoleWithFactorSourceIds::recovery_with_factors([ + FactorSourceID::sample_device(), + FactorSourceID::sample_ledger(), + ],), + RoleWithFactorSourceIds::confirmation_with_factors([ + FactorSourceID::sample_password() + ],), + ) + ); + } + + #[test] + fn config_1_3() { + let mut sut = make(); + + // Primary + sut.add_factor_source_to_primary_threshold(FactorSourceID::sample_device()) + .unwrap(); + let res = sut.add_factor_source_to_primary_threshold(FactorSourceID::sample_password()); + + assert_eq!( + res, + Err(MatrixBuilderValidation::RoleInIsolation { role: RoleKind::Primary, violation: RoleBuilderValidation::NotYetValid(NotYetValidReason::PrimaryRoleWithPasswordInThresholdListMustThresholdGreaterThanOne)} + )); + sut.set_threshold(2).unwrap(); + + // Recovery + sut.add_factor_source_to_recovery_override(FactorSourceID::sample_device()) + .unwrap(); + sut.add_factor_source_to_recovery_override(FactorSourceID::sample_ledger()) + .unwrap(); + + // Confirmation + sut.add_factor_source_to_confirmation_override(FactorSourceID::sample_password()) + .unwrap(); + + // Build + assert!(sut.validate().is_ok()); + let built = sut.build().unwrap(); + pretty_assertions::assert_eq!( + built, + MatrixWithFactorSourceIds::with_roles( + RoleWithFactorSourceIds::primary_with_factors( + 2, + [ + FactorSourceID::sample_device(), + FactorSourceID::sample_password() + ], + [], + ), + RoleWithFactorSourceIds::recovery_with_factors([ + FactorSourceID::sample_device(), + FactorSourceID::sample_ledger() + ],), + RoleWithFactorSourceIds::confirmation_with_factors([ + FactorSourceID::sample_password() + ],), + ) + ); + } + + #[test] + fn config_1_4() { + let mut sut = make(); + + // Primary + sut.add_factor_source_to_primary_threshold(FactorSourceID::sample_device()) + .unwrap(); + sut.set_threshold(1).unwrap(); + + // Recovery + sut.add_factor_source_to_recovery_override(FactorSourceID::sample_ledger()) + .unwrap(); + + // Confirmation + sut.add_factor_source_to_confirmation_override(FactorSourceID::sample_password()) + .unwrap(); + + // Build + assert!(sut.validate().is_ok()); + let built = sut.build().unwrap(); + pretty_assertions::assert_eq!( + built, + MatrixWithFactorSourceIds::with_roles( + RoleWithFactorSourceIds::primary_with_factors( + 1, + [FactorSourceID::sample_device(),], + [], + ), + RoleWithFactorSourceIds::recovery_with_factors([ + FactorSourceID::sample_ledger() + ],), + RoleWithFactorSourceIds::confirmation_with_factors([ + FactorSourceID::sample_password() + ],), + ) + ); + } + + #[test] + fn config_1_5() { + let mut sut = make(); + + // Primary + sut.add_factor_source_to_primary_threshold(FactorSourceID::sample_ledger()) + .unwrap(); + sut.set_threshold(1).unwrap(); + + // Recovery + sut.add_factor_source_to_recovery_override(FactorSourceID::sample_device()) + .unwrap(); + + // Confirmation + sut.add_factor_source_to_confirmation_override(FactorSourceID::sample_password()) + .unwrap(); + + // Build + assert!(sut.validate().is_ok()); + let built = sut.build().unwrap(); + pretty_assertions::assert_eq!( + built, + MatrixWithFactorSourceIds::with_roles( + RoleWithFactorSourceIds::primary_with_factors( + 1, + [FactorSourceID::sample_ledger(),], + [], + ), + RoleWithFactorSourceIds::recovery_with_factors([ + FactorSourceID::sample_device() + ],), + RoleWithFactorSourceIds::confirmation_with_factors([ + FactorSourceID::sample_password() + ],), + ) + ); + } + + #[test] + fn config_2_1() { + let mut sut = make(); + + // Primary + sut.add_factor_source_to_primary_threshold(FactorSourceID::sample_device()) + .unwrap(); + sut.add_factor_source_to_primary_threshold(FactorSourceID::sample_ledger()) + .unwrap(); + sut.set_threshold(2).unwrap(); + + // Recovery + sut.add_factor_source_to_recovery_override(FactorSourceID::sample_ledger()) + .unwrap(); + sut.add_factor_source_to_recovery_override(FactorSourceID::sample_ledger_other()) + .unwrap(); + + // Confirmation + sut.add_factor_source_to_confirmation_override(FactorSourceID::sample_device()) + .unwrap(); + + // Build + assert!(sut.validate().is_ok()); + let built = sut.build().unwrap(); + pretty_assertions::assert_eq!( + built, + MatrixWithFactorSourceIds::with_roles( + RoleWithFactorSourceIds::primary_with_factors( + 2, + [ + FactorSourceID::sample_device(), + FactorSourceID::sample_ledger() + ], + [], + ), + RoleWithFactorSourceIds::recovery_with_factors([ + FactorSourceID::sample_ledger(), + FactorSourceID::sample_ledger_other(), + ],), + RoleWithFactorSourceIds::confirmation_with_factors([ + FactorSourceID::sample_device() + ],), + ) + ); + } + + #[test] + fn config_2_2() { + let mut sut = make(); + + // Primary + sut.add_factor_source_to_primary_threshold(FactorSourceID::sample_ledger()) + .unwrap(); + sut.add_factor_source_to_primary_threshold(FactorSourceID::sample_ledger_other()) + .unwrap(); + sut.set_threshold(2).unwrap(); + + // Recovery + sut.add_factor_source_to_recovery_override(FactorSourceID::sample_ledger()) + .unwrap(); + sut.add_factor_source_to_recovery_override(FactorSourceID::sample_ledger_other()) + .unwrap(); + + // Confirmation + sut.add_factor_source_to_confirmation_override(FactorSourceID::sample_device()) + .unwrap(); + + // Build + assert!(sut.validate().is_ok()); + let built = sut.build().unwrap(); + pretty_assertions::assert_eq!( + built, + MatrixWithFactorSourceIds::with_roles( + RoleWithFactorSourceIds::primary_with_factors( + 2, + [ + FactorSourceID::sample_ledger(), + FactorSourceID::sample_ledger_other() + ], + [], + ), + RoleWithFactorSourceIds::recovery_with_factors([ + FactorSourceID::sample_ledger(), + FactorSourceID::sample_ledger_other(), + ],), + RoleWithFactorSourceIds::confirmation_with_factors([ + FactorSourceID::sample_device() + ],), + ) + ); + } + + #[test] + fn config_2_3() { + let mut sut = make(); + + // Primary + // TODO: Ask Matt about this, does he mean Threshold(1) or Override? + sut.add_factor_source_to_primary_override(FactorSourceID::sample_ledger()) + .unwrap(); + + // Recovery + sut.add_factor_source_to_recovery_override(FactorSourceID::sample_ledger_other()) + .unwrap(); + + // Confirmation + sut.add_factor_source_to_confirmation_override(FactorSourceID::sample_device()) + .unwrap(); + + // Build + assert!(sut.validate().is_ok()); + let built = sut.build().unwrap(); + pretty_assertions::assert_eq!( + built, + MatrixWithFactorSourceIds::with_roles( + RoleWithFactorSourceIds::primary_with_factors( + 0, + [], + [FactorSourceID::sample_ledger(),], + ), + RoleWithFactorSourceIds::recovery_with_factors([ + FactorSourceID::sample_ledger_other(), + ],), + RoleWithFactorSourceIds::confirmation_with_factors([ + FactorSourceID::sample_device() + ],), + ) + ); + } + + #[test] + fn config_2_4() { + let mut sut = make(); + + // Primary + // TODO: Ask Matt about this, does he mean Threshold(1) or Override? + sut.add_factor_source_to_primary_override(FactorSourceID::sample_device()) + .unwrap(); + + // Recovery + sut.add_factor_source_to_recovery_override(FactorSourceID::sample_ledger()) + .unwrap(); + + // Confirmation + sut.add_factor_source_to_confirmation_override(FactorSourceID::sample_ledger_other()) + .unwrap(); + + // Build + assert!(sut.validate().is_ok()); + let built = sut.build().unwrap(); + pretty_assertions::assert_eq!( + built, + MatrixWithFactorSourceIds::with_roles( + RoleWithFactorSourceIds::primary_with_factors( + 0, + [], + [FactorSourceID::sample_device(),], + ), + RoleWithFactorSourceIds::recovery_with_factors([ + FactorSourceID::sample_ledger(), + ],), + RoleWithFactorSourceIds::confirmation_with_factors([ + FactorSourceID::sample_ledger_other() + ],), + ) + ); + } + + #[test] + fn config_3() { + let mut sut = make(); + + // Primary + sut.add_factor_source_to_primary_threshold(FactorSourceID::sample_device()) + .unwrap(); + sut.add_factor_source_to_primary_threshold(FactorSourceID::sample_ledger()) + .unwrap(); + sut.set_threshold(2).unwrap(); + + // Recovery + sut.add_factor_source_to_recovery_override(FactorSourceID::sample_ledger()) + .unwrap(); + sut.add_factor_source_to_recovery_override(FactorSourceID::sample_ledger_other()) + .unwrap(); + + // Confirmation + sut.add_factor_source_to_confirmation_override(FactorSourceID::sample_device()) + .unwrap(); + sut.add_factor_source_to_confirmation_override(FactorSourceID::sample_password()) + .unwrap(); + + // Build + assert!(sut.validate().is_ok()); + let built = sut.build().unwrap(); + pretty_assertions::assert_eq!( + built, + MatrixWithFactorSourceIds::with_roles( + RoleWithFactorSourceIds::primary_with_factors( + 2, + [ + FactorSourceID::sample_device(), + FactorSourceID::sample_ledger() + ], + [], + ), + RoleWithFactorSourceIds::recovery_with_factors([ + FactorSourceID::sample_ledger(), + FactorSourceID::sample_ledger_other(), + ],), + RoleWithFactorSourceIds::confirmation_with_factors([ + FactorSourceID::sample_device(), + FactorSourceID::sample_password() + ],), + ) + ); + } + + #[test] + fn config_4() { + let mut sut = make(); + + // Primary + sut.add_factor_source_to_primary_threshold(FactorSourceID::sample_device()) + .unwrap(); + sut.add_factor_source_to_primary_threshold(FactorSourceID::sample_ledger()) + .unwrap(); + sut.set_threshold(2).unwrap(); + + // Recovery + sut.add_factor_source_to_recovery_override(FactorSourceID::sample_device()) + .unwrap(); + sut.add_factor_source_to_recovery_override(FactorSourceID::sample_ledger()) + .unwrap(); + + // Confirmation + sut.add_factor_source_to_confirmation_override(FactorSourceID::sample_password()) + .unwrap(); + sut.add_factor_source_to_confirmation_override(FactorSourceID::sample_password_other()) + .unwrap(); + sut.add_factor_source_to_confirmation_override(FactorSourceID::sample_passphrase()) + .unwrap(); + + // Build + assert!(sut.validate().is_ok()); + let built = sut.build().unwrap(); + pretty_assertions::assert_eq!( + built, + MatrixWithFactorSourceIds::with_roles( + RoleWithFactorSourceIds::primary_with_factors( + 2, + [ + FactorSourceID::sample_device(), + FactorSourceID::sample_ledger() + ], + [], + ), + RoleWithFactorSourceIds::recovery_with_factors([ + FactorSourceID::sample_device(), + FactorSourceID::sample_ledger(), + ],), + RoleWithFactorSourceIds::confirmation_with_factors([ + FactorSourceID::sample_password(), + FactorSourceID::sample_password_other(), + FactorSourceID::sample_passphrase() + ],), + ) + ); + } + } +} diff --git a/crates/rules/src/matrices/builder/mod.rs b/crates/rules/src/matrices/builder/mod.rs new file mode 100644 index 00000000..139df14b --- /dev/null +++ b/crates/rules/src/matrices/builder/mod.rs @@ -0,0 +1,7 @@ +mod error; +mod matrix_builder; +mod matrix_builder_unit_tests; + +pub use error::*; +#[allow(unused_imports)] +pub use matrix_builder::*; diff --git a/crates/rules/src/matrices/matrix_with_factor_source_ids.rs b/crates/rules/src/matrices/matrix_with_factor_source_ids.rs new file mode 100644 index 00000000..af92ca34 --- /dev/null +++ b/crates/rules/src/matrices/matrix_with_factor_source_ids.rs @@ -0,0 +1,51 @@ +use crate::prelude::*; + +pub type MatrixWithFactorSourceIds = AbstractMatrixBuilderOrBuilt; + +#[cfg(test)] +impl MatrixWithFactorSourceIds { + pub(crate) fn with_roles_and_days( + primary: RoleWithFactorSourceIds, + recovery: RoleWithFactorSourceIds, + confirmation: RoleWithFactorSourceIds, + number_of_days_until_auto_confirm: u16, + ) -> Self { + assert_eq!(primary.role(), sargon::RoleKind::Primary); + assert_eq!(recovery.role(), sargon::RoleKind::Recovery); + assert_eq!(confirmation.role(), sargon::RoleKind::Confirmation); + Self { + built: PhantomData, + primary_role: primary, + recovery_role: recovery, + confirmation_role: confirmation, + number_of_days_until_auto_confirm, + } + } + + pub(crate) fn with_roles( + primary: RoleWithFactorSourceIds, + recovery: RoleWithFactorSourceIds, + confirmation: RoleWithFactorSourceIds, + ) -> Self { + Self::with_roles_and_days( + primary, + recovery, + confirmation, + Self::DEFAULT_NUMBER_OF_DAYS_UNTIL_AUTO_CONFIRM, + ) + } +} + +impl MatrixWithFactorSourceIds { + pub fn primary(&self) -> &RoleWithFactorSourceIds { + &self.primary_role + } + + pub fn recovery(&self) -> &RoleWithFactorSourceIds { + &self.recovery_role + } + + pub fn confirmation(&self) -> &RoleWithFactorSourceIds { + &self.confirmation_role + } +} diff --git a/crates/rules/src/matrices/mod.rs b/crates/rules/src/matrices/mod.rs new file mode 100644 index 00000000..f56411f7 --- /dev/null +++ b/crates/rules/src/matrices/mod.rs @@ -0,0 +1,8 @@ +mod abstract_matrix_builder_or_built; +mod builder; +mod matrix_with_factor_source_ids; + +pub(crate) use abstract_matrix_builder_or_built::*; +#[allow(unused_imports)] +pub use builder::*; +pub use matrix_with_factor_source_ids::*; diff --git a/crates/rules/src/move_to_sargon.rs b/crates/rules/src/move_to_sargon.rs new file mode 100644 index 00000000..a10da35d --- /dev/null +++ b/crates/rules/src/move_to_sargon.rs @@ -0,0 +1,92 @@ +use crate::prelude::*; + +/// A kind of factor list, either threshold, or override kind. +#[derive(PartialEq, Eq, Clone, Copy, Debug)] +pub enum FactorListKind { + Threshold, + Override, +} + +/// TODO move to Sargon!!!! +pub trait HasFactorSourceKindObjectSafe { + fn get_factor_source_kind(&self) -> FactorSourceKind; +} +impl HasFactorSourceKindObjectSafe for FactorSourceID { + fn get_factor_source_kind(&self) -> FactorSourceKind { + match self { + FactorSourceID::Hash { value } => value.kind, + FactorSourceID::Address { value } => value.kind, + } + } +} + +#[allow(dead_code)] +// TODO REMOVE once migrated to sargon +pub trait SampleValues: Sized { + fn sample_device() -> Self; + fn sample_device_other() -> Self; + fn sample_ledger() -> Self; + fn sample_ledger_other() -> Self; + fn sample_arculus() -> Self; + fn sample_arculus_other() -> Self; + fn sample_password() -> Self; + fn sample_password_other() -> Self; + fn sample_passphrase() -> Self; + fn sample_passphrase_other() -> Self; + fn sample_security_questions() -> Self; + + fn sample_security_questions_other() -> Self; + fn sample_trusted_contact() -> Self; + fn sample_trusted_contact_other() -> Self; +} + +impl SampleValues for FactorSourceID { + fn sample_device() -> Self { + FactorSourceIDFromHash::sample_device().into() + } + fn sample_ledger() -> Self { + FactorSourceIDFromHash::sample_ledger().into() + } + fn sample_ledger_other() -> Self { + FactorSourceIDFromHash::sample_ledger_other().into() + } + fn sample_arculus() -> Self { + FactorSourceIDFromHash::sample_arculus().into() + } + fn sample_arculus_other() -> Self { + FactorSourceIDFromHash::sample_arculus_other().into() + } + + /// Matt calls `passphrase` "password" + fn sample_password() -> Self { + FactorSourceIDFromHash::sample_passphrase().into() + } + /// Matt calls `passphrase` "password" + fn sample_password_other() -> Self { + FactorSourceIDFromHash::sample_passphrase_other().into() + } + + /// Matt calls `off_device_mnemonic` "passphrase" + fn sample_passphrase() -> Self { + FactorSourceIDFromHash::sample_off_device().into() + } + /// Matt calls `off_device_mnemonic` "passphrase" + fn sample_passphrase_other() -> Self { + FactorSourceIDFromHash::sample_off_device_other().into() + } + fn sample_security_questions() -> Self { + FactorSourceIDFromHash::sample_security_questions().into() + } + fn sample_device_other() -> Self { + FactorSourceIDFromHash::sample_device_other().into() + } + fn sample_security_questions_other() -> Self { + FactorSourceIDFromHash::sample_security_questions_other().into() + } + fn sample_trusted_contact() -> Self { + sargon::FactorSource::sample_trusted_contact_frank().id() + } + fn sample_trusted_contact_other() -> Self { + sargon::FactorSource::sample_trusted_contact_grace().id() + } +} diff --git a/crates/rules/src/roles/abstract_role_builder_or_built.rs b/crates/rules/src/roles/abstract_role_builder_or_built.rs new file mode 100644 index 00000000..7b7e2625 --- /dev/null +++ b/crates/rules/src/roles/abstract_role_builder_or_built.rs @@ -0,0 +1,112 @@ +use std::marker::PhantomData; + +use serde::{Deserialize, Serialize}; + +use crate::prelude::*; + +#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] +pub struct AbstractRoleBuilderOrBuilt { + #[serde(skip)] + #[doc(hidden)] + built: PhantomData, + role: RoleKind, + threshold: u8, + threshold_factors: Vec, + override_factors: Vec, +} + +pub(crate) type AbstractBuiltRoleWithFactor = AbstractRoleBuilderOrBuilt; +pub(crate) type RoleBuilder = AbstractRoleBuilderOrBuilt; + +impl AbstractRoleBuilderOrBuilt { + pub(crate) fn with_factors( + role: RoleKind, + threshold: u8, + threshold_factors: impl IntoIterator, + override_factors: impl IntoIterator, + ) -> Self { + Self { + built: PhantomData, + role, + threshold, + threshold_factors: threshold_factors.into_iter().collect(), + override_factors: override_factors.into_iter().collect(), + } + } +} + +impl RoleBuilder { + pub(crate) fn new(role: RoleKind) -> Self { + Self { + built: PhantomData, + role, + threshold: 0, + threshold_factors: Vec::new(), + override_factors: Vec::new(), + } + } + + pub(crate) fn role(&self) -> RoleKind { + self.role + } + + pub(crate) fn threshold(&self) -> u8 { + self.threshold + } + + pub(crate) fn mut_threshold_factors(&mut self) -> &mut Vec { + &mut self.threshold_factors + } + + pub(crate) fn mut_override_factors(&mut self) -> &mut Vec { + &mut self.override_factors + } + + pub(crate) fn threshold_factors(&self) -> &Vec { + &self.threshold_factors + } + + pub(crate) fn override_factors(&self) -> &Vec { + &self.override_factors + } + + pub(crate) fn factors(&self) -> Vec<&FactorSourceID> { + self.threshold_factors + .iter() + .chain(self.override_factors.iter()) + .collect() + } + + pub(crate) fn unchecked_add_factor_source_to_list( + &mut self, + factor_source_id: FactorSourceID, + factor_list_kind: FactorListKind, + ) { + match factor_list_kind { + FactorListKind::Threshold => self.threshold_factors.push(factor_source_id), + FactorListKind::Override => self.override_factors.push(factor_source_id), + } + } + + pub(crate) fn unchecked_set_threshold(&mut self, threshold: u8) { + self.threshold = threshold; + } +} + +impl AbstractBuiltRoleWithFactor { + pub fn role(&self) -> RoleKind { + self.role + } + + pub fn threshold(&self) -> u8 { + self.threshold + } + + pub fn threshold_factors(&self) -> &Vec { + &self.threshold_factors + } + + pub fn override_factors(&self) -> &Vec { + &self.override_factors + } +} diff --git a/crates/rules/src/roles/builder/mod.rs b/crates/rules/src/roles/builder/mod.rs new file mode 100644 index 00000000..9a1e546e --- /dev/null +++ b/crates/rules/src/roles/builder/mod.rs @@ -0,0 +1,4 @@ +mod roles_builder; +mod roles_builder_unit_tests; + +pub use roles_builder::*; diff --git a/crates/rules/src/roles/builder/roles_builder.rs b/crates/rules/src/roles/builder/roles_builder.rs new file mode 100644 index 00000000..96166613 --- /dev/null +++ b/crates/rules/src/roles/builder/roles_builder.rs @@ -0,0 +1,603 @@ +use crate::prelude::*; + +impl RoleBuilder { + pub fn primary() -> Self { + Self::new(RoleKind::Primary) + } + + pub fn recovery() -> Self { + Self::new(RoleKind::Recovery) + } + + pub fn confirmation() -> Self { + Self::new(RoleKind::Confirmation) + } +} + +#[cfg(test)] +impl RoleWithFactorSourceIds { + pub(crate) fn primary_with_factors( + threshold: u8, + threshold_factors: impl IntoIterator, + override_factors: impl IntoIterator, + ) -> Self { + Self::with_factors( + RoleKind::Primary, + threshold, + threshold_factors, + override_factors, + ) + } + + pub(crate) fn recovery_with_factors( + override_factors: impl IntoIterator, + ) -> Self { + Self::with_factors(RoleKind::Recovery, 0, vec![], override_factors) + } + + pub(crate) fn confirmation_with_factors( + override_factors: impl IntoIterator, + ) -> Self { + Self::with_factors(RoleKind::Confirmation, 0, vec![], override_factors) + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, thiserror::Error)] +pub enum RoleBuilderValidation { + #[error("Basic violation: {0}")] + BasicViolation(#[from] BasicViolation), + + #[error("Forever invalid: {0}")] + ForeverInvalid(#[from] ForeverInvalidReason), + + #[error("Not yet valid: {0}")] + NotYetValid(#[from] NotYetValidReason), +} +type Validation = RoleBuilderValidation; + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, thiserror::Error)] +pub enum BasicViolation { + /// e.g. tried to remove a factor source which was not found. + #[error("FactorSourceID not found")] + FactorSourceNotFound, + + #[error("Recovery cannot set threshold")] + RecoveryCannotSetThreshold, + + #[error("Confirmation cannot set threshold")] + ConfirmationCannotSetThreshold, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, thiserror::Error)] +pub enum NotYetValidReason { + #[error("Role must have at least one factor")] + RoleMustHaveAtLeastOneFactor, + + #[error("Primary role with password in threshold list must have another factor")] + PrimaryRoleWithPasswordInThresholdListMustHaveAnotherFactor, + + #[error("Primary role with threshold factors cannot have a threshold of zero")] + PrimaryRoleWithThresholdCannotBeZeroWithFactors, + + #[error("Primary role with password in threshold list must have threshold greater than one")] + PrimaryRoleWithPasswordInThresholdListMustThresholdGreaterThanOne, + + #[error("Threshold higher than threshold factors len")] + ThresholdHigherThanThresholdFactorsLen, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, thiserror::Error)] +pub enum ForeverInvalidReason { + #[error("Factor source already present")] + FactorSourceAlreadyPresent, + + #[error("Primary role cannot have multiple devices")] + PrimaryCannotHaveMultipleDevices, + + #[error("Primary role cannot have password in override list")] + PrimaryCannotHavePasswordInOverrideList, + + #[error("Primary role cannot contain Security Questions")] + PrimaryCannotContainSecurityQuestions, + + #[error("Primary role cannot contain Trusted Contact")] + PrimaryCannotContainTrustedContact, + + #[error("Recovery role threshold list not supported")] + RecoveryRoleThresholdFactorsNotSupported, + + #[error("Recovery role Security Questions not supported")] + RecoveryRoleSecurityQuestionsNotSupported, + + #[error("Recovery role password not supported")] + RecoveryRolePasswordNotSupported, + + #[error("Confirmation role threshold list not supported")] + ConfirmationRoleThresholdFactorsNotSupported, + + #[error("Confirmation role cannot contain Trusted Contact")] + ConfirmationRoleTrustedContactNotSupported, +} + +pub(crate) trait FromForeverInvalid { + fn forever_invalid(reason: ForeverInvalidReason) -> Self; +} +impl FromForeverInvalid for std::result::Result { + fn forever_invalid(reason: ForeverInvalidReason) -> Self { + Err(Validation::ForeverInvalid(reason)) + } +} + +pub(crate) trait FromNotYetValid { + fn not_yet_valid(reason: NotYetValidReason) -> Self; +} +impl FromNotYetValid for std::result::Result { + fn not_yet_valid(reason: NotYetValidReason) -> Self { + Err(Validation::NotYetValid(reason)) + } +} + +pub(crate) trait FromBasicViolation { + fn basic_violation(reason: BasicViolation) -> Self; +} +impl FromBasicViolation for std::result::Result { + fn basic_violation(reason: BasicViolation) -> Self { + Err(Validation::BasicViolation(reason)) + } +} + +impl ForeverInvalidReason { + pub(crate) fn threshold_list_not_supported_for_role(role: RoleKind) -> Self { + match role { + RoleKind::Recovery => Self::RecoveryRoleThresholdFactorsNotSupported, + RoleKind::Confirmation => Self::ConfirmationRoleThresholdFactorsNotSupported, + RoleKind::Primary => { + unreachable!("Primary role DOES support threshold list. This is programmer error.") + } + } + } +} + +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct FactorSourceInRoleBuilderValidationStatus { + pub role: RoleKind, + pub factor_source_id: FactorSourceID, + pub validation: RoleBuilderMutateResult, +} + +impl FactorSourceInRoleBuilderValidationStatus { + pub(crate) fn new( + role: RoleKind, + factor_source_id: FactorSourceID, + validation: RoleBuilderMutateResult, + ) -> Self { + Self { + role, + factor_source_id, + validation, + } + } +} + +#[cfg(test)] +impl FactorSourceInRoleBuilderValidationStatus { + pub(crate) fn ok(role: RoleKind, factor_source_id: FactorSourceID) -> Self { + Self::new(role, factor_source_id, Ok(())) + } + + pub(crate) fn forever_invalid( + role: RoleKind, + factor_source_id: FactorSourceID, + reason: ForeverInvalidReason, + ) -> Self { + Self::new( + role, + factor_source_id, + RoleBuilderMutateResult::forever_invalid(reason), + ) + } + + pub(crate) fn not_yet_valid( + role: RoleKind, + factor_source_id: FactorSourceID, + reason: NotYetValidReason, + ) -> Self { + Self::new( + role, + factor_source_id, + RoleBuilderMutateResult::not_yet_valid(reason), + ) + } +} + +pub type RoleBuilderMutateResult = Result<(), RoleBuilderValidation>; +pub type RoleBuilderBuildResult = Result; + +use BasicViolation::*; +use ForeverInvalidReason::*; +use NotYetValidReason::*; +use RoleKind::*; + +impl RoleBuilder { + pub(crate) fn build(self) -> RoleBuilderBuildResult { + self.validate().map(|_| { + RoleWithFactorSourceIds::with_factors( + self.role(), + self.threshold(), + self.threshold_factors().clone(), + self.override_factors().clone(), + ) + }) + } + + #[allow(dead_code)] + pub(crate) fn set_threshold(&mut self, threshold: u8) -> RoleBuilderMutateResult { + match self.role() { + Primary => { + self.unchecked_set_threshold(threshold); + self.validate() + } + Recovery => RoleBuilderMutateResult::basic_violation(RecoveryCannotSetThreshold), + Confirmation => { + RoleBuilderMutateResult::basic_violation(ConfirmationCannotSetThreshold) + } + } + } + + fn override_contains_factor_source(&self, factor_source_id: &FactorSourceID) -> bool { + self.override_factors().contains(factor_source_id) + } + + fn threshold_contains_factor_source(&self, factor_source_id: &FactorSourceID) -> bool { + self.threshold_factors().contains(factor_source_id) + } + + fn override_contains_factor_source_of_kind( + &self, + factor_source_kind: FactorSourceKind, + ) -> bool { + self.override_factors() + .iter() + .any(|f| f.get_factor_source_kind() == factor_source_kind) + } + + fn threshold_contains_factor_source_of_kind( + &self, + factor_source_kind: FactorSourceKind, + ) -> bool { + self.threshold_factors() + .iter() + .any(|f| f.get_factor_source_kind() == factor_source_kind) + } + + /// If Ok => self is mutated + /// If Err(NotYetValid) => self is mutated + /// If Err(ForeverInvalid) => self is not mutated + pub(crate) fn add_factor_source_to_list( + &mut self, + factor_source_id: FactorSourceID, + factor_list_kind: FactorListKind, + ) -> RoleBuilderMutateResult { + let validation = self + .validation_for_addition_of_factor_source_to_list(&factor_source_id, factor_list_kind); + match validation.as_ref() { + Ok(()) | Err(Validation::NotYetValid(_)) => { + self.unchecked_add_factor_source_to_list(factor_source_id, factor_list_kind); + } + Err(Validation::ForeverInvalid(_)) | Err(Validation::BasicViolation(_)) => {} + } + validation + } + + /// Validates `self` by "replaying" the addition of each factor source in `self` to a + /// "simulation" (clone). If the simulation is valid, then `self` is valid. + pub(crate) fn validate(&self) -> RoleBuilderMutateResult { + let mut simulation = Self::new(self.role()); + + // Validate override factors + for override_factor in self.override_factors() { + let validation = + simulation.add_factor_source_to_list(*override_factor, FactorListKind::Override); + match validation.as_ref() { + Ok(()) | Err(Validation::NotYetValid(_)) => continue, + Err(Validation::ForeverInvalid(_)) | Err(Validation::BasicViolation(_)) => { + return validation + } + } + } + + // Validate threshold factors + for threshold_factor in self.threshold_factors() { + let validation = + simulation.add_factor_source_to_list(*threshold_factor, FactorListKind::Threshold); + match validation.as_ref() { + Ok(()) | Err(Validation::NotYetValid(_)) => continue, + Err(Validation::ForeverInvalid(_)) | Err(Validation::BasicViolation(_)) => { + return validation + } + } + } + + // Validate threshold count + if self.role() == RoleKind::Primary { + if self.threshold_factors().len() < self.threshold() as usize { + return RoleBuilderMutateResult::not_yet_valid( + NotYetValidReason::ThresholdHigherThanThresholdFactorsLen, + ); + } + if self.threshold() == 0 && !self.threshold_factors().is_empty() { + return RoleBuilderMutateResult::not_yet_valid( + NotYetValidReason::PrimaryRoleWithThresholdCannotBeZeroWithFactors, + ); + } + } else if self.threshold() != 0 { + match self.role() { + Primary => unreachable!("Primary role should have been handled earlier"), + Recovery => { + return RoleBuilderMutateResult::basic_violation(RecoveryCannotSetThreshold) + } + Confirmation => { + return RoleBuilderMutateResult::basic_violation(ConfirmationCannotSetThreshold) + } + } + } + + if self.factors().is_empty() { + return RoleBuilderMutateResult::not_yet_valid(RoleMustHaveAtLeastOneFactor); + } + + Ok(()) + } + + /// If we would add a factor of kind `factor_source_kind` to the list of kind `factor_list_kind` + /// what would be the validation status? + pub(crate) fn validation_for_addition_of_factor_source_of_kind_to_list( + &self, + factor_source_kind: FactorSourceKind, + factor_list_kind: FactorListKind, + ) -> RoleBuilderMutateResult { + match self.role() { + RoleKind::Primary => self.validation_for_addition_of_factor_source_of_kind_to_list_for_primary(factor_source_kind, factor_list_kind), + RoleKind::Recovery | RoleKind::Confirmation => match factor_list_kind { + FactorListKind::Threshold => { + RoleBuilderMutateResult::forever_invalid(ForeverInvalidReason::threshold_list_not_supported_for_role(self.role())) + } + FactorListKind::Override => self + .validation_for_addition_of_factor_source_of_kind_to_override_for_non_primary_role( + factor_source_kind, + ), + }, + } + } + + fn validation_for_addition_of_factor_source_of_kind_to_override_for_non_primary_role( + &self, + factor_source_kind: FactorSourceKind, + ) -> RoleBuilderMutateResult { + match self.role() { + RoleKind::Primary => { + unreachable!("Should have branched to 'primary' earlier, this is programmer error.") + } + RoleKind::Confirmation => self + .validation_for_addition_of_factor_source_of_kind_to_override_for_confirmation( + factor_source_kind, + ), + RoleKind::Recovery => self + .validation_for_addition_of_factor_source_of_kind_to_override_for_recovery( + factor_source_kind, + ), + } + } + + #[allow(dead_code)] + /// For each factor source in the given set, return a validation status + /// for adding it to factor list of the given kind (`factor_list_kind`) + pub(crate) fn validation_for_addition_of_factor_source_for_each( + &self, + factor_list_kind: FactorListKind, + factor_sources: &IndexSet, + ) -> IndexSet { + factor_sources + .iter() + .map(|factor_source_id| { + let validation_status = self.validation_for_addition_of_factor_source_to_list( + factor_source_id, + factor_list_kind, + ); + FactorSourceInRoleBuilderValidationStatus::new( + self.role(), + *factor_source_id, + validation_status, + ) + }) + .collect() + } + + fn validation_for_addition_of_factor_source_to_list( + &self, + factor_source_id: &FactorSourceID, + factor_list_kind: FactorListKind, + ) -> RoleBuilderMutateResult { + if self.contains_factor_source(factor_source_id) { + return RoleBuilderMutateResult::forever_invalid(FactorSourceAlreadyPresent); + } + let factor_source_kind = factor_source_id.get_factor_source_kind(); + self.validation_for_addition_of_factor_source_of_kind_to_list( + factor_source_kind, + factor_list_kind, + ) + } + + fn contains_factor_source(&self, factor_source_id: &FactorSourceID) -> bool { + self.override_contains_factor_source(factor_source_id) + || self.threshold_contains_factor_source(factor_source_id) + } + + fn contains_factor_source_of_kind(&self, factor_source_kind: FactorSourceKind) -> bool { + self.override_contains_factor_source_of_kind(factor_source_kind) + || self.threshold_contains_factor_source_of_kind(factor_source_kind) + } + + /// Lowers the threshold if the deleted factor source is in the threshold list + /// and if after removal of `factor_source_id` `self.threshold > self.threshold_factors.len()` + /// + /// Returns `Ok` if `factor_source_id` was found and deleted. However, does not call `self.validate()`, + /// So state might still be invalid, i.e. we return the result of the action of removal, not the + /// state validation status. + pub(crate) fn remove_factor_source( + &mut self, + factor_source_id: &FactorSourceID, + ) -> RoleBuilderMutateResult { + if !self.contains_factor_source(factor_source_id) { + return RoleBuilderMutateResult::basic_violation(FactorSourceNotFound); + } + let remove = |xs: &mut Vec| { + let index = xs + .iter() + .position(|f| f == factor_source_id) + .expect("Called remove of non existing FactorSourceID, this is a programmer error, should have checked if it exists before calling remove."); + xs.remove(index); + }; + + if self.override_contains_factor_source(factor_source_id) { + remove(self.mut_override_factors()) + } + if self.threshold_contains_factor_source(factor_source_id) { + remove(self.mut_threshold_factors()); + let threshold_factors_len = self.threshold_factors().len() as u8; + if self.threshold() > threshold_factors_len { + self.set_threshold(threshold_factors_len)?; + } + } + + Ok(()) + } + + fn validation_for_addition_of_factor_source_of_kind_to_list_for_primary( + &self, + factor_source_kind: FactorSourceKind, + factor_list_kind: FactorListKind, + ) -> RoleBuilderMutateResult { + match factor_source_kind { + FactorSourceKind::Passphrase => { + return self.validation_for_addition_of_password_to_primary(factor_list_kind) + } + FactorSourceKind::SecurityQuestions => { + return RoleBuilderMutateResult::forever_invalid( + PrimaryCannotContainSecurityQuestions, + ); + } + FactorSourceKind::TrustedContact => { + return RoleBuilderMutateResult::forever_invalid( + PrimaryCannotContainTrustedContact, + ); + } + FactorSourceKind::Device => { + if self.contains_factor_source_of_kind(FactorSourceKind::Device) { + return RoleBuilderMutateResult::forever_invalid( + PrimaryCannotHaveMultipleDevices, + ); + } + } + FactorSourceKind::LedgerHQHardwareWallet + | FactorSourceKind::ArculusCard + | FactorSourceKind::OffDeviceMnemonic => {} + } + Ok(()) + } + + fn validation_for_addition_of_factor_source_of_kind_to_override_for_confirmation( + &self, + factor_source_kind: FactorSourceKind, + ) -> RoleBuilderMutateResult { + assert_eq!(self.role(), RoleKind::Confirmation); + match factor_source_kind { + FactorSourceKind::Device + | FactorSourceKind::LedgerHQHardwareWallet + | FactorSourceKind::ArculusCard + | FactorSourceKind::Passphrase + | FactorSourceKind::OffDeviceMnemonic + | FactorSourceKind::SecurityQuestions => Ok(()), + FactorSourceKind::TrustedContact => { + RoleBuilderMutateResult::forever_invalid(ConfirmationRoleTrustedContactNotSupported) + } + } + } + + fn validation_for_addition_of_factor_source_of_kind_to_override_for_recovery( + &self, + factor_source_kind: FactorSourceKind, + ) -> RoleBuilderMutateResult { + assert_eq!(self.role(), RoleKind::Recovery); + match factor_source_kind { + FactorSourceKind::Device + | FactorSourceKind::LedgerHQHardwareWallet + | FactorSourceKind::ArculusCard + | FactorSourceKind::OffDeviceMnemonic + | FactorSourceKind::TrustedContact => Ok(()), + FactorSourceKind::SecurityQuestions => { + RoleBuilderMutateResult::forever_invalid(RecoveryRoleSecurityQuestionsNotSupported) + } + FactorSourceKind::Passphrase => { + RoleBuilderMutateResult::forever_invalid(RecoveryRolePasswordNotSupported) + } + } + } +} + +// ======================= +// ======== RULES ======== +// ======================= +impl RoleBuilder { + fn validation_for_addition_of_password_to_primary( + &self, + factor_list_kind: FactorListKind, + ) -> RoleBuilderMutateResult { + assert_eq!(self.role(), RoleKind::Primary); + let factor_source_kind = FactorSourceKind::Passphrase; + match factor_list_kind { + FactorListKind::Threshold => { + let is_alone = self + .factor_sources_not_of_kind_to_list_of_kind( + factor_source_kind, + FactorListKind::Threshold, + ) + .is_empty(); + if is_alone { + return RoleBuilderMutateResult::not_yet_valid( + PrimaryRoleWithPasswordInThresholdListMustHaveAnotherFactor, + ); + } + if self.threshold() < 2 { + return RoleBuilderMutateResult::not_yet_valid( + PrimaryRoleWithPasswordInThresholdListMustThresholdGreaterThanOne, + ); + } + } + FactorListKind::Override => { + return RoleBuilderMutateResult::forever_invalid( + PrimaryCannotHavePasswordInOverrideList, + ); + } + } + + Ok(()) + } + + pub(crate) fn factor_sources_not_of_kind_to_list_of_kind( + &self, + factor_source_kind: FactorSourceKind, + factor_list_kind: FactorListKind, + ) -> Vec { + let filter = |xs: &Vec| -> Vec { + xs.iter() + .filter(|f| f.get_factor_source_kind() != factor_source_kind) + .cloned() + .collect() + }; + match factor_list_kind { + FactorListKind::Override => filter(self.override_factors()), + FactorListKind::Threshold => filter(self.threshold_factors()), + } + } +} diff --git a/crates/rules/src/roles/builder/roles_builder_unit_tests.rs b/crates/rules/src/roles/builder/roles_builder_unit_tests.rs new file mode 100644 index 00000000..91f44676 --- /dev/null +++ b/crates/rules/src/roles/builder/roles_builder_unit_tests.rs @@ -0,0 +1,1825 @@ +#![cfg(test)] + +use crate::prelude::*; + +use NotYetValidReason::*; +type Validation = RoleBuilderValidation; + +#[allow(clippy::upper_case_acronyms)] +type SUT = RoleBuilder; +type MutRes = RoleBuilderMutateResult; +type BuildRes = RoleBuilderBuildResult; + +mod test_helper_functions { + + use super::*; + + #[test] + fn factor_sources_not_of_kind_to_list_of_kind_in_override() { + let mut sut = SUT::primary(); + sut.add_factor_source_to_list(FactorSourceID::sample_device(), FactorListKind::Override) + .unwrap(); + sut.add_factor_source_to_list(FactorSourceID::sample_ledger(), FactorListKind::Override) + .unwrap(); + sut.add_factor_source_to_list(FactorSourceID::sample_arculus(), FactorListKind::Override) + .unwrap(); + + let xs = sut.factor_sources_not_of_kind_to_list_of_kind( + FactorSourceKind::Device, + FactorListKind::Override, + ); + assert_eq!( + xs, + vec![ + FactorSourceID::sample_ledger(), + FactorSourceID::sample_arculus() + ] + ); + + let xs = sut.factor_sources_not_of_kind_to_list_of_kind( + FactorSourceKind::LedgerHQHardwareWallet, + FactorListKind::Override, + ); + assert_eq!( + xs, + vec![ + FactorSourceID::sample_device(), + FactorSourceID::sample_arculus() + ] + ); + + let xs = sut.factor_sources_not_of_kind_to_list_of_kind( + FactorSourceKind::ArculusCard, + FactorListKind::Override, + ); + assert_eq!( + xs, + vec![ + FactorSourceID::sample_device(), + FactorSourceID::sample_ledger() + ] + ); + } + + #[test] + fn factor_sources_not_of_kind_to_list_of_kind_in_threshold() { + let mut sut = SUT::primary(); + sut.add_factor_source_to_list(FactorSourceID::sample_device(), FactorListKind::Threshold) + .unwrap(); + sut.add_factor_source_to_list(FactorSourceID::sample_ledger(), FactorListKind::Threshold) + .unwrap(); + sut.add_factor_source_to_list(FactorSourceID::sample_arculus(), FactorListKind::Threshold) + .unwrap(); + + let xs = sut.factor_sources_not_of_kind_to_list_of_kind( + FactorSourceKind::Device, + FactorListKind::Threshold, + ); + assert_eq!( + xs, + vec![ + FactorSourceID::sample_ledger(), + FactorSourceID::sample_arculus() + ] + ); + + let xs = sut.factor_sources_not_of_kind_to_list_of_kind( + FactorSourceKind::LedgerHQHardwareWallet, + FactorListKind::Threshold, + ); + assert_eq!( + xs, + vec![ + FactorSourceID::sample_device(), + FactorSourceID::sample_arculus() + ] + ); + + let xs = sut.factor_sources_not_of_kind_to_list_of_kind( + FactorSourceKind::ArculusCard, + FactorListKind::Threshold, + ); + assert_eq!( + xs, + vec![ + FactorSourceID::sample_device(), + FactorSourceID::sample_ledger() + ] + ); + } +} + +fn test_duplicates_not_allowed(sut: SUT, list: FactorListKind, factor_source_id: FactorSourceID) { + // Arrange + let mut sut = sut; + + sut.add_factor_source_to_list(factor_source_id, list) + .unwrap(); + + // Act + let res = sut.add_factor_source_to_list( + factor_source_id, // oh no, duplicate! + list, + ); + + // Assert + assert!(matches!( + res, + MutRes::Err(Validation::ForeverInvalid( + ForeverInvalidReason::FactorSourceAlreadyPresent + )) + )); +} + +#[test] +fn new_builders() { + assert_eq!(SUT::primary().role(), RoleKind::Primary); + assert_eq!(SUT::recovery().role(), RoleKind::Recovery); + assert_eq!(SUT::confirmation().role(), RoleKind::Confirmation); +} + +#[test] +fn empty_is_err() { + [ + RoleKind::Primary, + RoleKind::Recovery, + RoleKind::Confirmation, + ] + .iter() + .for_each(|role| { + let sut = SUT::new(*role); + let res = sut.build(); + assert_eq!( + res, + BuildRes::not_yet_valid(NotYetValidReason::RoleMustHaveAtLeastOneFactor) + ); + }); +} + +#[test] +fn validate_override_for_ever_invalid() { + let sut = SUT::with_factors( + RoleKind::Primary, + 0, + vec![], + vec![ + FactorSourceID::sample_ledger(), + FactorSourceID::sample_ledger(), + ], + ); + let res = sut.validate(); + assert_eq!( + res, + MutRes::forever_invalid(ForeverInvalidReason::FactorSourceAlreadyPresent) + ); +} + +#[test] +fn validate_threshold_for_ever_invalid() { + let sut = SUT::with_factors( + RoleKind::Primary, + 1, + vec![ + FactorSourceID::sample_ledger(), + FactorSourceID::sample_ledger(), + ], + vec![], + ); + let res = sut.validate(); + assert_eq!( + res, + MutRes::forever_invalid(ForeverInvalidReason::FactorSourceAlreadyPresent) + ); +} + +#[test] +fn confirmation_validate_basic_violation() { + let sut = SUT::with_factors( + RoleKind::Confirmation, + 1, + vec![], + vec![FactorSourceID::sample_ledger()], + ); + let res = sut.validate(); + assert_eq!( + res, + MutRes::basic_violation(BasicViolation::ConfirmationCannotSetThreshold) + ); +} + +#[test] +fn recovery_validate_basic_violation() { + let sut = SUT::with_factors( + RoleKind::Recovery, + 1, + vec![], + vec![FactorSourceID::sample_ledger()], + ); + let res = sut.validate(); + assert_eq!( + res, + MutRes::basic_violation(BasicViolation::RecoveryCannotSetThreshold) + ); +} + +#[test] +fn primary_validate_not_yet_valid_for_threshold_greater_than_threshold_factors() { + let sut = SUT::with_factors( + RoleKind::Primary, + 1, + vec![], + vec![FactorSourceID::sample_ledger()], + ); + let res = sut.validate(); + assert_eq!( + res, + MutRes::not_yet_valid(ThresholdHigherThanThresholdFactorsLen) + ); +} + +#[cfg(test)] +mod recovery_in_isolation { + + use super::*; + + fn role() -> RoleKind { + RoleKind::Recovery + } + + fn make() -> SUT { + SUT::new(role()) + } + + fn list() -> FactorListKind { + FactorListKind::Override + } + + fn sample() -> FactorSourceID { + FactorSourceID::sample_device() + } + + #[test] + fn duplicates_not_allowed() { + test_duplicates_not_allowed(make(), list(), sample()) + } + + #[test] + fn validation_for_addition_of_factor_source_of_kind_to_list_is_err_for_threshold() { + let sut = make(); + let res = sut.validation_for_addition_of_factor_source_of_kind_to_list( + FactorSourceKind::Device, + FactorListKind::Threshold, + ); + assert_eq!( + res, + MutRes::forever_invalid(ForeverInvalidReason::threshold_list_not_supported_for_role( + role() + )) + ); + } + + #[test] + fn validation_for_addition_of_factor_source_of_kind_to_list() { + use FactorSourceKind::*; + let sut = make(); + let not_ok = |kind: FactorSourceKind| { + let res = sut.validation_for_addition_of_factor_source_of_kind_to_list(kind, list()); + assert!(res.is_err()); + }; + let ok = |kind: FactorSourceKind| { + let res = sut.validation_for_addition_of_factor_source_of_kind_to_list(kind, list()); + assert!(res.is_ok()); + }; + ok(Device); + ok(LedgerHQHardwareWallet); + ok(ArculusCard); + ok(TrustedContact); + ok(OffDeviceMnemonic); + + not_ok(Passphrase); + not_ok(SecurityQuestions); + } + + #[test] + fn set_threshold_is_unsupported() { + let mut sut = make(); + assert_eq!( + sut.set_threshold(1), + MutRes::basic_violation(BasicViolation::RecoveryCannotSetThreshold) + ); + } + + #[test] + fn cannot_add_factors_to_threshold() { + let mut sut = make(); + let res = sut.add_factor_source_to_list(sample(), FactorListKind::Threshold); + assert_eq!( + res, + Err(Validation::ForeverInvalid( + ForeverInvalidReason::RecoveryRoleThresholdFactorsNotSupported + )) + ); + } + + mod device_in_isolation { + use super::*; + + fn sample() -> FactorSourceID { + FactorSourceID::sample_device() + } + + fn sample_other() -> FactorSourceID { + FactorSourceID::sample_device_other() + } + + #[test] + fn allowed_as_first_and_only() { + // Arrange + let mut sut = make(); + + // Act + sut.add_factor_source_to_list(sample(), list()).unwrap(); + + // Assert + assert_eq!( + sut.build().unwrap(), + RoleWithFactorSourceIds::recovery_with_factors([sample()]) + ); + } + + #[test] + fn two_of_same_kind_allowed() { + // TODO: Ask Matt + // Arrange + let mut sut = make(); + + // Act + sut.add_factor_source_to_list(sample(), list()).unwrap(); + sut.add_factor_source_to_list(sample_other(), list()) + .unwrap(); + + // Assert + assert_eq!( + sut.build().unwrap(), + RoleWithFactorSourceIds::recovery_with_factors([sample(), sample_other()],) + ); + } + + #[test] + fn validation_for_addition_of_factor_source_for_each() { + let sut = make(); + let xs = sut.validation_for_addition_of_factor_source_for_each( + list(), + &IndexSet::from_iter([sample(), sample_other()]), + ); + assert_eq!( + xs.into_iter().collect::>(), + vec![ + FactorSourceInRoleBuilderValidationStatus::ok(RoleKind::Recovery, sample()), + FactorSourceInRoleBuilderValidationStatus::ok( + RoleKind::Recovery, + sample_other(), + ) + ] + ); + } + } + + mod ledger_in_isolation { + use super::*; + + fn sample() -> FactorSourceID { + FactorSourceID::sample_ledger() + } + + fn sample_other() -> FactorSourceID { + FactorSourceID::sample_ledger_other() + } + + #[test] + fn allowed_as_first_and_only() { + // Arrange + let mut sut = make(); + + // Act + sut.add_factor_source_to_list(sample(), list()).unwrap(); + + // Assert + assert_eq!( + sut.build().unwrap(), + RoleWithFactorSourceIds::recovery_with_factors([sample()],) + ); + } + + #[test] + fn two_of_same_kind_allowed() { + // TODO: Ask Matt + // Arrange + let mut sut = make(); + + // Act + sut.add_factor_source_to_list(sample(), list()).unwrap(); + sut.add_factor_source_to_list(sample_other(), list()) + .unwrap(); + + // Assert + assert_eq!( + sut.build().unwrap(), + RoleWithFactorSourceIds::recovery_with_factors([sample(), sample_other()]) + ); + } + } + + mod arculus_in_isolation { + use super::*; + + fn sample() -> FactorSourceID { + FactorSourceID::sample_arculus() + } + + fn sample_other() -> FactorSourceID { + FactorSourceID::sample_arculus_other() + } + + #[test] + fn allowed_as_first_and_only() { + // Arrange + let mut sut = make(); + + // Act + sut.add_factor_source_to_list(sample(), list()).unwrap(); + + // Assert + assert_eq!( + sut.build().unwrap(), + RoleWithFactorSourceIds::recovery_with_factors([sample(),]) + ); + } + + #[test] + fn two_of_same_kind_allowed() { + // TODO: Ask Matt + // Arrange + let mut sut = make(); + + // Act + sut.add_factor_source_to_list(sample(), list()).unwrap(); + sut.add_factor_source_to_list(sample_other(), list()) + .unwrap(); + + // Assert + assert_eq!( + sut.build().unwrap(), + RoleWithFactorSourceIds::recovery_with_factors([sample(), sample_other()]) + ); + } + } + + mod passphrase_in_isolation { + use super::*; + + fn sample() -> FactorSourceID { + FactorSourceID::sample_passphrase() + } + + fn sample_other() -> FactorSourceID { + FactorSourceID::sample_passphrase_other() + } + + #[test] + fn allowed_as_first_and_only() { + // Arrange + let mut sut = make(); + + // Act + sut.add_factor_source_to_list(sample(), list()).unwrap(); + + // Assert + assert_eq!( + sut.build().unwrap(), + RoleWithFactorSourceIds::recovery_with_factors([sample()]) + ); + } + + #[test] + fn two_of_same_kind_allowed() { + // TODO: Ask Matt + // Arrange + let mut sut = make(); + + // Act + sut.add_factor_source_to_list(sample(), list()).unwrap(); + sut.add_factor_source_to_list(sample_other(), list()) + .unwrap(); + + // Assert + assert_eq!( + sut.build().unwrap(), + RoleWithFactorSourceIds::recovery_with_factors([sample(), sample_other()]) + ); + } + } + + mod trusted_contact_in_isolation { + use super::*; + + fn sample() -> FactorSourceID { + FactorSourceID::sample_trusted_contact() + } + + fn sample_other() -> FactorSourceID { + FactorSourceID::sample_trusted_contact_other() + } + + #[test] + fn allowed_as_first_and_only() { + // Arrange + let mut sut = make(); + + // Act + sut.add_factor_source_to_list(sample(), list()).unwrap(); + + // Assert + assert_eq!( + sut.build().unwrap(), + RoleWithFactorSourceIds::recovery_with_factors([sample(),]) + ); + } + + #[test] + fn two_of_same_kind_allowed() { + // TODO: Ask Matt + // Arrange + let mut sut = make(); + + // Act + sut.add_factor_source_to_list(sample(), list()).unwrap(); + sut.add_factor_source_to_list(sample_other(), list()) + .unwrap(); + + // Assert + assert_eq!( + sut.build().unwrap(), + RoleWithFactorSourceIds::recovery_with_factors([sample(), sample_other()]) + ); + } + } + + mod password_in_isolation { + use super::*; + + fn sample() -> FactorSourceID { + FactorSourceID::sample_password() + } + + #[test] + fn unsupported() { + // Arrange + let mut sut = make(); + + // Act + let res = sut.add_factor_source_to_list(sample(), list()); + + // Assert + assert_eq!( + res, + MutRes::forever_invalid(ForeverInvalidReason::RecoveryRolePasswordNotSupported) + ); + } + + #[test] + fn valid_then_invalid_because_unsupported() { + // Arrange + let mut sut = make(); + + sut.add_factor_source_to_list(FactorSourceID::sample_ledger(), list()) + .unwrap(); + sut.add_factor_source_to_list(FactorSourceID::sample_arculus(), list()) + .unwrap(); + + // Act + let res = sut.add_factor_source_to_list(sample(), list()); + + // Assert + assert_eq!( + res, + MutRes::forever_invalid(ForeverInvalidReason::RecoveryRolePasswordNotSupported) + ); + } + } + + mod security_questions_in_isolation { + use super::*; + + fn sample() -> FactorSourceID { + FactorSourceID::sample_security_questions() + } + fn sample_other() -> FactorSourceID { + FactorSourceID::sample_security_questions_other() + } + + #[test] + fn unsupported() { + // Arrange + let mut sut = make(); + + // Act + let res = sut.add_factor_source_to_list(sample(), list()); + + // Assert + assert_eq!( + res, + MutRes::forever_invalid( + ForeverInvalidReason::RecoveryRoleSecurityQuestionsNotSupported + ) + ); + } + + #[test] + fn valid_then_invalid_because_unsupported() { + // Arrange + let mut sut = make(); + + sut.add_factor_source_to_list(FactorSourceID::sample_ledger(), list()) + .unwrap(); + sut.add_factor_source_to_list(FactorSourceID::sample_arculus(), list()) + .unwrap(); + + // Act + let res = sut.add_factor_source_to_list(sample_other(), list()); + + // Assert + let reason = ForeverInvalidReason::RecoveryRoleSecurityQuestionsNotSupported; + let err = MutRes::forever_invalid(reason); + assert_eq!(res, err); + + // .. erroneous action above did not change the state of the builder (SUT), + // so we can build and `sample` is not present in the built result. + assert_eq!( + sut.build(), + Ok(RoleWithFactorSourceIds::recovery_with_factors([ + FactorSourceID::sample_ledger(), + FactorSourceID::sample_arculus() + ])) + ); + } + } +} + +#[cfg(test)] +mod confirmation_in_isolation { + + use super::*; + + fn role() -> RoleKind { + RoleKind::Confirmation + } + + fn make() -> SUT { + SUT::new(role()) + } + + fn list() -> FactorListKind { + FactorListKind::Override + } + + fn sample() -> FactorSourceID { + FactorSourceID::sample_device() + } + + #[test] + fn validation_for_addition_of_factor_source_of_kind_to_list_is_err_for_threshold() { + let sut = make(); + let res = sut.validation_for_addition_of_factor_source_of_kind_to_list( + FactorSourceKind::Device, + FactorListKind::Threshold, + ); + assert_eq!( + res, + MutRes::forever_invalid(ForeverInvalidReason::threshold_list_not_supported_for_role( + role() + )) + ); + } + + #[test] + fn validation_for_addition_of_factor_source_of_kind_to_list() { + use FactorSourceKind::*; + let sut = make(); + let not_ok = |kind: FactorSourceKind| { + let res = sut.validation_for_addition_of_factor_source_of_kind_to_list(kind, list()); + assert!(res.is_err()); + }; + let ok = |kind: FactorSourceKind| { + let res = sut.validation_for_addition_of_factor_source_of_kind_to_list(kind, list()); + assert!(res.is_ok()); + }; + ok(Device); + ok(LedgerHQHardwareWallet); + ok(ArculusCard); + ok(SecurityQuestions); + ok(Passphrase); + ok(OffDeviceMnemonic); + not_ok(TrustedContact); + } + + #[test] + fn duplicates_not_allowed() { + test_duplicates_not_allowed(make(), list(), sample()) + } + + #[test] + fn cannot_add_factors_to_threshold() { + let mut sut = make(); + let res = sut.add_factor_source_to_list(sample(), FactorListKind::Threshold); + assert_eq!( + res, + Err(Validation::ForeverInvalid( + ForeverInvalidReason::ConfirmationRoleThresholdFactorsNotSupported + )) + ); + } + + mod device_in_isolation { + use super::*; + + fn sample() -> FactorSourceID { + FactorSourceID::sample_device() + } + + fn sample_other() -> FactorSourceID { + FactorSourceID::sample_device_other() + } + + #[test] + fn set_threshold_is_unsupported() { + let mut sut = make(); + assert_eq!( + sut.set_threshold(1), + MutRes::basic_violation(BasicViolation::ConfirmationCannotSetThreshold) + ); + } + + #[test] + fn allowed_as_first_and_only() { + // Arrange + let mut sut = make(); + + // Act + sut.add_factor_source_to_list(sample(), list()).unwrap(); + + // Assert + assert_eq!( + sut.build().unwrap(), + RoleWithFactorSourceIds::confirmation_with_factors([sample()]) + ); + } + + #[test] + fn two_of_same_kind_allowed() { + // TODO: Ask Matt + // Arrange + let mut sut = make(); + + // Act + sut.add_factor_source_to_list(sample(), list()).unwrap(); + sut.add_factor_source_to_list(sample_other(), list()) + .unwrap(); + + // Assert + let built = sut.build().unwrap(); + assert!(built.threshold_factors().is_empty()); + assert_eq!( + built, + RoleWithFactorSourceIds::confirmation_with_factors([sample(), sample_other()]) + ); + } + } + + mod ledger_in_isolation { + use super::*; + + fn sample() -> FactorSourceID { + FactorSourceID::sample_ledger() + } + + fn sample_other() -> FactorSourceID { + FactorSourceID::sample_ledger_other() + } + + #[test] + fn allowed_as_first_and_only() { + // Arrange + let mut sut = make(); + + // Act + sut.add_factor_source_to_list(sample(), list()).unwrap(); + + // Assert + assert_eq!( + sut.build().unwrap(), + RoleWithFactorSourceIds::confirmation_with_factors([sample(),]) + ); + } + + #[test] + fn two_of_same_kind_allowed() { + // TODO: Ask Matt + // Arrange + let mut sut = make(); + + // Act + sut.add_factor_source_to_list(sample(), list()).unwrap(); + sut.add_factor_source_to_list(sample_other(), list()) + .unwrap(); + + // Assert + assert_eq!( + sut.build().unwrap(), + RoleWithFactorSourceIds::confirmation_with_factors([sample(), sample_other()]) + ); + } + } + + mod arculus_in_isolation { + use super::*; + + fn sample() -> FactorSourceID { + FactorSourceID::sample_arculus() + } + + fn sample_other() -> FactorSourceID { + FactorSourceID::sample_arculus_other() + } + + #[test] + fn allowed_as_first_and_only() { + // Arrange + let mut sut = make(); + + // Act + sut.add_factor_source_to_list(sample(), list()).unwrap(); + + // Assert + assert_eq!( + sut.build().unwrap(), + RoleWithFactorSourceIds::confirmation_with_factors([sample(),]) + ); + } + + #[test] + fn two_of_same_kind_allowed() { + // TODO: Ask Matt + // Arrange + let mut sut = make(); + + // Act + sut.add_factor_source_to_list(sample(), list()).unwrap(); + sut.add_factor_source_to_list(sample_other(), list()) + .unwrap(); + + // Assert + assert_eq!( + sut.build().unwrap(), + RoleWithFactorSourceIds::confirmation_with_factors([sample(), sample_other()]) + ); + } + } + + mod passphrase_in_isolation { + use super::*; + + fn sample() -> FactorSourceID { + FactorSourceID::sample_passphrase() + } + + fn sample_other() -> FactorSourceID { + FactorSourceID::sample_passphrase_other() + } + + #[test] + fn allowed_as_first_and_only() { + // Arrange + let mut sut = make(); + + // Act + sut.add_factor_source_to_list(sample(), list()).unwrap(); + + // Assert + assert_eq!( + sut.build().unwrap(), + RoleWithFactorSourceIds::confirmation_with_factors([sample(),]) + ); + } + + #[test] + fn two_of_same_kind_allowed() { + // TODO: Ask Matt + // Arrange + let mut sut = make(); + + // Act + sut.add_factor_source_to_list(sample(), list()).unwrap(); + sut.add_factor_source_to_list(sample_other(), list()) + .unwrap(); + + // Assert + assert_eq!( + sut.build().unwrap(), + RoleWithFactorSourceIds::confirmation_with_factors([sample(), sample_other()]) + ); + } + } + + mod trusted_contact_in_isolation { + use super::*; + + fn sample() -> FactorSourceID { + FactorSourceID::sample_trusted_contact() + } + + #[test] + fn unsupported() { + // Arrange + let mut sut = make(); + + // Act + let res = sut.add_factor_source_to_list(sample(), list()); + + // Assert + assert_eq!( + res, + MutRes::forever_invalid( + ForeverInvalidReason::ConfirmationRoleTrustedContactNotSupported + ) + ); + } + + #[test] + fn valid_then_invalid_because_unsupported() { + // Arrange + let mut sut = make(); + + sut.add_factor_source_to_list(FactorSourceID::sample_ledger(), list()) + .unwrap(); + sut.add_factor_source_to_list(FactorSourceID::sample_arculus(), list()) + .unwrap(); + + // Act + let res = sut.add_factor_source_to_list(sample(), list()); + + // Assert + assert_eq!( + res, + MutRes::forever_invalid( + ForeverInvalidReason::ConfirmationRoleTrustedContactNotSupported + ) + ); + } + } + + mod password_in_isolation { + use super::*; + + fn sample() -> FactorSourceID { + FactorSourceID::sample_password() + } + + fn sample_other() -> FactorSourceID { + FactorSourceID::sample_password_other() + } + + #[test] + fn allowed_as_first_and_only() { + // Arrange + let mut sut = make(); + + // Act + sut.add_factor_source_to_list(sample(), list()).unwrap(); + + // Assert + assert_eq!( + sut.build().unwrap(), + RoleWithFactorSourceIds::confirmation_with_factors([sample(),]) + ); + } + + #[test] + fn two_of_same_kind_allowed() { + // TODO: Ask Matt + // Arrange + let mut sut = make(); + + // Act + sut.add_factor_source_to_list(sample(), list()).unwrap(); + sut.add_factor_source_to_list(sample_other(), list()) + .unwrap(); + + // Assert + assert_eq!( + sut.build().unwrap(), + RoleWithFactorSourceIds::confirmation_with_factors([sample(), sample_other()]) + ); + } + } +} + +#[cfg(test)] +mod primary_in_isolation { + + use super::*; + + fn role() -> RoleKind { + RoleKind::Primary + } + + fn make() -> SUT { + SUT::new(role()) + } + + #[cfg(test)] + mod threshold_suite { + use super::*; + + fn sample() -> FactorSourceID { + FactorSourceID::sample_device() + } + + fn sample_other() -> FactorSourceID { + FactorSourceID::sample_ledger() + } + + fn sample_third() -> FactorSourceID { + FactorSourceID::sample_arculus() + } + + fn list() -> FactorListKind { + FactorListKind::Threshold + } + + #[test] + fn remove_lowers_threshold_from_1_to_0() { + let mut sut = make(); + let fs = sample(); + sut.add_factor_source_to_list(fs, list()).unwrap(); + sut.set_threshold(1).unwrap(); + assert_eq!(sut.threshold(), 1); + assert_eq!( + sut.remove_factor_source(&fs), + Err(Validation::NotYetValid(RoleMustHaveAtLeastOneFactor)) + ); + assert_eq!(sut.threshold(), 0); + } + + #[test] + fn remove_lowers_threshold_from_3_to_1() { + let mut sut = make(); + let fs0 = sample(); + let fs1 = sample_other(); + sut.add_factor_source_to_list(fs0, list()).unwrap(); + sut.add_factor_source_to_list(fs1, list()).unwrap(); + sut.add_factor_source_to_list(FactorSourceID::sample_arculus_other(), list()) + .unwrap(); + sut.set_threshold(3).unwrap(); + assert_eq!(sut.threshold(), 3); + sut.remove_factor_source(&fs0).unwrap(); + sut.remove_factor_source(&fs1).unwrap(); + assert_eq!(sut.threshold(), 1); + } + + #[test] + fn remove_from_override_does_not_change_threshold() { + let mut sut = make(); + sut.add_factor_source_to_list(sample(), list()).unwrap(); + sut.add_factor_source_to_list(sample_other(), list()) + .unwrap(); + let fs = FactorSourceID::sample_arculus_other(); + sut.add_factor_source_to_list(fs, FactorListKind::Override) + .unwrap(); + sut.set_threshold(2).unwrap(); + assert_eq!(sut.threshold(), 2); + sut.remove_factor_source(&fs).unwrap(); + assert_eq!(sut.threshold(), 2); + + let built = sut.build().unwrap(); + assert_eq!(built.threshold(), 2); + + assert_eq!(built.role(), RoleKind::Primary); + + assert_eq!(built.threshold_factors(), &vec![sample(), sample_other()]); + + assert_eq!(built.override_factors(), &Vec::new()); + } + + #[test] + fn one_factor_then_set_threshold_to_one_is_ok() { + // Arrange + let mut sut = make(); + + // Act + sut.add_factor_source_to_list(sample_other(), list()) + .unwrap(); + sut.set_threshold(1).unwrap(); + + // Assert + let expected = RoleWithFactorSourceIds::primary_with_factors(1, [sample_other()], []); + assert_eq!(sut.build().unwrap(), expected); + } + + #[test] + fn zero_factor_then_set_threshold_to_one_is_not_yet_valid_then_add_one_factor_is_ok() { + // Arrange + let mut sut = make(); + + // Act + assert_eq!( + sut.set_threshold(1), + Err(Validation::NotYetValid( + ThresholdHigherThanThresholdFactorsLen + )) + ); + sut.add_factor_source_to_list(sample_other(), list()) + .unwrap(); + + // Assert + let expected = RoleWithFactorSourceIds::primary_with_factors(1, [sample_other()], []); + assert_eq!(sut.build().unwrap(), expected); + } + + #[test] + fn zero_factor_then_set_threshold_to_two_is_not_yet_valid_then_add_two_factor_is_ok() { + // Arrange + let mut sut = make(); + + // Act + assert_eq!( + sut.set_threshold(2), + Err(Validation::NotYetValid( + ThresholdHigherThanThresholdFactorsLen + )) + ); + sut.add_factor_source_to_list(sample(), list()).unwrap(); + + sut.add_factor_source_to_list(sample_other(), list()) + .unwrap(); + + // Assert + let expected = + RoleWithFactorSourceIds::primary_with_factors(2, [sample(), sample_other()], []); + assert_eq!(sut.build().unwrap(), expected); + } + + #[test] + fn add_two_factors_then_set_threshold_to_two_is_ok() { + // Arrange + let mut sut = make(); + + sut.add_factor_source_to_list(sample(), list()).unwrap(); + sut.add_factor_source_to_list(sample_other(), list()) + .unwrap(); + + // Act + assert_eq!(sut.set_threshold(2), Ok(())); + + // Assert + let expected = + RoleWithFactorSourceIds::primary_with_factors(2, [sample(), sample_other()], []); + assert_eq!(sut.build().unwrap(), expected); + } + + #[test] + fn add_two_factors_then_set_threshold_to_three_is_not_yet_valid_then_add_third_factor_is_ok( + ) { + // Arrange + let mut sut = make(); + + sut.add_factor_source_to_list(sample(), list()).unwrap(); + sut.add_factor_source_to_list(sample_other(), list()) + .unwrap(); + + // Act + assert_eq!( + sut.set_threshold(3), + Err(Validation::NotYetValid( + ThresholdHigherThanThresholdFactorsLen + )) + ); + + sut.add_factor_source_to_list(sample_third(), list()) + .unwrap(); + + // Assert + let expected = RoleWithFactorSourceIds::primary_with_factors( + 3, + [sample(), sample_other(), sample_third()], + [], + ); + assert_eq!(sut.build().unwrap(), expected); + } + + #[test] + fn one_factors_set_threshold_of_one_is_ok() { + // Arrange + let mut sut = make(); + + // Act + sut.add_factor_source_to_list(sample_other(), list()) + .unwrap(); + sut.set_threshold(1).unwrap(); + + // Assert + let expected = RoleWithFactorSourceIds::primary_with_factors(1, [sample_other()], []); + assert_eq!(sut.build().unwrap(), expected); + } + + #[test] + fn one_override_factors_set_threshold_to_one_is_not_yet_valid() { + // Arrange + let mut sut = make(); + + // Act + sut.add_factor_source_to_list(sample_other(), FactorListKind::Override) + .unwrap(); + assert_eq!( + sut.set_threshold(1), + Err(Validation::NotYetValid( + ThresholdHigherThanThresholdFactorsLen + )) + ); + + // Assert + + assert_eq!( + sut.build(), + Err(Validation::NotYetValid( + ThresholdHigherThanThresholdFactorsLen + )) + ); + } + + #[test] + fn validation_for_addition_of_factor_source_for_each_before_after_adding_a_factor() { + let mut sut = make(); + let fs0 = FactorSourceID::sample_ledger(); + let fs1 = FactorSourceID::sample_password(); + let fs2 = FactorSourceID::sample_arculus(); + let xs = sut.validation_for_addition_of_factor_source_for_each( + list(), + &IndexSet::from_iter([fs0, fs1, fs2]), + ); + assert_eq!( + xs.into_iter().collect::>(), + vec![ + FactorSourceInRoleBuilderValidationStatus::ok( + RoleKind::Primary, + fs0, + ), + FactorSourceInRoleBuilderValidationStatus::not_yet_valid( + RoleKind::Primary, + fs1, + NotYetValidReason::PrimaryRoleWithPasswordInThresholdListMustHaveAnotherFactor + ), + FactorSourceInRoleBuilderValidationStatus::ok( + RoleKind::Primary, + fs2, + ), + ] + ); + _ = sut.add_factor_source_to_list(fs0, list()); + _ = sut.set_threshold(2); + + let xs = sut.validation_for_addition_of_factor_source_for_each( + list(), + &IndexSet::from_iter([fs0, fs1, fs2]), + ); + assert_eq!( + xs.into_iter().collect::>(), + vec![ + FactorSourceInRoleBuilderValidationStatus::forever_invalid( + RoleKind::Primary, + fs0, + ForeverInvalidReason::FactorSourceAlreadyPresent + ), + FactorSourceInRoleBuilderValidationStatus::ok(RoleKind::Primary, fs1,), + FactorSourceInRoleBuilderValidationStatus::ok(RoleKind::Primary, fs2,), + ] + ); + } + } + + #[cfg(test)] + mod password { + use super::*; + + fn sample() -> FactorSourceID { + FactorSourceID::sample_password() + } + + fn sample_other() -> FactorSourceID { + FactorSourceID::sample_password_other() + } + + #[test] + fn test_suite_prerequisite() { + assert_eq!(sample(), sample()); + assert_eq!(sample_other(), sample_other()); + assert_ne!(sample(), sample_other()); + } + + mod threshold_in_isolation { + use super::*; + + fn list() -> FactorListKind { + FactorListKind::Threshold + } + + #[test] + fn duplicates_not_allowed() { + let mut sut = make(); + sut.add_factor_source_to_list( + FactorSourceID::sample_device(), + FactorListKind::Threshold, + ) + .unwrap(); + _ = sut.set_threshold(2); + test_duplicates_not_allowed(sut, list(), sample()); + } + + #[test] + fn alone_is_not_ok() { + // Arrange + let mut sut = make(); + + // Act + let res = sut.add_factor_source_to_list(sample(), list()); + + // Assert + assert_eq!( + res, + MutRes::not_yet_valid( + NotYetValidReason::PrimaryRoleWithPasswordInThresholdListMustHaveAnotherFactor + ) + ); + } + + #[test] + fn validation_for_addition_of_factor_source_of_kind_to_list() { + use FactorSourceKind::*; + + let not_ok = |kind: FactorSourceKind| { + let sut = make(); + let res = + sut.validation_for_addition_of_factor_source_of_kind_to_list(kind, list()); + assert!(res.is_err()); + }; + + let ok_with = |kind: FactorSourceKind, setup: fn(&mut SUT)| { + let mut sut = make(); + setup(&mut sut); + let res = + sut.validation_for_addition_of_factor_source_of_kind_to_list(kind, list()); + assert!(res.is_ok()); + }; + let ok = |kind: FactorSourceKind| { + ok_with(kind, |_| {}); + }; + + ok(LedgerHQHardwareWallet); + ok(ArculusCard); + ok(OffDeviceMnemonic); + + ok_with(Device, |sut| { + sut.add_factor_source_to_list(FactorSourceID::sample_ledger(), list()) + .unwrap(); + }); + ok_with(Passphrase, |sut| { + sut.add_factor_source_to_list(FactorSourceID::sample_device(), list()) + .unwrap(); + _ = sut.set_threshold(2); + }); + + not_ok(SecurityQuestions); + not_ok(TrustedContact); + } + } + + mod override_in_isolation { + use super::*; + + fn list() -> FactorListKind { + FactorListKind::Override + } + + #[test] + fn unsupported() { + // Arrange + let mut sut = make(); + + // Act + let res = sut.add_factor_source_to_list(sample(), list()); + + // Assert + assert_eq!( + res, + MutRes::forever_invalid( + ForeverInvalidReason::PrimaryCannotHavePasswordInOverrideList + ) + ); + } + + #[test] + fn valid_then_invalid_because_unsupported() { + // Arrange + let mut sut = make(); + sut.add_factor_source_to_list( + FactorSourceID::sample_device(), + FactorListKind::Threshold, + ) + .unwrap(); + sut.add_factor_source_to_list(FactorSourceID::sample_ledger(), list()) + .unwrap(); + sut.add_factor_source_to_list(FactorSourceID::sample_arculus(), list()) + .unwrap(); + + // Act + let res = sut.add_factor_source_to_list(sample(), list()); + + // Assert + assert_eq!( + res, + MutRes::forever_invalid( + ForeverInvalidReason::PrimaryCannotHavePasswordInOverrideList + ) + ); + } + + #[test] + fn validation_for_addition_of_factor_source_of_kind_to_list() { + use FactorSourceKind::*; + + let not_ok = |kind: FactorSourceKind| { + let sut = make(); + let res = + sut.validation_for_addition_of_factor_source_of_kind_to_list(kind, list()); + assert!(res.is_err()); + }; + + let ok_with = |kind: FactorSourceKind, setup: fn(&mut SUT)| { + let mut sut = make(); + setup(&mut sut); + let res = + sut.validation_for_addition_of_factor_source_of_kind_to_list(kind, list()); + assert!(res.is_ok()); + }; + let ok = |kind: FactorSourceKind| { + ok_with(kind, |_| {}); + }; + + ok(LedgerHQHardwareWallet); + ok(ArculusCard); + ok(OffDeviceMnemonic); + + ok_with(Device, |sut| { + sut.add_factor_source_to_list(FactorSourceID::sample_ledger(), list()) + .unwrap(); + }); + + not_ok(Passphrase); + + not_ok(SecurityQuestions); + not_ok(TrustedContact); + } + } + } + + #[cfg(test)] + mod ledger { + use super::*; + + fn sample() -> FactorSourceID { + FactorSourceID::sample_ledger() + } + + fn sample_other() -> FactorSourceID { + FactorSourceID::sample_ledger_other() + } + + #[test] + fn test_suite_prerequisite() { + assert_eq!(sample(), sample()); + assert_eq!(sample_other(), sample_other()); + assert_ne!(sample(), sample_other()); + } + + mod threshold_in_isolation { + use super::*; + fn list() -> FactorListKind { + FactorListKind::Threshold + } + + #[test] + fn duplicates_not_allowed() { + test_duplicates_not_allowed(make(), list(), sample()); + } + + #[test] + fn one_is_ok() { + // Arrange + let mut sut = make(); + + // Act + sut.add_factor_source_to_list(sample(), list()).unwrap(); + sut.set_threshold(1).unwrap(); + + // Assert + let expected = RoleWithFactorSourceIds::primary_with_factors(1, [sample()], []); + assert_eq!(sut.build().unwrap(), expected); + } + + #[test] + fn one_with_threshold_of_zero_is_err() { + // Arrange + let mut sut = make(); + + // Act + sut.add_factor_source_to_list(sample(), list()).unwrap(); + + // Assert + assert_eq!( + sut.build(), + RoleBuilderBuildResult::Err(RoleBuilderValidation::NotYetValid( + NotYetValidReason::PrimaryRoleWithThresholdCannotBeZeroWithFactors + )) + ); + } + + #[test] + fn two_different_is_ok() { + // Arrange + let mut sut = make(); + + // Act + sut.add_factor_source_to_list(sample(), list()).unwrap(); + sut.add_factor_source_to_list(sample_other(), list()) + .unwrap(); + sut.set_threshold(2).unwrap(); + + // Assert + let expected = RoleWithFactorSourceIds::primary_with_factors( + 2, + [sample(), sample_other()], + [], + ); + assert_eq!(sut.build().unwrap(), expected); + } + } + + mod override_in_isolation { + use super::*; + fn list() -> FactorListKind { + FactorListKind::Override + } + + #[test] + fn duplicates_not_allowed() { + test_duplicates_not_allowed(make(), list(), sample()); + } + + #[test] + fn one_is_ok() { + // Arrange + let mut sut = make(); + + // Act + sut.add_factor_source_to_list(sample(), list()).unwrap(); + + // Assert + let expected = RoleWithFactorSourceIds::primary_with_factors(0, [], [sample()]); + assert_eq!(sut.build().unwrap(), expected); + } + + #[test] + fn two_different_is_ok() { + // Arrange + let mut sut = make(); + + // Act + sut.add_factor_source_to_list(sample(), list()).unwrap(); + sut.add_factor_source_to_list(sample_other(), list()) + .unwrap(); + + // Assert + let expected = RoleWithFactorSourceIds::primary_with_factors( + 0, + [], + [sample(), sample_other()], + ); + assert_eq!(sut.build().unwrap(), expected); + } + } + } + + #[cfg(test)] + mod arculus { + use super::*; + + fn sample() -> FactorSourceID { + FactorSourceID::sample_arculus() + } + + fn sample_other() -> FactorSourceID { + FactorSourceID::sample_arculus_other() + } + + #[test] + fn test_suite_prerequisite() { + assert_eq!(sample(), sample()); + assert_eq!(sample_other(), sample_other()); + assert_ne!(sample(), sample_other()); + } + + mod threshold_in_isolation { + use super::*; + fn list() -> FactorListKind { + FactorListKind::Threshold + } + + #[test] + fn duplicates_not_allowed() { + test_duplicates_not_allowed(make(), list(), sample()); + } + + #[test] + fn one_is_ok() { + // Arrange + let mut sut = make(); + + // Act + sut.add_factor_source_to_list(sample(), list()).unwrap(); + sut.set_threshold(1).unwrap(); + + // Assert + let expected = RoleWithFactorSourceIds::primary_with_factors(1, [sample()], []); + assert_eq!(sut.build().unwrap(), expected); + } + + #[test] + fn two_different_is_ok() { + // Arrange + let mut sut = make(); + + // Act + sut.add_factor_source_to_list(sample(), list()).unwrap(); + sut.add_factor_source_to_list(sample_other(), list()) + .unwrap(); + sut.set_threshold(1).unwrap(); + + // Assert + let expected = RoleWithFactorSourceIds::primary_with_factors( + 1, + [sample(), sample_other()], + [], + ); + assert_eq!(sut.build().unwrap(), expected); + } + } + + mod override_in_isolation { + use super::*; + fn list() -> FactorListKind { + FactorListKind::Override + } + + #[test] + fn duplicates_not_allowed() { + test_duplicates_not_allowed(make(), list(), sample()); + } + + #[test] + fn one_is_ok() { + // Arrange + let mut sut = make(); + + // Act + sut.add_factor_source_to_list(sample(), list()).unwrap(); + + // Assert + let expected = RoleWithFactorSourceIds::primary_with_factors(0, [], [sample()]); + assert_eq!(sut.build().unwrap(), expected); + } + + #[test] + fn two_different_is_ok() { + // Arrange + let mut sut = make(); + + // Act + sut.add_factor_source_to_list(sample(), list()).unwrap(); + sut.add_factor_source_to_list(sample_other(), list()) + .unwrap(); + + // Assert + let expected = RoleWithFactorSourceIds::primary_with_factors( + 0, + [], + [sample(), sample_other()], + ); + assert_eq!(sut.build().unwrap(), expected); + } + } + } + + #[cfg(test)] + mod device_factor_source { + use super::*; + + fn sample() -> FactorSourceID { + FactorSourceID::sample_device() + } + + fn sample_other() -> FactorSourceID { + FactorSourceID::sample_device_other() + } + + #[test] + fn test_suite_prerequisite() { + assert_eq!(sample(), sample()); + assert_eq!(sample_other(), sample_other()); + assert_ne!(sample(), sample_other()); + } + + #[cfg(test)] + mod threshold_in_isolation { + use super::*; + + fn list() -> FactorListKind { + FactorListKind::Threshold + } + + #[test] + fn duplicates_not_allowed() { + test_duplicates_not_allowed(make(), list(), sample()) + } + + #[test] + fn one_is_ok() { + // Arrange + let mut sut = make(); + + // Act + sut.add_factor_source_to_list(sample(), list()).unwrap(); + sut.set_threshold(1).unwrap(); + + // Assert + let expected = RoleWithFactorSourceIds::primary_with_factors(1, [sample()], []); + assert_eq!(sut.build().unwrap(), expected); + } + + #[test] + fn two_different_is_err() { + // Arrange + let mut sut = make(); + + sut.add_factor_source_to_list(sample(), list()).unwrap(); + + // Act + let res = sut.add_factor_source_to_list(sample_other(), list()); + + // Assert + assert!(matches!( + res, + MutRes::Err(Validation::ForeverInvalid( + ForeverInvalidReason::PrimaryCannotHaveMultipleDevices + )) + )); + } + } + + mod override_in_isolation { + + use super::*; + + fn list() -> FactorListKind { + FactorListKind::Override + } + + #[test] + fn duplicates_not_allowed() { + test_duplicates_not_allowed(make(), list(), sample()) + } + + #[test] + fn one_is_ok() { + // Arrange + let mut sut = make(); + + // Act + sut.add_factor_source_to_list(sample(), list()).unwrap(); + + // Assert + let expected = RoleWithFactorSourceIds::primary_with_factors(0, [], [sample()]); + assert_eq!(sut.build().unwrap(), expected); + } + } + } +} diff --git a/crates/rules/src/roles/mod.rs b/crates/rules/src/roles/mod.rs new file mode 100644 index 00000000..1e7237a1 --- /dev/null +++ b/crates/rules/src/roles/mod.rs @@ -0,0 +1,7 @@ +mod abstract_role_builder_or_built; +mod builder; +mod roles_with_factor_ids; + +pub(crate) use abstract_role_builder_or_built::*; +pub use builder::*; +pub use roles_with_factor_ids::*; diff --git a/crates/rules/src/roles/roles_with_factor_ids.rs b/crates/rules/src/roles/roles_with_factor_ids.rs new file mode 100644 index 00000000..3b7d0259 --- /dev/null +++ b/crates/rules/src/roles/roles_with_factor_ids.rs @@ -0,0 +1,64 @@ +use sargon::HasSampleValues; + +use crate::prelude::*; + +pub type RoleWithFactorSourceIds = AbstractBuiltRoleWithFactor; + +impl RoleWithFactorSourceIds { + /// Config MFA 1.1 + pub fn sample_primary() -> Self { + let mut builder = RoleBuilder::primary(); + builder + .add_factor_source_to_list(FactorSourceID::sample_device(), FactorListKind::Threshold) + .unwrap(); + + builder + .add_factor_source_to_list(FactorSourceID::sample_ledger(), FactorListKind::Threshold) + .unwrap(); + builder.set_threshold(2).unwrap(); + builder.build().unwrap() + } + + /// Config MFA 1.1 + pub fn sample_recovery() -> Self { + let mut builder = RoleBuilder::recovery(); + builder + .add_factor_source_to_list(FactorSourceID::sample_device(), FactorListKind::Override) + .unwrap(); + + builder + .add_factor_source_to_list(FactorSourceID::sample_ledger(), FactorListKind::Override) + .unwrap(); + builder.build().unwrap() + } +} + +impl HasSampleValues for RoleWithFactorSourceIds { + fn sample() -> Self { + Self::sample_primary() + } + + fn sample_other() -> Self { + Self::sample_recovery() + } +} + +#[cfg(test)] +mod tests { + + use super::*; + + #[allow(clippy::upper_case_acronyms)] + type SUT = RoleWithFactorSourceIds; + + #[test] + fn equality() { + assert_eq!(SUT::sample(), SUT::sample()); + assert_eq!(SUT::sample_other(), SUT::sample_other()); + } + + #[test] + fn inequality() { + assert_ne!(SUT::sample(), SUT::sample_other()); + } +} diff --git a/crates/rules/src/rules/error.rs b/crates/rules/src/rules/error.rs deleted file mode 100644 index a4a0a97c..00000000 --- a/crates/rules/src/rules/error.rs +++ /dev/null @@ -1,8 +0,0 @@ -use crate::prelude::*; - - -#[derive(Clone, Copy, Debug, PartialEq, Eq, ThisError)] -pub enum Error { - #[error("Unknown")] - Unknown, -} \ No newline at end of file diff --git a/crates/rules/src/rules/mod.rs b/crates/rules/src/rules/mod.rs deleted file mode 100644 index 45d3692c..00000000 --- a/crates/rules/src/rules/mod.rs +++ /dev/null @@ -1,3 +0,0 @@ -mod error; - -pub use error::*; \ No newline at end of file diff --git a/rust-toolchain.toml b/rust-toolchain.toml index 08f1265f..c4904ee8 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,2 +1,2 @@ [toolchain] -channel = "nightly-2024-01-11" +channel = "nightly-2024-07-30" From 091dbc5b01bd9a67157ca263744c949e701d07f6 Mon Sep 17 00:00:00 2001 From: Alexander Cyon Date: Wed, 27 Nov 2024 13:34:11 +0100 Subject: [PATCH 02/33] do not validate combination of role builders in matrix builder except for validate/build. --- .../src/matrices/builder/matrix_builder.rs | 36 ++------- .../builder/matrix_builder_unit_tests.rs | 73 ++++++++++++++++--- 2 files changed, 70 insertions(+), 39 deletions(-) diff --git a/crates/rules/src/matrices/builder/matrix_builder.rs b/crates/rules/src/matrices/builder/matrix_builder.rs index f31702d0..94b5407d 100644 --- a/crates/rules/src/matrices/builder/matrix_builder.rs +++ b/crates/rules/src/matrices/builder/matrix_builder.rs @@ -165,9 +165,7 @@ impl MatrixBuilder { ) -> MatrixBuilderMutateResult { self.primary_role .add_factor_source_to_list(factor_source_id, FactorListKind::Threshold) - .into_matrix_err(RoleKind::Primary)?; - - self.validate_combination() + .into_matrix_err(RoleKind::Primary) } /// Adds the factor source to the primary role override list. @@ -177,52 +175,32 @@ impl MatrixBuilder { ) -> MatrixBuilderMutateResult { self.primary_role .add_factor_source_to_list(factor_source_id, FactorListKind::Override) - .into_matrix_err(RoleKind::Primary)?; - - self.validate_combination() + .into_matrix_err(RoleKind::Primary) } - /// Adds the factor source to the recovery role override list if not already present. - /// - /// Even if `Err(MatrixBuilderValidation::CombinationViolation(MatrixRolesInCombinationViolation::NotYetValid(MatrixRolesInCombinationNotYetValid::SingleFactorUsedInPrimaryMustNotBeUsedInAnyOtherRole))` is thrown, the factor is still added to the recovery role. - /// - /// However, if `Err(MatrixBuilderValidation::Basic(_))` or `Err(MatrixBuilderValidation::ForeverInvalid(_))` is thrown - /// then the factor is not added to the recovery role. pub fn add_factor_source_to_recovery_override( &mut self, factor_source_id: FactorSourceID, ) -> MatrixBuilderMutateResult { self.recovery_role .add_factor_source_to_list(factor_source_id, FactorListKind::Override) - .into_matrix_err(RoleKind::Recovery)?; - - self.validate_combination() + .into_matrix_err(RoleKind::Recovery) } - /// Adds the factor source to the confirmation role override list if not already present. - /// - /// Even if `Err(MatrixBuilderValidation::CombinationViolation(MatrixRolesInCombinationViolation::NotYetValid(MatrixRolesInCombinationNotYetValid::SingleFactorUsedInPrimaryMustNotBeUsedInAnyOtherRole))` is thrown, the factor is still added to the recovery role. - /// - /// However, if `Err(MatrixBuilderValidation::Basic(_))` or `Err(MatrixBuilderValidation::ForeverInvalid(_))` is thrown - /// then the factor is not added to the recovery role. pub fn add_factor_source_to_confirmation_override( &mut self, factor_source_id: FactorSourceID, ) -> MatrixBuilderMutateResult { self.confirmation_role .add_factor_source_to_list(factor_source_id, FactorListKind::Override) - .into_matrix_err(RoleKind::Confirmation)?; - - self.validate_combination() + .into_matrix_err(RoleKind::Confirmation) } /// Sets the threshold on the primary role builder. pub fn set_threshold(&mut self, threshold: u8) -> MatrixBuilderMutateResult { self.primary_role .set_threshold(threshold) - .into_matrix_err(RoleKind::Primary)?; - - self.validate_combination() + .into_matrix_err(RoleKind::Primary) } pub fn set_number_of_days_until_auto_confirm( @@ -231,7 +209,7 @@ impl MatrixBuilder { ) -> MatrixBuilderMutateResult { self.number_of_days_until_auto_confirm = number_of_days; - self.validate_combination() + self.validate_number_of_days_until_auto_confirm() } /// Removes `factor_source_id` from all three roles, if not found in any an error @@ -272,7 +250,7 @@ impl MatrixBuilder { ), )) } else { - self.validate_combination() + Ok(()) } } } diff --git a/crates/rules/src/matrices/builder/matrix_builder_unit_tests.rs b/crates/rules/src/matrices/builder/matrix_builder_unit_tests.rs index 2d5e0311..ca8f34c5 100644 --- a/crates/rules/src/matrices/builder/matrix_builder_unit_tests.rs +++ b/crates/rules/src/matrices/builder/matrix_builder_unit_tests.rs @@ -182,17 +182,20 @@ fn single_factor_in_primary_threshold_cannot_be_in_recovery() { let mut sut = make(); let fs = FactorSourceID::sample_ledger(); sut.add_factor_source_to_primary_threshold(fs).unwrap(); + sut.add_factor_source_to_confirmation_override(FactorSourceID::sample_arculus_other()) + .unwrap(); sut.set_threshold(1).unwrap(); - let res = sut.add_factor_source_to_recovery_override(fs); + // ACT + sut.add_factor_source_to_recovery_override(fs).unwrap(); + let res = sut.validate(); assert_eq!(res, Err(MatrixBuilderValidation::CombinationViolation( MatrixRolesInCombinationViolation::NotYetValid(MatrixRolesInCombinationNotYetValid::SingleFactorUsedInPrimaryMustNotBeUsedInAnyOtherRole) ))); sut.add_factor_source_to_primary_threshold(FactorSourceID::sample_arculus()) .unwrap(); - sut.add_factor_source_to_confirmation_override(FactorSourceID::sample_arculus_other()) - .unwrap(); + let built = sut.build().unwrap(); pretty_assertions::assert_eq!( built.primary(), @@ -220,10 +223,18 @@ fn single_factor_in_primary_threshold_cannot_be_in_recovery() { #[test] fn single_factor_in_primary_override_cannot_be_in_recovery() { + // ARRANGE let mut sut = make(); + sut.add_factor_source_to_confirmation_override(FactorSourceID::sample_arculus()) + .unwrap(); + + // ACT let fs = FactorSourceID::sample_ledger(); sut.add_factor_source_to_primary_override(fs).unwrap(); - let res = sut.add_factor_source_to_recovery_override(fs); + sut.add_factor_source_to_recovery_override(fs).unwrap(); + + // ASSERT + let res = sut.validate(); assert_eq!(res, Err(MatrixBuilderValidation::CombinationViolation( MatrixRolesInCombinationViolation::NotYetValid(MatrixRolesInCombinationNotYetValid::SingleFactorUsedInPrimaryMustNotBeUsedInAnyOtherRole) ))); @@ -231,10 +242,19 @@ fn single_factor_in_primary_override_cannot_be_in_recovery() { #[test] fn single_factor_in_primary_threshold_cannot_be_in_confirmation() { + // ARRANGE let mut sut = make(); + sut.add_factor_source_to_recovery_override(FactorSourceID::sample_arculus()) + .unwrap(); + _ = sut.set_threshold(1); + + // ACT let fs = FactorSourceID::sample_ledger(); sut.add_factor_source_to_primary_threshold(fs).unwrap(); - let res = sut.add_factor_source_to_confirmation_override(fs); + sut.add_factor_source_to_confirmation_override(fs).unwrap(); + + // ASSERT + let res = sut.validate(); assert_eq!(res, Err(MatrixBuilderValidation::CombinationViolation( MatrixRolesInCombinationViolation::NotYetValid(MatrixRolesInCombinationNotYetValid::SingleFactorUsedInPrimaryMustNotBeUsedInAnyOtherRole) ))); @@ -242,10 +262,18 @@ fn single_factor_in_primary_threshold_cannot_be_in_confirmation() { #[test] fn single_factor_in_primary_override_cannot_be_in_confirmation() { + // ARRANGE let mut sut = make(); + sut.add_factor_source_to_recovery_override(FactorSourceID::sample_arculus()) + .unwrap(); + + // ACT let fs = FactorSourceID::sample_ledger(); sut.add_factor_source_to_primary_override(fs).unwrap(); - let res = sut.add_factor_source_to_confirmation_override(fs); + sut.add_factor_source_to_confirmation_override(fs).unwrap(); + + // ASSERT + let res = sut.validate(); assert_eq!(res, Err(MatrixBuilderValidation::CombinationViolation( MatrixRolesInCombinationViolation::NotYetValid(MatrixRolesInCombinationNotYetValid::SingleFactorUsedInPrimaryMustNotBeUsedInAnyOtherRole) ))); @@ -253,10 +281,18 @@ fn single_factor_in_primary_override_cannot_be_in_confirmation() { #[test] fn add_factor_to_recovery_then_same_to_confirmation_is_err() { + // ARRANGE let mut sut = make(); + sut.add_factor_source_to_primary_override(FactorSourceID::sample_arculus()) + .unwrap(); + + // ACT let fs = FactorSourceID::sample_ledger(); sut.add_factor_source_to_confirmation_override(fs).unwrap(); - let res = sut.add_factor_source_to_recovery_override(fs); + sut.add_factor_source_to_recovery_override(fs).unwrap(); + + // ASSERT + let res = sut.validate(); assert_eq!( res, Err(MatrixBuilderValidation::CombinationViolation( @@ -268,11 +304,19 @@ fn add_factor_to_recovery_then_same_to_confirmation_is_err() { } #[test] -fn add_factor_to_confirmation_then_same_to_override_is_err() { +fn add_factor_to_confirmation_then_same_to_override_when_validated_is_err() { + // ARRANGE let mut sut = make(); let fs = FactorSourceID::sample_ledger(); + sut.add_factor_source_to_primary_override(FactorSourceID::sample_arculus()) + .unwrap(); + + // ACT sut.add_factor_source_to_recovery_override(fs).unwrap(); - let res = sut.add_factor_source_to_confirmation_override(fs); + sut.add_factor_source_to_confirmation_override(fs).unwrap(); + + // ASSERT + let res = sut.validate(); assert_eq!( res, Err(MatrixBuilderValidation::CombinationViolation( @@ -285,10 +329,19 @@ fn add_factor_to_confirmation_then_same_to_override_is_err() { #[test] fn add_factor_to_confirmation_then_same_to_primary_threshold_is_not_yet_valid() { + // ARRANGE let mut sut = make(); + sut.add_factor_source_to_confirmation_override(FactorSourceID::sample_arculus()) + .unwrap(); + _ = sut.set_threshold(1); + + // ACT let fs = FactorSourceID::sample_ledger(); sut.add_factor_source_to_recovery_override(fs).unwrap(); - let res = sut.add_factor_source_to_primary_threshold(fs); + sut.add_factor_source_to_primary_threshold(fs).unwrap(); + + // ASSERT + let res = sut.validate(); assert_eq!(res, Err(MatrixBuilderValidation::CombinationViolation( MatrixRolesInCombinationViolation::NotYetValid(MatrixRolesInCombinationNotYetValid::SingleFactorUsedInPrimaryMustNotBeUsedInAnyOtherRole) ))); From dea2acb77e020192f44ca939b5d698d3ff2a884b Mon Sep 17 00:00:00 2001 From: Alexander Cyon Date: Thu, 28 Nov 2024 12:38:43 +0100 Subject: [PATCH 03/33] json tests and HasSampleValues --- crates/rules/Cargo.toml | 2 + .../matrices/matrix_with_factor_source_ids.rs | 225 ++++++++++++++++++ crates/rules/src/move_to_sargon.rs | 168 +++++++++++++ .../rules/src/roles/roles_with_factor_ids.rs | 62 +++++ 4 files changed, 457 insertions(+) diff --git a/crates/rules/Cargo.toml b/crates/rules/Cargo.toml index 86679a0b..6fe9fabf 100644 --- a/crates/rules/Cargo.toml +++ b/crates/rules/Cargo.toml @@ -8,3 +8,5 @@ thiserror = { workspace = true } sargon = { workspace = true} serde = { version = "1.0.215", features = ["derive"] } pretty_assertions = "1.4.1" +serde_json = { version = "1.0.133", features = ["preserve_order"] } +assert-json-diff = "2.0.2" diff --git a/crates/rules/src/matrices/matrix_with_factor_source_ids.rs b/crates/rules/src/matrices/matrix_with_factor_source_ids.rs index af92ca34..d9c6c7f0 100644 --- a/crates/rules/src/matrices/matrix_with_factor_source_ids.rs +++ b/crates/rules/src/matrices/matrix_with_factor_source_ids.rs @@ -1,3 +1,5 @@ +use sargon::HasSampleValues; + use crate::prelude::*; pub type MatrixWithFactorSourceIds = AbstractMatrixBuilderOrBuilt; @@ -49,3 +51,226 @@ impl MatrixWithFactorSourceIds { &self.confirmation_role } } + +impl MatrixWithFactorSourceIds { + pub fn sample_config_12() -> Self { + let mut builder = MatrixBuilder::new(); + // Primary + builder + .add_factor_source_to_primary_threshold(FactorSourceID::sample_ledger()) + .unwrap(); + _ = builder.add_factor_source_to_primary_threshold(FactorSourceID::sample_password()); + + _ = builder.set_threshold(2); + + // Recovery + builder + .add_factor_source_to_recovery_override(FactorSourceID::sample_device()) + .unwrap(); + builder + .add_factor_source_to_recovery_override(FactorSourceID::sample_ledger()) + .unwrap(); + + // Confirmation + builder + .add_factor_source_to_confirmation_override(FactorSourceID::sample_password()) + .unwrap(); + + builder.build().unwrap() + } + + pub fn sample_config_11() -> Self { + let mut builder = MatrixBuilder::new(); + + // Primary + builder + .add_factor_source_to_primary_threshold(FactorSourceID::sample_device()) + .unwrap(); + builder + .add_factor_source_to_primary_threshold(FactorSourceID::sample_ledger()) + .unwrap(); + builder.set_threshold(2).unwrap(); + + // Recovery + builder + .add_factor_source_to_recovery_override(FactorSourceID::sample_device()) + .unwrap(); + builder + .add_factor_source_to_recovery_override(FactorSourceID::sample_ledger()) + .unwrap(); + + // Confirmation + builder + .add_factor_source_to_confirmation_override(FactorSourceID::sample_password()) + .unwrap(); + + // Build + builder.build().unwrap() + } +} + +impl HasSampleValues for MatrixWithFactorSourceIds { + fn sample() -> Self { + Self::sample_config_11() + } + + fn sample_other() -> Self { + Self::sample_config_12() + } +} + +#[cfg(test)] +mod tests { + use super::*; + #[allow(clippy::upper_case_acronyms)] + type SUT = MatrixWithFactorSourceIds; + + #[test] + fn equality() { + assert_eq!(SUT::sample(), SUT::sample()); + assert_eq!(SUT::sample_other(), SUT::sample_other()); + } + + #[test] + fn inequality() { + assert_ne!(SUT::sample(), SUT::sample_other()); + } + + #[test] + fn assert_json_sample() { + let sut = SUT::sample(); + assert_eq_after_json_roundtrip( + &sut, + r#" + { + "primary_role": { + "role": "primary", + "threshold": 2, + "threshold_factors": [ + { + "discriminator": "fromHash", + "fromHash": { + "kind": "device", + "body": "f1a93d324dd0f2bff89963ab81ed6e0c2ee7e18c0827dc1d3576b2d9f26bbd0a" + } + }, + { + "discriminator": "fromHash", + "fromHash": { + "kind": "ledgerHQHardwareWallet", + "body": "ab59987eedd181fe98e512c1ba0f5ff059f11b5c7c56f15614dcc9fe03fec58b" + } + } + ], + "override_factors": [] + }, + "recovery_role": { + "role": "recovery", + "threshold": 0, + "threshold_factors": [], + "override_factors": [ + { + "discriminator": "fromHash", + "fromHash": { + "kind": "device", + "body": "f1a93d324dd0f2bff89963ab81ed6e0c2ee7e18c0827dc1d3576b2d9f26bbd0a" + } + }, + { + "discriminator": "fromHash", + "fromHash": { + "kind": "ledgerHQHardwareWallet", + "body": "ab59987eedd181fe98e512c1ba0f5ff059f11b5c7c56f15614dcc9fe03fec58b" + } + } + ] + }, + "confirmation_role": { + "role": "confirmation", + "threshold": 0, + "threshold_factors": [], + "override_factors": [ + { + "discriminator": "fromHash", + "fromHash": { + "kind": "passphrase", + "body": "181ab662e19fac3ad9f08d5c673b286d4a5ed9cd3762356dc9831dc42427c1b9" + } + } + ] + }, + "number_of_days_until_auto_confirm": 14 + } + "#, + ); + } + + #[test] + fn assert_json_sample_other() { + let sut = SUT::sample_other(); + assert_eq_after_json_roundtrip( + &sut, + r#" + { + "primary_role": { + "role": "primary", + "threshold": 2, + "threshold_factors": [ + { + "discriminator": "fromHash", + "fromHash": { + "kind": "ledgerHQHardwareWallet", + "body": "ab59987eedd181fe98e512c1ba0f5ff059f11b5c7c56f15614dcc9fe03fec58b" + } + }, + { + "discriminator": "fromHash", + "fromHash": { + "kind": "passphrase", + "body": "181ab662e19fac3ad9f08d5c673b286d4a5ed9cd3762356dc9831dc42427c1b9" + } + } + ], + "override_factors": [] + }, + "recovery_role": { + "role": "recovery", + "threshold": 0, + "threshold_factors": [], + "override_factors": [ + { + "discriminator": "fromHash", + "fromHash": { + "kind": "device", + "body": "f1a93d324dd0f2bff89963ab81ed6e0c2ee7e18c0827dc1d3576b2d9f26bbd0a" + } + }, + { + "discriminator": "fromHash", + "fromHash": { + "kind": "ledgerHQHardwareWallet", + "body": "ab59987eedd181fe98e512c1ba0f5ff059f11b5c7c56f15614dcc9fe03fec58b" + } + } + ] + }, + "confirmation_role": { + "role": "confirmation", + "threshold": 0, + "threshold_factors": [], + "override_factors": [ + { + "discriminator": "fromHash", + "fromHash": { + "kind": "passphrase", + "body": "181ab662e19fac3ad9f08d5c673b286d4a5ed9cd3762356dc9831dc42427c1b9" + } + } + ] + }, + "number_of_days_until_auto_confirm": 14 + } + "#, + ); + } +} diff --git a/crates/rules/src/move_to_sargon.rs b/crates/rules/src/move_to_sargon.rs index a10da35d..7d925fa9 100644 --- a/crates/rules/src/move_to_sargon.rs +++ b/crates/rules/src/move_to_sargon.rs @@ -90,3 +90,171 @@ impl SampleValues for FactorSourceID { sargon::FactorSource::sample_trusted_contact_grace().id() } } + +use assert_json_diff::assert_json_include; +use core::fmt::Debug; +use pretty_assertions::assert_eq; +use serde::de::DeserializeOwned; +use serde_json::Value; +use std::str::FromStr; +use thiserror::Error as ThisError; + +#[derive(Debug, ThisError)] +pub enum TestingError { + #[error("File contents is not valid JSON '{0}'")] + FailedDoesNotContainValidJSON(String), + + #[error("Failed to JSON deserialize string")] + FailedToDeserialize(serde_json::Error), +} + +/// `name` is file name without extension, assuming it is json file + +pub fn fixture_and_json<'a, T>(vector: &str) -> Result<(T, serde_json::Value), TestingError> +where + T: for<'de> Deserialize<'de>, +{ + let json = serde_json::Value::from_str(vector) + .map_err(|_| TestingError::FailedDoesNotContainValidJSON(vector.to_owned()))?; + + serde_json::from_value::(json.clone()) + .map_err(TestingError::FailedToDeserialize) + .map(|v| (v, json)) +} + +/// `name` is file name without extension, assuming it is json file + +#[allow(unused)] +pub fn fixture<'a, T>(vector: &str) -> Result +where + T: for<'de> Deserialize<'de>, +{ + fixture_and_json(vector).map(|t| t.0) +} + +fn base_assert_equality_after_json_roundtrip(model: &T, json: Value, expect_eq: bool) +where + T: Serialize + DeserializeOwned + PartialEq + Debug, +{ + let serialized = serde_json::to_value(model).unwrap(); + let deserialized: T = serde_json::from_value(json.clone()).unwrap(); + if expect_eq { + pretty_assertions::assert_eq!(&deserialized, model, "Expected `model: T` and `T` deserialized from `json_string`, to be equal, but they were not."); + assert_json_include!(actual: serialized, expected: json); + } else { + pretty_assertions::assert_ne!(model, &deserialized); + pretty_assertions::assert_ne!(&deserialized, model, "Expected difference between `model: T` and `T` deserialized from `json_string`, but they were unexpectedly equal."); + pretty_assertions::assert_ne!(serialized, json, "Expected difference between `json` (string) and json serialized from `model`, but they were unexpectedly equal."); + } +} + +/// Asserts that (pseudocode) `model.to_json() == json_string` (serialization) +/// and also asserts the associative property: +/// `Model::from_json(json_string) == model` (deserialization) +pub fn assert_eq_after_json_roundtrip(model: &T, json_string: &str) +where + T: Serialize + DeserializeOwned + PartialEq + Debug, +{ + let json = json_string.parse::().unwrap(); + base_assert_equality_after_json_roundtrip(model, json, true) +} + +pub fn print_json(model: &T) +where + T: Serialize, +{ + println!( + "{}", + serde_json::to_string_pretty(model) + .expect("Should be able to JSON serialize passed in serializable model.") + ); +} + +/// Asserts that (pseudocode) `model.to_json() == json` (serialization) +/// and also asserts the associative property: +/// `Model::from_json(json) == model` (deserialization) + +pub fn assert_json_value_eq_after_roundtrip(model: &T, json: Value) +where + T: Serialize + DeserializeOwned + PartialEq + Debug, +{ + base_assert_equality_after_json_roundtrip(model, json, true) +} + +/// Asserts that (pseudocode) `model.to_json() != json_string` (serialization) +/// and also asserts the associative property: +/// `Model::from_json(json_string) != model` (deserialization) + +pub fn assert_ne_after_json_roundtrip(model: &T, json_string: &str) +where + T: Serialize + DeserializeOwned + PartialEq + Debug, +{ + let json = json_string.parse::().unwrap(); + base_assert_equality_after_json_roundtrip(model, json, false) +} + +/// Asserts that (pseudocode) `model.to_json() != json` (serialization) +/// and also asserts the associative property: +/// `Model::from_json(json) != model` (deserialization) + +pub fn assert_json_value_ne_after_roundtrip(model: &T, json: Value) +where + T: Serialize + DeserializeOwned + PartialEq + Debug, +{ + base_assert_equality_after_json_roundtrip(model, json, false) +} + +/// Asserts that (pseudocode) `Model::from_json(model.to_json()) == model`, +/// i.e. that a model after JSON roundtripping remain unchanged. + +pub fn assert_json_roundtrip(model: &T) +where + T: Serialize + DeserializeOwned + PartialEq + Debug, +{ + let serialized = serde_json::to_value(model).unwrap(); + let deserialized: T = serde_json::from_value(serialized.clone()).unwrap(); + assert_eq!(model, &deserialized); +} + +/// Creates JSON from `json_str` and tries to decode it, then encode the decoded, +/// value and compare it to the JSON value of the json_str. + +pub fn assert_json_str_roundtrip(json_str: &str) +where + T: Serialize + DeserializeOwned + PartialEq + Debug, +{ + let value = serde_json::Value::from_str(json_str).unwrap(); + let deserialized: T = serde_json::from_value(value.clone()).unwrap(); + let serialized = serde_json::to_value(&deserialized).unwrap(); + assert_eq!(value, serialized); +} + +pub fn assert_json_value_fails(json: Value) +where + T: Serialize + DeserializeOwned + PartialEq + Debug, +{ + let result = serde_json::from_value::(json.clone()); + + if let Ok(t) = result { + panic!( + "Expected JSON serialization to fail, but it did not, deserialized into: {:?},\n\nFrom JSON: {}", + t, + serde_json::to_string(&json).unwrap() + ); + } + // all good, expected fail. +} + +pub fn assert_json_fails(json_string: &str) +where + T: Serialize + DeserializeOwned + PartialEq + Debug, +{ + let json = json_string.parse::().unwrap(); + assert_json_value_fails::(json) +} + +pub fn assert_json_eq_ignore_whitespace(json1: &str, json2: &str) { + let value1: Value = serde_json::from_str(json1).expect("Invalid JSON in json1"); + let value2: Value = serde_json::from_str(json2).expect("Invalid JSON in json2"); + assert_eq!(value1, value2, "JSON strings do not match"); +} diff --git a/crates/rules/src/roles/roles_with_factor_ids.rs b/crates/rules/src/roles/roles_with_factor_ids.rs index 3b7d0259..7d97a51c 100644 --- a/crates/rules/src/roles/roles_with_factor_ids.rs +++ b/crates/rules/src/roles/roles_with_factor_ids.rs @@ -61,4 +61,66 @@ mod tests { fn inequality() { assert_ne!(SUT::sample(), SUT::sample_other()); } + + #[test] + fn assert_json_sample_primary() { + let sut = SUT::sample_primary(); + assert_eq_after_json_roundtrip( + &sut, + r#" + { + "role": "primary", + "threshold": 2, + "threshold_factors": [ + { + "discriminator": "fromHash", + "fromHash": { + "kind": "device", + "body": "f1a93d324dd0f2bff89963ab81ed6e0c2ee7e18c0827dc1d3576b2d9f26bbd0a" + } + }, + { + "discriminator": "fromHash", + "fromHash": { + "kind": "ledgerHQHardwareWallet", + "body": "ab59987eedd181fe98e512c1ba0f5ff059f11b5c7c56f15614dcc9fe03fec58b" + } + } + ], + "override_factors": [] + } + "#, + ); + } + + #[test] + fn assert_json_sample_recovery() { + let sut = SUT::sample_recovery(); + assert_eq_after_json_roundtrip( + &sut, + r#" + { + "role": "recovery", + "threshold": 0, + "threshold_factors": [], + "override_factors": [ + { + "discriminator": "fromHash", + "fromHash": { + "kind": "device", + "body": "f1a93d324dd0f2bff89963ab81ed6e0c2ee7e18c0827dc1d3576b2d9f26bbd0a" + } + }, + { + "discriminator": "fromHash", + "fromHash": { + "kind": "ledgerHQHardwareWallet", + "body": "ab59987eedd181fe98e512c1ba0f5ff059f11b5c7c56f15614dcc9fe03fec58b" + } + } + ] + } + "#, + ); + } } From 1af769c999057ec07b24a8eee4b9f67ffd1cda60 Mon Sep 17 00:00:00 2001 From: Alexander Cyon Date: Thu, 28 Nov 2024 13:12:46 +0100 Subject: [PATCH 04/33] wip MatrixOfFactorSources and MatrixOfFactorInstances --- crates/rules-uniffi/src/builder.rs | 6 +-- crates/rules/src/lib.rs | 3 +- .../src/matrices/builder/matrix_builder.rs | 13 ++++--- .../matrices/matrix_with_factor_instances.rs | 3 ++ .../matrices/matrix_with_factor_sources.rs | 3 ++ crates/rules/src/matrices/mod.rs | 4 ++ crates/rules/src/move_to_sargon.rs | 4 ++ .../roles/abstract_role_builder_or_built.rs | 38 +++++++++---------- .../rules/src/roles/builder/roles_builder.rs | 36 +++++++++--------- .../roles/builder/roles_builder_unit_tests.rs | 23 ++++++----- .../rules/src/roles/roles_with_factor_ids.rs | 16 ++++++++ 11 files changed, 92 insertions(+), 57 deletions(-) create mode 100644 crates/rules/src/matrices/matrix_with_factor_instances.rs create mode 100644 crates/rules/src/matrices/matrix_with_factor_sources.rs diff --git a/crates/rules-uniffi/src/builder.rs b/crates/rules-uniffi/src/builder.rs index 3f86d756..d352d5c3 100644 --- a/crates/rules-uniffi/src/builder.rs +++ b/crates/rules-uniffi/src/builder.rs @@ -264,15 +264,15 @@ mod tests { let shield = sut.build("test".to_owned()).unwrap(); assert_eq!( - shield.wrapped_matrix.primary().override_factors(), + shield.wrapped_matrix.primary().get_override_factors(), &vec![FactorSourceID::sample_arculus().inner] ); assert_eq!( - shield.wrapped_matrix.recovery().override_factors(), + shield.wrapped_matrix.recovery().get_override_factors(), &vec![FactorSourceID::sample_ledger().inner] ); assert_eq!( - shield.wrapped_matrix.confirmation().override_factors(), + shield.wrapped_matrix.confirmation().get_override_factors(), &vec![FactorSourceID::sample_device().inner] ); } diff --git a/crates/rules/src/lib.rs b/crates/rules/src/lib.rs index 9ba99b16..3b630a5e 100644 --- a/crates/rules/src/lib.rs +++ b/crates/rules/src/lib.rs @@ -4,7 +4,8 @@ mod roles; pub mod prelude { pub(crate) use sargon::{ - FactorSourceID, FactorSourceIDFromHash, FactorSourceKind, Identifiable, IndexSet, RoleKind, + FactorInstance, FactorSource, FactorSourceID, FactorSourceIDFromHash, FactorSourceKind, + Identifiable, IndexSet, RoleKind, }; #[allow(unused_imports)] diff --git a/crates/rules/src/matrices/builder/matrix_builder.rs b/crates/rules/src/matrices/builder/matrix_builder.rs index 94b5407d..762508ad 100644 --- a/crates/rules/src/matrices/builder/matrix_builder.rs +++ b/crates/rules/src/matrices/builder/matrix_builder.rs @@ -262,13 +262,13 @@ impl MatrixBuilder { fn validate_if_primary_has_single_it_must_not_be_used_by_any_other_role( &self, ) -> MatrixBuilderMutateResult { - let primary_has_single_factor = self.primary_role.factors().len() == 1; + let primary_has_single_factor = self.primary_role.all_factors().len() == 1; if primary_has_single_factor { - let primary_factors = self.primary_role.factors(); + let primary_factors = self.primary_role.all_factors(); let primary_factor = primary_factors.first().unwrap(); - let recovery_set = HashSet::<_>::from_iter(self.recovery_role.override_factors()); + let recovery_set = HashSet::<_>::from_iter(self.recovery_role.get_override_factors()); let confirmation_set = - HashSet::<_>::from_iter(self.confirmation_role.override_factors()); + HashSet::<_>::from_iter(self.confirmation_role.get_override_factors()); if recovery_set.contains(primary_factor) || confirmation_set.contains(primary_factor) { return Err(MatrixBuilderValidation::CombinationViolation( MatrixRolesInCombinationViolation::NotYetValid(MatrixRolesInCombinationNotYetValid::SingleFactorUsedInPrimaryMustNotBeUsedInAnyOtherRole), @@ -281,8 +281,9 @@ impl MatrixBuilder { fn validate_no_factor_may_be_used_in_both_recovery_and_confirmation( &self, ) -> MatrixBuilderMutateResult { - let recovery_set = HashSet::<_>::from_iter(self.recovery_role.override_factors()); - let confirmation_set = HashSet::<_>::from_iter(self.confirmation_role.override_factors()); + let recovery_set = HashSet::<_>::from_iter(self.recovery_role.get_override_factors()); + let confirmation_set = + HashSet::<_>::from_iter(self.confirmation_role.get_override_factors()); let intersection = recovery_set .intersection(&confirmation_set) .collect::>(); diff --git a/crates/rules/src/matrices/matrix_with_factor_instances.rs b/crates/rules/src/matrices/matrix_with_factor_instances.rs new file mode 100644 index 00000000..25fd2bfe --- /dev/null +++ b/crates/rules/src/matrices/matrix_with_factor_instances.rs @@ -0,0 +1,3 @@ +use crate::prelude::*; + +pub type MatrixOfFactorInstances = AbstractMatrixBuilderOrBuilt; diff --git a/crates/rules/src/matrices/matrix_with_factor_sources.rs b/crates/rules/src/matrices/matrix_with_factor_sources.rs new file mode 100644 index 00000000..efb38cc1 --- /dev/null +++ b/crates/rules/src/matrices/matrix_with_factor_sources.rs @@ -0,0 +1,3 @@ +use crate::prelude::*; + +pub type MatrixWithFactorSources = AbstractMatrixBuilderOrBuilt; diff --git a/crates/rules/src/matrices/mod.rs b/crates/rules/src/matrices/mod.rs index f56411f7..486e3933 100644 --- a/crates/rules/src/matrices/mod.rs +++ b/crates/rules/src/matrices/mod.rs @@ -1,8 +1,12 @@ mod abstract_matrix_builder_or_built; mod builder; +mod matrix_with_factor_instances; mod matrix_with_factor_source_ids; +mod matrix_with_factor_sources; pub(crate) use abstract_matrix_builder_or_built::*; #[allow(unused_imports)] pub use builder::*; +pub use matrix_with_factor_instances::*; pub use matrix_with_factor_source_ids::*; +pub use matrix_with_factor_sources::*; diff --git a/crates/rules/src/move_to_sargon.rs b/crates/rules/src/move_to_sargon.rs index 7d925fa9..b8796f7f 100644 --- a/crates/rules/src/move_to_sargon.rs +++ b/crates/rules/src/move_to_sargon.rs @@ -7,6 +7,10 @@ pub enum FactorListKind { Override, } +pub trait HasFactorInstances { + fn unique_factor_instances(&self) -> IndexSet; +} + /// TODO move to Sargon!!!! pub trait HasFactorSourceKindObjectSafe { fn get_factor_source_kind(&self) -> FactorSourceKind; diff --git a/crates/rules/src/roles/abstract_role_builder_or_built.rs b/crates/rules/src/roles/abstract_role_builder_or_built.rs index 7b7e2625..762b7abc 100644 --- a/crates/rules/src/roles/abstract_role_builder_or_built.rs +++ b/crates/rules/src/roles/abstract_role_builder_or_built.rs @@ -33,6 +33,25 @@ impl AbstractRoleBuilderOrBuilt { override_factors: override_factors.into_iter().collect(), } } + + pub fn all_factors(&self) -> Vec<&F> { + self.threshold_factors + .iter() + .chain(self.override_factors.iter()) + .collect() + } + + pub fn get_threshold_factors(&self) -> &Vec { + &self.threshold_factors + } + + pub fn get_override_factors(&self) -> &Vec { + &self.override_factors + } + + pub fn get_threshold(&self) -> u8 { + self.threshold + } } impl RoleBuilder { @@ -50,10 +69,6 @@ impl RoleBuilder { self.role } - pub(crate) fn threshold(&self) -> u8 { - self.threshold - } - pub(crate) fn mut_threshold_factors(&mut self) -> &mut Vec { &mut self.threshold_factors } @@ -62,21 +77,6 @@ impl RoleBuilder { &mut self.override_factors } - pub(crate) fn threshold_factors(&self) -> &Vec { - &self.threshold_factors - } - - pub(crate) fn override_factors(&self) -> &Vec { - &self.override_factors - } - - pub(crate) fn factors(&self) -> Vec<&FactorSourceID> { - self.threshold_factors - .iter() - .chain(self.override_factors.iter()) - .collect() - } - pub(crate) fn unchecked_add_factor_source_to_list( &mut self, factor_source_id: FactorSourceID, diff --git a/crates/rules/src/roles/builder/roles_builder.rs b/crates/rules/src/roles/builder/roles_builder.rs index 96166613..b4ff3c22 100644 --- a/crates/rules/src/roles/builder/roles_builder.rs +++ b/crates/rules/src/roles/builder/roles_builder.rs @@ -223,9 +223,9 @@ impl RoleBuilder { self.validate().map(|_| { RoleWithFactorSourceIds::with_factors( self.role(), - self.threshold(), - self.threshold_factors().clone(), - self.override_factors().clone(), + self.get_threshold(), + self.get_threshold_factors().clone(), + self.get_override_factors().clone(), ) }) } @@ -245,18 +245,18 @@ impl RoleBuilder { } fn override_contains_factor_source(&self, factor_source_id: &FactorSourceID) -> bool { - self.override_factors().contains(factor_source_id) + self.get_override_factors().contains(factor_source_id) } fn threshold_contains_factor_source(&self, factor_source_id: &FactorSourceID) -> bool { - self.threshold_factors().contains(factor_source_id) + self.get_threshold_factors().contains(factor_source_id) } fn override_contains_factor_source_of_kind( &self, factor_source_kind: FactorSourceKind, ) -> bool { - self.override_factors() + self.get_override_factors() .iter() .any(|f| f.get_factor_source_kind() == factor_source_kind) } @@ -265,7 +265,7 @@ impl RoleBuilder { &self, factor_source_kind: FactorSourceKind, ) -> bool { - self.threshold_factors() + self.get_threshold_factors() .iter() .any(|f| f.get_factor_source_kind() == factor_source_kind) } @@ -295,7 +295,7 @@ impl RoleBuilder { let mut simulation = Self::new(self.role()); // Validate override factors - for override_factor in self.override_factors() { + for override_factor in self.get_override_factors() { let validation = simulation.add_factor_source_to_list(*override_factor, FactorListKind::Override); match validation.as_ref() { @@ -307,7 +307,7 @@ impl RoleBuilder { } // Validate threshold factors - for threshold_factor in self.threshold_factors() { + for threshold_factor in self.get_threshold_factors() { let validation = simulation.add_factor_source_to_list(*threshold_factor, FactorListKind::Threshold); match validation.as_ref() { @@ -320,17 +320,17 @@ impl RoleBuilder { // Validate threshold count if self.role() == RoleKind::Primary { - if self.threshold_factors().len() < self.threshold() as usize { + if self.get_threshold_factors().len() < self.get_threshold() as usize { return RoleBuilderMutateResult::not_yet_valid( NotYetValidReason::ThresholdHigherThanThresholdFactorsLen, ); } - if self.threshold() == 0 && !self.threshold_factors().is_empty() { + if self.get_threshold() == 0 && !self.get_threshold_factors().is_empty() { return RoleBuilderMutateResult::not_yet_valid( NotYetValidReason::PrimaryRoleWithThresholdCannotBeZeroWithFactors, ); } - } else if self.threshold() != 0 { + } else if self.get_threshold() != 0 { match self.role() { Primary => unreachable!("Primary role should have been handled earlier"), Recovery => { @@ -342,7 +342,7 @@ impl RoleBuilder { } } - if self.factors().is_empty() { + if self.all_factors().is_empty() { return RoleBuilderMutateResult::not_yet_valid(RoleMustHaveAtLeastOneFactor); } @@ -464,8 +464,8 @@ impl RoleBuilder { } if self.threshold_contains_factor_source(factor_source_id) { remove(self.mut_threshold_factors()); - let threshold_factors_len = self.threshold_factors().len() as u8; - if self.threshold() > threshold_factors_len { + let threshold_factors_len = self.get_threshold_factors().len() as u8; + if self.get_threshold() > threshold_factors_len { self.set_threshold(threshold_factors_len)?; } } @@ -568,7 +568,7 @@ impl RoleBuilder { PrimaryRoleWithPasswordInThresholdListMustHaveAnotherFactor, ); } - if self.threshold() < 2 { + if self.get_threshold() < 2 { return RoleBuilderMutateResult::not_yet_valid( PrimaryRoleWithPasswordInThresholdListMustThresholdGreaterThanOne, ); @@ -596,8 +596,8 @@ impl RoleBuilder { .collect() }; match factor_list_kind { - FactorListKind::Override => filter(self.override_factors()), - FactorListKind::Threshold => filter(self.threshold_factors()), + FactorListKind::Override => filter(self.get_override_factors()), + FactorListKind::Threshold => filter(self.get_threshold_factors()), } } } diff --git a/crates/rules/src/roles/builder/roles_builder_unit_tests.rs b/crates/rules/src/roles/builder/roles_builder_unit_tests.rs index 91f44676..da8b4da1 100644 --- a/crates/rules/src/roles/builder/roles_builder_unit_tests.rs +++ b/crates/rules/src/roles/builder/roles_builder_unit_tests.rs @@ -788,7 +788,7 @@ mod confirmation_in_isolation { // Assert let built = sut.build().unwrap(); - assert!(built.threshold_factors().is_empty()); + assert!(built.get_threshold_factors().is_empty()); assert_eq!( built, RoleWithFactorSourceIds::confirmation_with_factors([sample(), sample_other()]) @@ -1063,12 +1063,12 @@ mod primary_in_isolation { let fs = sample(); sut.add_factor_source_to_list(fs, list()).unwrap(); sut.set_threshold(1).unwrap(); - assert_eq!(sut.threshold(), 1); + assert_eq!(sut.get_threshold(), 1); assert_eq!( sut.remove_factor_source(&fs), Err(Validation::NotYetValid(RoleMustHaveAtLeastOneFactor)) ); - assert_eq!(sut.threshold(), 0); + assert_eq!(sut.get_threshold(), 0); } #[test] @@ -1081,10 +1081,10 @@ mod primary_in_isolation { sut.add_factor_source_to_list(FactorSourceID::sample_arculus_other(), list()) .unwrap(); sut.set_threshold(3).unwrap(); - assert_eq!(sut.threshold(), 3); + assert_eq!(sut.get_threshold(), 3); sut.remove_factor_source(&fs0).unwrap(); sut.remove_factor_source(&fs1).unwrap(); - assert_eq!(sut.threshold(), 1); + assert_eq!(sut.get_threshold(), 1); } #[test] @@ -1097,18 +1097,21 @@ mod primary_in_isolation { sut.add_factor_source_to_list(fs, FactorListKind::Override) .unwrap(); sut.set_threshold(2).unwrap(); - assert_eq!(sut.threshold(), 2); + assert_eq!(sut.get_threshold(), 2); sut.remove_factor_source(&fs).unwrap(); - assert_eq!(sut.threshold(), 2); + assert_eq!(sut.get_threshold(), 2); let built = sut.build().unwrap(); - assert_eq!(built.threshold(), 2); + assert_eq!(built.get_threshold(), 2); assert_eq!(built.role(), RoleKind::Primary); - assert_eq!(built.threshold_factors(), &vec![sample(), sample_other()]); + assert_eq!( + built.get_threshold_factors(), + &vec![sample(), sample_other()] + ); - assert_eq!(built.override_factors(), &Vec::new()); + assert_eq!(built.get_override_factors(), &Vec::new()); } #[test] diff --git a/crates/rules/src/roles/roles_with_factor_ids.rs b/crates/rules/src/roles/roles_with_factor_ids.rs index 7d97a51c..716970ef 100644 --- a/crates/rules/src/roles/roles_with_factor_ids.rs +++ b/crates/rules/src/roles/roles_with_factor_ids.rs @@ -62,6 +62,22 @@ mod tests { assert_ne!(SUT::sample(), SUT::sample_other()); } + #[test] + fn get_all_factors() { + let sut = SUT::sample_primary(); + let factors = sut.all_factors(); + assert_eq!( + factors.len(), + sut.get_override_factors().len() + sut.get_threshold_factors().len() + ); + } + + #[test] + fn get_threshold() { + let sut = SUT::sample_primary(); + assert_eq!(sut.get_threshold(), 2); + } + #[test] fn assert_json_sample_primary() { let sut = SUT::sample_primary(); From f74477bbee7375f9a71b2b857f84d65fca0c0374 Mon Sep 17 00:00:00 2001 From: Alexander Cyon Date: Thu, 28 Nov 2024 13:33:29 +0100 Subject: [PATCH 05/33] from ID -> FactorSource conversion --- crates/rules/src/lib.rs | 4 +- .../matrices/matrix_with_factor_source_ids.rs | 2 - .../matrices/matrix_with_factor_sources.rs | 67 +++++++++++++++++++ crates/rules/src/roles/mod.rs | 2 + .../rules/src/roles/roles_with_factor_ids.rs | 2 - .../src/roles/roles_with_factor_sources.rs | 66 ++++++++++++++++++ 6 files changed, 137 insertions(+), 6 deletions(-) create mode 100644 crates/rules/src/roles/roles_with_factor_sources.rs diff --git a/crates/rules/src/lib.rs b/crates/rules/src/lib.rs index 3b630a5e..5144c39e 100644 --- a/crates/rules/src/lib.rs +++ b/crates/rules/src/lib.rs @@ -4,8 +4,8 @@ mod roles; pub mod prelude { pub(crate) use sargon::{ - FactorInstance, FactorSource, FactorSourceID, FactorSourceIDFromHash, FactorSourceKind, - Identifiable, IndexSet, RoleKind, + CommonError, FactorInstance, FactorSource, FactorSourceID, FactorSourceIDFromHash, + FactorSourceKind, FactorSources, HasSampleValues, Identifiable, IndexSet, RoleKind, }; #[allow(unused_imports)] diff --git a/crates/rules/src/matrices/matrix_with_factor_source_ids.rs b/crates/rules/src/matrices/matrix_with_factor_source_ids.rs index d9c6c7f0..790d20ea 100644 --- a/crates/rules/src/matrices/matrix_with_factor_source_ids.rs +++ b/crates/rules/src/matrices/matrix_with_factor_source_ids.rs @@ -1,5 +1,3 @@ -use sargon::HasSampleValues; - use crate::prelude::*; pub type MatrixWithFactorSourceIds = AbstractMatrixBuilderOrBuilt; diff --git a/crates/rules/src/matrices/matrix_with_factor_sources.rs b/crates/rules/src/matrices/matrix_with_factor_sources.rs index efb38cc1..a466b14b 100644 --- a/crates/rules/src/matrices/matrix_with_factor_sources.rs +++ b/crates/rules/src/matrices/matrix_with_factor_sources.rs @@ -1,3 +1,70 @@ use crate::prelude::*; pub type MatrixWithFactorSources = AbstractMatrixBuilderOrBuilt; + +impl MatrixWithFactorSources { + pub fn new( + matrix_of_factor_source_ids: MatrixWithFactorSourceIds, + factor_sources: &FactorSources, + ) -> Result { + let primary_role = + RoleWithFactorSources::new(matrix_of_factor_source_ids.primary_role, factor_sources)?; + + let recovery_role = + RoleWithFactorSources::new(matrix_of_factor_source_ids.recovery_role, factor_sources)?; + + let confirmation_role = RoleWithFactorSources::new( + matrix_of_factor_source_ids.confirmation_role, + factor_sources, + )?; + + if primary_role.role() != RoleKind::Primary + || recovery_role.role() != RoleKind::Recovery + || confirmation_role.role() != RoleKind::Confirmation + { + unreachable!("Programmer error!") + } + + Ok(Self { + built: PhantomData, + primary_role, + recovery_role, + confirmation_role, + number_of_days_until_auto_confirm: matrix_of_factor_source_ids + .number_of_days_until_auto_confirm, + }) + } +} + +impl HasSampleValues for MatrixWithFactorSources { + fn sample() -> Self { + let ids = MatrixWithFactorSourceIds::sample(); + let factor_sources = FactorSources::sample_values_all(); + Self::new(ids, &factor_sources).unwrap() + } + + fn sample_other() -> Self { + let ids = MatrixWithFactorSourceIds::sample_other(); + let factor_sources = FactorSources::sample_values_all(); + Self::new(ids, &factor_sources).unwrap() + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[allow(clippy::upper_case_acronyms)] + type SUT = MatrixWithFactorSources; + + #[test] + fn equality() { + assert_eq!(SUT::sample(), SUT::sample()); + assert_eq!(SUT::sample_other(), SUT::sample_other()); + } + + #[test] + fn inequality() { + assert_ne!(SUT::sample(), SUT::sample_other()); + } +} diff --git a/crates/rules/src/roles/mod.rs b/crates/rules/src/roles/mod.rs index 1e7237a1..9c242b6f 100644 --- a/crates/rules/src/roles/mod.rs +++ b/crates/rules/src/roles/mod.rs @@ -1,7 +1,9 @@ mod abstract_role_builder_or_built; mod builder; mod roles_with_factor_ids; +mod roles_with_factor_sources; pub(crate) use abstract_role_builder_or_built::*; pub use builder::*; pub use roles_with_factor_ids::*; +pub(crate) use roles_with_factor_sources::*; diff --git a/crates/rules/src/roles/roles_with_factor_ids.rs b/crates/rules/src/roles/roles_with_factor_ids.rs index 716970ef..e137dc13 100644 --- a/crates/rules/src/roles/roles_with_factor_ids.rs +++ b/crates/rules/src/roles/roles_with_factor_ids.rs @@ -1,5 +1,3 @@ -use sargon::HasSampleValues; - use crate::prelude::*; pub type RoleWithFactorSourceIds = AbstractBuiltRoleWithFactor; diff --git a/crates/rules/src/roles/roles_with_factor_sources.rs b/crates/rules/src/roles/roles_with_factor_sources.rs new file mode 100644 index 00000000..98ffcc30 --- /dev/null +++ b/crates/rules/src/roles/roles_with_factor_sources.rs @@ -0,0 +1,66 @@ +use crate::prelude::*; + +pub(crate) type RoleWithFactorSources = AbstractBuiltRoleWithFactor; + +impl RoleWithFactorSources { + pub fn new( + role_with_factor_source_ids: RoleWithFactorSourceIds, + factor_sources: &FactorSources, + ) -> Result { + let lookup_f = |id: &FactorSourceID| -> Result { + factor_sources + .get_id(id) + .ok_or(CommonError::FactorSourceDiscrepancy) + .cloned() + }; + + let lookup = |ids: &Vec| -> Result, CommonError> { + ids.iter() + .map(lookup_f) + .collect::, CommonError>>() + }; + + let threshold_factors = lookup(role_with_factor_source_ids.get_threshold_factors())?; + let override_factors = lookup(role_with_factor_source_ids.get_override_factors())?; + + Ok(Self::with_factors( + role_with_factor_source_ids.role(), + role_with_factor_source_ids.get_threshold(), + threshold_factors, + override_factors, + )) + } +} + +impl HasSampleValues for RoleWithFactorSources { + fn sample() -> Self { + let ids = RoleWithFactorSourceIds::sample(); + let factor_sources = FactorSources::sample_values_all(); + Self::new(ids, &factor_sources).unwrap() + } + + fn sample_other() -> Self { + let ids = RoleWithFactorSourceIds::sample_other(); + let factor_sources = FactorSources::sample_values_all(); + Self::new(ids, &factor_sources).unwrap() + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[allow(clippy::upper_case_acronyms)] + type SUT = RoleWithFactorSources; + + #[test] + fn equality() { + assert_eq!(SUT::sample(), SUT::sample()); + assert_eq!(SUT::sample_other(), SUT::sample_other()); + } + + #[test] + fn inequality() { + assert_ne!(SUT::sample(), SUT::sample_other()); + } +} From 5f07294206f52d5b20bec230e0cd3372e9a24e86 Mon Sep 17 00:00:00 2001 From: Alexander Cyon Date: Thu, 28 Nov 2024 13:50:19 +0100 Subject: [PATCH 06/33] Add SecurityStructureOfFactorSourceIds --- crates/rules-uniffi/src/builder.rs | 30 ++++++++++++----- crates/rules-uniffi/src/error_conversion.rs | 3 ++ crates/rules/src/lib.rs | 7 ++-- .../abstract_security_structure_of_factors.rs | 32 +++++++++++++++++++ .../src/security_structure_of_factors/mod.rs | 5 +++ ...security_structure_of_factor_source_ids.rs | 3 ++ 6 files changed, 70 insertions(+), 10 deletions(-) create mode 100644 crates/rules/src/security_structure_of_factors/abstract_security_structure_of_factors.rs create mode 100644 crates/rules/src/security_structure_of_factors/mod.rs create mode 100644 crates/rules/src/security_structure_of_factors/security_structure_of_factor_source_ids.rs diff --git a/crates/rules-uniffi/src/builder.rs b/crates/rules-uniffi/src/builder.rs index d352d5c3..e7aaaa16 100644 --- a/crates/rules-uniffi/src/builder.rs +++ b/crates/rules-uniffi/src/builder.rs @@ -16,8 +16,7 @@ pub struct SecurityShieldBuilder { #[derive(Debug, PartialEq, Eq, Hash, uniffi::Object)] #[uniffi::export(Debug, Eq, Hash)] pub struct SecurityStructureOfFactorSourceIds { - wrapped_matrix: MatrixWithFactorSourceIds, - name: String, + pub wrapped: rules::SecurityStructureOfFactorSourceIds, } impl SecurityShieldBuilder { @@ -226,11 +225,14 @@ impl SecurityShieldBuilder { .build() .map_err(|e| CommonError::BuildError(format!("{:?}", e)))?; + let display_name = + sargon::DisplayName::new(name).map_err(|e| CommonError::Sargon(format!("{:?}", e)))?; + let wrapped_shield = + rules::SecurityStructureOfFactorSourceIds::new(display_name, wrapped_matrix); + let shield = SecurityStructureOfFactorSourceIds { - wrapped_matrix, - name, + wrapped: wrapped_shield, }; - Ok(shield) } } @@ -264,15 +266,27 @@ mod tests { let shield = sut.build("test".to_owned()).unwrap(); assert_eq!( - shield.wrapped_matrix.primary().get_override_factors(), + shield + .wrapped + .matrix_of_factors + .primary() + .get_override_factors(), &vec![FactorSourceID::sample_arculus().inner] ); assert_eq!( - shield.wrapped_matrix.recovery().get_override_factors(), + shield + .wrapped + .matrix_of_factors + .recovery() + .get_override_factors(), &vec![FactorSourceID::sample_ledger().inner] ); assert_eq!( - shield.wrapped_matrix.confirmation().get_override_factors(), + shield + .wrapped + .matrix_of_factors + .confirmation() + .get_override_factors(), &vec![FactorSourceID::sample_device().inner] ); } diff --git a/crates/rules-uniffi/src/error_conversion.rs b/crates/rules-uniffi/src/error_conversion.rs index 52a833ae..7022a621 100644 --- a/crates/rules-uniffi/src/error_conversion.rs +++ b/crates/rules-uniffi/src/error_conversion.rs @@ -2,6 +2,9 @@ use crate::prelude::*; #[derive(Debug, Clone, PartialEq, Eq, thiserror::Error, uniffi::Error)] pub enum CommonError { + #[error("Sargon")] + Sargon(String), + #[error("AlreadyBuilt")] AlreadyBuilt, diff --git a/crates/rules/src/lib.rs b/crates/rules/src/lib.rs index 5144c39e..26d8fe28 100644 --- a/crates/rules/src/lib.rs +++ b/crates/rules/src/lib.rs @@ -1,17 +1,20 @@ mod matrices; mod move_to_sargon; mod roles; +mod security_structure_of_factors; pub mod prelude { pub(crate) use sargon::{ - CommonError, FactorInstance, FactorSource, FactorSourceID, FactorSourceIDFromHash, - FactorSourceKind, FactorSources, HasSampleValues, Identifiable, IndexSet, RoleKind, + CommonError, DisplayName, FactorInstance, FactorSource, FactorSourceID, + FactorSourceIDFromHash, FactorSourceKind, FactorSources, HasSampleValues, Identifiable, + IndexSet, RoleKind, }; #[allow(unused_imports)] pub use crate::matrices::*; pub use crate::move_to_sargon::*; pub use crate::roles::*; + pub use crate::security_structure_of_factors::*; pub(crate) use serde::{Deserialize, Serialize}; pub(crate) use std::collections::HashSet; diff --git a/crates/rules/src/security_structure_of_factors/abstract_security_structure_of_factors.rs b/crates/rules/src/security_structure_of_factors/abstract_security_structure_of_factors.rs new file mode 100644 index 00000000..a04fca4f --- /dev/null +++ b/crates/rules/src/security_structure_of_factors/abstract_security_structure_of_factors.rs @@ -0,0 +1,32 @@ +use crate::prelude::*; + +#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] +pub struct AbstractSecurityStructure { + /// Metadata of this Security Structure, such as globally unique and + /// stable identifier, creation date and user chosen label (name). + pub metadata: sargon::SecurityStructureMetadata, + + /// The structure of factors to use for certain roles, Primary, Recovery + /// and Confirmation role. + pub matrix_of_factors: AbstractMatrixBuilderOrBuilt, +} + +impl AbstractSecurityStructure { + pub fn with_metadata( + metadata: sargon::SecurityStructureMetadata, + matrix_of_factors: AbstractMatrixBuilderOrBuilt, + ) -> Self { + Self { + metadata, + matrix_of_factors, + } + } + + pub fn new( + display_name: DisplayName, + matrix_of_factors: AbstractMatrixBuilderOrBuilt, + ) -> Self { + let metadata = sargon::SecurityStructureMetadata::new(display_name); + Self::with_metadata(metadata, matrix_of_factors) + } +} diff --git a/crates/rules/src/security_structure_of_factors/mod.rs b/crates/rules/src/security_structure_of_factors/mod.rs new file mode 100644 index 00000000..ebe038dc --- /dev/null +++ b/crates/rules/src/security_structure_of_factors/mod.rs @@ -0,0 +1,5 @@ +mod abstract_security_structure_of_factors; +mod security_structure_of_factor_source_ids; + +pub(super) use abstract_security_structure_of_factors::*; +pub use security_structure_of_factor_source_ids::*; diff --git a/crates/rules/src/security_structure_of_factors/security_structure_of_factor_source_ids.rs b/crates/rules/src/security_structure_of_factors/security_structure_of_factor_source_ids.rs new file mode 100644 index 00000000..48b97fa3 --- /dev/null +++ b/crates/rules/src/security_structure_of_factors/security_structure_of_factor_source_ids.rs @@ -0,0 +1,3 @@ +use crate::prelude::*; + +pub type SecurityStructureOfFactorSourceIds = AbstractSecurityStructure; From b4bb6ac5e5b68991a430722b8fc16434104cb033 Mon Sep 17 00:00:00 2001 From: Alexander Cyon Date: Thu, 28 Nov 2024 13:58:59 +0100 Subject: [PATCH 07/33] add security structure of factor sources --- .../matrices/matrix_with_factor_source_ids.rs | 3 +- .../matrices/matrix_with_factor_sources.rs | 2 +- .../abstract_security_structure_of_factors.rs | 9 +- .../src/security_structure_of_factors/mod.rs | 2 + ...security_structure_of_factor_source_ids.rs | 184 ++++++++++++++++++ .../security_structure_of_factor_sources.rs | 33 ++++ 6 files changed, 225 insertions(+), 8 deletions(-) create mode 100644 crates/rules/src/security_structure_of_factors/security_structure_of_factor_sources.rs diff --git a/crates/rules/src/matrices/matrix_with_factor_source_ids.rs b/crates/rules/src/matrices/matrix_with_factor_source_ids.rs index 790d20ea..5886ca9f 100644 --- a/crates/rules/src/matrices/matrix_with_factor_source_ids.rs +++ b/crates/rules/src/matrices/matrix_with_factor_source_ids.rs @@ -1,6 +1,7 @@ use crate::prelude::*; -pub type MatrixWithFactorSourceIds = AbstractMatrixBuilderOrBuilt; +pub type AbstractMatrixBuilt = AbstractMatrixBuilderOrBuilt; +pub type MatrixWithFactorSourceIds = AbstractMatrixBuilt; #[cfg(test)] impl MatrixWithFactorSourceIds { diff --git a/crates/rules/src/matrices/matrix_with_factor_sources.rs b/crates/rules/src/matrices/matrix_with_factor_sources.rs index a466b14b..08252185 100644 --- a/crates/rules/src/matrices/matrix_with_factor_sources.rs +++ b/crates/rules/src/matrices/matrix_with_factor_sources.rs @@ -1,6 +1,6 @@ use crate::prelude::*; -pub type MatrixWithFactorSources = AbstractMatrixBuilderOrBuilt; +pub type MatrixWithFactorSources = AbstractMatrixBuilt; impl MatrixWithFactorSources { pub fn new( diff --git a/crates/rules/src/security_structure_of_factors/abstract_security_structure_of_factors.rs b/crates/rules/src/security_structure_of_factors/abstract_security_structure_of_factors.rs index a04fca4f..217c2dde 100644 --- a/crates/rules/src/security_structure_of_factors/abstract_security_structure_of_factors.rs +++ b/crates/rules/src/security_structure_of_factors/abstract_security_structure_of_factors.rs @@ -8,13 +8,13 @@ pub struct AbstractSecurityStructure { /// The structure of factors to use for certain roles, Primary, Recovery /// and Confirmation role. - pub matrix_of_factors: AbstractMatrixBuilderOrBuilt, + pub matrix_of_factors: AbstractMatrixBuilt, } impl AbstractSecurityStructure { pub fn with_metadata( metadata: sargon::SecurityStructureMetadata, - matrix_of_factors: AbstractMatrixBuilderOrBuilt, + matrix_of_factors: AbstractMatrixBuilt, ) -> Self { Self { metadata, @@ -22,10 +22,7 @@ impl AbstractSecurityStructure { } } - pub fn new( - display_name: DisplayName, - matrix_of_factors: AbstractMatrixBuilderOrBuilt, - ) -> Self { + pub fn new(display_name: DisplayName, matrix_of_factors: AbstractMatrixBuilt) -> Self { let metadata = sargon::SecurityStructureMetadata::new(display_name); Self::with_metadata(metadata, matrix_of_factors) } diff --git a/crates/rules/src/security_structure_of_factors/mod.rs b/crates/rules/src/security_structure_of_factors/mod.rs index ebe038dc..29db8460 100644 --- a/crates/rules/src/security_structure_of_factors/mod.rs +++ b/crates/rules/src/security_structure_of_factors/mod.rs @@ -1,5 +1,7 @@ mod abstract_security_structure_of_factors; mod security_structure_of_factor_source_ids; +mod security_structure_of_factor_sources; pub(super) use abstract_security_structure_of_factors::*; pub use security_structure_of_factor_source_ids::*; +pub use security_structure_of_factor_sources::*; diff --git a/crates/rules/src/security_structure_of_factors/security_structure_of_factor_source_ids.rs b/crates/rules/src/security_structure_of_factors/security_structure_of_factor_source_ids.rs index 48b97fa3..3dfebdea 100644 --- a/crates/rules/src/security_structure_of_factors/security_structure_of_factor_source_ids.rs +++ b/crates/rules/src/security_structure_of_factors/security_structure_of_factor_source_ids.rs @@ -1,3 +1,187 @@ use crate::prelude::*; pub type SecurityStructureOfFactorSourceIds = AbstractSecurityStructure; + +impl HasSampleValues for SecurityStructureOfFactorSourceIds { + fn sample() -> Self { + let metadata = sargon::SecurityStructureMetadata::sample(); + Self::with_metadata(metadata, MatrixWithFactorSourceIds::sample()) + } + + fn sample_other() -> Self { + let metadata = sargon::SecurityStructureMetadata::sample_other(); + Self::with_metadata(metadata, MatrixWithFactorSourceIds::sample_other()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + #[allow(clippy::upper_case_acronyms)] + type SUT = SecurityStructureOfFactorSourceIds; + + #[test] + fn equality() { + assert_eq!(SUT::sample(), SUT::sample()); + assert_eq!(SUT::sample_other(), SUT::sample_other()); + } + + #[test] + fn inequality() { + assert_ne!(SUT::sample(), SUT::sample_other()); + } + + #[test] + fn assert_json_sample() { + let sut = SUT::sample(); + assert_eq_after_json_roundtrip( + &sut, + r#" + { + "metadata": { + "id": "ffffffff-ffff-ffff-ffff-ffffffffffff", + "displayName": "Spending Account", + "createdOn": "2023-09-11T16:05:56.000Z", + "lastUpdatedOn": "2023-09-11T16:05:56.000Z" + }, + "matrix_of_factors": { + "primary_role": { + "role": "primary", + "threshold": 2, + "threshold_factors": [ + { + "discriminator": "fromHash", + "fromHash": { + "kind": "device", + "body": "f1a93d324dd0f2bff89963ab81ed6e0c2ee7e18c0827dc1d3576b2d9f26bbd0a" + } + }, + { + "discriminator": "fromHash", + "fromHash": { + "kind": "ledgerHQHardwareWallet", + "body": "ab59987eedd181fe98e512c1ba0f5ff059f11b5c7c56f15614dcc9fe03fec58b" + } + } + ], + "override_factors": [] + }, + "recovery_role": { + "role": "recovery", + "threshold": 0, + "threshold_factors": [], + "override_factors": [ + { + "discriminator": "fromHash", + "fromHash": { + "kind": "device", + "body": "f1a93d324dd0f2bff89963ab81ed6e0c2ee7e18c0827dc1d3576b2d9f26bbd0a" + } + }, + { + "discriminator": "fromHash", + "fromHash": { + "kind": "ledgerHQHardwareWallet", + "body": "ab59987eedd181fe98e512c1ba0f5ff059f11b5c7c56f15614dcc9fe03fec58b" + } + } + ] + }, + "confirmation_role": { + "role": "confirmation", + "threshold": 0, + "threshold_factors": [], + "override_factors": [ + { + "discriminator": "fromHash", + "fromHash": { + "kind": "passphrase", + "body": "181ab662e19fac3ad9f08d5c673b286d4a5ed9cd3762356dc9831dc42427c1b9" + } + } + ] + }, + "number_of_days_until_auto_confirm": 14 + } + } + "#, + ); + } + + #[test] + fn assert_json_sample_other() { + let sut = SUT::sample_other(); + assert_eq_after_json_roundtrip( + &sut, + r#" + { + "metadata": { + "id": "dededede-dede-dede-dede-dededededede", + "displayName": "Savings Account", + "createdOn": "2023-12-24T17:13:56.123Z", + "lastUpdatedOn": "2023-12-24T17:13:56.123Z" + }, + "matrix_of_factors": { + "primary_role": { + "role": "primary", + "threshold": 2, + "threshold_factors": [ + { + "discriminator": "fromHash", + "fromHash": { + "kind": "ledgerHQHardwareWallet", + "body": "ab59987eedd181fe98e512c1ba0f5ff059f11b5c7c56f15614dcc9fe03fec58b" + } + }, + { + "discriminator": "fromHash", + "fromHash": { + "kind": "passphrase", + "body": "181ab662e19fac3ad9f08d5c673b286d4a5ed9cd3762356dc9831dc42427c1b9" + } + } + ], + "override_factors": [] + }, + "recovery_role": { + "role": "recovery", + "threshold": 0, + "threshold_factors": [], + "override_factors": [ + { + "discriminator": "fromHash", + "fromHash": { + "kind": "device", + "body": "f1a93d324dd0f2bff89963ab81ed6e0c2ee7e18c0827dc1d3576b2d9f26bbd0a" + } + }, + { + "discriminator": "fromHash", + "fromHash": { + "kind": "ledgerHQHardwareWallet", + "body": "ab59987eedd181fe98e512c1ba0f5ff059f11b5c7c56f15614dcc9fe03fec58b" + } + } + ] + }, + "confirmation_role": { + "role": "confirmation", + "threshold": 0, + "threshold_factors": [], + "override_factors": [ + { + "discriminator": "fromHash", + "fromHash": { + "kind": "passphrase", + "body": "181ab662e19fac3ad9f08d5c673b286d4a5ed9cd3762356dc9831dc42427c1b9" + } + } + ] + }, + "number_of_days_until_auto_confirm": 14 + } + } + "#, + ); + } +} diff --git a/crates/rules/src/security_structure_of_factors/security_structure_of_factor_sources.rs b/crates/rules/src/security_structure_of_factors/security_structure_of_factor_sources.rs new file mode 100644 index 00000000..ad92f1ed --- /dev/null +++ b/crates/rules/src/security_structure_of_factors/security_structure_of_factor_sources.rs @@ -0,0 +1,33 @@ +use crate::prelude::*; + +pub type SecurityStructureOfFactorSources = AbstractSecurityStructure; + +impl HasSampleValues for SecurityStructureOfFactorSources { + fn sample() -> Self { + let metadata = sargon::SecurityStructureMetadata::sample(); + Self::with_metadata(metadata, MatrixWithFactorSources::sample()) + } + + fn sample_other() -> Self { + let metadata = sargon::SecurityStructureMetadata::sample_other(); + Self::with_metadata(metadata, MatrixWithFactorSources::sample_other()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + #[allow(clippy::upper_case_acronyms)] + type SUT = SecurityStructureOfFactorSources; + + #[test] + fn equality() { + assert_eq!(SUT::sample(), SUT::sample()); + assert_eq!(SUT::sample_other(), SUT::sample_other()); + } + + #[test] + fn inequality() { + assert_ne!(SUT::sample(), SUT::sample_other()); + } +} From ce4c59d4357c05281fad1253d2cf979782703af8 Mon Sep 17 00:00:00 2001 From: Alexander Cyon Date: Thu, 28 Nov 2024 14:53:10 +0100 Subject: [PATCH 08/33] impl matrix_of_factor_instances --- crates/rules/src/lib.rs | 10 +- .../abstract_matrix_builder_or_built.rs | 12 ++ .../src/matrices/builder/matrix_builder.rs | 11 +- .../builder/matrix_builder_unit_tests.rs | 26 +-- .../matrices/matrix_of_factor_instances.rs | 148 ++++++++++++++ ..._ids.rs => matrix_of_factor_source_ids.rs} | 13 +- ...sources.rs => matrix_of_factor_sources.rs} | 14 +- .../matrices/matrix_with_factor_instances.rs | 3 - crates/rules/src/matrices/mod.rs | 12 +- .../roles/abstract_role_builder_or_built.rs | 31 ++- crates/rules/src/roles/mod.rs | 2 + .../src/roles/role_with_factor_instances.rs | 186 ++++++++++++++++++ ...security_structure_of_factor_source_ids.rs | 4 +- .../security_structure_of_factor_sources.rs | 4 +- 14 files changed, 423 insertions(+), 53 deletions(-) create mode 100644 crates/rules/src/matrices/matrix_of_factor_instances.rs rename crates/rules/src/matrices/{matrix_with_factor_source_ids.rs => matrix_of_factor_source_ids.rs} (96%) rename crates/rules/src/matrices/{matrix_with_factor_sources.rs => matrix_of_factor_sources.rs} (82%) delete mode 100644 crates/rules/src/matrices/matrix_with_factor_instances.rs create mode 100644 crates/rules/src/roles/role_with_factor_instances.rs diff --git a/crates/rules/src/lib.rs b/crates/rules/src/lib.rs index 26d8fe28..c412638c 100644 --- a/crates/rules/src/lib.rs +++ b/crates/rules/src/lib.rs @@ -5,11 +5,15 @@ mod security_structure_of_factors; pub mod prelude { pub(crate) use sargon::{ - CommonError, DisplayName, FactorInstance, FactorSource, FactorSourceID, - FactorSourceIDFromHash, FactorSourceKind, FactorSources, HasSampleValues, Identifiable, - IndexSet, RoleKind, + BaseIsFactorSource, CommonError, DisplayName, FactorInstance, FactorInstances, + FactorSource, FactorSourceID, FactorSourceIDFromHash, FactorSourceKind, FactorSources, + HasSampleValues, HierarchicalDeterministicFactorInstance, Identifiable, IndexMap, IndexSet, + IsMaybeKeySpaceAware, KeySpace, RoleKind, }; + #[cfg(test)] + pub(crate) use sargon::JustKV; + #[allow(unused_imports)] pub use crate::matrices::*; pub use crate::move_to_sargon::*; diff --git a/crates/rules/src/matrices/abstract_matrix_builder_or_built.rs b/crates/rules/src/matrices/abstract_matrix_builder_or_built.rs index 265e2021..fc3ada77 100644 --- a/crates/rules/src/matrices/abstract_matrix_builder_or_built.rs +++ b/crates/rules/src/matrices/abstract_matrix_builder_or_built.rs @@ -15,3 +15,15 @@ pub struct AbstractMatrixBuilderOrBuilt { impl AbstractMatrixBuilderOrBuilt { pub const DEFAULT_NUMBER_OF_DAYS_UNTIL_AUTO_CONFIRM: u16 = 14; } + +pub type AbstractMatrixBuilt = AbstractMatrixBuilderOrBuilt; + +impl AbstractMatrixBuilt { + pub fn all_factors(&self) -> HashSet<&F> { + let mut factors = HashSet::new(); + factors.extend(self.primary_role.all_factors()); + factors.extend(self.recovery_role.all_factors()); + factors.extend(self.confirmation_role.all_factors()); + factors + } +} diff --git a/crates/rules/src/matrices/builder/matrix_builder.rs b/crates/rules/src/matrices/builder/matrix_builder.rs index 762508ad..0b7459a5 100644 --- a/crates/rules/src/matrices/builder/matrix_builder.rs +++ b/crates/rules/src/matrices/builder/matrix_builder.rs @@ -3,13 +3,10 @@ use crate::prelude::*; pub type MatrixBuilderMutateResult = Result<(), MatrixBuilderValidation>; -pub type MatrixBuilderBuildResult = Result; +pub type MatrixBuilderBuildResult = Result; -pub type MatrixBuilder = AbstractMatrixBuilderOrBuilt< - FactorSourceID, - MatrixWithFactorSourceIds, - RoleWithFactorSourceIds, ->; +pub type MatrixBuilder = + AbstractMatrixBuilderOrBuilt; // ================== // ===== PUBLIC ===== @@ -41,7 +38,7 @@ impl MatrixBuilder { .build() .into_matrix_err(RoleKind::Confirmation)?; - let built = MatrixWithFactorSourceIds { + let built = MatrixOfFactorSourceIds { built: PhantomData, primary_role: primary, recovery_role: recovery, diff --git a/crates/rules/src/matrices/builder/matrix_builder_unit_tests.rs b/crates/rules/src/matrices/builder/matrix_builder_unit_tests.rs index ca8f34c5..79567229 100644 --- a/crates/rules/src/matrices/builder/matrix_builder_unit_tests.rs +++ b/crates/rules/src/matrices/builder/matrix_builder_unit_tests.rs @@ -114,7 +114,7 @@ fn set_number_of_days_42() { let built = sut.build().unwrap(); pretty_assertions::assert_eq!( built, - MatrixWithFactorSourceIds::with_roles_and_days( + MatrixOfFactorSourceIds::with_roles_and_days( RoleWithFactorSourceIds::primary_with_factors( 1, [FactorSourceID::sample_device(),], @@ -155,7 +155,7 @@ fn set_number_of_days_if_not_set_uses_default() { let built = sut.build().unwrap(); pretty_assertions::assert_eq!( built, - MatrixWithFactorSourceIds::with_roles_and_days( + MatrixOfFactorSourceIds::with_roles_and_days( RoleWithFactorSourceIds::primary_with_factors( 1, [FactorSourceID::sample_device(),], @@ -1263,7 +1263,7 @@ mod shield_configs { let built = sut.build().unwrap(); pretty_assertions::assert_eq!( built, - MatrixWithFactorSourceIds::with_roles( + MatrixOfFactorSourceIds::with_roles( RoleWithFactorSourceIds::primary_with_factors( 2, [ @@ -1313,7 +1313,7 @@ mod shield_configs { let built = sut.build().unwrap(); pretty_assertions::assert_eq!( built, - MatrixWithFactorSourceIds::with_roles( + MatrixOfFactorSourceIds::with_roles( RoleWithFactorSourceIds::primary_with_factors( 2, [ @@ -1363,7 +1363,7 @@ mod shield_configs { let built = sut.build().unwrap(); pretty_assertions::assert_eq!( built, - MatrixWithFactorSourceIds::with_roles( + MatrixOfFactorSourceIds::with_roles( RoleWithFactorSourceIds::primary_with_factors( 2, [ @@ -1405,7 +1405,7 @@ mod shield_configs { let built = sut.build().unwrap(); pretty_assertions::assert_eq!( built, - MatrixWithFactorSourceIds::with_roles( + MatrixOfFactorSourceIds::with_roles( RoleWithFactorSourceIds::primary_with_factors( 1, [FactorSourceID::sample_device(),], @@ -1443,7 +1443,7 @@ mod shield_configs { let built = sut.build().unwrap(); pretty_assertions::assert_eq!( built, - MatrixWithFactorSourceIds::with_roles( + MatrixOfFactorSourceIds::with_roles( RoleWithFactorSourceIds::primary_with_factors( 1, [FactorSourceID::sample_ledger(),], @@ -1485,7 +1485,7 @@ mod shield_configs { let built = sut.build().unwrap(); pretty_assertions::assert_eq!( built, - MatrixWithFactorSourceIds::with_roles( + MatrixOfFactorSourceIds::with_roles( RoleWithFactorSourceIds::primary_with_factors( 2, [ @@ -1531,7 +1531,7 @@ mod shield_configs { let built = sut.build().unwrap(); pretty_assertions::assert_eq!( built, - MatrixWithFactorSourceIds::with_roles( + MatrixOfFactorSourceIds::with_roles( RoleWithFactorSourceIds::primary_with_factors( 2, [ @@ -1573,7 +1573,7 @@ mod shield_configs { let built = sut.build().unwrap(); pretty_assertions::assert_eq!( built, - MatrixWithFactorSourceIds::with_roles( + MatrixOfFactorSourceIds::with_roles( RoleWithFactorSourceIds::primary_with_factors( 0, [], @@ -1611,7 +1611,7 @@ mod shield_configs { let built = sut.build().unwrap(); pretty_assertions::assert_eq!( built, - MatrixWithFactorSourceIds::with_roles( + MatrixOfFactorSourceIds::with_roles( RoleWithFactorSourceIds::primary_with_factors( 0, [], @@ -1655,7 +1655,7 @@ mod shield_configs { let built = sut.build().unwrap(); pretty_assertions::assert_eq!( built, - MatrixWithFactorSourceIds::with_roles( + MatrixOfFactorSourceIds::with_roles( RoleWithFactorSourceIds::primary_with_factors( 2, [ @@ -1706,7 +1706,7 @@ mod shield_configs { let built = sut.build().unwrap(); pretty_assertions::assert_eq!( built, - MatrixWithFactorSourceIds::with_roles( + MatrixOfFactorSourceIds::with_roles( RoleWithFactorSourceIds::primary_with_factors( 2, [ diff --git a/crates/rules/src/matrices/matrix_of_factor_instances.rs b/crates/rules/src/matrices/matrix_of_factor_instances.rs new file mode 100644 index 00000000..ae7df3bd --- /dev/null +++ b/crates/rules/src/matrices/matrix_of_factor_instances.rs @@ -0,0 +1,148 @@ +use crate::prelude::*; + +pub type MatrixOfFactorInstances = AbstractMatrixBuilderOrBuilt; + +impl HasFactorInstances for MatrixOfFactorInstances { + fn unique_factor_instances(&self) -> IndexSet { + let mut set = IndexSet::new(); + set.extend(self.primary_role.all_factors().into_iter().cloned()); + set.extend(self.recovery_role.all_factors().into_iter().cloned()); + set.extend(self.confirmation_role.all_factors().into_iter().cloned()); + set + } +} + +impl HasSampleValues for MatrixOfFactorInstances { + fn sample() -> Self { + Self { + built: PhantomData, + primary_role: RoleWithFactorInstances::sample_primary(), + recovery_role: RoleWithFactorInstances::sample_recovery(), + confirmation_role: RoleWithFactorInstances::sample_confirmation(), + number_of_days_until_auto_confirm: 30, + } + } + + fn sample_other() -> Self { + Self { + built: PhantomData, + primary_role: RoleWithFactorInstances::sample_primary_other(), + recovery_role: RoleWithFactorInstances::sample_recovery_other(), + confirmation_role: RoleWithFactorInstances::sample_confirmation_other(), + number_of_days_until_auto_confirm: 15, + } + } +} + +impl MatrixOfFactorInstances { + /// Maps `MatrixOfFactorSources -> MatrixOfFactorInstances` by + /// "assigning" FactorInstances to each MatrixOfFactorInstances from + /// `consuming_instances`. + /// + /// NOTE: + /// **One FactorInstance might be used multiple times in the MatrixOfFactorInstances, + /// e.g. ones in the PrimaryRole(WithFactorInstances) and again in RecoveryRole(WithFactorInstances) or + /// in RecoveryRole(WithFactorInstances)**. + /// + /// However, the same FactorInstance is NEVER used in two different MatrixOfFactorInstances. + /// + /// + pub fn fulfilling_matrix_of_factor_sources_with_instances( + consuming_instances: &mut IndexMap, + matrix_of_factor_sources: MatrixOfFactorSources, + ) -> Result { + let instances = &consuming_instances.clone(); + + let primary_role = + RoleWithFactorInstances::fulfilling_role_of_factor_sources_with_factor_instances( + RoleKind::Primary, + instances, + &matrix_of_factor_sources, + )?; + let recovery_role = + RoleWithFactorInstances::fulfilling_role_of_factor_sources_with_factor_instances( + RoleKind::Recovery, + instances, + &matrix_of_factor_sources, + )?; + let confirmation_role = + RoleWithFactorInstances::fulfilling_role_of_factor_sources_with_factor_instances( + RoleKind::Confirmation, + instances, + &matrix_of_factor_sources, + )?; + + let matrix = Self { + built: PhantomData, + primary_role, + recovery_role, + confirmation_role, + number_of_days_until_auto_confirm: matrix_of_factor_sources + .number_of_days_until_auto_confirm, + }; + + // Now that we have assigned instances, **possibly the SAME INSTANCE to multiple roles**, + // lets delete them from the `consuming_instances` map. + for instance in matrix.all_factors() { + let fsid = &FactorSourceIDFromHash::try_from(instance.factor_source_id).unwrap(); + let existing = consuming_instances.get_mut(fsid).unwrap(); + + let to_remove = + HierarchicalDeterministicFactorInstance::try_from(instance.clone()).unwrap(); + + // We remove at the beginning of the list first. + existing.shift_remove(&to_remove); + + if existing.is_empty() { + // not needed per se, but feels prudent to "prune". + consuming_instances.shift_remove_entry(fsid); + } + } + + Ok(matrix) + } +} +#[cfg(test)] +mod tests { + + use super::*; + + #[allow(clippy::upper_case_acronyms)] + type SUT = MatrixOfFactorInstances; + + #[test] + fn equality() { + assert_eq!(SUT::sample(), SUT::sample()); + assert_eq!(SUT::sample_other(), SUT::sample_other()); + } + + #[test] + fn inequality() { + assert_ne!(SUT::sample(), SUT::sample_other()); + } + + #[test] + fn err_if_no_instance_found_for_factor_source() { + assert!(matches!( + SUT::fulfilling_matrix_of_factor_sources_with_instances( + &mut IndexMap::new(), + MatrixOfFactorSources::sample() + ), + Err(CommonError::MissingFactorMappingInstancesIntoRole) + )); + } + + #[test] + fn err_if_empty_instance_found_for_factor_source() { + assert!(matches!( + SUT::fulfilling_matrix_of_factor_sources_with_instances( + &mut IndexMap::kv( + FactorSource::sample_device_babylon().id_from_hash(), + FactorInstances::from_iter([]) + ), + MatrixOfFactorSources::sample() + ), + Err(CommonError::MissingFactorMappingInstancesIntoRole) + )); + } +} diff --git a/crates/rules/src/matrices/matrix_with_factor_source_ids.rs b/crates/rules/src/matrices/matrix_of_factor_source_ids.rs similarity index 96% rename from crates/rules/src/matrices/matrix_with_factor_source_ids.rs rename to crates/rules/src/matrices/matrix_of_factor_source_ids.rs index 5886ca9f..edca7043 100644 --- a/crates/rules/src/matrices/matrix_with_factor_source_ids.rs +++ b/crates/rules/src/matrices/matrix_of_factor_source_ids.rs @@ -1,10 +1,9 @@ use crate::prelude::*; -pub type AbstractMatrixBuilt = AbstractMatrixBuilderOrBuilt; -pub type MatrixWithFactorSourceIds = AbstractMatrixBuilt; +pub type MatrixOfFactorSourceIds = AbstractMatrixBuilt; #[cfg(test)] -impl MatrixWithFactorSourceIds { +impl MatrixOfFactorSourceIds { pub(crate) fn with_roles_and_days( primary: RoleWithFactorSourceIds, recovery: RoleWithFactorSourceIds, @@ -37,7 +36,7 @@ impl MatrixWithFactorSourceIds { } } -impl MatrixWithFactorSourceIds { +impl MatrixOfFactorSourceIds { pub fn primary(&self) -> &RoleWithFactorSourceIds { &self.primary_role } @@ -51,7 +50,7 @@ impl MatrixWithFactorSourceIds { } } -impl MatrixWithFactorSourceIds { +impl MatrixOfFactorSourceIds { pub fn sample_config_12() -> Self { let mut builder = MatrixBuilder::new(); // Primary @@ -108,7 +107,7 @@ impl MatrixWithFactorSourceIds { } } -impl HasSampleValues for MatrixWithFactorSourceIds { +impl HasSampleValues for MatrixOfFactorSourceIds { fn sample() -> Self { Self::sample_config_11() } @@ -122,7 +121,7 @@ impl HasSampleValues for MatrixWithFactorSourceIds { mod tests { use super::*; #[allow(clippy::upper_case_acronyms)] - type SUT = MatrixWithFactorSourceIds; + type SUT = MatrixOfFactorSourceIds; #[test] fn equality() { diff --git a/crates/rules/src/matrices/matrix_with_factor_sources.rs b/crates/rules/src/matrices/matrix_of_factor_sources.rs similarity index 82% rename from crates/rules/src/matrices/matrix_with_factor_sources.rs rename to crates/rules/src/matrices/matrix_of_factor_sources.rs index 08252185..745c1b48 100644 --- a/crates/rules/src/matrices/matrix_with_factor_sources.rs +++ b/crates/rules/src/matrices/matrix_of_factor_sources.rs @@ -1,10 +1,10 @@ use crate::prelude::*; -pub type MatrixWithFactorSources = AbstractMatrixBuilt; +pub type MatrixOfFactorSources = AbstractMatrixBuilt; -impl MatrixWithFactorSources { +impl MatrixOfFactorSources { pub fn new( - matrix_of_factor_source_ids: MatrixWithFactorSourceIds, + matrix_of_factor_source_ids: MatrixOfFactorSourceIds, factor_sources: &FactorSources, ) -> Result { let primary_role = @@ -36,15 +36,15 @@ impl MatrixWithFactorSources { } } -impl HasSampleValues for MatrixWithFactorSources { +impl HasSampleValues for MatrixOfFactorSources { fn sample() -> Self { - let ids = MatrixWithFactorSourceIds::sample(); + let ids = MatrixOfFactorSourceIds::sample(); let factor_sources = FactorSources::sample_values_all(); Self::new(ids, &factor_sources).unwrap() } fn sample_other() -> Self { - let ids = MatrixWithFactorSourceIds::sample_other(); + let ids = MatrixOfFactorSourceIds::sample_other(); let factor_sources = FactorSources::sample_values_all(); Self::new(ids, &factor_sources).unwrap() } @@ -55,7 +55,7 @@ mod tests { use super::*; #[allow(clippy::upper_case_acronyms)] - type SUT = MatrixWithFactorSources; + type SUT = MatrixOfFactorSources; #[test] fn equality() { diff --git a/crates/rules/src/matrices/matrix_with_factor_instances.rs b/crates/rules/src/matrices/matrix_with_factor_instances.rs deleted file mode 100644 index 25fd2bfe..00000000 --- a/crates/rules/src/matrices/matrix_with_factor_instances.rs +++ /dev/null @@ -1,3 +0,0 @@ -use crate::prelude::*; - -pub type MatrixOfFactorInstances = AbstractMatrixBuilderOrBuilt; diff --git a/crates/rules/src/matrices/mod.rs b/crates/rules/src/matrices/mod.rs index 486e3933..4c9d36a2 100644 --- a/crates/rules/src/matrices/mod.rs +++ b/crates/rules/src/matrices/mod.rs @@ -1,12 +1,12 @@ mod abstract_matrix_builder_or_built; mod builder; -mod matrix_with_factor_instances; -mod matrix_with_factor_source_ids; -mod matrix_with_factor_sources; +mod matrix_of_factor_instances; +mod matrix_of_factor_source_ids; +mod matrix_of_factor_sources; pub(crate) use abstract_matrix_builder_or_built::*; #[allow(unused_imports)] pub use builder::*; -pub use matrix_with_factor_instances::*; -pub use matrix_with_factor_source_ids::*; -pub use matrix_with_factor_sources::*; +pub use matrix_of_factor_instances::*; +pub use matrix_of_factor_source_ids::*; +pub use matrix_of_factor_sources::*; diff --git a/crates/rules/src/roles/abstract_role_builder_or_built.rs b/crates/rules/src/roles/abstract_role_builder_or_built.rs index 762b7abc..e2b1f49d 100644 --- a/crates/rules/src/roles/abstract_role_builder_or_built.rs +++ b/crates/rules/src/roles/abstract_role_builder_or_built.rs @@ -18,22 +18,47 @@ pub struct AbstractRoleBuilderOrBuilt { pub(crate) type AbstractBuiltRoleWithFactor = AbstractRoleBuilderOrBuilt; pub(crate) type RoleBuilder = AbstractRoleBuilderOrBuilt; -impl AbstractRoleBuilderOrBuilt { +impl AbstractRoleBuilderOrBuilt { pub(crate) fn with_factors( role: RoleKind, threshold: u8, threshold_factors: impl IntoIterator, override_factors: impl IntoIterator, ) -> Self { + let assert_is_securified = |factors: &Vec| -> Result<(), CommonError> { + let trait_objects: Vec<&dyn IsMaybeKeySpaceAware> = factors + .iter() + .map(|x| x as &dyn IsMaybeKeySpaceAware) + .collect(); + if trait_objects + .iter() + .filter_map(|x| x.maybe_key_space()) + .any(|x| x != KeySpace::Securified) + { + return Err(crate::CommonError::IndexUnsecurifiedExpectedSecurified); + } + Ok(()) + }; + + let threshold_factors = threshold_factors.into_iter().collect(); + let override_factors = override_factors.into_iter().collect(); + + assert_is_securified(&threshold_factors) + .expect("Should not have allowed building of invalid Role"); + assert_is_securified(&override_factors) + .expect("Should not have allowed building of invalid Role"); + Self { built: PhantomData, role, threshold, - threshold_factors: threshold_factors.into_iter().collect(), - override_factors: override_factors.into_iter().collect(), + threshold_factors, + override_factors, } } +} +impl AbstractRoleBuilderOrBuilt { pub fn all_factors(&self) -> Vec<&F> { self.threshold_factors .iter() diff --git a/crates/rules/src/roles/mod.rs b/crates/rules/src/roles/mod.rs index 9c242b6f..ffd64daa 100644 --- a/crates/rules/src/roles/mod.rs +++ b/crates/rules/src/roles/mod.rs @@ -1,9 +1,11 @@ mod abstract_role_builder_or_built; mod builder; +mod role_with_factor_instances; mod roles_with_factor_ids; mod roles_with_factor_sources; pub(crate) use abstract_role_builder_or_built::*; pub use builder::*; +pub(crate) use role_with_factor_instances::*; pub use roles_with_factor_ids::*; pub(crate) use roles_with_factor_sources::*; diff --git a/crates/rules/src/roles/role_with_factor_instances.rs b/crates/rules/src/roles/role_with_factor_instances.rs new file mode 100644 index 00000000..f7ab9467 --- /dev/null +++ b/crates/rules/src/roles/role_with_factor_instances.rs @@ -0,0 +1,186 @@ +use crate::prelude::*; + +pub(crate) type RoleWithFactorInstances = AbstractBuiltRoleWithFactor; + +impl RoleWithFactorInstances { + // TODO: MFA - Upgrade this method to follow the rules of when a factor instance might + // be used by MULTIPLE roles. This is a temporary solution to get the tests to pass. + // A proper solution should use follow the rules laid out in: + // https://radixdlt.atlassian.net/wiki/spaces/AT/pages/3758063620/MFA+Rules+for+Factors+and+Security+Shields + pub(crate) fn fulfilling_role_of_factor_sources_with_factor_instances( + role_kind: RoleKind, + consuming_instances: &IndexMap, + matrix_of_factor_sources: &MatrixOfFactorSources, + ) -> Result { + let role_of_sources = { + match role_kind { + RoleKind::Primary => &matrix_of_factor_sources.primary_role, + RoleKind::Recovery => &matrix_of_factor_sources.recovery_role, + RoleKind::Confirmation => &matrix_of_factor_sources.confirmation_role, + } + }; + assert_eq!(role_of_sources.role(), role_kind); + let threshold: u8 = role_of_sources.get_threshold(); + + // Threshold factors + let threshold_factors = + Self::try_filling_factor_list_of_role_of_factor_sources_with_factor_instances( + consuming_instances, + role_of_sources.get_threshold_factors(), + )?; + + // Override factors + let override_factors = + Self::try_filling_factor_list_of_role_of_factor_sources_with_factor_instances( + consuming_instances, + role_of_sources.get_override_factors(), + )?; + + let role_with_instances = + Self::with_factors(role_kind, threshold, threshold_factors, override_factors); + + assert_eq!(role_with_instances.role(), role_kind); + Ok(role_with_instances) + } + + fn try_filling_factor_list_of_role_of_factor_sources_with_factor_instances( + instances: &IndexMap, + from: &[FactorSource], + ) -> Result, CommonError> { + from.iter() + .map(|f| { + if let Some(existing) = instances.get(&f.id_from_hash()) { + let hd_instance = existing + .first() + .ok_or(CommonError::MissingFactorMappingInstancesIntoRole)?; + let instance = FactorInstance::from(hd_instance); + Ok(instance) + } else { + Err(CommonError::MissingFactorMappingInstancesIntoRole) + } + }) + .collect::, CommonError>>() + } +} + +impl RoleWithFactorInstances { + // TODO: MFA Rules change this, this might not be compatible with the rules! + pub fn sample_primary() -> Self { + Self::with_factors(RoleKind::Primary, 1, [ + HierarchicalDeterministicFactorInstance::sample_mainnet_account_device_factor_fs_0_securified_at_index(0).into() + ], [ + HierarchicalDeterministicFactorInstance::sample_mainnet_account_device_factor_fs_10_securified_at_index(0).into() + ]) + } + + // TODO: MFA Rules change this, this might not be compatible with the rules! + pub fn sample_primary_other() -> Self { + Self::with_factors( + RoleKind::Primary, + 1, + [HierarchicalDeterministicFactorInstance::sample_mainnet_account_device_factor_fs_0_securified_at_index(10).into(),], + [HierarchicalDeterministicFactorInstance::sample_mainnet_account_device_factor_fs_10_securified_at_index(60).into()], + ) + } + + // TODO: MFA Rules change this, this might not be compatible with the rules! + pub fn sample_recovery() -> Self { + Self::with_factors( + RoleKind::Recovery, + 0,[], [HierarchicalDeterministicFactorInstance::sample_mainnet_account_device_factor_fs_10_securified_at_index(237).into()] + ) + } + + // TODO: MFA Rules change this, this might not be compatible with the rules! + pub fn sample_recovery_other() -> Self { + Self::with_factors( + RoleKind::Recovery, + 0,[], [HierarchicalDeterministicFactorInstance::sample_mainnet_account_device_factor_fs_10_securified_at_index(42).into()] + ) + } + + // TODO: MFA Rules change this, this might not be compatible with the rules! + pub fn sample_confirmation() -> Self { + Self::with_factors( + RoleKind::Confirmation, + 0,[], [HierarchicalDeterministicFactorInstance::sample_mainnet_account_device_factor_fs_0_securified_at_index(1).into(), HierarchicalDeterministicFactorInstance::sample_mainnet_account_device_factor_fs_10_securified_at_index(2).into()] + ) + } + + // TODO: MFA Rules change this, this might not be compatible with the rules! + pub fn sample_confirmation_other() -> Self { + Self::with_factors( + RoleKind::Confirmation, + 0,[], [HierarchicalDeterministicFactorInstance::sample_mainnet_account_device_factor_fs_0_securified_at_index(10).into(), HierarchicalDeterministicFactorInstance::sample_mainnet_account_device_factor_fs_10_securified_at_index(20).into()] + ) + } +} + +impl HasSampleValues for RoleWithFactorInstances { + fn sample() -> Self { + Self::sample_primary() + } + + fn sample_other() -> Self { + Self::sample_recovery() + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[allow(clippy::upper_case_acronyms)] + type SUT = RoleWithFactorInstances; + + #[test] + fn equality() { + assert_eq!(SUT::sample_primary(), SUT::sample_primary()); + assert_eq!(SUT::sample_primary_other(), SUT::sample_primary_other()); + assert_eq!(SUT::sample_recovery(), SUT::sample_recovery()); + assert_eq!(SUT::sample_recovery_other(), SUT::sample_recovery_other()); + assert_eq!(SUT::sample_confirmation(), SUT::sample_confirmation()); + assert_eq!( + SUT::sample_confirmation_other(), + SUT::sample_confirmation_other() + ); + } + + #[test] + fn inequality() { + assert_ne!(SUT::sample(), SUT::sample_other()); + } + + #[test] + fn hash() { + let hash = HashSet::::from_iter([ + SUT::sample_primary(), + SUT::sample_primary_other(), + SUT::sample_recovery(), + SUT::sample_recovery_other(), + SUT::sample_confirmation(), + SUT::sample_confirmation_other(), + // Duplicates should be removed + SUT::sample_primary(), + SUT::sample_primary_other(), + SUT::sample_recovery(), + SUT::sample_recovery_other(), + SUT::sample_confirmation(), + SUT::sample_confirmation_other(), + ]); + assert_eq!(hash.len(), 6); + } + + #[test] + #[should_panic] + fn primary_role_non_securified_threshold_instances_is_err() { + let _ = SUT::with_factors( + RoleKind::Primary, + 1, + [ + HierarchicalDeterministicFactorInstance::sample_mainnet_account_device_factor_fs_10_unsecurified_at_index(0).into() + ], + [] + ); + } +} diff --git a/crates/rules/src/security_structure_of_factors/security_structure_of_factor_source_ids.rs b/crates/rules/src/security_structure_of_factors/security_structure_of_factor_source_ids.rs index 3dfebdea..68d09fa2 100644 --- a/crates/rules/src/security_structure_of_factors/security_structure_of_factor_source_ids.rs +++ b/crates/rules/src/security_structure_of_factors/security_structure_of_factor_source_ids.rs @@ -5,12 +5,12 @@ pub type SecurityStructureOfFactorSourceIds = AbstractSecurityStructure Self { let metadata = sargon::SecurityStructureMetadata::sample(); - Self::with_metadata(metadata, MatrixWithFactorSourceIds::sample()) + Self::with_metadata(metadata, MatrixOfFactorSourceIds::sample()) } fn sample_other() -> Self { let metadata = sargon::SecurityStructureMetadata::sample_other(); - Self::with_metadata(metadata, MatrixWithFactorSourceIds::sample_other()) + Self::with_metadata(metadata, MatrixOfFactorSourceIds::sample_other()) } } diff --git a/crates/rules/src/security_structure_of_factors/security_structure_of_factor_sources.rs b/crates/rules/src/security_structure_of_factors/security_structure_of_factor_sources.rs index ad92f1ed..205e3f9a 100644 --- a/crates/rules/src/security_structure_of_factors/security_structure_of_factor_sources.rs +++ b/crates/rules/src/security_structure_of_factors/security_structure_of_factor_sources.rs @@ -5,12 +5,12 @@ pub type SecurityStructureOfFactorSources = AbstractSecurityStructure Self { let metadata = sargon::SecurityStructureMetadata::sample(); - Self::with_metadata(metadata, MatrixWithFactorSources::sample()) + Self::with_metadata(metadata, MatrixOfFactorSources::sample()) } fn sample_other() -> Self { let metadata = sargon::SecurityStructureMetadata::sample_other(); - Self::with_metadata(metadata, MatrixWithFactorSources::sample_other()) + Self::with_metadata(metadata, MatrixOfFactorSources::sample_other()) } } From fe763b8ca288e283c5a97a8648abd8c0c80f12ac Mon Sep 17 00:00:00 2001 From: Alexander Cyon Date: Thu, 28 Nov 2024 14:55:59 +0100 Subject: [PATCH 09/33] json tests --- .../matrices/matrix_of_factor_instances.rs | 160 ++++++++++++++++++ .../src/roles/role_with_factor_instances.rs | 68 ++++++++ 2 files changed, 228 insertions(+) diff --git a/crates/rules/src/matrices/matrix_of_factor_instances.rs b/crates/rules/src/matrices/matrix_of_factor_instances.rs index ae7df3bd..81af37b5 100644 --- a/crates/rules/src/matrices/matrix_of_factor_instances.rs +++ b/crates/rules/src/matrices/matrix_of_factor_instances.rs @@ -145,4 +145,164 @@ mod tests { Err(CommonError::MissingFactorMappingInstancesIntoRole) )); } + + #[test] + fn assert_json_sample() { + let sut = SUT::sample(); + assert_eq_after_json_roundtrip( + &sut, + r#" + { + "primary_role": { + "role": "primary", + "threshold": 1, + "threshold_factors": [ + { + "factorSourceID": { + "discriminator": "fromHash", + "fromHash": { + "kind": "device", + "body": "f1a93d324dd0f2bff89963ab81ed6e0c2ee7e18c0827dc1d3576b2d9f26bbd0a" + } + }, + "badge": { + "discriminator": "virtualSource", + "virtualSource": { + "discriminator": "hierarchicalDeterministicPublicKey", + "hierarchicalDeterministicPublicKey": { + "publicKey": { + "curve": "curve25519", + "compressedData": "427969814e15d74c3ff4d9971465cb709d210c8a7627af9466bdaa67bd0929b7" + }, + "derivationPath": { + "scheme": "cap26", + "path": "m/44H/1022H/1H/525H/1460H/0S" + } + } + } + } + } + ], + "override_factors": [ + { + "factorSourceID": { + "discriminator": "fromHash", + "fromHash": { + "kind": "device", + "body": "5255999c65076ce9ced5a1881f1a621bba1ce3f1f68a61df462d96822a5190cd" + } + }, + "badge": { + "discriminator": "virtualSource", + "virtualSource": { + "discriminator": "hierarchicalDeterministicPublicKey", + "hierarchicalDeterministicPublicKey": { + "publicKey": { + "curve": "curve25519", + "compressedData": "e0293d4979bc303ea4fe361a62baf9c060c7d90267972b05c61eead9ef3eed3e" + }, + "derivationPath": { + "scheme": "cap26", + "path": "m/44H/1022H/1H/525H/1460H/0S" + } + } + } + } + } + ] + }, + "recovery_role": { + "role": "recovery", + "threshold": 0, + "threshold_factors": [], + "override_factors": [ + { + "factorSourceID": { + "discriminator": "fromHash", + "fromHash": { + "kind": "device", + "body": "5255999c65076ce9ced5a1881f1a621bba1ce3f1f68a61df462d96822a5190cd" + } + }, + "badge": { + "discriminator": "virtualSource", + "virtualSource": { + "discriminator": "hierarchicalDeterministicPublicKey", + "hierarchicalDeterministicPublicKey": { + "publicKey": { + "curve": "curve25519", + "compressedData": "23fa85f95c79684d2768f46ec4379b5e031757b727f76cfd01a50bd4cf8afcff" + }, + "derivationPath": { + "scheme": "cap26", + "path": "m/44H/1022H/1H/525H/1460H/237S" + } + } + } + } + } + ] + }, + "confirmation_role": { + "role": "confirmation", + "threshold": 0, + "threshold_factors": [], + "override_factors": [ + { + "factorSourceID": { + "discriminator": "fromHash", + "fromHash": { + "kind": "device", + "body": "f1a93d324dd0f2bff89963ab81ed6e0c2ee7e18c0827dc1d3576b2d9f26bbd0a" + } + }, + "badge": { + "discriminator": "virtualSource", + "virtualSource": { + "discriminator": "hierarchicalDeterministicPublicKey", + "hierarchicalDeterministicPublicKey": { + "publicKey": { + "curve": "curve25519", + "compressedData": "10ccd9b906660d49b3fe89651baa1284fc7b19ad2c3d423a7828ec350c0e5fe0" + }, + "derivationPath": { + "scheme": "cap26", + "path": "m/44H/1022H/1H/525H/1460H/1S" + } + } + } + } + }, + { + "factorSourceID": { + "discriminator": "fromHash", + "fromHash": { + "kind": "device", + "body": "5255999c65076ce9ced5a1881f1a621bba1ce3f1f68a61df462d96822a5190cd" + } + }, + "badge": { + "discriminator": "virtualSource", + "virtualSource": { + "discriminator": "hierarchicalDeterministicPublicKey", + "hierarchicalDeterministicPublicKey": { + "publicKey": { + "curve": "curve25519", + "compressedData": "6a92b3338dc74a50e8b3fff896a7e0f43c42742544af52de20353675d8bc7907" + }, + "derivationPath": { + "scheme": "cap26", + "path": "m/44H/1022H/1H/525H/1460H/2S" + } + } + } + } + } + ] + }, + "number_of_days_until_auto_confirm": 30 + } + "#, + ); + } } diff --git a/crates/rules/src/roles/role_with_factor_instances.rs b/crates/rules/src/roles/role_with_factor_instances.rs index f7ab9467..d419879c 100644 --- a/crates/rules/src/roles/role_with_factor_instances.rs +++ b/crates/rules/src/roles/role_with_factor_instances.rs @@ -183,4 +183,72 @@ mod tests { [] ); } + + #[test] + fn assert_json_sample() { + let sut = SUT::sample(); + assert_eq_after_json_roundtrip( + &sut, + r#" + { + "role": "primary", + "threshold": 1, + "threshold_factors": [ + { + "factorSourceID": { + "discriminator": "fromHash", + "fromHash": { + "kind": "device", + "body": "f1a93d324dd0f2bff89963ab81ed6e0c2ee7e18c0827dc1d3576b2d9f26bbd0a" + } + }, + "badge": { + "discriminator": "virtualSource", + "virtualSource": { + "discriminator": "hierarchicalDeterministicPublicKey", + "hierarchicalDeterministicPublicKey": { + "publicKey": { + "curve": "curve25519", + "compressedData": "427969814e15d74c3ff4d9971465cb709d210c8a7627af9466bdaa67bd0929b7" + }, + "derivationPath": { + "scheme": "cap26", + "path": "m/44H/1022H/1H/525H/1460H/0S" + } + } + } + } + } + ], + "override_factors": [ + { + "factorSourceID": { + "discriminator": "fromHash", + "fromHash": { + "kind": "device", + "body": "5255999c65076ce9ced5a1881f1a621bba1ce3f1f68a61df462d96822a5190cd" + } + }, + "badge": { + "discriminator": "virtualSource", + "virtualSource": { + "discriminator": "hierarchicalDeterministicPublicKey", + "hierarchicalDeterministicPublicKey": { + "publicKey": { + "curve": "curve25519", + "compressedData": "e0293d4979bc303ea4fe361a62baf9c060c7d90267972b05c61eead9ef3eed3e" + }, + "derivationPath": { + "scheme": "cap26", + "path": "m/44H/1022H/1H/525H/1460H/0S" + } + } + } + } + } + ] + } + "#, + ); + } } From fb1541b252979239bde7156273d46537f6088bf3 Mon Sep 17 00:00:00 2001 From: Alexander Cyon Date: Thu, 28 Nov 2024 16:50:08 +0100 Subject: [PATCH 10/33] [no ci] wip --- crates/rules/src/lib.rs | 3 + .../abstract_matrix_builder_or_built.rs | 6 +- .../src/matrices/builder/matrix_builder.rs | 16 +- .../matrices/matrix_of_factor_instances.rs | 12 +- .../matrices/matrix_of_factor_source_ids.rs | 24 +- .../roles/abstract_role_builder_or_built.rs | 53 +- .../rules/src/roles/builder/roles_builder.rs | 50 +- .../roles/builder/roles_builder_unit_tests.rs | 3661 +++++++++-------- .../src/roles/role_with_factor_instances.rs | 70 +- .../rules/src/roles/roles_with_factor_ids.rs | 93 +- .../src/roles/roles_with_factor_sources.rs | 61 +- 11 files changed, 2049 insertions(+), 2000 deletions(-) diff --git a/crates/rules/src/lib.rs b/crates/rules/src/lib.rs index c412638c..f3cc522e 100644 --- a/crates/rules/src/lib.rs +++ b/crates/rules/src/lib.rs @@ -1,3 +1,6 @@ +#![allow(incomplete_features)] +#![feature(inherent_associated_types)] + mod matrices; mod move_to_sargon; mod roles; diff --git a/crates/rules/src/matrices/abstract_matrix_builder_or_built.rs b/crates/rules/src/matrices/abstract_matrix_builder_or_built.rs index fc3ada77..6cd0a05e 100644 --- a/crates/rules/src/matrices/abstract_matrix_builder_or_built.rs +++ b/crates/rules/src/matrices/abstract_matrix_builder_or_built.rs @@ -6,9 +6,9 @@ pub struct AbstractMatrixBuilderOrBuilt { #[doc(hidden)] pub(crate) built: PhantomData, - pub(crate) primary_role: AbstractRoleBuilderOrBuilt, - pub(crate) recovery_role: AbstractRoleBuilderOrBuilt, - pub(crate) confirmation_role: AbstractRoleBuilderOrBuilt, + pub(crate) primary_role: AbstractRoleBuilderOrBuilt<{ ROLE_PRIMARY }, F, U>, + pub(crate) recovery_role: AbstractRoleBuilderOrBuilt<{ ROLE_RECOVERY }, F, U>, + pub(crate) confirmation_role: AbstractRoleBuilderOrBuilt<{ ROLE_CONFIRMATION }, F, U>, pub(crate) number_of_days_until_auto_confirm: u16, } diff --git a/crates/rules/src/matrices/builder/matrix_builder.rs b/crates/rules/src/matrices/builder/matrix_builder.rs index 0b7459a5..d3a19122 100644 --- a/crates/rules/src/matrices/builder/matrix_builder.rs +++ b/crates/rules/src/matrices/builder/matrix_builder.rs @@ -5,8 +5,14 @@ use crate::prelude::*; pub type MatrixBuilderMutateResult = Result<(), MatrixBuilderValidation>; pub type MatrixBuilderBuildResult = Result; -pub type MatrixBuilder = - AbstractMatrixBuilderOrBuilt; +#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] +pub struct Built; + +pub type MatrixBuilder = AbstractMatrixBuilderOrBuilt< + FactorSourceID, + MatrixOfFactorSourceIds, + Built, // this is HACKY +>; // ================== // ===== PUBLIC ===== @@ -15,9 +21,9 @@ impl MatrixBuilder { pub fn new() -> Self { Self { built: PhantomData, - primary_role: RoleBuilder::primary(), - recovery_role: RoleBuilder::recovery(), - confirmation_role: RoleBuilder::confirmation(), + primary_role: PrimaryRoleBuilder::new(), + recovery_role: RecoveryRoleBuilder::new(), + confirmation_role: ConfirmationRoleBuilder::new(), number_of_days_until_auto_confirm: Self::DEFAULT_NUMBER_OF_DAYS_UNTIL_AUTO_CONFIRM, } } diff --git a/crates/rules/src/matrices/matrix_of_factor_instances.rs b/crates/rules/src/matrices/matrix_of_factor_instances.rs index 81af37b5..299c740e 100644 --- a/crates/rules/src/matrices/matrix_of_factor_instances.rs +++ b/crates/rules/src/matrices/matrix_of_factor_instances.rs @@ -54,20 +54,17 @@ impl MatrixOfFactorInstances { let instances = &consuming_instances.clone(); let primary_role = - RoleWithFactorInstances::fulfilling_role_of_factor_sources_with_factor_instances( - RoleKind::Primary, + PrimaryRoleWithFactorInstances::fulfilling_role_of_factor_sources_with_factor_instances( instances, &matrix_of_factor_sources, )?; let recovery_role = - RoleWithFactorInstances::fulfilling_role_of_factor_sources_with_factor_instances( - RoleKind::Recovery, + RecoveryRoleWithFactorInstances::fulfilling_role_of_factor_sources_with_factor_instances( instances, &matrix_of_factor_sources, )?; let confirmation_role = - RoleWithFactorInstances::fulfilling_role_of_factor_sources_with_factor_instances( - RoleKind::Confirmation, + ConfirmationRoleWithFactorInstances::fulfilling_role_of_factor_sources_with_factor_instances( instances, &matrix_of_factor_sources, )?; @@ -154,7 +151,6 @@ mod tests { r#" { "primary_role": { - "role": "primary", "threshold": 1, "threshold_factors": [ { @@ -212,7 +208,6 @@ mod tests { ] }, "recovery_role": { - "role": "recovery", "threshold": 0, "threshold_factors": [], "override_factors": [ @@ -244,7 +239,6 @@ mod tests { ] }, "confirmation_role": { - "role": "confirmation", "threshold": 0, "threshold_factors": [], "override_factors": [ diff --git a/crates/rules/src/matrices/matrix_of_factor_source_ids.rs b/crates/rules/src/matrices/matrix_of_factor_source_ids.rs index edca7043..8ba9568e 100644 --- a/crates/rules/src/matrices/matrix_of_factor_source_ids.rs +++ b/crates/rules/src/matrices/matrix_of_factor_source_ids.rs @@ -5,9 +5,9 @@ pub type MatrixOfFactorSourceIds = AbstractMatrixBuilt; #[cfg(test)] impl MatrixOfFactorSourceIds { pub(crate) fn with_roles_and_days( - primary: RoleWithFactorSourceIds, - recovery: RoleWithFactorSourceIds, - confirmation: RoleWithFactorSourceIds, + primary: PrimaryRoleWithFactorSourceIds, + recovery: RecoveryRoleWithFactorSourceIds, + confirmation: ConfirmationRoleWithFactorSourceIds, number_of_days_until_auto_confirm: u16, ) -> Self { assert_eq!(primary.role(), sargon::RoleKind::Primary); @@ -23,9 +23,9 @@ impl MatrixOfFactorSourceIds { } pub(crate) fn with_roles( - primary: RoleWithFactorSourceIds, - recovery: RoleWithFactorSourceIds, - confirmation: RoleWithFactorSourceIds, + primary: PrimaryRoleWithFactorSourceIds, + recovery: RecoveryRoleWithFactorSourceIds, + confirmation: ConfirmationRoleWithFactorSourceIds, ) -> Self { Self::with_roles_and_days( primary, @@ -37,15 +37,15 @@ impl MatrixOfFactorSourceIds { } impl MatrixOfFactorSourceIds { - pub fn primary(&self) -> &RoleWithFactorSourceIds { + pub fn primary(&self) -> &PrimaryRoleWithFactorSourceIds { &self.primary_role } - pub fn recovery(&self) -> &RoleWithFactorSourceIds { + pub fn recovery(&self) -> &RecoveryRoleWithFactorSourceIds { &self.recovery_role } - pub fn confirmation(&self) -> &RoleWithFactorSourceIds { + pub fn confirmation(&self) -> &ConfirmationRoleWithFactorSourceIds { &self.confirmation_role } } @@ -142,7 +142,6 @@ mod tests { r#" { "primary_role": { - "role": "primary", "threshold": 2, "threshold_factors": [ { @@ -163,7 +162,6 @@ mod tests { "override_factors": [] }, "recovery_role": { - "role": "recovery", "threshold": 0, "threshold_factors": [], "override_factors": [ @@ -184,7 +182,6 @@ mod tests { ] }, "confirmation_role": { - "role": "confirmation", "threshold": 0, "threshold_factors": [], "override_factors": [ @@ -211,7 +208,6 @@ mod tests { r#" { "primary_role": { - "role": "primary", "threshold": 2, "threshold_factors": [ { @@ -232,7 +228,6 @@ mod tests { "override_factors": [] }, "recovery_role": { - "role": "recovery", "threshold": 0, "threshold_factors": [], "override_factors": [ @@ -253,7 +248,6 @@ mod tests { ] }, "confirmation_role": { - "role": "confirmation", "threshold": 0, "threshold_factors": [], "override_factors": [ diff --git a/crates/rules/src/roles/abstract_role_builder_or_built.rs b/crates/rules/src/roles/abstract_role_builder_or_built.rs index e2b1f49d..2b2aff14 100644 --- a/crates/rules/src/roles/abstract_role_builder_or_built.rs +++ b/crates/rules/src/roles/abstract_role_builder_or_built.rs @@ -5,22 +5,25 @@ use serde::{Deserialize, Serialize}; use crate::prelude::*; #[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] -pub struct AbstractRoleBuilderOrBuilt { +pub struct AbstractRoleBuilderOrBuilt { #[serde(skip)] #[doc(hidden)] built: PhantomData, - role: RoleKind, + threshold: u8, threshold_factors: Vec, override_factors: Vec, } -pub(crate) type AbstractBuiltRoleWithFactor = AbstractRoleBuilderOrBuilt; -pub(crate) type RoleBuilder = AbstractRoleBuilderOrBuilt; +pub(crate) type AbstractBuiltRoleWithFactor = AbstractRoleBuilderOrBuilt; +pub(crate) type RoleBuilder = AbstractRoleBuilderOrBuilt; + +impl AbstractRoleBuilderOrBuilt { + pub fn role(&self) -> RoleKind { + RoleKind::from_u8(R).expect("RoleKind should be valid") + } -impl AbstractRoleBuilderOrBuilt { pub(crate) fn with_factors( - role: RoleKind, threshold: u8, threshold_factors: impl IntoIterator, override_factors: impl IntoIterator, @@ -50,7 +53,6 @@ impl AbstractRoleBuilderOrBuilt { Self { built: PhantomData, - role, threshold, threshold_factors, override_factors, @@ -58,7 +60,7 @@ impl AbstractRoleBuilderOrBuilt { } } -impl AbstractRoleBuilderOrBuilt { +impl AbstractRoleBuilderOrBuilt { pub fn all_factors(&self) -> Vec<&F> { self.threshold_factors .iter() @@ -78,22 +80,37 @@ impl AbstractRoleBuilderOrBuilt { self.threshold } } +pub(crate) const ROLE_UNSPECIFIED: u8 = 0; +pub(crate) const ROLE_PRIMARY: u8 = 1; +pub(crate) const ROLE_RECOVERY: u8 = 2; +pub(crate) const ROLE_CONFIRMATION: u8 = 3; + +pub(crate) trait RoleFromDiscriminator { + fn from_u8(disciminator: u8) -> Option + where + Self: Sized; +} +impl RoleFromDiscriminator for RoleKind { + fn from_u8(disciminator: u8) -> Option { + match disciminator { + ROLE_PRIMARY => Some(RoleKind::Primary), + ROLE_RECOVERY => Some(RoleKind::Recovery), + ROLE_CONFIRMATION => Some(RoleKind::Confirmation), + _ => None, + } + } +} -impl RoleBuilder { - pub(crate) fn new(role: RoleKind) -> Self { +impl RoleBuilder { + pub(crate) fn new() -> Self { Self { built: PhantomData, - role, threshold: 0, threshold_factors: Vec::new(), override_factors: Vec::new(), } } - pub(crate) fn role(&self) -> RoleKind { - self.role - } - pub(crate) fn mut_threshold_factors(&mut self) -> &mut Vec { &mut self.threshold_factors } @@ -118,11 +135,7 @@ impl RoleBuilder { } } -impl AbstractBuiltRoleWithFactor { - pub fn role(&self) -> RoleKind { - self.role - } - +impl AbstractBuiltRoleWithFactor { pub fn threshold(&self) -> u8 { self.threshold } diff --git a/crates/rules/src/roles/builder/roles_builder.rs b/crates/rules/src/roles/builder/roles_builder.rs index b4ff3c22..b569a713 100644 --- a/crates/rules/src/roles/builder/roles_builder.rs +++ b/crates/rules/src/roles/builder/roles_builder.rs @@ -1,44 +1,33 @@ use crate::prelude::*; -impl RoleBuilder { - pub fn primary() -> Self { - Self::new(RoleKind::Primary) - } - - pub fn recovery() -> Self { - Self::new(RoleKind::Recovery) - } - - pub fn confirmation() -> Self { - Self::new(RoleKind::Confirmation) - } -} +pub type PrimaryRoleBuilder = RoleBuilder<{ ROLE_PRIMARY }>; +pub type RecoveryRoleBuilder = RoleBuilder<{ ROLE_RECOVERY }>; +pub type ConfirmationRoleBuilder = RoleBuilder<{ ROLE_CONFIRMATION }>; #[cfg(test)] -impl RoleWithFactorSourceIds { +impl PrimaryRoleWithFactorSourceIds { pub(crate) fn primary_with_factors( threshold: u8, threshold_factors: impl IntoIterator, override_factors: impl IntoIterator, ) -> Self { - Self::with_factors( - RoleKind::Primary, - threshold, - threshold_factors, - override_factors, - ) + Self::with_factors(threshold, threshold_factors, override_factors) } +} +impl RecoveryRoleWithFactorSourceIds { pub(crate) fn recovery_with_factors( override_factors: impl IntoIterator, ) -> Self { - Self::with_factors(RoleKind::Recovery, 0, vec![], override_factors) + Self::with_factors(0, vec![], override_factors) } +} +impl ConfirmationRoleWithFactorSourceIds { pub(crate) fn confirmation_with_factors( override_factors: impl IntoIterator, ) -> Self { - Self::with_factors(RoleKind::Confirmation, 0, vec![], override_factors) + Self::with_factors(0, vec![], override_factors) } } @@ -210,19 +199,20 @@ impl FactorSourceInRoleBuilderValidationStatus { } } -pub type RoleBuilderMutateResult = Result<(), RoleBuilderValidation>; -pub type RoleBuilderBuildResult = Result; - use BasicViolation::*; use ForeverInvalidReason::*; use NotYetValidReason::*; use RoleKind::*; -impl RoleBuilder { - pub(crate) fn build(self) -> RoleBuilderBuildResult { +pub type RoleBuilderMutateResult = Result<(), RoleBuilderValidation>; + +impl RoleBuilder { + pub type RoleBuilderBuildResult = Result, RoleBuilderValidation>; + + pub(crate) fn build(self) -> Self::RoleBuilderBuildResult { self.validate().map(|_| { RoleWithFactorSourceIds::with_factors( - self.role(), + // self.role(), self.get_threshold(), self.get_threshold_factors().clone(), self.get_override_factors().clone(), @@ -292,7 +282,7 @@ impl RoleBuilder { /// Validates `self` by "replaying" the addition of each factor source in `self` to a /// "simulation" (clone). If the simulation is valid, then `self` is valid. pub(crate) fn validate(&self) -> RoleBuilderMutateResult { - let mut simulation = Self::new(self.role()); + let mut simulation = Self::new(); // Validate override factors for override_factor in self.get_override_factors() { @@ -548,7 +538,7 @@ impl RoleBuilder { // ======================= // ======== RULES ======== // ======================= -impl RoleBuilder { +impl RoleBuilder { fn validation_for_addition_of_password_to_primary( &self, factor_list_kind: FactorListKind, diff --git a/crates/rules/src/roles/builder/roles_builder_unit_tests.rs b/crates/rules/src/roles/builder/roles_builder_unit_tests.rs index da8b4da1..82032b8d 100644 --- a/crates/rules/src/roles/builder/roles_builder_unit_tests.rs +++ b/crates/rules/src/roles/builder/roles_builder_unit_tests.rs @@ -1,1828 +1,1833 @@ -#![cfg(test)] - -use crate::prelude::*; - -use NotYetValidReason::*; -type Validation = RoleBuilderValidation; - -#[allow(clippy::upper_case_acronyms)] -type SUT = RoleBuilder; -type MutRes = RoleBuilderMutateResult; -type BuildRes = RoleBuilderBuildResult; - -mod test_helper_functions { - - use super::*; - - #[test] - fn factor_sources_not_of_kind_to_list_of_kind_in_override() { - let mut sut = SUT::primary(); - sut.add_factor_source_to_list(FactorSourceID::sample_device(), FactorListKind::Override) - .unwrap(); - sut.add_factor_source_to_list(FactorSourceID::sample_ledger(), FactorListKind::Override) - .unwrap(); - sut.add_factor_source_to_list(FactorSourceID::sample_arculus(), FactorListKind::Override) - .unwrap(); - - let xs = sut.factor_sources_not_of_kind_to_list_of_kind( - FactorSourceKind::Device, - FactorListKind::Override, - ); - assert_eq!( - xs, - vec![ - FactorSourceID::sample_ledger(), - FactorSourceID::sample_arculus() - ] - ); - - let xs = sut.factor_sources_not_of_kind_to_list_of_kind( - FactorSourceKind::LedgerHQHardwareWallet, - FactorListKind::Override, - ); - assert_eq!( - xs, - vec![ - FactorSourceID::sample_device(), - FactorSourceID::sample_arculus() - ] - ); - - let xs = sut.factor_sources_not_of_kind_to_list_of_kind( - FactorSourceKind::ArculusCard, - FactorListKind::Override, - ); - assert_eq!( - xs, - vec![ - FactorSourceID::sample_device(), - FactorSourceID::sample_ledger() - ] - ); - } - - #[test] - fn factor_sources_not_of_kind_to_list_of_kind_in_threshold() { - let mut sut = SUT::primary(); - sut.add_factor_source_to_list(FactorSourceID::sample_device(), FactorListKind::Threshold) - .unwrap(); - sut.add_factor_source_to_list(FactorSourceID::sample_ledger(), FactorListKind::Threshold) - .unwrap(); - sut.add_factor_source_to_list(FactorSourceID::sample_arculus(), FactorListKind::Threshold) - .unwrap(); - - let xs = sut.factor_sources_not_of_kind_to_list_of_kind( - FactorSourceKind::Device, - FactorListKind::Threshold, - ); - assert_eq!( - xs, - vec![ - FactorSourceID::sample_ledger(), - FactorSourceID::sample_arculus() - ] - ); - - let xs = sut.factor_sources_not_of_kind_to_list_of_kind( - FactorSourceKind::LedgerHQHardwareWallet, - FactorListKind::Threshold, - ); - assert_eq!( - xs, - vec![ - FactorSourceID::sample_device(), - FactorSourceID::sample_arculus() - ] - ); - - let xs = sut.factor_sources_not_of_kind_to_list_of_kind( - FactorSourceKind::ArculusCard, - FactorListKind::Threshold, - ); - assert_eq!( - xs, - vec![ - FactorSourceID::sample_device(), - FactorSourceID::sample_ledger() - ] - ); - } -} - -fn test_duplicates_not_allowed(sut: SUT, list: FactorListKind, factor_source_id: FactorSourceID) { - // Arrange - let mut sut = sut; - - sut.add_factor_source_to_list(factor_source_id, list) - .unwrap(); - - // Act - let res = sut.add_factor_source_to_list( - factor_source_id, // oh no, duplicate! - list, - ); - - // Assert - assert!(matches!( - res, - MutRes::Err(Validation::ForeverInvalid( - ForeverInvalidReason::FactorSourceAlreadyPresent - )) - )); -} - -#[test] -fn new_builders() { - assert_eq!(SUT::primary().role(), RoleKind::Primary); - assert_eq!(SUT::recovery().role(), RoleKind::Recovery); - assert_eq!(SUT::confirmation().role(), RoleKind::Confirmation); -} - -#[test] -fn empty_is_err() { - [ - RoleKind::Primary, - RoleKind::Recovery, - RoleKind::Confirmation, - ] - .iter() - .for_each(|role| { - let sut = SUT::new(*role); - let res = sut.build(); - assert_eq!( - res, - BuildRes::not_yet_valid(NotYetValidReason::RoleMustHaveAtLeastOneFactor) - ); - }); -} - -#[test] -fn validate_override_for_ever_invalid() { - let sut = SUT::with_factors( - RoleKind::Primary, - 0, - vec![], - vec![ - FactorSourceID::sample_ledger(), - FactorSourceID::sample_ledger(), - ], - ); - let res = sut.validate(); - assert_eq!( - res, - MutRes::forever_invalid(ForeverInvalidReason::FactorSourceAlreadyPresent) - ); -} - -#[test] -fn validate_threshold_for_ever_invalid() { - let sut = SUT::with_factors( - RoleKind::Primary, - 1, - vec![ - FactorSourceID::sample_ledger(), - FactorSourceID::sample_ledger(), - ], - vec![], - ); - let res = sut.validate(); - assert_eq!( - res, - MutRes::forever_invalid(ForeverInvalidReason::FactorSourceAlreadyPresent) - ); -} - -#[test] -fn confirmation_validate_basic_violation() { - let sut = SUT::with_factors( - RoleKind::Confirmation, - 1, - vec![], - vec![FactorSourceID::sample_ledger()], - ); - let res = sut.validate(); - assert_eq!( - res, - MutRes::basic_violation(BasicViolation::ConfirmationCannotSetThreshold) - ); -} - -#[test] -fn recovery_validate_basic_violation() { - let sut = SUT::with_factors( - RoleKind::Recovery, - 1, - vec![], - vec![FactorSourceID::sample_ledger()], - ); - let res = sut.validate(); - assert_eq!( - res, - MutRes::basic_violation(BasicViolation::RecoveryCannotSetThreshold) - ); -} - -#[test] -fn primary_validate_not_yet_valid_for_threshold_greater_than_threshold_factors() { - let sut = SUT::with_factors( - RoleKind::Primary, - 1, - vec![], - vec![FactorSourceID::sample_ledger()], - ); - let res = sut.validate(); - assert_eq!( - res, - MutRes::not_yet_valid(ThresholdHigherThanThresholdFactorsLen) - ); -} - -#[cfg(test)] -mod recovery_in_isolation { - - use super::*; - - fn role() -> RoleKind { - RoleKind::Recovery - } - - fn make() -> SUT { - SUT::new(role()) - } - - fn list() -> FactorListKind { - FactorListKind::Override - } - - fn sample() -> FactorSourceID { - FactorSourceID::sample_device() - } - - #[test] - fn duplicates_not_allowed() { - test_duplicates_not_allowed(make(), list(), sample()) - } - - #[test] - fn validation_for_addition_of_factor_source_of_kind_to_list_is_err_for_threshold() { - let sut = make(); - let res = sut.validation_for_addition_of_factor_source_of_kind_to_list( - FactorSourceKind::Device, - FactorListKind::Threshold, - ); - assert_eq!( - res, - MutRes::forever_invalid(ForeverInvalidReason::threshold_list_not_supported_for_role( - role() - )) - ); - } - - #[test] - fn validation_for_addition_of_factor_source_of_kind_to_list() { - use FactorSourceKind::*; - let sut = make(); - let not_ok = |kind: FactorSourceKind| { - let res = sut.validation_for_addition_of_factor_source_of_kind_to_list(kind, list()); - assert!(res.is_err()); - }; - let ok = |kind: FactorSourceKind| { - let res = sut.validation_for_addition_of_factor_source_of_kind_to_list(kind, list()); - assert!(res.is_ok()); - }; - ok(Device); - ok(LedgerHQHardwareWallet); - ok(ArculusCard); - ok(TrustedContact); - ok(OffDeviceMnemonic); - - not_ok(Passphrase); - not_ok(SecurityQuestions); - } - - #[test] - fn set_threshold_is_unsupported() { - let mut sut = make(); - assert_eq!( - sut.set_threshold(1), - MutRes::basic_violation(BasicViolation::RecoveryCannotSetThreshold) - ); - } - - #[test] - fn cannot_add_factors_to_threshold() { - let mut sut = make(); - let res = sut.add_factor_source_to_list(sample(), FactorListKind::Threshold); - assert_eq!( - res, - Err(Validation::ForeverInvalid( - ForeverInvalidReason::RecoveryRoleThresholdFactorsNotSupported - )) - ); - } - - mod device_in_isolation { - use super::*; - - fn sample() -> FactorSourceID { - FactorSourceID::sample_device() - } - - fn sample_other() -> FactorSourceID { - FactorSourceID::sample_device_other() - } - - #[test] - fn allowed_as_first_and_only() { - // Arrange - let mut sut = make(); - - // Act - sut.add_factor_source_to_list(sample(), list()).unwrap(); - - // Assert - assert_eq!( - sut.build().unwrap(), - RoleWithFactorSourceIds::recovery_with_factors([sample()]) - ); - } - - #[test] - fn two_of_same_kind_allowed() { - // TODO: Ask Matt - // Arrange - let mut sut = make(); - - // Act - sut.add_factor_source_to_list(sample(), list()).unwrap(); - sut.add_factor_source_to_list(sample_other(), list()) - .unwrap(); - - // Assert - assert_eq!( - sut.build().unwrap(), - RoleWithFactorSourceIds::recovery_with_factors([sample(), sample_other()],) - ); - } - - #[test] - fn validation_for_addition_of_factor_source_for_each() { - let sut = make(); - let xs = sut.validation_for_addition_of_factor_source_for_each( - list(), - &IndexSet::from_iter([sample(), sample_other()]), - ); - assert_eq!( - xs.into_iter().collect::>(), - vec![ - FactorSourceInRoleBuilderValidationStatus::ok(RoleKind::Recovery, sample()), - FactorSourceInRoleBuilderValidationStatus::ok( - RoleKind::Recovery, - sample_other(), - ) - ] - ); - } - } - - mod ledger_in_isolation { - use super::*; - - fn sample() -> FactorSourceID { - FactorSourceID::sample_ledger() - } - - fn sample_other() -> FactorSourceID { - FactorSourceID::sample_ledger_other() - } - - #[test] - fn allowed_as_first_and_only() { - // Arrange - let mut sut = make(); - - // Act - sut.add_factor_source_to_list(sample(), list()).unwrap(); - - // Assert - assert_eq!( - sut.build().unwrap(), - RoleWithFactorSourceIds::recovery_with_factors([sample()],) - ); - } - - #[test] - fn two_of_same_kind_allowed() { - // TODO: Ask Matt - // Arrange - let mut sut = make(); - - // Act - sut.add_factor_source_to_list(sample(), list()).unwrap(); - sut.add_factor_source_to_list(sample_other(), list()) - .unwrap(); - - // Assert - assert_eq!( - sut.build().unwrap(), - RoleWithFactorSourceIds::recovery_with_factors([sample(), sample_other()]) - ); - } - } - - mod arculus_in_isolation { - use super::*; - - fn sample() -> FactorSourceID { - FactorSourceID::sample_arculus() - } - - fn sample_other() -> FactorSourceID { - FactorSourceID::sample_arculus_other() - } - - #[test] - fn allowed_as_first_and_only() { - // Arrange - let mut sut = make(); - - // Act - sut.add_factor_source_to_list(sample(), list()).unwrap(); - - // Assert - assert_eq!( - sut.build().unwrap(), - RoleWithFactorSourceIds::recovery_with_factors([sample(),]) - ); - } - - #[test] - fn two_of_same_kind_allowed() { - // TODO: Ask Matt - // Arrange - let mut sut = make(); - - // Act - sut.add_factor_source_to_list(sample(), list()).unwrap(); - sut.add_factor_source_to_list(sample_other(), list()) - .unwrap(); - - // Assert - assert_eq!( - sut.build().unwrap(), - RoleWithFactorSourceIds::recovery_with_factors([sample(), sample_other()]) - ); - } - } - - mod passphrase_in_isolation { - use super::*; - - fn sample() -> FactorSourceID { - FactorSourceID::sample_passphrase() - } - - fn sample_other() -> FactorSourceID { - FactorSourceID::sample_passphrase_other() - } - - #[test] - fn allowed_as_first_and_only() { - // Arrange - let mut sut = make(); - - // Act - sut.add_factor_source_to_list(sample(), list()).unwrap(); - - // Assert - assert_eq!( - sut.build().unwrap(), - RoleWithFactorSourceIds::recovery_with_factors([sample()]) - ); - } - - #[test] - fn two_of_same_kind_allowed() { - // TODO: Ask Matt - // Arrange - let mut sut = make(); - - // Act - sut.add_factor_source_to_list(sample(), list()).unwrap(); - sut.add_factor_source_to_list(sample_other(), list()) - .unwrap(); - - // Assert - assert_eq!( - sut.build().unwrap(), - RoleWithFactorSourceIds::recovery_with_factors([sample(), sample_other()]) - ); - } - } - - mod trusted_contact_in_isolation { - use super::*; - - fn sample() -> FactorSourceID { - FactorSourceID::sample_trusted_contact() - } - - fn sample_other() -> FactorSourceID { - FactorSourceID::sample_trusted_contact_other() - } - - #[test] - fn allowed_as_first_and_only() { - // Arrange - let mut sut = make(); - - // Act - sut.add_factor_source_to_list(sample(), list()).unwrap(); - - // Assert - assert_eq!( - sut.build().unwrap(), - RoleWithFactorSourceIds::recovery_with_factors([sample(),]) - ); - } - - #[test] - fn two_of_same_kind_allowed() { - // TODO: Ask Matt - // Arrange - let mut sut = make(); - - // Act - sut.add_factor_source_to_list(sample(), list()).unwrap(); - sut.add_factor_source_to_list(sample_other(), list()) - .unwrap(); - - // Assert - assert_eq!( - sut.build().unwrap(), - RoleWithFactorSourceIds::recovery_with_factors([sample(), sample_other()]) - ); - } - } - - mod password_in_isolation { - use super::*; - - fn sample() -> FactorSourceID { - FactorSourceID::sample_password() - } - - #[test] - fn unsupported() { - // Arrange - let mut sut = make(); - - // Act - let res = sut.add_factor_source_to_list(sample(), list()); - - // Assert - assert_eq!( - res, - MutRes::forever_invalid(ForeverInvalidReason::RecoveryRolePasswordNotSupported) - ); - } - - #[test] - fn valid_then_invalid_because_unsupported() { - // Arrange - let mut sut = make(); - - sut.add_factor_source_to_list(FactorSourceID::sample_ledger(), list()) - .unwrap(); - sut.add_factor_source_to_list(FactorSourceID::sample_arculus(), list()) - .unwrap(); - - // Act - let res = sut.add_factor_source_to_list(sample(), list()); - - // Assert - assert_eq!( - res, - MutRes::forever_invalid(ForeverInvalidReason::RecoveryRolePasswordNotSupported) - ); - } - } - - mod security_questions_in_isolation { - use super::*; - - fn sample() -> FactorSourceID { - FactorSourceID::sample_security_questions() - } - fn sample_other() -> FactorSourceID { - FactorSourceID::sample_security_questions_other() - } - - #[test] - fn unsupported() { - // Arrange - let mut sut = make(); - - // Act - let res = sut.add_factor_source_to_list(sample(), list()); - - // Assert - assert_eq!( - res, - MutRes::forever_invalid( - ForeverInvalidReason::RecoveryRoleSecurityQuestionsNotSupported - ) - ); - } - - #[test] - fn valid_then_invalid_because_unsupported() { - // Arrange - let mut sut = make(); - - sut.add_factor_source_to_list(FactorSourceID::sample_ledger(), list()) - .unwrap(); - sut.add_factor_source_to_list(FactorSourceID::sample_arculus(), list()) - .unwrap(); - - // Act - let res = sut.add_factor_source_to_list(sample_other(), list()); - - // Assert - let reason = ForeverInvalidReason::RecoveryRoleSecurityQuestionsNotSupported; - let err = MutRes::forever_invalid(reason); - assert_eq!(res, err); - - // .. erroneous action above did not change the state of the builder (SUT), - // so we can build and `sample` is not present in the built result. - assert_eq!( - sut.build(), - Ok(RoleWithFactorSourceIds::recovery_with_factors([ - FactorSourceID::sample_ledger(), - FactorSourceID::sample_arculus() - ])) - ); - } - } -} - -#[cfg(test)] -mod confirmation_in_isolation { - - use super::*; - - fn role() -> RoleKind { - RoleKind::Confirmation - } - - fn make() -> SUT { - SUT::new(role()) - } - - fn list() -> FactorListKind { - FactorListKind::Override - } - - fn sample() -> FactorSourceID { - FactorSourceID::sample_device() - } - - #[test] - fn validation_for_addition_of_factor_source_of_kind_to_list_is_err_for_threshold() { - let sut = make(); - let res = sut.validation_for_addition_of_factor_source_of_kind_to_list( - FactorSourceKind::Device, - FactorListKind::Threshold, - ); - assert_eq!( - res, - MutRes::forever_invalid(ForeverInvalidReason::threshold_list_not_supported_for_role( - role() - )) - ); - } - - #[test] - fn validation_for_addition_of_factor_source_of_kind_to_list() { - use FactorSourceKind::*; - let sut = make(); - let not_ok = |kind: FactorSourceKind| { - let res = sut.validation_for_addition_of_factor_source_of_kind_to_list(kind, list()); - assert!(res.is_err()); - }; - let ok = |kind: FactorSourceKind| { - let res = sut.validation_for_addition_of_factor_source_of_kind_to_list(kind, list()); - assert!(res.is_ok()); - }; - ok(Device); - ok(LedgerHQHardwareWallet); - ok(ArculusCard); - ok(SecurityQuestions); - ok(Passphrase); - ok(OffDeviceMnemonic); - not_ok(TrustedContact); - } - - #[test] - fn duplicates_not_allowed() { - test_duplicates_not_allowed(make(), list(), sample()) - } - - #[test] - fn cannot_add_factors_to_threshold() { - let mut sut = make(); - let res = sut.add_factor_source_to_list(sample(), FactorListKind::Threshold); - assert_eq!( - res, - Err(Validation::ForeverInvalid( - ForeverInvalidReason::ConfirmationRoleThresholdFactorsNotSupported - )) - ); - } - - mod device_in_isolation { - use super::*; - - fn sample() -> FactorSourceID { - FactorSourceID::sample_device() - } - - fn sample_other() -> FactorSourceID { - FactorSourceID::sample_device_other() - } - - #[test] - fn set_threshold_is_unsupported() { - let mut sut = make(); - assert_eq!( - sut.set_threshold(1), - MutRes::basic_violation(BasicViolation::ConfirmationCannotSetThreshold) - ); - } - - #[test] - fn allowed_as_first_and_only() { - // Arrange - let mut sut = make(); - - // Act - sut.add_factor_source_to_list(sample(), list()).unwrap(); - - // Assert - assert_eq!( - sut.build().unwrap(), - RoleWithFactorSourceIds::confirmation_with_factors([sample()]) - ); - } - - #[test] - fn two_of_same_kind_allowed() { - // TODO: Ask Matt - // Arrange - let mut sut = make(); - - // Act - sut.add_factor_source_to_list(sample(), list()).unwrap(); - sut.add_factor_source_to_list(sample_other(), list()) - .unwrap(); - - // Assert - let built = sut.build().unwrap(); - assert!(built.get_threshold_factors().is_empty()); - assert_eq!( - built, - RoleWithFactorSourceIds::confirmation_with_factors([sample(), sample_other()]) - ); - } - } - - mod ledger_in_isolation { - use super::*; - - fn sample() -> FactorSourceID { - FactorSourceID::sample_ledger() - } - - fn sample_other() -> FactorSourceID { - FactorSourceID::sample_ledger_other() - } - - #[test] - fn allowed_as_first_and_only() { - // Arrange - let mut sut = make(); - - // Act - sut.add_factor_source_to_list(sample(), list()).unwrap(); - - // Assert - assert_eq!( - sut.build().unwrap(), - RoleWithFactorSourceIds::confirmation_with_factors([sample(),]) - ); - } - - #[test] - fn two_of_same_kind_allowed() { - // TODO: Ask Matt - // Arrange - let mut sut = make(); - - // Act - sut.add_factor_source_to_list(sample(), list()).unwrap(); - sut.add_factor_source_to_list(sample_other(), list()) - .unwrap(); - - // Assert - assert_eq!( - sut.build().unwrap(), - RoleWithFactorSourceIds::confirmation_with_factors([sample(), sample_other()]) - ); - } - } - - mod arculus_in_isolation { - use super::*; - - fn sample() -> FactorSourceID { - FactorSourceID::sample_arculus() - } - - fn sample_other() -> FactorSourceID { - FactorSourceID::sample_arculus_other() - } - - #[test] - fn allowed_as_first_and_only() { - // Arrange - let mut sut = make(); - - // Act - sut.add_factor_source_to_list(sample(), list()).unwrap(); - - // Assert - assert_eq!( - sut.build().unwrap(), - RoleWithFactorSourceIds::confirmation_with_factors([sample(),]) - ); - } - - #[test] - fn two_of_same_kind_allowed() { - // TODO: Ask Matt - // Arrange - let mut sut = make(); - - // Act - sut.add_factor_source_to_list(sample(), list()).unwrap(); - sut.add_factor_source_to_list(sample_other(), list()) - .unwrap(); - - // Assert - assert_eq!( - sut.build().unwrap(), - RoleWithFactorSourceIds::confirmation_with_factors([sample(), sample_other()]) - ); - } - } - - mod passphrase_in_isolation { - use super::*; - - fn sample() -> FactorSourceID { - FactorSourceID::sample_passphrase() - } - - fn sample_other() -> FactorSourceID { - FactorSourceID::sample_passphrase_other() - } - - #[test] - fn allowed_as_first_and_only() { - // Arrange - let mut sut = make(); - - // Act - sut.add_factor_source_to_list(sample(), list()).unwrap(); - - // Assert - assert_eq!( - sut.build().unwrap(), - RoleWithFactorSourceIds::confirmation_with_factors([sample(),]) - ); - } - - #[test] - fn two_of_same_kind_allowed() { - // TODO: Ask Matt - // Arrange - let mut sut = make(); - - // Act - sut.add_factor_source_to_list(sample(), list()).unwrap(); - sut.add_factor_source_to_list(sample_other(), list()) - .unwrap(); - - // Assert - assert_eq!( - sut.build().unwrap(), - RoleWithFactorSourceIds::confirmation_with_factors([sample(), sample_other()]) - ); - } - } - - mod trusted_contact_in_isolation { - use super::*; - - fn sample() -> FactorSourceID { - FactorSourceID::sample_trusted_contact() - } - - #[test] - fn unsupported() { - // Arrange - let mut sut = make(); - - // Act - let res = sut.add_factor_source_to_list(sample(), list()); - - // Assert - assert_eq!( - res, - MutRes::forever_invalid( - ForeverInvalidReason::ConfirmationRoleTrustedContactNotSupported - ) - ); - } - - #[test] - fn valid_then_invalid_because_unsupported() { - // Arrange - let mut sut = make(); - - sut.add_factor_source_to_list(FactorSourceID::sample_ledger(), list()) - .unwrap(); - sut.add_factor_source_to_list(FactorSourceID::sample_arculus(), list()) - .unwrap(); - - // Act - let res = sut.add_factor_source_to_list(sample(), list()); - - // Assert - assert_eq!( - res, - MutRes::forever_invalid( - ForeverInvalidReason::ConfirmationRoleTrustedContactNotSupported - ) - ); - } - } - - mod password_in_isolation { - use super::*; - - fn sample() -> FactorSourceID { - FactorSourceID::sample_password() - } - - fn sample_other() -> FactorSourceID { - FactorSourceID::sample_password_other() - } - - #[test] - fn allowed_as_first_and_only() { - // Arrange - let mut sut = make(); - - // Act - sut.add_factor_source_to_list(sample(), list()).unwrap(); - - // Assert - assert_eq!( - sut.build().unwrap(), - RoleWithFactorSourceIds::confirmation_with_factors([sample(),]) - ); - } - - #[test] - fn two_of_same_kind_allowed() { - // TODO: Ask Matt - // Arrange - let mut sut = make(); - - // Act - sut.add_factor_source_to_list(sample(), list()).unwrap(); - sut.add_factor_source_to_list(sample_other(), list()) - .unwrap(); - - // Assert - assert_eq!( - sut.build().unwrap(), - RoleWithFactorSourceIds::confirmation_with_factors([sample(), sample_other()]) - ); - } - } -} - -#[cfg(test)] -mod primary_in_isolation { - - use super::*; - - fn role() -> RoleKind { - RoleKind::Primary - } - - fn make() -> SUT { - SUT::new(role()) - } - - #[cfg(test)] - mod threshold_suite { - use super::*; - - fn sample() -> FactorSourceID { - FactorSourceID::sample_device() - } - - fn sample_other() -> FactorSourceID { - FactorSourceID::sample_ledger() - } - - fn sample_third() -> FactorSourceID { - FactorSourceID::sample_arculus() - } - - fn list() -> FactorListKind { - FactorListKind::Threshold - } - - #[test] - fn remove_lowers_threshold_from_1_to_0() { - let mut sut = make(); - let fs = sample(); - sut.add_factor_source_to_list(fs, list()).unwrap(); - sut.set_threshold(1).unwrap(); - assert_eq!(sut.get_threshold(), 1); - assert_eq!( - sut.remove_factor_source(&fs), - Err(Validation::NotYetValid(RoleMustHaveAtLeastOneFactor)) - ); - assert_eq!(sut.get_threshold(), 0); - } - - #[test] - fn remove_lowers_threshold_from_3_to_1() { - let mut sut = make(); - let fs0 = sample(); - let fs1 = sample_other(); - sut.add_factor_source_to_list(fs0, list()).unwrap(); - sut.add_factor_source_to_list(fs1, list()).unwrap(); - sut.add_factor_source_to_list(FactorSourceID::sample_arculus_other(), list()) - .unwrap(); - sut.set_threshold(3).unwrap(); - assert_eq!(sut.get_threshold(), 3); - sut.remove_factor_source(&fs0).unwrap(); - sut.remove_factor_source(&fs1).unwrap(); - assert_eq!(sut.get_threshold(), 1); - } - - #[test] - fn remove_from_override_does_not_change_threshold() { - let mut sut = make(); - sut.add_factor_source_to_list(sample(), list()).unwrap(); - sut.add_factor_source_to_list(sample_other(), list()) - .unwrap(); - let fs = FactorSourceID::sample_arculus_other(); - sut.add_factor_source_to_list(fs, FactorListKind::Override) - .unwrap(); - sut.set_threshold(2).unwrap(); - assert_eq!(sut.get_threshold(), 2); - sut.remove_factor_source(&fs).unwrap(); - assert_eq!(sut.get_threshold(), 2); - - let built = sut.build().unwrap(); - assert_eq!(built.get_threshold(), 2); - - assert_eq!(built.role(), RoleKind::Primary); - - assert_eq!( - built.get_threshold_factors(), - &vec![sample(), sample_other()] - ); - - assert_eq!(built.get_override_factors(), &Vec::new()); - } - - #[test] - fn one_factor_then_set_threshold_to_one_is_ok() { - // Arrange - let mut sut = make(); - - // Act - sut.add_factor_source_to_list(sample_other(), list()) - .unwrap(); - sut.set_threshold(1).unwrap(); - - // Assert - let expected = RoleWithFactorSourceIds::primary_with_factors(1, [sample_other()], []); - assert_eq!(sut.build().unwrap(), expected); - } - - #[test] - fn zero_factor_then_set_threshold_to_one_is_not_yet_valid_then_add_one_factor_is_ok() { - // Arrange - let mut sut = make(); - - // Act - assert_eq!( - sut.set_threshold(1), - Err(Validation::NotYetValid( - ThresholdHigherThanThresholdFactorsLen - )) - ); - sut.add_factor_source_to_list(sample_other(), list()) - .unwrap(); - - // Assert - let expected = RoleWithFactorSourceIds::primary_with_factors(1, [sample_other()], []); - assert_eq!(sut.build().unwrap(), expected); - } - - #[test] - fn zero_factor_then_set_threshold_to_two_is_not_yet_valid_then_add_two_factor_is_ok() { - // Arrange - let mut sut = make(); - - // Act - assert_eq!( - sut.set_threshold(2), - Err(Validation::NotYetValid( - ThresholdHigherThanThresholdFactorsLen - )) - ); - sut.add_factor_source_to_list(sample(), list()).unwrap(); - - sut.add_factor_source_to_list(sample_other(), list()) - .unwrap(); - - // Assert - let expected = - RoleWithFactorSourceIds::primary_with_factors(2, [sample(), sample_other()], []); - assert_eq!(sut.build().unwrap(), expected); - } - - #[test] - fn add_two_factors_then_set_threshold_to_two_is_ok() { - // Arrange - let mut sut = make(); - - sut.add_factor_source_to_list(sample(), list()).unwrap(); - sut.add_factor_source_to_list(sample_other(), list()) - .unwrap(); - - // Act - assert_eq!(sut.set_threshold(2), Ok(())); - - // Assert - let expected = - RoleWithFactorSourceIds::primary_with_factors(2, [sample(), sample_other()], []); - assert_eq!(sut.build().unwrap(), expected); - } - - #[test] - fn add_two_factors_then_set_threshold_to_three_is_not_yet_valid_then_add_third_factor_is_ok( - ) { - // Arrange - let mut sut = make(); - - sut.add_factor_source_to_list(sample(), list()).unwrap(); - sut.add_factor_source_to_list(sample_other(), list()) - .unwrap(); - - // Act - assert_eq!( - sut.set_threshold(3), - Err(Validation::NotYetValid( - ThresholdHigherThanThresholdFactorsLen - )) - ); - - sut.add_factor_source_to_list(sample_third(), list()) - .unwrap(); - - // Assert - let expected = RoleWithFactorSourceIds::primary_with_factors( - 3, - [sample(), sample_other(), sample_third()], - [], - ); - assert_eq!(sut.build().unwrap(), expected); - } - - #[test] - fn one_factors_set_threshold_of_one_is_ok() { - // Arrange - let mut sut = make(); - - // Act - sut.add_factor_source_to_list(sample_other(), list()) - .unwrap(); - sut.set_threshold(1).unwrap(); - - // Assert - let expected = RoleWithFactorSourceIds::primary_with_factors(1, [sample_other()], []); - assert_eq!(sut.build().unwrap(), expected); - } - - #[test] - fn one_override_factors_set_threshold_to_one_is_not_yet_valid() { - // Arrange - let mut sut = make(); - - // Act - sut.add_factor_source_to_list(sample_other(), FactorListKind::Override) - .unwrap(); - assert_eq!( - sut.set_threshold(1), - Err(Validation::NotYetValid( - ThresholdHigherThanThresholdFactorsLen - )) - ); - - // Assert - - assert_eq!( - sut.build(), - Err(Validation::NotYetValid( - ThresholdHigherThanThresholdFactorsLen - )) - ); - } - - #[test] - fn validation_for_addition_of_factor_source_for_each_before_after_adding_a_factor() { - let mut sut = make(); - let fs0 = FactorSourceID::sample_ledger(); - let fs1 = FactorSourceID::sample_password(); - let fs2 = FactorSourceID::sample_arculus(); - let xs = sut.validation_for_addition_of_factor_source_for_each( - list(), - &IndexSet::from_iter([fs0, fs1, fs2]), - ); - assert_eq!( - xs.into_iter().collect::>(), - vec![ - FactorSourceInRoleBuilderValidationStatus::ok( - RoleKind::Primary, - fs0, - ), - FactorSourceInRoleBuilderValidationStatus::not_yet_valid( - RoleKind::Primary, - fs1, - NotYetValidReason::PrimaryRoleWithPasswordInThresholdListMustHaveAnotherFactor - ), - FactorSourceInRoleBuilderValidationStatus::ok( - RoleKind::Primary, - fs2, - ), - ] - ); - _ = sut.add_factor_source_to_list(fs0, list()); - _ = sut.set_threshold(2); - - let xs = sut.validation_for_addition_of_factor_source_for_each( - list(), - &IndexSet::from_iter([fs0, fs1, fs2]), - ); - assert_eq!( - xs.into_iter().collect::>(), - vec![ - FactorSourceInRoleBuilderValidationStatus::forever_invalid( - RoleKind::Primary, - fs0, - ForeverInvalidReason::FactorSourceAlreadyPresent - ), - FactorSourceInRoleBuilderValidationStatus::ok(RoleKind::Primary, fs1,), - FactorSourceInRoleBuilderValidationStatus::ok(RoleKind::Primary, fs2,), - ] - ); - } - } - - #[cfg(test)] - mod password { - use super::*; - - fn sample() -> FactorSourceID { - FactorSourceID::sample_password() - } - - fn sample_other() -> FactorSourceID { - FactorSourceID::sample_password_other() - } - - #[test] - fn test_suite_prerequisite() { - assert_eq!(sample(), sample()); - assert_eq!(sample_other(), sample_other()); - assert_ne!(sample(), sample_other()); - } - - mod threshold_in_isolation { - use super::*; - - fn list() -> FactorListKind { - FactorListKind::Threshold - } - - #[test] - fn duplicates_not_allowed() { - let mut sut = make(); - sut.add_factor_source_to_list( - FactorSourceID::sample_device(), - FactorListKind::Threshold, - ) - .unwrap(); - _ = sut.set_threshold(2); - test_duplicates_not_allowed(sut, list(), sample()); - } - - #[test] - fn alone_is_not_ok() { - // Arrange - let mut sut = make(); - - // Act - let res = sut.add_factor_source_to_list(sample(), list()); - - // Assert - assert_eq!( - res, - MutRes::not_yet_valid( - NotYetValidReason::PrimaryRoleWithPasswordInThresholdListMustHaveAnotherFactor - ) - ); - } - - #[test] - fn validation_for_addition_of_factor_source_of_kind_to_list() { - use FactorSourceKind::*; - - let not_ok = |kind: FactorSourceKind| { - let sut = make(); - let res = - sut.validation_for_addition_of_factor_source_of_kind_to_list(kind, list()); - assert!(res.is_err()); - }; - - let ok_with = |kind: FactorSourceKind, setup: fn(&mut SUT)| { - let mut sut = make(); - setup(&mut sut); - let res = - sut.validation_for_addition_of_factor_source_of_kind_to_list(kind, list()); - assert!(res.is_ok()); - }; - let ok = |kind: FactorSourceKind| { - ok_with(kind, |_| {}); - }; - - ok(LedgerHQHardwareWallet); - ok(ArculusCard); - ok(OffDeviceMnemonic); - - ok_with(Device, |sut| { - sut.add_factor_source_to_list(FactorSourceID::sample_ledger(), list()) - .unwrap(); - }); - ok_with(Passphrase, |sut| { - sut.add_factor_source_to_list(FactorSourceID::sample_device(), list()) - .unwrap(); - _ = sut.set_threshold(2); - }); - - not_ok(SecurityQuestions); - not_ok(TrustedContact); - } - } - - mod override_in_isolation { - use super::*; - - fn list() -> FactorListKind { - FactorListKind::Override - } - - #[test] - fn unsupported() { - // Arrange - let mut sut = make(); - - // Act - let res = sut.add_factor_source_to_list(sample(), list()); - - // Assert - assert_eq!( - res, - MutRes::forever_invalid( - ForeverInvalidReason::PrimaryCannotHavePasswordInOverrideList - ) - ); - } - - #[test] - fn valid_then_invalid_because_unsupported() { - // Arrange - let mut sut = make(); - sut.add_factor_source_to_list( - FactorSourceID::sample_device(), - FactorListKind::Threshold, - ) - .unwrap(); - sut.add_factor_source_to_list(FactorSourceID::sample_ledger(), list()) - .unwrap(); - sut.add_factor_source_to_list(FactorSourceID::sample_arculus(), list()) - .unwrap(); - - // Act - let res = sut.add_factor_source_to_list(sample(), list()); - - // Assert - assert_eq!( - res, - MutRes::forever_invalid( - ForeverInvalidReason::PrimaryCannotHavePasswordInOverrideList - ) - ); - } - - #[test] - fn validation_for_addition_of_factor_source_of_kind_to_list() { - use FactorSourceKind::*; - - let not_ok = |kind: FactorSourceKind| { - let sut = make(); - let res = - sut.validation_for_addition_of_factor_source_of_kind_to_list(kind, list()); - assert!(res.is_err()); - }; - - let ok_with = |kind: FactorSourceKind, setup: fn(&mut SUT)| { - let mut sut = make(); - setup(&mut sut); - let res = - sut.validation_for_addition_of_factor_source_of_kind_to_list(kind, list()); - assert!(res.is_ok()); - }; - let ok = |kind: FactorSourceKind| { - ok_with(kind, |_| {}); - }; - - ok(LedgerHQHardwareWallet); - ok(ArculusCard); - ok(OffDeviceMnemonic); - - ok_with(Device, |sut| { - sut.add_factor_source_to_list(FactorSourceID::sample_ledger(), list()) - .unwrap(); - }); - - not_ok(Passphrase); - - not_ok(SecurityQuestions); - not_ok(TrustedContact); - } - } - } - - #[cfg(test)] - mod ledger { - use super::*; - - fn sample() -> FactorSourceID { - FactorSourceID::sample_ledger() - } - - fn sample_other() -> FactorSourceID { - FactorSourceID::sample_ledger_other() - } - - #[test] - fn test_suite_prerequisite() { - assert_eq!(sample(), sample()); - assert_eq!(sample_other(), sample_other()); - assert_ne!(sample(), sample_other()); - } - - mod threshold_in_isolation { - use super::*; - fn list() -> FactorListKind { - FactorListKind::Threshold - } - - #[test] - fn duplicates_not_allowed() { - test_duplicates_not_allowed(make(), list(), sample()); - } - - #[test] - fn one_is_ok() { - // Arrange - let mut sut = make(); - - // Act - sut.add_factor_source_to_list(sample(), list()).unwrap(); - sut.set_threshold(1).unwrap(); - - // Assert - let expected = RoleWithFactorSourceIds::primary_with_factors(1, [sample()], []); - assert_eq!(sut.build().unwrap(), expected); - } - - #[test] - fn one_with_threshold_of_zero_is_err() { - // Arrange - let mut sut = make(); - - // Act - sut.add_factor_source_to_list(sample(), list()).unwrap(); - - // Assert - assert_eq!( - sut.build(), - RoleBuilderBuildResult::Err(RoleBuilderValidation::NotYetValid( - NotYetValidReason::PrimaryRoleWithThresholdCannotBeZeroWithFactors - )) - ); - } - - #[test] - fn two_different_is_ok() { - // Arrange - let mut sut = make(); - - // Act - sut.add_factor_source_to_list(sample(), list()).unwrap(); - sut.add_factor_source_to_list(sample_other(), list()) - .unwrap(); - sut.set_threshold(2).unwrap(); - - // Assert - let expected = RoleWithFactorSourceIds::primary_with_factors( - 2, - [sample(), sample_other()], - [], - ); - assert_eq!(sut.build().unwrap(), expected); - } - } - - mod override_in_isolation { - use super::*; - fn list() -> FactorListKind { - FactorListKind::Override - } - - #[test] - fn duplicates_not_allowed() { - test_duplicates_not_allowed(make(), list(), sample()); - } - - #[test] - fn one_is_ok() { - // Arrange - let mut sut = make(); - - // Act - sut.add_factor_source_to_list(sample(), list()).unwrap(); - - // Assert - let expected = RoleWithFactorSourceIds::primary_with_factors(0, [], [sample()]); - assert_eq!(sut.build().unwrap(), expected); - } - - #[test] - fn two_different_is_ok() { - // Arrange - let mut sut = make(); - - // Act - sut.add_factor_source_to_list(sample(), list()).unwrap(); - sut.add_factor_source_to_list(sample_other(), list()) - .unwrap(); - - // Assert - let expected = RoleWithFactorSourceIds::primary_with_factors( - 0, - [], - [sample(), sample_other()], - ); - assert_eq!(sut.build().unwrap(), expected); - } - } - } - - #[cfg(test)] - mod arculus { - use super::*; - - fn sample() -> FactorSourceID { - FactorSourceID::sample_arculus() - } - - fn sample_other() -> FactorSourceID { - FactorSourceID::sample_arculus_other() - } - - #[test] - fn test_suite_prerequisite() { - assert_eq!(sample(), sample()); - assert_eq!(sample_other(), sample_other()); - assert_ne!(sample(), sample_other()); - } - - mod threshold_in_isolation { - use super::*; - fn list() -> FactorListKind { - FactorListKind::Threshold - } - - #[test] - fn duplicates_not_allowed() { - test_duplicates_not_allowed(make(), list(), sample()); - } - - #[test] - fn one_is_ok() { - // Arrange - let mut sut = make(); - - // Act - sut.add_factor_source_to_list(sample(), list()).unwrap(); - sut.set_threshold(1).unwrap(); - - // Assert - let expected = RoleWithFactorSourceIds::primary_with_factors(1, [sample()], []); - assert_eq!(sut.build().unwrap(), expected); - } - - #[test] - fn two_different_is_ok() { - // Arrange - let mut sut = make(); - - // Act - sut.add_factor_source_to_list(sample(), list()).unwrap(); - sut.add_factor_source_to_list(sample_other(), list()) - .unwrap(); - sut.set_threshold(1).unwrap(); - - // Assert - let expected = RoleWithFactorSourceIds::primary_with_factors( - 1, - [sample(), sample_other()], - [], - ); - assert_eq!(sut.build().unwrap(), expected); - } - } - - mod override_in_isolation { - use super::*; - fn list() -> FactorListKind { - FactorListKind::Override - } - - #[test] - fn duplicates_not_allowed() { - test_duplicates_not_allowed(make(), list(), sample()); - } - - #[test] - fn one_is_ok() { - // Arrange - let mut sut = make(); - - // Act - sut.add_factor_source_to_list(sample(), list()).unwrap(); - - // Assert - let expected = RoleWithFactorSourceIds::primary_with_factors(0, [], [sample()]); - assert_eq!(sut.build().unwrap(), expected); - } - - #[test] - fn two_different_is_ok() { - // Arrange - let mut sut = make(); - - // Act - sut.add_factor_source_to_list(sample(), list()).unwrap(); - sut.add_factor_source_to_list(sample_other(), list()) - .unwrap(); - - // Assert - let expected = RoleWithFactorSourceIds::primary_with_factors( - 0, - [], - [sample(), sample_other()], - ); - assert_eq!(sut.build().unwrap(), expected); - } - } - } - - #[cfg(test)] - mod device_factor_source { - use super::*; - - fn sample() -> FactorSourceID { - FactorSourceID::sample_device() - } - - fn sample_other() -> FactorSourceID { - FactorSourceID::sample_device_other() - } - - #[test] - fn test_suite_prerequisite() { - assert_eq!(sample(), sample()); - assert_eq!(sample_other(), sample_other()); - assert_ne!(sample(), sample_other()); - } - - #[cfg(test)] - mod threshold_in_isolation { - use super::*; - - fn list() -> FactorListKind { - FactorListKind::Threshold - } - - #[test] - fn duplicates_not_allowed() { - test_duplicates_not_allowed(make(), list(), sample()) - } - - #[test] - fn one_is_ok() { - // Arrange - let mut sut = make(); - - // Act - sut.add_factor_source_to_list(sample(), list()).unwrap(); - sut.set_threshold(1).unwrap(); - - // Assert - let expected = RoleWithFactorSourceIds::primary_with_factors(1, [sample()], []); - assert_eq!(sut.build().unwrap(), expected); - } - - #[test] - fn two_different_is_err() { - // Arrange - let mut sut = make(); - - sut.add_factor_source_to_list(sample(), list()).unwrap(); - - // Act - let res = sut.add_factor_source_to_list(sample_other(), list()); - - // Assert - assert!(matches!( - res, - MutRes::Err(Validation::ForeverInvalid( - ForeverInvalidReason::PrimaryCannotHaveMultipleDevices - )) - )); - } - } - - mod override_in_isolation { - - use super::*; - - fn list() -> FactorListKind { - FactorListKind::Override - } - - #[test] - fn duplicates_not_allowed() { - test_duplicates_not_allowed(make(), list(), sample()) - } - - #[test] - fn one_is_ok() { - // Arrange - let mut sut = make(); - - // Act - sut.add_factor_source_to_list(sample(), list()).unwrap(); - - // Assert - let expected = RoleWithFactorSourceIds::primary_with_factors(0, [], [sample()]); - assert_eq!(sut.build().unwrap(), expected); - } - } - } -} +// #![cfg(test)] + +// use crate::prelude::*; + +// use NotYetValidReason::*; +// type Validation = RoleBuilderValidation; + +// #[allow(clippy::upper_case_acronyms)] + +// type MutRes = RoleBuilderMutateResult; + +// mod primary_test_helper_functions { + +// use super::*; +// type SUT = PrimaryRoleBuilder; +// type BuildRes = SUT::RoleBuilderBuildResult; + +// #[test] +// fn factor_sources_not_of_kind_to_list_of_kind_in_override() { +// let mut sut = SUT::new(); +// sut.add_factor_source_to_list(FactorSourceID::sample_device(), FactorListKind::Override) +// .unwrap(); +// sut.add_factor_source_to_list(FactorSourceID::sample_ledger(), FactorListKind::Override) +// .unwrap(); +// sut.add_factor_source_to_list(FactorSourceID::sample_arculus(), FactorListKind::Override) +// .unwrap(); + +// let xs = sut.factor_sources_not_of_kind_to_list_of_kind( +// FactorSourceKind::Device, +// FactorListKind::Override, +// ); +// assert_eq!( +// xs, +// vec![ +// FactorSourceID::sample_ledger(), +// FactorSourceID::sample_arculus() +// ] +// ); + +// let xs = sut.factor_sources_not_of_kind_to_list_of_kind( +// FactorSourceKind::LedgerHQHardwareWallet, +// FactorListKind::Override, +// ); +// assert_eq!( +// xs, +// vec![ +// FactorSourceID::sample_device(), +// FactorSourceID::sample_arculus() +// ] +// ); + +// let xs = sut.factor_sources_not_of_kind_to_list_of_kind( +// FactorSourceKind::ArculusCard, +// FactorListKind::Override, +// ); +// assert_eq!( +// xs, +// vec![ +// FactorSourceID::sample_device(), +// FactorSourceID::sample_ledger() +// ] +// ); +// } + +// #[test] +// fn factor_sources_not_of_kind_to_list_of_kind_in_threshold() { +// let mut sut = SUT::new(); +// sut.add_factor_source_to_list(FactorSourceID::sample_device(), FactorListKind::Threshold) +// .unwrap(); +// sut.add_factor_source_to_list(FactorSourceID::sample_ledger(), FactorListKind::Threshold) +// .unwrap(); +// sut.add_factor_source_to_list(FactorSourceID::sample_arculus(), FactorListKind::Threshold) +// .unwrap(); + +// let xs = sut.factor_sources_not_of_kind_to_list_of_kind( +// FactorSourceKind::Device, +// FactorListKind::Threshold, +// ); +// assert_eq!( +// xs, +// vec![ +// FactorSourceID::sample_ledger(), +// FactorSourceID::sample_arculus() +// ] +// ); + +// let xs = sut.factor_sources_not_of_kind_to_list_of_kind( +// FactorSourceKind::LedgerHQHardwareWallet, +// FactorListKind::Threshold, +// ); +// assert_eq!( +// xs, +// vec![ +// FactorSourceID::sample_device(), +// FactorSourceID::sample_arculus() +// ] +// ); + +// let xs = sut.factor_sources_not_of_kind_to_list_of_kind( +// FactorSourceKind::ArculusCard, +// FactorListKind::Threshold, +// ); +// assert_eq!( +// xs, +// vec![ +// FactorSourceID::sample_device(), +// FactorSourceID::sample_ledger() +// ] +// ); +// } +// } + +// fn test_duplicates_not_allowed( +// sut: RoleBuilder, +// list: FactorListKind, +// factor_source_id: FactorSourceID, +// ) { +// // Arrange +// let mut sut = sut; + +// sut.add_factor_source_to_list(factor_source_id, list) +// .unwrap(); + +// // Act +// let res = sut.add_factor_source_to_list( +// factor_source_id, // oh no, duplicate! +// list, +// ); + +// // Assert +// assert!(matches!( +// res, +// MutRes::Err(Validation::ForeverInvalid( +// ForeverInvalidReason::FactorSourceAlreadyPresent +// )) +// )); +// } + +// #[test] +// fn new_builders() { +// assert_eq!(SUT::primary().role(), RoleKind::Primary); +// assert_eq!(SUT::recovery().role(), RoleKind::Recovery); +// assert_eq!(SUT::confirmation().role(), RoleKind::Confirmation); +// } + +// #[test] +// fn empty_is_err() { +// [ +// RoleKind::Primary, +// RoleKind::Recovery, +// RoleKind::Confirmation, +// ] +// .iter() +// .for_each(|role| { +// let sut = SUT::new(*role); +// let res = sut.build(); +// assert_eq!( +// res, +// BuildRes::not_yet_valid(NotYetValidReason::RoleMustHaveAtLeastOneFactor) +// ); +// }); +// } + +// #[test] +// fn validate_override_for_ever_invalid() { +// let sut = SUT::with_factors( +// RoleKind::Primary, +// 0, +// vec![], +// vec![ +// FactorSourceID::sample_ledger(), +// FactorSourceID::sample_ledger(), +// ], +// ); +// let res = sut.validate(); +// assert_eq!( +// res, +// MutRes::forever_invalid(ForeverInvalidReason::FactorSourceAlreadyPresent) +// ); +// } + +// #[test] +// fn validate_threshold_for_ever_invalid() { +// let sut = SUT::with_factors( +// RoleKind::Primary, +// 1, +// vec![ +// FactorSourceID::sample_ledger(), +// FactorSourceID::sample_ledger(), +// ], +// vec![], +// ); +// let res = sut.validate(); +// assert_eq!( +// res, +// MutRes::forever_invalid(ForeverInvalidReason::FactorSourceAlreadyPresent) +// ); +// } + +// #[test] +// fn confirmation_validate_basic_violation() { +// let sut = SUT::with_factors( +// RoleKind::Confirmation, +// 1, +// vec![], +// vec![FactorSourceID::sample_ledger()], +// ); +// let res = sut.validate(); +// assert_eq!( +// res, +// MutRes::basic_violation(BasicViolation::ConfirmationCannotSetThreshold) +// ); +// } + +// #[test] +// fn recovery_validate_basic_violation() { +// let sut = SUT::with_factors( +// RoleKind::Recovery, +// 1, +// vec![], +// vec![FactorSourceID::sample_ledger()], +// ); +// let res = sut.validate(); +// assert_eq!( +// res, +// MutRes::basic_violation(BasicViolation::RecoveryCannotSetThreshold) +// ); +// } + +// #[test] +// fn primary_validate_not_yet_valid_for_threshold_greater_than_threshold_factors() { +// let sut = SUT::with_factors( +// RoleKind::Primary, +// 1, +// vec![], +// vec![FactorSourceID::sample_ledger()], +// ); +// let res = sut.validate(); +// assert_eq!( +// res, +// MutRes::not_yet_valid(ThresholdHigherThanThresholdFactorsLen) +// ); +// } + +// #[cfg(test)] +// mod recovery_in_isolation { + +// use super::*; + +// fn role() -> RoleKind { +// RoleKind::Recovery +// } + +// fn make() -> SUT { +// SUT::new(role()) +// } + +// fn list() -> FactorListKind { +// FactorListKind::Override +// } + +// fn sample() -> FactorSourceID { +// FactorSourceID::sample_device() +// } + +// #[test] +// fn duplicates_not_allowed() { +// test_duplicates_not_allowed(make(), list(), sample()) +// } + +// #[test] +// fn validation_for_addition_of_factor_source_of_kind_to_list_is_err_for_threshold() { +// let sut = make(); +// let res = sut.validation_for_addition_of_factor_source_of_kind_to_list( +// FactorSourceKind::Device, +// FactorListKind::Threshold, +// ); +// assert_eq!( +// res, +// MutRes::forever_invalid(ForeverInvalidReason::threshold_list_not_supported_for_role( +// role() +// )) +// ); +// } + +// #[test] +// fn validation_for_addition_of_factor_source_of_kind_to_list() { +// use FactorSourceKind::*; +// let sut = make(); +// let not_ok = |kind: FactorSourceKind| { +// let res = sut.validation_for_addition_of_factor_source_of_kind_to_list(kind, list()); +// assert!(res.is_err()); +// }; +// let ok = |kind: FactorSourceKind| { +// let res = sut.validation_for_addition_of_factor_source_of_kind_to_list(kind, list()); +// assert!(res.is_ok()); +// }; +// ok(Device); +// ok(LedgerHQHardwareWallet); +// ok(ArculusCard); +// ok(TrustedContact); +// ok(OffDeviceMnemonic); + +// not_ok(Passphrase); +// not_ok(SecurityQuestions); +// } + +// #[test] +// fn set_threshold_is_unsupported() { +// let mut sut = make(); +// assert_eq!( +// sut.set_threshold(1), +// MutRes::basic_violation(BasicViolation::RecoveryCannotSetThreshold) +// ); +// } + +// #[test] +// fn cannot_add_factors_to_threshold() { +// let mut sut = make(); +// let res = sut.add_factor_source_to_list(sample(), FactorListKind::Threshold); +// assert_eq!( +// res, +// Err(Validation::ForeverInvalid( +// ForeverInvalidReason::RecoveryRoleThresholdFactorsNotSupported +// )) +// ); +// } + +// mod device_in_isolation { +// use super::*; + +// fn sample() -> FactorSourceID { +// FactorSourceID::sample_device() +// } + +// fn sample_other() -> FactorSourceID { +// FactorSourceID::sample_device_other() +// } + +// #[test] +// fn allowed_as_first_and_only() { +// // Arrange +// let mut sut = make(); + +// // Act +// sut.add_factor_source_to_list(sample(), list()).unwrap(); + +// // Assert +// assert_eq!( +// sut.build().unwrap(), +// RoleWithFactorSourceIds::recovery_with_factors([sample()]) +// ); +// } + +// #[test] +// fn two_of_same_kind_allowed() { +// // TODO: Ask Matt +// // Arrange +// let mut sut = make(); + +// // Act +// sut.add_factor_source_to_list(sample(), list()).unwrap(); +// sut.add_factor_source_to_list(sample_other(), list()) +// .unwrap(); + +// // Assert +// assert_eq!( +// sut.build().unwrap(), +// RoleWithFactorSourceIds::recovery_with_factors([sample(), sample_other()],) +// ); +// } + +// #[test] +// fn validation_for_addition_of_factor_source_for_each() { +// let sut = make(); +// let xs = sut.validation_for_addition_of_factor_source_for_each( +// list(), +// &IndexSet::from_iter([sample(), sample_other()]), +// ); +// assert_eq!( +// xs.into_iter().collect::>(), +// vec![ +// FactorSourceInRoleBuilderValidationStatus::ok(RoleKind::Recovery, sample()), +// FactorSourceInRoleBuilderValidationStatus::ok( +// RoleKind::Recovery, +// sample_other(), +// ) +// ] +// ); +// } +// } + +// mod ledger_in_isolation { +// use super::*; + +// fn sample() -> FactorSourceID { +// FactorSourceID::sample_ledger() +// } + +// fn sample_other() -> FactorSourceID { +// FactorSourceID::sample_ledger_other() +// } + +// #[test] +// fn allowed_as_first_and_only() { +// // Arrange +// let mut sut = make(); + +// // Act +// sut.add_factor_source_to_list(sample(), list()).unwrap(); + +// // Assert +// assert_eq!( +// sut.build().unwrap(), +// RoleWithFactorSourceIds::recovery_with_factors([sample()],) +// ); +// } + +// #[test] +// fn two_of_same_kind_allowed() { +// // TODO: Ask Matt +// // Arrange +// let mut sut = make(); + +// // Act +// sut.add_factor_source_to_list(sample(), list()).unwrap(); +// sut.add_factor_source_to_list(sample_other(), list()) +// .unwrap(); + +// // Assert +// assert_eq!( +// sut.build().unwrap(), +// RoleWithFactorSourceIds::recovery_with_factors([sample(), sample_other()]) +// ); +// } +// } + +// mod arculus_in_isolation { +// use super::*; + +// fn sample() -> FactorSourceID { +// FactorSourceID::sample_arculus() +// } + +// fn sample_other() -> FactorSourceID { +// FactorSourceID::sample_arculus_other() +// } + +// #[test] +// fn allowed_as_first_and_only() { +// // Arrange +// let mut sut = make(); + +// // Act +// sut.add_factor_source_to_list(sample(), list()).unwrap(); + +// // Assert +// assert_eq!( +// sut.build().unwrap(), +// RoleWithFactorSourceIds::recovery_with_factors([sample(),]) +// ); +// } + +// #[test] +// fn two_of_same_kind_allowed() { +// // TODO: Ask Matt +// // Arrange +// let mut sut = make(); + +// // Act +// sut.add_factor_source_to_list(sample(), list()).unwrap(); +// sut.add_factor_source_to_list(sample_other(), list()) +// .unwrap(); + +// // Assert +// assert_eq!( +// sut.build().unwrap(), +// RoleWithFactorSourceIds::recovery_with_factors([sample(), sample_other()]) +// ); +// } +// } + +// mod passphrase_in_isolation { +// use super::*; + +// fn sample() -> FactorSourceID { +// FactorSourceID::sample_passphrase() +// } + +// fn sample_other() -> FactorSourceID { +// FactorSourceID::sample_passphrase_other() +// } + +// #[test] +// fn allowed_as_first_and_only() { +// // Arrange +// let mut sut = make(); + +// // Act +// sut.add_factor_source_to_list(sample(), list()).unwrap(); + +// // Assert +// assert_eq!( +// sut.build().unwrap(), +// RoleWithFactorSourceIds::recovery_with_factors([sample()]) +// ); +// } + +// #[test] +// fn two_of_same_kind_allowed() { +// // TODO: Ask Matt +// // Arrange +// let mut sut = make(); + +// // Act +// sut.add_factor_source_to_list(sample(), list()).unwrap(); +// sut.add_factor_source_to_list(sample_other(), list()) +// .unwrap(); + +// // Assert +// assert_eq!( +// sut.build().unwrap(), +// RoleWithFactorSourceIds::recovery_with_factors([sample(), sample_other()]) +// ); +// } +// } + +// mod trusted_contact_in_isolation { +// use super::*; + +// fn sample() -> FactorSourceID { +// FactorSourceID::sample_trusted_contact() +// } + +// fn sample_other() -> FactorSourceID { +// FactorSourceID::sample_trusted_contact_other() +// } + +// #[test] +// fn allowed_as_first_and_only() { +// // Arrange +// let mut sut = make(); + +// // Act +// sut.add_factor_source_to_list(sample(), list()).unwrap(); + +// // Assert +// assert_eq!( +// sut.build().unwrap(), +// RoleWithFactorSourceIds::recovery_with_factors([sample(),]) +// ); +// } + +// #[test] +// fn two_of_same_kind_allowed() { +// // TODO: Ask Matt +// // Arrange +// let mut sut = make(); + +// // Act +// sut.add_factor_source_to_list(sample(), list()).unwrap(); +// sut.add_factor_source_to_list(sample_other(), list()) +// .unwrap(); + +// // Assert +// assert_eq!( +// sut.build().unwrap(), +// RoleWithFactorSourceIds::recovery_with_factors([sample(), sample_other()]) +// ); +// } +// } + +// mod password_in_isolation { +// use super::*; + +// fn sample() -> FactorSourceID { +// FactorSourceID::sample_password() +// } + +// #[test] +// fn unsupported() { +// // Arrange +// let mut sut = make(); + +// // Act +// let res = sut.add_factor_source_to_list(sample(), list()); + +// // Assert +// assert_eq!( +// res, +// MutRes::forever_invalid(ForeverInvalidReason::RecoveryRolePasswordNotSupported) +// ); +// } + +// #[test] +// fn valid_then_invalid_because_unsupported() { +// // Arrange +// let mut sut = make(); + +// sut.add_factor_source_to_list(FactorSourceID::sample_ledger(), list()) +// .unwrap(); +// sut.add_factor_source_to_list(FactorSourceID::sample_arculus(), list()) +// .unwrap(); + +// // Act +// let res = sut.add_factor_source_to_list(sample(), list()); + +// // Assert +// assert_eq!( +// res, +// MutRes::forever_invalid(ForeverInvalidReason::RecoveryRolePasswordNotSupported) +// ); +// } +// } + +// mod security_questions_in_isolation { +// use super::*; + +// fn sample() -> FactorSourceID { +// FactorSourceID::sample_security_questions() +// } +// fn sample_other() -> FactorSourceID { +// FactorSourceID::sample_security_questions_other() +// } + +// #[test] +// fn unsupported() { +// // Arrange +// let mut sut = make(); + +// // Act +// let res = sut.add_factor_source_to_list(sample(), list()); + +// // Assert +// assert_eq!( +// res, +// MutRes::forever_invalid( +// ForeverInvalidReason::RecoveryRoleSecurityQuestionsNotSupported +// ) +// ); +// } + +// #[test] +// fn valid_then_invalid_because_unsupported() { +// // Arrange +// let mut sut = make(); + +// sut.add_factor_source_to_list(FactorSourceID::sample_ledger(), list()) +// .unwrap(); +// sut.add_factor_source_to_list(FactorSourceID::sample_arculus(), list()) +// .unwrap(); + +// // Act +// let res = sut.add_factor_source_to_list(sample_other(), list()); + +// // Assert +// let reason = ForeverInvalidReason::RecoveryRoleSecurityQuestionsNotSupported; +// let err = MutRes::forever_invalid(reason); +// assert_eq!(res, err); + +// // .. erroneous action above did not change the state of the builder (SUT), +// // so we can build and `sample` is not present in the built result. +// assert_eq!( +// sut.build(), +// Ok(RoleWithFactorSourceIds::recovery_with_factors([ +// FactorSourceID::sample_ledger(), +// FactorSourceID::sample_arculus() +// ])) +// ); +// } +// } +// } + +// #[cfg(test)] +// mod confirmation_in_isolation { + +// use super::*; + +// fn role() -> RoleKind { +// RoleKind::Confirmation +// } + +// fn make() -> SUT { +// SUT::new(role()) +// } + +// fn list() -> FactorListKind { +// FactorListKind::Override +// } + +// fn sample() -> FactorSourceID { +// FactorSourceID::sample_device() +// } + +// #[test] +// fn validation_for_addition_of_factor_source_of_kind_to_list_is_err_for_threshold() { +// let sut = make(); +// let res = sut.validation_for_addition_of_factor_source_of_kind_to_list( +// FactorSourceKind::Device, +// FactorListKind::Threshold, +// ); +// assert_eq!( +// res, +// MutRes::forever_invalid(ForeverInvalidReason::threshold_list_not_supported_for_role( +// role() +// )) +// ); +// } + +// #[test] +// fn validation_for_addition_of_factor_source_of_kind_to_list() { +// use FactorSourceKind::*; +// let sut = make(); +// let not_ok = |kind: FactorSourceKind| { +// let res = sut.validation_for_addition_of_factor_source_of_kind_to_list(kind, list()); +// assert!(res.is_err()); +// }; +// let ok = |kind: FactorSourceKind| { +// let res = sut.validation_for_addition_of_factor_source_of_kind_to_list(kind, list()); +// assert!(res.is_ok()); +// }; +// ok(Device); +// ok(LedgerHQHardwareWallet); +// ok(ArculusCard); +// ok(SecurityQuestions); +// ok(Passphrase); +// ok(OffDeviceMnemonic); +// not_ok(TrustedContact); +// } + +// #[test] +// fn duplicates_not_allowed() { +// test_duplicates_not_allowed(make(), list(), sample()) +// } + +// #[test] +// fn cannot_add_factors_to_threshold() { +// let mut sut = make(); +// let res = sut.add_factor_source_to_list(sample(), FactorListKind::Threshold); +// assert_eq!( +// res, +// Err(Validation::ForeverInvalid( +// ForeverInvalidReason::ConfirmationRoleThresholdFactorsNotSupported +// )) +// ); +// } + +// mod device_in_isolation { +// use super::*; + +// fn sample() -> FactorSourceID { +// FactorSourceID::sample_device() +// } + +// fn sample_other() -> FactorSourceID { +// FactorSourceID::sample_device_other() +// } + +// #[test] +// fn set_threshold_is_unsupported() { +// let mut sut = make(); +// assert_eq!( +// sut.set_threshold(1), +// MutRes::basic_violation(BasicViolation::ConfirmationCannotSetThreshold) +// ); +// } + +// #[test] +// fn allowed_as_first_and_only() { +// // Arrange +// let mut sut = make(); + +// // Act +// sut.add_factor_source_to_list(sample(), list()).unwrap(); + +// // Assert +// assert_eq!( +// sut.build().unwrap(), +// RoleWithFactorSourceIds::confirmation_with_factors([sample()]) +// ); +// } + +// #[test] +// fn two_of_same_kind_allowed() { +// // TODO: Ask Matt +// // Arrange +// let mut sut = make(); + +// // Act +// sut.add_factor_source_to_list(sample(), list()).unwrap(); +// sut.add_factor_source_to_list(sample_other(), list()) +// .unwrap(); + +// // Assert +// let built = sut.build().unwrap(); +// assert!(built.get_threshold_factors().is_empty()); +// assert_eq!( +// built, +// RoleWithFactorSourceIds::confirmation_with_factors([sample(), sample_other()]) +// ); +// } +// } + +// mod ledger_in_isolation { +// use super::*; + +// fn sample() -> FactorSourceID { +// FactorSourceID::sample_ledger() +// } + +// fn sample_other() -> FactorSourceID { +// FactorSourceID::sample_ledger_other() +// } + +// #[test] +// fn allowed_as_first_and_only() { +// // Arrange +// let mut sut = make(); + +// // Act +// sut.add_factor_source_to_list(sample(), list()).unwrap(); + +// // Assert +// assert_eq!( +// sut.build().unwrap(), +// RoleWithFactorSourceIds::confirmation_with_factors([sample(),]) +// ); +// } + +// #[test] +// fn two_of_same_kind_allowed() { +// // TODO: Ask Matt +// // Arrange +// let mut sut = make(); + +// // Act +// sut.add_factor_source_to_list(sample(), list()).unwrap(); +// sut.add_factor_source_to_list(sample_other(), list()) +// .unwrap(); + +// // Assert +// assert_eq!( +// sut.build().unwrap(), +// RoleWithFactorSourceIds::confirmation_with_factors([sample(), sample_other()]) +// ); +// } +// } + +// mod arculus_in_isolation { +// use super::*; + +// fn sample() -> FactorSourceID { +// FactorSourceID::sample_arculus() +// } + +// fn sample_other() -> FactorSourceID { +// FactorSourceID::sample_arculus_other() +// } + +// #[test] +// fn allowed_as_first_and_only() { +// // Arrange +// let mut sut = make(); + +// // Act +// sut.add_factor_source_to_list(sample(), list()).unwrap(); + +// // Assert +// assert_eq!( +// sut.build().unwrap(), +// RoleWithFactorSourceIds::confirmation_with_factors([sample(),]) +// ); +// } + +// #[test] +// fn two_of_same_kind_allowed() { +// // TODO: Ask Matt +// // Arrange +// let mut sut = make(); + +// // Act +// sut.add_factor_source_to_list(sample(), list()).unwrap(); +// sut.add_factor_source_to_list(sample_other(), list()) +// .unwrap(); + +// // Assert +// assert_eq!( +// sut.build().unwrap(), +// RoleWithFactorSourceIds::confirmation_with_factors([sample(), sample_other()]) +// ); +// } +// } + +// mod passphrase_in_isolation { +// use super::*; + +// fn sample() -> FactorSourceID { +// FactorSourceID::sample_passphrase() +// } + +// fn sample_other() -> FactorSourceID { +// FactorSourceID::sample_passphrase_other() +// } + +// #[test] +// fn allowed_as_first_and_only() { +// // Arrange +// let mut sut = make(); + +// // Act +// sut.add_factor_source_to_list(sample(), list()).unwrap(); + +// // Assert +// assert_eq!( +// sut.build().unwrap(), +// RoleWithFactorSourceIds::confirmation_with_factors([sample(),]) +// ); +// } + +// #[test] +// fn two_of_same_kind_allowed() { +// // TODO: Ask Matt +// // Arrange +// let mut sut = make(); + +// // Act +// sut.add_factor_source_to_list(sample(), list()).unwrap(); +// sut.add_factor_source_to_list(sample_other(), list()) +// .unwrap(); + +// // Assert +// assert_eq!( +// sut.build().unwrap(), +// RoleWithFactorSourceIds::confirmation_with_factors([sample(), sample_other()]) +// ); +// } +// } + +// mod trusted_contact_in_isolation { +// use super::*; + +// fn sample() -> FactorSourceID { +// FactorSourceID::sample_trusted_contact() +// } + +// #[test] +// fn unsupported() { +// // Arrange +// let mut sut = make(); + +// // Act +// let res = sut.add_factor_source_to_list(sample(), list()); + +// // Assert +// assert_eq!( +// res, +// MutRes::forever_invalid( +// ForeverInvalidReason::ConfirmationRoleTrustedContactNotSupported +// ) +// ); +// } + +// #[test] +// fn valid_then_invalid_because_unsupported() { +// // Arrange +// let mut sut = make(); + +// sut.add_factor_source_to_list(FactorSourceID::sample_ledger(), list()) +// .unwrap(); +// sut.add_factor_source_to_list(FactorSourceID::sample_arculus(), list()) +// .unwrap(); + +// // Act +// let res = sut.add_factor_source_to_list(sample(), list()); + +// // Assert +// assert_eq!( +// res, +// MutRes::forever_invalid( +// ForeverInvalidReason::ConfirmationRoleTrustedContactNotSupported +// ) +// ); +// } +// } + +// mod password_in_isolation { +// use super::*; + +// fn sample() -> FactorSourceID { +// FactorSourceID::sample_password() +// } + +// fn sample_other() -> FactorSourceID { +// FactorSourceID::sample_password_other() +// } + +// #[test] +// fn allowed_as_first_and_only() { +// // Arrange +// let mut sut = make(); + +// // Act +// sut.add_factor_source_to_list(sample(), list()).unwrap(); + +// // Assert +// assert_eq!( +// sut.build().unwrap(), +// RoleWithFactorSourceIds::confirmation_with_factors([sample(),]) +// ); +// } + +// #[test] +// fn two_of_same_kind_allowed() { +// // TODO: Ask Matt +// // Arrange +// let mut sut = make(); + +// // Act +// sut.add_factor_source_to_list(sample(), list()).unwrap(); +// sut.add_factor_source_to_list(sample_other(), list()) +// .unwrap(); + +// // Assert +// assert_eq!( +// sut.build().unwrap(), +// RoleWithFactorSourceIds::confirmation_with_factors([sample(), sample_other()]) +// ); +// } +// } +// } + +// #[cfg(test)] +// mod primary_in_isolation { + +// use super::*; + +// fn role() -> RoleKind { +// RoleKind::Primary +// } + +// fn make() -> SUT { +// SUT::new(role()) +// } + +// #[cfg(test)] +// mod threshold_suite { +// use super::*; + +// fn sample() -> FactorSourceID { +// FactorSourceID::sample_device() +// } + +// fn sample_other() -> FactorSourceID { +// FactorSourceID::sample_ledger() +// } + +// fn sample_third() -> FactorSourceID { +// FactorSourceID::sample_arculus() +// } + +// fn list() -> FactorListKind { +// FactorListKind::Threshold +// } + +// #[test] +// fn remove_lowers_threshold_from_1_to_0() { +// let mut sut = make(); +// let fs = sample(); +// sut.add_factor_source_to_list(fs, list()).unwrap(); +// sut.set_threshold(1).unwrap(); +// assert_eq!(sut.get_threshold(), 1); +// assert_eq!( +// sut.remove_factor_source(&fs), +// Err(Validation::NotYetValid(RoleMustHaveAtLeastOneFactor)) +// ); +// assert_eq!(sut.get_threshold(), 0); +// } + +// #[test] +// fn remove_lowers_threshold_from_3_to_1() { +// let mut sut = make(); +// let fs0 = sample(); +// let fs1 = sample_other(); +// sut.add_factor_source_to_list(fs0, list()).unwrap(); +// sut.add_factor_source_to_list(fs1, list()).unwrap(); +// sut.add_factor_source_to_list(FactorSourceID::sample_arculus_other(), list()) +// .unwrap(); +// sut.set_threshold(3).unwrap(); +// assert_eq!(sut.get_threshold(), 3); +// sut.remove_factor_source(&fs0).unwrap(); +// sut.remove_factor_source(&fs1).unwrap(); +// assert_eq!(sut.get_threshold(), 1); +// } + +// #[test] +// fn remove_from_override_does_not_change_threshold() { +// let mut sut = make(); +// sut.add_factor_source_to_list(sample(), list()).unwrap(); +// sut.add_factor_source_to_list(sample_other(), list()) +// .unwrap(); +// let fs = FactorSourceID::sample_arculus_other(); +// sut.add_factor_source_to_list(fs, FactorListKind::Override) +// .unwrap(); +// sut.set_threshold(2).unwrap(); +// assert_eq!(sut.get_threshold(), 2); +// sut.remove_factor_source(&fs).unwrap(); +// assert_eq!(sut.get_threshold(), 2); + +// let built = sut.build().unwrap(); +// assert_eq!(built.get_threshold(), 2); + +// assert_eq!(built.role(), RoleKind::Primary); + +// assert_eq!( +// built.get_threshold_factors(), +// &vec![sample(), sample_other()] +// ); + +// assert_eq!(built.get_override_factors(), &Vec::new()); +// } + +// #[test] +// fn one_factor_then_set_threshold_to_one_is_ok() { +// // Arrange +// let mut sut = make(); + +// // Act +// sut.add_factor_source_to_list(sample_other(), list()) +// .unwrap(); +// sut.set_threshold(1).unwrap(); + +// // Assert +// let expected = RoleWithFactorSourceIds::primary_with_factors(1, [sample_other()], []); +// assert_eq!(sut.build().unwrap(), expected); +// } + +// #[test] +// fn zero_factor_then_set_threshold_to_one_is_not_yet_valid_then_add_one_factor_is_ok() { +// // Arrange +// let mut sut = make(); + +// // Act +// assert_eq!( +// sut.set_threshold(1), +// Err(Validation::NotYetValid( +// ThresholdHigherThanThresholdFactorsLen +// )) +// ); +// sut.add_factor_source_to_list(sample_other(), list()) +// .unwrap(); + +// // Assert +// let expected = RoleWithFactorSourceIds::primary_with_factors(1, [sample_other()], []); +// assert_eq!(sut.build().unwrap(), expected); +// } + +// #[test] +// fn zero_factor_then_set_threshold_to_two_is_not_yet_valid_then_add_two_factor_is_ok() { +// // Arrange +// let mut sut = make(); + +// // Act +// assert_eq!( +// sut.set_threshold(2), +// Err(Validation::NotYetValid( +// ThresholdHigherThanThresholdFactorsLen +// )) +// ); +// sut.add_factor_source_to_list(sample(), list()).unwrap(); + +// sut.add_factor_source_to_list(sample_other(), list()) +// .unwrap(); + +// // Assert +// let expected = +// RoleWithFactorSourceIds::primary_with_factors(2, [sample(), sample_other()], []); +// assert_eq!(sut.build().unwrap(), expected); +// } + +// #[test] +// fn add_two_factors_then_set_threshold_to_two_is_ok() { +// // Arrange +// let mut sut = make(); + +// sut.add_factor_source_to_list(sample(), list()).unwrap(); +// sut.add_factor_source_to_list(sample_other(), list()) +// .unwrap(); + +// // Act +// assert_eq!(sut.set_threshold(2), Ok(())); + +// // Assert +// let expected = +// RoleWithFactorSourceIds::primary_with_factors(2, [sample(), sample_other()], []); +// assert_eq!(sut.build().unwrap(), expected); +// } + +// #[test] +// fn add_two_factors_then_set_threshold_to_three_is_not_yet_valid_then_add_third_factor_is_ok( +// ) { +// // Arrange +// let mut sut = make(); + +// sut.add_factor_source_to_list(sample(), list()).unwrap(); +// sut.add_factor_source_to_list(sample_other(), list()) +// .unwrap(); + +// // Act +// assert_eq!( +// sut.set_threshold(3), +// Err(Validation::NotYetValid( +// ThresholdHigherThanThresholdFactorsLen +// )) +// ); + +// sut.add_factor_source_to_list(sample_third(), list()) +// .unwrap(); + +// // Assert +// let expected = RoleWithFactorSourceIds::primary_with_factors( +// 3, +// [sample(), sample_other(), sample_third()], +// [], +// ); +// assert_eq!(sut.build().unwrap(), expected); +// } + +// #[test] +// fn one_factors_set_threshold_of_one_is_ok() { +// // Arrange +// let mut sut = make(); + +// // Act +// sut.add_factor_source_to_list(sample_other(), list()) +// .unwrap(); +// sut.set_threshold(1).unwrap(); + +// // Assert +// let expected = RoleWithFactorSourceIds::primary_with_factors(1, [sample_other()], []); +// assert_eq!(sut.build().unwrap(), expected); +// } + +// #[test] +// fn one_override_factors_set_threshold_to_one_is_not_yet_valid() { +// // Arrange +// let mut sut = make(); + +// // Act +// sut.add_factor_source_to_list(sample_other(), FactorListKind::Override) +// .unwrap(); +// assert_eq!( +// sut.set_threshold(1), +// Err(Validation::NotYetValid( +// ThresholdHigherThanThresholdFactorsLen +// )) +// ); + +// // Assert + +// assert_eq!( +// sut.build(), +// Err(Validation::NotYetValid( +// ThresholdHigherThanThresholdFactorsLen +// )) +// ); +// } + +// #[test] +// fn validation_for_addition_of_factor_source_for_each_before_after_adding_a_factor() { +// let mut sut = make(); +// let fs0 = FactorSourceID::sample_ledger(); +// let fs1 = FactorSourceID::sample_password(); +// let fs2 = FactorSourceID::sample_arculus(); +// let xs = sut.validation_for_addition_of_factor_source_for_each( +// list(), +// &IndexSet::from_iter([fs0, fs1, fs2]), +// ); +// assert_eq!( +// xs.into_iter().collect::>(), +// vec![ +// FactorSourceInRoleBuilderValidationStatus::ok( +// RoleKind::Primary, +// fs0, +// ), +// FactorSourceInRoleBuilderValidationStatus::not_yet_valid( +// RoleKind::Primary, +// fs1, +// NotYetValidReason::PrimaryRoleWithPasswordInThresholdListMustHaveAnotherFactor +// ), +// FactorSourceInRoleBuilderValidationStatus::ok( +// RoleKind::Primary, +// fs2, +// ), +// ] +// ); +// _ = sut.add_factor_source_to_list(fs0, list()); +// _ = sut.set_threshold(2); + +// let xs = sut.validation_for_addition_of_factor_source_for_each( +// list(), +// &IndexSet::from_iter([fs0, fs1, fs2]), +// ); +// assert_eq!( +// xs.into_iter().collect::>(), +// vec![ +// FactorSourceInRoleBuilderValidationStatus::forever_invalid( +// RoleKind::Primary, +// fs0, +// ForeverInvalidReason::FactorSourceAlreadyPresent +// ), +// FactorSourceInRoleBuilderValidationStatus::ok(RoleKind::Primary, fs1,), +// FactorSourceInRoleBuilderValidationStatus::ok(RoleKind::Primary, fs2,), +// ] +// ); +// } +// } + +// #[cfg(test)] +// mod password { +// use super::*; + +// fn sample() -> FactorSourceID { +// FactorSourceID::sample_password() +// } + +// fn sample_other() -> FactorSourceID { +// FactorSourceID::sample_password_other() +// } + +// #[test] +// fn test_suite_prerequisite() { +// assert_eq!(sample(), sample()); +// assert_eq!(sample_other(), sample_other()); +// assert_ne!(sample(), sample_other()); +// } + +// mod threshold_in_isolation { +// use super::*; + +// fn list() -> FactorListKind { +// FactorListKind::Threshold +// } + +// #[test] +// fn duplicates_not_allowed() { +// let mut sut = make(); +// sut.add_factor_source_to_list( +// FactorSourceID::sample_device(), +// FactorListKind::Threshold, +// ) +// .unwrap(); +// _ = sut.set_threshold(2); +// test_duplicates_not_allowed(sut, list(), sample()); +// } + +// #[test] +// fn alone_is_not_ok() { +// // Arrange +// let mut sut = make(); + +// // Act +// let res = sut.add_factor_source_to_list(sample(), list()); + +// // Assert +// assert_eq!( +// res, +// MutRes::not_yet_valid( +// NotYetValidReason::PrimaryRoleWithPasswordInThresholdListMustHaveAnotherFactor +// ) +// ); +// } + +// #[test] +// fn validation_for_addition_of_factor_source_of_kind_to_list() { +// use FactorSourceKind::*; + +// let not_ok = |kind: FactorSourceKind| { +// let sut = make(); +// let res = +// sut.validation_for_addition_of_factor_source_of_kind_to_list(kind, list()); +// assert!(res.is_err()); +// }; + +// let ok_with = |kind: FactorSourceKind, setup: fn(&mut SUT)| { +// let mut sut = make(); +// setup(&mut sut); +// let res = +// sut.validation_for_addition_of_factor_source_of_kind_to_list(kind, list()); +// assert!(res.is_ok()); +// }; +// let ok = |kind: FactorSourceKind| { +// ok_with(kind, |_| {}); +// }; + +// ok(LedgerHQHardwareWallet); +// ok(ArculusCard); +// ok(OffDeviceMnemonic); + +// ok_with(Device, |sut| { +// sut.add_factor_source_to_list(FactorSourceID::sample_ledger(), list()) +// .unwrap(); +// }); +// ok_with(Passphrase, |sut| { +// sut.add_factor_source_to_list(FactorSourceID::sample_device(), list()) +// .unwrap(); +// _ = sut.set_threshold(2); +// }); + +// not_ok(SecurityQuestions); +// not_ok(TrustedContact); +// } +// } + +// mod override_in_isolation { +// use super::*; + +// fn list() -> FactorListKind { +// FactorListKind::Override +// } + +// #[test] +// fn unsupported() { +// // Arrange +// let mut sut = make(); + +// // Act +// let res = sut.add_factor_source_to_list(sample(), list()); + +// // Assert +// assert_eq!( +// res, +// MutRes::forever_invalid( +// ForeverInvalidReason::PrimaryCannotHavePasswordInOverrideList +// ) +// ); +// } + +// #[test] +// fn valid_then_invalid_because_unsupported() { +// // Arrange +// let mut sut = make(); +// sut.add_factor_source_to_list( +// FactorSourceID::sample_device(), +// FactorListKind::Threshold, +// ) +// .unwrap(); +// sut.add_factor_source_to_list(FactorSourceID::sample_ledger(), list()) +// .unwrap(); +// sut.add_factor_source_to_list(FactorSourceID::sample_arculus(), list()) +// .unwrap(); + +// // Act +// let res = sut.add_factor_source_to_list(sample(), list()); + +// // Assert +// assert_eq!( +// res, +// MutRes::forever_invalid( +// ForeverInvalidReason::PrimaryCannotHavePasswordInOverrideList +// ) +// ); +// } + +// #[test] +// fn validation_for_addition_of_factor_source_of_kind_to_list() { +// use FactorSourceKind::*; + +// let not_ok = |kind: FactorSourceKind| { +// let sut = make(); +// let res = +// sut.validation_for_addition_of_factor_source_of_kind_to_list(kind, list()); +// assert!(res.is_err()); +// }; + +// let ok_with = |kind: FactorSourceKind, setup: fn(&mut SUT)| { +// let mut sut = make(); +// setup(&mut sut); +// let res = +// sut.validation_for_addition_of_factor_source_of_kind_to_list(kind, list()); +// assert!(res.is_ok()); +// }; +// let ok = |kind: FactorSourceKind| { +// ok_with(kind, |_| {}); +// }; + +// ok(LedgerHQHardwareWallet); +// ok(ArculusCard); +// ok(OffDeviceMnemonic); + +// ok_with(Device, |sut| { +// sut.add_factor_source_to_list(FactorSourceID::sample_ledger(), list()) +// .unwrap(); +// }); + +// not_ok(Passphrase); + +// not_ok(SecurityQuestions); +// not_ok(TrustedContact); +// } +// } +// } + +// #[cfg(test)] +// mod ledger { +// use super::*; + +// fn sample() -> FactorSourceID { +// FactorSourceID::sample_ledger() +// } + +// fn sample_other() -> FactorSourceID { +// FactorSourceID::sample_ledger_other() +// } + +// #[test] +// fn test_suite_prerequisite() { +// assert_eq!(sample(), sample()); +// assert_eq!(sample_other(), sample_other()); +// assert_ne!(sample(), sample_other()); +// } + +// mod threshold_in_isolation { +// use super::*; +// fn list() -> FactorListKind { +// FactorListKind::Threshold +// } + +// #[test] +// fn duplicates_not_allowed() { +// test_duplicates_not_allowed(make(), list(), sample()); +// } + +// #[test] +// fn one_is_ok() { +// // Arrange +// let mut sut = make(); + +// // Act +// sut.add_factor_source_to_list(sample(), list()).unwrap(); +// sut.set_threshold(1).unwrap(); + +// // Assert +// let expected = RoleWithFactorSourceIds::primary_with_factors(1, [sample()], []); +// assert_eq!(sut.build().unwrap(), expected); +// } + +// #[test] +// fn one_with_threshold_of_zero_is_err() { +// // Arrange +// let mut sut = make(); + +// // Act +// sut.add_factor_source_to_list(sample(), list()).unwrap(); + +// // Assert +// assert_eq!( +// sut.build(), +// RoleBuilderBuildResult::Err(RoleBuilderValidation::NotYetValid( +// NotYetValidReason::PrimaryRoleWithThresholdCannotBeZeroWithFactors +// )) +// ); +// } + +// #[test] +// fn two_different_is_ok() { +// // Arrange +// let mut sut = make(); + +// // Act +// sut.add_factor_source_to_list(sample(), list()).unwrap(); +// sut.add_factor_source_to_list(sample_other(), list()) +// .unwrap(); +// sut.set_threshold(2).unwrap(); + +// // Assert +// let expected = RoleWithFactorSourceIds::primary_with_factors( +// 2, +// [sample(), sample_other()], +// [], +// ); +// assert_eq!(sut.build().unwrap(), expected); +// } +// } + +// mod override_in_isolation { +// use super::*; +// fn list() -> FactorListKind { +// FactorListKind::Override +// } + +// #[test] +// fn duplicates_not_allowed() { +// test_duplicates_not_allowed(make(), list(), sample()); +// } + +// #[test] +// fn one_is_ok() { +// // Arrange +// let mut sut = make(); + +// // Act +// sut.add_factor_source_to_list(sample(), list()).unwrap(); + +// // Assert +// let expected = RoleWithFactorSourceIds::primary_with_factors(0, [], [sample()]); +// assert_eq!(sut.build().unwrap(), expected); +// } + +// #[test] +// fn two_different_is_ok() { +// // Arrange +// let mut sut = make(); + +// // Act +// sut.add_factor_source_to_list(sample(), list()).unwrap(); +// sut.add_factor_source_to_list(sample_other(), list()) +// .unwrap(); + +// // Assert +// let expected = RoleWithFactorSourceIds::primary_with_factors( +// 0, +// [], +// [sample(), sample_other()], +// ); +// assert_eq!(sut.build().unwrap(), expected); +// } +// } +// } + +// #[cfg(test)] +// mod arculus { +// use super::*; + +// fn sample() -> FactorSourceID { +// FactorSourceID::sample_arculus() +// } + +// fn sample_other() -> FactorSourceID { +// FactorSourceID::sample_arculus_other() +// } + +// #[test] +// fn test_suite_prerequisite() { +// assert_eq!(sample(), sample()); +// assert_eq!(sample_other(), sample_other()); +// assert_ne!(sample(), sample_other()); +// } + +// mod threshold_in_isolation { +// use super::*; +// fn list() -> FactorListKind { +// FactorListKind::Threshold +// } + +// #[test] +// fn duplicates_not_allowed() { +// test_duplicates_not_allowed(make(), list(), sample()); +// } + +// #[test] +// fn one_is_ok() { +// // Arrange +// let mut sut = make(); + +// // Act +// sut.add_factor_source_to_list(sample(), list()).unwrap(); +// sut.set_threshold(1).unwrap(); + +// // Assert +// let expected = RoleWithFactorSourceIds::primary_with_factors(1, [sample()], []); +// assert_eq!(sut.build().unwrap(), expected); +// } + +// #[test] +// fn two_different_is_ok() { +// // Arrange +// let mut sut = make(); + +// // Act +// sut.add_factor_source_to_list(sample(), list()).unwrap(); +// sut.add_factor_source_to_list(sample_other(), list()) +// .unwrap(); +// sut.set_threshold(1).unwrap(); + +// // Assert +// let expected = RoleWithFactorSourceIds::primary_with_factors( +// 1, +// [sample(), sample_other()], +// [], +// ); +// assert_eq!(sut.build().unwrap(), expected); +// } +// } + +// mod override_in_isolation { +// use super::*; +// fn list() -> FactorListKind { +// FactorListKind::Override +// } + +// #[test] +// fn duplicates_not_allowed() { +// test_duplicates_not_allowed(make(), list(), sample()); +// } + +// #[test] +// fn one_is_ok() { +// // Arrange +// let mut sut = make(); + +// // Act +// sut.add_factor_source_to_list(sample(), list()).unwrap(); + +// // Assert +// let expected = RoleWithFactorSourceIds::primary_with_factors(0, [], [sample()]); +// assert_eq!(sut.build().unwrap(), expected); +// } + +// #[test] +// fn two_different_is_ok() { +// // Arrange +// let mut sut = make(); + +// // Act +// sut.add_factor_source_to_list(sample(), list()).unwrap(); +// sut.add_factor_source_to_list(sample_other(), list()) +// .unwrap(); + +// // Assert +// let expected = RoleWithFactorSourceIds::primary_with_factors( +// 0, +// [], +// [sample(), sample_other()], +// ); +// assert_eq!(sut.build().unwrap(), expected); +// } +// } +// } + +// #[cfg(test)] +// mod device_factor_source { +// use super::*; + +// fn sample() -> FactorSourceID { +// FactorSourceID::sample_device() +// } + +// fn sample_other() -> FactorSourceID { +// FactorSourceID::sample_device_other() +// } + +// #[test] +// fn test_suite_prerequisite() { +// assert_eq!(sample(), sample()); +// assert_eq!(sample_other(), sample_other()); +// assert_ne!(sample(), sample_other()); +// } + +// #[cfg(test)] +// mod threshold_in_isolation { +// use super::*; + +// fn list() -> FactorListKind { +// FactorListKind::Threshold +// } + +// #[test] +// fn duplicates_not_allowed() { +// test_duplicates_not_allowed(make(), list(), sample()) +// } + +// #[test] +// fn one_is_ok() { +// // Arrange +// let mut sut = make(); + +// // Act +// sut.add_factor_source_to_list(sample(), list()).unwrap(); +// sut.set_threshold(1).unwrap(); + +// // Assert +// let expected = RoleWithFactorSourceIds::primary_with_factors(1, [sample()], []); +// assert_eq!(sut.build().unwrap(), expected); +// } + +// #[test] +// fn two_different_is_err() { +// // Arrange +// let mut sut = make(); + +// sut.add_factor_source_to_list(sample(), list()).unwrap(); + +// // Act +// let res = sut.add_factor_source_to_list(sample_other(), list()); + +// // Assert +// assert!(matches!( +// res, +// MutRes::Err(Validation::ForeverInvalid( +// ForeverInvalidReason::PrimaryCannotHaveMultipleDevices +// )) +// )); +// } +// } + +// mod override_in_isolation { + +// use super::*; + +// fn list() -> FactorListKind { +// FactorListKind::Override +// } + +// #[test] +// fn duplicates_not_allowed() { +// test_duplicates_not_allowed(make(), list(), sample()) +// } + +// #[test] +// fn one_is_ok() { +// // Arrange +// let mut sut = make(); + +// // Act +// sut.add_factor_source_to_list(sample(), list()).unwrap(); + +// // Assert +// let expected = RoleWithFactorSourceIds::primary_with_factors(0, [], [sample()]); +// assert_eq!(sut.build().unwrap(), expected); +// } +// } +// } +// } diff --git a/crates/rules/src/roles/role_with_factor_instances.rs b/crates/rules/src/roles/role_with_factor_instances.rs index d419879c..5300b934 100644 --- a/crates/rules/src/roles/role_with_factor_instances.rs +++ b/crates/rules/src/roles/role_with_factor_instances.rs @@ -1,24 +1,41 @@ use crate::prelude::*; -pub(crate) type RoleWithFactorInstances = AbstractBuiltRoleWithFactor; +pub(crate) type RoleWithFactorInstances = + AbstractBuiltRoleWithFactor; -impl RoleWithFactorInstances { +impl RoleWithFactorSources { + fn from(other: &RoleWithFactorSources) -> Self { + Self::with_factors( + other.threshold(), + other.get_threshold_factors().clone(), + other.get_override_factors().clone(), + ) + } +} + +impl MatrixOfFactorSources { + pub(crate) fn get_role(&self) -> RoleWithFactorSources { + match R { + ROLE_PRIMARY => RoleWithFactorSources::from(&self.primary_role), + ROLE_RECOVERY => RoleWithFactorSources::from(&self.recovery_role), + ROLE_CONFIRMATION => RoleWithFactorSources::from(&self.confirmation_role), + _ => panic!("unknown"), + } + } +} + +impl RoleWithFactorInstances { // TODO: MFA - Upgrade this method to follow the rules of when a factor instance might // be used by MULTIPLE roles. This is a temporary solution to get the tests to pass. // A proper solution should use follow the rules laid out in: // https://radixdlt.atlassian.net/wiki/spaces/AT/pages/3758063620/MFA+Rules+for+Factors+and+Security+Shields pub(crate) fn fulfilling_role_of_factor_sources_with_factor_instances( - role_kind: RoleKind, consuming_instances: &IndexMap, matrix_of_factor_sources: &MatrixOfFactorSources, ) -> Result { - let role_of_sources = { - match role_kind { - RoleKind::Primary => &matrix_of_factor_sources.primary_role, - RoleKind::Recovery => &matrix_of_factor_sources.recovery_role, - RoleKind::Confirmation => &matrix_of_factor_sources.confirmation_role, - } - }; + let role_kind = RoleKind::from_u8(R).unwrap(); + + let role_of_sources = matrix_of_factor_sources.get_role::(); assert_eq!(role_of_sources.role(), role_kind); let threshold: u8 = role_of_sources.get_threshold(); @@ -37,7 +54,7 @@ impl RoleWithFactorInstances { )?; let role_with_instances = - Self::with_factors(role_kind, threshold, threshold_factors, override_factors); + Self::with_factors(threshold, threshold_factors, override_factors); assert_eq!(role_with_instances.role(), role_kind); Ok(role_with_instances) @@ -63,10 +80,17 @@ impl RoleWithFactorInstances { } } -impl RoleWithFactorInstances { +pub(crate) type PrimaryRoleWithFactorInstances = RoleWithFactorInstances<{ ROLE_PRIMARY }>; +pub(crate) type RecoveryRoleWithFactorInstances = RoleWithFactorInstances<{ ROLE_RECOVERY }>; +pub(crate) type ConfirmationRoleWithFactorInstances = + RoleWithFactorInstances<{ ROLE_CONFIRMATION }>; + +impl PrimaryRoleWithFactorInstances { // TODO: MFA Rules change this, this might not be compatible with the rules! pub fn sample_primary() -> Self { - Self::with_factors(RoleKind::Primary, 1, [ + Self::with_factors( + // RoleKind::Primary, + 1, [ HierarchicalDeterministicFactorInstance::sample_mainnet_account_device_factor_fs_0_securified_at_index(0).into() ], [ HierarchicalDeterministicFactorInstance::sample_mainnet_account_device_factor_fs_10_securified_at_index(0).into() @@ -76,17 +100,19 @@ impl RoleWithFactorInstances { // TODO: MFA Rules change this, this might not be compatible with the rules! pub fn sample_primary_other() -> Self { Self::with_factors( - RoleKind::Primary, + // RoleKind::Primary, 1, [HierarchicalDeterministicFactorInstance::sample_mainnet_account_device_factor_fs_0_securified_at_index(10).into(),], [HierarchicalDeterministicFactorInstance::sample_mainnet_account_device_factor_fs_10_securified_at_index(60).into()], ) } +} +impl RecoveryRoleWithFactorInstances { // TODO: MFA Rules change this, this might not be compatible with the rules! pub fn sample_recovery() -> Self { Self::with_factors( - RoleKind::Recovery, + // RoleKind::Recovery, 0,[], [HierarchicalDeterministicFactorInstance::sample_mainnet_account_device_factor_fs_10_securified_at_index(237).into()] ) } @@ -94,15 +120,17 @@ impl RoleWithFactorInstances { // TODO: MFA Rules change this, this might not be compatible with the rules! pub fn sample_recovery_other() -> Self { Self::with_factors( - RoleKind::Recovery, + // RoleKind::Recovery, 0,[], [HierarchicalDeterministicFactorInstance::sample_mainnet_account_device_factor_fs_10_securified_at_index(42).into()] ) } +} +impl ConfirmationRoleWithFactorInstances { // TODO: MFA Rules change this, this might not be compatible with the rules! pub fn sample_confirmation() -> Self { Self::with_factors( - RoleKind::Confirmation, + // RoleKind::Confirmation, 0,[], [HierarchicalDeterministicFactorInstance::sample_mainnet_account_device_factor_fs_0_securified_at_index(1).into(), HierarchicalDeterministicFactorInstance::sample_mainnet_account_device_factor_fs_10_securified_at_index(2).into()] ) } @@ -110,19 +138,20 @@ impl RoleWithFactorInstances { // TODO: MFA Rules change this, this might not be compatible with the rules! pub fn sample_confirmation_other() -> Self { Self::with_factors( - RoleKind::Confirmation, + // RoleKind::Confirmation, 0,[], [HierarchicalDeterministicFactorInstance::sample_mainnet_account_device_factor_fs_0_securified_at_index(10).into(), HierarchicalDeterministicFactorInstance::sample_mainnet_account_device_factor_fs_10_securified_at_index(20).into()] ) } } +/* -impl HasSampleValues for RoleWithFactorInstances { +impl HasSampleValues for PrimaryRoleWithFactorInstances { fn sample() -> Self { Self::sample_primary() } fn sample_other() -> Self { - Self::sample_recovery() + Self::sample_primary_other() } } @@ -252,3 +281,4 @@ mod tests { ); } } +*/ diff --git a/crates/rules/src/roles/roles_with_factor_ids.rs b/crates/rules/src/roles/roles_with_factor_ids.rs index e137dc13..b7cd00ae 100644 --- a/crates/rules/src/roles/roles_with_factor_ids.rs +++ b/crates/rules/src/roles/roles_with_factor_ids.rs @@ -1,11 +1,15 @@ use crate::prelude::*; -pub type RoleWithFactorSourceIds = AbstractBuiltRoleWithFactor; +pub type RoleWithFactorSourceIds = AbstractBuiltRoleWithFactor; -impl RoleWithFactorSourceIds { +pub type PrimaryRoleWithFactorSourceIds = RoleWithFactorSourceIds<{ ROLE_PRIMARY }>; +pub type RecoveryRoleWithFactorSourceIds = RoleWithFactorSourceIds<{ ROLE_RECOVERY }>; +pub type ConfirmationRoleWithFactorSourceIds = RoleWithFactorSourceIds<{ ROLE_CONFIRMATION }>; + +impl PrimaryRoleWithFactorSourceIds { /// Config MFA 1.1 pub fn sample_primary() -> Self { - let mut builder = RoleBuilder::primary(); + let mut builder = RoleBuilder::new(); builder .add_factor_source_to_list(FactorSourceID::sample_device(), FactorListKind::Threshold) .unwrap(); @@ -16,10 +20,12 @@ impl RoleWithFactorSourceIds { builder.set_threshold(2).unwrap(); builder.build().unwrap() } +} +impl RecoveryRoleWithFactorSourceIds { /// Config MFA 1.1 pub fn sample_recovery() -> Self { - let mut builder = RoleBuilder::recovery(); + let mut builder = RoleBuilder::new(); builder .add_factor_source_to_list(FactorSourceID::sample_device(), FactorListKind::Override) .unwrap(); @@ -31,13 +37,22 @@ impl RoleWithFactorSourceIds { } } -impl HasSampleValues for RoleWithFactorSourceIds { +impl HasSampleValues for PrimaryRoleWithFactorSourceIds { fn sample() -> Self { Self::sample_primary() } fn sample_other() -> Self { - Self::sample_recovery() + let mut builder = RoleBuilder::new(); + builder + .add_factor_source_to_list(FactorSourceID::sample_device(), FactorListKind::Threshold) + .unwrap(); + + builder + .add_factor_source_to_list(FactorSourceID::sample_ledger(), FactorListKind::Threshold) + .unwrap(); + builder.set_threshold(1).unwrap(); + builder.build().unwrap() } } @@ -47,7 +62,7 @@ mod tests { use super::*; #[allow(clippy::upper_case_acronyms)] - type SUT = RoleWithFactorSourceIds; + type SUT = PrimaryRoleWithFactorSourceIds; #[test] fn equality() { @@ -83,7 +98,6 @@ mod tests { &sut, r#" { - "role": "primary", "threshold": 2, "threshold_factors": [ { @@ -106,35 +120,36 @@ mod tests { "#, ); } - - #[test] - fn assert_json_sample_recovery() { - let sut = SUT::sample_recovery(); - assert_eq_after_json_roundtrip( - &sut, - r#" - { - "role": "recovery", - "threshold": 0, - "threshold_factors": [], - "override_factors": [ - { - "discriminator": "fromHash", - "fromHash": { - "kind": "device", - "body": "f1a93d324dd0f2bff89963ab81ed6e0c2ee7e18c0827dc1d3576b2d9f26bbd0a" - } - }, - { - "discriminator": "fromHash", - "fromHash": { - "kind": "ledgerHQHardwareWallet", - "body": "ab59987eedd181fe98e512c1ba0f5ff059f11b5c7c56f15614dcc9fe03fec58b" - } - } - ] - } - "#, - ); - } } + +// #[test] +// fn assert_json_sample_recovery() { +// let sut = SUT::sample_recovery(); +// assert_eq_after_json_roundtrip( +// &sut, +// r#" +// { +// "role": "recovery", +// "threshold": 0, +// "threshold_factors": [], +// "override_factors": [ +// { +// "discriminator": "fromHash", +// "fromHash": { +// "kind": "device", +// "body": "f1a93d324dd0f2bff89963ab81ed6e0c2ee7e18c0827dc1d3576b2d9f26bbd0a" +// } +// }, +// { +// "discriminator": "fromHash", +// "fromHash": { +// "kind": "ledgerHQHardwareWallet", +// "body": "ab59987eedd181fe98e512c1ba0f5ff059f11b5c7c56f15614dcc9fe03fec58b" +// } +// } +// ] +// } +// "#, +// ); +// } +// } diff --git a/crates/rules/src/roles/roles_with_factor_sources.rs b/crates/rules/src/roles/roles_with_factor_sources.rs index 98ffcc30..101f6c0d 100644 --- a/crates/rules/src/roles/roles_with_factor_sources.rs +++ b/crates/rules/src/roles/roles_with_factor_sources.rs @@ -1,10 +1,10 @@ use crate::prelude::*; -pub(crate) type RoleWithFactorSources = AbstractBuiltRoleWithFactor; +pub(crate) type RoleWithFactorSources = AbstractBuiltRoleWithFactor; -impl RoleWithFactorSources { +impl RoleWithFactorSources { pub fn new( - role_with_factor_source_ids: RoleWithFactorSourceIds, + role_with_factor_source_ids: RoleWithFactorSourceIds, factor_sources: &FactorSources, ) -> Result { let lookup_f = |id: &FactorSourceID| -> Result { @@ -24,7 +24,6 @@ impl RoleWithFactorSources { let override_factors = lookup(role_with_factor_source_ids.get_override_factors())?; Ok(Self::with_factors( - role_with_factor_source_ids.role(), role_with_factor_source_ids.get_threshold(), threshold_factors, override_factors, @@ -32,35 +31,35 @@ impl RoleWithFactorSources { } } -impl HasSampleValues for RoleWithFactorSources { - fn sample() -> Self { - let ids = RoleWithFactorSourceIds::sample(); - let factor_sources = FactorSources::sample_values_all(); - Self::new(ids, &factor_sources).unwrap() - } +// impl HasSampleValues for RoleWithFactorSources { +// fn sample() -> Self { +// let ids = RoleWithFactorSourceIds::sample(); +// let factor_sources = FactorSources::sample_values_all(); +// Self::new(ids, &factor_sources).unwrap() +// } - fn sample_other() -> Self { - let ids = RoleWithFactorSourceIds::sample_other(); - let factor_sources = FactorSources::sample_values_all(); - Self::new(ids, &factor_sources).unwrap() - } -} +// fn sample_other() -> Self { +// let ids = RoleWithFactorSourceIds::sample_other(); +// let factor_sources = FactorSources::sample_values_all(); +// Self::new(ids, &factor_sources).unwrap() +// } +// } -#[cfg(test)] -mod tests { - use super::*; +// #[cfg(test)] +// mod tests { +// use super::*; - #[allow(clippy::upper_case_acronyms)] - type SUT = RoleWithFactorSources; +// #[allow(clippy::upper_case_acronyms)] +// type SUT = RoleWithFactorSources; - #[test] - fn equality() { - assert_eq!(SUT::sample(), SUT::sample()); - assert_eq!(SUT::sample_other(), SUT::sample_other()); - } +// #[test] +// fn equality() { +// assert_eq!(SUT::sample(), SUT::sample()); +// assert_eq!(SUT::sample_other(), SUT::sample_other()); +// } - #[test] - fn inequality() { - assert_ne!(SUT::sample(), SUT::sample_other()); - } -} +// #[test] +// fn inequality() { +// assert_ne!(SUT::sample(), SUT::sample_other()); +// } +// } From 988580e9d20a6a3fdf342ab569cc2175636c1060 Mon Sep 17 00:00:00 2001 From: Alexander Cyon Date: Thu, 28 Nov 2024 16:54:23 +0100 Subject: [PATCH 11/33] fix tests --- .../security_structure_of_factor_source_ids.rs | 6 ------ 1 file changed, 6 deletions(-) diff --git a/crates/rules/src/security_structure_of_factors/security_structure_of_factor_source_ids.rs b/crates/rules/src/security_structure_of_factors/security_structure_of_factor_source_ids.rs index 68d09fa2..f1daa31b 100644 --- a/crates/rules/src/security_structure_of_factors/security_structure_of_factor_source_ids.rs +++ b/crates/rules/src/security_structure_of_factors/security_structure_of_factor_source_ids.rs @@ -46,7 +46,6 @@ mod tests { }, "matrix_of_factors": { "primary_role": { - "role": "primary", "threshold": 2, "threshold_factors": [ { @@ -67,7 +66,6 @@ mod tests { "override_factors": [] }, "recovery_role": { - "role": "recovery", "threshold": 0, "threshold_factors": [], "override_factors": [ @@ -88,7 +86,6 @@ mod tests { ] }, "confirmation_role": { - "role": "confirmation", "threshold": 0, "threshold_factors": [], "override_factors": [ @@ -123,7 +120,6 @@ mod tests { }, "matrix_of_factors": { "primary_role": { - "role": "primary", "threshold": 2, "threshold_factors": [ { @@ -144,7 +140,6 @@ mod tests { "override_factors": [] }, "recovery_role": { - "role": "recovery", "threshold": 0, "threshold_factors": [], "override_factors": [ @@ -165,7 +160,6 @@ mod tests { ] }, "confirmation_role": { - "role": "confirmation", "threshold": 0, "threshold_factors": [], "override_factors": [ From 33432b97c86a1ade173c97cbbdc140a1bb9d403d Mon Sep 17 00:00:00 2001 From: Alexander Cyon Date: Thu, 28 Nov 2024 17:02:32 +0100 Subject: [PATCH 12/33] fix tests --- .../roles/abstract_role_builder_or_built.rs | 7 +- .../rules/src/roles/builder/roles_builder.rs | 2 + .../roles/builder/roles_builder_unit_tests.rs | 3685 +++++++++-------- 3 files changed, 1857 insertions(+), 1837 deletions(-) diff --git a/crates/rules/src/roles/abstract_role_builder_or_built.rs b/crates/rules/src/roles/abstract_role_builder_or_built.rs index 2b2aff14..c3180a5a 100644 --- a/crates/rules/src/roles/abstract_role_builder_or_built.rs +++ b/crates/rules/src/roles/abstract_role_builder_or_built.rs @@ -80,19 +80,18 @@ impl AbstractRoleBuilderOrBuilt { self.threshold } } -pub(crate) const ROLE_UNSPECIFIED: u8 = 0; pub(crate) const ROLE_PRIMARY: u8 = 1; pub(crate) const ROLE_RECOVERY: u8 = 2; pub(crate) const ROLE_CONFIRMATION: u8 = 3; pub(crate) trait RoleFromDiscriminator { - fn from_u8(disciminator: u8) -> Option + fn from_u8(discriminator: u8) -> Option where Self: Sized; } impl RoleFromDiscriminator for RoleKind { - fn from_u8(disciminator: u8) -> Option { - match disciminator { + fn from_u8(discriminator: u8) -> Option { + match discriminator { ROLE_PRIMARY => Some(RoleKind::Primary), ROLE_RECOVERY => Some(RoleKind::Recovery), ROLE_CONFIRMATION => Some(RoleKind::Confirmation), diff --git a/crates/rules/src/roles/builder/roles_builder.rs b/crates/rules/src/roles/builder/roles_builder.rs index b569a713..e929010f 100644 --- a/crates/rules/src/roles/builder/roles_builder.rs +++ b/crates/rules/src/roles/builder/roles_builder.rs @@ -15,6 +15,7 @@ impl PrimaryRoleWithFactorSourceIds { } } +#[cfg(test)] impl RecoveryRoleWithFactorSourceIds { pub(crate) fn recovery_with_factors( override_factors: impl IntoIterator, @@ -23,6 +24,7 @@ impl RecoveryRoleWithFactorSourceIds { } } +#[cfg(test)] impl ConfirmationRoleWithFactorSourceIds { pub(crate) fn confirmation_with_factors( override_factors: impl IntoIterator, diff --git a/crates/rules/src/roles/builder/roles_builder_unit_tests.rs b/crates/rules/src/roles/builder/roles_builder_unit_tests.rs index 82032b8d..9dbd29c6 100644 --- a/crates/rules/src/roles/builder/roles_builder_unit_tests.rs +++ b/crates/rules/src/roles/builder/roles_builder_unit_tests.rs @@ -1,1833 +1,1852 @@ -// #![cfg(test)] - -// use crate::prelude::*; - -// use NotYetValidReason::*; -// type Validation = RoleBuilderValidation; - -// #[allow(clippy::upper_case_acronyms)] - -// type MutRes = RoleBuilderMutateResult; - -// mod primary_test_helper_functions { - -// use super::*; -// type SUT = PrimaryRoleBuilder; -// type BuildRes = SUT::RoleBuilderBuildResult; - -// #[test] -// fn factor_sources_not_of_kind_to_list_of_kind_in_override() { -// let mut sut = SUT::new(); -// sut.add_factor_source_to_list(FactorSourceID::sample_device(), FactorListKind::Override) -// .unwrap(); -// sut.add_factor_source_to_list(FactorSourceID::sample_ledger(), FactorListKind::Override) -// .unwrap(); -// sut.add_factor_source_to_list(FactorSourceID::sample_arculus(), FactorListKind::Override) -// .unwrap(); - -// let xs = sut.factor_sources_not_of_kind_to_list_of_kind( -// FactorSourceKind::Device, -// FactorListKind::Override, -// ); -// assert_eq!( -// xs, -// vec![ -// FactorSourceID::sample_ledger(), -// FactorSourceID::sample_arculus() -// ] -// ); - -// let xs = sut.factor_sources_not_of_kind_to_list_of_kind( -// FactorSourceKind::LedgerHQHardwareWallet, -// FactorListKind::Override, -// ); -// assert_eq!( -// xs, -// vec![ -// FactorSourceID::sample_device(), -// FactorSourceID::sample_arculus() -// ] -// ); - -// let xs = sut.factor_sources_not_of_kind_to_list_of_kind( -// FactorSourceKind::ArculusCard, -// FactorListKind::Override, -// ); -// assert_eq!( -// xs, -// vec![ -// FactorSourceID::sample_device(), -// FactorSourceID::sample_ledger() -// ] -// ); -// } - -// #[test] -// fn factor_sources_not_of_kind_to_list_of_kind_in_threshold() { -// let mut sut = SUT::new(); -// sut.add_factor_source_to_list(FactorSourceID::sample_device(), FactorListKind::Threshold) -// .unwrap(); -// sut.add_factor_source_to_list(FactorSourceID::sample_ledger(), FactorListKind::Threshold) -// .unwrap(); -// sut.add_factor_source_to_list(FactorSourceID::sample_arculus(), FactorListKind::Threshold) -// .unwrap(); - -// let xs = sut.factor_sources_not_of_kind_to_list_of_kind( -// FactorSourceKind::Device, -// FactorListKind::Threshold, -// ); -// assert_eq!( -// xs, -// vec![ -// FactorSourceID::sample_ledger(), -// FactorSourceID::sample_arculus() -// ] -// ); - -// let xs = sut.factor_sources_not_of_kind_to_list_of_kind( -// FactorSourceKind::LedgerHQHardwareWallet, -// FactorListKind::Threshold, -// ); -// assert_eq!( -// xs, -// vec![ -// FactorSourceID::sample_device(), -// FactorSourceID::sample_arculus() -// ] -// ); - -// let xs = sut.factor_sources_not_of_kind_to_list_of_kind( -// FactorSourceKind::ArculusCard, -// FactorListKind::Threshold, -// ); -// assert_eq!( -// xs, -// vec![ -// FactorSourceID::sample_device(), -// FactorSourceID::sample_ledger() -// ] -// ); -// } -// } - -// fn test_duplicates_not_allowed( -// sut: RoleBuilder, -// list: FactorListKind, -// factor_source_id: FactorSourceID, -// ) { -// // Arrange -// let mut sut = sut; - -// sut.add_factor_source_to_list(factor_source_id, list) -// .unwrap(); - -// // Act -// let res = sut.add_factor_source_to_list( -// factor_source_id, // oh no, duplicate! -// list, -// ); - -// // Assert -// assert!(matches!( -// res, -// MutRes::Err(Validation::ForeverInvalid( -// ForeverInvalidReason::FactorSourceAlreadyPresent -// )) -// )); -// } - -// #[test] -// fn new_builders() { -// assert_eq!(SUT::primary().role(), RoleKind::Primary); -// assert_eq!(SUT::recovery().role(), RoleKind::Recovery); -// assert_eq!(SUT::confirmation().role(), RoleKind::Confirmation); -// } - -// #[test] -// fn empty_is_err() { -// [ -// RoleKind::Primary, -// RoleKind::Recovery, -// RoleKind::Confirmation, -// ] -// .iter() -// .for_each(|role| { -// let sut = SUT::new(*role); -// let res = sut.build(); -// assert_eq!( -// res, -// BuildRes::not_yet_valid(NotYetValidReason::RoleMustHaveAtLeastOneFactor) -// ); -// }); -// } - -// #[test] -// fn validate_override_for_ever_invalid() { -// let sut = SUT::with_factors( -// RoleKind::Primary, -// 0, -// vec![], -// vec![ -// FactorSourceID::sample_ledger(), -// FactorSourceID::sample_ledger(), -// ], -// ); -// let res = sut.validate(); -// assert_eq!( -// res, -// MutRes::forever_invalid(ForeverInvalidReason::FactorSourceAlreadyPresent) -// ); -// } - -// #[test] -// fn validate_threshold_for_ever_invalid() { -// let sut = SUT::with_factors( -// RoleKind::Primary, -// 1, -// vec![ -// FactorSourceID::sample_ledger(), -// FactorSourceID::sample_ledger(), -// ], -// vec![], -// ); -// let res = sut.validate(); -// assert_eq!( -// res, -// MutRes::forever_invalid(ForeverInvalidReason::FactorSourceAlreadyPresent) -// ); -// } - -// #[test] -// fn confirmation_validate_basic_violation() { -// let sut = SUT::with_factors( -// RoleKind::Confirmation, -// 1, -// vec![], -// vec![FactorSourceID::sample_ledger()], -// ); -// let res = sut.validate(); -// assert_eq!( -// res, -// MutRes::basic_violation(BasicViolation::ConfirmationCannotSetThreshold) -// ); -// } - -// #[test] -// fn recovery_validate_basic_violation() { -// let sut = SUT::with_factors( -// RoleKind::Recovery, -// 1, -// vec![], -// vec![FactorSourceID::sample_ledger()], -// ); -// let res = sut.validate(); -// assert_eq!( -// res, -// MutRes::basic_violation(BasicViolation::RecoveryCannotSetThreshold) -// ); -// } - -// #[test] -// fn primary_validate_not_yet_valid_for_threshold_greater_than_threshold_factors() { -// let sut = SUT::with_factors( -// RoleKind::Primary, -// 1, -// vec![], -// vec![FactorSourceID::sample_ledger()], -// ); -// let res = sut.validate(); -// assert_eq!( -// res, -// MutRes::not_yet_valid(ThresholdHigherThanThresholdFactorsLen) -// ); -// } - -// #[cfg(test)] -// mod recovery_in_isolation { - -// use super::*; - -// fn role() -> RoleKind { -// RoleKind::Recovery -// } - -// fn make() -> SUT { -// SUT::new(role()) -// } - -// fn list() -> FactorListKind { -// FactorListKind::Override -// } - -// fn sample() -> FactorSourceID { -// FactorSourceID::sample_device() -// } - -// #[test] -// fn duplicates_not_allowed() { -// test_duplicates_not_allowed(make(), list(), sample()) -// } - -// #[test] -// fn validation_for_addition_of_factor_source_of_kind_to_list_is_err_for_threshold() { -// let sut = make(); -// let res = sut.validation_for_addition_of_factor_source_of_kind_to_list( -// FactorSourceKind::Device, -// FactorListKind::Threshold, -// ); -// assert_eq!( -// res, -// MutRes::forever_invalid(ForeverInvalidReason::threshold_list_not_supported_for_role( -// role() -// )) -// ); -// } - -// #[test] -// fn validation_for_addition_of_factor_source_of_kind_to_list() { -// use FactorSourceKind::*; -// let sut = make(); -// let not_ok = |kind: FactorSourceKind| { -// let res = sut.validation_for_addition_of_factor_source_of_kind_to_list(kind, list()); -// assert!(res.is_err()); -// }; -// let ok = |kind: FactorSourceKind| { -// let res = sut.validation_for_addition_of_factor_source_of_kind_to_list(kind, list()); -// assert!(res.is_ok()); -// }; -// ok(Device); -// ok(LedgerHQHardwareWallet); -// ok(ArculusCard); -// ok(TrustedContact); -// ok(OffDeviceMnemonic); - -// not_ok(Passphrase); -// not_ok(SecurityQuestions); -// } - -// #[test] -// fn set_threshold_is_unsupported() { -// let mut sut = make(); -// assert_eq!( -// sut.set_threshold(1), -// MutRes::basic_violation(BasicViolation::RecoveryCannotSetThreshold) -// ); -// } - -// #[test] -// fn cannot_add_factors_to_threshold() { -// let mut sut = make(); -// let res = sut.add_factor_source_to_list(sample(), FactorListKind::Threshold); -// assert_eq!( -// res, -// Err(Validation::ForeverInvalid( -// ForeverInvalidReason::RecoveryRoleThresholdFactorsNotSupported -// )) -// ); -// } - -// mod device_in_isolation { -// use super::*; - -// fn sample() -> FactorSourceID { -// FactorSourceID::sample_device() -// } - -// fn sample_other() -> FactorSourceID { -// FactorSourceID::sample_device_other() -// } - -// #[test] -// fn allowed_as_first_and_only() { -// // Arrange -// let mut sut = make(); - -// // Act -// sut.add_factor_source_to_list(sample(), list()).unwrap(); - -// // Assert -// assert_eq!( -// sut.build().unwrap(), -// RoleWithFactorSourceIds::recovery_with_factors([sample()]) -// ); -// } - -// #[test] -// fn two_of_same_kind_allowed() { -// // TODO: Ask Matt -// // Arrange -// let mut sut = make(); - -// // Act -// sut.add_factor_source_to_list(sample(), list()).unwrap(); -// sut.add_factor_source_to_list(sample_other(), list()) -// .unwrap(); - -// // Assert -// assert_eq!( -// sut.build().unwrap(), -// RoleWithFactorSourceIds::recovery_with_factors([sample(), sample_other()],) -// ); -// } - -// #[test] -// fn validation_for_addition_of_factor_source_for_each() { -// let sut = make(); -// let xs = sut.validation_for_addition_of_factor_source_for_each( -// list(), -// &IndexSet::from_iter([sample(), sample_other()]), -// ); -// assert_eq!( -// xs.into_iter().collect::>(), -// vec![ -// FactorSourceInRoleBuilderValidationStatus::ok(RoleKind::Recovery, sample()), -// FactorSourceInRoleBuilderValidationStatus::ok( -// RoleKind::Recovery, -// sample_other(), -// ) -// ] -// ); -// } -// } - -// mod ledger_in_isolation { -// use super::*; - -// fn sample() -> FactorSourceID { -// FactorSourceID::sample_ledger() -// } - -// fn sample_other() -> FactorSourceID { -// FactorSourceID::sample_ledger_other() -// } - -// #[test] -// fn allowed_as_first_and_only() { -// // Arrange -// let mut sut = make(); - -// // Act -// sut.add_factor_source_to_list(sample(), list()).unwrap(); - -// // Assert -// assert_eq!( -// sut.build().unwrap(), -// RoleWithFactorSourceIds::recovery_with_factors([sample()],) -// ); -// } - -// #[test] -// fn two_of_same_kind_allowed() { -// // TODO: Ask Matt -// // Arrange -// let mut sut = make(); - -// // Act -// sut.add_factor_source_to_list(sample(), list()).unwrap(); -// sut.add_factor_source_to_list(sample_other(), list()) -// .unwrap(); - -// // Assert -// assert_eq!( -// sut.build().unwrap(), -// RoleWithFactorSourceIds::recovery_with_factors([sample(), sample_other()]) -// ); -// } -// } - -// mod arculus_in_isolation { -// use super::*; - -// fn sample() -> FactorSourceID { -// FactorSourceID::sample_arculus() -// } - -// fn sample_other() -> FactorSourceID { -// FactorSourceID::sample_arculus_other() -// } - -// #[test] -// fn allowed_as_first_and_only() { -// // Arrange -// let mut sut = make(); - -// // Act -// sut.add_factor_source_to_list(sample(), list()).unwrap(); - -// // Assert -// assert_eq!( -// sut.build().unwrap(), -// RoleWithFactorSourceIds::recovery_with_factors([sample(),]) -// ); -// } - -// #[test] -// fn two_of_same_kind_allowed() { -// // TODO: Ask Matt -// // Arrange -// let mut sut = make(); - -// // Act -// sut.add_factor_source_to_list(sample(), list()).unwrap(); -// sut.add_factor_source_to_list(sample_other(), list()) -// .unwrap(); - -// // Assert -// assert_eq!( -// sut.build().unwrap(), -// RoleWithFactorSourceIds::recovery_with_factors([sample(), sample_other()]) -// ); -// } -// } - -// mod passphrase_in_isolation { -// use super::*; - -// fn sample() -> FactorSourceID { -// FactorSourceID::sample_passphrase() -// } - -// fn sample_other() -> FactorSourceID { -// FactorSourceID::sample_passphrase_other() -// } - -// #[test] -// fn allowed_as_first_and_only() { -// // Arrange -// let mut sut = make(); - -// // Act -// sut.add_factor_source_to_list(sample(), list()).unwrap(); - -// // Assert -// assert_eq!( -// sut.build().unwrap(), -// RoleWithFactorSourceIds::recovery_with_factors([sample()]) -// ); -// } - -// #[test] -// fn two_of_same_kind_allowed() { -// // TODO: Ask Matt -// // Arrange -// let mut sut = make(); - -// // Act -// sut.add_factor_source_to_list(sample(), list()).unwrap(); -// sut.add_factor_source_to_list(sample_other(), list()) -// .unwrap(); - -// // Assert -// assert_eq!( -// sut.build().unwrap(), -// RoleWithFactorSourceIds::recovery_with_factors([sample(), sample_other()]) -// ); -// } -// } - -// mod trusted_contact_in_isolation { -// use super::*; - -// fn sample() -> FactorSourceID { -// FactorSourceID::sample_trusted_contact() -// } - -// fn sample_other() -> FactorSourceID { -// FactorSourceID::sample_trusted_contact_other() -// } - -// #[test] -// fn allowed_as_first_and_only() { -// // Arrange -// let mut sut = make(); - -// // Act -// sut.add_factor_source_to_list(sample(), list()).unwrap(); - -// // Assert -// assert_eq!( -// sut.build().unwrap(), -// RoleWithFactorSourceIds::recovery_with_factors([sample(),]) -// ); -// } - -// #[test] -// fn two_of_same_kind_allowed() { -// // TODO: Ask Matt -// // Arrange -// let mut sut = make(); - -// // Act -// sut.add_factor_source_to_list(sample(), list()).unwrap(); -// sut.add_factor_source_to_list(sample_other(), list()) -// .unwrap(); - -// // Assert -// assert_eq!( -// sut.build().unwrap(), -// RoleWithFactorSourceIds::recovery_with_factors([sample(), sample_other()]) -// ); -// } -// } - -// mod password_in_isolation { -// use super::*; - -// fn sample() -> FactorSourceID { -// FactorSourceID::sample_password() -// } - -// #[test] -// fn unsupported() { -// // Arrange -// let mut sut = make(); - -// // Act -// let res = sut.add_factor_source_to_list(sample(), list()); - -// // Assert -// assert_eq!( -// res, -// MutRes::forever_invalid(ForeverInvalidReason::RecoveryRolePasswordNotSupported) -// ); -// } - -// #[test] -// fn valid_then_invalid_because_unsupported() { -// // Arrange -// let mut sut = make(); - -// sut.add_factor_source_to_list(FactorSourceID::sample_ledger(), list()) -// .unwrap(); -// sut.add_factor_source_to_list(FactorSourceID::sample_arculus(), list()) -// .unwrap(); - -// // Act -// let res = sut.add_factor_source_to_list(sample(), list()); - -// // Assert -// assert_eq!( -// res, -// MutRes::forever_invalid(ForeverInvalidReason::RecoveryRolePasswordNotSupported) -// ); -// } -// } - -// mod security_questions_in_isolation { -// use super::*; - -// fn sample() -> FactorSourceID { -// FactorSourceID::sample_security_questions() -// } -// fn sample_other() -> FactorSourceID { -// FactorSourceID::sample_security_questions_other() -// } - -// #[test] -// fn unsupported() { -// // Arrange -// let mut sut = make(); - -// // Act -// let res = sut.add_factor_source_to_list(sample(), list()); - -// // Assert -// assert_eq!( -// res, -// MutRes::forever_invalid( -// ForeverInvalidReason::RecoveryRoleSecurityQuestionsNotSupported -// ) -// ); -// } - -// #[test] -// fn valid_then_invalid_because_unsupported() { -// // Arrange -// let mut sut = make(); - -// sut.add_factor_source_to_list(FactorSourceID::sample_ledger(), list()) -// .unwrap(); -// sut.add_factor_source_to_list(FactorSourceID::sample_arculus(), list()) -// .unwrap(); - -// // Act -// let res = sut.add_factor_source_to_list(sample_other(), list()); - -// // Assert -// let reason = ForeverInvalidReason::RecoveryRoleSecurityQuestionsNotSupported; -// let err = MutRes::forever_invalid(reason); -// assert_eq!(res, err); - -// // .. erroneous action above did not change the state of the builder (SUT), -// // so we can build and `sample` is not present in the built result. -// assert_eq!( -// sut.build(), -// Ok(RoleWithFactorSourceIds::recovery_with_factors([ -// FactorSourceID::sample_ledger(), -// FactorSourceID::sample_arculus() -// ])) -// ); -// } -// } -// } - -// #[cfg(test)] -// mod confirmation_in_isolation { - -// use super::*; - -// fn role() -> RoleKind { -// RoleKind::Confirmation -// } - -// fn make() -> SUT { -// SUT::new(role()) -// } - -// fn list() -> FactorListKind { -// FactorListKind::Override -// } - -// fn sample() -> FactorSourceID { -// FactorSourceID::sample_device() -// } - -// #[test] -// fn validation_for_addition_of_factor_source_of_kind_to_list_is_err_for_threshold() { -// let sut = make(); -// let res = sut.validation_for_addition_of_factor_source_of_kind_to_list( -// FactorSourceKind::Device, -// FactorListKind::Threshold, -// ); -// assert_eq!( -// res, -// MutRes::forever_invalid(ForeverInvalidReason::threshold_list_not_supported_for_role( -// role() -// )) -// ); -// } - -// #[test] -// fn validation_for_addition_of_factor_source_of_kind_to_list() { -// use FactorSourceKind::*; -// let sut = make(); -// let not_ok = |kind: FactorSourceKind| { -// let res = sut.validation_for_addition_of_factor_source_of_kind_to_list(kind, list()); -// assert!(res.is_err()); -// }; -// let ok = |kind: FactorSourceKind| { -// let res = sut.validation_for_addition_of_factor_source_of_kind_to_list(kind, list()); -// assert!(res.is_ok()); -// }; -// ok(Device); -// ok(LedgerHQHardwareWallet); -// ok(ArculusCard); -// ok(SecurityQuestions); -// ok(Passphrase); -// ok(OffDeviceMnemonic); -// not_ok(TrustedContact); -// } - -// #[test] -// fn duplicates_not_allowed() { -// test_duplicates_not_allowed(make(), list(), sample()) -// } - -// #[test] -// fn cannot_add_factors_to_threshold() { -// let mut sut = make(); -// let res = sut.add_factor_source_to_list(sample(), FactorListKind::Threshold); -// assert_eq!( -// res, -// Err(Validation::ForeverInvalid( -// ForeverInvalidReason::ConfirmationRoleThresholdFactorsNotSupported -// )) -// ); -// } - -// mod device_in_isolation { -// use super::*; - -// fn sample() -> FactorSourceID { -// FactorSourceID::sample_device() -// } - -// fn sample_other() -> FactorSourceID { -// FactorSourceID::sample_device_other() -// } - -// #[test] -// fn set_threshold_is_unsupported() { -// let mut sut = make(); -// assert_eq!( -// sut.set_threshold(1), -// MutRes::basic_violation(BasicViolation::ConfirmationCannotSetThreshold) -// ); -// } - -// #[test] -// fn allowed_as_first_and_only() { -// // Arrange -// let mut sut = make(); - -// // Act -// sut.add_factor_source_to_list(sample(), list()).unwrap(); - -// // Assert -// assert_eq!( -// sut.build().unwrap(), -// RoleWithFactorSourceIds::confirmation_with_factors([sample()]) -// ); -// } - -// #[test] -// fn two_of_same_kind_allowed() { -// // TODO: Ask Matt -// // Arrange -// let mut sut = make(); - -// // Act -// sut.add_factor_source_to_list(sample(), list()).unwrap(); -// sut.add_factor_source_to_list(sample_other(), list()) -// .unwrap(); - -// // Assert -// let built = sut.build().unwrap(); -// assert!(built.get_threshold_factors().is_empty()); -// assert_eq!( -// built, -// RoleWithFactorSourceIds::confirmation_with_factors([sample(), sample_other()]) -// ); -// } -// } - -// mod ledger_in_isolation { -// use super::*; - -// fn sample() -> FactorSourceID { -// FactorSourceID::sample_ledger() -// } - -// fn sample_other() -> FactorSourceID { -// FactorSourceID::sample_ledger_other() -// } - -// #[test] -// fn allowed_as_first_and_only() { -// // Arrange -// let mut sut = make(); - -// // Act -// sut.add_factor_source_to_list(sample(), list()).unwrap(); - -// // Assert -// assert_eq!( -// sut.build().unwrap(), -// RoleWithFactorSourceIds::confirmation_with_factors([sample(),]) -// ); -// } - -// #[test] -// fn two_of_same_kind_allowed() { -// // TODO: Ask Matt -// // Arrange -// let mut sut = make(); - -// // Act -// sut.add_factor_source_to_list(sample(), list()).unwrap(); -// sut.add_factor_source_to_list(sample_other(), list()) -// .unwrap(); - -// // Assert -// assert_eq!( -// sut.build().unwrap(), -// RoleWithFactorSourceIds::confirmation_with_factors([sample(), sample_other()]) -// ); -// } -// } - -// mod arculus_in_isolation { -// use super::*; - -// fn sample() -> FactorSourceID { -// FactorSourceID::sample_arculus() -// } - -// fn sample_other() -> FactorSourceID { -// FactorSourceID::sample_arculus_other() -// } - -// #[test] -// fn allowed_as_first_and_only() { -// // Arrange -// let mut sut = make(); - -// // Act -// sut.add_factor_source_to_list(sample(), list()).unwrap(); - -// // Assert -// assert_eq!( -// sut.build().unwrap(), -// RoleWithFactorSourceIds::confirmation_with_factors([sample(),]) -// ); -// } - -// #[test] -// fn two_of_same_kind_allowed() { -// // TODO: Ask Matt -// // Arrange -// let mut sut = make(); - -// // Act -// sut.add_factor_source_to_list(sample(), list()).unwrap(); -// sut.add_factor_source_to_list(sample_other(), list()) -// .unwrap(); - -// // Assert -// assert_eq!( -// sut.build().unwrap(), -// RoleWithFactorSourceIds::confirmation_with_factors([sample(), sample_other()]) -// ); -// } -// } - -// mod passphrase_in_isolation { -// use super::*; - -// fn sample() -> FactorSourceID { -// FactorSourceID::sample_passphrase() -// } - -// fn sample_other() -> FactorSourceID { -// FactorSourceID::sample_passphrase_other() -// } - -// #[test] -// fn allowed_as_first_and_only() { -// // Arrange -// let mut sut = make(); - -// // Act -// sut.add_factor_source_to_list(sample(), list()).unwrap(); - -// // Assert -// assert_eq!( -// sut.build().unwrap(), -// RoleWithFactorSourceIds::confirmation_with_factors([sample(),]) -// ); -// } - -// #[test] -// fn two_of_same_kind_allowed() { -// // TODO: Ask Matt -// // Arrange -// let mut sut = make(); - -// // Act -// sut.add_factor_source_to_list(sample(), list()).unwrap(); -// sut.add_factor_source_to_list(sample_other(), list()) -// .unwrap(); - -// // Assert -// assert_eq!( -// sut.build().unwrap(), -// RoleWithFactorSourceIds::confirmation_with_factors([sample(), sample_other()]) -// ); -// } -// } - -// mod trusted_contact_in_isolation { -// use super::*; - -// fn sample() -> FactorSourceID { -// FactorSourceID::sample_trusted_contact() -// } - -// #[test] -// fn unsupported() { -// // Arrange -// let mut sut = make(); - -// // Act -// let res = sut.add_factor_source_to_list(sample(), list()); - -// // Assert -// assert_eq!( -// res, -// MutRes::forever_invalid( -// ForeverInvalidReason::ConfirmationRoleTrustedContactNotSupported -// ) -// ); -// } - -// #[test] -// fn valid_then_invalid_because_unsupported() { -// // Arrange -// let mut sut = make(); - -// sut.add_factor_source_to_list(FactorSourceID::sample_ledger(), list()) -// .unwrap(); -// sut.add_factor_source_to_list(FactorSourceID::sample_arculus(), list()) -// .unwrap(); - -// // Act -// let res = sut.add_factor_source_to_list(sample(), list()); - -// // Assert -// assert_eq!( -// res, -// MutRes::forever_invalid( -// ForeverInvalidReason::ConfirmationRoleTrustedContactNotSupported -// ) -// ); -// } -// } - -// mod password_in_isolation { -// use super::*; - -// fn sample() -> FactorSourceID { -// FactorSourceID::sample_password() -// } - -// fn sample_other() -> FactorSourceID { -// FactorSourceID::sample_password_other() -// } - -// #[test] -// fn allowed_as_first_and_only() { -// // Arrange -// let mut sut = make(); - -// // Act -// sut.add_factor_source_to_list(sample(), list()).unwrap(); - -// // Assert -// assert_eq!( -// sut.build().unwrap(), -// RoleWithFactorSourceIds::confirmation_with_factors([sample(),]) -// ); -// } - -// #[test] -// fn two_of_same_kind_allowed() { -// // TODO: Ask Matt -// // Arrange -// let mut sut = make(); - -// // Act -// sut.add_factor_source_to_list(sample(), list()).unwrap(); -// sut.add_factor_source_to_list(sample_other(), list()) -// .unwrap(); - -// // Assert -// assert_eq!( -// sut.build().unwrap(), -// RoleWithFactorSourceIds::confirmation_with_factors([sample(), sample_other()]) -// ); -// } -// } -// } - -// #[cfg(test)] -// mod primary_in_isolation { - -// use super::*; - -// fn role() -> RoleKind { -// RoleKind::Primary -// } - -// fn make() -> SUT { -// SUT::new(role()) -// } - -// #[cfg(test)] -// mod threshold_suite { -// use super::*; - -// fn sample() -> FactorSourceID { -// FactorSourceID::sample_device() -// } - -// fn sample_other() -> FactorSourceID { -// FactorSourceID::sample_ledger() -// } - -// fn sample_third() -> FactorSourceID { -// FactorSourceID::sample_arculus() -// } - -// fn list() -> FactorListKind { -// FactorListKind::Threshold -// } - -// #[test] -// fn remove_lowers_threshold_from_1_to_0() { -// let mut sut = make(); -// let fs = sample(); -// sut.add_factor_source_to_list(fs, list()).unwrap(); -// sut.set_threshold(1).unwrap(); -// assert_eq!(sut.get_threshold(), 1); -// assert_eq!( -// sut.remove_factor_source(&fs), -// Err(Validation::NotYetValid(RoleMustHaveAtLeastOneFactor)) -// ); -// assert_eq!(sut.get_threshold(), 0); -// } - -// #[test] -// fn remove_lowers_threshold_from_3_to_1() { -// let mut sut = make(); -// let fs0 = sample(); -// let fs1 = sample_other(); -// sut.add_factor_source_to_list(fs0, list()).unwrap(); -// sut.add_factor_source_to_list(fs1, list()).unwrap(); -// sut.add_factor_source_to_list(FactorSourceID::sample_arculus_other(), list()) -// .unwrap(); -// sut.set_threshold(3).unwrap(); -// assert_eq!(sut.get_threshold(), 3); -// sut.remove_factor_source(&fs0).unwrap(); -// sut.remove_factor_source(&fs1).unwrap(); -// assert_eq!(sut.get_threshold(), 1); -// } - -// #[test] -// fn remove_from_override_does_not_change_threshold() { -// let mut sut = make(); -// sut.add_factor_source_to_list(sample(), list()).unwrap(); -// sut.add_factor_source_to_list(sample_other(), list()) -// .unwrap(); -// let fs = FactorSourceID::sample_arculus_other(); -// sut.add_factor_source_to_list(fs, FactorListKind::Override) -// .unwrap(); -// sut.set_threshold(2).unwrap(); -// assert_eq!(sut.get_threshold(), 2); -// sut.remove_factor_source(&fs).unwrap(); -// assert_eq!(sut.get_threshold(), 2); - -// let built = sut.build().unwrap(); -// assert_eq!(built.get_threshold(), 2); - -// assert_eq!(built.role(), RoleKind::Primary); - -// assert_eq!( -// built.get_threshold_factors(), -// &vec![sample(), sample_other()] -// ); - -// assert_eq!(built.get_override_factors(), &Vec::new()); -// } - -// #[test] -// fn one_factor_then_set_threshold_to_one_is_ok() { -// // Arrange -// let mut sut = make(); - -// // Act -// sut.add_factor_source_to_list(sample_other(), list()) -// .unwrap(); -// sut.set_threshold(1).unwrap(); - -// // Assert -// let expected = RoleWithFactorSourceIds::primary_with_factors(1, [sample_other()], []); -// assert_eq!(sut.build().unwrap(), expected); -// } - -// #[test] -// fn zero_factor_then_set_threshold_to_one_is_not_yet_valid_then_add_one_factor_is_ok() { -// // Arrange -// let mut sut = make(); - -// // Act -// assert_eq!( -// sut.set_threshold(1), -// Err(Validation::NotYetValid( -// ThresholdHigherThanThresholdFactorsLen -// )) -// ); -// sut.add_factor_source_to_list(sample_other(), list()) -// .unwrap(); - -// // Assert -// let expected = RoleWithFactorSourceIds::primary_with_factors(1, [sample_other()], []); -// assert_eq!(sut.build().unwrap(), expected); -// } - -// #[test] -// fn zero_factor_then_set_threshold_to_two_is_not_yet_valid_then_add_two_factor_is_ok() { -// // Arrange -// let mut sut = make(); - -// // Act -// assert_eq!( -// sut.set_threshold(2), -// Err(Validation::NotYetValid( -// ThresholdHigherThanThresholdFactorsLen -// )) -// ); -// sut.add_factor_source_to_list(sample(), list()).unwrap(); - -// sut.add_factor_source_to_list(sample_other(), list()) -// .unwrap(); - -// // Assert -// let expected = -// RoleWithFactorSourceIds::primary_with_factors(2, [sample(), sample_other()], []); -// assert_eq!(sut.build().unwrap(), expected); -// } - -// #[test] -// fn add_two_factors_then_set_threshold_to_two_is_ok() { -// // Arrange -// let mut sut = make(); - -// sut.add_factor_source_to_list(sample(), list()).unwrap(); -// sut.add_factor_source_to_list(sample_other(), list()) -// .unwrap(); - -// // Act -// assert_eq!(sut.set_threshold(2), Ok(())); - -// // Assert -// let expected = -// RoleWithFactorSourceIds::primary_with_factors(2, [sample(), sample_other()], []); -// assert_eq!(sut.build().unwrap(), expected); -// } - -// #[test] -// fn add_two_factors_then_set_threshold_to_three_is_not_yet_valid_then_add_third_factor_is_ok( -// ) { -// // Arrange -// let mut sut = make(); - -// sut.add_factor_source_to_list(sample(), list()).unwrap(); -// sut.add_factor_source_to_list(sample_other(), list()) -// .unwrap(); - -// // Act -// assert_eq!( -// sut.set_threshold(3), -// Err(Validation::NotYetValid( -// ThresholdHigherThanThresholdFactorsLen -// )) -// ); - -// sut.add_factor_source_to_list(sample_third(), list()) -// .unwrap(); - -// // Assert -// let expected = RoleWithFactorSourceIds::primary_with_factors( -// 3, -// [sample(), sample_other(), sample_third()], -// [], -// ); -// assert_eq!(sut.build().unwrap(), expected); -// } - -// #[test] -// fn one_factors_set_threshold_of_one_is_ok() { -// // Arrange -// let mut sut = make(); - -// // Act -// sut.add_factor_source_to_list(sample_other(), list()) -// .unwrap(); -// sut.set_threshold(1).unwrap(); - -// // Assert -// let expected = RoleWithFactorSourceIds::primary_with_factors(1, [sample_other()], []); -// assert_eq!(sut.build().unwrap(), expected); -// } - -// #[test] -// fn one_override_factors_set_threshold_to_one_is_not_yet_valid() { -// // Arrange -// let mut sut = make(); - -// // Act -// sut.add_factor_source_to_list(sample_other(), FactorListKind::Override) -// .unwrap(); -// assert_eq!( -// sut.set_threshold(1), -// Err(Validation::NotYetValid( -// ThresholdHigherThanThresholdFactorsLen -// )) -// ); - -// // Assert - -// assert_eq!( -// sut.build(), -// Err(Validation::NotYetValid( -// ThresholdHigherThanThresholdFactorsLen -// )) -// ); -// } - -// #[test] -// fn validation_for_addition_of_factor_source_for_each_before_after_adding_a_factor() { -// let mut sut = make(); -// let fs0 = FactorSourceID::sample_ledger(); -// let fs1 = FactorSourceID::sample_password(); -// let fs2 = FactorSourceID::sample_arculus(); -// let xs = sut.validation_for_addition_of_factor_source_for_each( -// list(), -// &IndexSet::from_iter([fs0, fs1, fs2]), -// ); -// assert_eq!( -// xs.into_iter().collect::>(), -// vec![ -// FactorSourceInRoleBuilderValidationStatus::ok( -// RoleKind::Primary, -// fs0, -// ), -// FactorSourceInRoleBuilderValidationStatus::not_yet_valid( -// RoleKind::Primary, -// fs1, -// NotYetValidReason::PrimaryRoleWithPasswordInThresholdListMustHaveAnotherFactor -// ), -// FactorSourceInRoleBuilderValidationStatus::ok( -// RoleKind::Primary, -// fs2, -// ), -// ] -// ); -// _ = sut.add_factor_source_to_list(fs0, list()); -// _ = sut.set_threshold(2); - -// let xs = sut.validation_for_addition_of_factor_source_for_each( -// list(), -// &IndexSet::from_iter([fs0, fs1, fs2]), -// ); -// assert_eq!( -// xs.into_iter().collect::>(), -// vec![ -// FactorSourceInRoleBuilderValidationStatus::forever_invalid( -// RoleKind::Primary, -// fs0, -// ForeverInvalidReason::FactorSourceAlreadyPresent -// ), -// FactorSourceInRoleBuilderValidationStatus::ok(RoleKind::Primary, fs1,), -// FactorSourceInRoleBuilderValidationStatus::ok(RoleKind::Primary, fs2,), -// ] -// ); -// } -// } - -// #[cfg(test)] -// mod password { -// use super::*; - -// fn sample() -> FactorSourceID { -// FactorSourceID::sample_password() -// } - -// fn sample_other() -> FactorSourceID { -// FactorSourceID::sample_password_other() -// } - -// #[test] -// fn test_suite_prerequisite() { -// assert_eq!(sample(), sample()); -// assert_eq!(sample_other(), sample_other()); -// assert_ne!(sample(), sample_other()); -// } - -// mod threshold_in_isolation { -// use super::*; - -// fn list() -> FactorListKind { -// FactorListKind::Threshold -// } - -// #[test] -// fn duplicates_not_allowed() { -// let mut sut = make(); -// sut.add_factor_source_to_list( -// FactorSourceID::sample_device(), -// FactorListKind::Threshold, -// ) -// .unwrap(); -// _ = sut.set_threshold(2); -// test_duplicates_not_allowed(sut, list(), sample()); -// } - -// #[test] -// fn alone_is_not_ok() { -// // Arrange -// let mut sut = make(); - -// // Act -// let res = sut.add_factor_source_to_list(sample(), list()); - -// // Assert -// assert_eq!( -// res, -// MutRes::not_yet_valid( -// NotYetValidReason::PrimaryRoleWithPasswordInThresholdListMustHaveAnotherFactor -// ) -// ); -// } - -// #[test] -// fn validation_for_addition_of_factor_source_of_kind_to_list() { -// use FactorSourceKind::*; - -// let not_ok = |kind: FactorSourceKind| { -// let sut = make(); -// let res = -// sut.validation_for_addition_of_factor_source_of_kind_to_list(kind, list()); -// assert!(res.is_err()); -// }; - -// let ok_with = |kind: FactorSourceKind, setup: fn(&mut SUT)| { -// let mut sut = make(); -// setup(&mut sut); -// let res = -// sut.validation_for_addition_of_factor_source_of_kind_to_list(kind, list()); -// assert!(res.is_ok()); -// }; -// let ok = |kind: FactorSourceKind| { -// ok_with(kind, |_| {}); -// }; - -// ok(LedgerHQHardwareWallet); -// ok(ArculusCard); -// ok(OffDeviceMnemonic); - -// ok_with(Device, |sut| { -// sut.add_factor_source_to_list(FactorSourceID::sample_ledger(), list()) -// .unwrap(); -// }); -// ok_with(Passphrase, |sut| { -// sut.add_factor_source_to_list(FactorSourceID::sample_device(), list()) -// .unwrap(); -// _ = sut.set_threshold(2); -// }); - -// not_ok(SecurityQuestions); -// not_ok(TrustedContact); -// } -// } - -// mod override_in_isolation { -// use super::*; - -// fn list() -> FactorListKind { -// FactorListKind::Override -// } - -// #[test] -// fn unsupported() { -// // Arrange -// let mut sut = make(); - -// // Act -// let res = sut.add_factor_source_to_list(sample(), list()); - -// // Assert -// assert_eq!( -// res, -// MutRes::forever_invalid( -// ForeverInvalidReason::PrimaryCannotHavePasswordInOverrideList -// ) -// ); -// } - -// #[test] -// fn valid_then_invalid_because_unsupported() { -// // Arrange -// let mut sut = make(); -// sut.add_factor_source_to_list( -// FactorSourceID::sample_device(), -// FactorListKind::Threshold, -// ) -// .unwrap(); -// sut.add_factor_source_to_list(FactorSourceID::sample_ledger(), list()) -// .unwrap(); -// sut.add_factor_source_to_list(FactorSourceID::sample_arculus(), list()) -// .unwrap(); - -// // Act -// let res = sut.add_factor_source_to_list(sample(), list()); - -// // Assert -// assert_eq!( -// res, -// MutRes::forever_invalid( -// ForeverInvalidReason::PrimaryCannotHavePasswordInOverrideList -// ) -// ); -// } - -// #[test] -// fn validation_for_addition_of_factor_source_of_kind_to_list() { -// use FactorSourceKind::*; - -// let not_ok = |kind: FactorSourceKind| { -// let sut = make(); -// let res = -// sut.validation_for_addition_of_factor_source_of_kind_to_list(kind, list()); -// assert!(res.is_err()); -// }; - -// let ok_with = |kind: FactorSourceKind, setup: fn(&mut SUT)| { -// let mut sut = make(); -// setup(&mut sut); -// let res = -// sut.validation_for_addition_of_factor_source_of_kind_to_list(kind, list()); -// assert!(res.is_ok()); -// }; -// let ok = |kind: FactorSourceKind| { -// ok_with(kind, |_| {}); -// }; - -// ok(LedgerHQHardwareWallet); -// ok(ArculusCard); -// ok(OffDeviceMnemonic); - -// ok_with(Device, |sut| { -// sut.add_factor_source_to_list(FactorSourceID::sample_ledger(), list()) -// .unwrap(); -// }); - -// not_ok(Passphrase); - -// not_ok(SecurityQuestions); -// not_ok(TrustedContact); -// } -// } -// } - -// #[cfg(test)] -// mod ledger { -// use super::*; - -// fn sample() -> FactorSourceID { -// FactorSourceID::sample_ledger() -// } - -// fn sample_other() -> FactorSourceID { -// FactorSourceID::sample_ledger_other() -// } - -// #[test] -// fn test_suite_prerequisite() { -// assert_eq!(sample(), sample()); -// assert_eq!(sample_other(), sample_other()); -// assert_ne!(sample(), sample_other()); -// } - -// mod threshold_in_isolation { -// use super::*; -// fn list() -> FactorListKind { -// FactorListKind::Threshold -// } - -// #[test] -// fn duplicates_not_allowed() { -// test_duplicates_not_allowed(make(), list(), sample()); -// } - -// #[test] -// fn one_is_ok() { -// // Arrange -// let mut sut = make(); - -// // Act -// sut.add_factor_source_to_list(sample(), list()).unwrap(); -// sut.set_threshold(1).unwrap(); - -// // Assert -// let expected = RoleWithFactorSourceIds::primary_with_factors(1, [sample()], []); -// assert_eq!(sut.build().unwrap(), expected); -// } - -// #[test] -// fn one_with_threshold_of_zero_is_err() { -// // Arrange -// let mut sut = make(); - -// // Act -// sut.add_factor_source_to_list(sample(), list()).unwrap(); - -// // Assert -// assert_eq!( -// sut.build(), -// RoleBuilderBuildResult::Err(RoleBuilderValidation::NotYetValid( -// NotYetValidReason::PrimaryRoleWithThresholdCannotBeZeroWithFactors -// )) -// ); -// } - -// #[test] -// fn two_different_is_ok() { -// // Arrange -// let mut sut = make(); - -// // Act -// sut.add_factor_source_to_list(sample(), list()).unwrap(); -// sut.add_factor_source_to_list(sample_other(), list()) -// .unwrap(); -// sut.set_threshold(2).unwrap(); - -// // Assert -// let expected = RoleWithFactorSourceIds::primary_with_factors( -// 2, -// [sample(), sample_other()], -// [], -// ); -// assert_eq!(sut.build().unwrap(), expected); -// } -// } - -// mod override_in_isolation { -// use super::*; -// fn list() -> FactorListKind { -// FactorListKind::Override -// } - -// #[test] -// fn duplicates_not_allowed() { -// test_duplicates_not_allowed(make(), list(), sample()); -// } - -// #[test] -// fn one_is_ok() { -// // Arrange -// let mut sut = make(); - -// // Act -// sut.add_factor_source_to_list(sample(), list()).unwrap(); - -// // Assert -// let expected = RoleWithFactorSourceIds::primary_with_factors(0, [], [sample()]); -// assert_eq!(sut.build().unwrap(), expected); -// } - -// #[test] -// fn two_different_is_ok() { -// // Arrange -// let mut sut = make(); - -// // Act -// sut.add_factor_source_to_list(sample(), list()).unwrap(); -// sut.add_factor_source_to_list(sample_other(), list()) -// .unwrap(); - -// // Assert -// let expected = RoleWithFactorSourceIds::primary_with_factors( -// 0, -// [], -// [sample(), sample_other()], -// ); -// assert_eq!(sut.build().unwrap(), expected); -// } -// } -// } - -// #[cfg(test)] -// mod arculus { -// use super::*; - -// fn sample() -> FactorSourceID { -// FactorSourceID::sample_arculus() -// } - -// fn sample_other() -> FactorSourceID { -// FactorSourceID::sample_arculus_other() -// } - -// #[test] -// fn test_suite_prerequisite() { -// assert_eq!(sample(), sample()); -// assert_eq!(sample_other(), sample_other()); -// assert_ne!(sample(), sample_other()); -// } - -// mod threshold_in_isolation { -// use super::*; -// fn list() -> FactorListKind { -// FactorListKind::Threshold -// } - -// #[test] -// fn duplicates_not_allowed() { -// test_duplicates_not_allowed(make(), list(), sample()); -// } - -// #[test] -// fn one_is_ok() { -// // Arrange -// let mut sut = make(); - -// // Act -// sut.add_factor_source_to_list(sample(), list()).unwrap(); -// sut.set_threshold(1).unwrap(); - -// // Assert -// let expected = RoleWithFactorSourceIds::primary_with_factors(1, [sample()], []); -// assert_eq!(sut.build().unwrap(), expected); -// } - -// #[test] -// fn two_different_is_ok() { -// // Arrange -// let mut sut = make(); - -// // Act -// sut.add_factor_source_to_list(sample(), list()).unwrap(); -// sut.add_factor_source_to_list(sample_other(), list()) -// .unwrap(); -// sut.set_threshold(1).unwrap(); - -// // Assert -// let expected = RoleWithFactorSourceIds::primary_with_factors( -// 1, -// [sample(), sample_other()], -// [], -// ); -// assert_eq!(sut.build().unwrap(), expected); -// } -// } - -// mod override_in_isolation { -// use super::*; -// fn list() -> FactorListKind { -// FactorListKind::Override -// } - -// #[test] -// fn duplicates_not_allowed() { -// test_duplicates_not_allowed(make(), list(), sample()); -// } - -// #[test] -// fn one_is_ok() { -// // Arrange -// let mut sut = make(); - -// // Act -// sut.add_factor_source_to_list(sample(), list()).unwrap(); - -// // Assert -// let expected = RoleWithFactorSourceIds::primary_with_factors(0, [], [sample()]); -// assert_eq!(sut.build().unwrap(), expected); -// } - -// #[test] -// fn two_different_is_ok() { -// // Arrange -// let mut sut = make(); - -// // Act -// sut.add_factor_source_to_list(sample(), list()).unwrap(); -// sut.add_factor_source_to_list(sample_other(), list()) -// .unwrap(); - -// // Assert -// let expected = RoleWithFactorSourceIds::primary_with_factors( -// 0, -// [], -// [sample(), sample_other()], -// ); -// assert_eq!(sut.build().unwrap(), expected); -// } -// } -// } - -// #[cfg(test)] -// mod device_factor_source { -// use super::*; - -// fn sample() -> FactorSourceID { -// FactorSourceID::sample_device() -// } - -// fn sample_other() -> FactorSourceID { -// FactorSourceID::sample_device_other() -// } - -// #[test] -// fn test_suite_prerequisite() { -// assert_eq!(sample(), sample()); -// assert_eq!(sample_other(), sample_other()); -// assert_ne!(sample(), sample_other()); -// } - -// #[cfg(test)] -// mod threshold_in_isolation { -// use super::*; - -// fn list() -> FactorListKind { -// FactorListKind::Threshold -// } - -// #[test] -// fn duplicates_not_allowed() { -// test_duplicates_not_allowed(make(), list(), sample()) -// } - -// #[test] -// fn one_is_ok() { -// // Arrange -// let mut sut = make(); - -// // Act -// sut.add_factor_source_to_list(sample(), list()).unwrap(); -// sut.set_threshold(1).unwrap(); - -// // Assert -// let expected = RoleWithFactorSourceIds::primary_with_factors(1, [sample()], []); -// assert_eq!(sut.build().unwrap(), expected); -// } - -// #[test] -// fn two_different_is_err() { -// // Arrange -// let mut sut = make(); - -// sut.add_factor_source_to_list(sample(), list()).unwrap(); - -// // Act -// let res = sut.add_factor_source_to_list(sample_other(), list()); - -// // Assert -// assert!(matches!( -// res, -// MutRes::Err(Validation::ForeverInvalid( -// ForeverInvalidReason::PrimaryCannotHaveMultipleDevices -// )) -// )); -// } -// } - -// mod override_in_isolation { - -// use super::*; - -// fn list() -> FactorListKind { -// FactorListKind::Override -// } - -// #[test] -// fn duplicates_not_allowed() { -// test_duplicates_not_allowed(make(), list(), sample()) -// } - -// #[test] -// fn one_is_ok() { -// // Arrange -// let mut sut = make(); - -// // Act -// sut.add_factor_source_to_list(sample(), list()).unwrap(); - -// // Assert -// let expected = RoleWithFactorSourceIds::primary_with_factors(0, [], [sample()]); -// assert_eq!(sut.build().unwrap(), expected); -// } -// } -// } -// } +#![cfg(test)] + +use crate::prelude::*; + +use NotYetValidReason::*; +type Validation = RoleBuilderValidation; + +#[allow(clippy::upper_case_acronyms)] + +type MutRes = RoleBuilderMutateResult; + +mod primary_test_helper_functions { + + use super::*; + + #[allow(clippy::upper_case_acronyms)] + type SUT = PrimaryRoleBuilder; + + #[test] + fn factor_sources_not_of_kind_to_list_of_kind_in_override() { + let mut sut = SUT::new(); + sut.add_factor_source_to_list(FactorSourceID::sample_device(), FactorListKind::Override) + .unwrap(); + sut.add_factor_source_to_list(FactorSourceID::sample_ledger(), FactorListKind::Override) + .unwrap(); + sut.add_factor_source_to_list(FactorSourceID::sample_arculus(), FactorListKind::Override) + .unwrap(); + + let xs = sut.factor_sources_not_of_kind_to_list_of_kind( + FactorSourceKind::Device, + FactorListKind::Override, + ); + assert_eq!( + xs, + vec![ + FactorSourceID::sample_ledger(), + FactorSourceID::sample_arculus() + ] + ); + + let xs = sut.factor_sources_not_of_kind_to_list_of_kind( + FactorSourceKind::LedgerHQHardwareWallet, + FactorListKind::Override, + ); + assert_eq!( + xs, + vec![ + FactorSourceID::sample_device(), + FactorSourceID::sample_arculus() + ] + ); + + let xs = sut.factor_sources_not_of_kind_to_list_of_kind( + FactorSourceKind::ArculusCard, + FactorListKind::Override, + ); + assert_eq!( + xs, + vec![ + FactorSourceID::sample_device(), + FactorSourceID::sample_ledger() + ] + ); + } + + #[test] + fn factor_sources_not_of_kind_to_list_of_kind_in_threshold() { + let mut sut = SUT::new(); + sut.add_factor_source_to_list(FactorSourceID::sample_device(), FactorListKind::Threshold) + .unwrap(); + sut.add_factor_source_to_list(FactorSourceID::sample_ledger(), FactorListKind::Threshold) + .unwrap(); + sut.add_factor_source_to_list(FactorSourceID::sample_arculus(), FactorListKind::Threshold) + .unwrap(); + + let xs = sut.factor_sources_not_of_kind_to_list_of_kind( + FactorSourceKind::Device, + FactorListKind::Threshold, + ); + assert_eq!( + xs, + vec![ + FactorSourceID::sample_ledger(), + FactorSourceID::sample_arculus() + ] + ); + + let xs = sut.factor_sources_not_of_kind_to_list_of_kind( + FactorSourceKind::LedgerHQHardwareWallet, + FactorListKind::Threshold, + ); + assert_eq!( + xs, + vec![ + FactorSourceID::sample_device(), + FactorSourceID::sample_arculus() + ] + ); + + let xs = sut.factor_sources_not_of_kind_to_list_of_kind( + FactorSourceKind::ArculusCard, + FactorListKind::Threshold, + ); + assert_eq!( + xs, + vec![ + FactorSourceID::sample_device(), + FactorSourceID::sample_ledger() + ] + ); + } +} + +fn test_duplicates_not_allowed( + sut: RoleBuilder, + list: FactorListKind, + factor_source_id: FactorSourceID, +) { + // Arrange + let mut sut = sut; + + sut.add_factor_source_to_list(factor_source_id, list) + .unwrap(); + + // Act + let res = sut.add_factor_source_to_list( + factor_source_id, // oh no, duplicate! + list, + ); + + // Assert + assert!(matches!( + res, + MutRes::Err(Validation::ForeverInvalid( + ForeverInvalidReason::FactorSourceAlreadyPresent + )) + )); +} + +#[test] +fn new_builder_primary() { + assert_eq!(PrimaryRoleBuilder::new().role(), RoleKind::Primary); +} + +#[test] +fn new_builder_recovery() { + assert_eq!(RecoveryRoleBuilder::new().role(), RoleKind::Recovery); +} + +#[test] +fn new_builder_confirmation() { + assert_eq!( + ConfirmationRoleBuilder::new().role(), + RoleKind::Confirmation + ); +} + +#[test] +fn empty_is_err_primary() { + let sut = PrimaryRoleBuilder::new(); + let res = sut.build(); + assert_eq!( + res, + PrimaryRoleBuilder::RoleBuilderBuildResult::not_yet_valid( + NotYetValidReason::RoleMustHaveAtLeastOneFactor + ) + ); +} + +#[test] +fn empty_is_err_recovery() { + let sut = RecoveryRoleBuilder::new(); + let res = sut.build(); + assert_eq!( + res, + RecoveryRoleBuilder::RoleBuilderBuildResult::not_yet_valid( + NotYetValidReason::RoleMustHaveAtLeastOneFactor + ) + ); +} + +#[test] +fn empty_is_err_confirmation() { + let sut = ConfirmationRoleBuilder::new(); + let res = sut.build(); + assert_eq!( + res, + ConfirmationRoleBuilder::RoleBuilderBuildResult::not_yet_valid( + NotYetValidReason::RoleMustHaveAtLeastOneFactor + ) + ); +} + +#[test] +fn validate_override_for_ever_invalid() { + let sut = PrimaryRoleBuilder::with_factors( + 0, + vec![], + vec![ + FactorSourceID::sample_ledger(), + FactorSourceID::sample_ledger(), + ], + ); + let res = sut.validate(); + assert_eq!( + res, + MutRes::forever_invalid(ForeverInvalidReason::FactorSourceAlreadyPresent) + ); +} + +#[test] +fn validate_threshold_for_ever_invalid() { + let sut = PrimaryRoleBuilder::with_factors( + 1, + vec![ + FactorSourceID::sample_ledger(), + FactorSourceID::sample_ledger(), + ], + vec![], + ); + let res = sut.validate(); + assert_eq!( + res, + MutRes::forever_invalid(ForeverInvalidReason::FactorSourceAlreadyPresent) + ); +} + +#[test] +fn confirmation_validate_basic_violation() { + let sut = + ConfirmationRoleBuilder::with_factors(1, vec![], vec![FactorSourceID::sample_ledger()]); + let res = sut.validate(); + assert_eq!( + res, + MutRes::basic_violation(BasicViolation::ConfirmationCannotSetThreshold) + ); +} + +#[test] +fn recovery_validate_basic_violation() { + let sut = RecoveryRoleBuilder::with_factors(1, vec![], vec![FactorSourceID::sample_ledger()]); + let res = sut.validate(); + assert_eq!( + res, + MutRes::basic_violation(BasicViolation::RecoveryCannotSetThreshold) + ); +} + +#[test] +fn primary_validate_not_yet_valid_for_threshold_greater_than_threshold_factors() { + let sut = PrimaryRoleBuilder::with_factors(1, vec![], vec![FactorSourceID::sample_ledger()]); + let res = sut.validate(); + assert_eq!( + res, + MutRes::not_yet_valid(ThresholdHigherThanThresholdFactorsLen) + ); +} + +#[cfg(test)] +mod recovery_in_isolation { + + use super::*; + + #[allow(clippy::upper_case_acronyms)] + type SUT = RecoveryRoleBuilder; + + fn role() -> RoleKind { + RoleKind::Recovery + } + + fn make() -> SUT { + SUT::new() + } + + fn list() -> FactorListKind { + FactorListKind::Override + } + + fn sample() -> FactorSourceID { + FactorSourceID::sample_device() + } + + #[test] + fn duplicates_not_allowed() { + test_duplicates_not_allowed(make(), list(), sample()) + } + + #[test] + fn validation_for_addition_of_factor_source_of_kind_to_list_is_err_for_threshold() { + let sut = make(); + let res = sut.validation_for_addition_of_factor_source_of_kind_to_list( + FactorSourceKind::Device, + FactorListKind::Threshold, + ); + assert_eq!( + res, + MutRes::forever_invalid(ForeverInvalidReason::threshold_list_not_supported_for_role( + role() + )) + ); + } + + #[test] + fn validation_for_addition_of_factor_source_of_kind_to_list() { + use FactorSourceKind::*; + let sut = make(); + let not_ok = |kind: FactorSourceKind| { + let res = sut.validation_for_addition_of_factor_source_of_kind_to_list(kind, list()); + assert!(res.is_err()); + }; + let ok = |kind: FactorSourceKind| { + let res = sut.validation_for_addition_of_factor_source_of_kind_to_list(kind, list()); + assert!(res.is_ok()); + }; + ok(Device); + ok(LedgerHQHardwareWallet); + ok(ArculusCard); + ok(TrustedContact); + ok(OffDeviceMnemonic); + + not_ok(Passphrase); + not_ok(SecurityQuestions); + } + + #[test] + fn set_threshold_is_unsupported() { + let mut sut = make(); + assert_eq!( + sut.set_threshold(1), + MutRes::basic_violation(BasicViolation::RecoveryCannotSetThreshold) + ); + } + + #[test] + fn cannot_add_factors_to_threshold() { + let mut sut = make(); + let res = sut.add_factor_source_to_list(sample(), FactorListKind::Threshold); + assert_eq!( + res, + Err(Validation::ForeverInvalid( + ForeverInvalidReason::RecoveryRoleThresholdFactorsNotSupported + )) + ); + } + + mod device_in_isolation { + use super::*; + + fn sample() -> FactorSourceID { + FactorSourceID::sample_device() + } + + fn sample_other() -> FactorSourceID { + FactorSourceID::sample_device_other() + } + + #[test] + fn allowed_as_first_and_only() { + // Arrange + let mut sut = make(); + + // Act + sut.add_factor_source_to_list(sample(), list()).unwrap(); + + // Assert + assert_eq!( + sut.build().unwrap(), + RoleWithFactorSourceIds::recovery_with_factors([sample()]) + ); + } + + #[test] + fn two_of_same_kind_allowed() { + // TODO: Ask Matt + // Arrange + let mut sut = make(); + + // Act + sut.add_factor_source_to_list(sample(), list()).unwrap(); + sut.add_factor_source_to_list(sample_other(), list()) + .unwrap(); + + // Assert + assert_eq!( + sut.build().unwrap(), + RoleWithFactorSourceIds::recovery_with_factors([sample(), sample_other()],) + ); + } + + #[test] + fn validation_for_addition_of_factor_source_for_each() { + let sut = make(); + let xs = sut.validation_for_addition_of_factor_source_for_each( + list(), + &IndexSet::from_iter([sample(), sample_other()]), + ); + assert_eq!( + xs.into_iter().collect::>(), + vec![ + FactorSourceInRoleBuilderValidationStatus::ok(RoleKind::Recovery, sample()), + FactorSourceInRoleBuilderValidationStatus::ok( + RoleKind::Recovery, + sample_other(), + ) + ] + ); + } + } + + mod ledger_in_isolation { + use super::*; + + fn sample() -> FactorSourceID { + FactorSourceID::sample_ledger() + } + + fn sample_other() -> FactorSourceID { + FactorSourceID::sample_ledger_other() + } + + #[test] + fn allowed_as_first_and_only() { + // Arrange + let mut sut = make(); + + // Act + sut.add_factor_source_to_list(sample(), list()).unwrap(); + + // Assert + assert_eq!( + sut.build().unwrap(), + RoleWithFactorSourceIds::recovery_with_factors([sample()],) + ); + } + + #[test] + fn two_of_same_kind_allowed() { + // TODO: Ask Matt + // Arrange + let mut sut = make(); + + // Act + sut.add_factor_source_to_list(sample(), list()).unwrap(); + sut.add_factor_source_to_list(sample_other(), list()) + .unwrap(); + + // Assert + assert_eq!( + sut.build().unwrap(), + RoleWithFactorSourceIds::recovery_with_factors([sample(), sample_other()]) + ); + } + } + + mod arculus_in_isolation { + use super::*; + + fn sample() -> FactorSourceID { + FactorSourceID::sample_arculus() + } + + fn sample_other() -> FactorSourceID { + FactorSourceID::sample_arculus_other() + } + + #[test] + fn allowed_as_first_and_only() { + // Arrange + let mut sut = make(); + + // Act + sut.add_factor_source_to_list(sample(), list()).unwrap(); + + // Assert + assert_eq!( + sut.build().unwrap(), + RoleWithFactorSourceIds::recovery_with_factors([sample(),]) + ); + } + + #[test] + fn two_of_same_kind_allowed() { + // TODO: Ask Matt + // Arrange + let mut sut = make(); + + // Act + sut.add_factor_source_to_list(sample(), list()).unwrap(); + sut.add_factor_source_to_list(sample_other(), list()) + .unwrap(); + + // Assert + assert_eq!( + sut.build().unwrap(), + RoleWithFactorSourceIds::recovery_with_factors([sample(), sample_other()]) + ); + } + } + + mod passphrase_in_isolation { + use super::*; + + fn sample() -> FactorSourceID { + FactorSourceID::sample_passphrase() + } + + fn sample_other() -> FactorSourceID { + FactorSourceID::sample_passphrase_other() + } + + #[test] + fn allowed_as_first_and_only() { + // Arrange + let mut sut = make(); + + // Act + sut.add_factor_source_to_list(sample(), list()).unwrap(); + + // Assert + assert_eq!( + sut.build().unwrap(), + RoleWithFactorSourceIds::recovery_with_factors([sample()]) + ); + } + + #[test] + fn two_of_same_kind_allowed() { + // TODO: Ask Matt + // Arrange + let mut sut = make(); + + // Act + sut.add_factor_source_to_list(sample(), list()).unwrap(); + sut.add_factor_source_to_list(sample_other(), list()) + .unwrap(); + + // Assert + assert_eq!( + sut.build().unwrap(), + RoleWithFactorSourceIds::recovery_with_factors([sample(), sample_other()]) + ); + } + } + + mod trusted_contact_in_isolation { + use super::*; + + fn sample() -> FactorSourceID { + FactorSourceID::sample_trusted_contact() + } + + fn sample_other() -> FactorSourceID { + FactorSourceID::sample_trusted_contact_other() + } + + #[test] + fn allowed_as_first_and_only() { + // Arrange + let mut sut = make(); + + // Act + sut.add_factor_source_to_list(sample(), list()).unwrap(); + + // Assert + assert_eq!( + sut.build().unwrap(), + RoleWithFactorSourceIds::recovery_with_factors([sample(),]) + ); + } + + #[test] + fn two_of_same_kind_allowed() { + // TODO: Ask Matt + // Arrange + let mut sut = make(); + + // Act + sut.add_factor_source_to_list(sample(), list()).unwrap(); + sut.add_factor_source_to_list(sample_other(), list()) + .unwrap(); + + // Assert + assert_eq!( + sut.build().unwrap(), + RoleWithFactorSourceIds::recovery_with_factors([sample(), sample_other()]) + ); + } + } + + mod password_in_isolation { + use super::*; + + fn sample() -> FactorSourceID { + FactorSourceID::sample_password() + } + + #[test] + fn unsupported() { + // Arrange + let mut sut = make(); + + // Act + let res = sut.add_factor_source_to_list(sample(), list()); + + // Assert + assert_eq!( + res, + MutRes::forever_invalid(ForeverInvalidReason::RecoveryRolePasswordNotSupported) + ); + } + + #[test] + fn valid_then_invalid_because_unsupported() { + // Arrange + let mut sut = make(); + + sut.add_factor_source_to_list(FactorSourceID::sample_ledger(), list()) + .unwrap(); + sut.add_factor_source_to_list(FactorSourceID::sample_arculus(), list()) + .unwrap(); + + // Act + let res = sut.add_factor_source_to_list(sample(), list()); + + // Assert + assert_eq!( + res, + MutRes::forever_invalid(ForeverInvalidReason::RecoveryRolePasswordNotSupported) + ); + } + } + + mod security_questions_in_isolation { + use super::*; + + fn sample() -> FactorSourceID { + FactorSourceID::sample_security_questions() + } + fn sample_other() -> FactorSourceID { + FactorSourceID::sample_security_questions_other() + } + + #[test] + fn unsupported() { + // Arrange + let mut sut = make(); + + // Act + let res = sut.add_factor_source_to_list(sample(), list()); + + // Assert + assert_eq!( + res, + MutRes::forever_invalid( + ForeverInvalidReason::RecoveryRoleSecurityQuestionsNotSupported + ) + ); + } + + #[test] + fn valid_then_invalid_because_unsupported() { + // Arrange + let mut sut = make(); + + sut.add_factor_source_to_list(FactorSourceID::sample_ledger(), list()) + .unwrap(); + sut.add_factor_source_to_list(FactorSourceID::sample_arculus(), list()) + .unwrap(); + + // Act + let res = sut.add_factor_source_to_list(sample_other(), list()); + + // Assert + let reason = ForeverInvalidReason::RecoveryRoleSecurityQuestionsNotSupported; + let err = MutRes::forever_invalid(reason); + assert_eq!(res, err); + + // .. erroneous action above did not change the state of the builder (SUT), + // so we can build and `sample` is not present in the built result. + assert_eq!( + sut.build(), + Ok(RoleWithFactorSourceIds::recovery_with_factors([ + FactorSourceID::sample_ledger(), + FactorSourceID::sample_arculus() + ])) + ); + } + } +} + +#[cfg(test)] +mod confirmation_in_isolation { + + use super::*; + + fn role() -> RoleKind { + RoleKind::Confirmation + } + + #[allow(clippy::upper_case_acronyms)] + type SUT = ConfirmationRoleBuilder; + + fn make() -> SUT { + SUT::new() + } + + fn list() -> FactorListKind { + FactorListKind::Override + } + + fn sample() -> FactorSourceID { + FactorSourceID::sample_device() + } + + #[test] + fn validation_for_addition_of_factor_source_of_kind_to_list_is_err_for_threshold() { + let sut = make(); + let res = sut.validation_for_addition_of_factor_source_of_kind_to_list( + FactorSourceKind::Device, + FactorListKind::Threshold, + ); + assert_eq!( + res, + MutRes::forever_invalid(ForeverInvalidReason::threshold_list_not_supported_for_role( + role() + )) + ); + } + + #[test] + fn validation_for_addition_of_factor_source_of_kind_to_list() { + use FactorSourceKind::*; + let sut = make(); + let not_ok = |kind: FactorSourceKind| { + let res = sut.validation_for_addition_of_factor_source_of_kind_to_list(kind, list()); + assert!(res.is_err()); + }; + let ok = |kind: FactorSourceKind| { + let res = sut.validation_for_addition_of_factor_source_of_kind_to_list(kind, list()); + assert!(res.is_ok()); + }; + ok(Device); + ok(LedgerHQHardwareWallet); + ok(ArculusCard); + ok(SecurityQuestions); + ok(Passphrase); + ok(OffDeviceMnemonic); + not_ok(TrustedContact); + } + + #[test] + fn duplicates_not_allowed() { + test_duplicates_not_allowed(make(), list(), sample()) + } + + #[test] + fn cannot_add_factors_to_threshold() { + let mut sut = make(); + let res = sut.add_factor_source_to_list(sample(), FactorListKind::Threshold); + assert_eq!( + res, + Err(Validation::ForeverInvalid( + ForeverInvalidReason::ConfirmationRoleThresholdFactorsNotSupported + )) + ); + } + + mod device_in_isolation { + use super::*; + + fn sample() -> FactorSourceID { + FactorSourceID::sample_device() + } + + fn sample_other() -> FactorSourceID { + FactorSourceID::sample_device_other() + } + + #[test] + fn set_threshold_is_unsupported() { + let mut sut = make(); + assert_eq!( + sut.set_threshold(1), + MutRes::basic_violation(BasicViolation::ConfirmationCannotSetThreshold) + ); + } + + #[test] + fn allowed_as_first_and_only() { + // Arrange + let mut sut = make(); + + // Act + sut.add_factor_source_to_list(sample(), list()).unwrap(); + + // Assert + assert_eq!( + sut.build().unwrap(), + RoleWithFactorSourceIds::confirmation_with_factors([sample()]) + ); + } + + #[test] + fn two_of_same_kind_allowed() { + // TODO: Ask Matt + // Arrange + let mut sut = make(); + + // Act + sut.add_factor_source_to_list(sample(), list()).unwrap(); + sut.add_factor_source_to_list(sample_other(), list()) + .unwrap(); + + // Assert + let built = sut.build().unwrap(); + assert!(built.get_threshold_factors().is_empty()); + assert_eq!( + built, + RoleWithFactorSourceIds::confirmation_with_factors([sample(), sample_other()]) + ); + } + } + + mod ledger_in_isolation { + use super::*; + + fn sample() -> FactorSourceID { + FactorSourceID::sample_ledger() + } + + fn sample_other() -> FactorSourceID { + FactorSourceID::sample_ledger_other() + } + + #[test] + fn allowed_as_first_and_only() { + // Arrange + let mut sut = make(); + + // Act + sut.add_factor_source_to_list(sample(), list()).unwrap(); + + // Assert + assert_eq!( + sut.build().unwrap(), + RoleWithFactorSourceIds::confirmation_with_factors([sample(),]) + ); + } + + #[test] + fn two_of_same_kind_allowed() { + // TODO: Ask Matt + // Arrange + let mut sut = make(); + + // Act + sut.add_factor_source_to_list(sample(), list()).unwrap(); + sut.add_factor_source_to_list(sample_other(), list()) + .unwrap(); + + // Assert + assert_eq!( + sut.build().unwrap(), + RoleWithFactorSourceIds::confirmation_with_factors([sample(), sample_other()]) + ); + } + } + + mod arculus_in_isolation { + use super::*; + + fn sample() -> FactorSourceID { + FactorSourceID::sample_arculus() + } + + fn sample_other() -> FactorSourceID { + FactorSourceID::sample_arculus_other() + } + + #[test] + fn allowed_as_first_and_only() { + // Arrange + let mut sut = make(); + + // Act + sut.add_factor_source_to_list(sample(), list()).unwrap(); + + // Assert + assert_eq!( + sut.build().unwrap(), + RoleWithFactorSourceIds::confirmation_with_factors([sample(),]) + ); + } + + #[test] + fn two_of_same_kind_allowed() { + // TODO: Ask Matt + // Arrange + let mut sut = make(); + + // Act + sut.add_factor_source_to_list(sample(), list()).unwrap(); + sut.add_factor_source_to_list(sample_other(), list()) + .unwrap(); + + // Assert + assert_eq!( + sut.build().unwrap(), + RoleWithFactorSourceIds::confirmation_with_factors([sample(), sample_other()]) + ); + } + } + + mod passphrase_in_isolation { + use super::*; + + fn sample() -> FactorSourceID { + FactorSourceID::sample_passphrase() + } + + fn sample_other() -> FactorSourceID { + FactorSourceID::sample_passphrase_other() + } + + #[test] + fn allowed_as_first_and_only() { + // Arrange + let mut sut = make(); + + // Act + sut.add_factor_source_to_list(sample(), list()).unwrap(); + + // Assert + assert_eq!( + sut.build().unwrap(), + RoleWithFactorSourceIds::confirmation_with_factors([sample(),]) + ); + } + + #[test] + fn two_of_same_kind_allowed() { + // TODO: Ask Matt + // Arrange + let mut sut = make(); + + // Act + sut.add_factor_source_to_list(sample(), list()).unwrap(); + sut.add_factor_source_to_list(sample_other(), list()) + .unwrap(); + + // Assert + assert_eq!( + sut.build().unwrap(), + RoleWithFactorSourceIds::confirmation_with_factors([sample(), sample_other()]) + ); + } + } + + mod trusted_contact_in_isolation { + use super::*; + + fn sample() -> FactorSourceID { + FactorSourceID::sample_trusted_contact() + } + + #[test] + fn unsupported() { + // Arrange + let mut sut = make(); + + // Act + let res = sut.add_factor_source_to_list(sample(), list()); + + // Assert + assert_eq!( + res, + MutRes::forever_invalid( + ForeverInvalidReason::ConfirmationRoleTrustedContactNotSupported + ) + ); + } + + #[test] + fn valid_then_invalid_because_unsupported() { + // Arrange + let mut sut = make(); + + sut.add_factor_source_to_list(FactorSourceID::sample_ledger(), list()) + .unwrap(); + sut.add_factor_source_to_list(FactorSourceID::sample_arculus(), list()) + .unwrap(); + + // Act + let res = sut.add_factor_source_to_list(sample(), list()); + + // Assert + assert_eq!( + res, + MutRes::forever_invalid( + ForeverInvalidReason::ConfirmationRoleTrustedContactNotSupported + ) + ); + } + } + + mod password_in_isolation { + use super::*; + + fn sample() -> FactorSourceID { + FactorSourceID::sample_password() + } + + fn sample_other() -> FactorSourceID { + FactorSourceID::sample_password_other() + } + + #[test] + fn allowed_as_first_and_only() { + // Arrange + let mut sut = make(); + + // Act + sut.add_factor_source_to_list(sample(), list()).unwrap(); + + // Assert + assert_eq!( + sut.build().unwrap(), + RoleWithFactorSourceIds::confirmation_with_factors([sample(),]) + ); + } + + #[test] + fn two_of_same_kind_allowed() { + // TODO: Ask Matt + // Arrange + let mut sut = make(); + + // Act + sut.add_factor_source_to_list(sample(), list()).unwrap(); + sut.add_factor_source_to_list(sample_other(), list()) + .unwrap(); + + // Assert + assert_eq!( + sut.build().unwrap(), + RoleWithFactorSourceIds::confirmation_with_factors([sample(), sample_other()]) + ); + } + } +} + +#[cfg(test)] +mod primary_in_isolation { + + use super::*; + + #[allow(clippy::upper_case_acronyms)] + type SUT = PrimaryRoleBuilder; + + fn make() -> SUT { + SUT::new() + } + + #[cfg(test)] + mod threshold_suite { + use super::*; + + fn sample() -> FactorSourceID { + FactorSourceID::sample_device() + } + + fn sample_other() -> FactorSourceID { + FactorSourceID::sample_ledger() + } + + fn sample_third() -> FactorSourceID { + FactorSourceID::sample_arculus() + } + + fn list() -> FactorListKind { + FactorListKind::Threshold + } + + #[test] + fn remove_lowers_threshold_from_1_to_0() { + let mut sut = make(); + let fs = sample(); + sut.add_factor_source_to_list(fs, list()).unwrap(); + sut.set_threshold(1).unwrap(); + assert_eq!(sut.get_threshold(), 1); + assert_eq!( + sut.remove_factor_source(&fs), + Err(Validation::NotYetValid(RoleMustHaveAtLeastOneFactor)) + ); + assert_eq!(sut.get_threshold(), 0); + } + + #[test] + fn remove_lowers_threshold_from_3_to_1() { + let mut sut = make(); + let fs0 = sample(); + let fs1 = sample_other(); + sut.add_factor_source_to_list(fs0, list()).unwrap(); + sut.add_factor_source_to_list(fs1, list()).unwrap(); + sut.add_factor_source_to_list(FactorSourceID::sample_arculus_other(), list()) + .unwrap(); + sut.set_threshold(3).unwrap(); + assert_eq!(sut.get_threshold(), 3); + sut.remove_factor_source(&fs0).unwrap(); + sut.remove_factor_source(&fs1).unwrap(); + assert_eq!(sut.get_threshold(), 1); + } + + #[test] + fn remove_from_override_does_not_change_threshold() { + let mut sut = make(); + sut.add_factor_source_to_list(sample(), list()).unwrap(); + sut.add_factor_source_to_list(sample_other(), list()) + .unwrap(); + let fs = FactorSourceID::sample_arculus_other(); + sut.add_factor_source_to_list(fs, FactorListKind::Override) + .unwrap(); + sut.set_threshold(2).unwrap(); + assert_eq!(sut.get_threshold(), 2); + sut.remove_factor_source(&fs).unwrap(); + assert_eq!(sut.get_threshold(), 2); + + let built = sut.build().unwrap(); + assert_eq!(built.get_threshold(), 2); + + assert_eq!(built.role(), RoleKind::Primary); + + assert_eq!( + built.get_threshold_factors(), + &vec![sample(), sample_other()] + ); + + assert_eq!(built.get_override_factors(), &Vec::new()); + } + + #[test] + fn one_factor_then_set_threshold_to_one_is_ok() { + // Arrange + let mut sut = make(); + + // Act + sut.add_factor_source_to_list(sample_other(), list()) + .unwrap(); + sut.set_threshold(1).unwrap(); + + // Assert + let expected = RoleWithFactorSourceIds::primary_with_factors(1, [sample_other()], []); + assert_eq!(sut.build().unwrap(), expected); + } + + #[test] + fn zero_factor_then_set_threshold_to_one_is_not_yet_valid_then_add_one_factor_is_ok() { + // Arrange + let mut sut = make(); + + // Act + assert_eq!( + sut.set_threshold(1), + Err(Validation::NotYetValid( + ThresholdHigherThanThresholdFactorsLen + )) + ); + sut.add_factor_source_to_list(sample_other(), list()) + .unwrap(); + + // Assert + let expected = RoleWithFactorSourceIds::primary_with_factors(1, [sample_other()], []); + assert_eq!(sut.build().unwrap(), expected); + } + + #[test] + fn zero_factor_then_set_threshold_to_two_is_not_yet_valid_then_add_two_factor_is_ok() { + // Arrange + let mut sut = make(); + + // Act + assert_eq!( + sut.set_threshold(2), + Err(Validation::NotYetValid( + ThresholdHigherThanThresholdFactorsLen + )) + ); + sut.add_factor_source_to_list(sample(), list()).unwrap(); + + sut.add_factor_source_to_list(sample_other(), list()) + .unwrap(); + + // Assert + let expected = + RoleWithFactorSourceIds::primary_with_factors(2, [sample(), sample_other()], []); + assert_eq!(sut.build().unwrap(), expected); + } + + #[test] + fn add_two_factors_then_set_threshold_to_two_is_ok() { + // Arrange + let mut sut = make(); + + sut.add_factor_source_to_list(sample(), list()).unwrap(); + sut.add_factor_source_to_list(sample_other(), list()) + .unwrap(); + + // Act + assert_eq!(sut.set_threshold(2), Ok(())); + + // Assert + let expected = + RoleWithFactorSourceIds::primary_with_factors(2, [sample(), sample_other()], []); + assert_eq!(sut.build().unwrap(), expected); + } + + #[test] + fn add_two_factors_then_set_threshold_to_three_is_not_yet_valid_then_add_third_factor_is_ok( + ) { + // Arrange + let mut sut = make(); + + sut.add_factor_source_to_list(sample(), list()).unwrap(); + sut.add_factor_source_to_list(sample_other(), list()) + .unwrap(); + + // Act + assert_eq!( + sut.set_threshold(3), + Err(Validation::NotYetValid( + ThresholdHigherThanThresholdFactorsLen + )) + ); + + sut.add_factor_source_to_list(sample_third(), list()) + .unwrap(); + + // Assert + let expected = RoleWithFactorSourceIds::primary_with_factors( + 3, + [sample(), sample_other(), sample_third()], + [], + ); + assert_eq!(sut.build().unwrap(), expected); + } + + #[test] + fn one_factors_set_threshold_of_one_is_ok() { + // Arrange + let mut sut = make(); + + // Act + sut.add_factor_source_to_list(sample_other(), list()) + .unwrap(); + sut.set_threshold(1).unwrap(); + + // Assert + let expected = RoleWithFactorSourceIds::primary_with_factors(1, [sample_other()], []); + assert_eq!(sut.build().unwrap(), expected); + } + + #[test] + fn one_override_factors_set_threshold_to_one_is_not_yet_valid() { + // Arrange + let mut sut = make(); + + // Act + sut.add_factor_source_to_list(sample_other(), FactorListKind::Override) + .unwrap(); + assert_eq!( + sut.set_threshold(1), + Err(Validation::NotYetValid( + ThresholdHigherThanThresholdFactorsLen + )) + ); + + // Assert + + assert_eq!( + sut.build(), + Err(Validation::NotYetValid( + ThresholdHigherThanThresholdFactorsLen + )) + ); + } + + #[test] + fn validation_for_addition_of_factor_source_for_each_before_after_adding_a_factor() { + let mut sut = make(); + let fs0 = FactorSourceID::sample_ledger(); + let fs1 = FactorSourceID::sample_password(); + let fs2 = FactorSourceID::sample_arculus(); + let xs = sut.validation_for_addition_of_factor_source_for_each( + list(), + &IndexSet::from_iter([fs0, fs1, fs2]), + ); + assert_eq!( + xs.into_iter().collect::>(), + vec![ + FactorSourceInRoleBuilderValidationStatus::ok( + RoleKind::Primary, + fs0, + ), + FactorSourceInRoleBuilderValidationStatus::not_yet_valid( + RoleKind::Primary, + fs1, + NotYetValidReason::PrimaryRoleWithPasswordInThresholdListMustHaveAnotherFactor + ), + FactorSourceInRoleBuilderValidationStatus::ok( + RoleKind::Primary, + fs2, + ), + ] + ); + _ = sut.add_factor_source_to_list(fs0, list()); + _ = sut.set_threshold(2); + + let xs = sut.validation_for_addition_of_factor_source_for_each( + list(), + &IndexSet::from_iter([fs0, fs1, fs2]), + ); + assert_eq!( + xs.into_iter().collect::>(), + vec![ + FactorSourceInRoleBuilderValidationStatus::forever_invalid( + RoleKind::Primary, + fs0, + ForeverInvalidReason::FactorSourceAlreadyPresent + ), + FactorSourceInRoleBuilderValidationStatus::ok(RoleKind::Primary, fs1,), + FactorSourceInRoleBuilderValidationStatus::ok(RoleKind::Primary, fs2,), + ] + ); + } + } + + #[cfg(test)] + mod password { + use super::*; + + fn sample() -> FactorSourceID { + FactorSourceID::sample_password() + } + + fn sample_other() -> FactorSourceID { + FactorSourceID::sample_password_other() + } + + #[test] + fn test_suite_prerequisite() { + assert_eq!(sample(), sample()); + assert_eq!(sample_other(), sample_other()); + assert_ne!(sample(), sample_other()); + } + + mod threshold_in_isolation { + use super::*; + + fn list() -> FactorListKind { + FactorListKind::Threshold + } + + #[test] + fn duplicates_not_allowed() { + let mut sut = make(); + sut.add_factor_source_to_list( + FactorSourceID::sample_device(), + FactorListKind::Threshold, + ) + .unwrap(); + _ = sut.set_threshold(2); + test_duplicates_not_allowed(sut, list(), sample()); + } + + #[test] + fn alone_is_not_ok() { + // Arrange + let mut sut = make(); + + // Act + let res = sut.add_factor_source_to_list(sample(), list()); + + // Assert + assert_eq!( + res, + MutRes::not_yet_valid( + NotYetValidReason::PrimaryRoleWithPasswordInThresholdListMustHaveAnotherFactor + ) + ); + } + + #[test] + fn validation_for_addition_of_factor_source_of_kind_to_list() { + use FactorSourceKind::*; + + let not_ok = |kind: FactorSourceKind| { + let sut = make(); + let res = + sut.validation_for_addition_of_factor_source_of_kind_to_list(kind, list()); + assert!(res.is_err()); + }; + + let ok_with = |kind: FactorSourceKind, setup: fn(&mut SUT)| { + let mut sut = make(); + setup(&mut sut); + let res = + sut.validation_for_addition_of_factor_source_of_kind_to_list(kind, list()); + assert!(res.is_ok()); + }; + let ok = |kind: FactorSourceKind| { + ok_with(kind, |_| {}); + }; + + ok(LedgerHQHardwareWallet); + ok(ArculusCard); + ok(OffDeviceMnemonic); + + ok_with(Device, |sut| { + sut.add_factor_source_to_list(FactorSourceID::sample_ledger(), list()) + .unwrap(); + }); + ok_with(Passphrase, |sut| { + sut.add_factor_source_to_list(FactorSourceID::sample_device(), list()) + .unwrap(); + _ = sut.set_threshold(2); + }); + + not_ok(SecurityQuestions); + not_ok(TrustedContact); + } + } + + mod override_in_isolation { + use super::*; + + fn list() -> FactorListKind { + FactorListKind::Override + } + + #[test] + fn unsupported() { + // Arrange + let mut sut = make(); + + // Act + let res = sut.add_factor_source_to_list(sample(), list()); + + // Assert + assert_eq!( + res, + MutRes::forever_invalid( + ForeverInvalidReason::PrimaryCannotHavePasswordInOverrideList + ) + ); + } + + #[test] + fn valid_then_invalid_because_unsupported() { + // Arrange + let mut sut = make(); + sut.add_factor_source_to_list( + FactorSourceID::sample_device(), + FactorListKind::Threshold, + ) + .unwrap(); + sut.add_factor_source_to_list(FactorSourceID::sample_ledger(), list()) + .unwrap(); + sut.add_factor_source_to_list(FactorSourceID::sample_arculus(), list()) + .unwrap(); + + // Act + let res = sut.add_factor_source_to_list(sample(), list()); + + // Assert + assert_eq!( + res, + MutRes::forever_invalid( + ForeverInvalidReason::PrimaryCannotHavePasswordInOverrideList + ) + ); + } + + #[test] + fn validation_for_addition_of_factor_source_of_kind_to_list() { + use FactorSourceKind::*; + + let not_ok = |kind: FactorSourceKind| { + let sut = make(); + let res = + sut.validation_for_addition_of_factor_source_of_kind_to_list(kind, list()); + assert!(res.is_err()); + }; + + let ok_with = |kind: FactorSourceKind, setup: fn(&mut SUT)| { + let mut sut = make(); + setup(&mut sut); + let res = + sut.validation_for_addition_of_factor_source_of_kind_to_list(kind, list()); + assert!(res.is_ok()); + }; + let ok = |kind: FactorSourceKind| { + ok_with(kind, |_| {}); + }; + + ok(LedgerHQHardwareWallet); + ok(ArculusCard); + ok(OffDeviceMnemonic); + + ok_with(Device, |sut| { + sut.add_factor_source_to_list(FactorSourceID::sample_ledger(), list()) + .unwrap(); + }); + + not_ok(Passphrase); + + not_ok(SecurityQuestions); + not_ok(TrustedContact); + } + } + } + + #[cfg(test)] + mod ledger { + use super::*; + + fn sample() -> FactorSourceID { + FactorSourceID::sample_ledger() + } + + fn sample_other() -> FactorSourceID { + FactorSourceID::sample_ledger_other() + } + + #[test] + fn test_suite_prerequisite() { + assert_eq!(sample(), sample()); + assert_eq!(sample_other(), sample_other()); + assert_ne!(sample(), sample_other()); + } + + mod threshold_in_isolation { + use super::*; + fn list() -> FactorListKind { + FactorListKind::Threshold + } + + #[test] + fn duplicates_not_allowed() { + test_duplicates_not_allowed(make(), list(), sample()); + } + + #[test] + fn one_is_ok() { + // Arrange + let mut sut = make(); + + // Act + sut.add_factor_source_to_list(sample(), list()).unwrap(); + sut.set_threshold(1).unwrap(); + + // Assert + let expected = RoleWithFactorSourceIds::primary_with_factors(1, [sample()], []); + assert_eq!(sut.build().unwrap(), expected); + } + + #[test] + fn one_with_threshold_of_zero_is_err() { + // Arrange + let mut sut = make(); + + // Act + sut.add_factor_source_to_list(sample(), list()).unwrap(); + + // Assert + assert_eq!( + sut.build(), + SUT::RoleBuilderBuildResult::Err(RoleBuilderValidation::NotYetValid( + NotYetValidReason::PrimaryRoleWithThresholdCannotBeZeroWithFactors + )) + ); + } + + #[test] + fn two_different_is_ok() { + // Arrange + let mut sut = make(); + + // Act + sut.add_factor_source_to_list(sample(), list()).unwrap(); + sut.add_factor_source_to_list(sample_other(), list()) + .unwrap(); + sut.set_threshold(2).unwrap(); + + // Assert + let expected = RoleWithFactorSourceIds::primary_with_factors( + 2, + [sample(), sample_other()], + [], + ); + assert_eq!(sut.build().unwrap(), expected); + } + } + + mod override_in_isolation { + use super::*; + fn list() -> FactorListKind { + FactorListKind::Override + } + + #[test] + fn duplicates_not_allowed() { + test_duplicates_not_allowed(make(), list(), sample()); + } + + #[test] + fn one_is_ok() { + // Arrange + let mut sut = make(); + + // Act + sut.add_factor_source_to_list(sample(), list()).unwrap(); + + // Assert + let expected = RoleWithFactorSourceIds::primary_with_factors(0, [], [sample()]); + assert_eq!(sut.build().unwrap(), expected); + } + + #[test] + fn two_different_is_ok() { + // Arrange + let mut sut = make(); + + // Act + sut.add_factor_source_to_list(sample(), list()).unwrap(); + sut.add_factor_source_to_list(sample_other(), list()) + .unwrap(); + + // Assert + let expected = RoleWithFactorSourceIds::primary_with_factors( + 0, + [], + [sample(), sample_other()], + ); + assert_eq!(sut.build().unwrap(), expected); + } + } + } + + #[cfg(test)] + mod arculus { + use super::*; + + fn sample() -> FactorSourceID { + FactorSourceID::sample_arculus() + } + + fn sample_other() -> FactorSourceID { + FactorSourceID::sample_arculus_other() + } + + #[test] + fn test_suite_prerequisite() { + assert_eq!(sample(), sample()); + assert_eq!(sample_other(), sample_other()); + assert_ne!(sample(), sample_other()); + } + + mod threshold_in_isolation { + use super::*; + fn list() -> FactorListKind { + FactorListKind::Threshold + } + + #[test] + fn duplicates_not_allowed() { + test_duplicates_not_allowed(make(), list(), sample()); + } + + #[test] + fn one_is_ok() { + // Arrange + let mut sut = make(); + + // Act + sut.add_factor_source_to_list(sample(), list()).unwrap(); + sut.set_threshold(1).unwrap(); + + // Assert + let expected = RoleWithFactorSourceIds::primary_with_factors(1, [sample()], []); + assert_eq!(sut.build().unwrap(), expected); + } + + #[test] + fn two_different_is_ok() { + // Arrange + let mut sut = make(); + + // Act + sut.add_factor_source_to_list(sample(), list()).unwrap(); + sut.add_factor_source_to_list(sample_other(), list()) + .unwrap(); + sut.set_threshold(1).unwrap(); + + // Assert + let expected = RoleWithFactorSourceIds::primary_with_factors( + 1, + [sample(), sample_other()], + [], + ); + assert_eq!(sut.build().unwrap(), expected); + } + } + + mod override_in_isolation { + use super::*; + fn list() -> FactorListKind { + FactorListKind::Override + } + + #[test] + fn duplicates_not_allowed() { + test_duplicates_not_allowed(make(), list(), sample()); + } + + #[test] + fn one_is_ok() { + // Arrange + let mut sut = make(); + + // Act + sut.add_factor_source_to_list(sample(), list()).unwrap(); + + // Assert + let expected = RoleWithFactorSourceIds::primary_with_factors(0, [], [sample()]); + assert_eq!(sut.build().unwrap(), expected); + } + + #[test] + fn two_different_is_ok() { + // Arrange + let mut sut = make(); + + // Act + sut.add_factor_source_to_list(sample(), list()).unwrap(); + sut.add_factor_source_to_list(sample_other(), list()) + .unwrap(); + + // Assert + let expected = RoleWithFactorSourceIds::primary_with_factors( + 0, + [], + [sample(), sample_other()], + ); + assert_eq!(sut.build().unwrap(), expected); + } + } + } + + #[cfg(test)] + mod device_factor_source { + use super::*; + + fn sample() -> FactorSourceID { + FactorSourceID::sample_device() + } + + fn sample_other() -> FactorSourceID { + FactorSourceID::sample_device_other() + } + + #[test] + fn test_suite_prerequisite() { + assert_eq!(sample(), sample()); + assert_eq!(sample_other(), sample_other()); + assert_ne!(sample(), sample_other()); + } + + #[cfg(test)] + mod threshold_in_isolation { + use super::*; + + fn list() -> FactorListKind { + FactorListKind::Threshold + } + + #[test] + fn duplicates_not_allowed() { + test_duplicates_not_allowed(make(), list(), sample()) + } + + #[test] + fn one_is_ok() { + // Arrange + let mut sut = make(); + + // Act + sut.add_factor_source_to_list(sample(), list()).unwrap(); + sut.set_threshold(1).unwrap(); + + // Assert + let expected = RoleWithFactorSourceIds::primary_with_factors(1, [sample()], []); + assert_eq!(sut.build().unwrap(), expected); + } + + #[test] + fn two_different_is_err() { + // Arrange + let mut sut = make(); + + sut.add_factor_source_to_list(sample(), list()).unwrap(); + + // Act + let res = sut.add_factor_source_to_list(sample_other(), list()); + + // Assert + assert!(matches!( + res, + MutRes::Err(Validation::ForeverInvalid( + ForeverInvalidReason::PrimaryCannotHaveMultipleDevices + )) + )); + } + } + + mod override_in_isolation { + + use super::*; + + fn list() -> FactorListKind { + FactorListKind::Override + } + + #[test] + fn duplicates_not_allowed() { + test_duplicates_not_allowed(make(), list(), sample()) + } + + #[test] + fn one_is_ok() { + // Arrange + let mut sut = make(); + + // Act + sut.add_factor_source_to_list(sample(), list()).unwrap(); + + // Assert + let expected = RoleWithFactorSourceIds::primary_with_factors(0, [], [sample()]); + assert_eq!(sut.build().unwrap(), expected); + } + } + } +} From 9de466a4cfbb5f44d9bd8a4a5c95c796abf791d4 Mon Sep 17 00:00:00 2001 From: Alexander Cyon Date: Fri, 29 Nov 2024 06:47:37 +0100 Subject: [PATCH 13/33] re --- .../rules/src/roles/builder/roles_builder.rs | 1 - ...archical_deterministic_factor_instances.rs | 2 + crates/rules/src/roles/mod.rs | 2 + .../rules/src/roles/roles_with_factor_ids.rs | 197 ++++++++++++++---- .../src/roles/roles_with_factor_sources.rs | 133 +++++++++--- 5 files changed, 260 insertions(+), 75 deletions(-) create mode 100644 crates/rules/src/roles/general_role_with_hierarchical_deterministic_factor_instances.rs diff --git a/crates/rules/src/roles/builder/roles_builder.rs b/crates/rules/src/roles/builder/roles_builder.rs index e929010f..ccb08be1 100644 --- a/crates/rules/src/roles/builder/roles_builder.rs +++ b/crates/rules/src/roles/builder/roles_builder.rs @@ -214,7 +214,6 @@ impl RoleBuilder { pub(crate) fn build(self) -> Self::RoleBuilderBuildResult { self.validate().map(|_| { RoleWithFactorSourceIds::with_factors( - // self.role(), self.get_threshold(), self.get_threshold_factors().clone(), self.get_override_factors().clone(), diff --git a/crates/rules/src/roles/general_role_with_hierarchical_deterministic_factor_instances.rs b/crates/rules/src/roles/general_role_with_hierarchical_deterministic_factor_instances.rs new file mode 100644 index 00000000..36e08229 --- /dev/null +++ b/crates/rules/src/roles/general_role_with_hierarchical_deterministic_factor_instances.rs @@ -0,0 +1,2 @@ + +pub struct GeneralRoleWithHierarchicalDeterministicFactorInstances {} diff --git a/crates/rules/src/roles/mod.rs b/crates/rules/src/roles/mod.rs index ffd64daa..90812439 100644 --- a/crates/rules/src/roles/mod.rs +++ b/crates/rules/src/roles/mod.rs @@ -1,11 +1,13 @@ mod abstract_role_builder_or_built; mod builder; +mod general_role_with_hierarchical_deterministic_factor_instances; mod role_with_factor_instances; mod roles_with_factor_ids; mod roles_with_factor_sources; pub(crate) use abstract_role_builder_or_built::*; pub use builder::*; +pub use general_role_with_hierarchical_deterministic_factor_instances::*; pub(crate) use role_with_factor_instances::*; pub use roles_with_factor_ids::*; pub(crate) use roles_with_factor_sources::*; diff --git a/crates/rules/src/roles/roles_with_factor_ids.rs b/crates/rules/src/roles/roles_with_factor_ids.rs index b7cd00ae..02aff384 100644 --- a/crates/rules/src/roles/roles_with_factor_ids.rs +++ b/crates/rules/src/roles/roles_with_factor_ids.rs @@ -22,42 +22,74 @@ impl PrimaryRoleWithFactorSourceIds { } } -impl RecoveryRoleWithFactorSourceIds { - /// Config MFA 1.1 - pub fn sample_recovery() -> Self { +impl HasSampleValues for PrimaryRoleWithFactorSourceIds { + fn sample() -> Self { + Self::sample_primary() + } + + fn sample_other() -> Self { let mut builder = RoleBuilder::new(); builder - .add_factor_source_to_list(FactorSourceID::sample_device(), FactorListKind::Override) + .add_factor_source_to_list(FactorSourceID::sample_device(), FactorListKind::Threshold) .unwrap(); builder - .add_factor_source_to_list(FactorSourceID::sample_ledger(), FactorListKind::Override) + .add_factor_source_to_list(FactorSourceID::sample_ledger(), FactorListKind::Threshold) .unwrap(); + builder.set_threshold(1).unwrap(); builder.build().unwrap() } } -impl HasSampleValues for PrimaryRoleWithFactorSourceIds { +impl HasSampleValues for ConfirmationRoleWithFactorSourceIds { + /// Config MFA 1.1 fn sample() -> Self { - Self::sample_primary() + let mut builder = RoleBuilder::new(); + builder + .add_factor_source_to_list(FactorSourceID::sample_password(), FactorListKind::Override) + .unwrap(); + builder.build().unwrap() } + /// Config MFA 2.1 fn sample_other() -> Self { let mut builder = RoleBuilder::new(); builder - .add_factor_source_to_list(FactorSourceID::sample_device(), FactorListKind::Threshold) + .add_factor_source_to_list(FactorSourceID::sample_device(), FactorListKind::Override) + .unwrap(); + builder.build().unwrap() + } +} +impl HasSampleValues for RecoveryRoleWithFactorSourceIds { + /// Config MFA 1.1 + fn sample() -> Self { + let mut builder = RoleBuilder::new(); + builder + .add_factor_source_to_list(FactorSourceID::sample_device(), FactorListKind::Override) .unwrap(); builder - .add_factor_source_to_list(FactorSourceID::sample_ledger(), FactorListKind::Threshold) + .add_factor_source_to_list(FactorSourceID::sample_ledger(), FactorListKind::Override) .unwrap(); - builder.set_threshold(1).unwrap(); + builder.build().unwrap() + } + + /// Config MFA 3.3 + fn sample_other() -> Self { + let mut builder = RoleBuilder::new(); + builder + .add_factor_source_to_list( + FactorSourceID::sample_ledger_other(), + FactorListKind::Override, + ) + .unwrap(); + builder.build().unwrap() } } #[cfg(test)] -mod tests { +mod primary_tests { use super::*; @@ -122,34 +154,115 @@ mod tests { } } -// #[test] -// fn assert_json_sample_recovery() { -// let sut = SUT::sample_recovery(); -// assert_eq_after_json_roundtrip( -// &sut, -// r#" -// { -// "role": "recovery", -// "threshold": 0, -// "threshold_factors": [], -// "override_factors": [ -// { -// "discriminator": "fromHash", -// "fromHash": { -// "kind": "device", -// "body": "f1a93d324dd0f2bff89963ab81ed6e0c2ee7e18c0827dc1d3576b2d9f26bbd0a" -// } -// }, -// { -// "discriminator": "fromHash", -// "fromHash": { -// "kind": "ledgerHQHardwareWallet", -// "body": "ab59987eedd181fe98e512c1ba0f5ff059f11b5c7c56f15614dcc9fe03fec58b" -// } -// } -// ] -// } -// "#, -// ); -// } -// } +#[cfg(test)] +mod recovery_tests { + + use super::*; + + #[allow(clippy::upper_case_acronyms)] + type SUT = RecoveryRoleWithFactorSourceIds; + + #[test] + fn equality() { + assert_eq!(SUT::sample(), SUT::sample()); + assert_eq!(SUT::sample_other(), SUT::sample_other()); + } + + #[test] + fn inequality() { + assert_ne!(SUT::sample(), SUT::sample_other()); + } + + #[test] + fn get_all_factors() { + let sut = SUT::sample(); + let factors = sut.all_factors(); + assert_eq!( + factors.len(), + sut.get_override_factors().len() + sut.get_threshold_factors().len() + ); + } + + #[test] + fn assert_json() { + let sut = SUT::sample(); + assert_eq_after_json_roundtrip( + &sut, + r#" + { + "threshold": 0, + "threshold_factors": [], + "override_factors": [ + { + "discriminator": "fromHash", + "fromHash": { + "kind": "device", + "body": "f1a93d324dd0f2bff89963ab81ed6e0c2ee7e18c0827dc1d3576b2d9f26bbd0a" + } + }, + { + "discriminator": "fromHash", + "fromHash": { + "kind": "ledgerHQHardwareWallet", + "body": "ab59987eedd181fe98e512c1ba0f5ff059f11b5c7c56f15614dcc9fe03fec58b" + } + } + ] + } + "#, + ); + } +} + +#[cfg(test)] +mod confirmation_tests { + + use super::*; + + #[allow(clippy::upper_case_acronyms)] + type SUT = ConfirmationRoleWithFactorSourceIds; + + #[test] + fn equality() { + assert_eq!(SUT::sample(), SUT::sample()); + assert_eq!(SUT::sample_other(), SUT::sample_other()); + } + + #[test] + fn inequality() { + assert_ne!(SUT::sample(), SUT::sample_other()); + } + + #[test] + fn get_all_factors() { + let sut = SUT::sample(); + let factors = sut.all_factors(); + assert_eq!( + factors.len(), + sut.get_override_factors().len() + sut.get_threshold_factors().len() + ); + } + + #[test] + fn assert_json() { + let sut = SUT::sample(); + assert_eq_after_json_roundtrip( + &sut, + r#" + { + "threshold": 0, + "threshold_factors": [], + "override_factors": [ + { + "discriminator": "fromHash", + "fromHash": { + "kind": "passphrase", + "body": "181ab662e19fac3ad9f08d5c673b286d4a5ed9cd3762356dc9831dc42427c1b9" + } + } + ] + } + "#, + ); + } +} diff --git a/crates/rules/src/roles/roles_with_factor_sources.rs b/crates/rules/src/roles/roles_with_factor_sources.rs index 101f6c0d..b7304850 100644 --- a/crates/rules/src/roles/roles_with_factor_sources.rs +++ b/crates/rules/src/roles/roles_with_factor_sources.rs @@ -1,6 +1,9 @@ use crate::prelude::*; pub(crate) type RoleWithFactorSources = AbstractBuiltRoleWithFactor; +pub(crate) type PrimaryRoleWithFactorSources = RoleWithFactorSources<{ ROLE_PRIMARY }>; +pub(crate) type RecoveryRoleWithFactorSources = RoleWithFactorSources<{ ROLE_RECOVERY }>; +pub(crate) type ConfirmationRoleWithFactorSources = RoleWithFactorSources<{ ROLE_CONFIRMATION }>; impl RoleWithFactorSources { pub fn new( @@ -31,35 +34,101 @@ impl RoleWithFactorSources { } } -// impl HasSampleValues for RoleWithFactorSources { -// fn sample() -> Self { -// let ids = RoleWithFactorSourceIds::sample(); -// let factor_sources = FactorSources::sample_values_all(); -// Self::new(ids, &factor_sources).unwrap() -// } - -// fn sample_other() -> Self { -// let ids = RoleWithFactorSourceIds::sample_other(); -// let factor_sources = FactorSources::sample_values_all(); -// Self::new(ids, &factor_sources).unwrap() -// } -// } - -// #[cfg(test)] -// mod tests { -// use super::*; - -// #[allow(clippy::upper_case_acronyms)] -// type SUT = RoleWithFactorSources; - -// #[test] -// fn equality() { -// assert_eq!(SUT::sample(), SUT::sample()); -// assert_eq!(SUT::sample_other(), SUT::sample_other()); -// } - -// #[test] -// fn inequality() { -// assert_ne!(SUT::sample(), SUT::sample_other()); -// } -// } +impl HasSampleValues for PrimaryRoleWithFactorSources { + fn sample() -> Self { + let ids = PrimaryRoleWithFactorSourceIds::sample(); + let factor_sources = FactorSources::sample_values_all(); + Self::new(ids, &factor_sources).unwrap() + } + + fn sample_other() -> Self { + let ids = PrimaryRoleWithFactorSourceIds::sample_other(); + let factor_sources = FactorSources::sample_values_all(); + Self::new(ids, &factor_sources).unwrap() + } +} + +impl HasSampleValues for RecoveryRoleWithFactorSources { + fn sample() -> Self { + let ids = RecoveryRoleWithFactorSourceIds::sample(); + let factor_sources = FactorSources::sample_values_all(); + Self::new(ids, &factor_sources).unwrap() + } + + fn sample_other() -> Self { + let ids = RecoveryRoleWithFactorSourceIds::sample_other(); + let factor_sources = FactorSources::sample_values_all(); + Self::new(ids, &factor_sources).unwrap() + } +} + +impl HasSampleValues for ConfirmationRoleWithFactorSources { + fn sample() -> Self { + let ids = ConfirmationRoleWithFactorSourceIds::sample(); + let factor_sources = FactorSources::sample_values_all(); + Self::new(ids, &factor_sources).unwrap() + } + + fn sample_other() -> Self { + let ids = ConfirmationRoleWithFactorSourceIds::sample_other(); + let factor_sources = FactorSources::sample_values_all(); + Self::new(ids, &factor_sources).unwrap() + } +} + +#[cfg(test)] +mod primary_tests { + use super::*; + + #[allow(clippy::upper_case_acronyms)] + type SUT = PrimaryRoleWithFactorSources; + + #[test] + fn equality() { + assert_eq!(SUT::sample(), SUT::sample()); + assert_eq!(SUT::sample_other(), SUT::sample_other()); + } + + #[test] + fn inequality() { + assert_ne!(SUT::sample(), SUT::sample_other()); + } +} + +#[cfg(test)] +mod recovery_tests { + use super::*; + + #[allow(clippy::upper_case_acronyms)] + type SUT = RecoveryRoleWithFactorSources; + + #[test] + fn equality() { + assert_eq!(SUT::sample(), SUT::sample()); + assert_eq!(SUT::sample_other(), SUT::sample_other()); + } + + #[test] + fn inequality() { + assert_ne!(SUT::sample(), SUT::sample_other()); + } +} + +#[cfg(test)] +mod confirmation_tests { + use super::*; + + #[allow(clippy::upper_case_acronyms)] + type SUT = ConfirmationRoleWithFactorSources; + + #[test] + fn equality() { + assert_eq!(SUT::sample(), SUT::sample()); + assert_eq!(SUT::sample_other(), SUT::sample_other()); + } + + #[test] + fn inequality() { + assert_ne!(SUT::sample(), SUT::sample_other()); + } +} From 7818f76f27bd9de6e237ec7433561095b8038431 Mon Sep 17 00:00:00 2001 From: Alexander Cyon Date: Fri, 29 Nov 2024 07:46:28 +0100 Subject: [PATCH 14/33] safer API. --- crates/rules/src/lib.rs | 1 + .../src/matrices/builder/matrix_builder.rs | 28 +- .../confirmation_roles_builder_unit_tests.rs | 333 +++ crates/rules/src/roles/builder/mod.rs | 3 + .../primary_roles_builder_unit_tests.rs | 876 ++++++++ .../recovery_roles_builder_unit_tests.rs | 400 ++++ .../rules/src/roles/builder/roles_builder.rs | 287 ++- .../roles/builder/roles_builder_unit_tests.rs | 1780 ----------------- ...archical_deterministic_factor_instances.rs | 1 - .../rules/src/roles/roles_with_factor_ids.rs | 21 +- 10 files changed, 1874 insertions(+), 1856 deletions(-) create mode 100644 crates/rules/src/roles/builder/confirmation_roles_builder_unit_tests.rs create mode 100644 crates/rules/src/roles/builder/primary_roles_builder_unit_tests.rs create mode 100644 crates/rules/src/roles/builder/recovery_roles_builder_unit_tests.rs diff --git a/crates/rules/src/lib.rs b/crates/rules/src/lib.rs index f3cc522e..de09646e 100644 --- a/crates/rules/src/lib.rs +++ b/crates/rules/src/lib.rs @@ -1,5 +1,6 @@ #![allow(incomplete_features)] #![feature(inherent_associated_types)] +#![feature(generic_const_exprs)] mod matrices; mod move_to_sargon; diff --git a/crates/rules/src/matrices/builder/matrix_builder.rs b/crates/rules/src/matrices/builder/matrix_builder.rs index d3a19122..7f1de36e 100644 --- a/crates/rules/src/matrices/builder/matrix_builder.rs +++ b/crates/rules/src/matrices/builder/matrix_builder.rs @@ -59,10 +59,7 @@ impl MatrixBuilder { factor_source_kind: FactorSourceKind, ) -> RoleBuilderMutateResult { self.primary_role - .validation_for_addition_of_factor_source_of_kind_to_list( - factor_source_kind, - FactorListKind::Threshold, - ) + .validation_for_addition_of_factor_source_of_kind_to_threshold(factor_source_kind) } pub fn validation_for_addition_of_factor_source_of_kind_to_primary_override( @@ -70,10 +67,7 @@ impl MatrixBuilder { factor_source_kind: FactorSourceKind, ) -> RoleBuilderMutateResult { self.primary_role - .validation_for_addition_of_factor_source_of_kind_to_list( - factor_source_kind, - FactorListKind::Override, - ) + .validation_for_addition_of_factor_source_of_kind_to_override(factor_source_kind) } pub fn validation_for_addition_of_factor_source_to_primary_threshold_for_each( @@ -103,10 +97,7 @@ impl MatrixBuilder { factor_source_kind: FactorSourceKind, ) -> RoleBuilderMutateResult { self.recovery_role - .validation_for_addition_of_factor_source_of_kind_to_list( - factor_source_kind, - FactorListKind::Override, - ) + .validation_for_addition_of_factor_source_of_kind_to_override(factor_source_kind) } pub fn validation_for_addition_of_factor_source_to_recovery_override_for_each( @@ -125,10 +116,7 @@ impl MatrixBuilder { factor_source_kind: FactorSourceKind, ) -> RoleBuilderMutateResult { self.confirmation_role - .validation_for_addition_of_factor_source_of_kind_to_list( - factor_source_kind, - FactorListKind::Override, - ) + .validation_for_addition_of_factor_source_of_kind_to_override(factor_source_kind) } pub fn validation_for_addition_of_factor_source_to_confirmation_override_for_each( @@ -167,7 +155,7 @@ impl MatrixBuilder { factor_source_id: FactorSourceID, ) -> MatrixBuilderMutateResult { self.primary_role - .add_factor_source_to_list(factor_source_id, FactorListKind::Threshold) + .add_factor_source_to_threshold(factor_source_id) .into_matrix_err(RoleKind::Primary) } @@ -177,7 +165,7 @@ impl MatrixBuilder { factor_source_id: FactorSourceID, ) -> MatrixBuilderMutateResult { self.primary_role - .add_factor_source_to_list(factor_source_id, FactorListKind::Override) + .add_factor_source_to_override(factor_source_id) .into_matrix_err(RoleKind::Primary) } @@ -186,7 +174,7 @@ impl MatrixBuilder { factor_source_id: FactorSourceID, ) -> MatrixBuilderMutateResult { self.recovery_role - .add_factor_source_to_list(factor_source_id, FactorListKind::Override) + .add_factor_source_to_override(factor_source_id) .into_matrix_err(RoleKind::Recovery) } @@ -195,7 +183,7 @@ impl MatrixBuilder { factor_source_id: FactorSourceID, ) -> MatrixBuilderMutateResult { self.confirmation_role - .add_factor_source_to_list(factor_source_id, FactorListKind::Override) + .add_factor_source_to_override(factor_source_id) .into_matrix_err(RoleKind::Confirmation) } diff --git a/crates/rules/src/roles/builder/confirmation_roles_builder_unit_tests.rs b/crates/rules/src/roles/builder/confirmation_roles_builder_unit_tests.rs new file mode 100644 index 00000000..8d113044 --- /dev/null +++ b/crates/rules/src/roles/builder/confirmation_roles_builder_unit_tests.rs @@ -0,0 +1,333 @@ +#![cfg(test)] + +use crate::prelude::*; + +#[allow(clippy::upper_case_acronyms)] + +type MutRes = RoleBuilderMutateResult; + +#[test] +fn new_builder_confirmation() { + assert_eq!( + ConfirmationRoleBuilder::new().role(), + RoleKind::Confirmation + ); +} + +#[test] +fn empty_is_err_confirmation() { + let sut = ConfirmationRoleBuilder::new(); + let res = sut.build(); + assert_eq!( + res, + ConfirmationRoleBuilder::RoleBuilderBuildResult::not_yet_valid( + NotYetValidReason::RoleMustHaveAtLeastOneFactor + ) + ); +} + +#[allow(clippy::upper_case_acronyms)] +type SUT = ConfirmationRoleBuilder; + +fn make() -> SUT { + SUT::new() +} + +#[test] +fn validation_for_addition_of_factor_source_of_kind_to_list() { + use FactorSourceKind::*; + let sut = make(); + let not_ok = |kind: FactorSourceKind| { + let res = sut.validation_for_addition_of_factor_source_of_kind_to_override(kind); + assert!(res.is_err()); + }; + let ok = |kind: FactorSourceKind| { + let res = sut.validation_for_addition_of_factor_source_of_kind_to_override(kind); + assert!(res.is_ok()); + }; + ok(Device); + ok(LedgerHQHardwareWallet); + ok(ArculusCard); + ok(SecurityQuestions); + ok(Passphrase); + ok(OffDeviceMnemonic); + not_ok(TrustedContact); +} + +mod device_in_isolation { + use super::*; + + fn sample() -> FactorSourceID { + FactorSourceID::sample_device() + } + + fn sample_other() -> FactorSourceID { + FactorSourceID::sample_device_other() + } + + #[test] + fn set_threshold_is_unsupported() { + let mut sut = make(); + assert_eq!( + sut.set_threshold(1), + MutRes::basic_violation(BasicViolation::ConfirmationCannotSetThreshold) + ); + } + + #[test] + fn allowed_as_first_and_only() { + // Arrange + let mut sut = make(); + + // Act + sut.add_factor_source(sample()).unwrap(); + + // Assert + assert_eq!( + sut.build().unwrap(), + RoleWithFactorSourceIds::confirmation_with_factors([sample()]) + ); + } + + #[test] + fn two_of_same_kind_allowed() { + // TODO: Ask Matt + // Arrange + let mut sut = make(); + + // Act + sut.add_factor_source(sample()).unwrap(); + sut.add_factor_source(sample_other()).unwrap(); + + // Assert + let built = sut.build().unwrap(); + assert!(built.get_threshold_factors().is_empty()); + assert_eq!( + built, + RoleWithFactorSourceIds::confirmation_with_factors([sample(), sample_other()]) + ); + } +} + +mod ledger_in_isolation { + use super::*; + + fn sample() -> FactorSourceID { + FactorSourceID::sample_ledger() + } + + fn sample_other() -> FactorSourceID { + FactorSourceID::sample_ledger_other() + } + + #[test] + fn allowed_as_first_and_only() { + // Arrange + let mut sut = make(); + + // Act + sut.add_factor_source(sample()).unwrap(); + + // Assert + assert_eq!( + sut.build().unwrap(), + RoleWithFactorSourceIds::confirmation_with_factors([sample(),]) + ); + } + + #[test] + fn two_of_same_kind_allowed() { + // TODO: Ask Matt + // Arrange + let mut sut = make(); + + // Act + sut.add_factor_source(sample()).unwrap(); + sut.add_factor_source(sample_other()).unwrap(); + + // Assert + assert_eq!( + sut.build().unwrap(), + RoleWithFactorSourceIds::confirmation_with_factors([sample(), sample_other()]) + ); + } +} + +mod arculus_in_isolation { + use super::*; + + fn sample() -> FactorSourceID { + FactorSourceID::sample_arculus() + } + + fn sample_other() -> FactorSourceID { + FactorSourceID::sample_arculus_other() + } + + #[test] + fn allowed_as_first_and_only() { + // Arrange + let mut sut = make(); + + // Act + sut.add_factor_source(sample()).unwrap(); + + // Assert + assert_eq!( + sut.build().unwrap(), + RoleWithFactorSourceIds::confirmation_with_factors([sample(),]) + ); + } + + #[test] + fn two_of_same_kind_allowed() { + // TODO: Ask Matt + // Arrange + let mut sut = make(); + + // Act + sut.add_factor_source(sample()).unwrap(); + sut.add_factor_source(sample_other()).unwrap(); + + // Assert + assert_eq!( + sut.build().unwrap(), + RoleWithFactorSourceIds::confirmation_with_factors([sample(), sample_other()]) + ); + } +} + +mod passphrase_in_isolation { + use super::*; + + fn sample() -> FactorSourceID { + FactorSourceID::sample_passphrase() + } + + fn sample_other() -> FactorSourceID { + FactorSourceID::sample_passphrase_other() + } + + #[test] + fn allowed_as_first_and_only() { + // Arrange + let mut sut = make(); + + // Act + sut.add_factor_source(sample()).unwrap(); + + // Assert + assert_eq!( + sut.build().unwrap(), + RoleWithFactorSourceIds::confirmation_with_factors([sample(),]) + ); + } + + #[test] + fn two_of_same_kind_allowed() { + // TODO: Ask Matt + // Arrange + let mut sut = make(); + + // Act + sut.add_factor_source(sample()).unwrap(); + sut.add_factor_source(sample_other()).unwrap(); + + // Assert + assert_eq!( + sut.build().unwrap(), + RoleWithFactorSourceIds::confirmation_with_factors([sample(), sample_other()]) + ); + } +} + +mod trusted_contact_in_isolation { + use super::*; + + fn sample() -> FactorSourceID { + FactorSourceID::sample_trusted_contact() + } + + #[test] + fn unsupported() { + // Arrange + let mut sut = make(); + + // Act + let res = sut.add_factor_source(sample()); + + // Assert + assert_eq!( + res, + MutRes::forever_invalid( + ForeverInvalidReason::ConfirmationRoleTrustedContactNotSupported + ) + ); + } + + #[test] + fn valid_then_invalid_because_unsupported() { + // Arrange + let mut sut = make(); + + sut.add_factor_source(FactorSourceID::sample_ledger()) + .unwrap(); + sut.add_factor_source(FactorSourceID::sample_arculus()) + .unwrap(); + + // Act + let res = sut.add_factor_source(sample()); + + // Assert + assert_eq!( + res, + MutRes::forever_invalid( + ForeverInvalidReason::ConfirmationRoleTrustedContactNotSupported + ) + ); + } +} + +mod password_in_isolation { + use super::*; + + fn sample() -> FactorSourceID { + FactorSourceID::sample_password() + } + + fn sample_other() -> FactorSourceID { + FactorSourceID::sample_password_other() + } + + #[test] + fn allowed_as_first_and_only() { + // Arrange + let mut sut = make(); + + // Act + sut.add_factor_source(sample()).unwrap(); + + // Assert + assert_eq!( + sut.build().unwrap(), + RoleWithFactorSourceIds::confirmation_with_factors([sample(),]) + ); + } + + #[test] + fn two_of_same_kind_allowed() { + // TODO: Ask Matt + // Arrange + let mut sut = make(); + + // Act + sut.add_factor_source(sample()).unwrap(); + sut.add_factor_source(sample_other()).unwrap(); + + // Assert + assert_eq!( + sut.build().unwrap(), + RoleWithFactorSourceIds::confirmation_with_factors([sample(), sample_other()]) + ); + } +} diff --git a/crates/rules/src/roles/builder/mod.rs b/crates/rules/src/roles/builder/mod.rs index 9a1e546e..af9dc07f 100644 --- a/crates/rules/src/roles/builder/mod.rs +++ b/crates/rules/src/roles/builder/mod.rs @@ -1,3 +1,6 @@ +mod confirmation_roles_builder_unit_tests; +mod primary_roles_builder_unit_tests; +mod recovery_roles_builder_unit_tests; mod roles_builder; mod roles_builder_unit_tests; diff --git a/crates/rules/src/roles/builder/primary_roles_builder_unit_tests.rs b/crates/rules/src/roles/builder/primary_roles_builder_unit_tests.rs new file mode 100644 index 00000000..d55de7db --- /dev/null +++ b/crates/rules/src/roles/builder/primary_roles_builder_unit_tests.rs @@ -0,0 +1,876 @@ +#![cfg(test)] + +use crate::prelude::*; + +use NotYetValidReason::*; +type Validation = RoleBuilderValidation; + +#[allow(clippy::upper_case_acronyms)] + +type MutRes = RoleBuilderMutateResult; + +#[test] +fn new_builder_primary() { + assert_eq!(PrimaryRoleBuilder::new().role(), RoleKind::Primary); +} + +#[test] +fn empty_is_err_primary() { + let sut = PrimaryRoleBuilder::new(); + let res = sut.build(); + assert_eq!( + res, + PrimaryRoleBuilder::RoleBuilderBuildResult::not_yet_valid( + NotYetValidReason::RoleMustHaveAtLeastOneFactor + ) + ); +} + +mod primary_test_helper_functions { + + use super::*; + + #[allow(clippy::upper_case_acronyms)] + type SUT = PrimaryRoleBuilder; + + #[test] + fn factor_sources_not_of_kind_to_list_of_kind_in_override() { + let mut sut = SUT::new(); + sut.add_factor_source_to_override(FactorSourceID::sample_device()) + .unwrap(); + sut.add_factor_source_to_override(FactorSourceID::sample_ledger()) + .unwrap(); + sut.add_factor_source_to_override(FactorSourceID::sample_arculus()) + .unwrap(); + + let xs = sut.factor_sources_not_of_kind_to_list_of_kind( + FactorSourceKind::Device, + FactorListKind::Override, + ); + assert_eq!( + xs, + vec![ + FactorSourceID::sample_ledger(), + FactorSourceID::sample_arculus() + ] + ); + + let xs = sut.factor_sources_not_of_kind_to_list_of_kind( + FactorSourceKind::LedgerHQHardwareWallet, + FactorListKind::Override, + ); + assert_eq!( + xs, + vec![ + FactorSourceID::sample_device(), + FactorSourceID::sample_arculus() + ] + ); + + let xs = sut.factor_sources_not_of_kind_to_list_of_kind( + FactorSourceKind::ArculusCard, + FactorListKind::Override, + ); + assert_eq!( + xs, + vec![ + FactorSourceID::sample_device(), + FactorSourceID::sample_ledger() + ] + ); + } + + #[test] + fn factor_sources_not_of_kind_to_list_of_kind_in_threshold() { + let mut sut = SUT::new(); + sut.add_factor_source_to_threshold(FactorSourceID::sample_device()) + .unwrap(); + sut.add_factor_source_to_threshold(FactorSourceID::sample_ledger()) + .unwrap(); + sut.add_factor_source_to_threshold(FactorSourceID::sample_arculus()) + .unwrap(); + + let xs = sut.factor_sources_not_of_kind_to_list_of_kind( + FactorSourceKind::Device, + FactorListKind::Threshold, + ); + assert_eq!( + xs, + vec![ + FactorSourceID::sample_ledger(), + FactorSourceID::sample_arculus() + ] + ); + + let xs = sut.factor_sources_not_of_kind_to_list_of_kind( + FactorSourceKind::LedgerHQHardwareWallet, + FactorListKind::Threshold, + ); + assert_eq!( + xs, + vec![ + FactorSourceID::sample_device(), + FactorSourceID::sample_arculus() + ] + ); + + let xs = sut.factor_sources_not_of_kind_to_list_of_kind( + FactorSourceKind::ArculusCard, + FactorListKind::Threshold, + ); + assert_eq!( + xs, + vec![ + FactorSourceID::sample_device(), + FactorSourceID::sample_ledger() + ] + ); + } +} + +#[allow(clippy::upper_case_acronyms)] +type SUT = PrimaryRoleBuilder; + +fn make() -> SUT { + SUT::new() +} + +#[cfg(test)] +mod threshold_suite { + use super::*; + + fn sample() -> FactorSourceID { + FactorSourceID::sample_device() + } + + fn sample_other() -> FactorSourceID { + FactorSourceID::sample_ledger() + } + + fn sample_third() -> FactorSourceID { + FactorSourceID::sample_arculus() + } + + #[test] + fn remove_lowers_threshold_from_1_to_0() { + let mut sut = make(); + let fs = sample(); + sut.add_factor_source_to_threshold(fs).unwrap(); + sut.set_threshold(1).unwrap(); + assert_eq!(sut.get_threshold(), 1); + assert_eq!( + sut.remove_factor_source(&fs), + Err(Validation::NotYetValid(RoleMustHaveAtLeastOneFactor)) + ); + assert_eq!(sut.get_threshold(), 0); + } + + #[test] + fn remove_lowers_threshold_from_3_to_1() { + let mut sut = make(); + let fs0 = sample(); + let fs1 = sample_other(); + sut.add_factor_source_to_threshold(fs0).unwrap(); + sut.add_factor_source_to_threshold(fs1).unwrap(); + sut.add_factor_source_to_threshold(FactorSourceID::sample_arculus_other()) + .unwrap(); + sut.set_threshold(3).unwrap(); + assert_eq!(sut.get_threshold(), 3); + sut.remove_factor_source(&fs0).unwrap(); + sut.remove_factor_source(&fs1).unwrap(); + assert_eq!(sut.get_threshold(), 1); + } + + #[test] + fn remove_from_override_does_not_change_threshold() { + let mut sut = make(); + sut.add_factor_source_to_threshold(sample()).unwrap(); + sut.add_factor_source_to_threshold(sample_other()).unwrap(); + let fs = FactorSourceID::sample_arculus_other(); + sut.add_factor_source_to_override(fs).unwrap(); + sut.set_threshold(2).unwrap(); + assert_eq!(sut.get_threshold(), 2); + sut.remove_factor_source(&fs).unwrap(); + assert_eq!(sut.get_threshold(), 2); + + let built = sut.build().unwrap(); + assert_eq!(built.get_threshold(), 2); + + assert_eq!(built.role(), RoleKind::Primary); + + assert_eq!( + built.get_threshold_factors(), + &vec![sample(), sample_other()] + ); + + assert_eq!(built.get_override_factors(), &Vec::new()); + } + + #[test] + fn one_factor_then_set_threshold_to_one_is_ok() { + // Arrange + let mut sut = make(); + + // Act + sut.add_factor_source_to_threshold(sample_other()).unwrap(); + sut.set_threshold(1).unwrap(); + + // Assert + let expected = RoleWithFactorSourceIds::primary_with_factors(1, [sample_other()], []); + assert_eq!(sut.build().unwrap(), expected); + } + + #[test] + fn zero_factor_then_set_threshold_to_one_is_not_yet_valid_then_add_one_factor_is_ok() { + // Arrange + let mut sut = make(); + + // Act + assert_eq!( + sut.set_threshold(1), + Err(Validation::NotYetValid( + ThresholdHigherThanThresholdFactorsLen + )) + ); + sut.add_factor_source_to_threshold(sample_other()).unwrap(); + + // Assert + let expected = RoleWithFactorSourceIds::primary_with_factors(1, [sample_other()], []); + assert_eq!(sut.build().unwrap(), expected); + } + + #[test] + fn zero_factor_then_set_threshold_to_two_is_not_yet_valid_then_add_two_factor_is_ok() { + // Arrange + let mut sut = make(); + + // Act + assert_eq!( + sut.set_threshold(2), + Err(Validation::NotYetValid( + ThresholdHigherThanThresholdFactorsLen + )) + ); + sut.add_factor_source_to_threshold(sample()).unwrap(); + + sut.add_factor_source_to_threshold(sample_other()).unwrap(); + + // Assert + let expected = + RoleWithFactorSourceIds::primary_with_factors(2, [sample(), sample_other()], []); + assert_eq!(sut.build().unwrap(), expected); + } + + #[test] + fn add_two_factors_then_set_threshold_to_two_is_ok() { + // Arrange + let mut sut = make(); + + sut.add_factor_source_to_threshold(sample()).unwrap(); + sut.add_factor_source_to_threshold(sample_other()).unwrap(); + + // Act + assert_eq!(sut.set_threshold(2), Ok(())); + + // Assert + let expected = + RoleWithFactorSourceIds::primary_with_factors(2, [sample(), sample_other()], []); + assert_eq!(sut.build().unwrap(), expected); + } + + #[test] + fn add_two_factors_then_set_threshold_to_three_is_not_yet_valid_then_add_third_factor_is_ok() { + // Arrange + let mut sut = make(); + + sut.add_factor_source_to_threshold(sample()).unwrap(); + sut.add_factor_source_to_threshold(sample_other()).unwrap(); + + // Act + assert_eq!( + sut.set_threshold(3), + Err(Validation::NotYetValid( + ThresholdHigherThanThresholdFactorsLen + )) + ); + + sut.add_factor_source_to_threshold(sample_third()).unwrap(); + + // Assert + let expected = RoleWithFactorSourceIds::primary_with_factors( + 3, + [sample(), sample_other(), sample_third()], + [], + ); + assert_eq!(sut.build().unwrap(), expected); + } + + #[test] + fn one_factors_set_threshold_of_one_is_ok() { + // Arrange + let mut sut = make(); + + // Act + sut.add_factor_source_to_threshold(sample_other()).unwrap(); + sut.set_threshold(1).unwrap(); + + // Assert + let expected = RoleWithFactorSourceIds::primary_with_factors(1, [sample_other()], []); + assert_eq!(sut.build().unwrap(), expected); + } + + #[test] + fn one_override_factors_set_threshold_to_one_is_not_yet_valid() { + // Arrange + let mut sut = make(); + + // Act + sut.add_factor_source_to_override(sample_other()).unwrap(); + assert_eq!( + sut.set_threshold(1), + Err(Validation::NotYetValid( + ThresholdHigherThanThresholdFactorsLen + )) + ); + + // Assert + + assert_eq!( + sut.build(), + Err(Validation::NotYetValid( + ThresholdHigherThanThresholdFactorsLen + )) + ); + } + + #[test] + fn validation_for_addition_of_factor_source_for_each_before_after_adding_a_factor() { + let mut sut = make(); + let fs0 = FactorSourceID::sample_ledger(); + let fs1 = FactorSourceID::sample_password(); + let fs2 = FactorSourceID::sample_arculus(); + let xs = sut.validation_for_addition_of_factor_source_for_each( + FactorListKind::Threshold, + &IndexSet::from_iter([fs0, fs1, fs2]), + ); + assert_eq!( + xs.into_iter().collect::>(), + vec![ + FactorSourceInRoleBuilderValidationStatus::ok(RoleKind::Primary, fs0,), + FactorSourceInRoleBuilderValidationStatus::not_yet_valid( + RoleKind::Primary, + fs1, + NotYetValidReason::PrimaryRoleWithPasswordInThresholdListMustHaveAnotherFactor + ), + FactorSourceInRoleBuilderValidationStatus::ok(RoleKind::Primary, fs2,), + ] + ); + _ = sut.add_factor_source_to_threshold(fs0); + _ = sut.set_threshold(2); + + let xs = sut.validation_for_addition_of_factor_source_for_each( + FactorListKind::Threshold, + &IndexSet::from_iter([fs0, fs1, fs2]), + ); + assert_eq!( + xs.into_iter().collect::>(), + vec![ + FactorSourceInRoleBuilderValidationStatus::forever_invalid( + RoleKind::Primary, + fs0, + ForeverInvalidReason::FactorSourceAlreadyPresent + ), + FactorSourceInRoleBuilderValidationStatus::ok(RoleKind::Primary, fs1,), + FactorSourceInRoleBuilderValidationStatus::ok(RoleKind::Primary, fs2,), + ] + ); + } +} + +#[cfg(test)] +mod password { + use super::*; + + fn sample() -> FactorSourceID { + FactorSourceID::sample_password() + } + + fn sample_other() -> FactorSourceID { + FactorSourceID::sample_password_other() + } + + #[test] + fn test_suite_prerequisite() { + assert_eq!(sample(), sample()); + assert_eq!(sample_other(), sample_other()); + assert_ne!(sample(), sample_other()); + } + + mod threshold_in_isolation { + use super::*; + + #[test] + fn duplicates_not_allowed() { + let mut sut = make(); + sut.add_factor_source_to_threshold(FactorSourceID::sample_device()) + .unwrap(); + _ = sut.set_threshold(2); + test_duplicates_not_allowed(sut, FactorListKind::Threshold, sample()); + } + + #[test] + fn alone_is_not_ok() { + // Arrange + let mut sut = make(); + + // Act + let res = sut.add_factor_source_to_threshold(sample()); + + // Assert + assert_eq!( + res, + MutRes::not_yet_valid( + NotYetValidReason::PrimaryRoleWithPasswordInThresholdListMustHaveAnotherFactor + ) + ); + } + + #[test] + fn validation_for_addition_of_factor_source_of_kind_to_list() { + use FactorSourceKind::*; + + let not_ok = |kind: FactorSourceKind| { + let sut = make(); + let res = sut.validation_for_addition_of_factor_source_of_kind_to_list( + kind, + FactorListKind::Threshold, + ); + assert!(res.is_err()); + }; + + let ok_with = |kind: FactorSourceKind, setup: fn(&mut SUT)| { + let mut sut = make(); + setup(&mut sut); + let res = sut.validation_for_addition_of_factor_source_of_kind_to_list( + kind, + FactorListKind::Threshold, + ); + assert!(res.is_ok()); + }; + let ok = |kind: FactorSourceKind| { + ok_with(kind, |_| {}); + }; + + ok(LedgerHQHardwareWallet); + ok(ArculusCard); + ok(OffDeviceMnemonic); + + ok_with(Device, |sut| { + sut.add_factor_source_to_threshold(FactorSourceID::sample_ledger()) + .unwrap(); + }); + ok_with(Passphrase, |sut| { + sut.add_factor_source_to_threshold(FactorSourceID::sample_device()) + .unwrap(); + _ = sut.set_threshold(2); + }); + + not_ok(SecurityQuestions); + not_ok(TrustedContact); + } + } + + mod override_in_isolation { + use super::*; + + #[test] + fn unsupported() { + // Arrange + let mut sut = make(); + + // Act + let res = sut.add_factor_source_to_override(sample()); + + // Assert + assert_eq!( + res, + MutRes::forever_invalid( + ForeverInvalidReason::PrimaryCannotHavePasswordInOverrideList + ) + ); + } + + #[test] + fn valid_then_invalid_because_unsupported() { + // Arrange + let mut sut = make(); + sut.add_factor_source_to_override(FactorSourceID::sample_device()) + .unwrap(); + sut.add_factor_source_to_override(FactorSourceID::sample_ledger()) + .unwrap(); + sut.add_factor_source_to_override(FactorSourceID::sample_arculus()) + .unwrap(); + + // Act + let res = sut.add_factor_source_to_override(sample()); + + // Assert + assert_eq!( + res, + MutRes::forever_invalid( + ForeverInvalidReason::PrimaryCannotHavePasswordInOverrideList + ) + ); + } + + #[test] + fn validation_for_addition_of_factor_source_of_kind_to_override() { + use FactorSourceKind::*; + + let not_ok = |kind: FactorSourceKind| { + let sut = make(); + let res = sut.validation_for_addition_of_factor_source_of_kind_to_override(kind); + assert!(res.is_err()); + }; + + let ok_with = |kind: FactorSourceKind, setup: fn(&mut SUT)| { + let mut sut = make(); + setup(&mut sut); + let res = sut.validation_for_addition_of_factor_source_of_kind_to_override(kind); + assert!(res.is_ok()); + }; + let ok = |kind: FactorSourceKind| { + ok_with(kind, |_| {}); + }; + + ok(LedgerHQHardwareWallet); + ok(ArculusCard); + ok(OffDeviceMnemonic); + + ok_with(Device, |sut| { + sut.add_factor_source_to_override(FactorSourceID::sample_ledger()) + .unwrap(); + }); + + not_ok(Passphrase); + + not_ok(SecurityQuestions); + not_ok(TrustedContact); + } + } +} + +#[cfg(test)] +mod ledger { + use super::*; + + fn sample() -> FactorSourceID { + FactorSourceID::sample_ledger() + } + + fn sample_other() -> FactorSourceID { + FactorSourceID::sample_ledger_other() + } + + #[test] + fn test_suite_prerequisite() { + assert_eq!(sample(), sample()); + assert_eq!(sample_other(), sample_other()); + assert_ne!(sample(), sample_other()); + } + + mod threshold_in_isolation { + use super::*; + fn list() -> FactorListKind { + FactorListKind::Threshold + } + + #[test] + fn duplicates_not_allowed() { + test_duplicates_not_allowed(make(), list(), sample()); + } + + #[test] + fn one_is_ok() { + // Arrange + let mut sut = make(); + + // Act + sut.add_factor_source_to_threshold(sample()).unwrap(); + sut.set_threshold(1).unwrap(); + + // Assert + let expected = RoleWithFactorSourceIds::primary_with_factors(1, [sample()], []); + assert_eq!(sut.build().unwrap(), expected); + } + + #[test] + fn one_with_threshold_of_zero_is_err() { + // Arrange + let mut sut = make(); + + // Act + sut.add_factor_source_to_threshold(sample()).unwrap(); + + // Assert + assert_eq!( + sut.build(), + SUT::RoleBuilderBuildResult::Err(RoleBuilderValidation::NotYetValid( + NotYetValidReason::PrimaryRoleWithThresholdCannotBeZeroWithFactors + )) + ); + } + + #[test] + fn two_different_is_ok() { + // Arrange + let mut sut = make(); + + // Act + sut.add_factor_source_to_threshold(sample()).unwrap(); + sut.add_factor_source_to_threshold(sample_other()).unwrap(); + sut.set_threshold(2).unwrap(); + + // Assert + let expected = + RoleWithFactorSourceIds::primary_with_factors(2, [sample(), sample_other()], []); + assert_eq!(sut.build().unwrap(), expected); + } + } + + mod override_in_isolation { + use super::*; + fn list() -> FactorListKind { + FactorListKind::Override + } + + #[test] + fn duplicates_not_allowed() { + test_duplicates_not_allowed(make(), list(), sample()); + } + + #[test] + fn one_is_ok() { + // Arrange + let mut sut = make(); + + // Act + sut.add_factor_source_to_override(sample()).unwrap(); + + // Assert + let expected = RoleWithFactorSourceIds::primary_with_factors(0, [], [sample()]); + assert_eq!(sut.build().unwrap(), expected); + } + + #[test] + fn two_different_is_ok() { + // Arrange + let mut sut = make(); + + // Act + sut.add_factor_source_to_override(sample()).unwrap(); + sut.add_factor_source_to_override(sample_other()).unwrap(); + + // Assert + let expected = + RoleWithFactorSourceIds::primary_with_factors(0, [], [sample(), sample_other()]); + assert_eq!(sut.build().unwrap(), expected); + } + } +} + +#[cfg(test)] +mod arculus { + use super::*; + + fn sample() -> FactorSourceID { + FactorSourceID::sample_arculus() + } + + fn sample_other() -> FactorSourceID { + FactorSourceID::sample_arculus_other() + } + + #[test] + fn test_suite_prerequisite() { + assert_eq!(sample(), sample()); + assert_eq!(sample_other(), sample_other()); + assert_ne!(sample(), sample_other()); + } + + mod threshold_in_isolation { + use super::*; + fn list() -> FactorListKind { + FactorListKind::Threshold + } + + #[test] + fn duplicates_not_allowed() { + test_duplicates_not_allowed(make(), list(), sample()); + } + + #[test] + fn one_is_ok() { + // Arrange + let mut sut = make(); + + // Act + sut.add_factor_source_to_threshold(sample()).unwrap(); + sut.set_threshold(1).unwrap(); + + // Assert + let expected = RoleWithFactorSourceIds::primary_with_factors(1, [sample()], []); + assert_eq!(sut.build().unwrap(), expected); + } + + #[test] + fn two_different_is_ok() { + // Arrange + let mut sut = make(); + + // Act + sut.add_factor_source_to_threshold(sample()).unwrap(); + sut.add_factor_source_to_threshold(sample_other()).unwrap(); + sut.set_threshold(1).unwrap(); + + // Assert + let expected = + RoleWithFactorSourceIds::primary_with_factors(1, [sample(), sample_other()], []); + assert_eq!(sut.build().unwrap(), expected); + } + } + + mod override_in_isolation { + use super::*; + fn list() -> FactorListKind { + FactorListKind::Override + } + + #[test] + fn duplicates_not_allowed() { + test_duplicates_not_allowed(make(), list(), sample()); + } + + #[test] + fn one_is_ok() { + // Arrange + let mut sut = make(); + + // Act + sut.add_factor_source_to_override(sample()).unwrap(); + + // Assert + let expected = RoleWithFactorSourceIds::primary_with_factors(0, [], [sample()]); + assert_eq!(sut.build().unwrap(), expected); + } + + #[test] + fn two_different_is_ok() { + // Arrange + let mut sut = make(); + + // Act + sut.add_factor_source_to_override(sample()).unwrap(); + sut.add_factor_source_to_override(sample_other()).unwrap(); + + // Assert + let expected = + RoleWithFactorSourceIds::primary_with_factors(0, [], [sample(), sample_other()]); + assert_eq!(sut.build().unwrap(), expected); + } + } +} + +#[cfg(test)] +mod device_factor_source { + use super::*; + + fn sample() -> FactorSourceID { + FactorSourceID::sample_device() + } + + fn sample_other() -> FactorSourceID { + FactorSourceID::sample_device_other() + } + + #[test] + fn test_suite_prerequisite() { + assert_eq!(sample(), sample()); + assert_eq!(sample_other(), sample_other()); + assert_ne!(sample(), sample_other()); + } + + #[cfg(test)] + mod threshold_in_isolation { + use super::*; + + fn list() -> FactorListKind { + FactorListKind::Threshold + } + + #[test] + fn duplicates_not_allowed() { + test_duplicates_not_allowed(make(), list(), sample()) + } + + #[test] + fn one_is_ok() { + // Arrange + let mut sut = make(); + + // Act + sut.add_factor_source_to_threshold(sample()).unwrap(); + sut.set_threshold(1).unwrap(); + + // Assert + let expected = RoleWithFactorSourceIds::primary_with_factors(1, [sample()], []); + assert_eq!(sut.build().unwrap(), expected); + } + + #[test] + fn two_different_is_err() { + // Arrange + let mut sut = make(); + + sut.add_factor_source_to_threshold(sample()).unwrap(); + + // Act + let res = sut.add_factor_source_to_threshold(sample_other()); + + // Assert + assert!(matches!( + res, + MutRes::Err(Validation::ForeverInvalid( + ForeverInvalidReason::PrimaryCannotHaveMultipleDevices + )) + )); + } + } + + mod override_in_isolation { + + use super::*; + + fn list() -> FactorListKind { + FactorListKind::Override + } + + #[test] + fn duplicates_not_allowed() { + test_duplicates_not_allowed(make(), list(), sample()) + } + + #[test] + fn one_is_ok() { + // Arrange + let mut sut = make(); + + // Act + sut.add_factor_source_to_override(sample()).unwrap(); + + // Assert + let expected = RoleWithFactorSourceIds::primary_with_factors(0, [], [sample()]); + assert_eq!(sut.build().unwrap(), expected); + } + } +} diff --git a/crates/rules/src/roles/builder/recovery_roles_builder_unit_tests.rs b/crates/rules/src/roles/builder/recovery_roles_builder_unit_tests.rs new file mode 100644 index 00000000..6704c111 --- /dev/null +++ b/crates/rules/src/roles/builder/recovery_roles_builder_unit_tests.rs @@ -0,0 +1,400 @@ +#![cfg(test)] + +use crate::prelude::*; + +type MutRes = RoleBuilderMutateResult; + +#[test] +fn new_builder_recovery() { + assert_eq!(RecoveryRoleBuilder::new().role(), RoleKind::Recovery); +} + +#[test] +fn empty_is_err_recovery() { + let sut = RecoveryRoleBuilder::new(); + let res = sut.build(); + assert_eq!( + res, + RecoveryRoleBuilder::RoleBuilderBuildResult::not_yet_valid( + NotYetValidReason::RoleMustHaveAtLeastOneFactor + ) + ); +} + +#[allow(clippy::upper_case_acronyms)] +type SUT = RecoveryRoleBuilder; + +fn make() -> SUT { + SUT::new() +} + +fn list() -> FactorListKind { + FactorListKind::Override +} + +#[test] +fn validation_for_addition_of_factor_source_of_kind_to_list() { + use FactorSourceKind::*; + let sut = make(); + let not_ok = |kind: FactorSourceKind| { + let res = sut.validation_for_addition_of_factor_source_of_kind_to_override(kind); + assert!(res.is_err()); + }; + let ok = |kind: FactorSourceKind| { + let res = sut.validation_for_addition_of_factor_source_of_kind_to_override(kind); + assert!(res.is_ok()); + }; + ok(Device); + ok(LedgerHQHardwareWallet); + ok(ArculusCard); + ok(TrustedContact); + ok(OffDeviceMnemonic); + + not_ok(Passphrase); + not_ok(SecurityQuestions); +} + +#[test] +fn set_threshold_is_unsupported() { + let mut sut = make(); + assert_eq!( + sut.set_threshold(1), + MutRes::basic_violation(BasicViolation::RecoveryCannotSetThreshold) + ); +} + +mod device_in_isolation { + use super::*; + + fn sample() -> FactorSourceID { + FactorSourceID::sample_device() + } + + fn sample_other() -> FactorSourceID { + FactorSourceID::sample_device_other() + } + + #[test] + fn allowed_as_first_and_only() { + // Arrange + let mut sut = make(); + + // Act + sut.add_factor_source(sample()).unwrap(); + + // Assert + assert_eq!( + sut.build().unwrap(), + RoleWithFactorSourceIds::recovery_with_factors([sample()]) + ); + } + + #[test] + fn two_of_same_kind_allowed() { + // TODO: Ask Matt + // Arrange + let mut sut = make(); + + // Act + sut.add_factor_source(sample()).unwrap(); + sut.add_factor_source(sample_other()).unwrap(); + + // Assert + assert_eq!( + sut.build().unwrap(), + RoleWithFactorSourceIds::recovery_with_factors([sample(), sample_other()],) + ); + } + + #[test] + fn validation_for_addition_of_factor_source_for_each() { + let sut = make(); + let xs = sut.validation_for_addition_of_factor_source_for_each( + list(), + &IndexSet::from_iter([sample(), sample_other()]), + ); + assert_eq!( + xs.into_iter().collect::>(), + vec![ + FactorSourceInRoleBuilderValidationStatus::ok(RoleKind::Recovery, sample()), + FactorSourceInRoleBuilderValidationStatus::ok(RoleKind::Recovery, sample_other(),) + ] + ); + } +} + +mod ledger_in_isolation { + use super::*; + + fn sample() -> FactorSourceID { + FactorSourceID::sample_ledger() + } + + fn sample_other() -> FactorSourceID { + FactorSourceID::sample_ledger_other() + } + + #[test] + fn allowed_as_first_and_only() { + // Arrange + let mut sut = make(); + + // Act + sut.add_factor_source(sample()).unwrap(); + + // Assert + assert_eq!( + sut.build().unwrap(), + RoleWithFactorSourceIds::recovery_with_factors([sample()],) + ); + } + + #[test] + fn two_of_same_kind_allowed() { + // TODO: Ask Matt + // Arrange + let mut sut = make(); + + // Act + sut.add_factor_source(sample()).unwrap(); + sut.add_factor_source(sample_other()).unwrap(); + + // Assert + assert_eq!( + sut.build().unwrap(), + RoleWithFactorSourceIds::recovery_with_factors([sample(), sample_other()]) + ); + } +} + +mod arculus_in_isolation { + use super::*; + + fn sample() -> FactorSourceID { + FactorSourceID::sample_arculus() + } + + fn sample_other() -> FactorSourceID { + FactorSourceID::sample_arculus_other() + } + + #[test] + fn allowed_as_first_and_only() { + // Arrange + let mut sut = make(); + + // Act + sut.add_factor_source(sample()).unwrap(); + + // Assert + assert_eq!( + sut.build().unwrap(), + RoleWithFactorSourceIds::recovery_with_factors([sample(),]) + ); + } + + #[test] + fn two_of_same_kind_allowed() { + // TODO: Ask Matt + // Arrange + let mut sut = make(); + + // Act + sut.add_factor_source(sample()).unwrap(); + sut.add_factor_source(sample_other()).unwrap(); + + // Assert + assert_eq!( + sut.build().unwrap(), + RoleWithFactorSourceIds::recovery_with_factors([sample(), sample_other()]) + ); + } +} + +mod passphrase_in_isolation { + use super::*; + + fn sample() -> FactorSourceID { + FactorSourceID::sample_passphrase() + } + + fn sample_other() -> FactorSourceID { + FactorSourceID::sample_passphrase_other() + } + + #[test] + fn allowed_as_first_and_only() { + // Arrange + let mut sut = make(); + + // Act + sut.add_factor_source(sample()).unwrap(); + + // Assert + assert_eq!( + sut.build().unwrap(), + RoleWithFactorSourceIds::recovery_with_factors([sample()]) + ); + } + + #[test] + fn two_of_same_kind_allowed() { + // TODO: Ask Matt + // Arrange + let mut sut = make(); + + // Act + sut.add_factor_source(sample()).unwrap(); + sut.add_factor_source(sample_other()).unwrap(); + + // Assert + assert_eq!( + sut.build().unwrap(), + RoleWithFactorSourceIds::recovery_with_factors([sample(), sample_other()]) + ); + } +} + +mod trusted_contact_in_isolation { + use super::*; + + fn sample() -> FactorSourceID { + FactorSourceID::sample_trusted_contact() + } + + fn sample_other() -> FactorSourceID { + FactorSourceID::sample_trusted_contact_other() + } + + #[test] + fn allowed_as_first_and_only() { + // Arrange + let mut sut = make(); + + // Act + sut.add_factor_source(sample()).unwrap(); + + // Assert + assert_eq!( + sut.build().unwrap(), + RoleWithFactorSourceIds::recovery_with_factors([sample(),]) + ); + } + + #[test] + fn two_of_same_kind_allowed() { + // TODO: Ask Matt + // Arrange + let mut sut = make(); + + // Act + sut.add_factor_source(sample()).unwrap(); + sut.add_factor_source(sample_other()).unwrap(); + + // Assert + assert_eq!( + sut.build().unwrap(), + RoleWithFactorSourceIds::recovery_with_factors([sample(), sample_other()]) + ); + } +} + +mod password_in_isolation { + use super::*; + + fn sample() -> FactorSourceID { + FactorSourceID::sample_password() + } + + #[test] + fn unsupported() { + // Arrange + let mut sut = make(); + + // Act + let res = sut.add_factor_source(sample()); + + // Assert + assert_eq!( + res, + MutRes::forever_invalid(ForeverInvalidReason::RecoveryRolePasswordNotSupported) + ); + } + + #[test] + fn valid_then_invalid_because_unsupported() { + // Arrange + let mut sut = make(); + + sut.add_factor_source(FactorSourceID::sample_ledger()) + .unwrap(); + sut.add_factor_source(FactorSourceID::sample_arculus()) + .unwrap(); + + // Act + let res = sut.add_factor_source(sample()); + + // Assert + assert_eq!( + res, + MutRes::forever_invalid(ForeverInvalidReason::RecoveryRolePasswordNotSupported) + ); + } +} + +mod security_questions_in_isolation { + use super::*; + + fn sample() -> FactorSourceID { + FactorSourceID::sample_security_questions() + } + fn sample_other() -> FactorSourceID { + FactorSourceID::sample_security_questions_other() + } + + #[test] + fn unsupported() { + // Arrange + let mut sut = make(); + + // Act + let res = sut.add_factor_source(sample()); + + // Assert + assert_eq!( + res, + MutRes::forever_invalid( + ForeverInvalidReason::RecoveryRoleSecurityQuestionsNotSupported + ) + ); + } + + #[test] + fn valid_then_invalid_because_unsupported() { + // Arrange + let mut sut = make(); + + sut.add_factor_source(FactorSourceID::sample_ledger()) + .unwrap(); + sut.add_factor_source(FactorSourceID::sample_arculus()) + .unwrap(); + + // Act + let res = sut.add_factor_source(sample_other()); + + // Assert + let reason = ForeverInvalidReason::RecoveryRoleSecurityQuestionsNotSupported; + let err = MutRes::forever_invalid(reason); + assert_eq!(res, err); + + // .. erroneous action above did not change the state of the builder (SUT), + // so we can build and `sample` is not present in the built result. + assert_eq!( + sut.build(), + Ok(RoleWithFactorSourceIds::recovery_with_factors([ + FactorSourceID::sample_ledger(), + FactorSourceID::sample_arculus() + ])) + ); + } +} diff --git a/crates/rules/src/roles/builder/roles_builder.rs b/crates/rules/src/roles/builder/roles_builder.rs index ccb08be1..eacdda89 100644 --- a/crates/rules/src/roles/builder/roles_builder.rs +++ b/crates/rules/src/roles/builder/roles_builder.rs @@ -208,6 +208,125 @@ use RoleKind::*; pub type RoleBuilderMutateResult = Result<(), RoleBuilderValidation>; +pub enum Assert {} +pub trait IsTrue {} +impl IsTrue for Assert {} + +impl RoleBuilder +where + Assert<{ R == ROLE_PRIMARY }>: IsTrue, +{ + /// If Ok => self is mutated + /// If Err(NotYetValid) => self is mutated + /// If Err(ForeverInvalid) => self is not mutated + pub(crate) fn add_factor_source_to_threshold( + &mut self, + factor_source_id: FactorSourceID, + ) -> RoleBuilderMutateResult { + self._add_factor_source_to_list(factor_source_id, FactorListKind::Threshold) + } + + /// If we would add a factor of kind `factor_source_kind` to the list of kind `factor_list_kind` + /// what would be the validation status? + pub(crate) fn validation_for_addition_of_factor_source_of_kind_to_threshold( + &self, + factor_source_kind: FactorSourceKind, + ) -> RoleBuilderMutateResult { + self._validation_for_addition_of_factor_source_of_kind_to_list( + factor_source_kind, + FactorListKind::Threshold, + ) + } + + #[cfg(test)] + pub(crate) fn validation_for_addition_of_factor_source_of_kind_to_list( + &self, + factor_source_kind: FactorSourceKind, + list: FactorListKind, + ) -> RoleBuilderMutateResult { + self._validation_for_addition_of_factor_source_of_kind_to_list(factor_source_kind, list) + } +} + +impl RoleBuilder +where + Assert<{ R > ROLE_PRIMARY }>: IsTrue, +{ + /// If Ok => self is mutated + /// If Err(NotYetValid) => self is mutated + /// If Err(ForeverInvalid) => self is not mutated + pub(crate) fn add_factor_source( + &mut self, + factor_source_id: FactorSourceID, + ) -> RoleBuilderMutateResult { + self.add_factor_source_to_override(factor_source_id) + } +} + +impl RoleBuilder { + /// If Ok => self is mutated + /// If Err(NotYetValid) => self is mutated + /// If Err(ForeverInvalid) => self is not mutated + pub(crate) fn add_factor_source_to_override( + &mut self, + factor_source_id: FactorSourceID, + ) -> RoleBuilderMutateResult { + self._add_factor_source_to_list(factor_source_id, FactorListKind::Override) + } + + /// If Ok => self is mutated + /// If Err(NotYetValid) => self is mutated + /// If Err(ForeverInvalid) => self is not mutated + fn _add_factor_source_to_list( + &mut self, + factor_source_id: FactorSourceID, + factor_list_kind: FactorListKind, + ) -> RoleBuilderMutateResult { + let validation = self + .validation_for_addition_of_factor_source_to_list(&factor_source_id, factor_list_kind); + match validation.as_ref() { + Ok(()) | Err(Validation::NotYetValid(_)) => { + self.unchecked_add_factor_source_to_list(factor_source_id, factor_list_kind); + } + Err(Validation::ForeverInvalid(_)) | Err(Validation::BasicViolation(_)) => {} + } + validation + } + + /// If we would add a factor of kind `factor_source_kind` to the list of kind `factor_list_kind` + /// what would be the validation status? + pub(crate) fn validation_for_addition_of_factor_source_of_kind_to_override( + &self, + factor_source_kind: FactorSourceKind, + ) -> RoleBuilderMutateResult { + self._validation_for_addition_of_factor_source_of_kind_to_list( + factor_source_kind, + FactorListKind::Override, + ) + } + + /// If we would add a factor of kind `factor_source_kind` to the list of kind `factor_list_kind` + /// what would be the validation status? + fn _validation_for_addition_of_factor_source_of_kind_to_list( + &self, + factor_source_kind: FactorSourceKind, + factor_list_kind: FactorListKind, + ) -> RoleBuilderMutateResult { + match self.role() { + RoleKind::Primary => self.validation_for_addition_of_factor_source_of_kind_to_list_for_primary(factor_source_kind, factor_list_kind), + RoleKind::Recovery | RoleKind::Confirmation => match factor_list_kind { + FactorListKind::Threshold => { + RoleBuilderMutateResult::forever_invalid(ForeverInvalidReason::threshold_list_not_supported_for_role(self.role())) + } + FactorListKind::Override => self + .validation_for_addition_of_factor_source_of_kind_to_override_for_non_primary_role( + factor_source_kind, + ), + }, + } + } +} + impl RoleBuilder { pub type RoleBuilderBuildResult = Result, RoleBuilderValidation>; @@ -261,25 +380,6 @@ impl RoleBuilder { .any(|f| f.get_factor_source_kind() == factor_source_kind) } - /// If Ok => self is mutated - /// If Err(NotYetValid) => self is mutated - /// If Err(ForeverInvalid) => self is not mutated - pub(crate) fn add_factor_source_to_list( - &mut self, - factor_source_id: FactorSourceID, - factor_list_kind: FactorListKind, - ) -> RoleBuilderMutateResult { - let validation = self - .validation_for_addition_of_factor_source_to_list(&factor_source_id, factor_list_kind); - match validation.as_ref() { - Ok(()) | Err(Validation::NotYetValid(_)) => { - self.unchecked_add_factor_source_to_list(factor_source_id, factor_list_kind); - } - Err(Validation::ForeverInvalid(_)) | Err(Validation::BasicViolation(_)) => {} - } - validation - } - /// Validates `self` by "replaying" the addition of each factor source in `self` to a /// "simulation" (clone). If the simulation is valid, then `self` is valid. pub(crate) fn validate(&self) -> RoleBuilderMutateResult { @@ -288,7 +388,7 @@ impl RoleBuilder { // Validate override factors for override_factor in self.get_override_factors() { let validation = - simulation.add_factor_source_to_list(*override_factor, FactorListKind::Override); + simulation._add_factor_source_to_list(*override_factor, FactorListKind::Override); match validation.as_ref() { Ok(()) | Err(Validation::NotYetValid(_)) => continue, Err(Validation::ForeverInvalid(_)) | Err(Validation::BasicViolation(_)) => { @@ -300,7 +400,7 @@ impl RoleBuilder { // Validate threshold factors for threshold_factor in self.get_threshold_factors() { let validation = - simulation.add_factor_source_to_list(*threshold_factor, FactorListKind::Threshold); + simulation._add_factor_source_to_list(*threshold_factor, FactorListKind::Threshold); match validation.as_ref() { Ok(()) | Err(Validation::NotYetValid(_)) => continue, Err(Validation::ForeverInvalid(_)) | Err(Validation::BasicViolation(_)) => { @@ -340,27 +440,6 @@ impl RoleBuilder { Ok(()) } - /// If we would add a factor of kind `factor_source_kind` to the list of kind `factor_list_kind` - /// what would be the validation status? - pub(crate) fn validation_for_addition_of_factor_source_of_kind_to_list( - &self, - factor_source_kind: FactorSourceKind, - factor_list_kind: FactorListKind, - ) -> RoleBuilderMutateResult { - match self.role() { - RoleKind::Primary => self.validation_for_addition_of_factor_source_of_kind_to_list_for_primary(factor_source_kind, factor_list_kind), - RoleKind::Recovery | RoleKind::Confirmation => match factor_list_kind { - FactorListKind::Threshold => { - RoleBuilderMutateResult::forever_invalid(ForeverInvalidReason::threshold_list_not_supported_for_role(self.role())) - } - FactorListKind::Override => self - .validation_for_addition_of_factor_source_of_kind_to_override_for_non_primary_role( - factor_source_kind, - ), - }, - } - } - fn validation_for_addition_of_factor_source_of_kind_to_override_for_non_primary_role( &self, factor_source_kind: FactorSourceKind, @@ -413,7 +492,7 @@ impl RoleBuilder { return RoleBuilderMutateResult::forever_invalid(FactorSourceAlreadyPresent); } let factor_source_kind = factor_source_id.get_factor_source_kind(); - self.validation_for_addition_of_factor_source_of_kind_to_list( + self._validation_for_addition_of_factor_source_of_kind_to_list( factor_source_kind, factor_list_kind, ) @@ -592,3 +671,125 @@ impl RoleBuilder { } } } + +#[cfg(test)] +pub(crate) fn test_duplicates_not_allowed( + sut: RoleBuilder, + list: FactorListKind, + factor_source_id: FactorSourceID, +) { + // Arrange + let mut sut = sut; + + sut._add_factor_source_to_list(factor_source_id, list) + .unwrap(); + + // Act + let res = sut._add_factor_source_to_list( + factor_source_id, // oh no, duplicate! + list, + ); + + // Assert + assert!(matches!( + res, + RoleBuilderMutateResult::Err(Validation::ForeverInvalid( + ForeverInvalidReason::FactorSourceAlreadyPresent + )) + )); +} + +#[cfg(test)] +mod tests { + + use super::*; + + #[test] + fn primary_duplicates_not_allowed() { + test_duplicates_not_allowed( + PrimaryRoleBuilder::new(), + FactorListKind::Override, + FactorSourceID::sample_arculus(), + ); + test_duplicates_not_allowed( + PrimaryRoleBuilder::new(), + FactorListKind::Threshold, + FactorSourceID::sample_arculus(), + ); + } + + #[test] + fn recovery_duplicates_not_allowed() { + test_duplicates_not_allowed( + RecoveryRoleBuilder::new(), + FactorListKind::Override, + FactorSourceID::sample_arculus(), + ); + } + + #[test] + fn confirmation_duplicates_not_allowed() { + test_duplicates_not_allowed( + ConfirmationRoleBuilder::new(), + FactorListKind::Override, + FactorSourceID::sample_arculus(), + ); + } + + #[test] + fn recovery_cannot_add_factors_to_threshold() { + let mut sut = RecoveryRoleBuilder::new(); + let res = sut + ._add_factor_source_to_list(FactorSourceID::sample_ledger(), FactorListKind::Threshold); + assert_eq!( + res, + Err(Validation::ForeverInvalid( + ForeverInvalidReason::RecoveryRoleThresholdFactorsNotSupported + )) + ); + } + + #[test] + fn confirmation_cannot_add_factors_to_threshold() { + let mut sut = ConfirmationRoleBuilder::new(); + let res = sut + ._add_factor_source_to_list(FactorSourceID::sample_ledger(), FactorListKind::Threshold); + assert_eq!( + res, + Err(Validation::ForeverInvalid( + ForeverInvalidReason::ConfirmationRoleThresholdFactorsNotSupported + )) + ); + } + + #[test] + fn recovery_validation_for_addition_of_factor_source_of_kind_to_list_is_err_for_threshold() { + let sut = RecoveryRoleBuilder::new(); + let res = sut._validation_for_addition_of_factor_source_of_kind_to_list( + FactorSourceKind::Device, + FactorListKind::Threshold, + ); + assert_eq!( + res, + RoleBuilderMutateResult::forever_invalid( + ForeverInvalidReason::threshold_list_not_supported_for_role(RoleKind::Recovery) + ) + ); + } + + #[test] + fn confirmation_validation_for_addition_of_factor_source_of_kind_to_list_is_err_for_threshold() + { + let sut = ConfirmationRoleBuilder::new(); + let res = sut._validation_for_addition_of_factor_source_of_kind_to_list( + FactorSourceKind::Device, + FactorListKind::Threshold, + ); + assert_eq!( + res, + RoleBuilderMutateResult::forever_invalid( + ForeverInvalidReason::threshold_list_not_supported_for_role(RoleKind::Confirmation) + ) + ); + } +} diff --git a/crates/rules/src/roles/builder/roles_builder_unit_tests.rs b/crates/rules/src/roles/builder/roles_builder_unit_tests.rs index 9dbd29c6..ab92f399 100644 --- a/crates/rules/src/roles/builder/roles_builder_unit_tests.rs +++ b/crates/rules/src/roles/builder/roles_builder_unit_tests.rs @@ -3,194 +3,9 @@ use crate::prelude::*; use NotYetValidReason::*; -type Validation = RoleBuilderValidation; - -#[allow(clippy::upper_case_acronyms)] type MutRes = RoleBuilderMutateResult; -mod primary_test_helper_functions { - - use super::*; - - #[allow(clippy::upper_case_acronyms)] - type SUT = PrimaryRoleBuilder; - - #[test] - fn factor_sources_not_of_kind_to_list_of_kind_in_override() { - let mut sut = SUT::new(); - sut.add_factor_source_to_list(FactorSourceID::sample_device(), FactorListKind::Override) - .unwrap(); - sut.add_factor_source_to_list(FactorSourceID::sample_ledger(), FactorListKind::Override) - .unwrap(); - sut.add_factor_source_to_list(FactorSourceID::sample_arculus(), FactorListKind::Override) - .unwrap(); - - let xs = sut.factor_sources_not_of_kind_to_list_of_kind( - FactorSourceKind::Device, - FactorListKind::Override, - ); - assert_eq!( - xs, - vec![ - FactorSourceID::sample_ledger(), - FactorSourceID::sample_arculus() - ] - ); - - let xs = sut.factor_sources_not_of_kind_to_list_of_kind( - FactorSourceKind::LedgerHQHardwareWallet, - FactorListKind::Override, - ); - assert_eq!( - xs, - vec![ - FactorSourceID::sample_device(), - FactorSourceID::sample_arculus() - ] - ); - - let xs = sut.factor_sources_not_of_kind_to_list_of_kind( - FactorSourceKind::ArculusCard, - FactorListKind::Override, - ); - assert_eq!( - xs, - vec![ - FactorSourceID::sample_device(), - FactorSourceID::sample_ledger() - ] - ); - } - - #[test] - fn factor_sources_not_of_kind_to_list_of_kind_in_threshold() { - let mut sut = SUT::new(); - sut.add_factor_source_to_list(FactorSourceID::sample_device(), FactorListKind::Threshold) - .unwrap(); - sut.add_factor_source_to_list(FactorSourceID::sample_ledger(), FactorListKind::Threshold) - .unwrap(); - sut.add_factor_source_to_list(FactorSourceID::sample_arculus(), FactorListKind::Threshold) - .unwrap(); - - let xs = sut.factor_sources_not_of_kind_to_list_of_kind( - FactorSourceKind::Device, - FactorListKind::Threshold, - ); - assert_eq!( - xs, - vec![ - FactorSourceID::sample_ledger(), - FactorSourceID::sample_arculus() - ] - ); - - let xs = sut.factor_sources_not_of_kind_to_list_of_kind( - FactorSourceKind::LedgerHQHardwareWallet, - FactorListKind::Threshold, - ); - assert_eq!( - xs, - vec![ - FactorSourceID::sample_device(), - FactorSourceID::sample_arculus() - ] - ); - - let xs = sut.factor_sources_not_of_kind_to_list_of_kind( - FactorSourceKind::ArculusCard, - FactorListKind::Threshold, - ); - assert_eq!( - xs, - vec![ - FactorSourceID::sample_device(), - FactorSourceID::sample_ledger() - ] - ); - } -} - -fn test_duplicates_not_allowed( - sut: RoleBuilder, - list: FactorListKind, - factor_source_id: FactorSourceID, -) { - // Arrange - let mut sut = sut; - - sut.add_factor_source_to_list(factor_source_id, list) - .unwrap(); - - // Act - let res = sut.add_factor_source_to_list( - factor_source_id, // oh no, duplicate! - list, - ); - - // Assert - assert!(matches!( - res, - MutRes::Err(Validation::ForeverInvalid( - ForeverInvalidReason::FactorSourceAlreadyPresent - )) - )); -} - -#[test] -fn new_builder_primary() { - assert_eq!(PrimaryRoleBuilder::new().role(), RoleKind::Primary); -} - -#[test] -fn new_builder_recovery() { - assert_eq!(RecoveryRoleBuilder::new().role(), RoleKind::Recovery); -} - -#[test] -fn new_builder_confirmation() { - assert_eq!( - ConfirmationRoleBuilder::new().role(), - RoleKind::Confirmation - ); -} - -#[test] -fn empty_is_err_primary() { - let sut = PrimaryRoleBuilder::new(); - let res = sut.build(); - assert_eq!( - res, - PrimaryRoleBuilder::RoleBuilderBuildResult::not_yet_valid( - NotYetValidReason::RoleMustHaveAtLeastOneFactor - ) - ); -} - -#[test] -fn empty_is_err_recovery() { - let sut = RecoveryRoleBuilder::new(); - let res = sut.build(); - assert_eq!( - res, - RecoveryRoleBuilder::RoleBuilderBuildResult::not_yet_valid( - NotYetValidReason::RoleMustHaveAtLeastOneFactor - ) - ); -} - -#[test] -fn empty_is_err_confirmation() { - let sut = ConfirmationRoleBuilder::new(); - let res = sut.build(); - assert_eq!( - res, - ConfirmationRoleBuilder::RoleBuilderBuildResult::not_yet_valid( - NotYetValidReason::RoleMustHaveAtLeastOneFactor - ) - ); -} - #[test] fn validate_override_for_ever_invalid() { let sut = PrimaryRoleBuilder::with_factors( @@ -255,1598 +70,3 @@ fn primary_validate_not_yet_valid_for_threshold_greater_than_threshold_factors() MutRes::not_yet_valid(ThresholdHigherThanThresholdFactorsLen) ); } - -#[cfg(test)] -mod recovery_in_isolation { - - use super::*; - - #[allow(clippy::upper_case_acronyms)] - type SUT = RecoveryRoleBuilder; - - fn role() -> RoleKind { - RoleKind::Recovery - } - - fn make() -> SUT { - SUT::new() - } - - fn list() -> FactorListKind { - FactorListKind::Override - } - - fn sample() -> FactorSourceID { - FactorSourceID::sample_device() - } - - #[test] - fn duplicates_not_allowed() { - test_duplicates_not_allowed(make(), list(), sample()) - } - - #[test] - fn validation_for_addition_of_factor_source_of_kind_to_list_is_err_for_threshold() { - let sut = make(); - let res = sut.validation_for_addition_of_factor_source_of_kind_to_list( - FactorSourceKind::Device, - FactorListKind::Threshold, - ); - assert_eq!( - res, - MutRes::forever_invalid(ForeverInvalidReason::threshold_list_not_supported_for_role( - role() - )) - ); - } - - #[test] - fn validation_for_addition_of_factor_source_of_kind_to_list() { - use FactorSourceKind::*; - let sut = make(); - let not_ok = |kind: FactorSourceKind| { - let res = sut.validation_for_addition_of_factor_source_of_kind_to_list(kind, list()); - assert!(res.is_err()); - }; - let ok = |kind: FactorSourceKind| { - let res = sut.validation_for_addition_of_factor_source_of_kind_to_list(kind, list()); - assert!(res.is_ok()); - }; - ok(Device); - ok(LedgerHQHardwareWallet); - ok(ArculusCard); - ok(TrustedContact); - ok(OffDeviceMnemonic); - - not_ok(Passphrase); - not_ok(SecurityQuestions); - } - - #[test] - fn set_threshold_is_unsupported() { - let mut sut = make(); - assert_eq!( - sut.set_threshold(1), - MutRes::basic_violation(BasicViolation::RecoveryCannotSetThreshold) - ); - } - - #[test] - fn cannot_add_factors_to_threshold() { - let mut sut = make(); - let res = sut.add_factor_source_to_list(sample(), FactorListKind::Threshold); - assert_eq!( - res, - Err(Validation::ForeverInvalid( - ForeverInvalidReason::RecoveryRoleThresholdFactorsNotSupported - )) - ); - } - - mod device_in_isolation { - use super::*; - - fn sample() -> FactorSourceID { - FactorSourceID::sample_device() - } - - fn sample_other() -> FactorSourceID { - FactorSourceID::sample_device_other() - } - - #[test] - fn allowed_as_first_and_only() { - // Arrange - let mut sut = make(); - - // Act - sut.add_factor_source_to_list(sample(), list()).unwrap(); - - // Assert - assert_eq!( - sut.build().unwrap(), - RoleWithFactorSourceIds::recovery_with_factors([sample()]) - ); - } - - #[test] - fn two_of_same_kind_allowed() { - // TODO: Ask Matt - // Arrange - let mut sut = make(); - - // Act - sut.add_factor_source_to_list(sample(), list()).unwrap(); - sut.add_factor_source_to_list(sample_other(), list()) - .unwrap(); - - // Assert - assert_eq!( - sut.build().unwrap(), - RoleWithFactorSourceIds::recovery_with_factors([sample(), sample_other()],) - ); - } - - #[test] - fn validation_for_addition_of_factor_source_for_each() { - let sut = make(); - let xs = sut.validation_for_addition_of_factor_source_for_each( - list(), - &IndexSet::from_iter([sample(), sample_other()]), - ); - assert_eq!( - xs.into_iter().collect::>(), - vec![ - FactorSourceInRoleBuilderValidationStatus::ok(RoleKind::Recovery, sample()), - FactorSourceInRoleBuilderValidationStatus::ok( - RoleKind::Recovery, - sample_other(), - ) - ] - ); - } - } - - mod ledger_in_isolation { - use super::*; - - fn sample() -> FactorSourceID { - FactorSourceID::sample_ledger() - } - - fn sample_other() -> FactorSourceID { - FactorSourceID::sample_ledger_other() - } - - #[test] - fn allowed_as_first_and_only() { - // Arrange - let mut sut = make(); - - // Act - sut.add_factor_source_to_list(sample(), list()).unwrap(); - - // Assert - assert_eq!( - sut.build().unwrap(), - RoleWithFactorSourceIds::recovery_with_factors([sample()],) - ); - } - - #[test] - fn two_of_same_kind_allowed() { - // TODO: Ask Matt - // Arrange - let mut sut = make(); - - // Act - sut.add_factor_source_to_list(sample(), list()).unwrap(); - sut.add_factor_source_to_list(sample_other(), list()) - .unwrap(); - - // Assert - assert_eq!( - sut.build().unwrap(), - RoleWithFactorSourceIds::recovery_with_factors([sample(), sample_other()]) - ); - } - } - - mod arculus_in_isolation { - use super::*; - - fn sample() -> FactorSourceID { - FactorSourceID::sample_arculus() - } - - fn sample_other() -> FactorSourceID { - FactorSourceID::sample_arculus_other() - } - - #[test] - fn allowed_as_first_and_only() { - // Arrange - let mut sut = make(); - - // Act - sut.add_factor_source_to_list(sample(), list()).unwrap(); - - // Assert - assert_eq!( - sut.build().unwrap(), - RoleWithFactorSourceIds::recovery_with_factors([sample(),]) - ); - } - - #[test] - fn two_of_same_kind_allowed() { - // TODO: Ask Matt - // Arrange - let mut sut = make(); - - // Act - sut.add_factor_source_to_list(sample(), list()).unwrap(); - sut.add_factor_source_to_list(sample_other(), list()) - .unwrap(); - - // Assert - assert_eq!( - sut.build().unwrap(), - RoleWithFactorSourceIds::recovery_with_factors([sample(), sample_other()]) - ); - } - } - - mod passphrase_in_isolation { - use super::*; - - fn sample() -> FactorSourceID { - FactorSourceID::sample_passphrase() - } - - fn sample_other() -> FactorSourceID { - FactorSourceID::sample_passphrase_other() - } - - #[test] - fn allowed_as_first_and_only() { - // Arrange - let mut sut = make(); - - // Act - sut.add_factor_source_to_list(sample(), list()).unwrap(); - - // Assert - assert_eq!( - sut.build().unwrap(), - RoleWithFactorSourceIds::recovery_with_factors([sample()]) - ); - } - - #[test] - fn two_of_same_kind_allowed() { - // TODO: Ask Matt - // Arrange - let mut sut = make(); - - // Act - sut.add_factor_source_to_list(sample(), list()).unwrap(); - sut.add_factor_source_to_list(sample_other(), list()) - .unwrap(); - - // Assert - assert_eq!( - sut.build().unwrap(), - RoleWithFactorSourceIds::recovery_with_factors([sample(), sample_other()]) - ); - } - } - - mod trusted_contact_in_isolation { - use super::*; - - fn sample() -> FactorSourceID { - FactorSourceID::sample_trusted_contact() - } - - fn sample_other() -> FactorSourceID { - FactorSourceID::sample_trusted_contact_other() - } - - #[test] - fn allowed_as_first_and_only() { - // Arrange - let mut sut = make(); - - // Act - sut.add_factor_source_to_list(sample(), list()).unwrap(); - - // Assert - assert_eq!( - sut.build().unwrap(), - RoleWithFactorSourceIds::recovery_with_factors([sample(),]) - ); - } - - #[test] - fn two_of_same_kind_allowed() { - // TODO: Ask Matt - // Arrange - let mut sut = make(); - - // Act - sut.add_factor_source_to_list(sample(), list()).unwrap(); - sut.add_factor_source_to_list(sample_other(), list()) - .unwrap(); - - // Assert - assert_eq!( - sut.build().unwrap(), - RoleWithFactorSourceIds::recovery_with_factors([sample(), sample_other()]) - ); - } - } - - mod password_in_isolation { - use super::*; - - fn sample() -> FactorSourceID { - FactorSourceID::sample_password() - } - - #[test] - fn unsupported() { - // Arrange - let mut sut = make(); - - // Act - let res = sut.add_factor_source_to_list(sample(), list()); - - // Assert - assert_eq!( - res, - MutRes::forever_invalid(ForeverInvalidReason::RecoveryRolePasswordNotSupported) - ); - } - - #[test] - fn valid_then_invalid_because_unsupported() { - // Arrange - let mut sut = make(); - - sut.add_factor_source_to_list(FactorSourceID::sample_ledger(), list()) - .unwrap(); - sut.add_factor_source_to_list(FactorSourceID::sample_arculus(), list()) - .unwrap(); - - // Act - let res = sut.add_factor_source_to_list(sample(), list()); - - // Assert - assert_eq!( - res, - MutRes::forever_invalid(ForeverInvalidReason::RecoveryRolePasswordNotSupported) - ); - } - } - - mod security_questions_in_isolation { - use super::*; - - fn sample() -> FactorSourceID { - FactorSourceID::sample_security_questions() - } - fn sample_other() -> FactorSourceID { - FactorSourceID::sample_security_questions_other() - } - - #[test] - fn unsupported() { - // Arrange - let mut sut = make(); - - // Act - let res = sut.add_factor_source_to_list(sample(), list()); - - // Assert - assert_eq!( - res, - MutRes::forever_invalid( - ForeverInvalidReason::RecoveryRoleSecurityQuestionsNotSupported - ) - ); - } - - #[test] - fn valid_then_invalid_because_unsupported() { - // Arrange - let mut sut = make(); - - sut.add_factor_source_to_list(FactorSourceID::sample_ledger(), list()) - .unwrap(); - sut.add_factor_source_to_list(FactorSourceID::sample_arculus(), list()) - .unwrap(); - - // Act - let res = sut.add_factor_source_to_list(sample_other(), list()); - - // Assert - let reason = ForeverInvalidReason::RecoveryRoleSecurityQuestionsNotSupported; - let err = MutRes::forever_invalid(reason); - assert_eq!(res, err); - - // .. erroneous action above did not change the state of the builder (SUT), - // so we can build and `sample` is not present in the built result. - assert_eq!( - sut.build(), - Ok(RoleWithFactorSourceIds::recovery_with_factors([ - FactorSourceID::sample_ledger(), - FactorSourceID::sample_arculus() - ])) - ); - } - } -} - -#[cfg(test)] -mod confirmation_in_isolation { - - use super::*; - - fn role() -> RoleKind { - RoleKind::Confirmation - } - - #[allow(clippy::upper_case_acronyms)] - type SUT = ConfirmationRoleBuilder; - - fn make() -> SUT { - SUT::new() - } - - fn list() -> FactorListKind { - FactorListKind::Override - } - - fn sample() -> FactorSourceID { - FactorSourceID::sample_device() - } - - #[test] - fn validation_for_addition_of_factor_source_of_kind_to_list_is_err_for_threshold() { - let sut = make(); - let res = sut.validation_for_addition_of_factor_source_of_kind_to_list( - FactorSourceKind::Device, - FactorListKind::Threshold, - ); - assert_eq!( - res, - MutRes::forever_invalid(ForeverInvalidReason::threshold_list_not_supported_for_role( - role() - )) - ); - } - - #[test] - fn validation_for_addition_of_factor_source_of_kind_to_list() { - use FactorSourceKind::*; - let sut = make(); - let not_ok = |kind: FactorSourceKind| { - let res = sut.validation_for_addition_of_factor_source_of_kind_to_list(kind, list()); - assert!(res.is_err()); - }; - let ok = |kind: FactorSourceKind| { - let res = sut.validation_for_addition_of_factor_source_of_kind_to_list(kind, list()); - assert!(res.is_ok()); - }; - ok(Device); - ok(LedgerHQHardwareWallet); - ok(ArculusCard); - ok(SecurityQuestions); - ok(Passphrase); - ok(OffDeviceMnemonic); - not_ok(TrustedContact); - } - - #[test] - fn duplicates_not_allowed() { - test_duplicates_not_allowed(make(), list(), sample()) - } - - #[test] - fn cannot_add_factors_to_threshold() { - let mut sut = make(); - let res = sut.add_factor_source_to_list(sample(), FactorListKind::Threshold); - assert_eq!( - res, - Err(Validation::ForeverInvalid( - ForeverInvalidReason::ConfirmationRoleThresholdFactorsNotSupported - )) - ); - } - - mod device_in_isolation { - use super::*; - - fn sample() -> FactorSourceID { - FactorSourceID::sample_device() - } - - fn sample_other() -> FactorSourceID { - FactorSourceID::sample_device_other() - } - - #[test] - fn set_threshold_is_unsupported() { - let mut sut = make(); - assert_eq!( - sut.set_threshold(1), - MutRes::basic_violation(BasicViolation::ConfirmationCannotSetThreshold) - ); - } - - #[test] - fn allowed_as_first_and_only() { - // Arrange - let mut sut = make(); - - // Act - sut.add_factor_source_to_list(sample(), list()).unwrap(); - - // Assert - assert_eq!( - sut.build().unwrap(), - RoleWithFactorSourceIds::confirmation_with_factors([sample()]) - ); - } - - #[test] - fn two_of_same_kind_allowed() { - // TODO: Ask Matt - // Arrange - let mut sut = make(); - - // Act - sut.add_factor_source_to_list(sample(), list()).unwrap(); - sut.add_factor_source_to_list(sample_other(), list()) - .unwrap(); - - // Assert - let built = sut.build().unwrap(); - assert!(built.get_threshold_factors().is_empty()); - assert_eq!( - built, - RoleWithFactorSourceIds::confirmation_with_factors([sample(), sample_other()]) - ); - } - } - - mod ledger_in_isolation { - use super::*; - - fn sample() -> FactorSourceID { - FactorSourceID::sample_ledger() - } - - fn sample_other() -> FactorSourceID { - FactorSourceID::sample_ledger_other() - } - - #[test] - fn allowed_as_first_and_only() { - // Arrange - let mut sut = make(); - - // Act - sut.add_factor_source_to_list(sample(), list()).unwrap(); - - // Assert - assert_eq!( - sut.build().unwrap(), - RoleWithFactorSourceIds::confirmation_with_factors([sample(),]) - ); - } - - #[test] - fn two_of_same_kind_allowed() { - // TODO: Ask Matt - // Arrange - let mut sut = make(); - - // Act - sut.add_factor_source_to_list(sample(), list()).unwrap(); - sut.add_factor_source_to_list(sample_other(), list()) - .unwrap(); - - // Assert - assert_eq!( - sut.build().unwrap(), - RoleWithFactorSourceIds::confirmation_with_factors([sample(), sample_other()]) - ); - } - } - - mod arculus_in_isolation { - use super::*; - - fn sample() -> FactorSourceID { - FactorSourceID::sample_arculus() - } - - fn sample_other() -> FactorSourceID { - FactorSourceID::sample_arculus_other() - } - - #[test] - fn allowed_as_first_and_only() { - // Arrange - let mut sut = make(); - - // Act - sut.add_factor_source_to_list(sample(), list()).unwrap(); - - // Assert - assert_eq!( - sut.build().unwrap(), - RoleWithFactorSourceIds::confirmation_with_factors([sample(),]) - ); - } - - #[test] - fn two_of_same_kind_allowed() { - // TODO: Ask Matt - // Arrange - let mut sut = make(); - - // Act - sut.add_factor_source_to_list(sample(), list()).unwrap(); - sut.add_factor_source_to_list(sample_other(), list()) - .unwrap(); - - // Assert - assert_eq!( - sut.build().unwrap(), - RoleWithFactorSourceIds::confirmation_with_factors([sample(), sample_other()]) - ); - } - } - - mod passphrase_in_isolation { - use super::*; - - fn sample() -> FactorSourceID { - FactorSourceID::sample_passphrase() - } - - fn sample_other() -> FactorSourceID { - FactorSourceID::sample_passphrase_other() - } - - #[test] - fn allowed_as_first_and_only() { - // Arrange - let mut sut = make(); - - // Act - sut.add_factor_source_to_list(sample(), list()).unwrap(); - - // Assert - assert_eq!( - sut.build().unwrap(), - RoleWithFactorSourceIds::confirmation_with_factors([sample(),]) - ); - } - - #[test] - fn two_of_same_kind_allowed() { - // TODO: Ask Matt - // Arrange - let mut sut = make(); - - // Act - sut.add_factor_source_to_list(sample(), list()).unwrap(); - sut.add_factor_source_to_list(sample_other(), list()) - .unwrap(); - - // Assert - assert_eq!( - sut.build().unwrap(), - RoleWithFactorSourceIds::confirmation_with_factors([sample(), sample_other()]) - ); - } - } - - mod trusted_contact_in_isolation { - use super::*; - - fn sample() -> FactorSourceID { - FactorSourceID::sample_trusted_contact() - } - - #[test] - fn unsupported() { - // Arrange - let mut sut = make(); - - // Act - let res = sut.add_factor_source_to_list(sample(), list()); - - // Assert - assert_eq!( - res, - MutRes::forever_invalid( - ForeverInvalidReason::ConfirmationRoleTrustedContactNotSupported - ) - ); - } - - #[test] - fn valid_then_invalid_because_unsupported() { - // Arrange - let mut sut = make(); - - sut.add_factor_source_to_list(FactorSourceID::sample_ledger(), list()) - .unwrap(); - sut.add_factor_source_to_list(FactorSourceID::sample_arculus(), list()) - .unwrap(); - - // Act - let res = sut.add_factor_source_to_list(sample(), list()); - - // Assert - assert_eq!( - res, - MutRes::forever_invalid( - ForeverInvalidReason::ConfirmationRoleTrustedContactNotSupported - ) - ); - } - } - - mod password_in_isolation { - use super::*; - - fn sample() -> FactorSourceID { - FactorSourceID::sample_password() - } - - fn sample_other() -> FactorSourceID { - FactorSourceID::sample_password_other() - } - - #[test] - fn allowed_as_first_and_only() { - // Arrange - let mut sut = make(); - - // Act - sut.add_factor_source_to_list(sample(), list()).unwrap(); - - // Assert - assert_eq!( - sut.build().unwrap(), - RoleWithFactorSourceIds::confirmation_with_factors([sample(),]) - ); - } - - #[test] - fn two_of_same_kind_allowed() { - // TODO: Ask Matt - // Arrange - let mut sut = make(); - - // Act - sut.add_factor_source_to_list(sample(), list()).unwrap(); - sut.add_factor_source_to_list(sample_other(), list()) - .unwrap(); - - // Assert - assert_eq!( - sut.build().unwrap(), - RoleWithFactorSourceIds::confirmation_with_factors([sample(), sample_other()]) - ); - } - } -} - -#[cfg(test)] -mod primary_in_isolation { - - use super::*; - - #[allow(clippy::upper_case_acronyms)] - type SUT = PrimaryRoleBuilder; - - fn make() -> SUT { - SUT::new() - } - - #[cfg(test)] - mod threshold_suite { - use super::*; - - fn sample() -> FactorSourceID { - FactorSourceID::sample_device() - } - - fn sample_other() -> FactorSourceID { - FactorSourceID::sample_ledger() - } - - fn sample_third() -> FactorSourceID { - FactorSourceID::sample_arculus() - } - - fn list() -> FactorListKind { - FactorListKind::Threshold - } - - #[test] - fn remove_lowers_threshold_from_1_to_0() { - let mut sut = make(); - let fs = sample(); - sut.add_factor_source_to_list(fs, list()).unwrap(); - sut.set_threshold(1).unwrap(); - assert_eq!(sut.get_threshold(), 1); - assert_eq!( - sut.remove_factor_source(&fs), - Err(Validation::NotYetValid(RoleMustHaveAtLeastOneFactor)) - ); - assert_eq!(sut.get_threshold(), 0); - } - - #[test] - fn remove_lowers_threshold_from_3_to_1() { - let mut sut = make(); - let fs0 = sample(); - let fs1 = sample_other(); - sut.add_factor_source_to_list(fs0, list()).unwrap(); - sut.add_factor_source_to_list(fs1, list()).unwrap(); - sut.add_factor_source_to_list(FactorSourceID::sample_arculus_other(), list()) - .unwrap(); - sut.set_threshold(3).unwrap(); - assert_eq!(sut.get_threshold(), 3); - sut.remove_factor_source(&fs0).unwrap(); - sut.remove_factor_source(&fs1).unwrap(); - assert_eq!(sut.get_threshold(), 1); - } - - #[test] - fn remove_from_override_does_not_change_threshold() { - let mut sut = make(); - sut.add_factor_source_to_list(sample(), list()).unwrap(); - sut.add_factor_source_to_list(sample_other(), list()) - .unwrap(); - let fs = FactorSourceID::sample_arculus_other(); - sut.add_factor_source_to_list(fs, FactorListKind::Override) - .unwrap(); - sut.set_threshold(2).unwrap(); - assert_eq!(sut.get_threshold(), 2); - sut.remove_factor_source(&fs).unwrap(); - assert_eq!(sut.get_threshold(), 2); - - let built = sut.build().unwrap(); - assert_eq!(built.get_threshold(), 2); - - assert_eq!(built.role(), RoleKind::Primary); - - assert_eq!( - built.get_threshold_factors(), - &vec![sample(), sample_other()] - ); - - assert_eq!(built.get_override_factors(), &Vec::new()); - } - - #[test] - fn one_factor_then_set_threshold_to_one_is_ok() { - // Arrange - let mut sut = make(); - - // Act - sut.add_factor_source_to_list(sample_other(), list()) - .unwrap(); - sut.set_threshold(1).unwrap(); - - // Assert - let expected = RoleWithFactorSourceIds::primary_with_factors(1, [sample_other()], []); - assert_eq!(sut.build().unwrap(), expected); - } - - #[test] - fn zero_factor_then_set_threshold_to_one_is_not_yet_valid_then_add_one_factor_is_ok() { - // Arrange - let mut sut = make(); - - // Act - assert_eq!( - sut.set_threshold(1), - Err(Validation::NotYetValid( - ThresholdHigherThanThresholdFactorsLen - )) - ); - sut.add_factor_source_to_list(sample_other(), list()) - .unwrap(); - - // Assert - let expected = RoleWithFactorSourceIds::primary_with_factors(1, [sample_other()], []); - assert_eq!(sut.build().unwrap(), expected); - } - - #[test] - fn zero_factor_then_set_threshold_to_two_is_not_yet_valid_then_add_two_factor_is_ok() { - // Arrange - let mut sut = make(); - - // Act - assert_eq!( - sut.set_threshold(2), - Err(Validation::NotYetValid( - ThresholdHigherThanThresholdFactorsLen - )) - ); - sut.add_factor_source_to_list(sample(), list()).unwrap(); - - sut.add_factor_source_to_list(sample_other(), list()) - .unwrap(); - - // Assert - let expected = - RoleWithFactorSourceIds::primary_with_factors(2, [sample(), sample_other()], []); - assert_eq!(sut.build().unwrap(), expected); - } - - #[test] - fn add_two_factors_then_set_threshold_to_two_is_ok() { - // Arrange - let mut sut = make(); - - sut.add_factor_source_to_list(sample(), list()).unwrap(); - sut.add_factor_source_to_list(sample_other(), list()) - .unwrap(); - - // Act - assert_eq!(sut.set_threshold(2), Ok(())); - - // Assert - let expected = - RoleWithFactorSourceIds::primary_with_factors(2, [sample(), sample_other()], []); - assert_eq!(sut.build().unwrap(), expected); - } - - #[test] - fn add_two_factors_then_set_threshold_to_three_is_not_yet_valid_then_add_third_factor_is_ok( - ) { - // Arrange - let mut sut = make(); - - sut.add_factor_source_to_list(sample(), list()).unwrap(); - sut.add_factor_source_to_list(sample_other(), list()) - .unwrap(); - - // Act - assert_eq!( - sut.set_threshold(3), - Err(Validation::NotYetValid( - ThresholdHigherThanThresholdFactorsLen - )) - ); - - sut.add_factor_source_to_list(sample_third(), list()) - .unwrap(); - - // Assert - let expected = RoleWithFactorSourceIds::primary_with_factors( - 3, - [sample(), sample_other(), sample_third()], - [], - ); - assert_eq!(sut.build().unwrap(), expected); - } - - #[test] - fn one_factors_set_threshold_of_one_is_ok() { - // Arrange - let mut sut = make(); - - // Act - sut.add_factor_source_to_list(sample_other(), list()) - .unwrap(); - sut.set_threshold(1).unwrap(); - - // Assert - let expected = RoleWithFactorSourceIds::primary_with_factors(1, [sample_other()], []); - assert_eq!(sut.build().unwrap(), expected); - } - - #[test] - fn one_override_factors_set_threshold_to_one_is_not_yet_valid() { - // Arrange - let mut sut = make(); - - // Act - sut.add_factor_source_to_list(sample_other(), FactorListKind::Override) - .unwrap(); - assert_eq!( - sut.set_threshold(1), - Err(Validation::NotYetValid( - ThresholdHigherThanThresholdFactorsLen - )) - ); - - // Assert - - assert_eq!( - sut.build(), - Err(Validation::NotYetValid( - ThresholdHigherThanThresholdFactorsLen - )) - ); - } - - #[test] - fn validation_for_addition_of_factor_source_for_each_before_after_adding_a_factor() { - let mut sut = make(); - let fs0 = FactorSourceID::sample_ledger(); - let fs1 = FactorSourceID::sample_password(); - let fs2 = FactorSourceID::sample_arculus(); - let xs = sut.validation_for_addition_of_factor_source_for_each( - list(), - &IndexSet::from_iter([fs0, fs1, fs2]), - ); - assert_eq!( - xs.into_iter().collect::>(), - vec![ - FactorSourceInRoleBuilderValidationStatus::ok( - RoleKind::Primary, - fs0, - ), - FactorSourceInRoleBuilderValidationStatus::not_yet_valid( - RoleKind::Primary, - fs1, - NotYetValidReason::PrimaryRoleWithPasswordInThresholdListMustHaveAnotherFactor - ), - FactorSourceInRoleBuilderValidationStatus::ok( - RoleKind::Primary, - fs2, - ), - ] - ); - _ = sut.add_factor_source_to_list(fs0, list()); - _ = sut.set_threshold(2); - - let xs = sut.validation_for_addition_of_factor_source_for_each( - list(), - &IndexSet::from_iter([fs0, fs1, fs2]), - ); - assert_eq!( - xs.into_iter().collect::>(), - vec![ - FactorSourceInRoleBuilderValidationStatus::forever_invalid( - RoleKind::Primary, - fs0, - ForeverInvalidReason::FactorSourceAlreadyPresent - ), - FactorSourceInRoleBuilderValidationStatus::ok(RoleKind::Primary, fs1,), - FactorSourceInRoleBuilderValidationStatus::ok(RoleKind::Primary, fs2,), - ] - ); - } - } - - #[cfg(test)] - mod password { - use super::*; - - fn sample() -> FactorSourceID { - FactorSourceID::sample_password() - } - - fn sample_other() -> FactorSourceID { - FactorSourceID::sample_password_other() - } - - #[test] - fn test_suite_prerequisite() { - assert_eq!(sample(), sample()); - assert_eq!(sample_other(), sample_other()); - assert_ne!(sample(), sample_other()); - } - - mod threshold_in_isolation { - use super::*; - - fn list() -> FactorListKind { - FactorListKind::Threshold - } - - #[test] - fn duplicates_not_allowed() { - let mut sut = make(); - sut.add_factor_source_to_list( - FactorSourceID::sample_device(), - FactorListKind::Threshold, - ) - .unwrap(); - _ = sut.set_threshold(2); - test_duplicates_not_allowed(sut, list(), sample()); - } - - #[test] - fn alone_is_not_ok() { - // Arrange - let mut sut = make(); - - // Act - let res = sut.add_factor_source_to_list(sample(), list()); - - // Assert - assert_eq!( - res, - MutRes::not_yet_valid( - NotYetValidReason::PrimaryRoleWithPasswordInThresholdListMustHaveAnotherFactor - ) - ); - } - - #[test] - fn validation_for_addition_of_factor_source_of_kind_to_list() { - use FactorSourceKind::*; - - let not_ok = |kind: FactorSourceKind| { - let sut = make(); - let res = - sut.validation_for_addition_of_factor_source_of_kind_to_list(kind, list()); - assert!(res.is_err()); - }; - - let ok_with = |kind: FactorSourceKind, setup: fn(&mut SUT)| { - let mut sut = make(); - setup(&mut sut); - let res = - sut.validation_for_addition_of_factor_source_of_kind_to_list(kind, list()); - assert!(res.is_ok()); - }; - let ok = |kind: FactorSourceKind| { - ok_with(kind, |_| {}); - }; - - ok(LedgerHQHardwareWallet); - ok(ArculusCard); - ok(OffDeviceMnemonic); - - ok_with(Device, |sut| { - sut.add_factor_source_to_list(FactorSourceID::sample_ledger(), list()) - .unwrap(); - }); - ok_with(Passphrase, |sut| { - sut.add_factor_source_to_list(FactorSourceID::sample_device(), list()) - .unwrap(); - _ = sut.set_threshold(2); - }); - - not_ok(SecurityQuestions); - not_ok(TrustedContact); - } - } - - mod override_in_isolation { - use super::*; - - fn list() -> FactorListKind { - FactorListKind::Override - } - - #[test] - fn unsupported() { - // Arrange - let mut sut = make(); - - // Act - let res = sut.add_factor_source_to_list(sample(), list()); - - // Assert - assert_eq!( - res, - MutRes::forever_invalid( - ForeverInvalidReason::PrimaryCannotHavePasswordInOverrideList - ) - ); - } - - #[test] - fn valid_then_invalid_because_unsupported() { - // Arrange - let mut sut = make(); - sut.add_factor_source_to_list( - FactorSourceID::sample_device(), - FactorListKind::Threshold, - ) - .unwrap(); - sut.add_factor_source_to_list(FactorSourceID::sample_ledger(), list()) - .unwrap(); - sut.add_factor_source_to_list(FactorSourceID::sample_arculus(), list()) - .unwrap(); - - // Act - let res = sut.add_factor_source_to_list(sample(), list()); - - // Assert - assert_eq!( - res, - MutRes::forever_invalid( - ForeverInvalidReason::PrimaryCannotHavePasswordInOverrideList - ) - ); - } - - #[test] - fn validation_for_addition_of_factor_source_of_kind_to_list() { - use FactorSourceKind::*; - - let not_ok = |kind: FactorSourceKind| { - let sut = make(); - let res = - sut.validation_for_addition_of_factor_source_of_kind_to_list(kind, list()); - assert!(res.is_err()); - }; - - let ok_with = |kind: FactorSourceKind, setup: fn(&mut SUT)| { - let mut sut = make(); - setup(&mut sut); - let res = - sut.validation_for_addition_of_factor_source_of_kind_to_list(kind, list()); - assert!(res.is_ok()); - }; - let ok = |kind: FactorSourceKind| { - ok_with(kind, |_| {}); - }; - - ok(LedgerHQHardwareWallet); - ok(ArculusCard); - ok(OffDeviceMnemonic); - - ok_with(Device, |sut| { - sut.add_factor_source_to_list(FactorSourceID::sample_ledger(), list()) - .unwrap(); - }); - - not_ok(Passphrase); - - not_ok(SecurityQuestions); - not_ok(TrustedContact); - } - } - } - - #[cfg(test)] - mod ledger { - use super::*; - - fn sample() -> FactorSourceID { - FactorSourceID::sample_ledger() - } - - fn sample_other() -> FactorSourceID { - FactorSourceID::sample_ledger_other() - } - - #[test] - fn test_suite_prerequisite() { - assert_eq!(sample(), sample()); - assert_eq!(sample_other(), sample_other()); - assert_ne!(sample(), sample_other()); - } - - mod threshold_in_isolation { - use super::*; - fn list() -> FactorListKind { - FactorListKind::Threshold - } - - #[test] - fn duplicates_not_allowed() { - test_duplicates_not_allowed(make(), list(), sample()); - } - - #[test] - fn one_is_ok() { - // Arrange - let mut sut = make(); - - // Act - sut.add_factor_source_to_list(sample(), list()).unwrap(); - sut.set_threshold(1).unwrap(); - - // Assert - let expected = RoleWithFactorSourceIds::primary_with_factors(1, [sample()], []); - assert_eq!(sut.build().unwrap(), expected); - } - - #[test] - fn one_with_threshold_of_zero_is_err() { - // Arrange - let mut sut = make(); - - // Act - sut.add_factor_source_to_list(sample(), list()).unwrap(); - - // Assert - assert_eq!( - sut.build(), - SUT::RoleBuilderBuildResult::Err(RoleBuilderValidation::NotYetValid( - NotYetValidReason::PrimaryRoleWithThresholdCannotBeZeroWithFactors - )) - ); - } - - #[test] - fn two_different_is_ok() { - // Arrange - let mut sut = make(); - - // Act - sut.add_factor_source_to_list(sample(), list()).unwrap(); - sut.add_factor_source_to_list(sample_other(), list()) - .unwrap(); - sut.set_threshold(2).unwrap(); - - // Assert - let expected = RoleWithFactorSourceIds::primary_with_factors( - 2, - [sample(), sample_other()], - [], - ); - assert_eq!(sut.build().unwrap(), expected); - } - } - - mod override_in_isolation { - use super::*; - fn list() -> FactorListKind { - FactorListKind::Override - } - - #[test] - fn duplicates_not_allowed() { - test_duplicates_not_allowed(make(), list(), sample()); - } - - #[test] - fn one_is_ok() { - // Arrange - let mut sut = make(); - - // Act - sut.add_factor_source_to_list(sample(), list()).unwrap(); - - // Assert - let expected = RoleWithFactorSourceIds::primary_with_factors(0, [], [sample()]); - assert_eq!(sut.build().unwrap(), expected); - } - - #[test] - fn two_different_is_ok() { - // Arrange - let mut sut = make(); - - // Act - sut.add_factor_source_to_list(sample(), list()).unwrap(); - sut.add_factor_source_to_list(sample_other(), list()) - .unwrap(); - - // Assert - let expected = RoleWithFactorSourceIds::primary_with_factors( - 0, - [], - [sample(), sample_other()], - ); - assert_eq!(sut.build().unwrap(), expected); - } - } - } - - #[cfg(test)] - mod arculus { - use super::*; - - fn sample() -> FactorSourceID { - FactorSourceID::sample_arculus() - } - - fn sample_other() -> FactorSourceID { - FactorSourceID::sample_arculus_other() - } - - #[test] - fn test_suite_prerequisite() { - assert_eq!(sample(), sample()); - assert_eq!(sample_other(), sample_other()); - assert_ne!(sample(), sample_other()); - } - - mod threshold_in_isolation { - use super::*; - fn list() -> FactorListKind { - FactorListKind::Threshold - } - - #[test] - fn duplicates_not_allowed() { - test_duplicates_not_allowed(make(), list(), sample()); - } - - #[test] - fn one_is_ok() { - // Arrange - let mut sut = make(); - - // Act - sut.add_factor_source_to_list(sample(), list()).unwrap(); - sut.set_threshold(1).unwrap(); - - // Assert - let expected = RoleWithFactorSourceIds::primary_with_factors(1, [sample()], []); - assert_eq!(sut.build().unwrap(), expected); - } - - #[test] - fn two_different_is_ok() { - // Arrange - let mut sut = make(); - - // Act - sut.add_factor_source_to_list(sample(), list()).unwrap(); - sut.add_factor_source_to_list(sample_other(), list()) - .unwrap(); - sut.set_threshold(1).unwrap(); - - // Assert - let expected = RoleWithFactorSourceIds::primary_with_factors( - 1, - [sample(), sample_other()], - [], - ); - assert_eq!(sut.build().unwrap(), expected); - } - } - - mod override_in_isolation { - use super::*; - fn list() -> FactorListKind { - FactorListKind::Override - } - - #[test] - fn duplicates_not_allowed() { - test_duplicates_not_allowed(make(), list(), sample()); - } - - #[test] - fn one_is_ok() { - // Arrange - let mut sut = make(); - - // Act - sut.add_factor_source_to_list(sample(), list()).unwrap(); - - // Assert - let expected = RoleWithFactorSourceIds::primary_with_factors(0, [], [sample()]); - assert_eq!(sut.build().unwrap(), expected); - } - - #[test] - fn two_different_is_ok() { - // Arrange - let mut sut = make(); - - // Act - sut.add_factor_source_to_list(sample(), list()).unwrap(); - sut.add_factor_source_to_list(sample_other(), list()) - .unwrap(); - - // Assert - let expected = RoleWithFactorSourceIds::primary_with_factors( - 0, - [], - [sample(), sample_other()], - ); - assert_eq!(sut.build().unwrap(), expected); - } - } - } - - #[cfg(test)] - mod device_factor_source { - use super::*; - - fn sample() -> FactorSourceID { - FactorSourceID::sample_device() - } - - fn sample_other() -> FactorSourceID { - FactorSourceID::sample_device_other() - } - - #[test] - fn test_suite_prerequisite() { - assert_eq!(sample(), sample()); - assert_eq!(sample_other(), sample_other()); - assert_ne!(sample(), sample_other()); - } - - #[cfg(test)] - mod threshold_in_isolation { - use super::*; - - fn list() -> FactorListKind { - FactorListKind::Threshold - } - - #[test] - fn duplicates_not_allowed() { - test_duplicates_not_allowed(make(), list(), sample()) - } - - #[test] - fn one_is_ok() { - // Arrange - let mut sut = make(); - - // Act - sut.add_factor_source_to_list(sample(), list()).unwrap(); - sut.set_threshold(1).unwrap(); - - // Assert - let expected = RoleWithFactorSourceIds::primary_with_factors(1, [sample()], []); - assert_eq!(sut.build().unwrap(), expected); - } - - #[test] - fn two_different_is_err() { - // Arrange - let mut sut = make(); - - sut.add_factor_source_to_list(sample(), list()).unwrap(); - - // Act - let res = sut.add_factor_source_to_list(sample_other(), list()); - - // Assert - assert!(matches!( - res, - MutRes::Err(Validation::ForeverInvalid( - ForeverInvalidReason::PrimaryCannotHaveMultipleDevices - )) - )); - } - } - - mod override_in_isolation { - - use super::*; - - fn list() -> FactorListKind { - FactorListKind::Override - } - - #[test] - fn duplicates_not_allowed() { - test_duplicates_not_allowed(make(), list(), sample()) - } - - #[test] - fn one_is_ok() { - // Arrange - let mut sut = make(); - - // Act - sut.add_factor_source_to_list(sample(), list()).unwrap(); - - // Assert - let expected = RoleWithFactorSourceIds::primary_with_factors(0, [], [sample()]); - assert_eq!(sut.build().unwrap(), expected); - } - } - } -} diff --git a/crates/rules/src/roles/general_role_with_hierarchical_deterministic_factor_instances.rs b/crates/rules/src/roles/general_role_with_hierarchical_deterministic_factor_instances.rs index 36e08229..9c6ffeaf 100644 --- a/crates/rules/src/roles/general_role_with_hierarchical_deterministic_factor_instances.rs +++ b/crates/rules/src/roles/general_role_with_hierarchical_deterministic_factor_instances.rs @@ -1,2 +1 @@ - pub struct GeneralRoleWithHierarchicalDeterministicFactorInstances {} diff --git a/crates/rules/src/roles/roles_with_factor_ids.rs b/crates/rules/src/roles/roles_with_factor_ids.rs index 02aff384..82cb24bb 100644 --- a/crates/rules/src/roles/roles_with_factor_ids.rs +++ b/crates/rules/src/roles/roles_with_factor_ids.rs @@ -11,11 +11,11 @@ impl PrimaryRoleWithFactorSourceIds { pub fn sample_primary() -> Self { let mut builder = RoleBuilder::new(); builder - .add_factor_source_to_list(FactorSourceID::sample_device(), FactorListKind::Threshold) + .add_factor_source_to_threshold(FactorSourceID::sample_device()) .unwrap(); builder - .add_factor_source_to_list(FactorSourceID::sample_ledger(), FactorListKind::Threshold) + .add_factor_source_to_threshold(FactorSourceID::sample_ledger()) .unwrap(); builder.set_threshold(2).unwrap(); builder.build().unwrap() @@ -30,11 +30,11 @@ impl HasSampleValues for PrimaryRoleWithFactorSourceIds { fn sample_other() -> Self { let mut builder = RoleBuilder::new(); builder - .add_factor_source_to_list(FactorSourceID::sample_device(), FactorListKind::Threshold) + .add_factor_source_to_threshold(FactorSourceID::sample_device()) .unwrap(); builder - .add_factor_source_to_list(FactorSourceID::sample_ledger(), FactorListKind::Threshold) + .add_factor_source_to_threshold(FactorSourceID::sample_ledger()) .unwrap(); builder.set_threshold(1).unwrap(); builder.build().unwrap() @@ -46,7 +46,7 @@ impl HasSampleValues for ConfirmationRoleWithFactorSourceIds { fn sample() -> Self { let mut builder = RoleBuilder::new(); builder - .add_factor_source_to_list(FactorSourceID::sample_password(), FactorListKind::Override) + .add_factor_source(FactorSourceID::sample_password()) .unwrap(); builder.build().unwrap() } @@ -55,7 +55,7 @@ impl HasSampleValues for ConfirmationRoleWithFactorSourceIds { fn sample_other() -> Self { let mut builder = RoleBuilder::new(); builder - .add_factor_source_to_list(FactorSourceID::sample_device(), FactorListKind::Override) + .add_factor_source(FactorSourceID::sample_device()) .unwrap(); builder.build().unwrap() } @@ -65,11 +65,11 @@ impl HasSampleValues for RecoveryRoleWithFactorSourceIds { fn sample() -> Self { let mut builder = RoleBuilder::new(); builder - .add_factor_source_to_list(FactorSourceID::sample_device(), FactorListKind::Override) + .add_factor_source(FactorSourceID::sample_device()) .unwrap(); builder - .add_factor_source_to_list(FactorSourceID::sample_ledger(), FactorListKind::Override) + .add_factor_source(FactorSourceID::sample_ledger()) .unwrap(); builder.build().unwrap() } @@ -78,10 +78,7 @@ impl HasSampleValues for RecoveryRoleWithFactorSourceIds { fn sample_other() -> Self { let mut builder = RoleBuilder::new(); builder - .add_factor_source_to_list( - FactorSourceID::sample_ledger_other(), - FactorListKind::Override, - ) + .add_factor_source(FactorSourceID::sample_ledger_other()) .unwrap(); builder.build().unwrap() From 5623fde187893e26b82c6441df8bc13d34a059d9 Mon Sep 17 00:00:00 2001 From: Alexander Cyon Date: Fri, 29 Nov 2024 07:53:43 +0100 Subject: [PATCH 15/33] cleanup --- crates/rules/src/lib.rs | 1 - .../roles/builder/confirmation_roles_builder_unit_tests.rs | 2 +- .../src/roles/builder/primary_roles_builder_unit_tests.rs | 4 ++-- .../src/roles/builder/recovery_roles_builder_unit_tests.rs | 2 +- crates/rules/src/roles/builder/roles_builder.rs | 4 +--- 5 files changed, 5 insertions(+), 8 deletions(-) diff --git a/crates/rules/src/lib.rs b/crates/rules/src/lib.rs index de09646e..c059b544 100644 --- a/crates/rules/src/lib.rs +++ b/crates/rules/src/lib.rs @@ -1,5 +1,4 @@ #![allow(incomplete_features)] -#![feature(inherent_associated_types)] #![feature(generic_const_exprs)] mod matrices; diff --git a/crates/rules/src/roles/builder/confirmation_roles_builder_unit_tests.rs b/crates/rules/src/roles/builder/confirmation_roles_builder_unit_tests.rs index 8d113044..ea8a1df3 100644 --- a/crates/rules/src/roles/builder/confirmation_roles_builder_unit_tests.rs +++ b/crates/rules/src/roles/builder/confirmation_roles_builder_unit_tests.rs @@ -20,7 +20,7 @@ fn empty_is_err_confirmation() { let res = sut.build(); assert_eq!( res, - ConfirmationRoleBuilder::RoleBuilderBuildResult::not_yet_valid( + Result::not_yet_valid( NotYetValidReason::RoleMustHaveAtLeastOneFactor ) ); diff --git a/crates/rules/src/roles/builder/primary_roles_builder_unit_tests.rs b/crates/rules/src/roles/builder/primary_roles_builder_unit_tests.rs index d55de7db..40f1267e 100644 --- a/crates/rules/src/roles/builder/primary_roles_builder_unit_tests.rs +++ b/crates/rules/src/roles/builder/primary_roles_builder_unit_tests.rs @@ -20,7 +20,7 @@ fn empty_is_err_primary() { let res = sut.build(); assert_eq!( res, - PrimaryRoleBuilder::RoleBuilderBuildResult::not_yet_valid( + Result::not_yet_valid( NotYetValidReason::RoleMustHaveAtLeastOneFactor ) ); @@ -615,7 +615,7 @@ mod ledger { // Assert assert_eq!( sut.build(), - SUT::RoleBuilderBuildResult::Err(RoleBuilderValidation::NotYetValid( + Err(RoleBuilderValidation::NotYetValid( NotYetValidReason::PrimaryRoleWithThresholdCannotBeZeroWithFactors )) ); diff --git a/crates/rules/src/roles/builder/recovery_roles_builder_unit_tests.rs b/crates/rules/src/roles/builder/recovery_roles_builder_unit_tests.rs index 6704c111..87bb1e02 100644 --- a/crates/rules/src/roles/builder/recovery_roles_builder_unit_tests.rs +++ b/crates/rules/src/roles/builder/recovery_roles_builder_unit_tests.rs @@ -15,7 +15,7 @@ fn empty_is_err_recovery() { let res = sut.build(); assert_eq!( res, - RecoveryRoleBuilder::RoleBuilderBuildResult::not_yet_valid( + Result::not_yet_valid( NotYetValidReason::RoleMustHaveAtLeastOneFactor ) ); diff --git a/crates/rules/src/roles/builder/roles_builder.rs b/crates/rules/src/roles/builder/roles_builder.rs index eacdda89..92cb218d 100644 --- a/crates/rules/src/roles/builder/roles_builder.rs +++ b/crates/rules/src/roles/builder/roles_builder.rs @@ -328,9 +328,7 @@ impl RoleBuilder { } impl RoleBuilder { - pub type RoleBuilderBuildResult = Result, RoleBuilderValidation>; - - pub(crate) fn build(self) -> Self::RoleBuilderBuildResult { + pub(crate) fn build(self) -> Result, RoleBuilderValidation> { self.validate().map(|_| { RoleWithFactorSourceIds::with_factors( self.get_threshold(), From 8fe6a3fbd22239976593136a498afd672ff1d19b Mon Sep 17 00:00:00 2001 From: Alexander Cyon Date: Fri, 29 Nov 2024 09:20:20 +0100 Subject: [PATCH 16/33] [no ci] WIP --- crates/rules/Cargo.toml | 1 + crates/rules/src/lib.rs | 9 +- .../matrices/matrix_of_factor_instances.rs | 12 + crates/rules/src/move_to_sargon.rs | 241 ++++++++++++++++++ 4 files changed, 259 insertions(+), 4 deletions(-) diff --git a/crates/rules/Cargo.toml b/crates/rules/Cargo.toml index 6fe9fabf..1eb5ad29 100644 --- a/crates/rules/Cargo.toml +++ b/crates/rules/Cargo.toml @@ -10,3 +10,4 @@ serde = { version = "1.0.215", features = ["derive"] } pretty_assertions = "1.4.1" serde_json = { version = "1.0.133", features = ["preserve_order"] } assert-json-diff = "2.0.2" +once_cell = "1.20.2" diff --git a/crates/rules/src/lib.rs b/crates/rules/src/lib.rs index c059b544..47e593cf 100644 --- a/crates/rules/src/lib.rs +++ b/crates/rules/src/lib.rs @@ -8,10 +8,11 @@ mod security_structure_of_factors; pub mod prelude { pub(crate) use sargon::{ - BaseIsFactorSource, CommonError, DisplayName, FactorInstance, FactorInstances, - FactorSource, FactorSourceID, FactorSourceIDFromHash, FactorSourceKind, FactorSources, - HasSampleValues, HierarchicalDeterministicFactorInstance, Identifiable, IndexMap, IndexSet, - IsMaybeKeySpaceAware, KeySpace, RoleKind, + BIP39Passphrase, BaseIsFactorSource, CommonError, DisplayName, FactorInstance, + FactorInstances, FactorSource, FactorSourceID, FactorSourceIDFromHash, FactorSourceKind, + FactorSources, HasSampleValues, HierarchicalDeterministicFactorInstance, Identifiable, + IndexMap, IndexSet, IsMaybeKeySpaceAware, KeySpace, Mnemonic, MnemonicWithPassphrase, + RoleKind, }; #[cfg(test)] diff --git a/crates/rules/src/matrices/matrix_of_factor_instances.rs b/crates/rules/src/matrices/matrix_of_factor_instances.rs index 299c740e..57bafc1f 100644 --- a/crates/rules/src/matrices/matrix_of_factor_instances.rs +++ b/crates/rules/src/matrices/matrix_of_factor_instances.rs @@ -102,6 +102,10 @@ impl MatrixOfFactorInstances { #[cfg(test)] mod tests { + use std::sync::Arc; + + use sargon::{indexmap::IndexSet, FactorInstancesCacheClient, FactorInstancesProvider, FileSystemClient, HostId, HostInfo, InMemoryFileSystemDriver, Mnemonic, MnemonicWithPassphrase, Profile}; + use super::*; #[allow(clippy::upper_case_acronyms)] @@ -299,4 +303,12 @@ mod tests { "#, ); } + + #[test] + fn fulfilling_role_of_factor_sources_with_factor_instances() { + let matrix = MatrixOfFactorSources::sample(); + let mut consuming_instances = MnemonicWithPassphrase::derive_instances_for_all_factor_sources(); + let sut = SUT::fulfilling_matrix_of_factor_sources_with_instances(&mut consuming_instances, matrix).unwrap(); + } + } diff --git a/crates/rules/src/move_to_sargon.rs b/crates/rules/src/move_to_sargon.rs index b8796f7f..d3550a9e 100644 --- a/crates/rules/src/move_to_sargon.rs +++ b/crates/rules/src/move_to_sargon.rs @@ -262,3 +262,244 @@ pub fn assert_json_eq_ignore_whitespace(json1: &str, json2: &str) { let value2: Value = serde_json::from_str(json2).expect("Invalid JSON in json2"); assert_eq!(value1, value2, "JSON strings do not match"); } + +pub trait MnemonicWithPassphraseSamples: Sized { + fn sample_device() -> Self; + + fn sample_device_other() -> Self; + + fn sample_device_12_words() -> Self; + + fn sample_device_12_words_other() -> Self; + + fn sample_ledger() -> Self; + + fn sample_ledger_other() -> Self; + + fn sample_off_device() -> Self; + + fn sample_off_device_other() -> Self; + + fn sample_arculus() -> Self; + + fn sample_arculus_other() -> Self; + + fn sample_security_questions() -> Self; + + fn sample_security_questions_other() -> Self; + + fn sample_passphrase() -> Self; + + fn sample_passphrase_other() -> Self; + + fn all_samples() -> Vec { + vec![ + Self::sample_device(), + Self::sample_device_other(), + Self::sample_device_12_words(), + Self::sample_device_12_words_other(), + Self::sample_ledger(), + Self::sample_ledger_other(), + Self::sample_off_device(), + Self::sample_off_device_other(), + Self::sample_arculus(), + Self::sample_arculus_other(), + Self::sample_security_questions(), + Self::sample_security_questions_other(), + Self::sample_passphrase(), + Self::sample_passphrase_other(), + ] + } + + fn derive_instances_for_factor_sources( + sources: impl IntoIterator, + ) -> IndexMap { + let matrix = MatrixOfFactorSources::sample(); + let mut consuming_instances = IndexMap::::new(); + sources.into_iter().map(|fs| { + let mwp = fs.id_from_hash().sample_associated_mnemonic(); + let derivation_paths = (0..30) + mwp.derive_public_keys_vec(derivation_paths) + }); + todo!() + } + fn derive_instances_for_all_factor_sources() -> IndexMap + { + let factor_sources = FactorSources::sample_values_all(); + Self::derive_instances_for_factor_sources(factor_sources) + } +} + +use once_cell::sync::Lazy; +pub(crate) static ALL_FACTOR_SOURCE_ID_SAMPLES: Lazy<[FactorSourceIDFromHash; 12]> = + Lazy::new(|| { + [ + FactorSourceIDFromHash::sample_device(), + FactorSourceIDFromHash::sample_ledger(), + FactorSourceIDFromHash::sample_ledger_other(), + FactorSourceIDFromHash::sample_arculus(), + FactorSourceIDFromHash::sample_arculus_other(), + FactorSourceIDFromHash::sample_passphrase(), + FactorSourceIDFromHash::sample_passphrase_other(), + FactorSourceIDFromHash::sample_off_device(), + FactorSourceIDFromHash::sample_off_device_other(), + FactorSourceIDFromHash::sample_security_questions(), + FactorSourceIDFromHash::sample_device_other(), + FactorSourceIDFromHash::sample_security_questions_other(), + ] + }); + +pub(crate) static MNEMONIC_BY_ID_MAP: Lazy< + IndexMap, +> = Lazy::new(|| { + IndexMap::from_iter([ + ( + FactorSourceIDFromHash::sample_device(), + MnemonicWithPassphrase::sample_device(), + ), + ( + FactorSourceIDFromHash::sample_ledger(), + MnemonicWithPassphrase::sample_ledger(), + ), + ( + FactorSourceIDFromHash::sample_ledger_other(), + MnemonicWithPassphrase::sample_ledger_other(), + ), + ( + FactorSourceIDFromHash::sample_arculus(), + MnemonicWithPassphrase::sample_arculus(), + ), + ( + FactorSourceIDFromHash::sample_arculus_other(), + MnemonicWithPassphrase::sample_arculus_other(), + ), + ( + FactorSourceIDFromHash::sample_passphrase(), + MnemonicWithPassphrase::sample_passphrase(), + ), + ( + FactorSourceIDFromHash::sample_passphrase_other(), + MnemonicWithPassphrase::sample_passphrase_other(), + ), + ( + FactorSourceIDFromHash::sample_off_device(), + MnemonicWithPassphrase::sample_off_device(), + ), + ( + FactorSourceIDFromHash::sample_off_device_other(), + MnemonicWithPassphrase::sample_off_device_other(), + ), + ( + FactorSourceIDFromHash::sample_security_questions(), + MnemonicWithPassphrase::sample_security_questions(), + ), + ( + FactorSourceIDFromHash::sample_security_questions_other(), + MnemonicWithPassphrase::sample_security_questions_other(), + ), + ( + FactorSourceIDFromHash::sample_device_other(), + MnemonicWithPassphrase::sample_device_other(), + ), + ( + FactorSourceIDFromHash::sample_device_12_words(), + MnemonicWithPassphrase::sample_device_12_words(), + ), + ( + FactorSourceIDFromHash::sample_device_12_words_other(), + MnemonicWithPassphrase::sample_device_12_words_other(), + ), + ]) +}); + +pub trait MnemonicLookup { + fn sample_associated_mnemonic(&self) -> MnemonicWithPassphrase; +} + +impl MnemonicLookup for FactorSourceIDFromHash { + fn sample_associated_mnemonic(&self) -> MnemonicWithPassphrase { + MNEMONIC_BY_ID_MAP.get(self).cloned().unwrap() + } +} + +impl MnemonicWithPassphraseSamples for MnemonicWithPassphrase { + fn sample_device() -> Self { + Self::with_passphrase(Mnemonic::sample_device(), BIP39Passphrase::default()) + } + + fn sample_device_other() -> Self { + Self::with_passphrase(Mnemonic::sample_device_other(), BIP39Passphrase::default()) + } + + fn sample_device_12_words() -> Self { + Self::with_passphrase( + Mnemonic::sample_device_12_words(), + BIP39Passphrase::default(), + ) + } + + fn sample_device_12_words_other() -> Self { + Self::with_passphrase( + Mnemonic::sample_device_12_words_other(), + BIP39Passphrase::new("Olympia rules!"), + ) + } + + fn sample_ledger() -> Self { + Self::with_passphrase(Mnemonic::sample_ledger(), BIP39Passphrase::default()) + } + + fn sample_ledger_other() -> Self { + Self::with_passphrase( + Mnemonic::sample_ledger_other(), + BIP39Passphrase::new("Mellon"), + ) + } + + fn sample_off_device() -> Self { + Self::with_passphrase(Mnemonic::sample_off_device(), BIP39Passphrase::default()) + } + + fn sample_off_device_other() -> Self { + Self::with_passphrase( + Mnemonic::sample_off_device_other(), + BIP39Passphrase::new("open sesame"), + ) + } + + fn sample_arculus() -> Self { + Self::with_passphrase(Mnemonic::sample_arculus(), BIP39Passphrase::default()) + } + + fn sample_arculus_other() -> Self { + Self::with_passphrase( + Mnemonic::sample_arculus_other(), + BIP39Passphrase::new("Leonidas"), + ) + } + + fn sample_security_questions() -> Self { + Self::with_passphrase( + Mnemonic::sample_security_questions(), + BIP39Passphrase::default(), + ) + } + + fn sample_security_questions_other() -> Self { + Self::with_passphrase( + Mnemonic::sample_security_questions_other(), + BIP39Passphrase::default(), + ) + } + + fn sample_passphrase() -> Self { + Self::with_passphrase(Mnemonic::sample_passphrase(), BIP39Passphrase::default()) + } + + fn sample_passphrase_other() -> Self { + Self::with_passphrase( + Mnemonic::sample_security_questions_other(), + BIP39Passphrase::new("Pass phrase"), + ) + } +} From b3bb566fdae17bf1a747b56945f665800870ad15 Mon Sep 17 00:00:00 2001 From: Alexander Cyon Date: Fri, 29 Nov 2024 10:08:19 +0100 Subject: [PATCH 17/33] use fulfilling_matrix_of_factor_sources_with_instances --- crates/rules/src/lib.rs | 10 +- .../matrices/matrix_of_factor_instances.rs | 101 ++++++++---------- crates/rules/src/move_to_sargon.rs | 83 ++++++++------ .../confirmation_roles_builder_unit_tests.rs | 4 +- .../primary_roles_builder_unit_tests.rs | 4 +- .../recovery_roles_builder_unit_tests.rs | 4 +- 6 files changed, 108 insertions(+), 98 deletions(-) diff --git a/crates/rules/src/lib.rs b/crates/rules/src/lib.rs index 47e593cf..e0457540 100644 --- a/crates/rules/src/lib.rs +++ b/crates/rules/src/lib.rs @@ -8,11 +8,11 @@ mod security_structure_of_factors; pub mod prelude { pub(crate) use sargon::{ - BIP39Passphrase, BaseIsFactorSource, CommonError, DisplayName, FactorInstance, - FactorInstances, FactorSource, FactorSourceID, FactorSourceIDFromHash, FactorSourceKind, - FactorSources, HasSampleValues, HierarchicalDeterministicFactorInstance, Identifiable, - IndexMap, IndexSet, IsMaybeKeySpaceAware, KeySpace, Mnemonic, MnemonicWithPassphrase, - RoleKind, + BIP39Passphrase, BaseIsFactorSource, CommonError, DerivationPreset, DisplayName, + FactorInstance, FactorInstances, FactorSource, FactorSourceID, FactorSourceIDFromHash, + FactorSourceKind, FactorSources, HasSampleValues, HierarchicalDeterministicFactorInstance, + Identifiable, IndexMap, IndexSet, IsMaybeKeySpaceAware, KeySpace, Mnemonic, + MnemonicWithPassphrase, RoleKind, }; #[cfg(test)] diff --git a/crates/rules/src/matrices/matrix_of_factor_instances.rs b/crates/rules/src/matrices/matrix_of_factor_instances.rs index 57bafc1f..554c35f1 100644 --- a/crates/rules/src/matrices/matrix_of_factor_instances.rs +++ b/crates/rules/src/matrices/matrix_of_factor_instances.rs @@ -12,25 +12,30 @@ impl HasFactorInstances for MatrixOfFactorInstances { } } +impl MatrixOfFactorInstances { + fn from_matrix_of_sources(matrix_of_sources: MatrixOfFactorSources) -> Self { + let mut consuming_instances = MnemonicWithPassphrase::derive_instances_for_factor_sources( + sargon::NetworkID::Mainnet, + 1, + [DerivationPreset::AccountMfa], + matrix_of_sources.all_factors().into_iter().cloned(), + ); + + Self::fulfilling_matrix_of_factor_sources_with_instances( + &mut consuming_instances, + matrix_of_sources.clone(), + ) + .unwrap() + } +} + impl HasSampleValues for MatrixOfFactorInstances { fn sample() -> Self { - Self { - built: PhantomData, - primary_role: RoleWithFactorInstances::sample_primary(), - recovery_role: RoleWithFactorInstances::sample_recovery(), - confirmation_role: RoleWithFactorInstances::sample_confirmation(), - number_of_days_until_auto_confirm: 30, - } + Self::from_matrix_of_sources(MatrixOfFactorSources::sample()) } fn sample_other() -> Self { - Self { - built: PhantomData, - primary_role: RoleWithFactorInstances::sample_primary_other(), - recovery_role: RoleWithFactorInstances::sample_recovery_other(), - confirmation_role: RoleWithFactorInstances::sample_confirmation_other(), - number_of_days_until_auto_confirm: 15, - } + Self::from_matrix_of_sources(MatrixOfFactorSources::sample_other()) } } @@ -99,13 +104,10 @@ impl MatrixOfFactorInstances { Ok(matrix) } } + #[cfg(test)] mod tests { - use std::sync::Arc; - - use sargon::{indexmap::IndexSet, FactorInstancesCacheClient, FactorInstancesProvider, FileSystemClient, HostId, HostInfo, InMemoryFileSystemDriver, Mnemonic, MnemonicWithPassphrase, Profile}; - use super::*; #[allow(clippy::upper_case_acronyms)] @@ -155,7 +157,7 @@ mod tests { r#" { "primary_role": { - "threshold": 1, + "threshold": 2, "threshold_factors": [ { "factorSourceID": { @@ -181,15 +183,13 @@ mod tests { } } } - } - ], - "override_factors": [ + }, { "factorSourceID": { "discriminator": "fromHash", "fromHash": { - "kind": "device", - "body": "5255999c65076ce9ced5a1881f1a621bba1ce3f1f68a61df462d96822a5190cd" + "kind": "ledgerHQHardwareWallet", + "body": "ab59987eedd181fe98e512c1ba0f5ff059f11b5c7c56f15614dcc9fe03fec58b" } }, "badge": { @@ -199,7 +199,7 @@ mod tests { "hierarchicalDeterministicPublicKey": { "publicKey": { "curve": "curve25519", - "compressedData": "e0293d4979bc303ea4fe361a62baf9c060c7d90267972b05c61eead9ef3eed3e" + "compressedData": "92cd6838cd4e7b0523ed93d498e093f71139ffd5d632578189b39a26005be56b" }, "derivationPath": { "scheme": "cap26", @@ -209,7 +209,8 @@ mod tests { } } } - ] + ], + "override_factors": [] }, "recovery_role": { "threshold": 0, @@ -220,7 +221,7 @@ mod tests { "discriminator": "fromHash", "fromHash": { "kind": "device", - "body": "5255999c65076ce9ced5a1881f1a621bba1ce3f1f68a61df462d96822a5190cd" + "body": "f1a93d324dd0f2bff89963ab81ed6e0c2ee7e18c0827dc1d3576b2d9f26bbd0a" } }, "badge": { @@ -230,28 +231,22 @@ mod tests { "hierarchicalDeterministicPublicKey": { "publicKey": { "curve": "curve25519", - "compressedData": "23fa85f95c79684d2768f46ec4379b5e031757b727f76cfd01a50bd4cf8afcff" + "compressedData": "427969814e15d74c3ff4d9971465cb709d210c8a7627af9466bdaa67bd0929b7" }, "derivationPath": { "scheme": "cap26", - "path": "m/44H/1022H/1H/525H/1460H/237S" + "path": "m/44H/1022H/1H/525H/1460H/0S" } } } } - } - ] - }, - "confirmation_role": { - "threshold": 0, - "threshold_factors": [], - "override_factors": [ + }, { "factorSourceID": { "discriminator": "fromHash", "fromHash": { - "kind": "device", - "body": "f1a93d324dd0f2bff89963ab81ed6e0c2ee7e18c0827dc1d3576b2d9f26bbd0a" + "kind": "ledgerHQHardwareWallet", + "body": "ab59987eedd181fe98e512c1ba0f5ff059f11b5c7c56f15614dcc9fe03fec58b" } }, "badge": { @@ -261,22 +256,28 @@ mod tests { "hierarchicalDeterministicPublicKey": { "publicKey": { "curve": "curve25519", - "compressedData": "10ccd9b906660d49b3fe89651baa1284fc7b19ad2c3d423a7828ec350c0e5fe0" + "compressedData": "92cd6838cd4e7b0523ed93d498e093f71139ffd5d632578189b39a26005be56b" }, "derivationPath": { "scheme": "cap26", - "path": "m/44H/1022H/1H/525H/1460H/1S" + "path": "m/44H/1022H/1H/525H/1460H/0S" } } } } - }, + } + ] + }, + "confirmation_role": { + "threshold": 0, + "threshold_factors": [], + "override_factors": [ { "factorSourceID": { "discriminator": "fromHash", "fromHash": { - "kind": "device", - "body": "5255999c65076ce9ced5a1881f1a621bba1ce3f1f68a61df462d96822a5190cd" + "kind": "passphrase", + "body": "181ab662e19fac3ad9f08d5c673b286d4a5ed9cd3762356dc9831dc42427c1b9" } }, "badge": { @@ -286,11 +287,11 @@ mod tests { "hierarchicalDeterministicPublicKey": { "publicKey": { "curve": "curve25519", - "compressedData": "6a92b3338dc74a50e8b3fff896a7e0f43c42742544af52de20353675d8bc7907" + "compressedData": "4af49eb56b1af579aaf03f1760ec526f56e2297651f7a067f4b362f685417a81" }, "derivationPath": { "scheme": "cap26", - "path": "m/44H/1022H/1H/525H/1460H/2S" + "path": "m/44H/1022H/1H/525H/1460H/0S" } } } @@ -298,17 +299,9 @@ mod tests { } ] }, - "number_of_days_until_auto_confirm": 30 + "number_of_days_until_auto_confirm": 14 } "#, ); } - - #[test] - fn fulfilling_role_of_factor_sources_with_factor_instances() { - let matrix = MatrixOfFactorSources::sample(); - let mut consuming_instances = MnemonicWithPassphrase::derive_instances_for_all_factor_sources(); - let sut = SUT::fulfilling_matrix_of_factor_sources_with_instances(&mut consuming_instances, matrix).unwrap(); - } - } diff --git a/crates/rules/src/move_to_sargon.rs b/crates/rules/src/move_to_sargon.rs index d3550a9e..a7326e07 100644 --- a/crates/rules/src/move_to_sargon.rs +++ b/crates/rules/src/move_to_sargon.rs @@ -98,6 +98,7 @@ impl SampleValues for FactorSourceID { use assert_json_diff::assert_json_include; use core::fmt::Debug; use pretty_assertions::assert_eq; +use sargon::{DerivationPath, FactorInstancesCache, NetworkID, NextDerivationEntityIndexAssigner}; use serde::de::DeserializeOwned; use serde_json::Value; use std::str::FromStr; @@ -312,42 +313,64 @@ pub trait MnemonicWithPassphraseSamples: Sized { } fn derive_instances_for_factor_sources( + network_id: NetworkID, + quantity_per_factor: usize, + derivation_presets: impl IntoIterator, sources: impl IntoIterator, ) -> IndexMap { - let matrix = MatrixOfFactorSources::sample(); - let mut consuming_instances = IndexMap::::new(); - sources.into_iter().map(|fs| { - let mwp = fs.id_from_hash().sample_associated_mnemonic(); - let derivation_paths = (0..30) - mwp.derive_public_keys_vec(derivation_paths) - }); - todo!() - } - fn derive_instances_for_all_factor_sources() -> IndexMap - { - let factor_sources = FactorSources::sample_values_all(); - Self::derive_instances_for_factor_sources(factor_sources) + let next_index_assigner = NextDerivationEntityIndexAssigner::new( + network_id, + None, + FactorInstancesCache::default(), + ); + + let derivation_presets = derivation_presets.into_iter().collect::>(); + + sources + .into_iter() + .map(|fs| { + let fsid = fs.id_from_hash(); + let mwp = fsid.sample_associated_mnemonic(); + + let paths = derivation_presets + .clone() + .into_iter() + .map(|dp| (dp, quantity_per_factor)) + .collect::>(); + + let paths = paths + .into_iter() + .flat_map(|(derivation_preset, qty)| { + // `qty` many paths + (0..qty) + .map(|_| { + let index_agnostic_path = + derivation_preset.index_agnostic_path_on_network(network_id); + + next_index_assigner + .next(fsid, index_agnostic_path) + .map(|index| DerivationPath::from((index_agnostic_path, index))) + .unwrap() + }) + .collect::>() + }) + .collect::>(); + + let instances = mwp + .derive_public_keys(paths) + .into_iter() + .map(|public_key| { + HierarchicalDeterministicFactorInstance::new(fsid, public_key) + }) + .collect::(); + + (fsid, instances) + }) + .collect::>() } } use once_cell::sync::Lazy; -pub(crate) static ALL_FACTOR_SOURCE_ID_SAMPLES: Lazy<[FactorSourceIDFromHash; 12]> = - Lazy::new(|| { - [ - FactorSourceIDFromHash::sample_device(), - FactorSourceIDFromHash::sample_ledger(), - FactorSourceIDFromHash::sample_ledger_other(), - FactorSourceIDFromHash::sample_arculus(), - FactorSourceIDFromHash::sample_arculus_other(), - FactorSourceIDFromHash::sample_passphrase(), - FactorSourceIDFromHash::sample_passphrase_other(), - FactorSourceIDFromHash::sample_off_device(), - FactorSourceIDFromHash::sample_off_device_other(), - FactorSourceIDFromHash::sample_security_questions(), - FactorSourceIDFromHash::sample_device_other(), - FactorSourceIDFromHash::sample_security_questions_other(), - ] - }); pub(crate) static MNEMONIC_BY_ID_MAP: Lazy< IndexMap, diff --git a/crates/rules/src/roles/builder/confirmation_roles_builder_unit_tests.rs b/crates/rules/src/roles/builder/confirmation_roles_builder_unit_tests.rs index ea8a1df3..c1ae8115 100644 --- a/crates/rules/src/roles/builder/confirmation_roles_builder_unit_tests.rs +++ b/crates/rules/src/roles/builder/confirmation_roles_builder_unit_tests.rs @@ -20,9 +20,7 @@ fn empty_is_err_confirmation() { let res = sut.build(); assert_eq!( res, - Result::not_yet_valid( - NotYetValidReason::RoleMustHaveAtLeastOneFactor - ) + Result::not_yet_valid(NotYetValidReason::RoleMustHaveAtLeastOneFactor) ); } diff --git a/crates/rules/src/roles/builder/primary_roles_builder_unit_tests.rs b/crates/rules/src/roles/builder/primary_roles_builder_unit_tests.rs index 40f1267e..77f8fbc8 100644 --- a/crates/rules/src/roles/builder/primary_roles_builder_unit_tests.rs +++ b/crates/rules/src/roles/builder/primary_roles_builder_unit_tests.rs @@ -20,9 +20,7 @@ fn empty_is_err_primary() { let res = sut.build(); assert_eq!( res, - Result::not_yet_valid( - NotYetValidReason::RoleMustHaveAtLeastOneFactor - ) + Result::not_yet_valid(NotYetValidReason::RoleMustHaveAtLeastOneFactor) ); } diff --git a/crates/rules/src/roles/builder/recovery_roles_builder_unit_tests.rs b/crates/rules/src/roles/builder/recovery_roles_builder_unit_tests.rs index 87bb1e02..8f7030a8 100644 --- a/crates/rules/src/roles/builder/recovery_roles_builder_unit_tests.rs +++ b/crates/rules/src/roles/builder/recovery_roles_builder_unit_tests.rs @@ -15,9 +15,7 @@ fn empty_is_err_recovery() { let res = sut.build(); assert_eq!( res, - Result::not_yet_valid( - NotYetValidReason::RoleMustHaveAtLeastOneFactor - ) + Result::not_yet_valid(NotYetValidReason::RoleMustHaveAtLeastOneFactor) ); } From 4ed67dde3051a00e081eeb4245181cf159bddb77 Mon Sep 17 00:00:00 2001 From: Alexander Cyon Date: Fri, 29 Nov 2024 11:17:38 +0100 Subject: [PATCH 18/33] better samples --- .tarpaulin.toml | 1 + .../matrices/matrix_of_factor_source_ids.rs | 61 ++++--- .../src/roles/role_with_factor_instances.rs | 157 +++++++----------- ...security_structure_of_factor_source_ids.rs | 30 +--- 4 files changed, 109 insertions(+), 140 deletions(-) diff --git a/.tarpaulin.toml b/.tarpaulin.toml index 1a5914e5..a87a0335 100644 --- a/.tarpaulin.toml +++ b/.tarpaulin.toml @@ -2,6 +2,7 @@ exclude-files = [ "crates/rules-uniffi/src/unneeded_when_moved_to_sargon.rs", "crates/rules-uniffi/src/error_conversion.rs", + "crates/rules/src/move_to_sargon.rs", ] verbose = false force-clean = true diff --git a/crates/rules/src/matrices/matrix_of_factor_source_ids.rs b/crates/rules/src/matrices/matrix_of_factor_source_ids.rs index 8ba9568e..2221cf0f 100644 --- a/crates/rules/src/matrices/matrix_of_factor_source_ids.rs +++ b/crates/rules/src/matrices/matrix_of_factor_source_ids.rs @@ -77,6 +77,29 @@ impl MatrixOfFactorSourceIds { builder.build().unwrap() } + pub fn sample_config_24() -> Self { + let mut builder = MatrixBuilder::new(); + + // Primary + // TODO: Ask Matt about this, does he mean Threshold(1) or Override? + builder + .add_factor_source_to_primary_override(FactorSourceID::sample_device()) + .unwrap(); + + // Recovery + builder + .add_factor_source_to_recovery_override(FactorSourceID::sample_ledger()) + .unwrap(); + + // Confirmation + builder + .add_factor_source_to_confirmation_override(FactorSourceID::sample_ledger_other()) + .unwrap(); + + // Build + builder.build().unwrap() + } + pub fn sample_config_11() -> Self { let mut builder = MatrixBuilder::new(); @@ -113,7 +136,7 @@ impl HasSampleValues for MatrixOfFactorSourceIds { } fn sample_other() -> Self { - Self::sample_config_12() + Self::sample_config_24() } } @@ -132,6 +155,12 @@ mod tests { #[test] fn inequality() { assert_ne!(SUT::sample(), SUT::sample_other()); + assert_ne!(SUT::sample().primary(), SUT::sample_other().primary()); + assert_ne!(SUT::sample().recovery(), SUT::sample_other().recovery()); + assert_ne!( + SUT::sample().confirmation(), + SUT::sample_other().confirmation() + ); } #[test] @@ -208,36 +237,22 @@ mod tests { r#" { "primary_role": { - "threshold": 2, - "threshold_factors": [ - { - "discriminator": "fromHash", - "fromHash": { - "kind": "ledgerHQHardwareWallet", - "body": "ab59987eedd181fe98e512c1ba0f5ff059f11b5c7c56f15614dcc9fe03fec58b" - } - }, + "threshold": 0, + "threshold_factors": [], + "override_factors": [ { "discriminator": "fromHash", "fromHash": { - "kind": "passphrase", - "body": "181ab662e19fac3ad9f08d5c673b286d4a5ed9cd3762356dc9831dc42427c1b9" + "kind": "device", + "body": "f1a93d324dd0f2bff89963ab81ed6e0c2ee7e18c0827dc1d3576b2d9f26bbd0a" } } - ], - "override_factors": [] + ] }, "recovery_role": { "threshold": 0, "threshold_factors": [], "override_factors": [ - { - "discriminator": "fromHash", - "fromHash": { - "kind": "device", - "body": "f1a93d324dd0f2bff89963ab81ed6e0c2ee7e18c0827dc1d3576b2d9f26bbd0a" - } - }, { "discriminator": "fromHash", "fromHash": { @@ -254,8 +269,8 @@ mod tests { { "discriminator": "fromHash", "fromHash": { - "kind": "passphrase", - "body": "181ab662e19fac3ad9f08d5c673b286d4a5ed9cd3762356dc9831dc42427c1b9" + "kind": "ledgerHQHardwareWallet", + "body": "52ef052a0642a94279b296d6b3b17dedc035a7ae37b76c1d60f11f2725100077" } } ] diff --git a/crates/rules/src/roles/role_with_factor_instances.rs b/crates/rules/src/roles/role_with_factor_instances.rs index 5300b934..a3bcc3c8 100644 --- a/crates/rules/src/roles/role_with_factor_instances.rs +++ b/crates/rules/src/roles/role_with_factor_instances.rs @@ -85,94 +85,47 @@ pub(crate) type RecoveryRoleWithFactorInstances = RoleWithFactorInstances<{ ROLE pub(crate) type ConfirmationRoleWithFactorInstances = RoleWithFactorInstances<{ ROLE_CONFIRMATION }>; -impl PrimaryRoleWithFactorInstances { - // TODO: MFA Rules change this, this might not be compatible with the rules! - pub fn sample_primary() -> Self { - Self::with_factors( - // RoleKind::Primary, - 1, [ - HierarchicalDeterministicFactorInstance::sample_mainnet_account_device_factor_fs_0_securified_at_index(0).into() - ], [ - HierarchicalDeterministicFactorInstance::sample_mainnet_account_device_factor_fs_10_securified_at_index(0).into() - ]) - } - - // TODO: MFA Rules change this, this might not be compatible with the rules! - pub fn sample_primary_other() -> Self { - Self::with_factors( - // RoleKind::Primary, - 1, - [HierarchicalDeterministicFactorInstance::sample_mainnet_account_device_factor_fs_0_securified_at_index(10).into(),], - [HierarchicalDeterministicFactorInstance::sample_mainnet_account_device_factor_fs_10_securified_at_index(60).into()], - ) - } -} - -impl RecoveryRoleWithFactorInstances { - // TODO: MFA Rules change this, this might not be compatible with the rules! - pub fn sample_recovery() -> Self { - Self::with_factors( - // RoleKind::Recovery, - 0,[], [HierarchicalDeterministicFactorInstance::sample_mainnet_account_device_factor_fs_10_securified_at_index(237).into()] - ) +impl HasSampleValues for PrimaryRoleWithFactorInstances { + fn sample() -> Self { + MatrixOfFactorInstances::sample().primary_role } - // TODO: MFA Rules change this, this might not be compatible with the rules! - pub fn sample_recovery_other() -> Self { - Self::with_factors( - // RoleKind::Recovery, - 0,[], [HierarchicalDeterministicFactorInstance::sample_mainnet_account_device_factor_fs_10_securified_at_index(42).into()] - ) + fn sample_other() -> Self { + MatrixOfFactorInstances::sample_other().primary_role } } -impl ConfirmationRoleWithFactorInstances { - // TODO: MFA Rules change this, this might not be compatible with the rules! - pub fn sample_confirmation() -> Self { - Self::with_factors( - // RoleKind::Confirmation, - 0,[], [HierarchicalDeterministicFactorInstance::sample_mainnet_account_device_factor_fs_0_securified_at_index(1).into(), HierarchicalDeterministicFactorInstance::sample_mainnet_account_device_factor_fs_10_securified_at_index(2).into()] - ) +impl HasSampleValues for ConfirmationRoleWithFactorInstances { + fn sample() -> Self { + MatrixOfFactorInstances::sample().confirmation_role } - // TODO: MFA Rules change this, this might not be compatible with the rules! - pub fn sample_confirmation_other() -> Self { - Self::with_factors( - // RoleKind::Confirmation, - 0,[], [HierarchicalDeterministicFactorInstance::sample_mainnet_account_device_factor_fs_0_securified_at_index(10).into(), HierarchicalDeterministicFactorInstance::sample_mainnet_account_device_factor_fs_10_securified_at_index(20).into()] - ) + fn sample_other() -> Self { + MatrixOfFactorInstances::sample_other().confirmation_role } } -/* -impl HasSampleValues for PrimaryRoleWithFactorInstances { +impl HasSampleValues for RecoveryRoleWithFactorInstances { fn sample() -> Self { - Self::sample_primary() + MatrixOfFactorInstances::sample().recovery_role } fn sample_other() -> Self { - Self::sample_primary_other() + MatrixOfFactorInstances::sample_other().recovery_role } } #[cfg(test)] -mod tests { +mod primary_tests { use super::*; #[allow(clippy::upper_case_acronyms)] - type SUT = RoleWithFactorInstances; + type SUT = PrimaryRoleWithFactorInstances; #[test] fn equality() { - assert_eq!(SUT::sample_primary(), SUT::sample_primary()); - assert_eq!(SUT::sample_primary_other(), SUT::sample_primary_other()); - assert_eq!(SUT::sample_recovery(), SUT::sample_recovery()); - assert_eq!(SUT::sample_recovery_other(), SUT::sample_recovery_other()); - assert_eq!(SUT::sample_confirmation(), SUT::sample_confirmation()); - assert_eq!( - SUT::sample_confirmation_other(), - SUT::sample_confirmation_other() - ); + assert_eq!(SUT::sample(), SUT::sample()); + assert_eq!(SUT::sample_other(), SUT::sample_other()); } #[test] @@ -180,31 +133,10 @@ mod tests { assert_ne!(SUT::sample(), SUT::sample_other()); } - #[test] - fn hash() { - let hash = HashSet::::from_iter([ - SUT::sample_primary(), - SUT::sample_primary_other(), - SUT::sample_recovery(), - SUT::sample_recovery_other(), - SUT::sample_confirmation(), - SUT::sample_confirmation_other(), - // Duplicates should be removed - SUT::sample_primary(), - SUT::sample_primary_other(), - SUT::sample_recovery(), - SUT::sample_recovery_other(), - SUT::sample_confirmation(), - SUT::sample_confirmation_other(), - ]); - assert_eq!(hash.len(), 6); - } - #[test] #[should_panic] fn primary_role_non_securified_threshold_instances_is_err() { let _ = SUT::with_factors( - RoleKind::Primary, 1, [ HierarchicalDeterministicFactorInstance::sample_mainnet_account_device_factor_fs_10_unsecurified_at_index(0).into() @@ -220,8 +152,7 @@ mod tests { &sut, r#" { - "role": "primary", - "threshold": 1, + "threshold": 2, "threshold_factors": [ { "factorSourceID": { @@ -247,15 +178,13 @@ mod tests { } } } - } - ], - "override_factors": [ + }, { "factorSourceID": { "discriminator": "fromHash", "fromHash": { - "kind": "device", - "body": "5255999c65076ce9ced5a1881f1a621bba1ce3f1f68a61df462d96822a5190cd" + "kind": "ledgerHQHardwareWallet", + "body": "ab59987eedd181fe98e512c1ba0f5ff059f11b5c7c56f15614dcc9fe03fec58b" } }, "badge": { @@ -265,7 +194,7 @@ mod tests { "hierarchicalDeterministicPublicKey": { "publicKey": { "curve": "curve25519", - "compressedData": "e0293d4979bc303ea4fe361a62baf9c060c7d90267972b05c61eead9ef3eed3e" + "compressedData": "92cd6838cd4e7b0523ed93d498e093f71139ffd5d632578189b39a26005be56b" }, "derivationPath": { "scheme": "cap26", @@ -275,10 +204,48 @@ mod tests { } } } - ] + ], + "override_factors": [] } "#, ); } } -*/ + +#[cfg(test)] +mod confirmation_tests { + use super::*; + + #[allow(clippy::upper_case_acronyms)] + type SUT = ConfirmationRoleWithFactorInstances; + + #[test] + fn equality() { + assert_eq!(SUT::sample(), SUT::sample()); + assert_eq!(SUT::sample_other(), SUT::sample_other()); + } + + #[test] + fn inequality() { + assert_ne!(SUT::sample(), SUT::sample_other()); + } +} + +#[cfg(test)] +mod recovery_tests { + use super::*; + + #[allow(clippy::upper_case_acronyms)] + type SUT = RecoveryRoleWithFactorInstances; + + #[test] + fn equality() { + assert_eq!(SUT::sample(), SUT::sample()); + assert_eq!(SUT::sample_other(), SUT::sample_other()); + } + + #[test] + fn inequality() { + assert_ne!(SUT::sample(), SUT::sample_other()); + } +} diff --git a/crates/rules/src/security_structure_of_factors/security_structure_of_factor_source_ids.rs b/crates/rules/src/security_structure_of_factors/security_structure_of_factor_source_ids.rs index f1daa31b..4b68d7b2 100644 --- a/crates/rules/src/security_structure_of_factors/security_structure_of_factor_source_ids.rs +++ b/crates/rules/src/security_structure_of_factors/security_structure_of_factor_source_ids.rs @@ -120,36 +120,22 @@ mod tests { }, "matrix_of_factors": { "primary_role": { - "threshold": 2, - "threshold_factors": [ - { - "discriminator": "fromHash", - "fromHash": { - "kind": "ledgerHQHardwareWallet", - "body": "ab59987eedd181fe98e512c1ba0f5ff059f11b5c7c56f15614dcc9fe03fec58b" - } - }, + "threshold": 0, + "threshold_factors": [], + "override_factors": [ { "discriminator": "fromHash", "fromHash": { - "kind": "passphrase", - "body": "181ab662e19fac3ad9f08d5c673b286d4a5ed9cd3762356dc9831dc42427c1b9" + "kind": "device", + "body": "f1a93d324dd0f2bff89963ab81ed6e0c2ee7e18c0827dc1d3576b2d9f26bbd0a" } } - ], - "override_factors": [] + ] }, "recovery_role": { "threshold": 0, "threshold_factors": [], "override_factors": [ - { - "discriminator": "fromHash", - "fromHash": { - "kind": "device", - "body": "f1a93d324dd0f2bff89963ab81ed6e0c2ee7e18c0827dc1d3576b2d9f26bbd0a" - } - }, { "discriminator": "fromHash", "fromHash": { @@ -166,8 +152,8 @@ mod tests { { "discriminator": "fromHash", "fromHash": { - "kind": "passphrase", - "body": "181ab662e19fac3ad9f08d5c673b286d4a5ed9cd3762356dc9831dc42427c1b9" + "kind": "ledgerHQHardwareWallet", + "body": "52ef052a0642a94279b296d6b3b17dedc035a7ae37b76c1d60f11f2725100077" } } ] From 307bcd8d7ef3b33666b7e36f822ef44383855e75 Mon Sep 17 00:00:00 2001 From: Alexander Cyon Date: Fri, 29 Nov 2024 12:01:08 +0100 Subject: [PATCH 19/33] GeneralRoleWithHDFIs --- crates/rules/Cargo.toml | 1 + crates/rules/src/lib.rs | 9 +- .../abstract_matrix_builder_or_built.rs | 15 ++ .../matrices/matrix_of_factor_instances.rs | 20 +- .../matrices/matrix_of_factor_source_ids.rs | 58 ++--- .../roles/abstract_role_builder_or_built.rs | 1 + ...archical_deterministic_factor_instances.rs | 227 +++++++++++++++++- .../src/roles/role_with_factor_instances.rs | 6 +- .../rules/src/roles/roles_with_factor_ids.rs | 12 +- .../abstract_security_structure_of_factors.rs | 1 + ...security_structure_of_factor_source_ids.rs | 48 ++-- 11 files changed, 315 insertions(+), 83 deletions(-) diff --git a/crates/rules/Cargo.toml b/crates/rules/Cargo.toml index 1eb5ad29..c3bcb256 100644 --- a/crates/rules/Cargo.toml +++ b/crates/rules/Cargo.toml @@ -11,3 +11,4 @@ pretty_assertions = "1.4.1" serde_json = { version = "1.0.133", features = ["preserve_order"] } assert-json-diff = "2.0.2" once_cell = "1.20.2" +itertools = "0.13.0" diff --git a/crates/rules/src/lib.rs b/crates/rules/src/lib.rs index e0457540..5a0f32a0 100644 --- a/crates/rules/src/lib.rs +++ b/crates/rules/src/lib.rs @@ -10,11 +10,14 @@ pub mod prelude { pub(crate) use sargon::{ BIP39Passphrase, BaseIsFactorSource, CommonError, DerivationPreset, DisplayName, FactorInstance, FactorInstances, FactorSource, FactorSourceID, FactorSourceIDFromHash, - FactorSourceKind, FactorSources, HasSampleValues, HierarchicalDeterministicFactorInstance, - Identifiable, IndexMap, IndexSet, IsMaybeKeySpaceAware, KeySpace, Mnemonic, - MnemonicWithPassphrase, RoleKind, + FactorSourceKind, FactorSources, HasRoleKindObjectSafe, HasSampleValues, + HierarchicalDeterministicFactorInstance, Identifiable, IndexMap, IndexSet, + IsMaybeKeySpaceAware, IsSecurityStateAware, KeySpace, Mnemonic, MnemonicWithPassphrase, + RoleKind, }; + pub(crate) use itertools::*; + #[cfg(test)] pub(crate) use sargon::JustKV; diff --git a/crates/rules/src/matrices/abstract_matrix_builder_or_built.rs b/crates/rules/src/matrices/abstract_matrix_builder_or_built.rs index 6cd0a05e..16d34267 100644 --- a/crates/rules/src/matrices/abstract_matrix_builder_or_built.rs +++ b/crates/rules/src/matrices/abstract_matrix_builder_or_built.rs @@ -1,6 +1,7 @@ use crate::prelude::*; #[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] pub struct AbstractMatrixBuilderOrBuilt { #[serde(skip)] #[doc(hidden)] @@ -18,6 +19,20 @@ impl AbstractMatrixBuilderOrBuilt { pub type AbstractMatrixBuilt = AbstractMatrixBuilderOrBuilt; +impl AbstractMatrixBuilt { + pub fn primary(&self) -> &AbstractBuiltRoleWithFactor<{ ROLE_PRIMARY }, F> { + &self.primary_role + } + + pub fn recovery(&self) -> &AbstractBuiltRoleWithFactor<{ ROLE_RECOVERY }, F> { + &self.recovery_role + } + + pub fn confirmation(&self) -> &AbstractBuiltRoleWithFactor<{ ROLE_CONFIRMATION }, F> { + &self.confirmation_role + } +} + impl AbstractMatrixBuilt { pub fn all_factors(&self) -> HashSet<&F> { let mut factors = HashSet::new(); diff --git a/crates/rules/src/matrices/matrix_of_factor_instances.rs b/crates/rules/src/matrices/matrix_of_factor_instances.rs index 554c35f1..0529e70d 100644 --- a/crates/rules/src/matrices/matrix_of_factor_instances.rs +++ b/crates/rules/src/matrices/matrix_of_factor_instances.rs @@ -156,9 +156,9 @@ mod tests { &sut, r#" { - "primary_role": { + "primaryRole": { "threshold": 2, - "threshold_factors": [ + "thresholdFactors": [ { "factorSourceID": { "discriminator": "fromHash", @@ -210,12 +210,12 @@ mod tests { } } ], - "override_factors": [] + "overrideFactors": [] }, - "recovery_role": { + "recoveryRole": { "threshold": 0, - "threshold_factors": [], - "override_factors": [ + "thresholdFactors": [], + "overrideFactors": [ { "factorSourceID": { "discriminator": "fromHash", @@ -268,10 +268,10 @@ mod tests { } ] }, - "confirmation_role": { + "confirmationRole": { "threshold": 0, - "threshold_factors": [], - "override_factors": [ + "thresholdFactors": [], + "overrideFactors": [ { "factorSourceID": { "discriminator": "fromHash", @@ -299,7 +299,7 @@ mod tests { } ] }, - "number_of_days_until_auto_confirm": 14 + "numberOfDaysUntilAutoConfirm": 14 } "#, ); diff --git a/crates/rules/src/matrices/matrix_of_factor_source_ids.rs b/crates/rules/src/matrices/matrix_of_factor_source_ids.rs index 2221cf0f..67d909a7 100644 --- a/crates/rules/src/matrices/matrix_of_factor_source_ids.rs +++ b/crates/rules/src/matrices/matrix_of_factor_source_ids.rs @@ -36,20 +36,6 @@ impl MatrixOfFactorSourceIds { } } -impl MatrixOfFactorSourceIds { - pub fn primary(&self) -> &PrimaryRoleWithFactorSourceIds { - &self.primary_role - } - - pub fn recovery(&self) -> &RecoveryRoleWithFactorSourceIds { - &self.recovery_role - } - - pub fn confirmation(&self) -> &ConfirmationRoleWithFactorSourceIds { - &self.confirmation_role - } -} - impl MatrixOfFactorSourceIds { pub fn sample_config_12() -> Self { let mut builder = MatrixBuilder::new(); @@ -169,10 +155,10 @@ mod tests { assert_eq_after_json_roundtrip( &sut, r#" - { - "primary_role": { + { + "primaryRole": { "threshold": 2, - "threshold_factors": [ + "thresholdFactors": [ { "discriminator": "fromHash", "fromHash": { @@ -188,12 +174,12 @@ mod tests { } } ], - "override_factors": [] + "overrideFactors": [] }, - "recovery_role": { + "recoveryRole": { "threshold": 0, - "threshold_factors": [], - "override_factors": [ + "thresholdFactors": [], + "overrideFactors": [ { "discriminator": "fromHash", "fromHash": { @@ -210,10 +196,10 @@ mod tests { } ] }, - "confirmation_role": { + "confirmationRole": { "threshold": 0, - "threshold_factors": [], - "override_factors": [ + "thresholdFactors": [], + "overrideFactors": [ { "discriminator": "fromHash", "fromHash": { @@ -223,7 +209,7 @@ mod tests { } ] }, - "number_of_days_until_auto_confirm": 14 + "numberOfDaysUntilAutoConfirm": 14 } "#, ); @@ -235,11 +221,11 @@ mod tests { assert_eq_after_json_roundtrip( &sut, r#" - { - "primary_role": { + { + "primaryRole": { "threshold": 0, - "threshold_factors": [], - "override_factors": [ + "thresholdFactors": [], + "overrideFactors": [ { "discriminator": "fromHash", "fromHash": { @@ -249,10 +235,10 @@ mod tests { } ] }, - "recovery_role": { + "recoveryRole": { "threshold": 0, - "threshold_factors": [], - "override_factors": [ + "thresholdFactors": [], + "overrideFactors": [ { "discriminator": "fromHash", "fromHash": { @@ -262,10 +248,10 @@ mod tests { } ] }, - "confirmation_role": { + "confirmationRole": { "threshold": 0, - "threshold_factors": [], - "override_factors": [ + "thresholdFactors": [], + "overrideFactors": [ { "discriminator": "fromHash", "fromHash": { @@ -275,7 +261,7 @@ mod tests { } ] }, - "number_of_days_until_auto_confirm": 14 + "numberOfDaysUntilAutoConfirm": 14 } "#, ); diff --git a/crates/rules/src/roles/abstract_role_builder_or_built.rs b/crates/rules/src/roles/abstract_role_builder_or_built.rs index c3180a5a..de35d3b0 100644 --- a/crates/rules/src/roles/abstract_role_builder_or_built.rs +++ b/crates/rules/src/roles/abstract_role_builder_or_built.rs @@ -5,6 +5,7 @@ use serde::{Deserialize, Serialize}; use crate::prelude::*; #[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] pub struct AbstractRoleBuilderOrBuilt { #[serde(skip)] #[doc(hidden)] diff --git a/crates/rules/src/roles/general_role_with_hierarchical_deterministic_factor_instances.rs b/crates/rules/src/roles/general_role_with_hierarchical_deterministic_factor_instances.rs index 9c6ffeaf..287b130f 100644 --- a/crates/rules/src/roles/general_role_with_hierarchical_deterministic_factor_instances.rs +++ b/crates/rules/src/roles/general_role_with_hierarchical_deterministic_factor_instances.rs @@ -1 +1,226 @@ -pub struct GeneralRoleWithHierarchicalDeterministicFactorInstances {} +use crate::prelude::*; + +/// A general depiction of each of the roles in a `MatrixOfFactorInstances`. +/// `SignaturesCollector` can work on any `RoleKind` when dealing with a securified entity. +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, Hash)] +#[serde(rename_all = "camelCase")] +pub struct GeneralRoleWithHierarchicalDeterministicFactorInstances { + role: RoleKind, + threshold: u8, + threshold_factors: Vec, + override_factors: Vec, +} + +impl GeneralRoleWithHierarchicalDeterministicFactorInstances { + pub fn with_factors_and_role( + role: RoleKind, + threshold_factors: impl IntoIterator, + threshold: u8, + override_factors: impl IntoIterator, + ) -> Result { + let threshold_factors = threshold_factors.into_iter().collect_vec(); + let override_factors = override_factors.into_iter().collect_vec(); + + // validate + let _ = PrimaryRoleWithFactorInstances::with_factors( + threshold, + threshold_factors + .clone() + .into_iter() + .map(FactorInstance::from) + .collect_vec(), + override_factors + .clone() + .into_iter() + .map(FactorInstance::from) + .collect_vec(), + ); + + Ok(Self { + role, + threshold, + threshold_factors, + override_factors, + }) + } +} + +impl HasRoleKindObjectSafe for GeneralRoleWithHierarchicalDeterministicFactorInstances { + fn get_role_kind(&self) -> RoleKind { + self.role + } +} + +impl TryFrom<(MatrixOfFactorInstances, RoleKind)> + for GeneralRoleWithHierarchicalDeterministicFactorInstances +{ + type Error = CommonError; + + fn try_from( + (matrix, role_kind): (MatrixOfFactorInstances, RoleKind), + ) -> Result { + let threshold_factors: Vec; + let override_factors: Vec; + let threshold: u8; + + match role_kind { + RoleKind::Primary => { + let role = matrix.primary(); + threshold = role.get_threshold(); + threshold_factors = role.get_threshold_factors().clone(); + override_factors = role.get_override_factors().clone(); + } + RoleKind::Recovery => { + let role = matrix.recovery(); + threshold = role.get_threshold(); + threshold_factors = role.get_threshold_factors().clone(); + override_factors = role.get_override_factors().clone(); + } + RoleKind::Confirmation => { + let role = matrix.confirmation(); + threshold = role.get_threshold(); + threshold_factors = role.get_threshold_factors().clone(); + override_factors = role.get_override_factors().clone(); + } + } + + Self::with_factors_and_role( + role_kind, + threshold_factors + .iter() + .map(|f| { + HierarchicalDeterministicFactorInstance::try_from_factor_instance(f.clone()) + }) + .collect::, CommonError>>()?, + threshold, + override_factors + .iter() + .map(|f| { + HierarchicalDeterministicFactorInstance::try_from_factor_instance(f.clone()) + }) + .collect::, CommonError>>()?, + ) + } +} + +impl GeneralRoleWithHierarchicalDeterministicFactorInstances { + pub fn single_override( + role: RoleKind, + factor: HierarchicalDeterministicFactorInstance, + ) -> Self { + assert!(factor.is_securified(), "non securified factor"); + Self::with_factors_and_role(role, [], 0, [factor]) + .expect("Zero threshold with zero threshold factors and one override should not fail.") + } + + pub fn single_threshold( + role: RoleKind, + factor: HierarchicalDeterministicFactorInstance, + ) -> Self { + assert!(factor.is_securified(), "non securified factor"); + Self::with_factors_and_role(role, [factor], 1, []) + .expect("Single threshold with one threshold factor should not fail.") + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[allow(clippy::upper_case_acronyms)] + type SUT = GeneralRoleWithHierarchicalDeterministicFactorInstances; + + fn matrix() -> MatrixOfFactorInstances { + MatrixOfFactorInstances::sample() + } + + #[test] + fn test_from_primary_role() { + pretty_assertions::assert_eq!( + SUT::try_from( + (matrix(), RoleKind::Primary) + ).unwrap(), + SUT::with_factors_and_role( + RoleKind::Primary, + [ + HierarchicalDeterministicFactorInstance::sample_mainnet_account_device_factor_fs_0_securified_at_index(0), + HierarchicalDeterministicFactorInstance::sample_mainnet_account_device_factor_fs_1_securified_at_index(0) + ], + 2, + [] + ).unwrap() + ) + } + + #[test] + fn test_get_role() { + let test = |role: RoleKind| { + let sut = SUT::single_override( + role, + HierarchicalDeterministicFactorInstance::sample_mainnet_account_device_factor_fs_0_securified_at_index(0) + ); + assert_eq!(sut.role, role); + }; + test(RoleKind::Primary); + test(RoleKind::Confirmation); + test(RoleKind::Recovery); + } + + #[test] + fn test_from_recovery_role() { + let m = matrix(); + let r = m.recovery(); + assert_eq!( + SUT::try_from((matrix(), RoleKind::Recovery)).unwrap(), + SUT::with_factors_and_role( + RoleKind::Recovery, + [], + 0, + r.override_factors() + .clone() + .into_iter() + .map(|f: FactorInstance| { + HierarchicalDeterministicFactorInstance::try_from_factor_instance(f) + .unwrap() + }) + .collect_vec(), + ) + .unwrap() + ) + } + + #[test] + fn test_from_confirmation_role() { + let m = matrix(); + let r = m.confirmation(); + assert_eq!( + SUT::try_from((matrix(), RoleKind::Confirmation)).unwrap(), + SUT::with_factors_and_role( + RoleKind::Confirmation, + [], + 0, + r.override_factors() + .clone() + .into_iter() + .map(|f: FactorInstance| { + HierarchicalDeterministicFactorInstance::try_from_factor_instance(f) + .unwrap() + }) + .collect_vec(), + ) + .unwrap() + ) + } + + #[test] + fn test_from_matrix_containing_physical_badge() { + let mut matrix = MatrixOfFactorInstances::sample(); + matrix.primary_role = + PrimaryRoleWithFactorInstances::with_factors(0, [], [FactorInstance::sample_other()]); + + assert_eq!( + SUT::try_from((matrix, RoleKind::Primary)), + Err(CommonError::BadgeIsNotVirtualHierarchicalDeterministic) + ); + } +} diff --git a/crates/rules/src/roles/role_with_factor_instances.rs b/crates/rules/src/roles/role_with_factor_instances.rs index a3bcc3c8..d9404fe1 100644 --- a/crates/rules/src/roles/role_with_factor_instances.rs +++ b/crates/rules/src/roles/role_with_factor_instances.rs @@ -151,9 +151,9 @@ mod primary_tests { assert_eq_after_json_roundtrip( &sut, r#" - { + { "threshold": 2, - "threshold_factors": [ + "thresholdFactors": [ { "factorSourceID": { "discriminator": "fromHash", @@ -205,7 +205,7 @@ mod primary_tests { } } ], - "override_factors": [] + "overrideFactors": [] } "#, ); diff --git a/crates/rules/src/roles/roles_with_factor_ids.rs b/crates/rules/src/roles/roles_with_factor_ids.rs index 82cb24bb..47b36eee 100644 --- a/crates/rules/src/roles/roles_with_factor_ids.rs +++ b/crates/rules/src/roles/roles_with_factor_ids.rs @@ -128,7 +128,7 @@ mod primary_tests { r#" { "threshold": 2, - "threshold_factors": [ + "thresholdFactors": [ { "discriminator": "fromHash", "fromHash": { @@ -144,7 +144,7 @@ mod primary_tests { } } ], - "override_factors": [] + "overrideFactors": [] } "#, ); @@ -188,8 +188,8 @@ mod recovery_tests { r#" { "threshold": 0, - "threshold_factors": [], - "override_factors": [ + "thresholdFactors": [], + "overrideFactors": [ { "discriminator": "fromHash", "fromHash": { @@ -248,8 +248,8 @@ mod confirmation_tests { r#" { "threshold": 0, - "threshold_factors": [], - "override_factors": [ + "thresholdFactors": [], + "overrideFactors": [ { "discriminator": "fromHash", "fromHash": { diff --git a/crates/rules/src/security_structure_of_factors/abstract_security_structure_of_factors.rs b/crates/rules/src/security_structure_of_factors/abstract_security_structure_of_factors.rs index 217c2dde..615a23a2 100644 --- a/crates/rules/src/security_structure_of_factors/abstract_security_structure_of_factors.rs +++ b/crates/rules/src/security_structure_of_factors/abstract_security_structure_of_factors.rs @@ -1,6 +1,7 @@ use crate::prelude::*; #[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] pub struct AbstractSecurityStructure { /// Metadata of this Security Structure, such as globally unique and /// stable identifier, creation date and user chosen label (name). diff --git a/crates/rules/src/security_structure_of_factors/security_structure_of_factor_source_ids.rs b/crates/rules/src/security_structure_of_factors/security_structure_of_factor_source_ids.rs index 4b68d7b2..486d895a 100644 --- a/crates/rules/src/security_structure_of_factors/security_structure_of_factor_source_ids.rs +++ b/crates/rules/src/security_structure_of_factors/security_structure_of_factor_source_ids.rs @@ -37,17 +37,17 @@ mod tests { assert_eq_after_json_roundtrip( &sut, r#" - { + { "metadata": { "id": "ffffffff-ffff-ffff-ffff-ffffffffffff", "displayName": "Spending Account", "createdOn": "2023-09-11T16:05:56.000Z", "lastUpdatedOn": "2023-09-11T16:05:56.000Z" }, - "matrix_of_factors": { - "primary_role": { + "matrixOfFactors": { + "primaryRole": { "threshold": 2, - "threshold_factors": [ + "thresholdFactors": [ { "discriminator": "fromHash", "fromHash": { @@ -63,12 +63,12 @@ mod tests { } } ], - "override_factors": [] + "overrideFactors": [] }, - "recovery_role": { + "recoveryRole": { "threshold": 0, - "threshold_factors": [], - "override_factors": [ + "thresholdFactors": [], + "overrideFactors": [ { "discriminator": "fromHash", "fromHash": { @@ -85,10 +85,10 @@ mod tests { } ] }, - "confirmation_role": { + "confirmationRole": { "threshold": 0, - "threshold_factors": [], - "override_factors": [ + "thresholdFactors": [], + "overrideFactors": [ { "discriminator": "fromHash", "fromHash": { @@ -98,7 +98,7 @@ mod tests { } ] }, - "number_of_days_until_auto_confirm": 14 + "numberOfDaysUntilAutoConfirm": 14 } } "#, @@ -118,11 +118,11 @@ mod tests { "createdOn": "2023-12-24T17:13:56.123Z", "lastUpdatedOn": "2023-12-24T17:13:56.123Z" }, - "matrix_of_factors": { - "primary_role": { + "matrixOfFactors": { + "primaryRole": { "threshold": 0, - "threshold_factors": [], - "override_factors": [ + "thresholdFactors": [], + "overrideFactors": [ { "discriminator": "fromHash", "fromHash": { @@ -132,10 +132,10 @@ mod tests { } ] }, - "recovery_role": { + "recoveryRole": { "threshold": 0, - "threshold_factors": [], - "override_factors": [ + "thresholdFactors": [], + "overrideFactors": [ { "discriminator": "fromHash", "fromHash": { @@ -145,10 +145,10 @@ mod tests { } ] }, - "confirmation_role": { + "confirmationRole": { "threshold": 0, - "threshold_factors": [], - "override_factors": [ + "thresholdFactors": [], + "overrideFactors": [ { "discriminator": "fromHash", "fromHash": { @@ -158,10 +158,10 @@ mod tests { } ] }, - "number_of_days_until_auto_confirm": 14 + "numberOfDaysUntilAutoConfirm": 14 } } - "#, + "#, ); } } From cb0ecdcdccb09187fde3e9c5a6343f055f288532 Mon Sep 17 00:00:00 2001 From: Alexander Cyon Date: Fri, 29 Nov 2024 12:02:49 +0100 Subject: [PATCH 20/33] more tests --- .vscode/settings.json | 1 + ...archical_deterministic_factor_instances.rs | 23 +++++++++++++++++++ 2 files changed, 24 insertions(+) diff --git a/.vscode/settings.json b/.vscode/settings.json index f01e03c0..6d13a41c 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -4,6 +4,7 @@ "Appendable", "Banksy", "bdfs", + "fsid", "Fulfillable", "interactor", "Interactors", diff --git a/crates/rules/src/roles/general_role_with_hierarchical_deterministic_factor_instances.rs b/crates/rules/src/roles/general_role_with_hierarchical_deterministic_factor_instances.rs index 287b130f..e07fdcca 100644 --- a/crates/rules/src/roles/general_role_with_hierarchical_deterministic_factor_instances.rs +++ b/crates/rules/src/roles/general_role_with_hierarchical_deterministic_factor_instances.rs @@ -123,6 +123,18 @@ impl GeneralRoleWithHierarchicalDeterministicFactorInstances { } } +impl HasSampleValues for GeneralRoleWithHierarchicalDeterministicFactorInstances { + fn sample() -> Self { + Self::try_from((MatrixOfFactorInstances::sample(), RoleKind::Primary)) + .expect("Sample should not fail") + } + + fn sample_other() -> Self { + Self::try_from((MatrixOfFactorInstances::sample_other(), RoleKind::Recovery)) + .expect("Sample should not fail") + } +} + #[cfg(test)] mod test { use super::*; @@ -130,6 +142,17 @@ mod test { #[allow(clippy::upper_case_acronyms)] type SUT = GeneralRoleWithHierarchicalDeterministicFactorInstances; + #[test] + fn equality() { + assert_eq!(SUT::sample(), SUT::sample()); + assert_eq!(SUT::sample_other(), SUT::sample_other()); + } + + #[test] + fn inequality() { + assert_ne!(SUT::sample(), SUT::sample_other()); + } + fn matrix() -> MatrixOfFactorInstances { MatrixOfFactorInstances::sample() } From 8cccc58d39a59c9e228ed4bb2f6e547c71577bcd Mon Sep 17 00:00:00 2001 From: Alexander Cyon Date: Fri, 29 Nov 2024 12:09:38 +0100 Subject: [PATCH 21/33] tarpaulin accuracy --- .../rules/src/roles/builder/roles_builder.rs | 128 ++++++++---------- 1 file changed, 55 insertions(+), 73 deletions(-) diff --git a/crates/rules/src/roles/builder/roles_builder.rs b/crates/rules/src/roles/builder/roles_builder.rs index 92cb218d..4c2ac22b 100644 --- a/crates/rules/src/roles/builder/roles_builder.rs +++ b/crates/rules/src/roles/builder/roles_builder.rs @@ -1,5 +1,7 @@ use crate::prelude::*; +use FactorListKind::*; + pub type PrimaryRoleBuilder = RoleBuilder<{ ROLE_PRIMARY }>; pub type RecoveryRoleBuilder = RoleBuilder<{ ROLE_RECOVERY }>; pub type ConfirmationRoleBuilder = RoleBuilder<{ ROLE_CONFIRMATION }>; @@ -44,7 +46,7 @@ pub enum RoleBuilderValidation { #[error("Not yet valid: {0}")] NotYetValid(#[from] NotYetValidReason), } -type Validation = RoleBuilderValidation; +use RoleBuilderValidation::*; #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, thiserror::Error)] pub enum BasicViolation { @@ -115,7 +117,7 @@ pub(crate) trait FromForeverInvalid { } impl FromForeverInvalid for std::result::Result { fn forever_invalid(reason: ForeverInvalidReason) -> Self { - Err(Validation::ForeverInvalid(reason)) + Err(ForeverInvalid(reason)) } } @@ -124,7 +126,7 @@ pub(crate) trait FromNotYetValid { } impl FromNotYetValid for std::result::Result { fn not_yet_valid(reason: NotYetValidReason) -> Self { - Err(Validation::NotYetValid(reason)) + Err(NotYetValid(reason)) } } @@ -133,7 +135,7 @@ pub(crate) trait FromBasicViolation { } impl FromBasicViolation for std::result::Result { fn basic_violation(reason: BasicViolation) -> Self { - Err(Validation::BasicViolation(reason)) + Err(BasicViolation(reason)) } } @@ -223,7 +225,7 @@ where &mut self, factor_source_id: FactorSourceID, ) -> RoleBuilderMutateResult { - self._add_factor_source_to_list(factor_source_id, FactorListKind::Threshold) + self._add_factor_source_to_list(factor_source_id, Threshold) } /// If we would add a factor of kind `factor_source_kind` to the list of kind `factor_list_kind` @@ -232,10 +234,7 @@ where &self, factor_source_kind: FactorSourceKind, ) -> RoleBuilderMutateResult { - self._validation_for_addition_of_factor_source_of_kind_to_list( - factor_source_kind, - FactorListKind::Threshold, - ) + self._validation_add(factor_source_kind, Threshold) } #[cfg(test)] @@ -244,7 +243,7 @@ where factor_source_kind: FactorSourceKind, list: FactorListKind, ) -> RoleBuilderMutateResult { - self._validation_for_addition_of_factor_source_of_kind_to_list(factor_source_kind, list) + self._validation_add(factor_source_kind, list) } } @@ -271,7 +270,7 @@ impl RoleBuilder { &mut self, factor_source_id: FactorSourceID, ) -> RoleBuilderMutateResult { - self._add_factor_source_to_list(factor_source_id, FactorListKind::Override) + self._add_factor_source_to_list(factor_source_id, Override) } /// If Ok => self is mutated @@ -285,10 +284,10 @@ impl RoleBuilder { let validation = self .validation_for_addition_of_factor_source_to_list(&factor_source_id, factor_list_kind); match validation.as_ref() { - Ok(()) | Err(Validation::NotYetValid(_)) => { + Ok(()) | Err(NotYetValid(_)) => { self.unchecked_add_factor_source_to_list(factor_source_id, factor_list_kind); } - Err(Validation::ForeverInvalid(_)) | Err(Validation::BasicViolation(_)) => {} + Err(ForeverInvalid(_)) | Err(BasicViolation(_)) => {} } validation } @@ -299,31 +298,35 @@ impl RoleBuilder { &self, factor_source_kind: FactorSourceKind, ) -> RoleBuilderMutateResult { - self._validation_for_addition_of_factor_source_of_kind_to_list( - factor_source_kind, - FactorListKind::Override, - ) + self._validation_add(factor_source_kind, Override) } /// If we would add a factor of kind `factor_source_kind` to the list of kind `factor_list_kind` /// what would be the validation status? - fn _validation_for_addition_of_factor_source_of_kind_to_list( + fn _validation_add( &self, factor_source_kind: FactorSourceKind, factor_list_kind: FactorListKind, ) -> RoleBuilderMutateResult { match self.role() { - RoleKind::Primary => self.validation_for_addition_of_factor_source_of_kind_to_list_for_primary(factor_source_kind, factor_list_kind), + RoleKind::Primary => { + return self.validation_for_addition_of_factor_source_of_kind_to_list_for_primary( + factor_source_kind, + factor_list_kind, + ) + } RoleKind::Recovery | RoleKind::Confirmation => match factor_list_kind { - FactorListKind::Threshold => { - RoleBuilderMutateResult::forever_invalid(ForeverInvalidReason::threshold_list_not_supported_for_role(self.role())) + Threshold => { + return Result::forever_invalid( + ForeverInvalidReason::threshold_list_not_supported_for_role(self.role()), + ) } - FactorListKind::Override => self - .validation_for_addition_of_factor_source_of_kind_to_override_for_non_primary_role( - factor_source_kind, - ), + Override => {} }, } + self.validation_for_addition_of_factor_source_of_kind_to_override_for_non_primary_role( + factor_source_kind, + ) } } @@ -384,26 +387,20 @@ impl RoleBuilder { let mut simulation = Self::new(); // Validate override factors - for override_factor in self.get_override_factors() { - let validation = - simulation._add_factor_source_to_list(*override_factor, FactorListKind::Override); + for f in self.get_override_factors() { + let validation = simulation.add_factor_source_to_override(*f); match validation.as_ref() { - Ok(()) | Err(Validation::NotYetValid(_)) => continue, - Err(Validation::ForeverInvalid(_)) | Err(Validation::BasicViolation(_)) => { - return validation - } + Ok(()) | Err(NotYetValid(_)) => continue, + Err(ForeverInvalid(_)) | Err(BasicViolation(_)) => return validation, } } // Validate threshold factors - for threshold_factor in self.get_threshold_factors() { - let validation = - simulation._add_factor_source_to_list(*threshold_factor, FactorListKind::Threshold); + for f in self.get_threshold_factors() { + let validation = simulation._add_factor_source_to_list(*f, Threshold); match validation.as_ref() { - Ok(()) | Err(Validation::NotYetValid(_)) => continue, - Err(Validation::ForeverInvalid(_)) | Err(Validation::BasicViolation(_)) => { - return validation - } + Ok(()) | Err(NotYetValid(_)) => continue, + Err(ForeverInvalid(_)) | Err(BasicViolation(_)) => return validation, } } @@ -490,10 +487,7 @@ impl RoleBuilder { return RoleBuilderMutateResult::forever_invalid(FactorSourceAlreadyPresent); } let factor_source_kind = factor_source_id.get_factor_source_kind(); - self._validation_for_addition_of_factor_source_of_kind_to_list( - factor_source_kind, - factor_list_kind, - ) + self._validation_add(factor_source_kind, factor_list_kind) } fn contains_factor_source(&self, factor_source_id: &FactorSourceID) -> bool { @@ -624,12 +618,9 @@ impl RoleBuilder { assert_eq!(self.role(), RoleKind::Primary); let factor_source_kind = FactorSourceKind::Passphrase; match factor_list_kind { - FactorListKind::Threshold => { + Threshold => { let is_alone = self - .factor_sources_not_of_kind_to_list_of_kind( - factor_source_kind, - FactorListKind::Threshold, - ) + .factor_sources_not_of_kind_to_list_of_kind(factor_source_kind, Threshold) .is_empty(); if is_alone { return RoleBuilderMutateResult::not_yet_valid( @@ -642,7 +633,7 @@ impl RoleBuilder { ); } } - FactorListKind::Override => { + Override => { return RoleBuilderMutateResult::forever_invalid( PrimaryCannotHavePasswordInOverrideList, ); @@ -664,8 +655,8 @@ impl RoleBuilder { .collect() }; match factor_list_kind { - FactorListKind::Override => filter(self.get_override_factors()), - FactorListKind::Threshold => filter(self.get_threshold_factors()), + Override => filter(self.get_override_factors()), + Threshold => filter(self.get_threshold_factors()), } } } @@ -691,7 +682,7 @@ pub(crate) fn test_duplicates_not_allowed( // Assert assert!(matches!( res, - RoleBuilderMutateResult::Err(Validation::ForeverInvalid( + RoleBuilderMutateResult::Err(ForeverInvalid( ForeverInvalidReason::FactorSourceAlreadyPresent )) )); @@ -706,12 +697,12 @@ mod tests { fn primary_duplicates_not_allowed() { test_duplicates_not_allowed( PrimaryRoleBuilder::new(), - FactorListKind::Override, + Override, FactorSourceID::sample_arculus(), ); test_duplicates_not_allowed( PrimaryRoleBuilder::new(), - FactorListKind::Threshold, + Threshold, FactorSourceID::sample_arculus(), ); } @@ -720,7 +711,7 @@ mod tests { fn recovery_duplicates_not_allowed() { test_duplicates_not_allowed( RecoveryRoleBuilder::new(), - FactorListKind::Override, + Override, FactorSourceID::sample_arculus(), ); } @@ -729,7 +720,7 @@ mod tests { fn confirmation_duplicates_not_allowed() { test_duplicates_not_allowed( ConfirmationRoleBuilder::new(), - FactorListKind::Override, + Override, FactorSourceID::sample_arculus(), ); } @@ -737,11 +728,10 @@ mod tests { #[test] fn recovery_cannot_add_factors_to_threshold() { let mut sut = RecoveryRoleBuilder::new(); - let res = sut - ._add_factor_source_to_list(FactorSourceID::sample_ledger(), FactorListKind::Threshold); + let res = sut._add_factor_source_to_list(FactorSourceID::sample_ledger(), Threshold); assert_eq!( res, - Err(Validation::ForeverInvalid( + Err(ForeverInvalid( ForeverInvalidReason::RecoveryRoleThresholdFactorsNotSupported )) ); @@ -750,23 +740,19 @@ mod tests { #[test] fn confirmation_cannot_add_factors_to_threshold() { let mut sut = ConfirmationRoleBuilder::new(); - let res = sut - ._add_factor_source_to_list(FactorSourceID::sample_ledger(), FactorListKind::Threshold); + let res = sut._add_factor_source_to_list(FactorSourceID::sample_ledger(), Threshold); assert_eq!( res, - Err(Validation::ForeverInvalid( + Err(ForeverInvalid( ForeverInvalidReason::ConfirmationRoleThresholdFactorsNotSupported )) ); } #[test] - fn recovery_validation_for_addition_of_factor_source_of_kind_to_list_is_err_for_threshold() { + fn recovery_validation_add_is_err_for_threshold() { let sut = RecoveryRoleBuilder::new(); - let res = sut._validation_for_addition_of_factor_source_of_kind_to_list( - FactorSourceKind::Device, - FactorListKind::Threshold, - ); + let res = sut._validation_add(FactorSourceKind::Device, Threshold); assert_eq!( res, RoleBuilderMutateResult::forever_invalid( @@ -776,13 +762,9 @@ mod tests { } #[test] - fn confirmation_validation_for_addition_of_factor_source_of_kind_to_list_is_err_for_threshold() - { + fn confirmation_validation_add_is_err_for_threshold() { let sut = ConfirmationRoleBuilder::new(); - let res = sut._validation_for_addition_of_factor_source_of_kind_to_list( - FactorSourceKind::Device, - FactorListKind::Threshold, - ); + let res = sut._validation_add(FactorSourceKind::Device, Threshold); assert_eq!( res, RoleBuilderMutateResult::forever_invalid( From bc64b75ed46d8912e2aa4842bcb0bd3354958d35 Mon Sep 17 00:00:00 2001 From: Alexander Cyon Date: Fri, 29 Nov 2024 14:16:43 +0100 Subject: [PATCH 22/33] code covereage --- crates/rules/Cargo.toml | 5 ++++- .../matrices/matrix_of_factor_instances.rs | 4 ++++ .../matrices/matrix_of_factor_source_ids.rs | 1 + .../src/matrices/matrix_of_factor_sources.rs | 17 ++++++--------- .../roles/abstract_role_builder_or_built.rs | 14 ------------- .../rules/src/roles/builder/roles_builder.rs | 3 +++ ...archical_deterministic_factor_instances.rs | 21 ++++++++++++++++--- .../src/roles/role_with_factor_instances.rs | 2 +- 8 files changed, 37 insertions(+), 30 deletions(-) diff --git a/crates/rules/Cargo.toml b/crates/rules/Cargo.toml index c3bcb256..b9ce5098 100644 --- a/crates/rules/Cargo.toml +++ b/crates/rules/Cargo.toml @@ -5,10 +5,13 @@ edition = "2021" [dependencies] thiserror = { workspace = true } -sargon = { workspace = true} +sargon = { workspace = true } serde = { version = "1.0.215", features = ["derive"] } pretty_assertions = "1.4.1" serde_json = { version = "1.0.133", features = ["preserve_order"] } assert-json-diff = "2.0.2" once_cell = "1.20.2" itertools = "0.13.0" + +[lints.rust] +unexpected_cfgs = { level = "warn", check-cfg = ["cfg(tarpaulin_include)"] } diff --git a/crates/rules/src/matrices/matrix_of_factor_instances.rs b/crates/rules/src/matrices/matrix_of_factor_instances.rs index 0529e70d..efb8ede3 100644 --- a/crates/rules/src/matrices/matrix_of_factor_instances.rs +++ b/crates/rules/src/matrices/matrix_of_factor_instances.rs @@ -122,6 +122,10 @@ mod tests { #[test] fn inequality() { assert_ne!(SUT::sample(), SUT::sample_other()); + assert_ne!( + SUT::sample().unique_factor_instances(), + SUT::sample_other().unique_factor_instances() + ); } #[test] diff --git a/crates/rules/src/matrices/matrix_of_factor_source_ids.rs b/crates/rules/src/matrices/matrix_of_factor_source_ids.rs index 67d909a7..fc5b3a0a 100644 --- a/crates/rules/src/matrices/matrix_of_factor_source_ids.rs +++ b/crates/rules/src/matrices/matrix_of_factor_source_ids.rs @@ -141,6 +141,7 @@ mod tests { #[test] fn inequality() { assert_ne!(SUT::sample(), SUT::sample_other()); + assert_ne!(SUT::sample(), SUT::sample_config_12()); assert_ne!(SUT::sample().primary(), SUT::sample_other().primary()); assert_ne!(SUT::sample().recovery(), SUT::sample_other().recovery()); assert_ne!( diff --git a/crates/rules/src/matrices/matrix_of_factor_sources.rs b/crates/rules/src/matrices/matrix_of_factor_sources.rs index 745c1b48..b083100f 100644 --- a/crates/rules/src/matrices/matrix_of_factor_sources.rs +++ b/crates/rules/src/matrices/matrix_of_factor_sources.rs @@ -4,19 +4,15 @@ pub type MatrixOfFactorSources = AbstractMatrixBuilt; impl MatrixOfFactorSources { pub fn new( - matrix_of_factor_source_ids: MatrixOfFactorSourceIds, + matrix: MatrixOfFactorSourceIds, factor_sources: &FactorSources, ) -> Result { - let primary_role = - RoleWithFactorSources::new(matrix_of_factor_source_ids.primary_role, factor_sources)?; + let primary_role = RoleWithFactorSources::new(matrix.primary_role, factor_sources)?; - let recovery_role = - RoleWithFactorSources::new(matrix_of_factor_source_ids.recovery_role, factor_sources)?; + let recovery_role = RoleWithFactorSources::new(matrix.recovery_role, factor_sources)?; - let confirmation_role = RoleWithFactorSources::new( - matrix_of_factor_source_ids.confirmation_role, - factor_sources, - )?; + let confirmation_role = + RoleWithFactorSources::new(matrix.confirmation_role, factor_sources)?; if primary_role.role() != RoleKind::Primary || recovery_role.role() != RoleKind::Recovery @@ -30,8 +26,7 @@ impl MatrixOfFactorSources { primary_role, recovery_role, confirmation_role, - number_of_days_until_auto_confirm: matrix_of_factor_source_ids - .number_of_days_until_auto_confirm, + number_of_days_until_auto_confirm: matrix.number_of_days_until_auto_confirm, }) } } diff --git a/crates/rules/src/roles/abstract_role_builder_or_built.rs b/crates/rules/src/roles/abstract_role_builder_or_built.rs index de35d3b0..7958dcc4 100644 --- a/crates/rules/src/roles/abstract_role_builder_or_built.rs +++ b/crates/rules/src/roles/abstract_role_builder_or_built.rs @@ -134,17 +134,3 @@ impl RoleBuilder { self.threshold = threshold; } } - -impl AbstractBuiltRoleWithFactor { - pub fn threshold(&self) -> u8 { - self.threshold - } - - pub fn threshold_factors(&self) -> &Vec { - &self.threshold_factors - } - - pub fn override_factors(&self) -> &Vec { - &self.override_factors - } -} diff --git a/crates/rules/src/roles/builder/roles_builder.rs b/crates/rules/src/roles/builder/roles_builder.rs index 4c2ac22b..ebf4ef26 100644 --- a/crates/rules/src/roles/builder/roles_builder.rs +++ b/crates/rules/src/roles/builder/roles_builder.rs @@ -535,6 +535,7 @@ impl RoleBuilder { Ok(()) } + #[cfg(not(tarpaulin_include))] // false negative fn validation_for_addition_of_factor_source_of_kind_to_list_for_primary( &self, factor_source_kind: FactorSourceKind, @@ -568,6 +569,7 @@ impl RoleBuilder { Ok(()) } + #[cfg(not(tarpaulin_include))] // false negative fn validation_for_addition_of_factor_source_of_kind_to_override_for_confirmation( &self, factor_source_kind: FactorSourceKind, @@ -586,6 +588,7 @@ impl RoleBuilder { } } + #[cfg(not(tarpaulin_include))] // false negative fn validation_for_addition_of_factor_source_of_kind_to_override_for_recovery( &self, factor_source_kind: FactorSourceKind, diff --git a/crates/rules/src/roles/general_role_with_hierarchical_deterministic_factor_instances.rs b/crates/rules/src/roles/general_role_with_hierarchical_deterministic_factor_instances.rs index e07fdcca..1b8dcdad 100644 --- a/crates/rules/src/roles/general_role_with_hierarchical_deterministic_factor_instances.rs +++ b/crates/rules/src/roles/general_role_with_hierarchical_deterministic_factor_instances.rs @@ -175,6 +175,21 @@ mod test { ) } + #[test] + fn test_single_threshold() { + pretty_assertions::assert_eq!( + SUT::single_threshold(RoleKind::Primary, HierarchicalDeterministicFactorInstance::sample_mainnet_account_device_factor_fs_1_securified_at_index(0)), + SUT::with_factors_and_role( + RoleKind::Primary, + [ + HierarchicalDeterministicFactorInstance::sample_mainnet_account_device_factor_fs_1_securified_at_index(0) + ], + 1, + [] + ).unwrap() + ) + } + #[test] fn test_get_role() { let test = |role: RoleKind| { @@ -182,7 +197,7 @@ mod test { role, HierarchicalDeterministicFactorInstance::sample_mainnet_account_device_factor_fs_0_securified_at_index(0) ); - assert_eq!(sut.role, role); + assert_eq!(sut.get_role_kind(), role); }; test(RoleKind::Primary); test(RoleKind::Confirmation); @@ -199,7 +214,7 @@ mod test { RoleKind::Recovery, [], 0, - r.override_factors() + r.get_override_factors() .clone() .into_iter() .map(|f: FactorInstance| { @@ -222,7 +237,7 @@ mod test { RoleKind::Confirmation, [], 0, - r.override_factors() + r.get_override_factors() .clone() .into_iter() .map(|f: FactorInstance| { diff --git a/crates/rules/src/roles/role_with_factor_instances.rs b/crates/rules/src/roles/role_with_factor_instances.rs index d9404fe1..a643396b 100644 --- a/crates/rules/src/roles/role_with_factor_instances.rs +++ b/crates/rules/src/roles/role_with_factor_instances.rs @@ -6,7 +6,7 @@ pub(crate) type RoleWithFactorInstances = impl RoleWithFactorSources { fn from(other: &RoleWithFactorSources) -> Self { Self::with_factors( - other.threshold(), + other.get_threshold(), other.get_threshold_factors().clone(), other.get_override_factors().clone(), ) From 9734ea38d0803bb45e9ca4b8a18c83766e6df22d Mon Sep 17 00:00:00 2001 From: Alexander Cyon Date: Fri, 29 Nov 2024 14:36:28 +0100 Subject: [PATCH 23/33] split --- ...confirmation_role_with_factor_instances.rs | 33 +++ ...archical_deterministic_factor_instances.rs | 0 .../factor_instance_level/mod.rs | 11 + .../primary_role_with_factor_instances.rs | 110 ++++++++ .../recovery_role_with_factor_instances.rs | 32 +++ .../role_with_factor_instances.rs | 77 +++++ ...onfirmation_role_with_factor_source_ids.rs | 100 +++++++ .../factor_source_id_level/mod.rs | 9 + .../primary_role_with_factor_source_ids.rs | 104 +++++++ .../recovery_role_with_factor_source_ids.rs | 63 +++++ .../roles_with_factor_ids.rs | 3 + .../confirmation_role_with_factor_sources.rs | 36 +++ .../factor_levels/factor_source_level/mod.rs | 6 + .../primary_role_with_factor_sources.rs | 36 +++ .../recovery_role_with_factor_sources.rs | 36 +++ .../roles_with_factor_sources.rs | 32 +++ crates/rules/src/roles/factor_levels/mod.rs | 7 + crates/rules/src/roles/mod.rs | 10 +- .../src/roles/role_with_factor_instances.rs | 251 ----------------- .../rules/src/roles/roles_with_factor_ids.rs | 265 ------------------ .../src/roles/roles_with_factor_sources.rs | 134 --------- 21 files changed, 697 insertions(+), 658 deletions(-) create mode 100644 crates/rules/src/roles/factor_levels/factor_instance_level/confirmation_role_with_factor_instances.rs rename crates/rules/src/roles/{ => factor_levels/factor_instance_level}/general_role_with_hierarchical_deterministic_factor_instances.rs (100%) create mode 100644 crates/rules/src/roles/factor_levels/factor_instance_level/mod.rs create mode 100644 crates/rules/src/roles/factor_levels/factor_instance_level/primary_role_with_factor_instances.rs create mode 100644 crates/rules/src/roles/factor_levels/factor_instance_level/recovery_role_with_factor_instances.rs create mode 100644 crates/rules/src/roles/factor_levels/factor_instance_level/role_with_factor_instances.rs create mode 100644 crates/rules/src/roles/factor_levels/factor_source_id_level/confirmation_role_with_factor_source_ids.rs create mode 100644 crates/rules/src/roles/factor_levels/factor_source_id_level/mod.rs create mode 100644 crates/rules/src/roles/factor_levels/factor_source_id_level/primary_role_with_factor_source_ids.rs create mode 100644 crates/rules/src/roles/factor_levels/factor_source_id_level/recovery_role_with_factor_source_ids.rs create mode 100644 crates/rules/src/roles/factor_levels/factor_source_id_level/roles_with_factor_ids.rs create mode 100644 crates/rules/src/roles/factor_levels/factor_source_level/confirmation_role_with_factor_sources.rs create mode 100644 crates/rules/src/roles/factor_levels/factor_source_level/mod.rs create mode 100644 crates/rules/src/roles/factor_levels/factor_source_level/primary_role_with_factor_sources.rs create mode 100644 crates/rules/src/roles/factor_levels/factor_source_level/recovery_role_with_factor_sources.rs create mode 100644 crates/rules/src/roles/factor_levels/factor_source_level/roles_with_factor_sources.rs create mode 100644 crates/rules/src/roles/factor_levels/mod.rs delete mode 100644 crates/rules/src/roles/role_with_factor_instances.rs delete mode 100644 crates/rules/src/roles/roles_with_factor_ids.rs delete mode 100644 crates/rules/src/roles/roles_with_factor_sources.rs diff --git a/crates/rules/src/roles/factor_levels/factor_instance_level/confirmation_role_with_factor_instances.rs b/crates/rules/src/roles/factor_levels/factor_instance_level/confirmation_role_with_factor_instances.rs new file mode 100644 index 00000000..179d18f1 --- /dev/null +++ b/crates/rules/src/roles/factor_levels/factor_instance_level/confirmation_role_with_factor_instances.rs @@ -0,0 +1,33 @@ +use crate::prelude::*; + +pub(crate) type ConfirmationRoleWithFactorInstances = + RoleWithFactorInstances<{ ROLE_CONFIRMATION }>; + +impl HasSampleValues for ConfirmationRoleWithFactorInstances { + fn sample() -> Self { + MatrixOfFactorInstances::sample().confirmation_role + } + + fn sample_other() -> Self { + MatrixOfFactorInstances::sample_other().confirmation_role + } +} + +#[cfg(test)] +mod confirmation_tests { + use super::*; + + #[allow(clippy::upper_case_acronyms)] + type SUT = ConfirmationRoleWithFactorInstances; + + #[test] + fn equality() { + assert_eq!(SUT::sample(), SUT::sample()); + assert_eq!(SUT::sample_other(), SUT::sample_other()); + } + + #[test] + fn inequality() { + assert_ne!(SUT::sample(), SUT::sample_other()); + } +} diff --git a/crates/rules/src/roles/general_role_with_hierarchical_deterministic_factor_instances.rs b/crates/rules/src/roles/factor_levels/factor_instance_level/general_role_with_hierarchical_deterministic_factor_instances.rs similarity index 100% rename from crates/rules/src/roles/general_role_with_hierarchical_deterministic_factor_instances.rs rename to crates/rules/src/roles/factor_levels/factor_instance_level/general_role_with_hierarchical_deterministic_factor_instances.rs diff --git a/crates/rules/src/roles/factor_levels/factor_instance_level/mod.rs b/crates/rules/src/roles/factor_levels/factor_instance_level/mod.rs new file mode 100644 index 00000000..ca98a01d --- /dev/null +++ b/crates/rules/src/roles/factor_levels/factor_instance_level/mod.rs @@ -0,0 +1,11 @@ +mod confirmation_role_with_factor_instances; +mod general_role_with_hierarchical_deterministic_factor_instances; +mod primary_role_with_factor_instances; +mod recovery_role_with_factor_instances; +mod role_with_factor_instances; + +pub(crate) use confirmation_role_with_factor_instances::*; +pub use general_role_with_hierarchical_deterministic_factor_instances::*; +pub(crate) use primary_role_with_factor_instances::*; +pub(crate) use recovery_role_with_factor_instances::*; +pub(crate) use role_with_factor_instances::*; diff --git a/crates/rules/src/roles/factor_levels/factor_instance_level/primary_role_with_factor_instances.rs b/crates/rules/src/roles/factor_levels/factor_instance_level/primary_role_with_factor_instances.rs new file mode 100644 index 00000000..de794340 --- /dev/null +++ b/crates/rules/src/roles/factor_levels/factor_instance_level/primary_role_with_factor_instances.rs @@ -0,0 +1,110 @@ +use crate::prelude::*; + +pub(crate) type PrimaryRoleWithFactorInstances = RoleWithFactorInstances<{ ROLE_PRIMARY }>; + +impl HasSampleValues for PrimaryRoleWithFactorInstances { + fn sample() -> Self { + MatrixOfFactorInstances::sample().primary_role + } + + fn sample_other() -> Self { + MatrixOfFactorInstances::sample_other().primary_role + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[allow(clippy::upper_case_acronyms)] + type SUT = PrimaryRoleWithFactorInstances; + + #[test] + fn equality() { + assert_eq!(SUT::sample(), SUT::sample()); + assert_eq!(SUT::sample_other(), SUT::sample_other()); + } + + #[test] + fn inequality() { + assert_ne!(SUT::sample(), SUT::sample_other()); + } + + #[test] + #[should_panic] + fn primary_role_non_securified_threshold_instances_is_err() { + let _ = SUT::with_factors( + 1, + [ + HierarchicalDeterministicFactorInstance::sample_mainnet_account_device_factor_fs_10_unsecurified_at_index(0).into() + ], + [] + ); + } + + #[test] + fn assert_json_sample() { + let sut = SUT::sample(); + assert_eq_after_json_roundtrip( + &sut, + r#" + { + "threshold": 2, + "thresholdFactors": [ + { + "factorSourceID": { + "discriminator": "fromHash", + "fromHash": { + "kind": "device", + "body": "f1a93d324dd0f2bff89963ab81ed6e0c2ee7e18c0827dc1d3576b2d9f26bbd0a" + } + }, + "badge": { + "discriminator": "virtualSource", + "virtualSource": { + "discriminator": "hierarchicalDeterministicPublicKey", + "hierarchicalDeterministicPublicKey": { + "publicKey": { + "curve": "curve25519", + "compressedData": "427969814e15d74c3ff4d9971465cb709d210c8a7627af9466bdaa67bd0929b7" + }, + "derivationPath": { + "scheme": "cap26", + "path": "m/44H/1022H/1H/525H/1460H/0S" + } + } + } + } + }, + { + "factorSourceID": { + "discriminator": "fromHash", + "fromHash": { + "kind": "ledgerHQHardwareWallet", + "body": "ab59987eedd181fe98e512c1ba0f5ff059f11b5c7c56f15614dcc9fe03fec58b" + } + }, + "badge": { + "discriminator": "virtualSource", + "virtualSource": { + "discriminator": "hierarchicalDeterministicPublicKey", + "hierarchicalDeterministicPublicKey": { + "publicKey": { + "curve": "curve25519", + "compressedData": "92cd6838cd4e7b0523ed93d498e093f71139ffd5d632578189b39a26005be56b" + }, + "derivationPath": { + "scheme": "cap26", + "path": "m/44H/1022H/1H/525H/1460H/0S" + } + } + } + } + } + ], + "overrideFactors": [] + } + "#, + ); + } +} diff --git a/crates/rules/src/roles/factor_levels/factor_instance_level/recovery_role_with_factor_instances.rs b/crates/rules/src/roles/factor_levels/factor_instance_level/recovery_role_with_factor_instances.rs new file mode 100644 index 00000000..3ce01203 --- /dev/null +++ b/crates/rules/src/roles/factor_levels/factor_instance_level/recovery_role_with_factor_instances.rs @@ -0,0 +1,32 @@ +use crate::prelude::*; + +pub(crate) type RecoveryRoleWithFactorInstances = RoleWithFactorInstances<{ ROLE_RECOVERY }>; + +impl HasSampleValues for RecoveryRoleWithFactorInstances { + fn sample() -> Self { + MatrixOfFactorInstances::sample().recovery_role + } + + fn sample_other() -> Self { + MatrixOfFactorInstances::sample_other().recovery_role + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[allow(clippy::upper_case_acronyms)] + type SUT = RecoveryRoleWithFactorInstances; + + #[test] + fn equality() { + assert_eq!(SUT::sample(), SUT::sample()); + assert_eq!(SUT::sample_other(), SUT::sample_other()); + } + + #[test] + fn inequality() { + assert_ne!(SUT::sample(), SUT::sample_other()); + } +} diff --git a/crates/rules/src/roles/factor_levels/factor_instance_level/role_with_factor_instances.rs b/crates/rules/src/roles/factor_levels/factor_instance_level/role_with_factor_instances.rs new file mode 100644 index 00000000..e8204238 --- /dev/null +++ b/crates/rules/src/roles/factor_levels/factor_instance_level/role_with_factor_instances.rs @@ -0,0 +1,77 @@ +use crate::prelude::*; + +pub(crate) type RoleWithFactorInstances = + AbstractBuiltRoleWithFactor; + +impl RoleWithFactorSources { + fn from(other: &RoleWithFactorSources) -> Self { + Self::with_factors( + other.get_threshold(), + other.get_threshold_factors().clone(), + other.get_override_factors().clone(), + ) + } +} + +impl MatrixOfFactorSources { + pub(crate) fn get_role(&self) -> RoleWithFactorSources { + match R { + ROLE_PRIMARY => RoleWithFactorSources::from(&self.primary_role), + ROLE_RECOVERY => RoleWithFactorSources::from(&self.recovery_role), + ROLE_CONFIRMATION => RoleWithFactorSources::from(&self.confirmation_role), + _ => panic!("unknown"), + } + } +} + +impl RoleWithFactorInstances { + pub(crate) fn fulfilling_role_of_factor_sources_with_factor_instances( + consuming_instances: &IndexMap, + matrix_of_factor_sources: &MatrixOfFactorSources, + ) -> Result { + let role_kind = RoleKind::from_u8(R).unwrap(); + + let role_of_sources = matrix_of_factor_sources.get_role::(); + assert_eq!(role_of_sources.role(), role_kind); + let threshold: u8 = role_of_sources.get_threshold(); + + // Threshold factors + let threshold_factors = + Self::try_filling_factor_list_of_role_of_factor_sources_with_factor_instances( + consuming_instances, + role_of_sources.get_threshold_factors(), + )?; + + // Override factors + let override_factors = + Self::try_filling_factor_list_of_role_of_factor_sources_with_factor_instances( + consuming_instances, + role_of_sources.get_override_factors(), + )?; + + let role_with_instances = + Self::with_factors(threshold, threshold_factors, override_factors); + + assert_eq!(role_with_instances.role(), role_kind); + Ok(role_with_instances) + } + + fn try_filling_factor_list_of_role_of_factor_sources_with_factor_instances( + instances: &IndexMap, + from: &[FactorSource], + ) -> Result, CommonError> { + from.iter() + .map(|f| { + if let Some(existing) = instances.get(&f.id_from_hash()) { + let hd_instance = existing + .first() + .ok_or(CommonError::MissingFactorMappingInstancesIntoRole)?; + let instance = FactorInstance::from(hd_instance); + Ok(instance) + } else { + Err(CommonError::MissingFactorMappingInstancesIntoRole) + } + }) + .collect::, CommonError>>() + } +} diff --git a/crates/rules/src/roles/factor_levels/factor_source_id_level/confirmation_role_with_factor_source_ids.rs b/crates/rules/src/roles/factor_levels/factor_source_id_level/confirmation_role_with_factor_source_ids.rs new file mode 100644 index 00000000..df0810e8 --- /dev/null +++ b/crates/rules/src/roles/factor_levels/factor_source_id_level/confirmation_role_with_factor_source_ids.rs @@ -0,0 +1,100 @@ +use crate::prelude::*; + +pub type ConfirmationRoleWithFactorSourceIds = RoleWithFactorSourceIds<{ ROLE_CONFIRMATION }>; + +impl HasSampleValues for ConfirmationRoleWithFactorSourceIds { + /// Config MFA 1.1 + fn sample() -> Self { + let mut builder = RoleBuilder::new(); + builder + .add_factor_source(FactorSourceID::sample_password()) + .unwrap(); + builder.build().unwrap() + } + + /// Config MFA 2.1 + fn sample_other() -> Self { + let mut builder = RoleBuilder::new(); + builder + .add_factor_source(FactorSourceID::sample_device()) + .unwrap(); + builder.build().unwrap() + } +} +impl HasSampleValues for RecoveryRoleWithFactorSourceIds { + /// Config MFA 1.1 + fn sample() -> Self { + let mut builder = RoleBuilder::new(); + builder + .add_factor_source(FactorSourceID::sample_device()) + .unwrap(); + + builder + .add_factor_source(FactorSourceID::sample_ledger()) + .unwrap(); + builder.build().unwrap() + } + + /// Config MFA 3.3 + fn sample_other() -> Self { + let mut builder = RoleBuilder::new(); + builder + .add_factor_source(FactorSourceID::sample_ledger_other()) + .unwrap(); + + builder.build().unwrap() + } +} + +#[cfg(test)] +mod tests { + + use super::*; + + #[allow(clippy::upper_case_acronyms)] + type SUT = ConfirmationRoleWithFactorSourceIds; + + #[test] + fn equality() { + assert_eq!(SUT::sample(), SUT::sample()); + assert_eq!(SUT::sample_other(), SUT::sample_other()); + } + + #[test] + fn inequality() { + assert_ne!(SUT::sample(), SUT::sample_other()); + } + + #[test] + fn get_all_factors() { + let sut = SUT::sample(); + let factors = sut.all_factors(); + assert_eq!( + factors.len(), + sut.get_override_factors().len() + sut.get_threshold_factors().len() + ); + } + + #[test] + fn assert_json() { + let sut = SUT::sample(); + assert_eq_after_json_roundtrip( + &sut, + r#" + { + "threshold": 0, + "thresholdFactors": [], + "overrideFactors": [ + { + "discriminator": "fromHash", + "fromHash": { + "kind": "passphrase", + "body": "181ab662e19fac3ad9f08d5c673b286d4a5ed9cd3762356dc9831dc42427c1b9" + } + } + ] + } + "#, + ); + } +} diff --git a/crates/rules/src/roles/factor_levels/factor_source_id_level/mod.rs b/crates/rules/src/roles/factor_levels/factor_source_id_level/mod.rs new file mode 100644 index 00000000..206017bd --- /dev/null +++ b/crates/rules/src/roles/factor_levels/factor_source_id_level/mod.rs @@ -0,0 +1,9 @@ +mod confirmation_role_with_factor_source_ids; +mod primary_role_with_factor_source_ids; +mod recovery_role_with_factor_source_ids; +mod roles_with_factor_ids; + +pub use confirmation_role_with_factor_source_ids::*; +pub use primary_role_with_factor_source_ids::*; +pub use recovery_role_with_factor_source_ids::*; +pub use roles_with_factor_ids::*; diff --git a/crates/rules/src/roles/factor_levels/factor_source_id_level/primary_role_with_factor_source_ids.rs b/crates/rules/src/roles/factor_levels/factor_source_id_level/primary_role_with_factor_source_ids.rs new file mode 100644 index 00000000..d526ad79 --- /dev/null +++ b/crates/rules/src/roles/factor_levels/factor_source_id_level/primary_role_with_factor_source_ids.rs @@ -0,0 +1,104 @@ +use crate::prelude::*; + +pub type PrimaryRoleWithFactorSourceIds = RoleWithFactorSourceIds<{ ROLE_PRIMARY }>; + +impl PrimaryRoleWithFactorSourceIds { + /// Config MFA 1.1 + pub fn sample_primary() -> Self { + let mut builder = RoleBuilder::new(); + builder + .add_factor_source_to_threshold(FactorSourceID::sample_device()) + .unwrap(); + + builder + .add_factor_source_to_threshold(FactorSourceID::sample_ledger()) + .unwrap(); + builder.set_threshold(2).unwrap(); + builder.build().unwrap() + } +} + +impl HasSampleValues for PrimaryRoleWithFactorSourceIds { + fn sample() -> Self { + Self::sample_primary() + } + + fn sample_other() -> Self { + let mut builder = RoleBuilder::new(); + builder + .add_factor_source_to_threshold(FactorSourceID::sample_device()) + .unwrap(); + + builder + .add_factor_source_to_threshold(FactorSourceID::sample_ledger()) + .unwrap(); + builder.set_threshold(1).unwrap(); + builder.build().unwrap() + } +} + +#[cfg(test)] +mod tests { + + use super::*; + + #[allow(clippy::upper_case_acronyms)] + type SUT = PrimaryRoleWithFactorSourceIds; + + #[test] + fn equality() { + assert_eq!(SUT::sample(), SUT::sample()); + assert_eq!(SUT::sample_other(), SUT::sample_other()); + } + + #[test] + fn inequality() { + assert_ne!(SUT::sample(), SUT::sample_other()); + } + + #[test] + fn get_all_factors() { + let sut = SUT::sample_primary(); + let factors = sut.all_factors(); + assert_eq!( + factors.len(), + sut.get_override_factors().len() + sut.get_threshold_factors().len() + ); + } + + #[test] + fn get_threshold() { + let sut = SUT::sample_primary(); + assert_eq!(sut.get_threshold(), 2); + } + + #[test] + fn assert_json_sample_primary() { + let sut = SUT::sample_primary(); + assert_eq_after_json_roundtrip( + &sut, + r#" + { + "threshold": 2, + "thresholdFactors": [ + { + "discriminator": "fromHash", + "fromHash": { + "kind": "device", + "body": "f1a93d324dd0f2bff89963ab81ed6e0c2ee7e18c0827dc1d3576b2d9f26bbd0a" + } + }, + { + "discriminator": "fromHash", + "fromHash": { + "kind": "ledgerHQHardwareWallet", + "body": "ab59987eedd181fe98e512c1ba0f5ff059f11b5c7c56f15614dcc9fe03fec58b" + } + } + ], + "overrideFactors": [] + } + "#, + ); + } +} diff --git a/crates/rules/src/roles/factor_levels/factor_source_id_level/recovery_role_with_factor_source_ids.rs b/crates/rules/src/roles/factor_levels/factor_source_id_level/recovery_role_with_factor_source_ids.rs new file mode 100644 index 00000000..f0e7e111 --- /dev/null +++ b/crates/rules/src/roles/factor_levels/factor_source_id_level/recovery_role_with_factor_source_ids.rs @@ -0,0 +1,63 @@ +use crate::prelude::*; + +pub type RecoveryRoleWithFactorSourceIds = RoleWithFactorSourceIds<{ ROLE_RECOVERY }>; + +#[cfg(test)] +mod tests { + + use super::*; + + #[allow(clippy::upper_case_acronyms)] + type SUT = RecoveryRoleWithFactorSourceIds; + + #[test] + fn equality() { + assert_eq!(SUT::sample(), SUT::sample()); + assert_eq!(SUT::sample_other(), SUT::sample_other()); + } + + #[test] + fn inequality() { + assert_ne!(SUT::sample(), SUT::sample_other()); + } + + #[test] + fn get_all_factors() { + let sut = SUT::sample(); + let factors = sut.all_factors(); + assert_eq!( + factors.len(), + sut.get_override_factors().len() + sut.get_threshold_factors().len() + ); + } + + #[test] + fn assert_json() { + let sut = SUT::sample(); + assert_eq_after_json_roundtrip( + &sut, + r#" + { + "threshold": 0, + "thresholdFactors": [], + "overrideFactors": [ + { + "discriminator": "fromHash", + "fromHash": { + "kind": "device", + "body": "f1a93d324dd0f2bff89963ab81ed6e0c2ee7e18c0827dc1d3576b2d9f26bbd0a" + } + }, + { + "discriminator": "fromHash", + "fromHash": { + "kind": "ledgerHQHardwareWallet", + "body": "ab59987eedd181fe98e512c1ba0f5ff059f11b5c7c56f15614dcc9fe03fec58b" + } + } + ] + } + "#, + ); + } +} diff --git a/crates/rules/src/roles/factor_levels/factor_source_id_level/roles_with_factor_ids.rs b/crates/rules/src/roles/factor_levels/factor_source_id_level/roles_with_factor_ids.rs new file mode 100644 index 00000000..815877b9 --- /dev/null +++ b/crates/rules/src/roles/factor_levels/factor_source_id_level/roles_with_factor_ids.rs @@ -0,0 +1,3 @@ +use crate::prelude::*; + +pub type RoleWithFactorSourceIds = AbstractBuiltRoleWithFactor; diff --git a/crates/rules/src/roles/factor_levels/factor_source_level/confirmation_role_with_factor_sources.rs b/crates/rules/src/roles/factor_levels/factor_source_level/confirmation_role_with_factor_sources.rs new file mode 100644 index 00000000..d324f8fe --- /dev/null +++ b/crates/rules/src/roles/factor_levels/factor_source_level/confirmation_role_with_factor_sources.rs @@ -0,0 +1,36 @@ +use crate::prelude::*; + +pub(crate) type ConfirmationRoleWithFactorSources = RoleWithFactorSources<{ ROLE_CONFIRMATION }>; + +impl HasSampleValues for ConfirmationRoleWithFactorSources { + fn sample() -> Self { + let ids = ConfirmationRoleWithFactorSourceIds::sample(); + let factor_sources = FactorSources::sample_values_all(); + Self::new(ids, &factor_sources).unwrap() + } + + fn sample_other() -> Self { + let ids = ConfirmationRoleWithFactorSourceIds::sample_other(); + let factor_sources = FactorSources::sample_values_all(); + Self::new(ids, &factor_sources).unwrap() + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[allow(clippy::upper_case_acronyms)] + type SUT = ConfirmationRoleWithFactorSources; + + #[test] + fn equality() { + assert_eq!(SUT::sample(), SUT::sample()); + assert_eq!(SUT::sample_other(), SUT::sample_other()); + } + + #[test] + fn inequality() { + assert_ne!(SUT::sample(), SUT::sample_other()); + } +} diff --git a/crates/rules/src/roles/factor_levels/factor_source_level/mod.rs b/crates/rules/src/roles/factor_levels/factor_source_level/mod.rs new file mode 100644 index 00000000..b53a5e14 --- /dev/null +++ b/crates/rules/src/roles/factor_levels/factor_source_level/mod.rs @@ -0,0 +1,6 @@ +mod confirmation_role_with_factor_sources; +mod primary_role_with_factor_sources; +mod recovery_role_with_factor_sources; +mod roles_with_factor_sources; + +pub(crate) use roles_with_factor_sources::*; diff --git a/crates/rules/src/roles/factor_levels/factor_source_level/primary_role_with_factor_sources.rs b/crates/rules/src/roles/factor_levels/factor_source_level/primary_role_with_factor_sources.rs new file mode 100644 index 00000000..d4233277 --- /dev/null +++ b/crates/rules/src/roles/factor_levels/factor_source_level/primary_role_with_factor_sources.rs @@ -0,0 +1,36 @@ +use crate::prelude::*; + +pub(crate) type PrimaryRoleWithFactorSources = RoleWithFactorSources<{ ROLE_PRIMARY }>; + +impl HasSampleValues for PrimaryRoleWithFactorSources { + fn sample() -> Self { + let ids = PrimaryRoleWithFactorSourceIds::sample(); + let factor_sources = FactorSources::sample_values_all(); + Self::new(ids, &factor_sources).unwrap() + } + + fn sample_other() -> Self { + let ids = PrimaryRoleWithFactorSourceIds::sample_other(); + let factor_sources = FactorSources::sample_values_all(); + Self::new(ids, &factor_sources).unwrap() + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[allow(clippy::upper_case_acronyms)] + type SUT = PrimaryRoleWithFactorSources; + + #[test] + fn equality() { + assert_eq!(SUT::sample(), SUT::sample()); + assert_eq!(SUT::sample_other(), SUT::sample_other()); + } + + #[test] + fn inequality() { + assert_ne!(SUT::sample(), SUT::sample_other()); + } +} diff --git a/crates/rules/src/roles/factor_levels/factor_source_level/recovery_role_with_factor_sources.rs b/crates/rules/src/roles/factor_levels/factor_source_level/recovery_role_with_factor_sources.rs new file mode 100644 index 00000000..9c56d167 --- /dev/null +++ b/crates/rules/src/roles/factor_levels/factor_source_level/recovery_role_with_factor_sources.rs @@ -0,0 +1,36 @@ +use crate::prelude::*; + +pub(crate) type RecoveryRoleWithFactorSources = RoleWithFactorSources<{ ROLE_RECOVERY }>; + +impl HasSampleValues for RecoveryRoleWithFactorSources { + fn sample() -> Self { + let ids = RecoveryRoleWithFactorSourceIds::sample(); + let factor_sources = FactorSources::sample_values_all(); + Self::new(ids, &factor_sources).unwrap() + } + + fn sample_other() -> Self { + let ids = RecoveryRoleWithFactorSourceIds::sample_other(); + let factor_sources = FactorSources::sample_values_all(); + Self::new(ids, &factor_sources).unwrap() + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[allow(clippy::upper_case_acronyms)] + type SUT = RecoveryRoleWithFactorSources; + + #[test] + fn equality() { + assert_eq!(SUT::sample(), SUT::sample()); + assert_eq!(SUT::sample_other(), SUT::sample_other()); + } + + #[test] + fn inequality() { + assert_ne!(SUT::sample(), SUT::sample_other()); + } +} diff --git a/crates/rules/src/roles/factor_levels/factor_source_level/roles_with_factor_sources.rs b/crates/rules/src/roles/factor_levels/factor_source_level/roles_with_factor_sources.rs new file mode 100644 index 00000000..20955fd9 --- /dev/null +++ b/crates/rules/src/roles/factor_levels/factor_source_level/roles_with_factor_sources.rs @@ -0,0 +1,32 @@ +use crate::prelude::*; + +pub(crate) type RoleWithFactorSources = AbstractBuiltRoleWithFactor; + +impl RoleWithFactorSources { + pub fn new( + role_with_factor_source_ids: RoleWithFactorSourceIds, + factor_sources: &FactorSources, + ) -> Result { + let lookup_f = |id: &FactorSourceID| -> Result { + factor_sources + .get_id(id) + .ok_or(CommonError::FactorSourceDiscrepancy) + .cloned() + }; + + let lookup = |ids: &Vec| -> Result, CommonError> { + ids.iter() + .map(lookup_f) + .collect::, CommonError>>() + }; + + let threshold_factors = lookup(role_with_factor_source_ids.get_threshold_factors())?; + let override_factors = lookup(role_with_factor_source_ids.get_override_factors())?; + + Ok(Self::with_factors( + role_with_factor_source_ids.get_threshold(), + threshold_factors, + override_factors, + )) + } +} diff --git a/crates/rules/src/roles/factor_levels/mod.rs b/crates/rules/src/roles/factor_levels/mod.rs new file mode 100644 index 00000000..4f5b7785 --- /dev/null +++ b/crates/rules/src/roles/factor_levels/mod.rs @@ -0,0 +1,7 @@ +mod factor_instance_level; +mod factor_source_id_level; +mod factor_source_level; + +pub use factor_instance_level::*; +pub use factor_source_id_level::*; +pub(crate) use factor_source_level::*; diff --git a/crates/rules/src/roles/mod.rs b/crates/rules/src/roles/mod.rs index 90812439..c99cb397 100644 --- a/crates/rules/src/roles/mod.rs +++ b/crates/rules/src/roles/mod.rs @@ -1,13 +1,7 @@ mod abstract_role_builder_or_built; mod builder; -mod general_role_with_hierarchical_deterministic_factor_instances; -mod role_with_factor_instances; -mod roles_with_factor_ids; -mod roles_with_factor_sources; +mod factor_levels; pub(crate) use abstract_role_builder_or_built::*; pub use builder::*; -pub use general_role_with_hierarchical_deterministic_factor_instances::*; -pub(crate) use role_with_factor_instances::*; -pub use roles_with_factor_ids::*; -pub(crate) use roles_with_factor_sources::*; +pub use factor_levels::*; diff --git a/crates/rules/src/roles/role_with_factor_instances.rs b/crates/rules/src/roles/role_with_factor_instances.rs deleted file mode 100644 index a643396b..00000000 --- a/crates/rules/src/roles/role_with_factor_instances.rs +++ /dev/null @@ -1,251 +0,0 @@ -use crate::prelude::*; - -pub(crate) type RoleWithFactorInstances = - AbstractBuiltRoleWithFactor; - -impl RoleWithFactorSources { - fn from(other: &RoleWithFactorSources) -> Self { - Self::with_factors( - other.get_threshold(), - other.get_threshold_factors().clone(), - other.get_override_factors().clone(), - ) - } -} - -impl MatrixOfFactorSources { - pub(crate) fn get_role(&self) -> RoleWithFactorSources { - match R { - ROLE_PRIMARY => RoleWithFactorSources::from(&self.primary_role), - ROLE_RECOVERY => RoleWithFactorSources::from(&self.recovery_role), - ROLE_CONFIRMATION => RoleWithFactorSources::from(&self.confirmation_role), - _ => panic!("unknown"), - } - } -} - -impl RoleWithFactorInstances { - // TODO: MFA - Upgrade this method to follow the rules of when a factor instance might - // be used by MULTIPLE roles. This is a temporary solution to get the tests to pass. - // A proper solution should use follow the rules laid out in: - // https://radixdlt.atlassian.net/wiki/spaces/AT/pages/3758063620/MFA+Rules+for+Factors+and+Security+Shields - pub(crate) fn fulfilling_role_of_factor_sources_with_factor_instances( - consuming_instances: &IndexMap, - matrix_of_factor_sources: &MatrixOfFactorSources, - ) -> Result { - let role_kind = RoleKind::from_u8(R).unwrap(); - - let role_of_sources = matrix_of_factor_sources.get_role::(); - assert_eq!(role_of_sources.role(), role_kind); - let threshold: u8 = role_of_sources.get_threshold(); - - // Threshold factors - let threshold_factors = - Self::try_filling_factor_list_of_role_of_factor_sources_with_factor_instances( - consuming_instances, - role_of_sources.get_threshold_factors(), - )?; - - // Override factors - let override_factors = - Self::try_filling_factor_list_of_role_of_factor_sources_with_factor_instances( - consuming_instances, - role_of_sources.get_override_factors(), - )?; - - let role_with_instances = - Self::with_factors(threshold, threshold_factors, override_factors); - - assert_eq!(role_with_instances.role(), role_kind); - Ok(role_with_instances) - } - - fn try_filling_factor_list_of_role_of_factor_sources_with_factor_instances( - instances: &IndexMap, - from: &[FactorSource], - ) -> Result, CommonError> { - from.iter() - .map(|f| { - if let Some(existing) = instances.get(&f.id_from_hash()) { - let hd_instance = existing - .first() - .ok_or(CommonError::MissingFactorMappingInstancesIntoRole)?; - let instance = FactorInstance::from(hd_instance); - Ok(instance) - } else { - Err(CommonError::MissingFactorMappingInstancesIntoRole) - } - }) - .collect::, CommonError>>() - } -} - -pub(crate) type PrimaryRoleWithFactorInstances = RoleWithFactorInstances<{ ROLE_PRIMARY }>; -pub(crate) type RecoveryRoleWithFactorInstances = RoleWithFactorInstances<{ ROLE_RECOVERY }>; -pub(crate) type ConfirmationRoleWithFactorInstances = - RoleWithFactorInstances<{ ROLE_CONFIRMATION }>; - -impl HasSampleValues for PrimaryRoleWithFactorInstances { - fn sample() -> Self { - MatrixOfFactorInstances::sample().primary_role - } - - fn sample_other() -> Self { - MatrixOfFactorInstances::sample_other().primary_role - } -} - -impl HasSampleValues for ConfirmationRoleWithFactorInstances { - fn sample() -> Self { - MatrixOfFactorInstances::sample().confirmation_role - } - - fn sample_other() -> Self { - MatrixOfFactorInstances::sample_other().confirmation_role - } -} - -impl HasSampleValues for RecoveryRoleWithFactorInstances { - fn sample() -> Self { - MatrixOfFactorInstances::sample().recovery_role - } - - fn sample_other() -> Self { - MatrixOfFactorInstances::sample_other().recovery_role - } -} - -#[cfg(test)] -mod primary_tests { - use super::*; - - #[allow(clippy::upper_case_acronyms)] - type SUT = PrimaryRoleWithFactorInstances; - - #[test] - fn equality() { - assert_eq!(SUT::sample(), SUT::sample()); - assert_eq!(SUT::sample_other(), SUT::sample_other()); - } - - #[test] - fn inequality() { - assert_ne!(SUT::sample(), SUT::sample_other()); - } - - #[test] - #[should_panic] - fn primary_role_non_securified_threshold_instances_is_err() { - let _ = SUT::with_factors( - 1, - [ - HierarchicalDeterministicFactorInstance::sample_mainnet_account_device_factor_fs_10_unsecurified_at_index(0).into() - ], - [] - ); - } - - #[test] - fn assert_json_sample() { - let sut = SUT::sample(); - assert_eq_after_json_roundtrip( - &sut, - r#" - { - "threshold": 2, - "thresholdFactors": [ - { - "factorSourceID": { - "discriminator": "fromHash", - "fromHash": { - "kind": "device", - "body": "f1a93d324dd0f2bff89963ab81ed6e0c2ee7e18c0827dc1d3576b2d9f26bbd0a" - } - }, - "badge": { - "discriminator": "virtualSource", - "virtualSource": { - "discriminator": "hierarchicalDeterministicPublicKey", - "hierarchicalDeterministicPublicKey": { - "publicKey": { - "curve": "curve25519", - "compressedData": "427969814e15d74c3ff4d9971465cb709d210c8a7627af9466bdaa67bd0929b7" - }, - "derivationPath": { - "scheme": "cap26", - "path": "m/44H/1022H/1H/525H/1460H/0S" - } - } - } - } - }, - { - "factorSourceID": { - "discriminator": "fromHash", - "fromHash": { - "kind": "ledgerHQHardwareWallet", - "body": "ab59987eedd181fe98e512c1ba0f5ff059f11b5c7c56f15614dcc9fe03fec58b" - } - }, - "badge": { - "discriminator": "virtualSource", - "virtualSource": { - "discriminator": "hierarchicalDeterministicPublicKey", - "hierarchicalDeterministicPublicKey": { - "publicKey": { - "curve": "curve25519", - "compressedData": "92cd6838cd4e7b0523ed93d498e093f71139ffd5d632578189b39a26005be56b" - }, - "derivationPath": { - "scheme": "cap26", - "path": "m/44H/1022H/1H/525H/1460H/0S" - } - } - } - } - } - ], - "overrideFactors": [] - } - "#, - ); - } -} - -#[cfg(test)] -mod confirmation_tests { - use super::*; - - #[allow(clippy::upper_case_acronyms)] - type SUT = ConfirmationRoleWithFactorInstances; - - #[test] - fn equality() { - assert_eq!(SUT::sample(), SUT::sample()); - assert_eq!(SUT::sample_other(), SUT::sample_other()); - } - - #[test] - fn inequality() { - assert_ne!(SUT::sample(), SUT::sample_other()); - } -} - -#[cfg(test)] -mod recovery_tests { - use super::*; - - #[allow(clippy::upper_case_acronyms)] - type SUT = RecoveryRoleWithFactorInstances; - - #[test] - fn equality() { - assert_eq!(SUT::sample(), SUT::sample()); - assert_eq!(SUT::sample_other(), SUT::sample_other()); - } - - #[test] - fn inequality() { - assert_ne!(SUT::sample(), SUT::sample_other()); - } -} diff --git a/crates/rules/src/roles/roles_with_factor_ids.rs b/crates/rules/src/roles/roles_with_factor_ids.rs deleted file mode 100644 index 47b36eee..00000000 --- a/crates/rules/src/roles/roles_with_factor_ids.rs +++ /dev/null @@ -1,265 +0,0 @@ -use crate::prelude::*; - -pub type RoleWithFactorSourceIds = AbstractBuiltRoleWithFactor; - -pub type PrimaryRoleWithFactorSourceIds = RoleWithFactorSourceIds<{ ROLE_PRIMARY }>; -pub type RecoveryRoleWithFactorSourceIds = RoleWithFactorSourceIds<{ ROLE_RECOVERY }>; -pub type ConfirmationRoleWithFactorSourceIds = RoleWithFactorSourceIds<{ ROLE_CONFIRMATION }>; - -impl PrimaryRoleWithFactorSourceIds { - /// Config MFA 1.1 - pub fn sample_primary() -> Self { - let mut builder = RoleBuilder::new(); - builder - .add_factor_source_to_threshold(FactorSourceID::sample_device()) - .unwrap(); - - builder - .add_factor_source_to_threshold(FactorSourceID::sample_ledger()) - .unwrap(); - builder.set_threshold(2).unwrap(); - builder.build().unwrap() - } -} - -impl HasSampleValues for PrimaryRoleWithFactorSourceIds { - fn sample() -> Self { - Self::sample_primary() - } - - fn sample_other() -> Self { - let mut builder = RoleBuilder::new(); - builder - .add_factor_source_to_threshold(FactorSourceID::sample_device()) - .unwrap(); - - builder - .add_factor_source_to_threshold(FactorSourceID::sample_ledger()) - .unwrap(); - builder.set_threshold(1).unwrap(); - builder.build().unwrap() - } -} - -impl HasSampleValues for ConfirmationRoleWithFactorSourceIds { - /// Config MFA 1.1 - fn sample() -> Self { - let mut builder = RoleBuilder::new(); - builder - .add_factor_source(FactorSourceID::sample_password()) - .unwrap(); - builder.build().unwrap() - } - - /// Config MFA 2.1 - fn sample_other() -> Self { - let mut builder = RoleBuilder::new(); - builder - .add_factor_source(FactorSourceID::sample_device()) - .unwrap(); - builder.build().unwrap() - } -} -impl HasSampleValues for RecoveryRoleWithFactorSourceIds { - /// Config MFA 1.1 - fn sample() -> Self { - let mut builder = RoleBuilder::new(); - builder - .add_factor_source(FactorSourceID::sample_device()) - .unwrap(); - - builder - .add_factor_source(FactorSourceID::sample_ledger()) - .unwrap(); - builder.build().unwrap() - } - - /// Config MFA 3.3 - fn sample_other() -> Self { - let mut builder = RoleBuilder::new(); - builder - .add_factor_source(FactorSourceID::sample_ledger_other()) - .unwrap(); - - builder.build().unwrap() - } -} - -#[cfg(test)] -mod primary_tests { - - use super::*; - - #[allow(clippy::upper_case_acronyms)] - type SUT = PrimaryRoleWithFactorSourceIds; - - #[test] - fn equality() { - assert_eq!(SUT::sample(), SUT::sample()); - assert_eq!(SUT::sample_other(), SUT::sample_other()); - } - - #[test] - fn inequality() { - assert_ne!(SUT::sample(), SUT::sample_other()); - } - - #[test] - fn get_all_factors() { - let sut = SUT::sample_primary(); - let factors = sut.all_factors(); - assert_eq!( - factors.len(), - sut.get_override_factors().len() + sut.get_threshold_factors().len() - ); - } - - #[test] - fn get_threshold() { - let sut = SUT::sample_primary(); - assert_eq!(sut.get_threshold(), 2); - } - - #[test] - fn assert_json_sample_primary() { - let sut = SUT::sample_primary(); - assert_eq_after_json_roundtrip( - &sut, - r#" - { - "threshold": 2, - "thresholdFactors": [ - { - "discriminator": "fromHash", - "fromHash": { - "kind": "device", - "body": "f1a93d324dd0f2bff89963ab81ed6e0c2ee7e18c0827dc1d3576b2d9f26bbd0a" - } - }, - { - "discriminator": "fromHash", - "fromHash": { - "kind": "ledgerHQHardwareWallet", - "body": "ab59987eedd181fe98e512c1ba0f5ff059f11b5c7c56f15614dcc9fe03fec58b" - } - } - ], - "overrideFactors": [] - } - "#, - ); - } -} - -#[cfg(test)] -mod recovery_tests { - - use super::*; - - #[allow(clippy::upper_case_acronyms)] - type SUT = RecoveryRoleWithFactorSourceIds; - - #[test] - fn equality() { - assert_eq!(SUT::sample(), SUT::sample()); - assert_eq!(SUT::sample_other(), SUT::sample_other()); - } - - #[test] - fn inequality() { - assert_ne!(SUT::sample(), SUT::sample_other()); - } - - #[test] - fn get_all_factors() { - let sut = SUT::sample(); - let factors = sut.all_factors(); - assert_eq!( - factors.len(), - sut.get_override_factors().len() + sut.get_threshold_factors().len() - ); - } - - #[test] - fn assert_json() { - let sut = SUT::sample(); - assert_eq_after_json_roundtrip( - &sut, - r#" - { - "threshold": 0, - "thresholdFactors": [], - "overrideFactors": [ - { - "discriminator": "fromHash", - "fromHash": { - "kind": "device", - "body": "f1a93d324dd0f2bff89963ab81ed6e0c2ee7e18c0827dc1d3576b2d9f26bbd0a" - } - }, - { - "discriminator": "fromHash", - "fromHash": { - "kind": "ledgerHQHardwareWallet", - "body": "ab59987eedd181fe98e512c1ba0f5ff059f11b5c7c56f15614dcc9fe03fec58b" - } - } - ] - } - "#, - ); - } -} - -#[cfg(test)] -mod confirmation_tests { - - use super::*; - - #[allow(clippy::upper_case_acronyms)] - type SUT = ConfirmationRoleWithFactorSourceIds; - - #[test] - fn equality() { - assert_eq!(SUT::sample(), SUT::sample()); - assert_eq!(SUT::sample_other(), SUT::sample_other()); - } - - #[test] - fn inequality() { - assert_ne!(SUT::sample(), SUT::sample_other()); - } - - #[test] - fn get_all_factors() { - let sut = SUT::sample(); - let factors = sut.all_factors(); - assert_eq!( - factors.len(), - sut.get_override_factors().len() + sut.get_threshold_factors().len() - ); - } - - #[test] - fn assert_json() { - let sut = SUT::sample(); - assert_eq_after_json_roundtrip( - &sut, - r#" - { - "threshold": 0, - "thresholdFactors": [], - "overrideFactors": [ - { - "discriminator": "fromHash", - "fromHash": { - "kind": "passphrase", - "body": "181ab662e19fac3ad9f08d5c673b286d4a5ed9cd3762356dc9831dc42427c1b9" - } - } - ] - } - "#, - ); - } -} diff --git a/crates/rules/src/roles/roles_with_factor_sources.rs b/crates/rules/src/roles/roles_with_factor_sources.rs deleted file mode 100644 index b7304850..00000000 --- a/crates/rules/src/roles/roles_with_factor_sources.rs +++ /dev/null @@ -1,134 +0,0 @@ -use crate::prelude::*; - -pub(crate) type RoleWithFactorSources = AbstractBuiltRoleWithFactor; -pub(crate) type PrimaryRoleWithFactorSources = RoleWithFactorSources<{ ROLE_PRIMARY }>; -pub(crate) type RecoveryRoleWithFactorSources = RoleWithFactorSources<{ ROLE_RECOVERY }>; -pub(crate) type ConfirmationRoleWithFactorSources = RoleWithFactorSources<{ ROLE_CONFIRMATION }>; - -impl RoleWithFactorSources { - pub fn new( - role_with_factor_source_ids: RoleWithFactorSourceIds, - factor_sources: &FactorSources, - ) -> Result { - let lookup_f = |id: &FactorSourceID| -> Result { - factor_sources - .get_id(id) - .ok_or(CommonError::FactorSourceDiscrepancy) - .cloned() - }; - - let lookup = |ids: &Vec| -> Result, CommonError> { - ids.iter() - .map(lookup_f) - .collect::, CommonError>>() - }; - - let threshold_factors = lookup(role_with_factor_source_ids.get_threshold_factors())?; - let override_factors = lookup(role_with_factor_source_ids.get_override_factors())?; - - Ok(Self::with_factors( - role_with_factor_source_ids.get_threshold(), - threshold_factors, - override_factors, - )) - } -} - -impl HasSampleValues for PrimaryRoleWithFactorSources { - fn sample() -> Self { - let ids = PrimaryRoleWithFactorSourceIds::sample(); - let factor_sources = FactorSources::sample_values_all(); - Self::new(ids, &factor_sources).unwrap() - } - - fn sample_other() -> Self { - let ids = PrimaryRoleWithFactorSourceIds::sample_other(); - let factor_sources = FactorSources::sample_values_all(); - Self::new(ids, &factor_sources).unwrap() - } -} - -impl HasSampleValues for RecoveryRoleWithFactorSources { - fn sample() -> Self { - let ids = RecoveryRoleWithFactorSourceIds::sample(); - let factor_sources = FactorSources::sample_values_all(); - Self::new(ids, &factor_sources).unwrap() - } - - fn sample_other() -> Self { - let ids = RecoveryRoleWithFactorSourceIds::sample_other(); - let factor_sources = FactorSources::sample_values_all(); - Self::new(ids, &factor_sources).unwrap() - } -} - -impl HasSampleValues for ConfirmationRoleWithFactorSources { - fn sample() -> Self { - let ids = ConfirmationRoleWithFactorSourceIds::sample(); - let factor_sources = FactorSources::sample_values_all(); - Self::new(ids, &factor_sources).unwrap() - } - - fn sample_other() -> Self { - let ids = ConfirmationRoleWithFactorSourceIds::sample_other(); - let factor_sources = FactorSources::sample_values_all(); - Self::new(ids, &factor_sources).unwrap() - } -} - -#[cfg(test)] -mod primary_tests { - use super::*; - - #[allow(clippy::upper_case_acronyms)] - type SUT = PrimaryRoleWithFactorSources; - - #[test] - fn equality() { - assert_eq!(SUT::sample(), SUT::sample()); - assert_eq!(SUT::sample_other(), SUT::sample_other()); - } - - #[test] - fn inequality() { - assert_ne!(SUT::sample(), SUT::sample_other()); - } -} - -#[cfg(test)] -mod recovery_tests { - use super::*; - - #[allow(clippy::upper_case_acronyms)] - type SUT = RecoveryRoleWithFactorSources; - - #[test] - fn equality() { - assert_eq!(SUT::sample(), SUT::sample()); - assert_eq!(SUT::sample_other(), SUT::sample_other()); - } - - #[test] - fn inequality() { - assert_ne!(SUT::sample(), SUT::sample_other()); - } -} - -#[cfg(test)] -mod confirmation_tests { - use super::*; - - #[allow(clippy::upper_case_acronyms)] - type SUT = ConfirmationRoleWithFactorSources; - - #[test] - fn equality() { - assert_eq!(SUT::sample(), SUT::sample()); - assert_eq!(SUT::sample_other(), SUT::sample_other()); - } - - #[test] - fn inequality() { - assert_ne!(SUT::sample(), SUT::sample_other()); - } -} From e91c79bfb1d8b2d45e9583bd688c9511eb699e81 Mon Sep 17 00:00:00 2001 From: Alexander Cyon Date: Fri, 29 Nov 2024 14:43:52 +0100 Subject: [PATCH 24/33] [no ci] wip --- crates/rules-uniffi/src/builder.rs | 68 ++++++++++++++++++++++++++++++ 1 file changed, 68 insertions(+) diff --git a/crates/rules-uniffi/src/builder.rs b/crates/rules-uniffi/src/builder.rs index e7aaaa16..56dd514d 100644 --- a/crates/rules-uniffi/src/builder.rs +++ b/crates/rules-uniffi/src/builder.rs @@ -248,17 +248,85 @@ mod tests { #[test] fn test() { let sut = SUT::new(); + + // Primary + let sim_prim = sut + .validation_for_addition_of_factor_source_to_primary_override_for_each(vec![ + FactorSourceID::sample_arculus(), + ]) + .unwrap(); + + + let sim_kind_prim = sut + .validation_for_addition_of_factor_source_to_primary_override_for_each(vec![ + FactorSourceID::sample_arculus(), + ]) + .unwrap(); + + let sim_prim_threshold = sut + .validation_for_addition_of_factor_source_to_primary_threshold_for_each(vec![ + FactorSourceID::sample_arculus(), + ]) + .unwrap(); + sut.add_factor_source_to_primary_override(FactorSourceID::sample_arculus()) .unwrap(); sut.add_factor_source_to_primary_override(FactorSourceID::sample_arculus_other()) .unwrap(); + + // Recovery + let sim_rec = sut + .validation_for_addition_of_factor_source_to_recovery_override_for_each(vec![ + FactorSourceID::sample_ledger(), + ]) + .unwrap(); + sut.add_factor_source_to_recovery_override(FactorSourceID::sample_ledger()) .unwrap(); sut.add_factor_source_to_recovery_override(FactorSourceID::sample_ledger_other()) .unwrap(); + + // Confirmation + let sim_conf = sut + .validation_for_addition_of_factor_source_to_confirmation_override_for_each(vec![ + FactorSourceID::sample_device(), + ]) + .unwrap(); sut.add_factor_source_to_confirmation_override(FactorSourceID::sample_device()) .unwrap(); + assert_ne!( + sim_prim, + sut.validation_for_addition_of_factor_source_to_primary_override_for_each(vec![ + FactorSourceID::sample_arculus(), + ]) + .unwrap() + ); + + assert_ne!( + sim_prim_threshold, + sut.validation_for_addition_of_factor_source_to_primary_threshold_for_each(vec![ + FactorSourceID::sample_arculus() + ]) + .unwrap() + ); + + assert_ne!( + sim_rec, + sut.validation_for_addition_of_factor_source_to_recovery_override_for_each(vec![ + FactorSourceID::sample_ledger(), + ]) + .unwrap() + ); + + assert_ne!( + sim_conf, + sut.validation_for_addition_of_factor_source_to_confirmation_override_for_each(vec![ + FactorSourceID::sample_device(), + ]) + .unwrap() + ); + sut.remove_factor(FactorSourceID::sample_arculus_other()) .unwrap(); sut.remove_factor(FactorSourceID::sample_ledger_other()) From 60e451168c724de27c6589ed402da49c8889d038 Mon Sep 17 00:00:00 2001 From: Alexander Cyon Date: Fri, 29 Nov 2024 14:54:16 +0100 Subject: [PATCH 25/33] more tests --- crates/rules-uniffi/src/builder.rs | 83 +++++++++++++++++++++--------- 1 file changed, 60 insertions(+), 23 deletions(-) diff --git a/crates/rules-uniffi/src/builder.rs b/crates/rules-uniffi/src/builder.rs index 56dd514d..7a7e1999 100644 --- a/crates/rules-uniffi/src/builder.rs +++ b/crates/rules-uniffi/src/builder.rs @@ -250,36 +250,44 @@ mod tests { let sut = SUT::new(); // Primary - let sim_prim = sut - .validation_for_addition_of_factor_source_to_primary_override_for_each(vec![ - FactorSourceID::sample_arculus(), - ]) - .unwrap(); - - - let sim_kind_prim = sut - .validation_for_addition_of_factor_source_to_primary_override_for_each(vec![ + let sim_prim = + sut.validation_for_addition_of_factor_source_to_primary_override_for_each(vec![ FactorSourceID::sample_arculus(), - ]) - .unwrap(); + ]); let sim_prim_threshold = sut .validation_for_addition_of_factor_source_to_primary_threshold_for_each(vec![ FactorSourceID::sample_arculus(), - ]) - .unwrap(); + ]); + let sim_kind_prim = sut + .validation_for_addition_of_factor_source_of_kind_to_primary_override( + FactorSourceKind::Device, + ); + + let sim_kind_prim_threshold = sut + .validation_for_addition_of_factor_source_of_kind_to_primary_threshold( + FactorSourceKind::Device, + ); + + sut.add_factor_source_to_primary_threshold(FactorSourceID::sample_device()) + .unwrap(); + _ = sut.set_threshold(1); sut.add_factor_source_to_primary_override(FactorSourceID::sample_arculus()) .unwrap(); sut.add_factor_source_to_primary_override(FactorSourceID::sample_arculus_other()) .unwrap(); // Recovery - let sim_rec = sut - .validation_for_addition_of_factor_source_to_recovery_override_for_each(vec![ + let sim_rec = + sut.validation_for_addition_of_factor_source_to_recovery_override_for_each(vec![ FactorSourceID::sample_ledger(), - ]) - .unwrap(); + ]); + + let sim_kind_rec = sut + .validation_for_addition_of_factor_source_of_kind_to_recovery_override( + FactorSourceKind::ArculusCard, + ); sut.add_factor_source_to_recovery_override(FactorSourceID::sample_ledger()) .unwrap(); @@ -290,8 +298,13 @@ mod tests { let sim_conf = sut .validation_for_addition_of_factor_source_to_confirmation_override_for_each(vec![ FactorSourceID::sample_device(), - ]) - .unwrap(); + ]); + + let sim_kind_conf = sut + .validation_for_addition_of_factor_source_of_kind_to_confirmation_override( + FactorSourceKind::ArculusCard, + ); + sut.add_factor_source_to_confirmation_override(FactorSourceID::sample_device()) .unwrap(); @@ -300,7 +313,6 @@ mod tests { sut.validation_for_addition_of_factor_source_to_primary_override_for_each(vec![ FactorSourceID::sample_arculus(), ]) - .unwrap() ); assert_ne!( @@ -308,7 +320,6 @@ mod tests { sut.validation_for_addition_of_factor_source_to_primary_threshold_for_each(vec![ FactorSourceID::sample_arculus() ]) - .unwrap() ); assert_ne!( @@ -316,7 +327,6 @@ mod tests { sut.validation_for_addition_of_factor_source_to_recovery_override_for_each(vec![ FactorSourceID::sample_ledger(), ]) - .unwrap() ); assert_ne!( @@ -324,7 +334,34 @@ mod tests { sut.validation_for_addition_of_factor_source_to_confirmation_override_for_each(vec![ FactorSourceID::sample_device(), ]) - .unwrap() + ); + + assert_ne!( + sim_kind_prim, + sut.validation_for_addition_of_factor_source_of_kind_to_primary_override( + FactorSourceKind::Device, + ) + ); + + assert_ne!( + sim_kind_prim_threshold, + sut.validation_for_addition_of_factor_source_of_kind_to_primary_threshold( + FactorSourceKind::Device, + ) + ); + + assert_eq!( + sim_kind_rec, + sut.validation_for_addition_of_factor_source_of_kind_to_recovery_override( + FactorSourceKind::ArculusCard, + ) + ); + + assert_eq!( + sim_kind_conf, + sut.validation_for_addition_of_factor_source_of_kind_to_confirmation_override( + FactorSourceKind::ArculusCard, + ) ); sut.remove_factor(FactorSourceID::sample_arculus_other()) From c6c9b0efcb03cc17b244c954261883f3d7638d4a Mon Sep 17 00:00:00 2001 From: Alexander Cyon Date: Fri, 29 Nov 2024 15:03:56 +0100 Subject: [PATCH 26/33] [no ci] WIP --- crates/rules-uniffi/src/builder.rs | 28 +++++++++++++++++++ .../src/matrices/builder/matrix_builder.rs | 24 ++++++++++++++++ 2 files changed, 52 insertions(+) diff --git a/crates/rules-uniffi/src/builder.rs b/crates/rules-uniffi/src/builder.rs index 7a7e1999..54f2932f 100644 --- a/crates/rules-uniffi/src/builder.rs +++ b/crates/rules-uniffi/src/builder.rs @@ -20,6 +20,15 @@ pub struct SecurityStructureOfFactorSourceIds { } impl SecurityShieldBuilder { + fn get(&self, mut with_non_consumed_builder: impl FnMut(&MatrixBuilder) -> R) -> R { + let binding = self.wrapped.write().unwrap(); + + let Some(builder) = binding.as_ref() else { + unreachable!("Already built, should not have happened.") + }; + with_non_consumed_builder(builder) + } + fn with>( &self, mut with_non_consumed_builder: impl FnMut(&mut MatrixBuilder) -> Result, @@ -69,7 +78,26 @@ impl SecurityShieldBuilder { wrapped: RwLock::new(Some(MatrixBuilder::new())), }) } +} +// ==================== +// ==== GET / READ ==== +// ==================== +#[uniffi::export] +impl SecurityShieldBuilder { + pub fn get_primary_threshold(&self) -> u8 { + self.get(|builder| builder.get_threshold()) + } + pub fn get_primary_threshold_facto(&self) -> u8 { + self.get(|builder| builder.get_threshold()) + } +} + +// ==================== +// ===== MUTATION ===== +// ==================== +#[uniffi::export] +impl SecurityShieldBuilder { /// Adds the factor source to the primary role threshold list. pub fn add_factor_source_to_primary_threshold( &self, diff --git a/crates/rules/src/matrices/builder/matrix_builder.rs b/crates/rules/src/matrices/builder/matrix_builder.rs index 7f1de36e..5d4b2a2c 100644 --- a/crates/rules/src/matrices/builder/matrix_builder.rs +++ b/crates/rules/src/matrices/builder/matrix_builder.rs @@ -187,6 +187,22 @@ impl MatrixBuilder { .into_matrix_err(RoleKind::Confirmation) } + pub fn get_confirmation_factors(&self) -> &Vec { + self.confirmation_role.get_override_factors() + } + + pub fn get_recovery_factors(&self) -> &Vec { + self.recovery_role.get_override_factors() + } + + pub fn get_primary_threshold_factors(&self) -> &Vec { + self.primary_role.get_threshold_factors() + } + + pub fn get_primary_override_factors(&self) -> &Vec { + self.primary_role.get_override_factors() + } + /// Sets the threshold on the primary role builder. pub fn set_threshold(&mut self, threshold: u8) -> MatrixBuilderMutateResult { self.primary_role @@ -194,6 +210,10 @@ impl MatrixBuilder { .into_matrix_err(RoleKind::Primary) } + pub fn get_threshold(&self) -> u8 { + self.primary_role.get_threshold() + } + pub fn set_number_of_days_until_auto_confirm( &mut self, number_of_days: u16, @@ -203,6 +223,10 @@ impl MatrixBuilder { self.validate_number_of_days_until_auto_confirm() } + pub fn get_number_of_days_until_auto_confirm(&self) -> u16 { + self.number_of_days_until_auto_confirm + } + /// Removes `factor_source_id` from all three roles, if not found in any an error /// is thrown. /// From 3d6f4c7252f2d1a79bfe171656178a8800a6c3b5 Mon Sep 17 00:00:00 2001 From: Alexander Cyon Date: Fri, 29 Nov 2024 15:44:37 +0100 Subject: [PATCH 27/33] uniffi get set and tests --- crates/rules-uniffi/src/builder.rs | 89 +++++++++++++++++-- .../src/unneeded_when_moved_to_sargon.rs | 8 +- 2 files changed, 85 insertions(+), 12 deletions(-) diff --git a/crates/rules-uniffi/src/builder.rs b/crates/rules-uniffi/src/builder.rs index 54f2932f..d8ce6fd0 100644 --- a/crates/rules-uniffi/src/builder.rs +++ b/crates/rules-uniffi/src/builder.rs @@ -11,6 +11,7 @@ use crate::prelude::*; #[derive(Debug, uniffi::Object)] pub struct SecurityShieldBuilder { wrapped: RwLock>, + name: RwLock, } #[derive(Debug, PartialEq, Eq, Hash, uniffi::Object)] @@ -20,8 +21,8 @@ pub struct SecurityStructureOfFactorSourceIds { } impl SecurityShieldBuilder { - fn get(&self, mut with_non_consumed_builder: impl FnMut(&MatrixBuilder) -> R) -> R { - let binding = self.wrapped.write().unwrap(); + fn get(&self, with_non_consumed_builder: impl Fn(&MatrixBuilder) -> R) -> R { + let binding = self.wrapped.read().unwrap(); let Some(builder) = binding.as_ref() else { unreachable!("Already built, should not have happened.") @@ -76,6 +77,19 @@ impl SecurityShieldBuilder { pub fn new() -> Arc { Arc::new(Self { wrapped: RwLock::new(Some(MatrixBuilder::new())), + name: RwLock::new("My Shield".to_owned()), + }) + } +} + +impl SecurityShieldBuilder { + fn get_factors( + &self, + access: impl Fn(&MatrixBuilder) -> &Vec, + ) -> Vec> { + self.get(|builder| { + let factors = access(builder); + factors.iter().map(FactorSourceID::new).collect::>() }) } } @@ -88,8 +102,29 @@ impl SecurityShieldBuilder { pub fn get_primary_threshold(&self) -> u8 { self.get(|builder| builder.get_threshold()) } - pub fn get_primary_threshold_facto(&self) -> u8 { - self.get(|builder| builder.get_threshold()) + + pub fn get_number_of_days_until_auto_confirm(&self) -> u16 { + self.get(|builder| builder.get_number_of_days_until_auto_confirm()) + } + + pub fn get_name(&self) -> String { + self.name.read().unwrap().clone() + } + + pub fn get_primary_threshold_factors(&self) -> Vec> { + self.get_factors(|builder| builder.get_primary_threshold_factors()) + } + + pub fn get_primary_override_factors(&self) -> Vec> { + self.get_factors(|builder| builder.get_primary_override_factors()) + } + + pub fn get_recovery_factors(&self) -> Vec> { + self.get_factors(|builder| builder.get_recovery_factors()) + } + + pub fn get_confirmation_factors(&self) -> Vec> { + self.get_factors(|builder| builder.get_confirmation_factors()) } } @@ -98,6 +133,10 @@ impl SecurityShieldBuilder { // ==================== #[uniffi::export] impl SecurityShieldBuilder { + pub fn set_name(&self, name: String) { + *self.name.write().unwrap() = name + } + /// Adds the factor source to the primary role threshold list. pub fn add_factor_source_to_primary_threshold( &self, @@ -240,10 +279,7 @@ impl SecurityShieldBuilder { ) } - pub fn build( - self: Arc, - name: String, - ) -> Result { + pub fn build(self: Arc) -> Result { let mut binding = self .wrapped .write() @@ -253,6 +289,7 @@ impl SecurityShieldBuilder { .build() .map_err(|e| CommonError::BuildError(format!("{:?}", e)))?; + let name = self.get_name(); let display_name = sargon::DisplayName::new(name).map_err(|e| CommonError::Sargon(format!("{:?}", e)))?; let wrapped_shield = @@ -277,6 +314,13 @@ mod tests { fn test() { let sut = SUT::new(); + assert_eq!(sut.get_name(), "My Shield"); + sut.set_name("S.H.I.E.L.D.".to_owned()); + + assert_eq!(sut.get_number_of_days_until_auto_confirm(), 14); + sut.set_number_of_days_until_auto_confirm(u16::MAX).unwrap(); + assert_eq!(sut.get_number_of_days_until_auto_confirm(), u16::MAX); + // Primary let sim_prim = sut.validation_for_addition_of_factor_source_to_primary_override_for_each(vec![ @@ -300,12 +344,25 @@ mod tests { sut.add_factor_source_to_primary_threshold(FactorSourceID::sample_device()) .unwrap(); + assert_eq!( + sut.get_primary_threshold_factors(), + vec![FactorSourceID::sample_device()] + ); _ = sut.set_threshold(1); + assert_eq!(sut.get_primary_threshold(), 1); sut.add_factor_source_to_primary_override(FactorSourceID::sample_arculus()) .unwrap(); sut.add_factor_source_to_primary_override(FactorSourceID::sample_arculus_other()) .unwrap(); + assert_eq!( + sut.get_primary_override_factors(), + vec![ + FactorSourceID::sample_arculus(), + FactorSourceID::sample_arculus_other() + ] + ); + // Recovery let sim_rec = sut.validation_for_addition_of_factor_source_to_recovery_override_for_each(vec![ @@ -322,6 +379,14 @@ mod tests { sut.add_factor_source_to_recovery_override(FactorSourceID::sample_ledger_other()) .unwrap(); + assert_eq!( + sut.get_recovery_factors(), + vec![ + FactorSourceID::sample_ledger(), + FactorSourceID::sample_ledger_other() + ] + ); + // Confirmation let sim_conf = sut .validation_for_addition_of_factor_source_to_confirmation_override_for_each(vec![ @@ -336,6 +401,11 @@ mod tests { sut.add_factor_source_to_confirmation_override(FactorSourceID::sample_device()) .unwrap(); + assert_eq!( + sut.get_confirmation_factors(), + vec![FactorSourceID::sample_device(),] + ); + assert_ne!( sim_prim, sut.validation_for_addition_of_factor_source_to_primary_override_for_each(vec![ @@ -397,7 +467,8 @@ mod tests { sut.remove_factor(FactorSourceID::sample_ledger_other()) .unwrap(); - let shield = sut.build("test".to_owned()).unwrap(); + let shield = sut.build().unwrap(); + assert_eq!(shield.wrapped.metadata.display_name.value, "S.H.I.E.L.D."); assert_eq!( shield .wrapped diff --git a/crates/rules-uniffi/src/unneeded_when_moved_to_sargon.rs b/crates/rules-uniffi/src/unneeded_when_moved_to_sargon.rs index 0ccea71e..9d319fe9 100644 --- a/crates/rules-uniffi/src/unneeded_when_moved_to_sargon.rs +++ b/crates/rules-uniffi/src/unneeded_when_moved_to_sargon.rs @@ -1,4 +1,4 @@ -use std::sync::Arc; +use std::{borrow::Borrow, sync::Arc}; #[cfg(test)] use rules::SampleValues; @@ -34,8 +34,10 @@ pub struct FactorSourceID { pub inner: sargon::FactorSourceID, } impl FactorSourceID { - pub fn new(inner: sargon::FactorSourceID) -> Arc { - Arc::new(Self { inner }) + pub fn new(inner: impl Borrow) -> Arc { + Arc::new(Self { + inner: *inner.borrow(), + }) } } From e3524891458110cfa46f104019490354cda9ef0a Mon Sep 17 00:00:00 2001 From: Alexander Cyon Date: Fri, 29 Nov 2024 16:00:11 +0100 Subject: [PATCH 28/33] more configs --- .../builder/matrix_builder_unit_tests.rs | 18 ++ .../matrices/matrix_of_factor_source_ids.rs | 246 +++++++++++++++++- 2 files changed, 258 insertions(+), 6 deletions(-) diff --git a/crates/rules/src/matrices/builder/matrix_builder_unit_tests.rs b/crates/rules/src/matrices/builder/matrix_builder_unit_tests.rs index 79567229..1387f6d8 100644 --- a/crates/rules/src/matrices/builder/matrix_builder_unit_tests.rs +++ b/crates/rules/src/matrices/builder/matrix_builder_unit_tests.rs @@ -1281,6 +1281,7 @@ mod shield_configs { ],), ) ); + assert_eq!(built, MatrixOfFactorSourceIds::sample_config_11()); } #[test] @@ -1381,6 +1382,7 @@ mod shield_configs { ],), ) ); + pretty_assertions::assert_eq!(built, MatrixOfFactorSourceIds::sample_config_13()) } #[test] @@ -1419,6 +1421,8 @@ mod shield_configs { ],), ) ); + + pretty_assertions::assert_eq!(built, MatrixOfFactorSourceIds::sample_config_14()) } #[test] @@ -1457,6 +1461,8 @@ mod shield_configs { ],), ) ); + + pretty_assertions::assert_eq!(built, MatrixOfFactorSourceIds::sample_config_15()) } #[test] @@ -1503,6 +1509,8 @@ mod shield_configs { ],), ) ); + + pretty_assertions::assert_eq!(built, MatrixOfFactorSourceIds::sample_config_21()) } #[test] @@ -1549,6 +1557,8 @@ mod shield_configs { ],), ) ); + + pretty_assertions::assert_eq!(built, MatrixOfFactorSourceIds::sample_config_22()) } #[test] @@ -1587,6 +1597,8 @@ mod shield_configs { ],), ) ); + + pretty_assertions::assert_eq!(built, MatrixOfFactorSourceIds::sample_config_23()) } #[test] @@ -1625,6 +1637,8 @@ mod shield_configs { ],), ) ); + + pretty_assertions::assert_eq!(built, MatrixOfFactorSourceIds::sample_config_24()) } #[test] @@ -1674,6 +1688,8 @@ mod shield_configs { ],), ) ); + + pretty_assertions::assert_eq!(built, MatrixOfFactorSourceIds::sample_config_30()) } #[test] @@ -1726,6 +1742,8 @@ mod shield_configs { ],), ) ); + + pretty_assertions::assert_eq!(built, MatrixOfFactorSourceIds::sample_config_40()) } } } diff --git a/crates/rules/src/matrices/matrix_of_factor_source_ids.rs b/crates/rules/src/matrices/matrix_of_factor_source_ids.rs index fc5b3a0a..d12e7e95 100644 --- a/crates/rules/src/matrices/matrix_of_factor_source_ids.rs +++ b/crates/rules/src/matrices/matrix_of_factor_source_ids.rs @@ -37,6 +37,116 @@ impl MatrixOfFactorSourceIds { } impl MatrixOfFactorSourceIds { + pub fn sample_config_11() -> Self { + let mut builder = MatrixBuilder::new(); + + // Primary + builder + .add_factor_source_to_primary_threshold(FactorSourceID::sample_device()) + .unwrap(); + builder + .add_factor_source_to_primary_threshold(FactorSourceID::sample_ledger()) + .unwrap(); + builder.set_threshold(2).unwrap(); + + // Recovery + builder + .add_factor_source_to_recovery_override(FactorSourceID::sample_device()) + .unwrap(); + builder + .add_factor_source_to_recovery_override(FactorSourceID::sample_ledger()) + .unwrap(); + + // Confirmation + builder + .add_factor_source_to_confirmation_override(FactorSourceID::sample_password()) + .unwrap(); + + // Build + assert!(builder.validate().is_ok()); + builder.build().unwrap() + } + + pub fn sample_config_15() -> Self { + let mut builder = MatrixBuilder::new(); + + // Primary + builder + .add_factor_source_to_primary_threshold(FactorSourceID::sample_ledger()) + .unwrap(); + builder.set_threshold(1).unwrap(); + + // Recovery + builder + .add_factor_source_to_recovery_override(FactorSourceID::sample_device()) + .unwrap(); + + // Confirmation + builder + .add_factor_source_to_confirmation_override(FactorSourceID::sample_password()) + .unwrap(); + + // Build + assert!(builder.validate().is_ok()); + builder.build().unwrap() + } + + pub fn sample_config_14() -> Self { + let mut builder = MatrixBuilder::new(); + + // Primary + builder + .add_factor_source_to_primary_threshold(FactorSourceID::sample_device()) + .unwrap(); + builder.set_threshold(1).unwrap(); + + // Recovery + builder + .add_factor_source_to_recovery_override(FactorSourceID::sample_ledger()) + .unwrap(); + + // Confirmation + builder + .add_factor_source_to_confirmation_override(FactorSourceID::sample_password()) + .unwrap(); + + // Build + assert!(builder.validate().is_ok()); + builder.build().unwrap() + } + + pub fn sample_config_13() -> Self { + let mut builder = MatrixBuilder::new(); + + // Primary + builder + .add_factor_source_to_primary_threshold(FactorSourceID::sample_device()) + .unwrap(); + let res = builder.add_factor_source_to_primary_threshold(FactorSourceID::sample_password()); + + assert_eq!( + res, + Err(MatrixBuilderValidation::RoleInIsolation { role: RoleKind::Primary, violation: RoleBuilderValidation::NotYetValid(NotYetValidReason::PrimaryRoleWithPasswordInThresholdListMustThresholdGreaterThanOne)} + )); + builder.set_threshold(2).unwrap(); + + // Recovery + builder + .add_factor_source_to_recovery_override(FactorSourceID::sample_device()) + .unwrap(); + builder + .add_factor_source_to_recovery_override(FactorSourceID::sample_ledger()) + .unwrap(); + + // Confirmation + builder + .add_factor_source_to_confirmation_override(FactorSourceID::sample_password()) + .unwrap(); + + // Build + assert!(builder.validate().is_ok()); + builder.build().unwrap() + } pub fn sample_config_12() -> Self { let mut builder = MatrixBuilder::new(); // Primary @@ -60,33 +170,133 @@ impl MatrixOfFactorSourceIds { .add_factor_source_to_confirmation_override(FactorSourceID::sample_password()) .unwrap(); + assert!(builder.validate().is_ok()); builder.build().unwrap() } - pub fn sample_config_24() -> Self { + pub fn sample_config_40() -> Self { + let mut builder = MatrixBuilder::new(); + + // Primary + builder + .add_factor_source_to_primary_threshold(FactorSourceID::sample_device()) + .unwrap(); + builder + .add_factor_source_to_primary_threshold(FactorSourceID::sample_ledger()) + .unwrap(); + builder.set_threshold(2).unwrap(); + + // Recovery + builder + .add_factor_source_to_recovery_override(FactorSourceID::sample_device()) + .unwrap(); + builder + .add_factor_source_to_recovery_override(FactorSourceID::sample_ledger()) + .unwrap(); + + // Confirmation + builder + .add_factor_source_to_confirmation_override(FactorSourceID::sample_password()) + .unwrap(); + builder + .add_factor_source_to_confirmation_override(FactorSourceID::sample_password_other()) + .unwrap(); + builder + .add_factor_source_to_confirmation_override(FactorSourceID::sample_passphrase()) + .unwrap(); + + // Build + assert!(builder.validate().is_ok()); + builder.build().unwrap() + } + pub fn sample_config_30() -> Self { + let mut builder = MatrixBuilder::new(); + + // Primary + builder + .add_factor_source_to_primary_threshold(FactorSourceID::sample_device()) + .unwrap(); + builder + .add_factor_source_to_primary_threshold(FactorSourceID::sample_ledger()) + .unwrap(); + builder.set_threshold(2).unwrap(); + + // Recovery + builder + .add_factor_source_to_recovery_override(FactorSourceID::sample_ledger()) + .unwrap(); + builder + .add_factor_source_to_recovery_override(FactorSourceID::sample_ledger_other()) + .unwrap(); + + // Confirmation + builder + .add_factor_source_to_confirmation_override(FactorSourceID::sample_device()) + .unwrap(); + builder + .add_factor_source_to_confirmation_override(FactorSourceID::sample_password()) + .unwrap(); + + // Build + assert!(builder.validate().is_ok()); + builder.build().unwrap() + } + + pub fn sample_config_23() -> Self { let mut builder = MatrixBuilder::new(); // Primary // TODO: Ask Matt about this, does he mean Threshold(1) or Override? builder - .add_factor_source_to_primary_override(FactorSourceID::sample_device()) + .add_factor_source_to_primary_override(FactorSourceID::sample_ledger()) + .unwrap(); + + // Recovery + builder + .add_factor_source_to_recovery_override(FactorSourceID::sample_ledger_other()) + .unwrap(); + + // Confirmation + builder + .add_factor_source_to_confirmation_override(FactorSourceID::sample_device()) + .unwrap(); + + // Build + assert!(builder.validate().is_ok()); + builder.build().unwrap() + } + + pub fn sample_config_22() -> Self { + let mut builder = MatrixBuilder::new(); + + // Primary + builder + .add_factor_source_to_primary_threshold(FactorSourceID::sample_ledger()) .unwrap(); + builder + .add_factor_source_to_primary_threshold(FactorSourceID::sample_ledger_other()) + .unwrap(); + builder.set_threshold(2).unwrap(); // Recovery builder .add_factor_source_to_recovery_override(FactorSourceID::sample_ledger()) .unwrap(); + builder + .add_factor_source_to_recovery_override(FactorSourceID::sample_ledger_other()) + .unwrap(); // Confirmation builder - .add_factor_source_to_confirmation_override(FactorSourceID::sample_ledger_other()) + .add_factor_source_to_confirmation_override(FactorSourceID::sample_device()) .unwrap(); // Build + assert!(builder.validate().is_ok()); builder.build().unwrap() } - pub fn sample_config_11() -> Self { + pub fn sample_config_21() -> Self { let mut builder = MatrixBuilder::new(); // Primary @@ -100,15 +310,39 @@ impl MatrixOfFactorSourceIds { // Recovery builder - .add_factor_source_to_recovery_override(FactorSourceID::sample_device()) + .add_factor_source_to_recovery_override(FactorSourceID::sample_ledger()) + .unwrap(); + builder + .add_factor_source_to_recovery_override(FactorSourceID::sample_ledger_other()) + .unwrap(); + + // Confirmation + builder + .add_factor_source_to_confirmation_override(FactorSourceID::sample_device()) + .unwrap(); + + // Build + assert!(builder.validate().is_ok()); + builder.build().unwrap() + } + + pub fn sample_config_24() -> Self { + let mut builder = MatrixBuilder::new(); + + // Primary + // TODO: Ask Matt about this, does he mean Threshold(1) or Override? + builder + .add_factor_source_to_primary_override(FactorSourceID::sample_device()) .unwrap(); + + // Recovery builder .add_factor_source_to_recovery_override(FactorSourceID::sample_ledger()) .unwrap(); // Confirmation builder - .add_factor_source_to_confirmation_override(FactorSourceID::sample_password()) + .add_factor_source_to_confirmation_override(FactorSourceID::sample_ledger_other()) .unwrap(); // Build From fce7d5705933fa16c117f50e5a7aeea250f9257d Mon Sep 17 00:00:00 2001 From: Alexander Cyon Date: Fri, 29 Nov 2024 16:04:22 +0100 Subject: [PATCH 29/33] regroup --- .../matrices/matrix_of_factor_source_ids.rs | 114 +++++++++--------- 1 file changed, 58 insertions(+), 56 deletions(-) diff --git a/crates/rules/src/matrices/matrix_of_factor_source_ids.rs b/crates/rules/src/matrices/matrix_of_factor_source_ids.rs index d12e7e95..5a662770 100644 --- a/crates/rules/src/matrices/matrix_of_factor_source_ids.rs +++ b/crates/rules/src/matrices/matrix_of_factor_source_ids.rs @@ -67,40 +67,52 @@ impl MatrixOfFactorSourceIds { builder.build().unwrap() } - pub fn sample_config_15() -> Self { + pub fn sample_config_12() -> Self { let mut builder = MatrixBuilder::new(); - // Primary builder .add_factor_source_to_primary_threshold(FactorSourceID::sample_ledger()) .unwrap(); - builder.set_threshold(1).unwrap(); + _ = builder.add_factor_source_to_primary_threshold(FactorSourceID::sample_password()); + + _ = builder.set_threshold(2); // Recovery builder .add_factor_source_to_recovery_override(FactorSourceID::sample_device()) .unwrap(); + builder + .add_factor_source_to_recovery_override(FactorSourceID::sample_ledger()) + .unwrap(); // Confirmation builder .add_factor_source_to_confirmation_override(FactorSourceID::sample_password()) .unwrap(); - // Build assert!(builder.validate().is_ok()); builder.build().unwrap() } - pub fn sample_config_14() -> Self { + pub fn sample_config_13() -> Self { let mut builder = MatrixBuilder::new(); // Primary builder .add_factor_source_to_primary_threshold(FactorSourceID::sample_device()) .unwrap(); - builder.set_threshold(1).unwrap(); + let res = builder.add_factor_source_to_primary_threshold(FactorSourceID::sample_password()); + + assert_eq!( + res, + Err(MatrixBuilderValidation::RoleInIsolation { role: RoleKind::Primary, violation: RoleBuilderValidation::NotYetValid(NotYetValidReason::PrimaryRoleWithPasswordInThresholdListMustThresholdGreaterThanOne)} + )); + builder.set_threshold(2).unwrap(); // Recovery + builder + .add_factor_source_to_recovery_override(FactorSourceID::sample_device()) + .unwrap(); builder .add_factor_source_to_recovery_override(FactorSourceID::sample_ledger()) .unwrap(); @@ -115,25 +127,16 @@ impl MatrixOfFactorSourceIds { builder.build().unwrap() } - pub fn sample_config_13() -> Self { + pub fn sample_config_14() -> Self { let mut builder = MatrixBuilder::new(); // Primary builder .add_factor_source_to_primary_threshold(FactorSourceID::sample_device()) .unwrap(); - let res = builder.add_factor_source_to_primary_threshold(FactorSourceID::sample_password()); - - assert_eq!( - res, - Err(MatrixBuilderValidation::RoleInIsolation { role: RoleKind::Primary, violation: RoleBuilderValidation::NotYetValid(NotYetValidReason::PrimaryRoleWithPasswordInThresholdListMustThresholdGreaterThanOne)} - )); - builder.set_threshold(2).unwrap(); + builder.set_threshold(1).unwrap(); // Recovery - builder - .add_factor_source_to_recovery_override(FactorSourceID::sample_device()) - .unwrap(); builder .add_factor_source_to_recovery_override(FactorSourceID::sample_ledger()) .unwrap(); @@ -147,34 +150,32 @@ impl MatrixOfFactorSourceIds { assert!(builder.validate().is_ok()); builder.build().unwrap() } - pub fn sample_config_12() -> Self { + + pub fn sample_config_15() -> Self { let mut builder = MatrixBuilder::new(); + // Primary builder .add_factor_source_to_primary_threshold(FactorSourceID::sample_ledger()) .unwrap(); - _ = builder.add_factor_source_to_primary_threshold(FactorSourceID::sample_password()); - - _ = builder.set_threshold(2); + builder.set_threshold(1).unwrap(); // Recovery builder .add_factor_source_to_recovery_override(FactorSourceID::sample_device()) .unwrap(); - builder - .add_factor_source_to_recovery_override(FactorSourceID::sample_ledger()) - .unwrap(); // Confirmation builder .add_factor_source_to_confirmation_override(FactorSourceID::sample_password()) .unwrap(); + // Build assert!(builder.validate().is_ok()); builder.build().unwrap() } - pub fn sample_config_40() -> Self { + pub fn sample_config_21() -> Self { let mut builder = MatrixBuilder::new(); // Primary @@ -188,36 +189,31 @@ impl MatrixOfFactorSourceIds { // Recovery builder - .add_factor_source_to_recovery_override(FactorSourceID::sample_device()) + .add_factor_source_to_recovery_override(FactorSourceID::sample_ledger()) .unwrap(); builder - .add_factor_source_to_recovery_override(FactorSourceID::sample_ledger()) + .add_factor_source_to_recovery_override(FactorSourceID::sample_ledger_other()) .unwrap(); // Confirmation builder - .add_factor_source_to_confirmation_override(FactorSourceID::sample_password()) - .unwrap(); - builder - .add_factor_source_to_confirmation_override(FactorSourceID::sample_password_other()) - .unwrap(); - builder - .add_factor_source_to_confirmation_override(FactorSourceID::sample_passphrase()) + .add_factor_source_to_confirmation_override(FactorSourceID::sample_device()) .unwrap(); // Build assert!(builder.validate().is_ok()); builder.build().unwrap() } - pub fn sample_config_30() -> Self { + + pub fn sample_config_22() -> Self { let mut builder = MatrixBuilder::new(); // Primary builder - .add_factor_source_to_primary_threshold(FactorSourceID::sample_device()) + .add_factor_source_to_primary_threshold(FactorSourceID::sample_ledger()) .unwrap(); builder - .add_factor_source_to_primary_threshold(FactorSourceID::sample_ledger()) + .add_factor_source_to_primary_threshold(FactorSourceID::sample_ledger_other()) .unwrap(); builder.set_threshold(2).unwrap(); @@ -233,9 +229,6 @@ impl MatrixOfFactorSourceIds { builder .add_factor_source_to_confirmation_override(FactorSourceID::sample_device()) .unwrap(); - builder - .add_factor_source_to_confirmation_override(FactorSourceID::sample_password()) - .unwrap(); // Build assert!(builder.validate().is_ok()); @@ -266,37 +259,30 @@ impl MatrixOfFactorSourceIds { builder.build().unwrap() } - pub fn sample_config_22() -> Self { + pub fn sample_config_24() -> Self { let mut builder = MatrixBuilder::new(); // Primary + // TODO: Ask Matt about this, does he mean Threshold(1) or Override? builder - .add_factor_source_to_primary_threshold(FactorSourceID::sample_ledger()) - .unwrap(); - builder - .add_factor_source_to_primary_threshold(FactorSourceID::sample_ledger_other()) + .add_factor_source_to_primary_override(FactorSourceID::sample_device()) .unwrap(); - builder.set_threshold(2).unwrap(); // Recovery builder .add_factor_source_to_recovery_override(FactorSourceID::sample_ledger()) .unwrap(); - builder - .add_factor_source_to_recovery_override(FactorSourceID::sample_ledger_other()) - .unwrap(); // Confirmation builder - .add_factor_source_to_confirmation_override(FactorSourceID::sample_device()) + .add_factor_source_to_confirmation_override(FactorSourceID::sample_ledger_other()) .unwrap(); // Build - assert!(builder.validate().is_ok()); builder.build().unwrap() } - pub fn sample_config_21() -> Self { + pub fn sample_config_30() -> Self { let mut builder = MatrixBuilder::new(); // Primary @@ -320,32 +306,48 @@ impl MatrixOfFactorSourceIds { builder .add_factor_source_to_confirmation_override(FactorSourceID::sample_device()) .unwrap(); + builder + .add_factor_source_to_confirmation_override(FactorSourceID::sample_password()) + .unwrap(); // Build assert!(builder.validate().is_ok()); builder.build().unwrap() } - pub fn sample_config_24() -> Self { + pub fn sample_config_40() -> Self { let mut builder = MatrixBuilder::new(); // Primary - // TODO: Ask Matt about this, does he mean Threshold(1) or Override? builder - .add_factor_source_to_primary_override(FactorSourceID::sample_device()) + .add_factor_source_to_primary_threshold(FactorSourceID::sample_device()) + .unwrap(); + builder + .add_factor_source_to_primary_threshold(FactorSourceID::sample_ledger()) .unwrap(); + builder.set_threshold(2).unwrap(); // Recovery + builder + .add_factor_source_to_recovery_override(FactorSourceID::sample_device()) + .unwrap(); builder .add_factor_source_to_recovery_override(FactorSourceID::sample_ledger()) .unwrap(); // Confirmation builder - .add_factor_source_to_confirmation_override(FactorSourceID::sample_ledger_other()) + .add_factor_source_to_confirmation_override(FactorSourceID::sample_password()) + .unwrap(); + builder + .add_factor_source_to_confirmation_override(FactorSourceID::sample_password_other()) + .unwrap(); + builder + .add_factor_source_to_confirmation_override(FactorSourceID::sample_passphrase()) .unwrap(); // Build + assert!(builder.validate().is_ok()); builder.build().unwrap() } } From 5d2655561811e83bcd4dcda09f039e50216ae923 Mon Sep 17 00:00:00 2001 From: Alexander Cyon Date: Fri, 29 Nov 2024 16:06:47 +0100 Subject: [PATCH 30/33] more tests --- .../matrices/matrix_of_factor_source_ids.rs | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/crates/rules/src/matrices/matrix_of_factor_source_ids.rs b/crates/rules/src/matrices/matrix_of_factor_source_ids.rs index 5a662770..1c2a56a9 100644 --- a/crates/rules/src/matrices/matrix_of_factor_source_ids.rs +++ b/crates/rules/src/matrices/matrix_of_factor_source_ids.rs @@ -386,6 +386,39 @@ mod tests { ); } + #[test] + fn hash() { + assert_eq!( + HashSet::::from_iter([ + SUT::sample_config_11(), + SUT::sample_config_12(), + SUT::sample_config_13(), + SUT::sample_config_14(), + SUT::sample_config_15(), + SUT::sample_config_21(), + SUT::sample_config_22(), + SUT::sample_config_23(), + SUT::sample_config_24(), + SUT::sample_config_30(), + SUT::sample_config_40(), + // Duplicates should be removed + SUT::sample_config_11(), + SUT::sample_config_12(), + SUT::sample_config_13(), + SUT::sample_config_14(), + SUT::sample_config_15(), + SUT::sample_config_21(), + SUT::sample_config_22(), + SUT::sample_config_23(), + SUT::sample_config_24(), + SUT::sample_config_30(), + SUT::sample_config_40(), + ]) + .len(), + 11 + ); + } + #[test] fn assert_json_sample() { let sut = SUT::sample(); From 8a3cb714d453e2120a9be2f77c6278a9d0a48ca2 Mon Sep 17 00:00:00 2001 From: Alexander Cyon Date: Fri, 29 Nov 2024 16:25:16 +0100 Subject: [PATCH 31/33] more mfa configs --- .../matrices/matrix_of_factor_source_ids.rs | 162 +++++++++++++++++- 1 file changed, 161 insertions(+), 1 deletion(-) diff --git a/crates/rules/src/matrices/matrix_of_factor_source_ids.rs b/crates/rules/src/matrices/matrix_of_factor_source_ids.rs index 1c2a56a9..24764f0d 100644 --- a/crates/rules/src/matrices/matrix_of_factor_source_ids.rs +++ b/crates/rules/src/matrices/matrix_of_factor_source_ids.rs @@ -350,6 +350,156 @@ impl MatrixOfFactorSourceIds { assert!(builder.validate().is_ok()); builder.build().unwrap() } + + pub fn sample_config_51() -> Self { + let mut builder = MatrixBuilder::new(); + + // Primary + builder + .add_factor_source_to_primary_threshold(FactorSourceID::sample_device()) + .unwrap(); + let _ = builder.set_threshold(2); + builder + .add_factor_source_to_primary_threshold(FactorSourceID::sample_password()) + .unwrap(); + + // Recovery + builder + .add_factor_source_to_recovery_override(FactorSourceID::sample_trusted_contact()) + .unwrap(); + + // Confirmation + builder + .add_factor_source_to_confirmation_override(FactorSourceID::sample_password()) + .unwrap(); + + // Build + assert!(builder.validate().is_ok()); + builder.build().unwrap() + } + + pub fn sample_config_52() -> Self { + let mut builder = MatrixBuilder::new(); + + // Primary + builder + .add_factor_source_to_primary_threshold(FactorSourceID::sample_device()) + .unwrap(); + let _ = builder.set_threshold(2); + builder + .add_factor_source_to_primary_threshold(FactorSourceID::sample_password()) + .unwrap(); + + // Recovery + builder + .add_factor_source_to_recovery_override(FactorSourceID::sample_trusted_contact()) + .unwrap(); + builder + .add_factor_source_to_recovery_override(FactorSourceID::sample_trusted_contact_other()) + .unwrap(); + builder + .add_factor_source_to_recovery_override(FactorSourceID::sample_device()) + .unwrap(); + + // Confirmation + builder + .add_factor_source_to_confirmation_override(FactorSourceID::sample_password()) + .unwrap(); + builder + .add_factor_source_to_confirmation_override(FactorSourceID::sample_password_other()) + .unwrap(); + builder + .add_factor_source_to_confirmation_override(FactorSourceID::sample_passphrase()) + .unwrap(); + + // Build + assert!(builder.validate().is_ok()); + builder.build().unwrap() + } + + pub fn sample_config_60() -> Self { + let mut builder = MatrixBuilder::new(); + + // Primary + builder + .add_factor_source_to_primary_threshold(FactorSourceID::sample_device()) + .unwrap(); + let _ = builder.set_threshold(1); + + // Recovery + builder + .add_factor_source_to_recovery_override(FactorSourceID::sample_trusted_contact()) + .unwrap(); + + // Confirmation + builder + .add_factor_source_to_confirmation_override(FactorSourceID::sample_security_questions()) + .unwrap(); + + // Build + assert!(builder.validate().is_ok()); + builder.build().unwrap() + } + + pub fn sample_config_70() -> Self { + let mut builder = MatrixBuilder::new(); + + // Primary + builder + .add_factor_source_to_primary_threshold(FactorSourceID::sample_device()) + .unwrap(); + let _ = builder.set_threshold(2); + builder + .add_factor_source_to_primary_threshold(FactorSourceID::sample_ledger()) + .unwrap(); + + // Recovery + builder + .add_factor_source_to_recovery_override(FactorSourceID::sample_trusted_contact()) + .unwrap(); + builder + .add_factor_source_to_recovery_override(FactorSourceID::sample_ledger()) + .unwrap(); + + // Confirmation + builder + .add_factor_source_to_confirmation_override(FactorSourceID::sample_device()) + .unwrap(); + + // Build + assert!(builder.validate().is_ok()); + builder.build().unwrap() + } + + pub fn sample_config_80() -> Self { + let mut builder = MatrixBuilder::new(); + + // Primary + builder + .add_factor_source_to_primary_threshold(FactorSourceID::sample_device()) + .unwrap(); + let _ = builder.set_threshold(2); + builder + .add_factor_source_to_primary_threshold(FactorSourceID::sample_ledger()) + .unwrap(); + + // Recovery + builder + .add_factor_source_to_recovery_override(FactorSourceID::sample_ledger()) + .unwrap(); + builder + .add_factor_source_to_recovery_override(FactorSourceID::sample_device()) + .unwrap(); + + // Confirmation + builder + .add_factor_source_to_confirmation_override(FactorSourceID::sample_security_questions()) + .unwrap(); + + // Build + assert!(builder.validate().is_ok()); + builder.build().unwrap() + } } impl HasSampleValues for MatrixOfFactorSourceIds { @@ -401,6 +551,11 @@ mod tests { SUT::sample_config_24(), SUT::sample_config_30(), SUT::sample_config_40(), + SUT::sample_config_51(), + SUT::sample_config_52(), + SUT::sample_config_60(), + SUT::sample_config_70(), + SUT::sample_config_80(), // Duplicates should be removed SUT::sample_config_11(), SUT::sample_config_12(), @@ -413,9 +568,14 @@ mod tests { SUT::sample_config_24(), SUT::sample_config_30(), SUT::sample_config_40(), + SUT::sample_config_51(), + SUT::sample_config_52(), + SUT::sample_config_60(), + SUT::sample_config_70(), + SUT::sample_config_80(), ]) .len(), - 11 + 16 ); } From e49f2d18ab974bba447e401c4975cde544fad3a2 Mon Sep 17 00:00:00 2001 From: Alexander Cyon Date: Fri, 29 Nov 2024 16:28:47 +0100 Subject: [PATCH 32/33] cleanup --- .../matrices/matrix_of_factor_source_ids.rs | 34 ++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/crates/rules/src/matrices/matrix_of_factor_source_ids.rs b/crates/rules/src/matrices/matrix_of_factor_source_ids.rs index 24764f0d..b713d98f 100644 --- a/crates/rules/src/matrices/matrix_of_factor_source_ids.rs +++ b/crates/rules/src/matrices/matrix_of_factor_source_ids.rs @@ -500,6 +500,36 @@ impl MatrixOfFactorSourceIds { assert!(builder.validate().is_ok()); builder.build().unwrap() } + + pub fn sample_config_90() -> Self { + let mut builder = MatrixBuilder::new(); + + // Primary + builder + .add_factor_source_to_primary_threshold(FactorSourceID::sample_device()) + .unwrap(); + let _ = builder.set_threshold(2); + builder + .add_factor_source_to_primary_threshold(FactorSourceID::sample_ledger()) + .unwrap(); + + // Recovery + builder + .add_factor_source_to_recovery_override(FactorSourceID::sample_trusted_contact()) + .unwrap(); + builder + .add_factor_source_to_recovery_override(FactorSourceID::sample_device()) + .unwrap(); + + // Confirmation + builder + .add_factor_source_to_confirmation_override(FactorSourceID::sample_security_questions()) + .unwrap(); + + // Build + assert!(builder.validate().is_ok()); + builder.build().unwrap() + } } impl HasSampleValues for MatrixOfFactorSourceIds { @@ -556,6 +586,7 @@ mod tests { SUT::sample_config_60(), SUT::sample_config_70(), SUT::sample_config_80(), + SUT::sample_config_90(), // Duplicates should be removed SUT::sample_config_11(), SUT::sample_config_12(), @@ -573,9 +604,10 @@ mod tests { SUT::sample_config_60(), SUT::sample_config_70(), SUT::sample_config_80(), + SUT::sample_config_90(), ]) .len(), - 16 + 17 ); } From 146fd13a4ba82866fb0dad9012cfc69102975fa4 Mon Sep 17 00:00:00 2001 From: Alexander Cyon Date: Fri, 29 Nov 2024 16:47:33 +0100 Subject: [PATCH 33/33] fix --- Cargo.toml | 2 +- .../src/unneeded_when_moved_to_sargon.rs | 4 +-- crates/rules/src/lib.rs | 2 +- .../builder/matrix_builder_unit_tests.rs | 12 +++---- .../matrices/matrix_of_factor_instances.rs | 2 +- .../matrices/matrix_of_factor_source_ids.rs | 5 +-- crates/rules/src/move_to_sargon.rs | 33 +++++++++---------- .../confirmation_roles_builder_unit_tests.rs | 2 +- .../primary_roles_builder_unit_tests.rs | 4 +-- .../recovery_roles_builder_unit_tests.rs | 2 +- .../rules/src/roles/builder/roles_builder.rs | 8 ++--- ...onfirmation_role_with_factor_source_ids.rs | 2 +- ...security_structure_of_factor_source_ids.rs | 2 +- 13 files changed, 40 insertions(+), 40 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 787d49d0..4874ba61 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,4 +13,4 @@ debug = true [workspace.dependencies] thiserror = "2.0.3" -sargon = { git = "https://github.com/radixdlt/sargon", branch = "main" } +sargon = { git = "https://github.com/radixdlt/sargon", tag = "1.1.70" } diff --git a/crates/rules-uniffi/src/unneeded_when_moved_to_sargon.rs b/crates/rules-uniffi/src/unneeded_when_moved_to_sargon.rs index 9d319fe9..1e36c6ff 100644 --- a/crates/rules-uniffi/src/unneeded_when_moved_to_sargon.rs +++ b/crates/rules-uniffi/src/unneeded_when_moved_to_sargon.rs @@ -7,7 +7,7 @@ use rules::SampleValues; pub enum FactorSourceKind { Device, LedgerHQHardwareWallet, - Passphrase, + Password, OffDeviceMnemonic, TrustedContact, SecurityQuestions, @@ -20,7 +20,7 @@ impl From for sargon::FactorSourceKind { FactorSourceKind::LedgerHQHardwareWallet => { sargon::FactorSourceKind::LedgerHQHardwareWallet } - FactorSourceKind::Passphrase => sargon::FactorSourceKind::Passphrase, + FactorSourceKind::Password => sargon::FactorSourceKind::Password, FactorSourceKind::OffDeviceMnemonic => sargon::FactorSourceKind::OffDeviceMnemonic, FactorSourceKind::TrustedContact => sargon::FactorSourceKind::TrustedContact, FactorSourceKind::SecurityQuestions => sargon::FactorSourceKind::SecurityQuestions, diff --git a/crates/rules/src/lib.rs b/crates/rules/src/lib.rs index 5a0f32a0..2691d046 100644 --- a/crates/rules/src/lib.rs +++ b/crates/rules/src/lib.rs @@ -8,7 +8,7 @@ mod security_structure_of_factors; pub mod prelude { pub(crate) use sargon::{ - BIP39Passphrase, BaseIsFactorSource, CommonError, DerivationPreset, DisplayName, + BIP39Passphrase, BaseBaseIsFactorSource, CommonError, DerivationPreset, DisplayName, FactorInstance, FactorInstances, FactorSource, FactorSourceID, FactorSourceIDFromHash, FactorSourceKind, FactorSources, HasRoleKindObjectSafe, HasSampleValues, HierarchicalDeterministicFactorInstance, Identifiable, IndexMap, IndexSet, diff --git a/crates/rules/src/matrices/builder/matrix_builder_unit_tests.rs b/crates/rules/src/matrices/builder/matrix_builder_unit_tests.rs index 1387f6d8..a5197f3f 100644 --- a/crates/rules/src/matrices/builder/matrix_builder_unit_tests.rs +++ b/crates/rules/src/matrices/builder/matrix_builder_unit_tests.rs @@ -793,7 +793,7 @@ mod validation_of_addition_of_kind { test(FactorSourceKind::LedgerHQHardwareWallet, true); test(FactorSourceKind::ArculusCard, true); test(FactorSourceKind::SecurityQuestions, false); - test(FactorSourceKind::Passphrase, false); + test(FactorSourceKind::Password, false); test(FactorSourceKind::OffDeviceMnemonic, true); test(FactorSourceKind::TrustedContact, true); } @@ -813,7 +813,7 @@ mod validation_of_addition_of_kind { test(FactorSourceKind::LedgerHQHardwareWallet, true); test(FactorSourceKind::ArculusCard, true); test(FactorSourceKind::SecurityQuestions, false); - test(FactorSourceKind::Passphrase, false); + test(FactorSourceKind::Password, false); test(FactorSourceKind::OffDeviceMnemonic, true); test(FactorSourceKind::TrustedContact, true); } @@ -835,7 +835,7 @@ mod validation_of_addition_of_kind { test(FactorSourceKind::LedgerHQHardwareWallet, true); test(FactorSourceKind::ArculusCard, true); test(FactorSourceKind::SecurityQuestions, true); - test(FactorSourceKind::Passphrase, true); + test(FactorSourceKind::Password, true); test(FactorSourceKind::OffDeviceMnemonic, true); test(FactorSourceKind::TrustedContact, false); } @@ -856,7 +856,7 @@ mod validation_of_addition_of_kind { test(FactorSourceKind::LedgerHQHardwareWallet, true); test(FactorSourceKind::ArculusCard, true); test(FactorSourceKind::SecurityQuestions, true); - test(FactorSourceKind::Passphrase, true); + test(FactorSourceKind::Password, true); test(FactorSourceKind::OffDeviceMnemonic, true); test(FactorSourceKind::TrustedContact, false); } @@ -1088,7 +1088,7 @@ mod validation_of_addition_of_kind { // ASSERT let res = sut.validation_for_addition_of_factor_source_of_kind_to_primary_threshold( - FactorSourceKind::Passphrase, + FactorSourceKind::Password, ); assert!(res.is_err()); } @@ -1103,7 +1103,7 @@ mod validation_of_addition_of_kind { // ASSERT let res = sut.validation_for_addition_of_factor_source_of_kind_to_primary_threshold( - FactorSourceKind::Passphrase, + FactorSourceKind::Password, ); assert!(res.is_ok()); } diff --git a/crates/rules/src/matrices/matrix_of_factor_instances.rs b/crates/rules/src/matrices/matrix_of_factor_instances.rs index efb8ede3..54b505a0 100644 --- a/crates/rules/src/matrices/matrix_of_factor_instances.rs +++ b/crates/rules/src/matrices/matrix_of_factor_instances.rs @@ -280,7 +280,7 @@ mod tests { "factorSourceID": { "discriminator": "fromHash", "fromHash": { - "kind": "passphrase", + "kind": "password", "body": "181ab662e19fac3ad9f08d5c673b286d4a5ed9cd3762356dc9831dc42427c1b9" } }, diff --git a/crates/rules/src/matrices/matrix_of_factor_source_ids.rs b/crates/rules/src/matrices/matrix_of_factor_source_ids.rs index b713d98f..cedac836 100644 --- a/crates/rules/src/matrices/matrix_of_factor_source_ids.rs +++ b/crates/rules/src/matrices/matrix_of_factor_source_ids.rs @@ -614,10 +614,11 @@ mod tests { #[test] fn assert_json_sample() { let sut = SUT::sample(); + assert_eq_after_json_roundtrip( &sut, r#" - { + { "primaryRole": { "threshold": 2, "thresholdFactors": [ @@ -665,7 +666,7 @@ mod tests { { "discriminator": "fromHash", "fromHash": { - "kind": "passphrase", + "kind": "password", "body": "181ab662e19fac3ad9f08d5c673b286d4a5ed9cd3762356dc9831dc42427c1b9" } } diff --git a/crates/rules/src/move_to_sargon.rs b/crates/rules/src/move_to_sargon.rs index a7326e07..e413f3d2 100644 --- a/crates/rules/src/move_to_sargon.rs +++ b/crates/rules/src/move_to_sargon.rs @@ -61,13 +61,12 @@ impl SampleValues for FactorSourceID { FactorSourceIDFromHash::sample_arculus_other().into() } - /// Matt calls `passphrase` "password" fn sample_password() -> Self { - FactorSourceIDFromHash::sample_passphrase().into() + FactorSourceIDFromHash::sample_password().into() } - /// Matt calls `passphrase` "password" + fn sample_password_other() -> Self { - FactorSourceIDFromHash::sample_passphrase_other().into() + FactorSourceIDFromHash::sample_password_other().into() } /// Matt calls `off_device_mnemonic` "passphrase" @@ -289,9 +288,9 @@ pub trait MnemonicWithPassphraseSamples: Sized { fn sample_security_questions_other() -> Self; - fn sample_passphrase() -> Self; + fn sample_password() -> Self; - fn sample_passphrase_other() -> Self; + fn sample_password_other() -> Self; fn all_samples() -> Vec { vec![ @@ -307,8 +306,8 @@ pub trait MnemonicWithPassphraseSamples: Sized { Self::sample_arculus_other(), Self::sample_security_questions(), Self::sample_security_questions_other(), - Self::sample_passphrase(), - Self::sample_passphrase_other(), + Self::sample_password(), + Self::sample_password_other(), ] } @@ -397,12 +396,12 @@ pub(crate) static MNEMONIC_BY_ID_MAP: Lazy< MnemonicWithPassphrase::sample_arculus_other(), ), ( - FactorSourceIDFromHash::sample_passphrase(), - MnemonicWithPassphrase::sample_passphrase(), + FactorSourceIDFromHash::sample_password(), + MnemonicWithPassphrase::sample_password(), ), ( - FactorSourceIDFromHash::sample_passphrase_other(), - MnemonicWithPassphrase::sample_passphrase_other(), + FactorSourceIDFromHash::sample_off_device(), + MnemonicWithPassphrase::sample_off_device_other(), ), ( FactorSourceIDFromHash::sample_off_device(), @@ -515,14 +514,14 @@ impl MnemonicWithPassphraseSamples for MnemonicWithPassphrase { ) } - fn sample_passphrase() -> Self { - Self::with_passphrase(Mnemonic::sample_passphrase(), BIP39Passphrase::default()) + fn sample_password() -> Self { + Self::with_passphrase(Mnemonic::sample_password(), BIP39Passphrase::default()) } - fn sample_passphrase_other() -> Self { + fn sample_password_other() -> Self { Self::with_passphrase( - Mnemonic::sample_security_questions_other(), - BIP39Passphrase::new("Pass phrase"), + Mnemonic::sample_password_other(), + BIP39Passphrase::default(), ) } } diff --git a/crates/rules/src/roles/builder/confirmation_roles_builder_unit_tests.rs b/crates/rules/src/roles/builder/confirmation_roles_builder_unit_tests.rs index c1ae8115..af4c70f1 100644 --- a/crates/rules/src/roles/builder/confirmation_roles_builder_unit_tests.rs +++ b/crates/rules/src/roles/builder/confirmation_roles_builder_unit_tests.rs @@ -47,7 +47,7 @@ fn validation_for_addition_of_factor_source_of_kind_to_list() { ok(LedgerHQHardwareWallet); ok(ArculusCard); ok(SecurityQuestions); - ok(Passphrase); + ok(Password); ok(OffDeviceMnemonic); not_ok(TrustedContact); } diff --git a/crates/rules/src/roles/builder/primary_roles_builder_unit_tests.rs b/crates/rules/src/roles/builder/primary_roles_builder_unit_tests.rs index 77f8fbc8..dee40a53 100644 --- a/crates/rules/src/roles/builder/primary_roles_builder_unit_tests.rs +++ b/crates/rules/src/roles/builder/primary_roles_builder_unit_tests.rs @@ -467,7 +467,7 @@ mod password { sut.add_factor_source_to_threshold(FactorSourceID::sample_ledger()) .unwrap(); }); - ok_with(Passphrase, |sut| { + ok_with(Password, |sut| { sut.add_factor_source_to_threshold(FactorSourceID::sample_device()) .unwrap(); _ = sut.set_threshold(2); @@ -550,7 +550,7 @@ mod password { .unwrap(); }); - not_ok(Passphrase); + not_ok(Password); not_ok(SecurityQuestions); not_ok(TrustedContact); diff --git a/crates/rules/src/roles/builder/recovery_roles_builder_unit_tests.rs b/crates/rules/src/roles/builder/recovery_roles_builder_unit_tests.rs index 8f7030a8..d2fd4322 100644 --- a/crates/rules/src/roles/builder/recovery_roles_builder_unit_tests.rs +++ b/crates/rules/src/roles/builder/recovery_roles_builder_unit_tests.rs @@ -48,7 +48,7 @@ fn validation_for_addition_of_factor_source_of_kind_to_list() { ok(TrustedContact); ok(OffDeviceMnemonic); - not_ok(Passphrase); + not_ok(Password); not_ok(SecurityQuestions); } diff --git a/crates/rules/src/roles/builder/roles_builder.rs b/crates/rules/src/roles/builder/roles_builder.rs index ebf4ef26..b10e6ef0 100644 --- a/crates/rules/src/roles/builder/roles_builder.rs +++ b/crates/rules/src/roles/builder/roles_builder.rs @@ -542,7 +542,7 @@ impl RoleBuilder { factor_list_kind: FactorListKind, ) -> RoleBuilderMutateResult { match factor_source_kind { - FactorSourceKind::Passphrase => { + FactorSourceKind::Password => { return self.validation_for_addition_of_password_to_primary(factor_list_kind) } FactorSourceKind::SecurityQuestions => { @@ -579,7 +579,7 @@ impl RoleBuilder { FactorSourceKind::Device | FactorSourceKind::LedgerHQHardwareWallet | FactorSourceKind::ArculusCard - | FactorSourceKind::Passphrase + | FactorSourceKind::Password | FactorSourceKind::OffDeviceMnemonic | FactorSourceKind::SecurityQuestions => Ok(()), FactorSourceKind::TrustedContact => { @@ -603,7 +603,7 @@ impl RoleBuilder { FactorSourceKind::SecurityQuestions => { RoleBuilderMutateResult::forever_invalid(RecoveryRoleSecurityQuestionsNotSupported) } - FactorSourceKind::Passphrase => { + FactorSourceKind::Password => { RoleBuilderMutateResult::forever_invalid(RecoveryRolePasswordNotSupported) } } @@ -619,7 +619,7 @@ impl RoleBuilder { factor_list_kind: FactorListKind, ) -> RoleBuilderMutateResult { assert_eq!(self.role(), RoleKind::Primary); - let factor_source_kind = FactorSourceKind::Passphrase; + let factor_source_kind = FactorSourceKind::Password; match factor_list_kind { Threshold => { let is_alone = self diff --git a/crates/rules/src/roles/factor_levels/factor_source_id_level/confirmation_role_with_factor_source_ids.rs b/crates/rules/src/roles/factor_levels/factor_source_id_level/confirmation_role_with_factor_source_ids.rs index df0810e8..03c4d158 100644 --- a/crates/rules/src/roles/factor_levels/factor_source_id_level/confirmation_role_with_factor_source_ids.rs +++ b/crates/rules/src/roles/factor_levels/factor_source_id_level/confirmation_role_with_factor_source_ids.rs @@ -88,7 +88,7 @@ mod tests { { "discriminator": "fromHash", "fromHash": { - "kind": "passphrase", + "kind": "password", "body": "181ab662e19fac3ad9f08d5c673b286d4a5ed9cd3762356dc9831dc42427c1b9" } } diff --git a/crates/rules/src/security_structure_of_factors/security_structure_of_factor_source_ids.rs b/crates/rules/src/security_structure_of_factors/security_structure_of_factor_source_ids.rs index 486d895a..404cbd9f 100644 --- a/crates/rules/src/security_structure_of_factors/security_structure_of_factor_source_ids.rs +++ b/crates/rules/src/security_structure_of_factors/security_structure_of_factor_source_ids.rs @@ -92,7 +92,7 @@ mod tests { { "discriminator": "fromHash", "fromHash": { - "kind": "passphrase", + "kind": "password", "body": "181ab662e19fac3ad9f08d5c673b286d4a5ed9cd3762356dc9831dc42427c1b9" } }