-
-
Notifications
You must be signed in to change notification settings - Fork 384
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
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 <[email protected]>
- Loading branch information
1 parent
91ea3ae
commit ee1a4c1
Showing
4 changed files
with
243 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -30,6 +30,7 @@ barquette | |
blé | ||
boolean | ||
Brassicas | ||
Brevo | ||
canneberge | ||
canonicalize | ||
canonicalizes | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(); |