From fddb1653ac4bf2caa3a17822911d34c21dc2a2c1 Mon Sep 17 00:00:00 2001 From: Paul Craig Date: Fri, 18 Nov 2016 15:09:15 +0000 Subject: [PATCH] Add `.cost()` to notification model In the V2 API, the GET response for an individual notification returns a 'cost' value, which we can get by multiplying the billable units by the per-message rate of the supplier who sent the message. Any notifications with billable units > 0 but without a corresponding `ProviderRates` entry will blow up the application, so make sure you've got one. --- app/models.py | 17 +++++++++- tests/app/conftest.py | 16 ++++++++-- tests/app/test_model.py | 32 ++++++++++++++++--- .../test_notification_schemas.py | 1 - 4 files changed, 58 insertions(+), 8 deletions(-) diff --git a/app/models.py b/app/models.py index bd805c400..a1d7fa164 100644 --- a/app/models.py +++ b/app/models.py @@ -5,7 +5,7 @@ from sqlalchemy.dialects.postgresql import ( UUID, JSON ) -from sqlalchemy import UniqueConstraint, and_ +from sqlalchemy import UniqueConstraint, and_, desc from sqlalchemy.orm import foreign, remote from notifications_utils.recipients import ( validate_email_address, @@ -538,6 +538,21 @@ class Notification(db.Model): if personalisation: self._personalisation = encryption.encrypt(personalisation) + def cost(self): + if not self.sent_by or self.billable_units == 0: + return 0 + + provider_rate = db.session.query( + ProviderRates + ).join(ProviderDetails).filter( + ProviderDetails.identifier == self.sent_by, + ProviderRates.provider_id == ProviderDetails.id + ).order_by( + desc(ProviderRates.valid_from) + ).limit(1).one() + + return provider_rate.rate * self.billable_units + class NotificationHistory(db.Model): __tablename__ = 'notification_history' diff --git a/tests/app/conftest.py b/tests/app/conftest.py index 566c888e1..76187e6e2 100644 --- a/tests/app/conftest.py +++ b/tests/app/conftest.py @@ -29,6 +29,7 @@ from app.dao.api_key_dao import save_model_api_key from app.dao.jobs_dao import dao_create_job from app.dao.notifications_dao import dao_create_notification from app.dao.invited_user_dao import save_invited_user +from app.dao.provider_rates_dao import create_provider_rates from app.clients.sms.firetext import FiretextClient @@ -409,7 +410,8 @@ def sample_notification(notify_db, create=True, personalisation=None, api_key_id=None, - key_type=KEY_TYPE_NORMAL): + key_type=KEY_TYPE_NORMAL, + sent_by=None): if created_at is None: created_at = datetime.utcnow() if service is None: @@ -441,7 +443,8 @@ def sample_notification(notify_db, 'personalisation': personalisation, 'notification_type': template.template_type, 'api_key_id': api_key_id, - 'key_type': key_type + 'key_type': key_type, + 'sent_by': sent_by } if job_row_number: data['job_row_number'] = job_row_number @@ -841,3 +844,12 @@ def sample_service_whitelist(notify_db, notify_db_session, service=None, email_a notify_db.session.add(whitelisted_user) notify_db.session.commit() return whitelisted_user + + +@pytest.fixture(scope='function') +def sample_provider_rate(notify_db, notify_db_session, valid_from=None, rate=None, provider_identifier=None): + create_provider_rates( + provider_identifier=provider_identifier if provider_identifier is not None else 'mmg', + valid_from=valid_from if valid_from is not None else datetime.utcnow(), + rate=rate if rate is not None else 1, + ) diff --git a/tests/app/test_model.py b/tests/app/test_model.py index abbd167a5..1625d7119 100644 --- a/tests/app/test_model.py +++ b/tests/app/test_model.py @@ -1,9 +1,7 @@ -import uuid +import pytest from datetime import datetime -import pytest - -from app import DATETIME_FORMAT +from tests.app.conftest import sample_notification, sample_provider_rate from app.models import ( Notification, ServiceWhitelist, @@ -37,3 +35,29 @@ def test_should_build_service_whitelist_from_email_address(email_address): def test_should_not_build_service_whitelist_from_invalid_contact(recipient_type, contact): with pytest.raises(ValueError): ServiceWhitelist.from_string('service_id', recipient_type, contact) + + +@pytest.mark.parametrize('provider, billable_units, expected_cost', [ + ('mmg', 1, 3.5), + ('firetext', 2, 5), + ('ses', 0, 0) +]) +def test_calculate_cost_from_notification_billable_units( + notify_db, notify_db_session, provider, billable_units, expected_cost +): + provider_rates = [ + ('mmg', datetime(2016, 7, 1), 1.5), + ('firetext', datetime(2016, 7, 1), 2.5), + ('mmg', datetime.utcnow(), 3.5), + ] + for provider_identifier, valid_from, rate in provider_rates: + sample_provider_rate( + notify_db, + notify_db_session, + provider_identifier=provider_identifier, + valid_from=valid_from, + rate=rate + ) + + notification = sample_notification(notify_db, notify_db_session, billable_units=billable_units, sent_by=provider) + assert notification.cost() == expected_cost diff --git a/tests/app/v2/notifications/test_notification_schemas.py b/tests/app/v2/notifications/test_notification_schemas.py index 8b6620444..5597ad377 100644 --- a/tests/app/v2/notifications/test_notification_schemas.py +++ b/tests/app/v2/notifications/test_notification_schemas.py @@ -3,7 +3,6 @@ import uuid import pytest from flask import json from jsonschema import ValidationError -from notifications_utils.recipients import InvalidPhoneError, InvalidEmailError from app.v2.notifications.notification_schemas import post_sms_request, post_sms_response, post_email_request, \ post_email_response