From f8dc10796317d3ec42b2da95771ad05595034636 Mon Sep 17 00:00:00 2001 From: mat-fs Date: Tue, 13 Sep 2022 05:45:39 +0000 Subject: [PATCH 01/61] compliance/sancation_list_update_centralization From 1d178ecc056ee84cd33b874ab0141b70e02c3853 Mon Sep 17 00:00:00 2001 From: mat-fs Date: Tue, 13 Sep 2022 05:57:23 +0000 Subject: [PATCH 02/61] [ci] redis functionality added --- lib/Data/Validate/Sanctions.pm | 8 +- lib/Data/Validate/Sanctions/Fetcher.pm | 2 +- lib/Data/Validate/Sanctions/Redis.pm | 226 +++++++++++++++++++++++++ 3 files changed, 233 insertions(+), 3 deletions(-) create mode 100644 lib/Data/Validate/Sanctions/Redis.pm diff --git a/lib/Data/Validate/Sanctions.pm b/lib/Data/Validate/Sanctions.pm index 1bed060e..474b78ac 100644 --- a/lib/Data/Validate/Sanctions.pm +++ b/lib/Data/Validate/Sanctions.pm @@ -49,7 +49,11 @@ sub update_data { $self->{_data}->{$k} //= {}; $self->{_data}->{$k}->{updated} //= 0; $self->{_data}->{$k}->{content} //= []; - if ($self->{_data}{$k}->{updated} != $new_data->{$k}->{updated} + + if ($new_data->{$k}->{error}) { + warn "$ik list update failed because: $new_data->{$k}->{error}"; + } + elsif ($self->{_data}{$k}->{updated} != $new_data->{$k}->{updated} || scalar $self->{_data}{$k}->{content}->@* != scalar $new_data->{$k}->{content}->@*) { $self->{_data}->{$k} = $new_data->{$k}; @@ -323,7 +327,7 @@ sub _index_data { $self->{_index} = {}; for my $source (keys $self->{_data}->%*) { my @content = ($self->{_data}->{$source}->{content} // [])->@*; - warn "Content is empty for the sanction source $source. The sanctions file should be updated." unless @content; + for my $entry (@content) { $entry->{source} = $source; for my $name ($entry->{names}->@*) { diff --git a/lib/Data/Validate/Sanctions/Fetcher.pm b/lib/Data/Validate/Sanctions/Fetcher.pm index d83d8c8d..2032d05a 100644 --- a/lib/Data/Validate/Sanctions/Fetcher.pm +++ b/lib/Data/Validate/Sanctions/Fetcher.pm @@ -462,7 +462,7 @@ sub run { print "Source $id: $count entries fetched \n" if $args{verbose}; } } catch { - warn "$id list update failed because: $@"; + $result->{id}->{error} = $@; } } diff --git a/lib/Data/Validate/Sanctions/Redis.pm b/lib/Data/Validate/Sanctions/Redis.pm new file mode 100644 index 00000000..40460f45 --- /dev/null +++ b/lib/Data/Validate/Sanctions/Redis.pm @@ -0,0 +1,226 @@ +package Data::Validate::Sanctions::Redis; + +use strict; +use warnings; + +use parent 'Data::Validate::Sanctions'; + +use Carp; +use Data::Validate::Sanctions::Fetcher; +use Scalar::Util qw(blessed); +use Date::Utility; +use Data::Compare; +use List::Util qw(any uniq max min); +use Locale::Country; +use Text::Trim qw(trim); + +our $VERSION = '0.1'; + +# for OO +sub new { ## no critic (RequireArgUnpacking) + my ($class, %args) = @_; + + my $self = {}; + $self->{redis_read} = $args{redis_read} or 'Redis read connection is missing'; + $self->{redis_write} = $args{redis_write} or 'Redis write connection is missing'; + + $self->{sources} = [keys Data::Validate::Sanctions::Fetcher::config(eu_token => 'dummy')->%*]; + + $self->{args} = {%args}; + + $self->{last_time} = 0; + return bless $self, ref($class) || $class; +} + +sub last_updated { + my $self = shift; + my $list = shift; + + if ($list) { + return $self->{_data}->{$list}->{updated}; + } else { + $self->_load_data(); + return max(map { $_->{updated} } values %{$self->{_data}}); + } +} + +sub set_sanction_file { + die 'Not applicable'; +} + +sub get_sanction_file { + die 'Not applicable'; +} + +sub get_sanctioned_info { + my $self = shift; + unless ($self) { + die 'This method should be called on an object'; + } + + return Data::Validate::Sanctions::get_sanctioned_info($self, @_); +} + +sub _load_data { + my $self = shift; + + $self->{last_time} //= 0; + $self->{_data} //= {}; + $self->{_sanctioned_name_tokens} //= {}; + $self->{_token_sanctioned_names} //= {}; + + my $last_time; + for my $source ($self->{sources}->@*) { + my $updated = $redis_read->hget("SANCTIONS::$source", 'updated') // 0; + next if $updated <= $self->{last_time}; + + $self->{_data}->{$source}->{content} = decode_json_utf8($self->{redis_read}->hget("SANCTIONS::$source", 'content')); + $self->{_data}->{$source}->{updated} = $updated; + $last_time = $updated if $updated > $last_time; + } + $self->{_last_time} = $last_time; + + $self->_index_data(); + + foreach my $sanctioned_name (keys $self->{_index}->%*) { + my @tokens = _clean_names($sanctioned_name); + $self->{_sanctioned_name_tokens}->{$sanctioned_name} = \@tokens; + foreach my $token (@tokens){ + $self->{_token_sanctioned_names}->{$token}->{$sanctioned_name}=1; + } + } + + return $self->{_data}; +} + +sub _save_data { + my $self = shift; + + for my $source ($self->{sources}->@*) { + $self->{redis_write}->hmset( + "SANCTIONS::$source", + 'updated', $self->{_data}->{$source}->{updated}, + 'content', encode_json_utf8($self->{_data}->{$source}->{content}), + ); + } + + return; +} + +sub _default_sanction_file { + die 'Not applicable'; +} + +1; +__END__ + +=encoding utf-8 + +=head1 NAME + +Data::Validate::Sanctions - Validate a name against sanctions lists + +=head1 SYNOPSIS + + # as exported function + use Data::Validate::Sanctions qw/is_sanctioned get_sanction_file set_sanction_file/; + set_sanction_file('/var/storage/sanction.csv'); + + my ($first_name, $last_name) = ("First", "Last Name"); + print 'BAD' if is_sanctioned($first_name, $last_name); + + # as OO + use Data::Validate::Sanctions; + + #You can also set sanction_file in the new method. + my $validator = Data::Validate::Sanctions->new(sanction_file => '/var/storage/sanction.csv'); + print 'BAD' if $validator->is_sanctioned("$last_name $first_name"); + +=head1 DESCRIPTION + +Data::Validate::Sanctions is a simple validitor to validate a name against sanctions lists. + +The list is from: +- L, +- L +- L +- L + +run F to update the bundled csv. + +The path of list can be set by function L or by method L. If not set, then environment variable $ENV{SANCTION_FILE} will be checked, at last +the default file in this package will be used. + +=head1 METHODS + +=head2 is_sanctioned + + is_sanctioned($last_name, $first_name); + is_sanctioned($first_name, $last_name); + is_sanctioned("$last_name $first_name"); + +when one string is passed, please be sure last_name is before first_name. + +or you can pass first_name, last_name (last_name, first_name), we'll check both "$last_name $first_name" and "$first_name $last_name". + +retrun 1 if match is found and 0 if match is not found. + +It will remove all non-alpha chars and compare with the list we have. + +=head2 get_sanctioned_info + + my $result =get_sanctioned_info($last_name, $first_name, $date_of_birth); + print 'match: ', $result->{matched_args}->{name}, ' on list ', $result->{list} if $result->{matched}; + +return hashref with keys: + B 1 or 0, depends if name has matched + B name of list matched (present only if matched) + B The list of arguments matched (name, date of birth, residence, etc.) + +It will remove all non-alpha chars and compare with the list we have. + +=head2 update_data + +Fetches latest versions of sanction lists, and updates corresponding sections of stored file, if needed + +=head2 last_updated + +Returns timestamp of when the latest list was updated. +If argument is provided - return timestamp of when that list was updated. + +=head2 new + +Create the object, and set sanction_file + + my $validator = Data::Validate::Sanctions->new(sanction_file => '/var/storage/sanction.csv'); + +=head2 get_sanction_file + +get sanction_file which is used by L (procedure-oriented) + +=head2 set_sanction_file + +set sanction_file which is used by L (procedure-oriented) + +=head2 _name_matches + +Pass in the client's name and sanctioned individual's name to see if they are similar or not + +=head1 AUTHOR + +Binary.com Efayland@binary.comE + +=head1 COPYRIGHT + +Copyright 2014- Binary.com + +=head1 LICENSE + +This library is free software; you can redistribute it and/or modify +it under the same terms as Perl itself. + +=head1 SEE ALSO + +L + +=cut From 05082819f7eaa42c57f814f34a361068ba50c334 Mon Sep 17 00:00:00 2001 From: mat-fs Date: Tue, 13 Sep 2022 11:27:00 +0000 Subject: [PATCH 03/61] [ci] typo and minor bugs - tests pass --- lib/Data/Validate/Sanctions.pm | 2 +- lib/Data/Validate/Sanctions/Fetcher.pm | 6 ++-- lib/Data/Validate/Sanctions/Redis.pm | 6 ++-- t/05_basic.t | 6 ++-- t/fetcher.t | 48 ++++++++++++-------------- 5 files changed, 32 insertions(+), 36 deletions(-) diff --git a/lib/Data/Validate/Sanctions.pm b/lib/Data/Validate/Sanctions.pm index 474b78ac..8b8d4c14 100644 --- a/lib/Data/Validate/Sanctions.pm +++ b/lib/Data/Validate/Sanctions.pm @@ -51,7 +51,7 @@ sub update_data { $self->{_data}->{$k}->{content} //= []; if ($new_data->{$k}->{error}) { - warn "$ik list update failed because: $new_data->{$k}->{error}"; + warn "$k list update failed because: $new_data->{$k}->{error}"; } elsif ($self->{_data}{$k}->{updated} != $new_data->{$k}->{updated} || scalar $self->{_data}{$k}->{content}->@* != scalar $new_data->{$k}->{content}->@*) diff --git a/lib/Data/Validate/Sanctions/Fetcher.pm b/lib/Data/Validate/Sanctions/Fetcher.pm index 2032d05a..8c524ba3 100644 --- a/lib/Data/Validate/Sanctions/Fetcher.pm +++ b/lib/Data/Validate/Sanctions/Fetcher.pm @@ -342,7 +342,7 @@ sub _hmt_csv { # Fields to be added in the new file format (https://redmine.deriv.cloud/issues/51922) # We can read these fields normally after the data is released in the new format my ($passport_no, $non_latin_alias); - $passport_no = $row[$column{'Passport Number'}] if defined $column{'Passport Number'}; + $passport_no = $row[$column{'Passport Number'}] if defined $column{'Passport Number'}; $non_latin_alias = $row[$column{'Name Non-Latin Script'}] if defined $column{'Name Non-Latin Script'}; _process_sanction_entry( @@ -393,7 +393,7 @@ sub _eu_xml { my @place_of_birth = map { $_->{'-countryIso2Code'} || () } $entry->{birthdate}->@*; my @citizen = map { $_->{'-countryIso2Code'} || () } $entry->{citizenship}->@*; my @residence = map { $_->{'-countryIso2Code'} || () } $entry->{address}->@*; - my @postal_code = map { $_->{'-zipCode'} || $_->{'-poBox'} || () } $entry->{address}->@*; + my @postal_code = map { $_->{'-zipCode'} || $_->{'-poBox'} || () } $entry->{address}->@*; my @nationality = map { $_->{'-countryIso2Code'} || () } $entry->{identification}->@*; my @national_id = map { $_->{'-identificationTypeCode'} eq 'id' ? $_->{'-number'} || () : () } $entry->{identification}->@*; my @passport_no = map { $_->{'-identificationTypeCode'} eq 'passport' ? $_->{'-number'} || () : () } $entry->{identification}->@*; @@ -462,7 +462,7 @@ sub run { print "Source $id: $count entries fetched \n" if $args{verbose}; } } catch { - $result->{id}->{error} = $@; + $result->{$id}->{error} = $@; } } diff --git a/lib/Data/Validate/Sanctions/Redis.pm b/lib/Data/Validate/Sanctions/Redis.pm index 40460f45..6a4fe88e 100644 --- a/lib/Data/Validate/Sanctions/Redis.pm +++ b/lib/Data/Validate/Sanctions/Redis.pm @@ -21,8 +21,8 @@ sub new { ## no critic (RequireArgUnpacking) my ($class, %args) = @_; my $self = {}; - $self->{redis_read} = $args{redis_read} or 'Redis read connection is missing'; - $self->{redis_write} = $args{redis_write} or 'Redis write connection is missing'; + $self->{redis_read} = $args{redis_read} or die 'Redis read connection is missing'; + $self->{redis_write} = $args{redis_write} or die 'Redis write connection is missing'; $self->{sources} = [keys Data::Validate::Sanctions::Fetcher::config(eu_token => 'dummy')->%*]; @@ -71,7 +71,7 @@ sub _load_data { my $last_time; for my $source ($self->{sources}->@*) { - my $updated = $redis_read->hget("SANCTIONS::$source", 'updated') // 0; + my $updated = $self->{redis_read}->hget("SANCTIONS::$source", 'updated') // 0; next if $updated <= $self->{last_time}; $self->{_data}->{$source}->{content} = decode_json_utf8($self->{redis_read}->hget("SANCTIONS::$source", 'content')); diff --git a/t/05_basic.t b/t/05_basic.t index 6b9f6b89..abf99e81 100644 --- a/t/05_basic.t +++ b/t/05_basic.t @@ -53,9 +53,7 @@ $tempfile->spew( })); lives_ok { Data::Validate::Sanctions::set_sanction_file("$tempfile"); }; is(Data::Validate::Sanctions::get_sanction_file(), "$tempfile", "get sanction file ok"); -like Test::Warnings::warning { ok !Data::Validate::Sanctions::is_sanctioned(qw(Luke Lucky)) }, - qr/Content is empty for the sanction source test1. The sanctions file should be updated./, - 'Correct warnings for empty souorce content'; -ok !Data::Validate::Sanctions::is_sanctioned(qw(Luke Lucky)), "No warnings for the subsequent checks"; + +ok !Data::Validate::Sanctions::is_sanctioned(qw(Luke Lucky)), "No sanction match found with empty source"; done_testing; diff --git a/t/fetcher.t b/t/fetcher.t index 70cf50b7..3396b615 100644 --- a/t/fetcher.t +++ b/t/fetcher.t @@ -8,6 +8,7 @@ use YAML::XS qw(Dump); use Path::Tiny qw(tempfile); use List::Util qw(first); use Test::More; +use Test::Deep; use Test::Warnings; use Test::MockModule; use Test::Warn; @@ -39,22 +40,24 @@ subtest 'source url arguments' => sub { hmt_url => 'hmt.binary.com', ); - my $data; - warnings_like { - $data = Data::Validate::Sanctions::Fetcher::run(%test_args); - } - [ - qr/\bEU-Sanctions\b.*\bUser agent MockObject is hit by the url: eu.binary.com\b/, - qr/\bHMT-Sanctions\b.*\bUser agent MockObject is hit by the url: hmt.binary.com\b/, - qr/\bOFAC-Consolidated\b.*\bUser agent MockObject is hit by the url: ofac_con.binary.com\b/, - qr/\bOFAC-SDN\b.*\bUser agent MockObject is hit by the url: ofac_snd.binary.com\b/, - ], - 'Source urls are updated by params'; + my $data = Data::Validate::Sanctions::Fetcher::run(%test_args); + cmp_deeply $data, {'HMT-Sanctions' => { + error => ignore(), + }, + 'OFAC-Consolidated' => { + error => ignore(), + }, + 'EU-Sanctions' => { + error => ignore(), + }, + 'OFAC-SDN' => { + error => ignore(), + }, + }, + 'All sources return errors - no content'; is $calls, 3 * 4, 'the fetcher tried thrice per source and failed finally.'; - is_deeply $data, {}, 'There is no result with invalid urls'; - }; subtest 'EU Sanctions' => sub { @@ -64,30 +67,25 @@ subtest 'EU Sanctions' => sub { warnings_like { $data = Data::Validate::Sanctions::Fetcher::run(%args, eu_url => undef); } - [qr/EU Sanctions will fail whithout eu_token or eu_url/, qr/Url is empty for EU-Sanctions/], + [qr/EU Sanctions will fail whithout eu_token or eu_url/], 'Correct warning when the EU sanctions token is missing'; - is $data->{$source_name}, undef, 'Result is empty as expected'; + cmp_deeply $data->{$source_name}, {error => ignore()}, 'There is an error in the result'; + like $data->{$source_name}->{error}, qr/Url is empty for EU-Sanctions/, 'Correct error for missing EU url'; - warning_like { - $data = Data::Validate::Sanctions::Fetcher::run( + $data = Data::Validate::Sanctions::Fetcher::run( %args, eu_url => undef, eu_token => 'ASDF' ); - } - qr(\bEU-Sanctions\b.*\bUser agent MockObject is hit by the url: https://webgate.ec.europa.eu/fsd/fsf/public/files/xmlFullSanctionsList_1_1/content\?token=ASDF\b), - 'token is added to the default url'; - is $data->{$source_name}, undef, 'Result is empty'; + like $data->{$source_name}->{error}, qr(\bUser agent MockObject is hit by the url: https://webgate.ec.europa.eu/fsd/fsf/public/files/xmlFullSanctionsList_1_1/content\?token=ASDF\b), 'Token is added to the URL in error message'; - warning_like { - $data = Data::Validate::Sanctions::Fetcher::run( + $data = Data::Validate::Sanctions::Fetcher::run( %args, eu_url => 'http://dummy.binary.com', eu_token => 'ASDF' ); - } - qr(\bEU-Sanctions\b.*\bUser agent MockObject is hit by the url: http://dummy.binary.com at\b), 'token is not added to eu_url value'; + like $data->{$source_name}->{error}, qr(\bUser agent MockObject is hit by the url: http://dummy.binary.com\b), 'eu_url argument is directly used, without eu_token modification'; $data = Data::Validate::Sanctions::Fetcher::run(%args); ok $data->{$source_name}, 'EU Sanctions are loaded from the sample file'; From baefd84ff3f67de10614560d97b2e52a2562febc Mon Sep 17 00:00:00 2001 From: mat-fs Date: Tue, 13 Sep 2022 11:36:20 +0000 Subject: [PATCH 04/61] [ci] json module added --- cpanfile | 2 ++ lib/Data/Validate/Sanctions/Redis.pm | 1 + 2 files changed, 3 insertions(+) diff --git a/cpanfile b/cpanfile index f788ee9c..ff8969d3 100644 --- a/cpanfile +++ b/cpanfile @@ -19,6 +19,7 @@ requires 'Getopt::Long', '2.42'; requires 'Syntax::Keyword::Try', '0.18'; requires 'Locale::Country', '3.66'; requires 'Text::Trim', 0; +requires 'JSON::MaybeUTF8', 0; on test => sub { requires 'Test::More', '0.96'; @@ -26,6 +27,7 @@ on test => sub { requires 'Test::Warnings', '0.026'; requires 'Test::MockModule', '0.15'; requires 'Test::MockObject', '1.20161202'; + requires 'Test::Deep', '0'; requires 'FindBin', '0'; requires 'Path::Tiny', '0'; requires 'Class::Unload', '0'; diff --git a/lib/Data/Validate/Sanctions/Redis.pm b/lib/Data/Validate/Sanctions/Redis.pm index 6a4fe88e..9fa4b554 100644 --- a/lib/Data/Validate/Sanctions/Redis.pm +++ b/lib/Data/Validate/Sanctions/Redis.pm @@ -13,6 +13,7 @@ use Data::Compare; use List::Util qw(any uniq max min); use Locale::Country; use Text::Trim qw(trim); +use JSON::MaybeUTF8 qw(encode_json_utf8 decode_json_utf8); our $VERSION = '0.1'; From e39cedb76a8a697ecc3fc9cf9153b7aa3d54c04c Mon Sep 17 00:00:00 2001 From: mat-fs Date: Tue, 13 Sep 2022 12:50:42 +0000 Subject: [PATCH 05/61] [ci] singleton object implemented --- lib/Data/Validate/Sanctions/Redis.pm | 71 ++++++++++++---------------- 1 file changed, 29 insertions(+), 42 deletions(-) diff --git a/lib/Data/Validate/Sanctions/Redis.pm b/lib/Data/Validate/Sanctions/Redis.pm index 9fa4b554..720a5f8b 100644 --- a/lib/Data/Validate/Sanctions/Redis.pm +++ b/lib/Data/Validate/Sanctions/Redis.pm @@ -15,12 +15,15 @@ use Locale::Country; use Text::Trim qw(trim); use JSON::MaybeUTF8 qw(encode_json_utf8 decode_json_utf8); -our $VERSION = '0.1'; +our $VERSION = '0.13'; -# for OO -sub new { ## no critic (RequireArgUnpacking) +my $instance; + +sub new { my ($class, %args) = @_; + return $instance if $instance; + my $self = {}; $self->{redis_read} = $args{redis_read} or die 'Redis read connection is missing'; $self->{redis_write} = $args{redis_write} or die 'Redis write connection is missing'; @@ -54,10 +57,9 @@ sub get_sanction_file { } sub get_sanctioned_info { - my $self = shift; - unless ($self) { - die 'This method should be called on an object'; - } + my $self = blessed($_[0]) ? shift : $instance; + + die "This function can only be called on an object" unless $self; return Data::Validate::Sanctions::get_sanctioned_info($self, @_); } @@ -97,11 +99,14 @@ sub _load_data { sub _save_data { my $self = shift; + my $now = time; for my $source ($self->{sources}->@*) { $self->{redis_write}->hmset( "SANCTIONS::$source", 'updated', $self->{_data}->{$source}->{updated}, 'content', encode_json_utf8($self->{_data}->{$source}->{content}), + 'fetched', $now, + 'error', $self->{_data}->{$source}->{error} ); } @@ -119,38 +124,26 @@ __END__ =head1 NAME -Data::Validate::Sanctions - Validate a name against sanctions lists +Data::Validate::Sanctions::Redis - An extention of L that stores sanction data in redis rather than a local file. =head1 SYNOPSIS + # it only works with OO calls + use Data::Validate::Sanctions::Redis; - # as exported function - use Data::Validate::Sanctions qw/is_sanctioned get_sanction_file set_sanction_file/; - set_sanction_file('/var/storage/sanction.csv'); - - my ($first_name, $last_name) = ("First", "Last Name"); - print 'BAD' if is_sanctioned($first_name, $last_name); - - # as OO - use Data::Validate::Sanctions; - - #You can also set sanction_file in the new method. - my $validator = Data::Validate::Sanctions->new(sanction_file => '/var/storage/sanction.csv'); + my $validator = Data::Validate::Sanctions->new(redis_read => $redis_read, redis_write => $redis_write); print 'BAD' if $validator->is_sanctioned("$last_name $first_name"); -=head1 DESCRIPTION + # In order to update the sanction dataset: + my $validator = Data::Validate::Sanctions->new(redis_read => $redis_read, redis_write => $redis_write); -Data::Validate::Sanctions is a simple validitor to validate a name against sanctions lists. + # eu_token or eu_url is required + $validator->update_data(eu_token => $token); -The list is from: -- L, -- L -- L -- L -run F to update the bundled csv. +=head1 DESCRIPTION -The path of list can be set by function L or by method L. If not set, then environment variable $ENV{SANCTION_FILE} will be checked, at last -the default file in this package will be used. +Data::Validate::Sanctions::Redis is a simple validitor to validate a name against sanctions lists. +For more details about the sanction sources please refer to L. =head1 METHODS @@ -170,7 +163,7 @@ It will remove all non-alpha chars and compare with the list we have. =head2 get_sanctioned_info - my $result =get_sanctioned_info($last_name, $first_name, $date_of_birth); + my $result = $validator->get_sanctioned_info($last_name, $first_name, $date_of_birth); print 'match: ', $result->{matched_args}->{name}, ' on list ', $result->{list} if $result->{matched}; return hashref with keys: @@ -193,15 +186,7 @@ If argument is provided - return timestamp of when that list was updated. Create the object, and set sanction_file - my $validator = Data::Validate::Sanctions->new(sanction_file => '/var/storage/sanction.csv'); - -=head2 get_sanction_file - -get sanction_file which is used by L (procedure-oriented) - -=head2 set_sanction_file - -set sanction_file which is used by L (procedure-oriented) + my $validator = Data::Validate::Sanctions::Redis->new(redis_read => $redis_read, redis_write => $redis_write); =head2 _name_matches @@ -213,7 +198,7 @@ Binary.com Efayland@binary.comE =head1 COPYRIGHT -Copyright 2014- Binary.com +Copyright 2022- Binary.com =head1 LICENSE @@ -222,6 +207,8 @@ it under the same terms as Perl itself. =head1 SEE ALSO -L +L + +L =cut From 10838b40f69edaae7fb7ab00b5cf47ca40025fcc Mon Sep 17 00:00:00 2001 From: mat-fs Date: Tue, 13 Sep 2022 12:53:55 +0000 Subject: [PATCH 06/61] [ci] comment added about singleton cunstructor --- lib/Data/Validate/Sanctions/Redis.pm | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/Data/Validate/Sanctions/Redis.pm b/lib/Data/Validate/Sanctions/Redis.pm index 720a5f8b..c4b9fa1a 100644 --- a/lib/Data/Validate/Sanctions/Redis.pm +++ b/lib/Data/Validate/Sanctions/Redis.pm @@ -184,10 +184,12 @@ If argument is provided - return timestamp of when that list was updated. =head2 new -Create the object, and set sanction_file +Create the object, and set the redis reader and writer objects: my $validator = Data::Validate::Sanctions::Redis->new(redis_read => $redis_read, redis_write => $redis_write); +The validator is a singleton object; so it will always return the same object if it's called for multiple times in a process. + =head2 _name_matches Pass in the client's name and sanctioned individual's name to see if they are similar or not From 9b5a4adc2f70fc273abb0a49c5fc530e3c558c8e Mon Sep 17 00:00:00 2001 From: mat-fs Date: Tue, 13 Sep 2022 13:02:47 +0000 Subject: [PATCH 07/61] [ci] comments + test script fix --- lib/Data/Validate/Sanctions/Redis.pm | 11 +++++------ t/fetcher.t | 4 ++-- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/lib/Data/Validate/Sanctions/Redis.pm b/lib/Data/Validate/Sanctions/Redis.pm index c4b9fa1a..7fa6f9d6 100644 --- a/lib/Data/Validate/Sanctions/Redis.pm +++ b/lib/Data/Validate/Sanctions/Redis.pm @@ -5,14 +5,13 @@ use warnings; use parent 'Data::Validate::Sanctions'; -use Carp; +require Exporter; +our @ISA = qw(Exporter); +our @EXPORT_OK = qw/is_sanctioned set_sanction_file get_sanction_file/; + use Data::Validate::Sanctions::Fetcher; use Scalar::Util qw(blessed); -use Date::Utility; -use Data::Compare; -use List::Util qw(any uniq max min); -use Locale::Country; -use Text::Trim qw(trim); +use List::Util qw(max); use JSON::MaybeUTF8 qw(encode_json_utf8 decode_json_utf8); our $VERSION = '0.13'; diff --git a/t/fetcher.t b/t/fetcher.t index 3396b615..51d041dc 100644 --- a/t/fetcher.t +++ b/t/fetcher.t @@ -13,7 +13,7 @@ use Test::Warnings; use Test::MockModule; use Test::Warn; use Test::MockObject; -use List::Util qw (any); +use List::Util; my %args = ( eu_url => "file://t/data/sample_eu.xml", @@ -254,7 +254,7 @@ sub find_entry_by_name { my @result; for my $entry ($data->{content}->@*) { - push(@result, $entry) if any { $_ eq $name } $entry->{names}->@*; + push(@result, $entry) if List::Util::any { $_ eq $name } $entry->{names}->@*; } return undef unless @result; From 4a9584b0d6337811b5512e7e163fdf03b97e2398 Mon Sep 17 00:00:00 2001 From: mat-fs Date: Tue, 13 Sep 2022 13:06:29 +0000 Subject: [PATCH 08/61] [ci] version updated --- Changes | 3 +++ lib/Data/Validate/Sanctions.pm | 2 +- lib/Data/Validate/Sanctions/Fetcher.pm | 2 +- lib/Data/Validate/Sanctions/Redis.pm | 2 +- 4 files changed, 6 insertions(+), 3 deletions(-) diff --git a/Changes b/Changes index 5ec4bc43..4e7a8f1d 100644 --- a/Changes +++ b/Changes @@ -1,6 +1,9 @@ Revision history for Data-Validate-Sanctions {{$NEXT}} +0.14 2022-09-13 13:55:00 CST + New module for redis storage + 0.13 2022-07-26 13:55:00 CST Improving the search for larger sanction lists diff --git a/lib/Data/Validate/Sanctions.pm b/lib/Data/Validate/Sanctions.pm index 8b8d4c14..6921f91d 100644 --- a/lib/Data/Validate/Sanctions.pm +++ b/lib/Data/Validate/Sanctions.pm @@ -19,7 +19,7 @@ use List::Util qw(any uniq max min); use Locale::Country; use Text::Trim qw(trim); -our $VERSION = '0.13'; +# VERSION my $sanction_file = _default_sanction_file(); my $instance; diff --git a/lib/Data/Validate/Sanctions/Fetcher.pm b/lib/Data/Validate/Sanctions/Fetcher.pm index 8c524ba3..36b036f6 100644 --- a/lib/Data/Validate/Sanctions/Fetcher.pm +++ b/lib/Data/Validate/Sanctions/Fetcher.pm @@ -14,7 +14,7 @@ use Syntax::Keyword::Try; use XML::Fast; use Locale::Country; -our $VERSION = '0.10'; +# VERSION =head2 config diff --git a/lib/Data/Validate/Sanctions/Redis.pm b/lib/Data/Validate/Sanctions/Redis.pm index 7fa6f9d6..21fbaf32 100644 --- a/lib/Data/Validate/Sanctions/Redis.pm +++ b/lib/Data/Validate/Sanctions/Redis.pm @@ -14,7 +14,7 @@ use Scalar::Util qw(blessed); use List::Util qw(max); use JSON::MaybeUTF8 qw(encode_json_utf8 decode_json_utf8); -our $VERSION = '0.13'; +# VERSION my $instance; From 3db79036c448a4384088d7c6fd76ae9a78dd7cac Mon Sep 17 00:00:00 2001 From: mat-fs Date: Tue, 13 Sep 2022 13:08:09 +0000 Subject: [PATCH 09/61] [ci] version fixed --- lib/Data/Validate/Sanctions.pm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Data/Validate/Sanctions.pm b/lib/Data/Validate/Sanctions.pm index 6921f91d..c7b4e1f2 100644 --- a/lib/Data/Validate/Sanctions.pm +++ b/lib/Data/Validate/Sanctions.pm @@ -19,7 +19,7 @@ use List::Util qw(any uniq max min); use Locale::Country; use Text::Trim qw(trim); -# VERSION +our $VERSION = '0.14'; my $sanction_file = _default_sanction_file(); my $instance; From d38f513f8cc00d5bb1fc447759a6b11a62dc5b33 Mon Sep 17 00:00:00 2001 From: mat-fs Date: Wed, 14 Sep 2022 14:05:35 +0000 Subject: [PATCH 10/61] [ci] two new subtests --- cpanfile | 2 + lib/Data/Validate/Sanctions.pm | 27 +-- lib/Data/Validate/Sanctions/Fetcher.pm | 2 +- lib/Data/Validate/Sanctions/Redis.pm | 87 ++++---- t/sanctions_redis.t | 267 +++++++++++++++++++++++++ 5 files changed, 330 insertions(+), 55 deletions(-) create mode 100644 t/sanctions_redis.t diff --git a/cpanfile b/cpanfile index ff8969d3..df014d31 100644 --- a/cpanfile +++ b/cpanfile @@ -31,4 +31,6 @@ on test => sub { requires 'FindBin', '0'; requires 'Path::Tiny', '0'; requires 'Class::Unload', '0'; + requires 'Test::RedisServer', '0.23'; + requires 'RedisDB', '2.57'; }; diff --git a/lib/Data/Validate/Sanctions.pm b/lib/Data/Validate/Sanctions.pm index c7b4e1f2..2b08df9e 100644 --- a/lib/Data/Validate/Sanctions.pm +++ b/lib/Data/Validate/Sanctions.pm @@ -11,7 +11,7 @@ use Carp; use Data::Validate::Sanctions::Fetcher; use File::stat; use File::ShareDir; -use YAML::XS qw/DumpFile LoadFile/; +use YAML::XS qw/DumpFile LoadFile/; use Scalar::Util qw(blessed); use Date::Utility; use Data::Compare; @@ -52,8 +52,9 @@ sub update_data { if ($new_data->{$k}->{error}) { warn "$k list update failed because: $new_data->{$k}->{error}"; - } - elsif ($self->{_data}{$k}->{updated} != $new_data->{$k}->{updated} + $updated = 1; + $self->{_data}->{$k}->{error} = $new_data->{$k}->{error}; + } elsif ($self->{_data}{$k}->{updated} != $new_data->{$k}->{updated} || scalar $self->{_data}{$k}->{content}->@* != scalar $new_data->{$k}->{content}->@*) { $self->{_data}->{$k} = $new_data->{$k}; @@ -223,7 +224,7 @@ sub get_sanctioned_info { ## no critic (RequireArgUnpacking) # and deduplicate the list my $filtered_sanctioned_names = {}; foreach my $token (@client_name_tokens) { - foreach my $name ( keys %{$self->{_token_sanctioned_names}->{$token}}) { + foreach my $name (keys %{$self->{_token_sanctioned_names}->{$token}}) { $filtered_sanctioned_names->{$name} = 1; } } @@ -290,12 +291,12 @@ sub get_sanctioned_info { ## no critic (RequireArgUnpacking) } sub _load_data { - my $self = shift; - my $sanction_file = $self->{sanction_file}; - $self->{last_time} //= 0; - $self->{_data} //= {}; - $self->{_sanctioned_name_tokens} //= {}; - $self->{_token_sanctioned_names} //= {}; + my $self = shift; + my $sanction_file = $self->{sanction_file}; + $self->{last_time} //= 0; + $self->{_data} //= {}; + $self->{_sanctioned_name_tokens} //= {}; + $self->{_token_sanctioned_names} //= {}; if (-e $sanction_file) { return $self->{_data} if stat($sanction_file)->mtime <= $self->{last_time} && $self->{_data}; @@ -307,8 +308,8 @@ sub _load_data { foreach my $sanctioned_name (keys $self->{_index}->%*) { my @tokens = _clean_names($sanctioned_name); $self->{_sanctioned_name_tokens}->{$sanctioned_name} = \@tokens; - foreach my $token (@tokens){ - $self->{_token_sanctioned_names}->{$token}->{$sanctioned_name}=1; + foreach my $token (@tokens) { + $self->{_token_sanctioned_names}->{$token}->{$sanctioned_name} = 1; } } @@ -327,7 +328,7 @@ sub _index_data { $self->{_index} = {}; for my $source (keys $self->{_data}->%*) { my @content = ($self->{_data}->{$source}->{content} // [])->@*; - + for my $entry (@content) { $entry->{source} = $source; for my $name ($entry->{names}->@*) { diff --git a/lib/Data/Validate/Sanctions/Fetcher.pm b/lib/Data/Validate/Sanctions/Fetcher.pm index 36b036f6..a5f1f1aa 100644 --- a/lib/Data/Validate/Sanctions/Fetcher.pm +++ b/lib/Data/Validate/Sanctions/Fetcher.pm @@ -6,7 +6,7 @@ use warnings; use DateTime::Format::Strptime; use Date::Utility; use IO::Uncompress::Unzip qw(unzip $UnzipError); -use List::Util qw(uniq any); +use List::Util qw(uniq any); use Mojo::UserAgent; use Text::CSV; use Text::Trim qw(trim); diff --git a/lib/Data/Validate/Sanctions/Redis.pm b/lib/Data/Validate/Sanctions/Redis.pm index 21fbaf32..392bb61e 100644 --- a/lib/Data/Validate/Sanctions/Redis.pm +++ b/lib/Data/Validate/Sanctions/Redis.pm @@ -5,27 +5,20 @@ use warnings; use parent 'Data::Validate::Sanctions'; -require Exporter; -our @ISA = qw(Exporter); -our @EXPORT_OK = qw/is_sanctioned set_sanction_file get_sanction_file/; - use Data::Validate::Sanctions::Fetcher; -use Scalar::Util qw(blessed); -use List::Util qw(max); +use Scalar::Util qw(blessed); +use YAML::XS qw/DumpFile/; +use List::Util qw(max); use JSON::MaybeUTF8 qw(encode_json_utf8 decode_json_utf8); # VERSION -my $instance; - sub new { my ($class, %args) = @_; - return $instance if $instance; - my $self = {}; - $self->{redis_read} = $args{redis_read} or die 'Redis read connection is missing'; - $self->{redis_write} = $args{redis_write} or die 'Redis write connection is missing'; + $self->{redis_read} = $args{redis_read} or die 'Redis read connection is missing'; + $self->{redis_write} = $args{redis_write}; $self->{sources} = [keys Data::Validate::Sanctions::Fetcher::config(eu_token => 'dummy')->%*]; @@ -56,7 +49,7 @@ sub get_sanction_file { } sub get_sanctioned_info { - my $self = blessed($_[0]) ? shift : $instance; + my $self = shift; die "This function can only be called on an object" unless $self; @@ -64,31 +57,31 @@ sub get_sanctioned_info { } sub _load_data { - my $self = shift; - - $self->{last_time} //= 0; - $self->{_data} //= {}; - $self->{_sanctioned_name_tokens} //= {}; - $self->{_token_sanctioned_names} //= {}; - - my $last_time; + my $self = shift; + + $self->{last_time} //= 0; + $self->{_data} //= {}; + $self->{_sanctioned_name_tokens} //= {}; + $self->{_token_sanctioned_names} //= {}; + + my $last_time = $self->{last_time}; for my $source ($self->{sources}->@*) { - my $updated = $self->{redis_read}->hget("SANCTIONS::$source", 'updated') // 0; + my $updated = $self->{redis_read}->hget("SANCTIONS::$source", 'published') // 0; next if $updated <= $self->{last_time}; $self->{_data}->{$source}->{content} = decode_json_utf8($self->{redis_read}->hget("SANCTIONS::$source", 'content')); $self->{_data}->{$source}->{updated} = $updated; - $last_time = $updated if $updated > $last_time; + $last_time = $updated if $updated > $last_time; } - $self->{_last_time} = $last_time; + $self->{last_time} = $last_time; $self->_index_data(); foreach my $sanctioned_name (keys $self->{_index}->%*) { - my @tokens = _clean_names($sanctioned_name); + my @tokens = Data::Validate::Sanctions::_clean_names($sanctioned_name); $self->{_sanctioned_name_tokens}->{$sanctioned_name} = \@tokens; - foreach my $token (@tokens){ - $self->{_token_sanctioned_names}->{$token}->{$sanctioned_name}=1; + foreach my $token (@tokens) { + $self->{_token_sanctioned_names}->{$token}->{$sanctioned_name} = 1; } } @@ -98,15 +91,15 @@ sub _load_data { sub _save_data { my $self = shift; + die 'Redis write connection is missing' unless $self->{redis_write}; my $now = time; for my $source ($self->{sources}->@*) { $self->{redis_write}->hmset( - "SANCTIONS::$source", - 'updated', $self->{_data}->{$source}->{updated}, - 'content', encode_json_utf8($self->{_data}->{$source}->{content}), - 'fetched', $now, - 'error', $self->{_data}->{$source}->{error} - ); + "SANCTIONS::$source", + 'published' => $self->{_data}->{$source}->{updated} // 0, + 'content' => encode_json_utf8($self->{_data}->{$source}->{content} // []), + ($self->{_data}->{$source}->{error} ? () : ('verified' => $now)), + 'error' => $self->{_data}->{$source}->{error} // ''); } return; @@ -116,6 +109,14 @@ sub _default_sanction_file { die 'Not applicable'; } +sub export_data { + my ($self, $path) = @_; + + $self->_load_data(); + + DumpFile($path, $self->{_data}); +} + 1; __END__ @@ -146,6 +147,14 @@ For more details about the sanction sources please refer to Lnew(redis_read => $redis_read, redis_write => $redis_write); + +The validator is a singleton object; so it will always return the same object if it's called for multiple times in a process. + =head2 is_sanctioned is_sanctioned($last_name, $first_name); @@ -181,18 +190,14 @@ Fetches latest versions of sanction lists, and updates corresponding sections of Returns timestamp of when the latest list was updated. If argument is provided - return timestamp of when that list was updated. -=head2 new - -Create the object, and set the redis reader and writer objects: - - my $validator = Data::Validate::Sanctions::Redis->new(redis_read => $redis_read, redis_write => $redis_write); - -The validator is a singleton object; so it will always return the same object if it's called for multiple times in a process. - =head2 _name_matches Pass in the client's name and sanctioned individual's name to see if they are similar or not +=head2 export_data + +Exports the sanction lists to a local file in YAML format. + =head1 AUTHOR Binary.com Efayland@binary.comE diff --git a/t/sanctions_redis.t b/t/sanctions_redis.t new file mode 100644 index 00000000..dee0a998 --- /dev/null +++ b/t/sanctions_redis.t @@ -0,0 +1,267 @@ +use strict; +use warnings; + +use Class::Unload; +use YAML::XS qw(Dump); +use Path::Tiny qw(tempfile); +use Test::Warnings qw/warning/; +use Test::More; +use Test::Fatal; +use Test::MockModule; +use Test::RedisServer; +use Test::MockTime qw(set_fixed_time); +use RedisDB; +use JSON::MaybeUTF8 qw(decode_json_utf8); +use Clone qw(clone); + +use Data::Validate::Sanctions::Redis; + +my $redis_server = Test::RedisServer->new(); +my $redis = RedisDB->new($redis_server->connect_info); + +# my $mock_data = { +# 'EU-Sanctions' => { +# updated => 100, +# content => [{ +# names => ['TMPA'], +# dob_epoch => [], +# dob_year => [] +# }, +# { +# names => ['MOHAMMAD EWAZ Mohammad Wali'], +# dob_epoch => [], +# dob_year => [] +# }, +# ]}, +# 'HMT-Sanctions' => { +# updated => 101, +# content => [ +# { +# names => ['Zaki Izzat Zaki AHMAD'], +# dob_epoch => [], +# dob_year => [1999], +# dob_text => ['other info'], +# }, +# ]}, +# 'OFAC-Consolidated' => { +# updated => 102, +# content => [ +# { +# names => ['Atom'], +# dob_year => [1999], +# }, +# { +# names => ['Donald Trump'], +# dob_text => ['circa-1951'], +# }, +# ]}, +# 'OFAC-SDN' => { +# updated => 103, +# content => [ +# { +# names => ['Bandit Outlaw'], +# place_of_birth => ['ir'], +# residence => ['fr', 'us'], +# nationality => ['de', 'gb'], +# citizen => ['ru'], +# postal_code => ['123321'], +# national_id => ['321123'], +# passport_no => ['asdffdsa'], +# }] +# }, +# }; + +subtest 'Class constructor' => sub { + my $validator; + like exception { $validator = Data::Validate::Sanctions::Redis->new() }, qr/Redis read connection is missing/, + 'Correct error for missing redis-read'; + + is exception { $validator = Data::Validate::Sanctions::Redis->new(redis_read => $redis) }, undef, + 'Successfully created the object with redis-read object'; + + is_deeply $validator->{_data}, undef, 'There is no sanction data'; +}; + +subtest 'Update Data' => sub { + my $mock_fetcher = Test::MockModule->new('Data::Validate::Sanctions::Fetcher'); + my $mock_data = { + 'EU-Sanctions' => { + updated => 90, + content => []}}; + $mock_fetcher->redefine(run => sub { return clone($mock_data) }); + + clear_redis(); + set_fixed_time(1500); + my $validator = Data::Validate::Sanctions::Redis->new(redis_read => $redis); + like exception { $validator->update_data(verbose => 1) }, qr/Redis write connection is missing/, 'Redis-write is required for updating'; + + # load and save into redis + $validator = Data::Validate::Sanctions::Redis->new( + redis_read => $redis, + redis_write => $redis + ); + $validator->update_data(); + my $expected = { + 'EU-Sanctions' => { + content => [], + updated => 90 + }, + 'HMT-Sanctions' => {}, + 'OFAC-Consolidated' => {}, + 'OFAC-SDN' => {}, + }; + is_deeply $validator->{_data}, $expected, 'Data is correctly loaded'; + check_redis_content('EU-Sanctions', $mock_data->{'EU-Sanctions'}, 1500); + check_redis_content('HMT-Sanctions', {}, 1500); + check_redis_content('OFAC-Consolidated', {}, 1500); + check_redis_content('OFAC-SDN', {}, 1500); + + # rewrite to redis if update (publish) time is changed + set_fixed_time(1600); + $mock_data->{'EU-Sanctions'}->{updated} = 91; + $validator->update_data(); + $expected->{'EU-Sanctions'}->{updated} = 91; + is_deeply $validator->{_data}, $expected, 'Data is loaded with new update time'; + check_redis_content('EU-Sanctions', $mock_data->{'EU-Sanctions'}, 1600, 'Redis content changed by increased update time'); + + # don't rewrite if there is an error - just update verified time + set_fixed_time(1700); + $mock_data->{'EU-Sanctions'}->{error} = 'Test error'; + $mock_data->{'EU-Sanctions'}->{updated} = 92; + $mock_data->{'EU-Sanctions'}->{content} = [1,2,3]; + warning {$validator->update_data()}, qr/EU-Sanctions list update failed because: Test error/, 'Error warning appears in logs'; + $expected->{'EU-Sanctions'}->{error} = 'Test error'; + is_deeply $validator->{_data}, $expected, 'Data is not changed if there is error'; + $mock_data->{'EU-Sanctions'}->{updated} = 91; # this is not updated because of the error + $mock_data->{'EU-Sanctions'}->{content} = []; + check_redis_content('EU-Sanctions', $mock_data->{'EU-Sanctions'}, 1600, 'Redis content changed by increased update time'); +}; + +my $validator = Data::Validate::Sanctions::Redis->new( + redis_read => $redis, + redis_write => $redis +); + +ok !$validator->is_sanctioned(qw(sergei ivanov)), "Sergei Ivanov not is_sanctioned"; +ok $validator->is_sanctioned(qw(tmpa)), "now sanction file is tmpa, and tmpa is in test1 list"; +ok !$validator->is_sanctioned("Mohammad reere yuyuy", "wqwqw qqqqq"), "is not in test1 list"; +ok $validator->is_sanctioned("Zaki", "Ahmad"), "is in test1 list - searched without dob"; +ok $validator->is_sanctioned("Zaki", "Ahmad", '1999-01-05'), 'the guy is sanctioned when dob year is matching'; +ok $validator->is_sanctioned("atom", "test", '1999-01-05'), "Match correctly with one world name in sanction list"; + +is_deeply $validator->get_sanctioned_info("Zaki", "Ahmad", '1999-01-05'), + { + 'comment' => undef, + 'list' => 'test1', + 'matched' => 1, + 'matched_args' => { + 'dob_year' => 1999, + 'name' => 'Zaki Izzat Zaki AHMAD' + } + }, + 'Sanction info is correct'; +ok $validator->is_sanctioned("Ahmad", "Ahmad", '1999-10-10'), "is in test1 list"; + +is_deeply $validator->get_sanctioned_info("TMPA"), + { + 'comment' => undef, + 'list' => 'test1', + 'matched' => 1, + 'matched_args' => {'name' => 'TMPA'} + }, + 'Sanction info is correct'; + +is_deeply $validator->get_sanctioned_info('Donald', 'Trump', '1999-01-05'), + { + 'comment' => 'dob raw text: circa-1951', + 'list' => 'test1', + 'matched' => 1, + 'matched_args' => {'name' => 'Donald Trump'} + }, + "When client's name matches a case with dob_text"; + +is_deeply $validator->get_sanctioned_info('Bandit', 'Outlaw', '1999-01-05'), + { + 'comment' => undef, + 'list' => 'test1', + 'matched' => 1, + 'matched_args' => {'name' => 'Bandit Outlaw'} + }, + "If optional ares are empty, only name is matched"; + +my $args = { + first_name => 'Bandit', + last_name => 'Outlaw', + place_of_birth => 'Iran', + residence => 'France', + nationality => 'Germany', + citizen => 'Russia', + postal_code => '123321', + national_id => '321123', + passport_no => 'asdffdsa', +}; + +is_deeply $validator->get_sanctioned_info($args), + { + 'comment' => undef, + 'list' => 'test1', + 'matched' => 1, + 'matched_args' => { + name => 'Bandit Outlaw', + place_of_birth => 'ir', + residence => 'fr', + nationality => 'de', + citizen => 'ru', + postal_code => '123321', + national_id => '321123', + passport_no => 'asdffdsa', + } + }, + "All matched fields are returned"; + +for my $field (qw/place_of_birth residence nationality citizen postal_code national_id passport_no/) { + is_deeply $validator->get_sanctioned_info({%$args, $field => 'Israel'}), + {'matched' => 0}, "A single wrong field will result in mismatch - $field"; + + my $expected_result = { + 'list' => 'test1', + 'matched' => 1, + 'matched_args' => { + name => 'Bandit Outlaw', + place_of_birth => 'ir', + residence => 'fr', + nationality => 'de', + citizen => 'ru', + postal_code => '123321', + national_id => '321123', + passport_no => 'asdffdsa', + }, + comment => undef, + }; + + delete $expected_result->{matched_args}->{$field}; + is_deeply $validator->get_sanctioned_info({%$args, $field => undef}), $expected_result, "Missing optional args are ignored - $field"; +} + +sub clear_redis { + for my $key ($redis->keys('SANCTIONS::*')->@*) { + $redis->del($key); + } +} + +sub check_redis_content { + my ($source_name, $config, $verified_time, $comment) = @_; + + my %stored = $redis->hgetall("SANCTIONS::$source_name")->@*; + $stored{content} = decode_json_utf8($stored{content}); + is_deeply \%stored, + { + content => $config->{content} // [], + published => $config->{updated} // 0, + error => $config->{error} // '', + verified => $verified_time, + }, + $comment // "Redis content is correct for $source_name"; +} + +done_testing; From 1aa9ffb49c6c007352eff07ba2a59f223fe281e0 Mon Sep 17 00:00:00 2001 From: mat-fs Date: Thu, 15 Sep 2022 06:57:42 +0000 Subject: [PATCH 11/61] [ci] tests finished --- cpanfile | 1 + lib/Data/Validate/Sanctions.pm | 7 ++++++- lib/Data/Validate/Sanctions/Redis.pm | 15 +++++++++------ 3 files changed, 16 insertions(+), 7 deletions(-) diff --git a/cpanfile b/cpanfile index df014d31..2c10cf11 100644 --- a/cpanfile +++ b/cpanfile @@ -20,6 +20,7 @@ requires 'Syntax::Keyword::Try', '0.18'; requires 'Locale::Country', '3.66'; requires 'Text::Trim', 0; requires 'JSON::MaybeUTF8', 0; +requires 'Clone', 0; on test => sub { requires 'Test::More', '0.96'; diff --git a/lib/Data/Validate/Sanctions.pm b/lib/Data/Validate/Sanctions.pm index 2b08df9e..83a7298b 100644 --- a/lib/Data/Validate/Sanctions.pm +++ b/lib/Data/Validate/Sanctions.pm @@ -50,6 +50,11 @@ sub update_data { $self->{_data}->{$k}->{updated} //= 0; $self->{_data}->{$k}->{content} //= []; + if (!$new_data->{$k}->{error} && $self->{_data}->{$k}->{error}) { + delete $self->{_data}->{$k}->{error}; + $updated = 1; + } + if ($new_data->{$k}->{error}) { warn "$k list update failed because: $new_data->{$k}->{error}"; $updated = 1; @@ -327,7 +332,7 @@ sub _index_data { $self->{_index} = {}; for my $source (keys $self->{_data}->%*) { - my @content = ($self->{_data}->{$source}->{content} // [])->@*; + my @content = clone($self->{_data}->{$source}->{content} // [])->@*; for my $entry (@content) { $entry->{source} = $source; diff --git a/lib/Data/Validate/Sanctions/Redis.pm b/lib/Data/Validate/Sanctions/Redis.pm index 392bb61e..873f538a 100644 --- a/lib/Data/Validate/Sanctions/Redis.pm +++ b/lib/Data/Validate/Sanctions/Redis.pm @@ -17,15 +17,17 @@ sub new { my ($class, %args) = @_; my $self = {}; - $self->{redis_read} = $args{redis_read} or die 'Redis read connection is missing'; + $self->{redis_read} = $args{redis_read} or die 'Redis read connection is missing'; $self->{redis_write} = $args{redis_write}; $self->{sources} = [keys Data::Validate::Sanctions::Fetcher::config(eu_token => 'dummy')->%*]; - $self->{args} = {%args}; - + $self->{args} = {%args}; $self->{last_time} = 0; - return bless $self, ref($class) || $class; + my $object = bless $self, ref($class) || $class; + $object->_load_data(); + + return $object; } sub last_updated { @@ -67,7 +69,7 @@ sub _load_data { my $last_time = $self->{last_time}; for my $source ($self->{sources}->@*) { my $updated = $self->{redis_read}->hget("SANCTIONS::$source", 'published') // 0; - next if $updated <= $self->{last_time}; + next if $updated <= ($self->{_data}->{$source}->{updated} // 0); $self->{_data}->{$source}->{content} = decode_json_utf8($self->{redis_read}->hget("SANCTIONS::$source", 'content')); $self->{_data}->{$source}->{updated} = $updated; @@ -99,7 +101,8 @@ sub _save_data { 'published' => $self->{_data}->{$source}->{updated} // 0, 'content' => encode_json_utf8($self->{_data}->{$source}->{content} // []), ($self->{_data}->{$source}->{error} ? () : ('verified' => $now)), - 'error' => $self->{_data}->{$source}->{error} // ''); + 'error' => $self->{_data}->{$source}->{error} // '' + ); } return; From 03675729aa78cba7831c95a15e65866f4ba7b58a Mon Sep 17 00:00:00 2001 From: mat-fs Date: Thu, 15 Sep 2022 07:17:49 +0000 Subject: [PATCH 12/61] [ci] minor fixes --- lib/Data/Validate/Sanctions.pm | 1 + t/sanctions_redis.t | 376 +++++++++++++++++++-------------- 2 files changed, 222 insertions(+), 155 deletions(-) diff --git a/lib/Data/Validate/Sanctions.pm b/lib/Data/Validate/Sanctions.pm index 83a7298b..58595457 100644 --- a/lib/Data/Validate/Sanctions.pm +++ b/lib/Data/Validate/Sanctions.pm @@ -18,6 +18,7 @@ use Data::Compare; use List::Util qw(any uniq max min); use Locale::Country; use Text::Trim qw(trim); +use Clone qw(clone); our $VERSION = '0.14'; diff --git a/t/sanctions_redis.t b/t/sanctions_redis.t index dee0a998..7830867f 100644 --- a/t/sanctions_redis.t +++ b/t/sanctions_redis.t @@ -2,9 +2,10 @@ use strict; use warnings; use Class::Unload; -use YAML::XS qw(Dump); -use Path::Tiny qw(tempfile); -use Test::Warnings qw/warning/; +use YAML; +use File::Slurp; +use Path::Tiny qw(tempfile); +use Test::Warnings; use Test::More; use Test::Fatal; use Test::MockModule; @@ -12,85 +13,91 @@ use Test::RedisServer; use Test::MockTime qw(set_fixed_time); use RedisDB; use JSON::MaybeUTF8 qw(decode_json_utf8); -use Clone qw(clone); +use Clone qw(clone); use Data::Validate::Sanctions::Redis; my $redis_server = Test::RedisServer->new(); my $redis = RedisDB->new($redis_server->connect_info); -# my $mock_data = { -# 'EU-Sanctions' => { -# updated => 100, -# content => [{ -# names => ['TMPA'], -# dob_epoch => [], -# dob_year => [] -# }, -# { -# names => ['MOHAMMAD EWAZ Mohammad Wali'], -# dob_epoch => [], -# dob_year => [] -# }, -# ]}, -# 'HMT-Sanctions' => { -# updated => 101, -# content => [ -# { -# names => ['Zaki Izzat Zaki AHMAD'], -# dob_epoch => [], -# dob_year => [1999], -# dob_text => ['other info'], -# }, -# ]}, -# 'OFAC-Consolidated' => { -# updated => 102, -# content => [ -# { -# names => ['Atom'], -# dob_year => [1999], -# }, -# { -# names => ['Donald Trump'], -# dob_text => ['circa-1951'], -# }, -# ]}, -# 'OFAC-SDN' => { -# updated => 103, -# content => [ -# { -# names => ['Bandit Outlaw'], -# place_of_birth => ['ir'], -# residence => ['fr', 'us'], -# nationality => ['de', 'gb'], -# citizen => ['ru'], -# postal_code => ['123321'], -# national_id => ['321123'], -# passport_no => ['asdffdsa'], -# }] -# }, -# }; +my $sample_data = { + 'EU-Sanctions' => { + updated => 91, + content => [{ + names => ['TMPA'], + dob_epoch => [], + dob_year => [] + }, + { + names => ['MOHAMMAD EWAZ Mohammad Wali'], + dob_epoch => [], + dob_year => [] + }, + ] + }, + 'HMT-Sanctions' => { + updated => 150, + content => [{ + names => ['Zaki Izzat Zaki AHMAD'], + dob_epoch => [], + dob_year => [1999], + dob_text => ['other info'], + }, + ] + }, + 'OFAC-Consolidated' => { + updated => 50, + content => [{ + names => ['Atom'], + dob_year => [1999], + }, + { + names => ['Donald Trump'], + dob_text => ['circa-1951'], + }, + ] + }, + 'OFAC-SDN' => { + updated => 100, + content => [{ + names => ['Bandit Outlaw'], + place_of_birth => ['ir'], + residence => ['fr', 'us'], + nationality => ['de', 'gb'], + citizen => ['ru'], + postal_code => ['123321'], + national_id => ['321123'], + passport_no => ['asdffdsa'], + }] + }, +}; subtest 'Class constructor' => sub { + clear_redis(); my $validator; like exception { $validator = Data::Validate::Sanctions::Redis->new() }, qr/Redis read connection is missing/, 'Correct error for missing redis-read'; is exception { $validator = Data::Validate::Sanctions::Redis->new(redis_read => $redis) }, undef, 'Successfully created the object with redis-read object'; - - is_deeply $validator->{_data}, undef, 'There is no sanction data'; + is_deeply $validator->{_data}, + { + 'EU-Sanctions' => {}, + 'HMT-Sanctions' => {}, + 'OFAC-Consolidated' => {}, + 'OFAC-SDN' => {}, + }, + 'There is no sanction data'; }; subtest 'Update Data' => sub { + clear_redis(); my $mock_fetcher = Test::MockModule->new('Data::Validate::Sanctions::Fetcher'); my $mock_data = { 'EU-Sanctions' => { updated => 90, content => []}}; $mock_fetcher->redefine(run => sub { return clone($mock_data) }); - - clear_redis(); set_fixed_time(1500); my $validator = Data::Validate::Sanctions::Redis->new(redis_read => $redis); like exception { $validator->update_data(verbose => 1) }, qr/Redis write connection is missing/, 'Redis-write is required for updating'; @@ -124,107 +131,143 @@ subtest 'Update Data' => sub { is_deeply $validator->{_data}, $expected, 'Data is loaded with new update time'; check_redis_content('EU-Sanctions', $mock_data->{'EU-Sanctions'}, 1600, 'Redis content changed by increased update time'); - # don't rewrite if there is an error - just update verified time + # redis is updated with new entries, even if the publish date is the same + $mock_data = { + 'EU-Sanctions' => { + updated => 91, + content => [{ + names => ['TMPA'], + dob_epoch => [], + dob_year => [] + }, + { + names => ['MOHAMMAD EWAZ Mohammad Wali'], + dob_epoch => [], + dob_year => [] + }, + ] + }, + }; + $expected->{'EU-Sanctions'} = clone($mock_data->{'EU-Sanctions'}); set_fixed_time(1700); - $mock_data->{'EU-Sanctions'}->{error} = 'Test error'; + $validator->update_data(); + is_deeply $validator->{_data}, $expected, 'Data is changed with new entries, even with the same update date'; + check_redis_content('EU-Sanctions', $expected->{'EU-Sanctions'}, 1700, 'New entries appear in Redis'); + + # In case of error, content and dates are not changed + set_fixed_time(1800); + $mock_data->{'EU-Sanctions'}->{error} = 'Test error'; $mock_data->{'EU-Sanctions'}->{updated} = 92; - $mock_data->{'EU-Sanctions'}->{content} = [1,2,3]; - warning {$validator->update_data()}, qr/EU-Sanctions list update failed because: Test error/, 'Error warning appears in logs'; + $mock_data->{'EU-Sanctions'}->{content} = [1, 2, 3]; + like Test::Warnings::warning { $validator->update_data() }, qr/EU-Sanctions list update failed because: Test error/, 'Error warning appears in logs'; $expected->{'EU-Sanctions'}->{error} = 'Test error'; is_deeply $validator->{_data}, $expected, 'Data is not changed if there is error'; - $mock_data->{'EU-Sanctions'}->{updated} = 91; # this is not updated because of the error - $mock_data->{'EU-Sanctions'}->{content} = []; - check_redis_content('EU-Sanctions', $mock_data->{'EU-Sanctions'}, 1600, 'Redis content changed by increased update time'); + check_redis_content('EU-Sanctions', $expected->{'EU-Sanctions'}, 1700, 'Redis content is not changed when there is an error'); + + # All sources are updated at the same time + $mock_data = $sample_data; + $expected = clone($mock_data); + set_fixed_time(1900); + $validator->update_data(); + is_deeply $validator->{_data}, $expected, 'Data is populated from all sources'; + check_redis_content('EU-Sanctions', $mock_data->{'EU-Sanctions'}, 1900, 'EU-Sanctions error is removed with the same content and update date'); + check_redis_content('HMT-Sanctions', $mock_data->{'HMT-Sanctions'}, 1900, 'Sanction list is stored in redis'); + check_redis_content('OFAC-Consolidated', $mock_data->{'OFAC-Consolidated'}, 1900, 'Sanction list is stored in redis'); + check_redis_content('OFAC-SDN', $mock_data->{'OFAC-SDN'}, 1900, 'Sanction list is stored in redis'); + + # New objects load the same data + my $validator2 = Data::Validate::Sanctions::Redis->new(redis_read => $redis); + is_deeply $validator2->{_data}, $validator->{_data}, 'New validator object loads the same data from redis'; + + $mock_fetcher->unmock_all; }; -my $validator = Data::Validate::Sanctions::Redis->new( - redis_read => $redis, - redis_write => $redis -); - -ok !$validator->is_sanctioned(qw(sergei ivanov)), "Sergei Ivanov not is_sanctioned"; -ok $validator->is_sanctioned(qw(tmpa)), "now sanction file is tmpa, and tmpa is in test1 list"; -ok !$validator->is_sanctioned("Mohammad reere yuyuy", "wqwqw qqqqq"), "is not in test1 list"; -ok $validator->is_sanctioned("Zaki", "Ahmad"), "is in test1 list - searched without dob"; -ok $validator->is_sanctioned("Zaki", "Ahmad", '1999-01-05'), 'the guy is sanctioned when dob year is matching'; -ok $validator->is_sanctioned("atom", "test", '1999-01-05'), "Match correctly with one world name in sanction list"; - -is_deeply $validator->get_sanctioned_info("Zaki", "Ahmad", '1999-01-05'), - { - 'comment' => undef, - 'list' => 'test1', - 'matched' => 1, - 'matched_args' => { - 'dob_year' => 1999, - 'name' => 'Zaki Izzat Zaki AHMAD' - } - }, - 'Sanction info is correct'; -ok $validator->is_sanctioned("Ahmad", "Ahmad", '1999-10-10'), "is in test1 list"; - -is_deeply $validator->get_sanctioned_info("TMPA"), - { - 'comment' => undef, - 'list' => 'test1', - 'matched' => 1, - 'matched_args' => {'name' => 'TMPA'} - }, - 'Sanction info is correct'; - -is_deeply $validator->get_sanctioned_info('Donald', 'Trump', '1999-01-05'), - { - 'comment' => 'dob raw text: circa-1951', - 'list' => 'test1', - 'matched' => 1, - 'matched_args' => {'name' => 'Donald Trump'} - }, - "When client's name matches a case with dob_text"; - -is_deeply $validator->get_sanctioned_info('Bandit', 'Outlaw', '1999-01-05'), - { - 'comment' => undef, - 'list' => 'test1', - 'matched' => 1, - 'matched_args' => {'name' => 'Bandit Outlaw'} - }, - "If optional ares are empty, only name is matched"; - -my $args = { - first_name => 'Bandit', - last_name => 'Outlaw', - place_of_birth => 'Iran', - residence => 'France', - nationality => 'Germany', - citizen => 'Russia', - postal_code => '123321', - national_id => '321123', - passport_no => 'asdffdsa', +subtest 'Export data' => sub { + my $validator = Data::Validate::Sanctions::Redis->new(redis_read => $redis); + + my $tempfile = Path::Tiny->tempfile; + $validator->export_data($tempfile); + is_deeply YAML::LoadFile($tempfile), $validator->{_data}, 'File content is the same as the sanction list'; }; -is_deeply $validator->get_sanctioned_info($args), - { - 'comment' => undef, - 'list' => 'test1', - 'matched' => 1, - 'matched_args' => { - name => 'Bandit Outlaw', - place_of_birth => 'ir', - residence => 'fr', - nationality => 'de', - citizen => 'ru', +subtest 'get sanctioned info' => sub { + # reload data freshly from the sample data + clear_redis(); + my $mock_fetcher = Test::MockModule->new('Data::Validate::Sanctions::Fetcher'); + $mock_fetcher->redefine(run => sub { return clone($sample_data) }); + my $validator = Data::Validate::Sanctions::Redis->new( + redis_read => $redis, + redis_write => $redis + ); + $validator->update_data(); + + # create a new new validator for sanction checks. No write_redis is needed. + $validator = Data::Validate::Sanctions::Redis->new(redis_read => $redis); + is_deeply $validator->{_data}, $sample_data, 'Sample data is correctly loaded'; + + ok !$validator->is_sanctioned(qw(sergei ivanov)), "Sergei Ivanov not is_sanctioned"; + ok $validator->is_sanctioned(qw(tmpa)), "now sanction file is tmpa, and tmpa is in test1 list"; + ok !$validator->is_sanctioned("Mohammad reere yuyuy", "wqwqw qqqqq"), "is not in test1 list"; + ok $validator->is_sanctioned("Zaki", "Ahmad"), "is in test1 list - searched without dob"; + ok $validator->is_sanctioned("Zaki", "Ahmad", '1999-01-05'), 'the guy is sanctioned when dob year is matching'; + ok $validator->is_sanctioned("atom", "test", '1999-01-05'), "Match correctly with one world name in sanction list"; + + is_deeply $validator->get_sanctioned_info("Zaki", "Ahmad", '1999-01-05'), + { + 'comment' => undef, + 'list' => 'HMT-Sanctions', + 'matched' => 1, + 'matched_args' => { + 'dob_year' => 1999, + 'name' => 'Zaki Izzat Zaki AHMAD' + } + }, + 'Sanction info is correct'; + ok $validator->is_sanctioned("Ahmad", "Ahmad", '1999-10-10'), "is in test1 list"; + + is_deeply $validator->get_sanctioned_info("TMPA"), + { + 'comment' => undef, + 'list' => 'EU-Sanctions', + 'matched' => 1, + 'matched_args' => {'name' => 'TMPA'} + }, + 'Sanction info is correct'; + + is_deeply $validator->get_sanctioned_info('Donald', 'Trump', '1999-01-05'), + { + 'comment' => 'dob raw text: circa-1951', + 'list' => 'OFAC-Consolidated', + 'matched' => 1, + 'matched_args' => {'name' => 'Donald Trump'} + }, + "When client's name matches a case with dob_text"; + + is_deeply $validator->get_sanctioned_info('Bandit', 'Outlaw', '1999-01-05'), + { + 'comment' => undef, + 'list' => 'OFAC-SDN', + 'matched' => 1, + 'matched_args' => {'name' => 'Bandit Outlaw'} + }, + "If optional ares are empty, only name is matched"; + + my $args = { + first_name => 'Bandit', + last_name => 'Outlaw', + place_of_birth => 'Iran', + residence => 'France', + nationality => 'Germany', + citizen => 'Russia', postal_code => '123321', national_id => '321123', passport_no => 'asdffdsa', - } - }, - "All matched fields are returned"; - -for my $field (qw/place_of_birth residence nationality citizen postal_code national_id passport_no/) { - is_deeply $validator->get_sanctioned_info({%$args, $field => 'Israel'}), - {'matched' => 0}, "A single wrong field will result in mismatch - $field"; + }; - my $expected_result = { - 'list' => 'test1', + is_deeply $validator->get_sanctioned_info($args), + { + 'comment' => undef, + 'list' => 'OFAC-SDN', 'matched' => 1, 'matched_args' => { name => 'Bandit Outlaw', @@ -235,13 +278,34 @@ for my $field (qw/place_of_birth residence nationality citizen postal_code natio postal_code => '123321', national_id => '321123', passport_no => 'asdffdsa', + } }, - comment => undef, - }; + "All matched fields are returned"; - delete $expected_result->{matched_args}->{$field}; - is_deeply $validator->get_sanctioned_info({%$args, $field => undef}), $expected_result, "Missing optional args are ignored - $field"; -} + for my $field (qw/place_of_birth residence nationality citizen postal_code national_id passport_no/) { + is_deeply $validator->get_sanctioned_info({%$args, $field => 'Israel'}), + {'matched' => 0}, "A single wrong field will result in mismatch - $field"; + + my $expected_result = { + 'list' => 'OFAC-SDN', + 'matched' => 1, + 'matched_args' => { + name => 'Bandit Outlaw', + place_of_birth => 'ir', + residence => 'fr', + nationality => 'de', + citizen => 'ru', + postal_code => '123321', + national_id => '321123', + passport_no => 'asdffdsa', + }, + comment => undef, + }; + + delete $expected_result->{matched_args}->{$field}; + is_deeply $validator->get_sanctioned_info({%$args, $field => undef}), $expected_result, "Missing optional args are ignored - $field"; + } +}; sub clear_redis { for my $key ($redis->keys('SANCTIONS::*')->@*) { @@ -251,17 +315,19 @@ sub clear_redis { sub check_redis_content { my ($source_name, $config, $verified_time, $comment) = @_; + $comment //= 'Redis content is correct'; my %stored = $redis->hgetall("SANCTIONS::$source_name")->@*; $stored{content} = decode_json_utf8($stored{content}); + is_deeply \%stored, { content => $config->{content} // [], published => $config->{updated} // 0, - error => $config->{error} // '', + error => $config->{error} // '', verified => $verified_time, }, - $comment // "Redis content is correct for $source_name"; + "$comment - $source_name"; } done_testing; From 7c37288acfd1f2667f319e91feb4c3426672eccb Mon Sep 17 00:00:00 2001 From: mat-fs Date: Thu, 15 Sep 2022 10:29:27 +0000 Subject: [PATCH 13/61] [ci] verified time restored from redis --- lib/Data/Validate/Sanctions.pm | 2 +- lib/Data/Validate/Sanctions/Redis.pm | 36 +++++++++++-------- t/sanctions_redis.t | 54 ++++++++++++++-------------- 3 files changed, 50 insertions(+), 42 deletions(-) diff --git a/lib/Data/Validate/Sanctions.pm b/lib/Data/Validate/Sanctions.pm index 58595457..76b1181c 100644 --- a/lib/Data/Validate/Sanctions.pm +++ b/lib/Data/Validate/Sanctions.pm @@ -18,7 +18,7 @@ use Data::Compare; use List::Util qw(any uniq max min); use Locale::Country; use Text::Trim qw(trim); -use Clone qw(clone); +use Clone qw(clone); our $VERSION = '0.14'; diff --git a/lib/Data/Validate/Sanctions/Redis.pm b/lib/Data/Validate/Sanctions/Redis.pm index 873f538a..e8ebb44b 100644 --- a/lib/Data/Validate/Sanctions/Redis.pm +++ b/lib/Data/Validate/Sanctions/Redis.pm @@ -10,6 +10,7 @@ use Scalar::Util qw(blessed); use YAML::XS qw/DumpFile/; use List::Util qw(max); use JSON::MaybeUTF8 qw(encode_json_utf8 decode_json_utf8); +use Syntax::Keyword::Try; # VERSION @@ -68,12 +69,20 @@ sub _load_data { my $last_time = $self->{last_time}; for my $source ($self->{sources}->@*) { - my $updated = $self->{redis_read}->hget("SANCTIONS::$source", 'published') // 0; - next if $updated <= ($self->{_data}->{$source}->{updated} // 0); - - $self->{_data}->{$source}->{content} = decode_json_utf8($self->{redis_read}->hget("SANCTIONS::$source", 'content')); - $self->{_data}->{$source}->{updated} = $updated; - $last_time = $updated if $updated > $last_time; + try { + my $updated = $self->{redis_read}->hget("SANCTIONS::$source", 'published') // 0; + next if $updated <= ($self->{_data}->{$source}->{updated} // 0); + + $self->{_data}->{$source}->{content} = decode_json_utf8($self->{redis_read}->hget("SANCTIONS::$source", 'content')); + $self->{_data}->{$source}->{verified} = $self->{redis_read}->hget("SANCTIONS::$source", 'verified'); + $self->{_data}->{$source}->{updated} = $updated; + $last_time = $updated if $updated > $last_time; + } catch { + $self->{_data}->{$source}->{content} = []; + $self->{_data}->{$source}->{updated} = 0; + $self->{_data}->{$source}->{verified} = 0; + $self->{_data}->{$source}->{error} = "Failed to load from Redis: $@"; + } } $self->{last_time} = $last_time; @@ -94,14 +103,15 @@ sub _save_data { my $self = shift; die 'Redis write connection is missing' unless $self->{redis_write}; - my $now = time; + for my $source ($self->{sources}->@*) { + $self->{_data}->{$source}->{verified} = time; $self->{redis_write}->hmset( "SANCTIONS::$source", 'published' => $self->{_data}->{$source}->{updated} // 0, 'content' => encode_json_utf8($self->{_data}->{$source}->{content} // []), - ($self->{_data}->{$source}->{error} ? () : ('verified' => $now)), - 'error' => $self->{_data}->{$source}->{error} // '' + 'verified' => $self->{_data}->{$source}->{verified}, + 'error' => $self->{_data}->{$source}->{error} // '' ); } @@ -112,12 +122,10 @@ sub _default_sanction_file { die 'Not applicable'; } -sub export_data { - my ($self, $path) = @_; +sub data { + my ($self) = @_; - $self->_load_data(); - - DumpFile($path, $self->{_data}); + return $self->{_data}; } 1; diff --git a/t/sanctions_redis.t b/t/sanctions_redis.t index 7830867f..3d09d372 100644 --- a/t/sanctions_redis.t +++ b/t/sanctions_redis.t @@ -4,13 +4,13 @@ use warnings; use Class::Unload; use YAML; use File::Slurp; -use Path::Tiny qw(tempfile); +use Path::Tiny qw(tempfile); use Test::Warnings; use Test::More; use Test::Fatal; use Test::MockModule; use Test::RedisServer; -use Test::MockTime qw(set_fixed_time); +use Test::MockTime qw(set_fixed_time restore_time); use RedisDB; use JSON::MaybeUTF8 qw(decode_json_utf8); use Clone qw(clone); @@ -78,9 +78,8 @@ subtest 'Class constructor' => sub { like exception { $validator = Data::Validate::Sanctions::Redis->new() }, qr/Redis read connection is missing/, 'Correct error for missing redis-read'; - is exception { $validator = Data::Validate::Sanctions::Redis->new(redis_read => $redis) }, undef, - 'Successfully created the object with redis-read object'; - is_deeply $validator->{_data}, + ok $validator = Data::Validate::Sanctions::Redis->new(redis_read => $redis), 'Successfully created the object with redis-read object'; + is_deeply $validator->data, { 'EU-Sanctions' => {}, 'HMT-Sanctions' => {}, @@ -110,14 +109,15 @@ subtest 'Update Data' => sub { $validator->update_data(); my $expected = { 'EU-Sanctions' => { - content => [], - updated => 90 + content => [], + updated => 90, + verified => 1500, }, - 'HMT-Sanctions' => {}, - 'OFAC-Consolidated' => {}, - 'OFAC-SDN' => {}, + 'HMT-Sanctions' => {verified => 1500}, + 'OFAC-Consolidated' => {verified => 1500}, + 'OFAC-SDN' => {verified => 1500}, }; - is_deeply $validator->{_data}, $expected, 'Data is correctly loaded'; + is_deeply $validator->data, $expected, 'Data is correctly loaded'; check_redis_content('EU-Sanctions', $mock_data->{'EU-Sanctions'}, 1500); check_redis_content('HMT-Sanctions', {}, 1500); check_redis_content('OFAC-Consolidated', {}, 1500); @@ -128,7 +128,8 @@ subtest 'Update Data' => sub { $mock_data->{'EU-Sanctions'}->{updated} = 91; $validator->update_data(); $expected->{'EU-Sanctions'}->{updated} = 91; - is_deeply $validator->{_data}, $expected, 'Data is loaded with new update time'; + $expected->{$_}->{verified} = 1600 for keys %$expected; + is_deeply $validator->data, $expected, 'Data is loaded with new update time'; check_redis_content('EU-Sanctions', $mock_data->{'EU-Sanctions'}, 1600, 'Redis content changed by increased update time'); # redis is updated with new entries, even if the publish date is the same @@ -151,7 +152,8 @@ subtest 'Update Data' => sub { $expected->{'EU-Sanctions'} = clone($mock_data->{'EU-Sanctions'}); set_fixed_time(1700); $validator->update_data(); - is_deeply $validator->{_data}, $expected, 'Data is changed with new entries, even with the same update date'; + $expected->{$_}->{verified} = 1700 for keys %$expected; + is_deeply $validator->data, $expected, 'Data is changed with new entries, even with the same update date'; check_redis_content('EU-Sanctions', $expected->{'EU-Sanctions'}, 1700, 'New entries appear in Redis'); # In case of error, content and dates are not changed @@ -159,17 +161,20 @@ subtest 'Update Data' => sub { $mock_data->{'EU-Sanctions'}->{error} = 'Test error'; $mock_data->{'EU-Sanctions'}->{updated} = 92; $mock_data->{'EU-Sanctions'}->{content} = [1, 2, 3]; - like Test::Warnings::warning { $validator->update_data() }, qr/EU-Sanctions list update failed because: Test error/, 'Error warning appears in logs'; + like Test::Warnings::warning { $validator->update_data() }, qr/EU-Sanctions list update failed because: Test error/, + 'Error warning appears in logs'; $expected->{'EU-Sanctions'}->{error} = 'Test error'; - is_deeply $validator->{_data}, $expected, 'Data is not changed if there is error'; - check_redis_content('EU-Sanctions', $expected->{'EU-Sanctions'}, 1700, 'Redis content is not changed when there is an error'); + $expected->{$_}->{verified} = 1800 for keys %$expected; + is_deeply $validator->data, $expected, 'Data is not changed if there is error'; + check_redis_content('EU-Sanctions', $expected->{'EU-Sanctions'}, 1800, 'Redis content is not changed when there is an error'); # All sources are updated at the same time $mock_data = $sample_data; $expected = clone($mock_data); set_fixed_time(1900); $validator->update_data(); - is_deeply $validator->{_data}, $expected, 'Data is populated from all sources'; + $expected->{$_}->{verified} = 1900 for keys %$expected; + is_deeply $validator->data, $expected, 'Data is populated from all sources'; check_redis_content('EU-Sanctions', $mock_data->{'EU-Sanctions'}, 1900, 'EU-Sanctions error is removed with the same content and update date'); check_redis_content('HMT-Sanctions', $mock_data->{'HMT-Sanctions'}, 1900, 'Sanction list is stored in redis'); check_redis_content('OFAC-Consolidated', $mock_data->{'OFAC-Consolidated'}, 1900, 'Sanction list is stored in redis'); @@ -177,22 +182,16 @@ subtest 'Update Data' => sub { # New objects load the same data my $validator2 = Data::Validate::Sanctions::Redis->new(redis_read => $redis); - is_deeply $validator2->{_data}, $validator->{_data}, 'New validator object loads the same data from redis'; + is_deeply $validator2->data, $validator->data, 'New validator object loads the same data from redis'; + restore_time(); $mock_fetcher->unmock_all; }; -subtest 'Export data' => sub { - my $validator = Data::Validate::Sanctions::Redis->new(redis_read => $redis); - - my $tempfile = Path::Tiny->tempfile; - $validator->export_data($tempfile); - is_deeply YAML::LoadFile($tempfile), $validator->{_data}, 'File content is the same as the sanction list'; -}; - subtest 'get sanctioned info' => sub { # reload data freshly from the sample data clear_redis(); + set_fixed_time(1000); my $mock_fetcher = Test::MockModule->new('Data::Validate::Sanctions::Fetcher'); $mock_fetcher->redefine(run => sub { return clone($sample_data) }); my $validator = Data::Validate::Sanctions::Redis->new( @@ -203,7 +202,8 @@ subtest 'get sanctioned info' => sub { # create a new new validator for sanction checks. No write_redis is needed. $validator = Data::Validate::Sanctions::Redis->new(redis_read => $redis); - is_deeply $validator->{_data}, $sample_data, 'Sample data is correctly loaded'; + $sample_data->{$_}->{verified} = 1000 for keys %$sample_data; + is_deeply $validator->data, $sample_data, 'Sample data is correctly loaded'; ok !$validator->is_sanctioned(qw(sergei ivanov)), "Sergei Ivanov not is_sanctioned"; ok $validator->is_sanctioned(qw(tmpa)), "now sanction file is tmpa, and tmpa is in test1 list"; From f0b53a12589627b78fa1eafab49d586d4491ddb7 Mon Sep 17 00:00:00 2001 From: mat-fs Date: Thu, 15 Sep 2022 11:33:49 +0000 Subject: [PATCH 14/61] cleanup + load verified data --- lib/Data/Validate/Sanctions/Redis.pm | 11 +++++------ t/sanctions_redis.t | 8 ++++---- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/lib/Data/Validate/Sanctions/Redis.pm b/lib/Data/Validate/Sanctions/Redis.pm index e8ebb44b..bda45bd5 100644 --- a/lib/Data/Validate/Sanctions/Redis.pm +++ b/lib/Data/Validate/Sanctions/Redis.pm @@ -7,7 +7,6 @@ use parent 'Data::Validate::Sanctions'; use Data::Validate::Sanctions::Fetcher; use Scalar::Util qw(blessed); -use YAML::XS qw/DumpFile/; use List::Util qw(max); use JSON::MaybeUTF8 qw(encode_json_utf8 decode_json_utf8); use Syntax::Keyword::Try; @@ -70,7 +69,7 @@ sub _load_data { my $last_time = $self->{last_time}; for my $source ($self->{sources}->@*) { try { - my $updated = $self->{redis_read}->hget("SANCTIONS::$source", 'published') // 0; + my $updated = $self->{redis_read}->hget("SANCTIONS::$source", 'updated') // 0; next if $updated <= ($self->{_data}->{$source}->{updated} // 0); $self->{_data}->{$source}->{content} = decode_json_utf8($self->{redis_read}->hget("SANCTIONS::$source", 'content')); @@ -108,10 +107,10 @@ sub _save_data { $self->{_data}->{$source}->{verified} = time; $self->{redis_write}->hmset( "SANCTIONS::$source", - 'published' => $self->{_data}->{$source}->{updated} // 0, - 'content' => encode_json_utf8($self->{_data}->{$source}->{content} // []), - 'verified' => $self->{_data}->{$source}->{verified}, - 'error' => $self->{_data}->{$source}->{error} // '' + 'updated' => $self->{_data}->{$source}->{updated} // 0, + 'content' => encode_json_utf8($self->{_data}->{$source}->{content} // []), + 'verified' => $self->{_data}->{$source}->{verified}, + 'error' => $self->{_data}->{$source}->{error} // '' ); } diff --git a/t/sanctions_redis.t b/t/sanctions_redis.t index 3d09d372..06b119a3 100644 --- a/t/sanctions_redis.t +++ b/t/sanctions_redis.t @@ -322,10 +322,10 @@ sub check_redis_content { is_deeply \%stored, { - content => $config->{content} // [], - published => $config->{updated} // 0, - error => $config->{error} // '', - verified => $verified_time, + content => $config->{content} // [], + updated => $config->{updated} // 0, + error => $config->{error} // '', + verified => $verified_time, }, "$comment - $source_name"; } From 0604ffe3e80d21fa4e3299a4e4b120196ed91afd Mon Sep 17 00:00:00 2001 From: mat-fs Date: Thu, 15 Sep 2022 12:41:48 +0000 Subject: [PATCH 15/61] trigger tests [ci] 2022-09-15 12:41:48 From 2e8a508755eea3915db6dae23826aae5870ea1a8 Mon Sep 17 00:00:00 2001 From: mat-fs Date: Sun, 2 Oct 2022 12:42:17 +0000 Subject: [PATCH 16/61] [ci] just one redis arg + fix the failure for the missing sanction file --- lib/Data/Validate/Sanctions.pm | 14 ++-- lib/Data/Validate/Sanctions/Redis.pm | 107 ++++++++++++++++++--------- t/sanctions_redis.t | 23 ++---- 3 files changed, 87 insertions(+), 57 deletions(-) diff --git a/lib/Data/Validate/Sanctions.pm b/lib/Data/Validate/Sanctions.pm index 76b1181c..dd00aba1 100644 --- a/lib/Data/Validate/Sanctions.pm +++ b/lib/Data/Validate/Sanctions.pm @@ -22,7 +22,7 @@ use Clone qw(clone); our $VERSION = '0.14'; -my $sanction_file = _default_sanction_file(); +my $sanction_file; my $instance; # for OO @@ -98,6 +98,7 @@ sub set_sanction_file { ## no critic (RequireArgUnpacking) } sub get_sanction_file { + $sanction_file //= _default_sanction_file(); return $instance ? $instance->{sanction_file} : $sanction_file; } @@ -184,9 +185,12 @@ It returns a hash-ref containg the following data: =over 4 =item - matched: 1 if a match was found; 0 otherwise - list: the source for the matched entry, - matched_args: a name-value hash-ref of the similar arguments, - comment: additional comments if necessary, + +=item - list: the source for the matched entry, + +=item - matched_args: a name-value hash-ref of the similar arguments, + +=item - comment: additional comments if necessary, =back @@ -195,7 +199,7 @@ It returns a hash-ref containg the following data: sub get_sanctioned_info { ## no critic (RequireArgUnpacking) my $self = blessed($_[0]) ? shift : $instance; unless ($self) { - $instance = __PACKAGE__->new(sanction_file => $sanction_file); + $instance = __PACKAGE__->new(sanction_file => get_sanction_file()); $self = $instance; } diff --git a/lib/Data/Validate/Sanctions/Redis.pm b/lib/Data/Validate/Sanctions/Redis.pm index bda45bd5..75ffecb7 100644 --- a/lib/Data/Validate/Sanctions/Redis.pm +++ b/lib/Data/Validate/Sanctions/Redis.pm @@ -17,8 +17,7 @@ sub new { my ($class, %args) = @_; my $self = {}; - $self->{redis_read} = $args{redis_read} or die 'Redis read connection is missing'; - $self->{redis_write} = $args{redis_write}; + $self->{redis} = $args{redis} or die 'Redis connection is missing'; $self->{sources} = [keys Data::Validate::Sanctions::Fetcher::config(eu_token => 'dummy')->%*]; @@ -69,11 +68,11 @@ sub _load_data { my $last_time = $self->{last_time}; for my $source ($self->{sources}->@*) { try { - my $updated = $self->{redis_read}->hget("SANCTIONS::$source", 'updated') // 0; + my $updated = $self->{redis}->hget("SANCTIONS::$source", 'updated') // 0; next if $updated <= ($self->{_data}->{$source}->{updated} // 0); - $self->{_data}->{$source}->{content} = decode_json_utf8($self->{redis_read}->hget("SANCTIONS::$source", 'content')); - $self->{_data}->{$source}->{verified} = $self->{redis_read}->hget("SANCTIONS::$source", 'verified'); + $self->{_data}->{$source}->{content} = decode_json_utf8($self->{redis}->hget("SANCTIONS::$source", 'content')); + $self->{_data}->{$source}->{verified} = $self->{redis}->hget("SANCTIONS::$source", 'verified'); $self->{_data}->{$source}->{updated} = $updated; $last_time = $updated if $updated > $last_time; } catch { @@ -101,11 +100,9 @@ sub _load_data { sub _save_data { my $self = shift; - die 'Redis write connection is missing' unless $self->{redis_write}; - for my $source ($self->{sources}->@*) { $self->{_data}->{$source}->{verified} = time; - $self->{redis_write}->hmset( + $self->{redis}->hmset( "SANCTIONS::$source", 'updated' => $self->{_data}->{$source}->{updated} // 0, 'content' => encode_json_utf8($self->{_data}->{$source}->{content} // []), @@ -134,62 +131,102 @@ __END__ =head1 NAME -Data::Validate::Sanctions::Redis - An extention of L that stores sanction data in redis rather than a local file. +Data::Validate::Sanctions::Redis - An extention of L that stores sanction data in redis. =head1 SYNOPSIS - # it only works with OO calls + use Data::Validate::Sanctions::Redis; - my $validator = Data::Validate::Sanctions->new(redis_read => $redis_read, redis_write => $redis_write); + # to validate clients + my $validator = Data::Validate::Sanctions->new(redis => $redis_read); + # with their name print 'BAD' if $validator->is_sanctioned("$last_name $first_name"); + # or with more profile data + print 'BAD' if $validator->get_sanctioned_info(first_name => $first_name, last_name => $last_name, date_of_birth => $date_of_birth)->{matched}; - # In order to update the sanction dataset: - my $validator = Data::Validate::Sanctions->new(redis_read => $redis_read, redis_write => $redis_write); - - # eu_token or eu_url is required + # to update the sanction dataset (needs redis write access) + my $validator = Data::Validate::Sanctions->new(redis => $redis_write); $validator->update_data(eu_token => $token); =head1 DESCRIPTION Data::Validate::Sanctions::Redis is a simple validitor to validate a name against sanctions lists. -For more details about the sanction sources please refer to L. +For more details about the sanction sources please refer to the parent module L. =head1 METHODS =head2 new -Create the object, and set the redis reader and writer objects: - - my $validator = Data::Validate::Sanctions::Redis->new(redis_read => $redis_read, redis_write => $redis_write); +Create the object with the redis object: -The validator is a singleton object; so it will always return the same object if it's called for multiple times in a process. + my $validator = Data::Validate::Sanctions::Redis->new(redis => $redis); =head2 is_sanctioned - is_sanctioned($last_name, $first_name); - is_sanctioned($first_name, $last_name); - is_sanctioned("$last_name $first_name"); +Checks if the input profile info matches a sanctioned entity. +The arguments are the same as those of B. -when one string is passed, please be sure last_name is before first_name. +It returns 1 if a match is found, otherwise 0. -or you can pass first_name, last_name (last_name, first_name), we'll check both "$last_name $first_name" and "$first_name $last_name". +=cut + +=head2 get_sanctioned_info -retrun 1 if match is found and 0 if match is not found. +Tries to find a match a sanction entry matching the input profile args. +It takes arguments in two forms. In the new API, it takes a hashref containing the following named arguments: -It will remove all non-alpha chars and compare with the list we have. +=over 4 -=head2 get_sanctioned_info +=item * first_name: first name + +=item * last_name: last name + +=item * date_of_birth: (optional) date of birth as a string or epoch + +=item * place_of_birth: (optional) place of birth as a country name or code + +=item * residence: (optional) name or code of the country of residence + +=item * nationality: (optional) name or code of the country of nationality + +=item * citizen: (optional) name or code of the country of citizenship + +=item * postal_code: (optional) postal/zip code - my $result = $validator->get_sanctioned_info($last_name, $first_name, $date_of_birth); - print 'match: ', $result->{matched_args}->{name}, ' on list ', $result->{list} if $result->{matched}; +=item * national_id: (optional) national ID number -return hashref with keys: - B 1 or 0, depends if name has matched - B name of list matched (present only if matched) - B The list of arguments matched (name, date of birth, residence, etc.) +=item * passport_no: (oiptonal) passort number -It will remove all non-alpha chars and compare with the list we have. +=back + +For backward compatibility it also supports the old API, taking the following args: + +=over 4 + +=item * first_name: first name + +=item * last_name: last name + +=item * date_of_birth: (optional) date of birth as a string or epoch + +=back + +It returns a hash-ref containg the following data: + +=over 4 + +=item - matched: 1 if a match was found; 0 otherwise + +=item - list: the source for the matched entry, + +=item - matched_args: a name-value hash-ref of the similar arguments, + +=item - comment: additional comments if necessary, + +=back + +=cut =head2 update_data diff --git a/t/sanctions_redis.t b/t/sanctions_redis.t index 06b119a3..69ca5b99 100644 --- a/t/sanctions_redis.t +++ b/t/sanctions_redis.t @@ -75,10 +75,9 @@ my $sample_data = { subtest 'Class constructor' => sub { clear_redis(); my $validator; - like exception { $validator = Data::Validate::Sanctions::Redis->new() }, qr/Redis read connection is missing/, - 'Correct error for missing redis-read'; + like exception { $validator = Data::Validate::Sanctions::Redis->new() }, qr/Redis connection is missing/, 'Correct error for missing redis'; - ok $validator = Data::Validate::Sanctions::Redis->new(redis_read => $redis), 'Successfully created the object with redis-read object'; + ok $validator = Data::Validate::Sanctions::Redis->new(redis => $redis), 'Successfully created the object with redis object'; is_deeply $validator->data, { 'EU-Sanctions' => {}, @@ -98,14 +97,7 @@ subtest 'Update Data' => sub { content => []}}; $mock_fetcher->redefine(run => sub { return clone($mock_data) }); set_fixed_time(1500); - my $validator = Data::Validate::Sanctions::Redis->new(redis_read => $redis); - like exception { $validator->update_data(verbose => 1) }, qr/Redis write connection is missing/, 'Redis-write is required for updating'; - - # load and save into redis - $validator = Data::Validate::Sanctions::Redis->new( - redis_read => $redis, - redis_write => $redis - ); + my $validator = Data::Validate::Sanctions::Redis->new(redis => $redis); $validator->update_data(); my $expected = { 'EU-Sanctions' => { @@ -181,7 +173,7 @@ subtest 'Update Data' => sub { check_redis_content('OFAC-SDN', $mock_data->{'OFAC-SDN'}, 1900, 'Sanction list is stored in redis'); # New objects load the same data - my $validator2 = Data::Validate::Sanctions::Redis->new(redis_read => $redis); + my $validator2 = Data::Validate::Sanctions::Redis->new(redis => $redis); is_deeply $validator2->data, $validator->data, 'New validator object loads the same data from redis'; restore_time(); @@ -194,14 +186,11 @@ subtest 'get sanctioned info' => sub { set_fixed_time(1000); my $mock_fetcher = Test::MockModule->new('Data::Validate::Sanctions::Fetcher'); $mock_fetcher->redefine(run => sub { return clone($sample_data) }); - my $validator = Data::Validate::Sanctions::Redis->new( - redis_read => $redis, - redis_write => $redis - ); + my $validator = Data::Validate::Sanctions::Redis->new(redis => $redis); $validator->update_data(); # create a new new validator for sanction checks. No write_redis is needed. - $validator = Data::Validate::Sanctions::Redis->new(redis_read => $redis); + $validator = Data::Validate::Sanctions::Redis->new(redis => $redis); $sample_data->{$_}->{verified} = 1000 for keys %$sample_data; is_deeply $validator->data, $sample_data, 'Sample data is correctly loaded'; From e4832986edf187e005be2ed928385e743cef3f8c Mon Sep 17 00:00:00 2001 From: mat-fs Date: Sun, 2 Oct 2022 13:27:02 +0000 Subject: [PATCH 17/61] [ci] test failed --- t/05_basic.t | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/t/05_basic.t b/t/05_basic.t index abf99e81..ca86cf17 100644 --- a/t/05_basic.t +++ b/t/05_basic.t @@ -9,7 +9,7 @@ use Test::Exception; use Test::Warnings; use Test::More; -$ENV{SANCTION_FILE} = "../share/sanctions.yml"; +$ENV{SANCTION_FILE} = "./share/sanctions.yml"; ok Data::Validate::Sanctions::is_sanctioned('NEVEROV', 'Sergei Ivanovich', -253411200), "Sergei Ivanov is_sanctioned for sure"; ok Data::Validate::Sanctions::is_sanctioned('NEVEROV', 'Sergei Ivanovich'), "Sergei Ivanov is matched even without a birth date"; From 34804c51a0d09689849013aa2a87c64d8d6b3181 Mon Sep 17 00:00:00 2001 From: mat-fs Date: Sun, 2 Oct 2022 17:06:38 +0000 Subject: [PATCH 18/61] [ci] code cleanup --- lib/Data/Validate/Sanctions/Redis.pm | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/lib/Data/Validate/Sanctions/Redis.pm b/lib/Data/Validate/Sanctions/Redis.pm index 75ffecb7..7da0a485 100644 --- a/lib/Data/Validate/Sanctions/Redis.pm +++ b/lib/Data/Validate/Sanctions/Redis.pm @@ -68,11 +68,12 @@ sub _load_data { my $last_time = $self->{last_time}; for my $source ($self->{sources}->@*) { try { - my $updated = $self->{redis}->hget("SANCTIONS::$source", 'updated') // 0; + my ($content, $verified, $updated, $error) = $self->{redis}->hmget("SANCTIONS::$source", qw/content verified updated error/)->@*; + $updated //= 0; next if $updated <= ($self->{_data}->{$source}->{updated} // 0); - $self->{_data}->{$source}->{content} = decode_json_utf8($self->{redis}->hget("SANCTIONS::$source", 'content')); - $self->{_data}->{$source}->{verified} = $self->{redis}->hget("SANCTIONS::$source", 'verified'); + $self->{_data}->{$source}->{content} = decode_json_utf8($content); + $self->{_data}->{$source}->{verified} = $verified; $self->{_data}->{$source}->{updated} = $updated; $last_time = $updated if $updated > $last_time; } catch { @@ -102,12 +103,13 @@ sub _save_data { for my $source ($self->{sources}->@*) { $self->{_data}->{$source}->{verified} = time; + $self->{redis}->hmset( "SANCTIONS::$source", - 'updated' => $self->{_data}->{$source}->{updated} // 0, - 'content' => encode_json_utf8($self->{_data}->{$source}->{content} // []), - 'verified' => $self->{_data}->{$source}->{verified}, - 'error' => $self->{_data}->{$source}->{error} // '' + updated => $self->{_data}->{$source}->{updated} // 0, + content => encode_json_utf8($self->{_data}->{$source}->{content} // []), + verified => $self->{_data}->{$source}->{verified}, + error => $self->{_data}->{$source}->{error} // '' ); } From 59a14353b143a5682627ba9cca46ba5f26bc263a Mon Sep 17 00:00:00 2001 From: mat-fs Date: Sun, 2 Oct 2022 17:31:23 +0000 Subject: [PATCH 19/61] [ci] export data added --- lib/Data/Validate/Sanctions/Redis.pm | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/lib/Data/Validate/Sanctions/Redis.pm b/lib/Data/Validate/Sanctions/Redis.pm index 7da0a485..09fb32d8 100644 --- a/lib/Data/Validate/Sanctions/Redis.pm +++ b/lib/Data/Validate/Sanctions/Redis.pm @@ -9,6 +9,7 @@ use Data::Validate::Sanctions::Fetcher; use Scalar::Util qw(blessed); use List::Util qw(max); use JSON::MaybeUTF8 qw(encode_json_utf8 decode_json_utf8); +use YAML::XS qw(DumpFile); use Syntax::Keyword::Try; # VERSION @@ -126,6 +127,12 @@ sub data { return $self->{_data}; } +sub export_data { + my ($self, $path) = @_; + + DumpFile($path, $self->{_data}); +} + 1; __END__ From 3a3e7462634b39206f92aa44dc18285e2f87df72 Mon Sep 17 00:00:00 2001 From: mat-fs Date: Mon, 3 Oct 2022 10:39:05 +0000 Subject: [PATCH 20/61] trigger tests [ci] 2022-10-03 10:39:05 From 06ee21596831088b4a7e2ce89a9680fa57cb7fce Mon Sep 17 00:00:00 2001 From: mat-fs Date: Mon, 3 Oct 2022 13:33:21 +0000 Subject: [PATCH 21/61] [ci] factory pattern --- lib/Data/Validate/Sanctions.pm | 17 ++++++++++++++ lib/Data/Validate/Sanctions/Redis.pm | 33 ++++++++++++---------------- t/15_oo.t | 10 +++++++++ t/sanctions_redis.t | 10 ++++----- 4 files changed, 46 insertions(+), 24 deletions(-) diff --git a/lib/Data/Validate/Sanctions.pm b/lib/Data/Validate/Sanctions.pm index dd00aba1..aa707263 100644 --- a/lib/Data/Validate/Sanctions.pm +++ b/lib/Data/Validate/Sanctions.pm @@ -9,6 +9,7 @@ our @EXPORT_OK = qw/is_sanctioned set_sanction_file get_sanction_file/; use Carp; use Data::Validate::Sanctions::Fetcher; +use Data::Validate::Sanctions::Redis; use File::stat; use File::ShareDir; use YAML::XS qw/DumpFile LoadFile/; @@ -29,7 +30,12 @@ my $instance; sub new { ## no critic (RequireArgUnpacking) my ($class, %args) = @_; + my $storage = delete $args{storage} // ''; + + return Data::Validate::Sanctions::Redis->new(%args) if $storage eq 'redis'; + my $self = {}; + $self->{sanction_file} = $args{sanction_file} // _default_sanction_file(); $self->{args} = {%args}; @@ -407,6 +413,12 @@ sub _name_matches { return 0; } +sub export_data { + my ($self, $path) = @_; + + DumpFile($path, $self->{_data}); +} + 1; __END__ @@ -502,6 +514,11 @@ set sanction_file which is used by L (procedure-oriented) Pass in the client's name and sanctioned individual's name to see if they are similar or not + +=head2 export_data + +Exports the sanction lists to a local file in YAML format. + =head1 AUTHOR Binary.com Efayland@binary.comE diff --git a/lib/Data/Validate/Sanctions/Redis.pm b/lib/Data/Validate/Sanctions/Redis.pm index 09fb32d8..d693539e 100644 --- a/lib/Data/Validate/Sanctions/Redis.pm +++ b/lib/Data/Validate/Sanctions/Redis.pm @@ -18,7 +18,8 @@ sub new { my ($class, %args) = @_; my $self = {}; - $self->{redis} = $args{redis} or die 'Redis connection is missing'; + + $self->{connection} = $args{connection} or die 'Redis connection is missing'; $self->{sources} = [keys Data::Validate::Sanctions::Fetcher::config(eu_token => 'dummy')->%*]; @@ -69,7 +70,7 @@ sub _load_data { my $last_time = $self->{last_time}; for my $source ($self->{sources}->@*) { try { - my ($content, $verified, $updated, $error) = $self->{redis}->hmget("SANCTIONS::$source", qw/content verified updated error/)->@*; + my ($content, $verified, $updated, $error) = $self->{connection}->hmget("SANCTIONS::$source", qw/content verified updated error/)->@*; $updated //= 0; next if $updated <= ($self->{_data}->{$source}->{updated} // 0); @@ -105,7 +106,7 @@ sub _save_data { for my $source ($self->{sources}->@*) { $self->{_data}->{$source}->{verified} = time; - $self->{redis}->hmset( + $self->{connection}->hmset( "SANCTIONS::$source", updated => $self->{_data}->{$source}->{updated} // 0, content => encode_json_utf8($self->{_data}->{$source}->{content} // []), @@ -127,12 +128,6 @@ sub data { return $self->{_data}; } -sub export_data { - my ($self, $path) = @_; - - DumpFile($path, $self->{_data}); -} - 1; __END__ @@ -146,18 +141,22 @@ Data::Validate::Sanctions::Redis - An extention of Lnew(redis => $redis_read); - # with their name + my $validator = Data::Validate::Sanctions::Redis->new(connection => $redis_read); + + # to validate clients by their name print 'BAD' if $validator->is_sanctioned("$last_name $first_name"); - # or with more profile data + # or by more profile data print 'BAD' if $validator->get_sanctioned_info(first_name => $first_name, last_name => $last_name, date_of_birth => $date_of_birth)->{matched}; # to update the sanction dataset (needs redis write access) - my $validator = Data::Validate::Sanctions->new(redis => $redis_write); + my $validator = Data::Validate::Sanctions::Redis->new(connection => $redis_write); $validator->update_data(eu_token => $token); + # create object from the parent (factory) class + $validator = Data::Validate::Sanctions->new(storage => 'redis', connection => $redis_write); + + =head1 DESCRIPTION Data::Validate::Sanctions::Redis is a simple validitor to validate a name against sanctions lists. @@ -169,7 +168,7 @@ For more details about the sanction sources please refer to the parent module L< Create the object with the redis object: - my $validator = Data::Validate::Sanctions::Redis->new(redis => $redis); + my $validator = Data::Validate::Sanctions::Redis->new(connection => $redis); =head2 is_sanctioned @@ -250,10 +249,6 @@ If argument is provided - return timestamp of when that list was updated. Pass in the client's name and sanctioned individual's name to see if they are similar or not -=head2 export_data - -Exports the sanction lists to a local file in YAML format. - =head1 AUTHOR Binary.com Efayland@binary.comE diff --git a/t/15_oo.t b/t/15_oo.t index e9de43f4..0a9a5a96 100644 --- a/t/15_oo.t +++ b/t/15_oo.t @@ -5,6 +5,8 @@ use YAML::XS qw(Dump); use Path::Tiny qw(tempfile); use Test::Warnings; use Test::More; +use Test::RedisServer; +use RedisDB; my $validator = Data::Validate::Sanctions->new; @@ -193,4 +195,12 @@ ok $validator->is_sanctioned(qw(tmpb)), "get sanction file from ENV"; $validator = Data::Validate::Sanctions->new(sanction_file => "$tmpa"); ok $validator->is_sanctioned(qw(tmpa)), "get sanction file from args"; +subtest 'Subclass factory' => sub { + my $redis_server = Test::RedisServer->new(); + my $redis = RedisDB->new($redis_server->connect_info); + + my $validator = Data::Validate::Sanctions->new(storage => 'redis', connection => $redis); + is ref($validator), 'Data::Validate::Sanctions::Redis', 'A validator with redis storage is created'; +}; + done_testing; diff --git a/t/sanctions_redis.t b/t/sanctions_redis.t index 69ca5b99..69ee4adc 100644 --- a/t/sanctions_redis.t +++ b/t/sanctions_redis.t @@ -77,7 +77,7 @@ subtest 'Class constructor' => sub { my $validator; like exception { $validator = Data::Validate::Sanctions::Redis->new() }, qr/Redis connection is missing/, 'Correct error for missing redis'; - ok $validator = Data::Validate::Sanctions::Redis->new(redis => $redis), 'Successfully created the object with redis object'; + ok $validator = Data::Validate::Sanctions::Redis->new(connection => $redis), 'Successfully created the object with redis object'; is_deeply $validator->data, { 'EU-Sanctions' => {}, @@ -97,7 +97,7 @@ subtest 'Update Data' => sub { content => []}}; $mock_fetcher->redefine(run => sub { return clone($mock_data) }); set_fixed_time(1500); - my $validator = Data::Validate::Sanctions::Redis->new(redis => $redis); + my $validator = Data::Validate::Sanctions::Redis->new(connection => $redis); $validator->update_data(); my $expected = { 'EU-Sanctions' => { @@ -173,7 +173,7 @@ subtest 'Update Data' => sub { check_redis_content('OFAC-SDN', $mock_data->{'OFAC-SDN'}, 1900, 'Sanction list is stored in redis'); # New objects load the same data - my $validator2 = Data::Validate::Sanctions::Redis->new(redis => $redis); + my $validator2 = Data::Validate::Sanctions::Redis->new(connection => $redis); is_deeply $validator2->data, $validator->data, 'New validator object loads the same data from redis'; restore_time(); @@ -186,11 +186,11 @@ subtest 'get sanctioned info' => sub { set_fixed_time(1000); my $mock_fetcher = Test::MockModule->new('Data::Validate::Sanctions::Fetcher'); $mock_fetcher->redefine(run => sub { return clone($sample_data) }); - my $validator = Data::Validate::Sanctions::Redis->new(redis => $redis); + my $validator = Data::Validate::Sanctions::Redis->new(connection => $redis); $validator->update_data(); # create a new new validator for sanction checks. No write_redis is needed. - $validator = Data::Validate::Sanctions::Redis->new(redis => $redis); + $validator = Data::Validate::Sanctions::Redis->new(connection => $redis); $sample_data->{$_}->{verified} = 1000 for keys %$sample_data; is_deeply $validator->data, $sample_data, 'Sample data is correctly loaded'; From e00211368b9edc10751b05a2dec92b19b5a39995 Mon Sep 17 00:00:00 2001 From: mat-fs Date: Mon, 3 Oct 2022 14:02:33 +0000 Subject: [PATCH 22/61] [ci] trigger From 7d95b0c71aa6087e7712dbc0423167a046f1a1c0 Mon Sep 17 00:00:00 2001 From: mat-fs <53427463+mat-fs@users.noreply.github.com> Date: Sun, 9 Oct 2022 11:13:32 +0400 Subject: [PATCH 23/61] Update Redis.pm --- lib/Data/Validate/Sanctions/Redis.pm | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/lib/Data/Validate/Sanctions/Redis.pm b/lib/Data/Validate/Sanctions/Redis.pm index d693539e..55e55037 100644 --- a/lib/Data/Validate/Sanctions/Redis.pm +++ b/lib/Data/Validate/Sanctions/Redis.pm @@ -31,18 +31,6 @@ sub new { return $object; } -sub last_updated { - my $self = shift; - my $list = shift; - - if ($list) { - return $self->{_data}->{$list}->{updated}; - } else { - $self->_load_data(); - return max(map { $_->{updated} } values %{$self->{_data}}); - } -} - sub set_sanction_file { die 'Not applicable'; } From 2d7863fd0b953361a12e44340239c72f8b3209e8 Mon Sep 17 00:00:00 2001 From: mat-fs Date: Tue, 18 Oct 2022 22:44:55 +0000 Subject: [PATCH 24/61] trigger tests [ci] 2022-10-18 22:44:55 From 414d9b91753bad218dd17d1cc1f52374d13b68bb Mon Sep 17 00:00:00 2001 From: mat-fs Date: Wed, 19 Oct 2022 05:56:19 +0000 Subject: [PATCH 25/61] trigger tests [ci] 2022-10-19 05:56:19 From 653e6c2e32f1a642f39d9efa3435740f86deea7e Mon Sep 17 00:00:00 2001 From: mat-fs Date: Wed, 19 Oct 2022 09:30:18 +0000 Subject: [PATCH 26/61] [ci] load data is moved to the parent class --- lib/Data/Validate/Sanctions.pm | 12 ++++++++++++ lib/Data/Validate/Sanctions/Redis.pm | 6 ------ 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/lib/Data/Validate/Sanctions.pm b/lib/Data/Validate/Sanctions.pm index aa707263..c4d59e40 100644 --- a/lib/Data/Validate/Sanctions.pm +++ b/lib/Data/Validate/Sanctions.pm @@ -121,6 +121,14 @@ sub is_sanctioned { ## no critic (RequireArgUnpacking) return (get_sanctioned_info(@_))->{matched}; } +sub data { + my ($self) = @_; + + $self->_load_data() unless $self->{_data}; + + return $self->{_data}; +} + =head2 _match_other_fields Matches fields possibly available in addition to name and date of birth. @@ -519,6 +527,10 @@ Pass in the client's name and sanctioned individual's name to see if they are si Exports the sanction lists to a local file in YAML format. +=head2 data + +Gets the sanction list content with lazy loading. + =head1 AUTHOR Binary.com Efayland@binary.comE diff --git a/lib/Data/Validate/Sanctions/Redis.pm b/lib/Data/Validate/Sanctions/Redis.pm index 55e55037..1590ba9c 100644 --- a/lib/Data/Validate/Sanctions/Redis.pm +++ b/lib/Data/Validate/Sanctions/Redis.pm @@ -110,12 +110,6 @@ sub _default_sanction_file { die 'Not applicable'; } -sub data { - my ($self) = @_; - - return $self->{_data}; -} - 1; __END__ From 81c2c3552459c09c854a112a7f6d7befa35d532b Mon Sep 17 00:00:00 2001 From: mat-fs Date: Wed, 19 Oct 2022 13:11:54 +0000 Subject: [PATCH 27/61] [ci] test files are renamed + error load and save error is fixed --- lib/Data/Validate/Sanctions.pm | 7 +++---- lib/Data/Validate/Sanctions/Redis.pm | 2 +- t/{05_basic.t => 01_basic.t} | 0 t/{10_env.t => 02_env.t} | 0 t/{15_oo.t => 03_oo.t} | 0 t/{fetcher.t => 04_fetcher.t} | 0 t/{sanctions_redis.t => 05_sanctions_redis.t} | 16 +++++++++------- t/{fetcher_sources.t => 06_fetcher_sources.t} | 0 8 files changed, 13 insertions(+), 12 deletions(-) rename t/{05_basic.t => 01_basic.t} (100%) rename t/{10_env.t => 02_env.t} (100%) rename t/{15_oo.t => 03_oo.t} (100%) rename t/{fetcher.t => 04_fetcher.t} (100%) rename t/{sanctions_redis.t => 05_sanctions_redis.t} (96%) rename t/{fetcher_sources.t => 06_fetcher_sources.t} (100%) diff --git a/lib/Data/Validate/Sanctions.pm b/lib/Data/Validate/Sanctions.pm index c4d59e40..b5023b26 100644 --- a/lib/Data/Validate/Sanctions.pm +++ b/lib/Data/Validate/Sanctions.pm @@ -50,8 +50,7 @@ sub update_data { $self->_load_data(); my $new_data = Data::Validate::Sanctions::Fetcher::run($self->{args}->%*, %args); - - my $updated; + my $updated = 0; foreach my $k (keys %$new_data) { $self->{_data}->{$k} //= {}; $self->{_data}->{$k}->{updated} //= 0; @@ -64,14 +63,14 @@ sub update_data { if ($new_data->{$k}->{error}) { warn "$k list update failed because: $new_data->{$k}->{error}"; - $updated = 1; $self->{_data}->{$k}->{error} = $new_data->{$k}->{error}; + $updated = 1; } elsif ($self->{_data}{$k}->{updated} != $new_data->{$k}->{updated} || scalar $self->{_data}{$k}->{content}->@* != scalar $new_data->{$k}->{content}->@*) { + print "Source $k is updated with new data \n" if $args{verbose}; $self->{_data}->{$k} = $new_data->{$k}; $updated = 1; - print "Source $k is updated with new data \n" if $args{verbose}; } else { print "Source $k is not changed \n" if $args{verbose}; } diff --git a/lib/Data/Validate/Sanctions/Redis.pm b/lib/Data/Validate/Sanctions/Redis.pm index 1590ba9c..24bbca4a 100644 --- a/lib/Data/Validate/Sanctions/Redis.pm +++ b/lib/Data/Validate/Sanctions/Redis.pm @@ -65,6 +65,7 @@ sub _load_data { $self->{_data}->{$source}->{content} = decode_json_utf8($content); $self->{_data}->{$source}->{verified} = $verified; $self->{_data}->{$source}->{updated} = $updated; + $self->{_data}->{$source}->{error} = $error; $last_time = $updated if $updated > $last_time; } catch { $self->{_data}->{$source}->{content} = []; @@ -93,7 +94,6 @@ sub _save_data { for my $source ($self->{sources}->@*) { $self->{_data}->{$source}->{verified} = time; - $self->{connection}->hmset( "SANCTIONS::$source", updated => $self->{_data}->{$source}->{updated} // 0, diff --git a/t/05_basic.t b/t/01_basic.t similarity index 100% rename from t/05_basic.t rename to t/01_basic.t diff --git a/t/10_env.t b/t/02_env.t similarity index 100% rename from t/10_env.t rename to t/02_env.t diff --git a/t/15_oo.t b/t/03_oo.t similarity index 100% rename from t/15_oo.t rename to t/03_oo.t diff --git a/t/fetcher.t b/t/04_fetcher.t similarity index 100% rename from t/fetcher.t rename to t/04_fetcher.t diff --git a/t/sanctions_redis.t b/t/05_sanctions_redis.t similarity index 96% rename from t/sanctions_redis.t rename to t/05_sanctions_redis.t index 69ee4adc..66d5cd0c 100644 --- a/t/sanctions_redis.t +++ b/t/05_sanctions_redis.t @@ -33,7 +33,7 @@ my $sample_data = { dob_epoch => [], dob_year => [] }, - ] + ], }, 'HMT-Sanctions' => { updated => 150, @@ -94,7 +94,8 @@ subtest 'Update Data' => sub { my $mock_data = { 'EU-Sanctions' => { updated => 90, - content => []}}; + content => []}, + }; $mock_fetcher->redefine(run => sub { return clone($mock_data) }); set_fixed_time(1500); my $validator = Data::Validate::Sanctions::Redis->new(connection => $redis); @@ -124,7 +125,7 @@ subtest 'Update Data' => sub { is_deeply $validator->data, $expected, 'Data is loaded with new update time'; check_redis_content('EU-Sanctions', $mock_data->{'EU-Sanctions'}, 1600, 'Redis content changed by increased update time'); - # redis is updated with new entries, even if the publish date is the same + # redis is updated with change in the number of entities, even if the publish date is the same $mock_data = { 'EU-Sanctions' => { updated => 91, @@ -160,6 +161,10 @@ subtest 'Update Data' => sub { is_deeply $validator->data, $expected, 'Data is not changed if there is error'; check_redis_content('EU-Sanctions', $expected->{'EU-Sanctions'}, 1800, 'Redis content is not changed when there is an error'); + set_fixed_time(1850); + $validator = Data::Validate::Sanctions::Redis->new(connection => $redis); + is_deeply $validator->data->{'EU-Sanctions'}, $expected->{'EU-Sanctions'}, 'All fieds are correctly loaded form redis in constructor'; + # All sources are updated at the same time $mock_data = $sample_data; $expected = clone($mock_data); @@ -172,10 +177,6 @@ subtest 'Update Data' => sub { check_redis_content('OFAC-Consolidated', $mock_data->{'OFAC-Consolidated'}, 1900, 'Sanction list is stored in redis'); check_redis_content('OFAC-SDN', $mock_data->{'OFAC-SDN'}, 1900, 'Sanction list is stored in redis'); - # New objects load the same data - my $validator2 = Data::Validate::Sanctions::Redis->new(connection => $redis); - is_deeply $validator2->data, $validator->data, 'New validator object loads the same data from redis'; - restore_time(); $mock_fetcher->unmock_all; }; @@ -192,6 +193,7 @@ subtest 'get sanctioned info' => sub { # create a new new validator for sanction checks. No write_redis is needed. $validator = Data::Validate::Sanctions::Redis->new(connection => $redis); $sample_data->{$_}->{verified} = 1000 for keys %$sample_data; + $sample_data->{$_}->{error} = '' for keys %$sample_data; is_deeply $validator->data, $sample_data, 'Sample data is correctly loaded'; ok !$validator->is_sanctioned(qw(sergei ivanov)), "Sergei Ivanov not is_sanctioned"; diff --git a/t/fetcher_sources.t b/t/06_fetcher_sources.t similarity index 100% rename from t/fetcher_sources.t rename to t/06_fetcher_sources.t From 6fad98a62092e7c1acf77987dce1d49fb46e4383 Mon Sep 17 00:00:00 2001 From: mat-fs Date: Wed, 19 Oct 2022 13:44:07 +0000 Subject: [PATCH 28/61] [ci] error message cleanup --- lib/Data/Validate/Sanctions/Fetcher.pm | 26 +++++++++++++------------- lib/Data/Validate/Sanctions/Redis.pm | 4 ++-- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/lib/Data/Validate/Sanctions/Fetcher.pm b/lib/Data/Validate/Sanctions/Fetcher.pm index a5f1f1aa..865bbc09 100644 --- a/lib/Data/Validate/Sanctions/Fetcher.pm +++ b/lib/Data/Validate/Sanctions/Fetcher.pm @@ -234,7 +234,7 @@ sub _ofac_xml { $ref->{publshInformation}{Publish_Date} =~ m/(\d{1,2})\/(\d{1,2})\/(\d{4})/ ? _date_to_epoch("$3-$1-$2") : undef; # publshInformation is a typo in ofac xml tags - die 'Publication date is invalid' unless defined $publish_epoch; + die "Corrupt data. Release date is invalid\n" unless defined $publish_epoch; my $parse_list_node = sub { my ($entry, $parent, $child, $attribute) = @_; @@ -301,16 +301,16 @@ sub _hmt_csv { my $raw_data = shift; my $dataset = []; - my $csv = Text::CSV->new({binary => 1}) or die "Cannot use CSV: " . Text::CSV->error_diag(); + my $csv = Text::CSV->new({binary => 1}) or die "Cannot use CSV: " . Text::CSV->error_diag() . "\n"; my @lines = split("\n", $raw_data); my $parsed = $csv->parse(trim(shift @lines)); my @info = $parsed ? $csv->fields() : (); - die 'Publication date was not found' unless @info && _date_to_epoch($info[1]); + die "Currupt data. Release date was not found\n" unless @info && _date_to_epoch($info[1]); my $publish_epoch = _date_to_epoch($info[1]); - die 'Publication date is invalid' unless defined $publish_epoch; + die "Currupt data. Release date is invalid\n" unless defined $publish_epoch; $parsed = $csv->parse(trim(shift @lines)); my @row = $csv->fields(); @@ -415,7 +415,7 @@ sub _eu_xml { my @date_parts = split('T', $ref->{'-generationDate'} // ''); my $publish_epoch = _date_to_epoch($date_parts[0] // ''); - die 'Publication date is invalid' unless $publish_epoch; + die "Corrupt data. Release date is invalid\n" unless $publish_epoch; return { updated => $publish_epoch, @@ -440,7 +440,7 @@ sub run { foreach my $id (sort keys %$config) { my $source = $config->{$id}; try { - die "Url is empty for $id" unless $source->{url}; + die "Url is empty for $id\n" unless $source->{url}; my $raw_data; @@ -461,8 +461,8 @@ sub run { my $count = $data->{content}->@*; print "Source $id: $count entries fetched \n" if $args{verbose}; } - } catch { - $result->{$id}->{error} = $@; + } catch ($e) { + $result->{$id}->{error} = $e; } } @@ -480,7 +480,7 @@ sub _entries_from_file { my $entries; - open my $fh, '<', "$1" or die "Can't open $id file $1 $!"; + open my $fh, '<', "$1" or die "Can't open $id file $1 $!\n"; $entries = do { local $/; <$fh> }; close $fh; @@ -513,16 +513,16 @@ sub _entries_from_remote_src { try { my $resp = $ua->get($src_url); - die "File not downloaded for $id" if $resp->result->is_error; + die "File not downloaded for $id\n" if $resp->result->is_error; $entries = $resp->result->body; last; - } catch { - $error_log = $@; + } catch ($e) { + $error_log = $e; } } - return $entries // die "An error occurred while fetching data from '$src_url' due to $error_log"; + return $entries // die "An error occurred while fetching data from '$src_url' due to $error_log\n"; } 1; diff --git a/lib/Data/Validate/Sanctions/Redis.pm b/lib/Data/Validate/Sanctions/Redis.pm index 24bbca4a..1a155374 100644 --- a/lib/Data/Validate/Sanctions/Redis.pm +++ b/lib/Data/Validate/Sanctions/Redis.pm @@ -67,11 +67,11 @@ sub _load_data { $self->{_data}->{$source}->{updated} = $updated; $self->{_data}->{$source}->{error} = $error; $last_time = $updated if $updated > $last_time; - } catch { + } catch ($e) { $self->{_data}->{$source}->{content} = []; $self->{_data}->{$source}->{updated} = 0; $self->{_data}->{$source}->{verified} = 0; - $self->{_data}->{$source}->{error} = "Failed to load from Redis: $@"; + $self->{_data}->{$source}->{error} = "Failed to load from Redis: $e"; } } $self->{last_time} = $last_time; From 96659042baf11977c61e7d1177a916955a9e3fd2 Mon Sep 17 00:00:00 2001 From: mat-fs Date: Wed, 19 Oct 2022 13:57:35 +0000 Subject: [PATCH 29/61] trigger tests [ci] 2022-10-19 13:57:35 From 4a6746421d2056c38e314828cd16c49e79a52f6d Mon Sep 17 00:00:00 2001 From: mat-fs Date: Wed, 19 Oct 2022 14:28:36 +0000 Subject: [PATCH 30/61] trigger tests [ci] 2022-10-19 14:28:36 From 598cabd036e99ddd7146191b6fc5ed59453c23ad Mon Sep 17 00:00:00 2001 From: mat-fs Date: Thu, 20 Oct 2022 15:43:34 +0000 Subject: [PATCH 31/61] [ci] missing values initialized with default values + new test for loading --- lib/Data/Validate/Sanctions.pm | 3 +- lib/Data/Validate/Sanctions/Redis.pm | 11 ++++-- t/05_sanctions_redis.t | 59 ++++++++++++++++++++++++---- 3 files changed, 60 insertions(+), 13 deletions(-) diff --git a/lib/Data/Validate/Sanctions.pm b/lib/Data/Validate/Sanctions.pm index b5023b26..b330dfcc 100644 --- a/lib/Data/Validate/Sanctions.pm +++ b/lib/Data/Validate/Sanctions.pm @@ -92,7 +92,7 @@ sub last_updated { return $self->{_data}->{$list}->{updated}; } else { $self->_load_data(); - return max(map { $_->{updated} } values %{$self->{_data}}); + return max(map { $_->{updated} // 0 } values %{$self->{_data}}); } } @@ -348,6 +348,7 @@ Indexes data by name. Each name may have multiple matching entries. sub _index_data { my $self = shift; + $self->{_data} //= {}; $self->{_index} = {}; for my $source (keys $self->{_data}->%*) { my @content = clone($self->{_data}->{$source}->{content} // [])->@*; diff --git a/lib/Data/Validate/Sanctions/Redis.pm b/lib/Data/Validate/Sanctions/Redis.pm index 1a155374..e068ce90 100644 --- a/lib/Data/Validate/Sanctions/Redis.pm +++ b/lib/Data/Validate/Sanctions/Redis.pm @@ -58,14 +58,17 @@ sub _load_data { my $last_time = $self->{last_time}; for my $source ($self->{sources}->@*) { try { + $self->{_data}->{$source} //= {}; + my ($content, $verified, $updated, $error) = $self->{connection}->hmget("SANCTIONS::$source", qw/content verified updated error/)->@*; $updated //= 0; - next if $updated <= ($self->{_data}->{$source}->{updated} // 0); + my $current_update_date = $self->{_data}->{$source}->{updated} // 0; + next if $current_update_date && $updated <= $current_update_date; - $self->{_data}->{$source}->{content} = decode_json_utf8($content); - $self->{_data}->{$source}->{verified} = $verified; + $self->{_data}->{$source}->{content} = decode_json_utf8($content // '[]'); + $self->{_data}->{$source}->{verified} = $verified // 0; $self->{_data}->{$source}->{updated} = $updated; - $self->{_data}->{$source}->{error} = $error; + $self->{_data}->{$source}->{error} = $error // ''; $last_time = $updated if $updated > $last_time; } catch ($e) { $self->{_data}->{$source}->{content} = []; diff --git a/t/05_sanctions_redis.t b/t/05_sanctions_redis.t index 66d5cd0c..93761c33 100644 --- a/t/05_sanctions_redis.t +++ b/t/05_sanctions_redis.t @@ -12,7 +12,7 @@ use Test::MockModule; use Test::RedisServer; use Test::MockTime qw(set_fixed_time restore_time); use RedisDB; -use JSON::MaybeUTF8 qw(decode_json_utf8); +use JSON::MaybeUTF8 qw(encode_json_utf8 decode_json_utf8); use Clone qw(clone); use Data::Validate::Sanctions::Redis; @@ -80,10 +80,10 @@ subtest 'Class constructor' => sub { ok $validator = Data::Validate::Sanctions::Redis->new(connection => $redis), 'Successfully created the object with redis object'; is_deeply $validator->data, { - 'EU-Sanctions' => {}, - 'HMT-Sanctions' => {}, - 'OFAC-Consolidated' => {}, - 'OFAC-SDN' => {}, + 'EU-Sanctions' => {content => [], verified => 0, updated => 0, error=> ''}, + 'HMT-Sanctions' => {content => [], verified => 0, updated => 0, error=> ''}, + 'OFAC-Consolidated' => {content => [], verified => 0, updated => 0, error=> ''}, + 'OFAC-SDN' => {content => [], verified => 0, updated => 0, error=> ''}, }, 'There is no sanction data'; }; @@ -106,9 +106,9 @@ subtest 'Update Data' => sub { updated => 90, verified => 1500, }, - 'HMT-Sanctions' => {verified => 1500}, - 'OFAC-Consolidated' => {verified => 1500}, - 'OFAC-SDN' => {verified => 1500}, + 'HMT-Sanctions' => {content => [], verified => 1500, updated => 0, error=> ''}, + 'OFAC-Consolidated' => {content => [], verified => 1500, updated => 0, error=> ''}, + 'OFAC-SDN' => {content => [], verified => 1500, updated => 0, error=> ''}, }; is_deeply $validator->data, $expected, 'Data is correctly loaded'; check_redis_content('EU-Sanctions', $mock_data->{'EU-Sanctions'}, 1500); @@ -181,6 +181,48 @@ subtest 'Update Data' => sub { $mock_fetcher->unmock_all; }; +subtest 'load data' => sub { + clear_redis(); + my $validator = Data::Validate::Sanctions::Redis->new(connection => $redis); + my $expected = { + 'EU-Sanctions' => {content => [], verified => 0, updated => 0, error => ''}, + 'HMT-Sanctions'=> {content => [], verified => 0, updated => 0, error => ''}, + 'OFAC-Consolidated' => {content => [], verified => 0, updated => 0, error => ''}, + 'OFAC-SDN' => {content => [], verified => 0, updated => 0, error => ''} + }; + is_deeply $validator->data, $expected, 'Saction lists are loaded with default values when redis is empty'; + is $validator->last_updated, 0, 'Updated date is zero'; + + my $test_data = { + 'EU-Sanctions' => {}, + 'HMT-Sanctions'=> {updated => 1001, content => [{names => ['TMPA']}], verified => 1101, extra_field => 1}, + 'OFAC-SDN' => {updated => 1002, content => [], verified => 1102, error => 'Test error'} + }; + + $expected = { + 'EU-Sanctions' => {content => [], verified => 0, updated => 0, error => ''}, + 'HMT-Sanctions'=> {updated => 1001, content => [{names => ['TMPA']}], verified => 1101, error => ''}, + 'OFAC-Consolidated' => {content => [], verified => 0, updated => 0, error => ''}, + 'OFAC-SDN' => {updated => 1002, content => [], verified => 1102, error => 'Test error'} + }; + + for my $source (keys %$test_data) { + # save data to redis + for my $field (keys $test_data->{$source}->%*) { + my $value = $test_data->{$source}->{$field}; + $value = encode_json_utf8($value) if ref $value; + $redis->hmset("SANCTIONS::$source", $field, $value); + } + } + + $validator = Data::Validate::Sanctions::Redis->new(connection => $redis); + is_deeply $validator->data->{'EU-Sanctions'}, {content => [], verified => 0, updated => 0, error => ''}, 'EU sanctions list loaded with default values from Redis'; + is_deeply $validator->data->{'HMT-Sanctions'}, {updated => 1001, content => [{names => ['TMPA']}], verified => 1101, error => ''}, 'HMT sanctions loaded correctly with extra field ignored'; + is_deeply $validator->data->{'OFAC-SDN'}, {updated => 1002, content => [], verified => 1102, error => 'Test error'}, 'OFAC-SND loaded with correct error'; + is_deeply $validator->data->{'OFAC-Consolidated'}, {content => [], verified => 0, updated => 0, error => ''}, 'Missing source OFAC-Consolodated loaded with default values'; + is $validator->last_updated, 1002, 'Update date is the maximum of the dates in all sources'; +}; + subtest 'get sanctioned info' => sub { # reload data freshly from the sample data clear_redis(); @@ -296,6 +338,7 @@ subtest 'get sanctioned info' => sub { delete $expected_result->{matched_args}->{$field}; is_deeply $validator->get_sanctioned_info({%$args, $field => undef}), $expected_result, "Missing optional args are ignored - $field"; } + restore_time(); }; sub clear_redis { From 2434af8c6881550840893140de67329d992d27ba Mon Sep 17 00:00:00 2001 From: mat-fs Date: Thu, 20 Oct 2022 15:51:18 +0000 Subject: [PATCH 32/61] trigger tests [ci] 2022-10-20 15:51:18 From 64b6646769ce1739f5b09fb3126ae0b85847775d Mon Sep 17 00:00:00 2001 From: mat-fs Date: Sun, 23 Oct 2022 16:01:55 +0000 Subject: [PATCH 33/61] trigger tests [ci] 2022-10-23 16:01:55 From 8603eda4486d051259cbb93c6c03d5dfd99c174e Mon Sep 17 00:00:00 2001 From: mat-fs Date: Sun, 23 Oct 2022 16:04:17 +0000 Subject: [PATCH 34/61] trigger tests [ci] 2022-10-23 16:04:17 From df4777f15dcac1b06b86a177b45150c9c2858df4 Mon Sep 17 00:00:00 2001 From: mat-fs Date: Fri, 28 Oct 2022 17:45:29 +0000 Subject: [PATCH 35/61] trigger tests [ci] 2022-10-28 17:45:29 From 86b074921f0f03884370538c87b2ac6a88884398 Mon Sep 17 00:00:00 2001 From: mat-fs Date: Fri, 28 Oct 2022 19:01:35 +0000 Subject: [PATCH 36/61] trigger tests [ci] 2022-10-28 19:01:35 From 831663e96800aeca46b099d9b0aea4ea1f1355fd Mon Sep 17 00:00:00 2001 From: mat-fs Date: Sun, 30 Oct 2022 10:42:05 +0000 Subject: [PATCH 37/61] trigger tests [ci] 2022-10-30 10:42:05 From 35a5f663ba75948a8b06e58f5467dbf17322f4f0 Mon Sep 17 00:00:00 2001 From: ragheb-deriv Date: Tue, 1 Nov 2022 08:26:31 +0000 Subject: [PATCH 38/61] trigger tests [ci] 2022-11-01 08:26:31 From 0ede5ef70bde01200d965ed6c26f859549edbd84 Mon Sep 17 00:00:00 2001 From: ragheb-deriv Date: Tue, 1 Nov 2022 08:51:40 +0000 Subject: [PATCH 39/61] add MockTime module to cpanfile [ci] --- cpanfile | 1 + 1 file changed, 1 insertion(+) diff --git a/cpanfile b/cpanfile index 2c10cf11..59ca0bd2 100644 --- a/cpanfile +++ b/cpanfile @@ -28,6 +28,7 @@ on test => sub { requires 'Test::Warnings', '0.026'; requires 'Test::MockModule', '0.15'; requires 'Test::MockObject', '1.20161202'; + requires 'Test::MockTime'; requires 'Test::Deep', '0'; requires 'FindBin', '0'; requires 'Path::Tiny', '0'; From 8ab564dd8d6817dc5f22a06c316fc38cd4e66648 Mon Sep 17 00:00:00 2001 From: ragheb-deriv Date: Wed, 2 Nov 2022 07:36:18 +0000 Subject: [PATCH 40/61] added redis docker image for circleci [ci] --- .circleci/config.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.circleci/config.yml b/.circleci/config.yml index b3140676..d955fc60 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -7,6 +7,7 @@ jobs: default: "5.34" docker: - image: perldocker/perl-tester:<< parameters.perl-version >> + - image: cimg/redis:6.2.7 steps: - checkout - run: From be9cc9f715bd66c7816b8491781304cc29169ae8 Mon Sep 17 00:00:00 2001 From: ragheb-deriv Date: Wed, 2 Nov 2022 08:24:22 +0000 Subject: [PATCH 41/61] added apt instal redis-server and remved image[ci] --- .circleci/config.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index d955fc60..34afa4a4 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -7,7 +7,6 @@ jobs: default: "5.34" docker: - image: perldocker/perl-tester:<< parameters.perl-version >> - - image: cimg/redis:6.2.7 steps: - checkout - run: @@ -24,6 +23,10 @@ jobs: name: Install dist deps command: | dzil listdeps --author --missing --cpanm-versions | xargs cpanm -n + - run: + name: Install redis server + command: | + apt-get install redis-server - run: name: Run Tests command: | From bdf450d5426503017c3e372840935ff19553d0fb Mon Sep 17 00:00:00 2001 From: ragheb-deriv Date: Wed, 2 Nov 2022 08:43:22 +0000 Subject: [PATCH 42/61] wip [ci] --- .circleci/config.yml | 5 +---- t/03_oo.t | 5 +++-- t/05_sanctions_redis.t | 6 ++++-- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 34afa4a4..d955fc60 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -7,6 +7,7 @@ jobs: default: "5.34" docker: - image: perldocker/perl-tester:<< parameters.perl-version >> + - image: cimg/redis:6.2.7 steps: - checkout - run: @@ -23,10 +24,6 @@ jobs: name: Install dist deps command: | dzil listdeps --author --missing --cpanm-versions | xargs cpanm -n - - run: - name: Install redis server - command: | - apt-get install redis-server - run: name: Run Tests command: | diff --git a/t/03_oo.t b/t/03_oo.t index 0a9a5a96..71491f58 100644 --- a/t/03_oo.t +++ b/t/03_oo.t @@ -7,7 +7,9 @@ use Test::Warnings; use Test::More; use Test::RedisServer; use RedisDB; - +my $redis_server; + eval { require Test::RedisServer; $redis_server = Test::RedisServer->new(conf => {port => 6379}) } + or plan skip_all => 'Test::RedisServer is required for this test'; my $validator = Data::Validate::Sanctions->new; ok $validator->is_sanctioned('NEVEROV', 'Sergei Ivanovich', -253411200), "Sergei Ivanov is_sanctioned for sure"; @@ -196,7 +198,6 @@ $validator = Data::Validate::Sanctions->new(sanction_file => "$tmpa"); ok $validator->is_sanctioned(qw(tmpa)), "get sanction file from args"; subtest 'Subclass factory' => sub { - my $redis_server = Test::RedisServer->new(); my $redis = RedisDB->new($redis_server->connect_info); my $validator = Data::Validate::Sanctions->new(storage => 'redis', connection => $redis); diff --git a/t/05_sanctions_redis.t b/t/05_sanctions_redis.t index 93761c33..5799af94 100644 --- a/t/05_sanctions_redis.t +++ b/t/05_sanctions_redis.t @@ -9,7 +9,6 @@ use Test::Warnings; use Test::More; use Test::Fatal; use Test::MockModule; -use Test::RedisServer; use Test::MockTime qw(set_fixed_time restore_time); use RedisDB; use JSON::MaybeUTF8 qw(encode_json_utf8 decode_json_utf8); @@ -17,7 +16,10 @@ use Clone qw(clone); use Data::Validate::Sanctions::Redis; -my $redis_server = Test::RedisServer->new(); +my $redis_server; +eval { require Test::RedisServer; $redis_server = Test::RedisServer->new(conf => {port => 6379}) } + or plan skip_all => 'Test::RedisServer is required for this test'; + my $redis = RedisDB->new($redis_server->connect_info); my $sample_data = { From d561505a744546eab0d15130ebf19e416a5d6aa6 Mon Sep 17 00:00:00 2001 From: ragheb-deriv Date: Wed, 2 Nov 2022 08:51:26 +0000 Subject: [PATCH 43/61] added docker run step [ci] --- .circleci/config.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.circleci/config.yml b/.circleci/config.yml index d955fc60..a0825593 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -24,6 +24,10 @@ jobs: name: Install dist deps command: | dzil listdeps --author --missing --cpanm-versions | xargs cpanm -n + - run: + name: Run Redis server + command: | + docker run --name redis-testing -d -p 6378:6379 redis - run: name: Run Tests command: | From c5dae7204de79919e6d75a52424aedf9bc74ec9d Mon Sep 17 00:00:00 2001 From: ragheb-deriv Date: Wed, 2 Nov 2022 08:53:20 +0000 Subject: [PATCH 44/61] fix lint issues [ci] --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index a0825593..3c899097 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -27,7 +27,7 @@ jobs: - run: name: Run Redis server command: | - docker run --name redis-testing -d -p 6378:6379 redis + docker run --name redis-testing -d -p 6378:6379 redis - run: name: Run Tests command: | From 5242ccce8218813bb56772cf3e43e4add153bf6c Mon Sep 17 00:00:00 2001 From: ragheb-deriv Date: Wed, 2 Nov 2022 09:06:20 +0000 Subject: [PATCH 45/61] add docker client [ci] --- .circleci/config.yml | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 3c899097..46d4539e 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -10,6 +10,14 @@ jobs: - image: cimg/redis:6.2.7 steps: - checkout + - run: + name: Install Docker client + command: | + apk add docker-cli + - run: + name: Run Redis server + command: | + docker run --name redis-testing -d -p 6378:6379 redis - run: command: cpm install -g --no-test Dist::Zilla Dist::Zilla::App::Command::cover ExtUtils::MakeMaker @@ -24,10 +32,6 @@ jobs: name: Install dist deps command: | dzil listdeps --author --missing --cpanm-versions | xargs cpanm -n - - run: - name: Run Redis server - command: | - docker run --name redis-testing -d -p 6378:6379 redis - run: name: Run Tests command: | From b36e31dc28f2d535795ef66e00630b45df48a89b Mon Sep 17 00:00:00 2001 From: ragheb-deriv Date: Wed, 2 Nov 2022 09:30:44 +0000 Subject: [PATCH 46/61] wip [ci] --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 46d4539e..04c0a771 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -13,7 +13,7 @@ jobs: - run: name: Install Docker client command: | - apk add docker-cli + apt-get add docker-cli - run: name: Run Redis server command: | From a6f4f501831ed593208b25ece03589052ca7826d Mon Sep 17 00:00:00 2001 From: ragheb-deriv Date: Wed, 2 Nov 2022 09:32:17 +0000 Subject: [PATCH 47/61] wip [ci] --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 04c0a771..d35fed9e 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -13,7 +13,7 @@ jobs: - run: name: Install Docker client command: | - apt-get add docker-cli + apt-get install docker-cli - run: name: Run Redis server command: | From d2779d9a4bc1ef24d99e8cbee4ab683590df994c Mon Sep 17 00:00:00 2001 From: ragheb-deriv Date: Wed, 2 Nov 2022 10:06:56 +0000 Subject: [PATCH 48/61] refactor [ci] --- .circleci/config.yml | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index d35fed9e..49d43772 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -7,17 +7,12 @@ jobs: default: "5.34" docker: - image: perldocker/perl-tester:<< parameters.perl-version >> - - image: cimg/redis:6.2.7 steps: - checkout - run: - name: Install Docker client + name: Install Redis server command: | - apt-get install docker-cli - - run: - name: Run Redis server - command: | - docker run --name redis-testing -d -p 6378:6379 redis + apt-get install redis && apt-get install redis-server - run: command: cpm install -g --no-test Dist::Zilla Dist::Zilla::App::Command::cover ExtUtils::MakeMaker From 25ad2b09d12c3d4b0c5c75425a86f29eb2e3d007 Mon Sep 17 00:00:00 2001 From: ragheb-deriv Date: Wed, 2 Nov 2022 10:09:16 +0000 Subject: [PATCH 49/61] bug fix [ci] --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 49d43772..3b227ddc 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -12,7 +12,7 @@ jobs: - run: name: Install Redis server command: | - apt-get install redis && apt-get install redis-server + apt-get install redis && apt-get install redis-server -y - run: command: cpm install -g --no-test Dist::Zilla Dist::Zilla::App::Command::cover ExtUtils::MakeMaker From 0624ae11330f6cfe8c44a13aba9adb16f4241318 Mon Sep 17 00:00:00 2001 From: ragheb-deriv Date: Wed, 2 Nov 2022 10:10:58 +0000 Subject: [PATCH 50/61] bug fix [ci] --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 3b227ddc..bc8f76d3 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -12,7 +12,7 @@ jobs: - run: name: Install Redis server command: | - apt-get install redis && apt-get install redis-server -y + apt-get install -y redis && apt-get install -y redis-server - run: command: cpm install -g --no-test Dist::Zilla Dist::Zilla::App::Command::cover ExtUtils::MakeMaker From a00ff761708d514dedac9a0b65a063f2234f3277 Mon Sep 17 00:00:00 2001 From: ragheb-deriv Date: Wed, 2 Nov 2022 12:07:46 +0000 Subject: [PATCH 51/61] make tidy and fixed lint errors [ci] --- lib/Data/Validate/Sanctions.pm | 4 +- lib/Data/Validate/Sanctions/Fetcher.pm | 2 +- lib/Data/Validate/Sanctions/Redis.pm | 9 +- t/01_basic.t | 16 +-- t/02_env.t | 2 +- t/03_oo.t | 25 ++-- t/04_fetcher.t | 62 ++++----- t/05_sanctions_redis.t | 171 ++++++++++++++++++++----- xt/20_update.t | 2 +- 9 files changed, 206 insertions(+), 87 deletions(-) diff --git a/lib/Data/Validate/Sanctions.pm b/lib/Data/Validate/Sanctions.pm index b330dfcc..95e2e384 100644 --- a/lib/Data/Validate/Sanctions.pm +++ b/lib/Data/Validate/Sanctions.pm @@ -50,7 +50,7 @@ sub update_data { $self->_load_data(); my $new_data = Data::Validate::Sanctions::Fetcher::run($self->{args}->%*, %args); - my $updated = 0; + my $updated = 0; foreach my $k (keys %$new_data) { $self->{_data}->{$k} //= {}; $self->{_data}->{$k}->{updated} //= 0; @@ -424,7 +424,7 @@ sub _name_matches { sub export_data { my ($self, $path) = @_; - DumpFile($path, $self->{_data}); + return DumpFile($path, $self->{_data}); } 1; diff --git a/lib/Data/Validate/Sanctions/Fetcher.pm b/lib/Data/Validate/Sanctions/Fetcher.pm index 865bbc09..3e8a9165 100644 --- a/lib/Data/Validate/Sanctions/Fetcher.pm +++ b/lib/Data/Validate/Sanctions/Fetcher.pm @@ -461,7 +461,7 @@ sub run { my $count = $data->{content}->@*; print "Source $id: $count entries fetched \n" if $args{verbose}; } - } catch ($e) { + } catch ($e) { $result->{$id}->{error} = $e; } } diff --git a/lib/Data/Validate/Sanctions/Redis.pm b/lib/Data/Validate/Sanctions/Redis.pm index e068ce90..26244c59 100644 --- a/lib/Data/Validate/Sanctions/Redis.pm +++ b/lib/Data/Validate/Sanctions/Redis.pm @@ -120,13 +120,14 @@ __END__ =head1 NAME -Data::Validate::Sanctions::Redis - An extention of L that stores sanction data in redis. +Data::Validate::Sanctions::Redis - An extension of L that stores sanction data in redis. =head1 SYNOPSIS use Data::Validate::Sanctions::Redis; - - my $validator = Data::Validate::Sanctions::Redis->new(connection => $redis_read); + my $redis_read, $last_name, $first_name, $date_of_birth, $redis_write, $token, $validator; + + $validator = Data::Validate::Sanctions::Redis->new(connection => $redis_read); # to validate clients by their name print 'BAD' if $validator->is_sanctioned("$last_name $first_name"); @@ -134,7 +135,7 @@ Data::Validate::Sanctions::Redis - An extention of Lget_sanctioned_info(first_name => $first_name, last_name => $last_name, date_of_birth => $date_of_birth)->{matched}; # to update the sanction dataset (needs redis write access) - my $validator = Data::Validate::Sanctions::Redis->new(connection => $redis_write); + $validator = Data::Validate::Sanctions::Redis->new(connection => $redis_write); $validator->update_data(eu_token => $token); diff --git a/t/01_basic.t b/t/01_basic.t index ca86cf17..87f22ef5 100644 --- a/t/01_basic.t +++ b/t/01_basic.t @@ -3,7 +3,7 @@ use warnings; use Class::Unload; use Data::Validate::Sanctions; -use YAML::XS qw(Dump); +use YAML::XS qw(Dump); use Path::Tiny qw(tempfile); use Test::Exception; use Test::Warnings; @@ -12,10 +12,10 @@ use Test::More; $ENV{SANCTION_FILE} = "./share/sanctions.yml"; ok Data::Validate::Sanctions::is_sanctioned('NEVEROV', 'Sergei Ivanovich', -253411200), "Sergei Ivanov is_sanctioned for sure"; -ok Data::Validate::Sanctions::is_sanctioned('NEVEROV', 'Sergei Ivanovich'), "Sergei Ivanov is matched even without a birth date"; -ok !Data::Validate::Sanctions::is_sanctioned('NEVEROV', 'Sergei Ivanovich', 0), "Sergei Ivanov with incorrect dob does not match any entry"; -ok !Data::Validate::Sanctions::is_sanctioned(qw(chris down)), "Chris is a good guy (dummy name)"; -ok !Data::Validate::Sanctions::is_sanctioned(qw(Luke Lucky)), "Luke is a good boy (dummy name)"; +ok Data::Validate::Sanctions::is_sanctioned('NEVEROV', 'Sergei Ivanovich'), "Sergei Ivanov is matched even without a birth date"; +ok !Data::Validate::Sanctions::is_sanctioned('NEVEROV', 'Sergei Ivanovich', 0), "Sergei Ivanov with incorrect dob does not match any entry"; +ok !Data::Validate::Sanctions::is_sanctioned(qw(chris down)), "Chris is a good guy (dummy name)"; +ok !Data::Validate::Sanctions::is_sanctioned(qw(Luke Lucky)), "Luke is a good boy (dummy name)"; throws_ok { Data::Validate::Sanctions::set_sanction_file() } qr/sanction_file is needed/, "sanction file is required"; @@ -39,10 +39,10 @@ $tempfile->spew( lives_ok { Data::Validate::Sanctions::set_sanction_file("$tempfile"); }; is(Data::Validate::Sanctions::get_sanction_file(), "$tempfile", "get sanction file ok"); -ok !Data::Validate::Sanctions::is_sanctioned(qw(sergei ivanov)), "Sergei Ivanov is a good boy now"; -ok Data::Validate::Sanctions::is_sanctioned(qw(chris down)), "Chris is a bad boy now"; +ok !Data::Validate::Sanctions::is_sanctioned(qw(sergei ivanov)), "Sergei Ivanov is a good boy now"; +ok Data::Validate::Sanctions::is_sanctioned(qw(chris down)), "Chris is a bad boy now"; ok Data::Validate::Sanctions::is_sanctioned(qw(chris down), Date::Utility->new('1974-07-01')->epoch), "Chris is a bad boy even with birthdate"; -ok Data::Validate::Sanctions::is_sanctioned(qw(Luke Lucky)), "Luke is a bad boy without date of birth"; +ok Data::Validate::Sanctions::is_sanctioned(qw(Luke Lucky)), "Luke is a bad boy without date of birth"; ok Data::Validate::Sanctions::is_sanctioned(qw(Luke Lucky), Date::Utility->new('1996-10-10')->epoch), "Luke is a bad boy if year of birth matches"; ok !Data::Validate::Sanctions::is_sanctioned(qw(Luke Lucky), Date::Utility->new('1990-01-10')->epoch), "Luke is not sanctioned with mismatching year of birth"; diff --git a/t/02_env.t b/t/02_env.t index 23ea5d62..247e1d87 100644 --- a/t/02_env.t +++ b/t/02_env.t @@ -1,7 +1,7 @@ use strict; use warnings; -use YAML::XS qw(Dump); +use YAML::XS qw(Dump); use Path::Tiny qw(tempfile); use Test::Exception; use Test::Warnings; diff --git a/t/03_oo.t b/t/03_oo.t index 71491f58..763db459 100644 --- a/t/03_oo.t +++ b/t/03_oo.t @@ -1,20 +1,20 @@ use strict; use Class::Unload; use Data::Validate::Sanctions; -use YAML::XS qw(Dump); +use YAML::XS qw(Dump); use Path::Tiny qw(tempfile); use Test::Warnings; use Test::More; use Test::RedisServer; use RedisDB; my $redis_server; - eval { require Test::RedisServer; $redis_server = Test::RedisServer->new(conf => {port => 6379}) } +eval { require Test::RedisServer; $redis_server = Test::RedisServer->new(conf => {port => 6379}) } or plan skip_all => 'Test::RedisServer is required for this test'; my $validator = Data::Validate::Sanctions->new; ok $validator->is_sanctioned('NEVEROV', 'Sergei Ivanovich', -253411200), "Sergei Ivanov is_sanctioned for sure"; my $result = $validator->get_sanctioned_info('abu', 'usama', -306028800); -is $result->{matched}, 1; +is $result->{matched}, 1; is $result->{matched_args}->{dob_epoch}, -306028800; ok $result->{matched_args}->{name} =~ m/\babu\b/i and $result->{matched_args}->{name} =~ m/\busama\b/i; @@ -27,7 +27,7 @@ $result = $validator->get_sanctioned_info('Ali', 'Abu'); is $result->{matched}, 1, 'Should match because has dob_text'; $result = $validator->get_sanctioned_info('Abu', 'Salem', '1948-10-10'); -is $result->{matched}, 1; +is $result->{matched}, 1; is $result->{matched_args}->{dob_year}, 1948; ok $result->{matched_args}->{name} =~ m/\babu\b/i and $result->{matched_args}->{name} =~ m/\bsalem\b/i; @@ -88,12 +88,12 @@ $tmpb->spew( }); $validator = Data::Validate::Sanctions->new(sanction_file => "$tmpa"); -ok !$validator->is_sanctioned(qw(sergei ivanov)), "Sergei Ivanov not is_sanctioned"; -ok $validator->is_sanctioned(qw(tmpa)), "now sanction file is tmpa, and tmpa is in test1 list"; +ok !$validator->is_sanctioned(qw(sergei ivanov)), "Sergei Ivanov not is_sanctioned"; +ok $validator->is_sanctioned(qw(tmpa)), "now sanction file is tmpa, and tmpa is in test1 list"; ok !$validator->is_sanctioned("Mohammad reere yuyuy", "wqwqw qqqqq"), "is not in test1 list"; -ok $validator->is_sanctioned("Zaki", "Ahmad"), "is in test1 list - searched without dob"; -ok $validator->is_sanctioned("Zaki", "Ahmad", '1999-01-05'), 'the guy is sanctioned when dob year is matching'; -ok $validator->is_sanctioned("atom", "test", '1999-01-05'), "Match correctly with one world name in sanction list"; +ok $validator->is_sanctioned("Zaki", "Ahmad"), "is in test1 list - searched without dob"; +ok $validator->is_sanctioned("Zaki", "Ahmad", '1999-01-05'), 'the guy is sanctioned when dob year is matching'; +ok $validator->is_sanctioned("atom", "test", '1999-01-05'), "Match correctly with one world name in sanction list"; is_deeply $validator->get_sanctioned_info("Zaki", "Ahmad", '1999-01-05'), { @@ -198,9 +198,12 @@ $validator = Data::Validate::Sanctions->new(sanction_file => "$tmpa"); ok $validator->is_sanctioned(qw(tmpa)), "get sanction file from args"; subtest 'Subclass factory' => sub { - my $redis = RedisDB->new($redis_server->connect_info); + my $redis = RedisDB->new($redis_server->connect_info); - my $validator = Data::Validate::Sanctions->new(storage => 'redis', connection => $redis); + my $validator = Data::Validate::Sanctions->new( + storage => 'redis', + connection => $redis + ); is ref($validator), 'Data::Validate::Sanctions::Redis', 'A validator with redis storage is created'; }; diff --git a/t/04_fetcher.t b/t/04_fetcher.t index 51d041dc..3174905c 100644 --- a/t/04_fetcher.t +++ b/t/04_fetcher.t @@ -4,7 +4,7 @@ use utf8; use Class::Unload; use Data::Validate::Sanctions; -use YAML::XS qw(Dump); +use YAML::XS qw(Dump); use Path::Tiny qw(tempfile); use List::Util qw(first); use Test::More; @@ -41,18 +41,20 @@ subtest 'source url arguments' => sub { ); my $data = Data::Validate::Sanctions::Fetcher::run(%test_args); - cmp_deeply $data, {'HMT-Sanctions' => { - error => ignore(), - }, - 'OFAC-Consolidated' => { - error => ignore(), - }, - 'EU-Sanctions' => { - error => ignore(), - }, - 'OFAC-SDN' => { - error => ignore(), - }, + cmp_deeply $data, + { + 'HMT-Sanctions' => { + error => ignore(), + }, + 'OFAC-Consolidated' => { + error => ignore(), + }, + 'EU-Sanctions' => { + error => ignore(), + }, + 'OFAC-SDN' => { + error => ignore(), + }, }, 'All sources return errors - no content'; @@ -67,25 +69,27 @@ subtest 'EU Sanctions' => sub { warnings_like { $data = Data::Validate::Sanctions::Fetcher::run(%args, eu_url => undef); } - [qr/EU Sanctions will fail whithout eu_token or eu_url/], - 'Correct warning when the EU sanctions token is missing'; + [qr/EU Sanctions will fail whithout eu_token or eu_url/], 'Correct warning when the EU sanctions token is missing'; cmp_deeply $data->{$source_name}, {error => ignore()}, 'There is an error in the result'; like $data->{$source_name}->{error}, qr/Url is empty for EU-Sanctions/, 'Correct error for missing EU url'; $data = Data::Validate::Sanctions::Fetcher::run( - %args, - eu_url => undef, - eu_token => 'ASDF' - ); - like $data->{$source_name}->{error}, qr(\bUser agent MockObject is hit by the url: https://webgate.ec.europa.eu/fsd/fsf/public/files/xmlFullSanctionsList_1_1/content\?token=ASDF\b), 'Token is added to the URL in error message'; + %args, + eu_url => undef, + eu_token => 'ASDF' + ); + like $data->{$source_name}->{error}, + qr(\bUser agent MockObject is hit by the url: https://webgate.ec.europa.eu/fsd/fsf/public/files/xmlFullSanctionsList_1_1/content\?token=ASDF\b), + 'Token is added to the URL in error message'; $data = Data::Validate::Sanctions::Fetcher::run( - %args, - eu_url => 'http://dummy.binary.com', - eu_token => 'ASDF' - ); - like $data->{$source_name}->{error}, qr(\bUser agent MockObject is hit by the url: http://dummy.binary.com\b), 'eu_url argument is directly used, without eu_token modification'; + %args, + eu_url => 'http://dummy.binary.com', + eu_token => 'ASDF' + ); + like $data->{$source_name}->{error}, qr(\bUser agent MockObject is hit by the url: http://dummy.binary.com\b), + 'eu_url argument is directly used, without eu_token modification'; $data = Data::Validate::Sanctions::Fetcher::run(%args); ok $data->{$source_name}, 'EU Sanctions are loaded from the sample file'; @@ -144,8 +148,8 @@ subtest 'HMT Sanctions' => sub { $data = Data::Validate::Sanctions::Fetcher::run(%args); ok $data->{$source_name}, 'HMT Sanctions are loaded from the sample file'; - is $data->{$source_name}{updated}, 1587945600, "Sanctions update date matches the sample file"; - is scalar $data->{$source_name}{content}->@*, 23, "Number of names matches the content of the sample file"; + is $data->{$source_name}{updated}, 1587945600, "Sanctions update date matches the sample file"; + is scalar $data->{$source_name}{content}->@*, 23, "Number of names matches the content of the sample file"; is_deeply find_entry_by_name($data->{$source_name}, 'HOJATI Mohsen'), { @@ -195,8 +199,8 @@ subtest 'OFAC Sanctions' => sub { # OFAC sources have the same structure. We've created the samle sample file for both of them. ok $data->{$source_name}, 'Sanctions are loaded from the sample file'; - is $data->{$source_name}{updated}, 1587513600, "Sanctions update date matches the content of sample file"; - is scalar $data->{$source_name}{content}->@*, 6, "Number of names matches the content of the sample file"; + is $data->{$source_name}{updated}, 1587513600, "Sanctions update date matches the content of sample file"; + is scalar $data->{$source_name}{content}->@*, 6, "Number of names matches the content of the sample file"; my $dataset = $data->{$source_name}->{names_list}; diff --git a/t/05_sanctions_redis.t b/t/05_sanctions_redis.t index 5799af94..9b7063c8 100644 --- a/t/05_sanctions_redis.t +++ b/t/05_sanctions_redis.t @@ -20,7 +20,7 @@ my $redis_server; eval { require Test::RedisServer; $redis_server = Test::RedisServer->new(conf => {port => 6379}) } or plan skip_all => 'Test::RedisServer is required for this test'; -my $redis = RedisDB->new($redis_server->connect_info); +my $redis = RedisDB->new($redis_server->connect_info); my $sample_data = { 'EU-Sanctions' => { @@ -82,10 +82,30 @@ subtest 'Class constructor' => sub { ok $validator = Data::Validate::Sanctions::Redis->new(connection => $redis), 'Successfully created the object with redis object'; is_deeply $validator->data, { - 'EU-Sanctions' => {content => [], verified => 0, updated => 0, error=> ''}, - 'HMT-Sanctions' => {content => [], verified => 0, updated => 0, error=> ''}, - 'OFAC-Consolidated' => {content => [], verified => 0, updated => 0, error=> ''}, - 'OFAC-SDN' => {content => [], verified => 0, updated => 0, error=> ''}, + 'EU-Sanctions' => { + content => [], + verified => 0, + updated => 0, + error => '' + }, + 'HMT-Sanctions' => { + content => [], + verified => 0, + updated => 0, + error => '' + }, + 'OFAC-Consolidated' => { + content => [], + verified => 0, + updated => 0, + error => '' + }, + 'OFAC-SDN' => { + content => [], + verified => 0, + updated => 0, + error => '' + }, }, 'There is no sanction data'; }; @@ -96,7 +116,8 @@ subtest 'Update Data' => sub { my $mock_data = { 'EU-Sanctions' => { updated => 90, - content => []}, + content => [] + }, }; $mock_fetcher->redefine(run => sub { return clone($mock_data) }); set_fixed_time(1500); @@ -108,9 +129,24 @@ subtest 'Update Data' => sub { updated => 90, verified => 1500, }, - 'HMT-Sanctions' => {content => [], verified => 1500, updated => 0, error=> ''}, - 'OFAC-Consolidated' => {content => [], verified => 1500, updated => 0, error=> ''}, - 'OFAC-SDN' => {content => [], verified => 1500, updated => 0, error=> ''}, + 'HMT-Sanctions' => { + content => [], + verified => 1500, + updated => 0, + error => '' + }, + 'OFAC-Consolidated' => { + content => [], + verified => 1500, + updated => 0, + error => '' + }, + 'OFAC-SDN' => { + content => [], + verified => 1500, + updated => 0, + error => '' + }, }; is_deeply $validator->data, $expected, 'Data is correctly loaded'; check_redis_content('EU-Sanctions', $mock_data->{'EU-Sanctions'}, 1500); @@ -186,27 +222,74 @@ subtest 'Update Data' => sub { subtest 'load data' => sub { clear_redis(); my $validator = Data::Validate::Sanctions::Redis->new(connection => $redis); - my $expected = { - 'EU-Sanctions' => {content => [], verified => 0, updated => 0, error => ''}, - 'HMT-Sanctions'=> {content => [], verified => 0, updated => 0, error => ''}, - 'OFAC-Consolidated' => {content => [], verified => 0, updated => 0, error => ''}, - 'OFAC-SDN' => {content => [], verified => 0, updated => 0, error => ''} - }; + my $expected = { + 'EU-Sanctions' => { + content => [], + verified => 0, + updated => 0, + error => '' + }, + 'HMT-Sanctions' => { + content => [], + verified => 0, + updated => 0, + error => '' + }, + 'OFAC-Consolidated' => { + content => [], + verified => 0, + updated => 0, + error => '' + }, + 'OFAC-SDN' => { + content => [], + verified => 0, + updated => 0, + error => '' + }}; is_deeply $validator->data, $expected, 'Saction lists are loaded with default values when redis is empty'; is $validator->last_updated, 0, 'Updated date is zero'; my $test_data = { - 'EU-Sanctions' => {}, - 'HMT-Sanctions'=> {updated => 1001, content => [{names => ['TMPA']}], verified => 1101, extra_field => 1}, - 'OFAC-SDN' => {updated => 1002, content => [], verified => 1102, error => 'Test error'} - }; + 'EU-Sanctions' => {}, + 'HMT-Sanctions' => { + updated => 1001, + content => [{names => ['TMPA']}], + verified => 1101, + extra_field => 1 + }, + 'OFAC-SDN' => { + updated => 1002, + content => [], + verified => 1102, + error => 'Test error' + }}; $expected = { - 'EU-Sanctions' => {content => [], verified => 0, updated => 0, error => ''}, - 'HMT-Sanctions'=> {updated => 1001, content => [{names => ['TMPA']}], verified => 1101, error => ''}, - 'OFAC-Consolidated' => {content => [], verified => 0, updated => 0, error => ''}, - 'OFAC-SDN' => {updated => 1002, content => [], verified => 1102, error => 'Test error'} - }; + 'EU-Sanctions' => { + content => [], + verified => 0, + updated => 0, + error => '' + }, + 'HMT-Sanctions' => { + updated => 1001, + content => [{names => ['TMPA']}], + verified => 1101, + error => '' + }, + 'OFAC-Consolidated' => { + content => [], + verified => 0, + updated => 0, + error => '' + }, + 'OFAC-SDN' => { + updated => 1002, + content => [], + verified => 1102, + error => 'Test error' + }}; for my $source (keys %$test_data) { # save data to redis @@ -218,10 +301,38 @@ subtest 'load data' => sub { } $validator = Data::Validate::Sanctions::Redis->new(connection => $redis); - is_deeply $validator->data->{'EU-Sanctions'}, {content => [], verified => 0, updated => 0, error => ''}, 'EU sanctions list loaded with default values from Redis'; - is_deeply $validator->data->{'HMT-Sanctions'}, {updated => 1001, content => [{names => ['TMPA']}], verified => 1101, error => ''}, 'HMT sanctions loaded correctly with extra field ignored'; - is_deeply $validator->data->{'OFAC-SDN'}, {updated => 1002, content => [], verified => 1102, error => 'Test error'}, 'OFAC-SND loaded with correct error'; - is_deeply $validator->data->{'OFAC-Consolidated'}, {content => [], verified => 0, updated => 0, error => ''}, 'Missing source OFAC-Consolodated loaded with default values'; + is_deeply $validator->data->{'EU-Sanctions'}, + { + content => [], + verified => 0, + updated => 0, + error => '' + }, + 'EU sanctions list loaded with default values from Redis'; + is_deeply $validator->data->{'HMT-Sanctions'}, + { + updated => 1001, + content => [{names => ['TMPA']}], + verified => 1101, + error => '' + }, + 'HMT sanctions loaded correctly with extra field ignored'; + is_deeply $validator->data->{'OFAC-SDN'}, + { + updated => 1002, + content => [], + verified => 1102, + error => 'Test error' + }, + 'OFAC-SND loaded with correct error'; + is_deeply $validator->data->{'OFAC-Consolidated'}, + { + content => [], + verified => 0, + updated => 0, + error => '' + }, + 'Missing source OFAC-Consolodated loaded with default values'; is $validator->last_updated, 1002, 'Update date is the maximum of the dates in all sources'; }; @@ -235,9 +346,9 @@ subtest 'get sanctioned info' => sub { $validator->update_data(); # create a new new validator for sanction checks. No write_redis is needed. - $validator = Data::Validate::Sanctions::Redis->new(connection => $redis); + $validator = Data::Validate::Sanctions::Redis->new(connection => $redis); $sample_data->{$_}->{verified} = 1000 for keys %$sample_data; - $sample_data->{$_}->{error} = '' for keys %$sample_data; + $sample_data->{$_}->{error} = '' for keys %$sample_data; is_deeply $validator->data, $sample_data, 'Sample data is correctly loaded'; ok !$validator->is_sanctioned(qw(sergei ivanov)), "Sergei Ivanov not is_sanctioned"; diff --git a/xt/20_update.t b/xt/20_update.t index 81d6a32a..ce4eac51 100644 --- a/xt/20_update.t +++ b/xt/20_update.t @@ -3,7 +3,7 @@ use warnings; use Test::More; use File::Temp qw(tempfile); -use FindBin qw($Bin); +use FindBin qw($Bin); use File::stat; use Path::Tiny; use YAML::XS qw(Dump); From 86c5f993369ce95c99adfc03401009dac9c1b394 Mon Sep 17 00:00:00 2001 From: ragheb-deriv Date: Wed, 2 Nov 2022 12:37:05 +0000 Subject: [PATCH 52/61] added no critic to pod SYNOPSIS [ci] --- lib/Data/Validate/Sanctions/Redis.pm | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/lib/Data/Validate/Sanctions/Redis.pm b/lib/Data/Validate/Sanctions/Redis.pm index 26244c59..084de568 100644 --- a/lib/Data/Validate/Sanctions/Redis.pm +++ b/lib/Data/Validate/Sanctions/Redis.pm @@ -123,11 +123,11 @@ __END__ Data::Validate::Sanctions::Redis - An extension of L that stores sanction data in redis. =head1 SYNOPSIS - - use Data::Validate::Sanctions::Redis; - my $redis_read, $last_name, $first_name, $date_of_birth, $redis_write, $token, $validator; + ## no critic + + use Data::Validate::Sanctions::Redis; - $validator = Data::Validate::Sanctions::Redis->new(connection => $redis_read); + my $validator = Data::Validate::Sanctions::Redis->new(connection => $redis_read); # to validate clients by their name print 'BAD' if $validator->is_sanctioned("$last_name $first_name"); @@ -135,13 +135,14 @@ Data::Validate::Sanctions::Redis - An extension of Lget_sanctioned_info(first_name => $first_name, last_name => $last_name, date_of_birth => $date_of_birth)->{matched}; # to update the sanction dataset (needs redis write access) - $validator = Data::Validate::Sanctions::Redis->new(connection => $redis_write); + my $validator = Data::Validate::Sanctions::Redis->new(connection => $redis_write); ## no critic $validator->update_data(eu_token => $token); # create object from the parent (factory) class - $validator = Data::Validate::Sanctions->new(storage => 'redis', connection => $redis_write); + my $validator = Data::Validate::Sanctions->new(storage => 'redis', connection => $redis_write); + ## no critic =head1 DESCRIPTION From fad04a3a8f32bb1bf2f026e52c439586d4068487 Mon Sep 17 00:00:00 2001 From: ragheb-deriv Date: Wed, 2 Nov 2022 12:50:11 +0000 Subject: [PATCH 53/61] bug fix [ci] --- lib/Data/Validate/Sanctions/Redis.pm | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/lib/Data/Validate/Sanctions/Redis.pm b/lib/Data/Validate/Sanctions/Redis.pm index 084de568..6a344596 100644 --- a/lib/Data/Validate/Sanctions/Redis.pm +++ b/lib/Data/Validate/Sanctions/Redis.pm @@ -123,11 +123,11 @@ __END__ Data::Validate::Sanctions::Redis - An extension of L that stores sanction data in redis. =head1 SYNOPSIS + ## no critic + use Data::Validate::Sanctions::Redis; - use Data::Validate::Sanctions::Redis; - - my $validator = Data::Validate::Sanctions::Redis->new(connection => $redis_read); + my $validator = Data::Validate::Sanctions::Redis->new(connection => $redis_read); # to validate clients by their name print 'BAD' if $validator->is_sanctioned("$last_name $first_name"); @@ -138,7 +138,6 @@ Data::Validate::Sanctions::Redis - An extension of Lnew(connection => $redis_write); ## no critic $validator->update_data(eu_token => $token); - # create object from the parent (factory) class my $validator = Data::Validate::Sanctions->new(storage => 'redis', connection => $redis_write); From 5a5b28c2cc1e33f65c613878fb709da07d444964 Mon Sep 17 00:00:00 2001 From: ragheb-deriv Date: Wed, 2 Nov 2022 13:24:44 +0000 Subject: [PATCH 54/61] fixed eol errors [ci] --- lib/Data/Validate/Sanctions/Redis.pm | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/lib/Data/Validate/Sanctions/Redis.pm b/lib/Data/Validate/Sanctions/Redis.pm index 6a344596..bbc6d20b 100644 --- a/lib/Data/Validate/Sanctions/Redis.pm +++ b/lib/Data/Validate/Sanctions/Redis.pm @@ -123,12 +123,11 @@ __END__ Data::Validate::Sanctions::Redis - An extension of L that stores sanction data in redis. =head1 SYNOPSIS - ## no critic use Data::Validate::Sanctions::Redis; - + my $validator = Data::Validate::Sanctions::Redis->new(connection => $redis_read); - + # to validate clients by their name print 'BAD' if $validator->is_sanctioned("$last_name $first_name"); # or by more profile data @@ -141,8 +140,6 @@ Data::Validate::Sanctions::Redis - An extension of Lnew(storage => 'redis', connection => $redis_write); - ## no critic - =head1 DESCRIPTION Data::Validate::Sanctions::Redis is a simple validitor to validate a name against sanctions lists. From b02ef4940beeb41cb22b22d8e360183248ce6c19 Mon Sep 17 00:00:00 2001 From: ragheb-deriv Date: Thu, 3 Nov 2022 06:45:58 +0000 Subject: [PATCH 55/61] trigger tests [ci] 2022-11-03 06:45:58 From dd463f12d75257516082aaa8df6c97ab889a589c Mon Sep 17 00:00:00 2001 From: ragheb-deriv Date: Thu, 3 Nov 2022 14:11:28 +0000 Subject: [PATCH 56/61] trigger tests [ci] 2022-11-03 14:11:28 From 8ece39758252c8723b0a1befac86a6dabcea1129 Mon Sep 17 00:00:00 2001 From: ragheb-deriv Date: Mon, 7 Nov 2022 06:35:24 +0000 Subject: [PATCH 57/61] trigger tests [ci] 2022-11-07 06:35:24 From d65d0b9076ca691826b818222e395f055896e294 Mon Sep 17 00:00:00 2001 From: ragheb-deriv Date: Mon, 7 Nov 2022 07:55:58 +0000 Subject: [PATCH 58/61] trigger tests [ci] 2022-11-07 07:55:58 From bbb52afe2036219375c8d2b2e312b4d7b7e9a599 Mon Sep 17 00:00:00 2001 From: ragheb-deriv Date: Mon, 7 Nov 2022 08:31:40 +0000 Subject: [PATCH 59/61] removed redis-derive from ci [ci] --- .circleci/config.yml | 2 +- Changes | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index bc8f76d3..af456f3b 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -12,7 +12,7 @@ jobs: - run: name: Install Redis server command: | - apt-get install -y redis && apt-get install -y redis-server + apt-get install -y redis - run: command: cpm install -g --no-test Dist::Zilla Dist::Zilla::App::Command::cover ExtUtils::MakeMaker diff --git a/Changes b/Changes index 4e7a8f1d..a9861210 100644 --- a/Changes +++ b/Changes @@ -1,8 +1,6 @@ Revision history for Data-Validate-Sanctions {{$NEXT}} -0.14 2022-09-13 13:55:00 CST - New module for redis storage 0.13 2022-07-26 13:55:00 CST Improving the search for larger sanction lists From f8b1b00196afba8d25858ed52eaf7d970874d1f4 Mon Sep 17 00:00:00 2001 From: ragheb-deriv Date: Mon, 7 Nov 2022 08:53:50 +0000 Subject: [PATCH 60/61] trigger tests [ci] 2022-11-07 08:53:50 From a4639c1827e2a8f2b5c593782eed4c039627d6f7 Mon Sep 17 00:00:00 2001 From: ragheb-deriv Date: Wed, 9 Nov 2022 10:32:31 +0000 Subject: [PATCH 61/61] trigger tests [ci] 2022-11-09 10:32:31