From b89ea67fc75939a5648360ced63125ecf4eb0162 Mon Sep 17 00:00:00 2001 From: max funk Date: Fri, 27 Oct 2023 12:41:54 -0700 Subject: [PATCH 01/21] ignore profraw --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 129445f7..13d9962f 100644 --- a/.gitignore +++ b/.gitignore @@ -77,6 +77,7 @@ migrations/dumps/*.sql # rust /target/ +**/*.profraw # vscode test/thunder-tests/thunderActivity.json \ No newline at end of file From 0c7f6d4310558592919c59edbc9731654ff3406e Mon Sep 17 00:00:00 2001 From: max funk Date: Sat, 28 Oct 2023 18:34:56 -0700 Subject: [PATCH 02/21] add code coverage container to rust rule workflow --- .github/workflows/dev-rule.yaml | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/.github/workflows/dev-rule.yaml b/.github/workflows/dev-rule.yaml index e317448b..3137fd53 100644 --- a/.github/workflows/dev-rule.yaml +++ b/.github/workflows/dev-rule.yaml @@ -12,6 +12,9 @@ jobs: test: name: rule runs-on: ubuntu-latest + container: + image: xd009642/tarpaulin:develop-nightly + options: --security-opt seccomp=unconfined env: AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} @@ -28,5 +31,5 @@ jobs: run: | cargo fmt -- --check cargo clippy -- -Dwarnings - - name: unit tests - run: cargo test \ No newline at end of file + - name: unit tests and coverage + run: cargo tarpaulin \ No newline at end of file From f9e7fb16f1dacfce6acf46cbaa2fb88d25a678b5 Mon Sep 17 00:00:00 2001 From: max funk Date: Sat, 28 Oct 2023 18:35:06 -0700 Subject: [PATCH 03/21] crates workflow --- .github/workflows/dev-crates.yaml | 36 +++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 .github/workflows/dev-crates.yaml diff --git a/.github/workflows/dev-crates.yaml b/.github/workflows/dev-crates.yaml new file mode 100644 index 00000000..2f78c82f --- /dev/null +++ b/.github/workflows/dev-crates.yaml @@ -0,0 +1,36 @@ +name: dev-crates + +on: + push: + paths: + - 'crates/**' + branches-ignore: + - 'master' + +jobs: + test: + name: crates + runs-on: ubuntu-latest + container: + image: xd009642/tarpaulin:develop-nightly + options: --security-opt seccomp=unconfined + env: + AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + AWS_DEFAULT_REGION: us-east-1 + CI: true + steps: + - uses: actions/checkout@v3 + - uses: dtolnay/rust-toolchain@master + with: + toolchain: stable + components: clippy, rustfmt + - uses: taiki-e/install-action@nextest + - name: crates/types lint + run: | + cargo fmt -- --check + cargo clippy -- -Dwarnings + working-directory: crates/types + - name: crates/types unit tests and coverage report + run: cargo tarpaulin + working-directory: crates/types \ No newline at end of file From 3e67d70f1a3d6ca294238b4c2a0e0fa44686c6e5 Mon Sep 17 00:00:00 2001 From: max funk Date: Sat, 28 Oct 2023 18:36:35 -0700 Subject: [PATCH 04/21] rule module FromIterator unit test coverage --- crates/types/src/rule.rs | 56 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/crates/types/src/rule.rs b/crates/types/src/rule.rs index 095374e4..da8effb4 100644 --- a/crates/types/src/rule.rs +++ b/crates/types/src/rule.rs @@ -382,4 +382,60 @@ mod tests { panic!("got {:#?}, want {:#?}", got, want) } } + + #[test] + fn rule_instances_implement_from_iterator() { + let want = RuleInstances(vec![RuleInstance { + id: Some(String::from("1")), + rule_type: String::from("transaction_item"), + rule_name: String::from("multiplyItemValue"), + rule_instance_name: String::from("NinePercentSalesTax"), + variable_values: vec![ + String::from("ANY"), + String::from("StateOfCalifornia"), + String::from("9% state sales tax"), + String::from("0.09"), + ], + account_role: AccountRole::Creditor, + item_id: None, + price: None, + quantity: None, + unit_of_measurement: None, + units_measured: None, + account_name: None, + first_name: None, + middle_name: None, + last_name: None, + country_name: None, + street_id: None, + street_name: None, + floor_number: None, + unit_id: None, + city_name: None, + county_name: None, + region_name: None, + state_name: Some(String::from("California")), + postal_code: None, + latlng: None, + email_address: None, + telephone_country_code: None, + telephone_area_code: None, + telephone_number: None, + occupation_id: None, + industry_id: None, + disabled_time: None, + removed_time: None, + created_at: Some(TZTime( + DateTime::parse_from_rfc3339("2023-02-28T04:21:08.363Z") + .unwrap() + .with_timezone(&Utc), + )), + }]); + // create iterator + let test_rule_instances = std::iter::repeat(want.0[0].clone()).take(1); + // test method + let got = RuleInstances::from_iter(test_rule_instances); + // assert + assert_eq!(got, want, "got {:?}, want {:?}", got, want) + } } From 2fc120856a69d6183e1bfd050568363c5b3fba7f Mon Sep 17 00:00:00 2001 From: max funk Date: Sat, 28 Oct 2023 22:09:46 -0700 Subject: [PATCH 05/21] exclude Default impls from coverage reporting --- crates/types/src/account.rs | 1 + crates/types/src/rule.rs | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/crates/types/src/account.rs b/crates/types/src/account.rs index 898e9d77..011d8a95 100644 --- a/crates/types/src/account.rs +++ b/crates/types/src/account.rs @@ -68,6 +68,7 @@ impl AccountProfiles { } impl Default for AccountProfiles { + #[cfg(not(tarpaulin_include))] fn default() -> Self { Self::new() } diff --git a/crates/types/src/rule.rs b/crates/types/src/rule.rs index da8effb4..bcb4184d 100644 --- a/crates/types/src/rule.rs +++ b/crates/types/src/rule.rs @@ -78,6 +78,7 @@ impl FromIterator for RuleInstances { } impl Default for RuleInstances { + #[cfg(not(tarpaulin_include))] fn default() -> Self { Self::new() } @@ -384,7 +385,7 @@ mod tests { } #[test] - fn rule_instances_implement_from_iterator() { + fn it_implements_from_iterator_on_rule_instances() { let want = RuleInstances(vec![RuleInstance { id: Some(String::from("1")), rule_type: String::from("transaction_item"), From ddea93c0203464943245b367a36c5c9ba6ca2379 Mon Sep 17 00:00:00 2001 From: max funk Date: Sat, 28 Oct 2023 22:48:20 -0700 Subject: [PATCH 06/21] add regex dev dep to rust rule service --- services/rule/Cargo.toml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/services/rule/Cargo.toml b/services/rule/Cargo.toml index bcaff14e..1f73eab2 100644 --- a/services/rule/Cargo.toml +++ b/services/rule/Cargo.toml @@ -17,3 +17,6 @@ sqls = { path = "../../crates/sqls" } pg = { path = "../../crates/pg" } chrono = "0.4.23" nanoid = "0.4.0" + +[dev-dependencies] +regex = "1.0.0" \ No newline at end of file From 918a43a1ac158d8ae586bd22915caf71bd8d8f7b Mon Sep 17 00:00:00 2001 From: max funk Date: Sat, 28 Oct 2023 22:48:32 -0700 Subject: [PATCH 07/21] cargo lockfile --- Cargo.lock | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/Cargo.lock b/Cargo.lock index c3f1148e..3bb3544a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -17,6 +17,15 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" +[[package]] +name = "aho-corasick" +version = "0.7.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc936419f96fa211c1b9166887b38e5e40b19958e5b895be7c1f93adec7071ac" +dependencies = [ + "memchr", +] + [[package]] name = "android_system_properties" version = "0.1.5" @@ -965,6 +974,8 @@ version = "1.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "48aaa5748ba571fb95cd2c85c09f629215d3a6ece942baa100950af03a34f733" dependencies = [ + "aho-corasick", + "memchr", "regex-syntax", ] @@ -991,6 +1002,7 @@ dependencies = [ "chrono", "nanoid", "pg", + "regex", "serde", "sqls", "tokio", From eacecaca9a4fd08d9c7de931b3cdd1915d6e6057 Mon Sep 17 00:00:00 2001 From: max funk Date: Sat, 28 Oct 2023 22:48:53 -0700 Subject: [PATCH 08/21] rust rule service utils module unit tests --- services/rule/src/rules/utils.rs | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/services/rule/src/rules/utils.rs b/services/rule/src/rules/utils.rs index d16a2f90..14ff75e2 100644 --- a/services/rule/src/rules/utils.rs +++ b/services/rule/src/rules/utils.rs @@ -11,3 +11,24 @@ pub fn number_to_fixed_string(num: T) -> String { pub fn create_rule_exec_id() -> String { nanoid!(RULE_EXEC_ID_SIZE) } + +#[cfg(test)] +mod tests { + use super::*; + use regex::Regex; + + #[test] + fn it_converts_number_to_fixed_string() { + let test_number = 8.0; + let got = number_to_fixed_string(test_number); + let want = String::from("8.000"); + assert_eq!(got, want, "got {}, want {}", got, want) + } + + #[test] + fn it_creates_rule_exec_id() { + let got = create_rule_exec_id(); + let want = Regex::new(r"[\w*-]{8}").unwrap().is_match(&got); + assert!(want) + } +} From 6e55fe0f7c6811cb49b20a34bc8099bfc11be751 Mon Sep 17 00:00:00 2001 From: max funk Date: Sat, 28 Oct 2023 22:50:14 -0700 Subject: [PATCH 09/21] rust approval module unit test --- crates/types/src/approval.rs | 40 ++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/crates/types/src/approval.rs b/crates/types/src/approval.rs index aea6e783..763ce4c8 100644 --- a/crates/types/src/approval.rs +++ b/crates/types/src/approval.rs @@ -37,6 +37,46 @@ impl Approvals { mod tests { use super::*; + #[test] + fn it_gets_approvals_per_role() { + let want = Approvals(vec![Approval { + id: None, + rule_instance_id: None, + transaction_id: None, + transaction_item_id: None, + account_name: String::from("GroceryCo"), + account_role: AccountRole::Creditor, + device_id: None, + device_latlng: None, + approval_time: None, + rejection_time: None, + expiration_time: None, + }]); + + let test_role = AccountRole::Creditor; + + let test_approvals = Approvals(vec![ + Approval { + id: None, + rule_instance_id: None, + transaction_id: None, + transaction_item_id: None, + account_name: String::from("JoeCarter"), + account_role: AccountRole::Debitor, + device_id: None, + device_latlng: None, + approval_time: None, + rejection_time: None, + expiration_time: None, + }, + want.0[0].clone(), + ]); + + let got = test_approvals.get_approvals_per_role(test_role); + + assert_eq!(got, want, "got {:?}, want {:?}", got, want) + } + #[test] fn it_deserializes_an_approval() { let got: Approval = serde_json::from_str( From 65afb6c2e0c6af0bebbed0c1fadc820d77866e28 Mon Sep 17 00:00:00 2001 From: max funk Date: Sun, 29 Oct 2023 14:41:34 -0700 Subject: [PATCH 10/21] remove cloud creds --- .github/workflows/dev-crates.yaml | 3 --- .github/workflows/dev-rule.yaml | 3 --- 2 files changed, 6 deletions(-) diff --git a/.github/workflows/dev-crates.yaml b/.github/workflows/dev-crates.yaml index 2f78c82f..b09c793f 100644 --- a/.github/workflows/dev-crates.yaml +++ b/.github/workflows/dev-crates.yaml @@ -15,9 +15,6 @@ jobs: image: xd009642/tarpaulin:develop-nightly options: --security-opt seccomp=unconfined env: - AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} - AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} - AWS_DEFAULT_REGION: us-east-1 CI: true steps: - uses: actions/checkout@v3 diff --git a/.github/workflows/dev-rule.yaml b/.github/workflows/dev-rule.yaml index 3137fd53..11d3699a 100644 --- a/.github/workflows/dev-rule.yaml +++ b/.github/workflows/dev-rule.yaml @@ -16,9 +16,6 @@ jobs: image: xd009642/tarpaulin:develop-nightly options: --security-opt seccomp=unconfined env: - AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} - AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} - AWS_DEFAULT_REGION: us-east-1 CI: true steps: - uses: actions/checkout@v3 From c02bdf7fabc890de28b60a3403c2d837533ecfb5 Mon Sep 17 00:00:00 2001 From: max funk Date: Sun, 29 Oct 2023 14:42:21 -0700 Subject: [PATCH 11/21] install rust unit test coverage tool in devcontainers --- .devcontainer/Dockerfile | 5 +++++ docker/.gitpod.Dockerfile | 1 + 2 files changed, 6 insertions(+) diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index 21c43a43..f95e1970 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -3,6 +3,7 @@ FROM mcr.microsoft.com/devcontainers/universal:2 ARG TF_VERSION=1.2.7 ARG MIGRATE_VERSION=4.15.2 ARG WATCH_VERSION=8.4.0 +ARG TARPAULIN_VERSION=0.27.1 RUN go install github.com/99designs/gqlgen@latest && \ go install github.com/golang/mock/mockgen@latest && \ @@ -20,6 +21,10 @@ RUN go install github.com/99designs/gqlgen@latest && \ tar -xf cargo-watch-v${WATCH_VERSION}-x86_64-unknown-linux-gnu.tar.xz && \ sudo mv cargo-watch-v${WATCH_VERSION}-x86_64-unknown-linux-gnu/cargo-watch /usr/local/bin && \ rm -rf cargo-watch-v${WATCH_VERSION}-x86_64-unknown-linux-gnu* && \ + wget https://github.com/xd009642/tarpaulin/releases/download/${TARPAULIN_VERSION}/cargo-tarpaulin-x86_64-unknown-linux-gnu.tar.gz && \ + tar -xf cargo-tarpaulin-x86_64-unknown-linux-gnu.tar.gz && \ + sudo mv cargo-tarpaulin /usr/local/bin && \ + rm -rf cargo-tarpaulin* && \ npm install -g eslint && \ sudo sh -c 'echo "deb http://apt.postgresql.org/pub/repos/apt $(lsb_release -cs)-pgdg main" > /etc/apt/sources.list.d/pgdg.list' && \ wget --quiet -O - https://www.postgresql.org/media/keys/ACCC4CF8.asc | sudo apt-key add - && \ diff --git a/docker/.gitpod.Dockerfile b/docker/.gitpod.Dockerfile index 49690373..202524c7 100644 --- a/docker/.gitpod.Dockerfile +++ b/docker/.gitpod.Dockerfile @@ -16,6 +16,7 @@ RUN go install github.com/99designs/gqlgen@latest && \ sudo mv terraform /usr/local/bin && \ rm terraform_${TF_VERSION}_linux_amd64.zip && \ cargo install cross --git https://github.com/cross-rs/cross && \ + cargo install cargo-tarpaulin && \ brew install libpq && \ brew link --force libpq && \ brew install golang-migrate && \ From fdc830657ef0a378f00c57e51b85ba44051e13ac Mon Sep 17 00:00:00 2001 From: max funk Date: Sun, 29 Oct 2023 14:42:40 -0700 Subject: [PATCH 12/21] add rust coverage tool in project deps --- project.yaml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/project.yaml b/project.yaml index 4aaf553c..4f53d65c 100644 --- a/project.yaml +++ b/project.yaml @@ -838,4 +838,8 @@ params: [] - name: cargo-watch os: osx: - install: 'cargo install cargo-watch' \ No newline at end of file + install: 'cargo install cargo-watch' + - name: tarpaulin + os: + osx: + install: 'cargo install tarpaulin' \ No newline at end of file From e73e70fe1724221bf7aaffcd416c9d3ca0e5dc75 Mon Sep 17 00:00:00 2001 From: max funk Date: Sun, 29 Oct 2023 14:43:23 -0700 Subject: [PATCH 13/21] rust account module unit test coverage --- crates/types/src/account.rs | 197 ++++++++++++++++++++++++++++++++++++ 1 file changed, 197 insertions(+) diff --git a/crates/types/src/account.rs b/crates/types/src/account.rs index 011d8a95..cf604f05 100644 --- a/crates/types/src/account.rs +++ b/crates/types/src/account.rs @@ -88,6 +88,203 @@ impl FromIterator for AccountProfiles { mod tests { use super::*; + #[test] + fn it_matches_profile_by_account() { + let test_acct = String::from("GroceryStore"); + + let want = AccountProfile { + id: Some(String::from("7")), + account_name: test_acct.clone(), + description: Some(String::from("Sells groceries")), + first_name: Some(String::from("Grocery")), + middle_name: None, + last_name: Some(String::from("Store")), + country_name: String::from("United States of America"), + street_number: Some(String::from("8701")), + street_name: Some(String::from("Lincoln Blvd")), + floor_number: None, + unit_number: None, + city_name: String::from("Los Angeles"), + county_name: Some(String::from("Los Angeles County")), + region_name: None, + state_name: String::from("California"), + postal_code: String::from("90045"), + latlng: Some(String::from("(33.958050,-118.418388)")), + email_address: String::from("grocerystore@address.xz"), + telephone_country_code: Some(String::from("1")), + telephone_area_code: Some(String::from("310")), + telephone_number: Some(String::from("5555555")), + occupation_id: Some(String::from("11")), + industry_id: Some(String::from("11")), + }; + + let test_acct_profiles = AccountProfiles(vec![ + AccountProfile { + id: Some(String::from("11")), + account_name: String::from("JacobWebb"), + description: Some(String::from("Soccer coach")), + first_name: Some(String::from("Jacob")), + middle_name: Some(String::from("Curtis")), + last_name: Some(String::from("Webb")), + country_name: String::from("United States of America"), + street_number: Some(String::from("205")), + street_name: Some(String::from("N Mccarran Blvd")), + floor_number: None, + unit_number: None, + city_name: String::from("Sparks"), + county_name: Some(String::from("Washoe County")), + region_name: None, + state_name: String::from("Nevada"), + postal_code: String::from("89431"), + latlng: Some(String::from("(39.534552,-119.737825)")), + email_address: String::from("jacob@address.xz"), + telephone_country_code: Some(String::from("1")), + telephone_area_code: Some(String::from("775")), + telephone_number: Some(String::from("5555555")), + occupation_id: Some(String::from("7")), + industry_id: Some(String::from("7")), + }, + want.clone(), + ]); + + let got = test_acct_profiles + .match_profile_by_account(test_acct) + .unwrap(); + assert_eq!(got, want, "got {:?}, want {:?}", got, want) + } + + #[test] + fn it_matches_profile_by_account_with_none() { + let test_acct_profiles = AccountProfiles(vec![ + AccountProfile { + id: Some(String::from("11")), + account_name: String::from("JacobWebb"), + description: Some(String::from("Soccer coach")), + first_name: Some(String::from("Jacob")), + middle_name: Some(String::from("Curtis")), + last_name: Some(String::from("Webb")), + country_name: String::from("United States of America"), + street_number: Some(String::from("205")), + street_name: Some(String::from("N Mccarran Blvd")), + floor_number: None, + unit_number: None, + city_name: String::from("Sparks"), + county_name: Some(String::from("Washoe County")), + region_name: None, + state_name: String::from("Nevada"), + postal_code: String::from("89431"), + latlng: Some(String::from("(39.534552,-119.737825)")), + email_address: String::from("jacob@address.xz"), + telephone_country_code: Some(String::from("1")), + telephone_area_code: Some(String::from("775")), + telephone_number: Some(String::from("5555555")), + occupation_id: Some(String::from("7")), + industry_id: Some(String::from("7")), + }, + AccountProfile { + id: Some(String::from("7")), + account_name: String::from("GroceryStore"), + description: Some(String::from("Sells groceries")), + first_name: Some(String::from("Grocery")), + middle_name: None, + last_name: Some(String::from("Store")), + country_name: String::from("United States of America"), + street_number: Some(String::from("8701")), + street_name: Some(String::from("Lincoln Blvd")), + floor_number: None, + unit_number: None, + city_name: String::from("Los Angeles"), + county_name: Some(String::from("Los Angeles County")), + region_name: None, + state_name: String::from("California"), + postal_code: String::from("90045"), + latlng: Some(String::from("(33.958050,-118.418388)")), + email_address: String::from("grocerystore@address.xz"), + telephone_country_code: Some(String::from("1")), + telephone_area_code: Some(String::from("310")), + telephone_number: Some(String::from("5555555")), + occupation_id: Some(String::from("11")), + industry_id: Some(String::from("11")), + }, + ]); + + let got = test_acct_profiles.match_profile_by_account(String::from("DoesntExist")); + let want = None; + assert_eq!(got, want, "got {:?}, want {:?}", got, want) + } + + #[test] + fn it_creates_new_account_profiles() { + assert_eq!(AccountProfiles::new(), AccountProfiles(vec![])) + } + + #[test] + fn it_adds_account_profile() { + let mut got = AccountProfiles::new(); + let want = AccountProfiles(vec![AccountProfile { + id: Some(String::from("7")), + account_name: String::from("GroceryStore"), + description: Some(String::from("Sells groceries")), + first_name: Some(String::from("Grocery")), + middle_name: None, + last_name: Some(String::from("Store")), + country_name: String::from("United States of America"), + street_number: Some(String::from("8701")), + street_name: Some(String::from("Lincoln Blvd")), + floor_number: None, + unit_number: None, + city_name: String::from("Los Angeles"), + county_name: Some(String::from("Los Angeles County")), + region_name: None, + state_name: String::from("California"), + postal_code: String::from("90045"), + latlng: Some(String::from("(33.958050,-118.418388)")), + email_address: String::from("grocerystore@address.xz"), + telephone_country_code: Some(String::from("1")), + telephone_area_code: Some(String::from("310")), + telephone_number: Some(String::from("5555555")), + occupation_id: Some(String::from("11")), + industry_id: Some(String::from("11")), + }]); + got.add(want.0[0].clone()); + assert_eq!(got, want, "got {:?}, want {:?}", got, want) + } + + #[test] + fn it_implements_from_iterator_on_account_profiles() { + let want = AccountProfiles(vec![AccountProfile { + id: Some(String::from("7")), + account_name: String::from("GroceryStore"), + description: Some(String::from("Sells groceries")), + first_name: Some(String::from("Grocery")), + middle_name: None, + last_name: Some(String::from("Store")), + country_name: String::from("United States of America"), + street_number: Some(String::from("8701")), + street_name: Some(String::from("Lincoln Blvd")), + floor_number: None, + unit_number: None, + city_name: String::from("Los Angeles"), + county_name: Some(String::from("Los Angeles County")), + region_name: None, + state_name: String::from("California"), + postal_code: String::from("90045"), + latlng: Some(String::from("(33.958050,-118.418388)")), + email_address: String::from("grocerystore@address.xz"), + telephone_country_code: Some(String::from("1")), + telephone_area_code: Some(String::from("310")), + telephone_number: Some(String::from("5555555")), + occupation_id: Some(String::from("11")), + industry_id: Some(String::from("11")), + }]); + // create iterator + let test_account_profiles = std::iter::repeat(want.0[0].clone()).take(1); + // test method + let got = AccountProfiles::from_iter(test_account_profiles); + // assert + assert_eq!(got, want, "got {:?}, want {:?}", got, want) + } + #[test] fn it_deserializes_an_account_profile() { let got: AccountProfile = serde_json::from_str( From c90f0e999808635307a9b6189436c2e02ada373d Mon Sep 17 00:00:00 2001 From: max funk Date: Sun, 29 Oct 2023 16:38:16 -0700 Subject: [PATCH 14/21] add rust dev dep to types crate --- crates/types/Cargo.toml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/crates/types/Cargo.toml b/crates/types/Cargo.toml index a16efe24..2865560b 100644 --- a/crates/types/Cargo.toml +++ b/crates/types/Cargo.toml @@ -13,4 +13,7 @@ serde_with = {version = "2.2.0", features = ["chrono_0_4"]} strum = {version = "0.24.1", features = ["derive"]} strum_macros = "0.24.3" tokio-postgres = {version = "0.7.7", features = ["with-chrono-0_4"]} -async-trait = "0.1.73" \ No newline at end of file +async-trait = "0.1.73" + +[dev-dependencies] +bytes = "1.0" \ No newline at end of file From 92593c8be4bb65ee4e664ac6473bc22a9f6b8e0c Mon Sep 17 00:00:00 2001 From: max funk Date: Sun, 29 Oct 2023 16:38:25 -0700 Subject: [PATCH 15/21] cargo lockfile --- Cargo.lock | 1 + 1 file changed, 1 insertion(+) diff --git a/Cargo.lock b/Cargo.lock index 3bb3544a..aa1edde2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1536,6 +1536,7 @@ name = "types" version = "0.1.0" dependencies = [ "async-trait", + "bytes", "chrono", "postgres-protocol", "postgres-types", From 051a01ae63f1ba40cbdf93b869d68e387e94618f Mon Sep 17 00:00:00 2001 From: max funk Date: Sun, 29 Oct 2023 16:38:51 -0700 Subject: [PATCH 16/21] rust account_role module unit test coverage --- crates/types/src/account_role.rs | 55 ++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/crates/types/src/account_role.rs b/crates/types/src/account_role.rs index 9096823d..10053824 100644 --- a/crates/types/src/account_role.rs +++ b/crates/types/src/account_role.rs @@ -22,6 +22,7 @@ impl<'a> FromSql<'a> for AccountRole { } } + #[cfg(not(tarpaulin_include))] fn accepts(ty: &Type) -> bool { *ty == Type::TEXT } @@ -43,6 +44,7 @@ impl ToSql for AccountRole { } #[allow(unused_variables)] + #[cfg(not(tarpaulin_include))] fn accepts(ty: &Type) -> bool { true } @@ -59,6 +61,59 @@ pub const CREDITOR_FIRST: RoleSequence = [AccountRole::Creditor, AccountRole::De #[cfg(test)] mod tests { use super::*; + use bytes::BytesMut; + + #[test] + fn it_converts_to_debitor_account_role_from_sql() { + let test_pg_type = Type::TEXT; + let test_debitor = "debitor".as_bytes(); + let got = AccountRole::from_sql(&test_pg_type, test_debitor).unwrap(); + let want = AccountRole::Debitor; + assert_eq!(got, want, "got {}, want {}", got, want) + } + + #[test] + fn it_converts_to_creditor_account_role_from_sql() { + let test_pg_type = Type::TEXT; + let test_creditor = "creditor".as_bytes(); + let got = AccountRole::from_sql(&test_pg_type, test_creditor).unwrap(); + let want = AccountRole::Creditor; + assert_eq!(got, want, "got {}, want {}", got, want) + } + + #[test] + fn it_errs_on_not_debitor_or_creditor_from_sql() { + let test_pg_type = Type::TEXT; + let test_creditor = "doesntexist".as_bytes(); + let got = AccountRole::from_sql(&test_pg_type, test_creditor); + assert!(got.is_err()) + } + + #[test] + fn it_converts_from_creditor_account_role_to_sql() { + let test_account_creditor_role = AccountRole::Creditor; + let test_pg_type = Type::TEXT; + let mut test_buf = BytesMut::new(); + test_account_creditor_role + .to_sql(&test_pg_type, &mut test_buf) + .unwrap(); + let got = &test_buf[..]; + let want = b"creditor"; + assert_eq!(got, want, "got {:?}, want {:?}", got, want) + } + + #[test] + fn it_converts_from_debitor_account_role_to_sql() { + let test_account_debitor_role = AccountRole::Debitor; + let test_pg_type = Type::TEXT; + let mut test_buf = BytesMut::new(); + test_account_debitor_role + .to_sql(&test_pg_type, &mut test_buf) + .unwrap(); + let got = &test_buf[..]; + let want = b"debitor"; + assert_eq!(got, want, "got {:?}, want {:?}", got, want) + } #[test] fn it_deserializes_debitor() { From 79e4f449c1fe3dbd69113a352b9101c30256e193 Mon Sep 17 00:00:00 2001 From: max funk Date: Thu, 2 Nov 2023 14:35:50 -0700 Subject: [PATCH 17/21] cargo workspace resolver version 2 --- Cargo.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/Cargo.toml b/Cargo.toml index febdb22d..63e30498 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,4 +1,5 @@ [workspace] +resolver = "2" members = [ "services/rule", From 7276546d01f897ebf5ce1d8f2fa1c0d184078229 Mon Sep 17 00:00:00 2001 From: max funk Date: Thu, 2 Nov 2023 14:36:19 -0700 Subject: [PATCH 18/21] rust types crate dev deps --- crates/types/Cargo.toml | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/crates/types/Cargo.toml b/crates/types/Cargo.toml index 2865560b..19c3304c 100644 --- a/crates/types/Cargo.toml +++ b/crates/types/Cargo.toml @@ -6,14 +6,20 @@ edition = "2021" [dependencies] chrono = "0.4.23" postgres-protocol = "0.6.4" -postgres-types = {version = "0.2.4", features = ["derive", "array-impls", "with-chrono-0_4"]} -serde = {version = "1.0.152", features = [ "derive" ]} +postgres-types = { version = "0.2.4", features = [ + "derive", + "array-impls", + "with-chrono-0_4", +] } +serde = { version = "1.0.152", features = ["derive"] } serde_json = "1.0.93" -serde_with = {version = "2.2.0", features = ["chrono_0_4"]} -strum = {version = "0.24.1", features = ["derive"]} +serde_with = { version = "2.2.0", features = ["chrono_0_4"] } +strum = { version = "0.24.1", features = ["derive"] } strum_macros = "0.24.3" -tokio-postgres = {version = "0.7.7", features = ["with-chrono-0_4"]} +tokio-postgres = { version = "0.7.7", features = ["with-chrono-0_4"] } async-trait = "0.1.73" [dev-dependencies] -bytes = "1.0" \ No newline at end of file +bytes = "1.0" +time = { version = "0.3.30", features = ["formatting", "parsing"]} +serde_assert = "0.5.0" \ No newline at end of file From 0e7859928860578386fd6d2cb825c9e0ffb91f29 Mon Sep 17 00:00:00 2001 From: max funk Date: Thu, 2 Nov 2023 14:36:30 -0700 Subject: [PATCH 19/21] cargo lockfile --- Cargo.lock | 95 +++++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 83 insertions(+), 12 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index aa1edde2..20d2b006 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -17,6 +17,18 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" +[[package]] +name = "ahash" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91429305e9f0a25f6205c5b8e0d2db09e0708a7a6df0f42212bb56c32c8ac97a" +dependencies = [ + "cfg-if", + "once_cell", + "version_check", + "zerocopy", +] + [[package]] name = "aho-corasick" version = "0.7.20" @@ -335,6 +347,16 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "deranged" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f32d04922c60427da6f9fef14d042d9edddef64cb9d4ce0d64d0685fbeb1fd3" +dependencies = [ + "powerfmt", + "serde", +] + [[package]] name = "digest" version = "0.10.6" @@ -455,6 +477,15 @@ version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" +[[package]] +name = "hashbrown" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e" +dependencies = [ + "ahash", +] + [[package]] name = "heck" version = "0.4.1" @@ -585,7 +616,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1885e79c1fc4b10f0e172c475f458b7f7b93061064d98c3293e98c5ba0c8b399" dependencies = [ "autocfg", - "hashbrown", + "hashbrown 0.12.3", "serde", ] @@ -905,6 +936,12 @@ dependencies = [ "postgres-protocol", ] +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + [[package]] name = "ppv-lite86" version = "0.2.17" @@ -1045,18 +1082,28 @@ checksum = "ddccb15bcce173023b3fedd9436f882a0739b8dfb45e4f6b6002bee5929f61b2" [[package]] name = "serde" -version = "1.0.189" +version = "1.0.190" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e422a44e74ad4001bdc8eede9a4570ab52f71190e9c076d14369f38b9200537" +checksum = "91d3c334ca1ee894a2c6f6ad698fe8c435b76d504b13d436f0685d648d6d96f7" dependencies = [ "serde_derive", ] +[[package]] +name = "serde_assert" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eda563240c1288b044209be1f0d38bb4d15044fb3e00dc354fbc922ab4733e80" +dependencies = [ + "hashbrown 0.13.2", + "serde", +] + [[package]] name = "serde_derive" -version = "1.0.189" +version = "1.0.190" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e48d1f918009ce3145511378cf68d613e3b3d9137d67272562080d68a2b32d5" +checksum = "67c5609f394e5c2bd7fc51efda478004ea80ef42fee983d5c67a65e34f32c0e3" dependencies = [ "proc-macro2", "quote", @@ -1108,7 +1155,7 @@ dependencies = [ "serde", "serde_json", "serde_with_macros", - "time 0.3.20", + "time 0.3.30", ] [[package]] @@ -1301,11 +1348,13 @@ dependencies = [ [[package]] name = "time" -version = "0.3.20" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd0cbfecb4d19b5ea75bb31ad904eb5b9fa13f21079c3b92017ebdf4999a5890" +checksum = "c4a34ab300f2dee6e562c10a046fc05e358b29f9bf92277f30c3c8d82275f6f5" dependencies = [ + "deranged", "itoa", + "powerfmt", "serde", "time-core", "time-macros", @@ -1313,15 +1362,15 @@ dependencies = [ [[package]] name = "time-core" -version = "0.1.0" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e153e1f1acaef8acc537e68b44906d2db6436e2b35ac2c6b42640fff91f00fd" +checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" [[package]] name = "time-macros" -version = "0.2.8" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd80a657e71da814b8e5d60d3374fc6d35045062245d80224748ae522dd76f36" +checksum = "4ad70d68dba9e1f8aceda7aa6711965dfec1cac869f311a51bd08b3a2ccbce20" dependencies = [ "time-core", ] @@ -1541,10 +1590,12 @@ dependencies = [ "postgres-protocol", "postgres-types", "serde", + "serde_assert", "serde_json", "serde_with", "strum", "strum_macros", + "time 0.3.30", "tokio-postgres", ] @@ -1825,3 +1876,23 @@ name = "windows_x86_64_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + +[[package]] +name = "zerocopy" +version = "0.7.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e50cbb27c30666a6108abd6bc7577556265b44f243e2be89a8bc4e07a528c107" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a25f293fe55f0a48e7010d65552bb63704f6ceb55a1a385da10d41d8f78e4a3d" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.36", +] From e0ad72a2a059713f44c07dbe8a23f9f724a32ce8 Mon Sep 17 00:00:00 2001 From: max funk Date: Thu, 2 Nov 2023 14:37:21 -0700 Subject: [PATCH 20/21] increase rust time module unit test coverage --- crates/types/src/time.rs | 95 +++++++++++++++++++++++++++++++++++----- 1 file changed, 84 insertions(+), 11 deletions(-) diff --git a/crates/types/src/time.rs b/crates/types/src/time.rs index 75277005..7ac69938 100644 --- a/crates/types/src/time.rs +++ b/crates/types/src/time.rs @@ -1,17 +1,21 @@ -use chrono::{DateTime, Duration, NaiveDate, NaiveDateTime, Utc}; +use chrono::{DateTime, Duration, NaiveDate, NaiveDateTime, SecondsFormat, Utc}; use postgres_protocol::types; use postgres_types::{FromSql, ToSql, Type}; -use serde::{Deserialize, Serialize}; +use serde::{Deserialize, Serialize, Serializer}; use std::error::Error; -#[derive(Eq, PartialEq, Debug, Deserialize, Serialize, ToSql, Clone)] +#[derive(Eq, PartialEq, Debug, Deserialize, ToSql, Clone)] -pub struct TZTime(#[serde(serialize_with = "milli_tz_format::serialize")] pub DateTime); +pub struct TZTime(pub DateTime); impl TZTime { pub fn now() -> Self { Self(chrono::offset::Utc::now()) } + + pub fn to_milli_tz(&self) -> String { + self.0.to_rfc3339_opts(SecondsFormat::Millis, true) + } } // https://github.com/sfackler/rust-postgres/blob/7cd7b187a5cb990ceb0ea9531cd3345b1e2799c3/postgres-types/src/chrono_04.rs @@ -37,22 +41,91 @@ impl<'a> FromSql<'a> for TZTime { } } -mod milli_tz_format { - use chrono::{DateTime, SecondsFormat, Utc}; - use serde::{self, Serializer}; - - pub fn serialize(date: &DateTime, serializer: S) -> Result +// todo: test timestamp created by rule service == timestamp value stored in db by request-create in +// https://github.com/systemaccounting/mxfactorial/blob/fc7a27765bed840dce0876ce2fe75d8df6bc2dcf/services/request-create/cmd/main.go#L330-L336 +impl Serialize for TZTime { + fn serialize(&self, serializer: S) -> Result where S: Serializer, { - let s = date.to_rfc3339_opts(SecondsFormat::Millis, true); - serializer.serialize_str(&s) + serializer.serialize_str(&self.to_milli_tz()) } } #[cfg(test)] mod tests { use super::*; + use bytes::{BufMut, BytesMut}; + use serde_assert; + use std::time::SystemTime; + use time::{format_description, OffsetDateTime}; + + #[test] + fn it_serializes() { + let test_tz_time = TZTime( + DateTime::parse_from_rfc3339("2023-10-30T04:56:56Z") + .unwrap() + .with_timezone(&Utc), + ); + let test_serializer = serde_assert::Serializer::builder().build(); + + let got = test_tz_time.serialize(&test_serializer).unwrap(); + let want = serde_assert::Tokens(vec![serde_assert::Token::Str(String::from( + "2023-10-30T04:56:56.000Z", + ))]); + + assert_eq!(got, want, "got {:?}, want {:?}", got, want) + } + + #[test] + fn it_returns_now() { + let test_chrono_now = TZTime::now().0; + let test_sys_time_now: OffsetDateTime = SystemTime::now().into(); + + let chrono_time_format = "%FT%H:%M"; + let sys_time_format = + format_description::parse("[year]-[month]-[day]T[hour]:[minute]").unwrap(); + + let got = format!("{}", test_chrono_now.format(chrono_time_format)); + let want = test_sys_time_now.format(&sys_time_format).unwrap(); + + assert_eq!(got, want, "got {}, want {}", got, want) + } + + #[test] + fn it_returns_naive_date_time() { + let got = base(); + let want = NaiveDate::from_ymd_opt(2000, 1, 1) + .unwrap() + .and_hms_opt(0, 0, 0) + .unwrap(); + + assert_eq!(got, want, "got {}, want {}", got, want) + } + + #[test] + fn it_converts_to_tztime_from_sql() { + let test_pg_type = Type::TIMESTAMPTZ; + let mut test_buf = BytesMut::new(); + // https://github.com/sfackler/rust-postgres/blob/c5ff8cfd86e897b7c197f52684a37a4f17cecb75/postgres-types/src/lib.rs#L207 + const TIME_SEC_CONVERSION: i64 = 946_684_800; + const USEC_PER_SEC: i64 = 1_000_000; + test_buf.put_i64((1_698_641_816 - TIME_SEC_CONVERSION) * USEC_PER_SEC); + let got = TZTime::from_sql(&test_pg_type, &mut test_buf).unwrap(); + let want_date = "2023-10-30T04:56:56Z"; + let want = TZTime( + DateTime::parse_from_rfc3339(want_date) + .unwrap() + .with_timezone(&Utc), + ); + assert_eq!(got, want, "got {:?}, want {:?}", got, want) + } + + #[test] + fn it_accepts_timestamptz_type_from_sql() { + let test_pg_type = Type::TIMESTAMPTZ; + assert!(::accepts(&test_pg_type)) + } #[test] fn it_deserializes_time() { From 53c27becf823231d4a7ea3bf8a3c68c51443fa09 Mon Sep 17 00:00:00 2001 From: max funk Date: Thu, 2 Nov 2023 14:40:50 -0700 Subject: [PATCH 21/21] rust types crate minimum unit test coverage --- .github/workflows/dev-crates.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/dev-crates.yaml b/.github/workflows/dev-crates.yaml index b09c793f..4409fffe 100644 --- a/.github/workflows/dev-crates.yaml +++ b/.github/workflows/dev-crates.yaml @@ -29,5 +29,5 @@ jobs: cargo clippy -- -Dwarnings working-directory: crates/types - name: crates/types unit tests and coverage report - run: cargo tarpaulin + run: cargo tarpaulin --fail-under 95 working-directory: crates/types \ No newline at end of file