Skip to content

Commit

Permalink
feat(brevo): small changes after review
Browse files Browse the repository at this point in the history
  • Loading branch information
NicolasAbrn committed Feb 12, 2025
1 parent 4ad29c8 commit 98d72e1
Show file tree
Hide file tree
Showing 7 changed files with 76 additions and 116 deletions.
4 changes: 2 additions & 2 deletions lemarche/companies/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,6 @@ def save(self, *args, **kwargs):
@receiver(post_save, sender=Company)
def create_company_in_brevo(sender, instance, created, **kwargs):
if created:
from lemarche.utils.apis.api_brevo import create_company
from lemarche.utils.apis.api_brevo import create_brevo_company_from_company

create_company(instance)
create_brevo_company_from_company(instance)
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ def handle(self, recently_updated: bool, **options):

# Step 2: loop on the siaes
for index, siae in enumerate(siaes_qs):
api_brevo.create_company(siae)
api_brevo.create_brevo_company_from_siae(siae)
if (index % 10) == 0: # avoid API rate-limiting
time.sleep(1)
if (index % 500) == 0:
Expand Down
61 changes: 20 additions & 41 deletions lemarche/crm/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@


@override_settings(BITOUBI_ENV="production", BREVO_API_KEY="fake-key")
@patch("lemarche.utils.apis.api_brevo.sib_api_v3_sdk.CompaniesApi")
@patch("lemarche.utils.apis.api_brevo.sib_api_v3_sdk.Configuration")
@patch("lemarche.utils.apis.api_brevo.sib_api_v3_sdk.ApiClient")
class CrmBrevoSyncCompaniesCommandTest(TestCase):
@classmethod
def setUpTestData(cls):
Expand Down Expand Up @@ -55,66 +58,39 @@ def setUpTestData(cls):
Siae.objects.with_tender_stats(since_days=90).filter(id=cls.siae_with_brevo_id.id).first()
)

@patch("lemarche.utils.apis.api_brevo.sib_api_v3_sdk.CompaniesApi")
@patch("lemarche.utils.apis.api_brevo.sib_api_v3_sdk.Configuration")
@patch("lemarche.utils.apis.api_brevo.sib_api_v3_sdk.ApiClient")
def test_new_siaes_are_synced_in_brevo(self, mock_api_client, mock_configuration, mock_companies_api):
"""Test new siaes are synced in brevo"""
mock_config = MagicMock()
mock_configuration.return_value = mock_config

mock_client = MagicMock()
mock_api_client.return_value = mock_client

mock_api = MagicMock()
mock_companies_api.return_value = mock_api
mock_configuration.return_value = MagicMock()
mock_api_client.return_value = MagicMock()
mock_companies_api.return_value = MagicMock()

mock_response = MagicMock()
mock_response.id = 12345
mock_api.companies_post.return_value = mock_response

mock_companies_api.companies_post.return_value = mock_response

expected_count = Siae.objects.filter(brevo_company_id__isnull=True).count()

# Run the command
call_command("crm_brevo_sync_companies")

actual_count = mock_api.companies_post.call_count
actual_count = mock_companies_api.companies_post.call_count

self.assertEqual(actual_count, expected_count, f"Expected {expected_count} API calls, got {actual_count}")

@patch("lemarche.utils.apis.api_brevo.sib_api_v3_sdk.CompaniesApi")
@patch("lemarche.utils.apis.api_brevo.sib_api_v3_sdk.Configuration")
@patch("lemarche.utils.apis.api_brevo.sib_api_v3_sdk.ApiClient")
def test_siae_has_tender_stats(self, mock_api_client, mock_configuration, mock_companies_api):
# Setup mock API chain similar to above
mock_config = MagicMock()
mock_configuration.return_value = mock_config
mock_client = MagicMock()
mock_api_client.return_value = mock_client
mock_api = MagicMock()
mock_companies_api.return_value = mock_api
mock_response = MagicMock()
mock_response.id = 12345
mock_api.companies_post.return_value = mock_response
mock_configuration.return_value = MagicMock()
mock_api_client.return_value = MagicMock()
mock_companies_api.return_value = MagicMock()

self.assertIsNotNone(self.siae_with_user_stats)
self.assertIsNotNone(self.siae_with_brevo_id_all_stats)

@patch("lemarche.utils.apis.api_brevo.sib_api_v3_sdk.CompaniesApi")
@patch("lemarche.utils.apis.api_brevo.sib_api_v3_sdk.Configuration")
@patch("lemarche.utils.apis.api_brevo.sib_api_v3_sdk.ApiClient")
def test_siae_extra_data_is_preserved(self, mock_api_client, mock_configuration, mock_companies_api):
"""Test that creating a company in Brevo preserves existing extra_data"""
# Setup mock API chain
mock_config = MagicMock()
mock_configuration.return_value = mock_config
mock_client = MagicMock()
mock_api_client.return_value = mock_client
mock_api = MagicMock()
mock_companies_api.return_value = mock_api
mock_response = MagicMock()
mock_response.id = 12345
mock_api.companies_post.return_value = mock_response
mock_configuration.return_value = MagicMock()
mock_api_client.return_value = MagicMock()
mock_companies_api.return_value = MagicMock()

# Set initial extra_data and ensure other Siaes have brevo_company_id
initial_extra_data = {"test_data": "test value"}
Expand All @@ -123,13 +99,16 @@ def test_siae_extra_data_is_preserved(self, mock_api_client, mock_configuration,
self.siae_with_name.brevo_company_id = "999999"
self.siae_with_name.save()

mock_api.companies_post.reset_mock()
mock_companies_api.return_value = MagicMock()
mock_response = MagicMock()
mock_response.id = 12345
mock_companies_api.companies_post.return_value = mock_response

call_command("crm_brevo_sync_companies", recently_updated=True)

self.siae_with_user.refresh_from_db()

mock_api.companies_post.assert_called_once()
mock_companies_api.companies_post.assert_called_once()

self.assertEqual(self.siae_with_user.brevo_company_id, "12345")

Expand Down
4 changes: 2 additions & 2 deletions lemarche/siaes/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -1283,9 +1283,9 @@ def siae_post_save(sender, instance, created, **kwargs):

# Handle Brevo company creation
if created:
from lemarche.utils.apis.api_brevo import create_company
from lemarche.utils.apis.api_brevo import create_brevo_company_from_siae

create_company(instance)
create_brevo_company_from_siae(instance)


@receiver(m2m_changed, sender=Siae.users.through)
Expand Down
56 changes: 14 additions & 42 deletions lemarche/siaes/tests/test_models.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from django.core.exceptions import ValidationError
from django.test import TestCase
from django.test import TestCase, override_settings
from django.utils import timezone
from unittest.mock import patch, MagicMock

Expand Down Expand Up @@ -681,48 +681,20 @@ def test_last_activity_is_updated_at(self):


class SiaeSignalTest(TestCase):
@patch("lemarche.utils.apis.api_brevo.create_company")
def test_create_siae_in_brevo_signal(self, mock_create_company):
"""Test that creating a new SIAE triggers the Brevo sync"""
# Mock the environment check to always return True
with patch("lemarche.utils.apis.api_brevo.settings.BITOUBI_ENV", "production"):
# Create a new SIAE
siae = SiaeFactory()

# Verify create_company was called once with the SIAE instance
mock_create_company.assert_called_once_with(siae)
@patch("lemarche.utils.apis.api_brevo.sib_api_v3_sdk.CompaniesApi")
def test_siae_creation_with_brevo_integration(self, mock_companies_api):
mock_response = MagicMock()
mock_response.id = 12345
mock_companies_api_instance = mock_companies_api.return_value
mock_companies_api_instance.companies_post.return_value = mock_response

# Create another SIAE
siae2 = SiaeFactory()
self.assertEqual(mock_create_company.call_count, 2)
mock_create_company.assert_called_with(siae2)
siae = SiaeFactory(name="Test SIAE", website="https://example.com")

# Update existing SIAE
siae.name = "Updated Name"
siae.save()
self.assertTrue(mock_companies_api_instance.companies_post.called)
args, kwargs = mock_companies_api_instance.companies_post.call_args
body_obj = args[0]

# Call count should still be 2 since we only sync on creation
self.assertEqual(mock_create_company.call_count, 2)
self.assertEqual(body_obj.name, "Test SIAE")
self.assertEqual(body_obj.attributes, {"domain": "https://example.com", "app_id": siae.id, "siae": True})

@patch("lemarche.utils.apis.api_brevo.sib_api_v3_sdk.CompaniesApi")
def test_siae_attributes_sent_to_brevo(self, mock_companies_api):
"""Test the attributes sent to Brevo when creating a SIAE"""
# Mock the environment check to always return True
with patch("lemarche.utils.apis.api_brevo.settings.BITOUBI_ENV", "production"):
# Setup the mock response
mock_response = MagicMock()
mock_response.id = 12345
mock_instance = mock_companies_api.return_value
mock_instance.companies_post.return_value = mock_response

# Create a SIAE
siae = SiaeFactory(name="Test SIAE", website="https://example.com")

# Get the Body instance that was passed to companies_post
args, kwargs = mock_instance.companies_post.call_args
body_obj = args[0]

self.assertEqual(body_obj.name, "Test SIAE")
self.assertEqual(body_obj.attributes, {"domain": "https://example.com", "app_id": siae.id, "siae": True})

self.assertEqual(siae.brevo_company_id, 12345)
self.assertEqual(siae.brevo_company_id, 12345)
6 changes: 1 addition & 5 deletions lemarche/tenders/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -1289,8 +1289,4 @@ def tender_post_save(sender, instance, created, **kwargs):
if created:
from lemarche.utils.apis.api_brevo import create_deal

try:
create_deal(tender=instance)
except Exception as e:
# Log the error but don't prevent tender creation
logger.error(f"Error creating Brevo deal for tender {instance.id}: {e}")
create_deal(tender=instance)
59 changes: 36 additions & 23 deletions lemarche/utils/apis/api_brevo.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import logging
import time
from typing import Any, Dict, Optional, Union
from typing import Any, Dict, Optional

import sib_api_v3_sdk
from django.conf import settings
Expand All @@ -11,18 +11,9 @@
from lemarche.utils.constants import EMAIL_SUBJECT_PREFIX
from lemarche.utils.data import sanitize_to_send_by_email

from typing import TYPE_CHECKING

if TYPE_CHECKING:
from lemarche.users.models import User
from lemarche.tenders.models import Tender
from lemarche.siaes.models import Siae
from lemarche.companies.models import Company


logger = logging.getLogger(__name__)

ENV_NOT_ALLOWED = ("dev", "test")
ENV_NOT_ALLOWED = ("dev",)


def get_config():
Expand All @@ -36,7 +27,7 @@ def get_api_client():
return sib_api_v3_sdk.ApiClient(config)


def create_contact(user: "User", list_id: int, tender: Optional["Tender"] = None) -> None:
def create_contact(user, list_id: int, tender=None) -> None:
"""
Brevo docs
- Python library: https://github.com/sendinblue/APIv3-python-library/blob/master/docs/CreateContact.md
Expand Down Expand Up @@ -107,43 +98,65 @@ def update_contact_email_blacklisted(user_identifier: str, email_blacklisted: bo
logger.error(f"Exception when calling Brevo->ContactsApi->update_contact to update email_blacklisted: {e}")


def create_company(company: Union["Siae", "Company"]) -> None:
def create_brevo_company_from_company(company) -> None:
"""
Creates a Brevo company from a Company instance.
Brevo docs
- Python library: https://github.com/sendinblue/APIv3-python-library/blob/master/docs/CompaniesApi.md
- API: https://developers.brevo.com/reference/post_companies
"""
create_company(company)


def create_brevo_company_from_siae(siae) -> None:
"""
Creates a Brevo company from a Siae instance.
Brevo docs
- Python library: https://github.com/sendinblue/APIv3-python-library/blob/master/docs/CompaniesApi.md
- API: https://developers.brevo.com/reference/post_companies
"""
create_company(siae)


def create_company(company_or_siae) -> None:
"""
Brevo docs
- Python library: https://github.com/sendinblue/APIv3-python-library/blob/master/docs/CompaniesApi.md
- API: https://developers.brevo.com/reference/post_companies
Args:
company: Union["Siae", "Company"] instance to create in Brevo
company_or_siae: instance to create in Brevo
"""
api_client = get_api_client()
api_instance = sib_api_v3_sdk.CompaniesApi(api_client)

# Determine if this is a SIAE
from lemarche.siaes.models import Siae

is_siae = isinstance(company, Siae)
is_siae = isinstance(company_or_siae, Siae)

company_data = sib_api_v3_sdk.Body(
name=company.name,
name=company_or_siae.name,
attributes={
"domain": company.website if hasattr(company, "website") else "",
"app_id": company.id,
"domain": company_or_siae.website if hasattr(company_or_siae, "website") else "",
"app_id": company_or_siae.id,
"siae": is_siae,
},
)

if not company.brevo_company_id:
if not company_or_siae.brevo_company_id:
try:
api_response = api_instance.companies_post(company_data)
logger.info(f"Success Brevo->CompaniesApi->create_company (create): {api_response}")
company.brevo_company_id = api_response.id
company.save(update_fields=["brevo_company_id"])
company_or_siae.brevo_company_id = api_response.id
company_or_siae.save(update_fields=["brevo_company_id"])
except ApiException as e:
logger.error(f"Exception when calling Brevo->CompaniesApi->create_company (create): {e}")


def create_deal(tender: "Tender") -> None:
def create_deal(tender) -> None:
"""
Creates a new deal in Brevo CRM from a tender and logs the result.
Expand Down Expand Up @@ -184,7 +197,7 @@ def create_deal(tender: "Tender") -> None:
raise ApiException(e)


def link_company_with_contact_list(siae: "Siae", contact_list: list = None):
def link_company_with_contact_list(siae, contact_list=None):
"""
Links a Brevo company to a list of contacts. If no contact list is provided, it defaults
to linking the company with the siae's users.
Expand Down

0 comments on commit 98d72e1

Please sign in to comment.