From ee1a4c1d8385ae5fd1446dc655eb884f26a0f9ec Mon Sep 17 00:00:00 2001 From: Monalika Patnaik <99353300+MonalikaPatnaik@users.noreply.github.com> Date: Wed, 17 Jan 2024 16:11:26 +0530 Subject: [PATCH] feat: Allowing Users to subscribe to the pro-newsletter (#8856) Brevo.pm module for adding users to contact list using API at subscription time. --------- Co-authored-by: Alex Garel --- lib/ProductOpener/Brevo.pm | 120 +++++++++++++++++++++++++++++++++++++ lib/ProductOpener/Users.pm | 6 ++ stop_words.txt | 1 + tests/unit/brevo.t | 116 +++++++++++++++++++++++++++++++++++ 4 files changed, 243 insertions(+) create mode 100644 lib/ProductOpener/Brevo.pm create mode 100644 tests/unit/brevo.t diff --git a/lib/ProductOpener/Brevo.pm b/lib/ProductOpener/Brevo.pm new file mode 100644 index 0000000000000..17cdca06146ce --- /dev/null +++ b/lib/ProductOpener/Brevo.pm @@ -0,0 +1,120 @@ +# This file is part of Product Opener. +# +# Product Opener +# Copyright (C) 2011-2023 Association Open Food Facts +# Contact: contact@openfoodfacts.org +# Address: 21 rue des Iles, 94100 Saint-Maur des Fossés, France +# +# Product Opener is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +=head1 NAME + +ProductOpener::Brevo -add users' contact to the Brevo contact list. + +=head1 DESCRIPTION + +=cut + +package ProductOpener::Brevo; +use ProductOpener::PerlStandards; +use ProductOpener::Config2; +use Exporter qw< import >; +use Log::Any qw($log); +use JSON; + +BEGIN { + use vars qw(@ISA @EXPORT_OK %EXPORT_TAGS); + @EXPORT_OK = qw( + &add_contact_to_list + $list_id + $brevo_api_key + + ); + %EXPORT_TAGS = (all => [@EXPORT_OK]); +} +use vars @EXPORT_OK; + +use ProductOpener::Config qw/:all/; +use ProductOpener::Display qw/:all/; +use ProductOpener::Users qw/:all/; +use ProductOpener::Lang qw/:all/; +use ProductOpener::API qw/:all/; + +use LWP::UserAgent; +use HTTP::Request::Common; + +my $api_base_url = 'https://api.brevo.com/v3'; + +# Brevo API key +# We use a function to be able to override it for tests +sub get_brevo_api_key() { + return $ProductOpener::Config2::brevo_api_key; +} +# Brevo list ID +sub get_list_id() { + return $ProductOpener::Config2::list_id; +} + +sub add_contact_to_list ($email, $username, $country, $language) { + my $brevo_api_key = get_brevo_api_key(); + my $list_id = get_list_id(); + # We need a Brevo key to use this API, otherwise we silently fails + if (!$brevo_api_key) { + $log->debug("No Brevo key, no list subscription.") if $log->is_debug(); + return -1; + } + # Brevo API endpoint for adding a contact to a list + my $api_endpoint = '/contacts'; + + my $ua = LWP::UserAgent->new; + + # HTTP request headers + my %headers = ( + 'Accept' => 'application/json', + 'Content-Type' => 'application/json', + 'api-key' => $brevo_api_key, + ); + + my $contact_data = { + + email => $email, + attributes => { + USERNAME => $username, + COUNTRY => $country, + LANGUAGE => $language, + }, + listIds => [$list_id], + + }; + + my $json_data = encode_json($contact_data); + + my $request = POST("$api_base_url$api_endpoint", %headers, Content => $json_data); + + my $response = $ua->request($request); + + if ($response && $response->is_success) { + $log->debug("Contact added successfully! Response: " . $response->content) if $log->is_debug(); + return 1; # Contact added successfully + } + else { + $log->error("Failed to add contact. Status: " . $response->status_line . ", Response: " . $response->content) + if $log->is_error(); + return 0; # Failed to add contact + } + +} + +1; + diff --git a/lib/ProductOpener/Users.pm b/lib/ProductOpener/Users.pm index 289696c62de9a..16c335fb73d8b 100644 --- a/lib/ProductOpener/Users.pm +++ b/lib/ProductOpener/Users.pm @@ -715,6 +715,12 @@ EMAIL ; $error += send_email_to_admin("Inscription de $userid", $admin_mail_body); } + # Check if the user subscribed to the newsletter + if ($user_ref->{newsletter}) { + add_contact_to_list($user_ref->{email}, $user_ref->{user_id}, $user_ref->{country}, + $user_ref->{preferred_language}); + } + return $error; } diff --git a/stop_words.txt b/stop_words.txt index ac42319b800be..b2c9efcea68c6 100644 --- a/stop_words.txt +++ b/stop_words.txt @@ -30,6 +30,7 @@ barquette blé boolean Brassicas +Brevo canneberge canonicalize canonicalizes diff --git a/tests/unit/brevo.t b/tests/unit/brevo.t new file mode 100644 index 0000000000000..5985e6a1af5f7 --- /dev/null +++ b/tests/unit/brevo.t @@ -0,0 +1,116 @@ +use ProductOpener::PerlStandards; +use Test::More; +use Test::MockModule; +use HTTP::Response; +use HTTP::Headers; +use JSON; +use Test::Fake::HTTPD qw/run_http_server/; +use ProductOpener::Brevo qw/:all/; +use ProductOpener::APITest qw/:all/; +use ProductOpener::Test qw/:all/; +use File::Temp (); + +# Stores what will be sent to the mocked Brevo API +my $request_headers; +my $request_content; + +# Mock needed functions to simulate the Brevo API +sub do_mock ($brevo_api_key, $list_id, $code, $msg, $response) { + # reset results holders + $request_headers = undef; + $request_content = undef; + # Mock $ProductOpener::Brevo::get_brevo_api_key + my $mocked_brevo = Test::MockModule->new('ProductOpener::Brevo'); + $mocked_brevo->mock( + 'get_brevo_api_key' => sub { + return $brevo_api_key; + }, + 'get_list_id' => sub { + return $list_id; + }, + ); + + # Mock LWP::UserAgent to check our request parameters to brevo are correct and simulate a success + my $mocked_ua = Test::MockModule->new('LWP::UserAgent'); + $mocked_ua->mock( + 'request' => sub { + + my ($self, $request) = @_; + # store sent request to verify it in test + $request_headers = $request->headers(); + $request_content = $request->content; + return HTTP::Response->new($code, $msg, HTTP::Headers->new(), $response); + } + ); + return ($mocked_ua, $mocked_brevo); +} +# unmocking +sub do_unmock (@mocks) { + foreach my $mock (@mocks) { + $mock->unmock_all(); + } + return; +} + +# we use same values for tests +my $expected_headers = { + '::std_case' => {'api-key' => 'Api-Key'}, + 'accept' => 'application/json', + 'api-key' => 'abcdef1234', + 'content-length' => 123, + 'content-type' => 'application/json', +}; +my $expected_content = { + email => 'abc@example.com', + attributes => {USERNAME => 'elly', COUNTRY => 'world', LANGUAGE => 'english'}, + listIds => ["123456789"], +}; + +# Test the add_contact_to_list function +{ + my @mocks = do_mock("abcdef1234", "123456789", "200", "OK", '{"status": "success"}'); + + # Call the function + my $result = add_contact_to_list('abc@example.com', 'elly', 'world', 'english'); + + is($result, 1, 'Contact added successfully'); + + # Verify what we have sent to Brevo + is_deeply($request_headers, $expected_headers, 'Verify request headers for good request'); + is_deeply(decode_json($request_content), $expected_content, 'Verify request content for good request'); + + do_unmock(@mocks); + +} + +# Test with a bad response +{ + my @mocks = do_mock("abcdef1234", "123456789", "500", "Internal Server Error", '{"status": "error"}'); + + # Call the function + my $result = add_contact_to_list('abc@example.com', 'elly', 'world', 'english'); + + is($result, 0, 'Contact not added due to bad response'); + # Verify the sent data structures using is_deeply + is_deeply($request_headers, $expected_headers, 'Verify request headers for bad request'); + is_deeply(decode_json($request_content), $expected_content, 'Verify request content for bad request'); + + do_unmock(@mocks); +} + +# test everything ok without a key +{ + my @mocks = do_mock(undef, "123456789", "200", "OK", '{"status": "success"}'); + + # Call the function + my $result = add_contact_to_list('abc@example.com', 'elly', 'world', 'english'); + + is($result, -1, 'API not called due to no key'); + # Verify the sent data structures using is_deeply + is($request_headers, undef, 'Verify no brevo call for no key'); + is($request_content, undef, 'Verify no brevo call for no key (content)'); + + do_unmock(@mocks); +} + +done_testing();