2022-04-29 10:51:08 +01:00
|
|
|
from datetime import date, datetime
|
2017-08-15 17:26:10 +01:00
|
|
|
|
2017-12-06 11:03:42 +00:00
|
|
|
import pytest
|
2019-04-01 11:43:02 +01:00
|
|
|
from freezegun import freeze_time
|
2017-12-06 11:03:42 +00:00
|
|
|
|
2021-03-10 13:55:06 +00:00
|
|
|
from app.billing.rest import update_free_sms_fragment_limit_data
|
2017-12-06 11:03:42 +00:00
|
|
|
from app.dao.annual_billing_dao import dao_get_free_sms_fragment_limit_for_year
|
2022-04-29 10:40:16 +01:00
|
|
|
from app.dao.date_util import get_current_financial_year_start_year
|
2017-08-15 17:26:10 +01:00
|
|
|
from tests.app.db import (
|
2021-03-10 13:55:06 +00:00
|
|
|
create_annual_billing,
|
|
|
|
|
create_ft_billing,
|
2018-04-30 16:34:40 +01:00
|
|
|
create_service,
|
2021-03-10 13:55:06 +00:00
|
|
|
create_template,
|
2017-08-15 17:26:10 +01:00
|
|
|
)
|
|
|
|
|
|
2017-08-18 13:38:53 +01:00
|
|
|
APR_2016_MONTH_START = datetime(2016, 3, 31, 23, 00, 00)
|
|
|
|
|
APR_2016_MONTH_END = datetime(2016, 4, 30, 22, 59, 59, 99999)
|
|
|
|
|
|
2017-08-15 17:26:10 +01:00
|
|
|
IN_MAY_2016 = datetime(2016, 5, 10, 23, 00, 00)
|
|
|
|
|
IN_JUN_2016 = datetime(2016, 6, 3, 23, 00, 00)
|
|
|
|
|
|
|
|
|
|
|
2022-04-21 15:16:29 +01:00
|
|
|
def test_create_update_free_sms_fragment_limit_invalid_schema(admin_request, sample_service):
|
|
|
|
|
json_response = admin_request.post(
|
|
|
|
|
'billing.create_or_update_free_sms_fragment_limit',
|
|
|
|
|
service_id=sample_service.id,
|
|
|
|
|
_data={},
|
|
|
|
|
_expected_status=400
|
|
|
|
|
)
|
2017-10-24 13:23:24 +01:00
|
|
|
|
2022-04-21 15:16:29 +01:00
|
|
|
assert 'errors' in json_response
|
2017-10-24 13:23:24 +01:00
|
|
|
|
|
|
|
|
|
2017-12-06 11:03:42 +00:00
|
|
|
def test_create_free_sms_fragment_limit_current_year_updates_future_years(admin_request, sample_service):
|
2017-11-02 17:02:00 +00:00
|
|
|
current_year = get_current_financial_year_start_year()
|
2017-12-06 11:03:42 +00:00
|
|
|
future_billing = create_annual_billing(sample_service.id, 1, current_year + 1)
|
2017-10-24 13:23:24 +01:00
|
|
|
|
2017-12-06 11:03:42 +00:00
|
|
|
admin_request.post(
|
|
|
|
|
'billing.create_or_update_free_sms_fragment_limit',
|
|
|
|
|
service_id=sample_service.id,
|
|
|
|
|
_data={'free_sms_fragment_limit': 9999},
|
|
|
|
|
_expected_status=201
|
|
|
|
|
)
|
2017-10-24 13:23:24 +01:00
|
|
|
|
2017-12-06 11:03:42 +00:00
|
|
|
current_billing = dao_get_free_sms_fragment_limit_for_year(sample_service.id, current_year)
|
|
|
|
|
assert future_billing.free_sms_fragment_limit == 9999
|
|
|
|
|
assert current_billing.financial_year_start == current_year
|
|
|
|
|
assert current_billing.free_sms_fragment_limit == 9999
|
2017-10-24 13:23:24 +01:00
|
|
|
|
|
|
|
|
|
2017-12-06 11:03:42 +00:00
|
|
|
@pytest.mark.parametrize('update_existing', [True, False])
|
|
|
|
|
def test_create_or_update_free_sms_fragment_limit_past_year_doenst_update_other_years(
|
|
|
|
|
admin_request,
|
|
|
|
|
sample_service,
|
|
|
|
|
update_existing
|
|
|
|
|
):
|
|
|
|
|
current_year = get_current_financial_year_start_year()
|
|
|
|
|
create_annual_billing(sample_service.id, 1, current_year)
|
|
|
|
|
if update_existing:
|
|
|
|
|
create_annual_billing(sample_service.id, 1, current_year - 1)
|
2017-10-24 13:23:24 +01:00
|
|
|
|
2017-12-06 11:03:42 +00:00
|
|
|
data = {'financial_year_start': current_year - 1, 'free_sms_fragment_limit': 9999}
|
|
|
|
|
admin_request.post(
|
|
|
|
|
'billing.create_or_update_free_sms_fragment_limit',
|
|
|
|
|
service_id=sample_service.id,
|
|
|
|
|
_data=data,
|
|
|
|
|
_expected_status=201)
|
2017-10-24 13:23:24 +01:00
|
|
|
|
2017-12-06 11:03:42 +00:00
|
|
|
assert dao_get_free_sms_fragment_limit_for_year(sample_service.id, current_year - 1).free_sms_fragment_limit == 9999
|
|
|
|
|
assert dao_get_free_sms_fragment_limit_for_year(sample_service.id, current_year).free_sms_fragment_limit == 1
|
2017-10-24 14:46:10 +01:00
|
|
|
|
|
|
|
|
|
2017-12-06 11:03:42 +00:00
|
|
|
def test_create_free_sms_fragment_limit_updates_existing_year(admin_request, sample_service):
|
2017-11-02 12:19:17 +00:00
|
|
|
current_year = get_current_financial_year_start_year()
|
2017-12-06 11:03:42 +00:00
|
|
|
annual_billing = create_annual_billing(sample_service.id, 1, current_year)
|
2017-11-02 17:02:00 +00:00
|
|
|
|
2017-12-06 11:03:42 +00:00
|
|
|
admin_request.post(
|
|
|
|
|
'billing.create_or_update_free_sms_fragment_limit',
|
|
|
|
|
service_id=sample_service.id,
|
|
|
|
|
_data={'financial_year_start': current_year, 'free_sms_fragment_limit': 2},
|
|
|
|
|
_expected_status=201)
|
2017-11-02 17:02:00 +00:00
|
|
|
|
2017-12-06 11:03:42 +00:00
|
|
|
assert annual_billing.free_sms_fragment_limit == 2
|
2017-10-25 12:50:13 +01:00
|
|
|
|
2017-10-26 17:21:41 +01:00
|
|
|
|
2021-04-19 13:29:04 +01:00
|
|
|
@freeze_time('2021-04-02 13:00')
|
|
|
|
|
def test_get_free_sms_fragment_limit(
|
2022-04-21 15:16:29 +01:00
|
|
|
admin_request, sample_service
|
2021-04-19 13:29:04 +01:00
|
|
|
):
|
|
|
|
|
create_annual_billing(service_id=sample_service.id, free_sms_fragment_limit=11000, financial_year_start=2021)
|
2017-10-26 17:21:41 +01:00
|
|
|
|
2022-04-21 15:16:29 +01:00
|
|
|
json_response = admin_request.get(
|
|
|
|
|
'billing.get_free_sms_fragment_limit',
|
|
|
|
|
service_id=sample_service.id
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
assert json_response['financial_year_start'] == 2021
|
|
|
|
|
assert json_response['free_sms_fragment_limit'] == 11000
|
2017-10-30 17:10:12 +00:00
|
|
|
|
|
|
|
|
|
2021-04-19 13:29:04 +01:00
|
|
|
@freeze_time('2021-04-02 13:00')
|
|
|
|
|
def test_get_free_sms_fragment_limit_current_year_creates_new_row_if_annual_billing_is_missing(
|
2022-04-21 15:16:29 +01:00
|
|
|
admin_request, sample_service
|
2021-04-19 13:29:04 +01:00
|
|
|
):
|
2022-04-21 15:16:29 +01:00
|
|
|
json_response = admin_request.get(
|
|
|
|
|
'billing.get_free_sms_fragment_limit',
|
|
|
|
|
service_id=sample_service.id
|
|
|
|
|
)
|
2017-10-30 17:10:12 +00:00
|
|
|
|
2022-04-21 15:16:29 +01:00
|
|
|
assert json_response['financial_year_start'] == 2021
|
2022-09-26 16:47:57 +00:00
|
|
|
assert json_response['free_sms_fragment_limit'] == 150000 # based on other organisation type
|
2017-11-15 09:18:39 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_update_free_sms_fragment_limit_data(client, sample_service):
|
|
|
|
|
current_year = get_current_financial_year_start_year()
|
2017-12-06 11:03:42 +00:00
|
|
|
create_annual_billing(sample_service.id, free_sms_fragment_limit=250000, financial_year_start=current_year - 1)
|
2017-11-15 09:18:39 +00:00
|
|
|
|
2017-12-06 11:03:42 +00:00
|
|
|
update_free_sms_fragment_limit_data(sample_service.id, 9999, current_year)
|
2017-11-15 09:18:39 +00:00
|
|
|
|
|
|
|
|
annual_billing = dao_get_free_sms_fragment_limit_for_year(sample_service.id, current_year)
|
|
|
|
|
assert annual_billing.free_sms_fragment_limit == 9999
|
2018-04-30 16:34:40 +01:00
|
|
|
|
|
|
|
|
|
2022-04-29 10:58:41 +01:00
|
|
|
def test_get_yearly_usage_by_monthly_from_ft_billing(admin_request, notify_db_session):
|
2018-04-30 16:34:40 +01:00
|
|
|
service = create_service()
|
2022-04-29 10:58:41 +01:00
|
|
|
create_annual_billing(service_id=service.id, free_sms_fragment_limit=1, financial_year_start=2016)
|
|
|
|
|
|
2018-04-30 16:34:40 +01:00
|
|
|
sms_template = create_template(service=service, template_type="sms")
|
|
|
|
|
email_template = create_template(service=service, template_type="email")
|
2022-04-21 12:29:09 +01:00
|
|
|
|
2022-04-29 10:51:08 +01:00
|
|
|
for dt in (date(2016, 4, 28), date(2016, 11, 10), date(2017, 2, 26)):
|
2022-11-21 11:49:59 -05:00
|
|
|
create_ft_billing(local_date=dt, template=sms_template, rate=0.0162)
|
|
|
|
|
create_ft_billing(local_date=dt, template=email_template, billable_unit=0, rate=0)
|
2022-04-29 10:51:08 +01:00
|
|
|
|
2022-04-21 12:29:09 +01:00
|
|
|
json_response = admin_request.get(
|
2022-04-21 15:16:29 +01:00
|
|
|
'billing.get_yearly_usage_by_monthly_from_ft_billing',
|
|
|
|
|
service_id=service.id,
|
|
|
|
|
year=2016
|
|
|
|
|
)
|
2018-04-30 16:34:40 +01:00
|
|
|
|
2023-03-02 20:20:31 -05:00
|
|
|
assert len(json_response) == 3 # 3 billed months for SMS
|
2022-04-21 12:29:09 +01:00
|
|
|
|
|
|
|
|
email_rows = [row for row in json_response if row['notification_type'] == 'email']
|
|
|
|
|
assert len(email_rows) == 0
|
|
|
|
|
|
|
|
|
|
sms_row = next(x for x in json_response if x['notification_type'] == 'sms')
|
|
|
|
|
|
|
|
|
|
assert sms_row["month"] == "April"
|
|
|
|
|
assert sms_row["notification_type"] == "sms"
|
2022-04-29 10:51:08 +01:00
|
|
|
assert sms_row["chargeable_units"] == 1
|
|
|
|
|
assert sms_row["notifications_sent"] == 1
|
|
|
|
|
assert sms_row["rate"] == 0.0162
|
|
|
|
|
# free allowance is 1
|
|
|
|
|
assert sms_row["cost"] == 0
|
|
|
|
|
assert sms_row["free_allowance_used"] == 1
|
|
|
|
|
assert sms_row["charged_units"] == 0
|
2018-05-04 13:09:14 +01:00
|
|
|
|
|
|
|
|
|
2022-04-21 15:16:29 +01:00
|
|
|
def test_get_yearly_billing_usage_summary_from_ft_billing_returns_400_if_missing_year(admin_request, sample_service):
|
|
|
|
|
json_response = admin_request.get(
|
|
|
|
|
'billing.get_yearly_billing_usage_summary_from_ft_billing',
|
|
|
|
|
service_id=sample_service.id,
|
|
|
|
|
_expected_status=400
|
2018-05-11 16:25:16 +01:00
|
|
|
)
|
2022-04-21 15:16:29 +01:00
|
|
|
assert json_response == {
|
2018-05-11 16:25:16 +01:00
|
|
|
'message': 'No valid year provided', 'result': 'error'
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_get_yearly_billing_usage_summary_from_ft_billing_returns_empty_list_if_no_billing_data(
|
2022-04-21 15:16:29 +01:00
|
|
|
admin_request, sample_service
|
2018-05-11 16:25:16 +01:00
|
|
|
):
|
2022-04-21 15:16:29 +01:00
|
|
|
json_response = admin_request.get(
|
|
|
|
|
'billing.get_yearly_billing_usage_summary_from_ft_billing',
|
|
|
|
|
service_id=sample_service.id,
|
|
|
|
|
year=2016
|
2018-05-11 16:25:16 +01:00
|
|
|
)
|
2022-04-21 15:16:29 +01:00
|
|
|
assert json_response == []
|
2018-05-11 16:25:16 +01:00
|
|
|
|
|
|
|
|
|
2022-04-21 15:16:29 +01:00
|
|
|
def test_get_yearly_billing_usage_summary_from_ft_billing(admin_request, notify_db_session):
|
2022-04-29 10:58:41 +01:00
|
|
|
service = create_service()
|
|
|
|
|
create_annual_billing(service_id=service.id, free_sms_fragment_limit=1, financial_year_start=2016)
|
|
|
|
|
|
|
|
|
|
sms_template = create_template(service=service, template_type="sms")
|
|
|
|
|
email_template = create_template(service=service, template_type="email")
|
|
|
|
|
|
|
|
|
|
for dt in (date(2016, 4, 28), date(2016, 11, 10), date(2017, 2, 26)):
|
2022-11-21 11:49:59 -05:00
|
|
|
create_ft_billing(local_date=dt, template=sms_template, rate=0.0162)
|
|
|
|
|
create_ft_billing(local_date=dt, template=email_template, billable_unit=0, rate=0)
|
2018-05-11 16:25:16 +01:00
|
|
|
|
2022-04-21 15:16:29 +01:00
|
|
|
json_response = admin_request.get(
|
|
|
|
|
'billing.get_yearly_billing_usage_summary_from_ft_billing',
|
|
|
|
|
service_id=service.id,
|
|
|
|
|
year=2016
|
|
|
|
|
)
|
|
|
|
|
|
2023-03-02 20:20:31 -05:00
|
|
|
assert len(json_response) == 2
|
2022-04-20 16:55:38 +01:00
|
|
|
|
2018-05-11 16:25:16 +01:00
|
|
|
assert json_response[0]['notification_type'] == 'email'
|
2022-04-20 16:55:38 +01:00
|
|
|
assert json_response[0]['chargeable_units'] == 0
|
2022-04-29 10:51:08 +01:00
|
|
|
assert json_response[0]['notifications_sent'] == 3
|
2018-05-11 16:25:16 +01:00
|
|
|
assert json_response[0]['rate'] == 0
|
Add costs to each row in yearly usage API
This will replace the manual calculations in Admin [^1][^2] for SMS
and also in API [^3] for annual letter costs.
Doing the calculation here also means we correctly attribute free
allowance to the earliest rows in the billing table - Admin doesn't
know when a given rate was applied so can't do this without making
assumptions about when we change our rates.
Since the calculation now depends on annual billing, we need to
change all the tests to make sure a suitable row exists. I've also
adjusted the test data to match the assumption that there can only
be one SMS rate per bst_date.
Note about "OVER" clause
========================
Using "rows=" ("ROWS BETWEEN") makes more sense than "range=" as
we want the remainder to be incremental within each group in a
"GROUP BY" clause, as well as between groups i.e
# ROWS BETWEEN (arbitrary numbers to illustrate)
date=2021-04-03, units=3, cost=3.29
date=2021-04-03, units=2, cost=4.17
date=2021-04-04, units=2, cost=5.10
vs.
# RANGE BETWEEN
date=2021-04-03, units=3, cost=4.17
date=2021-04-03, units=2, cost=4.17
date=2021-04-04, units=2, cost=5.10
See [^4] for more details and examples.
[^1]: https://github.com/alphagov/notifications-admin/blob/master/app/templates/views/usage.html#L60
[^2]: https://github.com/alphagov/notifications-api/blob/072c3b207940597aacb5bebdbf3757f848d22cd6/app/billing/billing_schemas.py#L37
[^3]: https://github.com/alphagov/notifications-admin/blob/474d7dfda834ebf2f0966f176fb6da556808d8a1/app/templates/views/usage.html#L98
[^4]: https://learnsql.com/blog/difference-between-rows-range-window-functions/
2022-04-20 17:14:17 +01:00
|
|
|
assert json_response[0]['cost'] == 0
|
2022-04-21 12:55:30 +01:00
|
|
|
assert json_response[0]['free_allowance_used'] == 0
|
2022-04-26 17:56:17 +01:00
|
|
|
assert json_response[0]['charged_units'] == 0
|
2022-04-20 16:55:38 +01:00
|
|
|
|
2023-03-02 20:20:31 -05:00
|
|
|
assert json_response[1]['notification_type'] == 'sms'
|
2022-04-29 10:51:08 +01:00
|
|
|
assert json_response[1]['chargeable_units'] == 3
|
|
|
|
|
assert json_response[1]['notifications_sent'] == 3
|
2023-03-02 20:20:31 -05:00
|
|
|
assert json_response[1]['rate'] == 0.0162
|
|
|
|
|
assert json_response[1]['cost'] == 0.0324
|
|
|
|
|
assert json_response[1]['free_allowance_used'] == 1
|
|
|
|
|
assert json_response[1]['charged_units'] == 2
|