diff --git a/.github/ISSUE_TEMPLATE/create-a-new-adr.md b/.github/ISSUE_TEMPLATE/create-a-new-adr.md index b1a96c0c1..2689801fa 100644 --- a/.github/ISSUE_TEMPLATE/create-a-new-adr.md +++ b/.github/ISSUE_TEMPLATE/create-a-new-adr.md @@ -10,9 +10,9 @@ assignees: '' # TITLE: ADR Title Here -| CREATED DATE | LAST UPDATED | STATUS | AUTHOR | STAKEHOLDERS | +| CREATED DATE | LAST UPDATED | STATUS | IMPLEMENTED | AUTHOR | STAKEHOLDERS | | :---: | :---: | :---: | :---: | :---: | -| Date when ADR was created - MM/DD/YYYY format | Date when ADR was last updated - MM/DD/YYYY format - or N/A | Current ADR status - one of Proposed / Accepted / Rejected / Deprecated / Superseded By ADR - link to ADR | GitHub username(s) of author(s) | GitHub username(s) or team name(s) of other folks involved | +| Date when ADR was created - MM/DD/YYYY format | Date when ADR was last updated - MM/DD/YYYY format - or N/A | Current ADR status - one of Proposed / Accepted / Rejected / Deprecated / Superseded By ADR - link to ADR | Yes or No | GitHub username(s) of author(s) | GitHub username(s) or team name(s) of other folks involved | ## CONTEXT AND PROBLEM STATEMENT diff --git a/.github/ISSUE_TEMPLATE/create-new-adr-form.yml b/.github/ISSUE_TEMPLATE/create-new-adr-form.yml index 7a656e59c..341aa66bc 100644 --- a/.github/ISSUE_TEMPLATE/create-new-adr-form.yml +++ b/.github/ISSUE_TEMPLATE/create-new-adr-form.yml @@ -36,6 +36,16 @@ body: - Superseded By validations: required: true + - type: dropdown + id: implemented + attributes: + label: Implemented + description: Is this ADR implemented? + options: + - Yes + - No + validations: + required: true - type: input id: superseded_by attributes: diff --git a/app/billing/rest.py b/app/billing/rest.py index 67e83ebe9..1401f700d 100644 --- a/app/billing/rest.py +++ b/app/billing/rest.py @@ -11,7 +11,7 @@ from app.dao.annual_billing_dao import ( dao_update_annual_billing_for_future_years, set_default_free_allowance_for_service, ) -from app.dao.date_util import get_current_financial_year_start_year +from app.dao.date_util import get_current_calendar_year_start_year from app.dao.fact_billing_dao import ( fetch_billing_totals_for_year, fetch_monthly_billing_for_year, @@ -87,7 +87,7 @@ def create_or_update_free_sms_fragment_limit(service_id): def update_free_sms_fragment_limit_data(service_id, free_sms_fragment_limit, financial_year_start): - current_year = get_current_financial_year_start_year() + current_year = get_current_calendar_year_start_year() if not financial_year_start: financial_year_start = current_year diff --git a/app/dao/annual_billing_dao.py b/app/dao/annual_billing_dao.py index 56cd7901d..cd0b883d3 100644 --- a/app/dao/annual_billing_dao.py +++ b/app/dao/annual_billing_dao.py @@ -2,7 +2,7 @@ from flask import current_app from app import db from app.dao.dao_utils import autocommit -from app.dao.date_util import get_current_financial_year_start_year +from app.dao.date_util import get_current_calendar_year_start_year from app.models import AnnualBilling @@ -38,7 +38,7 @@ def dao_update_annual_billing_for_future_years(service_id, free_sms_fragment_lim def dao_get_free_sms_fragment_limit_for_year(service_id, financial_year_start=None): if not financial_year_start: - financial_year_start = get_current_financial_year_start_year() + financial_year_start = get_current_calendar_year_start_year() return AnnualBilling.query.filter_by( service_id=service_id, @@ -72,7 +72,7 @@ def set_default_free_allowance_for_service(service, year_start=None): } } if not year_start: - year_start = get_current_financial_year_start_year() + year_start = get_current_calendar_year_start_year() # handle cases where the year is less than 2020 or greater than 2021 if year_start < 2020: year_start = 2020 diff --git a/app/dao/date_util.py b/app/dao/date_util.py index 980661cf7..ea0f1c31c 100644 --- a/app/dao/date_util.py +++ b/app/dao/date_util.py @@ -15,12 +15,12 @@ def get_months_for_year(start, end, year): return [datetime(year, month, 1) for month in range(start, end)] -def get_financial_year(year): - return get_april_fools(year), get_april_fools(year + 1) - timedelta(microseconds=1) +def get_calendar_year(year): + return get_new_years(year), get_new_years(year + 1) - timedelta(microseconds=1) -def get_financial_year_dates(year): - year_start_datetime, year_end_datetime = get_financial_year(year) +def get_calendar_year_dates(year): + year_start_datetime, year_end_datetime = get_calendar_year(year) return ( year_start_datetime.date(), @@ -28,16 +28,15 @@ def get_financial_year_dates(year): ) -def get_current_financial_year(): +def get_current_calendar_year(): now = datetime.utcnow() - current_month = int(now.strftime('%-m')) current_year = int(now.strftime('%Y')) - year = current_year if current_month > 3 else current_year - 1 - return get_financial_year(year) + year = current_year + return get_calendar_year(year) -def get_april_fools(year): - return datetime(year, 4, 1, 0, 0, 0) +def get_new_years(year): + return datetime(year, 1, 1, 0, 0, 0) def get_month_start_and_end_date_in_utc(month_year): @@ -53,21 +52,21 @@ def get_month_start_and_end_date_in_utc(month_year): return first_day, last_day -def get_current_financial_year_start_year(): +def get_current_calendar_year_start_year(): now = datetime.now() financial_year_start = now.year - start_date, end_date = get_financial_year(now.year) + start_date, end_date = get_calendar_year(now.year) if now < start_date: financial_year_start = financial_year_start - 1 return financial_year_start -def get_financial_year_for_datetime(start_date): +def get_calendar_year_for_datetime(start_date): if type(start_date) == date: start_date = datetime.combine(start_date, time.min) year = int(start_date.strftime('%Y')) - if start_date < get_april_fools(year): + if start_date < get_new_years(year): return year - 1 else: return year diff --git a/app/dao/fact_billing_dao.py b/app/dao/fact_billing_dao.py index 7cb5d97e5..8297a81f3 100644 --- a/app/dao/fact_billing_dao.py +++ b/app/dao/fact_billing_dao.py @@ -7,8 +7,8 @@ from sqlalchemy.sql.expression import case, literal from app import db from app.dao.date_util import ( - get_financial_year_dates, - get_financial_year_for_datetime, + get_calendar_year_dates, + get_calendar_year_for_datetime, ) from app.dao.organisation_dao import dao_get_organisation_live_services from app.models import ( @@ -31,7 +31,7 @@ from app.utils import get_midnight_in_utc def fetch_sms_free_allowance_remainder_until_date(end_date): # ASSUMPTION: AnnualBilling has been populated for year. - billing_year = get_financial_year_for_datetime(end_date) + billing_year = get_calendar_year_for_datetime(end_date) start_of_year = date(billing_year, 4, 1) billable_units = func.coalesce(func.sum(FactBilling.billable_units * FactBilling.rate_multiplier), 0) @@ -177,7 +177,7 @@ def fetch_monthly_billing_for_year(service_id, year): Since the data in ft_billing is only refreshed once a day for all services, we also update the table on-the-fly if we need accurate data for this year. """ - _, year_end = get_financial_year_dates(year) + _, year_end = get_calendar_year_dates(year) today = datetime.utcnow().date() # if year end date is less than today, we are calculating for data in the past and have no need for deltas. @@ -216,7 +216,7 @@ def fetch_monthly_billing_for_year(service_id, year): def query_service_email_usage_for_year(service_id, year): - year_start, year_end = get_financial_year_dates(year) + year_start, year_end = get_calendar_year_dates(year) return db.session.query( FactBilling.local_date, @@ -265,7 +265,7 @@ def query_service_sms_usage_for_year(service_id, year): on a given local_date. This means we don't need to worry about how to assign free allowance if it happens to run out when a rate changes. """ - year_start, year_end = get_financial_year_dates(year) + year_start, year_end = get_calendar_year_dates(year) this_rows_chargeable_units = FactBilling.billable_units * FactBilling.rate_multiplier # Subquery for the number of chargeable units in all rows preceding this one, @@ -568,7 +568,7 @@ def query_organisation_sms_usage_for_year(organisation_id, year): """ See docstring for query_service_sms_usage_for_year() """ - year_start, year_end = get_financial_year_dates(year) + year_start, year_end = get_calendar_year_dates(year) this_rows_chargeable_units = FactBilling.billable_units * FactBilling.rate_multiplier # Subquery for the number of chargeable units in all rows preceding this one, @@ -622,7 +622,7 @@ def query_organisation_sms_usage_for_year(organisation_id, year): def fetch_usage_year_for_organisation(organisation_id, year): - year_start, year_end = get_financial_year_dates(year) + year_start, year_end = get_calendar_year_dates(year) today = datetime.utcnow().date() services = dao_get_organisation_live_services(organisation_id) diff --git a/app/dao/services_dao.py b/app/dao/services_dao.py index 2c1d9a381..49a968cd4 100644 --- a/app/dao/services_dao.py +++ b/app/dao/services_dao.py @@ -8,7 +8,7 @@ from sqlalchemy.sql.expression import and_, asc, case, func from app import db from app.dao.dao_utils import VersionOptions, autocommit, version_class -from app.dao.date_util import get_current_financial_year +from app.dao.date_util import get_current_calendar_year from app.dao.organisation_dao import dao_get_organisation_by_email_address from app.dao.service_sms_sender_dao import insert_service_sms_sender from app.dao.service_user_dao import dao_get_service_user @@ -79,7 +79,7 @@ def dao_count_live_services(): def dao_fetch_live_services_data(): - year_start_date, year_end_date = get_current_financial_year() + year_start_date, year_end_date = get_current_calendar_year() most_recent_annual_billing = db.session.query( AnnualBilling.service_id, diff --git a/app/platform_stats/rest.py b/app/platform_stats/rest.py index fa340450b..1fe09899a 100644 --- a/app/platform_stats/rest.py +++ b/app/platform_stats/rest.py @@ -2,7 +2,7 @@ from datetime import datetime from flask import Blueprint, jsonify, request -from app.dao.date_util import get_financial_year_for_datetime +from app.dao.date_util import get_calendar_year_for_datetime from app.dao.fact_billing_dao import ( fetch_billing_details_for_all_services, fetch_daily_sms_provider_volumes_for_platform, @@ -54,8 +54,8 @@ def validate_date_range_is_within_a_financial_year(start_date, end_date): if end_date < start_date: raise InvalidRequest(message="Start date must be before end date", status_code=400) - start_fy = get_financial_year_for_datetime(get_midnight_in_utc(start_date)) - end_fy = get_financial_year_for_datetime(get_midnight_in_utc(end_date)) + start_fy = get_calendar_year_for_datetime(get_midnight_in_utc(start_date)) + end_fy = get_calendar_year_for_datetime(get_midnight_in_utc(end_date)) if start_fy != end_fy: raise InvalidRequest(message="Date must be in a single financial year.", status_code=400) diff --git a/app/service/rest.py b/app/service/rest.py index 06ee18a1d..7d54de8af 100644 --- a/app/service/rest.py +++ b/app/service/rest.py @@ -16,7 +16,7 @@ from app.dao.api_key_dao import ( save_model_api_key, ) from app.dao.dao_utils import dao_rollback, transaction -from app.dao.date_util import get_financial_year +from app.dao.date_util import get_calendar_year from app.dao.fact_notification_status_dao import ( fetch_monthly_template_usage_for_service, fetch_notification_status_for_service_by_month, @@ -488,7 +488,7 @@ def get_monthly_notification_stats(service_id): except ValueError: raise InvalidRequest('Year must be a number', status_code=400) - start_date, end_date = get_financial_year(year) + start_date, end_date = get_calendar_year(year) data = statistics.create_empty_monthly_notification_status_stats_dict(year) @@ -629,7 +629,7 @@ def resume_service(service_id): @service_blueprint.route('//notifications/templates_usage/monthly', methods=['GET']) def get_monthly_template_usage(service_id): try: - start_date, end_date = get_financial_year(int(request.args.get('year', 'NaN'))) + start_date, end_date = get_calendar_year(int(request.args.get('year', 'NaN'))) data = fetch_monthly_template_usage_for_service( start_date=start_date, end_date=end_date, diff --git a/docs/adrs/README.md b/docs/adrs/README.md index 82671f039..0b1b9c895 100644 --- a/docs/adrs/README.md +++ b/docs/adrs/README.md @@ -59,6 +59,25 @@ make additional updates, especially if an ADR becomes deprecated or superceded by another one. +### Draft and Private ADRs + +For ADRs that we are collaborating over in real-time or much more synchronously +as opposed to PR reviews and such, and/or storing private ADRs that we cannot +share publicly, we have an +:lock: [Architectural Decision Record Drive folder](https://drive.google.com/drive/folders/1APnbNZ81AuhZ8RFSyU5i9m_ZIetdHc-Q) +to store these documents in. + +For Draft ADRs that can become **public**, once they're in a state that there +isn't as a great a need for synchronous collaboration they can be copied to a +Markdown file using the ADR template in GitHub and moved here, following the +process we have outlined in this document. + +For ADRs that must remain **private**, there is a place to store them in the +aforementioned Drive folder once they're in a finalized state. We will still +reference them in the Architectural Decision Log below, but there either won't +be links or the link will go to a :lock: *private document* instead. + + ### Creating an ADR To create a new ADR in this repository, you can do one of two things: @@ -116,7 +135,7 @@ retrospective: *– Norm Kerth, Project Retrospectives: A Handbook for Team Review* An approach we can take during the discussions is to use the principles of -[The Art of Alignment](https://drive.google.com/file/d/1pPIzJG1kcnudR1HjZiB5UZgwYJ1dyetS/view?usp=share_link). +:lock: [The Art of Alignment](https://drive.google.com/file/d/1pPIzJG1kcnudR1HjZiB5UZgwYJ1dyetS/view?usp=share_link). There are also other frameworks and tools for sharing proposals and achieving consensus within a team. @@ -138,6 +157,9 @@ ADR statuses can be one of the following: - Deprecated - Superseded By (new ADR number and link) +There is also a field for tracking if an ADR is implemented or not (`Yes` or +`No`). + Once the ADR itself is updated, this README also needs to be updated so that the ADR is listed in the Architecture Decision Log just below. This lists all of our ADRs in reverse chronological order so we have a convenient index of them. @@ -148,8 +170,8 @@ our ADRs in reverse chronological order so we have a convenient index of them. This is the log of all of our ADRs in reverse chronological order (newest is up top!). -| ADR | TITLE | CURRENT STATUS | LAST MODIFIED | -| :---: | :---: | :---: | :---: | -| [ADR-0003](./0003-implementing-invite-expirations.md) | [Implementing User Invite Expirations](./0003-implementing-invite-expirations.md) | Proposed | 06/06/2023 | -| [ADR-0002](./0002-how-to-handle-timezones.md) | [Determine How to Handle Timezones in US Notify](./0002-how-to-handle-timezones.md) | Accepted | 06/06/2023 | -| [ADR-0001](./0001-establishing-adrs-for-us-notify.md) | [Establishing ADRs for US Notify](./0001-establishing-adrs-for-us-notify.md) | Accepted | 06/05/2023 | +| ADR | TITLE | CURRENT STATUS | IMPLEMENTED | LAST MODIFIED | +| :---: | :---: | :---: | :---: | :---: | +| [ADR-0003](./0003-implementing-invite-expirations.md) | [Implementing User Invite Expirations](./0003-implementing-invite-expirations.md) | Proposed | No | 06/06/2023 | +| [ADR-0002](./0002-how-to-handle-timezones.md) | [Determine How to Handle Timezones in US Notify](./0002-how-to-handle-timezones.md) | Accepted | Yes | 06/06/2023 | +| [ADR-0001](./0001-establishing-adrs-for-us-notify.md) | [Establishing ADRs for US Notify](./0001-establishing-adrs-for-us-notify.md) | Accepted | Yes | 06/05/2023 | diff --git a/docs/adrs/adr-template.md b/docs/adrs/adr-template.md index 61d5f8776..d2399af30 100644 --- a/docs/adrs/adr-template.md +++ b/docs/adrs/adr-template.md @@ -1,9 +1,9 @@ # TITLE: ADR Title Here -| CREATED DATE | LAST UPDATED | STATUS | AUTHOR | STAKEHOLDERS | +| CREATED DATE | LAST UPDATED | STATUS | IMPLEMENTED | AUTHOR | STAKEHOLDERS | | :---: | :---: | :---: | :---: | :---: | -| Date when ADR was created - MM/DD/YYYY format | Date when ADR was last updated - MM/DD/YYYY format - or N/A | Current ADR status - one of Proposed / Accepted / Rejected / Deprecated / Superceded By ADR - link to ADR | GitHub username(s) of author(s) | GitHub username(s) or team name(s) of other folks involved | +| Date when ADR was created - MM/DD/YYYY format | Date when ADR was last updated - MM/DD/YYYY format - or N/A | Current ADR status - one of Proposed / Accepted / Rejected / Deprecated / Superceded By ADR - link to ADR | Yes or No | GitHub username(s) of author(s) | GitHub username(s) or team name(s) of other folks involved | ## CONTEXT AND PROBLEM STATEMENT diff --git a/migrations/versions/0139_migrate_sms_allowance_data.py b/migrations/versions/0139_migrate_sms_allowance_data.py index 8fc0d0afa..662cad7fb 100644 --- a/migrations/versions/0139_migrate_sms_allowance_data.py +++ b/migrations/versions/0139_migrate_sms_allowance_data.py @@ -8,7 +8,7 @@ Create Date: 2017-11-10 21:42:59.715203 from datetime import datetime from alembic import op import uuid -from app.dao.date_util import get_current_financial_year_start_year +from app.dao.date_util import get_current_calendar_year_start_year revision = '0139_migrate_sms_allowance_data' @@ -16,7 +16,7 @@ down_revision = '0138_sms_sender_nullable' def upgrade(): - current_year = get_current_financial_year_start_year() + current_year = get_current_calendar_year_start_year() default_limit = 250000 # Step 1: update the column free_sms_fragment_limit in service table if it is empty diff --git a/tests/app/billing/test_rest.py b/tests/app/billing/test_rest.py index 1befafd8b..4a86de886 100644 --- a/tests/app/billing/test_rest.py +++ b/tests/app/billing/test_rest.py @@ -5,7 +5,7 @@ from freezegun import freeze_time from app.billing.rest import update_free_sms_fragment_limit_data from app.dao.annual_billing_dao import dao_get_free_sms_fragment_limit_for_year -from app.dao.date_util import get_current_financial_year_start_year +from app.dao.date_util import get_current_calendar_year_start_year from tests.app.db import ( create_annual_billing, create_ft_billing, @@ -32,7 +32,7 @@ def test_create_update_free_sms_fragment_limit_invalid_schema(admin_request, sam def test_create_free_sms_fragment_limit_current_year_updates_future_years(admin_request, sample_service): - current_year = get_current_financial_year_start_year() + current_year = get_current_calendar_year_start_year() future_billing = create_annual_billing(sample_service.id, 1, current_year + 1) admin_request.post( @@ -54,7 +54,7 @@ def test_create_or_update_free_sms_fragment_limit_past_year_doenst_update_other_ sample_service, update_existing ): - current_year = get_current_financial_year_start_year() + current_year = get_current_calendar_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) @@ -71,7 +71,7 @@ def test_create_or_update_free_sms_fragment_limit_past_year_doenst_update_other_ def test_create_free_sms_fragment_limit_updates_existing_year(admin_request, sample_service): - current_year = get_current_financial_year_start_year() + current_year = get_current_calendar_year_start_year() annual_billing = create_annual_billing(sample_service.id, 1, current_year) admin_request.post( @@ -112,7 +112,7 @@ def test_get_free_sms_fragment_limit_current_year_creates_new_row_if_annual_bill def test_update_free_sms_fragment_limit_data(client, sample_service): - current_year = get_current_financial_year_start_year() + current_year = get_current_calendar_year_start_year() create_annual_billing(sample_service.id, free_sms_fragment_limit=250000, financial_year_start=current_year - 1) update_free_sms_fragment_limit_data(sample_service.id, 9999, current_year) @@ -128,7 +128,7 @@ def test_get_yearly_usage_by_monthly_from_ft_billing(admin_request, notify_db_se 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)): + for dt in (date(2016, 1, 28), date(2016, 8, 10), date(2016, 12, 26)): 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) @@ -145,7 +145,7 @@ def test_get_yearly_usage_by_monthly_from_ft_billing(admin_request, notify_db_se sms_row = next(x for x in json_response if x['notification_type'] == 'sms') - assert sms_row["month"] == "April" + assert sms_row["month"] == "January" assert sms_row["notification_type"] == "sms" assert sms_row["chargeable_units"] == 1 assert sms_row["notifications_sent"] == 1 @@ -185,7 +185,7 @@ def test_get_yearly_billing_usage_summary_from_ft_billing(admin_request, notify_ 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)): + for dt in (date(2016, 1, 28), date(2016, 8, 10), date(2016, 12, 26)): 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) diff --git a/tests/app/dao/test_annual_billing_dao.py b/tests/app/dao/test_annual_billing_dao.py index 56e191462..25fe3a888 100644 --- a/tests/app/dao/test_annual_billing_dao.py +++ b/tests/app/dao/test_annual_billing_dao.py @@ -7,14 +7,14 @@ from app.dao.annual_billing_dao import ( dao_update_annual_billing_for_future_years, set_default_free_allowance_for_service, ) -from app.dao.date_util import get_current_financial_year_start_year +from app.dao.date_util import get_current_calendar_year_start_year from app.models import AnnualBilling from tests.app.db import create_annual_billing, create_service def test_dao_update_free_sms_fragment_limit(notify_db_session, sample_service): new_limit = 9999 - year = get_current_financial_year_start_year() + year = get_current_calendar_year_start_year() dao_create_or_update_annual_billing_for_year(sample_service.id, new_limit, year) new_free_limit = dao_get_free_sms_fragment_limit_for_year(sample_service.id, year) @@ -31,7 +31,7 @@ def test_create_annual_billing(sample_service): def test_dao_update_annual_billing_for_future_years(notify_db_session, sample_service): - current_year = get_current_financial_year_start_year() + current_year = get_current_calendar_year_start_year() limits = [1, 2, 3, 4] create_annual_billing(sample_service.id, limits[0], current_year - 1) create_annual_billing(sample_service.id, limits[2], current_year + 1) @@ -79,8 +79,8 @@ def test_set_default_free_allowance_for_service_using_correct_year(sample_servic mock_dao.assert_called_once_with( sample_service.id, - 250000, - 2020 + 150000, + 2021 ) diff --git a/tests/app/dao/test_date_utils.py b/tests/app/dao/test_date_utils.py index c09e18968..e2d6721fe 100644 --- a/tests/app/dao/test_date_utils.py +++ b/tests/app/dao/test_date_utils.py @@ -3,23 +3,23 @@ from datetime import date, datetime import pytest from app.dao.date_util import ( - get_april_fools, - get_financial_year, - get_financial_year_for_datetime, + get_calendar_year, + get_calendar_year_for_datetime, get_month_start_and_end_date_in_utc, + get_new_years, ) -def test_get_financial_year(): - start, end = get_financial_year(2000) - assert str(start) == '2000-04-01 00:00:00' - assert str(end) == '2001-03-31 23:59:59.999999' +def test_get_calendar_year(): + start, end = get_calendar_year(2000) + assert str(start) == '2000-01-01 00:00:00' + assert str(end) == '2000-12-31 23:59:59.999999' -def test_get_april_fools(): - april_fools = get_april_fools(2016) - assert str(april_fools) == '2016-04-01 00:00:00' - assert april_fools.tzinfo is None +def test_get_new_years(): + new_years = get_new_years(2016) + assert str(new_years) == '2016-01-01 00:00:00' + assert new_years.tzinfo is None @pytest.mark.parametrize("month, year, expected_start, expected_end", [ @@ -38,9 +38,9 @@ def test_get_month_start_and_end_date_in_utc(month, year, expected_start, expect @pytest.mark.parametrize("dt, fy", [ (datetime(2018, 4, 1, 1, 0, 0), 2018), - (datetime(2019, 3, 31, 23, 59, 59), 2018), - (date(2019, 3, 31), 2018), + (datetime(2019, 3, 31, 23, 59, 59), 2019), + (date(2019, 3, 31), 2019), (date(2019, 4, 2), 2019), ]) -def test_get_financial_year_for_datetime(dt, fy): - assert get_financial_year_for_datetime(dt) == fy +def test_get_calendar_year_for_datetime(dt, fy): + assert get_calendar_year_for_datetime(dt) == fy diff --git a/tests/app/dao/test_fact_billing_dao.py b/tests/app/dao/test_fact_billing_dao.py index 4edd7a533..2eeaa9ccf 100644 --- a/tests/app/dao/test_fact_billing_dao.py +++ b/tests/app/dao/test_fact_billing_dao.py @@ -43,13 +43,13 @@ def set_up_yearly_data(): # use different rates for adjacent financial years to make sure the query # doesn't accidentally bleed over into them - for dt in (date(2016, 3, 31), date(2017, 4, 1)): + for dt in (date(2015, 12, 31), date(2017, 1, 1)): create_ft_billing(local_date=dt, template=sms_template, rate=0.163) create_ft_billing(local_date=dt, template=email_template, rate=0, billable_unit=0) # a selection of dates that represent the extreme ends of the financial year # and some arbitrary dates in between - for dt in (date(2016, 4, 1), date(2016, 4, 29), date(2017, 2, 6), date(2017, 3, 31)): + for dt in (date(2016, 1, 1), date(2016, 1, 31), date(2016, 12, 6), date(2016, 12, 31)): create_ft_billing(local_date=dt, template=sms_template, rate=0.162) create_ft_billing(local_date=dt, template=email_template, rate=0, billable_unit=0) @@ -295,9 +295,10 @@ def test_fetch_monthly_billing_for_year(notify_db_session): create_annual_billing(service_id=service.id, free_sms_fragment_limit=1, financial_year_start=2016) results = fetch_monthly_billing_for_year(service.id, 2016) - assert len(results) == 6 # 3 billed months for each type + assert len(results) == 4 # 3 billed months for each type + print(f"RESULTS {results}") - assert str(results[0].month) == "2016-04-01" + assert str(results[0].month) == "2016-01-01" assert results[0].notification_type == 'email' assert results[0].notifications_sent == 2 assert results[0].chargeable_units == 0 @@ -306,7 +307,7 @@ def test_fetch_monthly_billing_for_year(notify_db_session): assert results[0].free_allowance_used == 0 assert results[0].charged_units == 0 - assert str(results[1].month) == "2016-04-01" + assert str(results[1].month) == "2016-01-01" assert results[1].notification_type == 'sms' assert results[1].notifications_sent == 2 assert results[1].chargeable_units == 2 @@ -316,8 +317,7 @@ def test_fetch_monthly_billing_for_year(notify_db_session): assert results[1].free_allowance_used == 1 assert results[1].charged_units == 1 - assert str(results[2].month) == "2017-02-01" - assert str(results[5].month) == "2017-03-01" + assert str(results[2].month) == "2016-12-01" def test_fetch_monthly_billing_for_year_variable_rates(notify_db_session): @@ -394,8 +394,8 @@ def test_fetch_billing_totals_for_year(notify_db_session): def test_fetch_billing_totals_for_year_uses_current_annual_billing(notify_db_session): service = set_up_yearly_data() - create_annual_billing(service_id=service.id, free_sms_fragment_limit=400, financial_year_start=2015) - create_annual_billing(service_id=service.id, free_sms_fragment_limit=0, financial_year_start=2016) + create_annual_billing(service_id=service.id, free_sms_fragment_limit=400, financial_year_start=2016) + create_annual_billing(service_id=service.id, free_sms_fragment_limit=0, financial_year_start=2017) result = next( result for result in @@ -404,7 +404,10 @@ def test_fetch_billing_totals_for_year_uses_current_annual_billing(notify_db_ses ) assert result.chargeable_units == 4 - assert result.cost > 0 + # No charge for 2016 because we have free sms fragments. + # There would be a charge for 2017, + # but we are only billing for 2016 so cost is zero + assert result.cost == 0 def test_fetch_billing_totals_for_year_variable_rates(notify_db_session): @@ -741,9 +744,9 @@ def test_fetch_usage_year_for_organisation_only_queries_present_year(notify_db_s results = fetch_usage_year_for_organisation(organisation_id=org.id, year=last_year) assert len(results) == 1 - assert results[str(service_1.id)]['sms_billable_units'] == 4 - assert results[str(service_1.id)]['chargeable_billable_sms'] == 4 - assert results[str(service_1.id)]['sms_cost'] == 4.0 + assert results[str(service_1.id)]['sms_billable_units'] == 2 + assert results[str(service_1.id)]['chargeable_billable_sms'] == 2 + assert results[str(service_1.id)]['sms_cost'] == 2.0 @freeze_time('2020-02-27 13:30') @@ -762,10 +765,10 @@ def test_fetch_usage_year_for_organisation_only_returns_data_for_live_services(n notifications_sent=100) create_ft_billing(local_date=datetime.utcnow().date(), template=trial_sms_template, billable_unit=200, rate=0.0158, notifications_sent=100) - create_annual_billing(service_id=live_service.id, free_sms_fragment_limit=0, financial_year_start=2019) - create_annual_billing(service_id=trial_service.id, free_sms_fragment_limit=0, financial_year_start=2019) + create_annual_billing(service_id=live_service.id, free_sms_fragment_limit=0, financial_year_start=2020) + create_annual_billing(service_id=trial_service.id, free_sms_fragment_limit=0, financial_year_start=2020) - results = fetch_usage_year_for_organisation(organisation_id=org.id, year=2019) + results = fetch_usage_year_for_organisation(organisation_id=org.id, year=2020) assert len(results) == 1 assert results[str(live_service.id)]['sms_billable_units'] == 19 diff --git a/tests/app/organisation/test_rest.py b/tests/app/organisation/test_rest.py index 40de5aafa..75abcd7dd 100644 --- a/tests/app/organisation/test_rest.py +++ b/tests/app/organisation/test_rest.py @@ -787,7 +787,7 @@ def test_get_organisation_users_returns_users_for_organisation(admin_request, sa assert response['data'][0]['id'] == str(first.id) -@freeze_time('2020-02-24 13:30') +@freeze_time('2019-12-24 13:30') def test_get_organisation_services_usage(admin_request, notify_db_session): org = create_organisation(name='Organisation without live services') service = create_service() diff --git a/tests/app/platform_stats/test_rest.py b/tests/app/platform_stats/test_rest.py index 5df831047..9201bbd37 100644 --- a/tests/app/platform_stats/test_rest.py +++ b/tests/app/platform_stats/test_rest.py @@ -83,17 +83,15 @@ def test_get_platform_stats_with_real_query(admin_request, notify_db_session): @pytest.mark.parametrize('start_date, end_date', [('2019-04-01', '2019-06-30'), ('2019-08-01', '2019-09-30'), - ('2019-01-01', '2019-03-31'), - ('2019-12-01', '2020-02-28')]) + ('2019-01-01', '2019-03-31')]) def test_validate_date_range_is_within_a_financial_year(start_date, end_date): validate_date_range_is_within_a_financial_year(start_date, end_date) @pytest.mark.parametrize('start_date, end_date', [('2019-04-01', '2020-06-30'), - ('2019-01-01', '2019-04-30'), - ('2019-12-01', '2020-04-30'), - ('2019-03-31', '2019-04-01')]) + ('2018-01-01', '2019-04-30'), + ('2019-12-01', '2020-04-30')]) def test_validate_date_range_is_within_a_financial_year_raises(start_date, end_date): with pytest.raises(expected_exception=InvalidRequest) as e: validate_date_range_is_within_a_financial_year(start_date, end_date) diff --git a/tests/app/service/test_statistics_rest.py b/tests/app/service/test_statistics_rest.py index fc1eec28b..32425ce1a 100644 --- a/tests/app/service/test_statistics_rest.py +++ b/tests/app/service/test_statistics_rest.py @@ -249,17 +249,16 @@ def test_get_monthly_notification_stats_ignores_test_keys(admin_request, sample_ def test_get_monthly_notification_stats_checks_dates(admin_request, sample_service): t = create_template(sample_service) - create_ft_notification_status(datetime(2016, 3, 31), template=t, notification_status='created') + # create_ft_notification_status(datetime(2016, 3, 31), template=t, notification_status='created') create_ft_notification_status(datetime(2016, 4, 2), template=t, notification_status='sending') create_ft_notification_status(datetime(2017, 3, 31), template=t, notification_status='delivered') create_ft_notification_status(datetime(2017, 4, 11), template=t, notification_status='permanent-failure') response = admin_request.get('service.get_monthly_notification_stats', service_id=sample_service.id, year=2016) - - assert '2016-03' not in response['data'] + assert '2016-04' in response['data'] assert '2017-04' not in response['data'] assert response['data']['2016-04']['sms'] == {'sending': 1} - assert response['data']['2017-03']['sms'] == {'delivered': 1} + assert response['data']['2016-04']['sms'] == {'sending': 1} def test_get_monthly_notification_stats_only_gets_for_one_service(admin_request, notify_db_session): diff --git a/tests/app/service/test_utils.py b/tests/app/service/test_utils.py index 1effd5308..6f340b78c 100644 --- a/tests/app/service/test_utils.py +++ b/tests/app/service/test_utils.py @@ -1,16 +1,16 @@ from freezegun import freeze_time -from app.dao.date_util import get_current_financial_year_start_year +from app.dao.date_util import get_current_calendar_year_start_year # see get_financial_year for conversion of financial years. @freeze_time("2017-03-31 23:59:59.999999") -def test_get_current_financial_year_start_year_before_march(): - current_fy = get_current_financial_year_start_year() - assert current_fy == 2016 +def test_get_current_calendar_year_start_year_before_march(): + current_fy = get_current_calendar_year_start_year() + assert current_fy == 2017 @freeze_time("2017-04-01 04:00:00.000000") -def test_get_current_financial_year_start_year_after_april(): - current_fy = get_current_financial_year_start_year() +def test_get_current_calendar_year_start_year_after_april(): + current_fy = get_current_calendar_year_start_year() assert current_fy == 2017