Skip to content

Commit

Permalink
feat: Allowing Users to subscribe to the pro-newsletter (#8856)
Browse files Browse the repository at this point in the history
Brevo.pm module for adding users to contact list using API at subscription time.

---------
Co-authored-by: Alex Garel <[email protected]>
  • Loading branch information
MonalikaPatnaik authored Jan 17, 2024
1 parent 5df6d09 commit 385b03d
Show file tree
Hide file tree
Showing 4 changed files with 243 additions and 0 deletions.
120 changes: 120 additions & 0 deletions lib/ProductOpener/Brevo.pm
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
# This file is part of Product Opener.
#
# Product Opener
# Copyright (C) 2011-2023 Association Open Food Facts
# Contact: [email protected]
# 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 <http://www.gnu.org/licenses/>.

=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;

6 changes: 6 additions & 0 deletions lib/ProductOpener/Users.pm
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

Expand Down
1 change: 1 addition & 0 deletions stop_words.txt
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ barquette
blé
boolean
Brassicas
Brevo
canneberge
canonicalize
canonicalizes
Expand Down
116 changes: 116 additions & 0 deletions tests/unit/brevo.t
Original file line number Diff line number Diff line change
@@ -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 => '[email protected]',
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('[email protected]', '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('[email protected]', '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('[email protected]', '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();

0 comments on commit 385b03d

Please sign in to comment.