Commit Graph

4441 Commits

Author SHA1 Message Date
Ben Thorner
c27107fa74 Remove support for Reach provider
This provider was never active and support was never completed, so
there's little value in keeping all this potentially confusing code.
2022-04-29 12:28:08 +01:00
Ben Thorner
52b2982b92 Rename test_ft_billing... to match file under test
This tripped me up several times when modifying the DAO functions
and then trying to search for the test file associated with them.
2022-04-29 11:33:20 +01:00
Ben Thorner
b8cd99d9fa Move usage API test data inline with tests
This makes it easier to compare assertions against the test data,
especially since each function was only used in one test.
2022-04-29 11:33:19 +01:00
Ben Thorner
efae436c4a Speed up usage API tests with minimal test data
This slowed me down when making changes to the APIs. As well as
being unnecessary given the structural focus of these tests, I
found the way the test data was generated was quite confusing.

Before:

    time pytest tests/app/billing/test_rest.py
    ...
    pytest tests/app/billing/test_rest.py  4.16s user 0.43s system 55% cpu 8.355 total

After:

    time pytest tests/app/billing/test_rest.py
    ...
    pytest tests/app/billing/test_rest.py  2.16s user 0.25s system 70% cpu 3.413 total
2022-04-29 11:33:16 +01:00
Ben Thorner
2fdec58496 Remove unused line in annual usage API test data 2022-04-29 10:57:24 +01:00
Ben Thorner
f978a4fe45 Remove redundant test data for annual usage API
This test should be focussed on the structural properties of the
API. We can leave more detailed testing of rate multipliers, etc.
to lower-level DAO tests.
2022-04-29 10:57:23 +01:00
Ben Thorner
1fab73ef9e Speed up usage DAO tests with minimal test data
This slowed me down when making changes to the DAO functions. It's
really not necessary to do 3 * 367 DB insertions for both tests.

Before:

    time pytest tests/app/dao/test_ft_billing_dao.py -k for_year
    ...
    pytest tests/app/dao/test_ft_billing_dao.py -k for_year  3.95s user 0.40s system 62% cpu 6.971 total

After:

    time pytest tests/app/dao/test_ft_billing_dao.py -k for_year
    ...
    pytest tests/app/dao/test_ft_billing_dao.py -k for_year  1.84s user 0.25s system 69% cpu 3.006 total
2022-04-29 10:56:51 +01:00
Ben Thorner
c3bc1d0df9 Remove redundant test data for usage DAOs
This is unnecessary since we now have separate "_variable_rates"
tests that check the behaviour for multiple rates explicitly for
the two types of notifications it affects.
2022-04-29 10:22:09 +01:00
Ben Thorner
ebaef4b57b Add "charged_units" to service usage APIs
This can be calculated from the "free_allowance_used" field and the
"chargeable_units" field, but having it included separately is more
convenient as it can be used directly in Admin [^1].

[^1]: 417e7370bb/app/templates/views/usage.html (L38-L39)
2022-04-27 15:57:35 +01:00
Ben Thorner
555868c442 Add "free_allowance_units" to service usage APIs
This represents the number of chargeable_units that were actually
free due to the free allowance - they won't be included in "cost".

Although the existing calculations in Admin [^1][^2] will still be
correct with a change in SMS rates - it's cost that's the problem
- it makes sense to have all the knowledge about calculating usage
consistently in these two APIs.

Note that the Integer casting is covered by the API-level tests in
test_rest.

[^1]: 474d7dfda8/app/main/views/dashboard.py (L490)
[^2]: c63660d56d/app/main/views/dashboard.py (L350)
2022-04-27 15:57:34 +01:00
Ben Thorner
cd84928a1e 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]: 072c3b2079/app/billing/billing_schemas.py (L37)
[^3]: 474d7dfda8/app/templates/views/usage.html (L98)
[^4]: https://learnsql.com/blog/difference-between-rows-range-window-functions/
2022-04-27 15:57:33 +01:00
Ben Thorner
fc378fed96 Prepare to replace "billing_units" in usage APIs
There is no such thing as a "billing unit". The data this field
contained was also a confusing mixture of two types:

- For emails and letters, it was just "notifications_sent".

- For SMS, it was the "chargeable_units" (billable * multiplier).

This replaces the single, ambiguous "billing_units" field with
"chargeable_units" and "notifications_sent" in both usage APIs.
Once Admin is using them we can remove the old field.
2022-04-27 15:57:30 +01:00
Ben Thorner
46cbac62ef Fix misleading billable_units in letter test data
Letters are weird:

- "rate" is adjusted based on the number of pages [^1].

- "billable_units" is the number of sheets [^2], but doesn't seem
to be used for anything.

Instead of "billable_units", we multiply "notifications_sent" and
the page-adjusted "rate" to determine the cost of a batch [^3][^4].

[^1]: a4fe11a3aa/app/dao/fact_billing_dao.py (L473)
[^2]: a4fe11a3aa/app/letters/utils.py (L230)
[^3]: a4fe11a3aa/app/dao/fact_billing_dao.py (L828)
[^4]: a4fe11a3aa/app/dao/fact_billing_dao.py (L128)
2022-04-27 15:17:08 +01:00
Ben Thorner
bf66614899 Fix incorrect billable_units for email test data
Emails always retain the default of "0" [^1].

[^1]: a4fe11a3aa/app/models.py (L1441)
2022-04-27 13:56:11 +01:00
Ben Thorner
4cca01a8cb Remove duplicate edge case test for monthly usage
This doesn't change the structural behaviour of the API and can be
tested just as well at a lower level.
2022-04-26 13:24:09 +01:00
Ben Thorner
ee4da698fe Standardise timezones for service usage APIs
We want to query for service usage in the BST financial year:

    2022-04-01T00:00:00+01:00 to 2023-03-31T23:59:59+01:00 =>
    2022-04-01 to 2023-03-31  # bst_date

Previously we were only doing this explicitly for the monthly API
and it seemed like the yearly usage API was incorrectly querying:

    2022-03-31T23:00:00+00:00 to 2023-03-30T23:00:00+00:00 =>
    2022-03-31 to 2023-03-30  # "bst_date"

However, it turns out this isn't a problem for two reasons:

1. We've been lucky that none of our rates have changed since 2017,
which is long ago enough that no one would care.

2. There's a quirk somewhere in Sqlalchemy / Postgres that has been
compensating for the lack of explicit BST conversion.

To help ensure we do this consistently in future I've DRYed-up the
BST conversion into a new utility. I could have just hard-coded the
dates but it seemed strange to have the knowledge twice.

I've also adjusted the tests so they detect if we accidentally use
data from a different financial year. (2) is why none of the test
assertions actually need changing and users won't be affected.

Sqlalchemy / Postgres quirk
===========================

The following queries were run on the same data but results differ:

    FactBilling.query.filter(FactBilling.bst_date >= datetime(2021,3,31,23,0), FactBilling.bst_date <= '2021-04-05').order_by(FactBilling.bst_date).first().bst_date
    datetime.date(2021, 4, 1)

    FactBilling.query.filter(FactBilling.bst_date >= '2021-03-31 23:00:00', FactBilling.bst_date <= '2021-04-05').order_by(FactBilling.bst_date).first().bst_date
    datetime.date(2021, 3, 31)

Looking at the actual query for the first item above still suggests
the results should be the same, but for the use of "timestamp".

    SELECT ...
    FROM ft_billing
    WHERE ft_billing.service_id = '16b60315-9dab-45d3-a609-e871fbbf5345'::uuid AND ft_billing.bst_date >= '2016-03-31T23:00:00'::timestamp AND ft_billing.bst_date <= '2017-03-31T22:59:59.999999'::timestamp AND ft_billing.notification_type IN ('email', 'letter') GROUP BY ft_billing.rate, ft_billing.notification_type UNION ALL SELECT sum(ft_billing.notifications_sent) AS notifications_sent, sum(ft_billing.billable_units * ft_billing.rate_multiplier) AS billable_units, ft_billing.rate AS ft_billing_rate, ft_billing.notification_type AS ft_billing_notification_type
    FROM ft_billing
    WHERE ft_billing.service_id = '16b60315-9dab-45d3-a609-e871fbbf5345'::uuid AND ft_billing.bst_date >= '2016-03-31T23:00:00'::timestamp AND ft_billing.bst_date <= '2017-03-31T22:59:59.999999'::timestamp AND ft_billing.notification_type = 'sms' GROUP BY ft_billing.rate, ft_billing.notification_type) AS anon_1 ORDER BY anon_1.notification_type, anon_1.rate

If we try some manual queries with and without '::timestamp' we get:

    select distinct(bst_date) from ft_billing where bst_date >= '2022-04-20T23:00:00' order by bst_date desc;
      bst_date
    ------------
     2022-04-21
     2022-04-20

    select distinct(bst_date) from ft_billing where bst_date >= '2022-04-20T23:00:00'::timestamp order by bst_date desc;
      bst_date
    ------------
     2022-04-21
     2022-04-20

It looks like this is happening because all client connections are
aware of the local timezone, and naive datetimes are interpreted as
being in UTC - not necessarily true, but saves us here!

The monthly API datetimes were pre-converted to dates, so none of
this was relevant for deciding exactly which date to use.
2022-04-26 13:11:34 +01:00
Ben Thorner
fe6afd18d6 Refactor tests for monthly usage API
These are now consistent with the yearly usage API tests.
2022-04-26 13:11:32 +01:00
Ben Thorner
4d3c604faf Move edge case usage API tests down to DAO level
These tests weren't checking anything structural about the APIs
beyond what's covered by the other tests. They represent edge
cases that we can check at a lower level instead.

It was also unclear what these tests were actually testing, as
the term "all cases" is vague. Looking at the test data, there
are variations in rates, multipliers and billable units for SMS
and letters, which I've summarised as "variable rates".

Note: I've removed part of the test data - for the first class
letter rate - as it's not clearly adding anything.
2022-04-26 13:09:25 +01:00
Ben Thorner
0a8d35f909 Remove redundant test for monthly usage API
It was unclear why we had both of these tests when the one for
the financial year is more comprehensive - by checking data in
and beyond the specified financial year.

The only thing we lose in this file is checking multiple SMS
rates, which we will fix in the next commit when we import some
tests that are specific to variable rates.
2022-04-26 13:07:51 +01:00
Ben Thorner
8e74280e84 Remove test file to match file under test
This makes it easier to see at a glance that this file is testing
the endpoints vs. lower level code.
2022-04-21 18:36:36 +01:00
Ben Thorner
16133a5d4f Use admin_request consistently in billing tests
This avoids a bunch of boilerplate and makes it easier to see what
is being passed in the request.

Note that the "invalid_schema" test is slightly different because
it was previously passing an invalid JSON object - "{}" - instead
of a valid JSON but invalid by the schema - "\"{}\"". The new test
seems more valuable than the old one.
2022-04-21 15:43:01 +01:00
Leo Hemsted
0457850fc0 Remove redundant conditional for CF Redis
This is now used in all environments and we've removed support for
non-CF Redis.
2022-04-20 11:41:33 +01:00
Ben Thorner
f67f5d987d Merge pull request #3514 from alphagov/remove-redundant-cf-code
Remove redundant CloudFoundry config code
2022-04-20 11:25:13 +01:00
Pea Tyczynska
61b6e45da5 Merge pull request #3510 from alphagov/nhs_branding_default_for_nhs_org
When creating a new NHS org, set default email branding to NHS
2022-04-19 15:33:03 +01:00
Katie Smith
9435dfc385 Merge pull request #3512 from alphagov/bump-json-schemas
Bump jsonschema package from 3.2.0 to 4.4.0
2022-04-19 14:34:39 +01:00
Pea Tyczynska
124562b50a Refactor creating nhs branding in tests into a fixture 2022-04-19 12:25:17 +01:00
Pea Tyczynska
769b71cdc0 When updating org type to NHS type also update email branding if none set 2022-04-19 12:07:27 +01:00
Katie Smith
ec95163175 Update beautifulsoup4 to 4.11.1
`charset-normalizer` is now used by default if installed instead of
`chardet` (https://pyup.io/changelogs/beautifulsoup4/#4.11.0). We do
have `charset-normalizer` installed because it's a subdependency of the
requests library, so it is being used.

This caused the `test_content_too_long_returns_400` to fail since it
now thought that the encoding of `ŵ` is `{'encoding': 'Big5',
'language': 'Chinese', 'confidence': 1.0}`.

There are two options for fixing this
- change the test content so that it doesn't just contain a single
  letter - the docs state that you shouldn't run character detection on
  very tiny content
- add `chardet` as a requirement, so that the code functions exactly the
  same as before

I've chose the first option, since this avoids adding a dependency and
we should never have messages consisting of a single character.
2022-04-14 16:48:32 +01:00
Katie Smith
187e87c792 Remove one of our own jsonschema date-time formatters
We have three different ways of checking the formats of datetimes.
1. The built-in way that comes with the jsonschema package ("date-time")
2. A new way we added for broadcasts ("datetime") 61a5730596
3. An old way we defined in
   "/tests/app/public_contracts/schemas/v0/definitions.json"

In order to simplify things and make it clearer how datetimes are being
validated, this replaces the few places where we were using option 3 with option 1
instead. Option 3 was only being used to validate code that is no longer
used, the initial version of the API.
2022-04-14 14:47:45 +01:00
Katie Smith
5feb38f50a Bump jsonschema from 3.2.0 to 4.4.0
The big breaking change for our code (not mentioned in the changelog) is
that the built-in validator for the `date-time` format now requires the
`rfc3339-validator` package instead of the `strict-rfc3339` package.
This updates the requirements file to use `rfc3339-validator`. Without
this change, wrong `date-time` formats would always silently pass validation.
2022-04-14 14:47:42 +01:00
Katie Smith
b440f3f904 Use Draft-07 and Draft7Validator everywhere
We were using the Draft4Validator in one place, so this updates it to
the Draft7Validator instead.

The schemas were mostly using draft 4 of the JSON schema, though there
were a couple of schemas that were already of version 7. This updates
them all to version 7, which is the latest version fully supported by
the jsonschema Python package. There are some breaking changes in the
newer version of the schema, but I could not see anywhere would these
affect us. Some of these schemas were not valid in version 4, but are
now valid in version 7 because `"required": []` was not valid in earlier
versions.
2022-04-14 14:46:10 +01:00
Ben Thorner
95c5f0c079 Remove redundant CloudFoundry config code
These env vars can be set directly in the manifest, like we do for
Template Preview [^1].

[^1]: c08036189b/manifest.yml.j2 (L23-L26)
2022-04-13 14:46:52 +01:00
Pea Tyczynska
b1ed722252 When creating a new NHS org, set default email branding to NHS
This is more appropriate default for that org than gov.uk branding
and will help us with our work to make setting the branding more
self-service.
2022-04-12 17:24:32 +01:00
Ben Thorner
8c7ad16452 Merge pull request #3503 from alphagov/allow-repeat-send-letter
Don't error sending a letter that's sent already
2022-04-12 16:04:54 +01:00
Leo Hemsted
91200a2088 Merge pull request #3502 from alphagov/provider-report
add new daily sms provider volume report
2022-04-12 15:48:24 +01:00
Ben Thorner
a5e0fd6104 Merge pull request #3508 from alphagov/redis-ssl-181796569
Prepare to switch to Redis on PaaS
2022-04-12 15:47:46 +01:00
Ben Thorner
44d90b0a4f Remove redundant ternary on SMS client FROM_NUMBER
Logs over the past 14 days confirm we never call this code with
None as the sender, so it's safe to remove the ternary.
2022-04-12 14:59:21 +01:00
Ben Thorner
fb405977fa Allow REDIS_URL to optionally come from PaaS
This is to support a migration from Redislabs to PaaS native Redis,
allowing us to toggle between old and new using the env vars for
the instance - without needing to change the code.
2022-04-12 14:48:08 +01:00
Ben Thorner
1f83113e74 Move setting VCAP_SERVICES out of fixture
This was inconsistent with the source data for the fixture being
overidden in some of the tests. We only need to set it in the env
once, so it makes sense to just put the code there.
2022-04-12 14:46:47 +01:00
Ben Thorner
06aba23adb Remove redundant postgres CloudFoundry fixture 2022-04-12 14:45:16 +01:00
Leo Hemsted
259d4a0569 add new daily sms provider volume report
code generally lifted almost exactly from the daily_volumes_report, but
per provider and only for SMS.
2022-04-11 13:42:40 +01:00
Ben Thorner
70430f10ea Co-locate tests for sending a notification
I found the send letter tests hard to find as the name of the file
didn't match the name of the one containing the code under test.
2022-04-08 17:37:33 +01:00
Ben Thorner
fa10ec77ab DRY-up test send letter test data into fixture
This makes it easier to see what's different in each test.
2022-04-08 17:37:31 +01:00
Ben Thorner
5810d46d35 Don't error sending a letter that's sent already
Fixes this error (in Admin):

      File "/home/vcap/app/app/notify_client/notification_api_client.py", line 74, in send_precompiled_letter
        return self.post(url='/service/{}/send-pdf-letter'.format(service_id), data=data)
      File "/home/vcap/app/app/notify_client/__init__.py", line 59, in post
        return super().post(*args, **kwargs)
      File "/home/vcap/deps/0/python/lib/python3.9/site-packages/notifications_python_client/base.py", line 48, in post
        return self.request("POST", url, data=data)
      File "/home/vcap/deps/0/python/lib/python3.9/site-packages/notifications_python_client/base.py", line 64, in request
        response = self._perform_request(method, url, kwargs)
      File "/home/vcap/deps/0/python/lib/python3.9/site-packages/notifications_python_client/base.py", line 118, in _perform_request
        raise api_error
    notifications_python_client.errors.HTTPError: 500 - Internal server error

Due to this error (in API):

      File "/home/vcap/app/app/service/send_notification.py", line 178, in send_pdf_letter_notification
        raise e
      File "/home/vcap/app/app/service/send_notification.py", line 173, in send_pdf_letter_notification
        letter = utils_s3download(current_app.config['TRANSIENT_UPLOADED_LETTERS'], file_location)
      File "/home/vcap/deps/0/python/lib/python3.9/site-packages/notifications_utils/s3.py", line 53, in s3download
        raise S3ObjectNotFound(error.response, error.operation_name)
    notifications_utils.s3.S3ObjectNotFound: An error occurred (NoSuchKey) when calling the GetObject operation: The specified key does not exist.

I checked the DB to verify the letter does actually exist i.e. it
is an instance of the problem we're fixing here.
2022-04-08 17:20:44 +01:00
Ben Thorner
6ca550a1a6 Merge pull request #3497 from alphagov/broadcast-alert-earlier-181423293
Alert earlier when a broadcast is approved
2022-04-07 10:34:23 +01:00
Katie Smith
badd0e0894 Bump Flask and itsdangerous
This bumps Flask to version 2.1.0, which requires some minor changes to
the app code and itsdangerous to also be bumped.
2022-04-05 17:06:08 +01:00
Ben Thorner
e84014b86d Update broadcast approval alert content
This now links to the new runbook for the alert.
2022-04-05 12:57:09 +01:00
Ben Thorner
779b8e941f Rewrite broadcast Zendesk alert at approval time
The new alert happens earlier but is otherwise the same:

- We only create a ticket in Production.
- We only create a ticket on approval.

I took this opportunity to refactor the alert as a private function
and test this specifically in detail to avoid lots of repetitive
mocks, which are required when calling the main "update" function.

One test I haven't preserved was for when the "names" array is empty,
as this was added for a legacy data integrity scenario [^1].

[^1]: bf0bf4e31c
2022-04-05 12:57:08 +01:00
Ben Thorner
c7c5793da4 Shorten name of broadcast utility function
This is doing more than just validating and updating an is about to
do even more. Saying "update" is broad enough to cover the others.
2022-04-05 11:35:33 +01:00
Ben Thorner
4a40b169a2 Fix flakey test for notification status
This was creating data in the Notifications table but the funcetion
under test was - now the timestamps are in the past - looking in the
NotificationHistory table. Freezing the time of the test fixes that.
2022-04-05 11:07:05 +01:00