diff --git a/.travis.yml b/.travis.yml index f7d9a4b66..fa33b0e50 100644 --- a/.travis.yml +++ b/.travis.yml @@ -20,147 +20,7 @@ env: secure: tgeumbN2oayp1twu3iVuuo5eMz/XVO303A2wdnR6bFMCfLCA7SVeKvDZ21ZpSh+J7tu8/9MQD2ATo95qyO9oraSg09BQ7UoEtpyrrcP21UBcNMbIsAdmOUAostlvg4hy1ZuSjytpzLDMZfS0QCjWPtZiXKW3XzmHHJyIdatcHsO3Jpi1vPRP11cZHd1SKwd1POYXDuX3Y9e68yt0P7Wr1/3mZ8u0XHtSg++SnZ0qDDwnWIsHqkcxr7R/n1MYvyUD8XPB+guqEq/7G6ipR+QrHN0fCVGXFksXGLSMSBg9sGQ1Mr+2yiOXL+4EmCfpx9VofmEOFDTdK70lFFn1sOG/GmceW4JT2Y2vLG+6vSJTmaHxeZmpYoKRa1EJJqyEpvjRM3A8lV993qIdAEBIE8s0w+DhkmXXCI3chSDT+2B/SlFbGw7G7E4hto/3FUrk7N7w+c5WaOQSqz4ZxTX4iIg9T7Bxo3s8l+UYYw4NfzEreRieEiFo58FgYrghEOvMp9PZ3tN3u2H+2yISE0C/+MSFUB2CWgFiTTD2XtWuQJgGNxyTYD1sbHaYT1EeDoz8JbhsACvIxpQdycVibHjP4hvP32nFFaznNpCm1ArS+vDtzR6Psx2vYb/u0rf1QoipVE/GPzqB9bwGHZ/0Cpsqy4KxYM74MOu3Gi3KCYzKGq7hRGI= after_success: - coveralls -- ./scripts/trigger-dependent-build.sh notifications: slack: rooms: secure: vCmpAjdXkprqGzSnQzcco1lNiFuj/nRQdlVTGSnvg2HEqZUM5vzmWtQYw3WNS/bcgbVxV/nLgza17cdsmFqOuaZll4kDM0swnmk17eynImAHPFF/flGsNcw4oRV7WzWrq7j1TvGzJzxJkHYOjJFk0iMLM7+P76sWHI6Oa5NHQYH2l7a4hqzqmuUKaloHeZX/Ro2RmFE+/t/SfNr0pmtTRO4CfLfPUdUs9D8Mx0cyc1Z9Ke4Cq+R2Lsp8BwH0nye6pb9cI4h1YmUY/Xt8O1Z0QbncHZviWojSB2vV0V5WQPzqjjseAznJ1depK/LPOlRHlqddkEbGK28rDwsxbzY1q3DCZZiZZTXBD80HyNr/J4rfRAXhoCafwR57cqqIQ7G/fb5A/ckUM8TFKqUPh7wvu67OAiSWJmO78C8fumF3HxcoGp7DQOQPFLtWGuCfh2zjI9WbUhiHQwWl9/9qpgMEuXH7LOJAzgQbEp1A7XRmT2GupYEkGdFMwT8ojp8OXds08diEy1FNonWAtFZs8pO92urK862pCRB8WRQZWMjpcnif1Ht4QtwYFO9iVp5ygReTEoFuiQPMAypCVCzKgHe/W8LX55xzSnlUQqDU2NzN37LfC4ctS1h3BfIIU5KP9PmdgkKRUWcrFE3eMXPG36OdU4hRj4FMURzmAOZYhaSXuP8= -deploy: -- provider: s3 - access_key_id: AKIAJLWYN4T4D5WU4APA - secret_access_key: &1 - secure: j89/yB4NUSUwgG4UKVBMuzkGW8UwFS3Fp9W/7sxFh0zXndMwEN8yl9OtaJ/K9gKds8FAKYCwdSJ90wztdEuDO7c1k60WtD08vbXx1TIEvk9N+VRCOAo5dKy3ZpoF8vQJYNP/luQyU5Ev8zm69nzW+40KaHosyS0b6/dvxy4luZnBiiX9bmR+12Mg3qT/NN7qf4SsImxc0OKl37NdUc03ygCPRPLhDD1bOgfLhDaZNby7DNbP3rKSB81T5cww8Ib2PkwjR9UTurD5JpIBJmZXhqVDtZ9cYUjrrmRPtHtzWBVt3QR7pQeqGupdLTlljV7o6FkAbwjYcEo72cMlwYcoDvBwGc0nyZCgQsQDkHRo8ez5WbXuDq3QvNNOCUh+HPCYSze3uWRIwY/Sb48dwLI1Y5ieQ5b+u2dusxRQbOHajPAO6I9Zl2IRuV6/lHhtfKfTF2BIKT2hfRdkMCIlTBGSPF6wZVhBd3dSTXJxwsarphDvfVilHI0tzz3gfnbmwAz/Z5KAASlCE73oX+0dvZDke1tNIURCM8MzfmK3BmRzh0V0zdET1wzRvhSzyvHSfOL6qRjxDfbvRRPQXD2EkFuW3RJlguOrX5k2eJ5eG0Da9hcI2XZ7VI7cK/UdLgRYjad8l6pKtYIQcxRw4EPb4OtsnMlzpzV9uFpObIZSgUgOoC4= - local_dir: dpl_cd_upload - skip_cleanup: true - region: eu-west-1 - wait-until-deployed: true - on: &2 - repo: alphagov/notifications-api - bucket: notify.works-notifications-api-codedeploy -- provider: codedeploy - access_key_id: AKIAJLWYN4T4D5WU4APA - secret_access_key: *1 - bucket: notify.works-notifications-api-codedeploy - key: notifications-api-$TRAVIS_BRANCH-$TRAVIS_BUILD_NUMBER-$TRAVIS_COMMIT.zip - bundle_type: zip - application: api - deployment_group: notifications_admin_api_deployment_group - region: eu-west-1 - wait-until-deployed: true - on: *2 -- provider: codedeploy - access_key_id: AKIAJLWYN4T4D5WU4APA - secret_access_key: *1 - bucket: notify.works-notifications-api-codedeploy - key: notifications-api-$TRAVIS_BRANCH-$TRAVIS_BUILD_NUMBER-$TRAVIS_COMMIT.zip - bundle_type: zip - application: api - deployment_group: notifications_api_deployment_group - region: eu-west-1 - wait-until-deployed: true - on: *2 -- provider: codedeploy - access_key_id: AKIAJLWYN4T4D5WU4APA - secret_access_key: *1 - bucket: notify.works-notifications-api-codedeploy - key: notifications-api-$TRAVIS_BRANCH-$TRAVIS_BUILD_NUMBER-$TRAVIS_COMMIT.zip - bundle_type: zip - application: api - deployment_group: notifications_delivery_api_deployment_group - region: eu-west-1 - wait-until-deployed: true - on: *2 -- provider: s3 - access_key_id: AKIAJBHLXFS5UHJ6PXLA - secret_access_key: &1 - secure: wYEw/AMHxcfddKP7Hc3r08onLtkZyYBnm+xhxMum3rQjR6Otj4rQ9pROw9V3xOzPBq/nKPTkO6G+4UVrHlPwBmplfpeyK/PXLll8gcNPV0qWxyKs7uVBW6KAgsXm5utuAyLNSWINWpbinjtHCTg3g+JwVFj32/RTUYTKfqzKl/M91Cd1Ezs0olrUIqkONTWRbM3fwCce//77XpRRYIK6baZK4I/qXXJ4P52cj9iqFVF1x684082cxcROtREC0Aeucldvft4WuNtUGZlczcG00PhTP9+pkKhgsNB9q/ICc3aBtbhjdKtJjweNfaNzbL1f/UgG+xJdf7zl4LEpTdSjklJeelHh93UlPObOUsm+BUU9ZRLa8xDpuWxvOxDpSIwBAORkYTl1kxfwb2GJVACZHrmF3zl9Z+btPYGyu6gCSf+3ez0HWFR76DfpsZfMyFUF9mxrBfpQR6d7hYAOdOggOpTB3jCkzMtkDeeYedYErUnfP6CA0rrN5voTqq5RuOWjJPMnuL7BMyM2ZQ2z5QJaUP7FV8z20YV89QiHN83GpZBBqm4t0ai+OLE5tqxGGztfoYpashqPsU6131lkA6X1eSCi9k4TlB40Ll2vcLoW+NhQTYtRr2TcdUiu23CPzzzsi6DOabJFu6PyvZ9IC5nISud/eegbJyDczZfiHETJuBQ= - local_dir: dpl_cd_upload - skip_cleanup: true - region: eu-west-1 - wait-until-deployed: true - on: &2 - repo: alphagov/notifications-api - branch: staging - bucket: staging-notify.works-notifications-api-codedeploy -- provider: codedeploy - access_key_id: AKIAJBHLXFS5UHJ6PXLA - secret_access_key: *1 - bucket: staging-notify.works-notifications-api-codedeploy - key: notifications-api-$TRAVIS_BRANCH-$TRAVIS_BUILD_NUMBER-$TRAVIS_COMMIT.zip - bundle_type: zip - application: api - deployment_group: notifications_api_deployment_group - region: eu-west-1 - wait-until-deployed: true - on: *2 -- provider: codedeploy - access_key_id: AKIAJBHLXFS5UHJ6PXLA - secret_access_key: *1 - bucket: staging-notify.works-notifications-api-codedeploy - key: notifications-api-$TRAVIS_BRANCH-$TRAVIS_BUILD_NUMBER-$TRAVIS_COMMIT.zip - bundle_type: zip - application: api - deployment_group: notifications_admin_api_deployment_group - region: eu-west-1 - wait-until-deployed: true - on: *2 -- provider: codedeploy - access_key_id: AKIAJBHLXFS5UHJ6PXLA - secret_access_key: *1 - bucket: staging-notify.works-notifications-api-codedeploy - key: notifications-api-$TRAVIS_BRANCH-$TRAVIS_BUILD_NUMBER-$TRAVIS_COMMIT.zip - bundle_type: zip - application: api - deployment_group: notifications_delivery_api_deployment_group - region: eu-west-1 - wait-until-deployed: true - on: *2 -- provider: s3 - access_key_id: AKIAIFLN7IJIIQT6S37Q - secret_access_key: &1 - secure: ASg6jCj/On6IUN7Qdz9TpaY1E+sHqYlSX7A/h8qqwZ7ZuYR/zuW9fp7u/q+FxU2XLhyppNMG9fgOiS/uv+mTDLaxq+TIuGjI0tfxX8a/skSa/Uk5X+eqr1S0S4bN460EJvpcW/lv44f8z7Iz/whxU+Evi6k760Z9jIhu13lCtClBekymkK1wY0ul7QegtxA5W2psF/6aDo4MW7PYeq3DbFDfavZS5Xj6h57HImZ3O/BM0UFhBCulsbIC6QZUsXKbXFIVIwaUQbBwjRi2LxJsl0LxGQea4j5zXKWLR3bIvWQzWRTb6lUl29j45NRP5zcEBRld9yfd/Lb7LQtF/8yS33MvOQrCZ1vrbT/VIAjZS9sEzXIm7Pz7hLe/jhINnxvY1Iyqhpe2uI4bUQx3LfhN9PQKf7NoksqK1eoTLt4VD48sDTcNV6lA6d9ZUBkEA5FtSPfiCs0PTKiuRVJJZbY91DWrSdc5pLLi+4y4Ijgb/SaOAq1RnEWs/KPY91zdGOG2BEkpBQeOhQxrAiLEh8a9oC0UUnkqAfofTBqQjx9KV7ADsiKpb20Anhnqt7diuhgLKNCJesqd+66DSPOreBJLaFnACvN4ZJX9nhdz5eIoKe98jT4GWBjZoF5zTIipVPui47gfzbhfGZV4jyADEcJMtmRGpWCT3MXZVRAXLIlr4TQ= - local_dir: dpl_cd_upload - skip_cleanup: true - region: eu-west-1 - wait-until-deployed: true - on: &2 - repo: alphagov/notifications-api - branch: live - bucket: notifications.service.gov.uk-notifications-api-codedeploy -- provider: codedeploy - access_key_id: AKIAIFLN7IJIIQT6S37Q - secret_access_key: *1 - bucket: notifications.service.gov.uk-notifications-api-codedeploy - key: notifications-api-$TRAVIS_BRANCH-$TRAVIS_BUILD_NUMBER-$TRAVIS_COMMIT.zip - bundle_type: zip - application: api - deployment_group: notifications_admin_api_deployment_group - region: eu-west-1 - wait-until-deployed: true - on: *2 -- provider: codedeploy - access_key_id: AKIAIFLN7IJIIQT6S37Q - secret_access_key: *1 - bucket: notifications.service.gov.uk-notifications-api-codedeploy - key: notifications-api-$TRAVIS_BRANCH-$TRAVIS_BUILD_NUMBER-$TRAVIS_COMMIT.zip - bundle_type: zip - application: api - deployment_group: notifications_api_deployment_group - region: eu-west-1 - wait-until-deployed: true - on: *2 -- provider: codedeploy - access_key_id: AKIAIFLN7IJIIQT6S37Q - secret_access_key: *1 - bucket: notifications.service.gov.uk-notifications-api-codedeploy - key: notifications-api-$TRAVIS_BRANCH-$TRAVIS_BUILD_NUMBER-$TRAVIS_COMMIT.zip - bundle_type: zip - application: api - deployment_group: notifications_delivery_api_deployment_group - region: eu-west-1 - wait-until-deployed: true - on: *2 -before_deploy: - - zip -r --exclude=*__pycache__* notifications-api * - - mkdir -p dpl_cd_upload - - mv notifications-api.zip dpl_cd_upload/notifications-api-$TRAVIS_BRANCH-$TRAVIS_BUILD_NUMBER-$TRAVIS_COMMIT.zip diff --git a/Makefile b/Makefile index 5968a5a0c..03ed77f40 100644 --- a/Makefile +++ b/Makefile @@ -35,9 +35,9 @@ check-env-vars: ## Check mandatory environment variables $(if ${AWS_ACCESS_KEY_ID},,$(error Must specify AWS_ACCESS_KEY_ID)) $(if ${AWS_SECRET_ACCESS_KEY},,$(error Must specify AWS_SECRET_ACCESS_KEY)) -.PHONY: development -development: ## Set environment to development - $(eval export DEPLOY_ENV=development) +.PHONY: preview +preview: ## Set environment to preview + $(eval export DEPLOY_ENV=preview) $(eval export DNS_NAME="notify.works") @true @@ -105,6 +105,9 @@ build-with-docker: prepare-docker-build-image ## Build inside a Docker container --name "${DOCKER_CONTAINER_PREFIX}-build" \ -v `pwd`:/var/project \ -v ${PIP_ACCEL_CACHE}:/var/project/cache/pip-accel \ + -e GIT_COMMIT=${GIT_COMMIT} \ + -e BUILD_NUMBER=${BUILD_NUMBER} \ + -e BUILD_URL=${BUILD_URL} \ ${DOCKER_BUILDER_IMAGE_NAME} \ make build @@ -114,6 +117,9 @@ test-with-docker: prepare-docker-build-image create-docker-test-db ## Run tests --name "${DOCKER_CONTAINER_PREFIX}-test" \ --link "${DOCKER_CONTAINER_PREFIX}-db:postgres" \ -e TEST_DATABASE=postgresql://postgres:postgres@postgres/test_notification_api \ + -e GIT_COMMIT=${GIT_COMMIT} \ + -e BUILD_NUMBER=${BUILD_NUMBER} \ + -e BUILD_URL=${BUILD_URL} \ -v `pwd`:/var/project \ ${DOCKER_BUILDER_IMAGE_NAME} \ make test diff --git a/app/__init__.py b/app/__init__.py index af3f15c74..464157f60 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -58,7 +58,7 @@ def create_app(app_name=None): encryption.init_app(application) clients.init_app(sms_clients=[firetext_client, mmg_client, loadtest_client], email_clients=[aws_ses_client]) - from app.service.rest import service as service_blueprint + from app.service.rest import service_blueprint from app.user.rest import user as user_blueprint from app.template.rest import template as template_blueprint from app.status.healthcheck import status as status_blueprint @@ -66,7 +66,6 @@ def create_app(app_name=None): from app.notifications.rest import notifications as notifications_blueprint from app.invite.rest import invite as invite_blueprint from app.accept_invite.rest import accept_invite - from app.notifications_statistics.rest import notifications_statistics as notifications_statistics_blueprint from app.template_statistics.rest import template_statistics as template_statistics_blueprint from app.events.rest import events as events_blueprint from app.provider_details.rest import provider_details as provider_details_blueprint @@ -81,7 +80,7 @@ def create_app(app_name=None): application.register_blueprint(job_blueprint) application.register_blueprint(invite_blueprint) application.register_blueprint(accept_invite, url_prefix='/invite') - application.register_blueprint(notifications_statistics_blueprint) + application.register_blueprint(template_statistics_blueprint) application.register_blueprint(events_blueprint) application.register_blueprint(provider_details_blueprint, url_prefix='/provider-details') diff --git a/app/dao/jobs_dao.py b/app/dao/jobs_dao.py index 13742418a..1ebba4893 100644 --- a/app/dao/jobs_dao.py +++ b/app/dao/jobs_dao.py @@ -1,9 +1,24 @@ -from datetime import date, timedelta - from sqlalchemy import desc, cast, Date as sql_date from app import db from app.dao import days_ago -from app.models import Job +from app.models import Job, NotificationHistory +from app.statsd_decorators import statsd +from sqlalchemy import func, asc + + +@statsd(namespace="dao") +def dao_get_notification_outcomes_for_job(service_id, job_id): + query = db.session.query( + func.count(NotificationHistory.status).label('count'), + NotificationHistory.status.label('status') + ) + + return query \ + .filter(NotificationHistory.service_id == service_id) \ + .filter(NotificationHistory.job_id == job_id)\ + .group_by(NotificationHistory.status) \ + .order_by(asc(NotificationHistory.status)) \ + .all() def dao_get_job_by_service_id_and_job_id(service_id, job_id): diff --git a/app/dao/notifications_dao.py b/app/dao/notifications_dao.py index 86d5b986c..597ab5968 100644 --- a/app/dao/notifications_dao.py +++ b/app/dao/notifications_dao.py @@ -7,9 +7,8 @@ from datetime import ( from flask import current_app from werkzeug.datastructures import MultiDict -from sqlalchemy import (desc, func, Integer, or_, and_, asc) +from sqlalchemy import (desc, func, or_, and_, asc) from sqlalchemy.orm import joinedload -from sqlalchemy.sql.expression import cast from app import db from app.dao import days_ago @@ -34,31 +33,14 @@ from app.dao.dao_utils import transactional from app.statsd_decorators import statsd -@statsd(namespace="dao") -def dao_get_notification_statistics_for_service(service_id, limit_days=None): - query_filter = [NotificationStatistics.service_id == service_id] - if limit_days is not None: - query_filter.append(NotificationStatistics.day >= days_ago(limit_days)) - return NotificationStatistics.query.filter( - *query_filter - ).order_by( - desc(NotificationStatistics.day) - ).all() - - -@statsd(namespace="dao") def dao_get_notification_statistics_for_service_and_day(service_id, day): + # only used by stat-updating code in tasks.py return NotificationStatistics.query.filter_by( service_id=service_id, day=day ).order_by(desc(NotificationStatistics.day)).first() -@statsd(namespace="dao") -def dao_get_notification_statistics_for_day(day): - return NotificationStatistics.query.filter_by(day=day).all() - - @statsd(namespace="dao") def dao_get_potential_notification_statistics_for_day(day): all_services = db.session.query( @@ -107,37 +89,8 @@ def create_notification_statistics_dict(service_id, day): } -@statsd(namespace="dao") -def dao_get_7_day_agg_notification_statistics_for_service(service_id, - date_from, - week_count=52): - doy = date_from.timetuple().tm_yday - return db.session.query( - cast(func.floor((func.extract('doy', NotificationStatistics.day) - doy) / 7), Integer), - cast(func.sum(NotificationStatistics.emails_requested), Integer), - cast(func.sum(NotificationStatistics.emails_delivered), Integer), - cast(func.sum(NotificationStatistics.emails_failed), Integer), - cast(func.sum(NotificationStatistics.sms_requested), Integer), - cast(func.sum(NotificationStatistics.sms_delivered), Integer), - cast(func.sum(NotificationStatistics.sms_failed), Integer) - ).filter( - NotificationStatistics.service_id == service_id - ).filter( - NotificationStatistics.day >= date_from - ).filter( - NotificationStatistics.day < date_from + timedelta(days=7 * week_count) - ).group_by( - func.floor(((func.extract('doy', NotificationStatistics.day) - doy) / 7)) - ).order_by( - desc(func.floor(((func.extract('doy', NotificationStatistics.day) - doy) / 7))) - ).limit( - week_count - ) - - @statsd(namespace="dao") def dao_get_template_usage(service_id, limit_days=None): - table = NotificationHistory if limit_days and limit_days <= 7: # can get this data from notifications table @@ -155,28 +108,18 @@ def dao_get_template_usage(service_id, limit_days=None): query_filter.append(table.created_at >= days_ago(limit_days)) return query.filter(*query_filter) \ - .join(Template)\ - .group_by(table.template_id, Template.name, Template.template_type)\ - .order_by(asc(Template.name))\ + .join(Template) \ + .group_by(table.template_id, Template.name, Template.template_type) \ + .order_by(asc(Template.name)) \ .all() @statsd(namespace="dao") -def dao_get_template_statistics_for_service(service_id, limit_days=None): - query_filter = [TemplateStatistics.service_id == service_id] - if limit_days is not None: - query_filter.append(TemplateStatistics.day >= days_ago(limit_days)) - return TemplateStatistics.query.filter(*query_filter).order_by( - desc(TemplateStatistics.updated_at)).all() - - -@statsd(namespace="dao") -def dao_get_template_statistics_for_template(template_id): - return TemplateStatistics.query.filter( - TemplateStatistics.template_id == template_id - ).order_by( - desc(TemplateStatistics.updated_at) - ).all() +def dao_get_last_template_usage(template_id): + return NotificationHistory.query.filter(NotificationHistory.template_id == template_id)\ + .join(Template) \ + .order_by(desc(NotificationHistory.created_at)) \ + .first() @statsd(namespace="dao") diff --git a/app/dao/services_dao.py b/app/dao/services_dao.py index eff4679dd..031c10939 100644 --- a/app/dao/services_dao.py +++ b/app/dao/services_dao.py @@ -180,3 +180,25 @@ def dao_fetch_weekly_historical_stats_for_service(service_id): ).order_by( asc(monday_of_notification_week), NotificationHistory.status ).all() + + +@statsd(namespace='dao') +def dao_fetch_todays_stats_for_all_services(): + return db.session.query( + Notification.notification_type, + Notification.status, + Notification.service_id, + func.count(Notification.id).label('count') + ).select_from( + Service + ).join( + Notification + ).filter( + func.date(Notification.created_at) == date.today() + ).group_by( + Notification.notification_type, + Notification.status, + Notification.service_id + ).order_by( + Notification.service_id + ) diff --git a/app/job/rest.py b/app/job/rest.py index c40172a85..a9efb3ebf 100644 --- a/app/job/rest.py +++ b/app/job/rest.py @@ -8,7 +8,8 @@ from flask import ( from app.dao.jobs_dao import ( dao_create_job, dao_get_job_by_service_id_and_job_id, - dao_get_jobs_by_service_id + dao_get_jobs_by_service_id, + dao_get_notification_outcomes_for_job ) from app.dao.services_dao import ( @@ -42,7 +43,11 @@ register_errors(job) @job.route('/', methods=['GET']) def get_job_by_service_and_job_id(service_id, job_id): job = dao_get_job_by_service_id_and_job_id(service_id, job_id) + statistics = dao_get_notification_outcomes_for_job(service_id, job_id) data = job_schema.dump(job).data + + data['statistics'] = [{'status': statistic[1], 'count': statistic[0]} for statistic in statistics] + return jsonify(data=data) diff --git a/app/models.py b/app/models.py index efe5bb7b7..6bb870b6d 100644 --- a/app/models.py +++ b/app/models.py @@ -303,6 +303,19 @@ class ProviderDetails(db.Model): JOB_STATUS_TYPES = ['pending', 'in progress', 'finished', 'sending limits exceeded'] +JOB_STATUS_PENDING = 'pending' +JOB_STATUS_IN_PROGRESS = 'in progress' +JOB_STATUS_FINISHED = 'finished' +JOB_STATUS_SENDING_LIMITS_EXCEEDED = 'sending limits exceeded' +JOB_STATUS_SCHEDULED = 'scheduled' + + +class JobStatusTypes(db.Model): + __tablename__ = 'job_status' + + name = db.Column(db.String(255), primary_key=True) + + class Job(db.Model): __tablename__ = 'jobs' @@ -343,6 +356,13 @@ class Job(db.Model): nullable=True) created_by = db.relationship('User') created_by_id = db.Column(UUID(as_uuid=True), db.ForeignKey('users.id'), index=True, nullable=False) + scheduled_for = db.Column( + db.DateTime, + index=True, + unique=False, + nullable=True) + job_status = db.Column( + db.String(255), db.ForeignKey('job_status.name'), index=True, nullable=True) VERIFY_CODE_TYPES = [EMAIL_TYPE, SMS_TYPE] diff --git a/app/notifications_statistics/__init__.py b/app/notifications_statistics/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/app/notifications_statistics/rest.py b/app/notifications_statistics/rest.py deleted file mode 100644 index 6613b10f8..000000000 --- a/app/notifications_statistics/rest.py +++ /dev/null @@ -1,105 +0,0 @@ -from datetime import ( - date, - timedelta, - datetime -) - -from flask import ( - Blueprint, - jsonify, - request, - current_app -) - -from app import DATE_FORMAT - -from app.dao.notifications_dao import ( - dao_get_notification_statistics_for_service, - dao_get_7_day_agg_notification_statistics_for_service, - dao_get_notification_statistics_for_service_and_day -) -from app.schemas import ( - notifications_statistics_schema, - week_aggregate_notification_statistics_schema -) - -notifications_statistics = Blueprint( - 'notifications-statistics', - __name__, url_prefix='/service//notifications-statistics' -) - -from app.errors import ( - register_errors, - InvalidRequest -) - -register_errors(notifications_statistics) - - -@notifications_statistics.route('', methods=['GET']) -def get_all_notification_statistics_for_service(service_id): - - if request.args.get('limit_days'): - try: - statistics = dao_get_notification_statistics_for_service( - service_id=service_id, - limit_days=int(request.args['limit_days']) - ) - except ValueError as e: - message = '{} is not an integer'.format(request.args['limit_days']) - errors = {'limit_days': [message]} - raise InvalidRequest(errors, status_code=400) - else: - statistics = dao_get_notification_statistics_for_service(service_id=service_id) - - data, errors = notifications_statistics_schema.dump(statistics, many=True) - return jsonify(data=data) - - -@notifications_statistics.route('/day/', methods=['GET']) -def get_notification_statistics_for_service_for_day(service_id, day): - - try: - datetime.strptime(day, DATE_FORMAT) - except ValueError: - raise InvalidRequest('Invalid date {}'.format(day), status_code=400) - - service_stats = dao_get_notification_statistics_for_service_and_day( - service_id, - day - ) - - if not service_stats: - message = 'No statistics found for service id: {} on day: {} '.format(service_id, day) - errors = {'not found': [message]} - raise InvalidRequest(errors, status_code=404) - - data = notifications_statistics_schema.dump(service_stats).data - return jsonify(data=data) - - -@notifications_statistics.route('/seven_day_aggregate') -def get_notification_statistics_for_service_seven_day_aggregate(service_id): - data = week_aggregate_notification_statistics_schema.load(request.args).data - date_from = data['date_from'] if 'date_from' in data else date(date.today().year, 4, 1) - week_count = data['week_count'] if 'week_count' in data else 52 - stats = dao_get_7_day_agg_notification_statistics_for_service( - service_id, - date_from, - week_count).all() - json_stats = [] - for x in range(week_count - 1, -1, -1): - week_stats = stats.pop(0) if len(stats) > 0 and stats[0][0] == x else [x, 0, 0, 0, 0, 0, 0] - week_start = (date_from + timedelta(days=week_stats[0] * 7)) - if week_start <= date.today(): - json_stats.append({ - 'week_start': week_start.strftime('%Y-%m-%d'), - 'week_end': (date_from + timedelta(days=(week_stats[0] * 7) + 6)).strftime('%Y-%m-%d'), - 'emails_requested': week_stats[1], - 'emails_delivered': week_stats[2], - 'emails_failed': week_stats[3], - 'sms_requested': week_stats[4], - 'sms_delivered': week_stats[5], - 'sms_failed': week_stats[6] - }) - return jsonify(data=json_stats) diff --git a/app/schemas.py b/app/schemas.py index f9f66f0bf..211d1ed4a 100644 --- a/app/schemas.py +++ b/app/schemas.py @@ -210,7 +210,11 @@ class JobSchema(BaseSchema): class Meta: model = models.Job - exclude = ('notifications',) + exclude = ( + 'notifications', + 'notifications_sent', + 'notifications_delivered', + 'notifications_failed') strict = True @@ -469,30 +473,6 @@ class OrganisationSchema(BaseSchema): strict = True -class FromToDateSchema(ma.Schema): - - class Meta: - strict = True - - date_from = fields.Date() - date_to = fields.Date() - - @validates('date_from') - def validate_date_from(self, value): - _validate_not_in_future(value) - - @validates('date_to') - def validate_date_to(self, value): - _validate_not_in_future(value) - - @validates_schema - def validate_dates(self, data): - df = data.get('date_from') - dt = data.get('date_to') - if (df and dt) and (df > dt): - raise ValidationError("date_from needs to be greater than date_to") - - class DaySchema(ma.Schema): class Meta: @@ -505,23 +485,6 @@ class DaySchema(ma.Schema): _validate_not_in_future(value) -class WeekAggregateNotificationStatisticsSchema(ma.Schema): - - class Meta: - strict = True - - date_from = fields.Date() - week_count = fields.Int() - - @validates('date_from') - def validate_date_from(self, value): - _validate_not_in_future(value) - - @validates('week_count') - def validate_week_count(self, value): - _validate_positive_number(value) - - class UnarchivedTemplateSchema(BaseSchema): archived = fields.Boolean(required=True) @@ -562,8 +525,6 @@ api_key_history_schema = ApiKeyHistorySchema() template_history_schema = TemplateHistorySchema() event_schema = EventSchema() organisation_schema = OrganisationSchema() -from_to_date_schema = FromToDateSchema() provider_details_schema = ProviderDetailsSchema() -week_aggregate_notification_statistics_schema = WeekAggregateNotificationStatisticsSchema() day_schema = DaySchema() unarchived_template_schema = UnarchivedTemplateSchema() diff --git a/app/service/rest.py b/app/service/rest.py index 6f45b6da8..5a80cdc76 100644 --- a/app/service/rest.py +++ b/app/service/rest.py @@ -1,4 +1,4 @@ -from datetime import date, timedelta +import itertools from flask import ( jsonify, @@ -23,7 +23,8 @@ from app.dao.services_dao import ( dao_remove_user_from_service, dao_fetch_stats_for_service, dao_fetch_todays_stats_for_service, - dao_fetch_weekly_historical_stats_for_service + dao_fetch_weekly_historical_stats_for_service, + dao_fetch_todays_stats_for_all_services ) from app.dao import notifications_dao from app.dao.provider_statistics_dao import get_fragment_count @@ -32,7 +33,6 @@ from app.schemas import ( service_schema, api_key_schema, user_schema, - from_to_date_schema, permission_schema, notification_with_template_schema, notifications_filter_schema, @@ -45,25 +45,28 @@ from app.errors import ( ) from app.service import statistics -service = Blueprint('service', __name__) -register_errors(service) +service_blueprint = Blueprint('service', __name__) +register_errors(service_blueprint) -@service.route('', methods=['GET']) +@service_blueprint.route('', methods=['GET']) def get_services(): user_id = request.args.get('user_id', None) if user_id: services = dao_fetch_all_services_by_user(user_id) + elif request.args.get('detailed') == 'True': + return jsonify(data=get_detailed_services()) else: services = dao_fetch_all_services() data = service_schema.dump(services, many=True).data return jsonify(data=data) -@service.route('/', methods=['GET']) +@service_blueprint.route('/', methods=['GET']) def get_service_by_id(service_id): if request.args.get('detailed') == 'True': - return get_detailed_service(service_id, today_only=request.args.get('today_only') == 'True') + data = get_detailed_service(service_id, today_only=request.args.get('today_only') == 'True') + return jsonify(data=data) else: fetched = dao_fetch_service_by_id(service_id) @@ -71,7 +74,7 @@ def get_service_by_id(service_id): return jsonify(data=data) -@service.route('', methods=['POST']) +@service_blueprint.route('', methods=['POST']) def create_service(): data = request.get_json() if not data.get('user_id', None): @@ -85,7 +88,7 @@ def create_service(): return jsonify(data=service_schema.dump(valid_service).data), 201 -@service.route('/', methods=['POST']) +@service_blueprint.route('/', methods=['POST']) def update_service(service_id): fetched_service = dao_fetch_service_by_id(service_id) current_data = dict(service_schema.dump(fetched_service).data.items()) @@ -95,7 +98,7 @@ def update_service(service_id): return jsonify(data=service_schema.dump(fetched_service).data), 200 -@service.route('//api-key', methods=['POST']) +@service_blueprint.route('//api-key', methods=['POST']) def create_api_key(service_id=None): fetched_service = dao_fetch_service_by_id(service_id=service_id) valid_api_key = api_key_schema.load(request.get_json()).data @@ -105,14 +108,14 @@ def create_api_key(service_id=None): return jsonify(data=unsigned_api_key), 201 -@service.route('//api-key/revoke/', methods=['POST']) +@service_blueprint.route('//api-key/revoke/', methods=['POST']) def revoke_api_key(service_id, api_key_id): expire_api_key(service_id=service_id, api_key_id=api_key_id) return jsonify(), 202 -@service.route('//api-keys', methods=['GET']) -@service.route('//api-keys/', methods=['GET']) +@service_blueprint.route('//api-keys', methods=['GET']) +@service_blueprint.route('//api-keys/', methods=['GET']) def get_api_keys(service_id, key_id=None): dao_fetch_service_by_id(service_id=service_id) @@ -128,14 +131,14 @@ def get_api_keys(service_id, key_id=None): return jsonify(apiKeys=api_key_schema.dump(api_keys, many=True).data), 200 -@service.route('//users', methods=['GET']) +@service_blueprint.route('//users', methods=['GET']) def get_users_for_service(service_id): fetched = dao_fetch_service_by_id(service_id) result = user_schema.dump(fetched.users, many=True) return jsonify(data=result.data) -@service.route('//users/', methods=['POST']) +@service_blueprint.route('//users/', methods=['POST']) def add_user_to_service(service_id, user_id): service = dao_fetch_service_by_id(service_id) user = get_model_users(user_id=user_id) @@ -150,7 +153,7 @@ def add_user_to_service(service_id, user_id): return jsonify(data=data), 201 -@service.route('//users/', methods=['DELETE']) +@service_blueprint.route('//users/', methods=['DELETE']) def remove_user_from_service(service_id, user_id): service = dao_fetch_service_by_id(service_id) user = get_model_users(user_id=user_id) @@ -166,7 +169,7 @@ def remove_user_from_service(service_id, user_id): return jsonify({}), 204 -@service.route('//fragment/aggregate_statistics') +@service_blueprint.route('//fragment/aggregate_statistics') def get_service_provider_aggregate_statistics(service_id): return jsonify(data=get_fragment_count(service_id)) @@ -174,7 +177,7 @@ def get_service_provider_aggregate_statistics(service_id): # This is placeholder get method until more thought # goes into how we want to fetch and view various items in history # tables. This is so product owner can pass stories as done -@service.route('//history', methods=['GET']) +@service_blueprint.route('//history', methods=['GET']) def get_service_history(service_id): from app.models import (Service, ApiKey, Template, TemplateHistory, Event) from app.schemas import ( @@ -204,7 +207,7 @@ def get_service_history(service_id): return jsonify(data=data) -@service.route('//notifications', methods=['GET']) +@service_blueprint.route('//notifications', methods=['GET']) def get_all_notifications_for_service(service_id): data = notifications_filter_schema.load(request.args).data page = data['page'] if 'page' in data else 1 @@ -231,7 +234,7 @@ def get_all_notifications_for_service(service_id): ), 200 -@service.route('//notifications/weekly', methods=['GET']) +@service_blueprint.route('//notifications/weekly', methods=['GET']) def get_weekly_notification_stats(service_id): service = dao_fetch_service_by_id(service_id) stats = dao_fetch_weekly_historical_stats_for_service(service_id) @@ -246,5 +249,19 @@ def get_detailed_service(service_id, today_only=False): service.statistics = statistics.format_statistics(stats) - data = detailed_service_schema.dump(service).data - return jsonify(data=data) + return detailed_service_schema.dump(service).data + + +def get_detailed_services(): + services = {service.id: service for service in dao_fetch_all_services()} + stats = dao_fetch_todays_stats_for_all_services() + + for service_id, rows in itertools.groupby(stats, lambda x: x.service_id): + services[service_id].statistics = statistics.format_statistics(rows) + + # if service has not sent anything, query will not have set statistics correctly + for service in services.values(): + if not hasattr(service, 'statistics'): + service.statistics = statistics.create_zeroed_stats_dicts() + + return detailed_service_schema.dump(services.values(), many=True).data diff --git a/app/service/statistics.py b/app/service/statistics.py index abcb25740..3f06fcacb 100644 --- a/app/service/statistics.py +++ b/app/service/statistics.py @@ -8,7 +8,7 @@ def format_statistics(statistics): # statistics come in a named tuple with uniqueness from 'notification_type', 'status' - however missing # statuses/notification types won't be represented and the status types need to be simplified/summed up # so we can return emails/sms * created, sent, and failed - counts = _create_zeroed_stats_dicts() + counts = create_zeroed_stats_dicts() for row in statistics: _update_statuses_from_row(counts[row.notification_type], row) @@ -20,7 +20,7 @@ def format_weekly_notification_stats(statistics, service_created_at): # turn a datetime into midnight that day http://stackoverflow.com/a/1937636 preceeding_monday_midnight = datetime.combine(preceeding_monday.date(), datetime.min.time()) week_dict = { - week: _create_zeroed_stats_dicts() + week: create_zeroed_stats_dicts() for week in _weeks_for_range(preceeding_monday_midnight, datetime.utcnow()) } for row in statistics: @@ -29,7 +29,7 @@ def format_weekly_notification_stats(statistics, service_created_at): return week_dict -def _create_zeroed_stats_dicts(): +def create_zeroed_stats_dicts(): return { template_type: { status: 0 for status in ('requested', 'delivered', 'failed') diff --git a/app/template_statistics/rest.py b/app/template_statistics/rest.py index 191645b96..87335c439 100644 --- a/app/template_statistics/rest.py +++ b/app/template_statistics/rest.py @@ -6,11 +6,9 @@ from flask import ( from app.dao.notifications_dao import ( dao_get_template_usage, - dao_get_template_statistics_for_service, - dao_get_template_statistics_for_template -) + dao_get_last_template_usage) -from app.schemas import template_statistics_schema +from app.schemas import notifications_filter_schema, NotificationWithTemplateSchema, notification_with_template_schema template_statistics = Blueprint('template-statistics', __name__, @@ -47,6 +45,10 @@ def get_template_statistics_for_service_by_day(service_id): @template_statistics.route('/') def get_template_statistics_for_template_id(service_id, template_id): - stats = dao_get_template_statistics_for_template(template_id) - data = template_statistics_schema.dump(stats, many=True).data + notification = dao_get_last_template_usage(template_id) + if not notification: + message = 'No template found for id {}'.format(template_id) + errors = {'template_id': [message]} + raise InvalidRequest(errors, status_code=404) + data = notification_with_template_schema.dump(notification).data return jsonify(data=data) diff --git a/migrations/versions/0044_jobs_to_notification_hist.py b/migrations/versions/0044_jobs_to_notification_hist.py index dc4df6770..119d3e03b 100644 --- a/migrations/versions/0044_jobs_to_notification_hist.py +++ b/migrations/versions/0044_jobs_to_notification_hist.py @@ -20,59 +20,68 @@ from app.models import Job, Template, NotificationHistory def upgrade(): - session = Session(bind=op.get_bind()) - - go_live = datetime.datetime.strptime('2016-05-18', '%Y-%m-%d') - notifications_history_start_date = datetime.datetime.strptime('2016-06-26 23:21:55', '%Y-%m-%d %H:%M:%S') - jobs = session.query(Job).join(Template).filter(Job.service_id == '95316ff0-e555-462d-a6e7-95d26fbfd091', - Job.created_at >= go_live, - Job.created_at < notifications_history_start_date).all() - - for job in jobs: - for i in range(0, job.notifications_delivered): - notification = NotificationHistory(id=uuid.uuid4(), - job_id=job.id, - service_id=job.service_id, - template_id=job.template.id, - template_version=job.template_version, - key_type='normal', - content_char_count=len(job.template.content), - notification_type=job.template.template_type, - created_at=job.created_at, - sent_at=job.processing_finished, - sent_by='ses' if job.template.template_type == 'email' else 'mmg', - status='delivered') - - session.add(notification) - - for i in range(0, job.notifications_failed): - notification = NotificationHistory(id=uuid.uuid4(), - job_id=job.id, - service_id=job.service_id, - template_id=job.template.id, - template_version=job.template_version, - key_type='normal', - content_char_count=len(job.template.content), - notification_type=job.template.template_type, - created_at=job.created_at, - sent_at=job.processing_finished, - sent_by='ses' if job.template.template_type == 'email' else 'mmg', - status='permanent-failure') - session.add(notification) - session.commit() + # + # REMOVED + # This script has been applied and doesn't need to be re-applied + # note that by referencing the model objects in migration files, any subsequent alteration of the model and thus + # the database causes all previous migration scripts to fail as the model and DB will be inconsistent in this + # past state. + # + # session = Session(bind=op.get_bind()) + # + # go_live = datetime.datetime.strptime('2016-05-18', '%Y-%m-%d') + # notifications_history_start_date = datetime.datetime.strptime('2016-06-26 23:21:55', '%Y-%m-%d %H:%M:%S') + # jobs = session.query(Job).join(Template).filter(Job.service_id == '95316ff0-e555-462d-a6e7-95d26fbfd091', + # Job.created_at >= go_live, + # Job.created_at < notifications_history_start_date).all() + # + # for job in jobs: + # for i in range(0, job.notifications_delivered): + # notification = NotificationHistory(id=uuid.uuid4(), + # job_id=job.id, + # service_id=job.service_id, + # template_id=job.template.id, + # template_version=job.template_version, + # key_type='normal', + # content_char_count=len(job.template.content), + # notification_type=job.template.template_type, + # created_at=job.created_at, + # sent_at=job.processing_finished, + # sent_by='ses' if job.template.template_type == 'email' else 'mmg', + # status='delivered') + # + # session.add(notification) + # + # for i in range(0, job.notifications_failed): + # notification = NotificationHistory(id=uuid.uuid4(), + # job_id=job.id, + # service_id=job.service_id, + # template_id=job.template.id, + # template_version=job.template_version, + # key_type='normal', + # content_char_count=len(job.template.content), + # notification_type=job.template.template_type, + # created_at=job.created_at, + # sent_at=job.processing_finished, + # sent_by='ses' if job.template.template_type == 'email' else 'mmg', + # status='permanent-failure') + # session.add(notification) + # session.commit() + pass def downgrade(): - ### commands auto generated by Alembic - please adjust! ### - session = Session(bind=op.get_bind()) - - go_live = datetime.datetime.strptime('2016-05-18', '%Y-%m-%d') - notifications_history_start_date = datetime.datetime.strptime('2016-06-26 23:21:55', '%Y-%m-%d %H:%M:%S') - - session.query(NotificationHistory).filter( - NotificationHistory.created_at >= go_live, - NotificationHistory.service_id == '95316ff0-e555-462d-a6e7-95d26fbfd091', - NotificationHistory.created_at < notifications_history_start_date).delete() - - session.commit() - ### end Alembic commands ### + # ### commands auto generated by Alembic - please adjust! ### + # session = Session(bind=op.get_bind()) + # + # go_live = datetime.datetime.strptime('2016-05-18', '%Y-%m-%d') + # notifications_history_start_date = datetime.datetime.strptime('2016-06-26 23:21:55', '%Y-%m-%d %H:%M:%S') + # + # session.query(NotificationHistory).filter( + # NotificationHistory.created_at >= go_live, + # NotificationHistory.service_id == '95316ff0-e555-462d-a6e7-95d26fbfd091', + # NotificationHistory.created_at < notifications_history_start_date).delete() + # + # session.commit() + # ### end Alembic commands ### + pass diff --git a/migrations/versions/0048_job_scheduled_time.py b/migrations/versions/0048_job_scheduled_time.py new file mode 100644 index 000000000..8edbbb62e --- /dev/null +++ b/migrations/versions/0048_job_scheduled_time.py @@ -0,0 +1,41 @@ +"""empty message + +Revision ID: 0048_job_scheduled_time +Revises: 0047_ukvi_spelling +Create Date: 2016-08-24 13:21:51.744526 + +""" + +# revision identifiers, used by Alembic. +revision = '0048_job_scheduled_time' +down_revision = '0047_ukvi_spelling' + +from alembic import op +import sqlalchemy as sa + + +def upgrade(): + op.create_table('job_status', + sa.Column('name', sa.String(length=255), nullable=False), + sa.PrimaryKeyConstraint('name') + ) + op.add_column('jobs', sa.Column('job_status', sa.String(length=255), nullable=True)) + op.add_column('jobs', sa.Column('scheduled_for', sa.DateTime(), nullable=True)) + op.create_index(op.f('ix_jobs_job_status'), 'jobs', ['job_status'], unique=False) + op.create_index(op.f('ix_jobs_scheduled_for'), 'jobs', ['scheduled_for'], unique=False) + op.create_foreign_key(None, 'jobs', 'job_status', ['job_status'], ['name']) + + op.execute("insert into job_status values ('pending')") + op.execute("insert into job_status values ('in progress')") + op.execute("insert into job_status values ('finished')") + op.execute("insert into job_status values ('sending limits exceeded')") + op.execute("insert into job_status values ('scheduled')") + + +def downgrade(): + op.drop_constraint('jobs_job_status_fkey', 'jobs', type_='foreignkey') + op.drop_index(op.f('ix_jobs_scheduled_for'), table_name='jobs') + op.drop_index(op.f('ix_jobs_job_status'), table_name='jobs') + op.drop_column('jobs', 'scheduled_for') + op.drop_column('jobs', 'job_status') + op.drop_table('job_status') diff --git a/scripts/trigger-dependent-build.sh b/scripts/trigger-dependent-build.sh deleted file mode 100755 index 20c820add..000000000 --- a/scripts/trigger-dependent-build.sh +++ /dev/null @@ -1,18 +0,0 @@ -#!/bin/bash -x - -# This script lives in each of the upstream repos. Add this to .travis.yml to -# run after each successful build (assuming that the script is in the root of -# the repo): -# after_success: -# - ./trigger-dependent-build -# - -case $TRAVIS_BRANCH in - master|staging|live) - echo "Triggering dependent build for $TRAVIS_BRANCH" - curl -vvv -s -X POST -H "Content-Type: application/json" -H "Accept: application/json" -H "Travis-API-Version: 3" -H "Authorization: token $auth_token" -d '{"request":{"branch":"master","config":{"env":{"global":["ENVIRONMENT='$TRAVIS_BRANCH'"]}}}}' https://api.travis-ci.org/repo/alphagov%2Fnotifications-functional-tests/requests - ;; - *) - echo "Not triggering dependent build for $TRAVIS_BRANCH" - ;; -esac diff --git a/tests/app/dao/test_jobs_dao.py b/tests/app/dao/test_jobs_dao.py index 9f265ab0d..0a69ffbc3 100644 --- a/tests/app/dao/test_jobs_dao.py +++ b/tests/app/dao/test_jobs_dao.py @@ -5,10 +5,95 @@ from app.dao.jobs_dao import ( dao_get_job_by_service_id_and_job_id, dao_create_job, dao_update_job, - dao_get_jobs_by_service_id -) + dao_get_jobs_by_service_id, + dao_get_notification_outcomes_for_job) from app.models import Job +from tests.app.conftest import sample_notification, sample_job, sample_service + + +def test_should_have_decorated_notifications_dao_functions(): + assert dao_get_notification_outcomes_for_job.__wrapped__.__name__ == 'dao_get_notification_outcomes_for_job' # noqa + + +def test_should_get_all_statuses_for_notifications_associated_with_job( + notify_db, + notify_db_session, + sample_service, + sample_job): + + sample_notification(notify_db, notify_db_session, service=sample_service, job=sample_job, status='created') + sample_notification(notify_db, notify_db_session, service=sample_service, job=sample_job, status='sending') + sample_notification(notify_db, notify_db_session, service=sample_service, job=sample_job, status='delivered') + sample_notification(notify_db, notify_db_session, service=sample_service, job=sample_job, status='pending') + sample_notification(notify_db, notify_db_session, service=sample_service, job=sample_job, status='failed') + sample_notification(notify_db, notify_db_session, service=sample_service, job=sample_job, status='technical-failure') # noqa + sample_notification(notify_db, notify_db_session, service=sample_service, job=sample_job, status='temporary-failure') # noqa + sample_notification(notify_db, notify_db_session, service=sample_service, job=sample_job, status='permanent-failure') # noqa + + results = dao_get_notification_outcomes_for_job(sample_service.id, sample_job.id) + assert [(row.count, row.status) for row in results] == [ + (1, 'created'), + (1, 'sending'), + (1, 'delivered'), + (1, 'pending'), + (1, 'failed'), + (1, 'technical-failure'), + (1, 'temporary-failure'), + (1, 'permanent-failure') + ] + + +def test_should_count_of_statuses_for_notifications_associated_with_job( + notify_db, + notify_db_session, + sample_service, + sample_job): + sample_notification(notify_db, notify_db_session, service=sample_service, job=sample_job, status='created') + sample_notification(notify_db, notify_db_session, service=sample_service, job=sample_job, status='created') + sample_notification(notify_db, notify_db_session, service=sample_service, job=sample_job, status='sending') + sample_notification(notify_db, notify_db_session, service=sample_service, job=sample_job, status='sending') + sample_notification(notify_db, notify_db_session, service=sample_service, job=sample_job, status='sending') + sample_notification(notify_db, notify_db_session, service=sample_service, job=sample_job, status='sending') + sample_notification(notify_db, notify_db_session, service=sample_service, job=sample_job, status='delivered') + sample_notification(notify_db, notify_db_session, service=sample_service, job=sample_job, status='delivered') + + results = dao_get_notification_outcomes_for_job(sample_service.id, sample_job.id) + assert [(row.count, row.status) for row in results] == [ + (2, 'created'), + (4, 'sending'), + (2, 'delivered') + ] + + +def test_should_return_zero_length_array_if_no_notifications_for_job(sample_service, sample_job): + assert len(dao_get_notification_outcomes_for_job(sample_job.id, sample_service.id)) == 0 + + +def test_should_return_notifications_only_for_this_job(notify_db, notify_db_session, sample_service): + job_1 = sample_job(notify_db, notify_db_session, service=sample_service) + job_2 = sample_job(notify_db, notify_db_session, service=sample_service) + + sample_notification(notify_db, notify_db_session, service=sample_service, job=job_1, status='created') + sample_notification(notify_db, notify_db_session, service=sample_service, job=job_2, status='created') + + results = dao_get_notification_outcomes_for_job(sample_service.id, job_1.id) + assert [(row.count, row.status) for row in results] == [ + (1, 'created') + ] + + +def test_should_return_notifications_only_for_this_service(notify_db, notify_db_session): + service_1 = sample_service(notify_db, notify_db_session, service_name="one", email_from="one") + service_2 = sample_service(notify_db, notify_db_session, service_name="two", email_from="two") + + job_1 = sample_job(notify_db, notify_db_session, service=service_1) + job_2 = sample_job(notify_db, notify_db_session, service=service_2) + + sample_notification(notify_db, notify_db_session, service=service_1, job=job_1, status='created') + sample_notification(notify_db, notify_db_session, service=service_2, job=job_2, status='created') + + assert len(dao_get_notification_outcomes_for_job(service_1.id, job_2.id)) == 0 def test_create_job(sample_template): diff --git a/tests/app/dao/test_notification_dao.py b/tests/app/dao/test_notification_dao.py index 7a0aa8692..933746c48 100644 --- a/tests/app/dao/test_notification_dao.py +++ b/tests/app/dao/test_notification_dao.py @@ -5,7 +5,6 @@ from functools import partial import pytest from freezegun import freeze_time -from mock import ANY from sqlalchemy.exc import SQLAlchemyError, IntegrityError from app import db @@ -22,20 +21,21 @@ from app.models import ( from app.dao.notifications_dao import ( dao_create_notification, - dao_update_notification, - get_notification_with_personalisation, - get_notification_for_job, - get_notifications_for_job, - dao_get_notification_statistics_for_service, - delete_notifications_created_more_than_a_week_ago, + dao_get_last_template_usage, dao_get_notification_statistics_for_service_and_day, + dao_get_potential_notification_statistics_for_day, + dao_get_template_usage, + dao_update_notification, + delete_notifications_created_more_than_a_week_ago, + get_notification_by_id, + get_notification_for_job, + get_notification_with_personalisation, + get_notifications_for_job, + get_notifications_for_service, update_notification_status_by_id, - update_provider_stats, update_notification_status_by_reference, - dao_get_template_statistics_for_service, - get_notifications_for_service, dao_get_7_day_agg_notification_statistics_for_service, - dao_get_potential_notification_statistics_for_day, dao_get_notification_statistics_for_day, - dao_get_template_statistics_for_template, get_notification_by_id, dao_get_template_usage) + update_provider_stats, +) from notifications_utils.template import get_sms_fragment_count @@ -43,14 +43,9 @@ from tests.app.conftest import (sample_notification, sample_template, sample_ema def test_should_have_decorated_notifications_dao_functions(): - assert dao_get_notification_statistics_for_service.__wrapped__.__name__ == 'dao_get_notification_statistics_for_service' # noqa + assert dao_get_last_template_usage.__wrapped__.__name__ == 'dao_get_last_template_usage' # noqa assert dao_get_template_usage.__wrapped__.__name__ == 'dao_get_template_usage' # noqa - assert dao_get_notification_statistics_for_service_and_day.__wrapped__.__name__ == 'dao_get_notification_statistics_for_service_and_day' # noqa - assert dao_get_notification_statistics_for_day.__wrapped__.__name__ == 'dao_get_notification_statistics_for_day' # noqa assert dao_get_potential_notification_statistics_for_day.__wrapped__.__name__ == 'dao_get_potential_notification_statistics_for_day' # noqa - assert dao_get_7_day_agg_notification_statistics_for_service.__wrapped__.__name__ == 'dao_get_7_day_agg_notification_statistics_for_service' # noqa - assert dao_get_template_statistics_for_service.__wrapped__.__name__ == 'dao_get_template_statistics_for_service' # noqa - assert dao_get_template_statistics_for_template.__wrapped__.__name__ == 'dao_get_template_statistics_for_template' # noqa assert dao_create_notification.__wrapped__.__name__ == 'dao_create_notification' # noqa assert update_notification_status_by_id.__wrapped__.__name__ == 'update_notification_status_by_id' # noqa assert dao_update_notification.__wrapped__.__name__ == 'dao_update_notification' # noqa @@ -64,6 +59,44 @@ def test_should_have_decorated_notifications_dao_functions(): assert delete_notifications_created_more_than_a_week_ago.__wrapped__.__name__ == 'delete_notifications_created_more_than_a_week_ago' # noqa +def test_should_be_able_to_get_template_usage_history(notify_db, notify_db_session, sample_service): + with freeze_time('2000-01-01 12:00:00'): + sms = sample_template(notify_db, notify_db_session) + notification = sample_notification(notify_db, notify_db_session, service=sample_service, template=sms) + results = dao_get_last_template_usage(sms.id) + assert results.template.name == 'Template Name' + assert results.template.template_type == 'sms' + assert results.created_at == datetime(year=2000, month=1, day=1, hour=12, minute=0, second=0) + assert results.template_id == sms.id + assert results.id == notification.id + + +def test_should_be_able_to_get_all_template_usage_history_order_by_notification_created_at( + notify_db, + notify_db_session, + sample_service): + + sms = sample_template(notify_db, notify_db_session) + + sample_notification(notify_db, notify_db_session, service=sample_service, template=sms) + sample_notification(notify_db, notify_db_session, service=sample_service, template=sms) + sample_notification(notify_db, notify_db_session, service=sample_service, template=sms) + most_recent = sample_notification(notify_db, notify_db_session, service=sample_service, template=sms) + + results = dao_get_last_template_usage(sms.id) + assert results.id == most_recent.id + + +def test_should_be_able_to_get_no_template_usage_history_if_no_notifications_using_template( + notify_db, + notify_db_session): + + sms = sample_template(notify_db, notify_db_session) + + results = dao_get_last_template_usage(sms.id) + assert not results + + def test_should_by_able_to_get_template_count_from_notifications_history(notify_db, notify_db_session, sample_service): sms = sample_template(notify_db, notify_db_session) email = sample_email_template(notify_db, notify_db_session) @@ -404,67 +437,6 @@ def test_should_be_able_to_get_all_statistics_for_a_service(sample_template, mmg _assert_notification_stats(sample_template.service.id, sms_requested=3) -def test_should_be_able_to_get_all_statistics_for_a_service_for_several_days(sample_template, mmg_provider): - data = _notification_json(sample_template) - - today = datetime.utcnow() - yesterday = datetime.utcnow() - timedelta(days=1) - two_days_ago = datetime.utcnow() - timedelta(days=2) - data.update({'created_at': today}) - notification_1 = Notification(**data) - data.update({'created_at': yesterday}) - notification_2 = Notification(**data) - data.update({'created_at': two_days_ago}) - notification_3 = Notification(**data) - - dao_create_notification(notification_1, sample_template.template_type) - dao_create_notification(notification_2, sample_template.template_type) - dao_create_notification(notification_3, sample_template.template_type) - - stats = dao_get_notification_statistics_for_service(sample_template.service.id) - assert len(stats) == 3 - assert stats[0].emails_requested == 0 - assert stats[0].sms_requested == 1 - assert stats[0].day == today.date() - assert stats[1].emails_requested == 0 - assert stats[1].sms_requested == 1 - assert stats[1].day == yesterday.date() - assert stats[2].emails_requested == 0 - assert stats[2].sms_requested == 1 - assert stats[2].day == two_days_ago.date() - - -def test_should_be_empty_list_if_no_statistics_for_a_service(sample_service): - assert len(dao_get_notification_statistics_for_service(sample_service.id)) == 0 - - -def test_should_be_able_to_get_all_statistics_for_a_service_for_several_days_previous(sample_template, - mmg_provider): - data = _notification_json(sample_template) - - today = datetime.utcnow() - seven_days_ago = datetime.utcnow() - timedelta(days=7) - eight_days_ago = datetime.utcnow() - timedelta(days=8) - data.update({'created_at': today}) - notification_1 = Notification(**data) - data.update({'created_at': seven_days_ago}) - notification_2 = Notification(**data) - data.update({'created_at': eight_days_ago}) - notification_3 = Notification(**data) - dao_create_notification(notification_1, sample_template.template_type) - dao_create_notification(notification_2, sample_template.template_type) - dao_create_notification(notification_3, sample_template.template_type) - - stats = dao_get_notification_statistics_for_service(sample_template.service.id, 7) - assert len(stats) == 2 - assert stats[0].emails_requested == 0 - assert stats[0].sms_requested == 1 - assert stats[0].day == today.date() - assert stats[1].emails_requested == 0 - assert stats[1].sms_requested == 1 - assert stats[1].day == seven_days_ago.date() - - def test_create_notification_creates_notification_with_personalisation(notify_db, notify_db_session, sample_template_with_placeholders, sample_job, mmg_provider): @@ -487,7 +459,7 @@ def test_create_notification_creates_notification_with_personalisation(notify_db assert data.template == notification_from_db.template assert data.template_version == notification_from_db.template_version assert data.created_at == notification_from_db.created_at - assert 'created' == notification_from_db.status + assert notification_from_db.status == 'created' assert {'name': 'Jo'} == notification_from_db.personalisation _assert_job_stats(sample_job.id, sent=1, count=1, delivered=0, failed=0) @@ -523,7 +495,7 @@ def test_save_notification_creates_sms_and_template_stats(sample_template, sampl assert data['template'] == notification_from_db.template assert data['template_version'] == notification_from_db.template_version assert data['created_at'] == notification_from_db.created_at - assert 'created' == notification_from_db.status + assert notification_from_db.status == 'created' _assert_job_stats(sample_job.id, sent=1, count=1, delivered=0, failed=0) stats = NotificationStatistics.query.filter(NotificationStatistics.service_id == sample_template.service.id).first() @@ -556,7 +528,7 @@ def test_save_notification_and_create_email_and_template_stats(sample_email_temp assert data['template'] == notification_from_db.template assert data['template_version'] == notification_from_db.template_version assert data['created_at'] == notification_from_db.created_at - assert 'created' == notification_from_db.status + assert notification_from_db.status == 'created' _assert_job_stats(sample_job.id, sent=1, count=1, delivered=0, failed=0) stats = NotificationStatistics.query.filter( @@ -693,7 +665,7 @@ def test_save_notification_and_increment_job(sample_template, sample_job, mmg_pr assert data['template'] == notification_from_db.template assert data['template_version'] == notification_from_db.template_version assert data['created_at'] == notification_from_db.created_at - assert 'created' == notification_from_db.status + assert notification_from_db.status == 'created' assert Job.query.get(sample_job.id).notifications_sent == 1 notification_2 = Notification(**data) @@ -744,7 +716,7 @@ def test_save_notification_and_increment_correct_job(notify_db, notify_db_sessio assert data['template'] == notification_from_db.template assert data['template_version'] == notification_from_db.template_version assert data['created_at'] == notification_from_db.created_at - assert 'created' == notification_from_db.status + assert notification_from_db.status == 'created' assert job_1.id != job_2.id _assert_job_stats(job_id=job_1.id, sent=1, count=1) _assert_job_stats(job_id=job_2.id, sent=0, count=1) @@ -765,7 +737,7 @@ def test_save_notification_with_no_job(sample_template, mmg_provider): assert data['template'] == notification_from_db.template assert data['template_version'] == notification_from_db.template_version assert data['created_at'] == notification_from_db.created_at - assert 'created' == notification_from_db.status + assert notification_from_db.status == 'created' def test_get_notification(sample_notification): @@ -791,7 +763,7 @@ def test_save_notification_no_job_id(sample_template, mmg_provider): assert data['service'] == notification_from_db.service assert data['template'] == notification_from_db.template assert data['template_version'] == notification_from_db.template_version - assert 'created' == notification_from_db.status + assert notification_from_db.status == 'created' assert data.get('job_id') is None @@ -1012,103 +984,6 @@ def test_successful_notification_inserts_followed_by_failure_does_not_increment_ _assert_notification_stats(sample_template.service.id, sms_requested=3) -@freeze_time("2016-03-30") -def test_get_template_stats_for_service_returns_stats_in_reverse_date_order(sample_template, sample_job): - template_stats = dao_get_template_statistics_for_service(sample_template.service.id) - assert len(template_stats) == 0 - data = _notification_json(sample_template, job_id=sample_job.id) - - notification = Notification(**data) - dao_create_notification(notification, sample_template.template_type) - - # move on one day - with freeze_time('2016-03-31'): - new_notification = Notification(**data) - dao_create_notification(new_notification, sample_template.template_type) - - # move on one more day - with freeze_time('2016-04-01'): - new_notification = Notification(**data) - dao_create_notification(new_notification, sample_template.template_type) - - template_stats = dao_get_template_statistics_for_service(sample_template.service_id) - assert len(template_stats) == 3 - assert template_stats[0].day == date(2016, 4, 1) - assert template_stats[1].day == date(2016, 3, 31) - assert template_stats[2].day == date(2016, 3, 30) - - -@freeze_time('2016-04-09') -def test_get_template_stats_for_service_returns_stats_can_limit_number_of_days_returned(sample_template): - template_stats = dao_get_template_statistics_for_service(sample_template.service.id) - assert len(template_stats) == 0 - - # Make 9 stats records from 1st to 9th April - for i in range(1, 10): - past_date = '2016-04-0{}'.format(i) - with freeze_time(past_date): - template_stats = TemplateStatistics(template_id=sample_template.id, - service_id=sample_template.service_id) - db.session.add(template_stats) - db.session.commit() - - # Retrieve last week of stats - template_stats = dao_get_template_statistics_for_service(sample_template.service_id, limit_days=7) - assert len(template_stats) == 8 - assert template_stats[0].day == date(2016, 4, 9) - # Final day of stats should be the same as today, eg Monday - assert template_stats[0].day.isoweekday() == template_stats[7].day.isoweekday() - assert template_stats[7].day == date(2016, 4, 2) - - -@freeze_time('2016-04-09') -def test_get_template_stats_for_service_returns_stats_returns_all_stats_if_no_limit(sample_template): - template_stats = dao_get_template_statistics_for_service(sample_template.service.id) - assert len(template_stats) == 0 - - # make 9 stats records from 1st to 9th April - for i in range(1, 10): - past_date = '2016-04-0{}'.format(i) - with freeze_time(past_date): - template_stats = TemplateStatistics(template_id=sample_template.id, - service_id=sample_template.service_id) - db.session.add(template_stats) - db.session.commit() - - template_stats = dao_get_template_statistics_for_service(sample_template.service_id) - assert len(template_stats) == 9 - assert template_stats[0].day == date(2016, 4, 9) - assert template_stats[8].day == date(2016, 4, 1) - - -@freeze_time('2016-04-30') -def test_get_template_stats_for_service_returns_no_result_if_no_usage_within_limit_days(sample_template): - template_stats = dao_get_template_statistics_for_service(sample_template.service.id) - assert len(template_stats) == 0 - - # make 9 stats records from 1st to 9th April - no data after 10th - for i in range(1, 10): - past_date = '2016-04-0{}'.format(i) - with freeze_time(past_date): - template_stats = TemplateStatistics(template_id=sample_template.id, - service_id=sample_template.service_id) - db.session.add(template_stats) - db.session.commit() - - # Retrieve a week of stats - read date is 2016-04-30 - template_stats = dao_get_template_statistics_for_service(sample_template.service_id, limit_days=7) - assert len(template_stats) == 0 - - # Retrieve a month of stats - read date is 2016-04-30 - template_stats = dao_get_template_statistics_for_service(sample_template.service_id, limit_days=30) - assert len(template_stats) == 9 - - -def test_get_template_stats_for_service_with_limit_if_no_records_returns_empty_list(sample_template): - template_stats = dao_get_template_statistics_for_service(sample_template.service.id, limit_days=7) - assert len(template_stats) == 0 - - @freeze_time("2016-01-10") def test_should_limit_notifications_return_by_day_limit_plus_one(notify_db, notify_db_session, sample_service): assert len(Notification.query.all()) == 0 diff --git a/tests/app/dao/test_notification_statistics_dao.py b/tests/app/dao/test_notification_statistics_dao.py deleted file mode 100644 index b8ffe8445..000000000 --- a/tests/app/dao/test_notification_statistics_dao.py +++ /dev/null @@ -1,122 +0,0 @@ -from datetime import (date, timedelta) - -from app.models import NotificationStatistics -from tests.app.conftest import sample_notification_statistics as create_sample_notification_statistics -from app.dao.notifications_dao import dao_get_7_day_agg_notification_statistics_for_service - - -def test_display_weekly_notification_statistics_sum_over_week(notify_db, - notify_db_session, - sample_service): - fools = date(2016, 4, 1) - create_sample_notification_statistics( - notify_db, - notify_db_session, - day=fools - ) - create_sample_notification_statistics( - notify_db, - notify_db_session, - day=fools + timedelta(days=1) - ) - assert dao_get_7_day_agg_notification_statistics_for_service( - sample_service.id, - fools - ).all() == [(0, 4, 2, 2, 4, 2, 2)] - - -def test_display_weekly_notification_statistics_separate_over_weeks(notify_db, - notify_db_session, - sample_service): - fools = date(2016, 4, 1) - next_week = fools + timedelta(days=7) - create_sample_notification_statistics( - notify_db, - notify_db_session, - day=fools - ) - create_sample_notification_statistics( - notify_db, - notify_db_session, - day=next_week - ) - assert dao_get_7_day_agg_notification_statistics_for_service( - sample_service.id, - fools - ).all() == [(1, 2, 1, 1, 2, 1, 1), (0, 2, 1, 1, 2, 1, 1)] - - -def test_display_weekly_notification_statistics_7_days_from_date_from(notify_db, - notify_db_session, - sample_service): - fools = date(2016, 4, 1) - eow_fools = fools + timedelta(days=6) - next_week = fools + timedelta(days=7) - two_weeks_later = fools + timedelta(days=14) - create_sample_notification_statistics( - notify_db, - notify_db_session, - day=fools - ) - create_sample_notification_statistics( - notify_db, - notify_db_session, - day=eow_fools - ) - create_sample_notification_statistics( - notify_db, - notify_db_session, - day=next_week - ) - create_sample_notification_statistics( - notify_db, - notify_db_session, - day=two_weeks_later - ) - assert dao_get_7_day_agg_notification_statistics_for_service( - sample_service.id, - fools - ).all() == [(2, 2, 1, 1, 2, 1, 1), (1, 2, 1, 1, 2, 1, 1), (0, 4, 2, 2, 4, 2, 2)] - - -def test_display_weekly_notification_statistics_week_number_misses_week(notify_db, - notify_db_session, - sample_service): - fools = date(2016, 4, 1) - two_weeks_later = fools + timedelta(days=14) - create_sample_notification_statistics( - notify_db, - notify_db_session, - day=fools - ) - create_sample_notification_statistics( - notify_db, - notify_db_session, - day=two_weeks_later - ) - assert dao_get_7_day_agg_notification_statistics_for_service( - sample_service.id, - fools - ).all() == [(2, 2, 1, 1, 2, 1, 1), (0, 2, 1, 1, 2, 1, 1)] - - -def test_display_weekly_notification_statistics_week_limit(notify_db, - notify_db_session, - sample_service): - fools = date(2016, 4, 1) - two_weeks_later = fools + timedelta(days=14) - create_sample_notification_statistics( - notify_db, - notify_db_session, - day=fools - ) - create_sample_notification_statistics( - notify_db, - notify_db_session, - day=two_weeks_later - ) - assert dao_get_7_day_agg_notification_statistics_for_service( - sample_service.id, - fools, - 1 - ).all() == [(0, 2, 1, 1, 2, 1, 1)] diff --git a/tests/app/job/test_rest.py b/tests/app/job/test_rest.py index ea65bb447..3eeea5112 100644 --- a/tests/app/job/test_rest.py +++ b/tests/app/job/test_rest.py @@ -9,7 +9,7 @@ import app.celery.tasks from tests import create_authorization_header from tests.app.conftest import ( sample_job as create_job, - sample_notification as create_sample_notification) + sample_notification as create_sample_notification, sample_notification) from app.dao.templates_dao import dao_update_template from app.models import NOTIFICATION_STATUS_TYPES @@ -94,20 +94,6 @@ def test_get_job_with_unknown_id_returns404(notify_api, sample_template, fake_uu } -def test_get_job_by_id(notify_api, sample_job): - job_id = str(sample_job.id) - service_id = sample_job.service.id - with notify_api.test_request_context(): - with notify_api.test_client() as client: - path = '/service/{}/job/{}'.format(service_id, job_id) - auth_header = create_authorization_header(service_id=sample_job.service.id) - response = client.get(path, headers=[auth_header]) - assert response.status_code == 200 - resp_json = json.loads(response.get_data(as_text=True)) - assert resp_json['data']['id'] == job_id - assert resp_json['data']['created_by']['name'] == 'Test User' - - def test_create_job(notify_api, sample_template, mocker, fake_uuid): with notify_api.test_request_context(): with notify_api.test_client() as client: @@ -123,6 +109,7 @@ def test_create_job(notify_api, sample_template, mocker, fake_uuid): path = '/service/{}/job'.format(sample_template.service.id) auth_header = create_authorization_header(service_id=sample_template.service.id) headers = [('Content-Type', 'application/json'), auth_header] + response = client.post( path, data=json.dumps(data), @@ -350,3 +337,83 @@ def test_get_all_notifications_for_job_filtered_by_status( resp = json.loads(response.get_data(as_text=True)) assert len(resp['notifications']) == expected_notification_count assert response.status_code == 200 + + +def test_get_job_by_id(notify_api, sample_job): + job_id = str(sample_job.id) + service_id = sample_job.service.id + with notify_api.test_request_context(): + with notify_api.test_client() as client: + path = '/service/{}/job/{}'.format(service_id, job_id) + auth_header = create_authorization_header(service_id=sample_job.service.id) + response = client.get(path, headers=[auth_header]) + assert response.status_code == 200 + resp_json = json.loads(response.get_data(as_text=True)) + assert resp_json['data']['id'] == job_id + assert resp_json['data']['statistics'] == [] + assert resp_json['data']['created_by']['name'] == 'Test User' + + +def test_get_job_by_id_should_return_statistics(notify_db, notify_db_session, notify_api, sample_job): + job_id = str(sample_job.id) + service_id = sample_job.service.id + + sample_notification(notify_db, notify_db_session, service=sample_job.service, job=sample_job, status='created') + sample_notification(notify_db, notify_db_session, service=sample_job.service, job=sample_job, status='sending') + sample_notification(notify_db, notify_db_session, service=sample_job.service, job=sample_job, status='delivered') + sample_notification(notify_db, notify_db_session, service=sample_job.service, job=sample_job, status='pending') + sample_notification(notify_db, notify_db_session, service=sample_job.service, job=sample_job, status='failed') + sample_notification(notify_db, notify_db_session, service=sample_job.service, job=sample_job, status='technical-failure') # noqa + sample_notification(notify_db, notify_db_session, service=sample_job.service, job=sample_job, status='temporary-failure') # noqa + sample_notification(notify_db, notify_db_session, service=sample_job.service, job=sample_job, status='permanent-failure') # noqa + + with notify_api.test_request_context(): + with notify_api.test_client() as client: + path = '/service/{}/job/{}'.format(service_id, job_id) + auth_header = create_authorization_header(service_id=sample_job.service.id) + response = client.get(path, headers=[auth_header]) + assert response.status_code == 200 + resp_json = json.loads(response.get_data(as_text=True)) + print(resp_json) + assert resp_json['data']['id'] == job_id + assert {'status': 'created', 'count': 1} in resp_json['data']['statistics'] + assert {'status': 'sending', 'count': 1} in resp_json['data']['statistics'] + assert {'status': 'delivered', 'count': 1} in resp_json['data']['statistics'] + assert {'status': 'pending', 'count': 1} in resp_json['data']['statistics'] + assert {'status': 'failed', 'count': 1} in resp_json['data']['statistics'] + assert {'status': 'technical-failure', 'count': 1} in resp_json['data']['statistics'] + assert {'status': 'temporary-failure', 'count': 1} in resp_json['data']['statistics'] + assert {'status': 'permanent-failure', 'count': 1} in resp_json['data']['statistics'] + assert resp_json['data']['created_by']['name'] == 'Test User' + + +def test_get_job_by_id_should_return_summed_statistics(notify_db, notify_db_session, notify_api, sample_job): + job_id = str(sample_job.id) + service_id = sample_job.service.id + + sample_notification(notify_db, notify_db_session, service=sample_job.service, job=sample_job, status='created') + sample_notification(notify_db, notify_db_session, service=sample_job.service, job=sample_job, status='created') + sample_notification(notify_db, notify_db_session, service=sample_job.service, job=sample_job, status='created') + sample_notification(notify_db, notify_db_session, service=sample_job.service, job=sample_job, status='sending') + sample_notification(notify_db, notify_db_session, service=sample_job.service, job=sample_job, status='failed') + sample_notification(notify_db, notify_db_session, service=sample_job.service, job=sample_job, status='failed') + sample_notification(notify_db, notify_db_session, service=sample_job.service, job=sample_job, status='failed') + sample_notification(notify_db, notify_db_session, service=sample_job.service, job=sample_job, status='technical-failure') # noqa + sample_notification(notify_db, notify_db_session, service=sample_job.service, job=sample_job, status='temporary-failure') # noqa + sample_notification(notify_db, notify_db_session, service=sample_job.service, job=sample_job, status='temporary-failure') # noqa + + with notify_api.test_request_context(): + with notify_api.test_client() as client: + path = '/service/{}/job/{}'.format(service_id, job_id) + auth_header = create_authorization_header(service_id=sample_job.service.id) + response = client.get(path, headers=[auth_header]) + assert response.status_code == 200 + resp_json = json.loads(response.get_data(as_text=True)) + print(resp_json) + assert resp_json['data']['id'] == job_id + assert {'status': 'created', 'count': 3} in resp_json['data']['statistics'] + assert {'status': 'sending', 'count': 1} in resp_json['data']['statistics'] + assert {'status': 'failed', 'count': 3} in resp_json['data']['statistics'] + assert {'status': 'technical-failure', 'count': 1} in resp_json['data']['statistics'] + assert {'status': 'temporary-failure', 'count': 2} in resp_json['data']['statistics'] + assert resp_json['data']['created_by']['name'] == 'Test User' diff --git a/tests/app/notifications/rest/test_callbacks.py b/tests/app/notifications/rest/test_callbacks.py index 738e8ae95..b6fc84757 100644 --- a/tests/app/notifications/rest/test_callbacks.py +++ b/tests/app/notifications/rest/test_callbacks.py @@ -4,9 +4,9 @@ from flask import json import app.celery.tasks from app.dao.notifications_dao import ( - get_notification_by_id, - dao_get_notification_statistics_for_service + get_notification_by_id ) +from app.models import NotificationStatistics from tests.app.conftest import sample_notification as create_sample_notification @@ -162,7 +162,7 @@ def test_firetext_callback_should_update_notification_status(notify_api, sample_ updated = get_notification_by_id(sample_notification.id) assert updated.status == 'delivered' assert get_notification_by_id(sample_notification.id).status == 'delivered' - stats = dao_get_notification_statistics_for_service(sample_notification.service_id)[0] + stats = get_notification_stats(sample_notification.service_id) assert stats.sms_delivered == 1 assert stats.sms_requested == 1 assert stats.sms_failed == 0 @@ -189,7 +189,7 @@ def test_firetext_callback_should_update_notification_status_failed(notify_api, sample_notification.id ) assert get_notification_by_id(sample_notification.id).status == 'permanent-failure' - stats = dao_get_notification_statistics_for_service(sample_notification.service_id)[0] + stats = get_notification_stats(sample_notification.service_id) assert stats.sms_delivered == 0 assert stats.sms_requested == 1 assert stats.sms_failed == 1 @@ -217,7 +217,7 @@ def test_firetext_callback_should_update_notification_status_pending(notify_api, notification.id ) assert get_notification_by_id(notification.id).status == 'pending' - stats = dao_get_notification_statistics_for_service(notification.service_id)[0] + stats = get_notification_stats(notification.service_id) assert stats.sms_delivered == 0 assert stats.sms_requested == 1 assert stats.sms_failed == 0 @@ -257,7 +257,7 @@ def test_firetext_callback_should_update_multiple_notification_status_sent( ), headers=[('Content-Type', 'application/x-www-form-urlencoded')]) - stats = dao_get_notification_statistics_for_service(notification1.service_id)[0] + stats = get_notification_stats(notification1.service_id) assert stats.sms_delivered == 3 assert stats.sms_requested == 3 assert stats.sms_failed == 0 @@ -481,7 +481,7 @@ def test_ses_callback_should_update_notification_status( assert json_resp['result'] == 'success' assert json_resp['message'] == 'SES callback succeeded' assert get_notification_by_id(notification.id).status == 'delivered' - stats = dao_get_notification_statistics_for_service(notification.service_id)[0] + stats = get_notification_stats(notification.service_id) assert stats.emails_delivered == 1 assert stats.emails_requested == 1 assert stats.emails_failed == 0 @@ -536,7 +536,7 @@ def test_ses_callback_should_update_multiple_notification_status_sent( assert resp2.status_code == 200 assert resp3.status_code == 200 - stats = dao_get_notification_statistics_for_service(notification1.service_id)[0] + stats = get_notification_stats(notification1.service_id) assert stats.emails_delivered == 3 assert stats.emails_requested == 3 assert stats.emails_failed == 0 @@ -568,7 +568,7 @@ def test_ses_callback_should_set_status_to_temporary_failure(notify_api, assert json_resp['result'] == 'success' assert json_resp['message'] == 'SES callback succeeded' assert get_notification_by_id(notification.id).status == 'temporary-failure' - stats = dao_get_notification_statistics_for_service(notification.service_id)[0] + stats = get_notification_stats(notification.service_id) assert stats.emails_delivered == 0 assert stats.emails_requested == 1 assert stats.emails_failed == 1 @@ -628,7 +628,7 @@ def test_ses_callback_should_set_status_to_permanent_failure(notify_api, assert json_resp['result'] == 'success' assert json_resp['message'] == 'SES callback succeeded' assert get_notification_by_id(notification.id).status == 'permanent-failure' - stats = dao_get_notification_statistics_for_service(notification.service_id)[0] + stats = get_notification_stats(notification.service_id) assert stats.emails_delivered == 0 assert stats.emails_requested == 1 assert stats.emails_failed == 1 @@ -730,3 +730,7 @@ def ses_hard_bounce_callback(): def ses_soft_bounce_callback(): return b'{\n "Type" : "Notification",\n "MessageId" : "ref",\n "TopicArn" : "arn:aws:sns:eu-west-1:123456789012:testing",\n "Message" : "{\\"notificationType\\":\\"Bounce\\",\\"bounce\\":{\\"bounceType\\":\\"Undetermined\\",\\"bounceSubType\\":\\"General\\"}, \\"mail\\":{\\"messageId\\":\\"ref\\",\\"timestamp\\":\\"2016-03-14T12:35:25.909Z\\",\\"source\\":\\"test@test-domain.com\\",\\"sourceArn\\":\\"arn:aws:ses:eu-west-1:123456789012:identity/testing-notify\\",\\"sendingAccountId\\":\\"123456789012\\",\\"destination\\":[\\"testing@digital.cabinet-office.gov.uk\\"]},\\"delivery\\":{\\"timestamp\\":\\"2016-03-14T12:35:26.567Z\\",\\"processingTimeMillis\\":658,\\"recipients\\":[\\"testing@digital.cabinet-office.gov.uk\\"],\\"smtpResponse\\":\\"250 2.0.0 OK 1457958926 uo5si26480932wjc.221 - gsmtp\\",\\"reportingMTA\\":\\"a6-238.smtp-out.eu-west-1.amazonses.com\\"}}",\n "Timestamp" : "2016-03-14T12:35:26.665Z",\n "SignatureVersion" : "1",\n "Signature" : "X8d7eTAOZ6wlnrdVVPYanrAlsX0SMPfOzhoTEBnQqYkrNWTqQY91C0f3bxtPdUhUtOowyPAOkTQ4KnZuzphfhVb2p1MyVYMxNKcBFB05/qaCX99+92fjw4x9LeUOwyGwMv5F0Vkfi5qZCcEw69uVrhYLVSTFTrzi/yCtru+yFULMQ6UhbY09GwiP6hjxZMVr8aROQy5lLHglqQzOuSZ4KeD85JjifHdKzlx8jjQ+uj+FLzHXPMAPmPU1JK9kpoHZ1oPshAFgPDpphJe+HwcJ8ezmk+3AEUr3wWli3xF+49y8Z2anASSVp6YI2YP95UT8Rlh3qT3T+V9V8rbSVislxA==",\n "SigningCertURL" : "https://sns.eu-west-1.amazonaws.com/SimpleNotificationService-bb750dd426d95ee9390147a5624348ee.pem",\n "UnsubscribeURL" : "https://sns.eu-west-1.amazonaws.com/?Action=Unsubscribe&SubscriptionArn=arn:aws:sns:eu-west-1:302763885840:preview-emails:d6aad3ef-83d6-4cf3-a470-54e2e75916da"\n}' # noqa + + +def get_notification_stats(service_id): + return NotificationStatistics.query.filter_by(service_id=service_id).one() diff --git a/tests/app/notifications/test_notification_statistics_rest.py b/tests/app/notifications/test_notification_statistics_rest.py deleted file mode 100644 index d298aa7c5..000000000 --- a/tests/app/notifications/test_notification_statistics_rest.py +++ /dev/null @@ -1,207 +0,0 @@ -import json -from datetime import ( - date, - timedelta -) - -from flask import url_for -from tests import create_authorization_header -from tests.app.conftest import sample_notification_statistics as create_sample_notification_statistics - -from freezegun import freeze_time - - -def test_get_notification_statistics_returns_empty_list_if_no_stats(notify_api, - notify_db, - notify_db_session, - sample_template, - sample_email_template): - with notify_api.test_request_context(): - with notify_api.test_client() as client: - - path = '/service/{}/notifications-statistics'.format(sample_email_template.service.id) - - auth_header = create_authorization_header( - service_id=sample_email_template.service_id) - - response = client.get(path, headers=[auth_header]) - assert response.status_code == 200 - - stats = json.loads(response.get_data(as_text=True)) - assert stats['data'] == [] - - -def test_get_week_aggregate_statistics(notify_api, - notify_db, - notify_db_session, - sample_service): - with notify_api.test_request_context(): - sample_notification_statistics = create_sample_notification_statistics( - notify_db, - notify_db_session, - day=date(date.today().year, 4, 1)) - with notify_api.test_client() as client: - endpoint = url_for( - 'notifications-statistics.get_notification_statistics_for_service_seven_day_aggregate', - service_id=sample_service.id) - auth_header = create_authorization_header( - service_id=sample_service.id) - - resp = client.get(endpoint, headers=[auth_header]) - assert resp.status_code == 200 - json_resp = json.loads(resp.get_data(as_text=True)) - week_len_index = len(json_resp['data']) - 1 - assert json_resp['data'][week_len_index]['emails_requested'] == 2 - assert json_resp['data'][week_len_index]['sms_requested'] == 2 - assert json_resp['data'][week_len_index]['week_start'] == date(date.today().year, 4, 1).strftime('%Y-%m-%d') - assert json_resp['data'][week_len_index]['week_end'] == date(date.today().year, 4, 7).strftime('%Y-%m-%d') - - -def test_get_week_aggregate_statistics_date_from(notify_api, - notify_db, - notify_db_session, - sample_service): - with notify_api.test_request_context(): - sample_notification_statistics = create_sample_notification_statistics( - notify_db, - notify_db_session, - day=date(date.today().year, 4, 1)) - date_from_str = date(date.today().year, 4, 1).strftime('%Y-%m-%d') - with notify_api.test_client() as client: - endpoint = url_for( - 'notifications-statistics.get_notification_statistics_for_service_seven_day_aggregate', - service_id=sample_service.id, - date_from=date_from_str) - auth_header = create_authorization_header( - service_id=sample_service.id) - - resp = client.get(endpoint, headers=[auth_header]) - assert resp.status_code == 200 - json_resp = json.loads(resp.get_data(as_text=True)) - week_len_index = len(json_resp['data']) - 1 - assert json_resp['data'][week_len_index]['emails_requested'] == 2 - assert json_resp['data'][week_len_index]['sms_requested'] == 2 - assert json_resp['data'][week_len_index]['week_start'] == date_from_str - assert json_resp['data'][week_len_index]['week_end'] == date(date.today().year, 4, 7).strftime('%Y-%m-%d') - - -def test_get_week_aggregate_statistics_date_in_future(notify_api, - notify_db, - notify_db_session, - sample_service): - with notify_api.test_request_context(): - with notify_api.test_client() as client: - endpoint = url_for( - 'notifications-statistics.get_notification_statistics_for_service_seven_day_aggregate', - service_id=sample_service.id, - date_from=(date.today() + timedelta(days=1)).strftime('%Y-%m-%d')) - auth_header = create_authorization_header( - service_id=sample_service.id) - - resp = client.get(endpoint, headers=[auth_header]) - assert resp.status_code == 400 - json_resp = json.loads(resp.get_data(as_text=True)) - assert json_resp['result'] == 'error' - assert json_resp['message']['date_from'][0] == 'Date cannot be in the future' - - -def test_get_week_aggregate_statistics_invalid_week_count(notify_api, - notify_db, - notify_db_session, - sample_service): - with notify_api.test_request_context(): - with notify_api.test_client() as client: - endpoint = url_for( - 'notifications-statistics.get_notification_statistics_for_service_seven_day_aggregate', - service_id=sample_service.id, - week_count=-1) - auth_header = create_authorization_header( - service_id=sample_service.id) - - resp = client.get(endpoint, headers=[auth_header]) - assert resp.status_code == 400 - json_resp = json.loads(resp.get_data(as_text=True)) - assert json_resp['result'] == 'error' - assert json_resp['message']['week_count'][0] == 'Not a positive integer' - - -@freeze_time('2016-01-01') -def test_get_notification_statistics_for_specific_day(notify_api, - notify_db, - notify_db_session, - sample_template): - the_day = date.today() - - sample_notification_statistics = create_sample_notification_statistics( - notify_db, - notify_db_session, - day=the_day) - - with notify_api.test_request_context(): - with notify_api.test_client() as client: - path = '/service/{}/notifications-statistics/day/{}'.format(sample_template.service_id, the_day) - auth_header = create_authorization_header(service_id=sample_template.service_id) - response = client.get(path, headers=[auth_header]) - assert response.status_code == 200 - stats = json.loads(response.get_data(as_text=True)) - - assert stats['data']['id'] == str(sample_notification_statistics.id) - assert stats['data']['day'] == the_day.strftime('%Y-%m-%d') - - another_day = the_day - timedelta(days=1) - path = '/service/{}/notifications-statistics/day/{}'.format(sample_template.service_id, another_day) - - response = client.get(path, headers=[auth_header]) - assert response.status_code == 404 - - -@freeze_time('2016-01-01') -def test_get_notification_statistics_for_specific_day_returns_404_if_no_stats(notify_api, - notify_db, - notify_db_session, - sample_template): - the_day = date.today() - - with notify_api.test_request_context(): - with notify_api.test_client() as client: - path = '/service/{}/notifications-statistics/day/{}'.format(sample_template.service_id, the_day) - auth_header = create_authorization_header(service_id=sample_template.service_id) - response = client.get(path, headers=[auth_header]) - assert response.status_code == 404 - - -@freeze_time('2016-01-01') -def test_get_notification_statistics_for_specific_day_returns_400_for_incorrect_date(notify_api, - notify_db, - notify_db_session, - sample_template): - the_day = date.today() - incorrect_date_format = the_day.strftime('%d-%m-%Y') - - create_sample_notification_statistics( - notify_db, - notify_db_session, - day=the_day) - - with notify_api.test_request_context(): - with notify_api.test_client() as client: - path = '/service/{}/notifications-statistics/day/{}'.format( - sample_template.service_id, - incorrect_date_format) - auth_header = create_authorization_header(service_id=sample_template.service_id) - response = client.get(path, headers=[auth_header]) - assert response.status_code == 400 - resp_json = json.loads(response.get_data(as_text=True)) - assert resp_json['result'] == 'error' - assert resp_json['message'] == 'Invalid date 01-01-2016' - - another_dodgy_date = 'fish' - path = '/service/{}/notifications-statistics/day/{}'.format( - sample_template.service_id, - another_dodgy_date) - - response = client.get(path, headers=[auth_header]) - assert response.status_code == 400 - resp_json = json.loads(response.get_data(as_text=True)) - assert resp_json['result'] == 'error' - assert resp_json['message'] == 'Invalid date fish' diff --git a/tests/app/notifications/test_rest.py b/tests/app/notifications/test_rest.py index df611adca..778bbe66e 100644 --- a/tests/app/notifications/test_rest.py +++ b/tests/app/notifications/test_rest.py @@ -207,7 +207,7 @@ def test_normal_api_key_returns_notifications_created_from_jobs_and_from_api( notifications = json.loads(response.get_data(as_text=True))['notifications'] assert len(notifications) == 2 - assert set(x['id'] for x in notifications) == set([str(sample_notification.id), str(api_notification.id)]) + assert set(x['id'] for x in notifications) == {str(sample_notification.id), str(api_notification.id)} @pytest.mark.parametrize('key_type', [KEY_TYPE_NORMAL, KEY_TYPE_TEAM, KEY_TYPE_TEST]) diff --git a/tests/app/service/test_rest.py b/tests/app/service/test_rest.py index ff99c1654..ba6587a61 100644 --- a/tests/app/service/test_rest.py +++ b/tests/app/service/test_rest.py @@ -1,3 +1,4 @@ +from datetime import datetime import json import uuid @@ -263,7 +264,7 @@ def test_should_not_create_service_with_missing_if_user_id_is_not_in_database(no json_resp = json.loads(resp.get_data(as_text=True)) assert resp.status_code == 404 assert json_resp['result'] == 'error' - assert 'No result found' == json_resp['message'] + assert json_resp['message'] == 'No result found' def test_should_not_create_service_if_missing_data(notify_api, sample_user): @@ -288,8 +289,6 @@ def test_should_not_create_service_if_missing_data(notify_api, sample_user): def test_should_not_create_service_with_duplicate_name(notify_api, - notify_db, - notify_db_session, sample_user, sample_service): with notify_api.test_request_context(): @@ -314,8 +313,6 @@ def test_should_not_create_service_with_duplicate_name(notify_api, def test_create_service_should_throw_duplicate_key_constraint_for_existing_email_from(notify_api, - notify_db, - notify_db_session, service_factory, sample_user): first_service = service_factory.get('First service', email_from='first.service') @@ -432,7 +429,7 @@ def test_update_service_research_mode_throws_validation_error(notify_api, sample headers=[('Content-Type', 'application/json'), auth_header] ) result = json.loads(resp.get_data(as_text=True)) - result['message']['research_mode'][0] == "Not a valid boolean." + assert result['message']['research_mode'][0] == "Not a valid boolean." assert resp.status_code == 400 @@ -505,7 +502,7 @@ def test_should_not_update_service_with_duplicate_email_from(notify_api, ) -def test_update_service_should_404_if_id_is_invalid(notify_api, notify_db, notify_db_session): +def test_update_service_should_404_if_id_is_invalid(notify_api): with notify_api.test_request_context(): with notify_api.test_client() as client: data = { @@ -524,7 +521,7 @@ def test_update_service_should_404_if_id_is_invalid(notify_api, notify_db, notif assert resp.status_code == 404 -def test_get_users_by_service(notify_api, notify_db, notify_db_session, sample_service): +def test_get_users_by_service(notify_api, sample_service): with notify_api.test_request_context(): with notify_api.test_client() as client: user_on_service = sample_service.users[0] @@ -544,8 +541,6 @@ def test_get_users_by_service(notify_api, notify_db, notify_db_session, sample_s def test_get_users_for_service_returns_empty_list_if_no_users_associated_with_service(notify_api, - notify_db, - notify_db_session, sample_service): with notify_api.test_request_context(): with notify_api.test_client() as client: @@ -1110,8 +1105,7 @@ def test_set_sms_sender_for_service_rejects_invalid_characters(notify_api, sampl 'delivered': 0, 'failed': 0 }) -], ids=['seven_days', 'today'] -) +], ids=['seven_days', 'today']) def test_get_detailed_service(notify_db, notify_db_session, notify_api, sample_service, today_only, stats): with notify_api.test_request_context(), notify_api.test_client() as client: with freeze_time('2000-01-01T12:00:00'): @@ -1156,3 +1150,94 @@ def test_get_weekly_notification_stats(notify_api, notify_db, notify_db_session) } } } + + +def test_get_services_with_detailed_flag(notify_api, notify_db, notify_db_session): + notifications = [ + create_sample_notification(notify_db, notify_db_session), + create_sample_notification(notify_db, notify_db_session) + ] + with notify_api.test_request_context(), notify_api.test_client() as client: + resp = client.get( + '/service?detailed=True', + headers=[create_authorization_header()] + ) + + assert resp.status_code == 200 + data = json.loads(resp.get_data(as_text=True))['data'] + assert len(data) == 1 + assert data[0]['name'] == 'Sample service' + assert data[0]['id'] == str(notifications[0].service_id) + assert data[0]['statistics'] == { + 'email': {'delivered': 0, 'failed': 0, 'requested': 0}, + 'sms': {'delivered': 0, 'failed': 0, 'requested': 2} + } + + +def test_get_detailed_services_groups_by_service(notify_db, notify_db_session): + from app.service.rest import get_detailed_services + + service_1 = create_sample_service(notify_db, notify_db_session, service_name="1", email_from='1') + service_2 = create_sample_service(notify_db, notify_db_session, service_name="2", email_from='2') + + create_sample_notification(notify_db, notify_db_session, service=service_1, status='created') + create_sample_notification(notify_db, notify_db_session, service=service_2, status='created') + create_sample_notification(notify_db, notify_db_session, service=service_1, status='delivered') + create_sample_notification(notify_db, notify_db_session, service=service_1, status='created') + + data = get_detailed_services() + data = sorted(data, key=lambda x: x['name']) + + assert len(data) == 2 + assert data[0]['id'] == str(service_1.id) + assert data[0]['statistics'] == { + 'email': {'delivered': 0, 'failed': 0, 'requested': 0}, + 'sms': {'delivered': 1, 'failed': 0, 'requested': 3} + } + assert data[1]['id'] == str(service_2.id) + assert data[1]['statistics'] == { + 'email': {'delivered': 0, 'failed': 0, 'requested': 0}, + 'sms': {'delivered': 0, 'failed': 0, 'requested': 1} + } + + +def test_get_detailed_services_includes_services_with_no_notifications(notify_db, notify_db_session): + from app.service.rest import get_detailed_services + + service_1 = create_sample_service(notify_db, notify_db_session, service_name="1", email_from='1') + service_2 = create_sample_service(notify_db, notify_db_session, service_name="2", email_from='2') + + create_sample_notification(notify_db, notify_db_session, service=service_1) + + data = get_detailed_services() + data = sorted(data, key=lambda x: x['name']) + + assert len(data) == 2 + assert data[0]['id'] == str(service_1.id) + assert data[0]['statistics'] == { + 'email': {'delivered': 0, 'failed': 0, 'requested': 0}, + 'sms': {'delivered': 0, 'failed': 0, 'requested': 1} + } + assert data[1]['id'] == str(service_2.id) + assert data[1]['statistics'] == { + 'email': {'delivered': 0, 'failed': 0, 'requested': 0}, + 'sms': {'delivered': 0, 'failed': 0, 'requested': 0} + } + + +def test_get_detailed_services_only_includes_todays_notifications(notify_db, notify_db_session): + from app.service.rest import get_detailed_services + + create_sample_notification(notify_db, notify_db_session, created_at=datetime(2015, 10, 9, 23, 59)) + create_sample_notification(notify_db, notify_db_session, created_at=datetime(2015, 10, 10, 0, 0)) + create_sample_notification(notify_db, notify_db_session, created_at=datetime(2015, 10, 10, 12, 0)) + + with freeze_time('2015-10-10T12:00:00'): + data = get_detailed_services() + data = sorted(data, key=lambda x: x['id']) + + assert len(data) == 1 + assert data[0]['statistics'] == { + 'email': {'delivered': 0, 'failed': 0, 'requested': 0}, + 'sms': {'delivered': 0, 'failed': 0, 'requested': 2} + } diff --git a/tests/app/service/test_statistics.py b/tests/app/service/test_statistics.py index a4d8648e0..fcf854da8 100644 --- a/tests/app/service/test_statistics.py +++ b/tests/app/service/test_statistics.py @@ -7,7 +7,7 @@ from freezegun import freeze_time from app.service.statistics import ( format_statistics, _weeks_for_range, - _create_zeroed_stats_dicts, + create_zeroed_stats_dicts, format_weekly_notification_stats ) @@ -63,7 +63,7 @@ def test_weeks_for_range(start, end, dates): def test_create_zeroed_stats_dicts(): - assert _create_zeroed_stats_dicts() == { + assert create_zeroed_stats_dicts() == { 'sms': {'requested': 0, 'delivered': 0, 'failed': 0}, 'email': {'requested': 0, 'delivered': 0, 'failed': 0}, } diff --git a/tests/app/template_statistics/test_rest.py b/tests/app/template_statistics/test_rest.py index 1544874f7..1e85d0165 100644 --- a/tests/app/template_statistics/test_rest.py +++ b/tests/app/template_statistics/test_rest.py @@ -13,7 +13,6 @@ from tests.app.conftest import sample_template as create_sample_template, sample def test_get_all_template_statistics_with_bad_arg_returns_400(notify_api, sample_service): with notify_api.test_request_context(): with notify_api.test_client() as client: - auth_header = create_authorization_header() response = client.get( @@ -40,7 +39,6 @@ def test_get_template_statistics_for_service(notify_db, notify_db_session, notif with notify_api.test_request_context(): with notify_api.test_client() as client: - auth_header = create_authorization_header() response = client.get( @@ -77,7 +75,6 @@ def test_get_template_statistics_for_service_limited_by_day(notify_db, notify_db with notify_api.test_request_context(): with notify_api.test_client() as client: - auth_header = create_authorization_header() response = client.get( @@ -144,7 +141,6 @@ def test_get_template_statistics_for_service_limited_by_day(notify_db, notify_db def test_returns_empty_list_if_no_templates_used(notify_api, sample_service): with notify_api.test_request_context(): with notify_api.test_client() as client: - auth_header = create_authorization_header() response = client.get( @@ -157,62 +153,47 @@ def test_returns_empty_list_if_no_templates_used(notify_api, sample_service): assert len(json_resp['data']) == 0 -def test_get_template_statistics_for_template_only_returns_for_provided_template( +def test_get_template_statistics_by_id_returns_last_notification( notify_db, notify_db_session, notify_api, - sample_service -): - template_1 = create_sample_template( + sample_service): + + template = create_sample_template( notify_db, notify_db_session, template_name='Sample Template 1', service=sample_service ) - template_2 = create_sample_template( + + notification_1 = sample_notification( notify_db, notify_db_session, - template_name='Sample Template 2', - service=sample_service - ) - - template_1_stats_1 = TemplateStatistics( - template_id=template_1.id, - service_id=sample_service.id, - day=datetime(2001, 1, 1) - ) - template_1_stats_2 = TemplateStatistics( - template_id=template_1.id, - service_id=sample_service.id, - day=datetime(2001, 1, 2) - ) - template_2_stats = TemplateStatistics( - template_id=template_2.id, - service_id=sample_service.id, - day=datetime(2001, 1, 1) - ) - - # separate commit to ensure stats_1 has earlier updated_at time - db.session.add(template_1_stats_1) - db.session.commit() - - db.session.add_all([template_1_stats_2, template_2_stats]) - db.session.commit() + service=sample_service, + template=template) + notification_2 = sample_notification( + notify_db, + notify_db_session, + service=sample_service, + template=template) + notification_3 = sample_notification( + notify_db, + notify_db_session, + service=sample_service, + template=template) with notify_api.test_request_context(): with notify_api.test_client() as client: auth_header = create_authorization_header() response = client.get( - '/service/{}/template-statistics/{}'.format(sample_service.id, template_1.id), + '/service/{}/template-statistics/{}'.format(sample_service.id, template.id), headers=[('Content-Type', 'application/json'), auth_header], ) assert response.status_code == 200 - json_resp = json.loads(response.get_data(as_text=True)) - assert len(json_resp['data']) == 2 - assert json_resp['data'][0]['id'] == str(template_1_stats_2.id) - assert json_resp['data'][1]['id'] == str(template_1_stats_1.id) + json_resp = json.loads(response.get_data(as_text=True))['data'] + assert json_resp['id'] == str(notification_3.id) def test_get_template_statistics_for_template_returns_empty_if_no_statistics( @@ -221,36 +202,23 @@ def test_get_template_statistics_for_template_returns_empty_if_no_statistics( notify_api, sample_service ): - template_1 = create_sample_template( + template = create_sample_template( notify_db, notify_db_session, template_name='Sample Template 1', service=sample_service ) - template_2 = create_sample_template( - notify_db, - notify_db_session, - template_name='Sample Template 2', - service=sample_service - ) - - template_1_stats = TemplateStatistics( - template_id=template_1.id, - service_id=sample_service.id, - day=datetime(2001, 1, 1) - ) - db.session.add(template_1_stats) - db.session.commit() with notify_api.test_request_context(): with notify_api.test_client() as client: auth_header = create_authorization_header() response = client.get( - '/service/{}/template-statistics/{}'.format(sample_service.id, template_2.id), + '/service/{}/template-statistics/{}'.format(sample_service.id, template.id), headers=[('Content-Type', 'application/json'), auth_header], ) - assert response.status_code == 200 + assert response.status_code == 404 json_resp = json.loads(response.get_data(as_text=True)) - assert json_resp['data'] == [] + assert json_resp['result'] == 'error' + assert json_resp['message']['template_id'] == ['No template found for id {}'.format(template.id)]