diff --git a/app/__init__.py b/app/__init__.py index ce2c177ca..5f5bb4680 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -125,27 +125,24 @@ basic_auth = CustomBasicAuth() # The current service attached to the request stack. -current_service = LocalProxy(partial(getattr, request_ctx, 'service')) +current_service = LocalProxy(partial(getattr, request_ctx, "service")) # The current organization attached to the request stack. -current_organization = LocalProxy(partial(getattr, request_ctx, 'organization')) +current_organization = LocalProxy(partial(getattr, request_ctx, "organization")) navigation = { - 'casework_navigation': CaseworkNavigation(), - 'main_navigation': MainNavigation(), - 'header_navigation': HeaderNavigation(), - 'org_navigation': OrgNavigation(), + "casework_navigation": CaseworkNavigation(), + "main_navigation": MainNavigation(), + "header_navigation": HeaderNavigation(), + "org_navigation": OrgNavigation(), } def _csp(config): - asset_domain = config['ASSET_DOMAIN'] - logo_domain = config['LOGO_CDN_DOMAIN'] + asset_domain = config["ASSET_DOMAIN"] + logo_domain = config["LOGO_CDN_DOMAIN"] return { - "default-src": [ - "'self'", - asset_domain - ], + "default-src": ["'self'", asset_domain], "frame-ancestors": "'none'", "form-action": "'self'", "script-src": [ @@ -155,32 +152,22 @@ def _csp(config): "https://js-agent.newrelic.com", "https://gov-bam.nr-data.net", ], - "connect-src": [ - "'self'", - "https://gov-bam.nr-data.net" - ], - "style-src": [ - "'self'", - asset_domain - ], - "img-src": [ - "'self'", - asset_domain, - logo_domain - ] + "connect-src": ["'self'", "https://gov-bam.nr-data.net"], + "style-src": ["'self'", asset_domain], + "img-src": ["'self'", asset_domain, logo_domain], } def create_app(application): - notify_environment = os.environ['NOTIFY_ENVIRONMENT'] + notify_environment = os.environ["NOTIFY_ENVIRONMENT"] application.config.from_object(configs[notify_environment]) - asset_fingerprinter._asset_root = application.config['ASSET_PATH'] + asset_fingerprinter._asset_root = application.config["ASSET_PATH"] init_app(application) - if 'extensions' not in application.jinja_options: - application.jinja_options['extensions'] = [] + if "extensions" not in application.jinja_options: + application.jinja_options["extensions"] = [] init_govuk_frontend(application) init_jinja(application) @@ -190,7 +177,6 @@ def create_app(application): login_manager, proxy_fix, request_helper, - # API clients api_key_api_client, billing_api_client, @@ -212,41 +198,39 @@ def create_app(application): template_statistics_client, upload_api_client, user_api_client, - # External API clients redis_client, zendesk_client, - ): client.init_app(application) talisman.init_app( application, content_security_policy=_csp(application.config), - content_security_policy_nonce_in=['style-src', 'script-src'], + content_security_policy_nonce_in=["style-src", "script-src"], permissions_policy={ - 'accelerometer': '()', - 'ambient-light-sensor': '()', - 'autoplay': '()', - 'battery': '()', - 'camera': '()', - 'document-domain': '()', - 'geolocation': '()', - 'gyroscope': '()', - 'local-fonts': '()', - 'magnetometer': '()', - 'microphone': '()', - 'midi': '()', - 'payment': '()', - 'screen-wake-lock': '()' + "accelerometer": "()", + "ambient-light-sensor": "()", + "autoplay": "()", + "battery": "()", + "camera": "()", + "document-domain": "()", + "geolocation": "()", + "gyroscope": "()", + "local-fonts": "()", + "magnetometer": "()", + "microphone": "()", + "midi": "()", + "payment": "()", + "screen-wake-lock": "()", }, - frame_options='deny', - force_https=(application.config['HTTP_PROTOCOL'] == 'https') + frame_options="deny", + force_https=(application.config["HTTP_PROTOCOL"] == "https"), ) logging.init_app(application) - login_manager.login_view = 'main.sign_in' - login_manager.login_message_category = 'default' + login_manager.login_view = "main.sign_in" + login_manager.login_message_category = "default" login_manager.session_protection = None login_manager.anonymous_user = AnonymousUser @@ -276,22 +260,25 @@ def init_app(application): application.before_request(make_session_permanent) application.after_request(save_service_or_org_after_request) + start = len(asset_fingerprinter._filesystem_path) font_paths = [ - str(item)[len(asset_fingerprinter._filesystem_path):] - for item in pathlib.Path(asset_fingerprinter._filesystem_path).glob('fonts/*.woff2') + str(item)[start:] + for item in pathlib.Path(asset_fingerprinter._filesystem_path).glob( + "fonts/*.woff2" + ) ] @application.context_processor def _attach_current_service(): - return {'current_service': current_service} + return {"current_service": current_service} @application.context_processor def _attach_current_organization(): - return {'current_org': current_organization} + return {"current_org": current_organization} @application.context_processor def _attach_current_user(): - return {'current_user': current_user} + return {"current_user": current_user} @application.context_processor def _nav_selected(): @@ -302,10 +289,10 @@ def init_app(application): remaining_global_messages = 0 if current_app: - global_limit = current_app.config['GLOBAL_SERVICE_MESSAGE_LIMIT'] + global_limit = current_app.config["GLOBAL_SERVICE_MESSAGE_LIMIT"] global_messages_count = service_api_client.get_global_notification_count() remaining_global_messages = global_limit - global_messages_count - return {'daily_global_messages_remaining': remaining_global_messages} + return {"daily_global_messages_remaining": remaining_global_messages} @application.before_request def record_start_time(): @@ -315,16 +302,16 @@ def init_app(application): @application.context_processor def inject_global_template_variables(): return { - 'asset_path': application.config['ASSET_PATH'], - 'header_colour': application.config['HEADER_COLOUR'], - 'asset_url': asset_fingerprinter.get_url, - 'font_paths': font_paths, + "asset_path": application.config["ASSET_PATH"], + "header_colour": application.config["HEADER_COLOUR"], + "asset_url": asset_fingerprinter.get_url, + "font_paths": font_paths, } - application.url_map.converters['uuid'].to_python = lambda self, value: value - application.url_map.converters['template_type'] = TemplateTypeConverter - application.url_map.converters['ticket_type'] = TicketTypeConverter - application.url_map.converters['simple_date'] = SimpleDateTypeConverter + application.url_map.converters["uuid"].to_python = lambda self, value: value + application.url_map.converters["template_type"] = TemplateTypeConverter + application.url_map.converters["ticket_type"] = TicketTypeConverter + application.url_map.converters["simple_date"] = SimpleDateTypeConverter @login_manager.user_loader @@ -346,21 +333,21 @@ def make_session_permanent(): def load_service_before_request(): - if '/static/' in request.url: + if "/static/" in request.url: request_ctx.service = None return if request_ctx is not None: request_ctx.service = None if request.view_args: - service_id = request.view_args.get('service_id', session.get('service_id')) + service_id = request.view_args.get("service_id", session.get("service_id")) else: - service_id = session.get('service_id') + service_id = session.get("service_id") if service_id: try: request_ctx.service = Service( - service_api_client.get_service(service_id)['data'] + service_api_client.get_service(service_id)["data"] ) except HTTPError as exc: # if service id isn't real, then 404 rather than 500ing later because we expect service to be set @@ -371,14 +358,14 @@ def load_service_before_request(): def load_organization_before_request(): - if '/static/' in request.url: + if "/static/" in request.url: request_ctx.organization = None return if request_ctx is not None: request_ctx.organization = None if request.view_args: - org_id = request.view_args.get('org_id') + org_id = request.view_args.get("org_id") if org_id: try: @@ -393,15 +380,19 @@ def load_organization_before_request(): def save_service_or_org_after_request(response): # Only save the current session if the request is 200 - service_id = request.view_args.get('service_id', None) if request.view_args else None - organization_id = request.view_args.get('org_id', None) if request.view_args else None + service_id = ( + request.view_args.get("service_id", None) if request.view_args else None + ) + organization_id = ( + request.view_args.get("org_id", None) if request.view_args else None + ) if response.status_code == 200: if service_id: - session['service_id'] = service_id - session['organization_id'] = None + session["service_id"] = service_id + session["organization_id"] = None elif organization_id: - session['service_id'] = None - session['organization_id'] = organization_id + session["service_id"] = None + session["organization_id"] = organization_id return response @@ -409,32 +400,38 @@ def register_errorhandlers(application): # noqa (C901 too complex) def _error_response(error_code, error_page_template=None): if error_page_template is None: error_page_template = error_code - return make_response(render_template("error/{0}.html".format(error_page_template)), error_code) + return make_response( + render_template("error/{0}.html".format(error_page_template)), error_code + ) @application.errorhandler(HTTPError) def render_http_error(error): - application.logger.warning("API {} failed with status {} message {}".format( - error.response.url if error.response else 'unknown', - error.status_code, - error.message - )) + application.logger.warning( + "API {} failed with status {} message {}".format( + error.response.url if error.response else "unknown", + error.status_code, + error.message, + ) + ) error_code = error.status_code if error_code not in [401, 404, 403, 410]: # probably a 500 or 503. # it might be a 400, which we should handle as if it's an internal server error. If the API might # legitimately return a 400, we should handle that within the view or the client that calls it. - application.logger.exception("API {} failed with status {} message {}".format( - error.response.url if error.response else 'unknown', - error.status_code, - error.message - )) + application.logger.exception( + "API {} failed with status {} message {}".format( + error.response.url if error.response else "unknown", + error.status_code, + error.message, + ) + ) error_code = 500 return _error_response(error_code) @application.errorhandler(400) def handle_client_error(error): # This is tripped if we call `abort(400)`. - application.logger.exception('Unhandled 400 client error') + application.logger.exception("Unhandled 400 client error") return _error_response(400, error_page_template=500) @application.errorhandler(410) @@ -456,23 +453,24 @@ def register_errorhandlers(application): # noqa (C901 too complex) @application.errorhandler(BadSignature) def handle_bad_token(error): # if someone has a malformed token - flash('There’s something wrong with the link you’ve used.') + flash("There’s something wrong with the link you’ve used.") return _error_response(404) @application.errorhandler(CSRFError) def handle_csrf(reason): - application.logger.warning('csrf.error_message: {}'.format(reason)) + application.logger.warning("csrf.error_message: {}".format(reason)) - if 'user_id' not in session: + if "user_id" not in session: application.logger.warning( - u'csrf.session_expired: Redirecting user to log in page' + "csrf.session_expired: Redirecting user to log in page" ) return application.login_manager.unauthorized() application.logger.warning( - u'csrf.invalid_token: Aborting request, user_id: {user_id}', - extra={'user_id': session['user_id']}) + "csrf.invalid_token: Aborting request, user_id: {user_id}", + extra={"user_id": session["user_id"]}, + ) return _error_response(400, error_page_template=500) @@ -491,14 +489,14 @@ def register_errorhandlers(application): # noqa (C901 too complex) @application.errorhandler(InviteTokenError) def handle_bad_invite_token(error): flash(str(error)) - return redirect(url_for('main.sign_in')) + return redirect(url_for("main.sign_in")) @application.errorhandler(500) @application.errorhandler(Exception) def handle_bad_request(error): current_app.logger.exception(error) # We want the Flask in browser stacktrace - if current_app.config.get('DEBUG', None): + if current_app.config.get("DEBUG", None): raise error return _error_response(500) @@ -561,9 +559,9 @@ def add_template_filters(application): def init_jinja(application): - repo_root = os.path.abspath(os.path.join(os.path.dirname(__file__), '..')) + repo_root = os.path.abspath(os.path.join(os.path.dirname(__file__), "..")) template_folders = [ - os.path.join(repo_root, 'app/templates'), + os.path.join(repo_root, "app/templates"), ] jinja_loader = jinja2.FileSystemLoader(template_folders) application.jinja_loader = jinja_loader diff --git a/app/asset_fingerprinter.py b/app/asset_fingerprinter.py index fc4b60ad1..3a4298d9f 100644 --- a/app/asset_fingerprinter.py +++ b/app/asset_fingerprinter.py @@ -3,23 +3,23 @@ import hashlib class AssetFingerprinter(object): """ - Get a unique hash for an asset file, so that it doesn't stay cached - when it changes + Get a unique hash for an asset file, so that it doesn't stay cached + when it changes - Usage: + Usage: - in the application - template_data.asset_fingerprinter = AssetFingerprinter() + in the application + template_data.asset_fingerprinter = AssetFingerprinter() - where template data is how you pass variables to every template. + where template data is how you pass variables to every template. - in template.html: - {{ asset_fingerprinter.get_url('stylesheets/application.css') }} + in template.html: + {{ asset_fingerprinter.get_url('stylesheets/application.css') }} - * 'app/static' is assumed to be the root for all asset files + * 'app/static' is assumed to be the root for all asset files """ - def __init__(self, asset_root='/static/', filesystem_path='app/static/'): + def __init__(self, asset_root="/static/", filesystem_path="app/static/"): self._cache = {} self._asset_root = asset_root self._filesystem_path = filesystem_path @@ -29,10 +29,10 @@ class AssetFingerprinter(object): return self._asset_root + asset_path if asset_path not in self._cache: self._cache[asset_path] = ( - self._asset_root + - asset_path + - '?' + - self.get_asset_fingerprint(self._filesystem_path + asset_path) + self._asset_root + + asset_path + + "?" + + self.get_asset_fingerprint(self._filesystem_path + asset_path) ) return self._cache[asset_path] @@ -42,7 +42,7 @@ class AssetFingerprinter(object): ).hexdigest() def get_asset_file_contents(self, asset_file_path): - with open(asset_file_path, 'rb') as asset_file: + with open(asset_file_path, "rb") as asset_file: contents = asset_file.read() return contents diff --git a/app/cloudfoundry_config.py b/app/cloudfoundry_config.py index 1218e8852..b79d831c2 100644 --- a/app/cloudfoundry_config.py +++ b/app/cloudfoundry_config.py @@ -4,25 +4,24 @@ import os class CloudfoundryConfig: def __init__(self): - self.parsed_services = json.loads(os.environ.get('VCAP_SERVICES') or '{}') - buckets = self.parsed_services.get('s3') or [] - self.s3_buckets = {bucket['name']: bucket['credentials'] for bucket in buckets} + self.parsed_services = json.loads(os.environ.get("VCAP_SERVICES") or "{}") + buckets = self.parsed_services.get("s3") or [] + self.s3_buckets = {bucket["name"]: bucket["credentials"] for bucket in buckets} self._empty_bucket_credentials = { - 'bucket': '', - 'access_key_id': '', - 'secret_access_key': '', - 'region': '' + "bucket": "", + "access_key_id": "", + "secret_access_key": "", + "region": "", } @property def redis_url(self): try: - return self.parsed_services['aws-elasticache-redis'][0]['credentials']['uri'].replace( - 'redis://', - 'rediss://' - ) + return self.parsed_services["aws-elasticache-redis"][0]["credentials"][ + "uri" + ].replace("redis://", "rediss://") except KeyError: - return os.environ.get('REDIS_URL') + return os.environ.get("REDIS_URL") def s3_credentials(self, service_name): return self.s3_buckets.get(service_name) or self._empty_bucket_credentials diff --git a/app/config.py b/app/config.py index 568c51be7..a1f968359 100644 --- a/app/config.py +++ b/app/config.py @@ -8,40 +8,46 @@ from app.cloudfoundry_config import cloud_config class Config(object): - NOTIFY_APP_NAME = 'admin' - NOTIFY_ENVIRONMENT = getenv('NOTIFY_ENVIRONMENT', 'development') - API_HOST_NAME = getenv('API_HOST_NAME', 'localhost') - ADMIN_BASE_URL = getenv('ADMIN_BASE_URL', 'http://localhost:6012') - HEADER_COLOUR = '#81878b' # mix(govuk-colour("dark-grey"), govuk-colour("mid-grey")) - LOGO_CDN_DOMAIN = 'static-logos.notifications.service.gov.uk' # TODO use our own CDN + NOTIFY_APP_NAME = "admin" + NOTIFY_ENVIRONMENT = getenv("NOTIFY_ENVIRONMENT", "development") + API_HOST_NAME = getenv("API_HOST_NAME", "localhost") + ADMIN_BASE_URL = getenv("ADMIN_BASE_URL", "http://localhost:6012") + HEADER_COLOUR = ( + "#81878b" # mix(govuk-colour("dark-grey"), govuk-colour("mid-grey")) + ) + LOGO_CDN_DOMAIN = ( + "static-logos.notifications.service.gov.uk" # TODO use our own CDN + ) ASSETS_DEBUG = False # Credentials - ADMIN_CLIENT_SECRET = getenv('ADMIN_CLIENT_SECRET') - ADMIN_CLIENT_USER_NAME = getenv('ADMIN_CLIENT_USERNAME') - SECRET_KEY = getenv('SECRET_KEY') - DANGEROUS_SALT = getenv('DANGEROUS_SALT') + ADMIN_CLIENT_SECRET = getenv("ADMIN_CLIENT_SECRET") + ADMIN_CLIENT_USER_NAME = getenv("ADMIN_CLIENT_USERNAME") + SECRET_KEY = getenv("SECRET_KEY") + DANGEROUS_SALT = getenv("DANGEROUS_SALT") # ZENDESK_API_KEY = getenv('ZENDESK_API_KEY') - ROUTE_SECRET_KEY_1 = getenv('ROUTE_SECRET_KEY_1', 'dev-route-secret-key-1') - ROUTE_SECRET_KEY_2 = getenv('ROUTE_SECRET_KEY_2', 'dev-route-secret-key-2') - BASIC_AUTH_USERNAME = getenv('BASIC_AUTH_USERNAME') - BASIC_AUTH_PASSWORD = getenv('BASIC_AUTH_PASSWORD') + ROUTE_SECRET_KEY_1 = getenv("ROUTE_SECRET_KEY_1", "dev-route-secret-key-1") + ROUTE_SECRET_KEY_2 = getenv("ROUTE_SECRET_KEY_2", "dev-route-secret-key-2") + BASIC_AUTH_USERNAME = getenv("BASIC_AUTH_USERNAME") + BASIC_AUTH_PASSWORD = getenv("BASIC_AUTH_PASSWORD") - NR_ACCOUNT_ID = getenv('NR_ACCOUNT_ID') - NR_TRUST_KEY = getenv('NR_TRUST_KEY') - NR_AGENT_ID = getenv('NR_AGENT_ID') - NR_APP_ID = getenv('NR_APP_ID') - NR_BROWSER_KEY = getenv('NR_BROWSER_KEY') + NR_ACCOUNT_ID = getenv("NR_ACCOUNT_ID") + NR_TRUST_KEY = getenv("NR_TRUST_KEY") + NR_AGENT_ID = getenv("NR_AGENT_ID") + NR_APP_ID = getenv("NR_APP_ID") + NR_BROWSER_KEY = getenv("NR_BROWSER_KEY") settings = newrelic.agent.global_settings() NR_MONITOR_ON = settings and settings.monitor_mode - TEMPLATE_PREVIEW_API_HOST = getenv('TEMPLATE_PREVIEW_API_HOST', 'http://localhost:9999') - TEMPLATE_PREVIEW_API_KEY = getenv('TEMPLATE_PREVIEW_API_KEY', 'my-secret-key') + TEMPLATE_PREVIEW_API_HOST = getenv( + "TEMPLATE_PREVIEW_API_HOST", "http://localhost:9999" + ) + TEMPLATE_PREVIEW_API_KEY = getenv("TEMPLATE_PREVIEW_API_KEY", "my-secret-key") - GOVERNMENT_EMAIL_DOMAIN_NAMES = ['gov'] + GOVERNMENT_EMAIL_DOMAIN_NAMES = ["gov"] # Logging - NOTIFY_LOG_LEVEL = getenv('NOTIFY_LOG_LEVEL', 'INFO') + NOTIFY_LOG_LEVEL = getenv("NOTIFY_LOG_LEVEL", "INFO") DEFAULT_SERVICE_LIMIT = 50 @@ -55,7 +61,7 @@ class Config(object): REPLY_TO_EMAIL_ADDRESS_VALIDATION_TIMEOUT = 45 ACTIVITY_STATS_LIMIT_DAYS = 7 SESSION_COOKIE_HTTPONLY = True - SESSION_COOKIE_NAME = 'notify_admin_session' + SESSION_COOKIE_NAME = "notify_admin_session" SESSION_COOKIE_SECURE = True # don't send back the cookie if it hasn't been modified by the request. this means that the expiry time won't be # updated unless the session is changed - but it's generally refreshed by `save_service_or_org_after_request` @@ -66,31 +72,31 @@ class Config(object): CHECK_PROXY_HEADER = False REDIS_URL = cloud_config.redis_url - REDIS_ENABLED = getenv('REDIS_ENABLED', '1') == '1' + REDIS_ENABLED = getenv("REDIS_ENABLED", "1") == "1" # TODO: reassign this - NOTIFY_SERVICE_ID = 'd6aa2c68-a2d9-4437-ab19-3ae8eb202553' + NOTIFY_SERVICE_ID = "d6aa2c68-a2d9-4437-ab19-3ae8eb202553" - NOTIFY_BILLING_DETAILS = json.loads( - getenv('NOTIFY_BILLING_DETAILS') or 'null' - ) or { - 'account_number': '98765432', - 'sort_code': '01-23-45', - 'IBAN': 'GB33BUKB20201555555555', - 'swift': 'ABCDEF12', - 'notify_billing_email_addresses': [ - 'generic@digital.cabinet-office.gov.uk', - 'first.last@digital.cabinet-office.gov.uk', - ] + NOTIFY_BILLING_DETAILS = json.loads(getenv("NOTIFY_BILLING_DETAILS") or "null") or { + "account_number": "98765432", + "sort_code": "01-23-45", + "IBAN": "GB33BUKB20201555555555", + "swift": "ABCDEF12", + "notify_billing_email_addresses": [ + "generic@digital.cabinet-office.gov.uk", + "first.last@digital.cabinet-office.gov.uk", + ], } def _s3_credentials_from_env(bucket_prefix): return { - 'bucket': getenv(f"{bucket_prefix}_BUCKET_NAME", f"{bucket_prefix}-test-bucket-name"), - 'access_key_id': getenv(f"{bucket_prefix}_AWS_ACCESS_KEY_ID"), - 'secret_access_key': getenv(f"{bucket_prefix}_AWS_SECRET_ACCESS_KEY"), - 'region': getenv(f"{bucket_prefix}_AWS_REGION") + "bucket": getenv( + f"{bucket_prefix}_BUCKET_NAME", f"{bucket_prefix}-test-bucket-name" + ), + "access_key_id": getenv(f"{bucket_prefix}_AWS_ACCESS_KEY_ID"), + "secret_access_key": getenv(f"{bucket_prefix}_AWS_SECRET_ACCESS_KEY"), + "region": getenv(f"{bucket_prefix}_AWS_REGION"), } @@ -99,77 +105,83 @@ class Development(Config): DEBUG = True SESSION_COOKIE_SECURE = False SESSION_PROTECTION = None - HTTP_PROTOCOL = 'http' - ASSET_DOMAIN = '' - ASSET_PATH = '/static/' - NOTIFY_LOG_LEVEL = 'DEBUG' + HTTP_PROTOCOL = "http" + ASSET_DOMAIN = "" + ASSET_PATH = "/static/" + NOTIFY_LOG_LEVEL = "DEBUG" # Buckets - CSV_UPLOAD_BUCKET = _s3_credentials_from_env('CSV') - LOGO_UPLOAD_BUCKET = _s3_credentials_from_env('LOGO') + CSV_UPLOAD_BUCKET = _s3_credentials_from_env("CSV") + LOGO_UPLOAD_BUCKET = _s3_credentials_from_env("LOGO") # credential overrides - DANGEROUS_SALT = 'development-notify-salt' - SECRET_KEY = 'dev-notify-secret-key' # nosec B105 - only used in development + DANGEROUS_SALT = "development-notify-salt" + SECRET_KEY = "dev-notify-secret-key" # nosec B105 - only used in development # ADMIN_CLIENT_USER_NAME is called ADMIN_CLIENT_ID in api repo, they should match - ADMIN_CLIENT_USER_NAME = 'notify-admin' - ADMIN_CLIENT_SECRET = 'dev-notify-secret-key' # nosec B105 - only used in development + ADMIN_CLIENT_USER_NAME = "notify-admin" + ADMIN_CLIENT_SECRET = ( + "dev-notify-secret-key" # nosec B105 - only used in development + ) class Test(Development): TESTING = True WTF_CSRF_ENABLED = False - ASSET_DOMAIN = 'static.example.com' - ASSET_PATH = 'https://static.example.com/' + ASSET_DOMAIN = "static.example.com" + ASSET_PATH = "https://static.example.com/" - API_HOST_NAME = 'http://you-forgot-to-mock-an-api-call-to' - REDIS_URL = 'redis://you-forgot-to-mock-a-redis-call-to' - LOGO_CDN_DOMAIN = 'static-logos.test.com' + API_HOST_NAME = "http://you-forgot-to-mock-an-api-call-to" + REDIS_URL = "redis://you-forgot-to-mock-a-redis-call-to" + LOGO_CDN_DOMAIN = "static-logos.test.com" class Production(Config): - HEADER_COLOUR = '#005EA5' # $govuk-blue - HTTP_PROTOCOL = 'https' + HEADER_COLOUR = "#005EA5" # $govuk-blue + HTTP_PROTOCOL = "https" BASIC_AUTH_FORCE = True - ASSET_DOMAIN = '' # TODO use a CDN - ASSET_PATH = '/static/' # TODO use a CDN + ASSET_DOMAIN = "" # TODO use a CDN + ASSET_PATH = "/static/" # TODO use a CDN DEBUG = False # buckets CSV_UPLOAD_BUCKET = cloud_config.s3_credentials( - f"notify-api-csv-upload-bucket-{getenv('NOTIFY_ENVIRONMENT')}") + f"notify-api-csv-upload-bucket-{getenv('NOTIFY_ENVIRONMENT')}" + ) LOGO_UPLOAD_BUCKET = cloud_config.s3_credentials( - f"notify-admin-logo-upload-bucket-{getenv('NOTIFY_ENVIRONMENT')}") + f"notify-admin-logo-upload-bucket-{getenv('NOTIFY_ENVIRONMENT')}" + ) class Staging(Production): BASIC_AUTH_FORCE = True - HEADER_COLOUR = '#00ff00' # $green + HEADER_COLOUR = "#00ff00" # $green class Demo(Staging): - HEADER_COLOUR = '#6F72AF' # $mauve + HEADER_COLOUR = "#6F72AF" # $mauve class Sandbox(Staging): - HEADER_COLOUR = '#ff0000' # $red + HEADER_COLOUR = "#ff0000" # $red class Scanning(Production): BASIC_AUTH_FORCE = False - HTTP_PROTOCOL = 'http' - API_HOST_NAME = 'https://notify-api-staging.app.cloud.gov/' - SECRET_KEY = 'dev-notify-secret-key' # nosec B105 - only used in development - ADMIN_CLIENT_USER_NAME = 'notify-admin' - ADMIN_CLIENT_SECRET = 'dev-notify-secret-key' # nosec B105 - only used in development + HTTP_PROTOCOL = "http" + API_HOST_NAME = "https://notify-api-staging.app.cloud.gov/" + SECRET_KEY = "dev-notify-secret-key" # nosec B105 - only used in development + ADMIN_CLIENT_USER_NAME = "notify-admin" + ADMIN_CLIENT_SECRET = ( + "dev-notify-secret-key" # nosec B105 - only used in development + ) configs = { - 'development': Development, - 'test': Test, - 'scanning': Scanning, - 'staging': Staging, - 'demo': Demo, - 'sandbox': Sandbox, - 'production': Production + "development": Development, + "test": Test, + "scanning": Scanning, + "staging": Staging, + "demo": Demo, + "sandbox": Sandbox, + "production": Production, } diff --git a/app/custom_auth.py b/app/custom_auth.py index 8e116e113..8f5186b2c 100644 --- a/app/custom_auth.py +++ b/app/custom_auth.py @@ -4,13 +4,13 @@ from flask_basicauth import BasicAuth class CustomBasicAuth(BasicAuth): """ - Description: - Override BasicAuth to permit anonymous healthcheck at /_status?simple=true + Description: + Override BasicAuth to permit anonymous healthcheck at /_status?simple=true """ def challenge(self): if "/_status" in request.url: - if request.args.get('elb', None) or request.args.get('simple', None): + if request.args.get("elb", None) or request.args.get("simple", None): return jsonify(status="ok"), 200 return super(CustomBasicAuth, self).challenge() diff --git a/app/event_handlers.py b/app/event_handlers.py index 4884ab6c8..7e534a76d 100644 --- a/app/event_handlers.py +++ b/app/event_handlers.py @@ -4,13 +4,34 @@ from app.notify_client.events_api_client import events_api_client EVENT_SCHEMAS = { "sucessful_login": {"user_id"}, - "update_user_email": {"user_id", "updated_by_id", "original_email_address", "new_email_address"}, - "update_user_mobile_number": {"user_id", "updated_by_id", "original_mobile_number", "new_mobile_number"}, + "update_user_email": { + "user_id", + "updated_by_id", + "original_email_address", + "new_email_address", + }, + "update_user_mobile_number": { + "user_id", + "updated_by_id", + "original_mobile_number", + "new_mobile_number", + }, "remove_user_from_service": {"user_id", "removed_by_id", "service_id"}, "add_user_to_service": {"user_id", "invited_by_id", "service_id", "ui_permissions"}, - "invite_user_to_service": {"email_address", "invited_by_id", "service_id", "ui_permissions"}, + "invite_user_to_service": { + "email_address", + "invited_by_id", + "service_id", + "ui_permissions", + }, "cancel_user_invite_to_service": {"email_address", "canceled_by_id", "service_id"}, - "set_user_permissions": {"user_id", "service_id", "original_ui_permissions", "new_ui_permissions", "set_by_id"}, + "set_user_permissions": { + "user_id", + "service_id", + "original_ui_permissions", + "new_ui_permissions", + "set_by_id", + }, "archive_user": {"user_id", "archived_by_id"}, "archive_service": {"service_id", "archived_by_id"}, "suspend_service": {"service_id", "suspended_by_id"}, @@ -19,51 +40,51 @@ EVENT_SCHEMAS = { def on_user_logged_in(_sender, user): - _send_event('sucessful_login', user_id=user.id) + _send_event("sucessful_login", user_id=user.id) def create_email_change_event(**kwargs): - _send_event('update_user_email', **kwargs) + _send_event("update_user_email", **kwargs) def create_mobile_number_change_event(**kwargs): - _send_event('update_user_mobile_number', **kwargs) + _send_event("update_user_mobile_number", **kwargs) def create_remove_user_from_service_event(**kwargs): - _send_event('remove_user_from_service', **kwargs) + _send_event("remove_user_from_service", **kwargs) def create_invite_user_to_service_event(**kwargs): - _send_event('invite_user_to_service', **kwargs) + _send_event("invite_user_to_service", **kwargs) def create_cancel_user_invite_to_service_event(**kwargs): - _send_event('cancel_user_invite_to_service', **kwargs) + _send_event("cancel_user_invite_to_service", **kwargs) def create_add_user_to_service_event(**kwargs): - _send_event('add_user_to_service', **kwargs) + _send_event("add_user_to_service", **kwargs) def create_set_user_permissions_event(**kwargs): - _send_event('set_user_permissions', **kwargs) + _send_event("set_user_permissions", **kwargs) def create_archive_user_event(**kwargs): - _send_event('archive_user', **kwargs) + _send_event("archive_user", **kwargs) def create_suspend_service_event(**kwargs): - _send_event('suspend_service', **kwargs) + _send_event("suspend_service", **kwargs) def create_archive_service_event(**kwargs): - _send_event('archive_service', **kwargs) + _send_event("archive_service", **kwargs) def create_resume_service_event(**kwargs): - _send_event('resume_service', **kwargs) + _send_event("resume_service", **kwargs) def _send_event(event_type, **kwargs): @@ -71,7 +92,7 @@ def _send_event(event_type, **kwargs): actual_keys = set(kwargs.keys()) if expected_keys != actual_keys: - raise ValueError(f'Expected {expected_keys}, but got {actual_keys}') + raise ValueError(f"Expected {expected_keys}, but got {actual_keys}") event_data = _construct_event_data(request) event_data.update(kwargs) @@ -80,8 +101,10 @@ def _send_event(event_type, **kwargs): def _construct_event_data(request): - return {'ip_address': _get_remote_addr(request), - 'browser_fingerprint': _get_browser_fingerprint(request)} + return { + "ip_address": _get_remote_addr(request), + "browser_fingerprint": _get_browser_fingerprint(request), + } # This might not be totally correct depending on proxy setup @@ -98,9 +121,11 @@ def _get_browser_fingerprint(request): platform = request.user_agent.platform user_agent_string = request.user_agent.string # at some point this may be hashed? - finger_print = {'browser': browser, - 'platform': platform, - 'version': version, - 'user_agent_string': user_agent_string} + finger_print = { + "browser": browser, + "platform": platform, + "version": version, + "user_agent_string": user_agent_string, + } return finger_print diff --git a/app/formatters.py b/app/formatters.py index 113b374a4..a410aa076 100644 --- a/app/formatters.py +++ b/app/formatters.py @@ -22,23 +22,20 @@ from app.utils.time import parse_naive_dt def convert_to_boolean(value): if isinstance(value, str): - if value.lower() in ['t', 'true', 'on', 'yes', '1']: + if value.lower() in ["t", "true", "on", "yes", "1"]: return True - elif value.lower() in ['f', 'false', 'off', 'no', '0']: + elif value.lower() in ["f", "false", "off", "no", "0"]: return False return value def format_datetime(date): - return '{} at {} UTC'.format( - format_date(date), - format_time_24h(date) - ) + return "{} at {} UTC".format(format_date(date), format_time_24h(date)) def format_datetime_24h(date): - return '{} at {} UTC'.format( + return "{} at {} UTC".format( format_date(date), format_time_24h(date), ) @@ -49,28 +46,19 @@ def format_time(date): def format_datetime_normal(date): - return '{} at {} UTC'.format( - format_date_normal(date), - format_time_24h(date) - ) + return "{} at {} UTC".format(format_date_normal(date), format_time_24h(date)) def format_datetime_short(date): - return '{} at {} UTC'.format( - format_date_short(date), - format_time_24h(date) - ) + return "{} at {} UTC".format(format_date_short(date), format_time_24h(date)) def format_datetime_relative(date): - return '{} at {} UTC'.format( - get_human_day(date), - format_time_24h(date) - ) + return "{} at {} UTC".format(get_human_day(date), format_time_24h(date)) def format_datetime_numeric(date): - return '{} {} UTC'.format( + return "{} {} UTC".format( format_date_numeric(date), format_time_24h(date), ) @@ -78,34 +66,33 @@ def format_datetime_numeric(date): def format_date_numeric(date): date = parse_naive_dt(date) - return date.strftime('%Y-%m-%d') + return date.strftime("%Y-%m-%d") def format_time_24h(date): date = parse_naive_dt(date) - return date.strftime('%H:%M') + return date.strftime("%H:%M") -def get_human_day(time, date_prefix=''): - +def get_human_day(time, date_prefix=""): # Add 1 minute to transform 00:00 into ‘midnight today’ instead of ‘midnight tomorrow’ time = parse_naive_dt(time) date = (time - timedelta(minutes=1)).date() now = datetime.now(pytz.utc) if date == (now + timedelta(days=1)).date(): - return 'tomorrow' + return "tomorrow" if date == now.date(): - return 'today' + return "today" if date == (now - timedelta(days=1)).date(): - return 'yesterday' - if date.strftime('%Y') != now.strftime('%Y'): - return '{} {} {}'.format( + return "yesterday" + if date.strftime("%Y") != now.strftime("%Y"): + return "{} {} {}".format( date_prefix, _format_datetime_short(date), - date.strftime('%Y'), + date.strftime("%Y"), ).strip() - return '{} {}'.format( + return "{} {}".format( date_prefix, _format_datetime_short(date), ).strip() @@ -113,12 +100,12 @@ def get_human_day(time, date_prefix=''): def format_date(date): date = parse_naive_dt(date) - return date.strftime('%A %d %B %Y') + return date.strftime("%A %d %B %Y") def format_date_normal(date): date = parse_naive_dt(date) - return date.strftime('%d %B %Y').lstrip('0') + return date.strftime("%d %B %Y").lstrip("0") def format_date_short(date): @@ -130,26 +117,26 @@ def format_date_human(date): return get_human_day(date) -def format_datetime_human(date, date_prefix=''): - return '{} at {} UTC'.format( - get_human_day(date, date_prefix='on'), +def format_datetime_human(date, date_prefix=""): + return "{} at {} UTC".format( + get_human_day(date, date_prefix="on"), format_time_24h(date), ) def format_day_of_week(date): date = parse_naive_dt(date) - return date.strftime('%A') + return date.strftime("%A") def _format_datetime_short(datetime): - return datetime.strftime('%d %B').lstrip('0') + return datetime.strftime("%d %B").lstrip("0") def naturaltime_without_indefinite_article(date): return re.sub( - 'an? (.*) ago', - lambda match: '1 {} ago'.format(match.group(1)), + "an? (.*) ago", + lambda match: "1 {} ago".format(match.group(1)), humanize.naturaltime(date), ) @@ -157,11 +144,7 @@ def naturaltime_without_indefinite_article(date): def format_delta(date): # This method assumes that date is in UTC date = parse_naive_dt(date) - delta = ( - datetime.utcnow() - ) - ( - date - ) + delta = (datetime.utcnow()) - (date) if delta < timedelta(seconds=30): return "just now" if delta < timedelta(seconds=60): @@ -173,9 +156,9 @@ def format_delta_days(date): # This method assumes that date is in UTC date = parse_naive_dt(date) now = datetime.utcnow() - if date.strftime('%Y-%m-%d') == now.strftime('%Y-%m-%d'): + if date.strftime("%Y-%m-%d") == now.strftime("%Y-%m-%d"): return "today" - if date.strftime('%Y-%m-%d') == (now - timedelta(days=1)).strftime('%Y-%m-%d'): + if date.strftime("%Y-%m-%d") == (now - timedelta(days=1)).strftime("%Y-%m-%d"): return "yesterday" return naturaltime_without_indefinite_article(now - date) @@ -190,80 +173,84 @@ def valid_phone_number(phone_number): def format_notification_type(notification_type): return { - 'email': 'Email', - 'sms': 'Text message', + "email": "Email", + "sms": "Text message", }[notification_type] def format_notification_status(status, template_type): return { - 'email': { - 'failed': 'Failed', - 'technical-failure': 'Technical failure', - 'temporary-failure': 'Inbox not accepting messages right now', - 'permanent-failure': 'Email address does not exist', - 'delivered': 'Delivered', - 'sending': 'Sending', - 'created': 'Sending', - 'sent': 'Delivered' + "email": { + "failed": "Failed", + "technical-failure": "Technical failure", + "temporary-failure": "Inbox not accepting messages right now", + "permanent-failure": "Email address does not exist", + "delivered": "Delivered", + "sending": "Sending", + "created": "Sending", + "sent": "Delivered", }, - 'sms': { - 'failed': 'Failed', - 'technical-failure': 'Technical failure', - 'temporary-failure': 'Phone not accepting messages right now', - 'permanent-failure': 'Not delivered', - 'delivered': 'Delivered', - 'sending': 'Sending', - 'created': 'Sending', - 'pending': 'Sending', - 'sent': 'Sent' + "sms": { + "failed": "Failed", + "technical-failure": "Technical failure", + "temporary-failure": "Phone not accepting messages right now", + "permanent-failure": "Not delivered", + "delivered": "Delivered", + "sending": "Sending", + "created": "Sending", + "pending": "Sending", + "sent": "Sent", }, }[template_type].get(status, status) def format_notification_status_as_time(status, created, updated): return dict.fromkeys( - {'created', 'pending', 'sending'}, ' since {}'.format(created) + {"created", "pending", "sending"}, " since {}".format(created) ).get(status, updated) def format_notification_status_as_field_status(status, notification_type): return { - 'failed': 'error', - 'technical-failure': 'error', - 'temporary-failure': 'error', - 'permanent-failure': 'error', - 'delivered': None, - 'sent': 'sent-international' if notification_type == 'sms' else None, - 'sending': 'default', - 'created': 'default', - 'pending': 'default', - }.get(status, 'error') + "failed": "error", + "technical-failure": "error", + "temporary-failure": "error", + "permanent-failure": "error", + "delivered": None, + "sent": "sent-international" if notification_type == "sms" else None, + "sending": "default", + "created": "default", + "pending": "default", + }.get(status, "error") def format_notification_status_as_url(status, notification_type): url = partial(url_for, "main.message_status") if status not in { - 'technical-failure', 'temporary-failure', 'permanent-failure', + "technical-failure", + "temporary-failure", + "permanent-failure", }: return None return { - 'email': url(_anchor='email-statuses'), - 'sms': url(_anchor='text-message-statuses') + "email": url(_anchor="email-statuses"), + "sms": url(_anchor="text-message-statuses"), }.get(notification_type) def nl2br(value): if value: - return Markup(Take(Field( - value, - html='escape', - )).then( - utils_nl2br - )) - return '' + return Markup( + Take( + Field( + value, + html="escape", + ) + ).then(utils_nl2br) + ) + return "" # this formatter appears to only be used in the letter module @@ -290,34 +277,37 @@ def linkable_name(value): def format_thousands(value): if isinstance(value, Number): - return '{:,.0f}'.format(value) + return "{:,.0f}".format(value) if value is None: - return '' + return "" return value -def email_safe(string, whitespace='.'): +def email_safe(string, whitespace="."): # strips accents, diacritics etc - string = ''.join(c for c in unicodedata.normalize('NFD', string) if unicodedata.category(c) != 'Mn') - string = ''.join( - word.lower() if word.isalnum() or word == whitespace else '' - for word in re.sub(r'\s+', whitespace, string.strip()) + string = "".join( + c + for c in unicodedata.normalize("NFD", string) + if unicodedata.category(c) != "Mn" ) - string = re.sub(r'\.{2,}', '.', string) - return string.strip('.') + string = "".join( + word.lower() if word.isalnum() or word == whitespace else "" + for word in re.sub(r"\s+", whitespace, string.strip()) + ) + string = re.sub(r"\.{2,}", ".", string) + return string.strip(".") def id_safe(string): - return email_safe(string, whitespace='-') + return email_safe(string, whitespace="-") def round_to_significant_figures(value, number_of_significant_figures): if value == 0: return value - return int(round( - value, - number_of_significant_figures - int(floor(log10(abs(value)))) - 1 - )) + return int( + round(value, number_of_significant_figures - int(floor(log10(abs(value)))) - 1) + ) def redact_mobile_number(mobile_number, spacing=""): @@ -331,146 +321,132 @@ def redact_mobile_number(mobile_number, spacing=""): def get_time_left(created_at, service_data_retention_days=7): return ago.human( - ( - datetime.now(timezone.utc) - ) - ( - dateutil.parser.parse(created_at).replace(hour=0, minute=0, second=0) + timedelta( - days=service_data_retention_days + 1 - ) + (datetime.now(timezone.utc)) + - ( + dateutil.parser.parse(created_at).replace(hour=0, minute=0, second=0) + + timedelta(days=service_data_retention_days + 1) ), - future_tense='Data available for {}', - past_tense='Data no longer available', # No-one should ever see this - precision=1 + future_tense="Data available for {}", + past_tense="Data no longer available", # No-one should ever see this + precision=1, ) def starts_with_initial(name): - return bool(re.match(r'^.\.', name)) + return bool(re.match(r"^.\.", name)) def remove_middle_initial(name): - return re.sub(r'\s+.\s+', ' ', name) + return re.sub(r"\s+.\s+", " ", name) def remove_digits(name): - return ''.join(c for c in name if not c.isdigit()) + return "".join(c for c in name if not c.isdigit()) def normalize_spaces(name): - return ' '.join(name.split()) + return " ".join(name.split()) def guess_name_from_email_address(email_address): + possible_name = re.split(r"[\@\+]", email_address)[0] - possible_name = re.split(r'[\@\+]', email_address)[0] + if "." not in possible_name or starts_with_initial(possible_name): + return "" - if '.' not in possible_name or starts_with_initial(possible_name): - return '' - - return Take( - possible_name - ).then( - str.replace, '.', ' ' - ).then( - remove_digits - ).then( - remove_middle_initial - ).then( - str.title - ).then( - make_quotes_smart - ).then( - normalize_spaces + return ( + Take(possible_name) + .then(str.replace, ".", " ") + .then(remove_digits) + .then(remove_middle_initial) + .then(str.title) + .then(make_quotes_smart) + .then(normalize_spaces) ) -def message_count_label(count, template_type, suffix='sent'): +def message_count_label(count, template_type, suffix="sent"): if suffix: - return f'{message_count_noun(count, template_type)} {suffix}' + return f"{message_count_noun(count, template_type)} {suffix}" return message_count_noun(count, template_type) def message_count_noun(count, template_type): if template_type is None: if count == 1: - return 'message' + return "message" else: - return 'messages' + return "messages" - if template_type == 'sms': + if template_type == "sms": if count == 1: - return 'text message' + return "text message" else: - return 'text messages' + return "text messages" - elif template_type == 'email': + elif template_type == "email": if count == 1: - return 'email' + return "email" else: - return 'emails' + return "emails" def message_count(count, template_type): - return ( - f'{format_thousands(count)} ' - f'{message_count_noun(count, template_type)}' - ) + return f"{format_thousands(count)} " f"{message_count_noun(count, template_type)}" def recipient_count_label(count, template_type): - if template_type is None: if count == 1: - return 'recipient' + return "recipient" else: - return 'recipients' + return "recipients" - if template_type == 'sms': + if template_type == "sms": if count == 1: - return 'phone number' + return "phone number" else: - return 'phone numbers' + return "phone numbers" - elif template_type == 'email': + elif template_type == "email": if count == 1: - return 'email address' + return "email address" else: - return 'email addresses' + return "email addresses" def recipient_count(count, template_type): return ( - f'{format_thousands(count)} ' - f'{recipient_count_label(count, template_type)}' + f"{format_thousands(count)} " f"{recipient_count_label(count, template_type)}" ) def iteration_count(count): if count == 1: - return 'once' + return "once" elif count == 2: - return 'twice' + return "twice" else: - return f'{count} times' + return f"{count} times" def character_count(count): if count == 1: - return '1 character' - return f'{format_thousands(count)} characters' + return "1 character" + return f"{format_thousands(count)} characters" def format_mobile_network(network): - if network in ('three', 'vodafone', 'o2'): + if network in ("three", "vodafone", "o2"): return network.capitalize() - return 'EE' + return "EE" def format_billions(count): return humanize.intword(count) -def format_yes_no(value, yes='Yes', no='No', none='No'): +def format_yes_no(value, yes="Yes", no="No", none="No"): if value is None: return none return yes if value else no @@ -482,11 +458,11 @@ def square_metres_to_square_miles(area): def format_auth_type(auth_type, with_indefinite_article=False): indefinite_article, auth_type = { - 'email_auth': ('an', 'Email link'), - 'sms_auth': ('a', 'Text message code'), + "email_auth": ("an", "Email link"), + "sms_auth": ("a", "Text message code"), }[auth_type] if with_indefinite_article: - return f'{indefinite_article} {auth_type.lower()}' + return f"{indefinite_article} {auth_type.lower()}" return auth_type diff --git a/app/main/__init__.py b/app/main/__init__.py index 9ab666c27..e712f4dfd 100644 --- a/app/main/__init__.py +++ b/app/main/__init__.py @@ -1,6 +1,6 @@ from flask import Blueprint -main = Blueprint('main', __name__) +main = Blueprint("main", __name__) from app.main.views import ( # noqa isort:skip add_service, diff --git a/app/main/_commonly_used_passwords.py b/app/main/_commonly_used_passwords.py index 3c18f078e..a185bf3cc 100644 --- a/app/main/_commonly_used_passwords.py +++ b/app/main/_commonly_used_passwords.py @@ -1,6974 +1,6974 @@ commonly_used_passwords = [ - 'usnotify', - 'USnotify', - 'us Notify', - 'us notify', - '11111111', - '12345678', - '123456789', - 'access14', - 'alejandra', - 'alejandro', - 'baseball', - 'bigdaddy', - 'butthead', - 'cocacola', - 'computer', - 'consumer', - 'corvette', - 'danielle', - 'dolphins', - 'einstein', - 'estrella', - 'firebird', - 'football', - 'hardcore', - 'iloveyou', - 'internet', - 'jennifer', - 'mariposa', - 'marlboro', - 'maverick', - 'mercedes', - 'michelle', - 'midnight', - 'mistress', - 'mountain', - 'nicholas', - 'password', - 'password1', - 'password12', - 'password123', - 'princess', - 'qwertyui', - 'redskins', - 'redwings', - 'rush2112', - 'samantha', - 'scorpion', - 'sebastian', - 'srinivas', - 'startrek', - 'starwars', - 'steelers', - 'sunshine', - 'superman', - 'swimming', - 'tequiero', - 'trustno1', - 'victoria', - 'whatever', - 'xxxxxxxx', - '1234567890', - '1q2w3e4r5t', - 'qwertyuiop', - 'myspace1', - '1qaz2wsx', - 'target123', - '1g2w3e4r', - 'gwerty123', - 'zag12wsx', - '1q2w3e4r', - '987654321', - 'qwerty123', - 'asdfghjkl', - '123123123', - 'iloveyou1', - 'fuckyou1', - '789456123', - 'princess1', - 'linkedin', - '1234qwer', - 'j38ifUbn', - 'football1', - '123456789a', - 'abcd1234', - 'jordan23', - '88888888', - '12qwaszx', - 'FQRG7CS493', - 'blink182', - 'michael1', - 'babygirl1', - '0123456789', - 'iloveyou2', - '147258369', - 'q1w2e3r4', - 'jessica1', - 'qwer1234', - 'liverpool', - 'fuckyou2', - '1111111111', - 'qazwsxedc', - 'baseball1', - '0987654321', - 'anthony1', - '00000000', - '29rsavoy', - 'basketball', - 'qwerty12', - 'charlie1', - 'passw0rd', - 'asshole1', - 'superman1', - 'sunshine1', - 'babygirl', - 'asdf1234', - 'chocolate', - 'password2', - '12341234', - '12344321', - 'q1w2e3r4t5y6', - 'qweasdzxc', - 'a123456789', - 'VQsaBLPzLa', - 'hello123', - 'butterfly', - '1qazxsw2', - 'cjmasterinf', - 'brandon1', - '1234567891', - 'alexander', - 'PE#5GZ29PTZMSE', - 'dpbk1234', - 'DIOSESFIEL', - 'pakistan', - '123654789', - 'matthew1', - '3rJs1la7qE', - 'пїЅпїЅпїЅпїЅпїЅпїЅ', - 'barcelona', - 'computer1', - 'michelle1', - '12345678910', - 'jonathan', - 'liverpool1', - '11223344', - '12345qwert', - '111222tianya', - 'william1', - 'chicken1', - '0000000000', - 'jasmine1', - 'benjamin', - 'welcome1', - 'christian', - '1234554321', - 'chocolate1', - 'butterfly1', - 'q1w2e3r4t5', - 'slipknot', - 'zaq12wsx', - '147852369', - 'elizabeth', - '87654321', - '1password', - 'america1', - 'metallica', - 'chelsea1', - '1234567a', - 'iw14Fi9j', - 'juventus', - 'jennifer1', - '999999999', - 'elizabeth1', - '123qweasd', - 'tinkerbell', - 'samantha1', - 'Sojdlg123aljg', - 'myspace123', - 'freedom1', - 'whatever1', - 'valentina', - '741852963', - 'spongebob1', - '1234abcd', - 'hellokitty', - 'madison1', - 'spiderman', - 'diamond1', - 'pokemon1', - 'mustang1', - '1qaz2wsx3edc', - 'justinbieb', - 'friends1', - 'asdfasdf', - 'qwerty12345', - '123hfjdk147', - 'iloveyou!', - 'fuckoff1', - 'bubbles1', - 'a1b2c3d4', - '123456789q', - 'heather1', - '4815162342', - 'yankees1', - 'asdfghjkl1', - '1q2w3e4r5t6y', - 'patrick1', - '12121212', - 'alexander1', - 'raiders1', - 'Password1', - 'zxcvbnm1', - 'melissa1', - 'slipknot1', - 'spiderman1', - 'cowboys1', - 'a1234567', - 'november', - 'alexandra', - 'veronica', - 'cristina', - 'newyork1', - 'jackson1', - 'iloveyou12', - 'PolniyPizdec0211', - 'password!', - 'a838hfiD', - 'richard1', - 'beautiful1', - 'carolina', - 'patricia', - 'stephanie', - '421uiopy258', - 'myspace2', - 'monster1', - 'elephant', - '963852741', - 'destiny1', - '123456abc', - 'december', - '9876543210', - 'manchester', - '12345678a', - 'пїЅпїЅпїЅпїЅпїЅпїЅпїЅ', - 'kristina', - 'lovelove', - 'gangsta1', - 'charlotte', - 'scooter1', - 'caroline', - 'super123', - 'marseille', - 'metallica1', - 'beautiful', - 'danielle1', - 'blessed1', - '1029384756', - 'qazwsx123', - 'california', - 'christian1', - 'arsenal1', - 'babyboy1', - '1122334455', - 'aa123456', - 'forever1', - 'Password', - '1a2b3c4d', - 'playboy1', - 'creative', - 'пїЅпїЅпїЅпїЅпїЅпїЅпїЅпїЅ', - 'brittany1', - 'letmein1', - 'cameron1', - 'spongebob', - 'uQA9Ebw445', - 'fernando', - 'startfinding', - 'softball', - 'dolphin1', - 'qwerty1234', - 'september', - 'isabella', - 'abc123456', - 'password3', - 'abcdefg123', - 'loveyou1', - 'leonardo', - 'password.', - 'samsung1', - 'qwert123', - 'poohbear', - 'garfield', - 'YAgjecc826', - 'qwerty123456', - 'iloveme1', - 'nicholas1', - 'portugal', - 'precious', - 'jackass1', - 'jonathan1', - 'rainbow1', - 'angel123', - 'fuckyou!', - 'starwars1', - 'tiffany1', - 'poohbear1', - '1234512345', - 'qq123456', - 'abcdefg1', - 'crystal1', - 'azertyuiop', - 'angelina', - 'svetlana', - 'icecream', - 'popcorn1', - 'victoria1', - 'twilight', - 'brittany', - 'snickers', - 'aaaaaaaa', - 'swordfish', - 'fyfcnfcbz', - 'rockstar1', - 'yourmom1', - 'christine', - 'steelers1', - 'shannon1', - 'peaches1', - 'florida1', - 'stephanie1', - 'lollipop', - 'greenday1', - 'iverson3', - 'motorola', - 'rockstar', - 'lakers24', - 'southside1', - 'bismillah', - 'pa55word', - 'emmanuel', - '5555555555', - 'password11', - 'love4ever', - 'greenday', - 'isabelle', - 'babygurl1', - 'santiago', - 'chester1', - 'kimberly', - 'happy123', - '55555555', - 'satan666', - 'francesco', - 'vanessa1', - 'a12345678', - 'realmadrid', - '1123581321', - 'soccer12', - 'fktrcfylh', - 'qwert12345', - '1v7Upjw3nT', - 'p@ssw0rd', - 'thunder1', - 'zxcvbnm123', - 'midnight1', - 'lebron23', - 'strawberry', - 'love1234', - 'soccer10', - 'darkness', - 'qw123321', - '22222222', - 'd41d8cd98f00b204e9800998ecf8427e', - 'charles1', - 'logitech', - 'princess12', - 'precious1', - 'brooklyn1', - 'snowball', - 'courtney', - '123qwe123', - 'brooklyn', - 'vladimir', - '111222333', - 'asdfghjk', - 'lizottes', - '123454321', - '123qweasdzxc', - 'superstar', - 'rebecca1', - 'catherine', - '123698745', - 'vkontakte', - 'getmoney1', - 'hollister1', - 'remember', - 'abc12345', - '111111111', - 'cjkysirj', - 'money123', - 'element1', - 'P3Rat54797', - 'francesca', - 'undertaker', - 'asdfjkl;', - 'facebook', - 'chouchou', - 'password7', - 'kawasaki', - 'linkinpark', - 'ronaldo7', - 'asdasdasd', - 'alessandro', - 'courtney1', - 'qqww1122', - 'scarface', - 'angelica', - 'australia', - 'qti7Zxh18U', - 'пїЅпїЅпїЅпїЅпїЅ', - 'chicago1', - 'softball1', - 'natalie1', - 'monkey123', - 'bullshit', - 'sunflower', - '21212121', - 'volleyball', - 'пїЅпїЅпїЅпїЅпїЅпїЅпїЅпїЅпїЅ', - 'sweetpea', - 'zxcvbnm:', - 'bitch123', - 'babygirl12', - 'goodluck', - 'gateway1', - 'bigdaddy1', - '31415926', - 'christina', - 'aleksandr', - 'gabriel1', - 'ihateyou', - 'antonio1', - 'awesome1', - 'fishing1', - 'amoremio', - 'monkey12', - 'brianna1', - 'bitches1', - 'princesa', - 'asdfghjkl;', - 'changeme', - 'passwort', - 'valentin', - '12345qwerty', - 'paradise', - 'scarface1', - 'jesus123', - 'sweetheart', - 'qwertyuiop[]', - 'fuckyou123', - 'P@ssw0rd', - 'myspace!', - 'williams', - 'gabriela', - '77777777', - 'christopher', - 'soccer11', - 'westside', - 'giovanni', - 'apple123', - 'zachary1', - 'christophe', - '123456aa', - 'pumpkin1', - 'nirvana1', - 'hotmail1', - 'cookies1', - 'shopping', - 'password5', - 'superstar1', - 'maryjane', - 'benjamin1', - 'margarita', - 'cristian', - 'qwerasdf', - 'motdepasse', - '147896325', - '25802580', - 'westside1', - 'iloveyou.', - '123456789m', - 'grandma1', - 'dbrnjhbz', - 'dearbook', - 'chris123', - 'ladybug1', - 'loveyou2', - 'giuseppe', - 'football12', - 'sabrina1', - 'september1', - 'icecream1', - 'loverboy', - 'sterling', - 'йцукен', - 'christina1', - 'virginia', - 'savannah', - 'inuyasha1', - 'hallo123', - 'twilight1', - 'snickers1', - 'friendster', - 'adgjmptw', - '123456654321', - 'champion', - 'bestfriend', - 'rhbcnbyf', - 'internet1', - 'teddybear', - 'blessing', - 'abcdefgh', - 'happiness', - 'password01', - 'frankie1', - 'Tnk0Mk16VX', - 'aaaaaaaaaa', - 'flowers1', - 'cupcake1', - 'johncena1', - '123456qwerty', - '192837465', - 'PASSWORD', - 'rangers1', - 'bulldog1', - 'simpsons', - 'blahblah', - 'carpediem', - 'francisco', - '19871987', - 'veronika', - 'пароль', - 'airforce1', - 'inuyasha', - 'casanova', - '123456789z', - 'chocolat', - 'jackson5', - 'W5tXn36alfW', - 'nintendo', - '321654987', - '19851985', - 'hollywood', - 'runescape1', - 'pass1234', - 'spencer1', - 'cheyenne', - '99999999', - 'panasonic', - '12369874', - 'ciaociao', - 'florence', - '123321123', - 'phoenix1', - 'anderson', - 'warcraft', - 'poiuytrewq', - 'myspace12', - 'colorado', - 'Passw0rd', - 'gangster1', - 'mamapapa', - 'sweetie1', - 'business', - 'maradona', - 'rammstein', - 'microsoft', - 'kimberly1', - 'ihateyou1', - 'soccer13', - 'babygirl2', - 'hollywood1', - 'anastasia', - 'jesus777', - 'redneck1', - 'zzzzzzzz', - 'lasvegas', - 'stonecold', - 'maxwell1', - 'princess2', - '19861986', - 'sureno13', - 'savannah1', - 'engineer', - 'paintball1', - '19841984', - '135792468', - 'amsterdam', - 'skittles', - 'forever21', - '14789632', - '19921992', - 'orlando1', - 'children', - 'christmas', - 'asdasd123', - 'cocacola1', - 'snowball1', - '123456123', - 'sebastian1', - 'drowssap', - 'soccer123', - 'kingkong', - '123456123456', - 'gangster', - '7894561230', - 'shithead1', - 'patches1', - 'trouble1', - 'mercedes1', - 'MaprCheM56458', - 'digital1', - 'maryjane1', - 'stephen1', - 'kathleen', - 'marshall', - 'hahahaha', - 'zaq1xsw2', - 'minecraft', - 'argentina', - 'serenity', - 'password4', - 'a1s2d3f4', - 'alexandre', - 'barcelona1', - '123789456', - 'password00', - 'georgia1', - 'porsche1', - '1qaz1qaz', - 'megaparol12345', - 'iG4abOX4', - 'dragonball', - 'football2', - 'nathalie', - 'trinity1', - 'colombia', - 'killer123', - 'bullshit1', - 'terminator', - '69696969', - 'onelove1', - 'password13', - 'baseball12', - 'nothing1', - 'myspace.', - 'harrypotter', - 'security', - 'elephant1', - 'teddybear1', - 'winston1', - 'summer08', - 'sexybitch1', - 'welcome123', - 'katherine', - 'scotland', - 'dinosaur', - 'iloveyou3', - 'fuckyou69', - '19891989', - 'admin123', - 'federico', - 'success1', - 'cutiepie1', - 'green123', - 'trfnthbyf', - '12301230', - 'margaret', - 'godisgood', - 'charlotte1', - '11112222', - '19821982', - 'david123', - 'beatrice', - 'hardcore1', - 'franklin', - '123456789s', - 'любовь', - 'penelope', - 'mitchell', - '66666666', - 'hercules', - 'katerina', - 'allison1', - 'charmed1', - 'babydoll', - 'christine1', - '123456987', - 'india123', - 'monique1', - '19801980', - '1loveyou', - '20102010', - 'blackberry', - '19951995', - 'alejandro1', - 'iloveme2', - '1234asdf', - 'music123', - '9-11-1961', - 'skittles1', - 'cdtnkfyf', - 'tokiohotel', - '1234567q', - 'ka_dJKHJsy6', - 'qazwsx12', - 'idontknow', - 'truelove', - 'houston1', - 'shithead', - 'wolverine', - 'bradley1', - 'mahalkita', - 'sexygirl1', - 'timothy1', - '19831983', - 'yahoo.com', - 'platinum', - 'isabella1', - 'password10', - 'valentine', - 'vampires', - 'password0', - 'strength', - 'asd123456', - '10101010', - 'baseball2', - '11235813', - 'chopper1', - 'g9l2d1fzPY', - 'jamesbond', - 'goldfish', - 'carolina1', - 'vincent1', - 'summer09', - 'packers1', - 'martinez', - 'cutiepie', - 'D1lakiss', - 'qazxswedc', - 'diamonds', - 'ferrari1', - 'napoleon', - '13131313', - 'panther1', - 'zxcv1234', - 'lacrosse', - 'federica', - '123456789j', - 'passport', - 'buddy123', - 'omsairam', - 'bulldogs', - 'ilovehim1', - 'james123', - 'cleopatra', - '1qa2ws3ed', - 'Linkedin', - 'catalina', - 'wrestling1', - 'Megaparol12345', - 'fernanda', - 'myspace3', - 'harrison', - 'blondie1', - 'buttercup', - 'muhammad', - 'medicine', - 'fuckme69', - 'SZ9kQcCTwY', - 'gordon24', - '19881988', - 'stellina', - '1234567899', - 'pa55w0rd', - 'skateboard', - 'pebbles1', - 'stargate', - 'natasha1', - 'drummer1', - 'abigail1', - 'raymond1', - 'thuglife', - 'johnson1', - 'pokemon123', - 'remember1', - 'sporting', - 'salvatore', - 'blablabla', - 'handsome', - 'johncena', - '14531453', - 'penguin1', - 'budlight1', - 'infinity', - 'naruto123', - 'montana1', - '10203040', - 'scoobydoo', - 'jesuschrist', - 'devil666', - '44444444', - 'thebest1', - '1myspace', - 'kittycat', - 'pineapple', - 'qwerty11', - 'veronica1', - 'PolniyPizdec110211', - 'england1', - 'mypassword', - 'smoke420', - 'wordpass', - 'asdfasdf1', - 'aaliyah1', - 'genesis1', - 'lilwayne1', - 'spartan117', - 'kkkkkkkk', - 'password9', - 'alexandra1', - 'sk84life', - 'salvador', - 'newport1', - 'daniel123', - 'привет', - 'darkness1', - 'ilovejesus', - 'summer07', - 'melanie1', - 'lawrence', - 'alabama1', - 'monkeys1', - 'peterpan', - 'dumbass1', - 'ekaterina', - '2012comeer', - 'lollipop1', - 'cricket1', - 'blahblah1', - 'papillon', - '12131415', - 'michigan', - '19941994', - 'panthers', - 'idontknow1', - '369258147', - 'iloveyou7', - 'mexican1', - 'runescape', - 'fordf150', - 'ilovegod', - 'spitfire', - 'godzilla', - '33333333', - 'azerty123', - '19931993', - 'wildcats', - 'test1234', - 'mohammed', - 'ladygaga', - 'qweasd123', - '1princess', - 'dragons1', - 'bluefish', - 'dolphins1', - 'qwerty321', - 'miranda1', - 'cassandra', - 'password22', - 'something', - 'qwe12345', - 'dragon123', - 'pitbull1', - 'moonlight', - 'password69', - 'nonmember', - '5532361cnjqrf', - '19811981', - 'tiger123', - 'panthers1', - 'jeffrey1', - 'dodgers1', - 'dickhead1', - 'dragon12', - 'guinness', - '123456asd', - 'buttercup1', - 'vampire1', - 'loser123', - 'dIWtgm8492', - 'bulldogs1', - '123456789l', - '19901990', - 'cheyenne1', - 'friendship', - 'cambiami', - 'linkedin1', - 'abcde12345', - 'jamaica1', - 'lindsey1', - 'пїЅпїЅпїЅпїЅ', - 'teacher1', - 'zxcvbnm,./', - 'yousuck1', - 'myspace.co', - 'babydoll1', - '987456321', - 'bluebird', - 'casablanca', - 'password8', - 'death666', - 'watermelon', - 'asdqwe123', - 'predator', - 'soccer14', - '19911991', - '123qwerty', - 'candy123', - 'babygurl', - 'lucky123', - 'loverboy1', - 'lovelife', - 'special1', - '3rJs5la8qE', - '3rJs1la2qE', - 'sweetpea1', - 'insanity', - '123456789d', - 'ronaldinho', - 'birthday', - 'pussycat', - '123456qwe', - 'fountain', - 'christmas1', - '123456789k', - 'sexygirl', - 'viktoria', - 'kristen1', - 'shadow12', - '20092009', - 'kenneth1', - 'illinois', - 'formula1', - 'antonella', - '1357924680', - 'yankees2', - 'jaimatadi', - 'sk8ordie', - '1234567890q', - 'justice1', - 'fuckyou12', - 'kitty123', - 'broncos1', - 'qweqweqwe', - 'paramore', - 'atlanta1', - 'assassin', - 'alessandra', - 'creative1', - 'люблю', - 'naruto12', - 'drpepper', - 'valencia', - '19781978', - 'nks230kjs82', - 'lover123', - 'lovebug1', - 'killer12', - '01020304', - 'bella123', - 'sunflower1', - 'boobies1', - 'defender', - 'youngmoney', - 'anhyeuem', - 'пїЅпїЅпїЅпїЅпїЅпїЅпїЅпїЅпїЅпїЅ', - 'barbara1', - 'qazwsxedcrfv', - 'lilmama1', - '9999999999', - 'alejandra1', - '123456789p', - 'redskins1', - 'darkangel', - 'liberty1', - 'newlife1', - 'sammy123', - 'monalisa', - 'butterfly2', - 'prettygirl', - 'getmoney', - 'temppass', - 'drpepper1', - 'schalke04', - 'pantera1', - 'october1', - 'sexymama1', - 'a1b2c3d4e5', - 'agent007', - 'bubblegum1', - 'qazwsxedc1', - 'swordfish1', - 'scorpio1', - 'dickhead', - 'patricia1', - 'catherine1', - 'dominic1', - 'marissa1', - 'cherokee', - 'mommy123', - 'molly123', - 'katherine1', - 'lorraine', - 'explorer', - 'cooldude', - 'myspace7', - 'porsche911', - 'colombia1', - '12345679', - 'CM6E7Aumn9', - '19791979', - '2222222222', - 'bearshare', - 'qawsedrf', - 'indonesia', - 'monkey11', - 'justdoit', - 'marines1', - 'vikings1', - 'aquarius', - 'valentino', - 'cookie123', - 'baseball7', - '20082008', - 'september2', - 'kittycat1', - 'kissmyass', - 'Groupd2013', - 'as123456', - 'pickles1', - 'michigan1', - 'shadow123', - 'chargers1', - 'babyblue', - 'skyline1', - 'scoobydoo1', - 'november1', - 'wrestling', - 'warrior1', - 'superman12', - 'pakistan1', - 'ronaldo9', - 'december1', - 'dalejr88', - 'geronimo', - 'akopa123', - 'bollocks', - 'dominique', - 'football10', - 'perfect1', - 'thuglife1', - 'ghbdtnbr', - 'dragonfly', - 'bigdick1', - 'sapphire', - '000000000', - 'michael2', - 'jeremiah', - 'hotstuff1', - '19961996', - 'icehouse', - 'lindsay1', - '19751975', - 'istanbul', - 'OcPOOok325', - 'thailand', - 'personal', - 'passion1', - 'password23', - 'hershey1', - 'capricorn', - 'fucklove1', - 'babylove', - 'vaffanculo', - 'stanley1', - 'marketing', - '123456789o', - 'sandrine', - 'qwerqwer', - 'something1', - 'charlene', - 'baseball3', - 'angelito', - 'football7', - 'american', - 'aspirine', - 'maverick1', - 'claudia1', - 'brother1', - 'tottenham', - 'westlife', - '7777777777', - 'pokemon12', - 'bobmarley', - 'vacation', - 'hotstuff', - 'asdfghjkl:', - '1q2w3e4r5t6y7u8i9o0p', - 'password6', - 'stefania', - 'nicole12', - 'spiderman3', - 'caroline1', - 'gregory1', - 'smile123', - 'daddysgirl', - '1asshole', - 'lollypop', - 'lilwayne', - 'cashmoney1', - 'qqqqqqqq', - 'ireland1', - 'princess10', - 'skate4life', - 'scarlett', - 'asdfjkl:', - 'patriots1', - 'josephine', - 'callofduty', - 'princesse', - 'nightmare', - 'germany1', - 'soccer15', - 'magnolia', - 'longhorns1', - 'tristan1', - 'micheal1', - 'testpass', - '112233445566', - 'snowman1', - 'douglas1', - 'esperanza', - 'enterprise', - 'maria123', - 'master123', - 'warcraft1', - 'qwe123456', - 'asdfg123', - 'williams1', - 'cinderella', - 'marshall1', - 'baseball11', - 'vincenzo', - 'pavilion', - 'cheater1', - 'shamrock', - 'purple12', - 'australia1', - 'sebastien', - 'jellybean', - 'google123', - 'ironmaiden', - '3Odi15ngxB', - 'country1', - 'eternity', - '98765432', - 'simpsons1', - 'cracker1', - 'gabrielle', - 'Welcome1', - 'superman2', - 'shopping1', - 'bettyboop1', - 'ricardo1', - 'marlboro1', - 'juggalo1', - 'therock1', - 'monkey13', - 'garfield1', - 'arizona1', - 'fashion1', - 'rolltide', - '1234567890a', - 'iloveyou22', - 'daniel12', - 'testtest', - 'slayer666', - 'baseball10', - '393041123', - 'jordan12', - '12qw23we', - 'washington', - 'rfnthbyf', - 'bananas1', - '123456789987654321', - 'наташа', - 'brownie1', - 'everton1', - 'ashley12', - '123456789123', - '987654321a', - 'wangyut2', - 'butthead1', - 'newcastle', - 'magdalena', - 'rosebud1', - 'ashley123', - 'billabong', - 'boricua1', - '●●●●`', - 'underground', - '12345abc', - 'iloveyou11', - 'chandler', - 'qwe1122334', - 'andromeda', - 'punkrock', - 'just4fun', - 'hamilton', - 'iloveyou13', - 'fuckyou.', - 'whitney1', - 'hernandez', - 'juliette', - '19761976', - 'dreamer1', - 'pk3x7w9W', - 'golfcourse', - 'pineapple1', - 'godislove', - 'madeline', - 'my2girls', - 'babycakes1', - 'yuantuo2012', - 'marianne', - 'cutie123', - 'wildcats1', - 'loveme123', - 'fireball', - 'montreal', - '1babygirl', - '12345qwe', - 'hottie101', - '3d8Cubaj2E', - 'serenity1', - 'billabong1', - '1q2w3e4r5', - 'PolniyPizdec1102', - 'karolina', - 'thumper1', - 'e10adc3949ba59abbe56e057f20f883e', - '19971997', - 'zxasqw12', - 'baseball5', - 'champion1', - 'black123', - 'aobo2010', - 'rosemary', - 'scrappy1', - 'rocky123', - 'highheel', - 'eastside1', - 'monkey22', - 'qwe123qwe', - 'cynthia1', - 'asdf3423', - 'sublime1', - 'fernando1', - '19771977', - 'myspace11', - 'jellybean1', - 'preston1', - 'soccer22', - 'maximus1', - 'beatles1', - 'hongkong', - 'windows1', - 'максим', - 'nicole123', - 'marianna', - 'patriots', - '19731973', - 'jesucristo', - 'lovelove1', - 'babygirl13', - 'apollo13', - 'iloveyou14', - 'sentnece', - 'nichole1', - '123456789123456789', - 'football11', - 'hannah123', - 'telephone', - 'budlight', - 'TempPassWord', - '123456as', - 'kaitlyn1', - 'skywalker', - 'fuckyou3', - 'wonderful', - 'jessica123', - 'AKAX89Wn', - '23232323', - 'qwertyuio', - 'jordan123', - 'freckles', - 'guadalupe', - 'bubblegum', - 'oblivion', - 'asshole2', - 'angelica1', - 'esmeralda', - 'deftones', - 'lionking', - 'blackjack', - 'revolution', - 'purple123', - 'galatasaray', - '12345678q', - 'comeon11', - 'tyler123', - 'radiohead', - 'zxcvbnm,', - 'yahoo123', - 'truelove1', - 'puppies1', - 'rocknroll', - 'lkjhgfdsa', - 'february', - 'linkedin123', - 'russell1', - 'gabriele', - 'classof09', - '123456789b', - 'corvette1', - 'frederic', - 'makaveli', - 'dkflbvbh', - 'fucklove', - 'baller23', - 'handball', - 'soccer17', - 'nintendo1', - 'america10', - 'january1', - 'motherlode', - 'angelina1', - 'metal666', - 'anthony2', - 'password21', - 'garrett1', - 'starfish', - 'spiderman2', - 'caitlin1', - 'abcdefghij', - 'sarah123', - 'justin123', - 'paintball', - 'anything', - 'iloveyou4', - 'princess11', - 'sunshine2', - '1020304050', - 'y6p67FtrqJ', - 'princess13', - '1zn6FpN01x', - 'logitech1', - 'derrick1', - 'pinkfloyd', - 'starcraft', - 'snowflake', - 'stardust', - 'ncc1701d', - 'private1', - 'newcastle1', - 'football9', - 'bluemoon', - 'rodriguez', - 'q123456789', - 'lasvegas1', - 'bettyboop', - 'jason123', - 'blueeyes', - 'education', - '1b78ef23aa2506f41feecfcc45b66038', - 'smallville', - 'dietcoke', - 'toulouse', - 'daddy123', - '1a2s3d4f', - 'jumpman23', - 'snowboard', - 'college1', - 'blueberry', - 'fireman1', - '19741974', - 'flamingo', - 'stephane', - 'phantom1', - 'football3', - 'kissmyass1', - 'riccardo', - 'ilovehim', - 'drowssap1', - 'kingston', - 'eleven11', - 'anamaria', - 'munchkin', - 'michael123', - 'mitchell1', - 'hannah12', - 'detroit1', - 'giovanna', - 'iloveyou5', - 'michaela', - 'anderson1', - 'LinkedIn', - '25252525', - 'lighthouse', - '5hsU75kpoT', - 'singapore', - 'katrina1', - '123456qw', - 'aleksandra', - 'carlitos', - '123456ab', - 'justin12', - 'gabriella', - 'universal', - '1a2b3c4d5e', - 'daniela1', - 'cashmoney', - 'nuttertools', - 'ragnarok', - 'rastaman', - 'rebelde1', - 'labrador', - 'holiday1', - 'cookie12', - 'mexico13', - 'warcraft3', - 'blizzard', - 'hamster1', - 'adriana1', - 'delpiero', - 'cheese123', - 'gonzalez', - 'britney1', - 'hottie12', - 'thankyou', - 'princess3', - 'love12345', - 'myspace13', - 'naughty1', - 'godfather', - 'romashka', - 'marijuana', - 'valerie1', - 'qwertyuio1', - 'football5', - 'disturbed1', - 'princess!', - 'f00tball', - 'francis1', - '23456789', - 'chocolate2', - 'pizza123', - '123456qq', - 'sexybitch', - 'gladiator', - 'xiang123456', - 'vfrcbvrf', - 'babygirl10', - 'aaaa1111', - 'skorpion', - 'unicorn1', - 'skate123', - 'princess7', - 'southpark1', - 'crazy123', - '134679852', - 'franklin1', - 'summer06', - 'philippe', - 'd71lWz9zjS', - 'drjynfrnt', - 'cassidy1', - '789654123', - 'kevin123', - 'goldfish1', - 'snuggles', - 'amorcito', - 'mackenzie', - 'research', - 'babyblue1', - 'libertad', - 'charlie2', - 'blackcat', - 'bethany1', - 'buttons1', - 'francois', - 'flower123', - 'phillip1', - 'sunshine12', - 'soccer21', - 'power123', - 'passwort1', - 'hunting1', - 'sooners1', - '12345678900', - 'robinson', - 'virginia1', - 'baseball13', - 'warriors', - 'thegame1', - 'cuddles1', - 'nicolas1', - 'jessica2', - 'evolution', - 'hawaii50', - 'myspace5', - 'zeppelin', - 'trinidad', - 'billybob', - 'atlantis', - 'woaini1314', - 'mamamama', - 'hottie123', - 'clifford', - 'rhfcjnrf', - 'wordpass1', - 'agnieszka', - 'verbatim', - 'qwertyqwerty', - 'dthjybrf', - 'captain1', - 'sexyboy1', - 'марина', - 'ihateyou2', - 'farfalla', - 'natalia1', - 'princess01', - '123456789c', - 'calimero', - 'ilovemymom', - 'charlie123', - 'soccer16', - 'chemistry', - 'mauricio', - 'motocross', - 'daisy123', - 'cannabis', - 'immortal', - 'colorado1', - 'babyboo1', - 'applepie', - 'cadillac', - 'playstation', - 'losangeles', - 'fenerbahce', - '24682468', - 'breanna1', - 'alladin79', - 'jeremiah1', - 'arschloch', - 'mnbvcxz1', - 'semperfi', - 'iw14Fi9jxL', - 'vodafone', - 'YfDbUfNjH10305070', - 'blue1234', - 'indiana1', - 'marie123', - 'american1', - 'peter123', - '1million', - 'kingkong1', - 'louloute', - 'carebear1', - 'c43qpul5RZ', - 'pussy123', - 'justinbieber', - 'elizabeth2', - 'b9399f21060d4b5fcb6d3cf5fea8de', - '12345671', - 'godbless', - 'fuckyou7', - 'summer12', - 'fuckoff!', - 'swimming1', - '123456789r', - 'southside', - '1andonly', - 'lavender', - 'lacrosse1', - 'imissyou', - 'ericsson', - 'lightning', - 'cassandra1', - 'falcons1', - 'soccer23', - '19981998', - 'sweetness', - 'eclipse1', - 'laurence', - 'chrissy1', - 'mastermind', - 'yamahar1', - 'papamama', - 'layouts1', - 'kristin1', - 'qwertyu1', - 'nevermind', - 'felicidade', - 'halloween', - 'H2vWDuBjX4', - 'alberto1', - 'amandine', - 'kennedy1', - 'abhishek', - 'priyanka', - 'lovehurts1', - 'wolfgang', - 'cellphone1', - 'friends2', - 'kittykat', - 'scruffy1', - 'kristine', - 'master12', - 'zxcasdqwe', - 'letmein2', - 'danny123', - 'morrison', - 'lollypop1', - 'vladislav', - 'vRbGQnS997', - 'austin316', - 'кристина', - 'surfing1', - 'facebook1', - 'iamthebest', - 'eduardo1', - 'dingdong', - 'makayla1', - 'sweetness1', - 'sandiego', - 'hunter12', - 'godisgreat', - 'allstar1', - 'jackass2', - 'football21', - 'blackie1', - 'chevrolet', - 'hernandez1', - 'skipper1', - 'caramelo', - 'loveless', - 'batista1', - 'myspace01', - 'gerrard8', - 'jessica12', - 'ronaldo1', - 'nightmare1', - 'littleman1', - 'backspace', - 'fuckthis1', - '??????????', - 'jacqueline', - 'warhammer', - 'anastasiya', - 'cristiano', - 'hello1234', - 'madonna1', - 'wachtwoord', - '19721972', - 'southpark', - 'joseluis', - 'spongebob2', - 'qazwsxedc123', - 'starlight', - 'shadow13', - 'projectsadminx', - 'motorola1', - 'chicken2', - 'sk8board', - 'universe', - 'kristina1', - 'lincoln1', - 'password14', - 'x4ivygA51F', - 'sexylady1', - 'miami305', - 'scotland1', - 's8YLPe9jDPvYM', - 'dirtbike1', - 'michael12', - 'football22', - 'pearljam', - 'nokia6300', - 'babygirl3', - 'password1234', - 'skeeter1', - 'nathaniel', - 'victory1', - 'iloveyou123', - 'oscar123', - 'classof08', - 'a1a2a3a4', - 'monkey69', - 'm123456789', - 'chrisbrown', - 'jG3h4HFn', - 'diamonds1', - '123456789t', - 'peace123', - 'friday13', - 'солнышко', - 'ironman1', - 'asdfgh123', - 'andrew12', - 'lucas123', - 'platinum1', - 'mathilde', - 'анастасия', - 'maurice1', - 'pornstar', - 'rooster1', - 'opensesame', - 'андрей', - 'hotgirl1', - 'microlab', - 'player69', - 'microsoft1', - 'iloveu123', - 'michele1', - 'soulmate', - 'university', - 'toshiba1', - 'dallas214', - 'ab123456', - 'campbell', - 'pothead1', - 'sampson1', - 'london12', - 'pass1word', - 'motherfucker', - '18atcskD2W', - 'cinnamon', - 'monamour', - 'krystal1', - 'chevelle', - 'verizon1', - '0102030405', - 'dominique1', - 'eleonora', - 'theking1', - 'myspace4', - 'fktrcfylhf', - 'tacobell1', - 'asdfghjkl;'', - 'slimshady', - 'redhead1', - 'lovelife1', - 'sherlock', - '19071907', - 'mushroom', - 'w66YRyBgRa', - 'topolino', - 'computer12', - 'webhompass', - 'richmond', - 'r2d2c3po', - 'batman123', - 'michelle12', - 'loveme12', - 'chiquita', - '12345abcde', - 'tigger12', - 'Aa123456', - 'project1', - 'viewsonic', - 'freddie1', - 'baseball9', - '12345678901', - 'Parola12', - 'hermione', - 'president', - 'dedewang', - 'rolltide1', - 'qweasdzxc123', - 'gorgeous', - 'g13916055158', - 'amanda123', - 'X3LUym2MMJ', - 'virginie', - 'juancarlos', - 'marathon', - 'andrew123', - 'babylove1', - 'baseball8', - 'snowflake1', - 'tottenham1', - 'ILOVEYOU', - 'alfaromeo', - '20002000', - 'katie123', - 'kamikaze', - '321321321', - 'nickjonas1', - 'boomboom', - 'vfvfgfgf', - 'mexico123', - 'love123456', - 'hellohello', - 'bitch101', - 'Password123', - 'catfish1', - 'taekwondo', - 'anthony123', - '1Fr2rfq7xL', - 'keyboard', - 'francisco1', - 'snowboard1', - 'trucker1', - 'laetitia', - 'james007', - 'happiness1', - 'amber123', - 'holahola', - 'babyface', - 'summer123', - 'deathnote', - '1234567890-', - 'wolverine1', - 'professional', - 'roberto1', - 'brittney1', - 'jefferson', - 'mammamia', - 'vanilla1', - 'desiree1', - 'patience', - 'asdfgh12', - '11221122', - 'sasha123', - 'fuckoff2', - 'diosesamor', - 'estrella1', - 'sweet123', - 'Telechargement', - 'cartman1', - 'harrison1', - 'familyguy1', - 'walmart1', - 'elisabeth', - 'carebear', - 'azsxdcfv', - 'luckydog', - 'password99', - 'pingpong', - 'bobby123', - 'qwertyuiop123', - 'castillo', - 'kathleen1', - 'priscilla', - 'killbill', - '8888888888', - 'qweqwe123', - '12345654321', - 'badgirl1', - 'babygirl11', - 'iloveyou8', - 'gianluca', - 'a123456a', - 'onepiece', - 'angel101', - 'iloveyou10', - 'd9Zufqd92N', - 'ultimate', - 'summertime', - 'fussball', - 'jacob123', - 'paradise1', - 'johnjohn', - 'taylor12', - 'martinez1', - 'sparkle1', - 'hunter123', - 'mario123', - 'butterfly7', - 'p4ssw0rd', - 'amanda12', - 'jobsearch', - 'marijuana1', - 'syncmaster', - 'fuckyou13', - 'buster123', - 'squirrel', - 'youbye123', - 'general1', - 'oakland1', - 'converse', - '3children', - 'nounours', - 'babycakes', - 'honey123', - 'airforce', - '01234567', - 'blueberry1', - 'theresa1', - 'emily123', - 'cucciolo', - '19691969', - 'dragon13', - 'nascar24', - 'discovery', - 'darkside', - 'suckmydick', - 'jenny123', - 'yellow12', - 'ilovemusic', - 'annabelle', - 'beyonce1', - 'ashleigh', - '369852147', - 'networking', - 'abcdef123', - '24681012', - 'summer11', - 'mackenzie1', - 'huhbbhzu78', - 'download', - 'national', - '69camaro', - 'ilovegod1', - 'designer', - 'guillaume', - 'business1', - 'iloveher1', - 'socrates', - 'asshole123', - 'soccer18', - 'buffalo1', - 'chargers', - '1truelove', - 'rochelle', - 'student1', - 'voyager1', - 'nokia123', - 'Qwerty123', - 'godisgood1', - 'softball12', - 'j123456789', - 'cooldude1', - 'cheese12', - 'backspace1', - 'nopassword', - 'abdullah', - '87654321q', - 'hellfire', - 'valentine1', - 'domenico', - 'renegade', - 'pikachu1', - 'abracadabra', - '20012001', - 'harmony1', - 'tarheels', - '634142554', - '123mudar', - 'chandler1', - 'hendrix1', - '8ix6S1fceH', - 'packers4', - 'billybob1', - 'qwertyuiop1', - 'stoner420', - 'werewolf', - 'wolfpack', - 'love4life', - 'johannes', - 'tigger123', - 'playboy69', - 'jeffhardy1', - 'brandon2', - 'gsxr1000', - 'qwegta13091990', - 'neopets12', - 'my3girls', - 'faithful', - '12qw34er', - 'cfitymrf', - 'flamengo', - 'caterina', - 'baseball4', - 'westham1', - 'football23', - 'brucelee', - '123456789n', - 'telefono', - 'airborne', - 'smile4me', - 'babylon5', - 'omarion1', - 'buster12', - 'jesuschris', - '3333333333', - 'maximilian', - 'amazing1', - 'attitude', - 'univers2l', - 'shadow11', - 'myspace10', - 'santiago1', - 'fantasy1', - '25251325', - '15426378', - 'bentley1', - 'chargers21', - 'estrellita', - 'primavera', - 'treasure', - 'lorenzo1', - 'soldier1', - 'trigger1', - 'mersedes', - 'milagros', - 'water123', - 'bubbles2', - 'margaret1', - 'samsung123', - 'nokian73', - '1q2w3e4r5t6y7u', - 'yellow123', - 'fernandez', - 'anthony12', - 'doberman', - 'kingdom1', - 'summer10', - 'fuckfuck', - 'coldplay', - 'matematica', - 'clayton1', - 'anaconda', - 'qweasdzxc1', - 'welcome2', - 'godzilla1', - 'whocares', - 'kickass1', - 'katelyn1', - 'felicidad', - '19701970', - 'asdfg12345', - 'fuckyou666', - 'damilola', - 'princess5', - 'sandiego1', - 'angelika', - 'sexylady', - '6666666666', - '123456789g', - 'godfather1', - 'ilovemom1', - 'bangladesh', - 'nascar88', - 'sexybeast1', - 'presario', - 'mohammad', - 'joshua12', - 'dragonfly1', - 'p@ssword', - 'bastard1', - 'johndeere', - 'porkchop', - 'school123', - 'football13', - 'l1nk3d1n', - 'baseball22', - 'moneymaker', - 'mariposa1', - 'babyphat1', - 'iforgot1', - 'london123', - 'eastside', - '1qaz!QAZ', - 'beckham7', - 'mountain1', - 'geraldine', - 'vfhufhbnf', - 'messenger', - '1football', - 'puertorico', - 'michelle2', - 'dfktynbyf', - 'musicman', - 'michael7', - 'sayangku', - 'thomas12', - 'flower12', - 'batman12', - 'senha123', - 'rainbow6', - 'megadeth', - 'excalibur', - 'mississippi', - 'asd12345', - 'viktoriya', - 'александр', - 'information', - 'armagedon', - 'snuggles1', - 'abc123abc', - 'freestyle', - 'security1', - 'shirley1', - 'kakashka', - 'fuckyou22', - 'harrypotte', - 'johndeere1', - 'zxcvbnm12', - '13243546', - 'love2010', - 'elefante', - 'iloveme!', - 'q1234567', - 'wildcat1', - 'doraemon', - 'bitchass1', - '01230123', - 'buckeyes', - 'alexandr', - 'roadrunner', - 'darkstar', - 'p0o9i8u7', - 'international', - 'baby1234', - 'testing123', - 'trumpet1', - 'satellite', - 'mississipp', - 'princesita', - 'iloveyou69', - 'houston713', - 'chicken123', - 'angelique', - 'fucking1', - 'moneyman1', - 'brittney', - 'joker123', - 'ilovemyself', - 'scvMOFAS79', - 'sailormoon', - 'lalalala', - 'marjorie', - 'teamo123', - 'rodriguez1', - 'ilikepie', - 'pussycat1', - 'soccer09', - 'goodgirl', - 'babyface1', - 'sundance', - 'soccer101', - 'виктория', - 'scorpion1', - 'sexy1234', - 'amarillo', - 'cardinal', - 'anything1', - 'mama1234', - 'airplane', - 'angelbaby1', - 'honeybee', - 'hollister2', - 'home0401', - 'tacobell', - 'kisskiss', - 'summer69', - 'cardinals1', - 'tootsie1', - '20202020', - 'paramore1', - 'iloveme123', - 'december12', - 'qwerty13', - 'spartan1', - 'thomas123', - 'myspace08', - 'kendall1', - 'nintendo64', - 'nathaniel1', - 'chelsea123', - 'jordan11', - 'letmein123', - 'evangelion', - 'english1', - 'alexandru', - 'ilovejusti', - 'yfcntymrf', - 'megasecret', - 'babygirl14', - 'supernova', - 'jesusislord', - 'katarina', - 'cristina1', - 'catarina', - 'carlos123', - '1fuckyou', - 'puppy123', - 'lovergirl1', - 'trinity3', - 'jesusis1', - 'christy1', - 'michael3', - 'lawrence1', - 'noodles1', - 'fuckface1', - 'whatsup1', - 'wrangler', - 'babygirl01', - 'family123', - 'bubba123', - 'z1x2c3v4', - 'emmanuel1', - 'television', - 'dannyboy', - 'whiskers', - 'manunited', - 'malaysia', - '7253497a5e31bd64', - 'chivas10', - 'myspace101', - 'qaz123456', - 'chronic1', - 'qwerasdfzxcv', - 'goddess1', - 'baseball21', - '74108520', - 'jimmy123', - 'thirteen13', - 'gameover', - 'happyday', - 'gilbert1', - 'lowrider', - 'jeffhardy', - 'purple11', - 'hondacivic', - 'feder_1941', - 'simpleplan', - 'lovehurts', - 'hellothere', - 'dkflbckfd', - 'alex1234', - 'wow12345', - 'waheguru', - 'halloween1', - 'sanchez1', - 'пїЅпїЅпїЅпїЅпїЅпїЅпїЅпїЅпїЅпїЅпїЅ', - '1й2ц3у', - 'frances1', - 'princesa1', - 'purple13', - 'monkey01', - '123456789w', - 'mobster1', - 'megaman1', - 'iloveyou23', - 'iamnumber1', - 'surside13', - '741258963', - '19711971', - 'gotohell', - 'delphine', - 'kawasaki1', - 'hannibal', - 'stalker1', - 'oklahoma', - 'bordeaux', - 'q2w3e4r5', - 'sunshine7', - 'administrator', - 'manager1', - 'pavilion1', - 'sylvester', - 'orange123', - 'cherry123', - 'coconut1', - 'fredfred', - 'monkey10', - 'thompson', - 'classof201', - 'myspace9', - '123456789e', - 'babybaby', - '????????', - 'santana1', - 'standard', - 'sandy123', - 'orange12', - 'lovesucks1', - 'woodstock', - 'dragon11', - 'chloe123', - 'margarita1', - 'QWERTYUIOP', - 'rootbeer', - 'taylor123', - 'hotmama1', - 'loveyou123', - 'trombone', - 'pepper12', - 'marino13', - 'hockey12', - 'memphis1', - 'matthew2', - 'kayleigh', - 'hollister', - 'dietcoke1', - 'capslock', - '22446688', - 'maggie12', - 'princess14', - 'pirates1', - 'morpheus', - 'lesbian1', - '20022002', - 'alexander2', - '01010101', - 'mexico12', - 'sexymama', - 'lokomotiv', - 'football4', - 's123456789', - 'dangerous', - 'electric', - '827ccb0eea8a706c4c34a16891f84e7b', - '123456789f', - 'freebird', - 'redbull1', - '12345600', - 'penguins', - 'password15', - 'starbucks1', - 'cookiemons', - 'arianna1', - 'whatever!', - 'chipper1', - 'littleman', - '6V21wbgad', - 'kittykat1', - 'tarheels1', - 'emanuele', - 'giggles1', - 'chivas11', - 'trooper1', - 'az123456', - 'smackdown', - 'kangaroo', - 'whiskey1', - 'football8', - 'alexandria', - 'summer01', - 'bangalore', - 'sunny123', - 'brandon123', - 'shitface1', - 'punkrock1', - 'salvation', - 'penis123', - 'umbrella', - 'legolas1', - 'akatsuki', - '123456789h', - '456456456', - 'myfamily', - 'mazda626', - 'jonas123', - 'skater123', - 'faithful1', - 'leonardo1', - 'dont4get', - 'brandon12', - 'qwert1234', - 'polopolo', - 'cheesecake', - '159753456', - 'myspace23', - 'william2', - 'shadow01', - 'junior123', - 'jasmine2', - 'princess21', - 'welcome12', - 'motherfuck', - 'rachael1', - 'ilovemom', - 'kentucky1', - 'souljaboy1', - 'supergirl', - 'qwaszx12', - 'blackjack1', - 'rocknroll1', - 'guillermo', - 'corazon1', - 'mymother', - 'magic123', - 'demon666', - 'qqqq1111', - 'krasotka', - 'yomomma1', - 'bobmarley1', - 'nicole13', - 'johnlock', - 'roxanne1', - 'mother123', - 'friends123', - 'hunter01', - 'robert123', - 'yamahar6', - 'windows7', - 'warriors1', - 'changeme1', - '123йцу', - 'princess22', - '11111111111', - 'passport1', - 'griffin1', - '19051905', - 'kamasutra', - 'brigitte', - '12345678s', - 'никита', - 'gabrielle1', - 'fyutkbyf', - 'salvador1', - '1q1q1q1q', - '123456781', - 'nicole11', - 'biscuit1', - 'kelly123', - 'camille1', - 'myspace22', - 'secret666', - 'pimpdaddy1', - 'birthday1', - 'aaron431', - 'blackdog', - 'babygirl7', - 'monopoly', - 'ilovesex', - 'kayla123', - '123abc123', - '789789789', - '1212121212', - 'frogger1', - 'dancing1', - 'wonderland', - 'joshua123', - 'laura123', - 'matthias', - 'cutie101', - 'fletcher', - 'animals1', - 'daredevil', - 'kristian', - 'shanghai', - 'gabriela1', - 'stratfor', - 'любимая', - 'password09', - 'jeanette', - 'spitfire1', - 'lowrider1', - 'maggie123', - 'komputer', - 'eatshit1', - 'zaqxswcde', - '000webhost', - 'thug4life', - 'grandpa1', - 'annette1', - 'rachelle', - 'ji394su3', - 'dragon69', - 'kentucky', - 'america123', - 'choupette', - 'pokemon2', - 'ichliebedich', - 'shotgun1', - 'prettyboy1', - 'asdfghj1', - 'mittens1', - 'lol12345', - 'forzamilan', - 'positive', - 'dragonballz', - 'software', - 'buckeyes1', - 'bubbles123', - 'мамочка', - 'myspace09', - '19681968', - 'harry123', - 'rooney10', - 'kathryn1', - 'abrakadabra', - 'marihuana', - 'sexsexsex', - 'fabrizio', - 'pallmall', - 'marilyn1', - 'cherokee1', - 'Alexander', - 'Megaparol', - 'liverpool8', - 'mexico10', - 'guardian', - 'infiniti', - 'smackdown1', - 'monster123', - 'hayabusa', - 'qqqqqqqqqq', - 'baseball23', - 'starbucks', - 'seattle1', - 'sassy123', - 'monkey23', - 'clarence', - 'beethoven', - '123456789abc', - 'sunshine3', - 'jupiter1', - 'leavemealone', - 'hurricane', - 'sagitario', - 'guadalupe1', - 'mmmmmmmm', - 'hooters1', - 'justine1', - 'bernardo', - 'starcraft1', - 'lancelot', - 'iloveyou21', - 'stargate1', - 'lolipop1', - 'princess4', - 'vfitymrf', - 'stefanie', - 'maksimka', - 'rootbeer1', - 'mazdarx7', - 'fuckyou5', - '00112233', - 'charlie12', - '123456789.', - 'bubbles12', - 'brendan1', - 'rebound1', - 'passpass', - 'mongoose', - 'camaroz28', - 'jasmine123', - '1234567m', - 'lovingyou', - 'kleopatra', - 'motocross1', - 'unknown1', - 'tazmania', - 'portugal1', - 'original', - 'butterfly3', - 'preciosa', - 'bowling1', - 'diamante', - 'pepsi123', - 'linda123', - '963258741', - 'hercules1', - '4myspace', - 'pandora1', - 'volkswagen', - 'nikki123', - 'nigga123', - 'goldberg', - '123698741', - 'zacefron1', - 'jazmine1', - '1234zxcv', - 'su123456', - 'topsecret', - 'ginger12', - 'teddy123', - 'ashley11', - 'thesims2', - 'asdfzxcv', - 'together', - 'neveragain', - 'soccer19', - 'stranger', - 'samantha12', - 'colleen1', - 'ilovechris', - 'любимый', - 'bellissima', - 'peterpan1', - 'principessa', - 'titanic1', - 'pornstar1', - 'asdasd666', - 'ms0083jxj', - 'joejonas1', - 'jordan01', - 'myspace69', - 'lillian1', - 'vancouver', - 'missy123', - 'dance123', - 'butthole1', - 'nacional', - '12312312', - 'bkl29m2bk', - '14141414', - 'pothead420', - 'start123', - 'babyboy2', - 'mylove123', - 'yolanda1', - 'capricorn1', - 'linkedin2011', - 'stingray', - 'qazxsw123', - 'hannah01', - 'mustang2', - 'iloveyou9', - 'gonzales', - 'moonlight1', - 'fgtkmcby', - '1sunshine', - 'swimmer1', - 'mylinkedin', - 'dirtbike', - 'asdfghjkl123', - 'incorrect', - 'wallace1', - 'skater12', - 'daniel01', - 'dorothy1', - 'techn9ne', - 'loredana', - 'jesussaves', - 'moneyman', - 'lalala123', - 'a1s2d3f4g5', - 'brayden1', - 'azertyui', - '1234rewq', - 'myspace8', - 'princess23', - 'chivas123', - '5plK4L5Uc7', - '12qw12qw', - 'sandman1', - 'punisher', - 'longhorn', - 'konstantin', - 'bigpimpin1', - 'bearbear', - 'slipknot6', - 'panda123', - 'fuckyou11', - 'lonewolf', - 'charger1', - 'king1234', - 'iloveu12', - 'megan123', - 'temp1234', - '', - 'gretchen', - 'josefina', - 'escorpion', - 'robert12', - 'register', - 'cleveland', - 'director', - 'senior09', - 'computer2', - 'septiembre', - 'gandalf1', - '1234567j', - 'married1', - 'marcello', - 'jesusfreak', - 'shakira1', - 'chickens', - 'allah786', - 'redwings1', - 'mickeymouse', - 'mallorca', - 'fallout3', - 'jordan13', - 'madeline1', - 'mV46VkMz10', - 'koolaid1', - 'cardinals', - 'gamecube', - 'austin12', - 'felicia1', - 'dominican1', - 'bighead1', - 'darkangel1', - 'ilove123', - 'cowgirl1', - 'iamcool1', - 'mission1', - 'respect1', - 'portland', - 'armando1', - 'love2009', - 'columbia', - 'anthony3', - 'stunt101', - 'qwerty77', - 'november11', - 'isabelle1', - 'celeste1', - 'madagascar', - 'celtic1888', - 'madeleine', - 'bugsbunny', - 'ashley13', - '1iloveyou', - 'ohiostate1', - 'argentina1', - 'meredith', - 'simpson1', - 'finalfantasy', - 'madison2', - 'jeffery1', - 'computador', - 'basketbal1', - 'slipknot66', - 'coolgirl', - 'durango1', - 'cavalier', - 'meowmeow', - 'babygirl15', - 'ordinateur', - 'Pa55word', - 'children3', - 'incubus1', - 'woaiwojia', - 'freckles1', - '15151515', - '123456aaa', - 'honduras', - 'maryjane42', - 'warszawa', - 'babygirl5', - 'fabulous', - '1qazzaq1', - '20052005', - '123321123321', - '3.1415926', - 'coolman1', - 'teamomucho', - 'shorty13', - 'rammstein1', - 'qwerty777', - 'longhorns', - 'happydays', - 'anthony7', - 'mileycyrus', - 'harley01', - 'showtime', - 'asd123asd', - 'z123456789', - '123123123123', - 'abcde123', - 'пїЅпїЅпїЅпїЅпїЅпїЅ@mail.ru', - 'superman7', - 'ilovehim2', - 'google12', - 'imnumber1', - 'guitarra', - 'baseball24', - 'secret123', - 'mariana1', - 'pepper123', - 'girasole', - 'classic1', - 'multiplelog', - 'myspace0', - 'tabitha1', - 'deftones1', - 'gizmo123', - 'cheer123', - 'lifesucks1', - 'Jennifer', - 'valentina1', - 'bernard1', - 'Abcd1234', - 'babygurl12', - 'peekaboo', - 'fuckface', - 'violetta', - 'mechanical', - 'daniel11', - '168ASD168', - 'sexylove1', - 'redalert', - 'shorty12', - '12211221', - 'chivas12', - 'budweiser', - 'superman3', - 'iverson1', - 'bumblebee', - 'arsenal123', - 'newpassword', - 'ginger123', - 'bigmoney', - 'yankees13', - '1357913579', - 'wellington', - 'bunny123', - '789632145', - 'braveheart', - 'mercury1', - 'puppylove1', - 'wonderful1', - '1234567s', - 'venezuela', - 'hellsing', - 'revenge1', - 'applepie1', - 'racecar1', - '7777777a', - 'monkey101', - 'monkey21', - 'cleveland1', - 'blueeyes1', - 'bigmoney1', - '123456789qwe', - 'password88', - 'junebug1', - '19991999', - 'music101', - '19031903', - 'sterling1', - 'lemonade', - 'tigger01', - '1z2x3c4v', - '!qaz2wsx', - 'dortmund', - 'gorgeous1', - 'hustler1', - 'hockey11', - 'school12', - 'il0veyou', - 'cantona7', - 'W5tn36alfW', - 'vendetta', - 'waterloo', - 'lightning1', - 'technics', - 'spectrum', - 'princess15', - 'playboy123', - 'thankgod', - 'georgina', - 'broadway', - 'demon123', - 'central1', - 'babygirl09', - 'crip4life', - 'peanut12', - 'maradona10', - 'lifesucks', - 'clifford1', - 'chicken12', - 'ashleigh1', - 'michael23', - 'Michael1', - '1qay2wsx', - '19671967', - 'tenerife', - 'teiubesc', - 'mckenzie', - 'firebird1', - 'baseball14', - 'annamaria', - 'chemical', - 'testing1', - 'beckham23', - 'steelers7', - 'miracle1', - 'mariajose', - 'coolkid1', - 'c.ronaldo', - 'pooppoop', - 'softball11', - 'heineken', - '1111qqqq', - 'qwertyui1', - 'emachines1', - 'brighton', - 'abcd123456', - 'password24', - 'baseball6', - 'jasmine12', - 'grizzly1', - 'cruzazul', - 'babygirl!', - 'northside1', - 'addison1', - '1й2ц3у4к', - 'senior08', - 'youtube1', - 'carlotta', - 'stewart1', - 'scooter2', - '0192837465', - 'joshua01', - 'theused1', - 'qdujvyG5sxa', - 'pakistan123', - 'oklahoma1', - 'valeria1', - 'candice1', - 'overlord', - '1michael', - 'rockets1', - 'porcodio', - 'imperial', - 'astonvilla', - 'emiliano', - 'tommy123', - 'aerosmith', - 'tequila1', - 'briciola', - 'helloworld', - 'jermaine1', - 'semperfi1', - 'dylan123', - 'sonyericsson', - 'nokia5800', - 'junior12', - 'qwer4321', - 'diamond2', - 'lionheart', - 'sullivan', - 'money100', - 'chevys10', - 'mickey12', - 'jessica3', - 'qwerty78', - 'left4dead', - 'ssyu1314', - 'football!', - '111111111111', - 'bookmark', - '123456abcd', - 'daniel13', - 'classof200', - 'aaron123', - 'sexyman1', - 'lamborghini', - 'honduras1', - 'rainbows', - 'jillian1', - '159159159', - 'westwood', - 'girlfriend', - 'zaqwsxcde', - 'sunshine!', - 'mustangs', - 'noisette', - 'margherita', - 'coolcool', - 'password19', - 'password08', - 'mickey123', - '123qwe123qwe', - 'highlander', - '19651965', - 'passwords', - 'network1', - 'thirteen', - 'ilikepie1', - 'zeppelin1', - 'whatever2', - 'nicole01', - 'chelseafc', - 'blossom1', - 'xboxlive', - 'lampard8', - 'football24', - 'daniella', - 'myspace07', - 'classof07', - '20072007', - 'daughter', - '333333333', - 'adventure', - 'football20', - 'andrew11', - 'chevrolet1', - '100200300', - 'softball2', - '45454545', - '.adgjmptw', - '23jordan', - 'poptart1', - 'evergreen', - 'compton1', - 'fuckthis', - 'forzaroma', - '123456789v', - 'm1234567', - 'glitter1', - 'aaaaaaa1', - '0147258369', - 'guatemala', - 'iloveyou09', - 'cheetah1', - 'friends!', - 'shorty123', - 'cadillac1', - 'lorraine1', - 'carlos12', - 'webster1', - 'lifeisgood', - 'Michelle', - 'softball10', - 'theodore', - 'rdfhnbhf', - 'starlight1', - 'softball7', - 'zzzzzzzzzz', - 'justin11', - 'poseidon', - 'jennifer12', - 'october10', - 'godislove1', - 'chivas13', - 'peanuts1', - 'clarinet', - 'italian1', - 'supergirl1', - 'silverado', - 'lenochka', - 'iloveyou15', - '12345qwer', - 'commando', - 'porkchop1', - 'stanislav', - 'music4life', - 'qwerty69', - 'meandyou', - 'cucciola', - 'munchkin1', - 'george123', - 'flipper1', - 'whiteboy1', - 'nightwish', - 'principe', - 'ilovemysel', - '1234567z', - 'front242', - 'пїЅпїЅпїЅпїЅпїЅпїЅпїЅпїЅпїЅпїЅпїЅпїЅ', - 'fuckyou6', - 'iloveyou6', - 'handsome1', - 'coolcat1', - '159753123', - 'twilight12', - 'lipgloss1', - 'christian2', - 'aptx4869', - 'crjhgbjy', - 'forzainter', - 'freedom2', - 'soccer08', - 'halflife', - 'myspace200', - 'contraseña', - 'albatros', - 'killer13', - 'princess8', - 'livelife', - 'budweiser1', - 'jackjack', - 'emerald1', - 'blessings', - 'solomon1', - 'soccer07', - 'holland1', - '14881488', - 'resident', - 'zxcvb123', - 'control1', - 'bailey12', - 'america12', - 'justin01', - 'hannah11', - 'rainbow123', - 'a1a2a3a4a5', - '123456qwer', - 'йцукенгшщз', - 'thomas22', - 'ssssssss', - 'jessica7', - 'myspace201', - 'dr.pepper', - 'blackbird', - 'kittens1', - 'jamesbond007', - 'dfg5Fhg5VGFh1', - 'seniseviyorum', - 'babygirl21', - 'friends4', - 'pistons1', - '12345677', - 'fuckyou4', - 'spanish1', - 'confused1', - 'tecktonik', - 'geoffrey', - 'candycane1', - '1a2a3a4a', - 'bengals1', - 'abcdefgh1', - 'p455w0rd', - 'jessica13', - '1diamond', - 'love2008', - 'chivas100', - 'bernadette', - '22334455', - 'veronique', - 'martin123', - 'cecilia1', - 'brothers', - 'baseball15', - 'plymouth', - 'enrique1', - 'summer05', - 'killer11', - 'jerusalem', - 'cowboys22', - 'simon123', - 'XBLhInTB9w', - 'tomorrow', - 'spartans', - 'alexis12', - '476730751', - 'notebook', - '7uGd5HIp2J', - '456789123', - 'giovanni1', - '00001111', - 'soccer01', - 'matthew12', - 'barbie123', - 'huskers1', - 'babygirl23', - 'youandme', - 'history1', - 'babygirl16', - 'frederick', - 'maggie01', - 'softball13', - 'leonard1', - 'beautiful2', - 'clemson1', - 'kobebryant', - 'password20', - 'princess16', - 'paperino', - 'sephiroth', - 'innocent', - '73501505', - 'chronic420', - 'J1V1fp2BXm', - 'iloveyouba', - 'home1234', - 'gabriel123', - 'jamie123', - 'nokian70', - 'clarissa', - 'juventus1', - 'martina1', - 'lineage2', - 'football09', - 'ashley01', - 'goodbye1', - 'zxc123456', - 'hamilton1', - 'cristian1', - 'couponSC10', - 'woaini520', - 'murcielago', - 'buster01', - 'ncc1701a', - 'medicina', - 'fighter1', - 'soccer24', - 'soccer20', - 'milkshake1', - 'buckeye1', - 'maurizio', - '123admin321', - 'atlars10', - 'mikejones1', - '85208520', - '123stella', - 'florencia', - '5X1CJdsb9p', - 'winchester', - 'nigger123', - 'йцукенгшщзхъ', - 'gertrude', - 'aa123123', - 'sadie123', - 'freeman1', - '123456zxc', - 'vacation1', - 'il0vey0u', - 'bugsbunny1', - 'rastafari', - '20062006', - 'hardrock', - 'mallory1', - '1234509876', - 'jocelyn1', - 'explorer1', - 'lovergirl', - 'password07', - 'loveislife', - 'jesse123', - 'confused', - 'nokian95', - 'account1', - 'marseille13', - 'hurricane1', - 'qwerty666', - '20112011', - 'scarlett1', - 'daniel19', - 'pinkfloyd1', - 'mark_963', - 'livestrong', - '1butterfly', - 'twister1', - 'billy123', - 'lovely12', - 'hm9958123', - 'asshole12', - 'a1a1a1a1', - 'francine', - 'family12', - 'bigfoot1', - 'amsterdam1', - 'morning21', - 'redsox04', - 'gfhjkm123', - 'qwerty22', - 'tatiana1', - 'september9', - 'bailey123', - 'dragon01', - '@bigmir.net', - 'superfly', - 'manning18', - 'number12', - 'ilovehim!', - '1anthony', - '14121412', - 'mazda323', - 'melinda1', - '19641964', - 'tkbpfdtnf', - 'starfish1', - 'princess9', - 'candyman', - 'butterfly8', - 'terrell1', - 'jingjing', - 'rockstar12', - '010203040506', - 'panasonic1', - 'hotpink1', - 'aaaaaaaaa', - 'almighty', - '??????@mail.ru', - 'bridget1', - 'macarena', - '1q2w3e4r5t6y7u8i', - 'sexybaby1', - 'andrew01', - 'scarlet1', - 'meatball', - 'какашка', - 'qwertyuiop12', - '1qazxsw23edc', - 'малышка', - '1213141516', - 'Princess', - 'training', - 'hunter11', - 'buddyboy', - '24242424', - 'charlie3', - 'lovely123', - 'raiderz1', - 'deadman1', - 'mystery1', - 'shooter1', - 'pauline1', - 'linkedln', - 'disturbed', - '786786786', - 'adelaide', - '1zxcvbnm', - 'bluebird1', - 'rjycnfynby', - 'nursing1', - 'hgrFQg4577', - 'lauren12', - 'monkeybutt', - 'joseph12', - 'sophie123', - 'booboo12', - 'friends12', - 'deborah1', - 'lexmark1', - 'keyboard1', - 'remington', - 'babygirl69', - 'barbados', - 'xiaoxiao', - 'ghjcnjnfr', - 'blizzard1', - 'landrover', - 'italiano', - 'd123456789', - 'fireball1', - '5845201314', - 'matthew123', - 'london22', - 'davidson', - 'blackrose', - 'banana11', - 'sidekick3', - 'babygirl22', - 'arsenal14', - 'annalisa', - 'thomas01', - 'milkshake', - 'michael11', - 'gorilla1', - 'surenos13', - 'princess19', - 'newstart', - 'malcolm1', - 'mollydog', - 'football14', - '1234567890-=', - 'ilovepussy', - 'christelle', - 'extreme1', - 'trojans1', - 'hottie13', - 'концертных площадок и умных студентов:', - 'chocolate!', - 'sherman1', - 'nathan12', - 'comcast1', - 'W1aUbvOQ', - 'sparkles', - 'waterfall', - 'oliveira', - 'password17', - 'jordan10', - 'rbOTmvZ954', - 'raffaele', - 'navigator', - 'playstation3', - 'cellphone', - '135798642', - 'summer2010', - 'monkey14', - 'password16', - 'yangyang', - 'spike123', - 'qwerty01', - 'andrea123', - '123456789*', - 'millwall', - 'booboo123', - 'radiohead1', - 'deutschland', - 'dt123456', - 'william123', - 'naruto11', - 'softball3', - 'krokodil', - 'honda123', - 'darkstar1', - 'caramel1', - '2children', - 'love4you', - 'carolyn1', - 'Blink123', - 'ncc1701e', - 'koroleva', - 'love5683', - 'michael13', - 'jermaine', - 'monster2', - 'mike1234', - 'fucklove13', - 'firefly1', - 'peanut123', - 'slimshady1', - 'ihateyou!', - 'holly123', - 'liverpool9', - 'hospital', - 'cepetsugih', - 'blueblue', - 'shamrock1', - 'mickeymous', - 'rjhjktdf', - '19631963', - 'Sunshine', - 'babygirl4', - 'homework', - 'converse1', - 'cookies123', - 'twinkle1', - 'opelastra', - 'mother12', - 'badminton', - 'fucku123', - 'cinnamon1', - 'qw123456', - 'iloveher', - 'myspace21', - 'makaveli1', - 'julia123', - 'crosby87', - 'princess09', - 'yfdbufnjh63', - 'nissan350z', - 'victoire', - 'nathan123', - 'contrasena', - 'babygirl19', - 'cherry12', - 'knights1', - 'asshole!', - 'google.com', - 'bossman1', - 'snoopdog', - 'apples123', - 'purple22', - 'password77', - 'killer666', - 'fisherman', - 'besiktas', - 'Sample123', - 'babatunde', - 'godsmack', - 'believe1', - 'skinhead', - '21122112', - 'kochanie', - 'fuckyoubit', - 'bushido1', - 'pedro123', - 'mandarin', - 'champagne', - 'august12', - 'asd666fds', - 'gameboy1', - 'evanescence', - '123456qaz', - 'qwertasdfg', - 'pancakes', - '123456798', - 'marcella', - 'diego123', - 'skywalker1', - 'michaeljac', - 'ячсмит', - 'theboss1', - 'pokemon11', - '01011980', - 'fxzZ75yer', - '19661966', - 'tweetybird', - 'banana123', - 'vittoria', - 'harley123', - 'fantasia', - 'bmx4life', - 'summer99', - 'fuckyou8', - 'greenday12', - 'zxcasdqwe123', - 'familia1', - 'taylor01', - 'cutegirl', - 'carmelo15', - 'wsbe279qSG', - 'professor', - 'password33', - 'frank123', - 'zxcvb12345', - 'liverp00l', - 'gymnast1', - 'donovan1', - 'forever2', - 'sasuke12', - 'nicole22', - 'woaini123', - 'pudding1', - 'rhiannon', - 'poochie1', - 'everything', - 'guerrero', - 'fuckme123', - 'blackops', - 'bangbang', - 'pioneer1', - 'bailey01', - 'Letmein1', - 'thursday', - 'survivor', - 'polaris1', - 'anarchy1', - 'pharmacy', - 'matilda1', - 'liverpool2', - 'star1234', - '666666666', - 'Charlie1', - 'francisca', - 'dallas22', - 'coolguy1', - 'myspace14', - '1superman', - 'ilovemybab', - 'dominika', - 'badbitch1', - 'grace123', - 'heartbreak', - 'dragon88', - 'pasword1', - 'guatemala1', - 'offspring', - 'diablo666', - 'malachi1', - 'ромашка', - 'klapaucius', - 'guinness1', - 'grenouille', - 'lovehate', - 'ludacris', - 'lalaland', - 'october31', - 'prashant', - 'mitsubishi', - 'stallion', - 'kirsten1', - 'abcd12345', - 'babygirl08', - 'blingbling', - 'blackman', - 'princess08', - 'lovesucks', - 'iloveyou08', - '123soleil', - 'yellow11', - 'mash4077', - '987654321q', - '1q2w3e4r5t6y7u8i9o', - 'suzanne1', - 'mermaid1', - 'cherries', - 'bookworm', - 'mynameis', - '1a2s3d4f5g', - 'pinky123', - 'logan123', - '2bornot2b', - 'wednesday', - 'monster12', - 'thedoors', - 'emerica1', - 'darling1', - 'ghblehjr', - 'bonjour1', - 'miguelito', - 'austin123', - 'pr1ncess', - 'poohbear12', - 'nickjonas', - 'andre123', - 'harley12', - '12345678m', - 'richmond1', - 'juanita1', - 'anuradha', - 'pimping1', - 'andreas1', - 'elsalvador', - '555555555', - 'sweety12', - 'maryland', - 'aberdeen', - 'snoopy123', - 'moncoeur', - 'springer', - 'nssadmin', - 'goodness', - 'battlefield', - 'hottie11', - '12345678900987654321', - 'butthole', - '456123789', - 'printer1', - 'destiny2', - 'сергей', - 'tropical', - 'raiders13', - '56565656', - 'bionicle', - 'sexyback1', - '246813579', - 'theonly1', - 'rainbow2', - 'playstatio', - 'jefferson1', - 'telephone1', - 'novembre', - 'saturday', - 'godofwar', - 'asdfghjk1', - 'pasquale', - 'sinaloa1', - 'mushroom1', - 'sunshine11', - 'pazzword123', - 'fkbyjxrf', - 'fabregas', - 'cambridge', - 'michael01', - 'mamochka', - 'alexis09', - 'superman11', - 'gymnastics', - 'number11', - 'lucifer666', - 'kIkeunyw', - 'fuckyou9', - 'brian123', - '123asd123', - 'солнце', - 'lucky777', - 'owt243yGbJ', - 'lovestory', - 'casey123', - '147896321', - 'kifj9n7bfu', - 'fuckoff123', - 'nicole21', - 'asdffdsa', - 'puppylove', - 'edward123', - 'cutiepie12', - 'k2010302', - 'jaihanuman', - 'alfred19', - 'zimmer483', - 'robinhood', - 'qwerty789', - 'fabienne', - 'michael5', - 'paper123', - 'penelope1', - 'morgan12', - 'iloveyou16', - 'john!20130605at1753', - 'ichliebedi', - 'dynasty1', - 'catwoman', - 'password18', - 'toronto1', - '1qaz!qaz', - 'bonehead', - 'yourmom2', - 'thegreat1', - 'rencontre', - 'caliente', - 'desperado', - 'rhjrjlbk', - 'luckydog1', - 'elliott1', - 'stonecold1', - 'ячсмить', - 'crevette', - 'wedding1', - 'a1111111', - 'margarida', - 'daniel10', - 'joseph123', - 'ghostrider', - 'j1234567', - 'america2', - 'pretty12', - 'zanzibar', - 'Eh1K9oh335', - 'montecarlo', - 'kakashi1', - 'seventeen', - 'peaceout', - 'oranges1', - '123456789A', - 'filomena', - 'anthony13', - 'monkeyman1', - 'marcopolo', - 'babygirl20', - 'pokerface', - 'diciembre', - 'baller12', - 'technology', - 'babygirl9', - 'kingking', - 'littlebit1', - '2wsx3edc', - '20032003', - '1234567b', - 'sharingan', - 'taylor11', - 'concrete', - 'armstrong', - 'superman23', - 'chivas#1', - 'twisted1', - '1234567d', - 'bradford', - 'candygirl1', - 'alfredo1', - 'aaa123123', - 'ballin23', - '2blessed', - 'tweety12', - 'blaze420', - 'myspace6', - 'Benjamin', - '19411945', - 'lobster1', - 'jonathan12', - 'pimpin69', - 'mustang69', - '1chicken', - 'sunderland', - 'trinidad1', - 'ramones1', - 'borussia', - 'underoath1', - 'Linkedin1', - 'splinter', - 'littlebit', - 'justin13', - 'trenton1', - 'running1', - 'management', - 'priscilla1', - 'progress', - 'chicken!', - 'philips1', - 'diana123', - 'qazqazqaz', - 'екатерина', - 'bretagne', - 'hahaha123', - 'sneakers', - 'faith123', - 'angelbaby', - '19191919', - '12348765', - 'patriots12', - 'matthieu', - 'a987654321', - 'supermario', - 'алексей', - 'bryant24', - '78963214', - 'minouche', - 'dontforget', - 'crepusculo', - 'nicole14', - 'children1', - 'texas123', - 'november19', - 'qaz123wsx', - 'mmmmmmmmmm', - 'angelita', - 'startrek1', - 'hedgehog', - 'hooligan', - 'k123456789', - 'eminem123', - 'tennessee1', - 'qwerty21', - 'children2', - 'greenbay', - '?????????', - 'samuel01', - 'edward12', - '51505150', - 'budapest', - 'jesus4me', - 'colocolo', - '123456789i', - 'cupcakes', - 'pretty123', - 'password12345', - '4children', - 'gamecube1', - 'fabulous1', - 'TOPBUTTON', - '319f4d26e3c536b5dd871bb2c52e3178', - 'pontiac1', - 'ferdinand', - 'makemoney', - 'xxxxxxxxxx', - 'my.space', - 'darlene1', - 'angeline', - 'naruto13', - 'Liverpool', - 'mckenzie1', - 'ghbywtccf', - 'loveyou12', - 'mikemike', - 'boobear1', - 'rainbow7', - 'qwertzuiop', - '♥', - 'selenagome', - 'nokia3310', - 'Password01', - 'brandon3', - 'mostwanted', - 'alexis123', - 'polarbear', - 'pleasure', - 'charlie7', - 'pink1234', - 'pringles', - 'estefania', - 'hockey10', - 'buster11', - 'solution', - 'jasmine3', - 'football33', - 'boomboom1', - 'nicole10', - 'bautista', - 'football6', - 'august11', - 'aaa123456', - '9293709b13', - 'sonic123', - 'fordf250', - 'tigger11', - 'romance1', - 'bonjovi1', - 'birdman1', - 'aardvark', - 'florence1', - 'sexygurl1', - 'discover', - 'hogwarts', - 'eldorado', - '1qaz@wsx', - 'anthony5', - '4444444444', - 'tinkerbel1', - 'mygirls2', - 'marlene1', - 'fantastic', - 'Passwort', - 'supersonic', - 'ilovejosh1', - 'crackhead1', - 'snoopdogg', - 'senior07', - 'illusion', - 'sharpie1', - '0147852369', - '74107410', - '12345qaz', - 'whiskers1', - 'shinigami', - 'fuckmylife', - 'alexalex', - 'danielle12', - 'jack1234', - '123456789asd', - 'belinda1', - 'mazafaka', - 'cachorro', - 'desmond1', - 'sasuke123', - 'johanna1', - 'impossible', - 'deerhunter', - 'chihuahua', - 'grandkids', - 'samurai1', - 'architect', - 'wishbone', - 'liliana1', - 'killers1', - 'светлана', - 'tequiero1', - 'vanhalen', - 'robinson1', - 'joshua11', - 'guilherme', - 'blessing1', - 'michael!', - '8PHroWZ624', - 'matthew3', - 'bluesky1', - 'rosemary1', - 'lover101', - 'chocolate7', - '20082009', - 'loveless1', - 'iw14Fi9jwQa', - '1melissa', - 'taylor13', - 'fuckoff69', - 'football08', - 'columbus', - 'weare138', - 'hotchick1', - 'operator', - 'longhorn1', - 'goodlife', - 'underworld', - 'qwerty99', - 'tricolor', - 'falcons7', - 'yahoomail', - 'fuckyou23', - 'superman13', - 'smokeweed1', - 'kayleigh1', - 'sarajevo', - 'patrizia', - 'kathmandu', - 'iloveyou<3', - 'fullmoon', - 'crimson1', - 'andrea12', - '1111111a', - 'iloveyou01', - 'capoeira', - '123123qwe', - 'momanddad', - 'friends4ev', - 'charlie01', - 'bigsexy1', - 'sailboat', - '25800852', - 'aaaaaaaaa1', - 'poop1234', - 'minicooper', - 'jessica11', - 'promise1', - 'chocolate3', - 'aurelien', - 'gribouille', - 'creation', - '4everlove', - 'dutchess', - 'steven123', - 'starstar', - 'fearless', - 'anonymous', - 'crusader', - 'downtown', - 'achilles', - 'firewall', - 'canadian', - 'cucumber', - 'sheridan', - 'wireless', - 'atlantic', - 'wildfire', - 'highland', - 'alphabet', - 'webmaster', - 'question', - 'nebraska', - 'bullseye', - 'valhalla', - 'criminal', - 'crackers', - 'insomnia', - 'terminal', - 'paranoid', - 'doomsday', - 'reynolds', - 'magician', - 'intrepid', - 'dynamite', - 'username', - 'sherwood', - 'moonbeam', - 'honolulu', - 'crawford', - 'southern', - 'sunlight', - 'cerberus', - 'republic', - 'recovery', - 'intruder', - 'hastings', - 'goldstar', - 'commander', - 'blackout', - 'yesterday', - 'phillips', - 'monsters', - 'keystone', - 'grateful', - 'continue', - 'triangle', - 'peterson', - 'mandrake', - 'hardware', - 'ferguson', - 'dominick', - 'bullfrog', - 'transfer', - 'shepherd', - 'property', - 'pictures', - 'mischief', - 'macintosh', - 'daffodil', - 'charming', - 'underdog', - 'alliance', - 'adrienne', - 'sentinel', - 'richards', - 'mortimer', - 'magazine', - 'infantry', - 'hopeless', - 'fandango', - 'deadhead', - 'christie', - 'billyboy', - 'absolute', - 'titanium', - 'superior', - 'spaceman', - 'somebody', - 'sinclair', - 'pppppppp', - 'military', - 'felicity', - 'brewster', - 'valkyrie', - 'chuckles', - 'saratoga', - 'majestic', - 'kingfish', - 'japanese', - 'graphics', - 'flounder', - 'coltrane', - 'checkers', - 'augustus', - 'washburn', - 'stanford', - 'rasputin', - 'overkill', - 'meatloaf', - 'eastwood', - 'dominion', - 'destroyer', - 'chipmunk', - 'berkeley', - 'thinking', - 'seminole', - 'platypus', - 'mephisto', - 'lancaster', - 'knowledge', - 'darklord', - 'carnival', - 'blowfish', - 'sandwich', - 'knuckles', - 'benedict', - 'sprinter', - 'moonshine', - 'missouri', - 'meridian', - 'gargoyle', - 'disaster', - 'complete', - 'claymore', - 'chainsaw', - 'bluebell', - 'thunderbird', - 'smashing', - 'playtime', - 'lonestar', - 'heritage', - 'forsaken', - 'challenger', - 'backdoor', - 'yosemite', - 'yogibear', - 'talisman', - 'syracuse', - 'randolph', - 'raistlin', - 'preacher', - 'millions', - 'metallic', - 'dontknow', - 'charisma', - 'sinister', - 'mcdonald', - 'goldeneye', - 'frontier', - 'flipflop', - 'eggplant', - 'chrysler', - 'buckshot', - 'arkansas', - 'archangel', - 'romantic', - 'robotics', - 'megatron', - 'hyperion', - 'hamburger', - 'friendly', - 'dreaming', - 'doghouse', - 'christin', - 'addicted', - 'negative', - 'computers', - 'chestnut', - 'auckland', - 'wanderer', - 'tomahawk', - 'thanatos', - 'roderick', - 'pentagon', - 'millenium', - 'mechanic', - 'creature', - 'cornwall', - 'chadwick', - 'calendar', - 'supervisor', - 'revolver', - 'railroad', - 'minnesota', - 'mariners', - 'holyshit', - 'database', - 'bobafett', - 'amethyst', - 'albatross', - 'advanced', - 'whistler', - 'slamdunk', - 'sheffield', - 'scrabble', - 'roadkill', - 'obsidian', - 'northern', - 'learning', - 'independent', - 'elements', - 'electron', - 'customer', - 'brisbane', - 'baritone', - 'armageddon', - 'windmill', - 'surprise', - 'starfire', - 'speakers', - 'lifetime', - 'fredrick', - 'fidelity', - 'everyday', - 'coolness', - 'concorde', - 'blackhawk', - 'traveler', - 'potatoes', - 'pipeline', - 'pathfinder', - 'monterey', - 'lipstick', - 'lakeside', - 'fishbone', - 'biohazard', - 'windsurf', - 'velocity', - 'vagabond', - 'reloaded', - 'raindrop', - 'prudence', - 'peaceful', - 'multimedia', - 'montgomery', - 'marietta', - 'ladybird', - 'internal', - 'gigabyte', - 'fourteen', - 'chambers', - 'bunghole', - 'apocalypse', - 'aphrodite', - 'zerocool', - 'wrestler', - 'tortoise', - 'sysadmin', - 'starship', - 'primrose', - 'politics', - 'paranoia', - 'overload', - 'nevermore', - 'melbourne', - 'matthews', - 'marriage', - 'macaroni', - 'jonathon', - 'infinite', - 'heinrich', - 'graduate', - 'godspeed', - 'feedback', - 'cornelia', - 'corleone', - 'choochoo', - 'challenge', - 'chairman', - 'barracuda', - 'accounting', - 'sleeping', - 'quicksilver', - 'paradigm', - 'nickolas', - 'nautilus', - 'feathers', - 'aviation', - 'avalanche', - 'wildwood', - 'thrasher', - 'speedway', - 'songbird', - 'sickness', - 'screamer', - 'riverside', - 'princeton', - 'manhattan', - 'ambrosia', - 'adrianna', - 'spaghetti', - 'slapshot', - 'ministry', - 'lighting', - 'helsinki', - 'frederik', - 'flexible', - 'festival', - 'daydream', - 'coventry', - 'constant', - 'connection', - 'woodland', - 'signature', - 'rockford', - 'merchant', - 'greatest', - 'everlast', - 'espresso', - 'elizabet', - 'dddddddd', - 'community', - 'charlton', - 'stronger', - 'starbuck', - 'skeleton', - 'scissors', - 'reginald', - 'redeemer', - 'normandy', - 'laserjet', - 'graffiti', - 'doughboy', - 'building', - 'bbbbbbbb', - 'annabell', - 'alchemist', - 'zimbabwe', - 'wisconsin', - 'tunafish', - 'thisisit', - 'stafford', - 'spalding', - 'sometimes', - 'solitude', - 'robotech', - 'minister', - 'leonidas', - 'kirkland', - 'integral', - 'incognito', - 'ignatius', - 'heavenly', - 'gggggggg', - 'exchange', - 'winfield', - 'thriller', - 'sausages', - 'salamander', - 'printing', - 'palmtree', - 'opendoor', - 'mosquito', - 'milkyway', - 'mcdonalds', - 'laughter', - 'klondike', - 'kingsley', - 'invisible', - 'humphrey', - 'hillside', - 'hattrick', - 'hammerhead', - 'function', - 'forgotten', - 'fighting', - 'excellent', - 'delaware', - 'darthvader', - 'costello', - 'catalyst', - 'assholes', - 'andersen', - 'alexande', - 'whiplash', - 'solutions', - 'rockwell', - 'reddevil', - 'glendale', - 'foxylady', - 'fortress', - 'favorite', - 'doughnut', - 'comanche', - 'cheshire', - 'bertrand', - 'barefoot', - 'arabella', - 'alligator', - 'vanguard', - 'stuttgart', - 'rhapsody', - 'reckless', - 'powerful', - 'painting', - 'nocturne', - 'nickname', - 'llllllll', - 'leighton', - 'kingfisher', - 'johnston', - 'holidays', - 'henderson', - 'handyman', - 'flamenco', - 'escalade', - 'division', - 'covenant', - 'churchill', - 'cannibal', - 'annmarie', - 'alcatraz', - 'wwwwwwww', - 'wildcard', - 'whitesox', - 'thornton', - 'temporary', - 'survival', - 'supernatural', - 'sprocket', - 'somerset', - 'services', - 'saxophone', - 'sacrifice', - 'restless', - 'pumpkins', - 'operation', - 'nosferatu', - 'meathead', - 'licorice', - 'language', - 'generation', - 'flanders', - 'edinburgh', - 'disciple', - 'diplomat', - 'crescent', - 'counterstrike', - 'catholic', - 'calculator', - 'browning', - 'biscuits', - 'violator', - 'tangerine', - 'straight', - 'sorcerer', - 'sidekick', - 'shredder', - 'schubert', - 'prestige', - 'nonsense', - 'mulligan', - 'matchbox', - 'marauder', - 'longhair', - 'lisalisa', - 'islander', - 'grasshopper', - 'gardenia', - 'edmonton', - 'downhill', - 'cromwell', - 'chowchow', - 'terrapin', - 'tennessee', - 'stockton', - 'spartacus', - 'smoothie', - 'seahawks', - 'revelation', - 'puppydog', - 'marigold', - 'gregorio', - 'goldfinger', - 'gangbang', - 'daylight', - 'constantine', - 'clueless', - 'calamity', - 'beefcake', - 'aquarium', - 'anathema', - 'ambition', - 'wildlife', - 'undercover', - 'snowbird', - 'schneider', - 'prospect', - 'pendragon', - 'lockdown', - 'jellyfish', - 'irishman', - 'infamous', - 'hydrogen', - 'hartford', - 'goodyear', - 'generals', - 'garrison', - 'foxhound', - 'entrance', - 'eighteen', - 'dimension', - 'daedalus', - 'cocktail', - 'chameleon', - 'caligula', - 'borabora', - 'behemoth', - 'balloons', - 'bachelor', - 'waterman', - 'teenager', - 'spanking', - 'sergeant', - 'seashell', - 'seahorse', - 'scarecrow', - 'riffraff', - 'possible', - 'pittsburgh', - 'pinnacle', - 'nostromo', - 'latitude', - 'invasion', - 'hibiscus', - 'hallmark', - 'firestorm', - 'envision', - 'charcoal', - 'character', - 'antelope', - 'aircraft', - 'unlimited', - 'transport', - 'stripper', - 'snowwhite', - 'smirnoff', - 'seraphim', - 'reporter', - 'painkiller', - 'nineteen', - 'monolith', - 'memories', - 'memorial', - 'massacre', - 'goofball', - 'engineering', - 'doorknob', - 'dipstick', - 'commerce', - 'carousel', - 'callisto', - 'brilliant', - 'berenice', - 'barbarian', - 'wormwood', - 'schumacher', - 'rosewood', - 'rochester', - 'roadster', - 'rapunzel', - 'prisoner', - 'prescott', - 'phillies', - 'pasadena', - 'optimist', - 'monkeyboy', - 'metropolis', - 'kimberley', - 'junkmail', - 'inspiron', - 'hhhhhhhh', - 'griffith', - 'greenwood', - 'golfball', - 'forester', - 'euphoria', - 'cornelius', - 'constance', - 'conquest', - 'clitoris', - 'cartoons', - 'buckaroo', - 'bluejays', - 'volunteer', - 'violence', - 'terrence', - 'temporal', - 'teamwork', - 'shipping', - 'serendipity', - 'roosters', - 'prophecy', - 'playmate', - 'panorama', - 'landmark', - 'instinct', - 'infected', - 'illuminati', - 'honeydew', - 'foundation', - 'forbidden', - 'document', - 'deadline', - 'crocodile', - 'climbing', - 'bluestar', - 'birmingham', - 'bathroom', - 'baltimore', - 'whiteboy', - 'trinitron', - 'titleist', - 'tiberius', - 'superhero', - 'sidewinder', - 'rosemarie', - 'retarded', - 'peppermint', - 'palomino', - 'outsider', - 'oooooooo', - 'musician', - 'michelin', - 'juggernaut', - 'hyacinth', - 'gatorade', - 'fuzzball', - 'everyone', - 'dictionary', - 'development', - 'delirium', - 'critical', - 'cordelia', - 'collection', - 'capitals', - 'bobdylan', - 'birdhouse', - 'asparagus', - 'voltaire', - 'submarine', - 'stonewall', - 'southpaw', - 'sanctuary', - 'ruthless', - 'reaction', - 'qazwsxed', - 'prometheus', - 'portable', - 'passcode', - 'official', - 'neverland', - 'mindless', - 'masamune', - 'legendary', - 'incredible', - 'holloway', - 'heartless', - 'hairball', - 'genevieve', - 'fireworks', - 'dilligaf', - 'crossfire', - 'clippers', - 'caldwell', - 'waterpolo', - 'vertical', - 'timeless', - 'thegreat', - 'superuser', - 'spelling', - 'slippery', - 'rrrrrrrr', - 'ricochet', - 'redemption', - 'raspberry', - 'protocol', - 'producer', - 'patterson', - 'olivetti', - 'metalica', - 'mannheim', - 'mandingo', - 'magellan', - 'machines', - 'lovebird', - 'inflames', - 'important', - 'headache', - 'gemstone', - 'ffffffff', - 'cyclones', - 'colonial', - 'claudius', - 'bulgaria', - 'brunette', - 'bradshaw', - 'bastards', - 'basement', - 'applesauce', - 'acapulco', - 'yingyang', - 'workshop', - 'trueblue', - 'transformers', - 'tarantula', - 'sycamore', - 'stigmata', - 'stargazer', - 'override', - 'nighthawk', - 'mortgage', - 'macdaddy', - 'leicester', - 'knockers', - 'jjjjjjjj', - 'hysteria', - 'forgiven', - 'distance', - 'destruction', - 'cosworth', - 'coconuts', - 'carlisle', - 'breakfast', - 'antivirus', - 'yokohama', - 'unforgiven', - 'surrender', - 'sheepdog', - 'seinfeld', - 'sabotage', - 'reddragon', - 'pressure', - 'pinetree', - 'pavement', - 'oriental', - 'offshore', - 'newzealand', - 'netscape', - 'michaels', - 'junkyard', - 'jakejake', - 'invincible', - 'hawthorn', - 'hawaiian', - 'greyhound', - 'frenchie', - 'fastball', - 'deathrow', - 'carpenter', - 'breakout', - 'bismarck', - 'alkaline', - 'adrenalin', - 'tryagain', - 'thatcher', - 'stampede', - 'shakespeare', - 'scheisse', - 'sayonara', - 'santacruz', - 'passions', - 'notorious', - 'necromancer', - 'nameless', - 'mysterio', - 'millennium', - 'megabyte', - 'mccarthy', - 'magister', - 'madhouse', - 'liverpoo', - 'leviathan', - 'jennings', - 'holstein', - 'hellraiser', - 'freefall', - 'flawless', - 'emergency', - 'ebenezer', - 'divinity', - 'chewbacca', - 'chastity', - 'charlott', - 'buchanan', - 'aventura', - 'zildjian', - 'wargames', - 'vvvvvvvv', - 'unicorns', - 'timberland', - 'tasmania', - 'symphony', - 'splendid', - 'sonyvaio', - 'snapshot', - 'saunders', - 'reverend', - 'prototype', - 'polaroid', - 'perfecto', - 'mystical', - 'material', - 'maddison', - 'landlord', - 'juvenile', - 'goodwill', - 'goldwing', - 'gilberto', - 'flapjack', - 'finnegan', - 'erection', - 'clemente', - 'caterpillar', - 'capetown', - 'accounts', - 'abstract', - 'townsend', - 'technical', - 'smithers', - 'shooting', - 'shitshit', - 'senators', - 'sacramento', - 'redbaron', - 'programmer', - 'percival', - 'painless', - 'northstar', - 'newspaper', - 'mongolia', - 'miroslav', - 'lumberjack', - 'lakewood', - 'incoming', - 'immanuel', - 'hometown', - 'homeless', - 'hillbilly', - 'goodnight', - 'giordano', - 'genocide', - 'enforcer', - 'dreamcast', - 'dispatch', - 'developer', - 'copenhagen', - 'codename', - 'clockwork', - 'cccccccc', - 'callaway', - 'calculus', - 'bartender', - 'attorney', - 'asteroid', - 'angeleyes', - 'academia', - 'warehouse', - 'terrance', - 'stirling', - 'stamford', - 'stairway', - 'specialist', - 'soldiers', - 'shitface', - 'rotterdam', - 'pizzahut', - 'pepperoni', - 'patricio', - 'passwerd', - 'mulberry', - 'luscious', - 'lifeline', - 'legoland', - 'kickflip', - 'kennwort', - 'kathrine', - 'johnathan', - 'excelsior', - 'drummond', - 'disneyland', - 'delldell', - 'claudine', - 'christia', - 'checkmate', - 'centurion', - 'cashmere', - 'carthage', - 'bartlett', - 'animation', - 'alphonse', - 'woodside', - 'vengeance', - 'vaseline', - 'toxicity', - 'tommyboy', - 'ticktock', - 'teachers', - 'strategy', - 'stephens', - 'snowdrop', - 'smeghead', - 'shutdown', - 'sexysexy', - 'pretender', - 'popsicle', - 'philadelphia', - 'petersen', - 'moonstone', - 'masterkey', - 'maryanne', - 'magicman', - 'identity', - 'hannover', - 'glorious', - 'gathering', - 'forgetit', - 'fishtank', - 'fernandes', - 'epiphone', - 'elevator', - 'elegance', - 'drumline', - 'devilman', - 'delivery', - 'chrissie', - 'carnaval', - 'caffeine', - 'bukowski', - 'brownies', - 'bearcats', - 'woofwoof', - 'untitled', - 'tttttttt', - 'stickman', - 'starlite', - 'southwest', - 'smarties', - 'penthouse', - 'peanutbutter', - 'oxymoron', - 'oleander', - 'nightfall', - 'newjersey', - 'muhammed', - 'morphine', - 'mobydick', - 'meltdown', - 'medieval', - 'mahogany', - 'longshot', - 'lockheed', - 'livewire', - 'lakeland', - 'kenworth', - 'interpol', - 'integrity', - 'hibernia', - 'helpdesk', - 'fishhead', - 'everybody', - 'ethernet', - 'elemental', - 'duracell', - 'delicious', - 'crystals', - 'confidence', - 'colossus', - 'belladonna', - 'backlash', - 'academic', - 'abnormal', - 'vineyard', - 'terrible', - 'suburban', - 'stocking', - 'springfield', - 'snuffles', - 'sideways', - 'sensation', - 'schwartz', - 'salasana', - 'rosalind', - 'radiation', - 'purchase', - 'protection', - 'practice', - 'poiuytre', - 'piramide', - 'nashville', - 'montrose', - 'lunchbox', - 'lonesome', - 'limerick', - 'imagination', - 'ignition', - 'homebrew', - 'helicopter', - 'greenman', - 'firefire', - 'electronic', - 'economics', - 'contract', - 'conflict', - 'comeback', - 'cheeseburger', - 'believer', - 'beaumont', - 'arrowhead', - 'alternative', - 'woodward', - 'wolverin', - 'wellness', - 'timberlake', - 'terrorist', - 'temptation', - 'swingers', - 'solstice', - 'scratchy', - 'roosevelt', - 'rockport', - 'redlight', - 'perfection', - 'paulette', - 'overtime', - 'nazareth', - 'mudvayne', - 'movement', - 'miracles', - 'maserati', - 'marbella', - 'lifestyle', - 'kiwikiwi', - 'jurassic', - 'infernal', - 'hereford', - 'goodtime', - 'gamecock', - 'galadriel', - 'gabriell', - 'firefighter', - 'ferreira', - 'ethiopia', - 'dionysus', - 'different', - 'deadpool', - 'crossroads', - 'christos', - 'chauncey', - 'castaway', - 'carefree', - 'burnside', - 'boomerang', - 'bohemian', - 'blackice', - 'blackhole', - 'bigmouth', - 'baptiste', - 'augustin', - 'arlington', - 'ambassador', - 'alistair', - 'agamemnon', - 'advocate', - 'acoustic', - 'zimmerman', - 'yorkshire', - 'wallpaper', - 'vinicius', - 'vauxhall', - 'understand', - 'terminus', - 'surround', - 'stronghold', - 'sessions', - 'scirocco', - 'schiller', - 'schedule', - 'regional', - 'radiance', - 'pioneers', - 'phantasy', - 'obsession', - 'neutrino', - 'mountains', - 'marmalade', - 'kendrick', - 'heinlein', - 'gillette', - 'germania', - 'fruitcake', - 'fighters', - 'fastback', - 'exercise', - 'envelope', - 'eeeeeeee', - 'diabetes', - 'destination', - 'davenport', - 'damascus', - 'coronado', - 'chevalier', - 'cashflow', - 'cardigan', - 'boyfriend', - 'blueprint', - 'blackboy', - 'bitchass', - 'backpack', - 'aquamarine', - 'anakonda', - 'Victoria', - '911turbo', - 'worldwide', - 'viscount', - 'violette', - 'undertow', - 'traveller', - 'transformer', - 'tombstone', - 'surfboard', - 'stratocaster', - 'stephani', - 'stainless', - 'scorpions', - 'redstone', - 'premiere', - 'planning', - 'peacemaker', - 'numberone', - 'nitrogen', - 'natascha', - 'moonwalk', - 'marzipan', - 'mandolin', - 'maintain', - 'macgyver', - 'lexington', - 'landscape', - 'killkill', - 'jailbird', - 'goodnews', - 'gatekeeper', - 'freshman', - 'frankfurt', - 'frankenstein', - 'firestar', - 'dreamland', - 'discreet', - 'detective', - 'crossbow', - 'choppers', - 'betrayed', - 'bernhard', - 'basilisk', - 'armadillo', - 'antigone', - 'alterego', - 'alhambra', - 'aerobics', - 'advantage', - 'Superman', - 'yyyyyyyy', - 'yellowstone', - 'woodruff', - 'sunnyboy', - 'specialk', - 'sorrento', - 'reliance', - 'proverbs', - 'policeman', - 'playgirl', - 'pentium4', - 'pedigree', - 'partners', - 'overdrive', - 'observer', - 'nnnnnnnn', - 'newworld', - 'moriarty', - 'minotaur', - 'location', - 'knockout', - 'knickers', - 'kassandra', - 'hellyeah', - 'greentea', - 'goodgood', - 'gasoline', - 'flashman', - 'firestarter', - 'fatality', - 'ellipsis', - 'disorder', - 'deadlock', - 'davidoff', - 'couscous', - 'construction', - 'congress', - 'cleaning', - 'clarkson', - 'christoph', - 'cheerleader', - 'ceramics', - 'casandra', - 'cambodia', - 'blackstar', - 'ballerina', - 'backbone', - 'whatwhat', - 'westcoast', - 'watching', - 'underwear', - 'tomatoes', - 'tiramisu', - 'tiberian', - 'thurston', - 'spinning', - 'slippers', - 'response', - 'reindeer', - 'prosperity', - 'panchito', - 'nottingham', - 'mythology', - 'mayfield', - 'marquise', - 'manifest', - 'magnetic', - 'lovelace', - 'lesbians', - 'joystick', - 'inspector', - 'industry', - 'gulliver', - 'ganymede', - 'galactic', - 'furniture', - 'flashback', - 'esoteric', - 'dropdead', - 'drinking', - 'devildog', - 'copeland', - 'christop', - 'cheerios', - 'chatting', - 'chantelle', - 'changeit', - 'cerulean', - 'cabernet', - 'blackheart', - 'baltazar', - 'alpha123', - 'alleycat', - 'accident', - 'trickster', - 'tingting', - 'thething', - 'tallulah', - 'symmetry', - 'stonehenge', - 'smartass', - 'shortcake', - 'salesman', - 'rushmore', - 'resource', - 'pregnant', - 'pleasant', - 'playground', - 'plankton', - 'pendulum', - 'paterson', - 'partizan', - 'olympics', - 'northwest', - 'networks', - 'nederland', - 'mystique', - 'mckinley', - 'mcgregor', - 'maxpower', - 'mathematics', - 'lafayette', - 'kokakola', - 'katherin', - 'julianna', - 'jeannine', - 'horseman', - 'homeland', - 'hennessy', - 'guesswho', - 'greywolf', - 'gilligan', - 'gallardo', - 'freewill', - 'fleetwood', - 'fantomas', - 'eightball', - 'dutchman', - 'dementia', - 'breakdown', - 'berliner', - 'adrenaline', - 'woodwork', - 'winifred', - 'waterboy', - 'troopers', - 'theodora', - 'sometime', - 'sagittarius', - 'rocketman', - 'roadking', - 'rifleman', - 'pizzaman', - 'phantasm', - 'pathetic', - 'parliament', - 'oldschool', - 'nicotine', - 'nefertiti', - 'minstrel', - 'milwaukee', - 'millionaire', - 'kindness', - 'insurance', - 'independence', - 'hatfield', - 'freelance', - 'forsythe', - 'fontaine', - 'feelgood', - 'experience', - 'evidence', - 'erickson', - 'enter123', - 'energizer', - 'downfall', - 'deadwood', - 'dandelion', - 'crazyman', - 'corporate', - 'commandos', - 'citation', - 'chinchilla', - 'champions', - 'calliope', - 'broccoli', - 'bleeding', - 'berserker', - 'bergkamp', - 'backstreet', - 'asmodeus', - 'artistic', - 'antilles', - 'anteater', - 'Jonathan', - 'wrinkles', - 'triplets', - 'telecaster', - 'sunnyday', - 'students', - 'stockholm', - 'starshine', - 'sopranos', - 'siberian', - 'shetland', - 'sheppard', - 'scrapper', - 'schooner', - 'rebellion', - 'pershing', - 'parasite', - 'palmetto', - 'overture', - 'odysseus', - 'notredame', - 'narayana', - 'nakamura', - 'mushrooms', - 'moderator', - 'metalgear', - 'mediator', - 'mcintosh', - 'mayflower', - 'marykate', - 'manpower', - 'malamute', - 'louisiana', - 'kryptonite', - 'jeronimo', - 'jeremias', - 'jamaican', - 'imperium', - 'hurricanes', - 'humberto', - 'hoosiers', - 'goldmine', - 'futurama', - 'elisabet', - 'earthquake', - 'dumpling', - 'dragster', - 'dominica', - 'dominate', - 'dictator', - 'desperate', - 'cookbook', - 'confusion', - 'concerto', - 'christel', - 'blacksmith', - 'beholder', - 'babushka', - 'autobahn', - 'attention', - 'atmosphere', - 'anywhere', - 'aftermath', - 'acidburn', - 'whiteout', - 'typewriter', - 'thousand', - 'thorsten', - 'thematrix', - 'symantec', - 'splatter', - 'sonysony', - 'slaughter', - 'sensitive', - 'schaefer', - 'reddwarf', - 'providence', - 'position', - 'popopopo', - 'pikapika', - 'piercing', - 'performance', - 'paramedic', - 'pakistani', - 'neighbor', - 'motorcycle', - 'mireille', - 'lovesick', - 'loverman', - 'lockwood', - 'lifeguard', - 'kowalski', - 'kerberos', - 'kellyann', - 'jayhawks', - 'innuendo', - 'iiiiiiii', - 'hummingbird', - 'horrible', - 'himalaya', - 'highlife', - 'hetfield', - 'heartbeat', - 'guitarist', - 'graphite', - 'funnyman', - 'epiphany', - 'elvis123', - 'discount', - 'copyright', - 'concordia', - 'complicated', - 'clementine', - 'chouette', - 'chinchin', - 'chinatown', - 'chinaman', - 'chesterfield', - 'cervantes', - 'celestial', - 'calderon', - 'bullhead', - 'brussels', - 'broadband', - 'brasilia', - 'bellevue', - 'bagpipes', - 'aurelius', - 'aristotle', - 'altitude', - 'aloysius', - 'affinity', - '09876543', - 'winnipeg', - 'ultraman', - 'treefrog', - 'tigercat', - 'taratara', - 'tactical', - 'system32', - 'swastika', - 'searcher', - 'reserved', - 'redbeard', - 'realtime', - 'pyramids', - 'provider', - 'projects', - 'production', - 'poontang', - 'pinecone', - 'pericles', - 'pennywise', - 'paradiso', - 'parachute', - 'parabola', - 'palestine', - 'overflow', - 'motorbike', - 'mavericks', - 'marybeth', - 'marriott', - 'madalena', - 'loophole', - 'lonsdale', - 'lingerie', - 'ledzeppelin', - 'lavalamp', - 'joselito', - 'jerrylee', - 'jamboree', - 'interest', - 'hugoboss', - 'heythere', - 'hehehehe', - 'hangover', - 'greatone', - 'gardener', - 'exorcist', - 'dressage', - 'dominator', - 'domination', - 'dodgeram', - 'cummings', - 'commodore', - 'christen', - 'callahan', - 'calcutta', - 'burberry', - 'bulletproof', - 'bombshell', - 'blackburn', - 'betrayal', - 'atkinson', - 'athletic', - 'arachnid', - 'amaranth', - 'algernon', - 'alastair', - 'absinthe', - 'zoomzoom', - 'wonderboy', - 'whatthefuck', - 'watchman', - 'transform', - 'trampoline', - 'tortilla', - 'thunderbolt', - 'superduper', - 'squadron', - 'skylight', - 'sanfrancisco', - 'salamandra', - 'resistance', - 'reliable', - 'recorder', - 'provence', - 'porsche9', - 'piedmont', - 'pentagram', - 'overdose', - 'nightman', - 'nightingale', - 'monument', - 'longtime', - 'lolololo', - 'lokiloki', - 'laughing', - 'kerrigan', - 'jeopardy', - 'inspiration', - 'ibelieve', - 'houghton', - 'horsemen', - 'hologram', - 'hideaway', - 'handicap', - 'hamsters', - 'forklift', - 'finished', - 'dorothea', - 'devotion', - 'deathstar', - 'darkknight', - 'conchita', - 'classics', - 'bridgette', - 'bigballs', - 'barnyard', - 'baphomet', - 'badlands', - 'asterisk', - 'arcangel', - 'antoinette', - 'annemarie', - 'Christian', - 'whoknows', - 'trousers', - 'treehouse', - 'tranquil', - 'toriamos', - 'teardrop', - 'superboy', - 'shortcut', - 'shockwave', - 'shocking', - 'scranton', - 'sandoval', - 'roseanne', - 'red12345', - 'prospero', - 'products', - 'paperclip', - 'outdoors', - 'nemesis1', - 'nagasaki', - 'mousepad', - 'morrissey', - 'monkeyman', - 'modeling', - 'maranatha', - 'makeitso', - 'maelstrom', - 'limpbizkit', - 'lightbulb', - 'lalakers', - 'katharina', - 'kakaroto', - 'jeannette', - 'investor', - 'insecure', - 'humanoid', - 'holiness', - 'helpless', - 'hallelujah', - 'greenhouse', - 'germaine', - 'gallagher', - 'freefree', - 'francais', - 'firestone', - 'firebolt', - 'filipino', - 'falstaff', - 'electronics', - 'economist', - 'dominant', - 'diabolic', - 'deadbeat', - 'crockett', - 'crazycat', - 'composer', - 'coleslaw', - 'cincinnati', - 'cascades', - 'breaking', - 'bluenose', - 'bluegrass', - 'bisexual', - 'billions', - 'billbill', - 'bigbrother', - 'avengers', - 'athletics', - 'assembly', - 'asasasas', - 'allstars', - 'alakazam', - 'activate', - 'Computer', - 'zerozero', - 'windowsxp', - 'vigilant', - 'verygood', - 'trustnoone', - 'truffles', - 'toothpaste', - 'tigerman', - 'sweetwater', - 'summoner', - 'suicidal', - 'strummer', - 'stiletto', - 'squeaker', - 'sixtynine', - 'sithlord', - 'siegfried', - 'showcase', - 'serenade', - 'sepultura', - 'rotation', - 'rockhard', - 'quintana', - 'pepsicola', - 'passenger', - 'pacifica', - 'nightshade', - 'newhouse', - 'muenchen', - 'motorhead', - 'morrigan', - 'memememe', - 'maximize', - 'marciano', - 'macdonald', - 'loveable', - 'lakeview', - 'hyderabad', - 'historia', - 'highschool', - 'hiawatha', - 'hermitage', - 'goodtimes', - 'freeport', - 'flathead', - 'faulkner', - 'endymion', - 'emirates', - 'dreamers', - 'district', - 'dietrich', - 'cranberry', - 'cockroach', - 'clemence', - 'classified', - 'cellular', - 'catherin', - 'carmella', - 'burgundy', - 'blooming', - 'blitzkrieg', - 'bladerunner', - 'bigboobs', - 'beachbum', - 'backyard', - 'backward', - 'babybear', - 'argonaut', - 'appleton', - 'aguilera', - 'abundance', - 'Nicholas', - 'zaragoza', - 'woodcock', - 'wisteria', - 'westlake', - 'untouchable', - 'trapdoor', - 'tigereye', - 'thetruth', - 'testicle', - 'superbowl', - 'sprinkle', - 'snakebite', - 'silencer', - 'secretary', - 'scottish', - 'sanderson', - 'sanandreas', - 'robertson', - 'richelle', - 'richardson', - 'religion', - 'quiksilver', - 'queenbee', - 'psychology', - 'playhouse', - 'physical', - 'pensacola', - 'pedersen', - 'paperboy', - 'pandemonium', - 'nikolaus', - 'murderer', - 'montague', - 'mockingbird', - 'mercutio', - 'mercurio', - 'mcknight', - 'maxpayne', - 'mandragora', - 'mamacita', - 'madhatter', - 'lucretia', - 'kusanagi', - 'knoxville', - 'katmandu', - 'julianne', - 'jiujitsu', - 'jeanpaul', - 'infrared', - 'industrial', - 'humanity', - 'hotwheels', - 'honeypot', - 'honeybun', - 'herkules', - 'heartbreaker', - 'hawkeyes', - 'gilgamesh', - 'geometry', - 'friedman', - 'freiheit', - 'firework', - 'federation', - 'executive', - 'exclusive', - 'excellence', - 'emotional', - 'elbereth', - 'dragon99', - 'dollface', - 'devilish', - 'democrat', - 'darkmoon', - 'crackpot', - 'costarica', - 'costanza', - 'consuelo', - 'clarisse', - 'citibank', - 'cingular', - 'chrystal', - 'channing', - 'carvalho', - 'bluebear', - 'billyjoe', - 'benedikt', - 'beaufort', - 'barnabas', - 'baracuda', - 'augustine', - 'armitage', - 'alcapone', - 'afterlife', - 'adrianne', - 'Internet', - 'Football', - 'yourself', - 'yorktown', - 'yeahyeah', - 'wildflower', - 'valdemar', - 'unlocked', - 'unleashed', - 'twinkles', - 'trujillo', - 'torrents', - 'tonyhawk', - 'tanzania', - 'takedown', - 'takamine', - 'supercool', - 'subwoofer', - 'stitches', - 'standing', - 'stalingrad', - 'srilanka', - 'sparhawk', - 'slowpoke', - 'shoelace', - 'service1', - 'senorita', - 'seashore', - 'sandstorm', - 'roulette', - 'radiator', - 'problems', - 'powerhouse', - 'postmaster', - 'platform', - 'parallax', - 'nepenthe', - 'moonmoon', - 'livingston', - 'labyrinth', - 'jackhammer', - 'intrigue', - 'interface', - 'interact', - 'honeymoon', - 'grapefruit', - 'government', - 'geography', - 'galloway', - 'fullback', - 'fuckhead', - 'fairview', - 'divorced', - 'disabled', - 'defiance', - 'deeznutz', - 'communication', - 'cocksucker', - 'cheating', - 'buckwheat', - 'boarding', - 'blackdragon', - 'baseline', - 'bandicoot', - 'baldrick', - 'apollo11', - 'ambulance', - 'aluminum', - 'abercrombie', - 'woodwind', - 'woodpecker', - 'woodbury', - 'watchdog', - 'tribunal', - 'toreador', - 'tigerwoods', - 'thinkpad', - 'thebeach', - 'test12345', - 'terrific', - 'teaching', - 'successful', - 'stringer', - 'sovereign', - 'souvenir', - 'sombrero', - 'shuriken', - 'shotokan', - 'shinichi', - 'scooters', - 'schroeder', - 'schnitzel', - 'rosalinda', - 'regiment', - 'rainfall', - 'pistache', - 'pianoman', - 'paddington', - 'overseas', - 'orthodox', - 'nietzsche', - 'monorail', - 'minemine', - 'milhouse', - 'mermaids', - 'mansfield', - 'madrigal', - 'krakatoa', - 'junction', - 'intranet', - 'imperator', - 'humility', - 'harmless', - 'giuliana', - 'gauntlet', - 'fugitive', - 'flatland', - 'feelings', - 'entertainment', - 'election', - 'dumpster', - 'cruzeiro', - 'cracking', - 'cheaters', - 'centrino', - 'canberra', - 'botswana', - 'blockbuster', - 'blahblahblah', - 'blackfire', - 'blackbelt', - 'atreides', - 'asuncion', - 'astronomy', - 'astroboy', - 'aqualung', - 'amnesiac', - 'adorable', - '3edc4rfv', - 'worldcup', - 'wholesale', - 'vittorio', - 'underwood', - 'underwater', - 'touchdown', - 'theworld', - 'thebeast', - 'thaddeus', - 'telemark', - 'sylvania', - 'surveyor', - 'suitcase', - 'stroller', - 'stripped', - 'stratford', - 'stallone', - 'speedster', - 'septembe', - 'sandberg', - 'rousseau', - 'revenant', - 'protector', - 'protected', - 'pembroke', - 'parkside', - 'outbreak', - 'obsolete', - 'nutshell', - 'nonenone', - 'multisync', - 'momentum', - 'microwave', - 'marguerite', - 'maldives', - 'magdalen', - 'longbeach', - 'lockhart', - 'kensington', - 'humboldt', - 'homebase', - 'headshot', - 'headless', - 'hazelnut', - 'gremlins', - 'fireblade', - 'external', - 'entering', - 'electrical', - 'dulcinea', - 'dropkick', - 'draconis', - 'domestic', - 'daydreamer', - 'darkwing', - 'corporal', - 'cocorico', - 'chimaera', - 'cheyanne', - 'celebrate', - 'caballero', - 'breakers', - 'brainstorm', - 'bluesman', - 'blackpool', - 'bethesda', - 'basketba', - 'antichrist', - 'andyandy', - 'abcdefghi', - 'Garfield', - 'yoyoyoyo', - 'yeahbaby', - 'wetpussy', - 'vergessen', - 'variable', - 'trillium', - 'torrance', - 'tikitiki', - 'thesaint', - 'theforce', - 'succubus', - 'stockman', - 'steve123', - 'speeding', - 'solitaire', - 'sokrates', - 'slingshot', - 'skateboarding', - 'silverfox', - 'showboat', - 'sequence', - 'rottweiler', - 'rincewind', - 'rainmaker', - 'postcard', - 'polkadot', - 'photoshop', - 'persimmon', - 'pandabear', - 'nutrition', - 'nicknick', - 'mysterious', - 'marielle', - 'maneater', - 'lionlion', - 'leningrad', - 'leapfrog', - 'kristopher', - 'kirkwood', - 'kilkenny', - 'jediknight', - 'jabberwocky', - 'intercom', - 'informix', - 'hounddog', - 'homicide', - 'herschel', - 'henrietta', - 'hatteras', - 'harakiri', - 'halfmoon', - 'gunslinger', - 'fivestar', - 'firewood', - 'expedition', - 'executor', - 'elcamino', - 'egyptian', - 'duckling', - 'drumming', - 'drifting', - 'daisydog', - 'contrast', - 'collector', - 'choclate', - 'chilling', - 'channels', - 'catapult', - 'careless', - 'californ', - 'brunswick', - 'braindead', - 'bluedragon', - 'bloodline', - 'beverley', - 'atalanta', - 'antihero', - 'allright', - '43214321', - '18436572', - 'woodlands', - 'wilkinson', - 'wellcome', - 'waldemar', - 'valerian', - 'tornado1', - 'thunders', - 'testament', - 'tennyson', - 'tarragon', - 'tapestry', - 'tajmahal', - 'struggle', - 'starling', - 'starchild', - 'sobriety', - 'snowfall', - 'shalimar', - 'settings', - 'schnecke', - 'satriani', - 'sailfish', - 'roserose', - 'reference', - 'powerman', - 'powerade', - 'playstation2', - 'plastics', - 'pinkpink', - 'parallel', - 'papercut', - 'p4ssword', - 'navyseal', - 'monopoli', - 'mnemonic', - 'millenia', - 'mercenary', - 'membrane', - 'manitoba', - 'limelight', - 'leopards', - 'kurdistan', - 'karoline', - 'johnpaul', - 'interior', - 'interesting', - 'insomniac', - 'homepage', - 'heavymetal', - 'headhunter', - 'harvester', - 'greeting', - 'golfgolf', - 'glassman', - 'gladstone', - 'friction', - 'ethereal', - 'emotions', - 'dudedude', - 'douglass', - 'desperados', - 'demetrio', - 'demented', - 'decision', - 'cuthbert', - 'compound', - 'comatose', - 'civilwar', - 'charleston', - 'castello', - 'bulletin', - 'brandnew', - 'bluegill', - 'bloodhound', - 'baywatch', - 'bastardo', - 'bagheera', - 'allstate', - 'aldebaran', - 'Samantha', - 'Elizabeth', - '78945612', - 'wolverines', - 'wolfhound', - 'wildbill', - 'whittier', - 'virgilio', - 'vegetable', - 'unbreakable', - 'trusting', - 'troubles', - 'tonytony', - 'terriers', - 'template', - 'telefoon', - 'talented', - 'superpower', - 'supermen', - 'sugarplum', - 'starting', - 'sixpence', - 'simplicity', - 'sidewalk', - 'shoshana', - 'sasquatch', - 'rattlesnake', - 'rafferty', - 'qwerty00', - 'promotion', - 'pinocchio', - 'philosophy', - 'philippines', - 'pheasant', - 'pentium3', - 'overlook', - 'overhead', - 'operations', - 'okokokok', - 'nwo4life', - 'nostradamus', - 'newdelhi', - 'myfriend', - 'munchies', - 'mountaindew', - 'moneybag', - 'molecule', - 'melville', - 'mcintyre', - 'mattress', - 'marshmallow', - 'maritime', - 'mariachi', - 'lovesong', - 'lolalola', - 'lifeboat', - 'kasandra', - 'kalamazoo', - 'jackrabbit', - 'intelligent', - 'innocence', - 'henry123', - 'henrique', - 'hardball', - 'handbook', - 'hacienda', - 'grenoble', - 'goodmorning', - 'giuliano', - 'frostbite', - 'freehand', - 'fragment', - 'foreskin', - 'explosion', - 'experiment', - 'ensemble', - 'eclectic', - 'dogfight', - 'diogenes', - 'dillweed', - 'dickinson', - 'demetrius', - 'daybreak', - 'dagobert', - 'culinary', - 'crossing', - 'controls', - 'consulting', - 'cobblers', - 'chatterbox', - 'charissa', - 'celebrity', - 'bungalow', - 'broadcast', - 'bodyguard', - 'ballroom', - 'analysis', - 'afghanistan', - 'addiction', - '12131213', - 'witchcraft', - 'wertwert', - 'vivienne', - 'vermilion', - 'ultrasound', - 'tuppence', - 'tropicana', - 'trafford', - 'streamer', - 'starburst', - 'ssssssssss', - 'spotlight', - 'specialized', - 'sparrows', - 'sideshow', - 'sherbert', - 'seminoles', - 'sebastia', - 'scribble', - 'sarasota', - 'sarasara', - 'sanguine', - 'reflection', - 'redhorse', - 'rational', - 'radioman', - 'progressive', - 'poophead', - 'plutonium', - 'phantoms', - 'organize', - 'optiplex', - 'neverdie', - 'nantucket', - 'monsieur', - 'monkfish', - 'mauritius', - 'master01', - 'marymary', - 'marvelous', - 'manifesto', - 'macedonia', - 'logistic', - 'leprechaun', - 'lemmings', - 'langston', - 'innovation', - 'house123', - 'hihihihi', - 'hellhole', - 'hardwood', - 'generator', - 'funhouse', - 'fullhouse', - 'flowerpower', - 'fiorella', - 'farewell', - 'fantasma', - 'faithless', - 'failsafe', - 'explicit', - 'esposito', - 'enchanted', - 'duckduck', - 'drilling', - 'dragon10', - 'doodlebug', - 'dickweed', - 'crunchie', - 'crawfish', - 'condition', - 'chessman', - 'chanelle', - 'chamonix', - 'celebration', - 'brotherhood', - 'brainiac', - 'bluewater', - 'birdland', - 'binladen', - 'billings', - 'bareback', - 'bacteria', - 'authority', - 'astronaut', - 'asdfqwer', - 'arpeggio', - 'appleseed', - 'animator', - 'amazonas', - 'alpacino', - 'adelaida', - 'adamadam', - 'Einstein', - '90909090', - 'yamamoto', - 'wormhole', - 'windows98', - 'whiteman', - 'westgate', - 'watchmen', - 'vergeten', - 'veracruz', - 'vanquish', - 'uuuuuuuu', - 'undefined', - 'trucking', - 'tormentor', - 'timelord', - 'timberwolf', - 'strangle', - 'stoneman', - 'starless', - 'spiritual', - 'spagetti', - 'sorensen', - 'somethin', - 'snowhite', - 'slovakia', - 'skydiver', - 'silvester', - 'silicone', - 'silencio', - 'shevchenko', - 'selector', - 'scramble', - 'scott123', - 'salinger', - 'rendezvous', - 'reminder', - 'redheads', - 'propaganda', - 'presidente', - 'pocahontas', - 'photography', - 'phaedrus', - 'permanent', - 'paulchen', - 'paraguay', - 'palacios', - 'osbourne', - 'mutation', - 'murakami', - 'mercator', - 'manticore', - 'lynnette', - 'lookatme', - 'lightnin', - 'lifeless', - 'leopoldo', - 'knitting', - 'killerbee', - 'interactive', - 'hellhound', - 'happening', - 'gridlock', - 'greatness', - 'gigantic', - 'fred1234', - 'flatline', - 'firehouse', - 'firehawk', - 'fellatio', - 'eruption', - 'encounter', - 'delorean', - 'decipher', - 'darkblue', - 'creatine', - 'counting', - 'cornbread', - 'coolidge', - 'converge', - 'clubbing', - 'charmaine', - 'cbr600rr', - 'carnegie', - 'caribbean', - 'calabria', - 'buttfuck', - 'butterflies', - 'blueball', - 'beautifu', - 'barnacle', - 'barbarossa', - 'automatic', - 'asturias', - 'armchair', - 'archives', - 'aperture', - '12141214', - 'yourname', - 'whitewolf', - 'visionary', - 'versailles', - 'toothpick', - 'testuser', - 'swordfis', - 'superdog', - 'sunflowers', - 'sunflowe', - 'stevenson', - 'sportsman', - 'somewhere', - 'slovenia', - 'sinfonia', - 'silverfish', - 'scimitar', - 'rosebush', - 'resonance', - 'resolution', - 'registration', - 'redriver', - 'redeemed', - 'ramstein', - 'qweasdzx', - 'primetime', - 'precision', - 'plumbing', - 'pickwick', - 'parsifal', - 'paramount', - 'overcome', - 'nutcracker', - 'ninjutsu', - 'newcomer', - 'minority', - 'mariette', - 'loveland', - 'localhost', - 'leadership', - 'lagrange', - 'kaitlynn', - 'john1234', - 'invictus', - 'inventor', - 'inspired', - 'hurrican', - 'holbrook', - 'hiroshima', - 'heracles', - 'hawthorne', - 'hathaway', - 'governor', - 'goodrich', - 'fortytwo', - 'foreplay', - 'foolproof', - 'fishhook', - 'fishfish', - 'financial', - 'fillmore', - 'evangeline', - 'espinoza', - 'electricity', - 'edgewood', - 'duisburg', - 'drummers', - 'dowjones', - 'continental', - 'cameroon', - 'bracelet', - 'bogeyman', - 'bluerose', - 'birdcage', - 'billgates', - 'beepbeep', - 'architecture', - 'antonina', - 'anabolic', - 'allister', - 'albacore', - 'airedale', - 'activity', - 'Patricia', - '99887766', - 'yardbird', - 'xcountry', - 'wildrose', - 'watanabe', - 'wareagle', - 'wanderlust', - 'wakefield', - 'validate', - 'tripping', - 'treetree', - 'timbuktu', - 'tarantino', - 'syndicate', - 'summer00', - 'stuntman', - 'steelman', - 'spaceship', - 'snowshoe', - 'smuggler', - 'slowhand', - 'sharpshooter', - 'schuster', - 'satelite', - 'rightnow', - 'r4e3w2q1', - 'quagmire', - 'portsmouth', - 'porridge', - 'pinkerton', - 'peerless', - 'paganini', - 'orchestra', - 'optional', - 'nowayout', - 'nicaragua', - 'neworder', - 'michelangelo', - 'mcmillan', - 'lombardo', - 'lindberg', - 'larkspur', - 'lambchop', - 'kirakira', - 'kamehameha', - 'jellybeans', - 'innovision', - 'infinito', - 'identify', - 'hellomoto', - 'hellgate', - 'heatwave', - 'harlequin', - 'grounded', - 'greenish', - 'grandmother', - 'gorillaz', - 'goldsmith', - 'gerhardt', - 'generous', - 'gauthier', - 'frontera', - 'freezing', - 'fracture', - 'firewater', - 'fellowship', - 'fastlane', - 'explosive', - 'environment', - 'drafting', - 'donnelly', - 'dolomite', - 'direction', - 'deception', - 'damocles', - 'cunningham', - 'crossroad', - 'critters', - 'crickets', - 'crabtree', - 'cortland', - 'constantin', - 'connected', - 'confidential', - 'comrades', - 'clothing', - 'classical', - 'checking', - 'cathleen', - 'carter15', - 'carleton', - 'butterscotch', - 'butterball', - 'bulldozer', - 'bomberman', - 'blueline', - 'bittersweet', - 'bigblack', - 'backspin', - 'babababa', - 'audition', - 'argentum', - 'antonius', - 'antiques', - 'angelfish', - 'americana', - 'aluminium', - 'woodlawn', - 'woodchuck', - 'windward', - 'warranty', - 'visitors', - 'vanderbilt', - 'turquoise', - 'triathlon', - 'trespass', - 'trashcan', - 'topnotch', - 'switzerland', - 'sturgeon', - 'studioworks', - 'strikers', - 'skipjack', - 'simulator', - 'silverman', - 'shipyard', - 'shekinah', - 'scouting', - 'sanpedro', - 'sandrock', - 'rootroot', - 'reynaldo', - 'renaissance', - 'rembrandt', - 'relentless', - 'relative', - 'radagast', - 'qwertzui', - 'presidio', - 'presence', - 'prentice', - 'porcupine', - 'playback', - 'philippa', - 'peterman', - 'peregrin', - 'peaceman', - 'papabear', - 'organist', - 'octavian', - 'northside', - 'nightwing', - 'nathanael', - 'multipass', - 'monteiro', - 'millhouse', - 'metaphor', - 'manifold', - 'makelove', - 'lysander', - 'louisville', - 'logistics', - 'lobsters', - 'lifesaver', - 'kristofer', - 'kilimanjaro', - 'julie123', - 'johngalt', - 'jalapeno', - 'jacobsen', - 'islanders', - 'isengard', - 'hutchins', - 'horizons', - 'hitchcock', - 'hemingway', - 'heartland', - 'hawkwind', - 'happyboy', - 'gwendolyn', - 'gutentag', - 'graveyard', - 'gracious', - 'glenwood', - 'frogfrog', - 'freelancer', - 'fraction', - 'forgetful', - 'foreigner', - 'folklore', - 'firetruck', - 'fahrenheit', - 'express1', - 'exposure', - 'endurance', - 'employee', - 'economic', - 'ducksoup', - 'dragonslayer', - 'doggystyle', - 'diskette', - 'descartes', - 'delacruz', - 'dashboard', - 'damnation', - 'creepers', - 'copperhead', - 'clements', - 'cheerful', - 'characters', - 'certified', - 'cathedral', - 'catering', - 'capucine', - 'capacity', - 'bridgett', - 'breakaway', - 'boyscout', - 'bloodlust', - 'blackstone', - 'bingo123', - 'belgrade', - 'beginner', - 'bavarian', - 'backfire', - 'astaroth', - 'arsehole', - 'annelise', - 'anabelle', - 'albright', - 'airlines', - 'adminadmin', - 'adelante', - 'Maverick', - 'wretched', - 'winthrop', - 'whirlwind', - 'westwind', - 'weinberg', - 'tumbleweed', - 'trashman', - 'threesome', - 'thibault', - 'syndrome', - 'swinging', - 'sweetiepie', - 'sweetest', - 'superwoman', - 'sunburst', - 'sperling', - 'spectral', - 'silmaril', - 'shoulder', - 'shahrukh', - 'settlers', - 'seduction', - 'searching', - 'scotsman', - 'scofield', - 'schumann', - 'satisfaction', - 'santamaria', - 'rossignol', - 'rodrigues', - 'rockrock', - 'rockland', - 'retriever', - 'resurrection', - 'restaurant', - 'promises', - 'priscila', - 'priority', - 'principal', - 'playoffs', - 'persephone', - 'peregrine', - 'overseer', - 'opposite', - 'oldsmobile', - 'octavius', - 'nikenike', - 'nightcrawler', - 'nehemiah', - 'morticia', - 'morrowind', - 'moonraker', - 'mithrandir', - 'misty123', - 'milenium', - 'microphone', - 'matrix123', - 'lollollol', - 'lausanne', - 'kokokoko', - 'kilowatt', - 'interval', - 'ignorant', - 'huntsman', - 'hooligans', - 'homesick', - 'hobgoblin', - 'highlands', - 'highbury', - 'hellbent', - 'guerilla', - 'graywolf', - 'grandson', - 'georgetown', - 'gentleman', - 'gargamel', - 'gangsters', - 'gameplay', - 'flowerpot', - 'fielding', - 'familiar', - 'falconer', - 'ezequiel', - 'dynamics', - 'dominican', - 'demolition', - 'demetria', - 'darkroom', - 'curtains', - 'currency', - 'crocodil', - 'creativity', - 'crawling', - 'commercial', - 'cigarette', - 'chivalry', - 'celestine', - 'catriona', - 'cassiopeia', - 'carolann', - 'cannonball', - 'canfield', - 'buttocks', - 'brinkley', - 'bordello', - 'blissful', - 'blackwell', - 'blackbox', - 'billiard', - 'bigbooty', - 'belvedere', - 'bastille', - 'barbershop', - 'background', - 'australian', - 'astalavista', - 'assassins', - 'artofwar', - 'artichoke', - 'annalena', - 'animated', - 'alvarado', - 'alternate', - 'alicante', - 'alex2000', - 'alabaster', - 'aerospace', - 'accurate', - '17171717', + "usnotify", + "USnotify", + "us Notify", + "us notify", + "11111111", + "12345678", + "123456789", + "access14", + "alejandra", + "alejandro", + "baseball", + "bigdaddy", + "butthead", + "cocacola", + "computer", + "consumer", + "corvette", + "danielle", + "dolphins", + "einstein", + "estrella", + "firebird", + "football", + "hardcore", + "iloveyou", + "internet", + "jennifer", + "mariposa", + "marlboro", + "maverick", + "mercedes", + "michelle", + "midnight", + "mistress", + "mountain", + "nicholas", + "password", + "password1", + "password12", + "password123", + "princess", + "qwertyui", + "redskins", + "redwings", + "rush2112", + "samantha", + "scorpion", + "sebastian", + "srinivas", + "startrek", + "starwars", + "steelers", + "sunshine", + "superman", + "swimming", + "tequiero", + "trustno1", + "victoria", + "whatever", + "xxxxxxxx", + "1234567890", + "1q2w3e4r5t", + "qwertyuiop", + "myspace1", + "1qaz2wsx", + "target123", + "1g2w3e4r", + "gwerty123", + "zag12wsx", + "1q2w3e4r", + "987654321", + "qwerty123", + "asdfghjkl", + "123123123", + "iloveyou1", + "fuckyou1", + "789456123", + "princess1", + "linkedin", + "1234qwer", + "j38ifUbn", + "football1", + "123456789a", + "abcd1234", + "jordan23", + "88888888", + "12qwaszx", + "FQRG7CS493", + "blink182", + "michael1", + "babygirl1", + "0123456789", + "iloveyou2", + "147258369", + "q1w2e3r4", + "jessica1", + "qwer1234", + "liverpool", + "fuckyou2", + "1111111111", + "qazwsxedc", + "baseball1", + "0987654321", + "anthony1", + "00000000", + "29rsavoy", + "basketball", + "qwerty12", + "charlie1", + "passw0rd", + "asshole1", + "superman1", + "sunshine1", + "babygirl", + "asdf1234", + "chocolate", + "password2", + "12341234", + "12344321", + "q1w2e3r4t5y6", + "qweasdzxc", + "a123456789", + "VQsaBLPzLa", + "hello123", + "butterfly", + "1qazxsw2", + "cjmasterinf", + "brandon1", + "1234567891", + "alexander", + "PE#5GZ29PTZMSE", + "dpbk1234", + "DIOSESFIEL", + "pakistan", + "123654789", + "matthew1", + "3rJs1la7qE", + "пїЅпїЅпїЅпїЅпїЅпїЅ", + "barcelona", + "computer1", + "michelle1", + "12345678910", + "jonathan", + "liverpool1", + "11223344", + "12345qwert", + "111222tianya", + "william1", + "chicken1", + "0000000000", + "jasmine1", + "benjamin", + "welcome1", + "christian", + "1234554321", + "chocolate1", + "butterfly1", + "q1w2e3r4t5", + "slipknot", + "zaq12wsx", + "147852369", + "elizabeth", + "87654321", + "1password", + "america1", + "metallica", + "chelsea1", + "1234567a", + "iw14Fi9j", + "juventus", + "jennifer1", + "999999999", + "elizabeth1", + "123qweasd", + "tinkerbell", + "samantha1", + "Sojdlg123aljg", + "myspace123", + "freedom1", + "whatever1", + "valentina", + "741852963", + "spongebob1", + "1234abcd", + "hellokitty", + "madison1", + "spiderman", + "diamond1", + "pokemon1", + "mustang1", + "1qaz2wsx3edc", + "justinbieb", + "friends1", + "asdfasdf", + "qwerty12345", + "123hfjdk147", + "iloveyou!", + "fuckoff1", + "bubbles1", + "a1b2c3d4", + "123456789q", + "heather1", + "4815162342", + "yankees1", + "asdfghjkl1", + "1q2w3e4r5t6y", + "patrick1", + "12121212", + "alexander1", + "raiders1", + "Password1", + "zxcvbnm1", + "melissa1", + "slipknot1", + "spiderman1", + "cowboys1", + "a1234567", + "november", + "alexandra", + "veronica", + "cristina", + "newyork1", + "jackson1", + "iloveyou12", + "PolniyPizdec0211", + "password!", + "a838hfiD", + "richard1", + "beautiful1", + "carolina", + "patricia", + "stephanie", + "421uiopy258", + "myspace2", + "monster1", + "elephant", + "963852741", + "destiny1", + "123456abc", + "december", + "9876543210", + "manchester", + "12345678a", + "пїЅпїЅпїЅпїЅпїЅпїЅпїЅ", + "kristina", + "lovelove", + "gangsta1", + "charlotte", + "scooter1", + "caroline", + "super123", + "marseille", + "metallica1", + "beautiful", + "danielle1", + "blessed1", + "1029384756", + "qazwsx123", + "california", + "christian1", + "arsenal1", + "babyboy1", + "1122334455", + "aa123456", + "forever1", + "Password", + "1a2b3c4d", + "playboy1", + "creative", + "пїЅпїЅпїЅпїЅпїЅпїЅпїЅпїЅ", + "brittany1", + "letmein1", + "cameron1", + "spongebob", + "uQA9Ebw445", + "fernando", + "startfinding", + "softball", + "dolphin1", + "qwerty1234", + "september", + "isabella", + "abc123456", + "password3", + "abcdefg123", + "loveyou1", + "leonardo", + "password.", + "samsung1", + "qwert123", + "poohbear", + "garfield", + "YAgjecc826", + "qwerty123456", + "iloveme1", + "nicholas1", + "portugal", + "precious", + "jackass1", + "jonathan1", + "rainbow1", + "angel123", + "fuckyou!", + "starwars1", + "tiffany1", + "poohbear1", + "1234512345", + "qq123456", + "abcdefg1", + "crystal1", + "azertyuiop", + "angelina", + "svetlana", + "icecream", + "popcorn1", + "victoria1", + "twilight", + "brittany", + "snickers", + "aaaaaaaa", + "swordfish", + "fyfcnfcbz", + "rockstar1", + "yourmom1", + "christine", + "steelers1", + "shannon1", + "peaches1", + "florida1", + "stephanie1", + "lollipop", + "greenday1", + "iverson3", + "motorola", + "rockstar", + "lakers24", + "southside1", + "bismillah", + "pa55word", + "emmanuel", + "5555555555", + "password11", + "love4ever", + "greenday", + "isabelle", + "babygurl1", + "santiago", + "chester1", + "kimberly", + "happy123", + "55555555", + "satan666", + "francesco", + "vanessa1", + "a12345678", + "realmadrid", + "1123581321", + "soccer12", + "fktrcfylh", + "qwert12345", + "1v7Upjw3nT", + "p@ssw0rd", + "thunder1", + "zxcvbnm123", + "midnight1", + "lebron23", + "strawberry", + "love1234", + "soccer10", + "darkness", + "qw123321", + "22222222", + "d41d8cd98f00b204e9800998ecf8427e", + "charles1", + "logitech", + "princess12", + "precious1", + "brooklyn1", + "snowball", + "courtney", + "123qwe123", + "brooklyn", + "vladimir", + "111222333", + "asdfghjk", + "lizottes", + "123454321", + "123qweasdzxc", + "superstar", + "rebecca1", + "catherine", + "123698745", + "vkontakte", + "getmoney1", + "hollister1", + "remember", + "abc12345", + "111111111", + "cjkysirj", + "money123", + "element1", + "P3Rat54797", + "francesca", + "undertaker", + "asdfjkl;", + "facebook", + "chouchou", + "password7", + "kawasaki", + "linkinpark", + "ronaldo7", + "asdasdasd", + "alessandro", + "courtney1", + "qqww1122", + "scarface", + "angelica", + "australia", + "qti7Zxh18U", + "пїЅпїЅпїЅпїЅпїЅ", + "chicago1", + "softball1", + "natalie1", + "monkey123", + "bullshit", + "sunflower", + "21212121", + "volleyball", + "пїЅпїЅпїЅпїЅпїЅпїЅпїЅпїЅпїЅ", + "sweetpea", + "zxcvbnm:", + "bitch123", + "babygirl12", + "goodluck", + "gateway1", + "bigdaddy1", + "31415926", + "christina", + "aleksandr", + "gabriel1", + "ihateyou", + "antonio1", + "awesome1", + "fishing1", + "amoremio", + "monkey12", + "brianna1", + "bitches1", + "princesa", + "asdfghjkl;", + "changeme", + "passwort", + "valentin", + "12345qwerty", + "paradise", + "scarface1", + "jesus123", + "sweetheart", + "qwertyuiop[]", + "fuckyou123", + "P@ssw0rd", + "myspace!", + "williams", + "gabriela", + "77777777", + "christopher", + "soccer11", + "westside", + "giovanni", + "apple123", + "zachary1", + "christophe", + "123456aa", + "pumpkin1", + "nirvana1", + "hotmail1", + "cookies1", + "shopping", + "password5", + "superstar1", + "maryjane", + "benjamin1", + "margarita", + "cristian", + "qwerasdf", + "motdepasse", + "147896325", + "25802580", + "westside1", + "iloveyou.", + "123456789m", + "grandma1", + "dbrnjhbz", + "dearbook", + "chris123", + "ladybug1", + "loveyou2", + "giuseppe", + "football12", + "sabrina1", + "september1", + "icecream1", + "loverboy", + "sterling", + "йцукен", + "christina1", + "virginia", + "savannah", + "inuyasha1", + "hallo123", + "twilight1", + "snickers1", + "friendster", + "adgjmptw", + "123456654321", + "champion", + "bestfriend", + "rhbcnbyf", + "internet1", + "teddybear", + "blessing", + "abcdefgh", + "happiness", + "password01", + "frankie1", + "Tnk0Mk16VX", + "aaaaaaaaaa", + "flowers1", + "cupcake1", + "johncena1", + "123456qwerty", + "192837465", + "PASSWORD", + "rangers1", + "bulldog1", + "simpsons", + "blahblah", + "carpediem", + "francisco", + "19871987", + "veronika", + "пароль", + "airforce1", + "inuyasha", + "casanova", + "123456789z", + "chocolat", + "jackson5", + "W5tXn36alfW", + "nintendo", + "321654987", + "19851985", + "hollywood", + "runescape1", + "pass1234", + "spencer1", + "cheyenne", + "99999999", + "panasonic", + "12369874", + "ciaociao", + "florence", + "123321123", + "phoenix1", + "anderson", + "warcraft", + "poiuytrewq", + "myspace12", + "colorado", + "Passw0rd", + "gangster1", + "mamapapa", + "sweetie1", + "business", + "maradona", + "rammstein", + "microsoft", + "kimberly1", + "ihateyou1", + "soccer13", + "babygirl2", + "hollywood1", + "anastasia", + "jesus777", + "redneck1", + "zzzzzzzz", + "lasvegas", + "stonecold", + "maxwell1", + "princess2", + "19861986", + "sureno13", + "savannah1", + "engineer", + "paintball1", + "19841984", + "135792468", + "amsterdam", + "skittles", + "forever21", + "14789632", + "19921992", + "orlando1", + "children", + "christmas", + "asdasd123", + "cocacola1", + "snowball1", + "123456123", + "sebastian1", + "drowssap", + "soccer123", + "kingkong", + "123456123456", + "gangster", + "7894561230", + "shithead1", + "patches1", + "trouble1", + "mercedes1", + "MaprCheM56458", + "digital1", + "maryjane1", + "stephen1", + "kathleen", + "marshall", + "hahahaha", + "zaq1xsw2", + "minecraft", + "argentina", + "serenity", + "password4", + "a1s2d3f4", + "alexandre", + "barcelona1", + "123789456", + "password00", + "georgia1", + "porsche1", + "1qaz1qaz", + "megaparol12345", + "iG4abOX4", + "dragonball", + "football2", + "nathalie", + "trinity1", + "colombia", + "killer123", + "bullshit1", + "terminator", + "69696969", + "onelove1", + "password13", + "baseball12", + "nothing1", + "myspace.", + "harrypotter", + "security", + "elephant1", + "teddybear1", + "winston1", + "summer08", + "sexybitch1", + "welcome123", + "katherine", + "scotland", + "dinosaur", + "iloveyou3", + "fuckyou69", + "19891989", + "admin123", + "federico", + "success1", + "cutiepie1", + "green123", + "trfnthbyf", + "12301230", + "margaret", + "godisgood", + "charlotte1", + "11112222", + "19821982", + "david123", + "beatrice", + "hardcore1", + "franklin", + "123456789s", + "любовь", + "penelope", + "mitchell", + "66666666", + "hercules", + "katerina", + "allison1", + "charmed1", + "babydoll", + "christine1", + "123456987", + "india123", + "monique1", + "19801980", + "1loveyou", + "20102010", + "blackberry", + "19951995", + "alejandro1", + "iloveme2", + "1234asdf", + "music123", + "9-11-1961", + "skittles1", + "cdtnkfyf", + "tokiohotel", + "1234567q", + "ka_dJKHJsy6", + "qazwsx12", + "idontknow", + "truelove", + "houston1", + "shithead", + "wolverine", + "bradley1", + "mahalkita", + "sexygirl1", + "timothy1", + "19831983", + "yahoo.com", + "platinum", + "isabella1", + "password10", + "valentine", + "vampires", + "password0", + "strength", + "asd123456", + "10101010", + "baseball2", + "11235813", + "chopper1", + "g9l2d1fzPY", + "jamesbond", + "goldfish", + "carolina1", + "vincent1", + "summer09", + "packers1", + "martinez", + "cutiepie", + "D1lakiss", + "qazxswedc", + "diamonds", + "ferrari1", + "napoleon", + "13131313", + "panther1", + "zxcv1234", + "lacrosse", + "federica", + "123456789j", + "passport", + "buddy123", + "omsairam", + "bulldogs", + "ilovehim1", + "james123", + "cleopatra", + "1qa2ws3ed", + "Linkedin", + "catalina", + "wrestling1", + "Megaparol12345", + "fernanda", + "myspace3", + "harrison", + "blondie1", + "buttercup", + "muhammad", + "medicine", + "fuckme69", + "SZ9kQcCTwY", + "gordon24", + "19881988", + "stellina", + "1234567899", + "pa55w0rd", + "skateboard", + "pebbles1", + "stargate", + "natasha1", + "drummer1", + "abigail1", + "raymond1", + "thuglife", + "johnson1", + "pokemon123", + "remember1", + "sporting", + "salvatore", + "blablabla", + "handsome", + "johncena", + "14531453", + "penguin1", + "budlight1", + "infinity", + "naruto123", + "montana1", + "10203040", + "scoobydoo", + "jesuschrist", + "devil666", + "44444444", + "thebest1", + "1myspace", + "kittycat", + "pineapple", + "qwerty11", + "veronica1", + "PolniyPizdec110211", + "england1", + "mypassword", + "smoke420", + "wordpass", + "asdfasdf1", + "aaliyah1", + "genesis1", + "lilwayne1", + "spartan117", + "kkkkkkkk", + "password9", + "alexandra1", + "sk84life", + "salvador", + "newport1", + "daniel123", + "привет", + "darkness1", + "ilovejesus", + "summer07", + "melanie1", + "lawrence", + "alabama1", + "monkeys1", + "peterpan", + "dumbass1", + "ekaterina", + "2012comeer", + "lollipop1", + "cricket1", + "blahblah1", + "papillon", + "12131415", + "michigan", + "19941994", + "panthers", + "idontknow1", + "369258147", + "iloveyou7", + "mexican1", + "runescape", + "fordf150", + "ilovegod", + "spitfire", + "godzilla", + "33333333", + "azerty123", + "19931993", + "wildcats", + "test1234", + "mohammed", + "ladygaga", + "qweasd123", + "1princess", + "dragons1", + "bluefish", + "dolphins1", + "qwerty321", + "miranda1", + "cassandra", + "password22", + "something", + "qwe12345", + "dragon123", + "pitbull1", + "moonlight", + "password69", + "nonmember", + "5532361cnjqrf", + "19811981", + "tiger123", + "panthers1", + "jeffrey1", + "dodgers1", + "dickhead1", + "dragon12", + "guinness", + "123456asd", + "buttercup1", + "vampire1", + "loser123", + "dIWtgm8492", + "bulldogs1", + "123456789l", + "19901990", + "cheyenne1", + "friendship", + "cambiami", + "linkedin1", + "abcde12345", + "jamaica1", + "lindsey1", + "пїЅпїЅпїЅпїЅ", + "teacher1", + "zxcvbnm,./", + "yousuck1", + "myspace.co", + "babydoll1", + "987456321", + "bluebird", + "casablanca", + "password8", + "death666", + "watermelon", + "asdqwe123", + "predator", + "soccer14", + "19911991", + "123qwerty", + "candy123", + "babygurl", + "lucky123", + "loverboy1", + "lovelife", + "special1", + "3rJs5la8qE", + "3rJs1la2qE", + "sweetpea1", + "insanity", + "123456789d", + "ronaldinho", + "birthday", + "pussycat", + "123456qwe", + "fountain", + "christmas1", + "123456789k", + "sexygirl", + "viktoria", + "kristen1", + "shadow12", + "20092009", + "kenneth1", + "illinois", + "formula1", + "antonella", + "1357924680", + "yankees2", + "jaimatadi", + "sk8ordie", + "1234567890q", + "justice1", + "fuckyou12", + "kitty123", + "broncos1", + "qweqweqwe", + "paramore", + "atlanta1", + "assassin", + "alessandra", + "creative1", + "люблю", + "naruto12", + "drpepper", + "valencia", + "19781978", + "nks230kjs82", + "lover123", + "lovebug1", + "killer12", + "01020304", + "bella123", + "sunflower1", + "boobies1", + "defender", + "youngmoney", + "anhyeuem", + "пїЅпїЅпїЅпїЅпїЅпїЅпїЅпїЅпїЅпїЅ", + "barbara1", + "qazwsxedcrfv", + "lilmama1", + "9999999999", + "alejandra1", + "123456789p", + "redskins1", + "darkangel", + "liberty1", + "newlife1", + "sammy123", + "monalisa", + "butterfly2", + "prettygirl", + "getmoney", + "temppass", + "drpepper1", + "schalke04", + "pantera1", + "october1", + "sexymama1", + "a1b2c3d4e5", + "agent007", + "bubblegum1", + "qazwsxedc1", + "swordfish1", + "scorpio1", + "dickhead", + "patricia1", + "catherine1", + "dominic1", + "marissa1", + "cherokee", + "mommy123", + "molly123", + "katherine1", + "lorraine", + "explorer", + "cooldude", + "myspace7", + "porsche911", + "colombia1", + "12345679", + "CM6E7Aumn9", + "19791979", + "2222222222", + "bearshare", + "qawsedrf", + "indonesia", + "monkey11", + "justdoit", + "marines1", + "vikings1", + "aquarius", + "valentino", + "cookie123", + "baseball7", + "20082008", + "september2", + "kittycat1", + "kissmyass", + "Groupd2013", + "as123456", + "pickles1", + "michigan1", + "shadow123", + "chargers1", + "babyblue", + "skyline1", + "scoobydoo1", + "november1", + "wrestling", + "warrior1", + "superman12", + "pakistan1", + "ronaldo9", + "december1", + "dalejr88", + "geronimo", + "akopa123", + "bollocks", + "dominique", + "football10", + "perfect1", + "thuglife1", + "ghbdtnbr", + "dragonfly", + "bigdick1", + "sapphire", + "000000000", + "michael2", + "jeremiah", + "hotstuff1", + "19961996", + "icehouse", + "lindsay1", + "19751975", + "istanbul", + "OcPOOok325", + "thailand", + "personal", + "passion1", + "password23", + "hershey1", + "capricorn", + "fucklove1", + "babylove", + "vaffanculo", + "stanley1", + "marketing", + "123456789o", + "sandrine", + "qwerqwer", + "something1", + "charlene", + "baseball3", + "angelito", + "football7", + "american", + "aspirine", + "maverick1", + "claudia1", + "brother1", + "tottenham", + "westlife", + "7777777777", + "pokemon12", + "bobmarley", + "vacation", + "hotstuff", + "asdfghjkl:", + "1q2w3e4r5t6y7u8i9o0p", + "password6", + "stefania", + "nicole12", + "spiderman3", + "caroline1", + "gregory1", + "smile123", + "daddysgirl", + "1asshole", + "lollypop", + "lilwayne", + "cashmoney1", + "qqqqqqqq", + "ireland1", + "princess10", + "skate4life", + "scarlett", + "asdfjkl:", + "patriots1", + "josephine", + "callofduty", + "princesse", + "nightmare", + "germany1", + "soccer15", + "magnolia", + "longhorns1", + "tristan1", + "micheal1", + "testpass", + "112233445566", + "snowman1", + "douglas1", + "esperanza", + "enterprise", + "maria123", + "master123", + "warcraft1", + "qwe123456", + "asdfg123", + "williams1", + "cinderella", + "marshall1", + "baseball11", + "vincenzo", + "pavilion", + "cheater1", + "shamrock", + "purple12", + "australia1", + "sebastien", + "jellybean", + "google123", + "ironmaiden", + "3Odi15ngxB", + "country1", + "eternity", + "98765432", + "simpsons1", + "cracker1", + "gabrielle", + "Welcome1", + "superman2", + "shopping1", + "bettyboop1", + "ricardo1", + "marlboro1", + "juggalo1", + "therock1", + "monkey13", + "garfield1", + "arizona1", + "fashion1", + "rolltide", + "1234567890a", + "iloveyou22", + "daniel12", + "testtest", + "slayer666", + "baseball10", + "393041123", + "jordan12", + "12qw23we", + "washington", + "rfnthbyf", + "bananas1", + "123456789987654321", + "наташа", + "brownie1", + "everton1", + "ashley12", + "123456789123", + "987654321a", + "wangyut2", + "butthead1", + "newcastle", + "magdalena", + "rosebud1", + "ashley123", + "billabong", + "boricua1", + "●●●●`", + "underground", + "12345abc", + "iloveyou11", + "chandler", + "qwe1122334", + "andromeda", + "punkrock", + "just4fun", + "hamilton", + "iloveyou13", + "fuckyou.", + "whitney1", + "hernandez", + "juliette", + "19761976", + "dreamer1", + "pk3x7w9W", + "golfcourse", + "pineapple1", + "godislove", + "madeline", + "my2girls", + "babycakes1", + "yuantuo2012", + "marianne", + "cutie123", + "wildcats1", + "loveme123", + "fireball", + "montreal", + "1babygirl", + "12345qwe", + "hottie101", + "3d8Cubaj2E", + "serenity1", + "billabong1", + "1q2w3e4r5", + "PolniyPizdec1102", + "karolina", + "thumper1", + "e10adc3949ba59abbe56e057f20f883e", + "19971997", + "zxasqw12", + "baseball5", + "champion1", + "black123", + "aobo2010", + "rosemary", + "scrappy1", + "rocky123", + "highheel", + "eastside1", + "monkey22", + "qwe123qwe", + "cynthia1", + "asdf3423", + "sublime1", + "fernando1", + "19771977", + "myspace11", + "jellybean1", + "preston1", + "soccer22", + "maximus1", + "beatles1", + "hongkong", + "windows1", + "максим", + "nicole123", + "marianna", + "patriots", + "19731973", + "jesucristo", + "lovelove1", + "babygirl13", + "apollo13", + "iloveyou14", + "sentnece", + "nichole1", + "123456789123456789", + "football11", + "hannah123", + "telephone", + "budlight", + "TempPassWord", + "123456as", + "kaitlyn1", + "skywalker", + "fuckyou3", + "wonderful", + "jessica123", + "AKAX89Wn", + "23232323", + "qwertyuio", + "jordan123", + "freckles", + "guadalupe", + "bubblegum", + "oblivion", + "asshole2", + "angelica1", + "esmeralda", + "deftones", + "lionking", + "blackjack", + "revolution", + "purple123", + "galatasaray", + "12345678q", + "comeon11", + "tyler123", + "radiohead", + "zxcvbnm,", + "yahoo123", + "truelove1", + "puppies1", + "rocknroll", + "lkjhgfdsa", + "february", + "linkedin123", + "russell1", + "gabriele", + "classof09", + "123456789b", + "corvette1", + "frederic", + "makaveli", + "dkflbvbh", + "fucklove", + "baller23", + "handball", + "soccer17", + "nintendo1", + "america10", + "january1", + "motherlode", + "angelina1", + "metal666", + "anthony2", + "password21", + "garrett1", + "starfish", + "spiderman2", + "caitlin1", + "abcdefghij", + "sarah123", + "justin123", + "paintball", + "anything", + "iloveyou4", + "princess11", + "sunshine2", + "1020304050", + "y6p67FtrqJ", + "princess13", + "1zn6FpN01x", + "logitech1", + "derrick1", + "pinkfloyd", + "starcraft", + "snowflake", + "stardust", + "ncc1701d", + "private1", + "newcastle1", + "football9", + "bluemoon", + "rodriguez", + "q123456789", + "lasvegas1", + "bettyboop", + "jason123", + "blueeyes", + "education", + "1b78ef23aa2506f41feecfcc45b66038", + "smallville", + "dietcoke", + "toulouse", + "daddy123", + "1a2s3d4f", + "jumpman23", + "snowboard", + "college1", + "blueberry", + "fireman1", + "19741974", + "flamingo", + "stephane", + "phantom1", + "football3", + "kissmyass1", + "riccardo", + "ilovehim", + "drowssap1", + "kingston", + "eleven11", + "anamaria", + "munchkin", + "michael123", + "mitchell1", + "hannah12", + "detroit1", + "giovanna", + "iloveyou5", + "michaela", + "anderson1", + "LinkedIn", + "25252525", + "lighthouse", + "5hsU75kpoT", + "singapore", + "katrina1", + "123456qw", + "aleksandra", + "carlitos", + "123456ab", + "justin12", + "gabriella", + "universal", + "1a2b3c4d5e", + "daniela1", + "cashmoney", + "nuttertools", + "ragnarok", + "rastaman", + "rebelde1", + "labrador", + "holiday1", + "cookie12", + "mexico13", + "warcraft3", + "blizzard", + "hamster1", + "adriana1", + "delpiero", + "cheese123", + "gonzalez", + "britney1", + "hottie12", + "thankyou", + "princess3", + "love12345", + "myspace13", + "naughty1", + "godfather", + "romashka", + "marijuana", + "valerie1", + "qwertyuio1", + "football5", + "disturbed1", + "princess!", + "f00tball", + "francis1", + "23456789", + "chocolate2", + "pizza123", + "123456qq", + "sexybitch", + "gladiator", + "xiang123456", + "vfrcbvrf", + "babygirl10", + "aaaa1111", + "skorpion", + "unicorn1", + "skate123", + "princess7", + "southpark1", + "crazy123", + "134679852", + "franklin1", + "summer06", + "philippe", + "d71lWz9zjS", + "drjynfrnt", + "cassidy1", + "789654123", + "kevin123", + "goldfish1", + "snuggles", + "amorcito", + "mackenzie", + "research", + "babyblue1", + "libertad", + "charlie2", + "blackcat", + "bethany1", + "buttons1", + "francois", + "flower123", + "phillip1", + "sunshine12", + "soccer21", + "power123", + "passwort1", + "hunting1", + "sooners1", + "12345678900", + "robinson", + "virginia1", + "baseball13", + "warriors", + "thegame1", + "cuddles1", + "nicolas1", + "jessica2", + "evolution", + "hawaii50", + "myspace5", + "zeppelin", + "trinidad", + "billybob", + "atlantis", + "woaini1314", + "mamamama", + "hottie123", + "clifford", + "rhfcjnrf", + "wordpass1", + "agnieszka", + "verbatim", + "qwertyqwerty", + "dthjybrf", + "captain1", + "sexyboy1", + "марина", + "ihateyou2", + "farfalla", + "natalia1", + "princess01", + "123456789c", + "calimero", + "ilovemymom", + "charlie123", + "soccer16", + "chemistry", + "mauricio", + "motocross", + "daisy123", + "cannabis", + "immortal", + "colorado1", + "babyboo1", + "applepie", + "cadillac", + "playstation", + "losangeles", + "fenerbahce", + "24682468", + "breanna1", + "alladin79", + "jeremiah1", + "arschloch", + "mnbvcxz1", + "semperfi", + "iw14Fi9jxL", + "vodafone", + "YfDbUfNjH10305070", + "blue1234", + "indiana1", + "marie123", + "american1", + "peter123", + "1million", + "kingkong1", + "louloute", + "carebear1", + "c43qpul5RZ", + "pussy123", + "justinbieber", + "elizabeth2", + "b9399f21060d4b5fcb6d3cf5fea8de", + "12345671", + "godbless", + "fuckyou7", + "summer12", + "fuckoff!", + "swimming1", + "123456789r", + "southside", + "1andonly", + "lavender", + "lacrosse1", + "imissyou", + "ericsson", + "lightning", + "cassandra1", + "falcons1", + "soccer23", + "19981998", + "sweetness", + "eclipse1", + "laurence", + "chrissy1", + "mastermind", + "yamahar1", + "papamama", + "layouts1", + "kristin1", + "qwertyu1", + "nevermind", + "felicidade", + "halloween", + "H2vWDuBjX4", + "alberto1", + "amandine", + "kennedy1", + "abhishek", + "priyanka", + "lovehurts1", + "wolfgang", + "cellphone1", + "friends2", + "kittykat", + "scruffy1", + "kristine", + "master12", + "zxcasdqwe", + "letmein2", + "danny123", + "morrison", + "lollypop1", + "vladislav", + "vRbGQnS997", + "austin316", + "кристина", + "surfing1", + "facebook1", + "iamthebest", + "eduardo1", + "dingdong", + "makayla1", + "sweetness1", + "sandiego", + "hunter12", + "godisgreat", + "allstar1", + "jackass2", + "football21", + "blackie1", + "chevrolet", + "hernandez1", + "skipper1", + "caramelo", + "loveless", + "batista1", + "myspace01", + "gerrard8", + "jessica12", + "ronaldo1", + "nightmare1", + "littleman1", + "backspace", + "fuckthis1", + "??????????", + "jacqueline", + "warhammer", + "anastasiya", + "cristiano", + "hello1234", + "madonna1", + "wachtwoord", + "19721972", + "southpark", + "joseluis", + "spongebob2", + "qazwsxedc123", + "starlight", + "shadow13", + "projectsadminx", + "motorola1", + "chicken2", + "sk8board", + "universe", + "kristina1", + "lincoln1", + "password14", + "x4ivygA51F", + "sexylady1", + "miami305", + "scotland1", + "s8YLPe9jDPvYM", + "dirtbike1", + "michael12", + "football22", + "pearljam", + "nokia6300", + "babygirl3", + "password1234", + "skeeter1", + "nathaniel", + "victory1", + "iloveyou123", + "oscar123", + "classof08", + "a1a2a3a4", + "monkey69", + "m123456789", + "chrisbrown", + "jG3h4HFn", + "diamonds1", + "123456789t", + "peace123", + "friday13", + "солнышко", + "ironman1", + "asdfgh123", + "andrew12", + "lucas123", + "platinum1", + "mathilde", + "анастасия", + "maurice1", + "pornstar", + "rooster1", + "opensesame", + "андрей", + "hotgirl1", + "microlab", + "player69", + "microsoft1", + "iloveu123", + "michele1", + "soulmate", + "university", + "toshiba1", + "dallas214", + "ab123456", + "campbell", + "pothead1", + "sampson1", + "london12", + "pass1word", + "motherfucker", + "18atcskD2W", + "cinnamon", + "monamour", + "krystal1", + "chevelle", + "verizon1", + "0102030405", + "dominique1", + "eleonora", + "theking1", + "myspace4", + "fktrcfylhf", + "tacobell1", + "asdfghjkl;'", + "slimshady", + "redhead1", + "lovelife1", + "sherlock", + "19071907", + "mushroom", + "w66YRyBgRa", + "topolino", + "computer12", + "webhompass", + "richmond", + "r2d2c3po", + "batman123", + "michelle12", + "loveme12", + "chiquita", + "12345abcde", + "tigger12", + "Aa123456", + "project1", + "viewsonic", + "freddie1", + "baseball9", + "12345678901", + "Parola12", + "hermione", + "president", + "dedewang", + "rolltide1", + "qweasdzxc123", + "gorgeous", + "g13916055158", + "amanda123", + "X3LUym2MMJ", + "virginie", + "juancarlos", + "marathon", + "andrew123", + "babylove1", + "baseball8", + "snowflake1", + "tottenham1", + "ILOVEYOU", + "alfaromeo", + "20002000", + "katie123", + "kamikaze", + "321321321", + "nickjonas1", + "boomboom", + "vfvfgfgf", + "mexico123", + "love123456", + "hellohello", + "bitch101", + "Password123", + "catfish1", + "taekwondo", + "anthony123", + "1Fr2rfq7xL", + "keyboard", + "francisco1", + "snowboard1", + "trucker1", + "laetitia", + "james007", + "happiness1", + "amber123", + "holahola", + "babyface", + "summer123", + "deathnote", + "1234567890-", + "wolverine1", + "professional", + "roberto1", + "brittney1", + "jefferson", + "mammamia", + "vanilla1", + "desiree1", + "patience", + "asdfgh12", + "11221122", + "sasha123", + "fuckoff2", + "diosesamor", + "estrella1", + "sweet123", + "Telechargement", + "cartman1", + "harrison1", + "familyguy1", + "walmart1", + "elisabeth", + "carebear", + "azsxdcfv", + "luckydog", + "password99", + "pingpong", + "bobby123", + "qwertyuiop123", + "castillo", + "kathleen1", + "priscilla", + "killbill", + "8888888888", + "qweqwe123", + "12345654321", + "badgirl1", + "babygirl11", + "iloveyou8", + "gianluca", + "a123456a", + "onepiece", + "angel101", + "iloveyou10", + "d9Zufqd92N", + "ultimate", + "summertime", + "fussball", + "jacob123", + "paradise1", + "johnjohn", + "taylor12", + "martinez1", + "sparkle1", + "hunter123", + "mario123", + "butterfly7", + "p4ssw0rd", + "amanda12", + "jobsearch", + "marijuana1", + "syncmaster", + "fuckyou13", + "buster123", + "squirrel", + "youbye123", + "general1", + "oakland1", + "converse", + "3children", + "nounours", + "babycakes", + "honey123", + "airforce", + "01234567", + "blueberry1", + "theresa1", + "emily123", + "cucciolo", + "19691969", + "dragon13", + "nascar24", + "discovery", + "darkside", + "suckmydick", + "jenny123", + "yellow12", + "ilovemusic", + "annabelle", + "beyonce1", + "ashleigh", + "369852147", + "networking", + "abcdef123", + "24681012", + "summer11", + "mackenzie1", + "huhbbhzu78", + "download", + "national", + "69camaro", + "ilovegod1", + "designer", + "guillaume", + "business1", + "iloveher1", + "socrates", + "asshole123", + "soccer18", + "buffalo1", + "chargers", + "1truelove", + "rochelle", + "student1", + "voyager1", + "nokia123", + "Qwerty123", + "godisgood1", + "softball12", + "j123456789", + "cooldude1", + "cheese12", + "backspace1", + "nopassword", + "abdullah", + "87654321q", + "hellfire", + "valentine1", + "domenico", + "renegade", + "pikachu1", + "abracadabra", + "20012001", + "harmony1", + "tarheels", + "634142554", + "123mudar", + "chandler1", + "hendrix1", + "8ix6S1fceH", + "packers4", + "billybob1", + "qwertyuiop1", + "stoner420", + "werewolf", + "wolfpack", + "love4life", + "johannes", + "tigger123", + "playboy69", + "jeffhardy1", + "brandon2", + "gsxr1000", + "qwegta13091990", + "neopets12", + "my3girls", + "faithful", + "12qw34er", + "cfitymrf", + "flamengo", + "caterina", + "baseball4", + "westham1", + "football23", + "brucelee", + "123456789n", + "telefono", + "airborne", + "smile4me", + "babylon5", + "omarion1", + "buster12", + "jesuschris", + "3333333333", + "maximilian", + "amazing1", + "attitude", + "univers2l", + "shadow11", + "myspace10", + "santiago1", + "fantasy1", + "25251325", + "15426378", + "bentley1", + "chargers21", + "estrellita", + "primavera", + "treasure", + "lorenzo1", + "soldier1", + "trigger1", + "mersedes", + "milagros", + "water123", + "bubbles2", + "margaret1", + "samsung123", + "nokian73", + "1q2w3e4r5t6y7u", + "yellow123", + "fernandez", + "anthony12", + "doberman", + "kingdom1", + "summer10", + "fuckfuck", + "coldplay", + "matematica", + "clayton1", + "anaconda", + "qweasdzxc1", + "welcome2", + "godzilla1", + "whocares", + "kickass1", + "katelyn1", + "felicidad", + "19701970", + "asdfg12345", + "fuckyou666", + "damilola", + "princess5", + "sandiego1", + "angelika", + "sexylady", + "6666666666", + "123456789g", + "godfather1", + "ilovemom1", + "bangladesh", + "nascar88", + "sexybeast1", + "presario", + "mohammad", + "joshua12", + "dragonfly1", + "p@ssword", + "bastard1", + "johndeere", + "porkchop", + "school123", + "football13", + "l1nk3d1n", + "baseball22", + "moneymaker", + "mariposa1", + "babyphat1", + "iforgot1", + "london123", + "eastside", + "1qaz!QAZ", + "beckham7", + "mountain1", + "geraldine", + "vfhufhbnf", + "messenger", + "1football", + "puertorico", + "michelle2", + "dfktynbyf", + "musicman", + "michael7", + "sayangku", + "thomas12", + "flower12", + "batman12", + "senha123", + "rainbow6", + "megadeth", + "excalibur", + "mississippi", + "asd12345", + "viktoriya", + "александр", + "information", + "armagedon", + "snuggles1", + "abc123abc", + "freestyle", + "security1", + "shirley1", + "kakashka", + "fuckyou22", + "harrypotte", + "johndeere1", + "zxcvbnm12", + "13243546", + "love2010", + "elefante", + "iloveme!", + "q1234567", + "wildcat1", + "doraemon", + "bitchass1", + "01230123", + "buckeyes", + "alexandr", + "roadrunner", + "darkstar", + "p0o9i8u7", + "international", + "baby1234", + "testing123", + "trumpet1", + "satellite", + "mississipp", + "princesita", + "iloveyou69", + "houston713", + "chicken123", + "angelique", + "fucking1", + "moneyman1", + "brittney", + "joker123", + "ilovemyself", + "scvMOFAS79", + "sailormoon", + "lalalala", + "marjorie", + "teamo123", + "rodriguez1", + "ilikepie", + "pussycat1", + "soccer09", + "goodgirl", + "babyface1", + "sundance", + "soccer101", + "виктория", + "scorpion1", + "sexy1234", + "amarillo", + "cardinal", + "anything1", + "mama1234", + "airplane", + "angelbaby1", + "honeybee", + "hollister2", + "home0401", + "tacobell", + "kisskiss", + "summer69", + "cardinals1", + "tootsie1", + "20202020", + "paramore1", + "iloveme123", + "december12", + "qwerty13", + "spartan1", + "thomas123", + "myspace08", + "kendall1", + "nintendo64", + "nathaniel1", + "chelsea123", + "jordan11", + "letmein123", + "evangelion", + "english1", + "alexandru", + "ilovejusti", + "yfcntymrf", + "megasecret", + "babygirl14", + "supernova", + "jesusislord", + "katarina", + "cristina1", + "catarina", + "carlos123", + "1fuckyou", + "puppy123", + "lovergirl1", + "trinity3", + "jesusis1", + "christy1", + "michael3", + "lawrence1", + "noodles1", + "fuckface1", + "whatsup1", + "wrangler", + "babygirl01", + "family123", + "bubba123", + "z1x2c3v4", + "emmanuel1", + "television", + "dannyboy", + "whiskers", + "manunited", + "malaysia", + "7253497a5e31bd64", + "chivas10", + "myspace101", + "qaz123456", + "chronic1", + "qwerasdfzxcv", + "goddess1", + "baseball21", + "74108520", + "jimmy123", + "thirteen13", + "gameover", + "happyday", + "gilbert1", + "lowrider", + "jeffhardy", + "purple11", + "hondacivic", + "feder_1941", + "simpleplan", + "lovehurts", + "hellothere", + "dkflbckfd", + "alex1234", + "wow12345", + "waheguru", + "halloween1", + "sanchez1", + "пїЅпїЅпїЅпїЅпїЅпїЅпїЅпїЅпїЅпїЅпїЅ", + "1й2ц3у", + "frances1", + "princesa1", + "purple13", + "monkey01", + "123456789w", + "mobster1", + "megaman1", + "iloveyou23", + "iamnumber1", + "surside13", + "741258963", + "19711971", + "gotohell", + "delphine", + "kawasaki1", + "hannibal", + "stalker1", + "oklahoma", + "bordeaux", + "q2w3e4r5", + "sunshine7", + "administrator", + "manager1", + "pavilion1", + "sylvester", + "orange123", + "cherry123", + "coconut1", + "fredfred", + "monkey10", + "thompson", + "classof201", + "myspace9", + "123456789e", + "babybaby", + "????????", + "santana1", + "standard", + "sandy123", + "orange12", + "lovesucks1", + "woodstock", + "dragon11", + "chloe123", + "margarita1", + "QWERTYUIOP", + "rootbeer", + "taylor123", + "hotmama1", + "loveyou123", + "trombone", + "pepper12", + "marino13", + "hockey12", + "memphis1", + "matthew2", + "kayleigh", + "hollister", + "dietcoke1", + "capslock", + "22446688", + "maggie12", + "princess14", + "pirates1", + "morpheus", + "lesbian1", + "20022002", + "alexander2", + "01010101", + "mexico12", + "sexymama", + "lokomotiv", + "football4", + "s123456789", + "dangerous", + "electric", + "827ccb0eea8a706c4c34a16891f84e7b", + "123456789f", + "freebird", + "redbull1", + "12345600", + "penguins", + "password15", + "starbucks1", + "cookiemons", + "arianna1", + "whatever!", + "chipper1", + "littleman", + "6V21wbgad", + "kittykat1", + "tarheels1", + "emanuele", + "giggles1", + "chivas11", + "trooper1", + "az123456", + "smackdown", + "kangaroo", + "whiskey1", + "football8", + "alexandria", + "summer01", + "bangalore", + "sunny123", + "brandon123", + "shitface1", + "punkrock1", + "salvation", + "penis123", + "umbrella", + "legolas1", + "akatsuki", + "123456789h", + "456456456", + "myfamily", + "mazda626", + "jonas123", + "skater123", + "faithful1", + "leonardo1", + "dont4get", + "brandon12", + "qwert1234", + "polopolo", + "cheesecake", + "159753456", + "myspace23", + "william2", + "shadow01", + "junior123", + "jasmine2", + "princess21", + "welcome12", + "motherfuck", + "rachael1", + "ilovemom", + "kentucky1", + "souljaboy1", + "supergirl", + "qwaszx12", + "blackjack1", + "rocknroll1", + "guillermo", + "corazon1", + "mymother", + "magic123", + "demon666", + "qqqq1111", + "krasotka", + "yomomma1", + "bobmarley1", + "nicole13", + "johnlock", + "roxanne1", + "mother123", + "friends123", + "hunter01", + "robert123", + "yamahar6", + "windows7", + "warriors1", + "changeme1", + "123йцу", + "princess22", + "11111111111", + "passport1", + "griffin1", + "19051905", + "kamasutra", + "brigitte", + "12345678s", + "никита", + "gabrielle1", + "fyutkbyf", + "salvador1", + "1q1q1q1q", + "123456781", + "nicole11", + "biscuit1", + "kelly123", + "camille1", + "myspace22", + "secret666", + "pimpdaddy1", + "birthday1", + "aaron431", + "blackdog", + "babygirl7", + "monopoly", + "ilovesex", + "kayla123", + "123abc123", + "789789789", + "1212121212", + "frogger1", + "dancing1", + "wonderland", + "joshua123", + "laura123", + "matthias", + "cutie101", + "fletcher", + "animals1", + "daredevil", + "kristian", + "shanghai", + "gabriela1", + "stratfor", + "любимая", + "password09", + "jeanette", + "spitfire1", + "lowrider1", + "maggie123", + "komputer", + "eatshit1", + "zaqxswcde", + "000webhost", + "thug4life", + "grandpa1", + "annette1", + "rachelle", + "ji394su3", + "dragon69", + "kentucky", + "america123", + "choupette", + "pokemon2", + "ichliebedich", + "shotgun1", + "prettyboy1", + "asdfghj1", + "mittens1", + "lol12345", + "forzamilan", + "positive", + "dragonballz", + "software", + "buckeyes1", + "bubbles123", + "мамочка", + "myspace09", + "19681968", + "harry123", + "rooney10", + "kathryn1", + "abrakadabra", + "marihuana", + "sexsexsex", + "fabrizio", + "pallmall", + "marilyn1", + "cherokee1", + "Alexander", + "Megaparol", + "liverpool8", + "mexico10", + "guardian", + "infiniti", + "smackdown1", + "monster123", + "hayabusa", + "qqqqqqqqqq", + "baseball23", + "starbucks", + "seattle1", + "sassy123", + "monkey23", + "clarence", + "beethoven", + "123456789abc", + "sunshine3", + "jupiter1", + "leavemealone", + "hurricane", + "sagitario", + "guadalupe1", + "mmmmmmmm", + "hooters1", + "justine1", + "bernardo", + "starcraft1", + "lancelot", + "iloveyou21", + "stargate1", + "lolipop1", + "princess4", + "vfitymrf", + "stefanie", + "maksimka", + "rootbeer1", + "mazdarx7", + "fuckyou5", + "00112233", + "charlie12", + "123456789.", + "bubbles12", + "brendan1", + "rebound1", + "passpass", + "mongoose", + "camaroz28", + "jasmine123", + "1234567m", + "lovingyou", + "kleopatra", + "motocross1", + "unknown1", + "tazmania", + "portugal1", + "original", + "butterfly3", + "preciosa", + "bowling1", + "diamante", + "pepsi123", + "linda123", + "963258741", + "hercules1", + "4myspace", + "pandora1", + "volkswagen", + "nikki123", + "nigga123", + "goldberg", + "123698741", + "zacefron1", + "jazmine1", + "1234zxcv", + "su123456", + "topsecret", + "ginger12", + "teddy123", + "ashley11", + "thesims2", + "asdfzxcv", + "together", + "neveragain", + "soccer19", + "stranger", + "samantha12", + "colleen1", + "ilovechris", + "любимый", + "bellissima", + "peterpan1", + "principessa", + "titanic1", + "pornstar1", + "asdasd666", + "ms0083jxj", + "joejonas1", + "jordan01", + "myspace69", + "lillian1", + "vancouver", + "missy123", + "dance123", + "butthole1", + "nacional", + "12312312", + "bkl29m2bk", + "14141414", + "pothead420", + "start123", + "babyboy2", + "mylove123", + "yolanda1", + "capricorn1", + "linkedin2011", + "stingray", + "qazxsw123", + "hannah01", + "mustang2", + "iloveyou9", + "gonzales", + "moonlight1", + "fgtkmcby", + "1sunshine", + "swimmer1", + "mylinkedin", + "dirtbike", + "asdfghjkl123", + "incorrect", + "wallace1", + "skater12", + "daniel01", + "dorothy1", + "techn9ne", + "loredana", + "jesussaves", + "moneyman", + "lalala123", + "a1s2d3f4g5", + "brayden1", + "azertyui", + "1234rewq", + "myspace8", + "princess23", + "chivas123", + "5plK4L5Uc7", + "12qw12qw", + "sandman1", + "punisher", + "longhorn", + "konstantin", + "bigpimpin1", + "bearbear", + "slipknot6", + "panda123", + "fuckyou11", + "lonewolf", + "charger1", + "king1234", + "iloveu12", + "megan123", + "temp1234", + "", + "gretchen", + "josefina", + "escorpion", + "robert12", + "register", + "cleveland", + "director", + "senior09", + "computer2", + "septiembre", + "gandalf1", + "1234567j", + "married1", + "marcello", + "jesusfreak", + "shakira1", + "chickens", + "allah786", + "redwings1", + "mickeymouse", + "mallorca", + "fallout3", + "jordan13", + "madeline1", + "mV46VkMz10", + "koolaid1", + "cardinals", + "gamecube", + "austin12", + "felicia1", + "dominican1", + "bighead1", + "darkangel1", + "ilove123", + "cowgirl1", + "iamcool1", + "mission1", + "respect1", + "portland", + "armando1", + "love2009", + "columbia", + "anthony3", + "stunt101", + "qwerty77", + "november11", + "isabelle1", + "celeste1", + "madagascar", + "celtic1888", + "madeleine", + "bugsbunny", + "ashley13", + "1iloveyou", + "ohiostate1", + "argentina1", + "meredith", + "simpson1", + "finalfantasy", + "madison2", + "jeffery1", + "computador", + "basketbal1", + "slipknot66", + "coolgirl", + "durango1", + "cavalier", + "meowmeow", + "babygirl15", + "ordinateur", + "Pa55word", + "children3", + "incubus1", + "woaiwojia", + "freckles1", + "15151515", + "123456aaa", + "honduras", + "maryjane42", + "warszawa", + "babygirl5", + "fabulous", + "1qazzaq1", + "20052005", + "123321123321", + "3.1415926", + "coolman1", + "teamomucho", + "shorty13", + "rammstein1", + "qwerty777", + "longhorns", + "happydays", + "anthony7", + "mileycyrus", + "harley01", + "showtime", + "asd123asd", + "z123456789", + "123123123123", + "abcde123", + "пїЅпїЅпїЅпїЅпїЅпїЅ@mail.ru", + "superman7", + "ilovehim2", + "google12", + "imnumber1", + "guitarra", + "baseball24", + "secret123", + "mariana1", + "pepper123", + "girasole", + "classic1", + "multiplelog", + "myspace0", + "tabitha1", + "deftones1", + "gizmo123", + "cheer123", + "lifesucks1", + "Jennifer", + "valentina1", + "bernard1", + "Abcd1234", + "babygurl12", + "peekaboo", + "fuckface", + "violetta", + "mechanical", + "daniel11", + "168ASD168", + "sexylove1", + "redalert", + "shorty12", + "12211221", + "chivas12", + "budweiser", + "superman3", + "iverson1", + "bumblebee", + "arsenal123", + "newpassword", + "ginger123", + "bigmoney", + "yankees13", + "1357913579", + "wellington", + "bunny123", + "789632145", + "braveheart", + "mercury1", + "puppylove1", + "wonderful1", + "1234567s", + "venezuela", + "hellsing", + "revenge1", + "applepie1", + "racecar1", + "7777777a", + "monkey101", + "monkey21", + "cleveland1", + "blueeyes1", + "bigmoney1", + "123456789qwe", + "password88", + "junebug1", + "19991999", + "music101", + "19031903", + "sterling1", + "lemonade", + "tigger01", + "1z2x3c4v", + "!qaz2wsx", + "dortmund", + "gorgeous1", + "hustler1", + "hockey11", + "school12", + "il0veyou", + "cantona7", + "W5tn36alfW", + "vendetta", + "waterloo", + "lightning1", + "technics", + "spectrum", + "princess15", + "playboy123", + "thankgod", + "georgina", + "broadway", + "demon123", + "central1", + "babygirl09", + "crip4life", + "peanut12", + "maradona10", + "lifesucks", + "clifford1", + "chicken12", + "ashleigh1", + "michael23", + "Michael1", + "1qay2wsx", + "19671967", + "tenerife", + "teiubesc", + "mckenzie", + "firebird1", + "baseball14", + "annamaria", + "chemical", + "testing1", + "beckham23", + "steelers7", + "miracle1", + "mariajose", + "coolkid1", + "c.ronaldo", + "pooppoop", + "softball11", + "heineken", + "1111qqqq", + "qwertyui1", + "emachines1", + "brighton", + "abcd123456", + "password24", + "baseball6", + "jasmine12", + "grizzly1", + "cruzazul", + "babygirl!", + "northside1", + "addison1", + "1й2ц3у4к", + "senior08", + "youtube1", + "carlotta", + "stewart1", + "scooter2", + "0192837465", + "joshua01", + "theused1", + "qdujvyG5sxa", + "pakistan123", + "oklahoma1", + "valeria1", + "candice1", + "overlord", + "1michael", + "rockets1", + "porcodio", + "imperial", + "astonvilla", + "emiliano", + "tommy123", + "aerosmith", + "tequila1", + "briciola", + "helloworld", + "jermaine1", + "semperfi1", + "dylan123", + "sonyericsson", + "nokia5800", + "junior12", + "qwer4321", + "diamond2", + "lionheart", + "sullivan", + "money100", + "chevys10", + "mickey12", + "jessica3", + "qwerty78", + "left4dead", + "ssyu1314", + "football!", + "111111111111", + "bookmark", + "123456abcd", + "daniel13", + "classof200", + "aaron123", + "sexyman1", + "lamborghini", + "honduras1", + "rainbows", + "jillian1", + "159159159", + "westwood", + "girlfriend", + "zaqwsxcde", + "sunshine!", + "mustangs", + "noisette", + "margherita", + "coolcool", + "password19", + "password08", + "mickey123", + "123qwe123qwe", + "highlander", + "19651965", + "passwords", + "network1", + "thirteen", + "ilikepie1", + "zeppelin1", + "whatever2", + "nicole01", + "chelseafc", + "blossom1", + "xboxlive", + "lampard8", + "football24", + "daniella", + "myspace07", + "classof07", + "20072007", + "daughter", + "333333333", + "adventure", + "football20", + "andrew11", + "chevrolet1", + "100200300", + "softball2", + "45454545", + ".adgjmptw", + "23jordan", + "poptart1", + "evergreen", + "compton1", + "fuckthis", + "forzaroma", + "123456789v", + "m1234567", + "glitter1", + "aaaaaaa1", + "0147258369", + "guatemala", + "iloveyou09", + "cheetah1", + "friends!", + "shorty123", + "cadillac1", + "lorraine1", + "carlos12", + "webster1", + "lifeisgood", + "Michelle", + "softball10", + "theodore", + "rdfhnbhf", + "starlight1", + "softball7", + "zzzzzzzzzz", + "justin11", + "poseidon", + "jennifer12", + "october10", + "godislove1", + "chivas13", + "peanuts1", + "clarinet", + "italian1", + "supergirl1", + "silverado", + "lenochka", + "iloveyou15", + "12345qwer", + "commando", + "porkchop1", + "stanislav", + "music4life", + "qwerty69", + "meandyou", + "cucciola", + "munchkin1", + "george123", + "flipper1", + "whiteboy1", + "nightwish", + "principe", + "ilovemysel", + "1234567z", + "front242", + "пїЅпїЅпїЅпїЅпїЅпїЅпїЅпїЅпїЅпїЅпїЅпїЅ", + "fuckyou6", + "iloveyou6", + "handsome1", + "coolcat1", + "159753123", + "twilight12", + "lipgloss1", + "christian2", + "aptx4869", + "crjhgbjy", + "forzainter", + "freedom2", + "soccer08", + "halflife", + "myspace200", + "contraseña", + "albatros", + "killer13", + "princess8", + "livelife", + "budweiser1", + "jackjack", + "emerald1", + "blessings", + "solomon1", + "soccer07", + "holland1", + "14881488", + "resident", + "zxcvb123", + "control1", + "bailey12", + "america12", + "justin01", + "hannah11", + "rainbow123", + "a1a2a3a4a5", + "123456qwer", + "йцукенгшщз", + "thomas22", + "ssssssss", + "jessica7", + "myspace201", + "dr.pepper", + "blackbird", + "kittens1", + "jamesbond007", + "dfg5Fhg5VGFh1", + "seniseviyorum", + "babygirl21", + "friends4", + "pistons1", + "12345677", + "fuckyou4", + "spanish1", + "confused1", + "tecktonik", + "geoffrey", + "candycane1", + "1a2a3a4a", + "bengals1", + "abcdefgh1", + "p455w0rd", + "jessica13", + "1diamond", + "love2008", + "chivas100", + "bernadette", + "22334455", + "veronique", + "martin123", + "cecilia1", + "brothers", + "baseball15", + "plymouth", + "enrique1", + "summer05", + "killer11", + "jerusalem", + "cowboys22", + "simon123", + "XBLhInTB9w", + "tomorrow", + "spartans", + "alexis12", + "476730751", + "notebook", + "7uGd5HIp2J", + "456789123", + "giovanni1", + "00001111", + "soccer01", + "matthew12", + "barbie123", + "huskers1", + "babygirl23", + "youandme", + "history1", + "babygirl16", + "frederick", + "maggie01", + "softball13", + "leonard1", + "beautiful2", + "clemson1", + "kobebryant", + "password20", + "princess16", + "paperino", + "sephiroth", + "innocent", + "73501505", + "chronic420", + "J1V1fp2BXm", + "iloveyouba", + "home1234", + "gabriel123", + "jamie123", + "nokian70", + "clarissa", + "juventus1", + "martina1", + "lineage2", + "football09", + "ashley01", + "goodbye1", + "zxc123456", + "hamilton1", + "cristian1", + "couponSC10", + "woaini520", + "murcielago", + "buster01", + "ncc1701a", + "medicina", + "fighter1", + "soccer24", + "soccer20", + "milkshake1", + "buckeye1", + "maurizio", + "123admin321", + "atlars10", + "mikejones1", + "85208520", + "123stella", + "florencia", + "5X1CJdsb9p", + "winchester", + "nigger123", + "йцукенгшщзхъ", + "gertrude", + "aa123123", + "sadie123", + "freeman1", + "123456zxc", + "vacation1", + "il0vey0u", + "bugsbunny1", + "rastafari", + "20062006", + "hardrock", + "mallory1", + "1234509876", + "jocelyn1", + "explorer1", + "lovergirl", + "password07", + "loveislife", + "jesse123", + "confused", + "nokian95", + "account1", + "marseille13", + "hurricane1", + "qwerty666", + "20112011", + "scarlett1", + "daniel19", + "pinkfloyd1", + "mark_963", + "livestrong", + "1butterfly", + "twister1", + "billy123", + "lovely12", + "hm9958123", + "asshole12", + "a1a1a1a1", + "francine", + "family12", + "bigfoot1", + "amsterdam1", + "morning21", + "redsox04", + "gfhjkm123", + "qwerty22", + "tatiana1", + "september9", + "bailey123", + "dragon01", + "@bigmir.net", + "superfly", + "manning18", + "number12", + "ilovehim!", + "1anthony", + "14121412", + "mazda323", + "melinda1", + "19641964", + "tkbpfdtnf", + "starfish1", + "princess9", + "candyman", + "butterfly8", + "terrell1", + "jingjing", + "rockstar12", + "010203040506", + "panasonic1", + "hotpink1", + "aaaaaaaaa", + "almighty", + "??????@mail.ru", + "bridget1", + "macarena", + "1q2w3e4r5t6y7u8i", + "sexybaby1", + "andrew01", + "scarlet1", + "meatball", + "какашка", + "qwertyuiop12", + "1qazxsw23edc", + "малышка", + "1213141516", + "Princess", + "training", + "hunter11", + "buddyboy", + "24242424", + "charlie3", + "lovely123", + "raiderz1", + "deadman1", + "mystery1", + "shooter1", + "pauline1", + "linkedln", + "disturbed", + "786786786", + "adelaide", + "1zxcvbnm", + "bluebird1", + "rjycnfynby", + "nursing1", + "hgrFQg4577", + "lauren12", + "monkeybutt", + "joseph12", + "sophie123", + "booboo12", + "friends12", + "deborah1", + "lexmark1", + "keyboard1", + "remington", + "babygirl69", + "barbados", + "xiaoxiao", + "ghjcnjnfr", + "blizzard1", + "landrover", + "italiano", + "d123456789", + "fireball1", + "5845201314", + "matthew123", + "london22", + "davidson", + "blackrose", + "banana11", + "sidekick3", + "babygirl22", + "arsenal14", + "annalisa", + "thomas01", + "milkshake", + "michael11", + "gorilla1", + "surenos13", + "princess19", + "newstart", + "malcolm1", + "mollydog", + "football14", + "1234567890-=", + "ilovepussy", + "christelle", + "extreme1", + "trojans1", + "hottie13", + "концертных площадок и умных студентов:", + "chocolate!", + "sherman1", + "nathan12", + "comcast1", + "W1aUbvOQ", + "sparkles", + "waterfall", + "oliveira", + "password17", + "jordan10", + "rbOTmvZ954", + "raffaele", + "navigator", + "playstation3", + "cellphone", + "135798642", + "summer2010", + "monkey14", + "password16", + "yangyang", + "spike123", + "qwerty01", + "andrea123", + "123456789*", + "millwall", + "booboo123", + "radiohead1", + "deutschland", + "dt123456", + "william123", + "naruto11", + "softball3", + "krokodil", + "honda123", + "darkstar1", + "caramel1", + "2children", + "love4you", + "carolyn1", + "Blink123", + "ncc1701e", + "koroleva", + "love5683", + "michael13", + "jermaine", + "monster2", + "mike1234", + "fucklove13", + "firefly1", + "peanut123", + "slimshady1", + "ihateyou!", + "holly123", + "liverpool9", + "hospital", + "cepetsugih", + "blueblue", + "shamrock1", + "mickeymous", + "rjhjktdf", + "19631963", + "Sunshine", + "babygirl4", + "homework", + "converse1", + "cookies123", + "twinkle1", + "opelastra", + "mother12", + "badminton", + "fucku123", + "cinnamon1", + "qw123456", + "iloveher", + "myspace21", + "makaveli1", + "julia123", + "crosby87", + "princess09", + "yfdbufnjh63", + "nissan350z", + "victoire", + "nathan123", + "contrasena", + "babygirl19", + "cherry12", + "knights1", + "asshole!", + "google.com", + "bossman1", + "snoopdog", + "apples123", + "purple22", + "password77", + "killer666", + "fisherman", + "besiktas", + "Sample123", + "babatunde", + "godsmack", + "believe1", + "skinhead", + "21122112", + "kochanie", + "fuckyoubit", + "bushido1", + "pedro123", + "mandarin", + "champagne", + "august12", + "asd666fds", + "gameboy1", + "evanescence", + "123456qaz", + "qwertasdfg", + "pancakes", + "123456798", + "marcella", + "diego123", + "skywalker1", + "michaeljac", + "ячсмит", + "theboss1", + "pokemon11", + "01011980", + "fxzZ75yer", + "19661966", + "tweetybird", + "banana123", + "vittoria", + "harley123", + "fantasia", + "bmx4life", + "summer99", + "fuckyou8", + "greenday12", + "zxcasdqwe123", + "familia1", + "taylor01", + "cutegirl", + "carmelo15", + "wsbe279qSG", + "professor", + "password33", + "frank123", + "zxcvb12345", + "liverp00l", + "gymnast1", + "donovan1", + "forever2", + "sasuke12", + "nicole22", + "woaini123", + "pudding1", + "rhiannon", + "poochie1", + "everything", + "guerrero", + "fuckme123", + "blackops", + "bangbang", + "pioneer1", + "bailey01", + "Letmein1", + "thursday", + "survivor", + "polaris1", + "anarchy1", + "pharmacy", + "matilda1", + "liverpool2", + "star1234", + "666666666", + "Charlie1", + "francisca", + "dallas22", + "coolguy1", + "myspace14", + "1superman", + "ilovemybab", + "dominika", + "badbitch1", + "grace123", + "heartbreak", + "dragon88", + "pasword1", + "guatemala1", + "offspring", + "diablo666", + "malachi1", + "ромашка", + "klapaucius", + "guinness1", + "grenouille", + "lovehate", + "ludacris", + "lalaland", + "october31", + "prashant", + "mitsubishi", + "stallion", + "kirsten1", + "abcd12345", + "babygirl08", + "blingbling", + "blackman", + "princess08", + "lovesucks", + "iloveyou08", + "123soleil", + "yellow11", + "mash4077", + "987654321q", + "1q2w3e4r5t6y7u8i9o", + "suzanne1", + "mermaid1", + "cherries", + "bookworm", + "mynameis", + "1a2s3d4f5g", + "pinky123", + "logan123", + "2bornot2b", + "wednesday", + "monster12", + "thedoors", + "emerica1", + "darling1", + "ghblehjr", + "bonjour1", + "miguelito", + "austin123", + "pr1ncess", + "poohbear12", + "nickjonas", + "andre123", + "harley12", + "12345678m", + "richmond1", + "juanita1", + "anuradha", + "pimping1", + "andreas1", + "elsalvador", + "555555555", + "sweety12", + "maryland", + "aberdeen", + "snoopy123", + "moncoeur", + "springer", + "nssadmin", + "goodness", + "battlefield", + "hottie11", + "12345678900987654321", + "butthole", + "456123789", + "printer1", + "destiny2", + "сергей", + "tropical", + "raiders13", + "56565656", + "bionicle", + "sexyback1", + "246813579", + "theonly1", + "rainbow2", + "playstatio", + "jefferson1", + "telephone1", + "novembre", + "saturday", + "godofwar", + "asdfghjk1", + "pasquale", + "sinaloa1", + "mushroom1", + "sunshine11", + "pazzword123", + "fkbyjxrf", + "fabregas", + "cambridge", + "michael01", + "mamochka", + "alexis09", + "superman11", + "gymnastics", + "number11", + "lucifer666", + "kIkeunyw", + "fuckyou9", + "brian123", + "123asd123", + "солнце", + "lucky777", + "owt243yGbJ", + "lovestory", + "casey123", + "147896321", + "kifj9n7bfu", + "fuckoff123", + "nicole21", + "asdffdsa", + "puppylove", + "edward123", + "cutiepie12", + "k2010302", + "jaihanuman", + "alfred19", + "zimmer483", + "robinhood", + "qwerty789", + "fabienne", + "michael5", + "paper123", + "penelope1", + "morgan12", + "iloveyou16", + "john!20130605at1753", + "ichliebedi", + "dynasty1", + "catwoman", + "password18", + "toronto1", + "1qaz!qaz", + "bonehead", + "yourmom2", + "thegreat1", + "rencontre", + "caliente", + "desperado", + "rhjrjlbk", + "luckydog1", + "elliott1", + "stonecold1", + "ячсмить", + "crevette", + "wedding1", + "a1111111", + "margarida", + "daniel10", + "joseph123", + "ghostrider", + "j1234567", + "america2", + "pretty12", + "zanzibar", + "Eh1K9oh335", + "montecarlo", + "kakashi1", + "seventeen", + "peaceout", + "oranges1", + "123456789A", + "filomena", + "anthony13", + "monkeyman1", + "marcopolo", + "babygirl20", + "pokerface", + "diciembre", + "baller12", + "technology", + "babygirl9", + "kingking", + "littlebit1", + "2wsx3edc", + "20032003", + "1234567b", + "sharingan", + "taylor11", + "concrete", + "armstrong", + "superman23", + "chivas#1", + "twisted1", + "1234567d", + "bradford", + "candygirl1", + "alfredo1", + "aaa123123", + "ballin23", + "2blessed", + "tweety12", + "blaze420", + "myspace6", + "Benjamin", + "19411945", + "lobster1", + "jonathan12", + "pimpin69", + "mustang69", + "1chicken", + "sunderland", + "trinidad1", + "ramones1", + "borussia", + "underoath1", + "Linkedin1", + "splinter", + "littlebit", + "justin13", + "trenton1", + "running1", + "management", + "priscilla1", + "progress", + "chicken!", + "philips1", + "diana123", + "qazqazqaz", + "екатерина", + "bretagne", + "hahaha123", + "sneakers", + "faith123", + "angelbaby", + "19191919", + "12348765", + "patriots12", + "matthieu", + "a987654321", + "supermario", + "алексей", + "bryant24", + "78963214", + "minouche", + "dontforget", + "crepusculo", + "nicole14", + "children1", + "texas123", + "november19", + "qaz123wsx", + "mmmmmmmmmm", + "angelita", + "startrek1", + "hedgehog", + "hooligan", + "k123456789", + "eminem123", + "tennessee1", + "qwerty21", + "children2", + "greenbay", + "?????????", + "samuel01", + "edward12", + "51505150", + "budapest", + "jesus4me", + "colocolo", + "123456789i", + "cupcakes", + "pretty123", + "password12345", + "4children", + "gamecube1", + "fabulous1", + "TOPBUTTON", + "319f4d26e3c536b5dd871bb2c52e3178", + "pontiac1", + "ferdinand", + "makemoney", + "xxxxxxxxxx", + "my.space", + "darlene1", + "angeline", + "naruto13", + "Liverpool", + "mckenzie1", + "ghbywtccf", + "loveyou12", + "mikemike", + "boobear1", + "rainbow7", + "qwertzuiop", + "♥", + "selenagome", + "nokia3310", + "Password01", + "brandon3", + "mostwanted", + "alexis123", + "polarbear", + "pleasure", + "charlie7", + "pink1234", + "pringles", + "estefania", + "hockey10", + "buster11", + "solution", + "jasmine3", + "football33", + "boomboom1", + "nicole10", + "bautista", + "football6", + "august11", + "aaa123456", + "9293709b13", + "sonic123", + "fordf250", + "tigger11", + "romance1", + "bonjovi1", + "birdman1", + "aardvark", + "florence1", + "sexygurl1", + "discover", + "hogwarts", + "eldorado", + "1qaz@wsx", + "anthony5", + "4444444444", + "tinkerbel1", + "mygirls2", + "marlene1", + "fantastic", + "Passwort", + "supersonic", + "ilovejosh1", + "crackhead1", + "snoopdogg", + "senior07", + "illusion", + "sharpie1", + "0147852369", + "74107410", + "12345qaz", + "whiskers1", + "shinigami", + "fuckmylife", + "alexalex", + "danielle12", + "jack1234", + "123456789asd", + "belinda1", + "mazafaka", + "cachorro", + "desmond1", + "sasuke123", + "johanna1", + "impossible", + "deerhunter", + "chihuahua", + "grandkids", + "samurai1", + "architect", + "wishbone", + "liliana1", + "killers1", + "светлана", + "tequiero1", + "vanhalen", + "robinson1", + "joshua11", + "guilherme", + "blessing1", + "michael!", + "8PHroWZ624", + "matthew3", + "bluesky1", + "rosemary1", + "lover101", + "chocolate7", + "20082009", + "loveless1", + "iw14Fi9jwQa", + "1melissa", + "taylor13", + "fuckoff69", + "football08", + "columbus", + "weare138", + "hotchick1", + "operator", + "longhorn1", + "goodlife", + "underworld", + "qwerty99", + "tricolor", + "falcons7", + "yahoomail", + "fuckyou23", + "superman13", + "smokeweed1", + "kayleigh1", + "sarajevo", + "patrizia", + "kathmandu", + "iloveyou<3", + "fullmoon", + "crimson1", + "andrea12", + "1111111a", + "iloveyou01", + "capoeira", + "123123qwe", + "momanddad", + "friends4ev", + "charlie01", + "bigsexy1", + "sailboat", + "25800852", + "aaaaaaaaa1", + "poop1234", + "minicooper", + "jessica11", + "promise1", + "chocolate3", + "aurelien", + "gribouille", + "creation", + "4everlove", + "dutchess", + "steven123", + "starstar", + "fearless", + "anonymous", + "crusader", + "downtown", + "achilles", + "firewall", + "canadian", + "cucumber", + "sheridan", + "wireless", + "atlantic", + "wildfire", + "highland", + "alphabet", + "webmaster", + "question", + "nebraska", + "bullseye", + "valhalla", + "criminal", + "crackers", + "insomnia", + "terminal", + "paranoid", + "doomsday", + "reynolds", + "magician", + "intrepid", + "dynamite", + "username", + "sherwood", + "moonbeam", + "honolulu", + "crawford", + "southern", + "sunlight", + "cerberus", + "republic", + "recovery", + "intruder", + "hastings", + "goldstar", + "commander", + "blackout", + "yesterday", + "phillips", + "monsters", + "keystone", + "grateful", + "continue", + "triangle", + "peterson", + "mandrake", + "hardware", + "ferguson", + "dominick", + "bullfrog", + "transfer", + "shepherd", + "property", + "pictures", + "mischief", + "macintosh", + "daffodil", + "charming", + "underdog", + "alliance", + "adrienne", + "sentinel", + "richards", + "mortimer", + "magazine", + "infantry", + "hopeless", + "fandango", + "deadhead", + "christie", + "billyboy", + "absolute", + "titanium", + "superior", + "spaceman", + "somebody", + "sinclair", + "pppppppp", + "military", + "felicity", + "brewster", + "valkyrie", + "chuckles", + "saratoga", + "majestic", + "kingfish", + "japanese", + "graphics", + "flounder", + "coltrane", + "checkers", + "augustus", + "washburn", + "stanford", + "rasputin", + "overkill", + "meatloaf", + "eastwood", + "dominion", + "destroyer", + "chipmunk", + "berkeley", + "thinking", + "seminole", + "platypus", + "mephisto", + "lancaster", + "knowledge", + "darklord", + "carnival", + "blowfish", + "sandwich", + "knuckles", + "benedict", + "sprinter", + "moonshine", + "missouri", + "meridian", + "gargoyle", + "disaster", + "complete", + "claymore", + "chainsaw", + "bluebell", + "thunderbird", + "smashing", + "playtime", + "lonestar", + "heritage", + "forsaken", + "challenger", + "backdoor", + "yosemite", + "yogibear", + "talisman", + "syracuse", + "randolph", + "raistlin", + "preacher", + "millions", + "metallic", + "dontknow", + "charisma", + "sinister", + "mcdonald", + "goldeneye", + "frontier", + "flipflop", + "eggplant", + "chrysler", + "buckshot", + "arkansas", + "archangel", + "romantic", + "robotics", + "megatron", + "hyperion", + "hamburger", + "friendly", + "dreaming", + "doghouse", + "christin", + "addicted", + "negative", + "computers", + "chestnut", + "auckland", + "wanderer", + "tomahawk", + "thanatos", + "roderick", + "pentagon", + "millenium", + "mechanic", + "creature", + "cornwall", + "chadwick", + "calendar", + "supervisor", + "revolver", + "railroad", + "minnesota", + "mariners", + "holyshit", + "database", + "bobafett", + "amethyst", + "albatross", + "advanced", + "whistler", + "slamdunk", + "sheffield", + "scrabble", + "roadkill", + "obsidian", + "northern", + "learning", + "independent", + "elements", + "electron", + "customer", + "brisbane", + "baritone", + "armageddon", + "windmill", + "surprise", + "starfire", + "speakers", + "lifetime", + "fredrick", + "fidelity", + "everyday", + "coolness", + "concorde", + "blackhawk", + "traveler", + "potatoes", + "pipeline", + "pathfinder", + "monterey", + "lipstick", + "lakeside", + "fishbone", + "biohazard", + "windsurf", + "velocity", + "vagabond", + "reloaded", + "raindrop", + "prudence", + "peaceful", + "multimedia", + "montgomery", + "marietta", + "ladybird", + "internal", + "gigabyte", + "fourteen", + "chambers", + "bunghole", + "apocalypse", + "aphrodite", + "zerocool", + "wrestler", + "tortoise", + "sysadmin", + "starship", + "primrose", + "politics", + "paranoia", + "overload", + "nevermore", + "melbourne", + "matthews", + "marriage", + "macaroni", + "jonathon", + "infinite", + "heinrich", + "graduate", + "godspeed", + "feedback", + "cornelia", + "corleone", + "choochoo", + "challenge", + "chairman", + "barracuda", + "accounting", + "sleeping", + "quicksilver", + "paradigm", + "nickolas", + "nautilus", + "feathers", + "aviation", + "avalanche", + "wildwood", + "thrasher", + "speedway", + "songbird", + "sickness", + "screamer", + "riverside", + "princeton", + "manhattan", + "ambrosia", + "adrianna", + "spaghetti", + "slapshot", + "ministry", + "lighting", + "helsinki", + "frederik", + "flexible", + "festival", + "daydream", + "coventry", + "constant", + "connection", + "woodland", + "signature", + "rockford", + "merchant", + "greatest", + "everlast", + "espresso", + "elizabet", + "dddddddd", + "community", + "charlton", + "stronger", + "starbuck", + "skeleton", + "scissors", + "reginald", + "redeemer", + "normandy", + "laserjet", + "graffiti", + "doughboy", + "building", + "bbbbbbbb", + "annabell", + "alchemist", + "zimbabwe", + "wisconsin", + "tunafish", + "thisisit", + "stafford", + "spalding", + "sometimes", + "solitude", + "robotech", + "minister", + "leonidas", + "kirkland", + "integral", + "incognito", + "ignatius", + "heavenly", + "gggggggg", + "exchange", + "winfield", + "thriller", + "sausages", + "salamander", + "printing", + "palmtree", + "opendoor", + "mosquito", + "milkyway", + "mcdonalds", + "laughter", + "klondike", + "kingsley", + "invisible", + "humphrey", + "hillside", + "hattrick", + "hammerhead", + "function", + "forgotten", + "fighting", + "excellent", + "delaware", + "darthvader", + "costello", + "catalyst", + "assholes", + "andersen", + "alexande", + "whiplash", + "solutions", + "rockwell", + "reddevil", + "glendale", + "foxylady", + "fortress", + "favorite", + "doughnut", + "comanche", + "cheshire", + "bertrand", + "barefoot", + "arabella", + "alligator", + "vanguard", + "stuttgart", + "rhapsody", + "reckless", + "powerful", + "painting", + "nocturne", + "nickname", + "llllllll", + "leighton", + "kingfisher", + "johnston", + "holidays", + "henderson", + "handyman", + "flamenco", + "escalade", + "division", + "covenant", + "churchill", + "cannibal", + "annmarie", + "alcatraz", + "wwwwwwww", + "wildcard", + "whitesox", + "thornton", + "temporary", + "survival", + "supernatural", + "sprocket", + "somerset", + "services", + "saxophone", + "sacrifice", + "restless", + "pumpkins", + "operation", + "nosferatu", + "meathead", + "licorice", + "language", + "generation", + "flanders", + "edinburgh", + "disciple", + "diplomat", + "crescent", + "counterstrike", + "catholic", + "calculator", + "browning", + "biscuits", + "violator", + "tangerine", + "straight", + "sorcerer", + "sidekick", + "shredder", + "schubert", + "prestige", + "nonsense", + "mulligan", + "matchbox", + "marauder", + "longhair", + "lisalisa", + "islander", + "grasshopper", + "gardenia", + "edmonton", + "downhill", + "cromwell", + "chowchow", + "terrapin", + "tennessee", + "stockton", + "spartacus", + "smoothie", + "seahawks", + "revelation", + "puppydog", + "marigold", + "gregorio", + "goldfinger", + "gangbang", + "daylight", + "constantine", + "clueless", + "calamity", + "beefcake", + "aquarium", + "anathema", + "ambition", + "wildlife", + "undercover", + "snowbird", + "schneider", + "prospect", + "pendragon", + "lockdown", + "jellyfish", + "irishman", + "infamous", + "hydrogen", + "hartford", + "goodyear", + "generals", + "garrison", + "foxhound", + "entrance", + "eighteen", + "dimension", + "daedalus", + "cocktail", + "chameleon", + "caligula", + "borabora", + "behemoth", + "balloons", + "bachelor", + "waterman", + "teenager", + "spanking", + "sergeant", + "seashell", + "seahorse", + "scarecrow", + "riffraff", + "possible", + "pittsburgh", + "pinnacle", + "nostromo", + "latitude", + "invasion", + "hibiscus", + "hallmark", + "firestorm", + "envision", + "charcoal", + "character", + "antelope", + "aircraft", + "unlimited", + "transport", + "stripper", + "snowwhite", + "smirnoff", + "seraphim", + "reporter", + "painkiller", + "nineteen", + "monolith", + "memories", + "memorial", + "massacre", + "goofball", + "engineering", + "doorknob", + "dipstick", + "commerce", + "carousel", + "callisto", + "brilliant", + "berenice", + "barbarian", + "wormwood", + "schumacher", + "rosewood", + "rochester", + "roadster", + "rapunzel", + "prisoner", + "prescott", + "phillies", + "pasadena", + "optimist", + "monkeyboy", + "metropolis", + "kimberley", + "junkmail", + "inspiron", + "hhhhhhhh", + "griffith", + "greenwood", + "golfball", + "forester", + "euphoria", + "cornelius", + "constance", + "conquest", + "clitoris", + "cartoons", + "buckaroo", + "bluejays", + "volunteer", + "violence", + "terrence", + "temporal", + "teamwork", + "shipping", + "serendipity", + "roosters", + "prophecy", + "playmate", + "panorama", + "landmark", + "instinct", + "infected", + "illuminati", + "honeydew", + "foundation", + "forbidden", + "document", + "deadline", + "crocodile", + "climbing", + "bluestar", + "birmingham", + "bathroom", + "baltimore", + "whiteboy", + "trinitron", + "titleist", + "tiberius", + "superhero", + "sidewinder", + "rosemarie", + "retarded", + "peppermint", + "palomino", + "outsider", + "oooooooo", + "musician", + "michelin", + "juggernaut", + "hyacinth", + "gatorade", + "fuzzball", + "everyone", + "dictionary", + "development", + "delirium", + "critical", + "cordelia", + "collection", + "capitals", + "bobdylan", + "birdhouse", + "asparagus", + "voltaire", + "submarine", + "stonewall", + "southpaw", + "sanctuary", + "ruthless", + "reaction", + "qazwsxed", + "prometheus", + "portable", + "passcode", + "official", + "neverland", + "mindless", + "masamune", + "legendary", + "incredible", + "holloway", + "heartless", + "hairball", + "genevieve", + "fireworks", + "dilligaf", + "crossfire", + "clippers", + "caldwell", + "waterpolo", + "vertical", + "timeless", + "thegreat", + "superuser", + "spelling", + "slippery", + "rrrrrrrr", + "ricochet", + "redemption", + "raspberry", + "protocol", + "producer", + "patterson", + "olivetti", + "metalica", + "mannheim", + "mandingo", + "magellan", + "machines", + "lovebird", + "inflames", + "important", + "headache", + "gemstone", + "ffffffff", + "cyclones", + "colonial", + "claudius", + "bulgaria", + "brunette", + "bradshaw", + "bastards", + "basement", + "applesauce", + "acapulco", + "yingyang", + "workshop", + "trueblue", + "transformers", + "tarantula", + "sycamore", + "stigmata", + "stargazer", + "override", + "nighthawk", + "mortgage", + "macdaddy", + "leicester", + "knockers", + "jjjjjjjj", + "hysteria", + "forgiven", + "distance", + "destruction", + "cosworth", + "coconuts", + "carlisle", + "breakfast", + "antivirus", + "yokohama", + "unforgiven", + "surrender", + "sheepdog", + "seinfeld", + "sabotage", + "reddragon", + "pressure", + "pinetree", + "pavement", + "oriental", + "offshore", + "newzealand", + "netscape", + "michaels", + "junkyard", + "jakejake", + "invincible", + "hawthorn", + "hawaiian", + "greyhound", + "frenchie", + "fastball", + "deathrow", + "carpenter", + "breakout", + "bismarck", + "alkaline", + "adrenalin", + "tryagain", + "thatcher", + "stampede", + "shakespeare", + "scheisse", + "sayonara", + "santacruz", + "passions", + "notorious", + "necromancer", + "nameless", + "mysterio", + "millennium", + "megabyte", + "mccarthy", + "magister", + "madhouse", + "liverpoo", + "leviathan", + "jennings", + "holstein", + "hellraiser", + "freefall", + "flawless", + "emergency", + "ebenezer", + "divinity", + "chewbacca", + "chastity", + "charlott", + "buchanan", + "aventura", + "zildjian", + "wargames", + "vvvvvvvv", + "unicorns", + "timberland", + "tasmania", + "symphony", + "splendid", + "sonyvaio", + "snapshot", + "saunders", + "reverend", + "prototype", + "polaroid", + "perfecto", + "mystical", + "material", + "maddison", + "landlord", + "juvenile", + "goodwill", + "goldwing", + "gilberto", + "flapjack", + "finnegan", + "erection", + "clemente", + "caterpillar", + "capetown", + "accounts", + "abstract", + "townsend", + "technical", + "smithers", + "shooting", + "shitshit", + "senators", + "sacramento", + "redbaron", + "programmer", + "percival", + "painless", + "northstar", + "newspaper", + "mongolia", + "miroslav", + "lumberjack", + "lakewood", + "incoming", + "immanuel", + "hometown", + "homeless", + "hillbilly", + "goodnight", + "giordano", + "genocide", + "enforcer", + "dreamcast", + "dispatch", + "developer", + "copenhagen", + "codename", + "clockwork", + "cccccccc", + "callaway", + "calculus", + "bartender", + "attorney", + "asteroid", + "angeleyes", + "academia", + "warehouse", + "terrance", + "stirling", + "stamford", + "stairway", + "specialist", + "soldiers", + "shitface", + "rotterdam", + "pizzahut", + "pepperoni", + "patricio", + "passwerd", + "mulberry", + "luscious", + "lifeline", + "legoland", + "kickflip", + "kennwort", + "kathrine", + "johnathan", + "excelsior", + "drummond", + "disneyland", + "delldell", + "claudine", + "christia", + "checkmate", + "centurion", + "cashmere", + "carthage", + "bartlett", + "animation", + "alphonse", + "woodside", + "vengeance", + "vaseline", + "toxicity", + "tommyboy", + "ticktock", + "teachers", + "strategy", + "stephens", + "snowdrop", + "smeghead", + "shutdown", + "sexysexy", + "pretender", + "popsicle", + "philadelphia", + "petersen", + "moonstone", + "masterkey", + "maryanne", + "magicman", + "identity", + "hannover", + "glorious", + "gathering", + "forgetit", + "fishtank", + "fernandes", + "epiphone", + "elevator", + "elegance", + "drumline", + "devilman", + "delivery", + "chrissie", + "carnaval", + "caffeine", + "bukowski", + "brownies", + "bearcats", + "woofwoof", + "untitled", + "tttttttt", + "stickman", + "starlite", + "southwest", + "smarties", + "penthouse", + "peanutbutter", + "oxymoron", + "oleander", + "nightfall", + "newjersey", + "muhammed", + "morphine", + "mobydick", + "meltdown", + "medieval", + "mahogany", + "longshot", + "lockheed", + "livewire", + "lakeland", + "kenworth", + "interpol", + "integrity", + "hibernia", + "helpdesk", + "fishhead", + "everybody", + "ethernet", + "elemental", + "duracell", + "delicious", + "crystals", + "confidence", + "colossus", + "belladonna", + "backlash", + "academic", + "abnormal", + "vineyard", + "terrible", + "suburban", + "stocking", + "springfield", + "snuffles", + "sideways", + "sensation", + "schwartz", + "salasana", + "rosalind", + "radiation", + "purchase", + "protection", + "practice", + "poiuytre", + "piramide", + "nashville", + "montrose", + "lunchbox", + "lonesome", + "limerick", + "imagination", + "ignition", + "homebrew", + "helicopter", + "greenman", + "firefire", + "electronic", + "economics", + "contract", + "conflict", + "comeback", + "cheeseburger", + "believer", + "beaumont", + "arrowhead", + "alternative", + "woodward", + "wolverin", + "wellness", + "timberlake", + "terrorist", + "temptation", + "swingers", + "solstice", + "scratchy", + "roosevelt", + "rockport", + "redlight", + "perfection", + "paulette", + "overtime", + "nazareth", + "mudvayne", + "movement", + "miracles", + "maserati", + "marbella", + "lifestyle", + "kiwikiwi", + "jurassic", + "infernal", + "hereford", + "goodtime", + "gamecock", + "galadriel", + "gabriell", + "firefighter", + "ferreira", + "ethiopia", + "dionysus", + "different", + "deadpool", + "crossroads", + "christos", + "chauncey", + "castaway", + "carefree", + "burnside", + "boomerang", + "bohemian", + "blackice", + "blackhole", + "bigmouth", + "baptiste", + "augustin", + "arlington", + "ambassador", + "alistair", + "agamemnon", + "advocate", + "acoustic", + "zimmerman", + "yorkshire", + "wallpaper", + "vinicius", + "vauxhall", + "understand", + "terminus", + "surround", + "stronghold", + "sessions", + "scirocco", + "schiller", + "schedule", + "regional", + "radiance", + "pioneers", + "phantasy", + "obsession", + "neutrino", + "mountains", + "marmalade", + "kendrick", + "heinlein", + "gillette", + "germania", + "fruitcake", + "fighters", + "fastback", + "exercise", + "envelope", + "eeeeeeee", + "diabetes", + "destination", + "davenport", + "damascus", + "coronado", + "chevalier", + "cashflow", + "cardigan", + "boyfriend", + "blueprint", + "blackboy", + "bitchass", + "backpack", + "aquamarine", + "anakonda", + "Victoria", + "911turbo", + "worldwide", + "viscount", + "violette", + "undertow", + "traveller", + "transformer", + "tombstone", + "surfboard", + "stratocaster", + "stephani", + "stainless", + "scorpions", + "redstone", + "premiere", + "planning", + "peacemaker", + "numberone", + "nitrogen", + "natascha", + "moonwalk", + "marzipan", + "mandolin", + "maintain", + "macgyver", + "lexington", + "landscape", + "killkill", + "jailbird", + "goodnews", + "gatekeeper", + "freshman", + "frankfurt", + "frankenstein", + "firestar", + "dreamland", + "discreet", + "detective", + "crossbow", + "choppers", + "betrayed", + "bernhard", + "basilisk", + "armadillo", + "antigone", + "alterego", + "alhambra", + "aerobics", + "advantage", + "Superman", + "yyyyyyyy", + "yellowstone", + "woodruff", + "sunnyboy", + "specialk", + "sorrento", + "reliance", + "proverbs", + "policeman", + "playgirl", + "pentium4", + "pedigree", + "partners", + "overdrive", + "observer", + "nnnnnnnn", + "newworld", + "moriarty", + "minotaur", + "location", + "knockout", + "knickers", + "kassandra", + "hellyeah", + "greentea", + "goodgood", + "gasoline", + "flashman", + "firestarter", + "fatality", + "ellipsis", + "disorder", + "deadlock", + "davidoff", + "couscous", + "construction", + "congress", + "cleaning", + "clarkson", + "christoph", + "cheerleader", + "ceramics", + "casandra", + "cambodia", + "blackstar", + "ballerina", + "backbone", + "whatwhat", + "westcoast", + "watching", + "underwear", + "tomatoes", + "tiramisu", + "tiberian", + "thurston", + "spinning", + "slippers", + "response", + "reindeer", + "prosperity", + "panchito", + "nottingham", + "mythology", + "mayfield", + "marquise", + "manifest", + "magnetic", + "lovelace", + "lesbians", + "joystick", + "inspector", + "industry", + "gulliver", + "ganymede", + "galactic", + "furniture", + "flashback", + "esoteric", + "dropdead", + "drinking", + "devildog", + "copeland", + "christop", + "cheerios", + "chatting", + "chantelle", + "changeit", + "cerulean", + "cabernet", + "blackheart", + "baltazar", + "alpha123", + "alleycat", + "accident", + "trickster", + "tingting", + "thething", + "tallulah", + "symmetry", + "stonehenge", + "smartass", + "shortcake", + "salesman", + "rushmore", + "resource", + "pregnant", + "pleasant", + "playground", + "plankton", + "pendulum", + "paterson", + "partizan", + "olympics", + "northwest", + "networks", + "nederland", + "mystique", + "mckinley", + "mcgregor", + "maxpower", + "mathematics", + "lafayette", + "kokakola", + "katherin", + "julianna", + "jeannine", + "horseman", + "homeland", + "hennessy", + "guesswho", + "greywolf", + "gilligan", + "gallardo", + "freewill", + "fleetwood", + "fantomas", + "eightball", + "dutchman", + "dementia", + "breakdown", + "berliner", + "adrenaline", + "woodwork", + "winifred", + "waterboy", + "troopers", + "theodora", + "sometime", + "sagittarius", + "rocketman", + "roadking", + "rifleman", + "pizzaman", + "phantasm", + "pathetic", + "parliament", + "oldschool", + "nicotine", + "nefertiti", + "minstrel", + "milwaukee", + "millionaire", + "kindness", + "insurance", + "independence", + "hatfield", + "freelance", + "forsythe", + "fontaine", + "feelgood", + "experience", + "evidence", + "erickson", + "enter123", + "energizer", + "downfall", + "deadwood", + "dandelion", + "crazyman", + "corporate", + "commandos", + "citation", + "chinchilla", + "champions", + "calliope", + "broccoli", + "bleeding", + "berserker", + "bergkamp", + "backstreet", + "asmodeus", + "artistic", + "antilles", + "anteater", + "Jonathan", + "wrinkles", + "triplets", + "telecaster", + "sunnyday", + "students", + "stockholm", + "starshine", + "sopranos", + "siberian", + "shetland", + "sheppard", + "scrapper", + "schooner", + "rebellion", + "pershing", + "parasite", + "palmetto", + "overture", + "odysseus", + "notredame", + "narayana", + "nakamura", + "mushrooms", + "moderator", + "metalgear", + "mediator", + "mcintosh", + "mayflower", + "marykate", + "manpower", + "malamute", + "louisiana", + "kryptonite", + "jeronimo", + "jeremias", + "jamaican", + "imperium", + "hurricanes", + "humberto", + "hoosiers", + "goldmine", + "futurama", + "elisabet", + "earthquake", + "dumpling", + "dragster", + "dominica", + "dominate", + "dictator", + "desperate", + "cookbook", + "confusion", + "concerto", + "christel", + "blacksmith", + "beholder", + "babushka", + "autobahn", + "attention", + "atmosphere", + "anywhere", + "aftermath", + "acidburn", + "whiteout", + "typewriter", + "thousand", + "thorsten", + "thematrix", + "symantec", + "splatter", + "sonysony", + "slaughter", + "sensitive", + "schaefer", + "reddwarf", + "providence", + "position", + "popopopo", + "pikapika", + "piercing", + "performance", + "paramedic", + "pakistani", + "neighbor", + "motorcycle", + "mireille", + "lovesick", + "loverman", + "lockwood", + "lifeguard", + "kowalski", + "kerberos", + "kellyann", + "jayhawks", + "innuendo", + "iiiiiiii", + "hummingbird", + "horrible", + "himalaya", + "highlife", + "hetfield", + "heartbeat", + "guitarist", + "graphite", + "funnyman", + "epiphany", + "elvis123", + "discount", + "copyright", + "concordia", + "complicated", + "clementine", + "chouette", + "chinchin", + "chinatown", + "chinaman", + "chesterfield", + "cervantes", + "celestial", + "calderon", + "bullhead", + "brussels", + "broadband", + "brasilia", + "bellevue", + "bagpipes", + "aurelius", + "aristotle", + "altitude", + "aloysius", + "affinity", + "09876543", + "winnipeg", + "ultraman", + "treefrog", + "tigercat", + "taratara", + "tactical", + "system32", + "swastika", + "searcher", + "reserved", + "redbeard", + "realtime", + "pyramids", + "provider", + "projects", + "production", + "poontang", + "pinecone", + "pericles", + "pennywise", + "paradiso", + "parachute", + "parabola", + "palestine", + "overflow", + "motorbike", + "mavericks", + "marybeth", + "marriott", + "madalena", + "loophole", + "lonsdale", + "lingerie", + "ledzeppelin", + "lavalamp", + "joselito", + "jerrylee", + "jamboree", + "interest", + "hugoboss", + "heythere", + "hehehehe", + "hangover", + "greatone", + "gardener", + "exorcist", + "dressage", + "dominator", + "domination", + "dodgeram", + "cummings", + "commodore", + "christen", + "callahan", + "calcutta", + "burberry", + "bulletproof", + "bombshell", + "blackburn", + "betrayal", + "atkinson", + "athletic", + "arachnid", + "amaranth", + "algernon", + "alastair", + "absinthe", + "zoomzoom", + "wonderboy", + "whatthefuck", + "watchman", + "transform", + "trampoline", + "tortilla", + "thunderbolt", + "superduper", + "squadron", + "skylight", + "sanfrancisco", + "salamandra", + "resistance", + "reliable", + "recorder", + "provence", + "porsche9", + "piedmont", + "pentagram", + "overdose", + "nightman", + "nightingale", + "monument", + "longtime", + "lolololo", + "lokiloki", + "laughing", + "kerrigan", + "jeopardy", + "inspiration", + "ibelieve", + "houghton", + "horsemen", + "hologram", + "hideaway", + "handicap", + "hamsters", + "forklift", + "finished", + "dorothea", + "devotion", + "deathstar", + "darkknight", + "conchita", + "classics", + "bridgette", + "bigballs", + "barnyard", + "baphomet", + "badlands", + "asterisk", + "arcangel", + "antoinette", + "annemarie", + "Christian", + "whoknows", + "trousers", + "treehouse", + "tranquil", + "toriamos", + "teardrop", + "superboy", + "shortcut", + "shockwave", + "shocking", + "scranton", + "sandoval", + "roseanne", + "red12345", + "prospero", + "products", + "paperclip", + "outdoors", + "nemesis1", + "nagasaki", + "mousepad", + "morrissey", + "monkeyman", + "modeling", + "maranatha", + "makeitso", + "maelstrom", + "limpbizkit", + "lightbulb", + "lalakers", + "katharina", + "kakaroto", + "jeannette", + "investor", + "insecure", + "humanoid", + "holiness", + "helpless", + "hallelujah", + "greenhouse", + "germaine", + "gallagher", + "freefree", + "francais", + "firestone", + "firebolt", + "filipino", + "falstaff", + "electronics", + "economist", + "dominant", + "diabolic", + "deadbeat", + "crockett", + "crazycat", + "composer", + "coleslaw", + "cincinnati", + "cascades", + "breaking", + "bluenose", + "bluegrass", + "bisexual", + "billions", + "billbill", + "bigbrother", + "avengers", + "athletics", + "assembly", + "asasasas", + "allstars", + "alakazam", + "activate", + "Computer", + "zerozero", + "windowsxp", + "vigilant", + "verygood", + "trustnoone", + "truffles", + "toothpaste", + "tigerman", + "sweetwater", + "summoner", + "suicidal", + "strummer", + "stiletto", + "squeaker", + "sixtynine", + "sithlord", + "siegfried", + "showcase", + "serenade", + "sepultura", + "rotation", + "rockhard", + "quintana", + "pepsicola", + "passenger", + "pacifica", + "nightshade", + "newhouse", + "muenchen", + "motorhead", + "morrigan", + "memememe", + "maximize", + "marciano", + "macdonald", + "loveable", + "lakeview", + "hyderabad", + "historia", + "highschool", + "hiawatha", + "hermitage", + "goodtimes", + "freeport", + "flathead", + "faulkner", + "endymion", + "emirates", + "dreamers", + "district", + "dietrich", + "cranberry", + "cockroach", + "clemence", + "classified", + "cellular", + "catherin", + "carmella", + "burgundy", + "blooming", + "blitzkrieg", + "bladerunner", + "bigboobs", + "beachbum", + "backyard", + "backward", + "babybear", + "argonaut", + "appleton", + "aguilera", + "abundance", + "Nicholas", + "zaragoza", + "woodcock", + "wisteria", + "westlake", + "untouchable", + "trapdoor", + "tigereye", + "thetruth", + "testicle", + "superbowl", + "sprinkle", + "snakebite", + "silencer", + "secretary", + "scottish", + "sanderson", + "sanandreas", + "robertson", + "richelle", + "richardson", + "religion", + "quiksilver", + "queenbee", + "psychology", + "playhouse", + "physical", + "pensacola", + "pedersen", + "paperboy", + "pandemonium", + "nikolaus", + "murderer", + "montague", + "mockingbird", + "mercutio", + "mercurio", + "mcknight", + "maxpayne", + "mandragora", + "mamacita", + "madhatter", + "lucretia", + "kusanagi", + "knoxville", + "katmandu", + "julianne", + "jiujitsu", + "jeanpaul", + "infrared", + "industrial", + "humanity", + "hotwheels", + "honeypot", + "honeybun", + "herkules", + "heartbreaker", + "hawkeyes", + "gilgamesh", + "geometry", + "friedman", + "freiheit", + "firework", + "federation", + "executive", + "exclusive", + "excellence", + "emotional", + "elbereth", + "dragon99", + "dollface", + "devilish", + "democrat", + "darkmoon", + "crackpot", + "costarica", + "costanza", + "consuelo", + "clarisse", + "citibank", + "cingular", + "chrystal", + "channing", + "carvalho", + "bluebear", + "billyjoe", + "benedikt", + "beaufort", + "barnabas", + "baracuda", + "augustine", + "armitage", + "alcapone", + "afterlife", + "adrianne", + "Internet", + "Football", + "yourself", + "yorktown", + "yeahyeah", + "wildflower", + "valdemar", + "unlocked", + "unleashed", + "twinkles", + "trujillo", + "torrents", + "tonyhawk", + "tanzania", + "takedown", + "takamine", + "supercool", + "subwoofer", + "stitches", + "standing", + "stalingrad", + "srilanka", + "sparhawk", + "slowpoke", + "shoelace", + "service1", + "senorita", + "seashore", + "sandstorm", + "roulette", + "radiator", + "problems", + "powerhouse", + "postmaster", + "platform", + "parallax", + "nepenthe", + "moonmoon", + "livingston", + "labyrinth", + "jackhammer", + "intrigue", + "interface", + "interact", + "honeymoon", + "grapefruit", + "government", + "geography", + "galloway", + "fullback", + "fuckhead", + "fairview", + "divorced", + "disabled", + "defiance", + "deeznutz", + "communication", + "cocksucker", + "cheating", + "buckwheat", + "boarding", + "blackdragon", + "baseline", + "bandicoot", + "baldrick", + "apollo11", + "ambulance", + "aluminum", + "abercrombie", + "woodwind", + "woodpecker", + "woodbury", + "watchdog", + "tribunal", + "toreador", + "tigerwoods", + "thinkpad", + "thebeach", + "test12345", + "terrific", + "teaching", + "successful", + "stringer", + "sovereign", + "souvenir", + "sombrero", + "shuriken", + "shotokan", + "shinichi", + "scooters", + "schroeder", + "schnitzel", + "rosalinda", + "regiment", + "rainfall", + "pistache", + "pianoman", + "paddington", + "overseas", + "orthodox", + "nietzsche", + "monorail", + "minemine", + "milhouse", + "mermaids", + "mansfield", + "madrigal", + "krakatoa", + "junction", + "intranet", + "imperator", + "humility", + "harmless", + "giuliana", + "gauntlet", + "fugitive", + "flatland", + "feelings", + "entertainment", + "election", + "dumpster", + "cruzeiro", + "cracking", + "cheaters", + "centrino", + "canberra", + "botswana", + "blockbuster", + "blahblahblah", + "blackfire", + "blackbelt", + "atreides", + "asuncion", + "astronomy", + "astroboy", + "aqualung", + "amnesiac", + "adorable", + "3edc4rfv", + "worldcup", + "wholesale", + "vittorio", + "underwood", + "underwater", + "touchdown", + "theworld", + "thebeast", + "thaddeus", + "telemark", + "sylvania", + "surveyor", + "suitcase", + "stroller", + "stripped", + "stratford", + "stallone", + "speedster", + "septembe", + "sandberg", + "rousseau", + "revenant", + "protector", + "protected", + "pembroke", + "parkside", + "outbreak", + "obsolete", + "nutshell", + "nonenone", + "multisync", + "momentum", + "microwave", + "marguerite", + "maldives", + "magdalen", + "longbeach", + "lockhart", + "kensington", + "humboldt", + "homebase", + "headshot", + "headless", + "hazelnut", + "gremlins", + "fireblade", + "external", + "entering", + "electrical", + "dulcinea", + "dropkick", + "draconis", + "domestic", + "daydreamer", + "darkwing", + "corporal", + "cocorico", + "chimaera", + "cheyanne", + "celebrate", + "caballero", + "breakers", + "brainstorm", + "bluesman", + "blackpool", + "bethesda", + "basketba", + "antichrist", + "andyandy", + "abcdefghi", + "Garfield", + "yoyoyoyo", + "yeahbaby", + "wetpussy", + "vergessen", + "variable", + "trillium", + "torrance", + "tikitiki", + "thesaint", + "theforce", + "succubus", + "stockman", + "steve123", + "speeding", + "solitaire", + "sokrates", + "slingshot", + "skateboarding", + "silverfox", + "showboat", + "sequence", + "rottweiler", + "rincewind", + "rainmaker", + "postcard", + "polkadot", + "photoshop", + "persimmon", + "pandabear", + "nutrition", + "nicknick", + "mysterious", + "marielle", + "maneater", + "lionlion", + "leningrad", + "leapfrog", + "kristopher", + "kirkwood", + "kilkenny", + "jediknight", + "jabberwocky", + "intercom", + "informix", + "hounddog", + "homicide", + "herschel", + "henrietta", + "hatteras", + "harakiri", + "halfmoon", + "gunslinger", + "fivestar", + "firewood", + "expedition", + "executor", + "elcamino", + "egyptian", + "duckling", + "drumming", + "drifting", + "daisydog", + "contrast", + "collector", + "choclate", + "chilling", + "channels", + "catapult", + "careless", + "californ", + "brunswick", + "braindead", + "bluedragon", + "bloodline", + "beverley", + "atalanta", + "antihero", + "allright", + "43214321", + "18436572", + "woodlands", + "wilkinson", + "wellcome", + "waldemar", + "valerian", + "tornado1", + "thunders", + "testament", + "tennyson", + "tarragon", + "tapestry", + "tajmahal", + "struggle", + "starling", + "starchild", + "sobriety", + "snowfall", + "shalimar", + "settings", + "schnecke", + "satriani", + "sailfish", + "roserose", + "reference", + "powerman", + "powerade", + "playstation2", + "plastics", + "pinkpink", + "parallel", + "papercut", + "p4ssword", + "navyseal", + "monopoli", + "mnemonic", + "millenia", + "mercenary", + "membrane", + "manitoba", + "limelight", + "leopards", + "kurdistan", + "karoline", + "johnpaul", + "interior", + "interesting", + "insomniac", + "homepage", + "heavymetal", + "headhunter", + "harvester", + "greeting", + "golfgolf", + "glassman", + "gladstone", + "friction", + "ethereal", + "emotions", + "dudedude", + "douglass", + "desperados", + "demetrio", + "demented", + "decision", + "cuthbert", + "compound", + "comatose", + "civilwar", + "charleston", + "castello", + "bulletin", + "brandnew", + "bluegill", + "bloodhound", + "baywatch", + "bastardo", + "bagheera", + "allstate", + "aldebaran", + "Samantha", + "Elizabeth", + "78945612", + "wolverines", + "wolfhound", + "wildbill", + "whittier", + "virgilio", + "vegetable", + "unbreakable", + "trusting", + "troubles", + "tonytony", + "terriers", + "template", + "telefoon", + "talented", + "superpower", + "supermen", + "sugarplum", + "starting", + "sixpence", + "simplicity", + "sidewalk", + "shoshana", + "sasquatch", + "rattlesnake", + "rafferty", + "qwerty00", + "promotion", + "pinocchio", + "philosophy", + "philippines", + "pheasant", + "pentium3", + "overlook", + "overhead", + "operations", + "okokokok", + "nwo4life", + "nostradamus", + "newdelhi", + "myfriend", + "munchies", + "mountaindew", + "moneybag", + "molecule", + "melville", + "mcintyre", + "mattress", + "marshmallow", + "maritime", + "mariachi", + "lovesong", + "lolalola", + "lifeboat", + "kasandra", + "kalamazoo", + "jackrabbit", + "intelligent", + "innocence", + "henry123", + "henrique", + "hardball", + "handbook", + "hacienda", + "grenoble", + "goodmorning", + "giuliano", + "frostbite", + "freehand", + "fragment", + "foreskin", + "explosion", + "experiment", + "ensemble", + "eclectic", + "dogfight", + "diogenes", + "dillweed", + "dickinson", + "demetrius", + "daybreak", + "dagobert", + "culinary", + "crossing", + "controls", + "consulting", + "cobblers", + "chatterbox", + "charissa", + "celebrity", + "bungalow", + "broadcast", + "bodyguard", + "ballroom", + "analysis", + "afghanistan", + "addiction", + "12131213", + "witchcraft", + "wertwert", + "vivienne", + "vermilion", + "ultrasound", + "tuppence", + "tropicana", + "trafford", + "streamer", + "starburst", + "ssssssssss", + "spotlight", + "specialized", + "sparrows", + "sideshow", + "sherbert", + "seminoles", + "sebastia", + "scribble", + "sarasota", + "sarasara", + "sanguine", + "reflection", + "redhorse", + "rational", + "radioman", + "progressive", + "poophead", + "plutonium", + "phantoms", + "organize", + "optiplex", + "neverdie", + "nantucket", + "monsieur", + "monkfish", + "mauritius", + "master01", + "marymary", + "marvelous", + "manifesto", + "macedonia", + "logistic", + "leprechaun", + "lemmings", + "langston", + "innovation", + "house123", + "hihihihi", + "hellhole", + "hardwood", + "generator", + "funhouse", + "fullhouse", + "flowerpower", + "fiorella", + "farewell", + "fantasma", + "faithless", + "failsafe", + "explicit", + "esposito", + "enchanted", + "duckduck", + "drilling", + "dragon10", + "doodlebug", + "dickweed", + "crunchie", + "crawfish", + "condition", + "chessman", + "chanelle", + "chamonix", + "celebration", + "brotherhood", + "brainiac", + "bluewater", + "birdland", + "binladen", + "billings", + "bareback", + "bacteria", + "authority", + "astronaut", + "asdfqwer", + "arpeggio", + "appleseed", + "animator", + "amazonas", + "alpacino", + "adelaida", + "adamadam", + "Einstein", + "90909090", + "yamamoto", + "wormhole", + "windows98", + "whiteman", + "westgate", + "watchmen", + "vergeten", + "veracruz", + "vanquish", + "uuuuuuuu", + "undefined", + "trucking", + "tormentor", + "timelord", + "timberwolf", + "strangle", + "stoneman", + "starless", + "spiritual", + "spagetti", + "sorensen", + "somethin", + "snowhite", + "slovakia", + "skydiver", + "silvester", + "silicone", + "silencio", + "shevchenko", + "selector", + "scramble", + "scott123", + "salinger", + "rendezvous", + "reminder", + "redheads", + "propaganda", + "presidente", + "pocahontas", + "photography", + "phaedrus", + "permanent", + "paulchen", + "paraguay", + "palacios", + "osbourne", + "mutation", + "murakami", + "mercator", + "manticore", + "lynnette", + "lookatme", + "lightnin", + "lifeless", + "leopoldo", + "knitting", + "killerbee", + "interactive", + "hellhound", + "happening", + "gridlock", + "greatness", + "gigantic", + "fred1234", + "flatline", + "firehouse", + "firehawk", + "fellatio", + "eruption", + "encounter", + "delorean", + "decipher", + "darkblue", + "creatine", + "counting", + "cornbread", + "coolidge", + "converge", + "clubbing", + "charmaine", + "cbr600rr", + "carnegie", + "caribbean", + "calabria", + "buttfuck", + "butterflies", + "blueball", + "beautifu", + "barnacle", + "barbarossa", + "automatic", + "asturias", + "armchair", + "archives", + "aperture", + "12141214", + "yourname", + "whitewolf", + "visionary", + "versailles", + "toothpick", + "testuser", + "swordfis", + "superdog", + "sunflowers", + "sunflowe", + "stevenson", + "sportsman", + "somewhere", + "slovenia", + "sinfonia", + "silverfish", + "scimitar", + "rosebush", + "resonance", + "resolution", + "registration", + "redriver", + "redeemed", + "ramstein", + "qweasdzx", + "primetime", + "precision", + "plumbing", + "pickwick", + "parsifal", + "paramount", + "overcome", + "nutcracker", + "ninjutsu", + "newcomer", + "minority", + "mariette", + "loveland", + "localhost", + "leadership", + "lagrange", + "kaitlynn", + "john1234", + "invictus", + "inventor", + "inspired", + "hurrican", + "holbrook", + "hiroshima", + "heracles", + "hawthorne", + "hathaway", + "governor", + "goodrich", + "fortytwo", + "foreplay", + "foolproof", + "fishhook", + "fishfish", + "financial", + "fillmore", + "evangeline", + "espinoza", + "electricity", + "edgewood", + "duisburg", + "drummers", + "dowjones", + "continental", + "cameroon", + "bracelet", + "bogeyman", + "bluerose", + "birdcage", + "billgates", + "beepbeep", + "architecture", + "antonina", + "anabolic", + "allister", + "albacore", + "airedale", + "activity", + "Patricia", + "99887766", + "yardbird", + "xcountry", + "wildrose", + "watanabe", + "wareagle", + "wanderlust", + "wakefield", + "validate", + "tripping", + "treetree", + "timbuktu", + "tarantino", + "syndicate", + "summer00", + "stuntman", + "steelman", + "spaceship", + "snowshoe", + "smuggler", + "slowhand", + "sharpshooter", + "schuster", + "satelite", + "rightnow", + "r4e3w2q1", + "quagmire", + "portsmouth", + "porridge", + "pinkerton", + "peerless", + "paganini", + "orchestra", + "optional", + "nowayout", + "nicaragua", + "neworder", + "michelangelo", + "mcmillan", + "lombardo", + "lindberg", + "larkspur", + "lambchop", + "kirakira", + "kamehameha", + "jellybeans", + "innovision", + "infinito", + "identify", + "hellomoto", + "hellgate", + "heatwave", + "harlequin", + "grounded", + "greenish", + "grandmother", + "gorillaz", + "goldsmith", + "gerhardt", + "generous", + "gauthier", + "frontera", + "freezing", + "fracture", + "firewater", + "fellowship", + "fastlane", + "explosive", + "environment", + "drafting", + "donnelly", + "dolomite", + "direction", + "deception", + "damocles", + "cunningham", + "crossroad", + "critters", + "crickets", + "crabtree", + "cortland", + "constantin", + "connected", + "confidential", + "comrades", + "clothing", + "classical", + "checking", + "cathleen", + "carter15", + "carleton", + "butterscotch", + "butterball", + "bulldozer", + "bomberman", + "blueline", + "bittersweet", + "bigblack", + "backspin", + "babababa", + "audition", + "argentum", + "antonius", + "antiques", + "angelfish", + "americana", + "aluminium", + "woodlawn", + "woodchuck", + "windward", + "warranty", + "visitors", + "vanderbilt", + "turquoise", + "triathlon", + "trespass", + "trashcan", + "topnotch", + "switzerland", + "sturgeon", + "studioworks", + "strikers", + "skipjack", + "simulator", + "silverman", + "shipyard", + "shekinah", + "scouting", + "sanpedro", + "sandrock", + "rootroot", + "reynaldo", + "renaissance", + "rembrandt", + "relentless", + "relative", + "radagast", + "qwertzui", + "presidio", + "presence", + "prentice", + "porcupine", + "playback", + "philippa", + "peterman", + "peregrin", + "peaceman", + "papabear", + "organist", + "octavian", + "northside", + "nightwing", + "nathanael", + "multipass", + "monteiro", + "millhouse", + "metaphor", + "manifold", + "makelove", + "lysander", + "louisville", + "logistics", + "lobsters", + "lifesaver", + "kristofer", + "kilimanjaro", + "julie123", + "johngalt", + "jalapeno", + "jacobsen", + "islanders", + "isengard", + "hutchins", + "horizons", + "hitchcock", + "hemingway", + "heartland", + "hawkwind", + "happyboy", + "gwendolyn", + "gutentag", + "graveyard", + "gracious", + "glenwood", + "frogfrog", + "freelancer", + "fraction", + "forgetful", + "foreigner", + "folklore", + "firetruck", + "fahrenheit", + "express1", + "exposure", + "endurance", + "employee", + "economic", + "ducksoup", + "dragonslayer", + "doggystyle", + "diskette", + "descartes", + "delacruz", + "dashboard", + "damnation", + "creepers", + "copperhead", + "clements", + "cheerful", + "characters", + "certified", + "cathedral", + "catering", + "capucine", + "capacity", + "bridgett", + "breakaway", + "boyscout", + "bloodlust", + "blackstone", + "bingo123", + "belgrade", + "beginner", + "bavarian", + "backfire", + "astaroth", + "arsehole", + "annelise", + "anabelle", + "albright", + "airlines", + "adminadmin", + "adelante", + "Maverick", + "wretched", + "winthrop", + "whirlwind", + "westwind", + "weinberg", + "tumbleweed", + "trashman", + "threesome", + "thibault", + "syndrome", + "swinging", + "sweetiepie", + "sweetest", + "superwoman", + "sunburst", + "sperling", + "spectral", + "silmaril", + "shoulder", + "shahrukh", + "settlers", + "seduction", + "searching", + "scotsman", + "scofield", + "schumann", + "satisfaction", + "santamaria", + "rossignol", + "rodrigues", + "rockrock", + "rockland", + "retriever", + "resurrection", + "restaurant", + "promises", + "priscila", + "priority", + "principal", + "playoffs", + "persephone", + "peregrine", + "overseer", + "opposite", + "oldsmobile", + "octavius", + "nikenike", + "nightcrawler", + "nehemiah", + "morticia", + "morrowind", + "moonraker", + "mithrandir", + "misty123", + "milenium", + "microphone", + "matrix123", + "lollollol", + "lausanne", + "kokokoko", + "kilowatt", + "interval", + "ignorant", + "huntsman", + "hooligans", + "homesick", + "hobgoblin", + "highlands", + "highbury", + "hellbent", + "guerilla", + "graywolf", + "grandson", + "georgetown", + "gentleman", + "gargamel", + "gangsters", + "gameplay", + "flowerpot", + "fielding", + "familiar", + "falconer", + "ezequiel", + "dynamics", + "dominican", + "demolition", + "demetria", + "darkroom", + "curtains", + "currency", + "crocodil", + "creativity", + "crawling", + "commercial", + "cigarette", + "chivalry", + "celestine", + "catriona", + "cassiopeia", + "carolann", + "cannonball", + "canfield", + "buttocks", + "brinkley", + "bordello", + "blissful", + "blackwell", + "blackbox", + "billiard", + "bigbooty", + "belvedere", + "bastille", + "barbershop", + "background", + "australian", + "astalavista", + "assassins", + "artofwar", + "artichoke", + "annalena", + "animated", + "alvarado", + "alternate", + "alicante", + "alex2000", + "alabaster", + "aerospace", + "accurate", + "17171717", ] diff --git a/app/main/forms.py b/app/main/forms.py index 2cf8c723d..f1e1513de 100644 --- a/app/main/forms.py +++ b/app/main/forms.py @@ -70,30 +70,26 @@ from app.utils.user_permissions import all_ui_permissions, permission_options def get_time_value_and_label(future_time): return ( future_time.astimezone(pytz.utc).replace(tzinfo=None).isoformat(), - '{} at {} UTC'.format( + "{} at {} UTC".format( get_human_day(future_time.astimezone(pytz.utc)), - get_human_time(future_time.astimezone(pytz.utc)) - ) + get_human_time(future_time.astimezone(pytz.utc)), + ), ) def get_human_time(time): - return { - '0': 'midnight', - '12': 'noon' - }.get( - time.strftime('%-H'), - time.strftime('%-I%p').lower() + return {"0": "midnight", "12": "noon"}.get( + time.strftime("%-H"), time.strftime("%-I%p").lower() ) -def get_human_day(time, prefix_today_with='T'): +def get_human_day(time, prefix_today_with="T"): # Add 1 hour to get ‘midnight today’ instead of ‘midnight tomorrow’ - time = (time - timedelta(hours=1)).strftime('%A') - if time == datetime.now(pytz.utc).strftime('%A'): - return '{}oday'.format(prefix_today_with) - if time == (datetime.now(pytz.utc) + timedelta(days=1)).strftime('%A'): - return 'Tomorrow' + time = (time - timedelta(hours=1)).strftime("%A") + if time == datetime.now(pytz.utc).strftime("%A"): + return "{}oday".format(prefix_today_with) + if time == (datetime.now(pytz.utc) + timedelta(days=1)).strftime("%A"): + return "Tomorrow" return time @@ -115,22 +111,13 @@ def get_next_days_until(until): now = datetime.now(pytz.utc) days = int((until - now).total_seconds() / (60 * 60 * 24)) return [ - get_human_day( - (now + timedelta(days=i)), - prefix_today_with='Later t' - ) + get_human_day((now + timedelta(days=i)), prefix_today_with="Later t") for i in range(0, days + 1) ] class RadioField(WTFormsRadioField): - - def __init__( - self, - *args, - thing='an option', - **kwargs - ): + def __init__(self, *args, thing="an option", **kwargs): super().__init__(*args, **kwargs) self.thing = thing self.validate_choice = False @@ -138,11 +125,10 @@ class RadioField(WTFormsRadioField): def pre_validate(self, form): super().pre_validate(form) if self.data not in dict(self.choices).keys(): - raise ValidationError(f'Select {self.thing}') + raise ValidationError(f"Select {self.thing}") -def email_address(label='Email address', gov_user=True, required=True): - +def email_address(label="Email address", gov_user=True, required=True): validators = [ ValidEmail(), ] @@ -151,13 +137,13 @@ def email_address(label='Email address', gov_user=True, required=True): validators.append(ValidGovEmail()) if required: - validators.append(DataRequired(message='Cannot be empty')) + validators.append(DataRequired(message="Cannot be empty")) return GovukEmailField(label, validators) class UKMobileNumber(TelField): - def __init__(self, label='', validators=None, param_extensions=None, **kwargs): + def __init__(self, label="", validators=None, param_extensions=None, **kwargs): super(UKMobileNumber, self).__init__(label, validators, **kwargs) self.param_extensions = param_extensions @@ -166,7 +152,9 @@ class UKMobileNumber(TelField): # 2. calls field.widget # this bypasses that by making self.widget a method with the same interface as widget.__call__ def widget(self, field, param_extensions=None, **kwargs): - return govuk_text_input_field_widget(self, field, type="tel", param_extensions=param_extensions, **kwargs) + return govuk_text_input_field_widget( + self, field, type="tel", param_extensions=param_extensions, **kwargs + ) def pre_validate(self, form): try: @@ -176,8 +164,10 @@ class UKMobileNumber(TelField): class InternationalPhoneNumber(TelField): - def __init__(self, label='', validators=None, param_extensions=None, **kwargs): - super(InternationalPhoneNumber, self).__init__(label, validators=validators, **kwargs) + def __init__(self, label="", validators=None, param_extensions=None, **kwargs): + super(InternationalPhoneNumber, self).__init__( + label, validators=validators, **kwargs + ) self.param_extensions = param_extensions # self.__call__ renders the HTML for the field by: @@ -185,7 +175,9 @@ class InternationalPhoneNumber(TelField): # 2. calls field.widget # this bypasses that by making self.widget a method with the same interface as widget.__call__ def widget(self, field, param_extensions=None, **kwargs): - return govuk_text_input_field_widget(self, field, type="tel", param_extensions=param_extensions, **kwargs) + return govuk_text_input_field_widget( + self, field, type="tel", param_extensions=param_extensions, **kwargs + ) def pre_validate(self, form): try: @@ -195,44 +187,46 @@ class InternationalPhoneNumber(TelField): raise ValidationError(str(e)) -def uk_mobile_number(label='Mobile number'): - return UKMobileNumber(label, - validators=[DataRequired(message='Cannot be empty')]) +def uk_mobile_number(label="Mobile number"): + return UKMobileNumber(label, validators=[DataRequired(message="Cannot be empty")]) -def international_phone_number(label='Mobile number'): +def international_phone_number(label="Mobile number"): return InternationalPhoneNumber( - label, - validators=[DataRequired(message='Cannot be empty')] + label, validators=[DataRequired(message="Cannot be empty")] ) -def password(label='Password'): +def password(label="Password"): return GovukPasswordField( label, validators=[ - DataRequired(message='Cannot be empty'), - Length(8, 255, message='Must be at least 8 characters'), - CommonlyUsedPassword(message='Choose a password that’s harder to guess') - ] + DataRequired(message="Cannot be empty"), + Length(8, 255, message="Must be at least 8 characters"), + CommonlyUsedPassword(message="Choose a password that’s harder to guess"), + ], ) -def govuk_text_input_field_widget(self, field, type=None, param_extensions=None, **kwargs): +def govuk_text_input_field_widget( + self, field, type=None, param_extensions=None, **kwargs +): value = kwargs["value"] if "value" in kwargs else field.data value = str(value) if isinstance(value, Number) else value # error messages error_message = None if field.errors: - error_message_format = "html" if kwargs.get("error_message_with_html") else "text" + error_message_format = ( + "html" if kwargs.get("error_message_with_html") else "text" + ) error_message = { "attributes": { "data-module": "track-error", "data-error-type": field.errors[0], - "data-error-label": field.name + "data-error-label": field.name, }, - error_message_format: field.errors[0] + error_message_format: field.errors[0], } # convert to parameters that usa understands @@ -241,7 +235,7 @@ def govuk_text_input_field_widget(self, field, type=None, param_extensions=None, "id": field.id, "label": {"text": field.label.text}, "name": field.name, - "value": value + "value": value, } if type: @@ -253,11 +247,12 @@ def govuk_text_input_field_widget(self, field, type=None, param_extensions=None, merge_jsonlike(params, param_extensions) return Markup( - render_template('components/components/input/template.njk', params=params)) + render_template("components/components/input/template.njk", params=params) + ) class GovukTextInputField(StringField): - def __init__(self, label='', validators=None, param_extensions=None, **kwargs): + def __init__(self, label="", validators=None, param_extensions=None, **kwargs): super(GovukTextInputField, self).__init__(label, validators, **kwargs) self.param_extensions = param_extensions @@ -270,7 +265,7 @@ class GovukTextInputField(StringField): class GovukPasswordField(PasswordField): - def __init__(self, label='', validators=None, param_extensions=None, **kwargs): + def __init__(self, label="", validators=None, param_extensions=None, **kwargs): super(GovukPasswordField, self).__init__(label, validators, **kwargs) self.param_extensions = param_extensions @@ -279,11 +274,13 @@ class GovukPasswordField(PasswordField): # 2. calls field.widget # this bypasses that by making self.widget a method with the same interface as widget.__call__ def widget(self, field, param_extensions=None, **kwargs): - return govuk_text_input_field_widget(self, field, type="password", param_extensions=param_extensions, **kwargs) + return govuk_text_input_field_widget( + self, field, type="password", param_extensions=param_extensions, **kwargs + ) class GovukEmailField(EmailField): - def __init__(self, label='', validators=None, param_extensions=None, **kwargs): + def __init__(self, label="", validators=None, param_extensions=None, **kwargs): super(GovukEmailField, self).__init__(label, validators=validators, **kwargs) self.param_extensions = param_extensions @@ -292,15 +289,18 @@ class GovukEmailField(EmailField): # 2. calls field.widget # this bypasses that by making self.widget a method with the same interface as widget.__call__ def widget(self, field, param_extensions=None, **kwargs): - - params = {"attributes": {"spellcheck": "false"}} # email addresses don't need to be spellchecked + params = { + "attributes": {"spellcheck": "false"} + } # email addresses don't need to be spellchecked merge_jsonlike(params, param_extensions) - return govuk_text_input_field_widget(self, field, type="email", param_extensions=params, **kwargs) + return govuk_text_input_field_widget( + self, field, type="email", param_extensions=params, **kwargs + ) class GovukSearchField(SearchField): - def __init__(self, label='', validators=None, param_extensions=None, **kwargs): + def __init__(self, label="", validators=None, param_extensions=None, **kwargs): super(GovukSearchField, self).__init__(label, validators, **kwargs) self.param_extensions = param_extensions @@ -309,15 +309,16 @@ class GovukSearchField(SearchField): # 2. calls field.widget # this bypasses that by making self.widget a method with the same interface as widget.__call__ def widget(self, field, param_extensions=None, **kwargs): - params = {"classes": ""} # email addresses don't need to be spellchecked merge_jsonlike(params, param_extensions) - return govuk_text_input_field_widget(self, field, type="search", param_extensions=params, **kwargs) + return govuk_text_input_field_widget( + self, field, type="search", param_extensions=params, **kwargs + ) class GovukDateField(DateField): - def __init__(self, label='', validators=None, param_extensions=None, **kwargs): + def __init__(self, label="", validators=None, param_extensions=None, **kwargs): super(GovukDateField, self).__init__(label, validators, **kwargs) self.param_extensions = param_extensions @@ -326,11 +327,13 @@ class GovukDateField(DateField): # 2. calls field.widget # this bypasses that by making self.widget a method with the same interface as widget.__call__ def widget(self, field, param_extensions=None, **kwargs): - return govuk_text_input_field_widget(self, field, param_extensions=param_extensions, **kwargs) + return govuk_text_input_field_widget( + self, field, param_extensions=param_extensions, **kwargs + ) class GovukIntegerField(IntegerField): - def __init__(self, label='', validators=None, param_extensions=None, **kwargs): + def __init__(self, label="", validators=None, param_extensions=None, **kwargs): super(GovukIntegerField, self).__init__(label, validators, **kwargs) self.param_extensions = param_extensions @@ -339,15 +342,17 @@ class GovukIntegerField(IntegerField): # 2. calls field.widget # this bypasses that by making self.widget a method with the same interface as widget.__call__ def widget(self, field, param_extensions=None, **kwargs): - return govuk_text_input_field_widget(self, field, param_extensions=param_extensions, **kwargs) + return govuk_text_input_field_widget( + self, field, param_extensions=param_extensions, **kwargs + ) class SMSCode(GovukTextInputField): validators = [ - DataRequired(message='Cannot be empty'), - Regexp(regex=r'^\d+$', message='Numbers only'), - Length(min=6, message='Not enough numbers'), - Length(max=6, message='Too many numbers'), + DataRequired(message="Cannot be empty"), + Regexp(regex=r"^\d+$", message="Numbers only"), + Length(min=6, message="Not enough numbers"), + Length(max=6, message="Too many numbers"), ] def __call__(self, **kwargs): @@ -355,7 +360,7 @@ class SMSCode(GovukTextInputField): if "param_extensions" in kwargs: merge_jsonlike(kwargs["param_extensions"], params) - return super().__call__(type='tel', **kwargs) + return super().__call__(type="tel", **kwargs) def process_formdata(self, valuelist): if valuelist: @@ -363,49 +368,39 @@ class SMSCode(GovukTextInputField): class ForgivingIntegerField(GovukTextInputField): - # Actual value is 2147483647 but this is a scary looking arbitrary number POSTGRES_MAX_INT = 2000000000 - def __init__( - self, - label=None, - things='items', - format_error_suffix='', - **kwargs - ): + def __init__(self, label=None, things="items", format_error_suffix="", **kwargs): self.things = things self.format_error_suffix = format_error_suffix super().__init__(label, **kwargs) def process_formdata(self, valuelist): - if valuelist: - - value = valuelist[0].replace(',', '').replace(' ', '') + value = valuelist[0].replace(",", "").replace(" ", "") try: value = int(value) except ValueError: pass - if value == '': + if value == "": value = 0 return super().process_formdata([value]) def pre_validate(self, form): - if self.data: error = None try: if int(self.data) > self.POSTGRES_MAX_INT: - error = 'Number of {} must be {} or less'.format( + error = "Number of {} must be {} or less".format( self.things, format_thousands(self.POSTGRES_MAX_INT), ) except ValueError: - error = 'Enter the number of {} {}'.format( + error = "Enter the number of {} {}".format( self.things, self.format_error_suffix, ) @@ -416,28 +411,23 @@ class ForgivingIntegerField(GovukTextInputField): return super().pre_validate(form) def __call__(self, **kwargs): - if self.get_form().is_submitted() and not self.get_form().validate(): - return super().__call__( - value=(self.raw_data or [None])[0], - **kwargs - ) + return super().__call__(value=(self.raw_data or [None])[0], **kwargs) try: value = int(self.data) value = format_thousands(value) except (ValueError, TypeError): - value = self.data if self.data is not None else '' + value = self.data if self.data is not None else "" return super().__call__(value=value, **kwargs) -class FieldWithNoneOption(): - +class FieldWithNoneOption: # This is a special value that is specific to our forms. This is # more expicit than casting `None` to a string `'None'` which can # have unexpected edge cases - NONE_OPTION_VALUE = '__NONE__' + NONE_OPTION_VALUE = "__NONE__" # When receiving Python data, eg when instantiating the form object # we want to convert that data to our special value, so that it gets @@ -457,25 +447,28 @@ class RadioFieldWithNoneOption(FieldWithNoneOption, RadioField): class NestedFieldMixin: - def children(self): - # start map with root option as a single child entry - child_map = {None: [option for option in self - if option.data == self.NONE_OPTION_VALUE]} + child_map = { + None: [option for option in self if option.data == self.NONE_OPTION_VALUE] + } # add entries for all other children for option in self: # assign all options with a NONE_OPTION_VALUE (not always None) to the None key if option.data == self.NONE_OPTION_VALUE: child_ids = [ - folder['id'] for folder in self.all_template_folders - if folder['parent_id'] is None] + folder["id"] + for folder in self.all_template_folders + if folder["parent_id"] is None + ] key = self.NONE_OPTION_VALUE else: child_ids = [ - folder['id'] for folder in self.all_template_folders - if folder['parent_id'] == option.data] + folder["id"] + for folder in self.all_template_folders + if folder["parent_id"] == option.data + ] key = option.data child_map[key] = [option for option in self if option.data in child_ids] @@ -493,7 +486,9 @@ class NestedFieldMixin: for option in self._children[None]: item = self.get_item_from_option(option) if option.data in self._children: - item['children'] = self.render_children(field.name, option.label.text, self._children[option.data]) + item["children"] = self.render_children( + field.name, option.label.text, self._children[option.data] + ) items.append(item) return items @@ -501,27 +496,22 @@ class NestedFieldMixin: def render_children(self, name, label, options): params = { "name": name, - "fieldset": { - "legend": { - "text": label, - "classes": "usa-sr-only" - } - }, - "formGroup": { - "classes": "usa-form-group--nested" - }, + "fieldset": {"legend": {"text": label, "classes": "usa-sr-only"}}, + "formGroup": {"classes": "usa-form-group--nested"}, "asList": True, - "items": [] + "items": [], } for option in options: item = self.get_item_from_option(option) if len(self._children[option.data]): - item['children'] = self.render_children(name, option.label.text, self._children[option.data]) + item["children"] = self.render_children( + name, option.label.text, self._children[option.data] + ) - params['items'].append(item) + params["items"].append(item) - return render_template('forms/fields/checkboxes/template.njk', params=params) + return render_template("forms/fields/checkboxes/template.njk", params=params) class NestedRadioField(RadioFieldWithNoneOption, NestedFieldMixin): @@ -537,7 +527,7 @@ class HiddenFieldWithNoneOption(FieldWithNoneOption, HiddenField): class RadioFieldWithRequiredMessage(RadioField): - def __init__(self, *args, required_message='Not a valid choice', **kwargs): + def __init__(self, *args, required_message="Not a valid choice", **kwargs): self.required_message = required_message super().__init__(*args, **kwargs) @@ -554,50 +544,58 @@ class StripWhitespaceForm(Form): # FieldList simply doesn't support filters. # @see: https://github.com/wtforms/wtforms/issues/148 no_filter_fields = (FieldList, PasswordField, GovukPasswordField) - filters = [strip_all_whitespace] if not issubclass(unbound_field.field_class, no_filter_fields) else [] - filters += unbound_field.kwargs.get('filters', []) + filters = ( + [strip_all_whitespace] + if not issubclass(unbound_field.field_class, no_filter_fields) + else [] + ) + filters += unbound_field.kwargs.get("filters", []) bound = unbound_field.bind(form=form, filters=filters, **options) - bound.get_form = weakref.ref(form) # GC won't collect the form if we don't use a weakref + bound.get_form = weakref.ref( + form + ) # GC won't collect the form if we don't use a weakref return bound def render_field(self, field, render_kw): - render_kw.setdefault('required', False) + render_kw.setdefault("required", False) return super().render_field(field, render_kw) class StripWhitespaceStringField(GovukTextInputField): def __init__(self, label=None, param_extensions=None, **kwargs): - kwargs['filters'] = tuple(chain( - kwargs.get('filters', ()), - ( - strip_all_whitespace, - ), - )) + kwargs["filters"] = tuple( + chain( + kwargs.get("filters", ()), + (strip_all_whitespace,), + ) + ) super(GovukTextInputField, self).__init__(label, **kwargs) self.param_extensions = param_extensions class LoginForm(StripWhitespaceForm): - email_address = GovukEmailField('Email address', validators=[ - Length(min=5, max=255), - DataRequired(message='Cannot be empty'), - ValidEmail() - ]) - password = GovukPasswordField('Password', validators=[ - DataRequired(message='Enter your password') - ]) + email_address = GovukEmailField( + "Email address", + validators=[ + Length(min=5, max=255), + DataRequired(message="Cannot be empty"), + ValidEmail(), + ], + ) + password = GovukPasswordField( + "Password", validators=[DataRequired(message="Enter your password")] + ) class RegisterUserForm(StripWhitespaceForm): name = GovukTextInputField( - 'Full name', - validators=[DataRequired(message='Cannot be empty')] + "Full name", validators=[DataRequired(message="Cannot be empty")] ) email_address = email_address() mobile_number = international_phone_number() password = password() # always register as sms type - auth_type = HiddenField('auth_type', default='sms_auth') + auth_type = HiddenField("auth_type", default="sms_auth") class RegisterUserFromInviteForm(RegisterUserForm): @@ -606,19 +604,17 @@ class RegisterUserFromInviteForm(RegisterUserForm): service=invited_user.service, email_address=invited_user.email_address, auth_type=invited_user.auth_type, - name=guess_name_from_email_address( - invited_user.email_address - ), + name=guess_name_from_email_address(invited_user.email_address), ) - mobile_number = InternationalPhoneNumber('Mobile number', validators=[]) - service = HiddenField('service') - email_address = HiddenField('email_address') - auth_type = HiddenField('auth_type', validators=[DataRequired()]) + mobile_number = InternationalPhoneNumber("Mobile number", validators=[]) + service = HiddenField("service") + email_address = HiddenField("email_address") + auth_type = HiddenField("auth_type", validators=[DataRequired()]) def validate_mobile_number(self, field): - if self.auth_type.data == 'sms_auth' and not field.data: - raise ValidationError('Cannot be empty') + if self.auth_type.data == "sms_auth" and not field.data: + raise ValidationError("Cannot be empty") class RegisterUserFromOrgInviteForm(StripWhitespaceForm): @@ -629,19 +625,19 @@ class RegisterUserFromOrgInviteForm(StripWhitespaceForm): ) name = GovukTextInputField( - 'Full name', - validators=[DataRequired(message='Cannot be empty')] + "Full name", validators=[DataRequired(message="Cannot be empty")] ) - mobile_number = InternationalPhoneNumber('Mobile number', validators=[DataRequired(message='Cannot be empty')]) + mobile_number = InternationalPhoneNumber( + "Mobile number", validators=[DataRequired(message="Cannot be empty")] + ) password = password() - organization = HiddenField('organization') - email_address = HiddenField('email_address') - auth_type = HiddenField('auth_type', validators=[DataRequired()]) + organization = HiddenField("organization") + email_address = HiddenField("email_address") + auth_type = HiddenField("auth_type", validators=[DataRequired()]) def govuk_checkbox_field_widget(self, field, param_extensions=None, **kwargs): - # error messages error_message = None if field.errors: @@ -649,24 +645,23 @@ def govuk_checkbox_field_widget(self, field, param_extensions=None, **kwargs): "attributes": { "data-module": "track-error", "data-error-type": field.errors[0], - "data-error-label": field.name + "data-error-label": field.name, }, - "text": field.errors[0] + "text": field.errors[0], } params = { - 'name': field.name, - 'errorMessage': error_message, - 'items': [ + "name": field.name, + "errorMessage": error_message, + "items": [ { "name": field.name, "id": field.id, "text": field.label.text, - "value": 'y', - "checked": field.data + "value": "y", + "checked": field.data, } - ] - + ], } # extend default params with any sent in during instantiation @@ -677,20 +672,20 @@ def govuk_checkbox_field_widget(self, field, param_extensions=None, **kwargs): if param_extensions: merge_jsonlike(params, param_extensions) - return Markup( - render_template('forms/fields/checkboxes/macro.njk', params=params)) + return Markup(render_template("forms/fields/checkboxes/macro.njk", params=params)) -def govuk_checkboxes_field_widget(self, field, wrap_in_collapsible=False, param_extensions=None, **kwargs): - +def govuk_checkboxes_field_widget( + self, field, wrap_in_collapsible=False, param_extensions=None, **kwargs +): def _wrap_in_collapsible(field_label, checkboxes_string): # wrap the checkboxes HTML in the HTML needed by the collapisble JS result = Markup( f'
' - f' {checkboxes_string}' - f'
' + f" {checkboxes_string}" + f"" ) return result @@ -702,9 +697,9 @@ def govuk_checkboxes_field_widget(self, field, wrap_in_collapsible=False, param_ "attributes": { "data-module": "track-error", "data-error-type": field.errors[0], - "data-error-label": field.name + "data-error-label": field.name, }, - "text": field.errors[0] + "text": field.errors[0], } # returns either a list or a hierarchy of lists @@ -712,16 +707,16 @@ def govuk_checkboxes_field_widget(self, field, wrap_in_collapsible=False, param_ items = self.get_items_from_options(field) params = { - 'name': field.name, + "name": field.name, "fieldset": { "attributes": {"id": field.name}, "legend": { "text": field.label.text, - } + }, }, "asList": self.render_as_list, - 'errorMessage': error_message, - 'items': items + "errorMessage": error_message, + "items": items, } # extend default params with any sent in during instantiation @@ -733,22 +728,26 @@ def govuk_checkboxes_field_widget(self, field, wrap_in_collapsible=False, param_ merge_jsonlike(params, param_extensions) if wrap_in_collapsible: - # add a blank hint to act as an ARIA live-region params.update( - {"hint": {"html": "
"}}) + { + "hint": { + "html": '
' + } + } + ) return _wrap_in_collapsible( self.field_label, - Markup(render_template('forms/fields/checkboxes/macro.njk', params=params)) + Markup(render_template("forms/fields/checkboxes/macro.njk", params=params)), ) else: return Markup( - render_template('forms/fields/checkboxes/macro.njk', params=params)) + render_template("forms/fields/checkboxes/macro.njk", params=params) + ) def govuk_radios_field_widget(self, field, param_extensions=None, **kwargs): - # error messages error_message = None if field.errors: @@ -756,9 +755,9 @@ def govuk_radios_field_widget(self, field, param_extensions=None, **kwargs): "attributes": { "data-module": "track-error", "data-error-type": field.errors[0], - "data-error-label": field.name + "data-error-label": field.name, }, - "text": field.errors[0] + "text": field.errors[0], } # returns either a list or a hierarchy of lists @@ -766,16 +765,16 @@ def govuk_radios_field_widget(self, field, param_extensions=None, **kwargs): items = self.get_items_from_options(field) params = { - 'name': field.name, + "name": field.name, "fieldset": { "attributes": {"id": field.name}, "legend": { "text": field.label.text, - "classes": "govuk-fieldset__legend--s" - } + "classes": "govuk-fieldset__legend--s", + }, }, - 'errorMessage': error_message, - 'items': items + "errorMessage": error_message, + "items": items, } # extend default params with any sent in during instantiation @@ -787,13 +786,15 @@ def govuk_radios_field_widget(self, field, param_extensions=None, **kwargs): merge_jsonlike(params, param_extensions) return Markup( - render_template('components/components/radios/template.njk', params=params)) + render_template("components/components/radios/template.njk", params=params) + ) class GovukCheckboxField(BooleanField): - - def __init__(self, label='', validators=None, param_extensions=None, **kwargs): - super(GovukCheckboxField, self).__init__(label, validators, false_values=None, **kwargs) + def __init__(self, label="", validators=None, param_extensions=None, **kwargs): + super(GovukCheckboxField, self).__init__( + label, validators, false_values=None, **kwargs + ) self.param_extensions = param_extensions # self.__call__ renders the HTML for the field by: @@ -801,12 +802,13 @@ class GovukCheckboxField(BooleanField): # 2. calls field.widget # this bypasses that by making self.widget a method with the same interface as widget.__call__ def widget(self, field, param_extensions=None, **kwargs): - return govuk_checkbox_field_widget(self, field, param_extensions=param_extensions, **kwargs) + return govuk_checkbox_field_widget( + self, field, param_extensions=param_extensions, **kwargs + ) class GovukTextareaField(TextAreaField): - - def __init__(self, label='', validators=None, param_extensions=None, **kwargs): + def __init__(self, label="", validators=None, param_extensions=None, **kwargs): super(TextAreaField, self).__init__(label, validators, **kwargs) self.param_extensions = param_extensions @@ -827,12 +829,10 @@ class GovukTextareaField(TextAreaField): "label": { "text": field.label.text, "classes": None, - "isPageHeading": False + "isPageHeading": False, }, - "hint": { - "text": None - }, - "errorMessage": error_message + "hint": {"text": None}, + "errorMessage": error_message, } # extend default params with any sent in during instantiation @@ -844,15 +844,17 @@ class GovukTextareaField(TextAreaField): merge_jsonlike(params, param_extensions) return Markup( - render_template('components/components/textarea/template.njk', params=params)) + render_template( + "components/components/textarea/template.njk", params=params + ) + ) # based on work done by @richardjpope: https://github.com/richardjpope/recourse/blob/master/recourse/forms.py#L6 class GovukCheckboxesField(SelectMultipleField): - render_as_list = False - def __init__(self, label='', validators=None, param_extensions=None, **kwargs): + def __init__(self, label="", validators=None, param_extensions=None, **kwargs): super(GovukCheckboxesField, self).__init__(label, validators, **kwargs) self.param_extensions = param_extensions @@ -862,7 +864,7 @@ class GovukCheckboxesField(SelectMultipleField): "id": option.id, "text": option.label.text, "value": str(option.data), # to protect against non-string types like uuids - "checked": option.checked + "checked": option.checked, } def get_items_from_options(self, field): @@ -873,31 +875,39 @@ class GovukCheckboxesField(SelectMultipleField): # 2. calls field.widget # this bypasses that by making self.widget a method with the same interface as widget.__call__ def widget(self, field, param_extensions=None, **kwargs): - return govuk_checkboxes_field_widget(self, field, param_extensions=param_extensions, **kwargs) + return govuk_checkboxes_field_widget( + self, field, param_extensions=param_extensions, **kwargs + ) # Wraps checkboxes rendering in HTML needed by the collapsible JS class GovukCollapsibleCheckboxesField(GovukCheckboxesField): - def __init__(self, label='', validators=None, field_label='', param_extensions=None, **kwargs): - - super(GovukCollapsibleCheckboxesField, self).__init__(label, validators, param_extensions, **kwargs) + def __init__( + self, label="", validators=None, field_label="", param_extensions=None, **kwargs + ): + super(GovukCollapsibleCheckboxesField, self).__init__( + label, validators, param_extensions, **kwargs + ) self.field_label = field_label def widget(self, field, **kwargs): - return govuk_checkboxes_field_widget(self, field, wrap_in_collapsible=True, param_extensions=None, **kwargs) + return govuk_checkboxes_field_widget( + self, field, wrap_in_collapsible=True, param_extensions=None, **kwargs + ) # GovukCollapsibleCheckboxesField adds an ARIA live-region to the hint and wraps the render in HTML needed by the # collapsible JS # NestedFieldMixin puts the items into a tree hierarchy, pre-rendering the sub-trees of the top-level items -class GovukCollapsibleNestedCheckboxesField(NestedFieldMixin, GovukCollapsibleCheckboxesField): +class GovukCollapsibleNestedCheckboxesField( + NestedFieldMixin, GovukCollapsibleCheckboxesField +): NONE_OPTION_VALUE = None render_as_list = True class GovukRadiosField(RadioField): - - def __init__(self, label='', validators=None, param_extensions=None, **kwargs): + def __init__(self, label="", validators=None, param_extensions=None, **kwargs): super(GovukRadiosField, self).__init__(label, validators, **kwargs) self.param_extensions = param_extensions @@ -907,7 +917,7 @@ class GovukRadiosField(RadioField): "id": option.id, "text": option.label.text, "value": str(option.data), # to protect against non-string types like uuids - "checked": option.checked + "checked": option.checked, } def get_items_from_options(self, field): @@ -918,7 +928,9 @@ class GovukRadiosField(RadioField): # 2. calls field.widget # this bypasses that by making self.widget a method with the same interface as widget.__call__ def widget(self, field, param_extensions=None, **kwargs): - return govuk_radios_field_widget(self, field, param_extensions=param_extensions, **kwargs) + return govuk_radios_field_widget( + self, field, param_extensions=param_extensions, **kwargs + ) class OptionalGovukRadiosField(GovukRadiosField): @@ -929,16 +941,15 @@ class OptionalGovukRadiosField(GovukRadiosField): class OnOffField(GovukRadiosField): - def __init__(self, label, choices=None, *args, **kwargs): choices = choices or [ - (True, 'On'), - (False, 'Off'), + (True, "On"), + (False, "Off"), ] super().__init__( label, choices=choices, - thing=f'{choices[0][1].lower()} or {choices[1][1].lower()}', + thing=f"{choices[0][1].lower()} or {choices[1][1].lower()}", *args, **kwargs, ) @@ -946,37 +957,28 @@ class OnOffField(GovukRadiosField): def process_formdata(self, valuelist): if valuelist: value = valuelist[0] - self.data = (value == 'True') if value in ['True', 'False'] else value + self.data = (value == "True") if value in ["True", "False"] else value def iter_choices(self): for value, label in self.choices: # This overrides WTForms default behaviour which is to check # self.coerce(value) == self.data # where self.coerce returns a string for a boolean input - yield ( - value, - label, - (self.data in {value, self.coerce(value)}) - ) + yield (value, label, (self.data in {value, self.coerce(value)})) class OrganizationTypeField(GovukRadiosField): - def __init__( - self, - *args, - include_only=None, - validators=None, - **kwargs - ): + def __init__(self, *args, include_only=None, validators=None, **kwargs): super().__init__( *args, choices=[ - (value, label) for value, label in Organization.TYPE_LABELS.items() + (value, label) + for value, label in Organization.TYPE_LABELS.items() if not include_only or value in include_only ], - thing='the type of organization', + thing="the type of organization", validators=validators or [], - **kwargs + **kwargs, ) @@ -989,16 +991,20 @@ def filter_by_permissions(valuelist): if valuelist is None: return None else: - return [entry for entry in valuelist if any(entry in option for option in permission_options)] + return [ + entry + for entry in valuelist + if any(entry in option for option in permission_options) + ] class AuthTypeForm(StripWhitespaceForm): auth_type = GovukRadiosField( - 'Sign in using', + "Sign in using", choices=[ - ('sms_auth', format_auth_type('sms_auth')), - ('email_auth', format_auth_type('email_auth')), - ] + ("sms_auth", format_auth_type("sms_auth")), + ("email_auth", format_auth_type("email_auth")), + ], ) @@ -1009,32 +1015,29 @@ class BasePermissionsForm(StripWhitespaceForm): if all_template_folders is not None: self.folder_permissions.all_template_folders = all_template_folders self.folder_permissions.choices = [ - (item['id'], item['name']) for item in ([{'name': 'Templates', 'id': None}] + all_template_folders) + (item["id"], item["name"]) + for item in ([{"name": "Templates", "id": None}] + all_template_folders) ] folder_permissions = GovukCollapsibleNestedCheckboxesField( - 'Folders this team member can see', - field_label='folder') + "Folders this team member can see", field_label="folder" + ) login_authentication = GovukRadiosField( - 'Sign in using', + "Sign in using", choices=[ - ('sms_auth', 'Text message code'), - ('email_auth', 'Email link'), + ("sms_auth", "Text message code"), + ("email_auth", "Email link"), ], - thing='how this team member should sign in', - validators=[DataRequired()] + thing="how this team member should sign in", + validators=[DataRequired()], ) permissions_field = GovukCheckboxesField( - 'Permissions', + "Permissions", filters=[filter_by_permissions], - choices=[ - (value, label) for value, label in permission_options - ], - param_extensions={ - "hint": {"text": "All team members can see sent messages."} - } + choices=[(value, label) for value, label in permission_options], + param_extensions={"hint": {"text": "All team members can see sent messages."}}, ) @property @@ -1049,9 +1052,8 @@ class BasePermissionsForm(StripWhitespaceForm): "permissions_field": ( user.permissions_for_service(service_id) & all_ui_permissions ) - }, - login_authentication=user.auth_type + login_authentication=user.auth_type, ) return form @@ -1061,7 +1063,7 @@ class PermissionsForm(BasePermissionsForm): pass -class BaseInviteUserForm(): +class BaseInviteUserForm: email_address = email_address(gov_user=False) def __init__(self, inviter_email_address, *args, **kwargs): @@ -1085,17 +1087,16 @@ class InviteOrgUserForm(BaseInviteUserForm, StripWhitespaceForm): class TwoFactorForm(StripWhitespaceForm): def __init__(self, validate_code_func, *args, **kwargs): - ''' + """ Keyword arguments: validate_code_func -- Validates the code with the API. - ''' + """ self.validate_code_func = validate_code_func super(TwoFactorForm, self).__init__(*args, **kwargs) - sms_code = SMSCode('Text message code') + sms_code = SMSCode("Text message code") def validate(self, extra_validators=None): - if not self.sms_code.validate(self): return False @@ -1114,45 +1115,48 @@ class TextNotReceivedForm(StripWhitespaceForm): class RenameServiceForm(StripWhitespaceForm): name = GovukTextInputField( - u'Service name', + "Service name", validators=[ - DataRequired(message='Cannot be empty'), + DataRequired(message="Cannot be empty"), MustContainAlphanumericCharacters(), - Length(max=255, message='Service name must be 255 characters or fewer') - ]) + Length(max=255, message="Service name must be 255 characters or fewer"), + ], + ) class RenameOrganizationForm(StripWhitespaceForm): name = GovukTextInputField( - u'Organization name', + "Organization name", validators=[ - DataRequired(message='Cannot be empty'), + DataRequired(message="Cannot be empty"), MustContainAlphanumericCharacters(), - Length(max=255, message='Organization name must be 255 characters or fewer') - ]) + Length( + max=255, message="Organization name must be 255 characters or fewer" + ), + ], + ) class OrganizationOrganizationTypeForm(StripWhitespaceForm): - organization_type = OrganizationTypeField('What type of organization is this?') + organization_type = OrganizationTypeField("What type of organization is this?") class AdminOrganizationDomainsForm(StripWhitespaceForm): - def populate(self, domains_list): for index, value in enumerate(domains_list): self.domains[index].data = value domains = FieldList( StripWhitespaceStringField( - '', + "", validators=[ Optional(), ], - default='' + default="", ), min_entries=20, max_entries=20, - label="Domain names" + label="Domain names", ) @@ -1160,16 +1164,16 @@ class CreateServiceForm(StripWhitespaceForm): name = GovukTextInputField( "What’s your service called?", validators=[ - DataRequired(message='Cannot be empty'), + DataRequired(message="Cannot be empty"), MustContainAlphanumericCharacters(), - Length(max=255, message='Service name must be 255 characters or fewer') - ]) - organization_type = OrganizationTypeField('Where is this service run?') + Length(max=255, message="Service name must be 255 characters or fewer"), + ], + ) + organization_type = OrganizationTypeField("Where is this service run?") class AdminNewOrganizationForm( - RenameOrganizationForm, - OrganizationOrganizationTypeForm + RenameOrganizationForm, OrganizationOrganizationTypeForm ): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) @@ -1177,28 +1181,22 @@ class AdminNewOrganizationForm( class AdminServiceSMSAllowanceForm(StripWhitespaceForm): free_sms_allowance = GovukIntegerField( - 'Numbers of text message fragments per year', - validators=[ - InputRequired(message='Cannot be empty') - ] + "Numbers of text message fragments per year", + validators=[InputRequired(message="Cannot be empty")], ) class AdminServiceMessageLimitForm(StripWhitespaceForm): message_limit = GovukIntegerField( - 'Number of messages the service is allowed to send each day', - validators=[ - DataRequired(message='Cannot be empty') - ] + "Number of messages the service is allowed to send each day", + validators=[DataRequired(message="Cannot be empty")], ) class AdminServiceRateLimitForm(StripWhitespaceForm): rate_limit = GovukIntegerField( - 'Number of messages the service can send in a rolling 60 second window', - validators=[ - DataRequired(message='Cannot be empty') - ] + "Number of messages the service can send in a rolling 60 second window", + validators=[DataRequired(message="Cannot be empty")], ) @@ -1207,37 +1205,34 @@ class ConfirmPasswordForm(StripWhitespaceForm): self.validate_password_func = validate_password_func super(ConfirmPasswordForm, self).__init__(*args, **kwargs) - password = GovukPasswordField(u'Enter password') + password = GovukPasswordField("Enter password") def validate_password(self, field): if not self.validate_password_func(field.data): - raise ValidationError('Invalid password') + raise ValidationError("Invalid password") class BaseTemplateForm(StripWhitespaceForm): name = GovukTextInputField( - "Template name", - validators=[DataRequired(message="Cannot be empty")]) + "Template name", validators=[DataRequired(message="Cannot be empty")] + ) template_content = TextAreaField( "Message", - validators=[ - DataRequired(message="Cannot be empty"), - NoCommasInPlaceHolders() - ] + validators=[DataRequired(message="Cannot be empty"), NoCommasInPlaceHolders()], ) - process_type = HiddenField('normal') + process_type = HiddenField("normal") class SMSTemplateForm(BaseTemplateForm): def validate_template_content(self, field): - OnlySMSCharacters(template_type='sms')(None, field) + OnlySMSCharacters(template_type="sms")(None, field) class EmailTemplateForm(BaseTemplateForm): subject = TextAreaField( - u'Subject', - validators=[DataRequired(message="Cannot be empty")]) + "Subject", validators=[DataRequired(message="Cannot be empty")] + ) class ForgotPasswordForm(StripWhitespaceForm): @@ -1253,27 +1248,27 @@ class ChangePasswordForm(StripWhitespaceForm): self.validate_password_func = validate_password_func super(ChangePasswordForm, self).__init__(*args, **kwargs) - old_password = password('Current password') - new_password = password('New password') + old_password = password("Current password") + new_password = password("New password") def validate_old_password(self, field): if not self.validate_password_func(field.data): - raise ValidationError('Invalid password') + raise ValidationError("Invalid password") class CsvUploadForm(StripWhitespaceForm): - file = FileField('Add recipients', validators=[ - DataRequired(message='Please pick a file'), - CsvFileValidator(), - FileSize( - max_size=10e6, # 10Mb - message='File must be smaller than 10Mb' - ) - ]) + file = FileField( + "Add recipients", + validators=[ + DataRequired(message="Please pick a file"), + CsvFileValidator(), + FileSize(max_size=10e6, message="File must be smaller than 10Mb"), # 10Mb + ], + ) class ChangeNameForm(StripWhitespaceForm): - new_name = GovukTextInputField(u'Your name') + new_name = GovukTextInputField("Your name") class ChangeEmailForm(StripWhitespaceForm): @@ -1304,112 +1299,115 @@ class ChangeMobileNumberForm(StripWhitespaceForm): class ChooseTimeForm(StripWhitespaceForm): - def __init__(self, *args, **kwargs): super(ChooseTimeForm, self).__init__(*args, **kwargs) - self.scheduled_for.choices = [('', 'Now')] + [ - get_time_value_and_label(hour) for hour in get_next_hours_until( - get_furthest_possible_scheduled_time() - ) + self.scheduled_for.choices = [("", "Now")] + [ + get_time_value_and_label(hour) + for hour in get_next_hours_until(get_furthest_possible_scheduled_time()) ] - self.scheduled_for.categories = get_next_days_until(get_furthest_possible_scheduled_time()) + self.scheduled_for.categories = get_next_days_until( + get_furthest_possible_scheduled_time() + ) scheduled_for = GovukRadiosField( - 'When should Notify send these messages?', - default='', + "When should Notify send these messages?", + default="", ) class CreateKeyForm(StripWhitespaceForm): def __init__(self, existing_keys, *args, **kwargs): self.existing_key_names = [ - key['name'].lower() for key in existing_keys - if not key['expiry_date'] + key["name"].lower() for key in existing_keys if not key["expiry_date"] ] super().__init__(*args, **kwargs) key_type = GovukRadiosField( - 'Type of key', - thing='the type of key', + "Type of key", + thing="the type of key", ) - key_name = GovukTextInputField("Name for this key", validators=[ - DataRequired(message='You need to give the key a name') - ]) + key_name = GovukTextInputField( + "Name for this key", + validators=[DataRequired(message="You need to give the key a name")], + ) def validate_key_name(self, key_name): if key_name.data.lower() in self.existing_key_names: - raise ValidationError('A key with this name already exists') + raise ValidationError("A key with this name already exists") class SupportType(StripWhitespaceForm): support_type = GovukRadiosField( - 'How can we help you?', + "How can we help you?", choices=[ - (PROBLEM_TICKET_TYPE, 'Report a problem'), - (QUESTION_TICKET_TYPE, 'Ask a question or give feedback'), + (PROBLEM_TICKET_TYPE, "Report a problem"), + (QUESTION_TICKET_TYPE, "Ask a question or give feedback"), ], ) class SupportRedirect(StripWhitespaceForm): who = GovukRadiosField( - 'What do you need help with?', + "What do you need help with?", choices=[ - ('public-sector', 'I work in the public sector and need to send emails or text messages'), - ('public', 'I’m a member of the public with a question for the government'), + ( + "public-sector", + "I work in the public sector and need to send emails or text messages", + ), + ("public", "I’m a member of the public with a question for the government"), ], - param_extensions={ - "fieldset": {"legend": {"classes": "usa-sr-only"}} - } + param_extensions={"fieldset": {"legend": {"classes": "usa-sr-only"}}}, ) class FeedbackOrProblem(StripWhitespaceForm): - name = GovukTextInputField('Name (optional)') - email_address = email_address(label='Email address', gov_user=False, required=True) - feedback = TextAreaField('Your message', validators=[DataRequired(message="Cannot be empty")]) + name = GovukTextInputField("Name (optional)") + email_address = email_address(label="Email address", gov_user=False, required=True) + feedback = TextAreaField( + "Your message", validators=[DataRequired(message="Cannot be empty")] + ) class Triage(StripWhitespaceForm): severe = GovukRadiosField( - 'Is it an emergency?', + "Is it an emergency?", choices=[ - ('yes', 'Yes'), - ('no', 'No'), + ("yes", "Yes"), + ("no", "No"), ], - thing='yes or no', + thing="yes or no", ) class EstimateUsageForm(StripWhitespaceForm): - volume_email = ForgivingIntegerField( - 'How many emails do you expect to send in the next year?', - things='emails', - format_error_suffix='you expect to send', + "How many emails do you expect to send in the next year?", + things="emails", + format_error_suffix="you expect to send", ) volume_sms = ForgivingIntegerField( - 'How many text messages do you expect to send in the next year?', - things='text messages', - format_error_suffix='you expect to send', + "How many text messages do you expect to send in the next year?", + things="text messages", + format_error_suffix="you expect to send", ) consent_to_research = GovukRadiosField( - 'Can we contact you when we’re doing user research?', + "Can we contact you when we’re doing user research?", choices=[ - ('yes', 'Yes'), - ('no', 'No'), + ("yes", "Yes"), + ("no", "No"), ], - thing='yes or no', + thing="yes or no", param_extensions={ - 'hint': {'text': 'You do not have to take part and you can unsubscribe at any time'} - } + "hint": { + "text": "You do not have to take part and you can unsubscribe at any time" + } + }, ) at_least_one_volume_filled = True def validate(self, *args, **kwargs): - if self.volume_email.data == self.volume_sms.data == 0: self.at_least_one_volume_filled = False return False @@ -1424,51 +1422,52 @@ class AdminProviderRatioForm(Form): # hack: https://github.com/wtforms/wtforms/issues/736 self._unbound_fields = [ ( - provider['identifier'], + provider["identifier"], GovukIntegerField( f"{provider['display_name']} (%)", - validators=[validators.NumberRange( - min=0, max=100, message="Must be between 0 and 100" - )], + validators=[ + validators.NumberRange( + min=0, max=100, message="Must be between 0 and 100" + ) + ], param_extensions={ - 'classes': "govuk-input--width-3", - } - ) - ) for provider in providers + "classes": "govuk-input--width-3", + }, + ), + ) + for provider in providers ] - super().__init__(data={ - provider['identifier']: provider['priority'] - for provider in providers - }) + super().__init__( + data={ + provider["identifier"]: provider["priority"] for provider in providers + } + ) def validate(self, extra_validators=None): if not super().validate(extra_validators): return False total = sum( - getattr(self, provider['identifier']).data - for provider in self._providers + getattr(self, provider["identifier"]).data for provider in self._providers ) if total == 100: return True for provider in self._providers: - getattr(self, provider['identifier']).errors += [ - 'Must add up to 100%' - ] + getattr(self, provider["identifier"]).errors += ["Must add up to 100%"] return False class ServiceContactDetailsForm(StripWhitespaceForm): contact_details_type = RadioField( - 'Type of contact details', + "Type of contact details", choices=[ - ('url', 'Link'), - ('email_address', 'Email address'), - ('phone_number', 'Phone number'), + ("url", "Link"), + ("email_address", "Email address"), + ("phone_number", "Phone number"), ], ) @@ -1478,40 +1477,49 @@ class ServiceContactDetailsForm(StripWhitespaceForm): phone_number = GovukTextInputField("Phone number") def validate(self, extra_validators=None): + if self.contact_details_type.data == "url": + self.url.validators = [DataRequired(), URL(message="Must be a valid URL")] - if self.contact_details_type.data == 'url': - self.url.validators = [DataRequired(), URL(message='Must be a valid URL')] + elif self.contact_details_type.data == "email_address": + self.email_address.validators = [ + DataRequired(), + Length(min=5, max=255), + ValidEmail(), + ] - elif self.contact_details_type.data == 'email_address': - self.email_address.validators = [DataRequired(), Length(min=5, max=255), ValidEmail()] + elif self.contact_details_type.data == "phone_number": - elif self.contact_details_type.data == 'phone_number': def valid_phone_number(self, num): try: validate_phone_number(num.data, international=True) return True except InvalidPhoneError: - raise ValidationError('Must be a valid phone number') - self.phone_number.validators = [DataRequired(), Length(min=5, max=20), valid_phone_number] + raise ValidationError("Must be a valid phone number") + + self.phone_number.validators = [ + DataRequired(), + Length(min=5, max=20), + valid_phone_number, + ] return super().validate(extra_validators) class ServiceReplyToEmailForm(StripWhitespaceForm): - email_address = email_address(label='Reply-to email address', gov_user=False) + email_address = email_address(label="Reply-to email address", gov_user=False) is_default = GovukCheckboxField("Make this email address the default") class ServiceSmsSenderForm(StripWhitespaceForm): sms_sender = GovukTextInputField( - 'Text message sender', + "Text message sender", validators=[ DataRequired(message="Cannot be empty"), Length(max=11, message="Enter 11 characters or fewer"), Length(min=3, message="Enter 3 characters or more"), LettersNumbersSingleQuotesFullStopsAndUnderscoresOnly(), DoesNotStartWithDoubleZero(), - ] + ], ) is_default = GovukCheckboxField("Make this text message sender the default") @@ -1525,16 +1533,15 @@ class AdminNotesForm(StripWhitespaceForm): class AdminBillingDetailsForm(StripWhitespaceForm): - billing_contact_email_addresses = GovukTextInputField('Contact email addresses') - billing_contact_names = GovukTextInputField('Contact names') - billing_reference = GovukTextInputField('Reference') - purchase_order_number = GovukTextInputField('Purchase order number') + billing_contact_email_addresses = GovukTextInputField("Contact email addresses") + billing_contact_names = GovukTextInputField("Contact names") + billing_reference = GovukTextInputField("Reference") + purchase_order_number = GovukTextInputField("Purchase order number") notes = TextAreaField(validators=[]) class ServiceOnOffSettingForm(StripWhitespaceForm): - - def __init__(self, name, *args, truthy='On', falsey='Off', **kwargs): + def __init__(self, name, *args, truthy="On", falsey="Off", **kwargs): super().__init__(*args, **kwargs) self.enabled.label.text = name self.enabled.choices = [ @@ -1542,31 +1549,31 @@ class ServiceOnOffSettingForm(StripWhitespaceForm): (False, falsey), ] - enabled = OnOffField('Choices') + enabled = OnOffField("Choices") class ServiceSwitchChannelForm(ServiceOnOffSettingForm): def __init__(self, channel, *args, **kwargs): - name = 'Send {}'.format({ - 'email': 'emails', - 'sms': 'text messages', - }.get(channel)) + name = "Send {}".format( + { + "email": "emails", + "sms": "text messages", + }.get(channel) + ) super().__init__(name, *args, **kwargs) class AdminSetEmailBrandingForm(StripWhitespaceForm): - branding_style = GovukRadiosFieldWithNoneOption( - 'Branding style', - param_extensions={'fieldset': {'legend': {'classes': 'usa-sr-only'}}}, - thing='a branding style', + "Branding style", + param_extensions={"fieldset": {"legend": {"classes": "usa-sr-only"}}}, + thing="a branding style", ) - DEFAULT = (FieldWithNoneOption.NONE_OPTION_VALUE, 'GOV.UK') + DEFAULT = (FieldWithNoneOption.NONE_OPTION_VALUE, "GOV.UK") def __init__(self, all_branding_options, current_branding): - super().__init__(branding_style=current_branding) self.branding_style.choices = sorted( @@ -1580,48 +1587,52 @@ class AdminSetEmailBrandingForm(StripWhitespaceForm): class AdminPreviewBrandingForm(StripWhitespaceForm): - - branding_style = HiddenFieldWithNoneOption('branding_style') + branding_style = HiddenFieldWithNoneOption("branding_style") class AdminEditEmailBrandingForm(StripWhitespaceForm): - name = GovukTextInputField('Name of brand') - text = GovukTextInputField('Text') + name = GovukTextInputField("Name of brand") + text = GovukTextInputField("Text") colour = GovukTextInputField( - 'Colour', + "Colour", validators=[ - Regexp(regex="^$|^#(?:[0-9a-fA-F]{3}){1,2}$", message='Must be a valid color hex code (starting with #)') + Regexp( + regex="^$|^#(?:[0-9a-fA-F]{3}){1,2}$", + message="Must be a valid color hex code (starting with #)", + ) ], param_extensions={ "classes": "govuk-input--width-6", - "attributes": {"data-module": "colour-preview"} - } + "attributes": {"data-module": "colour-preview"}, + }, + ) + file = FileField_wtf( + "Upload a PNG logo", validators=[FileAllowed(["png"], "PNG Images only!")] ) - file = FileField_wtf('Upload a PNG logo', validators=[FileAllowed(['png'], 'PNG Images only!')]) brand_type = GovukRadiosField( "Brand type", choices=[ - ('both', 'GOV.UK and branding'), - ('org', 'Branding only'), - ('org_banner', 'Branding banner'), - ] + ("both", "GOV.UK and branding"), + ("org", "Branding only"), + ("org_banner", "Branding banner"), + ], ) def validate_name(self, name): - op = request.form.get('operation') - if op == 'email-branding-details' and not self.name.data: - raise ValidationError('This field is required') + op = request.form.get("operation") + if op == "email-branding-details" and not self.name.data: + raise ValidationError("This field is required") class SVGFileUpload(StripWhitespaceForm): file = FileField_wtf( - 'Upload an SVG logo', + "Upload an SVG logo", validators=[ - FileAllowed(['svg'], 'SVG Images only!'), + FileAllowed(["svg"], "SVG Images only!"), DataRequired(message="You need to upload a file to submit"), NoEmbeddedImagesInSVG(), NoTextInSVG(), - ] + ], ) @@ -1629,45 +1640,33 @@ class EmailFieldInGuestList(GovukEmailField, StripWhitespaceStringField): pass -class InternationalPhoneNumberInGuestList(InternationalPhoneNumber, StripWhitespaceStringField): +class InternationalPhoneNumberInGuestList( + InternationalPhoneNumber, StripWhitespaceStringField +): pass class GuestList(StripWhitespaceForm): - def populate(self, email_addresses, phone_numbers): for form_field, existing_guest_list in ( (self.email_addresses, email_addresses), - (self.phone_numbers, phone_numbers) + (self.phone_numbers, phone_numbers), ): for index, value in enumerate(existing_guest_list): form_field[index].data = value email_addresses = FieldList( - EmailFieldInGuestList( - '', - validators=[ - Optional(), - ValidEmail() - ], - default='' - ), + EmailFieldInGuestList("", validators=[Optional(), ValidEmail()], default=""), min_entries=5, max_entries=5, - label="Email addresses" + label="Email addresses", ) phone_numbers = FieldList( - InternationalPhoneNumberInGuestList( - '', - validators=[ - Optional() - ], - default='' - ), + InternationalPhoneNumberInGuestList("", validators=[Optional()], default=""), min_entries=5, max_entries=5, - label="Mobile numbers" + label="Mobile numbers", ) @@ -1688,90 +1687,92 @@ class BillingReportDateFilterForm(StripWhitespaceForm): class SearchByNameForm(StripWhitespaceForm): - search = GovukSearchField( - 'Search by name', - validators=[DataRequired("You need to enter full or partial name to search by.")], + "Search by name", + validators=[ + DataRequired("You need to enter full or partial name to search by.") + ], ) class AdminSearchUsersByEmailForm(StripWhitespaceForm): - search = GovukSearchField( - 'Search by name or email address', + "Search by name or email address", validators=[ - DataRequired("You need to enter full or partial email address to search by.") + DataRequired( + "You need to enter full or partial email address to search by." + ) ], ) class SearchUsersForm(StripWhitespaceForm): - - search = GovukSearchField('Search by name or email address') + search = GovukSearchField("Search by name or email address") class SearchNotificationsForm(StripWhitespaceForm): - to = GovukSearchField() labels = { - 'email': 'Search by email address', - 'sms': 'Search by phone number', + "email": "Search by email address", + "sms": "Search by phone number", } def __init__(self, message_type, *args, **kwargs): super().__init__(*args, **kwargs) self.to.label.text = self.labels.get( message_type, - 'Search by phone number or email address', + "Search by phone number or email address", ) class SearchTemplatesForm(StripWhitespaceForm): - search = GovukSearchField() def __init__(self, api_keys, *args, **kwargs): super().__init__(*args, **kwargs) self.search.label.text = ( - 'Search by name or ID' if api_keys else 'Search by name' + "Search by name or ID" if api_keys else "Search by name" ) class PlaceholderForm(StripWhitespaceForm): - pass class AdminServiceInboundNumberForm(StripWhitespaceForm): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) - self.inbound_number.choices = kwargs['inbound_number_choices'] + self.inbound_number.choices = kwargs["inbound_number_choices"] inbound_number = GovukRadiosField( "Set inbound number", - thing='an inbound number', + thing="an inbound number", ) class CallbackForm(StripWhitespaceForm): url = GovukTextInputField( "URL", - validators=[DataRequired(message='Cannot be empty'), - Regexp(regex="^https.*", message='Must be a valid https URL')] + validators=[ + DataRequired(message="Cannot be empty"), + Regexp(regex="^https.*", message="Must be a valid https URL"), + ], ) bearer_token = GovukPasswordField( "Bearer token", - validators=[DataRequired(message='Cannot be empty'), - Length(min=10, message='Must be at least 10 characters')] + validators=[ + DataRequired(message="Cannot be empty"), + Length(min=10, message="Must be at least 10 characters"), + ], ) def validate(self, extra_validators=None): - return super().validate(extra_validators) or self.url.data == '' + return super().validate(extra_validators) or self.url.data == "" class SMSPrefixForm(StripWhitespaceForm): - enabled = OnOffField('') # label is assigned on instantiation + enabled = OnOffField("") # label is assigned on instantiation def get_placeholder_form_instance( @@ -1780,69 +1781,66 @@ def get_placeholder_form_instance( template_type, allow_international_phone_numbers=False, ): - if ( - InsensitiveDict.make_key(placeholder_name) == 'emailaddress' and - template_type == 'email' + InsensitiveDict.make_key(placeholder_name) == "emailaddress" + and template_type == "email" ): field = email_address(label=placeholder_name, gov_user=False) elif ( - InsensitiveDict.make_key(placeholder_name) == 'phonenumber' and - template_type == 'sms' + InsensitiveDict.make_key(placeholder_name) == "phonenumber" + and template_type == "sms" ): if allow_international_phone_numbers: - field = international_phone_number(label=placeholder_name) # TODO: modify as necessary for non-us numbers + field = international_phone_number( + label=placeholder_name + ) # TODO: modify as necessary for non-us numbers else: - field = uk_mobile_number(label=placeholder_name) # TODO: replace with us_mobile_number + field = uk_mobile_number( + label=placeholder_name + ) # TODO: replace with us_mobile_number else: - field = GovukTextInputField(placeholder_name, validators=[ - DataRequired(message='Cannot be empty') - ]) + field = GovukTextInputField( + placeholder_name, validators=[DataRequired(message="Cannot be empty")] + ) PlaceholderForm.placeholder_value = field return PlaceholderForm( - placeholder_value=dict_to_populate_from.get(placeholder_name, '') + placeholder_value=dict_to_populate_from.get(placeholder_name, "") ) class SetSenderForm(StripWhitespaceForm): - def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) - self.sender.choices = kwargs['sender_choices'] - self.sender.label.text = kwargs['sender_label'] + self.sender.choices = kwargs["sender_choices"] + self.sender.label.text = kwargs["sender_label"] sender = GovukRadiosField() class SetTemplateSenderForm(StripWhitespaceForm): - def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) - self.sender.choices = kwargs['sender_choices'] - self.sender.label.text = 'Select your sender' + self.sender.choices = kwargs["sender_choices"] + self.sender.label.text = "Select your sender" sender = GovukRadiosField() class AdminSetOrganizationForm(StripWhitespaceForm): - def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) - self.organizations.choices = kwargs['choices'] + self.organizations.choices = kwargs["choices"] organizations = GovukRadiosField( - 'Select an organization', - validators=[ - DataRequired() - ] + "Select an organization", validators=[DataRequired()] ) class ChooseBrandingForm(StripWhitespaceForm): - FALLBACK_OPTION_VALUE = 'something_else' - FALLBACK_OPTION = (FALLBACK_OPTION_VALUE, 'Something else') + FALLBACK_OPTION_VALUE = "something_else" + FALLBACK_OPTION = (FALLBACK_OPTION_VALUE, "Something else") @property def something_else_is_only_option(self): @@ -1850,21 +1848,20 @@ class ChooseBrandingForm(StripWhitespaceForm): class ChooseEmailBrandingForm(ChooseBrandingForm): - options = RadioField('Choose your new email branding') + options = RadioField("Choose your new email branding") def __init__(self, service): super().__init__() self.options.choices = tuple( - list(branding.get_email_choices(service)) + - [self.FALLBACK_OPTION] + list(branding.get_email_choices(service)) + [self.FALLBACK_OPTION] ) class SomethingElseBrandingForm(StripWhitespaceForm): something_else = GovukTextareaField( - 'Describe the branding you want', - validators=[DataRequired('Cannot be empty')], + "Describe the branding you want", + validators=[DataRequired("Cannot be empty")], param_extensions={ "label": { "isPageHeading": True, @@ -1872,31 +1869,34 @@ class SomethingElseBrandingForm(StripWhitespaceForm): }, "hint": { "text": "Include links to your brand guidelines or examples of how to use your branding." - } - } + }, + }, ) class AdminServiceAddDataRetentionForm(StripWhitespaceForm): - notification_type = GovukRadiosField( - 'What notification type?', + "What notification type?", choices=[ - ('email', 'Email'), - ('sms', 'SMS'), + ("email", "Email"), + ("sms", "SMS"), ], - thing='notification type', + thing="notification type", ) days_of_retention = GovukIntegerField( label="Days of retention", - validators=[validators.NumberRange(min=3, max=90, message="Must be between 3 and 90")], + validators=[ + validators.NumberRange(min=3, max=90, message="Must be between 3 and 90") + ], ) class AdminServiceEditDataRetentionForm(StripWhitespaceForm): days_of_retention = GovukIntegerField( label="Days of retention", - validators=[validators.NumberRange(min=3, max=90, message="Must be between 3 and 90")], + validators=[ + validators.NumberRange(min=3, max=90, message="Must be between 3 and 90") + ], ) @@ -1910,9 +1910,11 @@ class TemplateFolderForm(StripWhitespaceForm): ] users_with_permission = GovukCollapsibleCheckboxesField( - 'Team members who can see this folder', - field_label='team member') - name = GovukTextInputField('Folder name', validators=[DataRequired(message='Cannot be empty')]) + "Team members who can see this folder", field_label="team member" + ) + name = GovukTextInputField( + "Folder name", validators=[DataRequired(message="Cannot be empty")] + ) def required_for_ops(*operations): @@ -1921,9 +1923,10 @@ def required_for_ops(*operations): def validate(form, field): if form.op not in operations and any(field.raw_data): # super weird - raise validators.StopValidation('Must be empty') + raise validators.StopValidation("Must be empty") if form.op in operations and not any(field.raw_data): - raise validators.StopValidation('Cannot be empty') + raise validators.StopValidation("Cannot be empty") + return validate @@ -1945,8 +1948,8 @@ class TemplateAndFoldersSelectionForm(Form): """ ALL_TEMPLATES_FOLDER = { - 'name': 'Templates', - 'id': RadioFieldWithNoneOption.NONE_OPTION_VALUE, + "name": "Templates", + "id": RadioFieldWithNoneOption.NONE_OPTION_VALUE, } def __init__( @@ -1956,9 +1959,8 @@ class TemplateAndFoldersSelectionForm(Form): available_template_types, allow_adding_copy_of_template, *args, - **kwargs + **kwargs, ): - super().__init__(*args, **kwargs) self.available_template_types = available_template_types @@ -1970,33 +1972,45 @@ class TemplateAndFoldersSelectionForm(Form): self.move_to.all_template_folders = all_template_folders self.move_to.choices = [ - (item['id'], item['name']) + (item["id"], item["name"]) for item in ([self.ALL_TEMPLATES_FOLDER] + all_template_folders) ] - self.add_template_by_template_type.choices = list(filter(None, [ - # ('email', 'Email') if 'email' in available_template_types else None, - ('sms', 'Text message') if 'sms' in available_template_types else None, - ('copy-existing', 'Copy an existing template') if allow_adding_copy_of_template else None, - ])) + self.add_template_by_template_type.choices = list( + filter( + None, + [ + # ('email', 'Email') if 'email' in available_template_types else None, + ("sms", "Text message") + if "sms" in available_template_types + else None, + ("copy-existing", "Copy an existing template") + if allow_adding_copy_of_template + else None, + ], + ) + ) @property def trying_to_add_unavailable_template_type(self): - return all(( - self.is_add_template_op, - self.add_template_by_template_type.data, - self.add_template_by_template_type.data not in self.available_template_types, - )) + return all( + ( + self.is_add_template_op, + self.add_template_by_template_type.data, + self.add_template_by_template_type.data + not in self.available_template_types, + ) + ) def is_selected(self, template_folder_id): return template_folder_id in (self.templates_and_folders.data or []) def validate(self, extra_validators=None): - self.op = request.form.get('operation') + self.op = request.form.get("operation") - self.is_move_op = self.op in {'move-to-existing-folder', 'move-to-new-folder'} - self.is_add_folder_op = self.op in {'add-new-folder', 'move-to-new-folder'} - self.is_add_template_op = self.op in {'add-new-template'} + self.is_move_op = self.op in {"move-to-existing-folder", "move-to-new-folder"} + self.is_add_folder_op = self.op in {"add-new-folder", "move-to-new-folder"} + self.is_add_template_op = self.op in {"add-new-template"} if not (self.is_add_folder_op or self.is_move_op or self.is_add_template_op): return False @@ -2004,65 +2018,66 @@ class TemplateAndFoldersSelectionForm(Form): return super().validate(extra_validators) def get_folder_name(self): - if self.op == 'add-new-folder': + if self.op == "add-new-folder": return self.add_new_folder_name.data - elif self.op == 'move-to-new-folder': + elif self.op == "move-to-new-folder": return self.move_to_new_folder_name.data return None templates_and_folders = GovukCheckboxesField( - 'Choose templates or folders', - validators=[required_for_ops('move-to-new-folder', 'move-to-existing-folder')], + "Choose templates or folders", + validators=[required_for_ops("move-to-new-folder", "move-to-existing-folder")], choices=[], # added to keep order of arguments, added properly in __init__ - param_extensions={ - "fieldset": { - "legend": { - "classes": "usa-sr-only" - } - } - } + param_extensions={"fieldset": {"legend": {"classes": "usa-sr-only"}}}, ) # if no default set, it is set to None, which process_data transforms to '__NONE__' # this means '__NONE__' (self.ALL_TEMPLATES option) is selected when no form data has been submitted # set default to empty string so process_data method doesn't perform any transformation move_to = NestedRadioField( - 'Choose a folder', - default='', - validators=[ - required_for_ops('move-to-existing-folder'), - Optional() - ]) - add_new_folder_name = GovukTextInputField('Folder name', validators=[required_for_ops('add-new-folder')]) - move_to_new_folder_name = GovukTextInputField('Folder name', validators=[required_for_ops('move-to-new-folder')]) + "Choose a folder", + default="", + validators=[required_for_ops("move-to-existing-folder"), Optional()], + ) + add_new_folder_name = GovukTextInputField( + "Folder name", validators=[required_for_ops("add-new-folder")] + ) + move_to_new_folder_name = GovukTextInputField( + "Folder name", validators=[required_for_ops("move-to-new-folder")] + ) - add_template_by_template_type = RadioFieldWithRequiredMessage('New template', validators=[ - required_for_ops('add-new-template'), - Optional(), - ], required_message='Select the type of template you want to add') + add_template_by_template_type = RadioFieldWithRequiredMessage( + "New template", + validators=[ + required_for_ops("add-new-template"), + Optional(), + ], + required_message="Select the type of template you want to add", + ) class AdminClearCacheForm(StripWhitespaceForm): model_type = GovukCheckboxesField( - 'What do you want to clear today', + "What do you want to clear today", ) def validate_model_type(self, field): if not field.data: - raise ValidationError('Select at least one option') + raise ValidationError("Select at least one option") class AdminOrganizationGoLiveNotesForm(StripWhitespaceForm): request_to_go_live_notes = TextAreaField( - 'Go live notes', + "Go live notes", filters=[lambda x: x or None], ) class ChangeSecurityKeyNameForm(StripWhitespaceForm): security_key_name = GovukTextInputField( - 'Name of key', + "Name of key", validators=[ - DataRequired(message='Cannot be empty'), + DataRequired(message="Cannot be empty"), MustContainAlphanumericCharacters(), - Length(max=255, message='Name of key must be 255 characters or fewer') - ]) + Length(max=255, message="Name of key must be 255 characters or fewer"), + ], + ) diff --git a/app/main/validators.py b/app/main/validators.py index 26e8c838f..8ea7934bf 100644 --- a/app/main/validators.py +++ b/app/main/validators.py @@ -15,7 +15,7 @@ from app.utils.user import is_gov_user class CommonlyUsedPassword: def __init__(self, message=None): if not message: - message = 'Password is in list of commonly used passwords.' + message = "Password is in list of commonly used passwords." self.message = message def __call__(self, form, field): @@ -24,37 +24,39 @@ class CommonlyUsedPassword: class CsvFileValidator: - - def __init__(self, message='Not a csv file'): + def __init__(self, message="Not a csv file"): self.message = message def __call__(self, form, field): if not Spreadsheet.can_handle(field.data.filename): - raise ValidationError("{} is not a spreadsheet that Notify can read".format(field.data.filename)) + raise ValidationError( + "{} is not a spreadsheet that Notify can read".format( + field.data.filename + ) + ) class ValidGovEmail: - def __call__(self, form, field): - - if field.data == '': + if field.data == "": return from flask import url_for - message = ''' + + message = """ Enter a public sector email address or find out who can use Notify - '''.format(url_for('main.features')) + """.format( + url_for("main.features") + ) if not is_gov_user(field.data.lower()): raise ValidationError(message) class ValidEmail: - - message = 'Enter a valid email address' + message = "Enter a valid email address" def __call__(self, form, field): - if not field.data: return @@ -65,17 +67,15 @@ class ValidEmail: class NoCommasInPlaceHolders: - - def __init__(self, message='You cannot put commas between double brackets'): + def __init__(self, message="You cannot put commas between double brackets"): self.message = message def __call__(self, form, field): - if ',' in ''.join(Field(field.data).placeholders): + if "," in "".join(Field(field.data).placeholders): raise ValidationError(self.message) class NoElementInSVG(ABC): - @property @abstractmethod def element(self): @@ -89,36 +89,42 @@ class NoElementInSVG(ABC): def __call__(self, form, field): svg_contents = field.data.stream.read().decode("utf-8") field.data.stream.seek(0) - if f'<{self.element}' in svg_contents.lower(): + if f"<{self.element}" in svg_contents.lower(): raise ValidationError(self.message) class NoEmbeddedImagesInSVG(NoElementInSVG): - element = 'image' - message = 'This SVG has an embedded raster image in it and will not render well' + element = "image" + message = "This SVG has an embedded raster image in it and will not render well" class NoTextInSVG(NoElementInSVG): - element = 'text' - message = 'This SVG has text which has not been converted to paths and may not render well' + element = "text" + message = "This SVG has text which has not been converted to paths and may not render well" class OnlySMSCharacters: - def __init__(self, *args, template_type, **kwargs): self._template_type = template_type super().__init__(*args, **kwargs) def __call__(self, form, field): - non_sms_characters = sorted(list(SanitiseSMS.get_non_compatible_characters(field.data))) + non_sms_characters = sorted( + list(SanitiseSMS.get_non_compatible_characters(field.data)) + ) if non_sms_characters: raise ValidationError( - 'You cannot use {} in {}. {} will not show up properly on everyone’s phones.'.format( - formatted_list(non_sms_characters, conjunction='or', before_each='', after_each=''), + "You cannot use {} in {}. {} will not show up properly on everyone’s phones.".format( + formatted_list( + non_sms_characters, + conjunction="or", + before_each="", + after_each="", + ), { - 'sms': 'text messages', + "sms": "text messages", }.get(self._template_type), - ('It' if len(non_sms_characters) == 1 else 'They') + ("It" if len(non_sms_characters) == 1 else "They"), ) ) @@ -136,10 +142,9 @@ class OnlySMSCharacters: class LettersNumbersSingleQuotesFullStopsAndUnderscoresOnly: - regex = re.compile(r"^[a-zA-Z0-9\s\._']+$") - def __init__(self, message='Use letters and numbers only'): + def __init__(self, message="Use letters and numbers only"): self.message = message def __call__(self, form, field): @@ -148,7 +153,6 @@ class LettersNumbersSingleQuotesFullStopsAndUnderscoresOnly: class DoesNotStartWithDoubleZero: - def __init__(self, message="Cannot start with 00"): self.message = message @@ -158,13 +162,9 @@ class DoesNotStartWithDoubleZero: class MustContainAlphanumericCharacters: - regex = re.compile(r".*[a-zA-Z0-9].*[a-zA-Z0-9].*") - def __init__( - self, - message="Must include at least two alphanumeric characters" - ): + def __init__(self, message="Must include at least two alphanumeric characters"): self.message = message def __call__(self, form, field): diff --git a/app/main/views/add_service.py b/app/main/views/add_service.py index f42331f43..898790f58 100644 --- a/app/main/views/add_service.py +++ b/app/main/views/add_service.py @@ -10,21 +10,20 @@ from app.utils.user import user_is_gov_user, user_is_logged_in def _create_service(service_name, organization_type, email_from, form): - try: service_id = service_api_client.create_service( service_name=service_name, organization_type=organization_type, - message_limit=current_app.config['DEFAULT_SERVICE_LIMIT'], + message_limit=current_app.config["DEFAULT_SERVICE_LIMIT"], restricted=True, - user_id=session['user_id'], + user_id=session["user_id"], email_from=email_from, ) - session['service_id'] = service_id + session["service_id"] = service_id return service_id, None except HTTPError as e: - if e.status_code == 400 and e.message['name']: + if e.status_code == 400 and e.message["name"]: form.name.errors.append("This service name is already in use") return None, e else: @@ -33,15 +32,15 @@ def _create_service(service_name, organization_type, email_from, form): def _create_example_template(service_id): example_sms_template = service_api_client.create_service_template( - 'Example text message template', - 'sms', - 'Hi, I’m trying out Notify.gov. Today is ((day of week)) and my favorite color is ((color)).', + "Example text message template", + "sms", + "Hi, I’m trying out Notify.gov. Today is ((day of week)) and my favorite color is ((color)).", service_id, ) return example_sms_template -@main.route("/add-service", methods=['GET', 'POST']) +@main.route("/add-service", methods=["GET", "POST"]) @user_is_logged_in @user_is_gov_user def add_service(): @@ -64,33 +63,42 @@ def add_service(): ) if error: return _render_add_service_page(form, default_organization_type) - if len(service_api_client.get_active_services({'user_id': session['user_id']}).get('data', [])) > 1: - return redirect(url_for('main.service_dashboard', service_id=service_id)) + if ( + len( + service_api_client.get_active_services( + {"user_id": session["user_id"]} + ).get("data", []) + ) + > 1 + ): + return redirect(url_for("main.service_dashboard", service_id=service_id)) example_sms_template = _create_example_template(service_id) - return redirect(url_for( - 'main.begin_tour', - service_id=service_id, - template_id=example_sms_template['data']['id'] - )) + return redirect( + url_for( + "main.begin_tour", + service_id=service_id, + template_id=example_sms_template["data"]["id"], + ) + ) else: return _render_add_service_page(form, default_organization_type) def _render_add_service_page(form, default_organization_type): - heading = 'About your service' + heading = "About your service" - if default_organization_type == 'local': + if default_organization_type == "local": return render_template( - 'views/add-service-local.html', + "views/add-service-local.html", form=form, heading=heading, default_organization_type=default_organization_type, ) return render_template( - 'views/add-service.html', + "views/add-service.html", form=form, heading=heading, default_organization_type=default_organization_type, diff --git a/app/main/views/api_keys.py b/app/main/views/api_keys.py index 2df60d7e8..e3aa9bca5 100644 --- a/app/main/views/api_keys.py +++ b/app/main/views/api_keys.py @@ -17,76 +17,84 @@ from app.notify_client.api_key_api_client import ( ) from app.utils.user import user_has_permissions -dummy_bearer_token = 'bearer_token_set' # nosec B105 - this is not a real token +dummy_bearer_token = "bearer_token_set" # nosec B105 - this is not a real token @main.route("/services//api") -@user_has_permissions('manage_api_keys') +@user_has_permissions("manage_api_keys") def api_integration(service_id): callbacks_link = ( - '.api_callbacks' if current_service.has_permission('inbound_sms') - else '.delivery_status_callback' + ".api_callbacks" + if current_service.has_permission("inbound_sms") + else ".delivery_status_callback" ) return render_template( - 'views/api/index.html', + "views/api/index.html", callbacks_link=callbacks_link, - api_notifications=notification_api_client.get_api_notifications_for_service(service_id) + api_notifications=notification_api_client.get_api_notifications_for_service( + service_id + ), ) @main.route("/services//api/documentation") -@user_has_permissions('manage_api_keys') +@user_has_permissions("manage_api_keys") def api_documentation(service_id): - return redirect(url_for('.documentation'), code=301) + return redirect(url_for(".documentation"), code=301) -@main.route("/services//api/whitelist", methods=['GET', 'POST'], endpoint='old_guest_list') -@main.route("/services//api/guest-list", methods=['GET', 'POST']) -@user_has_permissions('manage_api_keys') +@main.route( + "/services//api/whitelist", + methods=["GET", "POST"], + endpoint="old_guest_list", +) +@main.route("/services//api/guest-list", methods=["GET", "POST"]) +@user_has_permissions("manage_api_keys") def guest_list(service_id): form = GuestList() if form.validate_on_submit(): - service_api_client.update_guest_list(service_id, { - 'email_addresses': list(filter(None, form.email_addresses.data)), - 'phone_numbers': list(filter(None, form.phone_numbers.data)) - }) - flash('Guest list updated', 'default_with_tick') - return redirect(url_for('.api_integration', service_id=service_id)) + service_api_client.update_guest_list( + service_id, + { + "email_addresses": list(filter(None, form.email_addresses.data)), + "phone_numbers": list(filter(None, form.phone_numbers.data)), + }, + ) + flash("Guest list updated", "default_with_tick") + return redirect(url_for(".api_integration", service_id=service_id)) if not form.errors: form.populate(**service_api_client.get_guest_list(service_id)) - return render_template( - 'views/api/guest-list.html', - form=form - ) + return render_template("views/api/guest-list.html", form=form) @main.route("/services//api/keys") -@user_has_permissions('manage_api_keys') +@user_has_permissions("manage_api_keys") def api_keys(service_id): return render_template( - 'views/api/keys.html', + "views/api/keys.html", ) -@main.route("/services//api/keys/create", methods=['GET', 'POST']) -@user_has_permissions('manage_api_keys', restrict_admin_usage=True) +@main.route("/services//api/keys/create", methods=["GET", "POST"]) +@user_has_permissions("manage_api_keys", restrict_admin_usage=True) def create_api_key(service_id): form = CreateKeyForm(current_service.api_keys) form.key_type.choices = [ - (KEY_TYPE_NORMAL, 'Live – sends to anyone'), - (KEY_TYPE_TEAM, 'Team and guest list – limits who you can send to'), - (KEY_TYPE_TEST, 'Test – pretends to send messages'), + (KEY_TYPE_NORMAL, "Live – sends to anyone"), + (KEY_TYPE_TEAM, "Team and guest list – limits who you can send to"), + (KEY_TYPE_TEST, "Test – pretends to send messages"), ] # preserve order of items extended by starting with empty dicts - form.key_type.param_extensions = {'items': [{}, {}]} + form.key_type.param_extensions = {"items": [{}, {}]} if current_service.trial_mode: - form.key_type.param_extensions['items'][0] = { - 'disabled': True, - 'hint': { - 'html': Markup( - 'Not available because your service is in ' - 'trial mode') - } + form.key_type.param_extensions["items"][0] = { + "disabled": True, + "hint": { + "html": Markup( + "Not available because your service is in " + 'trial mode' + ) + }, } if form.validate_on_submit(): if current_service.trial_mode and form.key_type.data == KEY_TYPE_NORMAL: @@ -94,36 +102,38 @@ def create_api_key(service_id): secret = api_key_api_client.create_api_key( service_id=service_id, key_name=form.key_name.data, - key_type=form.key_type.data + key_type=form.key_type.data, ) return render_template( - 'views/api/keys/show.html', + "views/api/keys/show.html", secret=secret, service_id=service_id, - key_name=email_safe(form.key_name.data, whitespace='_') + key_name=email_safe(form.key_name.data, whitespace="_"), ) - return render_template( - 'views/api/keys/create.html', - form=form - ) + return render_template("views/api/keys/create.html", form=form) -@main.route("/services//api/keys/revoke/", methods=['GET', 'POST']) -@user_has_permissions('manage_api_keys') +@main.route( + "/services//api/keys/revoke/", methods=["GET", "POST"] +) +@user_has_permissions("manage_api_keys") def revoke_api_key(service_id, key_id): - key_name = current_service.get_api_key(key_id)['name'] - if request.method == 'GET': - flash([ - "Are you sure you want to revoke ‘{}’?".format(key_name), - "You will not be able to use this API key to connect to U.S. Notify." - ], 'revoke this API key') - return render_template( - 'views/api/keys.html', + key_name = current_service.get_api_key(key_id)["name"] + if request.method == "GET": + flash( + [ + "Are you sure you want to revoke ‘{}’?".format(key_name), + "You will not be able to use this API key to connect to U.S. Notify.", + ], + "revoke this API key", ) - elif request.method == 'POST': + return render_template( + "views/api/keys.html", + ) + elif request.method == "POST": api_key_api_client.revoke_api_key(service_id=service_id, key_id=key_id) - flash('‘{}’ was revoked'.format(key_name), 'default_with_tick') - return redirect(url_for('.api_keys', service_id=service_id)) + flash("‘{}’ was revoked".format(key_name), "default_with_tick") + return redirect(url_for(".api_keys", service_id=service_id)) def get_apis(): @@ -131,13 +141,11 @@ def get_apis(): inbound_api = None if current_service.service_callback_api: callback_api = service_api_client.get_service_callback_api( - current_service.id, - current_service.service_callback_api[0] + current_service.id, current_service.service_callback_api[0] ) if current_service.inbound_api: inbound_api = service_api_client.get_service_inbound_api( - current_service.id, - current_service.inbound_api[0] + current_service.id, current_service.inbound_api[0] ) return (callback_api, inbound_api) @@ -147,71 +155,79 @@ def check_token_against_dummy_bearer(token): if token != dummy_bearer_token: return token else: - return '' + return "" -@main.route("/services//api/callbacks", methods=['GET']) -@user_has_permissions('manage_api_keys') +@main.route("/services//api/callbacks", methods=["GET"]) +@user_has_permissions("manage_api_keys") def api_callbacks(service_id): - if not current_service.has_permission('inbound_sms'): - return redirect(url_for('.delivery_status_callback', service_id=service_id)) + if not current_service.has_permission("inbound_sms"): + return redirect(url_for(".delivery_status_callback", service_id=service_id)) delivery_status_callback, received_text_messages_callback = get_apis() return render_template( - 'views/api/callbacks.html', - received_text_messages_callback=received_text_messages_callback['url'] - if received_text_messages_callback else None, - delivery_status_callback=delivery_status_callback['url'] if delivery_status_callback else None + "views/api/callbacks.html", + received_text_messages_callback=received_text_messages_callback["url"] + if received_text_messages_callback + else None, + delivery_status_callback=delivery_status_callback["url"] + if delivery_status_callback + else None, ) def get_delivery_status_callback_details(): if current_service.service_callback_api: return service_api_client.get_service_callback_api( - current_service.id, - current_service.service_callback_api[0] + current_service.id, current_service.service_callback_api[0] ) -@main.route("/services//api/callbacks/delivery-status-callback", methods=['GET', 'POST']) -@user_has_permissions('manage_api_keys') +@main.route( + "/services//api/callbacks/delivery-status-callback", + methods=["GET", "POST"], +) +@user_has_permissions("manage_api_keys") def delivery_status_callback(service_id): delivery_status_callback = get_delivery_status_callback_details() back_link = ( - '.api_callbacks' if current_service.has_permission('inbound_sms') - else '.api_integration' + ".api_callbacks" + if current_service.has_permission("inbound_sms") + else ".api_integration" ) form = CallbackForm( - url=delivery_status_callback.get('url') if delivery_status_callback else '', - bearer_token=dummy_bearer_token if delivery_status_callback else '' + url=delivery_status_callback.get("url") if delivery_status_callback else "", + bearer_token=dummy_bearer_token if delivery_status_callback else "", ) if form.validate_on_submit(): if delivery_status_callback and form.url.data: if ( - delivery_status_callback.get('url') != form.url.data or - form.bearer_token.data != dummy_bearer_token + delivery_status_callback.get("url") != form.url.data + or form.bearer_token.data != dummy_bearer_token ): service_api_client.update_service_callback_api( service_id, url=form.url.data, - bearer_token=check_token_against_dummy_bearer(form.bearer_token.data), + bearer_token=check_token_against_dummy_bearer( + form.bearer_token.data + ), user_id=current_user.id, - callback_api_id=delivery_status_callback.get('id') + callback_api_id=delivery_status_callback.get("id"), ) elif delivery_status_callback and not form.url.data: service_api_client.delete_service_callback_api( service_id, - delivery_status_callback['id'], + delivery_status_callback["id"], ) elif form.url.data: service_api_client.create_service_callback_api( service_id, url=form.url.data, bearer_token=form.bearer_token.data, - user_id=current_user.id + user_id=current_user.id, ) else: # If no callback is set up and the user chooses to continue @@ -222,7 +238,7 @@ def delivery_status_callback(service_id): return redirect(url_for(back_link, service_id=service_id)) return render_template( - 'views/api/callbacks/delivery-status-callback.html', + "views/api/callbacks/delivery-status-callback.html", back_link=back_link, form=form, ) @@ -231,50 +247,56 @@ def delivery_status_callback(service_id): def get_received_text_messages_callback(): if current_service.inbound_api: return service_api_client.get_service_inbound_api( - current_service.id, - current_service.inbound_api[0] + current_service.id, current_service.inbound_api[0] ) -@main.route("/services//api/callbacks/received-text-messages-callback", methods=['GET', 'POST']) -@user_has_permissions('manage_api_keys') +@main.route( + "/services//api/callbacks/received-text-messages-callback", + methods=["GET", "POST"], +) +@user_has_permissions("manage_api_keys") def received_text_messages_callback(service_id): - if not current_service.has_permission('inbound_sms'): - return redirect(url_for('.api_integration', service_id=service_id)) + if not current_service.has_permission("inbound_sms"): + return redirect(url_for(".api_integration", service_id=service_id)) received_text_messages_callback = get_received_text_messages_callback() form = CallbackForm( - url=received_text_messages_callback.get('url') if received_text_messages_callback else '', - bearer_token=dummy_bearer_token if received_text_messages_callback else '' + url=received_text_messages_callback.get("url") + if received_text_messages_callback + else "", + bearer_token=dummy_bearer_token if received_text_messages_callback else "", ) if form.validate_on_submit(): if received_text_messages_callback and form.url.data: if ( - received_text_messages_callback.get('url') != form.url.data or - form.bearer_token.data != dummy_bearer_token + received_text_messages_callback.get("url") != form.url.data + or form.bearer_token.data != dummy_bearer_token ): service_api_client.update_service_inbound_api( service_id, url=form.url.data, - bearer_token=check_token_against_dummy_bearer(form.bearer_token.data), + bearer_token=check_token_against_dummy_bearer( + form.bearer_token.data + ), user_id=current_user.id, - inbound_api_id=received_text_messages_callback.get('id') + inbound_api_id=received_text_messages_callback.get("id"), ) elif received_text_messages_callback and not form.url.data: service_api_client.delete_service_inbound_api( service_id, - received_text_messages_callback['id'], + received_text_messages_callback["id"], ) elif form.url.data: service_api_client.create_service_inbound_api( service_id, url=form.url.data, bearer_token=form.bearer_token.data, - user_id=current_user.id + user_id=current_user.id, ) - return redirect(url_for('.api_callbacks', service_id=service_id)) + return redirect(url_for(".api_callbacks", service_id=service_id)) return render_template( - 'views/api/callbacks/received-text-messages-callback.html', + "views/api/callbacks/received-text-messages-callback.html", form=form, ) diff --git a/app/main/views/choose_account.py b/app/main/views/choose_account.py index e1d9ffb42..dec0b3254 100644 --- a/app/main/views/choose_account.py +++ b/app/main/views/choose_account.py @@ -10,12 +10,12 @@ from app.utils.user import user_is_logged_in @main.route("/services") def choose_service(): - raise PermanentRedirect(url_for('.choose_account')) + raise PermanentRedirect(url_for(".choose_account")) @main.route("/services-or-dashboard") def services_or_dashboard(): - raise PermanentRedirect(url_for('.show_accounts_or_dashboard')) + raise PermanentRedirect(url_for(".show_accounts_or_dashboard")) @main.route("/accounts") @@ -25,10 +25,12 @@ def choose_account(): if current_user.platform_admin: org_count, live_service_count = ( len(AllOrganizations()), - status_api_client.get_count_of_live_services_and_organizations()['services'], + status_api_client.get_count_of_live_services_and_organizations()[ + "services" + ], ) return render_template( - 'views/choose-account.html', + "views/choose-account.html", can_add_service=current_user.is_gov_user, org_count=org_count, live_service_count=live_service_count, @@ -37,22 +39,30 @@ def choose_account(): @main.route("/accounts-or-dashboard") def show_accounts_or_dashboard(): - if not current_user.is_authenticated: - return redirect(url_for('.index')) + return redirect(url_for(".index")) - service_id = session.get('service_id') - if service_id and (current_user.belongs_to_service(service_id) or current_user.platform_admin): - return redirect(url_for('.service_dashboard', service_id=service_id)) + service_id = session.get("service_id") + if service_id and ( + current_user.belongs_to_service(service_id) or current_user.platform_admin + ): + return redirect(url_for(".service_dashboard", service_id=service_id)) - organization_id = session.get('organization_id') - if organization_id and (current_user.belongs_to_organization(organization_id) or current_user.platform_admin): - return redirect(url_for('.organization_dashboard', org_id=organization_id)) + organization_id = session.get("organization_id") + if organization_id and ( + current_user.belongs_to_organization(organization_id) + or current_user.platform_admin + ): + return redirect(url_for(".organization_dashboard", org_id=organization_id)) if len(current_user.service_ids) == 1 and not current_user.organization_ids: - return redirect(url_for('.service_dashboard', service_id=current_user.service_ids[0])) + return redirect( + url_for(".service_dashboard", service_id=current_user.service_ids[0]) + ) if len(current_user.organization_ids) == 1 and not current_user.trial_mode_services: - return redirect(url_for('.organization_dashboard', org_id=current_user.organization_ids[0])) + return redirect( + url_for(".organization_dashboard", org_id=current_user.organization_ids[0]) + ) - return redirect(url_for('.choose_account')) + return redirect(url_for(".choose_account")) diff --git a/app/main/views/code_not_received.py b/app/main/views/code_not_received.py index b5401a933..1beee3216 100644 --- a/app/main/views/code_not_received.py +++ b/app/main/views/code_not_received.py @@ -7,55 +7,59 @@ from app.models.user import User from app.utils.login import redirect_to_sign_in -@main.route('/resend-email-verification') +@main.route("/resend-email-verification") @redirect_to_sign_in def resend_email_verification(): - user = User.from_email_address(session['user_details']['email']) + user = User.from_email_address(session["user_details"]["email"]) user.send_verify_email() - return render_template('views/resend-email-verification.html', email=user.email_address) + return render_template( + "views/resend-email-verification.html", email=user.email_address + ) -@main.route('/text-not-received', methods=['GET', 'POST']) +@main.route("/text-not-received", methods=["GET", "POST"]) @redirect_to_sign_in def check_and_resend_text_code(): - user = User.from_email_address(session['user_details']['email']) - redirect_url = request.args.get('next') + user = User.from_email_address(session["user_details"]["email"]) + redirect_url = request.args.get("next") - if user.state == 'active': + if user.state == "active": # this is a verified user and therefore redirect to page to request resend without edit mobile - return render_template('views/verification-not-received.html', redirect_url=redirect_url) + return render_template( + "views/verification-not-received.html", redirect_url=redirect_url + ) form = TextNotReceivedForm(mobile_number=user.mobile_number) if form.validate_on_submit(): user.send_verify_code(to=form.mobile_number.data) user.update(mobile_number=form.mobile_number.data) - return redirect(url_for('.verify', next=redirect_url)) + return redirect(url_for(".verify", next=redirect_url)) - return render_template('views/text-not-received.html', form=form) + return render_template("views/text-not-received.html", form=form) -@main.route('/send-new-code', methods=['GET']) +@main.route("/send-new-code", methods=["GET"]) @redirect_to_sign_in def check_and_resend_verification_code(): - user = User.from_email_address(session['user_details']['email']) + user = User.from_email_address(session["user_details"]["email"]) user.send_verify_code() - redirect_url = request.args.get('next') - if user.state == 'pending': - return redirect(url_for('main.verify', next=redirect_url)) + redirect_url = request.args.get("next") + if user.state == "pending": + return redirect(url_for("main.verify", next=redirect_url)) else: - return redirect(url_for('main.two_factor_sms', next=redirect_url)) + return redirect(url_for("main.two_factor_sms", next=redirect_url)) -@main.route('/email-not-received', methods=['GET']) +@main.route("/email-not-received", methods=["GET"]) @redirect_to_sign_in def email_not_received(): - redirect_url = request.args.get('next') - return render_template('views/email-not-received.html', redirect_url=redirect_url) + redirect_url = request.args.get("next") + return render_template("views/email-not-received.html", redirect_url=redirect_url) -@main.route('/send-new-email-token', methods=['GET']) +@main.route("/send-new-email-token", methods=["GET"]) @redirect_to_sign_in def resend_email_link(): - user_api_client.send_verify_code(session['user_details']['id'], 'email', None) - session.pop('user_details') - return redirect(url_for('main.two_factor_email_sent', email_resent=True)) + user_api_client.send_verify_code(session["user_details"]["id"], "email", None) + session.pop("user_details") + return redirect(url_for("main.two_factor_email_sent", email_resent=True)) diff --git a/app/main/views/conversation.py b/app/main/views/conversation.py index 5125b6709..4b14ddf31 100644 --- a/app/main/views/conversation.py +++ b/app/main/views/conversation.py @@ -12,77 +12,86 @@ from app.utils.user import user_has_permissions @main.route("/services//conversation/") -@user_has_permissions('view_activity') +@user_has_permissions("view_activity") def conversation(service_id, notification_id): - user_number = get_user_number(service_id, notification_id) return render_template( - 'views/conversations/conversation.html', + "views/conversations/conversation.html", user_number=user_number, partials=get_conversation_partials(service_id, user_number), - updates_url=url_for('.conversation_updates', service_id=service_id, notification_id=notification_id), + updates_url=url_for( + ".conversation_updates", + service_id=service_id, + notification_id=notification_id, + ), notification_id=notification_id, ) @main.route("/services//conversation/.json") -@user_has_permissions('view_activity') +@user_has_permissions("view_activity") def conversation_updates(service_id, notification_id): - - return jsonify(get_conversation_partials( - service_id, - get_user_number(service_id, notification_id) - )) + return jsonify( + get_conversation_partials( + service_id, get_user_number(service_id, notification_id) + ) + ) -@main.route("/services//conversation//reply-with") -@main.route("/services//conversation//reply-with/from-folder/") -@user_has_permissions('send_messages') +@main.route( + "/services//conversation//reply-with" +) +@main.route( + "/services//conversation//reply-with/from-folder/" +) +@user_has_permissions("send_messages") def conversation_reply( service_id, notification_id, from_folder=None, ): return render_template( - 'views/templates/choose-reply.html', + "views/templates/choose-reply.html", templates_and_folders=TemplateList( current_service, template_folder_id=from_folder, user=current_user, - template_type='sms' + template_type="sms", ), template_folder_path=current_service.get_template_folder_path(from_folder), search_form=SearchByNameForm(), notification_id=notification_id, - template_type='sms' + template_type="sms", ) -@main.route("/services//conversation//reply-with/") -@user_has_permissions('send_messages') +@main.route( + "/services//conversation//reply-with/" +) +@user_has_permissions("send_messages") def conversation_reply_with_template( service_id, notification_id, template_id, ): + session["recipient"] = get_user_number(service_id, notification_id) + session["placeholders"] = {"phone number": session["recipient"]} - session['recipient'] = get_user_number(service_id, notification_id) - session['placeholders'] = {'phone number': session['recipient']} - - return redirect(url_for( - 'main.send_one_off_step', - service_id=service_id, - template_id=template_id, - step_index=1, - )) + return redirect( + url_for( + "main.send_one_off_step", + service_id=service_id, + template_id=template_id, + step_index=1, + ) + ) def get_conversation_partials(service_id, user_number): - return { - 'messages': render_template( - 'views/conversations/messages.html', + "messages": render_template( + "views/conversations/messages.html", conversation=get_sms_thread(service_id, user_number), ) } @@ -90,44 +99,54 @@ def get_conversation_partials(service_id, user_number): def get_user_number(service_id, notification_id): try: - user_number = service_api_client.get_inbound_sms_by_id(service_id, notification_id)['user_number'] + user_number = service_api_client.get_inbound_sms_by_id( + service_id, notification_id + )["user_number"] except HTTPError as e: if e.status_code != 404: raise - user_number = notification_api_client.get_notification(service_id, notification_id)['to'] + user_number = notification_api_client.get_notification( + service_id, notification_id + )["to"] return format_phone_number_human_readable(user_number) def get_sms_thread(service_id, user_number): - - for notification in sorted(( # noqa: B020 - notification_api_client.get_notifications_for_service(service_id, - to=user_number, - template_type='sms')['notifications'] + - service_api_client.get_inbound_sms(service_id, user_number=user_number)['data'] - ), key=lambda notification: notification['created_at']): - - is_inbound = ('notify_number' in notification) - redact_personalisation = not is_inbound and notification['template']['redact_personalisation'] + for notification in sorted( + ( # noqa: B020 + notification_api_client.get_notifications_for_service( + service_id, to=user_number, template_type="sms" + )["notifications"] + + service_api_client.get_inbound_sms(service_id, user_number=user_number)[ + "data" + ] + ), + key=lambda notification: notification["created_at"], + ): + is_inbound = "notify_number" in notification + redact_personalisation = ( + not is_inbound and notification["template"]["redact_personalisation"] + ) if redact_personalisation: - notification['personalisation'] = {} + notification["personalisation"] = {} yield { - 'inbound': is_inbound, - 'content': SMSPreviewTemplate( + "inbound": is_inbound, + "content": SMSPreviewTemplate( { - 'template_type': 'sms', - 'content': ( - notification['content'] if is_inbound else - notification['template']['content'] - ) + "template_type": "sms", + "content": ( + notification["content"] + if is_inbound + else notification["template"]["content"] + ), }, - notification.get('personalisation'), + notification.get("personalisation"), downgrade_non_sms_characters=(not is_inbound), redact_missing_personalisation=redact_personalisation, ), - 'created_at': notification['created_at'], - 'status': notification.get('status'), - 'id': notification['id'], + "created_at": notification["created_at"], + "status": notification.get("status"), + "id": notification["id"], } diff --git a/app/main/views/dashboard.py b/app/main/views/dashboard.py index 0b7394d68..161b00eda 100644 --- a/app/main/views/dashboard.py +++ b/app/main/views/dashboard.py @@ -30,136 +30,145 @@ from app.utils.user import user_has_permissions @main.route("/services//dashboard") -@user_has_permissions('view_activity', 'send_messages') +@user_has_permissions("view_activity", "send_messages") def old_service_dashboard(service_id): - return redirect(url_for('.service_dashboard', service_id=service_id)) + return redirect(url_for(".service_dashboard", service_id=service_id)) @main.route("/services/") @user_has_permissions() def service_dashboard(service_id): + if session.get("invited_user_id"): + session.pop("invited_user_id", None) + session["service_id"] = service_id - if session.get('invited_user_id'): - session.pop('invited_user_id', None) - session['service_id'] = service_id - - if not current_user.has_permissions('view_activity'): - return redirect(url_for('main.choose_template', service_id=service_id)) + if not current_user.has_permissions("view_activity"): + return redirect(url_for("main.choose_template", service_id=service_id)) return render_template( - 'views/dashboard/dashboard.html', + "views/dashboard/dashboard.html", updates_url=url_for(".service_dashboard_updates", service_id=service_id), - partials=get_dashboard_partials(service_id) + partials=get_dashboard_partials(service_id), ) @main.route("/services//dashboard.json") -@user_has_permissions('view_activity') +@user_has_permissions("view_activity") def service_dashboard_updates(service_id): return jsonify(**get_dashboard_partials(service_id)) @main.route("/services//template-activity") -@user_has_permissions('view_activity') +@user_has_permissions("view_activity") def template_history(service_id): - - return redirect(url_for('main.template_usage', service_id=service_id), code=301) + return redirect(url_for("main.template_usage", service_id=service_id), code=301) @main.route("/services//template-usage") -@user_has_permissions('view_activity') +@user_has_permissions("view_activity") def template_usage(service_id): - year, current_financial_year = requested_and_current_financial_year(request) - stats = template_statistics_client.get_monthly_template_usage_for_service(service_id, year) + stats = template_statistics_client.get_monthly_template_usage_for_service( + service_id, year + ) - stats = sorted(stats, key=lambda x: (x['count']), reverse=True) + stats = sorted(stats, key=lambda x: (x["count"]), reverse=True) def get_monthly_template_stats(month_name, stats): return { - 'name': month_name, - 'templates_used': [ + "name": month_name, + "templates_used": [ { - 'id': stat['template_id'], - 'name': stat['name'], - 'type': stat['type'], - 'requested_count': stat['count'] + "id": stat["template_id"], + "name": stat["name"], + "type": stat["type"], + "requested_count": stat["count"], } for stat in stats - if calendar.month_name[int(stat['month'])] == month_name + if calendar.month_name[int(stat["month"])] == month_name ], } months = [ get_monthly_template_stats(month, stats) - for month in get_months_for_financial_year(year, time_format='%B') + for month in get_months_for_financial_year(year, time_format="%B") ] return render_template( - 'views/dashboard/all-template-statistics.html', + "views/dashboard/all-template-statistics.html", months=months, stats=stats, most_used_template_count=max( - max(( - template['requested_count'] - for template in month['templates_used'] - ), default=0) + max( + (template["requested_count"] for template in month["templates_used"]), + default=0, + ) for month in months ), years=get_tuples_of_financial_years( - partial(url_for, '.template_usage', service_id=service_id), + partial(url_for, ".template_usage", service_id=service_id), start=current_financial_year - 2, end=current_financial_year, ), - selected_year=year + selected_year=year, ) @main.route("/services//usage") -@user_has_permissions('manage_service', allow_org_user=True) +@user_has_permissions("manage_service", allow_org_user=True) def usage(service_id): year, current_financial_year = requested_and_current_financial_year(request) - free_sms_allowance = billing_api_client.get_free_sms_fragment_limit_for_year(service_id, year) + free_sms_allowance = billing_api_client.get_free_sms_fragment_limit_for_year( + service_id, year + ) units = billing_api_client.get_monthly_usage_for_service(service_id, year) yearly_usage = billing_api_client.get_annual_usage_for_service(service_id, year) more_stats = format_monthly_stats_to_list( - service_api_client.get_monthly_notification_stats(service_id, year)['data'] + service_api_client.get_monthly_notification_stats(service_id, year)["data"] ) if year == current_financial_year: # This includes Oct, Nov, Dec # but we don't need next year's data yet - more_stats = [month for month in more_stats if month['name'] in ['October', 'November', 'December']] + more_stats = [ + month + for month in more_stats + if month["name"] in ["October", "November", "December"] + ] elif year == (current_financial_year + 1): # This is all the other months # and we need last year's data - more_stats = [month for month in more_stats if month['name'] not in ['October', 'November', 'December']] + more_stats = [ + month + for month in more_stats + if month["name"] not in ["October", "November", "December"] + ] return render_template( - 'views/usage.html', + "views/usage.html", months=list(get_monthly_usage_breakdown(year, units, more_stats)), selected_year=year, years=get_tuples_of_financial_years( - partial(url_for, '.usage', service_id=service_id), + partial(url_for, ".usage", service_id=service_id), start=current_financial_year - 2, end=current_financial_year, ), - **get_annual_usage_breakdown(yearly_usage, free_sms_allowance) + **get_annual_usage_breakdown(yearly_usage, free_sms_allowance), ) @main.route("/services//monthly") -@user_has_permissions('view_activity') +@user_has_permissions("view_activity") def monthly(service_id): year, current_financial_year = requested_and_current_financial_year(request) return render_template( - 'views/dashboard/monthly.html', + "views/dashboard/monthly.html", months=format_monthly_stats_to_list( - service_api_client.get_monthly_notification_stats(service_id, year)['data'] + service_api_client.get_monthly_notification_stats(service_id, year)["data"] ), years=get_tuples_of_financial_years( - partial_url=partial(url_for, '.monthly', service_id=service_id), + partial_url=partial(url_for, ".monthly", service_id=service_id), start=current_financial_year - 2, end=current_financial_year, ), @@ -168,53 +177,61 @@ def monthly(service_id): @main.route("/services//inbox") -@user_has_permissions('view_activity') -@service_has_permission('inbound_sms') +@user_has_permissions("view_activity") +@service_has_permission("inbound_sms") def inbox(service_id): - return render_template( - 'views/dashboard/inbox.html', + "views/dashboard/inbox.html", partials=get_inbox_partials(service_id), - updates_url=url_for('.inbox_updates', service_id=service_id, page=request.args.get('page')), + updates_url=url_for( + ".inbox_updates", service_id=service_id, page=request.args.get("page") + ), ) @main.route("/services//inbox.json") -@user_has_permissions('view_activity') -@service_has_permission('inbound_sms') +@user_has_permissions("view_activity") +@service_has_permission("inbound_sms") def inbox_updates(service_id): - return jsonify(get_inbox_partials(service_id)) @main.route("/services//inbox.csv") -@user_has_permissions('view_activity') +@user_has_permissions("view_activity") def inbox_download(service_id): return Response( Spreadsheet.from_rows( - [[ - 'Phone number', - 'Message', - 'Received', - ]] + [[ - format_phone_number_human_readable(message['user_number']), - message['content'].lstrip(('=+-@')), - format_datetime_numeric(message['created_at']), - ] for message in service_api_client.get_inbound_sms(service_id)['data']] + [ + [ + "Phone number", + "Message", + "Received", + ] + ] + + [ + [ + format_phone_number_human_readable(message["user_number"]), + message["content"].lstrip(("=+-@")), + format_datetime_numeric(message["created_at"]), + ] + for message in service_api_client.get_inbound_sms(service_id)["data"] + ] ).as_csv_data, - mimetype='text/csv', + mimetype="text/csv", headers={ - 'Content-Disposition': 'inline; filename="Received text messages {}.csv"'.format( + "Content-Disposition": 'inline; filename="Received text messages {}.csv"'.format( format_date_numeric(datetime.utcnow().isoformat()) ) - } + }, ) def get_inbox_partials(service_id): - page = int(request.args.get('page', 1)) - inbound_messages_data = service_api_client.get_most_recent_inbound_sms(service_id, page=page) - inbound_messages = inbound_messages_data['data'] + page = int(request.args.get("page", 1)) + inbound_messages_data = service_api_client.get_most_recent_inbound_sms( + service_id, page=page + ) + inbound_messages = inbound_messages_data["data"] if not inbound_messages: inbound_number = current_service.inbound_number else: @@ -222,36 +239,43 @@ def get_inbox_partials(service_id): prev_page = None if page > 1: - prev_page = generate_previous_dict('main.inbox', service_id, page) + prev_page = generate_previous_dict("main.inbox", service_id, page) next_page = None - if inbound_messages_data['has_next']: - next_page = generate_next_dict('main.inbox', service_id, page) + if inbound_messages_data["has_next"]: + next_page = generate_next_dict("main.inbox", service_id, page) - return {'messages': render_template( - 'views/dashboard/_inbox_messages.html', - messages=inbound_messages, - inbound_number=inbound_number, - prev_page=prev_page, - next_page=next_page - )} + return { + "messages": render_template( + "views/dashboard/_inbox_messages.html", + messages=inbound_messages, + inbound_number=inbound_number, + prev_page=prev_page, + next_page=next_page, + ) + } def filter_out_cancelled_stats(template_statistics): return [s for s in template_statistics if s["status"] != "cancelled"] -def aggregate_template_usage(template_statistics, sort_key='count'): +def aggregate_template_usage(template_statistics, sort_key="count"): template_statistics = filter_out_cancelled_stats(template_statistics) templates = [] - for k, v in groupby(sorted(template_statistics, key=lambda x: x['template_id']), key=lambda x: x['template_id']): + for k, v in groupby( + sorted(template_statistics, key=lambda x: x["template_id"]), + key=lambda x: x["template_id"], + ): template_stats = list(v) - templates.append({ - "template_id": k, - "template_name": template_stats[0]['template_name'], - "template_type": template_stats[0]['template_type'], - "count": sum(s['count'] for s in template_stats) - }) + templates.append( + { + "template_id": k, + "template_name": template_stats[0]["template_name"], + "template_type": template_stats[0]["template_type"], + "count": sum(s["count"] for s in template_stats), + } + ) return sorted(templates, key=lambda x: x[sort_key], reverse=True) @@ -259,9 +283,8 @@ def aggregate_template_usage(template_statistics, sort_key='count'): def aggregate_notifications_stats(template_statistics): template_statistics = filter_out_cancelled_stats(template_statistics) notifications = { - template_type: { - status: 0 for status in ('requested', 'delivered', 'failed') - } for template_type in ["sms", "email"] + template_type: {status: 0 for status in ("requested", "delivered", "failed")} + for template_type in ["sms", "email"] } for stat in template_statistics: notifications[stat["template_type"]]["requested"] += stat["count"] @@ -274,11 +297,13 @@ def aggregate_notifications_stats(template_statistics): def get_dashboard_partials(service_id): - all_statistics = template_statistics_client.get_template_statistics_for_service(service_id, limit_days=7) + all_statistics = template_statistics_client.get_template_statistics_for_service( + service_id, limit_days=7 + ) template_statistics = aggregate_template_usage(all_statistics) stats = aggregate_notifications_stats(all_statistics) - dashboard_totals = get_dashboard_totals(stats), + dashboard_totals = (get_dashboard_totals(stats),) free_sms_allowance = billing_api_client.get_free_sms_fragment_limit_for_year( current_service.id, get_current_financial_year(), @@ -288,26 +313,26 @@ def get_dashboard_partials(service_id): get_current_financial_year(), ) return { - 'upcoming': render_template( - 'views/dashboard/_upcoming.html', + "upcoming": render_template( + "views/dashboard/_upcoming.html", ), - 'inbox': render_template( - 'views/dashboard/_inbox.html', + "inbox": render_template( + "views/dashboard/_inbox.html", ), - 'totals': render_template( - 'views/dashboard/_totals.html', + "totals": render_template( + "views/dashboard/_totals.html", service_id=service_id, statistics=dashboard_totals[0], ), - 'template-statistics': render_template( - 'views/dashboard/template-statistics.html', + "template-statistics": render_template( + "views/dashboard/template-statistics.html", template_statistics=template_statistics, most_used_template_count=max( - [row['count'] for row in template_statistics] or [0] + [row["count"] for row in template_statistics] or [0] ), ), - 'usage': render_template( - 'views/dashboard/_usage.html', + "usage": render_template( + "views/dashboard/_usage.html", **get_annual_usage_breakdown(yearly_usage, free_sms_allowance), ), } @@ -315,39 +340,45 @@ def get_dashboard_partials(service_id): def get_dashboard_totals(statistics): for msg_type in statistics.values(): - msg_type['failed_percentage'] = get_formatted_percentage(msg_type['failed'], msg_type['requested']) - msg_type['show_warning'] = float(msg_type['failed_percentage']) > 3 + msg_type["failed_percentage"] = get_formatted_percentage( + msg_type["failed"], msg_type["requested"] + ) + msg_type["show_warning"] = float(msg_type["failed_percentage"]) > 3 return statistics def get_annual_usage_breakdown(usage, free_sms_fragment_limit): - sms = get_usage_breakdown_by_type(usage, 'sms') - sms_chargeable_units = sum(row['chargeable_units'] for row in sms) + sms = get_usage_breakdown_by_type(usage, "sms") + sms_chargeable_units = sum(row["chargeable_units"] for row in sms) sms_free_allowance = free_sms_fragment_limit - sms_cost = sum(row['cost'] for row in sms) + sms_cost = sum(row["cost"] for row in sms) - emails = get_usage_breakdown_by_type(usage, 'email') - emails_sent = sum(row['notifications_sent'] for row in emails) + emails = get_usage_breakdown_by_type(usage, "email") + emails_sent = sum(row["notifications_sent"] for row in emails) return { - 'emails_sent': emails_sent, - 'sms_free_allowance': sms_free_allowance, - 'sms_sent': sms_chargeable_units, - 'sms_allowance_remaining': max(0, (sms_free_allowance - sms_chargeable_units)), - 'sms_cost': sms_cost, - 'sms_breakdown': sms, + "emails_sent": emails_sent, + "sms_free_allowance": sms_free_allowance, + "sms_sent": sms_chargeable_units, + "sms_allowance_remaining": max(0, (sms_free_allowance - sms_chargeable_units)), + "sms_cost": sms_cost, + "sms_breakdown": sms, } def format_monthly_stats_to_list(historical_stats): - return sorted(( - dict( - date=key, - future=yyyy_mm_to_datetime(key) > datetime.utcnow(), - name=yyyy_mm_to_datetime(key).strftime('%B'), - **aggregate_status_types(value) - ) for key, value in historical_stats.items() - ), key=lambda x: x['date']) + return sorted( + ( + dict( + date=key, + future=yyyy_mm_to_datetime(key) > datetime.utcnow(), + name=yyyy_mm_to_datetime(key).strftime("%B"), + **aggregate_status_types(value), + ) + for key, value in historical_stats.items() + ), + key=lambda x: x["date"], + ) def yyyy_mm_to_datetime(string): @@ -355,24 +386,22 @@ def yyyy_mm_to_datetime(string): def aggregate_status_types(counts_dict): - return get_dashboard_totals({ - '{}_counts'.format(message_type): { - 'failed': sum( - stats.get(status, 0) for status in FAILURE_STATUSES - ), - 'requested': sum( - stats.get(status, 0) for status in REQUESTED_STATUSES - ) - } for message_type, stats in counts_dict.items() - }) + return get_dashboard_totals( + { + "{}_counts".format(message_type): { + "failed": sum(stats.get(status, 0) for status in FAILURE_STATUSES), + "requested": sum(stats.get(status, 0) for status in REQUESTED_STATUSES), + } + for message_type, stats in counts_dict.items() + } + ) -def get_months_for_financial_year(year, time_format='%B'): +def get_months_for_financial_year(year, time_format="%B"): return [ month.strftime(time_format) for month in ( - get_months_for_year(10, 13, year) + - get_months_for_year(1, 10, year + 1) + get_months_for_year(10, 13, year) + get_months_for_year(1, 10, year + 1) ) if month < datetime.now() ] @@ -383,32 +412,36 @@ def get_months_for_year(start, end, year): def get_usage_breakdown_by_type(usage, notification_type): - return [row for row in usage if row['notification_type'] == notification_type] + return [row for row in usage if row["notification_type"] == notification_type] def get_monthly_usage_breakdown(year, monthly_usage, more_stats): - sms = get_usage_breakdown_by_type(monthly_usage, 'sms') + sms = get_usage_breakdown_by_type(monthly_usage, "sms") for month in get_months_for_financial_year(year): - monthly_sms = [row for row in sms if row['month'] == month] - sms_free_allowance_used = sum(row['free_allowance_used'] for row in monthly_sms) - sms_cost = sum(row['cost'] for row in monthly_sms) - sms_breakdown = [row for row in monthly_sms if row['charged_units']] - sms_counts = [row['sms_counts'] for row in more_stats if row['sms_counts'] and row['name'] == month] + monthly_sms = [row for row in sms if row["month"] == month] + sms_free_allowance_used = sum(row["free_allowance_used"] for row in monthly_sms) + sms_cost = sum(row["cost"] for row in monthly_sms) + sms_breakdown = [row for row in monthly_sms if row["charged_units"]] + sms_counts = [ + row["sms_counts"] + for row in more_stats + if row["sms_counts"] and row["name"] == month + ] yield { - 'month': month, - 'sms_free_allowance_used': sms_free_allowance_used, - 'sms_breakdown': sms_breakdown, - 'sms_cost': sms_cost, - 'sms_counts': sms_counts, + "month": month, + "sms_free_allowance_used": sms_free_allowance_used, + "sms_breakdown": sms_breakdown, + "sms_cost": sms_cost, + "sms_counts": sms_counts, } def requested_and_current_financial_year(request): try: return ( - int(request.args.get('year', get_current_financial_year())), + int(request.args.get("year", get_current_financial_year())), get_current_financial_year(), ) except ValueError: @@ -422,10 +455,10 @@ def get_tuples_of_financial_years( ): return ( ( - 'fiscal year', + "fiscal year", year, partial_url(year=year), - '{} to {}'.format(year, year + 1), + "{} to {}".format(year, year + 1), ) for year in reversed(range(start, end + 1)) ) diff --git a/app/main/views/email_branding.py b/app/main/views/email_branding.py index 20c6e2715..4a050f849 100644 --- a/app/main/views/email_branding.py +++ b/app/main/views/email_branding.py @@ -14,47 +14,55 @@ from app.s3_client.s3_logo_client import ( from app.utils.user import user_is_platform_admin -@main.route("/email-branding", methods=['GET', 'POST']) +@main.route("/email-branding", methods=["GET", "POST"]) @user_is_platform_admin def email_branding(): - brandings = email_branding_client.get_all_email_branding(sort_key='name') + brandings = email_branding_client.get_all_email_branding(sort_key="name") return render_template( - 'views/email-branding/select-branding.html', + "views/email-branding/select-branding.html", email_brandings=brandings, - search_form=SearchByNameForm() + search_form=SearchByNameForm(), ) -@main.route("/email-branding//edit", methods=['GET', 'POST']) -@main.route("/email-branding//edit/", methods=['GET', 'POST']) +@main.route("/email-branding//edit", methods=["GET", "POST"]) +@main.route("/email-branding//edit/", methods=["GET", "POST"]) @user_is_platform_admin def update_email_branding(branding_id, logo=None): - email_branding = email_branding_client.get_email_branding(branding_id)['email_branding'] + email_branding = email_branding_client.get_email_branding(branding_id)[ + "email_branding" + ] form = AdminEditEmailBrandingForm( - name=email_branding['name'], - text=email_branding['text'], - colour=email_branding['colour'], - brand_type=email_branding['brand_type'] + name=email_branding["name"], + text=email_branding["text"], + colour=email_branding["colour"], + brand_type=email_branding["brand_type"], ) - logo = logo if logo else email_branding.get('logo') if email_branding else None + logo = logo if logo else email_branding.get("logo") if email_branding else None if form.validate_on_submit(): if form.file.data: upload_filename = upload_email_logo( - form.file.data.filename, - form.file.data, - user_id=session["user_id"] + form.file.data.filename, form.file.data, user_id=session["user_id"] ) - if logo and logo.startswith(TEMP_TAG.format(user_id=session['user_id'])): + if logo and logo.startswith(TEMP_TAG.format(user_id=session["user_id"])): delete_email_temp_file(logo) - return redirect(url_for('.update_email_branding', branding_id=branding_id, logo=upload_filename)) + return redirect( + url_for( + ".update_email_branding", + branding_id=branding_id, + logo=upload_filename, + ) + ) - updated_logo_name = permanent_email_logo_name(logo, session["user_id"]) if logo else None + updated_logo_name = ( + permanent_email_logo_name(logo, session["user_id"]) if logo else None + ) email_branding_client.update_email_branding( branding_id=branding_id, @@ -70,37 +78,37 @@ def update_email_branding(branding_id, logo=None): delete_email_temp_files_created_by(session["user_id"]) - return redirect(url_for('.email_branding', branding_id=branding_id)) + return redirect(url_for(".email_branding", branding_id=branding_id)) return render_template( - 'views/email-branding/manage-branding.html', + "views/email-branding/manage-branding.html", form=form, email_branding=email_branding, - cdn_url=current_app.config['LOGO_CDN_DOMAIN'], - logo=logo + cdn_url=current_app.config["LOGO_CDN_DOMAIN"], + logo=logo, ) -@main.route("/email-branding/create", methods=['GET', 'POST']) -@main.route("/email-branding/create/", methods=['GET', 'POST']) +@main.route("/email-branding/create", methods=["GET", "POST"]) +@main.route("/email-branding/create/", methods=["GET", "POST"]) @user_is_platform_admin def create_email_branding(logo=None): - form = AdminEditEmailBrandingForm(brand_type='org') + form = AdminEditEmailBrandingForm(brand_type="org") if form.validate_on_submit(): if form.file.data: upload_filename = upload_email_logo( - form.file.data.filename, - form.file.data, - user_id=session["user_id"] + form.file.data.filename, form.file.data, user_id=session["user_id"] ) - if logo and logo.startswith(TEMP_TAG.format(user_id=session['user_id'])): + if logo and logo.startswith(TEMP_TAG.format(user_id=session["user_id"])): delete_email_temp_file(logo) - return redirect(url_for('.create_email_branding', logo=upload_filename)) + return redirect(url_for(".create_email_branding", logo=upload_filename)) - updated_logo_name = permanent_email_logo_name(logo, session["user_id"]) if logo else None + updated_logo_name = ( + permanent_email_logo_name(logo, session["user_id"]) if logo else None + ) email_branding_client.create_email_branding( logo=updated_logo_name, @@ -115,11 +123,11 @@ def create_email_branding(logo=None): delete_email_temp_files_created_by(session["user_id"]) - return redirect(url_for('.email_branding')) + return redirect(url_for(".email_branding")) return render_template( - 'views/email-branding/manage-branding.html', + "views/email-branding/manage-branding.html", form=form, - cdn_url=current_app.config['LOGO_CDN_DOMAIN'], - logo=logo + cdn_url=current_app.config["LOGO_CDN_DOMAIN"], + logo=logo, ) diff --git a/app/main/views/feedback.py b/app/main/views/feedback.py index e654d2b3d..a68cc798a 100644 --- a/app/main/views/feedback.py +++ b/app/main/views/feedback.py @@ -20,85 +20,86 @@ from app.utils import hide_from_search_engines bank_holidays = BankHolidays(use_cached_holidays=True) -@main.route('/support', methods=['GET', 'POST']) +@main.route("/support", methods=["GET", "POST"]) @hide_from_search_engines def support(): - if current_user.is_authenticated: form = SupportType() if form.validate_on_submit(): - return redirect(url_for( - '.feedback', - ticket_type=form.support_type.data, - )) + return redirect( + url_for( + ".feedback", + ticket_type=form.support_type.data, + ) + ) else: form = SupportRedirect() if form.validate_on_submit(): - if form.who.data == 'public': - return redirect(url_for( - '.support_public' - )) + if form.who.data == "public": + return redirect(url_for(".support_public")) else: - return redirect(url_for( - '.feedback', - ticket_type=GENERAL_TICKET_TYPE, - )) + return redirect( + url_for( + ".feedback", + ticket_type=GENERAL_TICKET_TYPE, + ) + ) - return render_template('views/support/index.html', form=form) + return render_template("views/support/index.html", form=form) -@main.route('/support/public') +@main.route("/support/public") @hide_from_search_engines def support_public(): - return render_template('views/support/public.html') + return render_template("views/support/public.html") -@main.route('/support/triage', methods=['GET', 'POST']) -@main.route('/support/triage/', methods=['GET', 'POST']) +@main.route("/support/triage", methods=["GET", "POST"]) +@main.route("/support/triage/", methods=["GET", "POST"]) @hide_from_search_engines def triage(ticket_type=PROBLEM_TICKET_TYPE): form = Triage() if form.validate_on_submit(): - return redirect(url_for( - '.feedback', - ticket_type=ticket_type, - severe=form.severe.data - )) + return redirect( + url_for(".feedback", ticket_type=ticket_type, severe=form.severe.data) + ) return render_template( - 'views/support/triage.html', + "views/support/triage.html", form=form, page_title={ - PROBLEM_TICKET_TYPE: 'Report a problem', - GENERAL_TICKET_TYPE: 'Contact Notify.gov support', - }.get(ticket_type) + PROBLEM_TICKET_TYPE: "Report a problem", + GENERAL_TICKET_TYPE: "Contact Notify.gov support", + }.get(ticket_type), ) -@main.route('/support/', methods=['GET', 'POST']) +@main.route("/support/", methods=["GET", "POST"]) @hide_from_search_engines def feedback(ticket_type): form = FeedbackOrProblem() if not form.feedback.data: - form.feedback.data = session.pop('feedback_message', '') + form.feedback.data = session.pop("feedback_message", "") - if request.args.get('severe') in ['yes', 'no']: - severe = convert_to_boolean(request.args.get('severe')) + if request.args.get("severe") in ["yes", "no"]: + severe = convert_to_boolean(request.args.get("severe")) else: severe = None - out_of_hours_emergency = all(( - ticket_type != QUESTION_TICKET_TYPE, - not in_business_hours(), - severe, - )) + out_of_hours_emergency = all( + ( + ticket_type != QUESTION_TICKET_TYPE, + not in_business_hours(), + severe, + ) + ) if needs_triage(ticket_type, severe): - session['feedback_message'] = form.feedback.data - return redirect(url_for('.triage', ticket_type=ticket_type)) + session["feedback_message"] = form.feedback.data + return redirect(url_for(".triage", ticket_type=ticket_type)) if needs_escalation(ticket_type, severe): - return redirect(url_for('.bat_phone')) + return redirect(url_for(".bat_phone")) if current_user.is_authenticated: form.email_address.data = current_user.email_address @@ -109,12 +110,12 @@ def feedback(ticket_type): user_name = form.name.data or None feedback_msg = render_template( - 'support-tickets/support-ticket.txt', + "support-tickets/support-ticket.txt", content=form.feedback.data, ) ticket = NotifySupportTicket( - subject='Notify feedback', + subject="Notify feedback", message=feedback_msg, ticket_type=get_zendesk_ticket_type(ticket_type), p1=out_of_hours_emergency, @@ -126,54 +127,58 @@ def feedback(ticket_type): ) zendesk_client.send_ticket_to_zendesk(ticket) - return redirect(url_for( - '.thanks', - out_of_hours_emergency=out_of_hours_emergency, - email_address_provided=( - current_user.is_authenticated or bool(form.email_address.data) - ), - )) + return redirect( + url_for( + ".thanks", + out_of_hours_emergency=out_of_hours_emergency, + email_address_provided=( + current_user.is_authenticated or bool(form.email_address.data) + ), + ) + ) return render_template( - 'views/support/form.html', + "views/support/form.html", form=form, back_link=( - url_for('.support') - if severe is None else - url_for('.triage', ticket_type=ticket_type) + url_for(".support") + if severe is None + else url_for(".triage", ticket_type=ticket_type) ), show_status_page_banner=(ticket_type == PROBLEM_TICKET_TYPE), page_title={ - GENERAL_TICKET_TYPE: 'Contact Notify.gov support', - PROBLEM_TICKET_TYPE: 'Report a problem', - QUESTION_TICKET_TYPE: 'Ask a question or give feedback', + GENERAL_TICKET_TYPE: "Contact Notify.gov support", + PROBLEM_TICKET_TYPE: "Report a problem", + QUESTION_TICKET_TYPE: "Ask a question or give feedback", }.get(ticket_type), ) -@main.route('/support/escalate', methods=['GET', 'POST']) +@main.route("/support/escalate", methods=["GET", "POST"]) @hide_from_search_engines def bat_phone(): - if current_user.is_authenticated: - return redirect(url_for('main.feedback', ticket_type=PROBLEM_TICKET_TYPE)) + return redirect(url_for("main.feedback", ticket_type=PROBLEM_TICKET_TYPE)) - return render_template('views/support/bat-phone.html') + return render_template("views/support/bat-phone.html") -@main.route('/support/thanks', methods=['GET', 'POST']) +@main.route("/support/thanks", methods=["GET", "POST"]) @hide_from_search_engines def thanks(): return render_template( - 'views/support/thanks.html', - out_of_hours_emergency=convert_to_boolean(request.args.get('out_of_hours_emergency')), - email_address_provided=convert_to_boolean(request.args.get('email_address_provided')), + "views/support/thanks.html", + out_of_hours_emergency=convert_to_boolean( + request.args.get("out_of_hours_emergency") + ), + email_address_provided=convert_to_boolean( + request.args.get("email_address_provided") + ), out_of_hours=not in_business_hours(), ) def in_business_hours(): - now = datetime.utcnow().replace(tzinfo=pytz.utc) if is_weekend(now) or is_bank_holiday(now): @@ -183,15 +188,17 @@ def in_business_hours(): def london_time_today_as_utc(hour, minute): - return pytz.timezone('Europe/London').localize( - datetime.now().replace(hour=hour, minute=minute) - ).astimezone(pytz.utc) + return ( + pytz.timezone("Europe/London") + .localize(datetime.now().replace(hour=hour, minute=minute)) + .astimezone(pytz.utc) + ) def is_weekend(time): - return time.strftime('%A') in { - 'Saturday', - 'Sunday', + return time.strftime("%A") in { + "Saturday", + "Sunday", } @@ -200,23 +207,25 @@ def is_bank_holiday(time): def needs_triage(ticket_type, severe): - return all(( - ticket_type != QUESTION_TICKET_TYPE, - severe is None, + return all( ( - not current_user.is_authenticated or current_user.live_services - ), - not in_business_hours(), - )) + ticket_type != QUESTION_TICKET_TYPE, + severe is None, + (not current_user.is_authenticated or current_user.live_services), + not in_business_hours(), + ) + ) def needs_escalation(ticket_type, severe): - return all(( - ticket_type != QUESTION_TICKET_TYPE, - severe, - not current_user.is_authenticated, - not in_business_hours(), - )) + return all( + ( + ticket_type != QUESTION_TICKET_TYPE, + severe, + not current_user.is_authenticated, + not in_business_hours(), + ) + ) def get_zendesk_ticket_type(ticket_type): diff --git a/app/main/views/find_services.py b/app/main/views/find_services.py index 8156c7f3b..8b9801603 100644 --- a/app/main/views/find_services.py +++ b/app/main/views/find_services.py @@ -9,20 +9,23 @@ from app.main.forms import SearchByNameForm from app.utils.user import user_is_platform_admin -@main.route("/find-services-by-name", methods=['GET', 'POST']) +@main.route("/find-services-by-name", methods=["GET", "POST"]) @user_is_platform_admin def find_services_by_name(): form = SearchByNameForm() services_found = None if form.validate_on_submit(): with suppress(ValueError): - return redirect(url_for( - 'main.service_dashboard', - service_id=uuid.UUID(form.search.data) - )) - services_found = service_api_client.find_services_by_name(service_name=form.search.data)['data'] + return redirect( + url_for( + "main.service_dashboard", service_id=uuid.UUID(form.search.data) + ) + ) + services_found = service_api_client.find_services_by_name( + service_name=form.search.data + )["data"] return render_template( - 'views/find-services/find-services-by-name.html', + "views/find-services/find-services-by-name.html", form=form, - services_found=services_found + services_found=services_found, ) diff --git a/app/main/views/find_users.py b/app/main/views/find_users.py index 2ca737234..19ec5dce7 100644 --- a/app/main/views/find_users.py +++ b/app/main/views/find_users.py @@ -10,49 +10,54 @@ from app.models.user import User from app.utils.user import user_is_platform_admin -@main.route("/find-users-by-email", methods=['GET', 'POST']) +@main.route("/find-users-by-email", methods=["GET", "POST"]) @user_is_platform_admin def find_users_by_email(): form = AdminSearchUsersByEmailForm() users_found = None if form.validate_on_submit(): - users_found = user_api_client.find_users_by_full_or_partial_email(form.search.data)['data'] + users_found = user_api_client.find_users_by_full_or_partial_email( + form.search.data + )["data"] return render_template( - 'views/find-users/find-users-by-email.html', - form=form, - users_found=users_found + "views/find-users/find-users-by-email.html", form=form, users_found=users_found ) -@main.route("/users/", methods=['GET']) +@main.route("/users/", methods=["GET"]) @user_is_platform_admin def user_information(user_id): return render_template( - 'views/find-users/user-information.html', + "views/find-users/user-information.html", user=User.from_id(user_id), ) -@main.route("/users//archive", methods=['GET', 'POST']) +@main.route("/users//archive", methods=["GET", "POST"]) @user_is_platform_admin def archive_user(user_id): - if request.method == 'POST': + if request.method == "POST": try: user_api_client.archive_user(user_id) except HTTPError as e: - if e.status_code == 400 and 'manage_settings' in e.message: - flash('User can’t be removed from a service - ' - 'check all services have another team member with manage_settings') - return redirect(url_for('main.user_information', user_id=user_id)) + if e.status_code == 400 and "manage_settings" in e.message: + flash( + "User can’t be removed from a service - " + "check all services have another team member with manage_settings" + ) + return redirect(url_for("main.user_information", user_id=user_id)) create_archive_user_event(user_id=str(user_id), archived_by_id=current_user.id) - return redirect(url_for('.user_information', user_id=user_id)) + return redirect(url_for(".user_information", user_id=user_id)) else: - flash('There\'s no way to reverse this! Are you sure you want to archive this user?', 'delete') + flash( + "There's no way to reverse this! Are you sure you want to archive this user?", + "delete", + ) return user_information(user_id) -@main.route("/users//change_auth", methods=['GET', 'POST']) +@main.route("/users//change_auth", methods=["GET", "POST"]) @user_is_platform_admin def change_user_auth(user_id): user = User.from_id(user_id) @@ -61,10 +66,10 @@ def change_user_auth(user_id): if form.validate_on_submit(): user.update(auth_type=form.auth_type.data) - return redirect(url_for('.user_information', user_id=user_id)) + return redirect(url_for(".user_information", user_id=user_id)) return render_template( - 'views/find-users/auth_type.html', + "views/find-users/auth_type.html", form=form, user=user, ) diff --git a/app/main/views/forgot_password.py b/app/main/views/forgot_password.py index 359ad40c9..0aa72e0de 100644 --- a/app/main/views/forgot_password.py +++ b/app/main/views/forgot_password.py @@ -6,17 +6,19 @@ from app.main import main from app.main.forms import ForgotPasswordForm -@main.route('/forgot-password', methods=['GET', 'POST']) +@main.route("/forgot-password", methods=["GET", "POST"]) def forgot_password(): form = ForgotPasswordForm() if form.validate_on_submit(): try: - user_api_client.send_reset_password_url(form.email_address.data, next_string=request.args.get('next')) + user_api_client.send_reset_password_url( + form.email_address.data, next_string=request.args.get("next") + ) except HTTPError as e: if e.status_code == 404: - return render_template('views/password-reset-sent.html') + return render_template("views/password-reset-sent.html") else: raise e - return render_template('views/password-reset-sent.html') + return render_template("views/password-reset-sent.html") - return render_template('views/forgot-password.html', form=form) + return render_template("views/forgot-password.html", form=form) diff --git a/app/main/views/history.py b/app/main/views/history.py index 6c0603a7c..f2911e478 100644 --- a/app/main/views/history.py +++ b/app/main/views/history.py @@ -10,34 +10,31 @@ from app.utils.user import user_has_permissions @main.route("/services//history") -@user_has_permissions('manage_service') +@user_has_permissions("manage_service") def history(service_id): - - events = _get_events(current_service.id, request.args.get('selected')) + events = _get_events(current_service.id, request.args.get("selected")) return render_template( - 'views/temp-history.html', + "views/temp-history.html", days=_chunk_events_by_day(events), - show_navigation=request.args.get('selected') or any( - isinstance(event, APIKeyEvent) for event in events - ), + show_navigation=request.args.get("selected") + or any(isinstance(event, APIKeyEvent) for event in events), user_getter=current_service.active_users.get_name_from_id, ) def _get_events(service_id, selected): - if selected == 'api': + if selected == "api": return APIKeyEvents(service_id) - if selected == 'service': + if selected == "service": return ServiceEvents(service_id) return APIKeyEvents(service_id) + ServiceEvents(service_id) def _chunk_events_by_day(events): - days = defaultdict(list) - for event in sorted(events, key=attrgetter('time'), reverse=True): + for event in sorted(events, key=attrgetter("time"), reverse=True): days[format_date_numeric(event.time)].append(event) return sorted(days.items(), reverse=True) diff --git a/app/main/views/inbound_number.py b/app/main/views/inbound_number.py index c56620b33..777772882 100644 --- a/app/main/views/inbound_number.py +++ b/app/main/views/inbound_number.py @@ -5,9 +5,9 @@ from app.main import main from app.utils.user import user_is_platform_admin -@main.route('/inbound-sms-admin', methods=['GET', 'POST']) +@main.route("/inbound-sms-admin", methods=["GET", "POST"]) @user_is_platform_admin def inbound_sms_admin(): data = inbound_number_client.get_all_inbound_sms_number_service() - return render_template('views/inbound-sms-admin.html', inbound_num_list=data) + return render_template("views/inbound-sms-admin.html", inbound_num_list=data) diff --git a/app/main/views/index.py b/app/main/views/index.py index c0f293fa2..5ee4cac23 100644 --- a/app/main/views/index.py +++ b/app/main/views/index.py @@ -18,64 +18,67 @@ from app.main.views.sub_navigation_dictionaries import features_nav, using_notif from app.utils.user import user_is_logged_in -@main.route('/') +@main.route("/") def index(): - if current_user and current_user.is_authenticated: - return redirect(url_for('main.choose_account')) + return redirect(url_for("main.choose_account")) return render_template( - 'views/signedout.html', + "views/signedout.html", sms_rate=CURRENT_SMS_RATE, counts=status_api_client.get_count_of_live_services_and_organizations(), ) -@main.route('/error/') +@main.route("/error/") def error(status_code): if status_code >= 500: abort(404) abort(status_code) -@main.route('/privacy') +@main.route("/privacy") @user_is_logged_in def privacy(): - return render_template('views/privacy.html') + return render_template("views/privacy.html") -@main.route('/accessibility-statement') +@main.route("/accessibility-statement") @user_is_logged_in def accessibility_statement(): - return render_template('views/accessibility_statement.html') + return render_template("views/accessibility_statement.html") -@main.route('/delivery-and-failure') -@main.route('/features/messages-status') +@main.route("/delivery-and-failure") +@main.route("/features/messages-status") def delivery_and_failure(): - return redirect(url_for('.message_status'), 301) + return redirect(url_for(".message_status"), 301) -@main.route('/design-patterns-content-guidance') +@main.route("/design-patterns-content-guidance") @user_is_logged_in def design_content(): - return redirect('https://www.gov.uk/service-manual/design/sending-emails-and-text-messages', 301) + return redirect( + "https://www.gov.uk/service-manual/design/sending-emails-and-text-messages", 301 + ) -@main.route('/_email') +@main.route("/_email") @user_is_logged_in def email_template(): - branding_type = 'govuk' - branding_style = request.args.get('branding_style', None) + branding_type = "govuk" + branding_style = request.args.get("branding_style", None) if branding_style == FieldWithNoneOption.NONE_OPTION_VALUE: branding_style = None if branding_style is not None: - email_branding = email_branding_client.get_email_branding(branding_style)['email_branding'] - branding_type = email_branding['brand_type'] + email_branding = email_branding_client.get_email_branding(branding_style)[ + "email_branding" + ] + branding_type = email_branding["brand_type"] - if branding_type == 'govuk': + if branding_type == "govuk": brand_text = None brand_colour = None brand_logo = None @@ -83,262 +86,260 @@ def email_template(): brand_banner = False brand_name = None else: - colour = email_branding['colour'] - brand_text = email_branding['text'] + colour = email_branding["colour"] + brand_text = email_branding["text"] brand_colour = colour - brand_logo = (f"https://{current_app.config['LOGO_CDN_DOMAIN']}/{email_branding['logo']}" - if email_branding['logo'] else None) - govuk_banner = branding_type in ['govuk', 'both'] - brand_banner = branding_type == 'org_banner' - brand_name = email_branding['name'] + brand_logo = ( + f"https://{current_app.config['LOGO_CDN_DOMAIN']}/{email_branding['logo']}" + if email_branding["logo"] + else None + ) + govuk_banner = branding_type in ["govuk", "both"] + brand_banner = branding_type == "org_banner" + brand_name = email_branding["name"] template = { - 'template_type': 'email', - 'subject': 'Email branding preview', - 'content': ( - 'Lorem Ipsum is simply dummy text of the printing and typesetting ' - 'industry.\n\nLorem Ipsum has been the industry’s standard dummy ' - 'text ever since the 1500s, when an unknown printer took a galley ' - 'of type and scrambled it to make a type specimen book. ' - '\n\n' - '# History' - '\n\n' - 'It has ' - 'survived not only' - '\n\n' - '* five centuries' - '\n' - '* but also the leap into electronic typesetting' - '\n\n' - 'It was ' - 'popularised in the 1960s with the release of Letraset sheets ' - 'containing Lorem Ipsum passages, and more recently with desktop ' - 'publishing software like Aldus PageMaker including versions of ' - 'Lorem Ipsum.' - '\n\n' - '^ It is a long established fact that a reader will be distracted ' - 'by the readable content of a page when looking at its layout.' - '\n\n' - 'The point of using Lorem Ipsum is that it has a more-or-less ' - 'normal distribution of letters, as opposed to using ‘Content ' - 'here, content here’, making it look like readable English.' - '\n\n\n' - '1. One' - '\n' - '2. Two' - '\n' - '10. Three' - '\n\n' - 'This is an example of an email sent using U.S. Notify.' - '\n\n' - 'https://www.notifications.service.gov.uk' - ) + "template_type": "email", + "subject": "Email branding preview", + "content": ( + "Lorem Ipsum is simply dummy text of the printing and typesetting " + "industry.\n\nLorem Ipsum has been the industry’s standard dummy " + "text ever since the 1500s, when an unknown printer took a galley " + "of type and scrambled it to make a type specimen book. " + "\n\n" + "# History" + "\n\n" + "It has " + "survived not only" + "\n\n" + "* five centuries" + "\n" + "* but also the leap into electronic typesetting" + "\n\n" + "It was " + "popularised in the 1960s with the release of Letraset sheets " + "containing Lorem Ipsum passages, and more recently with desktop " + "publishing software like Aldus PageMaker including versions of " + "Lorem Ipsum." + "\n\n" + "^ It is a long established fact that a reader will be distracted " + "by the readable content of a page when looking at its layout." + "\n\n" + "The point of using Lorem Ipsum is that it has a more-or-less " + "normal distribution of letters, as opposed to using ‘Content " + "here, content here’, making it look like readable English." + "\n\n\n" + "1. One" + "\n" + "2. Two" + "\n" + "10. Three" + "\n\n" + "This is an example of an email sent using U.S. Notify." + "\n\n" + "https://www.notifications.service.gov.uk" + ), } if not bool(request.args): resp = make_response(str(HTMLEmailTemplate(template))) else: - resp = make_response(str(HTMLEmailTemplate( - template, - govuk_banner=govuk_banner, - brand_text=brand_text, - brand_colour=brand_colour, - brand_logo=brand_logo, - brand_banner=brand_banner, - brand_name=brand_name, - ))) + resp = make_response( + str( + HTMLEmailTemplate( + template, + govuk_banner=govuk_banner, + brand_text=brand_text, + brand_colour=brand_colour, + brand_logo=brand_logo, + brand_banner=brand_banner, + brand_name=brand_name, + ) + ) + ) - resp.headers['X-Frame-Options'] = 'SAMEORIGIN' + resp.headers["X-Frame-Options"] = "SAMEORIGIN" return resp -@main.route('/documentation') +@main.route("/documentation") @user_is_logged_in def documentation(): return render_template( - 'views/documentation.html', + "views/documentation.html", navigation_links=using_notify_nav(), ) -@main.route('/integration-testing') +@main.route("/integration-testing") def integration_testing(): - return render_template('views/integration-testing.html'), 410 + return render_template("views/integration-testing.html"), 410 -@main.route('/callbacks') +@main.route("/callbacks") def callbacks(): - return redirect(url_for('main.documentation'), 301) + return redirect(url_for("main.documentation"), 301) # --- Features page set --- # -@main.route('/features') + +@main.route("/features") @user_is_logged_in def features(): - return render_template( - 'views/features.html', - navigation_links=features_nav() - ) + return render_template("views/features.html", navigation_links=features_nav()) -@main.route('/features/roadmap', endpoint='roadmap') +@main.route("/features/roadmap", endpoint="roadmap") @user_is_logged_in def roadmap(): - return render_template( - 'views/roadmap.html', - navigation_links=features_nav() - ) + return render_template("views/roadmap.html", navigation_links=features_nav()) -@main.route('/features/email') +@main.route("/features/email") @user_is_logged_in def features_email(): return render_template( - 'views/features/emails.html', - navigation_links=features_nav() + "views/features/emails.html", navigation_links=features_nav() ) -@main.route('/features/sms') +@main.route("/features/sms") @user_is_logged_in def features_sms(): return render_template( - 'views/features/text-messages.html', - navigation_links=features_nav() + "views/features/text-messages.html", navigation_links=features_nav() ) -@main.route('/features/security', endpoint='security') +@main.route("/features/security", endpoint="security") @user_is_logged_in def security(): - return render_template( - 'views/security.html', - navigation_links=features_nav() - ) + return render_template("views/security.html", navigation_links=features_nav()) -@main.route('/features/terms', endpoint='terms') +@main.route("/features/terms", endpoint="terms") @user_is_logged_in def terms(): return render_template( - 'views/terms-of-use.html', + "views/terms-of-use.html", navigation_links=features_nav(), ) -@main.route('/features/using-notify') +@main.route("/features/using-notify") @user_is_logged_in def using_notify(): - return render_template( - 'views/using-notify.html', - navigation_links=features_nav() - ), 410 + return ( + render_template("views/using-notify.html", navigation_links=features_nav()), + 410, + ) -@main.route('/using-notify/delivery-status') +@main.route("/using-notify/delivery-status") @user_is_logged_in def message_status(): return render_template( - 'views/message-status.html', + "views/message-status.html", navigation_links=using_notify_nav(), ) -@main.route('/features/get-started') +@main.route("/features/get-started") @user_is_logged_in def get_started_old(): - return redirect(url_for('.get_started'), 301) + return redirect(url_for(".get_started"), 301) -@main.route('/using-notify/get-started') +@main.route("/using-notify/get-started") @user_is_logged_in def get_started(): return render_template( - 'views/get-started.html', + "views/get-started.html", navigation_links=using_notify_nav(), ) -@main.route('/using-notify/who-its-for') +@main.route("/using-notify/who-its-for") def who_its_for(): - return redirect(url_for('.features'), 301) + return redirect(url_for(".features"), 301) -@main.route('/trial-mode') -@main.route('/features/trial-mode') +@main.route("/trial-mode") +@main.route("/features/trial-mode") def trial_mode(): - return redirect(url_for('.trial_mode_new'), 301) + return redirect(url_for(".trial_mode_new"), 301) -@main.route('/using-notify/trial-mode') +@main.route("/using-notify/trial-mode") def trial_mode_new(): return render_template( - 'views/trial-mode.html', + "views/trial-mode.html", navigation_links=using_notify_nav(), ) -@main.route('/using-notify/guidance') +@main.route("/using-notify/guidance") @user_is_logged_in def guidance_index(): return render_template( - 'views/guidance/index.html', + "views/guidance/index.html", navigation_links=using_notify_nav(), ) -@main.route('/using-notify/guidance/branding-and-customisation') +@main.route("/using-notify/guidance/branding-and-customisation") @user_is_logged_in def branding_and_customisation(): return render_template( - 'views/guidance/branding-and-customisation.html', + "views/guidance/branding-and-customisation.html", navigation_links=using_notify_nav(), ) -@main.route('/using-notify/guidance/create-and-send-messages') +@main.route("/using-notify/guidance/create-and-send-messages") @user_is_logged_in def create_and_send_messages(): return render_template( - 'views/guidance/create-and-send-messages.html', + "views/guidance/create-and-send-messages.html", navigation_links=using_notify_nav(), ) -@main.route('/using-notify/guidance/edit-and-format-messages') +@main.route("/using-notify/guidance/edit-and-format-messages") @user_is_logged_in def edit_and_format_messages(): return render_template( - 'views/guidance/edit-and-format-messages.html', + "views/guidance/edit-and-format-messages.html", navigation_links=using_notify_nav(), ) -@main.route('/using-notify/guidance/send-files-by-email') +@main.route("/using-notify/guidance/send-files-by-email") @user_is_logged_in def send_files_by_email(): return render_template( - 'views/guidance/send-files-by-email.html', + "views/guidance/send-files-by-email.html", navigation_links=using_notify_nav(), ) # --- Redirects --- # -@main.route('/roadmap', endpoint='old_roadmap') -@main.route('/terms', endpoint='old_terms') -@main.route('/information-security', endpoint='information_security') -@main.route('/using_notify', endpoint='old_using_notify') -@main.route('/information-risk-management', endpoint='information_risk_management') -@main.route('/integration_testing', endpoint='old_integration_testing') + +@main.route("/roadmap", endpoint="old_roadmap") +@main.route("/terms", endpoint="old_terms") +@main.route("/information-security", endpoint="information_security") +@main.route("/using_notify", endpoint="old_using_notify") +@main.route("/information-risk-management", endpoint="information_risk_management") +@main.route("/integration_testing", endpoint="old_integration_testing") def old_page_redirects(): redirects = { - 'main.old_roadmap': 'main.roadmap', - 'main.old_terms': 'main.terms', - 'main.information_security': 'main.using_notify', - 'main.old_using_notify': 'main.using_notify', - 'main.information_risk_management': 'main.security', - 'main.old_integration_testing': 'main.integration_testing', + "main.old_roadmap": "main.roadmap", + "main.old_terms": "main.terms", + "main.information_security": "main.using_notify", + "main.old_using_notify": "main.using_notify", + "main.information_risk_management": "main.security", + "main.old_integration_testing": "main.integration_testing", } return redirect(url_for(redirects[request.endpoint]), code=301) diff --git a/app/main/views/invites.py b/app/main/views/invites.py index 44bc03ce0..49fb66f88 100644 --- a/app/main/views/invites.py +++ b/app/main/views/invites.py @@ -12,31 +12,40 @@ from app.models.user import InvitedOrgUser, InvitedUser, OrganizationUsers, User def accept_invite(token): invited_user = InvitedUser.from_token(token) - if not current_user.is_anonymous and current_user.email_address.lower() != invited_user.email_address.lower(): - message = Markup(""" + if ( + not current_user.is_anonymous + and current_user.email_address.lower() != invited_user.email_address.lower() + ): + message = Markup( + """ You’re signed in as {}. This invite is for another email address. Sign out and click the link again to accept this invite. """.format( - current_user.email_address, - url_for("main.sign_out"))) + current_user.email_address, url_for("main.sign_out") + ) + ) flash(message=message) abort(403) - if invited_user.status == 'cancelled': + if invited_user.status == "cancelled": service = Service.from_id(invited_user.service) - return render_template('views/cancelled-invitation.html', - from_user=invited_user.from_user.name, - service_name=service.name) - if invited_user.status == 'accepted': - session.pop('invited_user_id', None) + return render_template( + "views/cancelled-invitation.html", + from_user=invited_user.from_user.name, + service_name=service.name, + ) + if invited_user.status == "accepted": + session.pop("invited_user_id", None) service = Service.from_id(invited_user.service) - return redirect(url_for('main.service_dashboard', service_id=invited_user.service)) + return redirect( + url_for("main.service_dashboard", service_id=invited_user.service) + ) - session['invited_user_id'] = invited_user.id + session["invited_user_id"] = invited_user.id existing_user = User.from_email_address_or_none(invited_user.email_address) @@ -44,17 +53,23 @@ def accept_invite(token): existing_user.update_email_access_validated_at() invited_user.accept_invite() if existing_user in Users(invited_user.service): - return redirect(url_for('main.service_dashboard', service_id=invited_user.service)) + return redirect( + url_for("main.service_dashboard", service_id=invited_user.service) + ) else: service = Service.from_id(invited_user.service) # if the service you're being added to can modify auth type, then check if we can do this; # if the user is a Platform Admin, we silently leave this unchanged to prevent a security # issue where someone could switch their auth type to something less secure - if service.has_permission('email_auth') and not existing_user.platform_admin: - if invited_user.auth_type == 'email_auth' or ( + if ( + service.has_permission("email_auth") + and not existing_user.platform_admin + ): + if invited_user.auth_type == "email_auth" or ( # they have a phone number, we want them to start using it. # if they dont have a mobile we just ignore that option of the invite - existing_user.mobile_number and invited_user.auth_type == 'sms_auth' + existing_user.mobile_number + and invited_user.auth_type == "sms_auth" ): existing_user.update(auth_type=invited_user.auth_type) existing_user.add_to_service( @@ -63,40 +78,49 @@ def accept_invite(token): folder_permissions=invited_user.folder_permissions, invited_by_id=invited_user.from_user.id, ) - return redirect(url_for('main.service_dashboard', service_id=service.id)) + return redirect(url_for("main.service_dashboard", service_id=service.id)) else: - return redirect(url_for('main.register_from_invite')) + return redirect(url_for("main.register_from_invite")) @main.route("/organization-invitation/") def accept_org_invite(token): invited_org_user = InvitedOrgUser.from_token(token) - if not current_user.is_anonymous and current_user.email_address.lower() != invited_org_user.email_address.lower(): - message = Markup(""" + if ( + not current_user.is_anonymous + and current_user.email_address.lower() != invited_org_user.email_address.lower() + ): + message = Markup( + """ You’re signed in as {}. This invite is for another email address. Sign out and click the link again to accept this invite. """.format( - current_user.email_address, - url_for("main.sign_out"))) + current_user.email_address, url_for("main.sign_out") + ) + ) flash(message=message) abort(403) - if invited_org_user.status == 'cancelled': + if invited_org_user.status == "cancelled": organization = Organization.from_id(invited_org_user.organization) - return render_template('views/cancelled-invitation.html', - from_user=invited_org_user.invited_by.name, - organization_name=organization.name) + return render_template( + "views/cancelled-invitation.html", + from_user=invited_org_user.invited_by.name, + organization_name=organization.name, + ) - if invited_org_user.status == 'accepted': - session.pop('invited_org_user_id', None) - return redirect(url_for('main.organization_dashboard', org_id=invited_org_user.organization)) + if invited_org_user.status == "accepted": + session.pop("invited_org_user_id", None) + return redirect( + url_for("main.organization_dashboard", org_id=invited_org_user.organization) + ) - session['invited_org_user_id'] = invited_org_user.id + session["invited_org_user_id"] = invited_org_user.id existing_user = User.from_email_address_or_none(invited_org_user.email_address) organization_users = OrganizationUsers(invited_org_user.organization) @@ -105,7 +129,11 @@ def accept_org_invite(token): existing_user.update_email_access_validated_at() invited_org_user.accept_invite() if existing_user not in organization_users: - existing_user.add_to_organization(organization_id=invited_org_user.organization) - return redirect(url_for('main.organization_dashboard', org_id=invited_org_user.organization)) + existing_user.add_to_organization( + organization_id=invited_org_user.organization + ) + return redirect( + url_for("main.organization_dashboard", org_id=invited_org_user.organization) + ) else: - return redirect(url_for('main.register_from_org_invite')) + return redirect(url_for("main.register_from_org_invite")) diff --git a/app/main/views/jobs.py b/app/main/views/jobs.py index 12e2f0f35..ed8f051a3 100644 --- a/app/main/views/jobs.py +++ b/app/main/views/jobs.py @@ -39,10 +39,12 @@ from app.utils.user import user_has_permissions @main.route("/services//jobs") @user_has_permissions() def view_jobs(service_id): - return redirect(url_for( - 'main.uploads', - service_id=current_service.id, - )) + return redirect( + url_for( + "main.uploads", + service_id=current_service.id, + ) + ) @main.route("/services//jobs/") @@ -53,205 +55,215 @@ def view_job(service_id, job_id): abort(404) filter_args = parse_filter_args(request.args) - filter_args['status'] = set_status_filters(filter_args) + filter_args["status"] = set_status_filters(filter_args) return render_template( - 'views/jobs/job.html', + "views/jobs/job.html", job=job, - status=request.args.get('status', ''), + status=request.args.get("status", ""), updates_url=url_for( ".view_job_updates", service_id=service_id, job_id=job.id, - status=request.args.get('status', ''), + status=request.args.get("status", ""), ), partials=get_job_partials(job), ) @main.route("/services//jobs/.csv") -@user_has_permissions('view_activity') +@user_has_permissions("view_activity") def view_job_csv(service_id, job_id): job = Job.from_id(job_id, service_id=service_id) filter_args = parse_filter_args(request.args) - filter_args['status'] = set_status_filters(filter_args) + filter_args["status"] = set_status_filters(filter_args) return Response( stream_with_context( generate_notifications_csv( service_id=service_id, job_id=job_id, - status=filter_args.get('status'), - page=request.args.get('page', 1), + status=filter_args.get("status"), + page=request.args.get("page", 1), page_size=5000, format_for_csv=True, template_type=job.template_type, ) ), - mimetype='text/csv', + mimetype="text/csv", headers={ - 'Content-Disposition': 'inline; filename="{} - {}.csv"'.format( - job.template['name'], - format_datetime_short(job.created_at) + "Content-Disposition": 'inline; filename="{} - {}.csv"'.format( + job.template["name"], format_datetime_short(job.created_at) ) - } + }, ) -@main.route("/services//jobs/", methods=['POST']) -@user_has_permissions('send_messages') +@main.route("/services//jobs/", methods=["POST"]) +@user_has_permissions("send_messages") def cancel_job(service_id, job_id): Job.from_id(job_id, service_id=service_id).cancel() - return redirect(url_for('main.service_dashboard', service_id=service_id)) + return redirect(url_for("main.service_dashboard", service_id=service_id)) @main.route("/services//jobs/.json") @user_has_permissions() def view_job_updates(service_id, job_id): - job = Job.from_id(job_id, service_id=service_id) return jsonify(**get_job_partials(job)) -@main.route('/services//notifications', methods=['GET', 'POST']) -@main.route('/services//notifications/', methods=['GET', 'POST']) +@main.route("/services//notifications", methods=["GET", "POST"]) +@main.route( + "/services//notifications/", + methods=["GET", "POST"], +) @user_has_permissions() def view_notifications(service_id, message_type=None): return render_template( - 'views/notifications.html', + "views/notifications.html", partials=get_notifications(service_id, message_type), message_type=message_type, - status=request.args.get('status') or 'sending,delivered,failed', - page=request.args.get('page', 1), + status=request.args.get("status") or "sending,delivered,failed", + page=request.args.get("page", 1), search_form=SearchNotificationsForm( message_type=message_type, - to=request.form.get('to'), + to=request.form.get("to"), ), things_you_can_search_by={ - 'email': ['email address'], - 'sms': ['phone number'], + "email": ["email address"], + "sms": ["phone number"], # We say recipient here because combining all 3 types, plus # reference gets too long for the hint text - None: ['recipient'], - }.get(message_type) + { - True: ['reference'], + None: ["recipient"], + }.get(message_type) + + { + True: ["reference"], False: [], }.get(bool(current_service.api_keys)), download_link=url_for( - '.download_notifications_csv', + ".download_notifications_csv", service_id=current_service.id, message_type=message_type, - status=request.args.get('status') + status=request.args.get("status"), + ), + ) + + +@main.route("/services//notifications.json", methods=["GET", "POST"]) +@main.route( + "/services//notifications/.json", + methods=["GET", "POST"], +) +@user_has_permissions() +def get_notifications_as_json(service_id, message_type=None): + return jsonify( + get_notifications( + service_id, message_type, status_override=request.args.get("status") ) ) -@main.route('/services//notifications.json', methods=['GET', 'POST']) -@main.route('/services//notifications/.json', methods=['GET', 'POST']) -@user_has_permissions() -def get_notifications_as_json(service_id, message_type=None): - return jsonify(get_notifications( - service_id, message_type, status_override=request.args.get('status') - )) - - -@main.route('/services//notifications.csv', endpoint="view_notifications_csv") @main.route( - '/services//notifications/.csv', - endpoint="view_notifications_csv" + "/services//notifications.csv", endpoint="view_notifications_csv" +) +@main.route( + "/services//notifications/.csv", + endpoint="view_notifications_csv", ) @user_has_permissions() -def get_notifications(service_id, message_type, status_override=None): # noqa +def get_notifications(service_id, message_type, status_override=None): # noqa # TODO get the api to return count of pages as well. page = get_page_from_request() if page is None: - abort(404, "Invalid page argument ({}).".format(request.args.get('page'))) + abort(404, "Invalid page argument ({}).".format(request.args.get("page"))) filter_args = parse_filter_args(request.args) - filter_args['status'] = set_status_filters(filter_args) + filter_args["status"] = set_status_filters(filter_args) service_data_retention_days = None - search_term = request.form.get('to', '') + search_term = request.form.get("to", "") if message_type is not None: - service_data_retention_days = current_service.get_days_of_retention(message_type) + service_data_retention_days = current_service.get_days_of_retention( + message_type + ) - if request.path.endswith('csv') and current_user.has_permissions('view_activity'): + if request.path.endswith("csv") and current_user.has_permissions("view_activity"): return Response( generate_notifications_csv( service_id=service_id, page=page, page_size=5000, template_type=[message_type], - status=filter_args.get('status'), - limit_days=service_data_retention_days + status=filter_args.get("status"), + limit_days=service_data_retention_days, ), - mimetype='text/csv', - headers={ - 'Content-Disposition': 'inline; filename="notifications.csv"'} + mimetype="text/csv", + headers={"Content-Disposition": 'inline; filename="notifications.csv"'}, ) notifications = notification_api_client.get_notifications_for_service( service_id=service_id, page=page, template_type=[message_type] if message_type else [], - status=filter_args.get('status'), + status=filter_args.get("status"), limit_days=service_data_retention_days, to=search_term, ) - url_args = { - 'message_type': message_type, - 'status': request.args.get('status') - } + url_args = {"message_type": message_type, "status": request.args.get("status")} prev_page = None - if 'links' in notifications and notifications['links'].get('prev', None): - prev_page = generate_previous_dict('main.view_notifications', service_id, page, url_args=url_args) + if "links" in notifications and notifications["links"].get("prev", None): + prev_page = generate_previous_dict( + "main.view_notifications", service_id, page, url_args=url_args + ) next_page = None - if 'links' in notifications and notifications['links'].get('next', None): - next_page = generate_next_dict('main.view_notifications', service_id, page, url_args) + if "links" in notifications and notifications["links"].get("next", None): + next_page = generate_next_dict( + "main.view_notifications", service_id, page, url_args + ) if message_type: download_link = url_for( - '.view_notifications_csv', + ".view_notifications_csv", service_id=current_service.id, message_type=message_type, - status=request.args.get('status') + status=request.args.get("status"), ) else: download_link = None return { - 'service_data_retention_days': service_data_retention_days, - 'counts': render_template( - 'views/activity/counts.html', - status=request.args.get('status'), + "service_data_retention_days": service_data_retention_days, + "counts": render_template( + "views/activity/counts.html", + status=request.args.get("status"), status_filters=get_status_filters( current_service, message_type, service_api_client.get_service_statistics( - service_id, - limit_days=service_data_retention_days - ) - ) + service_id, limit_days=service_data_retention_days + ), + ), ), - 'notifications': render_template( - 'views/activity/notifications.html', - notifications=list(add_preview_of_content_to_notifications( - notifications['notifications'] - )), + "notifications": render_template( + "views/activity/notifications.html", + notifications=list( + add_preview_of_content_to_notifications(notifications["notifications"]) + ), page=page, limit_days=service_data_retention_days, prev_page=prev_page, next_page=next_page, show_pagination=(not search_term), - status=request.args.get('status'), + status=request.args.get("status"), message_type=message_type, download_link=download_link, single_notification_url=partial( url_for, - '.view_notification', + ".view_notification", service_id=current_service.id, - ) + ), ), } @@ -259,22 +271,19 @@ def get_notifications(service_id, message_type, status_override=None): # noqa def get_status_filters(service, message_type, statistics): if message_type is None: stats = { - key: sum( - statistics[message_type][key] - for message_type in {'email', 'sms'} - ) - for key in {'requested', 'delivered', 'failed'} + key: sum(statistics[message_type][key] for message_type in {"email", "sms"}) + for key in {"requested", "delivered", "failed"} } else: stats = statistics[message_type] - stats['sending'] = stats['requested'] - stats['delivered'] - stats['failed'] + stats["sending"] = stats["requested"] - stats["delivered"] - stats["failed"] filters = [ # key, label, option - ('requested', 'total', 'sending,delivered,failed'), - ('sending', 'pending', 'pending'), - ('delivered', 'delivered', 'delivered'), - ('failed', 'failed', 'failed'), + ("requested", "total", "sending,delivered,failed"), + ("sending", "pending", "pending"), + ("delivered", "delivered", "delivered"), + ("failed", "failed", "failed"), ] return [ # return list containing label, option, link, count @@ -282,12 +291,12 @@ def get_status_filters(service, message_type, statistics): label, option, url_for( - '.view_notifications', + ".view_notifications", service_id=service.id, message_type=message_type, - status=option + status=option, ), - stats[key] + stats[key], ) for key, label, option in filters ] @@ -305,106 +314,114 @@ def _get_job_counts(job): job_id=job.id, status=query_param, ), - count - ) for label, query_param, count in [ + count, + ) + for label, query_param, count in [ [ Markup( - f'''total - {"text message" if job_type == "sms" else job_type}s''' + f"""total + {"text message" if job_type == "sms" else job_type}s""" ), - '', - job.notification_count + "", + job.notification_count, ], [ Markup( - f'''pending - {message_count_noun(job.notifications_sending, job_type)}''' + f"""pending + {message_count_noun(job.notifications_sending, job_type)}""" ), - 'pending', - job.notifications_sending + "pending", + job.notifications_sending, ], [ Markup( - f'''delivered - {message_count_noun(job.notifications_delivered, job_type)}''' + f"""delivered + {message_count_noun(job.notifications_delivered, job_type)}""" ), - 'delivered', - job.notifications_delivered + "delivered", + job.notifications_delivered, ], [ Markup( - f'''failed - {message_count_noun(job.notifications_failed, job_type)}''' + f"""failed + {message_count_noun(job.notifications_failed, job_type)}""" ), - 'failed', - job.notifications_failed - ] + "failed", + job.notifications_failed, + ], ] ] def get_job_partials(job): filter_args = parse_filter_args(request.args) - filter_args['status'] = set_status_filters(filter_args) - notifications = job.get_notifications(status=filter_args['status']) + filter_args["status"] = set_status_filters(filter_args) + notifications = job.get_notifications(status=filter_args["status"]) counts = render_template( - 'partials/count.html', + "partials/count.html", counts=_get_job_counts(job), - status=filter_args['status'], + status=filter_args["status"], notifications_deleted=( - job.status == 'finished' and not notifications['notifications'] + job.status == "finished" and not notifications["notifications"] ), ) - service_data_retention_days = current_service.get_days_of_retention(job.template_type) + service_data_retention_days = current_service.get_days_of_retention( + job.template_type + ) return { - 'counts': counts, - 'notifications': render_template( - 'partials/jobs/notifications.html', + "counts": counts, + "notifications": render_template( + "partials/jobs/notifications.html", notifications=list( - add_preview_of_content_to_notifications(notifications['notifications']) + add_preview_of_content_to_notifications(notifications["notifications"]) ), - more_than_one_page=bool(notifications.get('links', {}).get('next')), + more_than_one_page=bool(notifications.get("links", {}).get("next")), download_link=url_for( - '.view_job_csv', + ".view_job_csv", service_id=current_service.id, job_id=job.id, - status=request.args.get('status') + status=request.args.get("status"), + ), + time_left=get_time_left( + job.created_at, service_data_retention_days=service_data_retention_days ), - time_left=get_time_left(job.created_at, service_data_retention_days=service_data_retention_days), job=job, service_data_retention_days=service_data_retention_days, ), - 'status': render_template( - 'partials/jobs/status.html', + "status": render_template( + "partials/jobs/status.html", job=job, ), } def add_preview_of_content_to_notifications(notifications): - for notification in notifications: - yield (dict( - preview_of_content=get_preview_of_content(notification), - **notification - )) + yield ( + dict( + preview_of_content=get_preview_of_content(notification), **notification + ) + ) def get_preview_of_content(notification): + if notification["template"].get("redact_personalisation"): + notification["personalisation"] = {} - if notification['template'].get('redact_personalisation'): - notification['personalisation'] = {} + if notification["template"]["template_type"] == "sms": + return str( + SMSBodyPreviewTemplate( + notification["template"], + notification["personalisation"], + ) + ) - if notification['template']['template_type'] == 'sms': - return str(SMSBodyPreviewTemplate( - notification['template'], - notification['personalisation'], - )) - - if notification['template']['template_type'] == 'email': - return Markup(EmailPreviewTemplate( - notification['template'], - notification['personalisation'], - redact_missing_personalisation=True, - ).subject) + if notification["template"]["template_type"] == "email": + return Markup( + EmailPreviewTemplate( + notification["template"], + notification["personalisation"], + redact_missing_personalisation=True, + ).subject + ) diff --git a/app/main/views/manage_users.py b/app/main/views/manage_users.py index 977e04d1a..b1527db3a 100644 --- a/app/main/views/manage_users.py +++ b/app/main/views/manage_users.py @@ -29,7 +29,7 @@ from app.utils.user_permissions import permission_options @user_has_permissions(allow_org_user=True) def manage_users(service_id): return render_template( - 'views/manage-users.html', + "views/manage-users.html", users=current_service.team_members, current_user=current_user, show_search_box=(len(current_service.team_members) > 7), @@ -38,29 +38,30 @@ def manage_users(service_id): ) -@main.route("/services//users/invite", methods=['GET', 'POST']) -@main.route("/services//users/invite/", methods=['GET', 'POST']) -@user_has_permissions('manage_service') +@main.route("/services//users/invite", methods=["GET", "POST"]) +@main.route( + "/services//users/invite/", methods=["GET", "POST"] +) +@user_has_permissions("manage_service") def invite_user(service_id, user_id=None): - form_class = InviteUserForm form = form_class( inviter_email_address=current_user.email_address, all_template_folders=current_service.all_template_folders, - folder_permissions=[f['id'] for f in current_service.all_template_folders] + folder_permissions=[f["id"] for f in current_service.all_template_folders], ) if user_id: user_to_invite = User.from_id(user_id) if user_to_invite.belongs_to_service(current_service.id): return render_template( - 'views/user-already-team-member.html', + "views/user-already-team-member.html", user_to_invite=user_to_invite, ) if current_service.invite_pending_for(user_to_invite.email_address): return render_template( - 'views/user-already-invited.html', + "views/user-already-invited.html", user_to_invite=user_to_invite, ) if not user_to_invite.default_organization: @@ -71,9 +72,9 @@ def invite_user(service_id, user_id=None): else: user_to_invite = None - service_has_email_auth = current_service.has_permission('email_auth') + service_has_email_auth = current_service.has_permission("email_auth") if not service_has_email_auth: - form.login_authentication.data = 'sms_auth' + form.login_authentication.data = "sms_auth" if form.validate_on_submit(): email_address = form.email_address.data @@ -92,11 +93,13 @@ def invite_user(service_id, user_id=None): ui_permissions=form.permissions, ) - flash('Invite sent to {}'.format(invited_user.email_address), 'default_with_tick') - return redirect(url_for('.manage_users', service_id=service_id)) + flash( + "Invite sent to {}".format(invited_user.email_address), "default_with_tick" + ) + return redirect(url_for(".manage_users", service_id=service_id)) return render_template( - 'views/invite-user.html', + "views/invite-user.html", form=form, service_has_email_auth=service_has_email_auth, mobile_number=True, @@ -104,10 +107,10 @@ def invite_user(service_id, user_id=None): ) -@main.route("/services//users/", methods=['GET', 'POST']) -@user_has_permissions('manage_service') +@main.route("/services//users/", methods=["GET", "POST"]) +@user_has_permissions("manage_service") def edit_user_permissions(service_id, user_id): - service_has_email_auth = current_service.has_permission('email_auth') + service_has_email_auth = current_service.has_permission("email_auth") user = current_service.get_team_member(user_id) mobile_number = None @@ -119,11 +122,16 @@ def edit_user_permissions(service_id, user_id): form = form_class.from_user( user, service_id, - folder_permissions=None if user.platform_admin else [ - f['id'] for f in current_service.all_template_folders + folder_permissions=None + if user.platform_admin + else [ + f["id"] + for f in current_service.all_template_folders if user.has_template_folder_permission(f) ], - all_template_folders=None if user.platform_admin else current_service.all_template_folders + all_template_folders=None + if user.platform_admin + else current_service.all_template_folders, ) if form.validate_on_submit(): @@ -136,87 +144,86 @@ def edit_user_permissions(service_id, user_id): # Only change the auth type if this is supported for a service. if service_has_email_auth: user.update(auth_type=form.login_authentication.data) - return redirect(url_for('.manage_users', service_id=service_id)) + return redirect(url_for(".manage_users", service_id=service_id)) return render_template( - 'views/edit-user-permissions.html', + "views/edit-user-permissions.html", user=user, form=form, service_has_email_auth=service_has_email_auth, mobile_number=mobile_number, - delete=request.args.get('delete'), + delete=request.args.get("delete"), ) -@main.route("/services//users//delete", methods=['POST']) -@user_has_permissions('manage_service') +@main.route("/services//users//delete", methods=["POST"]) +@user_has_permissions("manage_service") def remove_user_from_service(service_id, user_id): try: service_api_client.remove_user_from_service(service_id, user_id) except HTTPError as e: msg = "You cannot remove the only user for a service" if e.status_code == 400 and msg in e.message: - flash(msg, 'info') - return redirect(url_for( - '.manage_users', - service_id=service_id)) + flash(msg, "info") + return redirect(url_for(".manage_users", service_id=service_id)) else: abort(500, e) else: create_remove_user_from_service_event( - user_id=user_id, - removed_by_id=current_user.id, - service_id=service_id + user_id=user_id, removed_by_id=current_user.id, service_id=service_id ) - return redirect(url_for( - '.manage_users', - service_id=service_id - )) + return redirect(url_for(".manage_users", service_id=service_id)) -@main.route("/services//users//edit-email", methods=['GET', 'POST']) -@user_has_permissions('manage_service') +@main.route( + "/services//users//edit-email", + methods=["GET", "POST"], +) +@user_has_permissions("manage_service") def edit_user_email(service_id, user_id): user = current_service.get_team_member(user_id) user_email = user.email_address - session_key = 'team_member_email_change-{}'.format(user_id) + session_key = "team_member_email_change-{}".format(user_id) if is_gov_user(user_email): form = ChangeEmailForm(User.already_registered, email_address=user_email) else: form = ChangeNonGovEmailForm(User.already_registered, email_address=user_email) - if request.form.get('email_address', '').strip() == user_email: - return redirect(url_for('.manage_users', service_id=current_service.id)) + if request.form.get("email_address", "").strip() == user_email: + return redirect(url_for(".manage_users", service_id=current_service.id)) if form.validate_on_submit(): session[session_key] = form.email_address.data - return redirect(url_for('.confirm_edit_user_email', user_id=user.id, service_id=service_id)) + return redirect( + url_for(".confirm_edit_user_email", user_id=user.id, service_id=service_id) + ) return render_template( - 'views/manage-users/edit-user-email.html', + "views/manage-users/edit-user-email.html", user=user, form=form, - service_id=service_id + service_id=service_id, ) -@main.route("/services//users//edit-email/confirm", methods=['GET', 'POST']) -@user_has_permissions('manage_service') +@main.route( + "/services//users//edit-email/confirm", + methods=["GET", "POST"], +) +@user_has_permissions("manage_service") def confirm_edit_user_email(service_id, user_id): user = current_service.get_team_member(user_id) - session_key = 'team_member_email_change-{}'.format(user_id) + session_key = "team_member_email_change-{}".format(user_id) if session_key in session: new_email = session[session_key] else: - return redirect(url_for( - '.edit_user_email', - service_id=service_id, - user_id=user_id - )) - if request.method == 'POST': + return redirect( + url_for(".edit_user_email", service_id=service_id, user_id=user_id) + ) + if request.method == "POST": try: user.update(email_address=new_email, updated_by=current_user.id) except HTTPError as e: @@ -226,60 +233,64 @@ def confirm_edit_user_email(service_id, user_id): user_id=user.id, updated_by_id=current_user.id, original_email_address=user.email_address, - new_email_address=new_email + new_email_address=new_email, ) finally: session.pop(session_key, None) - return redirect(url_for( - '.manage_users', - service_id=service_id - )) + return redirect(url_for(".manage_users", service_id=service_id)) return render_template( - 'views/manage-users/confirm-edit-user-email.html', + "views/manage-users/confirm-edit-user-email.html", user=user, service_id=service_id, - new_email=new_email + new_email=new_email, ) -@main.route("/services//users//edit-mobile-number", methods=['GET', 'POST']) -@user_has_permissions('manage_service') +@main.route( + "/services//users//edit-mobile-number", + methods=["GET", "POST"], +) +@user_has_permissions("manage_service") def edit_user_mobile_number(service_id, user_id): user = current_service.get_team_member(user_id) user_mobile_number = redact_mobile_number(user.mobile_number) form = ChangeMobileNumberForm(mobile_number=user_mobile_number) - if form.mobile_number.data == user_mobile_number and request.method == 'POST': - return redirect(url_for( - '.manage_users', - service_id=service_id - )) + if form.mobile_number.data == user_mobile_number and request.method == "POST": + return redirect(url_for(".manage_users", service_id=service_id)) if form.validate_on_submit(): - session['team_member_mobile_change'] = form.mobile_number.data + session["team_member_mobile_change"] = form.mobile_number.data - return redirect(url_for('.confirm_edit_user_mobile_number', user_id=user.id, service_id=service_id)) + return redirect( + url_for( + ".confirm_edit_user_mobile_number", + user_id=user.id, + service_id=service_id, + ) + ) return render_template( - 'views/manage-users/edit-user-mobile.html', + "views/manage-users/edit-user-mobile.html", user=user, form=form, - service_id=service_id + service_id=service_id, ) -@main.route("/services//users//edit-mobile-number/confirm", methods=['GET', 'POST']) -@user_has_permissions('manage_service') +@main.route( + "/services//users//edit-mobile-number/confirm", + methods=["GET", "POST"], +) +@user_has_permissions("manage_service") def confirm_edit_user_mobile_number(service_id, user_id): user = current_service.get_team_member(user_id) - if 'team_member_mobile_change' in session: - new_number = session['team_member_mobile_change'] + if "team_member_mobile_change" in session: + new_number = session["team_member_mobile_change"] else: - return redirect(url_for( - '.edit_user_mobile_number', - service_id=service_id, - user_id=user_id - )) - if request.method == 'POST': + return redirect( + url_for(".edit_user_mobile_number", service_id=service_id, user_id=user_id) + ) + if request.method == "POST": try: user.update(mobile_number=new_number, updated_by=current_user.id) except HTTPError as e: @@ -289,26 +300,26 @@ def confirm_edit_user_mobile_number(service_id, user_id): user_id=user.id, updated_by_id=current_user.id, original_mobile_number=user.mobile_number, - new_mobile_number=new_number + new_mobile_number=new_number, ) finally: - session.pop('team_member_mobile_change', None) + session.pop("team_member_mobile_change", None) - return redirect(url_for( - '.manage_users', - service_id=service_id - )) + return redirect(url_for(".manage_users", service_id=service_id)) return render_template( - 'views/manage-users/confirm-edit-user-mobile-number.html', + "views/manage-users/confirm-edit-user-mobile-number.html", user=user, service_id=service_id, - new_mobile_number=new_number + new_mobile_number=new_number, ) -@main.route("/services//cancel-invited-user/", methods=['GET']) -@user_has_permissions('manage_service') +@main.route( + "/services//cancel-invited-user/", + methods=["GET"], +) +@user_has_permissions("manage_service") def cancel_invited_user(service_id, invited_user_id): current_service.cancel_invite(invited_user_id) @@ -319,5 +330,5 @@ def cancel_invited_user(service_id, invited_user_id): service_id=service_id, ) - flash(f'Invitation cancelled for {invited_user.email_address}', 'default_with_tick') - return redirect(url_for('main.manage_users', service_id=service_id)) + flash(f"Invitation cancelled for {invited_user.email_address}", "default_with_tick") + return redirect(url_for("main.manage_users", service_id=service_id)) diff --git a/app/main/views/new_password.py b/app/main/views/new_password.py index 55895e13d..4bf374717 100644 --- a/app/main/views/new_password.py +++ b/app/main/views/new_password.py @@ -18,38 +18,49 @@ from app.models.user import User from app.utils.login import log_in_user -@main.route('/new-password/', methods=['GET', 'POST']) +@main.route("/new-password/", methods=["GET", "POST"]) def new_password(token): try: - token_data = check_token(token, current_app.config['SECRET_KEY'], current_app.config['DANGEROUS_SALT'], - current_app.config['EMAIL_EXPIRY_SECONDS']) + token_data = check_token( + token, + current_app.config["SECRET_KEY"], + current_app.config["DANGEROUS_SALT"], + current_app.config["EMAIL_EXPIRY_SECONDS"], + ) except SignatureExpired: - flash('The link in the email we sent you has expired. Enter your email address to resend.') - return redirect(url_for('.forgot_password')) + flash( + "The link in the email we sent you has expired. Enter your email address to resend." + ) + return redirect(url_for(".forgot_password")) - email_address = json.loads(token_data)['email'] + email_address = json.loads(token_data)["email"] user = User.from_email_address(email_address) - if user.password_changed_more_recently_than(json.loads(token_data)['created_at']): - flash('The link in the email has already been used') - return redirect(url_for('main.index')) + if user.password_changed_more_recently_than(json.loads(token_data)["created_at"]): + flash("The link in the email has already been used") + return redirect(url_for("main.index")) - if request.method == 'GET': + if request.method == "GET": user.update_email_access_validated_at() form = NewPasswordForm() if form.validate_on_submit(): user.reset_failed_login_count() - session['user_details'] = { - 'id': user.id, - 'email': user.email_address, - 'password': form.new_password.data} + session["user_details"] = { + "id": user.id, + "email": user.email_address, + "password": form.new_password.data, + } if user.email_auth: # they've just clicked an email link, so have done an email auth journey anyway. Just log them in. return log_in_user(user.id) else: # send user a 2fa sms code user.send_verify_code() - return redirect(url_for('main.two_factor_sms', next=request.args.get('next'))) + return redirect( + url_for("main.two_factor_sms", next=request.args.get("next")) + ) else: - return render_template('views/new-password.html', token=token, form=form, user=user) + return render_template( + "views/new-password.html", token=token, form=form, user=user + ) diff --git a/app/main/views/notifications.py b/app/main/views/notifications.py index 3b3d2b1fb..f64fdbdd4 100644 --- a/app/main/views/notifications.py +++ b/app/main/views/notifications.py @@ -31,135 +31,137 @@ from app.utils.user import user_has_permissions @main.route("/services//notification/") -@user_has_permissions('view_activity', 'send_messages') +@user_has_permissions("view_activity", "send_messages") def view_notification(service_id, notification_id): - notification = notification_api_client.get_notification(service_id, str(notification_id)) - notification['template'].update({'reply_to_text': notification['reply_to_text']}) + notification = notification_api_client.get_notification( + service_id, str(notification_id) + ) + notification["template"].update({"reply_to_text": notification["reply_to_text"]}) personalisation = get_all_personalisation_from_notification(notification) error_message = None template = get_template( - notification['template'], + notification["template"], current_service, show_recipient=True, redact_missing_personalisation=True, - sms_sender=notification['reply_to_text'], - email_reply_to=notification['reply_to_text'], + sms_sender=notification["reply_to_text"], + email_reply_to=notification["reply_to_text"], ) template.values = personalisation - if notification['job']: - job = job_api_client.get_job(service_id, notification['job']['id'])['data'] + if notification["job"]: + job = job_api_client.get_job(service_id, notification["job"]["id"])["data"] else: job = None - if get_help_argument() or request.args.get('help') == '0': + if get_help_argument() or request.args.get("help") == "0": # help=0 is set when you’ve just sent a notification. We # only want to show the back link when you’ve navigated to a # notification, not when you’ve just sent it. back_link = None - elif request.args.get('from_job'): + elif request.args.get("from_job"): back_link = url_for( - 'main.view_job', + "main.view_job", service_id=current_service.id, - job_id=request.args.get('from_job'), + job_id=request.args.get("from_job"), ) else: back_link = url_for( - 'main.view_notifications', + "main.view_notifications", service_id=current_service.id, message_type=template.template_type, - status='sending,delivered,failed', + status="sending,delivered,failed", ) return render_template( - 'views/notifications/notification.html', - finished=(notification['status'] in (DELIVERED_STATUSES + FAILURE_STATUSES)), - notification_status=notification['status'], + "views/notifications/notification.html", + finished=(notification["status"] in (DELIVERED_STATUSES + FAILURE_STATUSES)), + notification_status=notification["status"], message=error_message, - uploaded_file_name='Report', + uploaded_file_name="Report", template=template, job=job, updates_url=url_for( ".view_notification_updates", service_id=service_id, - notification_id=notification['id'], - status=request.args.get('status'), - help=get_help_argument() + notification_id=notification["id"], + status=request.args.get("status"), + help=get_help_argument(), ), partials=get_single_notification_partials(notification), - created_by=notification.get('created_by'), - created_at=notification['created_at'], - updated_at=notification['updated_at'], + created_by=notification.get("created_by"), + created_at=notification["created_at"], + updated_at=notification["updated_at"], help=get_help_argument(), - notification_id=notification['id'], - can_receive_inbound=(current_service.has_permission('inbound_sms')), - sent_with_test_key=( - notification.get('key_type') == KEY_TYPE_TEST - ), + notification_id=notification["id"], + can_receive_inbound=(current_service.has_permission("inbound_sms")), + sent_with_test_key=(notification.get("key_type") == KEY_TYPE_TEST), back_link=back_link, ) @main.route("/services//notification/.json") -@user_has_permissions('view_activity', 'send_messages') +@user_has_permissions("view_activity", "send_messages") def view_notification_updates(service_id, notification_id): - return jsonify(**get_single_notification_partials( - notification_api_client.get_notification(service_id, notification_id) - )) + return jsonify( + **get_single_notification_partials( + notification_api_client.get_notification(service_id, notification_id) + ) + ) def get_single_notification_partials(notification): return { - 'status': render_template( - 'partials/notifications/status.html', + "status": render_template( + "partials/notifications/status.html", notification=notification, - sent_with_test_key=( - notification.get('key_type') == KEY_TYPE_TEST - ), + sent_with_test_key=(notification.get("key_type") == KEY_TYPE_TEST), ), } def get_all_personalisation_from_notification(notification): + if notification["template"].get("redact_personalisation"): + notification["personalisation"] = {} - if notification['template'].get('redact_personalisation'): - notification['personalisation'] = {} + if notification["template"]["template_type"] == "email": + notification["personalisation"]["email_address"] = notification["to"] - if notification['template']['template_type'] == 'email': - notification['personalisation']['email_address'] = notification['to'] + if notification["template"]["template_type"] == "sms": + notification["personalisation"]["phone_number"] = notification["to"] - if notification['template']['template_type'] == 'sms': - notification['personalisation']['phone_number'] = notification['to'] - - return notification['personalisation'] + return notification["personalisation"] @main.route("/services//download-notifications.csv") -@user_has_permissions('view_activity') +@user_has_permissions("view_activity") def download_notifications_csv(service_id): filter_args = parse_filter_args(request.args) - filter_args['status'] = set_status_filters(filter_args) + filter_args["status"] = set_status_filters(filter_args) - service_data_retention_days = current_service.get_days_of_retention(filter_args.get('message_type')[0]) + service_data_retention_days = current_service.get_days_of_retention( + filter_args.get("message_type")[0] + ) return Response( stream_with_context( generate_notifications_csv( service_id=service_id, job_id=None, - status=filter_args.get('status'), - page=request.args.get('page', 1), + status=filter_args.get("status"), + page=request.args.get("page", 1), page_size=10000, format_for_csv=True, - template_type=filter_args.get('message_type'), + template_type=filter_args.get("message_type"), limit_days=service_data_retention_days, ) ), - mimetype='text/csv', + mimetype="text/csv", headers={ - 'Content-Disposition': 'inline; filename="{} - {} - {} report.csv"'.format( + "Content-Disposition": 'inline; filename="{} - {} - {} report.csv"'.format( format_date_numeric(datetime.now().strftime("%Y-%m-%dT%H:%M:%S.%fZ")), - filter_args['message_type'][0], - current_service.name) - } + filter_args["message_type"][0], + current_service.name, + ) + }, ) diff --git a/app/main/views/organizations.py b/app/main/views/organizations.py index fbd2214aa..1a1ba44f2 100644 --- a/app/main/views/organizations.py +++ b/app/main/views/organizations.py @@ -38,156 +38,156 @@ from app.utils.csv import Spreadsheet from app.utils.user import user_has_permissions, user_is_platform_admin -@main.route("/organizations", methods=['GET']) +@main.route("/organizations", methods=["GET"]) @user_is_platform_admin def organizations(): return render_template( - 'views/organizations/index.html', + "views/organizations/index.html", organizations=AllOrganizations(), search_form=SearchByNameForm(), ) -@main.route("/organizations/add", methods=['GET', 'POST']) +@main.route("/organizations/add", methods=["GET", "POST"]) @user_is_platform_admin def add_organization(): form = AdminNewOrganizationForm() if form.validate_on_submit(): try: - return redirect(url_for( - '.organization_settings', - org_id=Organization.create_from_form(form).id, - )) + return redirect( + url_for( + ".organization_settings", + org_id=Organization.create_from_form(form).id, + ) + ) except HTTPError as e: - msg = 'Organization name already exists' + msg = "Organization name already exists" if e.status_code == 400 and msg in e.message: form.name.errors.append("This organization name is already in use") else: raise e - return render_template( - 'views/organizations/add-organization.html', - form=form - ) + return render_template("views/organizations/add-organization.html", form=form) -@main.route("/organizations/", methods=['GET']) +@main.route("/organizations/", methods=["GET"]) @user_has_permissions() def organization_dashboard(org_id): year, current_financial_year = requested_and_current_financial_year(request) - services = current_organization.services_and_usage( - financial_year=year - )['services'] + services = current_organization.services_and_usage(financial_year=year)["services"] return render_template( - 'views/organizations/organization/index.html', + "views/organizations/organization/index.html", services=services, years=get_tuples_of_financial_years( - partial(url_for, '.organization_dashboard', org_id=current_organization.id), + partial(url_for, ".organization_dashboard", org_id=current_organization.id), start=current_financial_year - 2, end=current_financial_year, ), selected_year=year, search_form=SearchByNameForm() if len(services) > 7 else None, **{ - f'total_{key}': sum(service[key] for service in services) - for key in ('emails_sent', 'sms_cost') + f"total_{key}": sum(service[key] for service in services) + for key in ("emails_sent", "sms_cost") }, download_link=url_for( - '.download_organization_usage_report', - org_id=org_id, - selected_year=year - ) + ".download_organization_usage_report", org_id=org_id, selected_year=year + ), ) -@main.route("/organizations//download-usage-report.csv", methods=['GET']) +@main.route("/organizations//download-usage-report.csv", methods=["GET"]) @user_has_permissions() def download_organization_usage_report(org_id): - selected_year = request.args.get('selected_year') + selected_year = request.args.get("selected_year") services_usage = current_organization.services_and_usage( financial_year=selected_year - )['services'] + )["services"] - unit_column_names = OrderedDict([ - ('service_id', 'Service ID'), - ('service_name', 'Service Name'), - ('emails_sent', 'Emails sent'), - ('sms_remainder', 'Free text message allowance remaining'), - ]) + unit_column_names = OrderedDict( + [ + ("service_id", "Service ID"), + ("service_name", "Service Name"), + ("emails_sent", "Emails sent"), + ("sms_remainder", "Free text message allowance remaining"), + ] + ) - monetary_column_names = OrderedDict([ - ('sms_cost', 'Spent on text messages ($)'), - ]) + monetary_column_names = OrderedDict( + [ + ("sms_cost", "Spent on text messages ($)"), + ] + ) org_usage_data = [ list(unit_column_names.values()) + list(monetary_column_names.values()) ] + [ - [ - service[attribute] for attribute in unit_column_names.keys() - ] + [ - '{:,.2f}'.format(service[attribute]) for attribute in monetary_column_names.keys() + [service[attribute] for attribute in unit_column_names.keys()] + + [ + "{:,.2f}".format(service[attribute]) + for attribute in monetary_column_names.keys() ] for service in services_usage ] - return Spreadsheet.from_rows(org_usage_data).as_csv_data, 200, { - 'Content-Type': 'text/csv; charset=utf-8', - 'Content-Disposition': ( - 'inline;' - 'filename="{} organization usage report for year {}' - ' - generated on {}.csv"'.format( - current_organization.name, - selected_year, - datetime.now().strftime("%Y-%m-%dT%H:%M:%S.%fZ") - )) - } + return ( + Spreadsheet.from_rows(org_usage_data).as_csv_data, + 200, + { + "Content-Type": "text/csv; charset=utf-8", + "Content-Disposition": ( + "inline;" + 'filename="{} organization usage report for year {}' + ' - generated on {}.csv"'.format( + current_organization.name, + selected_year, + datetime.now().strftime("%Y-%m-%dT%H:%M:%S.%fZ"), + ) + ), + }, + ) -@main.route("/organizations//trial-services", methods=['GET']) +@main.route("/organizations//trial-services", methods=["GET"]) @user_is_platform_admin def organization_trial_mode_services(org_id): return render_template( - 'views/organizations/organization/trial-mode-services.html', + "views/organizations/organization/trial-mode-services.html", search_form=SearchByNameForm(), ) -@main.route("/organizations//users", methods=['GET']) +@main.route("/organizations//users", methods=["GET"]) @user_has_permissions() def manage_org_users(org_id): return render_template( - 'views/organizations/organization/users/index.html', + "views/organizations/organization/users/index.html", users=current_organization.team_members, show_search_box=(len(current_organization.team_members) > 7), form=SearchUsersForm(), ) -@main.route("/organizations//users/invite", methods=['GET', 'POST']) +@main.route("/organizations//users/invite", methods=["GET", "POST"]) @user_has_permissions() def invite_org_user(org_id): - form = InviteOrgUserForm( - inviter_email_address=current_user.email_address - ) + form = InviteOrgUserForm(inviter_email_address=current_user.email_address) if form.validate_on_submit(): email_address = form.email_address.data - invited_org_user = InvitedOrgUser.create( - current_user.id, - org_id, - email_address - ) + invited_org_user = InvitedOrgUser.create(current_user.id, org_id, email_address) - flash('Invite sent to {}'.format(invited_org_user.email_address), 'default_with_tick') - return redirect(url_for('.manage_org_users', org_id=org_id)) + flash( + "Invite sent to {}".format(invited_org_user.email_address), + "default_with_tick", + ) + return redirect(url_for(".manage_org_users", org_id=org_id)) return render_template( - 'views/organizations/organization/users/invite-org-user.html', - form=form + "views/organizations/organization/users/invite-org-user.html", form=form ) -@main.route("/organizations//users/", methods=['GET']) +@main.route("/organizations//users/", methods=["GET"]) @user_has_permissions() def edit_organization_user(org_id, user_id): # The only action that can be done to an org user is to remove them from the org. @@ -195,69 +195,77 @@ def edit_organization_user(org_id, user_id): # query string, but it uses the template for all org team members in order to avoid # having a page containing a single link. return render_template( - 'views/organizations/organization/users/index.html', + "views/organizations/organization/users/index.html", users=current_organization.team_members, show_search_box=(len(current_organization.team_members) > 7), form=SearchUsersForm(), - user_to_remove=User.from_id(user_id) + user_to_remove=User.from_id(user_id), ) -@main.route("/organizations//users//delete", methods=['POST']) +@main.route( + "/organizations//users//delete", methods=["POST"] +) @user_has_permissions() def remove_user_from_organization(org_id, user_id): organizations_client.remove_user_from_organization(org_id, user_id) - return redirect(url_for('.show_accounts_or_dashboard')) + return redirect(url_for(".show_accounts_or_dashboard")) -@main.route("/organizations//cancel-invited-user/", methods=['GET']) +@main.route( + "/organizations//cancel-invited-user/", + methods=["GET"], +) @user_has_permissions() def cancel_invited_org_user(org_id, invited_user_id): - org_invite_api_client.cancel_invited_user(org_id=org_id, invited_user_id=invited_user_id) + org_invite_api_client.cancel_invited_user( + org_id=org_id, invited_user_id=invited_user_id + ) invited_org_user = InvitedOrgUser.by_id_and_org_id(org_id, invited_user_id) - flash(f'Invitation cancelled for {invited_org_user.email_address}', 'default_with_tick') - return redirect(url_for('main.manage_org_users', org_id=org_id)) + flash( + f"Invitation cancelled for {invited_org_user.email_address}", + "default_with_tick", + ) + return redirect(url_for("main.manage_org_users", org_id=org_id)) -@main.route("/organizations//settings/", methods=['GET']) +@main.route("/organizations//settings/", methods=["GET"]) @user_is_platform_admin def organization_settings(org_id): return render_template( - 'views/organizations/organization/settings/index.html', + "views/organizations/organization/settings/index.html", ) -@main.route("/organizations//settings/edit-name", methods=['GET', 'POST']) +@main.route("/organizations//settings/edit-name", methods=["GET", "POST"]) @user_is_platform_admin def edit_organization_name(org_id): form = RenameOrganizationForm(name=current_organization.name) if form.validate_on_submit(): - try: current_organization.update(name=form.name.data) except HTTPError as http_error: - error_msg = 'Organization name already exists' + error_msg = "Organization name already exists" if http_error.status_code == 400 and error_msg in http_error.message: - form.name.errors.append('This organization name is already in use') + form.name.errors.append("This organization name is already in use") else: raise http_error else: - return redirect(url_for('.organization_settings', org_id=org_id)) + return redirect(url_for(".organization_settings", org_id=org_id)) return render_template( - 'views/organizations/organization/settings/edit-name.html', + "views/organizations/organization/settings/edit-name.html", form=form, ) -@main.route("/organizations//settings/edit-type", methods=['GET', 'POST']) +@main.route("/organizations//settings/edit-type", methods=["GET", "POST"]) @user_is_platform_admin def edit_organization_type(org_id): - form = OrganizationOrganizationTypeForm( organization_type=current_organization.organization_type ) @@ -267,18 +275,19 @@ def edit_organization_type(org_id): organization_type=form.organization_type.data, delete_services_cache=True, ) - return redirect(url_for('.organization_settings', org_id=org_id)) + return redirect(url_for(".organization_settings", org_id=org_id)) return render_template( - 'views/organizations/organization/settings/edit-type.html', + "views/organizations/organization/settings/edit-type.html", form=form, ) -@main.route("/organizations//settings/set-email-branding", methods=['GET', 'POST']) +@main.route( + "/organizations//settings/set-email-branding", methods=["GET", "POST"] +) @user_is_platform_admin def edit_organization_email_branding(org_id): - email_branding = email_branding_client.get_all_email_branding() form = AdminSetEmailBrandingForm( @@ -287,24 +296,28 @@ def edit_organization_email_branding(org_id): ) if form.validate_on_submit(): - return redirect(url_for( - '.organization_preview_email_branding', - org_id=org_id, - branding_style=form.branding_style.data, - )) + return redirect( + url_for( + ".organization_preview_email_branding", + org_id=org_id, + branding_style=form.branding_style.data, + ) + ) return render_template( - 'views/organizations/organization/settings/set-email-branding.html', + "views/organizations/organization/settings/set-email-branding.html", form=form, - search_form=SearchByNameForm() + search_form=SearchByNameForm(), ) -@main.route("/organizations//settings/preview-email-branding", methods=['GET', 'POST']) +@main.route( + "/organizations//settings/preview-email-branding", + methods=["GET", "POST"], +) @user_is_platform_admin def organization_preview_email_branding(org_id): - - branding_style = request.args.get('branding_style', None) + branding_style = request.args.get("branding_style", None) form = AdminPreviewBrandingForm(branding_style=branding_style) @@ -313,94 +326,97 @@ def organization_preview_email_branding(org_id): email_branding_id=form.branding_style.data, delete_services_cache=True, ) - return redirect(url_for('.organization_settings', org_id=org_id)) + return redirect(url_for(".organization_settings", org_id=org_id)) return render_template( - 'views/organizations/organization/settings/preview-email-branding.html', + "views/organizations/organization/settings/preview-email-branding.html", form=form, - action=url_for('main.organization_preview_email_branding', org_id=org_id), + action=url_for("main.organization_preview_email_branding", org_id=org_id), ) -@main.route("/organizations//settings/edit-organization-domains", methods=['GET', 'POST']) +@main.route( + "/organizations//settings/edit-organization-domains", + methods=["GET", "POST"], +) @user_is_platform_admin def edit_organization_domains(org_id): - form = AdminOrganizationDomainsForm() if form.validate_on_submit(): try: organizations_client.update_organization( org_id, - domains=list(OrderedDict.fromkeys( - domain.lower() - for domain in filter(None, form.domains.data) - )), + domains=list( + OrderedDict.fromkeys( + domain.lower() for domain in filter(None, form.domains.data) + ) + ), ) except HTTPError as e: error_message = "Domain already exists" if e.status_code == 400 and error_message in e.message: flash("This domain is already in use", "error") return render_template( - 'views/organizations/organization/settings/edit-domains.html', + "views/organizations/organization/settings/edit-domains.html", form=form, ) else: raise e - return redirect(url_for('.organization_settings', org_id=org_id)) + return redirect(url_for(".organization_settings", org_id=org_id)) form.populate(current_organization.domains) return render_template( - 'views/organizations/organization/settings/edit-domains.html', + "views/organizations/organization/settings/edit-domains.html", form=form, ) -@main.route("/organizations//settings/edit-go-live-notes", methods=['GET', 'POST']) +@main.route( + "/organizations//settings/edit-go-live-notes", methods=["GET", "POST"] +) @user_is_platform_admin def edit_organization_go_live_notes(org_id): - form = AdminOrganizationGoLiveNotesForm() if form.validate_on_submit(): organizations_client.update_organization( - org_id, - request_to_go_live_notes=form.request_to_go_live_notes.data + org_id, request_to_go_live_notes=form.request_to_go_live_notes.data ) - return redirect(url_for('.organization_settings', org_id=org_id)) + return redirect(url_for(".organization_settings", org_id=org_id)) org = organizations_client.get_organization(org_id) - form.request_to_go_live_notes.data = org['request_to_go_live_notes'] + form.request_to_go_live_notes.data = org["request_to_go_live_notes"] return render_template( - 'views/organizations/organization/settings/edit-go-live-notes.html', + "views/organizations/organization/settings/edit-go-live-notes.html", form=form, ) -@main.route("/organizations//settings/notes", methods=['GET', 'POST']) +@main.route("/organizations//settings/notes", methods=["GET", "POST"]) @user_is_platform_admin def edit_organization_notes(org_id): form = AdminNotesForm(notes=current_organization.notes) if form.validate_on_submit(): - if form.notes.data == current_organization.notes: - return redirect(url_for('.organization_settings', org_id=org_id)) + return redirect(url_for(".organization_settings", org_id=org_id)) - current_organization.update( - notes=form.notes.data - ) - return redirect(url_for('.organization_settings', org_id=org_id)) + current_organization.update(notes=form.notes.data) + return redirect(url_for(".organization_settings", org_id=org_id)) return render_template( - 'views/organizations/organization/settings/edit-organization-notes.html', + "views/organizations/organization/settings/edit-organization-notes.html", form=form, ) -@main.route("/organizations//settings/edit-billing-details", methods=['GET', 'POST']) +@main.route( + "/organizations//settings/edit-billing-details", + methods=["GET", "POST"], +) @user_is_platform_admin def edit_organization_billing_details(org_id): form = AdminBillingDetailsForm( @@ -419,10 +435,10 @@ def edit_organization_billing_details(org_id): purchase_order_number=form.purchase_order_number.data, notes=form.notes.data, ) - return redirect(url_for('.organization_settings', org_id=org_id)) + return redirect(url_for(".organization_settings", org_id=org_id)) return render_template( - 'views/organizations/organization/settings/edit-organization-billing-details.html', + "views/organizations/organization/settings/edit-organization-billing-details.html", form=form, ) @@ -430,6 +446,4 @@ def edit_organization_billing_details(org_id): @main.route("/organizations//billing") @user_is_platform_admin def organization_billing(org_id): - return render_template( - 'views/organizations/organization/billing.html' - ) + return render_template("views/organizations/organization/billing.html") diff --git a/app/main/views/performance.py b/app/main/views/performance.py index efd41ed18..b7dbd00a6 100644 --- a/app/main/views/performance.py +++ b/app/main/views/performance.py @@ -16,27 +16,23 @@ def performance(): start_date=(datetime.now(pytz.utc) - timedelta(days=7)).date(), end_date=datetime.now(pytz.utc).date(), ) - stats['organizations_using_notify'] = sorted( + stats["organizations_using_notify"] = sorted( [ { - 'organization_name': organization_name or 'No organization', - 'count_of_live_services': len(list(group)), + "organization_name": organization_name or "No organization", + "count_of_live_services": len(list(group)), } for organization_name, group in groupby( - stats['services_using_notify'], - itemgetter('organization_name'), + stats["services_using_notify"], + itemgetter("organization_name"), ) ], - key=itemgetter('organization_name'), + key=itemgetter("organization_name"), ) - stats['average_percentage_under_10_seconds'] = mean([ - row['percentage_under_10_seconds'] - for row in stats['processing_time'] - ] or [0]) - stats['count_of_live_services_and_organizations'] = ( - status_api_client.get_count_of_live_services_and_organizations() - ) - return render_template( - 'views/performance.html', - **stats + stats["average_percentage_under_10_seconds"] = mean( + [row["percentage_under_10_seconds"] for row in stats["processing_time"]] or [0] ) + stats[ + "count_of_live_services_and_organizations" + ] = status_api_client.get_count_of_live_services_and_organizations() + return render_template("views/performance.html", **stats) diff --git a/app/main/views/platform_admin.py b/app/main/views/platform_admin.py index fb4316604..c3aa98e4c 100644 --- a/app/main/views/platform_admin.py +++ b/app/main/views/platform_admin.py @@ -44,29 +44,29 @@ ZERO_FAILURE_THRESHOLD = 0 @user_is_platform_admin def platform_admin_splash_page(): return render_template( - 'views/platform-admin/splash-page.html', + "views/platform-admin/splash-page.html", ) @main.route("/platform-admin/summary") @user_is_platform_admin def platform_admin(): - form = DateFilterForm(request.args, meta={'csrf': False}) + form = DateFilterForm(request.args, meta={"csrf": False}) api_args = {} form.validate() if form.start_date.data: - api_args['start_date'] = form.start_date.data - api_args['end_date'] = form.end_date.data or datetime.utcnow().date() + api_args["start_date"] = form.start_date.data + api_args["end_date"] = form.end_date.data or datetime.utcnow().date() platform_stats = platform_stats_api_client.get_aggregate_platform_stats(api_args) number_of_complaints = complaint_api_client.get_complaint_count(api_args) return render_template( - 'views/platform-admin/index.html', + "views/platform-admin/index.html", form=form, - global_stats=make_columns(platform_stats, number_of_complaints) + global_stats=make_columns(platform_stats, number_of_complaints), ) @@ -77,20 +77,18 @@ def is_over_threshold(number, total, threshold): def get_status_box_data(stats, key, label, threshold=FAILURE_THRESHOLD): return { - 'number': "{:,}".format(stats['failures'][key]), - 'label': label, - 'failing': is_over_threshold( - stats['failures'][key], - stats['total'], - threshold - ), - 'percentage': get_formatted_percentage(stats['failures'][key], stats['total']) + "number": "{:,}".format(stats["failures"][key]), + "label": label, + "failing": is_over_threshold(stats["failures"][key], stats["total"], threshold), + "percentage": get_formatted_percentage(stats["failures"][key], stats["total"]), } def get_tech_failure_status_box_data(stats): - stats = get_status_box_data(stats, 'technical-failure', 'technical failures', ZERO_FAILURE_THRESHOLD) - stats.pop('percentage') + stats = get_status_box_data( + stats, "technical-failure", "technical failures", ZERO_FAILURE_THRESHOLD + ) + stats.pop("percentage") return stats @@ -98,83 +96,99 @@ def make_columns(global_stats, complaints_number): return [ # email { - 'black_box': { - 'number': global_stats['email']['total'], - 'notification_type': 'email' + "black_box": { + "number": global_stats["email"]["total"], + "notification_type": "email", }, - 'other_data': [ - get_tech_failure_status_box_data(global_stats['email']), - get_status_box_data(global_stats['email'], 'permanent-failure', 'permanent failures'), - get_status_box_data(global_stats['email'], 'temporary-failure', 'temporary failures'), + "other_data": [ + get_tech_failure_status_box_data(global_stats["email"]), + get_status_box_data( + global_stats["email"], "permanent-failure", "permanent failures" + ), + get_status_box_data( + global_stats["email"], "temporary-failure", "temporary failures" + ), { - 'number': complaints_number, - 'label': 'complaints', - 'failing': is_over_threshold(complaints_number, - global_stats['email']['total'], COMPLAINT_THRESHOLD), - 'percentage': get_formatted_percentage_two_dp(complaints_number, global_stats['email']['total']), - 'url': url_for('main.platform_admin_list_complaints') - } + "number": complaints_number, + "label": "complaints", + "failing": is_over_threshold( + complaints_number, + global_stats["email"]["total"], + COMPLAINT_THRESHOLD, + ), + "percentage": get_formatted_percentage_two_dp( + complaints_number, global_stats["email"]["total"] + ), + "url": url_for("main.platform_admin_list_complaints"), + }, ], - 'test_data': { - 'number': global_stats['email']['test-key'], - 'label': 'test emails' - } + "test_data": { + "number": global_stats["email"]["test-key"], + "label": "test emails", + }, }, # sms { - 'black_box': { - 'number': global_stats['sms']['total'], - 'notification_type': 'sms' + "black_box": { + "number": global_stats["sms"]["total"], + "notification_type": "sms", }, - 'other_data': [ - get_tech_failure_status_box_data(global_stats['sms']), - get_status_box_data(global_stats['sms'], 'permanent-failure', 'permanent failures'), - get_status_box_data(global_stats['sms'], 'temporary-failure', 'temporary failures') + "other_data": [ + get_tech_failure_status_box_data(global_stats["sms"]), + get_status_box_data( + global_stats["sms"], "permanent-failure", "permanent failures" + ), + get_status_box_data( + global_stats["sms"], "temporary-failure", "temporary failures" + ), ], - 'test_data': { - 'number': global_stats['sms']['test-key'], - 'label': 'test text messages' - } + "test_data": { + "number": global_stats["sms"]["test-key"], + "label": "test text messages", + }, }, ] -@main.route("/platform-admin/live-services", endpoint='live_services') -@main.route("/platform-admin/trial-services", endpoint='trial_services') +@main.route("/platform-admin/live-services", endpoint="live_services") +@main.route("/platform-admin/trial-services", endpoint="trial_services") @user_is_platform_admin def platform_admin_services(): form = DateFilterForm(request.args) - if all(( - request.args.get('include_from_test_key') is None, - request.args.get('start_date') is None, - request.args.get('end_date') is None, - )): + if all( + ( + request.args.get("include_from_test_key") is None, + request.args.get("start_date") is None, + request.args.get("end_date") is None, + ) + ): # Default to True if the user hasn’t done any filtering, # otherwise respect their choice form.include_from_test_key.data = True include_from_test_key = form.include_from_test_key.data - api_args = {'detailed': True, - 'only_active': False, # specifically DO get inactive services - 'include_from_test_key': include_from_test_key, - } + api_args = { + "detailed": True, + "only_active": False, # specifically DO get inactive services + "include_from_test_key": include_from_test_key, + } if form.start_date.data: - api_args['start_date'] = form.start_date.data - api_args['end_date'] = form.end_date.data or datetime.utcnow().date() + api_args["start_date"] = form.start_date.data + api_args["end_date"] = form.end_date.data or datetime.utcnow().date() services = filter_and_sort_services( - service_api_client.get_services(api_args)['data'], - trial_mode_services=request.endpoint == 'main.trial_services', + service_api_client.get_services(api_args)["data"], + trial_mode_services=request.endpoint == "main.trial_services", ) return render_template( - 'views/platform-admin/services.html', + "views/platform-admin/services.html", include_from_test_key=include_from_test_key, form=form, services=list(format_stats_by_service(services)), - page_title='{} services'.format( - 'Trial mode' if request.endpoint == 'main.trial_services' else 'Live' + page_title="{} services".format( + "Trial mode" if request.endpoint == "main.trial_services" else "Live" ), global_stats=create_global_stats(services), ) @@ -183,9 +197,7 @@ def platform_admin_services(): @main.route("/platform-admin/reports") @user_is_platform_admin def platform_admin_reports(): - return render_template( - 'views/platform-admin/reports.html' - ) + return render_template("views/platform-admin/reports.html") @main.route("/platform-admin/reports/live-services.csv") @@ -193,41 +205,51 @@ def platform_admin_reports(): def live_services_csv(): results = service_api_client.get_live_services_data()["data"] - column_names = OrderedDict([ - ('service_id', 'Service ID'), - ('organization_name', 'Organization'), - ('organization_type', 'Organization type'), - ('service_name', 'Service name'), - ('consent_to_research', 'Consent to research'), - ('contact_name', 'Main contact'), - ('contact_email', 'Contact email'), - ('contact_mobile', 'Contact mobile'), - ('live_date', 'Live date'), - ('sms_volume_intent', 'SMS volume intent'), - ('email_volume_intent', 'Email volume intent'), - ('sms_totals', 'SMS sent this year'), - ('email_totals', 'Emails sent this year'), - ('free_sms_fragment_limit', 'Free sms allowance'), - ]) + column_names = OrderedDict( + [ + ("service_id", "Service ID"), + ("organization_name", "Organization"), + ("organization_type", "Organization type"), + ("service_name", "Service name"), + ("consent_to_research", "Consent to research"), + ("contact_name", "Main contact"), + ("contact_email", "Contact email"), + ("contact_mobile", "Contact mobile"), + ("live_date", "Live date"), + ("sms_volume_intent", "SMS volume intent"), + ("email_volume_intent", "Email volume intent"), + ("sms_totals", "SMS sent this year"), + ("email_totals", "Emails sent this year"), + ("free_sms_fragment_limit", "Free sms allowance"), + ] + ) # initialise with header row live_services_data = [[x for x in column_names.values()]] for row in results: - if row['live_date']: - row['live_date'] = datetime.strptime(row["live_date"], '%a, %d %b %Y %X %Z').strftime("%d-%m-%Y") + if row["live_date"]: + row["live_date"] = datetime.strptime( + row["live_date"], "%a, %d %b %Y %X %Z" + ).strftime("%d-%m-%Y") live_services_data.append([row[api_key] for api_key in column_names.keys()]) - return Spreadsheet.from_rows(live_services_data).as_csv_data, 200, { - 'Content-Type': 'text/csv; charset=utf-8', - 'Content-Disposition': 'inline; filename="{} live services report.csv"'.format( - format_date_numeric(datetime.now().strftime("%Y-%m-%dT%H:%M:%S.%fZ")), - ) - } + return ( + Spreadsheet.from_rows(live_services_data).as_csv_data, + 200, + { + "Content-Type": "text/csv; charset=utf-8", + "Content-Disposition": 'inline; filename="{} live services report.csv"'.format( + format_date_numeric(datetime.now().strftime("%Y-%m-%dT%H:%M:%S.%fZ")), + ), + }, + ) -@main.route("/platform-admin/reports/notifications-sent-by-service", methods=['GET', 'POST']) +@main.route( + "/platform-admin/reports/notifications-sent-by-service", methods=["GET", "POST"] +) @user_is_platform_admin def notifications_sent_by_service(): form = RequiredDateFilterForm() @@ -237,21 +259,39 @@ def notifications_sent_by_service(): end_date = form.end_date.data headers = [ - 'date_created', 'service_id', 'service_name', 'notification_type', 'count_sending', 'count_delivered', - 'count_technical_failure', 'count_temporary_failure', 'count_permanent_failure', 'count_sent' + "date_created", + "service_id", + "service_name", + "notification_type", + "count_sending", + "count_delivered", + "count_technical_failure", + "count_temporary_failure", + "count_permanent_failure", + "count_sent", ] - result = notification_api_client.get_notification_status_by_service(start_date, end_date) + result = notification_api_client.get_notification_status_by_service( + start_date, end_date + ) + content_disposition = ( + 'attachment; filename="{} to {} notification status ' + 'per service report.csv"'.format(start_date, end_date) + ) + return ( + Spreadsheet.from_rows([headers] + result).as_csv_data, + 200, + { + "Content-Type": "text/csv; charset=utf-8", + "Content-Disposition": content_disposition, + }, + ) - return Spreadsheet.from_rows([headers] + result).as_csv_data, 200, { - 'Content-Type': 'text/csv; charset=utf-8', - 'Content-Disposition': 'attachment; filename="{} to {} notification status per service report.csv"'.format( - start_date, end_date) - } - - return render_template('views/platform-admin/notifications_by_service.html', form=form) + return render_template( + "views/platform-admin/notifications_by_service.html", form=form + ) -@main.route("/platform-admin/reports/usage-for-all-services", methods=['GET', 'POST']) +@main.route("/platform-admin/reports/usage-for-all-services", methods=["GET", "POST"]) @user_is_platform_admin def get_billing_report(): form = BillingReportDateFilterForm() @@ -260,46 +300,71 @@ def get_billing_report(): start_date = form.start_date.data end_date = form.end_date.data headers = [ - "organization_id", "organization_name", "service_id", "service_name", - "sms_cost", "sms_chargeable_units", - "purchase_order_number", "contact_names", "contact_email_addresses", "billing_reference" + "organization_id", + "organization_name", + "service_id", + "service_name", + "sms_cost", + "sms_chargeable_units", + "purchase_order_number", + "contact_names", + "contact_email_addresses", + "billing_reference", ] try: - result = billing_api_client.get_data_for_billing_report(start_date, end_date) + result = billing_api_client.get_data_for_billing_report( + start_date, end_date + ) except HTTPError as e: - message = 'Date must be in a single financial year.' + message = "Date must be in a single financial year." if e.status_code == 400 and e.message == message: flash(message) - return render_template('views/platform-admin/get-billing-report.html', form=form) + return render_template( + "views/platform-admin/get-billing-report.html", form=form + ) else: raise e rows = [ [ - r["organization_id"], r["organization_name"], r["service_id"], r["service_name"], - r["sms_cost"], r["sms_chargeable_units"], - r.get("purchase_order_number"), r.get("contact_names"), - r.get("contact_email_addresses"), r.get("billing_reference") + r["organization_id"], + r["organization_name"], + r["service_id"], + r["service_name"], + r["sms_cost"], + r["sms_chargeable_units"], + r.get("purchase_order_number"), + r.get("contact_names"), + r.get("contact_email_addresses"), + r.get("billing_reference"), ] for r in result ] if rows: - return Spreadsheet.from_rows([headers] + rows).as_csv_data, 200, { - 'Content-Type': 'text/csv; charset=utf-8', - 'Content-Disposition': 'attachment; filename="Billing Report from {} to {}.csv"'.format( - start_date, end_date - ) - } + return ( + Spreadsheet.from_rows([headers] + rows).as_csv_data, + 200, + { + "Content-Type": "text/csv; charset=utf-8", + "Content-Disposition": 'attachment; filename="Billing Report from {} to {}.csv"'.format( + start_date, end_date + ), + }, + ) else: - flash('No results for dates') - return render_template('views/platform-admin/get-billing-report.html', form=form) + flash("No results for dates") + return render_template("views/platform-admin/get-billing-report.html", form=form) -@main.route("/platform-admin/reports/get-users-report", methods=['GET', 'POST']) +@main.route("/platform-admin/reports/get-users-report", methods=["GET", "POST"]) @user_is_platform_admin def get_users_report(): headers = [ - "name", "services", "platform admin", "permissions", "password changed at", - "state" + "name", + "services", + "platform admin", + "permissions", + "password changed at", + "state", ] try: result = user_api_client.get_all_users() @@ -311,16 +376,20 @@ def get_users_report(): for r in result: rows.append(_get_user_row(r)) if rows: - return Spreadsheet.from_rows([headers] + rows).as_csv_data, 200, { - 'Content-Type': 'text/csv; charset=utf-8', - 'Content-Disposition': f'attachment; filename="User Report {datetime.utcnow()}.csv"' - } + return ( + Spreadsheet.from_rows([headers] + rows).as_csv_data, + 200, + { + "Content-Type": "text/csv; charset=utf-8", + "Content-Disposition": f'attachment; filename="User Report {datetime.utcnow()}.csv"', + }, + ) else: - flash('No results') - return render_template('views/platform-admin/get-users-report.html') + flash("No results") + return render_template("views/platform-admin/get-users-report.html") -@main.route("/platform-admin/reports/volumes-by-service", methods=['GET', 'POST']) +@main.route("/platform-admin/reports/volumes-by-service", methods=["GET", "POST"]) @user_is_platform_admin def get_volumes_by_service(): form = BillingReportDateFilterForm() @@ -329,31 +398,51 @@ def get_volumes_by_service(): start_date = form.start_date.data end_date = form.end_date.data headers = [ - "organization id", "organization name", "service id", "service name", - "free allowance", "sms notifications", "sms chargeable units", "email totals", + "organization id", + "organization name", + "service id", + "service name", + "free allowance", + "sms notifications", + "sms chargeable units", + "email totals", ] - result = billing_api_client.get_data_for_volumes_by_service_report(start_date, end_date) + result = billing_api_client.get_data_for_volumes_by_service_report( + start_date, end_date + ) rows = [ [ - r["organization_id"], r["organization_name"], r["service_id"], r["service_name"], - r["free_allowance"], r["sms_notifications"], r["sms_chargeable_units"], r["email_totals"], + r["organization_id"], + r["organization_name"], + r["service_id"], + r["service_name"], + r["free_allowance"], + r["sms_notifications"], + r["sms_chargeable_units"], + r["email_totals"], ] for r in result ] if rows: - return Spreadsheet.from_rows([headers] + rows).as_csv_data, 200, { - 'Content-Type': 'text/csv; charset=utf-8', - 'Content-Disposition': 'attachment; filename="Volumes by service report from {} to {}.csv"'.format( - start_date, end_date - ) - } + return ( + Spreadsheet.from_rows([headers] + rows).as_csv_data, + 200, + { + "Content-Type": "text/csv; charset=utf-8", + "Content-Disposition": 'attachment; filename="Volumes by service report from {} to {}.csv"'.format( + start_date, end_date + ), + }, + ) else: - flash('No results for dates') - return render_template('views/platform-admin/volumes-by-service-report.html', form=form) + flash("No results for dates") + return render_template( + "views/platform-admin/volumes-by-service-report.html", form=form + ) -@main.route("/platform-admin/reports/daily-volumes-report", methods=['GET', 'POST']) +@main.route("/platform-admin/reports/daily-volumes-report", methods=["GET", "POST"]) @user_is_platform_admin def get_daily_volumes(): form = BillingReportDateFilterForm() @@ -362,31 +451,45 @@ def get_daily_volumes(): start_date = form.start_date.data end_date = form.end_date.data headers = [ - "day", "sms totals", "sms fragment totals", "sms chargeable units", + "day", + "sms totals", + "sms fragment totals", + "sms chargeable units", "email totals", ] - result = billing_api_client.get_data_for_daily_volumes_report(start_date, end_date) + result = billing_api_client.get_data_for_daily_volumes_report( + start_date, end_date + ) rows = [ [ - r["day"], r["sms_totals"], r["sms_fragment_totals"], r["sms_chargeable_units"], + r["day"], + r["sms_totals"], + r["sms_fragment_totals"], + r["sms_chargeable_units"], r["email_totals"], ] for r in result ] if rows: - return Spreadsheet.from_rows([headers] + rows).as_csv_data, 200, { - 'Content-Type': 'text/csv; charset=utf-8', - 'Content-Disposition': 'attachment; filename="Daily volumes report from {} to {}.csv"'.format( - start_date, end_date - ) - } + return ( + Spreadsheet.from_rows([headers] + rows).as_csv_data, + 200, + { + "Content-Type": "text/csv; charset=utf-8", + "Content-Disposition": 'attachment; filename="Daily volumes report from {} to {}.csv"'.format( + start_date, end_date + ), + }, + ) else: - flash('No results for dates') - return render_template('views/platform-admin/daily-volumes-report.html', form=form) + flash("No results for dates") + return render_template("views/platform-admin/daily-volumes-report.html", form=form) -@main.route("/platform-admin/reports/daily-sms-provider-volumes-report", methods=['GET', 'POST']) +@main.route( + "/platform-admin/reports/daily-sms-provider-volumes-report", methods=["GET", "POST"] +) @user_is_platform_admin def get_daily_sms_provider_volumes(): form = BillingReportDateFilterForm() @@ -402,7 +505,9 @@ def get_daily_sms_provider_volumes(): "sms chargeable units", "sms cost", ] - result = billing_api_client.get_data_for_daily_sms_provider_volumes_report(start_date, end_date) + result = billing_api_client.get_data_for_daily_sms_provider_volumes_report( + start_date, end_date + ) rows = [ [ @@ -411,19 +516,25 @@ def get_daily_sms_provider_volumes(): r["sms_totals"], r["sms_fragment_totals"], r["sms_chargeable_units"], - r["sms_cost"] + r["sms_cost"], ] for r in result ] + content_disp = f'attachment; filename="Daily SMS provider volumes report from {start_date} to {end_date}.csv"' if rows: - return Spreadsheet.from_rows([headers] + rows).as_csv_data, 200, { - 'Content-Type': 'text/csv; charset=utf-8', - 'Content-Disposition': - f'attachment; filename="Daily SMS provider volumes report from {start_date} to {end_date}.csv"' - } + return ( + Spreadsheet.from_rows([headers] + rows).as_csv_data, + 200, + { + "Content-Type": "text/csv; charset=utf-8", + "Content-Disposition": content_disp, + }, + ) else: - flash('No results for dates') - return render_template('views/platform-admin/daily-sms-provider-volumes-report.html', form=form) + flash("No results for dates") + return render_template( + "views/platform-admin/daily-sms-provider-volumes-report.html", form=form + ) @main.route("/platform-admin/complaints") @@ -431,62 +542,83 @@ def get_daily_sms_provider_volumes(): def platform_admin_list_complaints(): page = get_page_from_request() if page is None: - abort(404, "Invalid page argument ({}).".format(request.args.get('page'))) + abort(404, "Invalid page argument ({}).".format(request.args.get("page"))) response = complaint_api_client.get_all_complaints(page=page) prev_page = None - if response['links'].get('prev'): - prev_page = generate_previous_dict('main.platform_admin_list_complaints', None, page) + if response["links"].get("prev"): + prev_page = generate_previous_dict( + "main.platform_admin_list_complaints", None, page + ) next_page = None - if response['links'].get('next'): - next_page = generate_next_dict('main.platform_admin_list_complaints', None, page) + if response["links"].get("next"): + next_page = generate_next_dict( + "main.platform_admin_list_complaints", None, page + ) return render_template( - 'views/platform-admin/complaints.html', - complaints=response['complaints'], + "views/platform-admin/complaints.html", + complaints=response["complaints"], page=page, prev_page=prev_page, next_page=next_page, ) -@main.route("/platform-admin/clear-cache", methods=['GET', 'POST']) +@main.route("/platform-admin/clear-cache", methods=["GET", "POST"]) @user_is_platform_admin def clear_cache(): # note: `service-{uuid}-templates` cache is cleared for both services and templates. - CACHE_KEYS = OrderedDict([ - ('user', [ - 'user-????????-????-????-????-????????????', - ]), - ('service', [ - 'has_jobs-????????-????-????-????-????????????', - 'service-????????-????-????-????-????????????', - 'service-????????-????-????-????-????????????-templates', - 'service-????????-????-????-????-????????????-data-retention', - 'service-????????-????-????-????-????????????-template-folders', - ]), - ('template', [ - 'service-????????-????-????-????-????????????-templates', - 'service-????????-????-????-????-????????????-template-????????-????-????-????-????????????-version-*', - 'service-????????-????-????-????-????????????-template-????????-????-????-????-????????????-versions', - ]), - ('email_branding', [ - 'email_branding', - 'email_branding-????????-????-????-????-????????????', - ]), - ('organization', [ - 'organizations', - 'domains', - 'live-service-and-organization-counts', - 'organization-????????-????-????-????-????????????-name', - ]), - ]) + CACHE_KEYS = OrderedDict( + [ + ( + "user", + [ + "user-????????-????-????-????-????????????", + ], + ), + ( + "service", + [ + "has_jobs-????????-????-????-????-????????????", + "service-????????-????-????-????-????????????", + "service-????????-????-????-????-????????????-templates", + "service-????????-????-????-????-????????????-data-retention", + "service-????????-????-????-????-????????????-template-folders", + ], + ), + ( + "template", + [ + "service-????????-????-????-????-????????????-templates", + "service-????????-????-????-????-????????????-template-????????-????-????-????-????????????-version-*", # noqa + "service-????????-????-????-????-????????????-template-????????-????-????-????-????????????-versions", # noqa + ], + ), + ( + "email_branding", + [ + "email_branding", + "email_branding-????????-????-????-????-????????????", + ], + ), + ( + "organization", + [ + "organizations", + "domains", + "live-service-and-organization-counts", + "organization-????????-????-????-????-????????????-name", + ], + ), + ] + ) form = AdminClearCacheForm() form.model_type.choices = [ - (key, key.replace('_', ' ').title()) for key in CACHE_KEYS + (key, key.replace("_", " ").title()) for key in CACHE_KEYS ] if form.validate_on_submit(): @@ -495,83 +627,75 @@ def clear_cache(): patterns = list(itertools.chain(*groups)) num_deleted = sum( - redis_client.delete_by_pattern(pattern) - for pattern in patterns + redis_client.delete_by_pattern(pattern) for pattern in patterns ) msg = ( - f'Removed {num_deleted} objects ' - f'across {len(patterns)} key formats ' + f"Removed {num_deleted} objects " + f"across {len(patterns)} key formats " f'for {", ".join(group_keys)}' ) - flash(msg, category='default') + flash(msg, category="default") - return render_template( - 'views/platform-admin/clear-cache.html', - form=form - ) + return render_template("views/platform-admin/clear-cache.html", form=form) def sum_service_usage(service): total = 0 - for notification_type in service['statistics'].keys(): - total += service['statistics'][notification_type]['requested'] + for notification_type in service["statistics"].keys(): + total += service["statistics"][notification_type]["requested"] return total def filter_and_sort_services(services, trial_mode_services=False): return [ - service for service in sorted( + service + for service in sorted( services, key=lambda service: ( - service['active'], + service["active"], sum_service_usage(service), - service['created_at'] + service["created_at"], ), reverse=True, ) - if service['restricted'] == trial_mode_services + if service["restricted"] == trial_mode_services ] def create_global_stats(services): stats = { - 'email': { - 'delivered': 0, - 'failed': 0, - 'requested': 0 - }, - 'sms': { - 'delivered': 0, - 'failed': 0, - 'requested': 0 - }, + "email": {"delivered": 0, "failed": 0, "requested": 0}, + "sms": {"delivered": 0, "failed": 0, "requested": 0}, } for service in services: - for msg_type, status in itertools.product(('sms', 'email'), ('delivered', 'failed', 'requested')): - stats[msg_type][status] += service['statistics'][msg_type][status] + for msg_type, status in itertools.product( + ("sms", "email"), ("delivered", "failed", "requested") + ): + stats[msg_type][status] += service["statistics"][msg_type][status] for stat in stats.values(): - stat['failure_rate'] = get_formatted_percentage(stat['failed'], stat['requested']) + stat["failure_rate"] = get_formatted_percentage( + stat["failed"], stat["requested"] + ) return stats def format_stats_by_service(services): for service in services: yield { - 'id': service['id'], - 'name': service['name'], - 'stats': service['statistics'], - 'restricted': service['restricted'], - 'research_mode': service['research_mode'], - 'created_at': service['created_at'], - 'active': service['active'] + "id": service["id"], + "name": service["name"], + "stats": service["statistics"], + "restricted": service["restricted"], + "research_mode": service["research_mode"], + "created_at": service["created_at"], + "active": service["active"], } def _get_user_row(r): - # [{ # 'name': 'Kenneth Kehl', # 'organizations': [], @@ -581,20 +705,20 @@ def _get_user_row(r): # 'platform_admin': True, 'services': ['672b8a66-e22e-40f6-b1e5-39cc1c6bf857'], 'state': 'active'}] row = [] - row.append(r['name']) + row.append(r["name"]) service_id_name_lookup = {} services = [] - for s in r['services']: + for s in r["services"]: my_service = service_api_client.get_service(s) - service_id_name_lookup[my_service['data']['id']] = my_service['data']['name'] - services.append(my_service['data']['name']) + service_id_name_lookup[my_service["data"]["id"]] = my_service["data"]["name"] + services.append(my_service["data"]["name"]) services = str(services) services = services.replace("[", "") services = services.replace("]", "") row.append(services) - row.append(r['platform_admin']) - permissions = r['permissions'] + row.append(r["platform_admin"]) + permissions = r["permissions"] for k, v in service_id_name_lookup.items(): if permissions.get(k): permissions[v] = permissions[k] @@ -602,6 +726,6 @@ def _get_user_row(r): permissions = json.dumps(permissions, indent=4) row.append(permissions) - row.append(r['password_changed_at']) - row.append(r['state']) + row.append(r["password_changed_at"]) + row.append(r["state"]) return row diff --git a/app/main/views/pricing.py b/app/main/views/pricing.py index a5a325d7d..0e5fb361d 100644 --- a/app/main/views/pricing.py +++ b/app/main/views/pricing.py @@ -7,43 +7,46 @@ from app.main.forms import SearchByNameForm from app.main.views.sub_navigation_dictionaries import using_notify_nav from app.utils.user import user_is_logged_in -CURRENT_SMS_RATE = '1.72' +CURRENT_SMS_RATE = "1.72" -@main.route('/using-notify/pricing') +@main.route("/using-notify/pricing") @user_is_logged_in def pricing(): return render_template( - 'views/pricing/index.html', + "views/pricing/index.html", sms_rate=CURRENT_SMS_RATE, - international_sms_rates=sorted([ - (cc, country['names'], country['billable_units']) - for cc, country in INTERNATIONAL_BILLING_RATES.items() - ], key=lambda x: x[0]), + international_sms_rates=sorted( + [ + (cc, country["names"], country["billable_units"]) + for cc, country in INTERNATIONAL_BILLING_RATES.items() + ], + key=lambda x: x[0], + ), search_form=SearchByNameForm(), navigation_links=using_notify_nav(), ) -@main.route('/pricing/how-to-pay') +@main.route("/pricing/how-to-pay") @user_is_logged_in def how_to_pay(): return render_template( - 'views/pricing/how-to-pay.html', + "views/pricing/how-to-pay.html", navigation_links=using_notify_nav(), ) -@main.route('/pricing/billing-details') +@main.route("/pricing/billing-details") @user_is_logged_in def billing_details(): if current_user.is_authenticated: return render_template( - 'views/pricing/billing-details.html', - billing_details=current_app.config['NOTIFY_BILLING_DETAILS'], + "views/pricing/billing-details.html", + billing_details=current_app.config["NOTIFY_BILLING_DETAILS"], navigation_links=using_notify_nav(), ) return render_template( - 'views/pricing/billing-details-signed-out.html', + "views/pricing/billing-details-signed-out.html", navigation_links=using_notify_nav(), ) diff --git a/app/main/views/providers.py b/app/main/views/providers.py index 08dfa0060..fb4b024ee 100644 --- a/app/main/views/providers.py +++ b/app/main/views/providers.py @@ -14,55 +14,59 @@ PROVIDER_PRIORITY_MEANING_SWITCHOVER = datetime(2019, 11, 29, 11, 0).isoformat() @main.route("/providers") @user_is_platform_admin def view_providers(): - providers = provider_client.get_all_providers()['provider_details'] + providers = provider_client.get_all_providers()["provider_details"] domestic_email_providers, domestic_sms_providers, intl_sms_providers = [], [], [] for provider in providers: - if provider['notification_type'] == 'sms': + if provider["notification_type"] == "sms": domestic_sms_providers.append(provider) - if provider.get('supports_international', None): + if provider.get("supports_international", None): intl_sms_providers.append(provider) - elif provider['notification_type'] == 'email': + elif provider["notification_type"] == "email": domestic_email_providers.append(provider) add_monthly_traffic(domestic_sms_providers) return render_template( - 'views/providers/providers.html', + "views/providers/providers.html", email_providers=domestic_email_providers, domestic_sms_providers=domestic_sms_providers, - intl_sms_providers=intl_sms_providers + intl_sms_providers=intl_sms_providers, ) def add_monthly_traffic(domestic_sms_providers): - total_sms_sent = sum(provider['current_month_billable_sms'] for provider in domestic_sms_providers) + total_sms_sent = sum( + provider["current_month_billable_sms"] for provider in domestic_sms_providers + ) for provider in domestic_sms_providers: - percentage = (provider['current_month_billable_sms'] / total_sms_sent * 100) if total_sms_sent else 0 - provider['monthly_traffic'] = round(percentage) + percentage = ( + (provider["current_month_billable_sms"] / total_sms_sent * 100) + if total_sms_sent + else 0 + ) + provider["monthly_traffic"] = round(percentage) -@main.route("/provider/edit-sms-provider-ratio", methods=['GET', 'POST']) +@main.route("/provider/edit-sms-provider-ratio", methods=["GET", "POST"]) @user_is_platform_admin def edit_sms_provider_ratio(): providers = [ provider - for provider in provider_client.get_all_providers()['provider_details'] - if provider['notification_type'] == 'sms' and provider['active'] + for provider in provider_client.get_all_providers()["provider_details"] + if provider["notification_type"] == "sms" and provider["active"] ] form = AdminProviderRatioForm(providers) if form.validate_on_submit(): for provider in providers: - field = getattr(form, provider['identifier']) - provider_client.update_provider(provider['id'], field.data) - return redirect(url_for('.view_providers')) + field = getattr(form, provider["identifier"]) + provider_client.update_provider(provider["id"], field.data) + return redirect(url_for(".view_providers")) return render_template( - 'views/providers/edit-sms-provider-ratio.html', - form=form, - providers=providers + "views/providers/edit-sms-provider-ratio.html", form=form, providers=providers ) @@ -70,4 +74,6 @@ def edit_sms_provider_ratio(): @user_is_platform_admin def view_provider(provider_id): versions = provider_client.get_provider_versions(provider_id) - return render_template('views/providers/provider.html', provider_versions=versions['data']) + return render_template( + "views/providers/provider.html", provider_versions=versions["data"] + ) diff --git a/app/main/views/register.py b/app/main/views/register.py index 51136989c..8d3809ae9 100644 --- a/app/main/views/register.py +++ b/app/main/views/register.py @@ -14,21 +14,21 @@ from app.models.user import InvitedOrgUser, InvitedUser, User from app.utils import hide_from_search_engines -@main.route('/register', methods=['GET', 'POST']) +@main.route("/register", methods=["GET", "POST"]) @hide_from_search_engines def register(): if current_user and current_user.is_authenticated: - return redirect(url_for('main.show_accounts_or_dashboard')) + return redirect(url_for("main.show_accounts_or_dashboard")) form = RegisterUserForm() if form.validate_on_submit(): _do_registration(form, send_sms=False) - return redirect(url_for('main.registration_continue')) + return redirect(url_for("main.registration_continue")) - return render_template('views/register.html', form=form) + return render_template("views/register.html", form=form) -@main.route('/register-from-invite', methods=['GET', 'POST']) +@main.route("/register-from-invite", methods=["GET", "POST"]) def register_from_invite(): invited_user = InvitedUser.from_session() if not invited_user: @@ -37,21 +37,26 @@ def register_from_invite(): form = RegisterUserFromInviteForm(invited_user) if form.validate_on_submit(): - if form.service.data != invited_user.service or form.email_address.data != invited_user.email_address: + if ( + form.service.data != invited_user.service + or form.email_address.data != invited_user.email_address + ): abort(400) _do_registration(form, send_email=False, send_sms=invited_user.sms_auth) invited_user.accept_invite() if invited_user.sms_auth: - return redirect(url_for('main.verify')) + return redirect(url_for("main.verify")) else: # we've already proven this user has email because they clicked the invite link, # so just activate them straight away - return activate_user(session['user_details']['id']) + return activate_user(session["user_details"]["id"]) - return render_template('views/register-from-invite.html', invited_user=invited_user, form=form) + return render_template( + "views/register-from-invite.html", invited_user=invited_user, form=form + ) -@main.route('/register-from-org-invite', methods=['GET', 'POST']) +@main.route("/register-from-org-invite", methods=["GET", "POST"]) def register_from_org_invite(): invited_org_user = InvitedOrgUser.from_session() if not invited_org_user: @@ -60,17 +65,28 @@ def register_from_org_invite(): form = RegisterUserFromOrgInviteForm( invited_org_user, ) - form.auth_type.data = 'sms_auth' + form.auth_type.data = "sms_auth" if form.validate_on_submit(): - if (form.organization.data != invited_org_user.organization or - form.email_address.data != invited_org_user.email_address): + if ( + form.organization.data != invited_org_user.organization + or form.email_address.data != invited_org_user.email_address + ): abort(400) - _do_registration(form, send_email=False, send_sms=True, organization_id=invited_org_user.organization) + _do_registration( + form, + send_email=False, + send_sms=True, + organization_id=invited_org_user.organization, + ) invited_org_user.accept_invite() - return redirect(url_for('main.verify')) - return render_template('views/register-from-org-invite.html', invited_org_user=invited_org_user, form=form) + return redirect(url_for("main.verify")) + return render_template( + "views/register-from-org-invite.html", + invited_org_user=invited_org_user, + form=form, + ) def _do_registration(form, send_sms=True, send_email=True, organization_id=None): @@ -78,8 +94,8 @@ def _do_registration(form, send_sms=True, send_email=True, organization_id=None) if user: if send_email: user.send_already_registered_email() - session['expiry_date'] = str(datetime.utcnow() + timedelta(hours=1)) - session['user_details'] = {"email": user.email_address, "id": user.id} + session["expiry_date"] = str(datetime.utcnow() + timedelta(hours=1)) + session["user_details"] = {"email": user.email_address, "id": user.id} else: user = User.register( name=form.name.data, @@ -94,14 +110,14 @@ def _do_registration(form, send_sms=True, send_email=True, organization_id=None) if send_sms: user.send_verify_code() - session['expiry_date'] = str(datetime.utcnow() + timedelta(hours=1)) - session['user_details'] = {"email": user.email_address, "id": user.id} + session["expiry_date"] = str(datetime.utcnow() + timedelta(hours=1)) + session["user_details"] = {"email": user.email_address, "id": user.id} if organization_id: - session['organization_id'] = organization_id + session["organization_id"] = organization_id -@main.route('/registration-continue') +@main.route("/registration-continue") def registration_continue(): - if not session.get('user_details'): - return redirect(url_for('.show_accounts_or_dashboard')) - return render_template('views/registration-continue.html') + if not session.get("user_details"): + return redirect(url_for(".show_accounts_or_dashboard")) + return render_template("views/registration-continue.html") diff --git a/app/main/views/security_policy.py b/app/main/views/security_policy.py index b4d284c0f..35ffd359e 100644 --- a/app/main/views/security_policy.py +++ b/app/main/views/security_policy.py @@ -3,8 +3,8 @@ from flask import redirect from app.main import main -@main.route('/.well-known/security.txt', methods=['GET']) -@main.route('/security.txt', methods=['GET']) +@main.route("/.well-known/security.txt", methods=["GET"]) +@main.route("/security.txt", methods=["GET"]) def security_policy(): # See GDS Way security policy which this implements # https://gds-way.cloudapps.digital/standards/vulnerability-disclosure.html#vulnerability-disclosure-and-security-txt diff --git a/app/main/views/send.py b/app/main/views/send.py index c4b56c301..87b795cd1 100644 --- a/app/main/views/send.py +++ b/app/main/views/send.py @@ -59,44 +59,56 @@ def get_example_csv_fields(column_headers, use_example_as_example, submitted_fie def get_example_csv_rows(template, use_example_as_example=True, submitted_fields=False): return { - 'email': ['test@example.com'] if use_example_as_example else [current_user.email_address], - 'sms': ['12223334444'] if use_example_as_example else [current_user.mobile_number], + "email": ["test@example.com"] + if use_example_as_example + else [current_user.email_address], + "sms": ["12223334444"] + if use_example_as_example + else [current_user.mobile_number], }[template.template_type] + get_example_csv_fields( ( - placeholder for placeholder in template.placeholders - if placeholder not in InsensitiveDict.from_keys( + placeholder + for placeholder in template.placeholders + if placeholder + not in InsensitiveDict.from_keys( first_column_headings[template.template_type] ) ), use_example_as_example, - submitted_fields + submitted_fields, ) -@main.route("/services//send//csv", methods=['GET', 'POST']) -@user_has_permissions('send_messages', restrict_admin_usage=True) +@main.route( + "/services//send//csv", methods=["GET", "POST"] +) +@user_has_permissions("send_messages", restrict_admin_usage=True) def send_messages(service_id, template_id): notification_count = service_api_client.get_notification_count(service_id) remaining_messages = current_service.message_limit - notification_count - db_template = current_service.get_template_with_user_permission_or_403(template_id, current_user) + db_template = current_service.get_template_with_user_permission_or_403( + template_id, current_user + ) email_reply_to = None sms_sender = None - if db_template['template_type'] == 'email': + if db_template["template_type"] == "email": email_reply_to = get_email_reply_to_address_from_session() - elif db_template['template_type'] == 'sms': + elif db_template["template_type"] == "sms": sms_sender = get_sms_sender_from_session() - if db_template['template_type'] not in current_service.available_template_types: - return redirect(url_for( - '.action_blocked', - service_id=service_id, - notification_type=db_template['template_type'], - return_to='view_template', - template_id=template_id - )) + if db_template["template_type"] not in current_service.available_template_types: + return redirect( + url_for( + ".action_blocked", + service_id=service_id, + notification_type=db_template["template_type"], + return_to="view_template", + template_id=template_id, + ) + ) template = get_template( db_template, @@ -114,31 +126,32 @@ def send_messages(service_id, template_id): Spreadsheet.from_file_form(form).as_dict, ) file_name_metadata = unicode_truncate( - SanitiseASCII.encode(form.file.data.filename), - 1600 + SanitiseASCII.encode(form.file.data.filename), 1600 ) set_metadata_on_csv_upload( - service_id, - upload_id, - original_file_name=file_name_metadata + service_id, upload_id, original_file_name=file_name_metadata + ) + return redirect( + url_for( + ".check_messages", + service_id=service_id, + upload_id=upload_id, + template_id=template.id, + ) ) - return redirect(url_for( - '.check_messages', - service_id=service_id, - upload_id=upload_id, - template_id=template.id, - )) except (UnicodeDecodeError, BadZipFile, XLRDError): - flash('Could not read {}. Try using a different file format.'.format( - form.file.data.filename - )) - except (XLDateError): - flash(( - '{} contains numbers or dates that Notify cannot understand. ' - 'Try formatting all columns as ‘text’ or export your file as CSV.' - ).format( - form.file.data.filename - )) + flash( + "Could not read {}. Try using a different file format.".format( + form.file.data.filename + ) + ) + except XLDateError: + flash( + ( + "{} contains numbers or dates that Notify cannot understand. " + "Try formatting all columns as ‘text’ or export your file as CSV." + ).format(form.file.data.filename) + ) elif form.errors: # just show the first error, as we don't expect the form to have more # than one, since it only has one field @@ -148,184 +161,228 @@ def send_messages(service_id, template_id): column_headings = get_spreadsheet_column_headings_from_template(template) return render_template( - 'views/send.html', + "views/send.html", template=template, - column_headings=list(ascii_uppercase[:len(column_headings)]), + column_headings=list(ascii_uppercase[: len(column_headings)]), example=[column_headings, get_example_csv_rows(template)], form=form, allowed_file_extensions=Spreadsheet.ALLOWED_FILE_EXTENSIONS, - remaining_messages=remaining_messages + remaining_messages=remaining_messages, ) -@main.route("/services//send/.csv", methods=['GET']) -@user_has_permissions('send_messages', 'manage_templates') +@main.route("/services//send/.csv", methods=["GET"]) +@user_has_permissions("send_messages", "manage_templates") def get_example_csv(service_id, template_id): template = get_template( - service_api_client.get_service_template(service_id, template_id)['data'], current_service + service_api_client.get_service_template(service_id, template_id)["data"], + current_service, + ) + return ( + Spreadsheet.from_rows( + [ + get_spreadsheet_column_headings_from_template(template), + get_example_csv_rows(template), + ] + ).as_csv_data, + 200, + { + "Content-Type": "text/csv; charset=utf-8", + "Content-Disposition": 'inline; filename="{}.csv"'.format(template.name), + }, ) - return Spreadsheet.from_rows([ - get_spreadsheet_column_headings_from_template(template), - get_example_csv_rows(template) - ]).as_csv_data, 200, { - 'Content-Type': 'text/csv; charset=utf-8', - 'Content-Disposition': 'inline; filename="{}.csv"'.format(template.name) - } -@main.route("/services//send//set-sender", methods=['GET', 'POST']) -@user_has_permissions('send_messages', restrict_admin_usage=True) +@main.route( + "/services//send//set-sender", + methods=["GET", "POST"], +) +@user_has_permissions("send_messages", restrict_admin_usage=True) def set_sender(service_id, template_id): - session['sender_id'] = None + session["sender_id"] = None redirect_to_one_off = redirect( - url_for('.send_one_off', service_id=service_id, template_id=template_id) + url_for(".send_one_off", service_id=service_id, template_id=template_id) ) - template = current_service.get_template_with_user_permission_or_403(template_id, current_user) + template = current_service.get_template_with_user_permission_or_403( + template_id, current_user + ) - sender_details = get_sender_details(service_id, template['template_type']) + sender_details = get_sender_details(service_id, template["template_type"]) if len(sender_details) == 1: - session['sender_id'] = sender_details[0]['id'] + session["sender_id"] = sender_details[0]["id"] if len(sender_details) <= 1: return redirect_to_one_off - sender_context = get_sender_context(sender_details, template['template_type']) + sender_context = get_sender_context(sender_details, template["template_type"]) form = SetSenderForm( - sender=sender_context['default_id'], - sender_choices=sender_context['value_and_label'], - sender_label=sender_context['description'] + sender=sender_context["default_id"], + sender_choices=sender_context["value_and_label"], + sender_label=sender_context["description"], ) - option_hints = {sender_context['default_id']: '(Default)'} - if sender_context.get('receives_text_message', None): - option_hints.update({sender_context['receives_text_message']: '(Receives replies)'}) - if sender_context.get('default_and_receives', None): - option_hints = {sender_context['default_and_receives']: '(Default and receives replies)'} + option_hints = {sender_context["default_id"]: "(Default)"} + if sender_context.get("receives_text_message", None): + option_hints.update( + {sender_context["receives_text_message"]: "(Receives replies)"} + ) + if sender_context.get("default_and_receives", None): + option_hints = { + sender_context["default_and_receives"]: "(Default and receives replies)" + } # extend all radios that need hint text - form.sender.param_extensions = {'items': []} + form.sender.param_extensions = {"items": []} for item_id, _item_value in form.sender.choices: if item_id in option_hints: - extensions = {'hint': {'text': option_hints[item_id]}} + extensions = {"hint": {"text": option_hints[item_id]}} else: - extensions = {} # if no extensions needed, send an empty dict to preserve order of items - form.sender.param_extensions['items'].append(extensions) + extensions = ( + {} + ) # if no extensions needed, send an empty dict to preserve order of items + form.sender.param_extensions["items"].append(extensions) if form.validate_on_submit(): - session['sender_id'] = form.sender.data - return redirect(url_for('.send_one_off', - service_id=service_id, - template_id=template_id)) + session["sender_id"] = form.sender.data + return redirect( + url_for(".send_one_off", service_id=service_id, template_id=template_id) + ) return render_template( - 'views/templates/set-sender.html', + "views/templates/set-sender.html", form=form, template_id=template_id, - sender_context={'title': sender_context['title'], 'description': sender_context['description']}, - option_hints=option_hints + sender_context={ + "title": sender_context["title"], + "description": sender_context["description"], + }, + option_hints=option_hints, ) def get_sender_context(sender_details, template_type): context = { - 'email': { - 'title': 'Where should replies come back to?', - 'description': 'Where should replies come back to?', - 'field_name': 'email_address' + "email": { + "title": "Where should replies come back to?", + "description": "Where should replies come back to?", + "field_name": "email_address", + }, + "sms": { + "title": "Who should the message come from?", + "description": "Who should the message come from?", + "field_name": "sms_sender", }, - 'sms': { - 'title': 'Who should the message come from?', - 'description': 'Who should the message come from?', - 'field_name': 'sms_sender' - } }[template_type] - sender_format = context['field_name'] + sender_format = context["field_name"] - context['default_id'] = next(sender['id'] for sender in sender_details if sender['is_default']) - if template_type == 'sms': - inbound = [sender['id'] for sender in sender_details if sender['inbound_number_id']] + context["default_id"] = next( + sender["id"] for sender in sender_details if sender["is_default"] + ) + if template_type == "sms": + inbound = [ + sender["id"] for sender in sender_details if sender["inbound_number_id"] + ] if inbound: - context['receives_text_message'] = next(iter(inbound)) - if context['default_id'] == context.get('receives_text_message', None): - context['default_and_receives'] = context['default_id'] + context["receives_text_message"] = next(iter(inbound)) + if context["default_id"] == context.get("receives_text_message", None): + context["default_and_receives"] = context["default_id"] - context['value_and_label'] = [(sender['id'], nl2br(sender[sender_format])) for sender in sender_details] + context["value_and_label"] = [ + (sender["id"], nl2br(sender[sender_format])) for sender in sender_details + ] return context def get_sender_details(service_id, template_type): api_call = { - 'email': service_api_client.get_reply_to_email_addresses, - 'sms': service_api_client.get_sms_senders + "email": service_api_client.get_reply_to_email_addresses, + "sms": service_api_client.get_sms_senders, }[template_type] return api_call(service_id) @main.route("/services//send//one-off") -@user_has_permissions('send_messages', restrict_admin_usage=True) +@user_has_permissions("send_messages", restrict_admin_usage=True) def send_one_off(service_id, template_id): - session['recipient'] = None - session['placeholders'] = {} + session["recipient"] = None + session["placeholders"] = {} - db_template = current_service.get_template_with_user_permission_or_403(template_id, current_user) + db_template = current_service.get_template_with_user_permission_or_403( + template_id, current_user + ) - if db_template['template_type'] not in current_service.available_template_types: - return redirect(url_for( - '.action_blocked', + if db_template["template_type"] not in current_service.available_template_types: + return redirect( + url_for( + ".action_blocked", + service_id=service_id, + notification_type=db_template["template_type"], + return_to="view_template", + template_id=template_id, + ) + ) + + return redirect( + url_for( + ".send_one_off_step", service_id=service_id, - notification_type=db_template['template_type'], - return_to='view_template', - template_id=template_id)) - - return redirect(url_for( - '.send_one_off_step', - service_id=service_id, - template_id=template_id, - step_index=0, - )) + template_id=template_id, + step_index=0, + ) + ) def get_notification_check_endpoint(service_id, template): - return redirect(url_for( - 'main.check_notification', - service_id=service_id, - template_id=template.id, - )) + return redirect( + url_for( + "main.check_notification", + service_id=service_id, + template_id=template.id, + ) + ) @main.route( "/services//send//one-off/step-", - methods=['GET', 'POST'], + methods=["GET", "POST"], ) -@user_has_permissions('send_messages', restrict_admin_usage=True) +@user_has_permissions("send_messages", restrict_admin_usage=True) def send_one_off_step(service_id, template_id, step_index): - if {'recipient', 'placeholders'} - set(session.keys()): - return redirect(url_for( - ".send_one_off", - service_id=service_id, - template_id=template_id, - )) + if {"recipient", "placeholders"} - set(session.keys()): + return redirect( + url_for( + ".send_one_off", + service_id=service_id, + template_id=template_id, + ) + ) - db_template = current_service.get_template_with_user_permission_or_403(template_id, current_user) + db_template = current_service.get_template_with_user_permission_or_403( + template_id, current_user + ) email_reply_to = None sms_sender = None - if db_template['template_type'] == 'email': + if db_template["template_type"] == "email": email_reply_to = get_email_reply_to_address_from_session() - elif db_template['template_type'] == 'sms': - sms_sender = get_sms_sender_from_session() # TODO: verify default sender is U.S. Notify + elif db_template["template_type"] == "sms": + sms_sender = ( + get_sms_sender_from_session() + ) # TODO: verify default sender is U.S. Notify - template_values = get_recipient_and_placeholders_from_session(db_template['template_type']) + template_values = get_recipient_and_placeholders_from_session( + db_template["template_type"] + ) template = get_template( db_template, current_service, show_recipient=True, email_reply_to=email_reply_to, - sms_sender=sms_sender + sms_sender=sms_sender, ) placeholders = fields_to_fill_in(template) @@ -335,36 +392,42 @@ def send_one_off_step(service_id, template_id, step_index): except IndexError: if all_placeholders_in_session(placeholders): return get_notification_check_endpoint(service_id, template) - return redirect(url_for( - '.send_one_off', - service_id=service_id, - template_id=template_id, - )) + return redirect( + url_for( + ".send_one_off", + service_id=service_id, + template_id=template_id, + ) + ) form = get_placeholder_form_instance( current_placeholder, dict_to_populate_from=get_normalised_placeholders_from_session(), template_type=template.template_type, - allow_international_phone_numbers=current_service.has_permission('international_sms'), + allow_international_phone_numbers=current_service.has_permission( + "international_sms" + ), ) if form.validate_on_submit(): # if it's the first input (phone/email), we store against `recipient` as well, for easier extraction. # Only if we're not on the test route, since that will already have the user's own number set if step_index == 0: - session['recipient'] = form.placeholder_value.data + session["recipient"] = form.placeholder_value.data - session['placeholders'][current_placeholder] = form.placeholder_value.data + session["placeholders"][current_placeholder] = form.placeholder_value.data if all_placeholders_in_session(placeholders): return get_notification_check_endpoint(service_id, template) - return redirect(url_for( - request.endpoint, - service_id=service_id, - template_id=template_id, - step_index=step_index + 1, - )) + return redirect( + url_for( + request.endpoint, + service_id=service_id, + template_id=template_id, + step_index=step_index + 1, + ) + ) back_link = get_back_link(service_id, template, step_index, placeholders) @@ -372,10 +435,10 @@ def send_one_off_step(service_id, template_id, step_index): template.values[current_placeholder] = None return render_template( - 'views/send-test.html', + "views/send-test.html", page_title=get_send_test_page_title( template.template_type, - entering_recipient=not session['recipient'], + entering_recipient=not session["recipient"], name=template.name, ), template=template, @@ -383,8 +446,7 @@ def send_one_off_step(service_id, template_id, step_index): skip_link=get_skip_link(step_index, template), back_link=back_link, link_to_upload=( - request.endpoint == 'main.send_one_off_step' - and step_index == 0 + request.endpoint == "main.send_one_off_step" and step_index == 0 ), ) @@ -399,27 +461,29 @@ def _check_messages(service_id, template_id, upload_id, preview_row): # If we just return a `redirect` (302) object here, we'll get # errors when we try and unpack in the check_messages route. # Rasing a werkzeug.routing redirect means that doesn't happen. - raise PermanentRedirect(url_for( - 'main.send_messages', - service_id=service_id, - template_id=template_id - )) + raise PermanentRedirect( + url_for( + "main.send_messages", service_id=service_id, template_id=template_id + ) + ) except HTTPError as e: if e.status_code != 404: raise notification_count = service_api_client.get_notification_count(service_id) - remaining_messages = (current_service.message_limit - notification_count) + remaining_messages = current_service.message_limit - notification_count contents = s3download(service_id, upload_id) - db_template = current_service.get_template_with_user_permission_or_403(template_id, current_user) + db_template = current_service.get_template_with_user_permission_or_403( + template_id, current_user + ) email_reply_to = None sms_sender = None - if db_template['template_type'] == 'email': + if db_template["template_type"] == "email": email_reply_to = get_email_reply_to_address_from_session() - elif db_template['template_type'] == 'sms': + elif db_template["template_type"] == "sms": sms_sender = get_sms_sender_from_session() template = get_template( @@ -435,18 +499,25 @@ def _check_messages(service_id, template_id, upload_id, preview_row): max_initial_rows_shown=50, max_errors_shown=50, guestlist=itertools.chain.from_iterable( - [user.name, user.mobile_number, user.email_address] for user in Users(service_id) - ) if current_service.trial_mode else None, + [user.name, user.mobile_number, user.email_address] + for user in Users(service_id) + ) + if current_service.trial_mode + else None, remaining_messages=remaining_messages, - allow_international_sms=current_service.has_permission('international_sms'), + allow_international_sms=current_service.has_permission("international_sms"), ) - if request.args.get('from_test'): + if request.args.get("from_test"): # TODO: may not be required after letters code removed - back_link = url_for('main.send_one_off', service_id=service_id, template_id=template.id) + back_link = url_for( + "main.send_one_off", service_id=service_id, template_id=template.id + ) choose_time_form = None else: - back_link = url_for('main.send_messages', service_id=service_id, template_id=template.id) + back_link = url_for( + "main.send_messages", service_id=service_id, template_id=template.id + ) choose_time_form = ChooseTimeForm() if preview_row < 2: @@ -457,7 +528,9 @@ def _check_messages(service_id, template_id, upload_id, preview_row): elif preview_row > 2: abort(404) - original_file_name = get_csv_metadata(service_id, upload_id).get('original_file_name', '') + original_file_name = get_csv_metadata(service_id, upload_id).get( + "original_file_name", "" + ) return dict( recipients=recipients, @@ -475,69 +548,69 @@ def _check_messages(service_id, template_id, upload_id, preview_row): first_recipient_column=recipients.recipient_column_headers[0], preview_row=preview_row, sent_previously=job_api_client.has_sent_previously( - service_id, template.id, db_template['version'], original_file_name + service_id, template.id, db_template["version"], original_file_name ), ) -@main.route("/services///check/", methods=['GET']) +@main.route( + "/services///check/", + methods=["GET"], +) @main.route( "/services///check//row-", - methods=['GET'] + methods=["GET"], ) -@user_has_permissions('send_messages', restrict_admin_usage=True) +@user_has_permissions("send_messages", restrict_admin_usage=True) def check_messages(service_id, template_id, upload_id, row_index=2): data = _check_messages(service_id, template_id, upload_id, row_index) - data['allowed_file_extensions'] = Spreadsheet.ALLOWED_FILE_EXTENSIONS + data["allowed_file_extensions"] = Spreadsheet.ALLOWED_FILE_EXTENSIONS if ( - data['recipients'].too_many_rows - or not data['count_of_recipients'] - or not data['recipients'].has_recipient_columns - or data['recipients'].duplicate_recipient_column_headers - or data['recipients'].missing_column_headers - or data['sent_previously'] + data["recipients"].too_many_rows + or not data["count_of_recipients"] + or not data["recipients"].has_recipient_columns + or data["recipients"].duplicate_recipient_column_headers + or data["recipients"].missing_column_headers + or data["sent_previously"] ): - return render_template('views/check/column-errors.html', **data) + return render_template("views/check/column-errors.html", **data) - if data['row_errors']: - return render_template('views/check/row-errors.html', **data) + if data["row_errors"]: + return render_template("views/check/row-errors.html", **data) - if ( - data['errors'] - ): - return render_template('views/check/column-errors.html', **data) + if data["errors"]: + return render_template("views/check/column-errors.html", **data) metadata_kwargs = { - 'notification_count': data['count_of_recipients'], - 'template_id': template_id, - 'valid': True, - 'original_file_name': data.get('original_file_name', ''), + "notification_count": data["count_of_recipients"], + "template_id": template_id, + "valid": True, + "original_file_name": data.get("original_file_name", ""), } - if session.get('sender_id'): - metadata_kwargs['sender_id'] = session['sender_id'] + if session.get("sender_id"): + metadata_kwargs["sender_id"] = session["sender_id"] set_metadata_on_csv_upload(service_id, upload_id, **metadata_kwargs) - return render_template('views/check/ok.html', **data) + return render_template("views/check/ok.html", **data) -@main.route("/services//start-job/", methods=['POST']) -@user_has_permissions('send_messages', restrict_admin_usage=True) +@main.route("/services//start-job/", methods=["POST"]) +@user_has_permissions("send_messages", restrict_admin_usage=True) def start_job(service_id, upload_id): - job_api_client.create_job( upload_id, service_id, - scheduled_for=request.form.get('scheduled_for', ''), + scheduled_for=request.form.get("scheduled_for", ""), ) - session.pop('sender_id', None) + session.pop("sender_id", None) return redirect( url_for( - 'main.view_job', + "main.view_job", job_id=upload_id, service_id=service_id, ) @@ -545,64 +618,66 @@ def start_job(service_id, upload_id): def fields_to_fill_in(template, prefill_current_user=False): - if not prefill_current_user: - return first_column_headings[template.template_type] + list(template.placeholders) + return first_column_headings[template.template_type] + list( + template.placeholders + ) - if template.template_type == 'sms': - session['recipient'] = current_user.mobile_number - session['placeholders']['phone number'] = current_user.mobile_number + if template.template_type == "sms": + session["recipient"] = current_user.mobile_number + session["placeholders"]["phone number"] = current_user.mobile_number else: - session['recipient'] = current_user.email_address - session['placeholders']['email address'] = current_user.email_address + session["recipient"] = current_user.email_address + session["placeholders"]["email address"] = current_user.email_address return list(template.placeholders) def get_normalised_placeholders_from_session(): - return InsensitiveDict(session.get('placeholders', {})) + return InsensitiveDict(session.get("placeholders", {})) def get_recipient_and_placeholders_from_session(template_type): placeholders = get_normalised_placeholders_from_session() - if template_type == 'sms': - placeholders['phone_number'] = session['recipient'] + if template_type == "sms": + placeholders["phone_number"] = session["recipient"] else: - placeholders['email_address'] = session['recipient'] + placeholders["email_address"] = session["recipient"] return placeholders def all_placeholders_in_session(placeholders): return all( - get_normalised_placeholders_from_session().get(placeholder, False) not in (False, None) + get_normalised_placeholders_from_session().get(placeholder, False) + not in (False, None) for placeholder in placeholders ) def get_send_test_page_title(template_type, entering_recipient, name=None): if entering_recipient: - return 'Send ‘{}’'.format(name) - return 'Personalize this message' + return "Send ‘{}’".format(name) + return "Personalize this message" def get_back_link(service_id, template, step_index, placeholders=None): if step_index == 0: if should_skip_template_page(template._template): return url_for( - '.choose_template', + ".choose_template", service_id=service_id, ) else: return url_for( - '.view_template', + ".view_template", service_id=service_id, template_id=template.id, ) return url_for( - 'main.send_one_off_step', + "main.send_one_off_step", service_id=service_id, template_id=template.id, step_index=step_index - 1, @@ -611,24 +686,33 @@ def get_back_link(service_id, template, step_index, placeholders=None): def get_skip_link(step_index, template): if ( - request.endpoint == 'main.send_one_off_step' + request.endpoint == "main.send_one_off_step" and step_index == 0 and template.template_type in ("sms", "email") - and not (template.template_type == 'sms' and current_user.mobile_number is None) - and current_user.has_permissions('manage_templates', 'manage_service') + and not (template.template_type == "sms" and current_user.mobile_number is None) + and current_user.has_permissions("manage_templates", "manage_service") ): return ( - 'Use my {}'.format(first_column_headings[template.template_type][0]), - url_for('.send_one_off_to_myself', service_id=current_service.id, template_id=template.id), + "Use my {}".format(first_column_headings[template.template_type][0]), + url_for( + ".send_one_off_to_myself", + service_id=current_service.id, + template_id=template.id, + ), ) -@main.route("/services//template//one-off/send-to-myself", methods=['GET']) -@user_has_permissions('send_messages', restrict_admin_usage=True) +@main.route( + "/services//template//one-off/send-to-myself", + methods=["GET"], +) +@user_has_permissions("send_messages", restrict_admin_usage=True) def send_one_off_to_myself(service_id, template_id): - db_template = current_service.get_template_with_user_permission_or_403(template_id, current_user) + db_template = current_service.get_template_with_user_permission_or_403( + template_id, current_user + ) - if db_template['template_type'] not in ("sms", "email"): + if db_template["template_type"] not in ("sms", "email"): abort(404) # We aren't concerned with creating the exact template (for example adding recipient and sender names) @@ -639,30 +723,37 @@ def send_one_off_to_myself(service_id, template_id): ) fields_to_fill_in(template, prefill_current_user=True) - return redirect(url_for( - 'main.send_one_off_step', - service_id=service_id, - template_id=template_id, - step_index=1, - )) + return redirect( + url_for( + "main.send_one_off_step", + service_id=service_id, + template_id=template_id, + step_index=1, + ) + ) -@main.route("/services//template//notification/check", methods=['GET']) -@user_has_permissions('send_messages', restrict_admin_usage=True) +@main.route( + "/services//template//notification/check", + methods=["GET"], +) +@user_has_permissions("send_messages", restrict_admin_usage=True) def check_notification(service_id, template_id): return render_template( - 'views/notifications/check.html', + "views/notifications/check.html", **_check_notification(service_id, template_id), ) def _check_notification(service_id, template_id, exception=None): - db_template = current_service.get_template_with_user_permission_or_403(template_id, current_user) + db_template = current_service.get_template_with_user_permission_or_403( + template_id, current_user + ) email_reply_to = None sms_sender = None - if db_template['template_type'] == 'email': + if db_template["template_type"] == "email": email_reply_to = get_email_reply_to_address_from_session() - elif db_template['template_type'] == 'sms': + elif db_template["template_type"] == "sms": sms_sender = get_sms_sender_from_session() template = get_template( db_template, @@ -676,15 +767,14 @@ def _check_notification(service_id, template_id, exception=None): back_link = get_back_link(service_id, template, len(placeholders), placeholders) - if ( - ( - not session.get('recipient') - ) - or not all_placeholders_in_session(template.placeholders) + if (not session.get("recipient")) or not all_placeholders_in_session( + template.placeholders ): raise PermanentRedirect(back_link) - template.values = get_recipient_and_placeholders_from_session(template.template_type) + template.values = get_recipient_and_placeholders_from_session( + template.template_type + ) return dict( template=template, back_link=back_link, @@ -694,86 +784,96 @@ def _check_notification(service_id, template_id, exception=None): def get_template_error_dict(exception): # TODO: Make API return some computer-friendly identifier as well as the end user error messages - if 'service is in trial mode' in exception.message: - error = 'not-allowed-to-send-to' - elif 'Exceeded send limits' in exception.message: - error = 'too-many-messages' + if "service is in trial mode" in exception.message: + error = "not-allowed-to-send-to" + elif "Exceeded send limits" in exception.message: + error = "too-many-messages" # the error from the api is changing for message-too-long, but we need both until the api is deployed. - elif 'Content for template has a character count greater than the limit of' in exception.message: - error = 'message-too-long' - elif 'Text messages cannot be longer than' in exception.message: - error = 'message-too-long' + elif ( + "Content for template has a character count greater than the limit of" + in exception.message + ): + error = "message-too-long" + elif "Text messages cannot be longer than" in exception.message: + error = "message-too-long" else: raise exception return { - 'error': error, - 'SMS_CHAR_COUNT_LIMIT': SMS_CHAR_COUNT_LIMIT, - 'current_service': current_service, - + "error": error, + "SMS_CHAR_COUNT_LIMIT": SMS_CHAR_COUNT_LIMIT, + "current_service": current_service, # used to trigger CSV specific err msg content, so not needed for single notification errors. - 'original_file_name': False + "original_file_name": False, } -@main.route("/services//template//notification/check", methods=['POST']) -@user_has_permissions('send_messages', restrict_admin_usage=True) +@main.route( + "/services//template//notification/check", + methods=["POST"], +) +@user_has_permissions("send_messages", restrict_admin_usage=True) def send_notification(service_id, template_id): recipient = get_recipient() if not recipient: - return redirect(url_for( - '.send_one_off', - service_id=service_id, - template_id=template_id, - )) + return redirect( + url_for( + ".send_one_off", + service_id=service_id, + template_id=template_id, + ) + ) - db_template = current_service.get_template_with_user_permission_or_403(template_id, current_user) + db_template = current_service.get_template_with_user_permission_or_403( + template_id, current_user + ) try: noti = notification_api_client.send_notification( service_id, - template_id=db_template['id'], + template_id=db_template["id"], recipient=recipient, - personalisation=session['placeholders'], - sender_id=session.get('sender_id', None), + personalisation=session["placeholders"], + sender_id=session.get("sender_id", None), ) except HTTPError as exception: - current_app.logger.error('Service {} could not send notification: "{}"'.format( - current_service.id, - exception.message - )) + current_app.logger.error( + 'Service {} could not send notification: "{}"'.format( + current_service.id, exception.message + ) + ) return render_template( - 'views/notifications/check.html', + "views/notifications/check.html", **_check_notification(service_id, template_id, exception), ) - session.pop('placeholders') - session.pop('recipient') - session.pop('sender_id', None) + session.pop("placeholders") + session.pop("recipient") + session.pop("sender_id", None) - return redirect(url_for( - '.view_notification', - service_id=service_id, - notification_id=noti['id'], - # used to show the final step of the tour (help=3) or not show - # a back link on a just sent one off notification (help=0) - help=request.args.get('help') - )) + return redirect( + url_for( + ".view_notification", + service_id=service_id, + notification_id=noti["id"], + # used to show the final step of the tour (help=3) or not show + # a back link on a just sent one off notification (help=0) + help=request.args.get("help"), + ) + ) def get_email_reply_to_address_from_session(): - if session.get('sender_id'): - return current_service.get_email_reply_to_address( - session['sender_id'] - )['email_address'] + if session.get("sender_id"): + return current_service.get_email_reply_to_address(session["sender_id"])[ + "email_address" + ] def get_sms_sender_from_session(): - if session.get('sender_id'): - return current_service.get_sms_sender( - session['sender_id'] - )['sms_sender'] + if session.get("sender_id"): + return current_service.get_sms_sender(session["sender_id"])["sms_sender"] def get_spreadsheet_column_headings_from_template(template): @@ -781,9 +881,7 @@ def get_spreadsheet_column_headings_from_template(template): recipient_columns = first_column_headings[template.template_type] - for column_heading in ( - recipient_columns + list(template.placeholders) - ): + for column_heading in recipient_columns + list(template.placeholders): if column_heading not in InsensitiveDict.from_keys(column_headings): column_headings.append(column_heading) @@ -791,10 +889,9 @@ def get_spreadsheet_column_headings_from_template(template): def get_recipient(): - if {'recipient', 'placeholders'} - set(session.keys()): + if {"recipient", "placeholders"} - set(session.keys()): return None - return ( - session['recipient'] or - InsensitiveDict(session['placeholders']).get('address line 1') + return session["recipient"] or InsensitiveDict(session["placeholders"]).get( + "address line 1" ) diff --git a/app/main/views/service_settings.py b/app/main/views/service_settings.py index cee6bc353..70ad3ef92 100644 --- a/app/main/views/service_settings.py +++ b/app/main/views/service_settings.py @@ -66,29 +66,39 @@ from app.utils.user import ( user_is_platform_admin, ) -PLATFORM_ADMIN_SERVICE_PERMISSIONS = OrderedDict([ - ('inbound_sms', {'title': 'Receive inbound SMS', 'requires': 'sms', 'endpoint': '.service_set_inbound_number'}), - ('email_auth', {'title': 'Email authentication'}), -]) +PLATFORM_ADMIN_SERVICE_PERMISSIONS = OrderedDict( + [ + ( + "inbound_sms", + { + "title": "Receive inbound SMS", + "requires": "sms", + "endpoint": ".service_set_inbound_number", + }, + ), + ("email_auth", {"title": "Email authentication"}), + ] +) @main.route("/services//service-settings") -@user_has_permissions('manage_service', 'manage_api_keys') +@user_has_permissions("manage_service", "manage_api_keys") def service_settings(service_id): return render_template( - 'views/service-settings.html', + "views/service-settings.html", service_permissions=PLATFORM_ADMIN_SERVICE_PERMISSIONS, - email_branding_options=ChooseEmailBrandingForm(current_service) + email_branding_options=ChooseEmailBrandingForm(current_service), ) -@main.route("/services//service-settings/name", methods=['GET', 'POST']) -@user_has_permissions('manage_service') +@main.route( + "/services//service-settings/name", methods=["GET", "POST"] +) +@user_has_permissions("manage_service") def service_name_change(service_id): form = RenameServiceForm(name=current_service.name) if form.validate_on_submit(): - try: current_service.update( name=form.name.data, @@ -96,37 +106,39 @@ def service_name_change(service_id): ) except HTTPError as http_error: if http_error.status_code == 400 and any( - name_error_message.startswith('Duplicate service name') - for name_error_message in http_error.message['name'] + name_error_message.startswith("Duplicate service name") + for name_error_message in http_error.message["name"] ): - form.name.errors.append('This service name is already in use') + form.name.errors.append("This service name is already in use") else: raise http_error else: - return redirect(url_for('.service_settings', service_id=service_id)) + return redirect(url_for(".service_settings", service_id=service_id)) - if current_service.organization_type == 'local': + if current_service.organization_type == "local": return render_template( - 'views/service-settings/name-local.html', + "views/service-settings/name-local.html", form=form, ) return render_template( - 'views/service-settings/name.html', + "views/service-settings/name.html", form=form, ) -@main.route("/services//service-settings/request-to-go-live/estimate-usage", methods=['GET', 'POST']) -@user_has_permissions('manage_service') +@main.route( + "/services//service-settings/request-to-go-live/estimate-usage", + methods=["GET", "POST"], +) +@user_has_permissions("manage_service") def estimate_usage(service_id): - form = EstimateUsageForm( volume_email=current_service.volume_email, volume_sms=current_service.volume_sms, consent_to_research={ - True: 'yes', - False: 'no', + True: "yes", + False: "no", }.get(current_service.consent_to_research), ) @@ -134,38 +146,42 @@ def estimate_usage(service_id): current_service.update( volume_email=form.volume_email.data, volume_sms=form.volume_sms.data, - consent_to_research=(form.consent_to_research.data == 'yes'), + consent_to_research=(form.consent_to_research.data == "yes"), + ) + return redirect( + url_for( + "main.request_to_go_live", + service_id=service_id, + ) ) - return redirect(url_for( - 'main.request_to_go_live', - service_id=service_id, - )) return render_template( - 'views/service-settings/estimate-usage.html', + "views/service-settings/estimate-usage.html", form=form, ) -@main.route("/services//service-settings/request-to-go-live", methods=['GET']) -@user_has_permissions('manage_service') +@main.route( + "/services//service-settings/request-to-go-live", methods=["GET"] +) +@user_has_permissions("manage_service") def request_to_go_live(service_id): if current_service.live: - return render_template('views/service-settings/service-already-live.html') + return render_template("views/service-settings/service-already-live.html") - return render_template( - 'views/service-settings/request-to-go-live.html' - ) + return render_template("views/service-settings/request-to-go-live.html") -@main.route("/services//service-settings/request-to-go-live", methods=['POST']) -@user_has_permissions('manage_service') +@main.route( + "/services//service-settings/request-to-go-live", methods=["POST"] +) +@user_has_permissions("manage_service") @user_is_gov_user def submit_request_to_go_live(service_id): - ticket_message = render_template('support-tickets/go-live-request.txt') + '\n' + ticket_message = render_template("support-tickets/go-live-request.txt") + "\n" ticket = NotifySupportTicket( - subject=f'Request to go live - {current_service.name}', + subject=f"Request to go live - {current_service.name}", message=ticket_message, ticket_type=NotifySupportTicket.TYPE_QUESTION, user_name=current_user.name, @@ -179,61 +195,69 @@ def submit_request_to_go_live(service_id): current_service.update(go_live_user=current_user.id) - flash('Thanks for your request to go live. We’ll get back to you within one working day.', 'default') - return redirect(url_for('.service_settings', service_id=service_id)) + flash( + "Thanks for your request to go live. We’ll get back to you within one working day.", + "default", + ) + return redirect(url_for(".service_settings", service_id=service_id)) -@main.route("/services//service-settings/switch-live", methods=["GET", "POST"]) +@main.route( + "/services//service-settings/switch-live", methods=["GET", "POST"] +) @user_is_platform_admin def service_switch_live(service_id): form = ServiceOnOffSettingForm( - name="Make service live", - enabled=not current_service.trial_mode + name="Make service live", enabled=not current_service.trial_mode ) if form.validate_on_submit(): current_service.update_status(live=form.enabled.data) - return redirect(url_for('.service_settings', service_id=service_id)) + return redirect(url_for(".service_settings", service_id=service_id)) return render_template( - 'views/service-settings/set-service-setting.html', + "views/service-settings/set-service-setting.html", title="Make service live", form=form, ) -@main.route("/services//service-settings/switch-count-as-live", methods=["GET", "POST"]) +@main.route( + "/services//service-settings/switch-count-as-live", + methods=["GET", "POST"], +) @user_is_platform_admin def service_switch_count_as_live(service_id): - form = ServiceOnOffSettingForm( name="Count in list of live services", enabled=current_service.count_as_live, - truthy='Yes', - falsey='No', + truthy="Yes", + falsey="No", ) if form.validate_on_submit(): current_service.update_count_as_live(form.enabled.data) - return redirect(url_for('.service_settings', service_id=service_id)) + return redirect(url_for(".service_settings", service_id=service_id)) return render_template( - 'views/service-settings/set-service-setting.html', + "views/service-settings/set-service-setting.html", title="Count in list of live services", form=form, ) -@main.route("/services//service-settings/permissions/", methods=["GET", "POST"]) +@main.route( + "/services//service-settings/permissions/", + methods=["GET", "POST"], +) @user_is_platform_admin def service_set_permission(service_id, permission): if permission not in PLATFORM_ADMIN_SERVICE_PERMISSIONS: abort(404) - title = PLATFORM_ADMIN_SERVICE_PERMISSIONS[permission]['title'] + title = PLATFORM_ADMIN_SERVICE_PERMISSIONS[permission]["title"] form = ServiceOnOffSettingForm( - name=title, - enabled=current_service.has_permission(permission) + name=title, enabled=current_service.has_permission(permission) ) if form.validate_on_submit(): @@ -242,72 +266,94 @@ def service_set_permission(service_id, permission): return redirect(url_for(".service_settings", service_id=service_id)) return render_template( - 'views/service-settings/set-service-setting.html', + "views/service-settings/set-service-setting.html", title=title, form=form, ) -@main.route("/services//service-settings/archive", methods=['GET', 'POST']) -@user_has_permissions('manage_service') +@main.route( + "/services//service-settings/archive", methods=["GET", "POST"] +) +@user_has_permissions("manage_service") def archive_service(service_id): if not current_service.active or not ( current_service.trial_mode or current_user.platform_admin ): abort(403) - if request.method == 'POST': + if request.method == "POST": # We need to purge the cache for the services users as otherwise, although they will have had their permissions # removed in the DB, they would still have permissions in the cache to view/edit/manage this service cached_service_user_ids = [user.id for user in current_service.active_users] service_api_client.archive_service(service_id, cached_service_user_ids) - create_archive_service_event(service_id=service_id, archived_by_id=current_user.id) + create_archive_service_event( + service_id=service_id, archived_by_id=current_user.id + ) flash( - '‘{}’ was deleted'.format(current_service.name), - 'default_with_tick', + "‘{}’ was deleted".format(current_service.name), + "default_with_tick", ) - return redirect(url_for('.choose_account')) + return redirect(url_for(".choose_account")) else: flash( - 'Are you sure you want to delete ‘{}’? There’s no way to undo this.'.format(current_service.name), - 'delete', + "Are you sure you want to delete ‘{}’? There’s no way to undo this.".format( + current_service.name + ), + "delete", ) return service_settings(service_id) -@main.route("/services//service-settings/suspend", methods=["GET", "POST"]) +@main.route( + "/services//service-settings/suspend", methods=["GET", "POST"] +) @user_is_platform_admin def suspend_service(service_id): - if request.method == 'POST': + if request.method == "POST": service_api_client.suspend_service(service_id) - create_suspend_service_event(service_id=service_id, suspended_by_id=current_user.id) - return redirect(url_for('.service_settings', service_id=service_id)) + create_suspend_service_event( + service_id=service_id, suspended_by_id=current_user.id + ) + return redirect(url_for(".service_settings", service_id=service_id)) else: - flash("This will suspend the service and revoke all api keys. Are you sure you want to suspend this service?", - 'suspend') + flash( + "This will suspend the service and revoke all api keys. Are you sure you want to suspend this service?", + "suspend", + ) return service_settings(service_id) -@main.route("/services//service-settings/resume", methods=["GET", "POST"]) +@main.route( + "/services//service-settings/resume", methods=["GET", "POST"] +) @user_is_platform_admin def resume_service(service_id): - if request.method == 'POST': + if request.method == "POST": service_api_client.resume_service(service_id) - create_resume_service_event(service_id=service_id, resumed_by_id=current_user.id) - return redirect(url_for('.service_settings', service_id=service_id)) + create_resume_service_event( + service_id=service_id, resumed_by_id=current_user.id + ) + return redirect(url_for(".service_settings", service_id=service_id)) else: - flash("This will resume the service. New api key are required for this service to use the API.", 'resume') + flash( + "This will resume the service. New api key are required for this service to use the API.", + "resume", + ) return service_settings(service_id) -@main.route("/services//service-settings/send-files-by-email", methods=['GET', 'POST']) -@user_has_permissions('manage_service') +@main.route( + "/services//service-settings/send-files-by-email", + methods=["GET", "POST"], +) +@user_has_permissions("manage_service") def send_files_by_email_contact_details(service_id): form = ServiceContactDetailsForm() contact_details = None - if request.method == 'GET': + if request.method == "GET": contact_details = current_service.contact_link if contact_details: contact_type = check_contact_details_type(contact_details) @@ -319,30 +365,37 @@ def send_files_by_email_contact_details(service_id): if form.validate_on_submit(): contact_type = form.contact_details_type.data - current_service.update( - contact_link=form.data[contact_type] - ) - return redirect(url_for('.service_settings', service_id=current_service.id)) + current_service.update(contact_link=form.data[contact_type]) + return redirect(url_for(".service_settings", service_id=current_service.id)) return render_template( - 'views/service-settings/send-files-by-email.html', form=form, contact_details=contact_details + "views/service-settings/send-files-by-email.html", + form=form, + contact_details=contact_details, ) -@main.route("/services//service-settings/set-reply-to-email", methods=['GET']) -@user_has_permissions('manage_service') +@main.route( + "/services//service-settings/set-reply-to-email", methods=["GET"] +) +@user_has_permissions("manage_service") def service_set_reply_to_email(service_id): - return redirect(url_for('.service_email_reply_to', service_id=service_id)) + return redirect(url_for(".service_email_reply_to", service_id=service_id)) -@main.route("/services//service-settings/email-reply-to", methods=['GET']) -@user_has_permissions('manage_service', 'manage_api_keys') +@main.route( + "/services//service-settings/email-reply-to", methods=["GET"] +) +@user_has_permissions("manage_service", "manage_api_keys") def service_email_reply_to(service_id): - return render_template('views/service-settings/email_reply_to.html') + return render_template("views/service-settings/email_reply_to.html") -@main.route("/services//service-settings/email-reply-to/add", methods=['GET', 'POST']) -@user_has_permissions('manage_service') +@main.route( + "/services//service-settings/email-reply-to/add", + methods=["GET", "POST"], +) +@user_has_permissions("manage_service") def service_add_email_reply_to(service_id): form = ServiceReplyToEmailForm() first_email_address = current_service.count_email_reply_to_addresses == 0 @@ -350,11 +403,9 @@ def service_add_email_reply_to(service_id): if form.validate_on_submit(): if current_user.platform_admin: service_api_client.add_reply_to_email_address( - service_id, - email_address=form.email_address.data, - is_default=is_default + service_id, email_address=form.email_address.data, is_default=is_default ) - return redirect(url_for('.service_email_reply_to', service_id=service_id)) + return redirect(url_for(".service_email_reply_to", service_id=service_id)) else: try: notification_id = service_api_client.verify_reply_to_email_address( @@ -362,86 +413,105 @@ def service_add_email_reply_to(service_id): )["data"]["id"] except HTTPError as e: if e.status_code == 409: - flash(e.message, 'error') - return redirect(url_for('.service_email_reply_to', service_id=service_id)) + flash(e.message, "error") + return redirect( + url_for(".service_email_reply_to", service_id=service_id) + ) else: raise e - return redirect(url_for( - '.service_verify_reply_to_address', - service_id=service_id, - notification_id=notification_id, - is_default=is_default - )) + return redirect( + url_for( + ".service_verify_reply_to_address", + service_id=service_id, + notification_id=notification_id, + is_default=is_default, + ) + ) return render_template( - 'views/service-settings/email-reply-to/add.html', + "views/service-settings/email-reply-to/add.html", form=form, - first_email_address=first_email_address) + first_email_address=first_email_address, + ) @main.route( "/services//service-settings/email-reply-to//verify", - methods=['GET', 'POST'] + methods=["GET", "POST"], ) -@user_has_permissions('manage_service') +@user_has_permissions("manage_service") def service_verify_reply_to_address(service_id, notification_id): - replace = request.args.get('replace', False) - is_default = request.args.get('is_default', False) + replace = request.args.get("replace", False) + is_default = request.args.get("is_default", False) return render_template( - 'views/service-settings/email-reply-to/verify.html', + "views/service-settings/email-reply-to/verify.html", service_id=service_id, notification_id=notification_id, - partials=get_service_verify_reply_to_address_partials(service_id, notification_id), + partials=get_service_verify_reply_to_address_partials( + service_id, notification_id + ), verb=("Change" if replace else "Add"), replace=replace, - is_default=is_default + is_default=is_default, ) -@main.route("/services//service-settings/email-reply-to//verify.json") -@user_has_permissions('manage_service') +@main.route( + "/services//service-settings/email-reply-to//verify.json" +) +@user_has_permissions("manage_service") def service_verify_reply_to_address_updates(service_id, notification_id): - return jsonify(**get_service_verify_reply_to_address_partials(service_id, notification_id)) + return jsonify( + **get_service_verify_reply_to_address_partials(service_id, notification_id) + ) def get_service_verify_reply_to_address_partials(service_id, notification_id): form = ServiceReplyToEmailForm() first_email_address = current_service.count_email_reply_to_addresses == 0 - notification = notification_api_client.get_notification(current_app.config["NOTIFY_SERVICE_ID"], notification_id) - replace = request.args.get('replace', False) + notification = notification_api_client.get_notification( + current_app.config["NOTIFY_SERVICE_ID"], notification_id + ) + replace = request.args.get("replace", False) replace = False if replace == "False" else replace existing_is_default = False if replace: existing = current_service.get_email_reply_to_address(replace) - existing_is_default = existing['is_default'] + existing_is_default = existing["is_default"] verification_status = "pending" - is_default = True if (request.args.get('is_default', False) == "True") else False + is_default = True if (request.args.get("is_default", False) == "True") else False if notification["status"] in DELIVERED_STATUSES: verification_status = "success" - if notification["to"] not in [i["email_address"] for i in current_service.email_reply_to_addresses]: + if notification["to"] not in [ + i["email_address"] for i in current_service.email_reply_to_addresses + ]: if replace: service_api_client.update_reply_to_email_address( - current_service.id, replace, email_address=notification["to"], is_default=is_default + current_service.id, + replace, + email_address=notification["to"], + is_default=is_default, ) else: service_api_client.add_reply_to_email_address( current_service.id, email_address=notification["to"], - is_default=is_default + is_default=is_default, ) seconds_since_sending = ( - datetime.utcnow() - parse_naive_dt(notification['created_at']) + datetime.utcnow() - parse_naive_dt(notification["created_at"]) ).seconds if notification["status"] in FAILURE_STATUSES or ( - notification["status"] in SENDING_STATUSES and - seconds_since_sending > current_app.config['REPLY_TO_EMAIL_ADDRESS_VALIDATION_TIMEOUT'] + notification["status"] in SENDING_STATUSES + and seconds_since_sending + > current_app.config["REPLY_TO_EMAIL_ADDRESS_VALIDATION_TIMEOUT"] ): verification_status = "failure" - form.email_address.data = notification['to'] + form.email_address.data = notification["to"] form.is_default.data = is_default return { - 'status': render_template( - 'views/service-settings/email-reply-to/_verify-updates.html', + "status": render_template( + "views/service-settings/email-reply-to/_verify-updates.html", reply_to_email_address=notification["to"], service_id=current_service.id, notification_id=notification_id, @@ -450,91 +520,109 @@ def get_service_verify_reply_to_address_partials(service_id, notification_id): existing_is_default=existing_is_default, form=form, first_email_address=first_email_address, - replace=replace + replace=replace, ), - 'stop': 0 if verification_status == "pending" else 1 + "stop": 0 if verification_status == "pending" else 1, } @main.route( "/services//service-settings/email-reply-to//edit", - methods=['GET', 'POST'], - endpoint="service_edit_email_reply_to" + methods=["GET", "POST"], + endpoint="service_edit_email_reply_to", ) @main.route( "/services//service-settings/email-reply-to//delete", - methods=['GET'], - endpoint="service_confirm_delete_email_reply_to" + methods=["GET"], + endpoint="service_confirm_delete_email_reply_to", ) -@user_has_permissions('manage_service') +@user_has_permissions("manage_service") def service_edit_email_reply_to(service_id, reply_to_email_id): form = ServiceReplyToEmailForm() - reply_to_email_address = current_service.get_email_reply_to_address(reply_to_email_id) + reply_to_email_address = current_service.get_email_reply_to_address( + reply_to_email_id + ) - if request.method == 'GET': - form.email_address.data = reply_to_email_address['email_address'] - form.is_default.data = reply_to_email_address['is_default'] + if request.method == "GET": + form.email_address.data = reply_to_email_address["email_address"] + form.is_default.data = reply_to_email_address["is_default"] - show_choice_of_default_checkbox = not reply_to_email_address['is_default'] + show_choice_of_default_checkbox = not reply_to_email_address["is_default"] if form.validate_on_submit(): - if form.email_address.data == reply_to_email_address["email_address"] or current_user.platform_admin: + if ( + form.email_address.data == reply_to_email_address["email_address"] + or current_user.platform_admin + ): service_api_client.update_reply_to_email_address( current_service.id, reply_to_email_id=reply_to_email_id, email_address=form.email_address.data, - is_default=True if reply_to_email_address['is_default'] else form.is_default.data + is_default=True + if reply_to_email_address["is_default"] + else form.is_default.data, ) - return redirect(url_for('.service_email_reply_to', service_id=service_id)) + return redirect(url_for(".service_email_reply_to", service_id=service_id)) try: notification_id = service_api_client.verify_reply_to_email_address( service_id, form.email_address.data )["data"]["id"] except HTTPError as e: if e.status_code == 409: - flash(e.message, 'error') - return redirect(url_for('.service_email_reply_to', service_id=service_id)) + flash(e.message, "error") + return redirect( + url_for(".service_email_reply_to", service_id=service_id) + ) else: raise e - return redirect(url_for( - '.service_verify_reply_to_address', - service_id=service_id, - notification_id=notification_id, - is_default=True if reply_to_email_address['is_default'] else form.is_default.data, - replace=reply_to_email_id - )) + return redirect( + url_for( + ".service_verify_reply_to_address", + service_id=service_id, + notification_id=notification_id, + is_default=True + if reply_to_email_address["is_default"] + else form.is_default.data, + replace=reply_to_email_id, + ) + ) - if (request.endpoint == "main.service_confirm_delete_email_reply_to"): - flash("Are you sure you want to delete this reply-to email address?", 'delete') + if request.endpoint == "main.service_confirm_delete_email_reply_to": + flash("Are you sure you want to delete this reply-to email address?", "delete") return render_template( - 'views/service-settings/email-reply-to/edit.html', + "views/service-settings/email-reply-to/edit.html", form=form, reply_to_email_address_id=reply_to_email_id, - show_choice_of_default_checkbox=show_choice_of_default_checkbox + show_choice_of_default_checkbox=show_choice_of_default_checkbox, ) @main.route( "/services//service-settings/email-reply-to//delete", - methods=['POST'] + methods=["POST"], ) -@user_has_permissions('manage_service') +@user_has_permissions("manage_service") def service_delete_email_reply_to(service_id, reply_to_email_id): service_api_client.delete_reply_to_email_address( service_id=current_service.id, reply_to_email_id=reply_to_email_id, ) - return redirect(url_for('.service_email_reply_to', service_id=service_id)) + return redirect(url_for(".service_email_reply_to", service_id=service_id)) -@main.route("/services//service-settings/set-inbound-number", methods=['GET', 'POST']) -@user_has_permissions('manage_service') +@main.route( + "/services//service-settings/set-inbound-number", + methods=["GET", "POST"], +) +@user_has_permissions("manage_service") def service_set_inbound_number(service_id): - available_inbound_numbers = inbound_number_client.get_available_inbound_sms_numbers() + available_inbound_numbers = ( + inbound_number_client.get_available_inbound_sms_numbers() + ) inbound_numbers_value_and_label = [ - (number['id'], number['number']) for number in available_inbound_numbers['data'] + (number["id"], number["number"]) for number in available_inbound_numbers["data"] ] - no_available_numbers = available_inbound_numbers['data'] == [] + no_available_numbers = available_inbound_numbers["data"] == [] form = AdminServiceInboundNumberForm( inbound_number_choices=inbound_numbers_value_and_label ) @@ -544,77 +632,79 @@ def service_set_inbound_number(service_id): current_service.id, sms_sender=form.inbound_number.data, is_default=True, - inbound_number_id=form.inbound_number.data + inbound_number_id=form.inbound_number.data, ) - current_service.force_permission('inbound_sms', on=True) - return redirect(url_for('.service_settings', service_id=service_id)) + current_service.force_permission("inbound_sms", on=True) + return redirect(url_for(".service_settings", service_id=service_id)) return render_template( - 'views/service-settings/set-inbound-number.html', + "views/service-settings/set-inbound-number.html", form=form, no_available_numbers=no_available_numbers, ) -@main.route("/services//service-settings/sms-prefix", methods=['GET', 'POST']) -@user_has_permissions('manage_service') +@main.route( + "/services//service-settings/sms-prefix", methods=["GET", "POST"] +) +@user_has_permissions("manage_service") def service_set_sms_prefix(service_id): - form = SMSPrefixForm(enabled=current_service.prefix_sms) - form.enabled.label.text = 'Start all text messages with ‘{}:’'.format(current_service.name) - - if form.validate_on_submit(): - current_service.update( - prefix_sms=form.enabled.data - ) - return redirect(url_for('.service_settings', service_id=service_id)) - - return render_template( - 'views/service-settings/sms-prefix.html', - form=form + form.enabled.label.text = "Start all text messages with ‘{}:’".format( + current_service.name ) + if form.validate_on_submit(): + current_service.update(prefix_sms=form.enabled.data) + return redirect(url_for(".service_settings", service_id=service_id)) -@main.route("/services//service-settings/set-international-sms", methods=['GET', 'POST']) -@user_has_permissions('manage_service') + return render_template("views/service-settings/sms-prefix.html", form=form) + + +@main.route( + "/services//service-settings/set-international-sms", + methods=["GET", "POST"], +) +@user_has_permissions("manage_service") def service_set_international_sms(service_id): form = ServiceOnOffSettingForm( - 'Send text messages to international phone numbers', - enabled=current_service.has_permission('international_sms'), + "Send text messages to international phone numbers", + enabled=current_service.has_permission("international_sms"), ) if form.validate_on_submit(): current_service.force_permission( - 'international_sms', + "international_sms", on=form.enabled.data, ) - return redirect( - url_for(".service_settings", service_id=service_id) - ) + return redirect(url_for(".service_settings", service_id=service_id)) return render_template( - 'views/service-settings/set-international-sms.html', + "views/service-settings/set-international-sms.html", form=form, ) -@main.route("/services//service-settings/set-inbound-sms", methods=['GET']) -@user_has_permissions('manage_service') +@main.route( + "/services//service-settings/set-inbound-sms", methods=["GET"] +) +@user_has_permissions("manage_service") def service_set_inbound_sms(service_id): return render_template( - 'views/service-settings/set-inbound-sms.html', + "views/service-settings/set-inbound-sms.html", ) -@main.route("/services//service-settings/set-", methods=['GET', 'POST']) -@user_has_permissions('manage_service') +@main.route( + "/services//service-settings/set-", + methods=["GET", "POST"], +) +@user_has_permissions("manage_service") def service_set_channel(service_id, channel): - - if channel not in {'email', 'sms'}: + if channel not in {"email", "sms"}: abort(404) form = ServiceSwitchChannelForm( - channel=channel, - enabled=current_service.has_permission(channel) + channel=channel, enabled=current_service.has_permission(channel) ) if form.validate_on_submit(): @@ -622,66 +712,70 @@ def service_set_channel(service_id, channel): channel, on=form.enabled.data, ) - return redirect( - url_for(".service_settings", service_id=service_id) - ) + return redirect(url_for(".service_settings", service_id=service_id)) return render_template( - 'views/service-settings/set-{}.html'.format(channel), + "views/service-settings/set-{}.html".format(channel), form=form, ) -@main.route("/services//service-settings/set-auth-type", methods=['GET']) -@user_has_permissions('manage_service') +@main.route( + "/services//service-settings/set-auth-type", methods=["GET"] +) +@user_has_permissions("manage_service") def service_set_auth_type(service_id): return render_template( - 'views/service-settings/set-auth-type.html', + "views/service-settings/set-auth-type.html", ) -@main.route("/services//service-settings/sms-sender", methods=['GET']) -@user_has_permissions('manage_service', 'manage_api_keys') +@main.route("/services//service-settings/sms-sender", methods=["GET"]) +@user_has_permissions("manage_service", "manage_api_keys") def service_sms_senders(service_id): return render_template( - 'views/service-settings/sms-senders.html', + "views/service-settings/sms-senders.html", ) -@main.route("/services//service-settings/sms-sender/add", methods=['GET', 'POST']) -@user_has_permissions('manage_service') +@main.route( + "/services//service-settings/sms-sender/add", + methods=["GET", "POST"], +) +@user_has_permissions("manage_service") def service_add_sms_sender(service_id): form = ServiceSmsSenderForm() first_sms_sender = current_service.count_sms_senders == 0 if form.validate_on_submit(): service_api_client.add_sms_sender( current_service.id, - sms_sender=form.sms_sender.data.replace('\r', '') or None, - is_default=first_sms_sender if first_sms_sender else form.is_default.data + sms_sender=form.sms_sender.data.replace("\r", "") or None, + is_default=first_sms_sender if first_sms_sender else form.is_default.data, ) - return redirect(url_for('.service_sms_senders', service_id=service_id)) + return redirect(url_for(".service_sms_senders", service_id=service_id)) return render_template( - 'views/service-settings/sms-sender/add.html', + "views/service-settings/sms-sender/add.html", form=form, - first_sms_sender=first_sms_sender) + first_sms_sender=first_sms_sender, + ) @main.route( "/services//service-settings/sms-sender//edit", - methods=['GET', 'POST'], - endpoint="service_edit_sms_sender" + methods=["GET", "POST"], + endpoint="service_edit_sms_sender", ) @main.route( "/services//service-settings/sms-sender//delete", - methods=['GET'], - endpoint="service_confirm_delete_sms_sender" + methods=["GET"], + endpoint="service_confirm_delete_sms_sender", ) -@user_has_permissions('manage_service') +@user_has_permissions("manage_service") def service_edit_sms_sender(service_id, sms_sender_id): sms_sender = current_service.get_sms_sender(sms_sender_id) - is_inbound_number = sms_sender['inbound_number_id'] + is_inbound_number = sms_sender["inbound_number_id"] if is_inbound_number: - form = ServiceEditInboundNumberForm(is_default=sms_sender['is_default']) + form = ServiceEditInboundNumberForm(is_default=sms_sender["is_default"]) else: form = ServiceSmsSenderForm(**sms_sender) @@ -689,88 +783,103 @@ def service_edit_sms_sender(service_id, sms_sender_id): service_api_client.update_sms_sender( current_service.id, sms_sender_id=sms_sender_id, - sms_sender=sms_sender['sms_sender'] if is_inbound_number else form.sms_sender.data.replace('\r', ''), - is_default=True if sms_sender['is_default'] else form.is_default.data + sms_sender=sms_sender["sms_sender"] + if is_inbound_number + else form.sms_sender.data.replace("\r", ""), + is_default=True if sms_sender["is_default"] else form.is_default.data, ) - return redirect(url_for('.service_sms_senders', service_id=service_id)) + return redirect(url_for(".service_sms_senders", service_id=service_id)) - form.is_default.data = sms_sender['is_default'] - if (request.endpoint == "main.service_confirm_delete_sms_sender"): - flash("Are you sure you want to delete this text message sender?", 'delete') + form.is_default.data = sms_sender["is_default"] + if request.endpoint == "main.service_confirm_delete_sms_sender": + flash("Are you sure you want to delete this text message sender?", "delete") return render_template( - 'views/service-settings/sms-sender/edit.html', + "views/service-settings/sms-sender/edit.html", form=form, sms_sender=sms_sender, inbound_number=is_inbound_number, - sms_sender_id=sms_sender_id + sms_sender_id=sms_sender_id, ) @main.route( "/services//service-settings/sms-sender//delete", - methods=['POST'], + methods=["POST"], ) -@user_has_permissions('manage_service') +@user_has_permissions("manage_service") def service_delete_sms_sender(service_id, sms_sender_id): service_api_client.delete_sms_sender( service_id=current_service.id, sms_sender_id=sms_sender_id, ) - return redirect(url_for('.service_sms_senders', service_id=service_id)) + return redirect(url_for(".service_sms_senders", service_id=service_id)) -@main.route("/services//service-settings/set-free-sms-allowance", methods=['GET', 'POST']) +@main.route( + "/services//service-settings/set-free-sms-allowance", + methods=["GET", "POST"], +) @user_is_platform_admin def set_free_sms_allowance(service_id): - - form = AdminServiceSMSAllowanceForm(free_sms_allowance=current_service.free_sms_fragment_limit) + form = AdminServiceSMSAllowanceForm( + free_sms_allowance=current_service.free_sms_fragment_limit + ) if form.validate_on_submit(): - billing_api_client.create_or_update_free_sms_fragment_limit(service_id, form.free_sms_allowance.data) + billing_api_client.create_or_update_free_sms_fragment_limit( + service_id, form.free_sms_allowance.data + ) - return redirect(url_for('.service_settings', service_id=service_id)) + return redirect(url_for(".service_settings", service_id=service_id)) return render_template( - 'views/service-settings/set-free-sms-allowance.html', + "views/service-settings/set-free-sms-allowance.html", form=form, ) -@main.route("/services//service-settings/set-message-limit", methods=['GET', 'POST']) +@main.route( + "/services//service-settings/set-message-limit", + methods=["GET", "POST"], +) @user_is_platform_admin def set_message_limit(service_id): - form = AdminServiceMessageLimitForm(message_limit=current_service.message_limit) if form.validate_on_submit(): current_service.update(message_limit=form.message_limit.data) - return redirect(url_for('.service_settings', service_id=service_id)) + return redirect(url_for(".service_settings", service_id=service_id)) return render_template( - 'views/service-settings/set-message-limit.html', + "views/service-settings/set-message-limit.html", form=form, ) -@main.route("/services//service-settings/set-rate-limit", methods=['GET', 'POST']) +@main.route( + "/services//service-settings/set-rate-limit", + methods=["GET", "POST"], +) @user_is_platform_admin def set_rate_limit(service_id): - form = AdminServiceRateLimitForm(rate_limit=current_service.rate_limit) if form.validate_on_submit(): current_service.update(rate_limit=form.rate_limit.data) - return redirect(url_for('.service_settings', service_id=service_id)) + return redirect(url_for(".service_settings", service_id=service_id)) return render_template( - 'views/service-settings/set-rate-limit.html', + "views/service-settings/set-rate-limit.html", form=form, ) -@main.route("/services//service-settings/set-email-branding", methods=['GET', 'POST']) +@main.route( + "/services//service-settings/set-email-branding", + methods=["GET", "POST"], +) @user_is_platform_admin def service_set_email_branding(service_id): email_branding = email_branding_client.get_all_email_branding() @@ -781,61 +890,67 @@ def service_set_email_branding(service_id): ) if form.validate_on_submit(): - return redirect(url_for( - '.service_preview_email_branding', - service_id=service_id, - branding_style=form.branding_style.data, - )) + return redirect( + url_for( + ".service_preview_email_branding", + service_id=service_id, + branding_style=form.branding_style.data, + ) + ) return render_template( - 'views/service-settings/set-email-branding.html', + "views/service-settings/set-email-branding.html", form=form, - search_form=SearchByNameForm() + search_form=SearchByNameForm(), ) -@main.route("/services//service-settings/preview-email-branding", methods=['GET', 'POST']) +@main.route( + "/services//service-settings/preview-email-branding", + methods=["GET", "POST"], +) @user_is_platform_admin def service_preview_email_branding(service_id): - branding_style = request.args.get('branding_style', None) + branding_style = request.args.get("branding_style", None) form = AdminPreviewBrandingForm(branding_style=branding_style) if form.validate_on_submit(): - current_service.update( - email_branding=form.branding_style.data - ) - return redirect(url_for('.service_settings', service_id=service_id)) + current_service.update(email_branding=form.branding_style.data) + return redirect(url_for(".service_settings", service_id=service_id)) return render_template( - 'views/service-settings/preview-email-branding.html', + "views/service-settings/preview-email-branding.html", form=form, service_id=service_id, - action=url_for('main.service_preview_email_branding', service_id=service_id), + action=url_for("main.service_preview_email_branding", service_id=service_id), ) -@main.route("/services//service-settings/link-service-to-organization", methods=['GET', 'POST']) +@main.route( + "/services//service-settings/link-service-to-organization", + methods=["GET", "POST"], +) @user_is_platform_admin def link_service_to_organization(service_id): - all_organizations = organizations_client.get_organizations() form = AdminSetOrganizationForm( - choices=convert_dictionary_to_wtforms_choices_format(all_organizations, 'id', 'name'), - organizations=current_service.organization_id + choices=convert_dictionary_to_wtforms_choices_format( + all_organizations, "id", "name" + ), + organizations=current_service.organization_id, ) if form.validate_on_submit(): if form.organizations.data != current_service.organization_id: organizations_client.update_service_organization( - service_id, - form.organizations.data + service_id, form.organizations.data ) - return redirect(url_for('.service_settings', service_id=service_id)) + return redirect(url_for(".service_settings", service_id=service_id)) return render_template( - 'views/service-settings/link-service-to-organization.html', + "views/service-settings/link-service-to-organization.html", has_organizations=all_organizations, form=form, search_form=SearchByNameForm(), @@ -846,174 +961,203 @@ def create_email_branding_zendesk_ticket(form_option_selected, detail=None): form = ChooseEmailBrandingForm(current_service) ticket_message = render_template( - 'support-tickets/branding-request.txt', + "support-tickets/branding-request.txt", current_branding=current_service.email_branding_name, branding_requested=dict(form.options.choices)[form_option_selected], detail=detail, ) ticket = NotifySupportTicket( - subject=f'Email branding request - {current_service.name}', + subject=f"Email branding request - {current_service.name}", message=ticket_message, ticket_type=NotifySupportTicket.TYPE_QUESTION, user_name=current_user.name, user_email=current_user.email_address, org_id=current_service.organization_id, org_type=current_service.organization_type, - service_id=current_service.id + service_id=current_service.id, ) zendesk_client.send_ticket_to_zendesk(ticket) -@main.route("/services//service-settings/email-branding", methods=['GET', 'POST']) -@user_has_permissions('manage_service') +@main.route( + "/services//service-settings/email-branding", + methods=["GET", "POST"], +) +@user_has_permissions("manage_service") def email_branding_request(service_id): form = ChooseEmailBrandingForm(current_service) branding_name = current_service.email_branding_name if form.validate_on_submit(): return redirect( url_for( - f'.email_branding_{form.options.data}', + f".email_branding_{form.options.data}", service_id=current_service.id, ) ) return render_template( - 'views/service-settings/branding/email-branding-options.html', + "views/service-settings/branding/email-branding-options.html", form=form, branding_name=branding_name, ) def check_email_branding_allowed_for_service(branding): - allowed_branding_for_service = dict( - get_email_branding_choices(current_service) - ) + allowed_branding_for_service = dict(get_email_branding_choices(current_service)) if branding not in allowed_branding_for_service: abort(404) -@main.route("/services//service-settings/email-branding/govuk", methods=['GET', 'POST']) -@user_has_permissions('manage_service') +@main.route( + "/services//service-settings/email-branding/govuk", + methods=["GET", "POST"], +) +@user_has_permissions("manage_service") def email_branding_govuk(service_id): - check_email_branding_allowed_for_service('govuk') + check_email_branding_allowed_for_service("govuk") - if request.method == 'POST': + if request.method == "POST": current_service.update(email_branding=None) - flash('You’ve updated your email branding', 'default') - return redirect(url_for('.service_settings', service_id=current_service.id)) + flash("You’ve updated your email branding", "default") + return redirect(url_for(".service_settings", service_id=current_service.id)) - return render_template('views/service-settings/branding/email-branding-govuk.html') + return render_template("views/service-settings/branding/email-branding-govuk.html") -@main.route("/services//service-settings/email-branding/govuk-and-org", methods=['GET', 'POST']) -@user_has_permissions('manage_service') +@main.route( + "/services//service-settings/email-branding/govuk-and-org", + methods=["GET", "POST"], +) +@user_has_permissions("manage_service") def email_branding_govuk_and_org(service_id): - check_email_branding_allowed_for_service('govuk_and_org') + check_email_branding_allowed_for_service("govuk_and_org") - if request.method == 'POST': - create_email_branding_zendesk_ticket('govuk_and_org') + if request.method == "POST": + create_email_branding_zendesk_ticket("govuk_and_org") - flash('Thanks for your branding request. We’ll get back to you within one working day.', 'default') - return redirect(url_for('.service_settings', service_id=current_service.id)) + flash( + "Thanks for your branding request. We’ll get back to you within one working day.", + "default", + ) + return redirect(url_for(".service_settings", service_id=current_service.id)) - return render_template('views/service-settings/branding/email-branding-govuk-org.html') + return render_template( + "views/service-settings/branding/email-branding-govuk-org.html" + ) -@main.route("/services//service-settings/email-branding/organization", methods=['GET', 'POST']) -@user_has_permissions('manage_service') +@main.route( + "/services//service-settings/email-branding/organization", + methods=["GET", "POST"], +) +@user_has_permissions("manage_service") def email_branding_organization(service_id): - check_email_branding_allowed_for_service('organization') + check_email_branding_allowed_for_service("organization") - if request.method == 'POST': - create_email_branding_zendesk_ticket('organization') + if request.method == "POST": + create_email_branding_zendesk_ticket("organization") - flash('Thanks for your branding request. We’ll get back to you within one working day.', 'default') - return redirect(url_for('.service_settings', service_id=current_service.id)) + flash( + "Thanks for your branding request. We’ll get back to you within one working day.", + "default", + ) + return redirect(url_for(".service_settings", service_id=current_service.id)) - return render_template('views/service-settings/branding/email-branding-organization.html') + return render_template( + "views/service-settings/branding/email-branding-organization.html" + ) -@main.route("/services//service-settings/email-branding/something-else", methods=['GET', 'POST']) -@user_has_permissions('manage_service') +@main.route( + "/services//service-settings/email-branding/something-else", + methods=["GET", "POST"], +) +@user_has_permissions("manage_service") def email_branding_something_else(service_id): form = SomethingElseBrandingForm() if form.validate_on_submit(): - create_email_branding_zendesk_ticket('something_else', detail=form.something_else.data) + create_email_branding_zendesk_ticket( + "something_else", detail=form.something_else.data + ) - flash('Thanks for your branding request. We’ll get back to you within one working day.', 'default') - return redirect(url_for('.service_settings', service_id=current_service.id)) + flash( + "Thanks for your branding request. We’ll get back to you within one working day.", + "default", + ) + return redirect(url_for(".service_settings", service_id=current_service.id)) return render_template( - 'views/service-settings/branding/email-branding-something-else.html', + "views/service-settings/branding/email-branding-something-else.html", form=form, - branding_options=ChooseEmailBrandingForm(current_service) + branding_options=ChooseEmailBrandingForm(current_service), ) -@main.route("/services//data-retention", methods=['GET']) +@main.route("/services//data-retention", methods=["GET"]) @user_is_platform_admin def data_retention(service_id): return render_template( - 'views/service-settings/data-retention.html', + "views/service-settings/data-retention.html", ) -@main.route("/services//data-retention/add", methods=['GET', 'POST']) +@main.route("/services//data-retention/add", methods=["GET", "POST"]) @user_is_platform_admin def add_data_retention(service_id): form = AdminServiceAddDataRetentionForm() if form.validate_on_submit(): - service_api_client.create_service_data_retention(service_id, - form.notification_type.data, - form.days_of_retention.data) - return redirect(url_for('.data_retention', service_id=service_id)) - return render_template( - 'views/service-settings/data-retention/add.html', - form=form - ) + service_api_client.create_service_data_retention( + service_id, form.notification_type.data, form.days_of_retention.data + ) + return redirect(url_for(".data_retention", service_id=service_id)) + return render_template("views/service-settings/data-retention/add.html", form=form) -@main.route("/services//data-retention//edit", methods=['GET', 'POST']) +@main.route( + "/services//data-retention//edit", + methods=["GET", "POST"], +) @user_is_platform_admin def edit_data_retention(service_id, data_retention_id): data_retention_item = current_service.get_data_retention_item(data_retention_id) - form = AdminServiceEditDataRetentionForm(days_of_retention=data_retention_item['days_of_retention']) + form = AdminServiceEditDataRetentionForm( + days_of_retention=data_retention_item["days_of_retention"] + ) if form.validate_on_submit(): - service_api_client.update_service_data_retention(service_id, data_retention_id, form.days_of_retention.data) - return redirect(url_for('.data_retention', service_id=service_id)) + service_api_client.update_service_data_retention( + service_id, data_retention_id, form.days_of_retention.data + ) + return redirect(url_for(".data_retention", service_id=service_id)) return render_template( - 'views/service-settings/data-retention/edit.html', + "views/service-settings/data-retention/edit.html", form=form, data_retention_id=data_retention_id, - notification_type=data_retention_item['notification_type'] + notification_type=data_retention_item["notification_type"], ) -@main.route("/services//notes", methods=['GET', 'POST']) +@main.route("/services//notes", methods=["GET", "POST"]) @user_is_platform_admin def edit_service_notes(service_id): form = AdminNotesForm(notes=current_service.notes) if form.validate_on_submit(): - if form.notes.data == current_service.notes: - return redirect(url_for('.service_settings', service_id=service_id)) + return redirect(url_for(".service_settings", service_id=service_id)) - current_service.update( - notes=form.notes.data - ) - return redirect(url_for('.service_settings', service_id=service_id)) + current_service.update(notes=form.notes.data) + return redirect(url_for(".service_settings", service_id=service_id)) return render_template( - 'views/service-settings/edit-service-notes.html', + "views/service-settings/edit-service-notes.html", form=form, ) -@main.route("/services//edit-billing-details", methods=['GET', 'POST']) +@main.route("/services//edit-billing-details", methods=["GET", "POST"]) @user_is_platform_admin def edit_service_billing_details(service_id): form = AdminBillingDetailsForm( @@ -1032,31 +1176,26 @@ def edit_service_billing_details(service_id): purchase_order_number=form.purchase_order_number.data, notes=form.notes.data, ) - return redirect(url_for('.service_settings', service_id=service_id)) + return redirect(url_for(".service_settings", service_id=service_id)) return render_template( - 'views/service-settings/edit-service-billing-details.html', + "views/service-settings/edit-service-billing-details.html", form=form, ) def get_branding_as_value_and_label(email_branding): - return [ - (branding['id'], branding['name']) - for branding in email_branding - ] + return [(branding["id"], branding["name"]) for branding in email_branding] def convert_dictionary_to_wtforms_choices_format(dictionary, value, label): - return [ - (item[value], item[label]) for item in dictionary - ] + return [(item[value], item[label]) for item in dictionary] def check_contact_details_type(contact_details): - if contact_details.startswith('http'): - return 'url' - elif '@' in contact_details: - return 'email_address' + if contact_details.startswith("http"): + return "url" + elif "@" in contact_details: + return "email_address" else: - return 'phone_number' + return "phone_number" diff --git a/app/main/views/sign_in.py b/app/main/views/sign_in.py index 54db0a4a1..75ca23117 100644 --- a/app/main/views/sign_in.py +++ b/app/main/views/sign_in.py @@ -18,37 +18,38 @@ from app.utils import hide_from_search_engines from app.utils.login import is_safe_redirect_url -@main.route('/sign-in', methods=(['GET', 'POST'])) +@main.route("/sign-in", methods=(["GET", "POST"])) @hide_from_search_engines def sign_in(): - redirect_url = request.args.get('next') + redirect_url = request.args.get("next") if current_user and current_user.is_authenticated: if redirect_url and is_safe_redirect_url(redirect_url): return redirect(redirect_url) - return redirect(url_for('main.show_accounts_or_dashboard')) + return redirect(url_for("main.show_accounts_or_dashboard")) form = LoginForm() - password_reset_url = url_for('.forgot_password', next=request.args.get('next')) + password_reset_url = url_for(".forgot_password", next=request.args.get("next")) if form.validate_on_submit(): - user = User.from_email_address_and_password_or_none( form.email_address.data, form.password.data ) if user: # add user to session to mark us as in the process of signing the user in - session['user_details'] = {"email": user.email_address, "id": user.id} + session["user_details"] = {"email": user.email_address, "id": user.id} - if user.state == 'pending': - return redirect(url_for('main.resend_email_verification', next=redirect_url)) + if user.state == "pending": + return redirect( + url_for("main.resend_email_verification", next=redirect_url) + ) if user.is_active: - if session.get('invited_user_id'): + if session.get("invited_user_id"): invited_user = InvitedUser.from_session() if user.email_address.lower() != invited_user.email_address.lower(): flash("You cannot accept an invite for another person.") - session.pop('invited_user_id', None) + session.pop("invited_user_id", None) abort(403) else: invited_user.accept_invite() @@ -56,30 +57,32 @@ def sign_in(): user.send_login_code() if user.sms_auth: - return redirect(url_for('.two_factor_sms', next=redirect_url)) + return redirect(url_for(".two_factor_sms", next=redirect_url)) if user.email_auth: - return redirect(url_for('.two_factor_email_sent', next=redirect_url)) + return redirect( + url_for(".two_factor_email_sent", next=redirect_url) + ) # Vague error message for login in case of user not known, locked, inactive or password not verified - flash(Markup( - ( - f"The email address or password you entered is incorrect." - f" Forgot your password?" + flash( + Markup( + ( + f"The email address or password you entered is incorrect." + f" Forgot your password?" + ) ) - )) + ) other_device = current_user.logged_in_elsewhere() return render_template( - 'views/signin.html', + "views/signin.html", form=form, again=bool(redirect_url), other_device=other_device, - password_reset_url=password_reset_url + password_reset_url=password_reset_url, ) @login_manager.unauthorized_handler def sign_in_again(): - return redirect( - url_for('main.sign_in', next=request.path) - ) + return redirect(url_for("main.sign_in", next=request.path)) diff --git a/app/main/views/sign_out.py b/app/main/views/sign_out.py index 38c84a0ef..978397a52 100644 --- a/app/main/views/sign_out.py +++ b/app/main/views/sign_out.py @@ -4,9 +4,9 @@ from flask_login import current_user from app.main import main -@main.route('/sign-out', methods=(['GET'])) +@main.route("/sign-out", methods=(["GET"])) def sign_out(): # An AnonymousUser does not have an id if current_user.is_authenticated: current_user.sign_out() - return redirect(url_for('main.index')) + return redirect(url_for("main.index")) diff --git a/app/main/views/sub_navigation_dictionaries.py b/app/main/views/sub_navigation_dictionaries.py index 9c41fccb7..609e74814 100644 --- a/app/main/views/sub_navigation_dictionaries.py +++ b/app/main/views/sub_navigation_dictionaries.py @@ -12,7 +12,7 @@ def features_nav(): # "name": "Text messages", # "link": "main.features_sms", # }, - ] + ], }, { "name": "Roadmap", diff --git a/app/main/views/templates.py b/app/main/views/templates.py index 8a1e99130..87f32b60f 100644 --- a/app/main/views/templates.py +++ b/app/main/views/templates.py @@ -31,8 +31,8 @@ from app.utils.templates import get_template from app.utils.user import user_has_permissions form_objects = { - 'email': EmailTemplateForm, - 'sms': SMSTemplateForm, + "email": EmailTemplateForm, + "sms": SMSTemplateForm, } @@ -40,16 +40,18 @@ form_objects = { @user_has_permissions() def view_template(service_id, template_id): template = current_service.get_template(template_id) - template_folder = current_service.get_template_folder(template['folder']) + template_folder = current_service.get_template_folder(template["folder"]) - user_has_template_permission = current_user.has_template_folder_permission(template_folder) + user_has_template_permission = current_user.has_template_folder_permission( + template_folder + ) if should_skip_template_page(template): - return redirect(url_for( - '.set_sender', service_id=service_id, template_id=template_id - )) + return redirect( + url_for(".set_sender", service_id=service_id, template_id=template_id) + ) return render_template( - 'views/templates/template.html', + "views/templates/template.html", template=get_template( template, current_service, @@ -59,25 +61,40 @@ def view_template(service_id, template_id): ) -@main.route("/services//templates/all", methods=['GET', 'POST']) -@main.route("/services//templates", methods=['GET', 'POST']) -@main.route("/services//templates/folders/", methods=['GET', 'POST']) -@main.route("/services//templates/", methods=['GET', 'POST']) -@main.route("/services//templates/all/folders/", methods=['GET', 'POST']) +@main.route("/services//templates/all", methods=["GET", "POST"]) +@main.route("/services//templates", methods=["GET", "POST"]) +@main.route( + "/services//templates/folders/", + methods=["GET", "POST"], +) +@main.route( + "/services//templates/", + methods=["GET", "POST"], +) +@main.route( + "/services//templates/all/folders/", + methods=["GET", "POST"], +) @main.route( "/services//templates//folders/", - methods=['GET', 'POST'] + methods=["GET", "POST"], ) @user_has_permissions() -def choose_template(service_id, template_type='all', template_folder_id=None): +def choose_template(service_id, template_type="all", template_folder_id=None): template_folder = current_service.get_template_folder(template_folder_id) - user_has_template_folder_permission = current_user.has_template_folder_permission(template_folder) + user_has_template_folder_permission = current_user.has_template_folder_permission( + template_folder + ) - template_list = TemplateList(current_service, template_type, template_folder_id, current_user) + template_list = TemplateList( + current_service, template_type, template_folder_id, current_user + ) all_template_folders = [ - item.folder for item in TemplateList(service=current_service, user=current_user) if item.is_folder + item.folder + for item in TemplateList(service=current_service, user=current_user) + if item.is_folder ] templates_and_folders_form = TemplateAndFoldersSelectionForm( @@ -89,39 +106,47 @@ def choose_template(service_id, template_type='all', template_folder_id=None): current_service.all_templates or len(current_user.service_ids) > 1 ), ) - option_hints = {template_folder_id: 'current folder'} + option_hints = {template_folder_id: "current folder"} single_notification_channel = None - notification_channels = list(set(current_service.permissions).intersection(NOTIFICATION_TYPES)) + notification_channels = list( + set(current_service.permissions).intersection(NOTIFICATION_TYPES) + ) if len(notification_channels) == 1: single_notification_channel = notification_channels[0] - if request.method == 'POST' and templates_and_folders_form.validate_on_submit(): - if not current_user.has_permissions('manage_templates'): + if request.method == "POST" and templates_and_folders_form.validate_on_submit(): + if not current_user.has_permissions("manage_templates"): abort(403) try: - return process_folder_management_form(templates_and_folders_form, template_folder_id) + return process_folder_management_form( + templates_and_folders_form, template_folder_id + ) except HTTPError as e: flash(e.message) elif templates_and_folders_form.trying_to_add_unavailable_template_type: - return redirect(url_for( - '.action_blocked', - service_id=current_service.id, - notification_type=templates_and_folders_form.add_template_by_template_type.data, - return_to='add_new_template', - )) + return redirect( + url_for( + ".action_blocked", + service_id=current_service.id, + notification_type=templates_and_folders_form.add_template_by_template_type.data, + return_to="add_new_template", + ) + ) - if 'templates_and_folders' in templates_and_folders_form.errors: - flash('Select at least one template or folder') + if "templates_and_folders" in templates_and_folders_form.errors: + flash("Select at least one template or folder") - initial_state = request.args.get('initial_state') - if request.method == 'GET' and initial_state: + initial_state = request.args.get("initial_state") + if request.method == "GET" and initial_state: templates_and_folders_form.op = initial_state return render_template( - 'views/templates/choose.html', + "views/templates/choose.html", current_template_folder_id=template_folder_id, - template_folder_path=current_service.get_template_folder_path(template_folder_id), + template_folder_path=current_service.get_template_folder_path( + template_folder_id + ), template_list=template_list, show_search_box=current_service.count_of_templates_and_folders > 7, show_template_nav=( @@ -135,12 +160,14 @@ def choose_template(service_id, template_type='all', template_folder_id=None): move_to_children=templates_and_folders_form.move_to.children(), user_has_template_folder_permission=user_has_template_folder_permission, single_notification_channel=single_notification_channel, - option_hints=option_hints + option_hints=option_hints, ) def process_folder_management_form(form, current_folder_id): - current_service.get_template_folder_with_user_permission_or_403(current_folder_id, current_user) + current_service.get_template_folder_with_user_permission_or_403( + current_folder_id, current_user + ) new_folder_id = None if form.is_add_template_op: @@ -151,9 +178,7 @@ def process_folder_management_form(form, current_folder_id): if form.is_add_folder_op: new_folder_id = template_folder_api_client.create_template_folder( - current_service.id, - name=form.get_folder_name(), - parent_id=current_folder_id + current_service.id, name=form.get_folder_name(), parent_id=current_folder_id ) if form.is_move_op: @@ -161,8 +186,7 @@ def process_folder_management_form(form, current_folder_id): move_to_id = new_folder_id or form.move_to.data current_service.move_to_folder( - ids_to_move=form.templates_and_folders.data, - move_to=move_to_id + ids_to_move=form.templates_and_folders.data, move_to=move_to_id ) return redirect(request.url) @@ -170,9 +194,9 @@ def process_folder_management_form(form, current_folder_id): def get_template_nav_label(value): return { - 'all': 'All', - 'sms': 'Text message', - 'email': 'Email', + "all": "All", + "sms": "Text message", + "email": "Email", }[value] @@ -182,73 +206,79 @@ def get_template_nav_items(template_folder_id): get_template_nav_label(key), key, url_for( - '.choose_template', service_id=current_service.id, - template_type=key, template_folder_id=template_folder_id + ".choose_template", + service_id=current_service.id, + template_type=key, + template_folder_id=template_folder_id, ), - '' + "", ) - for key in ['all'] + current_service.available_template_types + for key in ["all"] + current_service.available_template_types ] def _view_template_version(service_id, template_id, version): - return dict(template=get_template( - current_service.get_template(template_id, version=version), - current_service - )) + return dict( + template=get_template( + current_service.get_template(template_id, version=version), current_service + ) + ) -@main.route("/services//templates//version/") +@main.route( + "/services//templates//version/" +) @user_has_permissions() def view_template_version(service_id, template_id, version): return render_template( - 'views/templates/template_history.html', - **_view_template_version(service_id=service_id, template_id=template_id, version=version) + "views/templates/template_history.html", + **_view_template_version( + service_id=service_id, template_id=template_id, version=version + ), ) def _add_template_by_type(template_type, template_folder_id): + if template_type == "copy-existing": + return redirect( + url_for( + ".choose_template_to_copy", + service_id=current_service.id, + ) + ) - if template_type == 'copy-existing': - return redirect(url_for( - '.choose_template_to_copy', + return redirect( + url_for( + ".add_service_template", service_id=current_service.id, - )) - - return redirect(url_for( - '.add_service_template', - service_id=current_service.id, - template_type=template_type, - template_folder_id=template_folder_id, - )) + template_type=template_type, + template_folder_id=template_folder_id, + ) + ) @main.route("/services//templates/copy") @main.route("/services//templates/copy/from-folder/") -@main.route("/services//templates/copy/from-service/") +@main.route( + "/services//templates/copy/from-service/" +) @main.route( "/services//templates/copy/from-service//from-folder/" ) -@user_has_permissions('manage_templates') +@user_has_permissions("manage_templates") def choose_template_to_copy( service_id, from_service=None, from_folder=None, ): - if from_service: - current_user.belongs_to_service_or_403(from_service) - service = Service( - service_api_client.get_service(from_service)['data'] - ) + service = Service(service_api_client.get_service(from_service)["data"]) return render_template( - 'views/templates/copy.html', + "views/templates/copy.html", services_templates_and_folders=TemplateList( - service, - template_folder_id=from_folder, - user=current_user + service, template_folder_id=from_folder, user=current_user ), template_folder_path=service.get_template_folder_path(from_folder), from_service=service, @@ -257,97 +287,122 @@ def choose_template_to_copy( else: return render_template( - 'views/templates/copy.html', + "views/templates/copy.html", services_templates_and_folders=TemplateLists(current_user), search_form=SearchTemplatesForm(current_service.api_keys), ) -@main.route("/services//templates/copy/", methods=['GET', 'POST']) -@user_has_permissions('manage_templates') +@main.route( + "/services//templates/copy/", + methods=["GET", "POST"], +) +@user_has_permissions("manage_templates") def copy_template(service_id, template_id): - from_service = request.args.get('from_service') + from_service = request.args.get("from_service") current_user.belongs_to_service_or_403(from_service) - template = service_api_client.get_service_template(from_service, template_id)['data'] + template = service_api_client.get_service_template(from_service, template_id)[ + "data" + ] - template_folder = template_folder_api_client.get_template_folder(from_service, template['folder']) + template_folder = template_folder_api_client.get_template_folder( + from_service, template["folder"] + ) if not current_user.has_template_folder_permission(template_folder): abort(403) - if request.method == 'POST': - return add_service_template(service_id, template['template_type']) + if request.method == "POST": + return add_service_template(service_id, template["template_type"]) - template['template_content'] = template['content'] - template['name'] = _get_template_copy_name(template, current_service.all_templates) - form = form_objects[template['template_type']](**template) + template["template_content"] = template["content"] + template["name"] = _get_template_copy_name(template, current_service.all_templates) + form = form_objects[template["template_type"]](**template) return render_template( - 'views/edit-{}-template.html'.format(template['template_type']), + "views/edit-{}-template.html".format(template["template_type"]), form=form, template=template, - heading_action='Add', + heading_action="Add", services=current_user.service_ids, ) def _get_template_copy_name(template, existing_templates): - - template_names = [existing['name'] for existing in existing_templates] + template_names = [existing["name"] for existing in existing_templates] for index in reversed(range(1, 10)): - if '{} (copy {})'.format(template['name'], index) in template_names: - return '{} (copy {})'.format(template['name'], index + 1) + if "{} (copy {})".format(template["name"], index) in template_names: + return "{} (copy {})".format(template["name"], index + 1) - if '{} (copy)'.format(template['name']) in template_names: - return '{} (copy 2)'.format(template['name']) + if "{} (copy)".format(template["name"]) in template_names: + return "{} (copy 2)".format(template["name"]) - return '{} (copy)'.format(template['name']) + return "{} (copy)".format(template["name"]) -@main.route(( - '/services//templates/action-blocked/' - '/' -)) -@main.route(( - '/services//templates/action-blocked/' - '//' -)) -@user_has_permissions('manage_templates') +@main.route( + ( + "/services//templates/action-blocked/" + "/" + ) +) +@main.route( + ( + "/services//templates/action-blocked/" + "//" + ) +) +@user_has_permissions("manage_templates") def action_blocked(service_id, notification_type, return_to, template_id=None): - back_link = { - 'add_new_template': partial( - url_for, '.choose_template', service_id=current_service.id + "add_new_template": partial( + url_for, ".choose_template", service_id=current_service.id ), - 'templates': partial( - url_for, '.choose_template', service_id=current_service.id + "templates": partial( + url_for, ".choose_template", service_id=current_service.id ), - 'view_template': partial( - url_for, '.view_template', service_id=current_service.id, template_id=template_id + "view_template": partial( + url_for, + ".view_template", + service_id=current_service.id, + template_id=template_id, ), }.get(return_to) - return render_template( - 'views/templates/action_blocked.html', - service_id=service_id, - notification_type=notification_type, - back_link=back_link(), - ), 403 + return ( + render_template( + "views/templates/action_blocked.html", + service_id=service_id, + notification_type=notification_type, + back_link=back_link(), + ), + 403, + ) -@main.route("/services//templates/folders//manage", methods=['GET', 'POST']) -@user_has_permissions('manage_templates') +@main.route( + "/services//templates/folders//manage", + methods=["GET", "POST"], +) +@user_has_permissions("manage_templates") def manage_template_folder(service_id, template_folder_id): - template_folder = current_service.get_template_folder_with_user_permission_or_403(template_folder_id, current_user) + template_folder = current_service.get_template_folder_with_user_permission_or_403( + template_folder_id, current_user + ) form = TemplateFolderForm( - name=template_folder['name'], - users_with_permission=template_folder.get('users_with_permission', None), - all_service_users=[user for user in current_service.active_users if user.id != current_user.id] + name=template_folder["name"], + users_with_permission=template_folder.get("users_with_permission", None), + all_service_users=[ + user for user in current_service.active_users if user.id != current_user.id + ], ) if form.validate_on_submit(): - if current_user.has_permissions("manage_service") and form.users_with_permission.all_service_users: + if ( + current_user.has_permissions("manage_service") + and form.users_with_permission.all_service_users + ): users_with_permission = form.users_with_permission.data + [current_user.id] else: users_with_permission = None @@ -355,86 +410,113 @@ def manage_template_folder(service_id, template_folder_id): current_service.id, template_folder_id, name=form.name.data, - users_with_permission=users_with_permission + users_with_permission=users_with_permission, ) return redirect( - url_for('.choose_template', service_id=service_id, template_folder_id=template_folder_id) + url_for( + ".choose_template", + service_id=service_id, + template_folder_id=template_folder_id, + ) ) return render_template( - 'views/templates/manage-template-folder.html', + "views/templates/manage-template-folder.html", form=form, - template_folder_path=current_service.get_template_folder_path(template_folder_id), + template_folder_path=current_service.get_template_folder_path( + template_folder_id + ), current_service_id=current_service.id, template_folder_id=template_folder_id, template_type="all", ) -@main.route("/services//templates/folders//delete", methods=['GET', 'POST']) -@user_has_permissions('manage_templates') +@main.route( + "/services//templates/folders//delete", + methods=["GET", "POST"], +) +@user_has_permissions("manage_templates") def delete_template_folder(service_id, template_folder_id): - template_folder = current_service.get_template_folder_with_user_permission_or_403(template_folder_id, current_user) - template_list = TemplateList(service=current_service, template_folder_id=template_folder_id) + template_folder = current_service.get_template_folder_with_user_permission_or_403( + template_folder_id, current_user + ) + template_list = TemplateList( + service=current_service, template_folder_id=template_folder_id + ) if not template_list.folder_is_empty: - flash("You must empty this folder before you can delete it", 'info') + flash("You must empty this folder before you can delete it", "info") return redirect( url_for( - '.choose_template', service_id=service_id, template_type="all", template_folder_id=template_folder_id + ".choose_template", + service_id=service_id, + template_type="all", + template_folder_id=template_folder_id, ) ) - if request.method == 'POST': - + if request.method == "POST": try: - template_folder_api_client.delete_template_folder(current_service.id, template_folder_id) + template_folder_api_client.delete_template_folder( + current_service.id, template_folder_id + ) return redirect( - url_for('.choose_template', service_id=service_id, template_folder_id=template_folder['parent_id']) + url_for( + ".choose_template", + service_id=service_id, + template_folder_id=template_folder["parent_id"], + ) ) except HTTPError as e: msg = "Folder is not empty" if e.status_code == 400 and msg in e.message: - flash("You must empty this folder before you can delete it", 'info') + flash("You must empty this folder before you can delete it", "info") return redirect( url_for( - '.choose_template', + ".choose_template", service_id=service_id, template_type="all", - template_folder_id=template_folder_id + template_folder_id=template_folder_id, ) ) else: abort(500, e) else: - flash("Are you sure you want to delete the ‘{}’ folder?".format(template_folder['name']), 'delete') + flash( + "Are you sure you want to delete the ‘{}’ folder?".format( + template_folder["name"] + ), + "delete", + ) return manage_template_folder(service_id, template_folder_id) @main.route( "/services//templates/add-", - methods=['GET', 'POST'], + methods=["GET", "POST"], ) @main.route( "/services//templates/folders//add-", - methods=['GET', 'POST'], + methods=["GET", "POST"], ) -@user_has_permissions('manage_templates') +@user_has_permissions("manage_templates") def add_service_template(service_id, template_type, template_folder_id=None): - if template_type not in current_service.available_template_types: - return redirect(url_for( - '.action_blocked', - service_id=service_id, - notification_type=template_type, - template_folder_id=template_folder_id, - return_to='templates', - )) + return redirect( + url_for( + ".action_blocked", + service_id=service_id, + notification_type=template_type, + template_folder_id=template_folder_id, + return_to="templates", + ) + ) form = form_objects[template_type]() if form.validate_on_submit(): - if form.process_type.data == 'priority': + if form.process_type.data == "priority": abort_403_if_not_admin_user() try: new_template = service_api_client.create_service_template( @@ -442,29 +524,35 @@ def add_service_template(service_id, template_type, template_folder_id=None): template_type, form.template_content.data, service_id, - form.subject.data if hasattr(form, 'subject') else None, - template_folder_id + form.subject.data if hasattr(form, "subject") else None, + template_folder_id, ) except HTTPError as e: if ( e.status_code == 400 - and 'content' in e.message - and any(['character count greater than' in x for x in e.message['content']]) + and "content" in e.message + and any( + ["character count greater than" in x for x in e.message["content"]] + ) ): - form.template_content.errors.extend(e.message['content']) + form.template_content.errors.extend(e.message["content"]) else: raise e else: return redirect( - url_for('.view_template', service_id=service_id, template_id=new_template['data']['id']) + url_for( + ".view_template", + service_id=service_id, + template_id=new_template["data"]["id"], + ) ) return render_template( - 'views/edit-{}-template.html'.format(template_type), + "views/edit-{}-template.html".format(template_type), form=form, template_type=template_type, template_folder_id=template_folder_id, - heading_action='New', + heading_action="New", ) @@ -473,38 +561,45 @@ def abort_403_if_not_admin_user(): abort(403) -@main.route("/services//templates//edit", methods=['GET', 'POST']) -@user_has_permissions('manage_templates') +@main.route( + "/services//templates//edit", + methods=["GET", "POST"], +) +@user_has_permissions("manage_templates") def edit_service_template(service_id, template_id): - template = current_service.get_template_with_user_permission_or_403(template_id, current_user) - template['template_content'] = template['content'] - form = form_objects[template['template_type']](**template) + template = current_service.get_template_with_user_permission_or_403( + template_id, current_user + ) + template["template_content"] = template["content"] + form = form_objects[template["template_type"]](**template) if form.validate_on_submit(): - if form.process_type.data != template['process_type']: + if form.process_type.data != template["process_type"]: abort_403_if_not_admin_user() - subject = form.subject.data if hasattr(form, 'subject') else None + subject = form.subject.data if hasattr(form, "subject") else None new_template_data = { - 'name': form.name.data, - 'content': form.template_content.data, - 'subject': subject, - 'template_type': template['template_type'], - 'id': template['id'], - 'process_type': form.process_type.data, - 'reply_to_text': template['reply_to_text'], + "name": form.name.data, + "content": form.template_content.data, + "subject": subject, + "template_type": template["template_type"], + "id": template["id"], + "process_type": form.process_type.data, + "reply_to_text": template["reply_to_text"], } new_template = get_template(new_template_data, current_service) - template_change = get_template(template, current_service).compare_to(new_template) + template_change = get_template(template, current_service).compare_to( + new_template + ) if ( - template_change.placeholders_added and - not request.form.get('confirm') and - current_service.api_keys + template_change.placeholders_added + and not request.form.get("confirm") + and current_service.api_keys ): return render_template( - 'views/templates/breaking-change.html', + "views/templates/breaking-change.html", template_change=template_change, new_template=new_template, form=form, @@ -513,106 +608,128 @@ def edit_service_template(service_id, template_id): service_api_client.update_service_template( template_id, form.name.data, - template['template_type'], + template["template_type"], form.template_content.data, service_id, - subject + subject, ) except HTTPError as e: if e.status_code == 400: - if 'content' in e.message and any(['character count greater than' in x for x in e.message['content']]): - form.template_content.errors.extend(e.message['content']) + if "content" in e.message and any( + ["character count greater than" in x for x in e.message["content"]] + ): + form.template_content.errors.extend(e.message["content"]) else: raise e else: raise e else: - return redirect(url_for( - '.view_template', - service_id=service_id, - template_id=template_id - )) + return redirect( + url_for( + ".view_template", service_id=service_id, template_id=template_id + ) + ) - if template['template_type'] not in current_service.available_template_types: - return redirect(url_for( - '.action_blocked', - service_id=service_id, - notification_type=template['template_type'], - return_to='view_template', - template_id=template_id - )) + if template["template_type"] not in current_service.available_template_types: + return redirect( + url_for( + ".action_blocked", + service_id=service_id, + notification_type=template["template_type"], + return_to="view_template", + template_id=template_id, + ) + ) else: return render_template( - 'views/edit-{}-template.html'.format(template['template_type']), + "views/edit-{}-template.html".format(template["template_type"]), form=form, template=template, - heading_action='Edit', + heading_action="Edit", ) @main.route( "/services//templates/count--length", - methods=['POST'], + methods=["POST"], ) @user_has_permissions() def count_content_length(service_id, template_type): - if template_type not in {'sms'}: + if template_type not in {"sms"}: abort(404) error, message = _get_content_count_error_and_message_for_template( - get_template({ - 'template_type': template_type, - 'content': request.form.get('template_content', ''), - }, current_service) + get_template( + { + "template_type": template_type, + "content": request.form.get("template_content", ""), + }, + current_service, + ) ) - return jsonify({ - 'html': render_template( - 'partials/templates/content-count-message.html', - error=error, - message=message, - ) - }) + return jsonify( + { + "html": render_template( + "partials/templates/content-count-message.html", + error=error, + message=message, + ) + } + ) def _get_content_count_error_and_message_for_template(template): - - if template.template_type == 'sms': + if template.template_type == "sms": if template.is_message_too_long(): return True, ( - f'You have ' - f'{character_count(template.content_count_without_prefix - SMS_CHAR_COUNT_LIMIT)} ' - f'too many' + f"You have " + f"{character_count(template.content_count_without_prefix - SMS_CHAR_COUNT_LIMIT)} " + f"too many" ) if template.placeholders: return False, ( - f'Will be charged as {message_count(template.fragment_count, template.template_type)} ' - f'(not including personalization)' + f"Will be charged as {message_count(template.fragment_count, template.template_type)} " + f"(not including personalization)" ) return False, ( - f'Will be charged as {message_count(template.fragment_count, template.template_type)} ' + f"Will be charged as {message_count(template.fragment_count, template.template_type)} " ) -@main.route("/services//templates//delete", methods=['GET', 'POST']) -@user_has_permissions('manage_templates') +@main.route( + "/services//templates//delete", + methods=["GET", "POST"], +) +@user_has_permissions("manage_templates") def delete_service_template(service_id, template_id): - template = current_service.get_template_with_user_permission_or_403(template_id, current_user) + template = current_service.get_template_with_user_permission_or_403( + template_id, current_user + ) - if request.method == 'POST': + if request.method == "POST": service_api_client.delete_service_template(service_id, template_id) - return redirect(url_for( - '.choose_template', - service_id=service_id, - template_folder_id=template['folder'], - )) + return redirect( + url_for( + ".choose_template", + service_id=service_id, + template_folder_id=template["folder"], + ) + ) try: - last_used_notification = template_statistics_client.get_last_used_date_for_template( - service_id, template['id'] + last_used_notification = ( + template_statistics_client.get_last_used_date_for_template( + service_id, template["id"] + ) + ) + message = ( + "This template has never been used." + if not last_used_notification + else "This template was last used {}.".format( + format_delta(last_used_notification) + ) ) - message = 'This template has never been used.' if not last_used_notification else \ - 'This template was last used {}.'.format(format_delta(last_used_notification)) except HTTPError as e: if e.status_code == 404: @@ -620,9 +737,16 @@ def delete_service_template(service_id, template_id): else: raise e - flash(["Are you sure you want to delete ‘{}’?".format(template['name']), message, template['name']], 'delete') + flash( + [ + "Are you sure you want to delete ‘{}’?".format(template["name"]), + message, + template["name"], + ], + "delete", + ) return render_template( - 'views/templates/template.html', + "views/templates/template.html", template=get_template( template, current_service, @@ -632,13 +756,17 @@ def delete_service_template(service_id, template_id): ) -@main.route("/services//templates//redact", methods=['GET']) -@user_has_permissions('manage_templates') +@main.route( + "/services//templates//redact", methods=["GET"] +) +@user_has_permissions("manage_templates") def confirm_redact_template(service_id, template_id): - template = current_service.get_template_with_user_permission_or_403(template_id, current_user) + template = current_service.get_template_with_user_permission_or_403( + template_id, current_user + ) return render_template( - 'views/templates/template.html', + "views/templates/template.html", template=get_template( template, current_service, @@ -649,58 +777,70 @@ def confirm_redact_template(service_id, template_id): ) -@main.route("/services//templates//redact", methods=['POST']) -@user_has_permissions('manage_templates') +@main.route( + "/services//templates//redact", methods=["POST"] +) +@user_has_permissions("manage_templates") def redact_template(service_id, template_id): - service_api_client.redact_service_template(service_id, template_id) flash( - 'Personalized content will be hidden for messages sent with this template', - 'default_with_tick' + "Personalized content will be hidden for messages sent with this template", + "default_with_tick", ) - return redirect(url_for( - '.view_template', - service_id=service_id, - template_id=template_id, - )) + return redirect( + url_for( + ".view_template", + service_id=service_id, + template_id=template_id, + ) + ) -@main.route('/services//templates//versions') -@user_has_permissions('view_activity') +@main.route("/services//templates//versions") +@user_has_permissions("view_activity") def view_template_versions(service_id, template_id): return render_template( - 'views/templates/choose_history.html', + "views/templates/choose_history.html", versions=[ get_template( template, current_service, ) - for template in service_api_client.get_service_template_versions(service_id, template_id)['data'] - ] + for template in service_api_client.get_service_template_versions( + service_id, template_id + )["data"] + ], ) -@main.route('/services//templates//set-template-sender', methods=['GET', 'POST']) -@user_has_permissions('manage_templates') +@main.route( + "/services//templates//set-template-sender", + methods=["GET", "POST"], +) +@user_has_permissions("manage_templates") def set_template_sender(service_id, template_id): - template = current_service.get_template_with_user_permission_or_403(template_id, current_user) + template = current_service.get_template_with_user_permission_or_403( + template_id, current_user + ) sender_details = get_template_sender_form_dict(service_id, template) - no_senders = sender_details.get('no_senders', False) + no_senders = sender_details.get("no_senders", False) form = SetTemplateSenderForm( - sender=sender_details['current_choice'], - sender_choices=sender_details['value_and_label'], + sender=sender_details["current_choice"], + sender_choices=sender_details["value_and_label"], ) - form.sender.param_extensions = {'items': []} - for item_value, _item_label in sender_details['value_and_label']: - if item_value == sender_details['default_sender']: - extensions = {'hint': {'text': '(Default)'}} + form.sender.param_extensions = {"items": []} + for item_value, _item_label in sender_details["value_and_label"]: + if item_value == sender_details["default_sender"]: + extensions = {"hint": {"text": "(Default)"}} else: - extensions = {} # if no extensions needed, send an empty dict to preserve order of items + extensions = ( + {} + ) # if no extensions needed, send an empty dict to preserve order of items - form.sender.param_extensions['items'].append(extensions) + form.sender.param_extensions["items"].append(extensions) if form.validate_on_submit(): service_api_client.update_service_template_sender( @@ -708,36 +848,38 @@ def set_template_sender(service_id, template_id): template_id, form.sender.data if form.sender.data else None, ) - return redirect(url_for('.view_template', service_id=service_id, template_id=template_id)) + return redirect( + url_for(".view_template", service_id=service_id, template_id=template_id) + ) return render_template( - 'views/templates/set-template-sender.html', + "views/templates/set-template-sender.html", form=form, template_id=template_id, - no_senders=no_senders + no_senders=no_senders, ) def get_template_sender_form_dict(service_id, template): context = { - 'email': { - 'field_name': 'email_address' - }, - 'sms': { - 'field_name': 'sms_sender' - } - }[template['template_type']] + "email": {"field_name": "email_address"}, + "sms": {"field_name": "sms_sender"}, + }[template["template_type"]] - sender_format = context['field_name'] - service_senders = get_sender_details(service_id, template['template_type']) - context['default_sender'] = next( - (x['id'] for x in service_senders if x['is_default']), "Not set" + sender_format = context["field_name"] + service_senders = get_sender_details(service_id, template["template_type"]) + context["default_sender"] = next( + (x["id"] for x in service_senders if x["is_default"]), "Not set" ) if not service_senders: - context['no_senders'] = True + context["no_senders"] = True - context['value_and_label'] = [(sender['id'], nl2br(sender[sender_format])) for sender in service_senders] - context['value_and_label'].insert(0, ('', 'Blank')) # Add blank option to start of list + context["value_and_label"] = [ + (sender["id"], nl2br(sender[sender_format])) for sender in service_senders + ] + context["value_and_label"].insert( + 0, ("", "Blank") + ) # Add blank option to start of list - context['current_choice'] = '' + context["current_choice"] = "" return context diff --git a/app/main/views/tour.py b/app/main/views/tour.py index 63feb90bb..3a93b7384 100644 --- a/app/main/views/tour.py +++ b/app/main/views/tour.py @@ -14,11 +14,13 @@ from app.utils.user import user_has_permissions @main.route("/services//tour/") -@user_has_permissions('send_messages') +@user_has_permissions("send_messages") def begin_tour(service_id, template_id): - db_template = current_service.get_template_with_user_permission_or_403(template_id, current_user) + db_template = current_service.get_template_with_user_permission_or_403( + template_id, current_user + ) - if (db_template['template_type'] != 'sms' or not current_user.mobile_number): + if db_template["template_type"] != "sms" or not current_user.mobile_number: abort(404) template = get_template( @@ -29,31 +31,37 @@ def begin_tour(service_id, template_id): template.values = {"phone_number": current_user.mobile_number} - session['placeholders'] = {} + session["placeholders"] = {} return render_template( - 'views/templates/start-tour.html', + "views/templates/start-tour.html", template=template, - help='1', - continue_link=url_for('.tour_step', service_id=service_id, template_id=template_id, step_index=1) + help="1", + continue_link=url_for( + ".tour_step", service_id=service_id, template_id=template_id, step_index=1 + ), ) @main.route( "/services//tour//step-", - methods=['GET', 'POST'], + methods=["GET", "POST"], ) -@user_has_permissions('send_messages', restrict_admin_usage=True) +@user_has_permissions("send_messages", restrict_admin_usage=True) def tour_step(service_id, template_id, step_index): - db_template = current_service.get_template_with_user_permission_or_403(template_id, current_user) + db_template = current_service.get_template_with_user_permission_or_403( + template_id, current_user + ) - if (db_template['template_type'] != 'sms' or step_index == 0): + if db_template["template_type"] != "sms" or step_index == 0: abort(404) - if 'placeholders' not in session: - return redirect(url_for( - '.begin_tour', service_id=current_service.id, template_id=template_id - )) + if "placeholders" not in session: + return redirect( + url_for( + ".begin_tour", service_id=current_service.id, template_id=template_id + ) + ) template = get_template( db_template, @@ -67,57 +75,88 @@ def tour_step(service_id, template_id, step_index): current_placeholder = placeholders[step_index - 1] except IndexError: if all_placeholders_in_session(placeholders): - return redirect(url_for( - '.check_tour_notification', service_id=current_service.id, template_id=template_id - )) - return redirect(url_for( - '.tour_step', service_id=current_service.id, template_id=template_id, step_index=1 - )) + return redirect( + url_for( + ".check_tour_notification", + service_id=current_service.id, + template_id=template_id, + ) + ) + return redirect( + url_for( + ".tour_step", + service_id=current_service.id, + template_id=template_id, + step_index=1, + ) + ) form = get_placeholder_form_instance( current_placeholder, dict_to_populate_from=get_normalised_placeholders_from_session(), template_type=template.template_type, - allow_international_phone_numbers=current_service.has_permission('international_sms') + allow_international_phone_numbers=current_service.has_permission( + "international_sms" + ), ) if form.validate_on_submit(): - session['placeholders'][current_placeholder] = form.placeholder_value.data + session["placeholders"][current_placeholder] = form.placeholder_value.data if all_placeholders_in_session(placeholders): - return redirect(url_for( - '.check_tour_notification', service_id=current_service.id, template_id=template_id - )) - return redirect(url_for( - '.tour_step', service_id=current_service.id, template_id=template_id, step_index=step_index + 1 - )) + return redirect( + url_for( + ".check_tour_notification", + service_id=current_service.id, + template_id=template_id, + ) + ) + return redirect( + url_for( + ".tour_step", + service_id=current_service.id, + template_id=template_id, + step_index=step_index + 1, + ) + ) back_link = _get_tour_step_back_link(service_id, template_id, step_index) - template.values = get_recipient_and_placeholders_from_session(db_template['template_type']) + template.values = get_recipient_and_placeholders_from_session( + db_template["template_type"] + ) template.values[current_placeholder] = None return render_template( - 'views/send-test.html', + "views/send-test.html", page_title="Example text message", template=template, form=form, back_link=back_link, - help='2' + help="2", ) def _get_tour_step_back_link(service_id, template_id, step_index): if step_index == 1: - return url_for('.begin_tour', service_id=service_id, template_id=template_id) + return url_for(".begin_tour", service_id=service_id, template_id=template_id) - return url_for('.tour_step', service_id=service_id, template_id=template_id, step_index=step_index - 1) + return url_for( + ".tour_step", + service_id=service_id, + template_id=template_id, + step_index=step_index - 1, + ) -@main.route("/services//tour//check", methods=['GET']) -@user_has_permissions('send_messages', restrict_admin_usage=True) +@main.route( + "/services//tour//check", methods=["GET"] +) +@user_has_permissions("send_messages", restrict_admin_usage=True) def check_tour_notification(service_id, template_id): - db_template = current_service.get_template_with_user_permission_or_403(template_id, current_user) + db_template = current_service.get_template_with_user_permission_or_403( + template_id, current_user + ) template = get_template( db_template, @@ -125,38 +164,47 @@ def check_tour_notification(service_id, template_id): show_recipient=True, ) - if 'placeholders' not in session: - return redirect(url_for( - '.begin_tour', service_id=current_service.id, template_id=template_id - )) + if "placeholders" not in session: + return redirect( + url_for( + ".begin_tour", service_id=current_service.id, template_id=template_id + ) + ) placeholders = fields_to_fill_in(template, prefill_current_user=True) if not all_placeholders_in_session(template.placeholders): - return redirect(url_for( - '.tour_step', service_id=current_service.id, template_id=template_id, step_index=1 - )) + return redirect( + url_for( + ".tour_step", + service_id=current_service.id, + template_id=template_id, + step_index=1, + ) + ) back_link = url_for( - '.tour_step', service_id=current_service.id, template_id=template_id, step_index=len(placeholders) + ".tour_step", + service_id=current_service.id, + template_id=template_id, + step_index=len(placeholders), ) - template.values = get_recipient_and_placeholders_from_session(template.template_type) + template.values = get_recipient_and_placeholders_from_session( + template.template_type + ) return render_template( - 'views/notifications/check.html', + "views/notifications/check.html", template=template, back_link=back_link, - help='2', + help="2", ) @main.route("/services//end-tour/") -@user_has_permissions('manage_templates') +@user_has_permissions("manage_templates") def go_to_dashboard_after_tour(service_id, example_template_id): - service_api_client.delete_service_template(service_id, example_template_id) - return redirect( - url_for('main.service_dashboard', service_id=service_id) - ) + return redirect(url_for("main.service_dashboard", service_id=service_id)) diff --git a/app/main/views/two_factor.py b/app/main/views/two_factor.py index ffa429a1c..560df1fad 100644 --- a/app/main/views/two_factor.py +++ b/app/main/views/two_factor.py @@ -17,71 +17,83 @@ from app.utils.login import ( ) -@main.route('/two-factor-email-sent', methods=['GET']) +@main.route("/two-factor-email-sent", methods=["GET"]) def two_factor_email_sent(): - title = 'Email resent' if request.args.get('email_resent') else 'Check your email' + title = "Email resent" if request.args.get("email_resent") else "Check your email" return render_template( - 'views/two-factor-email.html', + "views/two-factor-email.html", title=title, - redirect_url=request.args.get('next') + redirect_url=request.args.get("next"), ) -@main.route('/email-auth/', methods=['GET']) +@main.route("/email-auth/", methods=["GET"]) def two_factor_email_interstitial(token): - return render_template('views/email-link-interstitial.html') + return render_template("views/email-link-interstitial.html") -@main.route('/email-auth/', methods=['POST']) +@main.route("/email-auth/", methods=["POST"]) def two_factor_email(token): - redirect_url = request.args.get('next') + redirect_url = request.args.get("next") if current_user.is_authenticated: return redirect_when_logged_in(platform_admin=current_user.platform_admin) # checks url is valid, and hasn't timed out try: - token_data = json.loads(check_token( - token, - current_app.config['SECRET_KEY'], - current_app.config['DANGEROUS_SALT'], - current_app.config['EMAIL_EXPIRY_SECONDS'] - )) + token_data = json.loads( + check_token( + token, + current_app.config["SECRET_KEY"], + current_app.config["DANGEROUS_SALT"], + current_app.config["EMAIL_EXPIRY_SECONDS"], + ) + ) except SignatureExpired: - return render_template('views/email-link-invalid.html', redirect_url=redirect_url) + return render_template( + "views/email-link-invalid.html", redirect_url=redirect_url + ) - user_id = token_data['user_id'] + user_id = token_data["user_id"] # checks if code was already used - logged_in, msg = user_api_client.check_verify_code(user_id, token_data['secret_code'], "email") + logged_in, msg = user_api_client.check_verify_code( + user_id, token_data["secret_code"], "email" + ) if not logged_in: - return render_template('views/email-link-invalid.html', redirect_url=redirect_url) + return render_template( + "views/email-link-invalid.html", redirect_url=redirect_url + ) return log_in_user(user_id) -@main.route('/two-factor-sms', methods=['GET', 'POST']) +@main.route("/two-factor-sms", methods=["GET", "POST"]) @redirect_to_sign_in def two_factor_sms(): - user_id = session['user_details']['id'] + user_id = session["user_details"]["id"] user = User.from_id(user_id) def _check_code(code): return user_api_client.check_verify_code(user_id, code, "sms") form = TwoFactorForm(_check_code) - redirect_url = request.args.get('next') + redirect_url = request.args.get("next") if form.validate_on_submit(): if email_needs_revalidating(user): - user_api_client.send_verify_code(user.id, 'email', None, redirect_url) - return redirect(url_for('.revalidate_email_sent', next=redirect_url)) + user_api_client.send_verify_code(user.id, "email", None, redirect_url) + return redirect(url_for(".revalidate_email_sent", next=redirect_url)) else: return log_in_user(user_id) - return render_template('views/two-factor-sms.html', form=form, redirect_url=redirect_url) + return render_template( + "views/two-factor-sms.html", form=form, redirect_url=redirect_url + ) -@main.route('/re-validate-email', methods=['GET']) +@main.route("/re-validate-email", methods=["GET"]) def revalidate_email_sent(): - title = 'Email resent' if request.args.get('email_resent') else 'Check your email' - redirect_url = request.args.get('next') - return render_template('views/re-validate-email-sent.html', title=title, redirect_url=redirect_url) + title = "Email resent" if request.args.get("email_resent") else "Check your email" + redirect_url = request.args.get("next") + return render_template( + "views/re-validate-email-sent.html", title=title, redirect_url=redirect_url + ) diff --git a/app/main/views/uploads.py b/app/main/views/uploads.py index 0fcc6184d..d8b8ac57c 100644 --- a/app/main/views/uploads.py +++ b/app/main/views/uploads.py @@ -15,25 +15,24 @@ MAX_FILE_UPLOAD_SIZE = 2 * 1024 * 1024 # 2MB def uploads(service_id): # No tests have been written, this has been quickly prepared for user research. # It's also very like that a new view will be created to show uploads. - uploads = current_service.get_page_of_uploads(page=request.args.get('page')) + uploads = current_service.get_page_of_uploads(page=request.args.get("page")) prev_page = None if uploads.prev_page: - prev_page = generate_previous_dict('main.uploads', service_id, uploads.current_page) + prev_page = generate_previous_dict( + "main.uploads", service_id, uploads.current_page + ) next_page = None if uploads.next_page: - next_page = generate_next_dict('main.uploads', service_id, uploads.current_page) + next_page = generate_next_dict("main.uploads", service_id, uploads.current_page) if uploads.current_page == 1: - listed_uploads = ( - current_service.scheduled_jobs + - uploads - ) + listed_uploads = current_service.scheduled_jobs + uploads else: listed_uploads = uploads return render_template( - 'views/jobs/jobs.html', + "views/jobs/jobs.html", jobs=listed_uploads, prev_page=prev_page, next_page=next_page, diff --git a/app/main/views/user_profile.py b/app/main/views/user_profile.py index 34558b238..9bd844254 100644 --- a/app/main/views/user_profile.py +++ b/app/main/views/user_profile.py @@ -31,149 +31,155 @@ from app.main.forms import ( from app.models.user import User from app.utils.user import user_is_gov_user, user_is_logged_in -NEW_EMAIL = 'new-email' -NEW_MOBILE = 'new-mob' -NEW_MOBILE_PASSWORD_CONFIRMED = 'new-mob-password-confirmed' # nosec B105 - this is not a password +NEW_EMAIL = "new-email" +NEW_MOBILE = "new-mob" +NEW_MOBILE_PASSWORD_CONFIRMED = ( + "new-mob-password-confirmed" # nosec B105 - this is not a password +) @main.route("/user-profile") @user_is_logged_in def user_profile(): return render_template( - 'views/user-profile.html', + "views/user-profile.html", can_see_edit=current_user.is_gov_user, ) -@main.route("/user-profile/name", methods=['GET', 'POST']) +@main.route("/user-profile/name", methods=["GET", "POST"]) @user_is_logged_in def user_profile_name(): - form = ChangeNameForm(new_name=current_user.name) if form.validate_on_submit(): current_user.update(name=form.new_name.data) - return redirect(url_for('.user_profile')) + return redirect(url_for(".user_profile")) return render_template( - 'views/user-profile/change.html', - thing='name', - form_field=form.new_name + "views/user-profile/change.html", thing="name", form_field=form.new_name ) -@main.route("/user-profile/email", methods=['GET', 'POST']) +@main.route("/user-profile/email", methods=["GET", "POST"]) @user_is_logged_in @user_is_gov_user def user_profile_email(): - - form = ChangeEmailForm(User.already_registered, - email_address=current_user.email_address) + form = ChangeEmailForm( + User.already_registered, email_address=current_user.email_address + ) if form.validate_on_submit(): session[NEW_EMAIL] = form.email_address.data - return redirect(url_for('.user_profile_email_authenticate')) + return redirect(url_for(".user_profile_email_authenticate")) return render_template( - 'views/user-profile/change.html', - thing='email address', - form_field=form.email_address + "views/user-profile/change.html", + thing="email address", + form_field=form.email_address, ) -@main.route("/user-profile/email/authenticate", methods=['GET', 'POST']) +@main.route("/user-profile/email/authenticate", methods=["GET", "POST"]) @user_is_logged_in def user_profile_email_authenticate(): # Validate password for form def _check_password(pwd): return user_api_client.verify_password(current_user.id, pwd) + form = ConfirmPasswordForm(_check_password) if NEW_EMAIL not in session: - return redirect('main.user_profile_email') + return redirect("main.user_profile_email") if form.validate_on_submit(): - user_api_client.send_change_email_verification(current_user.id, session[NEW_EMAIL]) + user_api_client.send_change_email_verification( + current_user.id, session[NEW_EMAIL] + ) create_email_change_event( user_id=current_user.id, updated_by_id=current_user.id, original_email_address=current_user.email_address, new_email_address=session[NEW_EMAIL], ) - return render_template('views/change-email-continue.html', - new_email=session[NEW_EMAIL]) + return render_template( + "views/change-email-continue.html", new_email=session[NEW_EMAIL] + ) return render_template( - 'views/user-profile/authenticate.html', - thing='email address', + "views/user-profile/authenticate.html", + thing="email address", form=form, - back_link=url_for('.user_profile_email') + back_link=url_for(".user_profile_email"), ) -@main.route("/user-profile/email/confirm/", methods=['GET']) +@main.route("/user-profile/email/confirm/", methods=["GET"]) @user_is_logged_in def user_profile_email_confirm(token): - token_data = check_token(token, - current_app.config['SECRET_KEY'], - current_app.config['DANGEROUS_SALT'], - current_app.config['EMAIL_EXPIRY_SECONDS']) + token_data = check_token( + token, + current_app.config["SECRET_KEY"], + current_app.config["DANGEROUS_SALT"], + current_app.config["EMAIL_EXPIRY_SECONDS"], + ) token_data = json.loads(token_data) - user = User.from_id(token_data['user_id']) - user.update(email_address=token_data['email']) + user = User.from_id(token_data["user_id"]) + user.update(email_address=token_data["email"]) session.pop(NEW_EMAIL, None) - return redirect(url_for('.user_profile')) + return redirect(url_for(".user_profile")) -@main.route("/user-profile/mobile-number", methods=['GET', 'POST']) +@main.route("/user-profile/mobile-number", methods=["GET", "POST"]) @main.route( "/user-profile/mobile-number/delete", - methods=['GET'], - endpoint="user_profile_confirm_delete_mobile_number" + methods=["GET"], + endpoint="user_profile_confirm_delete_mobile_number", ) @user_is_logged_in def user_profile_mobile_number(): - user = User.from_id(current_user.id) form = ChangeMobileNumberForm(mobile_number=current_user.mobile_number) if form.validate_on_submit(): session[NEW_MOBILE] = form.mobile_number.data - return redirect(url_for('.user_profile_mobile_number_authenticate')) + return redirect(url_for(".user_profile_mobile_number_authenticate")) - if (request.endpoint == "main.user_profile_confirm_delete_mobile_number"): - flash("Are you sure you want to delete your mobile number from Notify?", 'delete') + if request.endpoint == "main.user_profile_confirm_delete_mobile_number": + flash( + "Are you sure you want to delete your mobile number from Notify?", "delete" + ) return render_template( - 'views/user-profile/change.html', - thing='mobile number', + "views/user-profile/change.html", + thing="mobile number", form_field=form.mobile_number, - user_auth=user.auth_type + user_auth=user.auth_type, ) -@main.route("/user-profile/mobile-number/delete", methods=['POST']) +@main.route("/user-profile/mobile-number/delete", methods=["POST"]) @user_is_logged_in def user_profile_mobile_number_delete(): - if current_user.auth_type != 'email_auth': + if current_user.auth_type != "email_auth": abort(403) current_user.update(mobile_number=None) - return redirect(url_for('.user_profile')) + return redirect(url_for(".user_profile")) -@main.route("/user-profile/mobile-number/authenticate", methods=['GET', 'POST']) +@main.route("/user-profile/mobile-number/authenticate", methods=["GET", "POST"]) @user_is_logged_in def user_profile_mobile_number_authenticate(): - # Validate password for form def _check_password(pwd): return user_api_client.verify_password(current_user.id, pwd) + form = ConfirmPasswordForm(_check_password) if NEW_MOBILE not in session: - return redirect(url_for('.user_profile_mobile_number')) + return redirect(url_for(".user_profile_mobile_number")) if form.validate_on_submit(): session[NEW_MOBILE_PASSWORD_CONFIRMED] = True @@ -182,28 +188,27 @@ def user_profile_mobile_number_authenticate(): user_id=current_user.id, updated_by_id=current_user.id, original_mobile_number=current_user.mobile_number, - new_mobile_number=session[NEW_MOBILE] + new_mobile_number=session[NEW_MOBILE], ) - return redirect(url_for('.user_profile_mobile_number_confirm')) + return redirect(url_for(".user_profile_mobile_number_confirm")) return render_template( - 'views/user-profile/authenticate.html', - thing='mobile number', + "views/user-profile/authenticate.html", + thing="mobile number", form=form, - back_link=url_for('.user_profile_mobile_number_confirm') + back_link=url_for(".user_profile_mobile_number_confirm"), ) -@main.route("/user-profile/mobile-number/confirm", methods=['GET', 'POST']) +@main.route("/user-profile/mobile-number/confirm", methods=["GET", "POST"]) @user_is_logged_in def user_profile_mobile_number_confirm(): - # Validate verify code for form def _check_code(cde): - return user_api_client.check_verify_code(current_user.id, cde, 'sms') + return user_api_client.check_verify_code(current_user.id, cde, "sms") if NEW_MOBILE_PASSWORD_CONFIRMED not in session: - return redirect(url_for('.user_profile_mobile_number')) + return redirect(url_for(".user_profile_mobile_number")) form = TwoFactorForm(_check_code) @@ -213,54 +218,56 @@ def user_profile_mobile_number_confirm(): del session[NEW_MOBILE] del session[NEW_MOBILE_PASSWORD_CONFIRMED] current_user.update(mobile_number=mobile_number) - return redirect(url_for('.user_profile')) + return redirect(url_for(".user_profile")) return render_template( - 'views/user-profile/confirm.html', + "views/user-profile/confirm.html", form_field=form.sms_code, - thing='mobile number' + thing="mobile number", ) -@main.route("/user-profile/password", methods=['GET', 'POST']) +@main.route("/user-profile/password", methods=["GET", "POST"]) @user_is_logged_in def user_profile_password(): - # Validate password for form def _check_password(pwd): return user_api_client.verify_password(current_user.id, pwd) + form = ChangePasswordForm(_check_password) if form.validate_on_submit(): - user_api_client.update_password(current_user.id, password=form.new_password.data) - return redirect(url_for('.user_profile')) + user_api_client.update_password( + current_user.id, password=form.new_password.data + ) + return redirect(url_for(".user_profile")) - return render_template( - 'views/user-profile/change-password.html', - form=form - ) + return render_template("views/user-profile/change-password.html", form=form) -@main.route("/user-profile/disable-platform-admin-view", methods=['GET', 'POST']) +@main.route("/user-profile/disable-platform-admin-view", methods=["GET", "POST"]) @user_is_logged_in def user_profile_disable_platform_admin_view(): - if not current_user.platform_admin and not session.get('disable_platform_admin_view'): + if not current_user.platform_admin and not session.get( + "disable_platform_admin_view" + ): abort(403) form = ServiceOnOffSettingForm( name="Use platform admin view", - enabled=not session.get('disable_platform_admin_view'), - truthy='Yes', - falsey='No', + enabled=not session.get("disable_platform_admin_view"), + truthy="Yes", + falsey="No", ) - form.enabled.param_extensions = {"hint": {"text": "Signing in again clears this setting"}} + form.enabled.param_extensions = { + "hint": {"text": "Signing in again clears this setting"} + } if form.validate_on_submit(): - session['disable_platform_admin_view'] = not form.enabled.data - return redirect(url_for('.user_profile')) + session["disable_platform_admin_view"] = not form.enabled.data + return redirect(url_for(".user_profile")) return render_template( - 'views/user-profile/disable-platform-admin-view.html', - form=form + "views/user-profile/disable-platform-admin-view.html", form=form ) diff --git a/app/main/views/verify.py b/app/main/views/verify.py index 6f3470a74..0201edfcc 100644 --- a/app/main/views/verify.py +++ b/app/main/views/verify.py @@ -11,80 +11,82 @@ from app.models.user import InvitedOrgUser, InvitedUser, User from app.utils.login import redirect_to_sign_in -@main.route('/verify', methods=['GET', 'POST']) +@main.route("/verify", methods=["GET", "POST"]) @redirect_to_sign_in def verify(): - user_id = session['user_details']['id'] + user_id = session["user_details"]["id"] def _check_code(code): - return user_api_client.check_verify_code(user_id, code, 'sms') + return user_api_client.check_verify_code(user_id, code, "sms") form = TwoFactorForm(_check_code) if form.validate_on_submit(): - session.pop('user_details', None) + session.pop("user_details", None) return activate_user(user_id) - return render_template('views/two-factor-sms.html', form=form) + return render_template("views/two-factor-sms.html", form=form) -@main.route('/verify-email/') +@main.route("/verify-email/") def verify_email(token): try: token_data = check_token( token, - current_app.config['SECRET_KEY'], - current_app.config['DANGEROUS_SALT'], - current_app.config['EMAIL_EXPIRY_SECONDS'] + current_app.config["SECRET_KEY"], + current_app.config["DANGEROUS_SALT"], + current_app.config["EMAIL_EXPIRY_SECONDS"], ) except SignatureExpired: - flash("The link in the email we sent you has expired. We've sent you a new one.") - return redirect(url_for('main.resend_email_verification')) + flash( + "The link in the email we sent you has expired. We've sent you a new one." + ) + return redirect(url_for("main.resend_email_verification")) # token contains json blob of format: {'user_id': '...', 'secret_code': '...'} (secret_code is unused) token_data = json.loads(token_data) - user = User.from_id(token_data['user_id']) + user = User.from_id(token_data["user_id"]) if not user: abort(404) if user.is_active: flash("That verification link has expired.") - return redirect(url_for('main.sign_in')) + return redirect(url_for("main.sign_in")) if user.email_auth: - session.pop('user_details', None) + session.pop("user_details", None) return activate_user(user.id) user.send_verify_code() - session['user_details'] = {"email": user.email_address, "id": user.id} - return redirect(url_for('main.verify')) + session["user_details"] = {"email": user.email_address, "id": user.id} + return redirect(url_for("main.verify")) def activate_user(user_id): user = User.from_id(user_id) # the user will have a new current_session_id set by the API - store it in the cookie for future requests - session['current_session_id'] = user.current_session_id - organization_id = session.get('organization_id') + session["current_session_id"] = user.current_session_id + organization_id = session.get("organization_id") activated_user = user.activate() activated_user.login() invited_user = InvitedUser.from_session() if invited_user: service_id = _add_invited_user_to_service(invited_user) - return redirect(url_for('main.service_dashboard', service_id=service_id)) + return redirect(url_for("main.service_dashboard", service_id=service_id)) invited_org_user = InvitedOrgUser.from_session() if invited_org_user: user_api_client.add_user_to_organization(invited_org_user.organization, user_id) if organization_id: - return redirect(url_for('main.organization_dashboard', org_id=organization_id)) + return redirect(url_for("main.organization_dashboard", org_id=organization_id)) else: - return redirect(url_for('main.add_service', first='first')) + return redirect(url_for("main.add_service", first="first")) def _add_invited_user_to_service(invitation): - user = User.from_id(session['user_id']) + user = User.from_id(session["user_id"]) service_id = invitation.service user.add_to_service( service_id, diff --git a/app/models/__init__.py b/app/models/__init__.py index 2fd7bf723..0c3613519 100644 --- a/app/models/__init__.py +++ b/app/models/__init__.py @@ -8,7 +8,6 @@ from notifications_utils.serialised_model import ( class JSONModel(SerialisedModel): - def __init__(self, _dict): # in the case of a bad request _dict may be `None` self._dict = _dict or {} @@ -27,13 +26,12 @@ class JSONModel(SerialisedModel): def _get_by_id(self, things, id): try: - return next(thing for thing in things if thing['id'] == str(id)) + return next(thing for thing in things if thing["id"] == str(id)) except StopIteration: abort(404) class ModelList(SerialisedModelCollection): - @property @abstractmethod def client_method(self): @@ -44,8 +42,7 @@ class ModelList(SerialisedModelCollection): class PaginatedModelList(ModelList): - - response_key = 'data' + response_key = "data" def __init__(self, *args, page=None, **kwargs): try: @@ -58,11 +55,10 @@ class PaginatedModelList(ModelList): page=self.current_page, ) self.items = response[self.response_key] - self.prev_page = response.get('links', {}).get('prev', None) - self.next_page = response.get('links', {}).get('next', None) + self.prev_page = response.get("links", {}).get("prev", None) + self.next_page = response.get("links", {}).get("next", None) -class SortByNameMixin(): - +class SortByNameMixin: def __lt__(self, other): return self.name.lower() < other.name.lower() diff --git a/app/models/event.py b/app/models/event.py index 2697ec01b..548123b58 100644 --- a/app/models/event.py +++ b/app/models/event.py @@ -8,7 +8,6 @@ from app.notify_client.service_api_client import service_api_client class Event(ABC): - def __init__( self, item, @@ -17,8 +16,8 @@ class Event(ABC): value_to=None, ): self.item = item - self.time = item['updated_at'] or item['created_at'] - self.user_id = item['created_by_id'] + self.time = item["updated_at"] or item["created_at"] + self.user_id = item["created_by_id"] self.key = key self.value_from = value_from self.value_to = value_to @@ -34,17 +33,13 @@ class Event(ABC): class ServiceCreationEvent(Event): - relevant = True def __str__(self): - return 'Created this service and called it ‘{}’'.format( - self.item['name'] - ) + return "Created this service and called it ‘{}’".format(self.item["name"]) class ServiceEvent(Event): - @property def relevant(self): return self.value_from != self.value_to and bool(self._formatter) @@ -54,44 +49,38 @@ class ServiceEvent(Event): @property def _formatter(self): - return getattr(self, 'format_{}'.format(self.key), None) + return getattr(self, "format_{}".format(self.key), None) def format_restricted(self): if self.value_to is False: - return 'Made this service live' + return "Made this service live" if self.value_to is True: - return 'Put this service back into trial mode' + return "Put this service back into trial mode" def format_active(self): if self.value_to is False: - return 'Deleted this service' + return "Deleted this service" if self.value_to is True: - return 'Unsuspended this service' + return "Unsuspended this service" def format_contact_link(self): - return 'Set the contact details for this service to ‘{}’'.format( - self.value_to - ) + return "Set the contact details for this service to ‘{}’".format(self.value_to) def format_email_branding(self): - return 'Updated this service’s email branding' + return "Updated this service’s email branding" def format_inbound_api(self): - return 'Updated the callback for received text messages' + return "Updated the callback for received text messages" def format_message_limit(self): - return ( - '{} this service’s daily message limit from {} to {}' - ).format( - 'Reduced' if self.value_from > self.value_to else 'Increased', + return ("{} this service’s daily message limit from {} to {}").format( + "Reduced" if self.value_from > self.value_to else "Increased", format_thousands(self.value_from), format_thousands(self.value_to), ) def format_name(self): - return ( - 'Renamed this service from ‘{}’ to ‘{}’' - ).format( + return ("Renamed this service from ‘{}’ to ‘{}’").format( self.value_from, self.value_to ) @@ -99,61 +88,54 @@ class ServiceEvent(Event): added = list(sorted(set(self.value_to) - set(self.value_from))) removed = list(sorted(set(self.value_from) - set(self.value_to))) if removed and added: - return 'Removed {} from this service’s permissions, added {}'.format( + return "Removed {} from this service’s permissions, added {}".format( formatted_list(removed), formatted_list(added), ) if added: - return 'Added {} to this service’s permissions'.format( + return "Added {} to this service’s permissions".format( formatted_list(added) ) if removed: - return 'Removed {} from this service’s permissions'.format( + return "Removed {} from this service’s permissions".format( formatted_list(removed) ) def format_prefix_sms(self): if self.value_to is True: - return 'Set text messages to start with the name of this service' + return "Set text messages to start with the name of this service" else: - return 'Set text messages to not start with the name of this service' + return "Set text messages to not start with the name of this service" def format_research_mode(self): if self.value_to is True: - return 'Put this service into research mode' + return "Put this service into research mode" else: - return 'Took this service out of research mode' + return "Took this service out of research mode" def format_service_callback_api(self): - return 'Updated the callback for delivery receipts' + return "Updated the callback for delivery receipts" def format_go_live_user(self): - return 'Requested for this service to go live' + return "Requested for this service to go live" class APIKeyEvent(Event): - relevant = True def __str__(self): - if self.item['updated_at']: - return ( - 'Revoked the ‘{}’ API key' - ).format(self.item['name']) + if self.item["updated_at"]: + return ("Revoked the ‘{}’ API key").format(self.item["name"]) else: - return ( - 'Created an API key called ‘{}’' - ).format(self.item['name']) + return ("Created an API key called ‘{}’").format(self.item["name"]) class APIKeyEvents(ModelList): - model = APIKeyEvent client_method = service_api_client.get_service_api_key_history class ServiceEvents(ModelList): - client_method = service_api_client.get_service_service_history @property @@ -162,10 +144,9 @@ class ServiceEvents(ModelList): @staticmethod def splat(events): - for index, item in enumerate(sorted( - events, - key=lambda event: event['updated_at'] or event['created_at'] - )): + for index, item in enumerate( + sorted(events, key=lambda event: event["updated_at"] or event["created_at"]) + ): if index == 0: yield ServiceCreationEvent(item) else: @@ -179,5 +160,7 @@ class ServiceEvents(ModelList): def __init__(self, service_id): self.items = [ - event for event in self.splat(self.client_method(service_id)) if event.relevant + event + for event in self.splat(self.client_method(service_id)) + if event.relevant ] diff --git a/app/models/feedback.py b/app/models/feedback.py index 345be5e6a..31a669ac2 100644 --- a/app/models/feedback.py +++ b/app/models/feedback.py @@ -1,3 +1,3 @@ -QUESTION_TICKET_TYPE = 'ask-question-give-feedback' -PROBLEM_TICKET_TYPE = 'report-problem' -GENERAL_TICKET_TYPE = 'general' +QUESTION_TICKET_TYPE = "ask-question-give-feedback" +PROBLEM_TICKET_TYPE = "report-problem" +GENERAL_TICKET_TYPE = "general" diff --git a/app/models/job.py b/app/models/job.py index 3fa1f7d33..b31af24fe 100644 --- a/app/models/job.py +++ b/app/models/job.py @@ -9,65 +9,68 @@ from app.utils.time import is_less_than_days_ago class Job(JSONModel): - ALLOWED_PROPERTIES = { - 'id', - 'service', - 'template_name', - 'template_version', - 'original_file_name', - 'created_at', - 'notification_count', - 'created_by', - 'template_type', - 'recipient', + "id", + "service", + "template_name", + "template_version", + "original_file_name", + "created_at", + "notification_count", + "created_by", + "template_type", + "recipient", } @classmethod def from_id(cls, job_id, service_id): - return cls(job_api_client.get_job(service_id, job_id)['data']) + return cls(job_api_client.get_job(service_id, job_id)["data"]) @property def status(self): - return self._dict.get('job_status') + return self._dict.get("job_status") @property def cancelled(self): - return self.status == 'cancelled' + return self.status == "cancelled" @property def scheduled(self): - return self.status == 'scheduled' + return self.status == "scheduled" @property def scheduled_for(self): - return self._dict.get('scheduled_for') + return self._dict.get("scheduled_for") @property def upload_type(self): - return self._dict.get('upload_type') + return self._dict.get("upload_type") @property def processing_started(self): - if not self._dict.get('processing_started'): + if not self._dict.get("processing_started"): return None - return self._dict['processing_started'] + return self._dict["processing_started"] def _aggregate_statistics(self, *statuses): return sum( - outcome['count'] for outcome in self._dict['statistics'] - if not statuses or outcome['status'] in statuses + outcome["count"] + for outcome in self._dict["statistics"] + if not statuses or outcome["status"] in statuses ) @property def notifications_delivered(self): - return self._aggregate_statistics('delivered', 'sent') + return self._aggregate_statistics("delivered", "sent") @property def notifications_failed(self): return self._aggregate_statistics( - 'failed', 'technical-failure', 'temporary-failure', - 'permanent-failure', 'cancelled', + "failed", + "technical-failure", + "temporary-failure", + "permanent-failure", + "cancelled", ) @property @@ -92,9 +95,7 @@ class Job(JSONModel): @property def still_processing(self): - return ( - self.status != 'finished' or self.percentage_complete < 100 - ) + return self.status != "finished" or self.percentage_complete < 100 @cached_property def finished_processing(self): @@ -111,7 +112,7 @@ class Job(JSONModel): @property def template_id(self): - return self._dict['template'] + return self._dict["template"] @cached_property def template(self): @@ -119,7 +120,7 @@ class Job(JSONModel): service_id=self.service, template_id=self.template_id, version=self.template_version, - )['data'] + )["data"] @property def percentage_complete(self): @@ -127,23 +128,21 @@ class Job(JSONModel): @cached_property def all_notifications(self): - return self.get_notifications(set_status_filters({}))['notifications'] + return self.get_notifications(set_status_filters({}))["notifications"] @property def uncancellable_notifications(self): # TODO: this is redundant now - return ( - n for n in self.all_notifications - ) + return (n for n in self.all_notifications) @property def failure_rate(self): if not self.notifications_delivered: return 100 if self.notifications_failed else 0 return ( - self.notifications_failed / ( - self.notifications_failed + self.notifications_delivered - ) * 100 + self.notifications_failed + / (self.notifications_failed + self.notifications_delivered) + * 100 ) @property @@ -152,7 +151,9 @@ class Job(JSONModel): def get_notifications(self, status): return notification_api_client.get_notifications_for_service( - self.service, self.id, status=status, + self.service, + self.id, + status=status, ) def cancel(self): diff --git a/app/models/organization.py b/app/models/organization.py index 766ff052b..8be65a662 100644 --- a/app/models/organization.py +++ b/app/models/organization.py @@ -8,31 +8,32 @@ from app.notify_client.organizations_api_client import organizations_client class Organization(JSONModel, SortByNameMixin): + TYPE_FEDERAL = "federal" + TYPE_STATE = "state" + TYPE_OTHER = "other" - TYPE_FEDERAL = 'federal' - TYPE_STATE = 'state' - TYPE_OTHER = 'other' - - TYPE_LABELS = OrderedDict([ - (TYPE_FEDERAL, 'Federal government'), - (TYPE_STATE, 'State government'), - (TYPE_OTHER, 'Other'), - ]) + TYPE_LABELS = OrderedDict( + [ + (TYPE_FEDERAL, "Federal government"), + (TYPE_STATE, "State government"), + (TYPE_OTHER, "Other"), + ] + ) ALLOWED_PROPERTIES = { - 'id', - 'name', - 'active', - 'organization_type', - 'email_branding_id', - 'domains', - 'request_to_go_live_notes', - 'count_of_live_services', - 'billing_contact_email_addresses', - 'billing_contact_names', - 'billing_reference', - 'purchase_order_number', - 'notes', + "id", + "name", + "active", + "organization_type", + "email_branding_id", + "domains", + "request_to_go_live_notes", + "count_of_live_services", + "billing_contact_email_addresses", + "billing_contact_names", + "billing_reference", + "purchase_order_number", + "notes", } @classmethod @@ -58,13 +59,14 @@ class Organization(JSONModel, SortByNameMixin): @classmethod def create(cls, name, organization_type): - return cls(organizations_client.create_organization( - name=name, - organization_type=organization_type, - )) + return cls( + organizations_client.create_organization( + name=name, + organization_type=organization_type, + ) + ) def __init__(self, _dict): - super().__init__(_dict) if self._dict == {}: @@ -84,7 +86,7 @@ class Organization(JSONModel, SortByNameMixin): self.billing_contact_email_addresses, self.billing_contact_names, self.billing_reference, - self.purchase_order_number + self.purchase_order_number, ] if any(billing_details): return billing_details @@ -97,24 +99,26 @@ class Organization(JSONModel, SortByNameMixin): @cached_property def service_ids(self): - return [s['id'] for s in self.services] + return [s["id"] for s in self.services] @property def live_services(self): - return [s for s in self.services if s['active'] and not s['restricted']] + return [s for s in self.services if s["active"] and not s["restricted"]] @property def trial_services(self): - return [s for s in self.services if not s['active'] or s['restricted']] + return [s for s in self.services if not s["active"] or s["restricted"]] @cached_property def invited_users(self): from app.models.user import OrganizationInvitedUsers + return OrganizationInvitedUsers(self.id) @cached_property def active_users(self): from app.models.user import OrganizationUsers + return OrganizationUsers(self.id) @cached_property @@ -127,15 +131,15 @@ class Organization(JSONModel, SortByNameMixin): @cached_property def email_branding(self): if self.email_branding_id: - return email_branding_client.get_email_branding( - self.email_branding_id - )['email_branding'] + return email_branding_client.get_email_branding(self.email_branding_id)[ + "email_branding" + ] @property def email_branding_name(self): if self.email_branding_id: - return self.email_branding['name'] - return 'GOV.UK' + return self.email_branding["name"] + return "GOV.UK" def update(self, delete_services_cache=False, **kwargs): response = organizations_client.update_organization( @@ -146,10 +150,7 @@ class Organization(JSONModel, SortByNameMixin): self.__init__(response) def associate_service(self, service_id): - organizations_client.update_service_organization( - service_id, - self.id - ) + organizations_client.update_service_organization(service_id, self.id) def services_and_usage(self, financial_year): return organizations_client.get_services_and_usage(self.id, financial_year) diff --git a/app/models/service.py b/app/models/service.py index a32161966..9a0cdd1e7 100644 --- a/app/models/service.py +++ b/app/models/service.py @@ -19,52 +19,51 @@ from app.utils import get_default_sms_sender class Service(JSONModel, SortByNameMixin): - ALLOWED_PROPERTIES = { - 'active', - 'billing_contact_email_addresses', - 'billing_contact_names', - 'billing_reference', - 'consent_to_research', - 'contact_link', - 'count_as_live', - 'email_from', - 'go_live_at', - 'go_live_user', - 'id', - 'inbound_api', - 'message_limit', - 'rate_limit', - 'name', - 'notes', - 'prefix_sms', - 'purchase_order_number', - 'research_mode', - 'service_callback_api', - 'volume_email', - 'volume_sms', + "active", + "billing_contact_email_addresses", + "billing_contact_names", + "billing_reference", + "consent_to_research", + "contact_link", + "count_as_live", + "email_from", + "go_live_at", + "go_live_user", + "id", + "inbound_api", + "message_limit", + "rate_limit", + "name", + "notes", + "prefix_sms", + "purchase_order_number", + "research_mode", + "service_callback_api", + "volume_email", + "volume_sms", } TEMPLATE_TYPES = ( - 'email', - 'sms', + "email", + "sms", ) ALL_PERMISSIONS = TEMPLATE_TYPES + ( - 'edit_folder_permissions', - 'email_auth', - 'inbound_sms', - 'international_sms', - 'upload_document', + "edit_folder_permissions", + "email_auth", + "inbound_sms", + "international_sms", + "upload_document", ) @classmethod def from_id(cls, service_id): - return cls(service_api_client.get_service(service_id)['data']) + return cls(service_api_client.get_service(service_id)["data"]) @property def permissions(self): - return self._dict.get('permissions', self.TEMPLATE_TYPES) + return self._dict.get("permissions", self.TEMPLATE_TYPES) @property def billing_details(self): @@ -72,7 +71,7 @@ class Service(JSONModel, SortByNameMixin): self.billing_contact_email_addresses, self.billing_contact_names, self.billing_reference, - self.purchase_order_number + self.purchase_order_number, ] if any(billing_details): return billing_details @@ -83,7 +82,9 @@ class Service(JSONModel, SortByNameMixin): return service_api_client.update_service(self.id, **kwargs) def update_count_as_live(self, count_as_live): - return service_api_client.update_count_as_live(self.id, count_as_live=count_as_live) + return service_api_client.update_count_as_live( + self.id, count_as_live=count_as_live + ) def update_status(self, live): return service_api_client.update_status(self.id, live=live) @@ -95,7 +96,6 @@ class Service(JSONModel, SortByNameMixin): ) def force_permission(self, permission, on=False): - permissions, permission = set(self.permissions), {permission} return self.update_permissions( @@ -110,7 +110,7 @@ class Service(JSONModel, SortByNameMixin): @property def trial_mode(self): - return self._dict['restricted'] + return self._dict["restricted"] @property def live(self): @@ -118,7 +118,7 @@ class Service(JSONModel, SortByNameMixin): def has_permission(self, permission): if permission not in self.ALL_PERMISSIONS: - raise KeyError(f'{permission} is not a service permission') + raise KeyError(f"{permission} is not a service permission") return permission in self.permissions def get_page_of_jobs(self, page): @@ -146,7 +146,7 @@ class Service(JSONModel, SortByNameMixin): @cached_property def scheduled_job_stats(self): if not self.has_jobs: - return {'count': 0} + return {"count": 0} return job_api_client.get_scheduled_job_stats(self.id) @cached_property @@ -155,8 +155,7 @@ class Service(JSONModel, SortByNameMixin): def invite_pending_for(self, email_address): return email_address.lower() in ( - invited_user.email_address.lower() - for invited_user in self.invited_users + invited_user.email_address.lower() for invited_user in self.invited_users ) @cached_property @@ -172,10 +171,16 @@ class Service(JSONModel, SortByNameMixin): @cached_property def has_team_members(self): - return len([ - user for user in self.team_members - if user.has_permission_for_service(self.id, 'manage_service') - ]) > 1 + return ( + len( + [ + user + for user in self.team_members + if user.has_permission_for_service(self.id, "manage_service") + ] + ) + > 1 + ) def cancel_invite(self, invited_user_id): if str(invited_user_id) not in {user.id for user in self.invited_users}: @@ -187,7 +192,6 @@ class Service(JSONModel, SortByNameMixin): ) def get_team_member(self, user_id): - if str(user_id) not in {user.id for user in self.active_users}: abort(404) @@ -195,20 +199,22 @@ class Service(JSONModel, SortByNameMixin): @cached_property def all_templates(self): - - templates = service_api_client.get_service_templates(self.id)['data'] + templates = service_api_client.get_service_templates(self.id)["data"] return [ - template for template in templates - if template['template_type'] in self.available_template_types + template + for template in templates + if template["template_type"] in self.available_template_types ] @cached_property def all_template_ids(self): - return {template['id'] for template in self.all_templates} + return {template["id"] for template in self.all_templates} def get_template(self, template_id, version=None): - return service_api_client.get_service_template(self.id, template_id, version)['data'] + return service_api_client.get_service_template(self.id, template_id, version)[ + "data" + ] def get_template_folder_with_user_permission_or_403(self, folder_id, user): template_folder = self.get_template_folder(folder_id) @@ -221,7 +227,7 @@ class Service(JSONModel, SortByNameMixin): def get_template_with_user_permission_or_403(self, template_id, user): template = self.get_template(template_id) - self.get_template_folder_with_user_permission_or_403(template['folder'], user) + self.get_template_folder_with_user_permission_or_403(template["folder"], user) return template @@ -238,32 +244,31 @@ class Service(JSONModel, SortByNameMixin): @property def has_multiple_template_types(self): - return len({ - template['template_type'] for template in self.all_templates - }) > 1 + return len({template["template_type"] for template in self.all_templates}) > 1 @property def has_estimated_usage(self): - return ( - self.consent_to_research is not None and any(( + return self.consent_to_research is not None and any( + ( self.volume_email, self.volume_sms, - )) + ) ) def has_templates_of_type(self, template_type): return any( - template for template in self.all_templates - if template['template_type'] == template_type + template + for template in self.all_templates + if template["template_type"] == template_type ) @property def has_email_templates(self): - return self.has_templates_of_type('email') + return self.has_templates_of_type("email") @property def has_sms_templates(self): - return self.has_templates_of_type('sms') + return self.has_templates_of_type("sms") @property def intending_to_send_email(self): @@ -293,9 +298,11 @@ class Service(JSONModel, SortByNameMixin): def default_email_reply_to_address(self): return next( ( - x['email_address'] - for x in self.email_reply_to_addresses if x['is_default'] - ), None + x["email_address"] + for x in self.email_reply_to_addresses + if x["is_default"] + ), + None, ) def get_email_reply_to_address(self, id): @@ -315,15 +322,14 @@ class Service(JSONModel, SortByNameMixin): @property def sms_senders_with_hints(self): - def attach_hint(sender): hints = [] - if sender['is_default']: + if sender["is_default"]: hints += ["default"] - if sender['inbound_number_id']: + if sender["inbound_number_id"]: hints += ["receives replies"] if hints: - sender['hint'] = "(" + " and ".join(hints) + ")" + sender["hint"] = "(" + " and ".join(hints) + ")" return sender return [attach_hint(sender) for sender in self.sms_senders] @@ -338,39 +344,48 @@ class Service(JSONModel, SortByNameMixin): @property def sms_sender_is_govuk(self): - return self.default_sms_sender in {'GOVUK', 'None'} + return self.default_sms_sender in {"GOVUK", "None"} def get_sms_sender(self, id): return service_api_client.get_sms_sender(self.id, id) @property def needs_to_change_sms_sender(self): - return all(( - self.intending_to_send_sms, - self.shouldnt_use_govuk_as_sms_sender, - self.sms_sender_is_govuk, - )) + return all( + ( + self.intending_to_send_sms, + self.shouldnt_use_govuk_as_sms_sender, + self.sms_sender_is_govuk, + ) + ) @property def volumes(self): - return sum(filter(None, ( - self.volume_email, - self.volume_sms, - ))) + return sum( + filter( + None, + ( + self.volume_email, + self.volume_sms, + ), + ) + ) @property def go_live_checklist_completed(self): - return all(( - bool(self.volumes), - self.has_team_members, - self.has_templates, - not self.needs_to_add_email_reply_to_address, - not self.needs_to_change_sms_sender, - )) + return all( + ( + bool(self.volumes), + self.has_team_members, + self.has_templates, + not self.needs_to_add_email_reply_to_address, + not self.needs_to_change_sms_sender, + ) + ) @property def go_live_checklist_completed_as_yes_no(self): - return 'Yes' if self.go_live_checklist_completed else 'No' + return "Yes" if self.go_live_checklist_completed else "No" @cached_property def free_sms_fragment_limit(self): @@ -381,36 +396,42 @@ class Service(JSONModel, SortByNameMixin): return service_api_client.get_service_data_retention(self.id) def get_data_retention_item(self, id): - return next( - (dr for dr in self.data_retention if dr['id'] == id), - None - ) + return next((dr for dr in self.data_retention if dr["id"] == id), None) def get_days_of_retention(self, notification_type): return next( - (dr for dr in self.data_retention if dr['notification_type'] == notification_type), - {} - ).get('days_of_retention', current_app.config['ACTIVITY_STATS_LIMIT_DAYS']) + ( + dr + for dr in self.data_retention + if dr["notification_type"] == notification_type + ), + {}, + ).get("days_of_retention", current_app.config["ACTIVITY_STATS_LIMIT_DAYS"]) @property def email_branding_id(self): - return self._dict['email_branding'] + return self._dict["email_branding"] @cached_property def email_branding(self): if self.email_branding_id: - return email_branding_client.get_email_branding(self.email_branding_id)['email_branding'] + return email_branding_client.get_email_branding(self.email_branding_id)[ + "email_branding" + ] return None @cached_property def email_branding_name(self): if self.email_branding is None: - return 'GOV.UK' - return self.email_branding['name'] + return "GOV.UK" + return self.email_branding["name"] @property def needs_to_change_email_branding(self): - return self.email_branding_id is None and self.organization_type != Organization.TYPE_CENTRAL + return ( + self.email_branding_id is None + and self.organization_type != Organization.TYPE_CENTRAL + ) @cached_property def organization(self): @@ -418,11 +439,11 @@ class Service(JSONModel, SortByNameMixin): @property def organization_id(self): - return self._dict['organization'] + return self._dict["organization"] @property def organization_type(self): - return self.organization.organization_type or self._dict['organization_type'] + return self.organization.organization_type or self._dict["organization_type"] @property def organization_name(self): @@ -436,7 +457,9 @@ class Service(JSONModel, SortByNameMixin): @cached_property def inbound_number(self): - return inbound_number_client.get_inbound_sms_number_for_service(self.id)['data'].get('number', '') + return inbound_number_client.get_inbound_sms_number_for_service(self.id)[ + "data" + ].get("number", "") @property def has_inbound_number(self): @@ -444,7 +467,7 @@ class Service(JSONModel, SortByNameMixin): @cached_property def inbound_sms_summary(self): - if not self.has_permission('inbound_sms'): + if not self.has_permission("inbound_sms"): return None return service_api_client.get_inbound_sms_summary(self.id) @@ -452,35 +475,34 @@ class Service(JSONModel, SortByNameMixin): def all_template_folders(self): return sorted( template_folder_api_client.get_template_folders(self.id), - key=lambda folder: folder['name'].lower(), + key=lambda folder: folder["name"].lower(), ) @cached_property def all_template_folder_ids(self): - return {folder['id'] for folder in self.all_template_folders} + return {folder["id"] for folder in self.all_template_folders} def get_template_folder(self, folder_id): if folder_id is None: return { - 'id': None, - 'name': 'Templates', - 'parent_id': None, + "id": None, + "name": "Templates", + "parent_id": None, } return self._get_by_id(self.all_template_folders, folder_id) def get_template_folder_path(self, template_folder_id): - folder = self.get_template_folder(template_folder_id) - if folder['id'] is None: + if folder["id"] is None: return [folder] - return self.get_template_folder_path(folder['parent_id']) + [ - self.get_template_folder(folder['id']) + return self.get_template_folder_path(folder["parent_id"]) + [ + self.get_template_folder(folder["id"]) ] def get_template_path(self, template): - return self.get_template_folder_path(template['folder']) + [ + return self.get_template_folder_path(template["folder"]) + [ template, ] @@ -489,7 +511,6 @@ class Service(JSONModel, SortByNameMixin): return len(self.all_templates + self.all_template_folders) def move_to_folder(self, ids_to_move, move_to): - ids_to_move = set(ids_to_move) template_folder_api_client.move_to_folder( @@ -502,8 +523,8 @@ class Service(JSONModel, SortByNameMixin): @cached_property def api_keys(self): return sorted( - api_key_api_client.get_api_keys(self.id)['apiKeys'], - key=lambda key: key['name'].lower(), + api_key_api_client.get_api_keys(self.id)["apiKeys"], + key=lambda key: key["name"].lower(), ) def get_api_key(self, id): diff --git a/app/models/spreadsheet.py b/app/models/spreadsheet.py index 59ca2bdd7..ceb9a4fd5 100644 --- a/app/models/spreadsheet.py +++ b/app/models/spreadsheet.py @@ -6,26 +6,21 @@ import pyexcel import pyexcel_xlsx -class Spreadsheet(): - - ALLOWED_FILE_EXTENSIONS = ('csv', 'xlsx', 'xls', 'ods', 'xlsm', 'tsv') - - def __init__(self, csv_data=None, rows=None, filename=''): +class Spreadsheet: + ALLOWED_FILE_EXTENSIONS = ("csv", "xlsx", "xls", "ods", "xlsm", "tsv") + def __init__(self, csv_data=None, rows=None, filename=""): self.filename = filename if csv_data and rows: - raise TypeError('Spreadsheet must be created from either rows or CSV data') + raise TypeError("Spreadsheet must be created from either rows or CSV data") - self._csv_data = csv_data or '' + self._csv_data = csv_data or "" self._rows = rows or [] @property def as_dict(self): - return { - 'file_name': self.filename, - 'data': self.as_csv_data - } + return {"file_name": self.filename, "data": self.as_csv_data} @property def as_csv_data(self): @@ -43,37 +38,36 @@ class Spreadsheet(): @staticmethod def get_extension(filename): - return path.splitext(filename)[1].lower().lstrip('.') + return path.splitext(filename)[1].lower().lstrip(".") @staticmethod def normalise_newlines(file_content): - return '\r\n'.join(file_content.read().decode('utf-8').splitlines()) + return "\r\n".join(file_content.read().decode("utf-8").splitlines()) @classmethod - def from_rows(cls, rows, filename=''): + def from_rows(cls, rows, filename=""): return cls(rows=rows, filename=filename) @classmethod - def from_dict(cls, dictionary, filename=''): + def from_dict(cls, dictionary, filename=""): return cls.from_rows( - zip( - *sorted(dictionary.items(), key=lambda pair: pair[0]) - ), + zip(*sorted(dictionary.items(), key=lambda pair: pair[0])), filename=filename, ) @classmethod - def from_file(cls, file_content, filename=''): + def from_file(cls, file_content, filename=""): extension = cls.get_extension(filename) - if extension == 'csv': - return cls(csv_data=Spreadsheet.normalise_newlines(file_content), filename=filename) + if extension == "csv": + return cls( + csv_data=Spreadsheet.normalise_newlines(file_content), filename=filename + ) - if extension == 'tsv': - file_content = StringIO( - Spreadsheet.normalise_newlines(file_content)) + if extension == "tsv": + file_content = StringIO(Spreadsheet.normalise_newlines(file_content)) - if extension == 'xlsm': + if extension == "xlsm": file_data = pyexcel_xlsx.get_data(file_content) instance = cls.from_rows( # Get the first sheet from the workbook @@ -83,10 +77,8 @@ class Spreadsheet(): return instance instance = cls.from_rows( - pyexcel.iget_array( - file_type=extension, - file_stream=file_content), - filename) + pyexcel.iget_array(file_type=extension, file_stream=file_content), filename + ) pyexcel.free_resources() return instance diff --git a/app/models/template_list.py b/app/models/template_list.py index 4b30bfdf4..b2f53b493 100644 --- a/app/models/template_list.py +++ b/app/models/template_list.py @@ -3,12 +3,11 @@ from werkzeug.utils import cached_property from app import format_notification_type -class TemplateList(): - +class TemplateList: def __init__( self, service, - template_type='all', + template_type="all", template_folder_id=None, user=None, ): @@ -22,33 +21,35 @@ class TemplateList(): @cached_property def items(self): - return list(self.get_templates_and_folders( - self.template_type, self.template_folder_id, ancestors=[] - )) + return list( + self.get_templates_and_folders( + self.template_type, self.template_folder_id, ancestors=[] + ) + ) def get_templates_and_folders(self, template_type, template_folder_id, ancestors): - for item in self.get_template_folders( - template_type, template_folder_id, + template_type, + template_folder_id, ): yield TemplateListFolder( item, folders=self.get_template_folders( - template_type, item['id'], - ), - templates=self.get_templates( - template_type, item['id'] + template_type, + item["id"], ), + templates=self.get_templates(template_type, item["id"]), ancestors=ancestors, service_id=self.service.id, ) for sub_item in self.get_templates_and_folders( - template_type, item['id'], ancestors + [item] + template_type, item["id"], ancestors + [item] ): yield sub_item for item in self.get_templates( - template_type, template_folder_id, + template_type, + template_folder_id, ): yield TemplateListTemplate( item, @@ -56,7 +57,7 @@ class TemplateList(): service_id=self.service.id, ) - def get_templates(self, template_type='all', template_folder_id=None): + def get_templates(self, template_type="all", template_folder_id=None): if self.user and template_folder_id: folder = self.service.get_template_folder(template_folder_id) if not self.user.has_template_folder_permission(folder): @@ -67,9 +68,10 @@ class TemplateList(): if template_folder_id: template_folder_id = str(template_folder_id) return [ - template for template in self.service.all_templates - if (set(template_type) & {'all', template['template_type']}) - and template.get('folder') == template_folder_id + template + for template in self.service.all_templates + if (set(template_type) & {"all", template["template_type"]}) + and template.get("folder") == template_folder_id ] @cached_property @@ -89,15 +91,19 @@ class TemplateList(): """ user_folders = [] for folder in self.service.all_template_folders: - if not self.user.has_template_folder_permission(folder, service=self.service): + if not self.user.has_template_folder_permission( + folder, service=self.service + ): continue parent = self.service.get_template_folder(folder["parent_id"]) if self.user.has_template_folder_permission(parent, service=self.service): user_folders.append(folder) else: folder_attrs = { - "id": folder["id"], "name": folder["name"], "parent_id": folder["parent_id"], - "users_with_permission": folder["users_with_permission"] + "id": folder["id"], + "name": folder["name"], + "parent_id": folder["parent_id"], + "users_with_permission": folder["users_with_permission"], } while folder_attrs["parent_id"] is not None: folder_attrs["name"] = [ @@ -109,12 +115,14 @@ class TemplateList(): else: parent = self.service.get_template_folder(parent["parent_id"]) folder_attrs["parent_id"] = parent.get("id", None) - if self.user.has_template_folder_permission(parent, service=self.service): + if self.user.has_template_folder_permission( + parent, service=self.service + ): break user_folders.append(folder_attrs) return user_folders - def get_template_folders(self, template_type='all', parent_folder_id=None): + def get_template_folders(self, template_type="all", parent_folder_id=None): if self.user: folders = self.user_template_folders else: @@ -123,24 +131,26 @@ class TemplateList(): parent_folder_id = str(parent_folder_id) return [ - folder for folder in folders + folder + for folder in folders if ( - folder['parent_id'] == parent_folder_id - and self.is_folder_visible(folder['id'], template_type) + folder["parent_id"] == parent_folder_id + and self.is_folder_visible(folder["id"], template_type) ) ] - def is_folder_visible(self, template_folder_id, template_type='all'): - - if template_type == 'all': + def is_folder_visible(self, template_folder_id, template_type="all"): + if template_type == "all": return True if self.get_templates(template_type, template_folder_id): return True if any( - self.is_folder_visible(child_folder['id'], template_type) - for child_folder in self.get_template_folders(template_type, template_folder_id) + self.is_folder_visible(child_folder["id"], template_type) + for child_folder in self.get_template_folders( + template_type, template_folder_id + ) ): return True @@ -156,9 +166,9 @@ class TemplateList(): @property def folder_is_empty(self): - return not any(self.get_templates_and_folders( - 'all', self.template_folder_id, [] - )) + return not any( + self.get_templates_and_folders("all", self.template_folder_id, []) + ) class ServiceTemplateList(TemplateList): @@ -178,12 +188,11 @@ class ServiceTemplateList(TemplateList): yield from self.get_templates_and_folders( self.template_type, self.template_folder_id, - ancestors=[template_list_service] + ancestors=[template_list_service], ) -class TemplateLists(): - +class TemplateLists: def __init__(self, user): self.services = sorted( user.services, @@ -210,8 +219,7 @@ class TemplateLists(): return bool(self.services) -class TemplateListItem(): - +class TemplateListItem: is_service = False def __init__( @@ -219,13 +227,12 @@ class TemplateListItem(): template_or_folder, ancestors, ): - self.id = template_or_folder['id'] - self.name = template_or_folder['name'] + self.id = template_or_folder["id"] + self.name = template_or_folder["name"] self.ancestors = ancestors class TemplateListTemplate(TemplateListItem): - is_folder = False def __init__( @@ -236,16 +243,15 @@ class TemplateListTemplate(TemplateListItem): ): super().__init__(template, ancestors) self.service_id = service_id - self.template_type = template['template_type'] - self.content = template.get('content') + self.template_type = template["template_type"] + self.content = template.get("content") @property def hint(self): - return format_notification_type(self.template_type) + ' template' + return format_notification_type(self.template_type) + " template" class TemplateListFolder(TemplateListItem): - is_folder = True def __init__( @@ -264,23 +270,22 @@ class TemplateListFolder(TemplateListItem): @property def _hint_parts(self): - if self.number_of_folders == self.number_of_templates == 0: - yield 'Empty' + yield "Empty" if self.number_of_templates == 1: - yield '1 template' + yield "1 template" elif self.number_of_templates > 1: - yield '{} templates'.format(self.number_of_templates) + yield "{} templates".format(self.number_of_templates) if self.number_of_folders == 1: - yield '1 folder' + yield "1 folder" elif self.number_of_folders > 1: - yield '{} folders'.format(self.number_of_folders) + yield "{} folders".format(self.number_of_folders) @property def hint(self): - return ', '.join(self._hint_parts) + return ", ".join(self._hint_parts) class TemplateListService(TemplateListFolder): diff --git a/app/models/user.py b/app/models/user.py index 4d1b66e6f..081ee2f6c 100644 --- a/app/models/user.py +++ b/app/models/user.py @@ -24,36 +24,35 @@ from app.utils.user_permissions import ( def _get_service_id_from_view_args(): - return str(request.view_args.get('service_id', '')) or None + return str(request.view_args.get("service_id", "")) or None def _get_org_id_from_view_args(): - return str(request.view_args.get('org_id', '')) or None + return str(request.view_args.get("org_id", "")) or None class User(JSONModel, UserMixin): - MAX_FAILED_LOGIN_COUNT = 10 ALLOWED_PROPERTIES = { - 'id', - 'name', - 'email_address', - 'auth_type', - 'current_session_id', - 'failed_login_count', - 'email_access_validated_at', - 'logged_in_at', - 'mobile_number', - 'password_changed_at', - 'permissions', - 'state', + "id", + "name", + "email_address", + "auth_type", + "current_session_id", + "failed_login_count", + "email_access_validated_at", + "logged_in_at", + "mobile_number", + "password_changed_at", + "permissions", + "state", } def __init__(self, _dict): super().__init__(_dict) - self.permissions = _dict.get('permissions', {}) - self._platform_admin = _dict['platform_admin'] + self.permissions = _dict.get("permissions", {}) + self._platform_admin = _dict["platform_admin"] @classmethod def from_id(cls, user_id): @@ -104,8 +103,7 @@ class User(JSONModel, UserMixin): """ self._permissions = { service: translate_permissions_from_db_to_ui(permissions) - for service, permissions - in permissions_by_service.items() + for service, permissions in permissions_by_service.items() } def update(self, **kwargs): @@ -117,9 +115,7 @@ class User(JSONModel, UserMixin): self.__init__(response) def update_email_access_validated_at(self): - self.update( - email_access_validated_at=datetime.utcnow().isoformat() - ) + self.update(email_access_validated_at=datetime.utcnow().isoformat()) def password_changed_more_recently_than(self, datetime_string): if not self.password_changed_at: @@ -144,24 +140,26 @@ class User(JSONModel, UserMixin): ) def logged_in_elsewhere(self): - return session.get('current_session_id') != self.current_session_id + return session.get("current_session_id") != self.current_session_id def activate(self): if self.is_pending: user_data = user_api_client.activate_user(self.id) - return self.__class__(user_data['data']) + return self.__class__(user_data["data"]) else: return self def login(self): login_user(self) - session['user_id'] = self.id + session["user_id"] = self.id def send_login_code(self): if self.email_auth: - user_api_client.send_verify_code(self.id, 'email', None, request.args.get('next')) + user_api_client.send_verify_code( + self.id, "email", None, request.args.get("next") + ) if self.sms_auth: - user_api_client.send_verify_code(self.id, 'sms', self.mobile_number) + user_api_client.send_verify_code(self.id, "sms", self.mobile_number) def sign_out(self): session.clear() @@ -172,22 +170,22 @@ class User(JSONModel, UserMixin): @property def sms_auth(self): - return self.auth_type == 'sms_auth' + return self.auth_type == "sms_auth" @property def email_auth(self): - return self.auth_type == 'email_auth' + return self.auth_type == "email_auth" def reset_failed_login_count(self): user_api_client.reset_failed_login_count(self.id) @property def is_active(self): - return self.state == 'active' + return self.state == "active" @property def is_pending(self): - return self.state == 'pending' + return self.state == "pending" @property def is_gov_user(self): @@ -197,20 +195,25 @@ class User(JSONModel, UserMixin): @property def is_authenticated(self): - return ( - not self.logged_in_elsewhere() and - super(User, self).is_authenticated - ) + return not self.logged_in_elsewhere() and super(User, self).is_authenticated @property def platform_admin(self): - current_app.logger.warn(f"Checking User {self.id} for platform admin: {self._platform_admin}") - return self._platform_admin and not session.get('disable_platform_admin_view', False) + current_app.logger.warn( + f"Checking User {self.id} for platform admin: {self._platform_admin}" + ) + return self._platform_admin and not session.get( + "disable_platform_admin_view", False + ) - def has_permissions(self, *permissions, restrict_admin_usage=False, allow_org_user=False): + def has_permissions( + self, *permissions, restrict_admin_usage=False, allow_org_user=False + ): unknown_permissions = set(permissions) - all_ui_permissions if unknown_permissions: - raise TypeError('{} are not valid permissions'.format(list(unknown_permissions))) + raise TypeError( + "{} are not valid permissions".format(list(unknown_permissions)) + ) # Service id is always set on the request for service specific views. service_id = _get_service_id_from_view_args() @@ -236,9 +239,7 @@ class User(JSONModel, UserMixin): current_app.logger.warn(f"{log_msg} True because belongs_to_service") return True - if any( - self.permissions_for_service(service_id) & set(permissions) - ): + if any(self.permissions_for_service(service_id) & set(permissions)): current_app.logger.warn(f"{log_msg} permissions valid") return True @@ -266,7 +267,7 @@ class User(JSONModel, UserMixin): return True # Top-level templates are always visible - if template_folder is None or template_folder['id'] is None: + if template_folder is None or template_folder["id"] is None: return True return self.id in template_folder.get("users_with_permission", []) @@ -297,7 +298,7 @@ class User(JSONModel, UserMixin): @property def email_domain(self): - return self.email_address.split('@')[-1] + return self.email_address.split("@")[-1] @cached_property def orgs_and_services(self): @@ -306,38 +307,36 @@ class User(JSONModel, UserMixin): @property def services(self): from app.models.service import Services - return Services(self.orgs_and_services['services']) + + return Services(self.orgs_and_services["services"]) @property def services_with_organization(self): return [ - service for service in self.services + service + for service in self.services if self.belongs_to_organization(service.organization_id) ] @property def service_ids(self): - return self._dict['services'] + return self._dict["services"] @property def trial_mode_services(self): - return [ - service for service in self.services if service.trial_mode - ] + return [service for service in self.services if service.trial_mode] @property def live_services(self): - return [ - service for service in self.services if service.live - ] + return [service for service in self.services if service.live] @property def organizations(self): - return Organizations(self.orgs_and_services['organizations']) + return Organizations(self.orgs_and_services["organizations"]) @property def organization_ids(self): - return self._dict['organizations'] + return self._dict["organizations"] @cached_property def default_organization(self): @@ -351,11 +350,7 @@ class User(JSONModel, UserMixin): @property def has_access_to_live_and_trial_mode_services(self): - return ( - self.organizations or self.live_services - ) and ( - self.trial_mode_services - ) + return (self.organizations or self.live_services) and (self.trial_mode_services) def serialize(self): dct = { @@ -368,10 +363,10 @@ class User(JSONModel, UserMixin): "failed_login_count": self.failed_login_count, "permissions": [x for x in self._permissions], "organizations": self.organization_ids, - "current_session_id": self.current_session_id + "current_session_id": self.current_session_id, } - if hasattr(self, '_password'): - dct['password'] = self._password + if hasattr(self, "_password"): + dct["password"] = self._password return dct @classmethod @@ -383,13 +378,15 @@ class User(JSONModel, UserMixin): password, auth_type, ): - return cls(user_api_client.register_user( - name, - email_address, - mobile_number or None, - password, - auth_type, - )) + return cls( + user_api_client.register_user( + name, + email_address, + mobile_number or None, + password, + auth_type, + ) + ) def set_password(self, pwd): self._password = pwd @@ -398,16 +395,20 @@ class User(JSONModel, UserMixin): user_api_client.send_verify_email(self.id, self.email_address) def send_verify_code(self, to=None): - user_api_client.send_verify_code(self.id, 'sms', to or self.mobile_number) + user_api_client.send_verify_code(self.id, "sms", to or self.mobile_number) def send_already_registered_email(self): user_api_client.send_already_registered_email(self.id, self.email_address) def refresh_session_id(self): - self.current_session_id = user_api_client.get_user(self.id).get('current_session_id') - session['current_session_id'] = self.current_session_id + self.current_session_id = user_api_client.get_user(self.id).get( + "current_session_id" + ) + session["current_session_id"] = self.current_session_id - def add_to_service(self, service_id, permissions, folder_permissions, invited_by_id): + def add_to_service( + self, service_id, permissions, folder_permissions, invited_by_id + ): try: user_api_client.add_user_to_service( service_id, @@ -422,7 +423,10 @@ class User(JSONModel, UserMixin): ui_permissions=permissions, ) except HTTPError as exception: - if exception.status_code == 400 and 'already part of service' in exception.message: + if ( + exception.status_code == 400 + and "already part of service" in exception.message + ): pass else: raise exception @@ -442,22 +446,21 @@ class User(JSONModel, UserMixin): class InvitedUser(JSONModel): - ALLOWED_PROPERTIES = { - 'id', - 'service', - 'email_address', - 'permissions', - 'status', - 'created_at', - 'auth_type', - 'folder_permissions', + "id", + "service", + "email_address", + "permissions", + "status", + "created_at", + "auth_type", + "folder_permissions", } def __init__(self, _dict): super().__init__(_dict) - self.permissions = _dict.get('permissions') or [] - self._from_user = _dict['from_user'] + self.permissions = _dict.get("permissions") or [] + self._from_user = _dict["from_user"] @classmethod def create( @@ -469,14 +472,16 @@ class InvitedUser(JSONModel): auth_type, folder_permissions, ): - return cls(invite_api_client.create_invite( - invite_from_id, - service_id, - email_address, - permissions, - auth_type, - folder_permissions, - )) + return cls( + invite_api_client.create_invite( + invite_from_id, + service_id, + email_address, + permissions, + auth_type, + folder_permissions, + ) + ) @classmethod def by_id_and_service_id(cls, service_id, invited_user_id): @@ -486,9 +491,7 @@ class InvitedUser(JSONModel): @classmethod def by_id(cls, invited_user_id): - return cls( - invite_api_client.get_invited_user(invited_user_id) - ) + return cls(invite_api_client.get_invited_user(invited_user_id)) def accept_invite(self): invite_api_client.accept_invite(self.service, self.id) @@ -502,7 +505,7 @@ class InvitedUser(JSONModel): if isinstance(permissions, list): self._permissions = permissions else: - self._permissions = permissions.split(',') + self._permissions = permissions.split(",") self._permissions = translate_permissions_from_db_to_ui(self.permissions) @property @@ -511,110 +514,122 @@ class InvitedUser(JSONModel): @property def sms_auth(self): - return self.auth_type == 'sms_auth' + return self.auth_type == "sms_auth" @property def email_auth(self): - return self.auth_type == 'email_auth' + return self.auth_type == "email_auth" @classmethod def from_token(cls, token): try: return cls(invite_api_client.check_token(token)) except HTTPError as exception: - if exception.status_code == 400 and 'invitation' in exception.message: - raise InviteTokenError(exception.message['invitation']) + if exception.status_code == 400 and "invitation" in exception.message: + raise InviteTokenError(exception.message["invitation"]) else: raise exception @classmethod def from_session(cls): - invited_user_id = session.get('invited_user_id') + invited_user_id = session.get("invited_user_id") return cls.by_id(invited_user_id) if invited_user_id else None def has_permissions(self, *permissions): - current_app.logger.warn(f"Checking invited user {self.id} for permissions: {permissions}") - if self.status == 'cancelled': + current_app.logger.warn( + f"Checking invited user {self.id} for permissions: {permissions}" + ) + if self.status == "cancelled": return False return set(self.permissions) > set(permissions) def has_permission_for_service(self, service_id, permission): - current_app.logger.warn(f"Checking invited user {self.id} for permission: {permission} on service {service_id}") - if self.status == 'cancelled': + current_app.logger.warn( + f"Checking invited user {self.id} for permission: {permission} on service {service_id}" + ) + if self.status == "cancelled": return False return self.service == service_id and permission in self.permissions def __eq__(self, other): - return ((self.id, - self.service, - self._from_user, - self.email_address, - self.auth_type, - self.status) == (other.id, - other.service, - other._from_user, - other.email_address, - other.auth_type, - other.status)) + return ( + self.id, + self.service, + self._from_user, + self.email_address, + self.auth_type, + self.status, + ) == ( + other.id, + other.service, + other._from_user, + other.email_address, + other.auth_type, + other.status, + ) def serialize(self, permissions_as_string=False): - data = {'id': self.id, - 'service': self.service, - 'from_user': self._from_user, - 'email_address': self.email_address, - 'status': self.status, - 'created_at': str(self.created_at), - 'auth_type': self.auth_type, - 'folder_permissions': self.folder_permissions - } + data = { + "id": self.id, + "service": self.service, + "from_user": self._from_user, + "email_address": self.email_address, + "status": self.status, + "created_at": str(self.created_at), + "auth_type": self.auth_type, + "folder_permissions": self.folder_permissions, + } if permissions_as_string: - data['permissions'] = ','.join(self.permissions) + data["permissions"] = ",".join(self.permissions) else: - data['permissions'] = sorted(self.permissions) + data["permissions"] = sorted(self.permissions) return data def template_folders_for_service(self, service=None): # only used on the manage users page to display the count, so okay to not be fully fledged for now - return [{'id': x} for x in self.folder_permissions] + return [{"id": x} for x in self.folder_permissions] def is_editable_by(self, other): return False class InvitedOrgUser(JSONModel): - ALLOWED_PROPERTIES = { - 'id', - 'organization', - 'email_address', - 'status', - 'created_at', + "id", + "organization", + "email_address", + "status", + "created_at", } def __init__(self, _dict): super().__init__(_dict) - self._invited_by = _dict['invited_by'] + self._invited_by = _dict["invited_by"] def __eq__(self, other): - return ((self.id, - self.organization, - self._invited_by, - self.email_address, - self.status) == (other.id, - other.organization, - other._invited_by, - other.email_address, - other.status)) + return ( + self.id, + self.organization, + self._invited_by, + self.email_address, + self.status, + ) == ( + other.id, + other.organization, + other._invited_by, + other.email_address, + other.status, + ) @classmethod def create(cls, invite_from_id, org_id, email_address): - return cls(org_invite_api_client.create_invite( - invite_from_id, org_id, email_address - )) + return cls( + org_invite_api_client.create_invite(invite_from_id, org_id, email_address) + ) @classmethod def from_session(cls): - invited_org_user_id = session.get('invited_org_user_id') + invited_org_user_id = session.get("invited_org_user_id") return cls.by_id(invited_org_user_id) if invited_org_user_id else None @classmethod @@ -625,18 +640,17 @@ class InvitedOrgUser(JSONModel): @classmethod def by_id(cls, invited_user_id): - return cls( - org_invite_api_client.get_invited_user(invited_user_id) - ) + return cls(org_invite_api_client.get_invited_user(invited_user_id)) def serialize(self, permissions_as_string=False): - data = {'id': self.id, - 'organization': self.organization, - 'invited_by': self._invited_by, - 'email_address': self.email_address, - 'status': self.status, - 'created_at': str(self.created_at) - } + data = { + "id": self.id, + "organization": self.organization, + "invited_by": self._invited_by, + "email_address": self.email_address, + "status": self.status, + "created_at": str(self.created_at), + } return data @property @@ -648,8 +662,8 @@ class InvitedOrgUser(JSONModel): try: return cls(org_invite_api_client.check_token(token)) except HTTPError as exception: - if exception.status_code == 400 and 'invitation' in exception.message: - raise InviteTokenError(exception.message['invitation']) + if exception.status_code == 400 and "invitation" in exception.message: + raise InviteTokenError(exception.message["invitation"]) else: raise exception @@ -669,7 +683,6 @@ class AnonymousUser(AnonymousUserMixin): class Users(ModelList): - client_method = user_api_client.get_users_for_service model = User @@ -683,7 +696,7 @@ class Users(ModelList): user = User.from_id(id) if user and user.name: return user.name - return 'Unknown' + return "Unknown" class OrganizationUsers(Users): @@ -691,14 +704,14 @@ class OrganizationUsers(Users): class InvitedUsers(Users): - client_method = invite_api_client.get_invites_for_service model = InvitedUser def __init__(self, service_id): self.items = [ - user for user in self.client_method(service_id) - if user['status'] != 'accepted' + user + for user in self.client_method(service_id) + if user["status"] != "accepted" ] diff --git a/app/navigation.py b/app/navigation.py index 1cf3260cb..8fb06f7f5 100644 --- a/app/navigation.py +++ b/app/navigation.py @@ -4,7 +4,6 @@ from flask import request class Navigation: - mapping = {} selected_class = "usa-current" @@ -12,114 +11,116 @@ class Navigation: self.mapping = { navigation: { # if not specified, assume endpoints are all in the `main` blueprint. - self.get_endpoint_with_blueprint(endpoint) for endpoint in endpoints - } for navigation, endpoints in self.mapping.items() + self.get_endpoint_with_blueprint(endpoint) + for endpoint in endpoints + } + for navigation, endpoints in self.mapping.items() } @property def endpoints_with_navigation(self): - return tuple(chain.from_iterable(( - endpoints - for navigation_item, endpoints in self.mapping.items() - ))) + return tuple( + chain.from_iterable( + (endpoints for navigation_item, endpoints in self.mapping.items()) + ) + ) def is_selected(self, navigation_item): if request.endpoint in self.mapping[navigation_item]: return self.selected_class - return '' + return "" @staticmethod def get_endpoint_with_blueprint(endpoint): - return endpoint if '.' in endpoint else 'main.{}'.format(endpoint) + return endpoint if "." in endpoint else "main.{}".format(endpoint) class HeaderNavigation(Navigation): - mapping = { - 'support': { - 'bat_phone', - 'feedback', - 'support', - 'support_public', - 'thanks', - 'triage', + "support": { + "bat_phone", + "feedback", + "support", + "support_public", + "thanks", + "triage", }, - 'features': { - 'features', - 'features_email', - 'features_sms', - 'roadmap', - 'security', - 'terms', + "features": { + "features", + "features_email", + "features_sms", + "roadmap", + "security", + "terms", }, - 'using_notify': { - 'get_started', - 'using_notify', - 'pricing', - 'trial_mode_new', - 'message_status', - 'guidance_index', + "using_notify": { + "get_started", + "using_notify", + "pricing", + "trial_mode_new", + "message_status", + "guidance_index", }, - 'pricing': { - 'how_to_pay', - 'billing_details', + "pricing": { + "how_to_pay", + "billing_details", }, - 'documentation': { - 'documentation', - 'integration_testing', + "documentation": { + "documentation", + "integration_testing", }, - 'user-profile': { - 'user_profile', - 'user_profile_confirm_delete_mobile_number', - 'user_profile_email', - 'user_profile_email_authenticate', - 'user_profile_email_confirm', - 'user_profile_mobile_number', - 'user_profile_mobile_number_authenticate', - 'user_profile_mobile_number_confirm', - 'user_profile_mobile_number_delete', - 'user_profile_name', - 'user_profile_password', - 'user_profile_disable_platform_admin_view', + "user-profile": { + "user_profile", + "user_profile_confirm_delete_mobile_number", + "user_profile_email", + "user_profile_email_authenticate", + "user_profile_email_confirm", + "user_profile_mobile_number", + "user_profile_mobile_number_authenticate", + "user_profile_mobile_number_confirm", + "user_profile_mobile_number_delete", + "user_profile_name", + "user_profile_password", + "user_profile_disable_platform_admin_view", }, - 'platform-admin': { - 'archive_user', - 'change_user_auth', - 'clear_cache', - 'create_email_branding', - 'edit_sms_provider_ratio', - 'email_branding', - 'find_services_by_name', - 'find_users_by_email', - 'live_services', - 'live_services_csv', - 'notifications_sent_by_service', - 'get_billing_report', - 'get_users_report', - 'get_daily_volumes', - 'get_daily_sms_provider_volumes', - 'get_volumes_by_service', - 'organizations', - 'platform_admin', - 'platform_admin_list_complaints', - 'platform_admin_reports', - 'platform_admin_splash_page', - 'suspend_service', - 'trial_services', - 'update_email_branding', - 'user_information', - 'view_provider', - 'view_providers', + "platform-admin": { + "archive_user", + "change_user_auth", + "clear_cache", + "create_email_branding", + "edit_sms_provider_ratio", + "email_branding", + "find_services_by_name", + "find_users_by_email", + "live_services", + "live_services_csv", + "notifications_sent_by_service", + "get_billing_report", + "get_users_report", + "get_daily_volumes", + "get_daily_sms_provider_volumes", + "get_volumes_by_service", + "organizations", + "platform_admin", + "platform_admin_list_complaints", + "platform_admin_reports", + "platform_admin_splash_page", + "suspend_service", + "trial_services", + "update_email_branding", + "user_information", + "view_provider", + "view_providers", }, - 'sign-in': { - 'revalidate_email_sent', - 'sign_in', - 'two_factor_sms', - 'two_factor_email', - 'two_factor_email_sent', - 'two_factor_email_interstitial', - 'verify', - 'verify_email', + "sign-in": { + "revalidate_email_sent", + "sign_in", + "two_factor_sms", + "two_factor_email", + "two_factor_email_sent", + "two_factor_email_interstitial", + "verify", + "verify_email", }, } @@ -129,159 +130,155 @@ class HeaderNavigation(Navigation): class MainNavigation(Navigation): - mapping = { - 'dashboard': { - 'conversation', - 'inbox', - 'monthly', - 'service_dashboard', - 'template_usage', - 'view_notification', - 'view_notifications', + "dashboard": { + "conversation", + "inbox", + "monthly", + "service_dashboard", + "template_usage", + "view_notification", + "view_notifications", }, - 'templates': { - 'action_blocked', - 'add_service_template', - 'check_messages', - 'check_notification', - 'choose_template', - 'choose_template_to_copy', - 'confirm_redact_template', - 'conversation_reply', - 'copy_template', - 'delete_service_template', - 'edit_service_template', - 'manage_template_folder', - 'send_messages', - 'send_one_off', - 'send_one_off_step', - 'send_one_off_to_myself', - 'set_sender', - 'set_template_sender', - 'view_template', - 'view_template_version', - 'view_template_versions', + "templates": { + "action_blocked", + "add_service_template", + "check_messages", + "check_notification", + "choose_template", + "choose_template_to_copy", + "confirm_redact_template", + "conversation_reply", + "copy_template", + "delete_service_template", + "edit_service_template", + "manage_template_folder", + "send_messages", + "send_one_off", + "send_one_off_step", + "send_one_off_to_myself", + "set_sender", + "set_template_sender", + "view_template", + "view_template_version", + "view_template_versions", }, - 'uploads': { - 'uploads', - 'view_job', - 'view_jobs', + "uploads": { + "uploads", + "view_job", + "view_jobs", }, - 'team-members': { - 'confirm_edit_user_email', - 'confirm_edit_user_mobile_number', - 'edit_user_email', - 'edit_user_mobile_number', - 'edit_user_permissions', - 'invite_user', - 'manage_users', - 'remove_user_from_service', + "team-members": { + "confirm_edit_user_email", + "confirm_edit_user_mobile_number", + "edit_user_email", + "edit_user_mobile_number", + "edit_user_permissions", + "invite_user", + "manage_users", + "remove_user_from_service", }, - 'usage': { - 'usage', + "usage": { + "usage", }, - 'settings': { + "settings": { # 'add_organization_from_gp_service', - 'email_branding_govuk', - 'email_branding_govuk_and_org', - 'email_branding_organization', - 'email_branding_request', - 'email_branding_something_else', - 'estimate_usage', - 'link_service_to_organization', - 'request_to_go_live', - 'service_add_email_reply_to', - 'service_add_sms_sender', - 'service_confirm_delete_email_reply_to', - 'service_confirm_delete_sms_sender', - 'service_edit_email_reply_to', - 'service_edit_sms_sender', - 'service_email_reply_to', - 'service_name_change', - 'service_preview_email_branding', - 'service_set_auth_type', - 'service_set_channel', - 'send_files_by_email_contact_details', - 'service_set_email_branding', - 'service_set_inbound_number', - 'service_set_inbound_sms', - 'service_set_international_sms', - 'service_set_reply_to_email', - 'service_set_sms_prefix', - 'service_verify_reply_to_address', - 'service_verify_reply_to_address_updates', - 'service_settings', - 'service_sms_senders', - 'set_free_sms_allowance', - 'set_message_limit', - 'set_rate_limit', - 'submit_request_to_go_live', + "email_branding_govuk", + "email_branding_govuk_and_org", + "email_branding_organization", + "email_branding_request", + "email_branding_something_else", + "estimate_usage", + "link_service_to_organization", + "request_to_go_live", + "service_add_email_reply_to", + "service_add_sms_sender", + "service_confirm_delete_email_reply_to", + "service_confirm_delete_sms_sender", + "service_edit_email_reply_to", + "service_edit_sms_sender", + "service_email_reply_to", + "service_name_change", + "service_preview_email_branding", + "service_set_auth_type", + "service_set_channel", + "send_files_by_email_contact_details", + "service_set_email_branding", + "service_set_inbound_number", + "service_set_inbound_sms", + "service_set_international_sms", + "service_set_reply_to_email", + "service_set_sms_prefix", + "service_verify_reply_to_address", + "service_verify_reply_to_address_updates", + "service_settings", + "service_sms_senders", + "set_free_sms_allowance", + "set_message_limit", + "set_rate_limit", + "submit_request_to_go_live", }, - 'api-integration': { - 'api_callbacks', - 'api_documentation', - 'api_integration', - 'api_keys', - 'create_api_key', - 'delivery_status_callback', - 'received_text_messages_callback', - 'revoke_api_key', - 'guest_list', - 'old_guest_list', + "api-integration": { + "api_callbacks", + "api_documentation", + "api_integration", + "api_keys", + "create_api_key", + "delivery_status_callback", + "received_text_messages_callback", + "revoke_api_key", + "guest_list", + "old_guest_list", }, } class CaseworkNavigation(Navigation): - mapping = { - 'send-one-off': { - 'choose_template', - 'send_one_off', - 'send_one_off_step', - 'send_one_off_to_myself', + "send-one-off": { + "choose_template", + "send_one_off", + "send_one_off_step", + "send_one_off_to_myself", }, - 'sent-messages': { - 'view_notifications', - 'view_notification', + "sent-messages": { + "view_notifications", + "view_notification", }, - 'uploads': { - 'view_jobs', - 'view_job', - 'uploads', + "uploads": { + "view_jobs", + "view_job", + "uploads", }, } class OrgNavigation(Navigation): - mapping = { - 'dashboard': { - 'organization_dashboard', + "dashboard": { + "organization_dashboard", }, - 'settings': { - 'edit_organization_billing_details', - 'edit_organization_domains', - 'edit_organization_email_branding', - 'edit_organization_go_live_notes', - 'edit_organization_name', - 'edit_organization_notes', - 'edit_organization_type', - 'organization_preview_email_branding', - 'organization_settings', - + "settings": { + "edit_organization_billing_details", + "edit_organization_domains", + "edit_organization_email_branding", + "edit_organization_go_live_notes", + "edit_organization_name", + "edit_organization_notes", + "edit_organization_type", + "organization_preview_email_branding", + "organization_settings", }, - 'team-members': { - 'edit_organization_user', - 'invite_org_user', - 'manage_org_users', - 'remove_user_from_organization', + "team-members": { + "edit_organization_user", + "invite_org_user", + "manage_org_users", + "remove_user_from_organization", }, - 'trial-services': { - 'organization_trial_mode_services', + "trial-services": { + "organization_trial_mode_services", + }, + "billing": { + "organization_billing", }, - 'billing': { - 'organization_billing', - } } diff --git a/app/notify_client/__init__.py b/app/notify_client/__init__.py index 3145ebc44..1fc14f811 100644 --- a/app/notify_client/__init__.py +++ b/app/notify_client/__init__.py @@ -10,29 +10,25 @@ cache = RequestCache(redis_client) def _attach_current_user(data): - return dict( - created_by=current_user.id, - **data - ) + return dict(created_by=current_user.id, **data) class NotifyAdminAPIClient(BaseAPIClient): - def __init__(self): super().__init__("a" * 73, "b") def init_app(self, app): - self.base_url = app.config['API_HOST_NAME'] - self.service_id = app.config['ADMIN_CLIENT_USER_NAME'] - self.api_key = app.config['ADMIN_CLIENT_SECRET'] - self.route_secret = app.config['ROUTE_SECRET_KEY_1'] + self.base_url = app.config["API_HOST_NAME"] + self.service_id = app.config["ADMIN_CLIENT_USER_NAME"] + self.api_key = app.config["ADMIN_CLIENT_SECRET"] + self.route_secret = app.config["ROUTE_SECRET_KEY_1"] def generate_headers(self, api_token): headers = { "Content-type": "application/json", "Authorization": "Bearer {}".format(api_token), "X-Custom-Forwarder": self.route_secret, - "User-agent": "NOTIFY-API-PYTHON-CLIENT/{}".format(__version__) + "User-agent": "NOTIFY-API-PYTHON-CLIENT/{}".format(__version__), } return self._add_request_id_header(headers) @@ -40,8 +36,8 @@ class NotifyAdminAPIClient(BaseAPIClient): def _add_request_id_header(headers): if not has_request_context(): return headers - headers['X-B3-TraceId'] = request.request_id - headers['X-B3-SpanId'] = request.span_id + headers["X-B3-TraceId"] = request.request_id + headers["X-B3-SpanId"] = request.span_id return headers def check_inactive_service(self): @@ -51,7 +47,11 @@ class NotifyAdminAPIClient(BaseAPIClient): # if the current service is inactive and the user isn't a platform admin, we should block them from making any # stateful modifications to that service - if current_service and not current_service.active and not current_user.platform_admin: + if ( + current_service + and not current_service.active + and not current_user.platform_admin + ): abort(403) def post(self, *args, **kwargs): diff --git a/app/notify_client/api_key_api_client.py b/app/notify_client/api_key_api_client.py index 1e0bfeec3..0383e8f8c 100644 --- a/app/notify_client/api_key_api_client.py +++ b/app/notify_client/api_key_api_client.py @@ -1,30 +1,26 @@ from app.notify_client import NotifyAdminAPIClient, _attach_current_user # must match key types in notifications-api/app/models.py -KEY_TYPE_NORMAL = 'normal' -KEY_TYPE_TEAM = 'team' -KEY_TYPE_TEST = 'test' +KEY_TYPE_NORMAL = "normal" +KEY_TYPE_TEAM = "team" +KEY_TYPE_TEST = "test" class ApiKeyApiClient(NotifyAdminAPIClient): - def get_api_keys(self, service_id): - return self.get(url='/service/{}/api-keys'.format(service_id)) + return self.get(url="/service/{}/api-keys".format(service_id)) def create_api_key(self, service_id, key_name, key_type): - data = { - 'name': key_name, - 'key_type': key_type - } + data = {"name": key_name, "key_type": key_type} data = _attach_current_user(data) - key = self.post(url='/service/{}/api-key'.format(service_id), data=data) - return key['data'] + key = self.post(url="/service/{}/api-key".format(service_id), data=data) + return key["data"] def revoke_api_key(self, service_id, key_id): data = _attach_current_user({}) return self.post( - url='/service/{0}/api-key/revoke/{1}'.format(service_id, key_id), - data=data) + url="/service/{0}/api-key/revoke/{1}".format(service_id, key_id), data=data + ) api_key_api_client = ApiKeyApiClient() diff --git a/app/notify_client/billing_api_client.py b/app/notify_client/billing_api_client.py index 3c70b573f..a6226f14a 100644 --- a/app/notify_client/billing_api_client.py +++ b/app/notify_client/billing_api_client.py @@ -2,66 +2,73 @@ from app.notify_client import NotifyAdminAPIClient class BillingAPIClient(NotifyAdminAPIClient): - def get_monthly_usage_for_service(self, service_id, year): return self.get( - '/service/{0}/billing/monthly-usage'.format(service_id), - params=dict(year=year) + "/service/{0}/billing/monthly-usage".format(service_id), + params=dict(year=year), ) def get_annual_usage_for_service(self, service_id, year=None): return self.get( - '/service/{0}/billing/yearly-usage-summary'.format(service_id), - params=dict(year=year) + "/service/{0}/billing/yearly-usage-summary".format(service_id), + params=dict(year=year), ) def get_free_sms_fragment_limit_for_year(self, service_id, year=None): result = self.get( - '/service/{0}/billing/free-sms-fragment-limit'.format(service_id), - params=dict(financial_year_start=year) + "/service/{0}/billing/free-sms-fragment-limit".format(service_id), + params=dict(financial_year_start=year), ) - return result['free_sms_fragment_limit'] + return result["free_sms_fragment_limit"] - def create_or_update_free_sms_fragment_limit(self, service_id, free_sms_fragment_limit, year=None): + def create_or_update_free_sms_fragment_limit( + self, service_id, free_sms_fragment_limit, year=None + ): # year = None will update current and future year in the API data = { "financial_year_start": year, - "free_sms_fragment_limit": free_sms_fragment_limit + "free_sms_fragment_limit": free_sms_fragment_limit, } return self.post( - url='/service/{0}/billing/free-sms-fragment-limit'.format(service_id), - data=data + url="/service/{0}/billing/free-sms-fragment-limit".format(service_id), + data=data, ) def get_data_for_billing_report(self, start_date, end_date): - return self.get(url='/platform-stats/data-for-billing-report', - params={ - 'start_date': str(start_date), - 'end_date': str(end_date), - }) + return self.get( + url="/platform-stats/data-for-billing-report", + params={ + "start_date": str(start_date), + "end_date": str(end_date), + }, + ) def get_data_for_volumes_by_service_report(self, start_date, end_date): - return self.get(url='/platform-stats/volumes-by-service', - params={ - 'start_date': str(start_date), - 'end_date': str(end_date), - }) + return self.get( + url="/platform-stats/volumes-by-service", + params={ + "start_date": str(start_date), + "end_date": str(end_date), + }, + ) def get_data_for_daily_volumes_report(self, start_date, end_date): - return self.get(url='/platform-stats/daily-volumes-report', - params={ - 'start_date': str(start_date), - 'end_date': str(end_date), - }) + return self.get( + url="/platform-stats/daily-volumes-report", + params={ + "start_date": str(start_date), + "end_date": str(end_date), + }, + ) def get_data_for_daily_sms_provider_volumes_report(self, start_date, end_date): return self.get( - url='/platform-stats/daily-sms-provider-volumes-report', + url="/platform-stats/daily-sms-provider-volumes-report", params={ - 'start_date': str(start_date), - 'end_date': str(end_date), - } + "start_date": str(start_date), + "end_date": str(end_date), + }, ) diff --git a/app/notify_client/complaint_api_client.py b/app/notify_client/complaint_api_client.py index d9eaf7bd3..156e6360a 100644 --- a/app/notify_client/complaint_api_client.py +++ b/app/notify_client/complaint_api_client.py @@ -2,13 +2,12 @@ from app.notify_client import NotifyAdminAPIClient class ComplaintApiClient(NotifyAdminAPIClient): - def get_all_complaints(self, page=1): - params = {'page': page} - return self.get('/complaint', params=params) + params = {"page": page} + return self.get("/complaint", params=params) def get_complaint_count(self, params_dict=None): - return self.get('/complaint/count-by-date-range', params=params_dict) + return self.get("/complaint/count-by-date-range", params=params_dict) complaint_api_client = ComplaintApiClient() diff --git a/app/notify_client/email_branding_client.py b/app/notify_client/email_branding_client.py index 469e6f7c5..fe939cfd8 100644 --- a/app/notify_client/email_branding_client.py +++ b/app/notify_client/email_branding_client.py @@ -2,38 +2,37 @@ from app.notify_client import NotifyAdminAPIClient, cache class EmailBrandingClient(NotifyAdminAPIClient): - - @cache.set('email_branding-{branding_id}') + @cache.set("email_branding-{branding_id}") def get_email_branding(self, branding_id): - return self.get(url='/email-branding/{}'.format(branding_id)) + return self.get(url="/email-branding/{}".format(branding_id)) - @cache.set('email_branding') + @cache.set("email_branding") def get_all_email_branding(self, sort_key=None): - brandings = self.get(url='/email-branding')['email_branding'] + brandings = self.get(url="/email-branding")["email_branding"] if sort_key and sort_key in brandings[0]: brandings.sort(key=lambda branding: branding[sort_key].lower()) return brandings - @cache.delete('email_branding') + @cache.delete("email_branding") def create_email_branding(self, logo, name, text, colour, brand_type): data = { "logo": logo, "name": name, "text": text, "colour": colour, - "brand_type": brand_type + "brand_type": brand_type, } return self.post(url="/email-branding", data=data) - @cache.delete('email_branding') - @cache.delete('email_branding-{branding_id}') + @cache.delete("email_branding") + @cache.delete("email_branding-{branding_id}") def update_email_branding(self, branding_id, logo, name, text, colour, brand_type): data = { "logo": logo, "name": name, "text": text, "colour": colour, - "brand_type": brand_type + "brand_type": brand_type, } return self.post(url="/email-branding/{}".format(branding_id), data=data) diff --git a/app/notify_client/events_api_client.py b/app/notify_client/events_api_client.py index b6400c0c9..835316203 100644 --- a/app/notify_client/events_api_client.py +++ b/app/notify_client/events_api_client.py @@ -2,14 +2,10 @@ from app.notify_client import NotifyAdminAPIClient class EventsApiClient(NotifyAdminAPIClient): - def create_event(self, event_type, event_data): - data = { - 'event_type': event_type, - 'data': event_data - } - resp = self.post(url='/events', data=data) - return resp['data'] + data = {"event_type": event_type, "data": event_data} + resp = self.post(url="/events", data=data) + return resp["data"] events_api_client = EventsApiClient() diff --git a/app/notify_client/inbound_number_client.py b/app/notify_client/inbound_number_client.py index 6b3607f97..69131b7b7 100644 --- a/app/notify_client/inbound_number_client.py +++ b/app/notify_client/inbound_number_client.py @@ -2,15 +2,14 @@ from app.notify_client import NotifyAdminAPIClient class InboundNumberClient(NotifyAdminAPIClient): - def get_available_inbound_sms_numbers(self): - return self.get(url='/inbound-number/available') + return self.get(url="/inbound-number/available") def get_all_inbound_sms_number_service(self): - return self.get('/inbound-number') + return self.get("/inbound-number") def get_inbound_sms_number_for_service(self, service_id): - return self.get('/inbound-number/service/{}'.format(service_id)) + return self.get("/inbound-number/service/{}".format(service_id)) inbound_number_client = InboundNumberClient() diff --git a/app/notify_client/invite_api_client.py b/app/notify_client/invite_api_client.py index 464c3ba9c..1debeaea2 100644 --- a/app/notify_client/invite_api_client.py +++ b/app/notify_client/invite_api_client.py @@ -6,70 +6,72 @@ from app.utils.user_permissions import ( class InviteApiClient(NotifyAdminAPIClient): - def init_app(self, app): super().init_app(app) - self.admin_url = app.config['ADMIN_BASE_URL'] + self.admin_url = app.config["ADMIN_BASE_URL"] - def create_invite(self, - invite_from_id, - service_id, - email_address, - permissions, - auth_type, - folder_permissions): + def create_invite( + self, + invite_from_id, + service_id, + email_address, + permissions, + auth_type, + folder_permissions, + ): data = { - 'service': service_id, - 'email_address': email_address, - 'from_user': invite_from_id, - 'permissions': ','.join(sorted(translate_permissions_from_ui_to_db(permissions))), - 'auth_type': auth_type, - 'invite_link_host': self.admin_url, - 'folder_permissions': folder_permissions, + "service": service_id, + "email_address": email_address, + "from_user": invite_from_id, + "permissions": ",".join( + sorted(translate_permissions_from_ui_to_db(permissions)) + ), + "auth_type": auth_type, + "invite_link_host": self.admin_url, + "folder_permissions": folder_permissions, } data = _attach_current_user(data) - resp = self.post(url='/service/{}/invite'.format(service_id), data=data) - return resp['data'] + resp = self.post(url="/service/{}/invite".format(service_id), data=data) + return resp["data"] def get_invites_for_service(self, service_id): - return self.get( - '/service/{}/invite'.format(service_id) - )['data'] + return self.get("/service/{}/invite".format(service_id))["data"] def get_invited_user(self, invited_user_id): - return self.get( - f'/invite/service/{invited_user_id}' - )['data'] + return self.get(f"/invite/service/{invited_user_id}")["data"] def get_invited_user_for_service(self, service_id, invited_user_id): - return self.get( - f'/service/{service_id}/invite/{invited_user_id}' - )['data'] + return self.get(f"/service/{service_id}/invite/{invited_user_id}")["data"] def get_count_of_invites_with_permission(self, service_id, permission): if permission not in all_ui_permissions: - raise TypeError('{} is not a valid permission'.format(permission)) - return len([ - invited_user for invited_user in self.get_invites_for_service(service_id) - if invited_user.has_permission_for_service(service_id, permission) - ]) + raise TypeError("{} is not a valid permission".format(permission)) + return len( + [ + invited_user + for invited_user in self.get_invites_for_service(service_id) + if invited_user.has_permission_for_service(service_id, permission) + ] + ) def check_token(self, token): - return self.get(url='/invite/service/check/{}'.format(token))['data'] + return self.get(url="/invite/service/check/{}".format(token))["data"] def cancel_invited_user(self, service_id, invited_user_id): - data = {'status': 'cancelled'} + data = {"status": "cancelled"} data = _attach_current_user(data) - self.post(url='/service/{0}/invite/{1}'.format(service_id, invited_user_id), - data=data) + self.post( + url="/service/{0}/invite/{1}".format(service_id, invited_user_id), data=data + ) - @cache.delete('service-{service_id}') - @cache.delete('user-{invited_user_id}') + @cache.delete("service-{service_id}") + @cache.delete("user-{invited_user_id}") def accept_invite(self, service_id, invited_user_id): - data = {'status': 'accepted'} - self.post(url='/service/{0}/invite/{1}'.format(service_id, invited_user_id), - data=data) + data = {"status": "accepted"} + self.post( + url="/service/{0}/invite/{1}".format(service_id, invited_user_id), data=data + ) invite_api_client = InviteApiClient() diff --git a/app/notify_client/job_api_client.py b/app/notify_client/job_api_client.py index c54220657..ac1cdfd9b 100644 --- a/app/notify_client/job_api_client.py +++ b/app/notify_client/job_api_client.py @@ -3,52 +3,58 @@ from app.notify_client import NotifyAdminAPIClient, _attach_current_user, cache class JobApiClient(NotifyAdminAPIClient): - JOB_STATUSES = { - 'scheduled', - 'pending', - 'in progress', - 'finished', - 'cancelled', - 'sending limits exceeded', - 'ready to send', - 'sent to dvla' + "scheduled", + "pending", + "in progress", + "finished", + "cancelled", + "sending limits exceeded", + "ready to send", + "sent to dvla", } - SCHEDULED_JOB_STATUS = 'scheduled' - CANCELLED_JOB_STATUS = 'cancelled' + SCHEDULED_JOB_STATUS = "scheduled" + CANCELLED_JOB_STATUS = "cancelled" NON_CANCELLED_JOB_STATUSES = JOB_STATUSES - {CANCELLED_JOB_STATUS} - NON_SCHEDULED_JOB_STATUSES = JOB_STATUSES - {SCHEDULED_JOB_STATUS, CANCELLED_JOB_STATUS} + NON_SCHEDULED_JOB_STATUSES = JOB_STATUSES - { + SCHEDULED_JOB_STATUS, + CANCELLED_JOB_STATUS, + } def get_job(self, service_id, job_id): params = {} - job = self.get(url='/service/{}/job/{}'.format(service_id, job_id), params=params) + job = self.get( + url="/service/{}/job/{}".format(service_id, job_id), params=params + ) return job def get_jobs(self, service_id, *, limit_days=None, statuses=None, page=1): - params = {'page': page} + params = {"page": page} if limit_days is not None: - params['limit_days'] = limit_days + params["limit_days"] = limit_days if statuses is not None: - params['statuses'] = ','.join(statuses) + params["statuses"] = ",".join(statuses) - return self.get(url='/service/{}/job'.format(service_id), params=params) + return self.get(url="/service/{}/job".format(service_id), params=params) def get_uploads(self, service_id, limit_days=None, page=1): - params = {'page': page} + params = {"page": page} if limit_days is not None: - params['limit_days'] = limit_days - return self.get(url='/service/{}/upload'.format(service_id), params=params) + params["limit_days"] = limit_days + return self.get(url="/service/{}/upload".format(service_id), params=params) - def has_sent_previously(self, service_id, template_id, template_version, original_file_name): - return ( - template_id, template_version, original_file_name - ) in ( + def has_sent_previously( + self, service_id, template_id, template_version, original_file_name + ): + return (template_id, template_version, original_file_name) in ( ( - job['template'], job['template_version'], job['original_file_name'], + job["template"], + job["template_version"], + job["original_file_name"], ) - for job in self.get_jobs(service_id, limit_days=0)['data'] - if job['job_status'] != 'cancelled' + for job in self.get_jobs(service_id, limit_days=0)["data"] + if job["job_status"] != "cancelled" ) def get_page_of_jobs(self, service_id, *, page, statuses=None, limit_days=None): @@ -64,49 +70,43 @@ class JobApiClient(NotifyAdminAPIClient): service_id, limit_days=7, statuses=self.NON_SCHEDULED_JOB_STATUSES, - )['data'] + )["data"] def get_scheduled_jobs(self, service_id): return sorted( - self.get_jobs( - service_id, - statuses=[self.SCHEDULED_JOB_STATUS] - )['data'], - key=lambda job: job['scheduled_for'], + self.get_jobs(service_id, statuses=[self.SCHEDULED_JOB_STATUS])["data"], + key=lambda job: job["scheduled_for"], reverse=True, ) def get_scheduled_job_stats(self, service_id): - return self.get( - url=f'/service/{service_id}/job/scheduled-job-stats' - ) + return self.get(url=f"/service/{service_id}/job/scheduled-job-stats") - @cache.set('has_jobs-{service_id}') + @cache.set("has_jobs-{service_id}") def has_jobs(self, service_id): - return bool(self.get_jobs(service_id)['data']) + return bool(self.get_jobs(service_id)["data"]) def create_job(self, job_id, service_id, scheduled_for=None): data = {"id": job_id} if scheduled_for: - data.update({'scheduled_for': scheduled_for}) + data.update({"scheduled_for": scheduled_for}) data = _attach_current_user(data) - job = self.post(url='/service/{}/job'.format(service_id), data=data) + job = self.post(url="/service/{}/job".format(service_id), data=data) redis_client.set( - 'has_jobs-{}'.format(service_id), - b'true', + "has_jobs-{}".format(service_id), + b"true", ex=int(cache.DEFAULT_TTL), ) return job - @cache.delete('has_jobs-{service_id}') + @cache.delete("has_jobs-{service_id}") def cancel_job(self, service_id, job_id): return self.post( - url='/service/{}/job/{}/cancel'.format(service_id, job_id), - data={} + url="/service/{}/job/{}/cancel".format(service_id, job_id), data={} ) diff --git a/app/notify_client/notification_api_client.py b/app/notify_client/notification_api_client.py index 36afbbf2e..95ac96a04 100644 --- a/app/notify_client/notification_api_client.py +++ b/app/notify_client/notification_api_client.py @@ -2,7 +2,6 @@ from app.notify_client import NotifyAdminAPIClient, _attach_current_user class NotificationApiClient(NotifyAdminAPIClient): - def get_notifications_for_service( self, service_id, @@ -20,16 +19,16 @@ class NotificationApiClient(NotifyAdminAPIClient): include_one_off=None, ): params = { - 'page': page, - 'page_size': page_size, - 'template_type': template_type, - 'status': status, - 'include_jobs': include_jobs, - 'include_from_test_key': include_from_test_key, - 'format_for_csv': format_for_csv, - 'to': to, - 'include_one_off': include_one_off, - 'count_pages': count_pages, + "page": page, + "page_size": page_size, + "template_type": template_type, + "status": status, + "include_jobs": include_jobs, + "include_from_test_key": include_from_test_key, + "format_for_csv": format_for_csv, + "to": to, + "include_one_off": include_one_off, + "count_pages": count_pages, } params = {k: v for k, v in params.items() if v is not None} @@ -37,34 +36,37 @@ class NotificationApiClient(NotifyAdminAPIClient): # if `to` is set it is likely PII like an email address or mobile which # we do not want in our logs, so we do a POST request instead of a GET method = self.post if to else self.get - kwargs = {'data': params} if to else {'params': params} + kwargs = {"data": params} if to else {"params": params} if job_id: return method( - url='/service/{}/job/{}/notifications'.format(service_id, job_id), + url="/service/{}/job/{}/notifications".format(service_id, job_id), **kwargs ) else: if limit_days is not None: - params['limit_days'] = limit_days - return method( - url='/service/{}/notifications'.format(service_id), - **kwargs - ) + params["limit_days"] = limit_days + return method(url="/service/{}/notifications".format(service_id), **kwargs) - def send_notification(self, service_id, *, template_id, recipient, personalisation, sender_id): + def send_notification( + self, service_id, *, template_id, recipient, personalisation, sender_id + ): data = { - 'template_id': template_id, - 'to': recipient, - 'personalisation': personalisation, + "template_id": template_id, + "to": recipient, + "personalisation": personalisation, } if sender_id: - data['sender_id'] = sender_id + data["sender_id"] = sender_id data = _attach_current_user(data) - return self.post(url='/service/{}/send-notification'.format(service_id), data=data) + return self.post( + url="/service/{}/send-notification".format(service_id), data=data + ) def get_notification(self, service_id, notification_id): - return self.get(url='/service/{}/notifications/{}'.format(service_id, notification_id)) + return self.get( + url="/service/{}/notifications/{}".format(service_id, notification_id) + ) def get_api_notifications_for_service(self, service_id): ret = self.get_notifications_for_service( @@ -72,26 +74,31 @@ class NotificationApiClient(NotifyAdminAPIClient): include_jobs=False, include_from_test_key=True, include_one_off=False, - count_pages=False + count_pages=False, ) return ret def update_notification_to_cancelled(self, service_id, notification_id): return self.post( - url='/service/{}/notifications/{}/cancel'.format(service_id, notification_id), - data={}) + url="/service/{}/notifications/{}/cancel".format( + service_id, notification_id + ), + data={}, + ) def get_notification_status_by_service(self, start_date, end_date): return self.get( - url='service/monthly-data-by-service', + url="service/monthly-data-by-service", params={ - 'start_date': str(start_date), - 'end_date': str(end_date), - } + "start_date": str(start_date), + "end_date": str(end_date), + }, ) def get_notification_count_for_job_id(self, *, service_id, job_id): - return self.get(url='/service/{}/job/{}/notification_count'.format(service_id, job_id))["count"] + return self.get( + url="/service/{}/job/{}/notification_count".format(service_id, job_id) + )["count"] notification_api_client = NotificationApiClient() diff --git a/app/notify_client/org_invite_api_client.py b/app/notify_client/org_invite_api_client.py index 60563a537..d85787748 100644 --- a/app/notify_client/org_invite_api_client.py +++ b/app/notify_client/org_invite_api_client.py @@ -2,51 +2,50 @@ from app.notify_client import NotifyAdminAPIClient, _attach_current_user class OrgInviteApiClient(NotifyAdminAPIClient): - def init_app(self, app): super().init_app(app) - self.admin_url = app.config['ADMIN_BASE_URL'] + self.admin_url = app.config["ADMIN_BASE_URL"] def create_invite(self, invite_from_id, org_id, email_address): data = { - 'email_address': email_address, - 'invited_by': invite_from_id, - 'invite_link_host': self.admin_url, + "email_address": email_address, + "invited_by": invite_from_id, + "invite_link_host": self.admin_url, } data = _attach_current_user(data) - resp = self.post(url='/organization/{}/invite'.format(org_id), data=data) - return resp['data'] + resp = self.post(url="/organization/{}/invite".format(org_id), data=data) + return resp["data"] def get_invites_for_organization(self, org_id): - endpoint = '/organization/{}/invite'.format(org_id) + endpoint = "/organization/{}/invite".format(org_id) resp = self.get(endpoint) - return resp['data'] + return resp["data"] def get_invited_user_for_org(self, org_id, invited_org_user_id): - return self.get( - f'/organization/{org_id}/invite/{invited_org_user_id}' - )['data'] + return self.get(f"/organization/{org_id}/invite/{invited_org_user_id}")["data"] def get_invited_user(self, invited_user_id): - return self.get( - f'/invite/organization/{invited_user_id}' - )['data'] + return self.get(f"/invite/organization/{invited_user_id}")["data"] def check_token(self, token): - resp = self.get(url='/invite/organization/check/{}'.format(token)) - return resp['data'] + resp = self.get(url="/invite/organization/check/{}".format(token)) + return resp["data"] def cancel_invited_user(self, org_id, invited_user_id): - data = {'status': 'cancelled'} + data = {"status": "cancelled"} data = _attach_current_user(data) - self.post(url='/organization/{0}/invite/{1}'.format(org_id, invited_user_id), - data=data) + self.post( + url="/organization/{0}/invite/{1}".format(org_id, invited_user_id), + data=data, + ) def accept_invite(self, org_id, invited_user_id): - data = {'status': 'accepted'} - self.post(url='/organization/{0}/invite/{1}'.format(org_id, invited_user_id), - data=data) + data = {"status": "accepted"} + self.post( + url="/organization/{0}/invite/{1}".format(org_id, invited_user_id), + data=data, + ) org_invite_api_client = OrgInviteApiClient() diff --git a/app/notify_client/organizations_api_client.py b/app/notify_client/organizations_api_client.py index 18935c47a..b37acda74 100644 --- a/app/notify_client/organizations_api_client.py +++ b/app/notify_client/organizations_api_client.py @@ -7,81 +7,76 @@ from app.notify_client import NotifyAdminAPIClient, cache class OrganizationsClient(NotifyAdminAPIClient): - - @cache.set('organizations') + @cache.set("organizations") def get_organizations(self): - return self.get(url='/organizations') + return self.get(url="/organizations") - @cache.set('domains') + @cache.set("domains") def get_domains(self): - return list(chain.from_iterable( - organization['domains'] - for organization in self.get_organizations() - )) + return list( + chain.from_iterable( + organization["domains"] for organization in self.get_organizations() + ) + ) def get_organization(self, org_id): - return self.get(url='/organizations/{}'.format(org_id)) + return self.get(url="/organizations/{}".format(org_id)) - @cache.set('organization-{org_id}-name') + @cache.set("organization-{org_id}-name") def get_organization_name(self, org_id): - return self.get_organization(org_id)['name'] + return self.get_organization(org_id)["name"] def get_organization_by_domain(self, domain): try: return self.get( - url='/organizations/by-domain?domain={}'.format(domain), + url="/organizations/by-domain?domain={}".format(domain), ) except HTTPError as error: if error.status_code == 404: return None raise error - @cache.delete('organizations') + @cache.delete("organizations") def create_organization(self, name, organization_type): return self.post( url="/organizations", data={ "name": name, "organization_type": organization_type, - } + }, ) - @cache.delete('domains') - @cache.delete('organizations') + @cache.delete("domains") + @cache.delete("organizations") def update_organization(self, org_id, cached_service_ids=None, **kwargs): api_response = self.post(url="/organizations/{}".format(org_id), data=kwargs) if cached_service_ids: - redis_client.delete(*map('service-{}'.format, cached_service_ids)) + redis_client.delete(*map("service-{}".format, cached_service_ids)) - if 'name' in kwargs: - redis_client.delete(f'organization-{org_id}-name') + if "name" in kwargs: + redis_client.delete(f"organization-{org_id}-name") return api_response - @cache.delete('service-{service_id}') - @cache.delete('live-service-and-organization-counts') - @cache.delete('organizations') + @cache.delete("service-{service_id}") + @cache.delete("live-service-and-organization-counts") + @cache.delete("organizations") def update_service_organization(self, service_id, org_id): - data = { - 'service_id': service_id - } - return self.post( - url="/organizations/{}/service".format(org_id), - data=data - ) + data = {"service_id": service_id} + return self.post(url="/organizations/{}/service".format(org_id), data=data) def get_organization_services(self, org_id): return self.get(url="/organizations/{}/services".format(org_id)) - @cache.delete('user-{user_id}') + @cache.delete("user-{user_id}") def remove_user_from_organization(self, org_id, user_id): - return self.delete(f'/organizations/{org_id}/users/{user_id}') + return self.delete(f"/organizations/{org_id}/users/{user_id}") def get_services_and_usage(self, org_id, year): return self.get( url=f"/organizations/{org_id}/services-with-usage", - params={"year": str(year)} + params={"year": str(year)}, ) diff --git a/app/notify_client/performance_dashboard_api_client.py b/app/notify_client/performance_dashboard_api_client.py index e8800b321..befa93880 100644 --- a/app/notify_client/performance_dashboard_api_client.py +++ b/app/notify_client/performance_dashboard_api_client.py @@ -2,8 +2,7 @@ from app.notify_client import NotifyAdminAPIClient, cache class PerformanceDashboardAPIClient(NotifyAdminAPIClient): - - @cache.set('performance-stats-{start_date}-to-{end_date}', ttl_in_seconds=3600) + @cache.set("performance-stats-{start_date}-to-{end_date}", ttl_in_seconds=3600) def get_performance_dashboard_stats( self, *, @@ -11,11 +10,11 @@ class PerformanceDashboardAPIClient(NotifyAdminAPIClient): end_date, ): return self.get( - '/performance-dashboard', + "/performance-dashboard", params={ - 'start_date': str(start_date), - 'end_date': str(end_date), - } + "start_date": str(start_date), + "end_date": str(end_date), + }, ) diff --git a/app/notify_client/platform_stats_api_client.py b/app/notify_client/platform_stats_api_client.py index a91beba14..23a2fe26b 100644 --- a/app/notify_client/platform_stats_api_client.py +++ b/app/notify_client/platform_stats_api_client.py @@ -2,7 +2,6 @@ from app.notify_client import NotifyAdminAPIClient class PlatformStatsAPIClient(NotifyAdminAPIClient): - def get_aggregate_platform_stats(self, params_dict=None): return self.get("/platform-stats", params=params_dict) diff --git a/app/notify_client/provider_client.py b/app/notify_client/provider_client.py index e17ea76be..f12fc3d47 100644 --- a/app/notify_client/provider_client.py +++ b/app/notify_client/provider_client.py @@ -1,30 +1,20 @@ - from app.notify_client import NotifyAdminAPIClient, _attach_current_user class ProviderClient(NotifyAdminAPIClient): - def get_all_providers(self): - return self.get( - url='/provider-details' - ) + return self.get(url="/provider-details") def get_provider_by_id(self, provider_id): - return self.get( - url='/provider-details/{}'.format(provider_id) - ) + return self.get(url="/provider-details/{}".format(provider_id)) def get_provider_versions(self, provider_id): - return self.get( - url='/provider-details/{}/versions'.format(provider_id) - ) + return self.get(url="/provider-details/{}/versions".format(provider_id)) def update_provider(self, provider_id, priority): - data = { - "priority": priority - } + data = {"priority": priority} data = _attach_current_user(data) - return self.post(url='/provider-details/{}'.format(provider_id), data=data) + return self.post(url="/provider-details/{}".format(provider_id), data=data) provider_client = ProviderClient() diff --git a/app/notify_client/service_api_client.py b/app/notify_client/service_api_client.py index ac1823bb8..3c29111df 100644 --- a/app/notify_client/service_api_client.py +++ b/app/notify_client/service_api_client.py @@ -7,7 +7,7 @@ from app.notify_client import NotifyAdminAPIClient, _attach_current_user, cache class ServiceAPIClient(NotifyAdminAPIClient): - @cache.delete('user-{user_id}') + @cache.delete("user-{user_id}") def create_service( self, service_name, @@ -30,97 +30,100 @@ class ServiceAPIClient(NotifyAdminAPIClient): "email_from": email_from, } data = _attach_current_user(data) - return self.post("/service", data)['data']['id'] + return self.post("/service", data)["data"]["id"] - @cache.set('service-{service_id}') + @cache.set("service-{service_id}") def get_service(self, service_id): """ Retrieve a service. """ - return self.get('/service/{0}'.format(service_id)) + return self.get("/service/{0}".format(service_id)) def get_service_statistics(self, service_id, limit_days=None): - return self.get('/service/{0}/statistics'.format(service_id), params={'limit_days': limit_days})['data'] + return self.get( + "/service/{0}/statistics".format(service_id), + params={"limit_days": limit_days}, + )["data"] def get_services(self, params_dict=None): """ Retrieve a list of services. """ - return self.get('/service', params=params_dict) + return self.get("/service", params=params_dict) def find_services_by_name(self, service_name): - return self.get('/service/find-services-by-name', params={"service_name": service_name}) + return self.get( + "/service/find-services-by-name", params={"service_name": service_name} + ) def get_live_services_data(self, params_dict=None): """ Retrieve a list of live services data with contact names and notification counts. """ - return self.get('/service/live-services-data', params=params_dict) + return self.get("/service/live-services-data", params=params_dict) def get_active_services(self, params_dict=None): """ Retrieve a list of active services. """ - params_dict['only_active'] = True + params_dict["only_active"] = True return self.get_services(params_dict) - @cache.delete('service-{service_id}') - def update_service( - self, - service_id, - **kwargs - ): + @cache.delete("service-{service_id}") + def update_service(self, service_id, **kwargs): """ Update a service. """ data = _attach_current_user(kwargs) disallowed_attributes = set(data.keys()) - { - 'active', - 'billing_contact_email_addresses', - 'billing_contact_names', - 'billing_reference', - 'consent_to_research', - 'contact_link', - 'created_by', - 'count_as_live', - 'email_branding', - 'email_from', - 'free_sms_fragment_limit', - 'go_live_at', - 'go_live_user', - 'message_limit', - 'name', - 'notes', - 'organization_type', - 'permissions', - 'prefix_sms', - 'purchase_order_number', - 'rate_limit', - 'reply_to_email_address', - 'research_mode', - 'restricted', - 'sms_sender', - 'volume_email', - 'volume_sms', + "active", + "billing_contact_email_addresses", + "billing_contact_names", + "billing_reference", + "consent_to_research", + "contact_link", + "created_by", + "count_as_live", + "email_branding", + "email_from", + "free_sms_fragment_limit", + "go_live_at", + "go_live_user", + "message_limit", + "name", + "notes", + "organization_type", + "permissions", + "prefix_sms", + "purchase_order_number", + "rate_limit", + "reply_to_email_address", + "research_mode", + "restricted", + "sms_sender", + "volume_email", + "volume_sms", } if disallowed_attributes: - raise TypeError('Not allowed to update service attributes: {}'.format( - ", ".join(disallowed_attributes) - )) + raise TypeError( + "Not allowed to update service attributes: {}".format( + ", ".join(disallowed_attributes) + ) + ) endpoint = "/service/{0}".format(service_id) return self.post(endpoint, data) - @cache.delete('live-service-and-organization-counts') + @cache.delete("live-service-and-organization-counts") def update_status(self, service_id, live): return self.update_service( service_id, message_limit=250000 if live else 50, restricted=(not live), - go_live_at=str(datetime.utcnow()) if live else None + go_live_at=str(datetime.utcnow()) if live else None, ) - @cache.delete('live-service-and-organization-counts') + @cache.delete("live-service-and-organization-counts") def update_count_as_live(self, service_id, count_as_live): return self.update_service( service_id, @@ -131,37 +134,38 @@ class ServiceAPIClient(NotifyAdminAPIClient): def update_service_with_properties(self, service_id, properties): return self.update_service(service_id, **properties) - @cache.delete('service-{service_id}') - @cache.delete('service-{service_id}-templates') - @cache.delete_by_pattern('service-{service_id}-template-*') + @cache.delete("service-{service_id}") + @cache.delete("service-{service_id}-templates") + @cache.delete_by_pattern("service-{service_id}-template-*") def archive_service(self, service_id, cached_service_user_ids): if cached_service_user_ids: - redis_client.delete(*map('user-{}'.format, cached_service_user_ids)) - return self.post('/service/{}/archive'.format(service_id), data=None) + redis_client.delete(*map("user-{}".format, cached_service_user_ids)) + return self.post("/service/{}/archive".format(service_id), data=None) - @cache.delete('service-{service_id}') + @cache.delete("service-{service_id}") def suspend_service(self, service_id): - return self.post('/service/{}/suspend'.format(service_id), data=None) + return self.post("/service/{}/suspend".format(service_id), data=None) - @cache.delete('service-{service_id}') + @cache.delete("service-{service_id}") def resume_service(self, service_id): - return self.post('/service/{}/resume'.format(service_id), data=None) + return self.post("/service/{}/resume".format(service_id), data=None) - @cache.delete('service-{service_id}') - @cache.delete('user-{user_id}') + @cache.delete("service-{service_id}") + @cache.delete("user-{user_id}") def remove_user_from_service(self, service_id, user_id): """ Remove a user from a service """ - endpoint = '/service/{service_id}/users/{user_id}'.format( - service_id=service_id, - user_id=user_id) + endpoint = "/service/{service_id}/users/{user_id}".format( + service_id=service_id, user_id=user_id + ) data = _attach_current_user({}) return self.delete(endpoint, data) - @cache.delete('service-{service_id}-templates') - def create_service_template(self, name, type_, content, service_id, subject=None, - parent_folder_id=None): + @cache.delete("service-{service_id}-templates") + def create_service_template( + self, name, type_, content, service_id, subject=None, parent_folder_id=None + ): """ Create a service template. """ @@ -170,22 +174,18 @@ class ServiceAPIClient(NotifyAdminAPIClient): "template_type": type_, "content": content, "service": service_id, - "process_type": 'normal', + "process_type": "normal", } if subject: - data.update({ - 'subject': subject - }) + data.update({"subject": subject}) if parent_folder_id: - data.update({ - 'parent_folder_id': parent_folder_id - }) + data.update({"parent_folder_id": parent_folder_id}) data = _attach_current_user(data) endpoint = "/service/{0}/template".format(service_id) return self.post(endpoint, data) - @cache.delete('service-{service_id}-templates') - @cache.delete_by_pattern('service-{service_id}-template-*') + @cache.delete("service-{service_id}-templates") + @cache.delete_by_pattern("service-{service_id}-template-*") def update_service_template( self, id_, name, type_, content, service_id, subject=None ): @@ -193,65 +193,57 @@ class ServiceAPIClient(NotifyAdminAPIClient): Update a service template. """ data = { - 'id': id_, - 'name': name, - 'template_type': type_, - 'content': content, - 'service': service_id + "id": id_, + "name": name, + "template_type": type_, + "content": content, + "service": service_id, } if subject: - data.update({ - 'subject': subject - }) - data.update({ - 'process_type': 'normal' - }) + data.update({"subject": subject}) + data.update({"process_type": "normal"}) data = _attach_current_user(data) endpoint = "/service/{0}/template/{1}".format(service_id, id_) return self.post(endpoint, data) - @cache.delete('service-{service_id}-templates') - @cache.delete_by_pattern('service-{service_id}-template-*') + @cache.delete("service-{service_id}-templates") + @cache.delete_by_pattern("service-{service_id}-template-*") def redact_service_template(self, service_id, id_): return self.post( "/service/{}/template/{}".format(service_id, id_), - _attach_current_user( - {'redact_personalisation': True} - ), + _attach_current_user({"redact_personalisation": True}), ) - @cache.delete('service-{service_id}-templates') - @cache.delete_by_pattern('service-{service_id}-template-*') + @cache.delete("service-{service_id}-templates") + @cache.delete_by_pattern("service-{service_id}-template-*") def update_service_template_sender(self, service_id, template_id, reply_to): data = { - 'reply_to': reply_to, + "reply_to": reply_to, } data = _attach_current_user(data) return self.post( - "/service/{0}/template/{1}".format(service_id, template_id), - data + "/service/{0}/template/{1}".format(service_id, template_id), data ) - @cache.set('service-{service_id}-template-{template_id}-version-{version}') + @cache.set("service-{service_id}-template-{template_id}-version-{version}") def get_service_template(self, service_id, template_id, version=None): """ Retrieve a service template. """ - endpoint = '/service/{service_id}/template/{template_id}'.format( - service_id=service_id, - template_id=template_id) + endpoint = "/service/{service_id}/template/{template_id}".format( + service_id=service_id, template_id=template_id + ) if version: - endpoint = '{base}/version/{version}'.format(base=endpoint, version=version) + endpoint = "{base}/version/{version}".format(base=endpoint, version=version) return self.get(endpoint) - @cache.set('service-{service_id}-template-{template_id}-versions') + @cache.set("service-{service_id}-template-{template_id}-versions") def get_service_template_versions(self, service_id, template_id): """ Retrieve a list of versions for a template """ - endpoint = '/service/{service_id}/template/{template_id}/versions'.format( - service_id=service_id, - template_id=template_id + endpoint = "/service/{service_id}/template/{template_id}/versions".format( + service_id=service_id, template_id=template_id ) return self.get(endpoint) @@ -259,268 +251,244 @@ class ServiceAPIClient(NotifyAdminAPIClient): """ Returns the precompiled template for a service, creating it if it doesn't already exist """ - return self.get('/service/{}/template/precompiled'.format(service_id)) + return self.get("/service/{}/template/precompiled".format(service_id)) - @cache.set('service-{service_id}-templates') + @cache.set("service-{service_id}-templates") def get_service_templates(self, service_id): """ Retrieve all templates for service. """ - endpoint = '/service/{service_id}/template?detailed=False'.format( - service_id=service_id) + endpoint = "/service/{service_id}/template?detailed=False".format( + service_id=service_id + ) return self.get(endpoint) # This doesn’t need caching because it calls through to a method which is cached def count_service_templates(self, service_id, template_type=None): - return len([ - template for template in - self.get_service_templates(service_id)['data'] - if ( - not template_type - or template['template_type'] == template_type - ) - ]) + return len( + [ + template + for template in self.get_service_templates(service_id)["data"] + if (not template_type or template["template_type"] == template_type) + ] + ) - @cache.delete('service-{service_id}-templates') - @cache.delete_by_pattern('service-{service_id}-template-*') + @cache.delete("service-{service_id}-templates") + @cache.delete_by_pattern("service-{service_id}-template-*") def delete_service_template(self, service_id, template_id): """ Set a service template's archived flag to True """ endpoint = "/service/{0}/template/{1}".format(service_id, template_id) - data = { - 'archived': True - } + data = {"archived": True} data = _attach_current_user(data) return self.post(endpoint, data=data) # Temp access of service history data. Includes service and api key history def get_service_history(self, service_id): - return self.get('/service/{0}/history'.format(service_id))['data'] + return self.get("/service/{0}/history".format(service_id))["data"] def get_service_service_history(self, service_id): - return self.get_service_history(service_id)['service_history'] + return self.get_service_history(service_id)["service_history"] def get_service_api_key_history(self, service_id): - return self.get_service_history(service_id)['api_key_history'] + return self.get_service_history(service_id)["api_key_history"] def get_monthly_notification_stats(self, service_id, year): - return self.get(url='/service/{}/notifications/monthly?year={}'.format(service_id, year)) + return self.get( + url="/service/{}/notifications/monthly?year={}".format(service_id, year) + ) def get_guest_list(self, service_id): - return self.get(url='/service/{}/guest-list'.format(service_id)) + return self.get(url="/service/{}/guest-list".format(service_id)) - @cache.delete('service-{service_id}') + @cache.delete("service-{service_id}") def update_guest_list(self, service_id, data): - return self.put(url='/service/{}/guest-list'.format(service_id), data=data) + return self.put(url="/service/{}/guest-list".format(service_id), data=data) - def get_inbound_sms(self, service_id, user_number=''): + def get_inbound_sms(self, service_id, user_number=""): # POST prevents the user phone number leaking into our logs return self.post( - '/service/{}/inbound-sms'.format( + "/service/{}/inbound-sms".format( service_id, ), - data={'phone_number': user_number} + data={"phone_number": user_number}, ) def get_most_recent_inbound_sms(self, service_id, page=None): return self.get( - '/service/{}/inbound-sms/most-recent'.format( + "/service/{}/inbound-sms/most-recent".format( service_id, ), - params={ - 'page': page - } + params={"page": page}, ) def get_inbound_sms_by_id(self, service_id, notification_id): return self.get( - '/service/{}/inbound-sms/{}'.format( + "/service/{}/inbound-sms/{}".format( service_id, notification_id, ) ) def get_inbound_sms_summary(self, service_id): - return self.get( - '/service/{}/inbound-sms/summary'.format(service_id) - ) + return self.get("/service/{}/inbound-sms/summary".format(service_id)) - @cache.delete('service-{service_id}') + @cache.delete("service-{service_id}") def create_service_inbound_api(self, service_id, url, bearer_token, user_id): - data = { - "url": url, - "bearer_token": bearer_token, - "updated_by_id": user_id - } + data = {"url": url, "bearer_token": bearer_token, "updated_by_id": user_id} return self.post("/service/{}/inbound-api".format(service_id), data) - @cache.delete('service-{service_id}') - def update_service_inbound_api(self, service_id, url, bearer_token, user_id, inbound_api_id): - data = { - "url": url, - "updated_by_id": user_id - } + @cache.delete("service-{service_id}") + def update_service_inbound_api( + self, service_id, url, bearer_token, user_id, inbound_api_id + ): + data = {"url": url, "updated_by_id": user_id} if bearer_token: - data['bearer_token'] = bearer_token - return self.post("/service/{}/inbound-api/{}".format(service_id, inbound_api_id), data) + data["bearer_token"] = bearer_token + return self.post( + "/service/{}/inbound-api/{}".format(service_id, inbound_api_id), data + ) def get_service_inbound_api(self, service_id, inbound_sms_api_id): return self.get( - "/service/{}/inbound-api/{}".format( - service_id, inbound_sms_api_id - ) - )['data'] + "/service/{}/inbound-api/{}".format(service_id, inbound_sms_api_id) + )["data"] - @cache.delete('service-{service_id}') + @cache.delete("service-{service_id}") def delete_service_inbound_api(self, service_id, callback_api_id): - return self.delete("/service/{}/inbound-api/{}".format( - service_id, callback_api_id - )) + return self.delete( + "/service/{}/inbound-api/{}".format(service_id, callback_api_id) + ) def get_reply_to_email_addresses(self, service_id): - return self.get( - "/service/{}/email-reply-to".format( - service_id - ) - ) + return self.get("/service/{}/email-reply-to".format(service_id)) def get_reply_to_email_address(self, service_id, reply_to_email_id): return self.get( - "/service/{}/email-reply-to/{}".format( - service_id, - reply_to_email_id - ) + "/service/{}/email-reply-to/{}".format(service_id, reply_to_email_id) ) def verify_reply_to_email_address(self, service_id, email_address): return self.post( "/service/{}/email-reply-to/verify".format(service_id), - data={"email": email_address} + data={"email": email_address}, ) - @cache.delete('service-{service_id}') - @cache.delete_by_pattern('service-{service_id}-template-*') + @cache.delete("service-{service_id}") + @cache.delete_by_pattern("service-{service_id}-template-*") def add_reply_to_email_address(self, service_id, email_address, is_default=False): return self.post( "/service/{}/email-reply-to".format(service_id), - data={ - "email_address": email_address, - "is_default": is_default - } + data={"email_address": email_address, "is_default": is_default}, ) - @cache.delete('service-{service_id}') - @cache.delete_by_pattern('service-{service_id}-template-*') - def update_reply_to_email_address(self, service_id, reply_to_email_id, email_address, is_default=False): + @cache.delete("service-{service_id}") + @cache.delete_by_pattern("service-{service_id}-template-*") + def update_reply_to_email_address( + self, service_id, reply_to_email_id, email_address, is_default=False + ): return self.post( "/service/{}/email-reply-to/{}".format( service_id, reply_to_email_id, ), - data={ - "email_address": email_address, - "is_default": is_default - } + data={"email_address": email_address, "is_default": is_default}, ) - @cache.delete('service-{service_id}') - @cache.delete_by_pattern('service-{service_id}-template-*') + @cache.delete("service-{service_id}") + @cache.delete_by_pattern("service-{service_id}-template-*") def delete_reply_to_email_address(self, service_id, reply_to_email_id): return self.post( - "/service/{}/email-reply-to/{}/archive".format(service_id, reply_to_email_id), - data=None + "/service/{}/email-reply-to/{}/archive".format( + service_id, reply_to_email_id + ), + data=None, ) def get_sms_senders(self, service_id): - return self.get( - "/service/{}/sms-sender".format(service_id) - ) + return self.get("/service/{}/sms-sender".format(service_id)) def get_sms_sender(self, service_id, sms_sender_id): - return self.get( - "/service/{}/sms-sender/{}".format(service_id, sms_sender_id) - ) + return self.get("/service/{}/sms-sender/{}".format(service_id, sms_sender_id)) - @cache.delete('service-{service_id}') - @cache.delete_by_pattern('service-{service_id}-template-*') - def add_sms_sender(self, service_id, sms_sender, is_default=False, inbound_number_id=None): - data = { - "sms_sender": sms_sender, - "is_default": is_default - } + @cache.delete("service-{service_id}") + @cache.delete_by_pattern("service-{service_id}-template-*") + def add_sms_sender( + self, service_id, sms_sender, is_default=False, inbound_number_id=None + ): + data = {"sms_sender": sms_sender, "is_default": is_default} if inbound_number_id: data["inbound_number_id"] = inbound_number_id return self.post("/service/{}/sms-sender".format(service_id), data=data) - @cache.delete('service-{service_id}') - @cache.delete_by_pattern('service-{service_id}-template-*') - def update_sms_sender(self, service_id, sms_sender_id, sms_sender, is_default=False): + @cache.delete("service-{service_id}") + @cache.delete_by_pattern("service-{service_id}-template-*") + def update_sms_sender( + self, service_id, sms_sender_id, sms_sender, is_default=False + ): return self.post( "/service/{}/sms-sender/{}".format(service_id, sms_sender_id), - data={ - "sms_sender": sms_sender, - "is_default": is_default - } + data={"sms_sender": sms_sender, "is_default": is_default}, ) - @cache.delete('service-{service_id}') - @cache.delete_by_pattern('service-{service_id}-template-*') + @cache.delete("service-{service_id}") + @cache.delete_by_pattern("service-{service_id}-template-*") def delete_sms_sender(self, service_id, sms_sender_id): return self.post( "/service/{}/sms-sender/{}/archive".format(service_id, sms_sender_id), - data=None + data=None, ) def get_service_callback_api(self, service_id, callback_api_id): return self.get( - "/service/{}/delivery-receipt-api/{}".format( - service_id, callback_api_id - ) - )['data'] + "/service/{}/delivery-receipt-api/{}".format(service_id, callback_api_id) + )["data"] - @cache.delete('service-{service_id}') - def update_service_callback_api(self, service_id, url, bearer_token, user_id, callback_api_id): - data = { - "url": url, - "updated_by_id": user_id - } + @cache.delete("service-{service_id}") + def update_service_callback_api( + self, service_id, url, bearer_token, user_id, callback_api_id + ): + data = {"url": url, "updated_by_id": user_id} if bearer_token: - data['bearer_token'] = bearer_token - return self.post("/service/{}/delivery-receipt-api/{}".format(service_id, callback_api_id), data) + data["bearer_token"] = bearer_token + return self.post( + "/service/{}/delivery-receipt-api/{}".format(service_id, callback_api_id), + data, + ) - @cache.delete('service-{service_id}') + @cache.delete("service-{service_id}") def delete_service_callback_api(self, service_id, callback_api_id): - return self.delete("/service/{}/delivery-receipt-api/{}".format( - service_id, callback_api_id - )) + return self.delete( + "/service/{}/delivery-receipt-api/{}".format(service_id, callback_api_id) + ) - @cache.delete('service-{service_id}') + @cache.delete("service-{service_id}") def create_service_callback_api(self, service_id, url, bearer_token, user_id): - data = { - "url": url, - "bearer_token": bearer_token, - "updated_by_id": user_id - } + data = {"url": url, "bearer_token": bearer_token, "updated_by_id": user_id} return self.post("/service/{}/delivery-receipt-api".format(service_id), data) - @cache.delete('service-{service_id}-data-retention') - def create_service_data_retention(self, service_id, notification_type, days_of_retention): + @cache.delete("service-{service_id}-data-retention") + def create_service_data_retention( + self, service_id, notification_type, days_of_retention + ): data = { "notification_type": notification_type, - "days_of_retention": days_of_retention + "days_of_retention": days_of_retention, } return self.post("/service/{}/data-retention".format(service_id), data) - @cache.delete('service-{service_id}-data-retention') - def update_service_data_retention(self, service_id, data_retention_id, days_of_retention): - data = { - "days_of_retention": days_of_retention - } - return self.post("/service/{}/data-retention/{}".format(service_id, data_retention_id), data) + @cache.delete("service-{service_id}-data-retention") + def update_service_data_retention( + self, service_id, data_retention_id, days_of_retention + ): + data = {"days_of_retention": days_of_retention} + return self.post( + "/service/{}/data-retention/{}".format(service_id, data_retention_id), data + ) - @cache.set('service-{service_id}-data-retention') + @cache.set("service-{service_id}-data-retention") def get_service_data_retention(self, service_id): return self.get("/service/{}/data-retention".format(service_id)) diff --git a/app/notify_client/status_api_client.py b/app/notify_client/status_api_client.py index 910ff0181..d1a0cffcd 100644 --- a/app/notify_client/status_api_client.py +++ b/app/notify_client/status_api_client.py @@ -2,17 +2,16 @@ from app.notify_client import NotifyAdminAPIClient, cache class StatusApiClient(NotifyAdminAPIClient): - def get_status(self, *params): - return self.get(url='/_status', *params) + return self.get(url="/_status", *params) - @cache.set('live-service-and-organization-counts', ttl_in_seconds=3600) + @cache.set("live-service-and-organization-counts", ttl_in_seconds=3600) def get_count_of_live_services_and_organizations(self): - return self.get(url='/_status/live-service-and-organization-counts') + return self.get(url="/_status/live-service-and-organization-counts") - @cache.set('live-service-and-organization-counts', ttl_in_seconds=3600) + @cache.set("live-service-and-organization-counts", ttl_in_seconds=3600) def get_count_of_live_services_and_organizations_cached(self): - return self.get(url='/_status/live-service-and-organization-counts') + return self.get(url="/_status/live-service-and-organization-counts") status_api_client = StatusApiClient() diff --git a/app/notify_client/template_folder_api_client.py b/app/notify_client/template_folder_api_client.py index 005e82898..bdb10bea2 100644 --- a/app/notify_client/template_folder_api_client.py +++ b/app/notify_client/template_folder_api_client.py @@ -3,67 +3,76 @@ from app.notify_client import NotifyAdminAPIClient, cache class TemplateFolderAPIClient(NotifyAdminAPIClient): + @cache.delete("service-{service_id}-template-folders") + def create_template_folder(self, service_id, name, parent_id=None): + data = {"name": name, "parent_id": parent_id} + return self.post("/service/{}/template-folder".format(service_id), data)[ + "data" + ]["id"] - @cache.delete('service-{service_id}-template-folders') - def create_template_folder( - self, - service_id, - name, - parent_id=None - ): - data = { - 'name': name, - 'parent_id': parent_id - } - return self.post('/service/{}/template-folder'.format(service_id), data)['data']['id'] - - @cache.set('service-{service_id}-template-folders') + @cache.set("service-{service_id}-template-folders") def get_template_folders(self, service_id): - return self.get('/service/{}/template-folder'.format(service_id))['template_folders'] + return self.get("/service/{}/template-folder".format(service_id))[ + "template_folders" + ] def get_template_folder(self, service_id, folder_id): if folder_id is None: return { - 'id': None, - 'name': 'Templates', - 'parent_id': None, + "id": None, + "name": "Templates", + "parent_id": None, } else: return next( - folder for folder in self.get_template_folders(service_id) - if folder['id'] == str(folder_id) + folder + for folder in self.get_template_folders(service_id) + if folder["id"] == str(folder_id) ) - @cache.delete('service-{service_id}-template-folders') - @cache.delete('service-{service_id}-templates') + @cache.delete("service-{service_id}-template-folders") + @cache.delete("service-{service_id}-templates") def move_to_folder(self, service_id, folder_id, template_ids, folder_ids): - if folder_id: - url = '/service/{}/template-folder/{}/contents'.format(service_id, folder_id) + url = "/service/{}/template-folder/{}/contents".format( + service_id, folder_id + ) else: - url = '/service/{}/template-folder/contents'.format(service_id) + url = "/service/{}/template-folder/contents".format(service_id) - self.post(url, { - 'templates': list(template_ids), - 'folders': list(folder_ids), - }) + self.post( + url, + { + "templates": list(template_ids), + "folders": list(folder_ids), + }, + ) if template_ids: - redis_client.delete(*(f'service-{service_id}-template-{id}-version-None' for id in template_ids)) + redis_client.delete( + *( + f"service-{service_id}-template-{id}-version-None" + for id in template_ids + ) + ) - @cache.delete('service-{service_id}-template-folders') - def update_template_folder(self, service_id, template_folder_id, name, users_with_permission=None): + @cache.delete("service-{service_id}-template-folders") + def update_template_folder( + self, service_id, template_folder_id, name, users_with_permission=None + ): data = {"name": name} if users_with_permission: data["users_with_permission"] = users_with_permission self.post( - '/service/{}/template-folder/{}'.format(service_id, template_folder_id), - data + "/service/{}/template-folder/{}".format(service_id, template_folder_id), + data, ) - @cache.delete('service-{service_id}-template-folders') + @cache.delete("service-{service_id}-template-folders") def delete_template_folder(self, service_id, template_folder_id): - self.delete('/service/{}/template-folder/{}'.format(service_id, template_folder_id), {}) + self.delete( + "/service/{}/template-folder/{}".format(service_id, template_folder_id), {} + ) template_folder_api_client = TemplateFolderAPIClient() diff --git a/app/notify_client/template_statistics_api_client.py b/app/notify_client/template_statistics_api_client.py index 4973a679b..106da1c96 100644 --- a/app/notify_client/template_statistics_api_client.py +++ b/app/notify_client/template_statistics_api_client.py @@ -2,27 +2,28 @@ from app.notify_client import NotifyAdminAPIClient class TemplateStatisticsApiClient(NotifyAdminAPIClient): - def get_template_statistics_for_service(self, service_id, limit_days=None): params = {} if limit_days is not None: - params['limit_days'] = limit_days + params["limit_days"] = limit_days return self.get( - url='/service/{}/template-statistics'.format(service_id), - params=params - )['data'] + url="/service/{}/template-statistics".format(service_id), params=params + )["data"] def get_monthly_template_usage_for_service(self, service_id, year): - return self.get( - url='/service/{}/notifications/templates_usage/monthly?year={}'.format(service_id, year) - )['stats'] + url="/service/{}/notifications/templates_usage/monthly?year={}".format( + service_id, year + ) + )["stats"] def get_last_used_date_for_template(self, service_id, template_id): return self.get( - url='/service/{}/template-statistics/last-used/{}'.format(service_id, template_id) - )['last_date_used'] + url="/service/{}/template-statistics/last-used/{}".format( + service_id, template_id + ) + )["last_date_used"] template_statistics_client = TemplateStatisticsApiClient() diff --git a/app/notify_client/user_api_client.py b/app/notify_client/user_api_client.py index 326882f1c..1866b8d8d 100644 --- a/app/notify_client/user_api_client.py +++ b/app/notify_client/user_api_client.py @@ -5,21 +5,20 @@ from app.notify_client import NotifyAdminAPIClient, cache from app.utils.user_permissions import translate_permissions_from_ui_to_db ALLOWED_ATTRIBUTES = { - 'name', - 'email_address', - 'mobile_number', - 'auth_type', - 'updated_by', - 'current_session_id', - 'email_access_validated_at', + "name", + "email_address", + "mobile_number", + "auth_type", + "updated_by", + "current_session_id", + "email_access_validated_at", } class UserApiClient(NotifyAdminAPIClient): - def init_app(self, app): super().init_app(app) - self.admin_url = app.config['ADMIN_BASE_URL'] + self.admin_url = app.config["ADMIN_BASE_URL"] def register_user(self, name, email_address, mobile_number, password, auth_type): data = { @@ -27,21 +26,21 @@ class UserApiClient(NotifyAdminAPIClient): "email_address": email_address, "mobile_number": mobile_number, "password": password, - "auth_type": auth_type + "auth_type": auth_type, } user_data = self.post("/user", data) - return user_data['data'] + return user_data["data"] def get_user(self, user_id): - return self._get_user(user_id)['data'] + return self._get_user(user_id)["data"] - @cache.set('user-{user_id}') + @cache.set("user-{user_id}") def _get_user(self, user_id): return self.get("/user/{}".format(user_id)) def get_user_by_email(self, email_address): - user_data = self.post('/user/email', data={'email': email_address}) - return user_data['data'] + user_data = self.post("/user/email", data={"email": email_address}) + return user_data["data"] def get_user_by_email_or_none(self, email_address): try: @@ -51,37 +50,39 @@ class UserApiClient(NotifyAdminAPIClient): return None raise e - @cache.delete('user-{user_id}') + @cache.delete("user-{user_id}") def update_user_attribute(self, user_id, **kwargs): data = dict(kwargs) disallowed_attributes = set(data.keys()) - ALLOWED_ATTRIBUTES if disallowed_attributes: - raise TypeError('Not allowed to update user attributes: {}'.format( - ", ".join(disallowed_attributes) - )) + raise TypeError( + "Not allowed to update user attributes: {}".format( + ", ".join(disallowed_attributes) + ) + ) url = "/user/{}".format(user_id) user_data = self.post(url, data=data) - return user_data['data'] + return user_data["data"] - @cache.delete('user-{user_id}') + @cache.delete("user-{user_id}") def archive_user(self, user_id): - return self.post('/user/{}/archive'.format(user_id), data=None) + return self.post("/user/{}/archive".format(user_id), data=None) - @cache.delete('user-{user_id}') + @cache.delete("user-{user_id}") def reset_failed_login_count(self, user_id): url = "/user/{}/reset-failed-login-count".format(user_id) user_data = self.post(url, data={}) - return user_data['data'] + return user_data["data"] - @cache.delete('user-{user_id}') + @cache.delete("user-{user_id}") def update_password(self, user_id, password): data = {"_password": password} url = "/user/{}/update-password".format(user_id) user_data = self.post(url, data=data) - return user_data['data'] + return user_data["data"] - @cache.delete('user-{user_id}') + @cache.delete("user-{user_id}") def verify_password(self, user_id, password): try: current_app.logger.warn(f"Checking password for {user_id}") @@ -96,36 +97,36 @@ class UserApiClient(NotifyAdminAPIClient): raise def send_verify_code(self, user_id, code_type, to, next_string=None): - data = {'to': to} + data = {"to": to} if next_string: - data['next'] = next_string - if code_type == 'email': - data['email_auth_link_host'] = self.admin_url - endpoint = '/user/{0}/{1}-code'.format(user_id, code_type) + data["next"] = next_string + if code_type == "email": + data["email_auth_link_host"] = self.admin_url + endpoint = "/user/{0}/{1}-code".format(user_id, code_type) current_app.logger.warn(f"Sending verify_code {code_type} to {user_id}") self.post(endpoint, data=data) def send_verify_email(self, user_id, to): data = { - 'to': to, - 'admin_base_url': self.admin_url, + "to": to, + "admin_base_url": self.admin_url, } - endpoint = '/user/{0}/email-verification'.format(user_id) + endpoint = "/user/{0}/email-verification".format(user_id) self.post(endpoint, data=data) def send_already_registered_email(self, user_id, to): - data = {'email': to} - endpoint = '/user/{0}/email-already-registered'.format(user_id) + data = {"email": to} + endpoint = "/user/{0}/email-already-registered".format(user_id) self.post(endpoint, data=data) - @cache.delete('user-{user_id}') + @cache.delete("user-{user_id}") def check_verify_code(self, user_id, code, code_type): - data = {'code_type': code_type, 'code': code} - endpoint = '/user/{}/verify/code'.format(user_id) + data = {"code_type": code_type, "code": code} + endpoint = "/user/{}/verify/code".format(user_id) try: current_app.logger.warn(f"Checking verify code for {user_id}") self.post(endpoint, data=data) - return True, '' + return True, "" except HTTPError as e: if e.status_code == 400 or e.status_code == 404: current_app.logger.error(f"Verify code for {user_id} was invalid") @@ -133,76 +134,84 @@ class UserApiClient(NotifyAdminAPIClient): raise def get_users_for_service(self, service_id): - endpoint = '/service/{}/users'.format(service_id) - return self.get(endpoint)['data'] + endpoint = "/service/{}/users".format(service_id) + return self.get(endpoint)["data"] def get_users_for_organization(self, org_id): - endpoint = '/organizations/{}/users'.format(org_id) - return self.get(endpoint)['data'] + endpoint = "/organizations/{}/users".format(org_id) + return self.get(endpoint)["data"] def get_all_users(self): - endpoint = '/user' - return self.get(endpoint)['data'] + endpoint = "/user" + return self.get(endpoint)["data"] - @cache.delete('service-{service_id}') - @cache.delete('service-{service_id}-template-folders') - @cache.delete('user-{user_id}') + @cache.delete("service-{service_id}") + @cache.delete("service-{service_id}-template-folders") + @cache.delete("user-{user_id}") def add_user_to_service(self, service_id, user_id, permissions, folder_permissions): # permissions passed in are the combined UI permissions, not DB permissions - endpoint = '/service/{}/users/{}'.format(service_id, user_id) + endpoint = "/service/{}/users/{}".format(service_id, user_id) data = { - 'permissions': [{'permission': x} for x in translate_permissions_from_ui_to_db(permissions)], - 'folder_permissions': folder_permissions, + "permissions": [ + {"permission": x} + for x in translate_permissions_from_ui_to_db(permissions) + ], + "folder_permissions": folder_permissions, } self.post(endpoint, data=data) - @cache.delete('user-{user_id}') + @cache.delete("user-{user_id}") def add_user_to_organization(self, org_id, user_id): - resp = self.post('/organizations/{}/users/{}'.format(org_id, user_id), data={}) - return resp['data'] + resp = self.post("/organizations/{}/users/{}".format(org_id, user_id), data={}) + return resp["data"] - @cache.delete('service-{service_id}-template-folders') - @cache.delete('user-{user_id}') - def set_user_permissions(self, user_id, service_id, permissions, folder_permissions=None): + @cache.delete("service-{service_id}-template-folders") + @cache.delete("user-{user_id}") + def set_user_permissions( + self, user_id, service_id, permissions, folder_permissions=None + ): # permissions passed in are the combined UI permissions, not DB permissions data = { - 'permissions': [{'permission': x} for x in translate_permissions_from_ui_to_db(permissions)], + "permissions": [ + {"permission": x} + for x in translate_permissions_from_ui_to_db(permissions) + ], } if folder_permissions is not None: - data['folder_permissions'] = folder_permissions + data["folder_permissions"] = folder_permissions - endpoint = '/user/{}/service/{}/permission'.format(user_id, service_id) + endpoint = "/user/{}/service/{}/permission".format(user_id, service_id) self.post(endpoint, data=data) def send_reset_password_url(self, email_address, next_string=None): - endpoint = '/user/reset-password' + endpoint = "/user/reset-password" data = { - 'email': email_address, - 'admin_base_url': self.admin_url, + "email": email_address, + "admin_base_url": self.admin_url, } if next_string: - data['next'] = next_string + data["next"] = next_string self.post(endpoint, data=data) def find_users_by_full_or_partial_email(self, email_address): - endpoint = '/user/find-users-by-email' - data = {'email': email_address} + endpoint = "/user/find-users-by-email" + data = {"email": email_address} users = self.post(endpoint, data=data) return users - @cache.delete('user-{user_id}') + @cache.delete("user-{user_id}") def activate_user(self, user_id): return self.post("/user/{}/activate".format(user_id), data=None) def send_change_email_verification(self, user_id, new_email): - endpoint = '/user/{}/change-email-verification'.format(user_id) - data = {'email': new_email} + endpoint = "/user/{}/change-email-verification".format(user_id) + data = {"email": new_email} self.post(endpoint, data) def get_organizations_and_services_for_user(self, user_id): - endpoint = '/user/{}/organizations-and-services'.format(user_id) + endpoint = "/user/{}/organizations-and-services".format(user_id) return self.get(endpoint) diff --git a/app/proxy_fix.py b/app/proxy_fix.py index 795597438..56b0a55ab 100644 --- a/app/proxy_fix.py +++ b/app/proxy_fix.py @@ -7,11 +7,9 @@ class CustomProxyFix(object): self.forwarded_proto = forwarded_proto def __call__(self, environ, start_response): - environ.update({ - "HTTP_X_FORWARDED_PROTO": self.forwarded_proto - }) + environ.update({"HTTP_X_FORWARDED_PROTO": self.forwarded_proto}) return self.app(environ, start_response) def init_app(app): - app.wsgi_app = CustomProxyFix(app.wsgi_app, app.config.get('HTTP_PROTOCOL', 'http')) + app.wsgi_app = CustomProxyFix(app.wsgi_app, app.config.get("HTTP_PROTOCOL", "http")) diff --git a/app/s3_client/__init__.py b/app/s3_client/__init__.py index 540366200..e0933b464 100644 --- a/app/s3_client/__init__.py +++ b/app/s3_client/__init__.py @@ -8,9 +8,9 @@ AWS_CLIENT_CONFIG = Config( # endpoints. See https://aws.amazon.com/compliance/fips/ for more # information. s3={ - 'addressing_style': 'virtual', + "addressing_style": "virtual", }, - use_fips_endpoint=True + use_fips_endpoint=True, ) @@ -25,38 +25,40 @@ def get_s3_object( session = Session( aws_access_key_id=access_key, aws_secret_access_key=secret_key, - region_name=region + region_name=region, ) - s3 = session.resource('s3', config=AWS_CLIENT_CONFIG) + s3 = session.resource("s3", config=AWS_CLIENT_CONFIG) obj = s3.Object(bucket_name, filename) return obj def get_s3_metadata(obj): try: - return obj.get()['Metadata'] + return obj.get()["Metadata"] except botocore.exceptions.ClientError as client_error: - current_app.logger.error(f"Unable to download s3 file {obj.bucket_name}/{obj.key}") + current_app.logger.error( + f"Unable to download s3 file {obj.bucket_name}/{obj.key}" + ) raise client_error def set_s3_metadata(obj, **kwargs): copy_from_object_result = obj.copy_from( CopySource=f"{obj.bucket_name}/{obj.key}", - ServerSideEncryption='AES256', - Metadata={ - key: str(value) for key, value in kwargs.items() - }, - MetadataDirective='REPLACE', + ServerSideEncryption="AES256", + Metadata={key: str(value) for key, value in kwargs.items()}, + MetadataDirective="REPLACE", ) return copy_from_object_result def get_s3_contents(obj): - contents = '' + contents = "" try: - contents = obj.get()['Body'].read().decode('utf-8') + contents = obj.get()["Body"].read().decode("utf-8") except botocore.exceptions.ClientError as client_error: - current_app.logger.error(f"Unable to download s3 file {obj.bucket_name}/{obj.key}") + current_app.logger.error( + f"Unable to download s3 file {obj.bucket_name}/{obj.key}" + ) raise client_error return contents diff --git a/app/s3_client/s3_csv_client.py b/app/s3_client/s3_csv_client.py index 8422a11e8..d3be10b94 100644 --- a/app/s3_client/s3_csv_client.py +++ b/app/s3_client/s3_csv_client.py @@ -10,16 +10,16 @@ from app.s3_client import ( set_s3_metadata, ) -FILE_LOCATION_STRUCTURE = 'service-{}-notify/{}.csv' +FILE_LOCATION_STRUCTURE = "service-{}-notify/{}.csv" def get_csv_location(service_id, upload_id): return ( - current_app.config['CSV_UPLOAD_BUCKET']['bucket'], + current_app.config["CSV_UPLOAD_BUCKET"]["bucket"], FILE_LOCATION_STRUCTURE.format(service_id, upload_id), - current_app.config['CSV_UPLOAD_BUCKET']['access_key_id'], - current_app.config['CSV_UPLOAD_BUCKET']['secret_access_key'], - current_app.config['CSV_UPLOAD_BUCKET']['region'], + current_app.config["CSV_UPLOAD_BUCKET"]["access_key_id"], + current_app.config["CSV_UPLOAD_BUCKET"]["secret_access_key"], + current_app.config["CSV_UPLOAD_BUCKET"]["region"], ) @@ -29,9 +29,11 @@ def get_csv_upload(service_id, upload_id): def s3upload(service_id, filedata): upload_id = str(uuid.uuid4()) - bucket_name, file_location, access_key, secret_key, region = get_csv_location(service_id, upload_id) + bucket_name, file_location, access_key, secret_key, region = get_csv_location( + service_id, upload_id + ) utils_s3upload( - filedata=filedata['data'], + filedata=filedata["data"], region=region, bucket_name=bucket_name, file_location=file_location, diff --git a/app/s3_client/s3_logo_client.py b/app/s3_client/s3_logo_client.py index dc432abec..e27218fef 100644 --- a/app/s3_client/s3_logo_client.py +++ b/app/s3_client/s3_logo_client.py @@ -6,22 +6,22 @@ from notifications_utils.s3 import s3upload as utils_s3upload from app.s3_client import get_s3_object -TEMP_TAG = 'temp-{user_id}_' -EMAIL_LOGO_LOCATION_STRUCTURE = '{temp}{unique_id}-{filename}' +TEMP_TAG = "temp-{user_id}_" +EMAIL_LOGO_LOCATION_STRUCTURE = "{temp}{unique_id}-{filename}" def get_logo_location(filename=None): return ( - bucket_creds('bucket'), + bucket_creds("bucket"), filename, - bucket_creds('access_key_id'), - bucket_creds('secret_access_key'), - bucket_creds('region'), + bucket_creds("access_key_id"), + bucket_creds("secret_access_key"), + bucket_creds("region"), ) def bucket_creds(key): - return current_app.config['LOGO_UPLOAD_BUCKET'][key] + return current_app.config["LOGO_UPLOAD_BUCKET"][key] def delete_s3_object(filename): @@ -33,38 +33,42 @@ def persist_logo(old_name, new_name): return bucket_name, filename, access_key, secret_key, region = get_logo_location(new_name) get_s3_object(bucket_name, filename, access_key, secret_key, region).copy_from( - CopySource='{}/{}'.format(bucket_name, old_name)) + CopySource="{}/{}".format(bucket_name, old_name) + ) delete_s3_object(old_name) def get_s3_objects_filter_by_prefix(prefix): - bucket_name = bucket_creds('bucket') - session = Session(aws_access_key_id=bucket_creds('access_key_id'), - aws_secret_access_key=bucket_creds('secret_access_key'), - region_name=bucket_creds('region')) - s3 = session.resource('s3') + bucket_name = bucket_creds("bucket") + session = Session( + aws_access_key_id=bucket_creds("access_key_id"), + aws_secret_access_key=bucket_creds("secret_access_key"), + region_name=bucket_creds("region"), + ) + s3 = session.resource("s3") return s3.Bucket(bucket_name).objects.filter(Prefix=prefix) def get_temp_truncated_filename(filename, user_id): - return filename[len(TEMP_TAG.format(user_id=user_id)):] + start = len(TEMP_TAG.format(user_id=user_id)) + return filename[start:] def upload_email_logo(filename, filedata, user_id): upload_file_name = EMAIL_LOGO_LOCATION_STRUCTURE.format( temp=TEMP_TAG.format(user_id=user_id), unique_id=str(uuid.uuid4()), - filename=filename + filename=filename, ) - bucket_name = bucket_creds('bucket') + bucket_name = bucket_creds("bucket") utils_s3upload( filedata=filedata, - region=bucket_creds('region'), + region=bucket_creds("region"), bucket_name=bucket_name, file_location=upload_file_name, - content_type='image/png', - access_key=bucket_creds('access_key_id'), - secret_key=bucket_creds('secret_access_key'), + content_type="image/png", + access_key=bucket_creds("access_key_id"), + secret_key=bucket_creds("secret_access_key"), ) return upload_file_name @@ -84,6 +88,6 @@ def delete_email_temp_files_created_by(user_id): def delete_email_temp_file(filename): if not filename.startswith(TEMP_TAG[:5]): - raise ValueError('Not a temp file: {}'.format(filename)) + raise ValueError("Not a temp file: {}".format(filename)) delete_s3_object(filename) diff --git a/app/statistics_utils.py b/app/statistics_utils.py index 4fda30193..953140d63 100644 --- a/app/statistics_utils.py +++ b/app/statistics_utils.py @@ -5,41 +5,35 @@ from dateutil import parser def sum_of_statistics(delivery_statistics): - statistics_keys = ( - 'emails_delivered', - 'emails_requested', - 'emails_failed', - 'sms_requested', - 'sms_delivered', - 'sms_failed' + "emails_delivered", + "emails_requested", + "emails_failed", + "sms_requested", + "sms_delivered", + "sms_failed", ) if not delivery_statistics or not delivery_statistics[0]: - return { - key: 0 for key in statistics_keys - } + return {key: 0 for key in statistics_keys} return reduce( - lambda x, y: { - key: x.get(key, 0) + y.get(key, 0) - for key in statistics_keys - }, - delivery_statistics + lambda x, y: {key: x.get(key, 0) + y.get(key, 0) for key in statistics_keys}, + delivery_statistics, ) def add_rates_to(delivery_statistics): - return dict( emails_failure_rate=get_formatted_percentage( - delivery_statistics['emails_failed'], - delivery_statistics['emails_requested']), + delivery_statistics["emails_failed"], + delivery_statistics["emails_requested"], + ), sms_failure_rate=get_formatted_percentage( - delivery_statistics['sms_failed'], - delivery_statistics['sms_requested']), + delivery_statistics["sms_failed"], delivery_statistics["sms_requested"] + ), week_end_datetime=parser.parse( - delivery_statistics.get('week_end', str(datetime.utcnow())) + delivery_statistics.get("week_end", str(datetime.utcnow())) ), **delivery_statistics ) @@ -49,32 +43,36 @@ def get_formatted_percentage(x, tot): """ Return a percentage to one decimal place (respecting ) """ - return "{0:.1f}".format((float(x) / tot * 100)) if tot else '0' + return "{0:.1f}".format((float(x) / tot * 100)) if tot else "0" def get_formatted_percentage_two_dp(x, tot): """ Return a percentage to two decimal places """ - return "{0:.2f}".format((float(x) / tot * 100)) if tot else '0' + return "{0:.2f}".format((float(x) / tot * 100)) if tot else "0" def statistics_by_state(statistics): return { - 'sms': { - 'processed': statistics['sms_requested'], - 'sending': ( - statistics['sms_requested'] - statistics['sms_failed'] - statistics['sms_delivered'] + "sms": { + "processed": statistics["sms_requested"], + "sending": ( + statistics["sms_requested"] + - statistics["sms_failed"] + - statistics["sms_delivered"] ), - 'delivered': statistics['sms_delivered'], - 'failed': statistics['sms_failed'] + "delivered": statistics["sms_delivered"], + "failed": statistics["sms_failed"], }, - 'email': { - 'processed': statistics['emails_requested'], - 'sending': ( - statistics['emails_requested'] - statistics['emails_failed'] - statistics['emails_delivered'] + "email": { + "processed": statistics["emails_requested"], + "sending": ( + statistics["emails_requested"] + - statistics["emails_failed"] + - statistics["emails_delivered"] ), - 'delivered': statistics['emails_delivered'], - 'failed': statistics['emails_failed'] - } + "delivered": statistics["emails_delivered"], + "failed": statistics["emails_failed"], + }, } diff --git a/app/status/__init__.py b/app/status/__init__.py index 5736b1267..3366b43bc 100644 --- a/app/status/__init__.py +++ b/app/status/__init__.py @@ -1,5 +1,5 @@ from flask import Blueprint -status = Blueprint('status', __name__) +status = Blueprint("status", __name__) from app.status.views import healthcheck # noqa isort:skip diff --git a/app/status/views/healthcheck.py b/app/status/views/healthcheck.py index bb84b7512..b0c11091a 100644 --- a/app/status/views/healthcheck.py +++ b/app/status/views/healthcheck.py @@ -10,9 +10,9 @@ from app.extensions import redis_client from app.status import status -@status.route('/_status', methods=['GET']) +@status.route("/_status", methods=["GET"]) def show_status(): - if request.args.get('elb', None) or request.args.get('simple', None): + if request.args.get("elb", None) or request.args.get("simple", None): return jsonify(status="ok"), 200 else: try: @@ -20,46 +20,66 @@ def show_status(): except HTTPError as err: current_app.logger.exception("API failed to respond") return jsonify(status="error", message=str(err.message)), 500 - return jsonify( - status="ok", - api=api_status, - git_commit=version.__git_commit__, - build_time=version.__time__), 200 + return ( + jsonify( + status="ok", + api=api_status, + git_commit=version.__git_commit__, + build_time=version.__time__, + ), + 200, + ) -@status.route('/_status/redis', methods=['GET']) +@status.route("/_status/redis", methods=["GET"]) def show_redis_status(): try: try: api_status = {"error": "unexpected error"} - redis_uri = current_app.config['REDIS_URL'] + redis_uri = current_app.config["REDIS_URL"] current_app.logger.info(f"config['REDIS_URL']: {redis_uri}") - redis_enabled = current_app.config['REDIS_ENABLED'] + redis_enabled = current_app.config["REDIS_ENABLED"] current_app.logger.info(f"config['REDIS_ENABLED']: {redis_enabled}") if redis_enabled: current_app.logger.info("config['REDIS_ENABLED'] evaluates as True") try: now = time.time() - redis_client.set('mytestkey', now) - val = redis_client.get('mytestkey') - current_app.logger.info(f"Retrieved value from redis for mytestkey is: {val}") + redis_client.set("mytestkey", now) + val = redis_client.get("mytestkey") + current_app.logger.info( + f"Retrieved value from redis for mytestkey is: {val}" + ) except RedisError as err: - current_app.logger.exception(f"Redis service failed to respond with {err}") + current_app.logger.exception( + f"Redis service failed to respond with {err}" + ) - api_status = status_api_client.get_count_of_live_services_and_organizations_cached() + api_status = ( + status_api_client.get_count_of_live_services_and_organizations_cached() + ) except HTTPError as err: current_app.logger.exception("API failed to respond") return jsonify(status="error", message=str(err.message)), 500 - return jsonify( - status="ok", - api=api_status, - git_commit=version.__git_commit__, - build_time=version.__time__), 200 + return ( + jsonify( + status="ok", + api=api_status, + git_commit=version.__git_commit__, + build_time=version.__time__, + ), + 200, + ) except Exception as err: - current_app.logger.exception(F"Unhandled exception: {err} - {traceback.format_exc()}") - return jsonify( - status=f"error: {err}", - api=api_status, - git_commit=version.__git_commit__, - build_time=version.__time__), 500 + current_app.logger.exception( + f"Unhandled exception: {err} - {traceback.format_exc()}" + ) + return ( + jsonify( + status=f"error: {err}", + api=api_status, + git_commit=version.__git_commit__, + build_time=version.__time__, + ), + 500, + ) diff --git a/app/url_converters.py b/app/url_converters.py index a3dd92cbc..15d3f3b73 100644 --- a/app/url_converters.py +++ b/app/url_converters.py @@ -9,14 +9,12 @@ from app.models.service import Service class TemplateTypeConverter(BaseConverter): - - regex = '(?:{})'.format('|'.join(Service.TEMPLATE_TYPES)) + regex = "(?:{})".format("|".join(Service.TEMPLATE_TYPES)) class TicketTypeConverter(BaseConverter): - - regex = f'(?:{PROBLEM_TICKET_TYPE}|{QUESTION_TICKET_TYPE}|{GENERAL_TICKET_TYPE})' + regex = f"(?:{PROBLEM_TICKET_TYPE}|{QUESTION_TICKET_TYPE}|{GENERAL_TICKET_TYPE})" class SimpleDateTypeConverter(BaseConverter): - regex = r'([12]\d{3}-(0[1-9]|1[0-2])-(0[1-9]|[12]\d|3[01]))' + regex = r"([12]\d{3}-(0[1-9]|1[0-2])-(0[1-9]|[12]\d|3[01]))" diff --git a/app/utils/__init__.py b/app/utils/__init__.py index 9409056b1..814ce150c 100644 --- a/app/utils/__init__.py +++ b/app/utils/__init__.py @@ -8,17 +8,21 @@ from orderedset._orderedset import OrderedSet from werkzeug.datastructures import MultiDict from werkzeug.routing import RequestRedirect -SENDING_STATUSES = ['created', 'pending', 'sending'] -DELIVERED_STATUSES = ['delivered', 'sent'] -FAILURE_STATUSES = ['failed', 'temporary-failure', 'permanent-failure', - 'technical-failure', 'validation-failed'] +SENDING_STATUSES = ["created", "pending", "sending"] +DELIVERED_STATUSES = ["delivered", "sent"] +FAILURE_STATUSES = [ + "failed", + "temporary-failure", + "permanent-failure", + "technical-failure", + "validation-failed", +] REQUESTED_STATUSES = SENDING_STATUSES + DELIVERED_STATUSES + FAILURE_STATUSES NOTIFICATION_TYPES = ["sms", "email"] def service_has_permission(permission): - from app import current_service def wrap(func): @@ -27,12 +31,18 @@ def service_has_permission(permission): if not current_service or not current_service.has_permission(permission): abort(403) return func(*args, **kwargs) + return wrap_func + return wrap def get_help_argument(): - return request.args.get('help') if request.args.get('help') in ('1', '2', '3') else None + return ( + request.args.get("help") + if request.args.get("help") in ("1", "2", "3") + else None + ) def parse_filter_args(filter_dict): @@ -40,43 +50,50 @@ def parse_filter_args(filter_dict): filter_dict = MultiDict(filter_dict) return MultiDict( - ( - key, - (','.join(filter_dict.getlist(key))).split(',') - ) + (key, (",".join(filter_dict.getlist(key))).split(",")) for key in filter_dict.keys() - if ''.join(filter_dict.getlist(key)) + if "".join(filter_dict.getlist(key)) ) def set_status_filters(filter_args): - status_filters = filter_args.get('status', []) - return list(OrderedSet(chain( - (status_filters or REQUESTED_STATUSES), - DELIVERED_STATUSES if 'delivered' in status_filters else [], - SENDING_STATUSES if 'sending' in status_filters else [], - FAILURE_STATUSES if 'failed' in status_filters else [] - ))) + status_filters = filter_args.get("status", []) + return list( + OrderedSet( + chain( + (status_filters or REQUESTED_STATUSES), + DELIVERED_STATUSES if "delivered" in status_filters else [], + SENDING_STATUSES if "sending" in status_filters else [], + FAILURE_STATUSES if "failed" in status_filters else [], + ) + ) + ) def unicode_truncate(s, length): - encoded = s.encode('utf-8')[:length] - return encoded.decode('utf-8', 'ignore') + encoded = s.encode("utf-8")[:length] + return encoded.decode("utf-8", "ignore") def should_skip_template_page(db_template): return ( - current_user.has_permissions('send_messages') - and not current_user.has_permissions('manage_templates', 'manage_api_keys') - and not db_template['archived'] + current_user.has_permissions("send_messages") + and not current_user.has_permissions("manage_templates", "manage_api_keys") + and not db_template["archived"] ) def get_default_sms_sender(sms_senders): - return str(next(( - Field(x['sms_sender'], html='escape') - for x in sms_senders if x['is_default'] - ), "None")) + return str( + next( + ( + Field(x["sms_sender"], html="escape") + for x in sms_senders + if x["is_default"] + ), + "None", + ) + ) class PermanentRedirect(RequestRedirect): @@ -85,6 +102,7 @@ class PermanentRedirect(RequestRedirect): 308 status codes are not supported when Internet Explorer is used with Windows 7 and Windows 8.1, so this class keeps the original status code of 301. """ + code = 301 @@ -93,8 +111,9 @@ def hide_from_search_engines(f): def decorated_function(*args, **kwargs): g.hide_from_search_engines = True response = make_response(f(*args, **kwargs)) - response.headers['X-Robots-Tag'] = 'noindex' + response.headers["X-Robots-Tag"] = "noindex" return response + return decorated_function diff --git a/app/utils/branding.py b/app/utils/branding.py index 90f991eca..dd35fea30 100644 --- a/app/utils/branding.py +++ b/app/utils/branding.py @@ -2,19 +2,22 @@ from app.models.organization import Organization def get_email_choices(service): - organization_branding_id = service.organization.email_branding_id if service.organization else None + organization_branding_id = ( + service.organization.email_branding_id if service.organization else None + ) if ( service.organization_type == Organization.TYPE_FEDERAL and service.email_branding_id is not None # GOV.UK is not current branding and organization_branding_id is None # no default to supersede it (GOV.UK) ): - yield ('govuk', 'GOV.UK') + yield ("govuk", "GOV.UK") if ( service.organization_type == Organization.TYPE_FEDERAL and service.organization and organization_branding_id is None # don't offer both if org has default - and service.email_branding_name.lower() != f'GOV.UK and {service.organization.name}'.lower() + and service.email_branding_name.lower() + != f"GOV.UK and {service.organization.name}".lower() ): - yield ('govuk_and_org', f'GOV.UK and {service.organization.name}') + yield ("govuk_and_org", f"GOV.UK and {service.organization.name}") diff --git a/app/utils/csv.py b/app/utils/csv.py index a30d47375..8f63eb4d5 100644 --- a/app/utils/csv.py +++ b/app/utils/csv.py @@ -5,17 +5,16 @@ from app.utils.templates import get_sample_template def get_errors_for_csv(recipients, template_type): - errors = [] if any(recipients.rows_with_bad_recipients): number_of_bad_recipients = len(list(recipients.rows_with_bad_recipients)) - if 'sms' == template_type: + if "sms" == template_type: if 1 == number_of_bad_recipients: errors.append("fix 1 phone number") else: errors.append("fix {} phone numbers".format(number_of_bad_recipients)) - elif 'email' == template_type: + elif "email" == template_type: if 1 == number_of_bad_recipients: errors.append("fix 1 email address") else: @@ -26,23 +25,35 @@ def get_errors_for_csv(recipients, template_type): if 1 == number_of_rows_with_missing_data: errors.append("enter missing data in 1 row") else: - errors.append("enter missing data in {} rows".format(number_of_rows_with_missing_data)) + errors.append( + "enter missing data in {} rows".format(number_of_rows_with_missing_data) + ) if any(recipients.rows_with_message_too_long): - number_of_rows_with_message_too_long = len(list(recipients.rows_with_message_too_long)) + number_of_rows_with_message_too_long = len( + list(recipients.rows_with_message_too_long) + ) if 1 == number_of_rows_with_message_too_long: errors.append("shorten the message in 1 row") else: - errors.append("shorten the messages in {} rows".format(number_of_rows_with_message_too_long)) + errors.append( + "shorten the messages in {} rows".format( + number_of_rows_with_message_too_long + ) + ) if any(recipients.rows_with_empty_message): - number_of_rows_with_empty_message = len(list(recipients.rows_with_empty_message)) + number_of_rows_with_empty_message = len( + list(recipients.rows_with_empty_message) + ) if 1 == number_of_rows_with_empty_message: errors.append("check you have content for the empty message in 1 row") else: - errors.append("check you have content for the empty messages in {} rows".format( - number_of_rows_with_empty_message - )) + errors.append( + "check you have content for the empty messages in {} rows".format( + number_of_rows_with_empty_message + ) + ) return errors @@ -50,52 +61,71 @@ def get_errors_for_csv(recipients, template_type): def generate_notifications_csv(**kwargs): from app import notification_api_client from app.s3_client.s3_csv_client import s3download - if 'page' not in kwargs: - kwargs['page'] = 1 - if kwargs.get('job_id'): - original_file_contents = s3download(kwargs['service_id'], kwargs['job_id']) + if "page" not in kwargs: + kwargs["page"] = 1 + + if kwargs.get("job_id"): + original_file_contents = s3download(kwargs["service_id"], kwargs["job_id"]) original_upload = RecipientCSV( original_file_contents, - template=get_sample_template(kwargs['template_type']), + template=get_sample_template(kwargs["template_type"]), ) original_column_headers = original_upload.column_headers - fieldnames = ['Row number'] + original_column_headers + ['Template', 'Type', 'Job', 'Status', 'Time'] + fieldnames = ( + ["Row number"] + + original_column_headers + + ["Template", "Type", "Job", "Status", "Time"] + ) else: - fieldnames = ['Recipient', 'Template', 'Type', 'Sent by', 'Job', 'Status', 'Time'] + fieldnames = [ + "Recipient", + "Template", + "Type", + "Sent by", + "Job", + "Status", + "Time", + ] - yield ','.join(fieldnames) + '\n' + yield ",".join(fieldnames) + "\n" - while kwargs['page']: - notifications_resp = notification_api_client.get_notifications_for_service(**kwargs) - for notification in notifications_resp['notifications']: - if kwargs.get('job_id'): - values = [ - notification['row_number'], - ] + [ - original_upload[notification['row_number'] - 1].get(header).data - for header in original_column_headers - ] + [ - notification['template_name'], - notification['template_type'], - notification['job_name'], - notification['status'], - notification['created_at'], - ] + while kwargs["page"]: + notifications_resp = notification_api_client.get_notifications_for_service( + **kwargs + ) + for notification in notifications_resp["notifications"]: + if kwargs.get("job_id"): + values = ( + [ + notification["row_number"], + ] + + [ + original_upload[notification["row_number"] - 1].get(header).data + for header in original_column_headers + ] + + [ + notification["template_name"], + notification["template_type"], + notification["job_name"], + notification["status"], + notification["created_at"], + ] + ) else: values = [ - notification['recipient'], - notification['template_name'], - notification['template_type'], - notification['created_by_name'] or '', - notification['job_name'] or '', - notification['status'], - notification['created_at'] + notification["recipient"], + notification["template_name"], + notification["template_type"], + notification["created_by_name"] or "", + notification["job_name"] or "", + notification["status"], + notification["created_at"], ] yield Spreadsheet.from_rows([map(str, values)]).as_csv_data - if notifications_resp['links'].get('next'): - kwargs['page'] += 1 + if notifications_resp["links"].get("next"): + kwargs["page"] += 1 else: return raise Exception("Should never reach here") diff --git a/app/utils/login.py b/app/utils/login.py index 30946dfdc..2d060ac86 100644 --- a/app/utils/login.py +++ b/app/utils/login.py @@ -9,10 +9,11 @@ from app.utils.time import is_less_than_days_ago def redirect_to_sign_in(f): @wraps(f) def wrapped(*args, **kwargs): - if 'user_details' not in session: - return redirect(url_for('main.sign_in')) + if "user_details" not in session: + return redirect(url_for("main.sign_in")) else: return f(*args, **kwargs) + return wrapped @@ -20,10 +21,10 @@ def log_in_user(user_id): try: user = User.from_id(user_id) # the user will have a new current_session_id set by the API - store it in the cookie for future requests - session['current_session_id'] = user.current_session_id + session["current_session_id"] = user.current_session_id # Check if coming from new password page - if 'password' in session.get('user_details', {}): - user.update_password(session['user_details']['password']) + if "password" in session.get("user_details", {}): + user.update_password(session["user_details"]["password"]) user.activate() user.login() finally: @@ -35,11 +36,11 @@ def log_in_user(user_id): def redirect_when_logged_in(platform_admin): - next_url = request.args.get('next') + next_url = request.args.get("next") if next_url and is_safe_redirect_url(next_url): return redirect(next_url) - return redirect(url_for('main.show_accounts_or_dashboard')) + return redirect(url_for("main.show_accounts_or_dashboard")) def email_needs_revalidating(user): @@ -49,7 +50,10 @@ def email_needs_revalidating(user): # see https://stackoverflow.com/questions/60532973/how-do-i-get-a-is-safe-url-function-to-use-with-flask-and-how-does-it-work # noqa def is_safe_redirect_url(target): from urllib.parse import urljoin, urlparse + host_url = urlparse(request.host_url) redirect_url = urlparse(urljoin(request.host_url, target)) - return redirect_url.scheme in ('http', 'https') and \ - host_url.netloc == redirect_url.netloc + return ( + redirect_url.scheme in ("http", "https") + and host_url.netloc == redirect_url.netloc + ) diff --git a/app/utils/pagination.py b/app/utils/pagination.py index b6c8bf53b..a9ed28d3c 100644 --- a/app/utils/pagination.py +++ b/app/utils/pagination.py @@ -2,9 +2,9 @@ from flask import request, url_for def get_page_from_request(): - if 'page' in request.args: + if "page" in request.args: try: - return int(request.args['page']) + return int(request.args["page"]) except ValueError: return None else: @@ -12,16 +12,20 @@ def get_page_from_request(): def generate_previous_dict(view, service_id, page, url_args=None): - return generate_previous_next_dict(view, service_id, page - 1, 'Previous page', url_args or {}) + return generate_previous_next_dict( + view, service_id, page - 1, "Previous page", url_args or {} + ) def generate_next_dict(view, service_id, page, url_args=None): - return generate_previous_next_dict(view, service_id, page + 1, 'Next page', url_args or {}) + return generate_previous_next_dict( + view, service_id, page + 1, "Next page", url_args or {} + ) def generate_previous_next_dict(view, service_id, page, title, url_args): return { - 'url': url_for(view, service_id=service_id, page=page, **url_args), - 'title': title, - 'label': 'page {}'.format(page) + "url": url_for(view, service_id=service_id, page=page, **url_args), + "title": title, + "label": "page {}".format(page), } diff --git a/app/utils/templates.py b/app/utils/templates.py index b099d0ebc..30add3c12 100644 --- a/app/utils/templates.py +++ b/app/utils/templates.py @@ -2,10 +2,12 @@ from notifications_utils.template import EmailPreviewTemplate, SMSPreviewTemplat def get_sample_template(template_type): - if template_type == 'email': - return EmailPreviewTemplate({'content': 'any', 'subject': '', 'template_type': 'email'}) - if template_type == 'sms': - return SMSPreviewTemplate({'content': 'any', 'template_type': 'sms'}) + if template_type == "email": + return EmailPreviewTemplate( + {"content": "any", "subject": "", "template_type": "email"} + ) + if template_type == "sms": + return SMSPreviewTemplate({"content": "any", "template_type": "sms"}) def get_template( @@ -16,16 +18,16 @@ def get_template( email_reply_to=None, sms_sender=None, ): - if 'email' == template['template_type']: + if "email" == template["template_type"]: return EmailPreviewTemplate( template, from_name=service.name, - from_address='{}@notifications.service.gov.uk'.format(service.email_from), + from_address="{}@notifications.service.gov.uk".format(service.email_from), show_recipient=show_recipient, redact_missing_personalisation=redact_missing_personalisation, reply_to=email_reply_to, ) - if 'sms' == template['template_type']: + if "sms" == template["template_type"]: return SMSPreviewTemplate( template, prefix=service.name, diff --git a/app/utils/time.py b/app/utils/time.py index 2bbc7f36f..4351b10aa 100644 --- a/app/utils/time.py +++ b/app/utils/time.py @@ -6,8 +6,8 @@ from dateutil import parser def get_current_financial_year(): now = datetime.now(pytz.utc) - current_month = int(now.strftime('%-m')) - current_year = int(now.strftime('%Y')) + current_month = int(now.strftime("%-m")) + current_year = int(now.strftime("%Y")) return current_year if current_month > 9 else current_year - 1 diff --git a/app/utils/user.py b/app/utils/user.py index ef42f6fc7..668fcb646 100644 --- a/app/utils/user.py +++ b/app/utils/user.py @@ -18,7 +18,9 @@ def user_has_permissions(*permissions, **permission_kwargs): if not current_user.has_permissions(*permissions, **permission_kwargs): abort(403) return func(*args, **kwargs) + return wrap_func + return wrap @@ -30,6 +32,7 @@ def user_is_gov_user(f): if not current_user.is_gov_user: abort(403) return f(*args, **kwargs) + return wrapped @@ -41,23 +44,24 @@ def user_is_platform_admin(f): if not current_user.platform_admin: abort(403) return f(*args, **kwargs) + return wrapped def is_gov_user(email_address): return _email_address_ends_with( email_address, config.Config.GOVERNMENT_EMAIL_DOMAIN_NAMES - ) or _email_address_ends_with( - email_address, organizations_client.get_domains() - ) + ) or _email_address_ends_with(email_address, organizations_client.get_domains()) def _email_address_ends_with(email_address, known_domains): return any( - email_address.lower().endswith(( - "@{}".format(known), - ".{}".format(known), - )) + email_address.lower().endswith( + ( + "@{}".format(known), + ".{}".format(known), + ) + ) for known in known_domains ) diff --git a/app/utils/user_permissions.py b/app/utils/user_permissions.py index 6d8dec6f2..0b624bce9 100644 --- a/app/utils/user_permissions.py +++ b/app/utils/user_permissions.py @@ -2,22 +2,22 @@ from itertools import chain permission_mappings = { # TODO: consider turning off email-sending permissions during SMS pilot - 'send_messages': ['send_texts', 'send_emails'], - 'manage_templates': ['manage_templates'], - 'manage_service': ['manage_users', 'manage_settings'], - 'manage_api_keys': ['manage_api_keys'], - 'view_activity': ['view_activity'], + "send_messages": ["send_texts", "send_emails"], + "manage_templates": ["manage_templates"], + "manage_service": ["manage_users", "manage_settings"], + "manage_api_keys": ["manage_api_keys"], + "view_activity": ["view_activity"], } all_ui_permissions = set(permission_mappings.keys()) all_db_permissions = set(chain(*permission_mappings.values())) permission_options = ( - ('view_activity', 'See dashboard'), - ('send_messages', 'Send messages'), - ('manage_templates', 'Add and edit templates'), - ('manage_service', 'Manage settings, team and usage'), - ('manage_api_keys', 'Manage API integration'), + ("view_activity", "See dashboard"), + ("send_messages", "Send messages"), + ("manage_templates", "Add and edit templates"), + ("manage_service", "Manage settings, team and usage"), + ("manage_api_keys", "Manage API integration"), ) @@ -31,7 +31,8 @@ def translate_permissions_from_db_to_ui(db_permissions): unknown_database_permissions = set(db_permissions) - all_db_permissions return { - ui_permission for ui_permission, db_permissions_for_ui_permission in permission_mappings.items() + ui_permission + for ui_permission, db_permissions_for_ui_permission in permission_mappings.items() if set(db_permissions_for_ui_permission) <= set(db_permissions) } | unknown_database_permissions @@ -42,6 +43,9 @@ def translate_permissions_from_ui_to_db(ui_permissions): Looks them up in the mapping, falling back to just passing through if they're not recognised. """ - return set(chain.from_iterable( - permission_mappings.get(ui_permission, [ui_permission]) for ui_permission in ui_permissions - )) + return set( + chain.from_iterable( + permission_mappings.get(ui_permission, [ui_permission]) + for ui_permission in ui_permissions + ) + ) diff --git a/application.py b/application.py index 1393b882f..6f89988ba 100644 --- a/application.py +++ b/application.py @@ -2,6 +2,6 @@ from flask import Flask from app import create_app -application = Flask('app') +application = Flask("app") create_app(application) diff --git a/get_zendesk_tickets.py b/get_zendesk_tickets.py index 708ef19d4..6d32d484d 100644 --- a/get_zendesk_tickets.py +++ b/get_zendesk_tickets.py @@ -17,24 +17,24 @@ NOTIFY_GROUP_ID = 360000036529 NOTIFY_ORG_ID = 21891972 # the account used to authenticate with. If no requester is provided, the ticket will come from this account. -NOTIFY_ZENDESK_EMAIL = 'zd-api-notify@digital.cabinet-office.gov.uk' -ZENDESK_API_KEY = os.environ.get('ZENDESK_API_KEY') +NOTIFY_ZENDESK_EMAIL = "zd-api-notify@digital.cabinet-office.gov.uk" +ZENDESK_API_KEY = os.environ.get("ZENDESK_API_KEY") def get_tickets(): - ZENDESK_TICKET_URL = 'https://govuk.zendesk.com/api/v2/search.json?query={}' - query_params = 'type:ticket group:{}'.format(NOTIFY_GROUP_ID) + ZENDESK_TICKET_URL = "https://govuk.zendesk.com/api/v2/search.json?query={}" + query_params = "type:ticket group:{}".format(NOTIFY_GROUP_ID) query_params = urllib.parse.quote(query_params) next_page = ZENDESK_TICKET_URL.format(query_params) - with open("zendesk_ticket_data.csv", 'w') as csvfile: + with open("zendesk_ticket_data.csv", "w") as csvfile: fieldnames = [ - 'Service id', - 'Ticket id', - 'Subject line', - 'Date ticket created', - 'Tags', + "Service id", + "Ticket id", + "Subject line", + "Date ticket created", + "Tags", ] writer = csv.DictWriter(csvfile, fieldnames=fieldnames) writer.writeheader() @@ -42,40 +42,45 @@ def get_tickets(): print(next_page) response = requests.get( next_page, - headers={'Content-type': 'application/json'}, - auth=( - '{}/token'.format(NOTIFY_ZENDESK_EMAIL), - ZENDESK_API_KEY - ) + headers={"Content-type": "application/json"}, + auth=("{}/token".format(NOTIFY_ZENDESK_EMAIL), ZENDESK_API_KEY), ) data = response.json() print(data) for row in data["results"]: - service_url = [x for x in row["description"].split('\n') - if x.startswith("https://www.notifications.service.gov.uk/services/")] + service_url = [ + x + for x in row["description"].split("\n") + if x.startswith( + "https://www.notifications.service.gov.uk/services/" + ) + ] service_url = service_url[0][50:] if len(service_url) > 0 else None if service_url: - writer.writerow({'Service id': service_url, - 'Ticket id': row['id'], - 'Subject line': row['subject'], - 'Date ticket created': row["created_at"], - 'Tags': row.get('tags', '') - }) + writer.writerow( + { + "Service id": service_url, + "Ticket id": row["id"], + "Subject line": row["subject"], + "Date ticket created": row["created_at"], + "Tags": row.get("tags", ""), + } + ) next_page = data["next_page"] def get_tickets_without_service_id(): - ZENDESK_TICKET_URL = 'https://govuk.zendesk.com/api/v2/search.json?query={}' - query_params = 'type:ticket group:{}'.format(NOTIFY_GROUP_ID) + ZENDESK_TICKET_URL = "https://govuk.zendesk.com/api/v2/search.json?query={}" + query_params = "type:ticket group:{}".format(NOTIFY_GROUP_ID) query_params = urllib.parse.quote(query_params) next_page = ZENDESK_TICKET_URL.format(query_params) - with open("zendesk_ticket_data_without_service.csv", 'w') as csvfile: + with open("zendesk_ticket_data_without_service.csv", "w") as csvfile: fieldnames = [ - 'Ticket id', - 'Subject line', - 'Date ticket created', - 'Tags', + "Ticket id", + "Subject line", + "Date ticket created", + "Tags", ] writer = csv.DictWriter(csvfile, fieldnames=fieldnames) writer.writeheader() @@ -83,40 +88,45 @@ def get_tickets_without_service_id(): print(next_page) response = requests.get( next_page, - headers={'Content-type': 'application/json'}, - auth=( - '{}/token'.format(NOTIFY_ZENDESK_EMAIL), - ZENDESK_API_KEY - ) + headers={"Content-type": "application/json"}, + auth=("{}/token".format(NOTIFY_ZENDESK_EMAIL), ZENDESK_API_KEY), ) data = response.json() print(data) for row in data["results"]: - service_url = [x for x in row["description"].split('\n') - if x.startswith("https://www.notifications.service.gov.uk/services/")] + service_url = [ + x + for x in row["description"].split("\n") + if x.startswith( + "https://www.notifications.service.gov.uk/services/" + ) + ] service_url = service_url[0][50:] if len(service_url) > 0 else None if not service_url: - writer.writerow({'Ticket id': row['id'], - 'Subject line': row['subject'], - 'Date ticket created': row["created_at"], - 'Tags': row.get('tags', '') - }) + writer.writerow( + { + "Ticket id": row["id"], + "Subject line": row["subject"], + "Date ticket created": row["created_at"], + "Tags": row.get("tags", ""), + } + ) next_page = data["next_page"] def get_tickets_with_description(): - ZENDESK_TICKET_URL = 'https://govuk.zendesk.com/api/v2/search.json?query={}' - query_params = 'type:ticket group:{}, created>2019-07-01'.format(NOTIFY_GROUP_ID) + ZENDESK_TICKET_URL = "https://govuk.zendesk.com/api/v2/search.json?query={}" + query_params = "type:ticket group:{}, created>2019-07-01".format(NOTIFY_GROUP_ID) query_params = urllib.parse.quote(query_params) next_page = ZENDESK_TICKET_URL.format(query_params) - with open("zendesk_ticket.csv", 'w') as csvfile: + with open("zendesk_ticket.csv", "w") as csvfile: fieldnames = [ - 'Ticket id', - 'Subject line', - 'Description', - 'Date ticket created', - 'Tags', + "Ticket id", + "Subject line", + "Description", + "Date ticket created", + "Tags", ] writer = csv.DictWriter(csvfile, fieldnames=fieldnames) writer.writeheader() @@ -124,19 +134,19 @@ def get_tickets_with_description(): print(next_page) response = requests.get( next_page, - headers={'Content-type': 'application/json'}, - auth=( - '{}/token'.format(NOTIFY_ZENDESK_EMAIL), - ZENDESK_API_KEY - ) + headers={"Content-type": "application/json"}, + auth=("{}/token".format(NOTIFY_ZENDESK_EMAIL), ZENDESK_API_KEY), ) data = response.json() print(data) for row in data["results"]: - writer.writerow({'Ticket id': row['id'], - 'Subject line': row['subject'], - 'Description': row['description'], - 'Date ticket created': row["created_at"], - 'Tags': row.get('tags', '') - }) + writer.writerow( + { + "Ticket id": row["id"], + "Subject line": row["subject"], + "Description": row["description"], + "Date ticket created": row["created_at"], + "Tags": row.get("tags", ""), + } + ) next_page = data["next_page"] diff --git a/gunicorn_config.py b/gunicorn_config.py index e65bbb483..d8b39233c 100644 --- a/gunicorn_config.py +++ b/gunicorn_config.py @@ -11,13 +11,13 @@ workers = 5 worker_class = "eventlet" bind = "0.0.0.0:{}".format(os.getenv("PORT")) disable_redirect_access_to_syslog = True -gunicorn.SERVER_SOFTWARE = 'None' +gunicorn.SERVER_SOFTWARE = "None" def worker_abort(worker): worker.log.info("worker received ABORT") for stack in sys._current_frames().values(): - worker.log.error(''.join(traceback.format_stack(stack))) + worker.log.error("".join(traceback.format_stack(stack))) def fix_ssl_monkeypatching(): diff --git a/tests/__init__.py b/tests/__init__.py index b3966b6b9..7c522f435 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -16,7 +16,7 @@ from app.models.user import User # In tests where we freeze time, the code in the test function will get the frozen time but the # fixtures will be using the current time. This causes itsdangerous to raise an exception - when # the session is decoded it appears to be created in the future. -freezegun.configure(extend_ignore_list=['itsdangerous']) +freezegun.configure(extend_ignore_list=["itsdangerous"]) class TestClient(FlaskClient): @@ -24,16 +24,18 @@ class TestClient(FlaskClient): # Skipping authentication here and just log them in model_user = User(user) with self.session_transaction() as session: - session['current_session_id'] = model_user.current_session_id - session['user_id'] = model_user.id + session["current_session_id"] = model_user.current_session_id + session["user_id"] = model_user.id if mocker: - mocker.patch('app.user_api_client.get_user', return_value=user) + mocker.patch("app.user_api_client.get_user", return_value=user) if mocker and service: with self.session_transaction() as session: - session['service_id'] = service['id'] - mocker.patch('app.service_api_client.get_service', return_value={'data': service}) + session["service_id"] = service["id"] + mocker.patch( + "app.service_api_client.get_service", return_value={"data": service} + ) - with patch('app.events_api_client.create_event'): + with patch("app.events_api_client.create_event"): login_user(model_user) with self.session_transaction() as test_session: for key, value in flask_session.items(): @@ -51,106 +53,108 @@ def generate_uuid(): return uuid.uuid4() -def created_by_json(id_, name='', email_address=''): - return {'id': id_, 'name': name, 'email_address': email_address} +def created_by_json(id_, name="", email_address=""): + return {"id": id_, "name": name, "email_address": email_address} def user_json( - id_='1234', - name='Test User', - email_address='test@gsa.gov', - mobile_number='+12028675109', + id_="1234", + name="Test User", + email_address="test@gsa.gov", + mobile_number="+12028675109", password_changed_at=None, permissions=None, - auth_type='sms_auth', + auth_type="sms_auth", failed_login_count=0, logged_in_at=None, - state='active', + state="active", platform_admin=False, - current_session_id='1234', + current_session_id="1234", organizations=None, - services=None - + services=None, ): if permissions is None: - permissions = {str(generate_uuid()): [ - 'view_activity', - 'send_texts', - 'send_emails', - 'manage_users', - 'manage_templates', - 'manage_settings', - 'manage_api_keys', - ]} + permissions = { + str(generate_uuid()): [ + "view_activity", + "send_texts", + "send_emails", + "manage_users", + "manage_templates", + "manage_settings", + "manage_api_keys", + ] + } if services is None: services = [str(service_id) for service_id in permissions.keys()] return { - 'id': id_, - 'name': name, - 'email_address': email_address, - 'mobile_number': mobile_number, - 'password_changed_at': password_changed_at, - 'permissions': permissions, - 'auth_type': auth_type, - 'failed_login_count': failed_login_count, - 'logged_in_at': logged_in_at or datetime.utcnow().strftime('%Y-%m-%d %H:%M:%S.%f'), - 'state': state, - 'platform_admin': platform_admin, - 'current_session_id': current_session_id, - 'organizations': organizations or [], - 'services': services + "id": id_, + "name": name, + "email_address": email_address, + "mobile_number": mobile_number, + "password_changed_at": password_changed_at, + "permissions": permissions, + "auth_type": auth_type, + "failed_login_count": failed_login_count, + "logged_in_at": logged_in_at + or datetime.utcnow().strftime("%Y-%m-%d %H:%M:%S.%f"), + "state": state, + "platform_admin": platform_admin, + "current_session_id": current_session_id, + "organizations": organizations or [], + "services": services, } def invited_user( - _id='1234', + _id="1234", service=None, - from_user='1234', - email_address='testinviteduser@gsa.gov', + from_user="1234", + email_address="testinviteduser@gsa.gov", permissions=None, - status='pending', + status="pending", created_at=None, - auth_type='sms_auth', - organization=None + auth_type="sms_auth", + organization=None, ): data = { - 'id': _id, - 'from_user': from_user, - 'email_address': email_address, - 'status': status, - 'created_at': created_at or datetime.utcnow(), - 'auth_type': auth_type, + "id": _id, + "from_user": from_user, + "email_address": email_address, + "status": status, + "created_at": created_at or datetime.utcnow(), + "auth_type": auth_type, } if service: - data['service'] = service + data["service"] = service if permissions: - data['permissions'] = permissions + data["permissions"] = permissions if organization: - data['organization'] = organization + data["organization"] = organization return data def service_json( - id_='1234', - name='Test Service', + id_="1234", + name="Test Service", users=None, message_limit=1000, active=True, restricted=True, email_from=None, reply_to_email_address=None, - sms_sender='GOVUK', + sms_sender="GOVUK", research_mode=False, email_branding=None, - branding='govuk', + branding="govuk", created_at=None, inbound_api=None, service_callback_api=None, permissions=None, - organization_type='federal', + organization_type="federal", prefix_sms=True, contact_link=None, organization_id=None, @@ -164,47 +168,47 @@ def service_json( if users is None: users = [] if permissions is None: - permissions = ['email', 'sms'] + permissions = ["email", "sms"] if service_callback_api is None: service_callback_api = [] if inbound_api is None: inbound_api = [] return { - 'id': id_, - 'name': name, - 'users': users, - 'message_limit': message_limit, - 'rate_limit': rate_limit, - 'active': active, - 'restricted': restricted, - 'email_from': email_from, - 'reply_to_email_address': reply_to_email_address, - 'sms_sender': sms_sender, - 'research_mode': research_mode, - 'organization_type': organization_type, - 'email_branding': email_branding, - 'branding': branding, - 'created_at': created_at or str(datetime.utcnow()), - 'permissions': permissions, - 'inbound_api': inbound_api, - 'service_callback_api': service_callback_api, - 'prefix_sms': prefix_sms, - 'contact_link': contact_link, - 'volume_email': 111111, - 'volume_sms': 222222, - 'consent_to_research': True, - 'count_as_live': True, - 'organization': organization_id, - 'notes': notes, - 'billing_contact_email_addresses': billing_contact_email_addresses, - 'billing_contact_names': billing_contact_names, - 'billing_reference': billing_reference, - 'purchase_order_number': purchase_order_number, + "id": id_, + "name": name, + "users": users, + "message_limit": message_limit, + "rate_limit": rate_limit, + "active": active, + "restricted": restricted, + "email_from": email_from, + "reply_to_email_address": reply_to_email_address, + "sms_sender": sms_sender, + "research_mode": research_mode, + "organization_type": organization_type, + "email_branding": email_branding, + "branding": branding, + "created_at": created_at or str(datetime.utcnow()), + "permissions": permissions, + "inbound_api": inbound_api, + "service_callback_api": service_callback_api, + "prefix_sms": prefix_sms, + "contact_link": contact_link, + "volume_email": 111111, + "volume_sms": 222222, + "consent_to_research": True, + "count_as_live": True, + "organization": organization_id, + "notes": notes, + "billing_contact_email_addresses": billing_contact_email_addresses, + "billing_contact_names": billing_contact_names, + "billing_reference": billing_reference, + "purchase_order_number": purchase_order_number, } def organization_json( - id_='1234', + id_="1234", name=False, users=None, active=True, @@ -217,177 +221,178 @@ def organization_json( agreement_signed_by_id=None, agreement_signed_on_behalf_of_name=None, agreement_signed_on_behalf_of_email_address=None, - organization_type='federal', + organization_type="federal", request_to_go_live_notes=None, notes=None, billing_contact_email_addresses=None, billing_contact_names=None, billing_reference=None, - purchase_order_number=None + purchase_order_number=None, ): if users is None: users = [] if services is None: services = [] return { - 'id': id_, - 'name': 'Test Organization' if name is False else name, - 'active': active, - 'users': users, - 'created_at': created_at or str(datetime.utcnow()), - 'email_branding_id': email_branding_id, - 'organization_type': organization_type, - 'agreement_signed': agreement_signed, - 'agreement_signed_at': None, - 'agreement_signed_by_id': agreement_signed_by_id, - 'agreement_signed_version': agreement_signed_version, - 'agreement_signed_on_behalf_of_name': agreement_signed_on_behalf_of_name, - 'agreement_signed_on_behalf_of_email_address': agreement_signed_on_behalf_of_email_address, - 'domains': domains or [], - 'request_to_go_live_notes': request_to_go_live_notes, - 'count_of_live_services': len(services), - 'notes': notes, - 'billing_contact_email_addresses': billing_contact_email_addresses, - 'billing_contact_names': billing_contact_names, - 'billing_reference': billing_reference, - 'purchase_order_number': purchase_order_number, + "id": id_, + "name": "Test Organization" if name is False else name, + "active": active, + "users": users, + "created_at": created_at or str(datetime.utcnow()), + "email_branding_id": email_branding_id, + "organization_type": organization_type, + "agreement_signed": agreement_signed, + "agreement_signed_at": None, + "agreement_signed_by_id": agreement_signed_by_id, + "agreement_signed_version": agreement_signed_version, + "agreement_signed_on_behalf_of_name": agreement_signed_on_behalf_of_name, + "agreement_signed_on_behalf_of_email_address": agreement_signed_on_behalf_of_email_address, + "domains": domains or [], + "request_to_go_live_notes": request_to_go_live_notes, + "count_of_live_services": len(services), + "notes": notes, + "billing_contact_email_addresses": billing_contact_email_addresses, + "billing_contact_names": billing_contact_names, + "billing_reference": billing_reference, + "purchase_order_number": purchase_order_number, } -def template_json(service_id, - id_, - name="sample template", - type_=None, - content=None, - subject=None, - version=1, - archived=False, - process_type='normal', - redact_personalisation=None, - reply_to=None, - reply_to_text=None, - folder=None - ): +def template_json( + service_id, + id_, + name="sample template", + type_=None, + content=None, + subject=None, + version=1, + archived=False, + process_type="normal", + redact_personalisation=None, + reply_to=None, + reply_to_text=None, + folder=None, +): template = { - 'id': id_, - 'name': name, - 'template_type': type_ or "sms", - 'content': content, - 'service': service_id, - 'version': version, - 'updated_at': datetime.utcnow().strftime('%Y-%m-%d %H:%M:%S.%f'), - 'archived': archived, - 'process_type': process_type, - 'reply_to': reply_to, - 'reply_to_text': reply_to_text, - 'folder': folder, + "id": id_, + "name": name, + "template_type": type_ or "sms", + "content": content, + "service": service_id, + "version": version, + "updated_at": datetime.utcnow().strftime("%Y-%m-%d %H:%M:%S.%f"), + "archived": archived, + "process_type": process_type, + "reply_to": reply_to, + "reply_to_text": reply_to_text, + "folder": folder, } if content is None: - template['content'] = "template content" - if subject is None and type_ != 'sms': - template['subject'] = "template subject" + template["content"] = "template content" + if subject is None and type_ != "sms": + template["subject"] = "template subject" if subject is not None: - template['subject'] = subject + template["subject"] = subject if redact_personalisation is not None: - template['redact_personalisation'] = redact_personalisation + template["redact_personalisation"] = redact_personalisation return template -def template_version_json(service_id, - id_, - created_by, - version=1, - created_at=None, - **kwargs): +def template_version_json( + service_id, id_, created_by, version=1, created_at=None, **kwargs +): template = template_json(service_id, id_, **kwargs) - template['created_by'] = created_by_json( - created_by['id'], - created_by['name'], - created_by['email_address'], + template["created_by"] = created_by_json( + created_by["id"], + created_by["name"], + created_by["email_address"], ) if created_at is None: - created_at = datetime.utcnow().strftime('%Y-%m-%d %H:%M:%S.%f') - template['created_at'] = created_at - template['version'] = version + created_at = datetime.utcnow().strftime("%Y-%m-%d %H:%M:%S.%f") + template["created_at"] = created_at + template["version"] = version return template def api_key_json(id_, name, expiry_date=None): - return {'id': id_, - 'name': name, - 'expiry_date': expiry_date - } + return {"id": id_, "name": name, "expiry_date": expiry_date} -def invite_json(id_, - from_user, - service_id, - email_address, - permissions, - created_at, - status, - auth_type, - folder_permissions): +def invite_json( + id_, + from_user, + service_id, + email_address, + permissions, + created_at, + status, + auth_type, + folder_permissions, +): return { - 'id': id_, - 'from_user': from_user, - 'service': service_id, - 'email_address': email_address, - 'status': status, - 'permissions': permissions, - 'created_at': created_at, - 'auth_type': auth_type, - 'folder_permissions': folder_permissions, + "id": id_, + "from_user": from_user, + "service": service_id, + "email_address": email_address, + "status": status, + "permissions": permissions, + "created_at": created_at, + "auth_type": auth_type, + "folder_permissions": folder_permissions, } def org_invite_json(id_, invited_by, org_id, email_address, created_at, status): return { - 'id': id_, - 'invited_by': invited_by, - 'organization': org_id, - 'email_address': email_address, - 'status': status, - 'created_at': created_at, + "id": id_, + "invited_by": invited_by, + "organization": org_id, + "email_address": email_address, + "status": status, + "created_at": created_at, } def inbound_sms_json(): return { - 'has_next': True, - 'data': [{ - 'user_number': phone_number, - 'notify_number': '+12028675309', - 'content': f'message-{index + 1}', - 'created_at': ( - datetime.utcnow() - timedelta(minutes=60 * hours_ago, seconds=index) - ).isoformat(), - 'id': sample_uuid(), - } for index, hours_ago, phone_number in ( - (0, 1, '+12028675300'), - (1, 1, '2028675300'), - (2, 1, '2028675300'), - (3, 3, '2028675302'), - (4, 5, '+33(0)1 12345678'), # France - (5, 7, '+1-202-555-0104'), # USA in one format - (6, 9, '+12025550104'), # USA in another format - (7, 9, '+68212345'), # Cook Islands - )] + "has_next": True, + "data": [ + { + "user_number": phone_number, + "notify_number": "+12028675309", + "content": f"message-{index + 1}", + "created_at": ( + datetime.utcnow() - timedelta(minutes=60 * hours_ago, seconds=index) + ).isoformat(), + "id": sample_uuid(), + } + for index, hours_ago, phone_number in ( + (0, 1, "+12028675300"), + (1, 1, "2028675300"), + (2, 1, "2028675300"), + (3, 3, "2028675302"), + (4, 5, "+33(0)1 12345678"), # France + (5, 7, "+1-202-555-0104"), # USA in one format + (6, 9, "+12025550104"), # USA in another format + (7, 9, "+68212345"), # Cook Islands + ) + ], } -TEST_USER_EMAIL = 'test@user.gsa.gov' +TEST_USER_EMAIL = "test@user.gsa.gov" def create_test_api_user(state, permissions=None): - user_data = {'id': 1, - 'name': 'Test User', - 'password': 'somepassword', - 'email_address': TEST_USER_EMAIL, - 'mobile_number': '+12021234123', - 'state': state, - 'permissions': permissions or {} - } + user_data = { + "id": 1, + "name": "Test User", + "password": "somepassword", + "email_address": TEST_USER_EMAIL, + "mobile_number": "+12021234123", + "state": state, + "permissions": permissions or {}, + } return user_data @@ -397,16 +402,16 @@ def job_json( job_id=None, template_id=None, template_version=1, - template_type='sms', - template_name='Example template', + template_type="sms", + template_name="Example template", created_at=None, - bucket_name='', + bucket_name="", original_file_name="thisisatest.csv", notification_count=1, notifications_sent=1, notifications_requested=1, - job_status='finished', - scheduled_for='', + job_status="finished", + scheduled_for="", processing_started=None, ): if job_id is None: @@ -414,30 +419,30 @@ def job_json( if template_id is None: template_id = "5d729fbd-239c-44ab-b498-75a985f3198f" if created_at is None: - created_at = str(datetime.now(timezone.utc).strftime('%Y-%m-%dT%H:%M:%S.%f%z')) + created_at = str(datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%S.%f%z")) data = { - 'id': job_id, - 'service': service_id, - 'template': template_id, - 'template_name': template_name, - 'template_version': template_version, - 'template_type': template_type, - 'original_file_name': original_file_name, - 'created_at': created_at, - 'notification_count': notification_count, - 'notifications_sent': notifications_sent, - 'notifications_requested': notifications_requested, - 'job_status': job_status, - 'statistics': [ + "id": job_id, + "service": service_id, + "template": template_id, + "template_name": template_name, + "template_version": template_version, + "template_type": template_type, + "original_file_name": original_file_name, + "created_at": created_at, + "notification_count": notification_count, + "notifications_sent": notifications_sent, + "notifications_requested": notifications_requested, + "job_status": job_status, + "statistics": [ { - 'status': 'blah', - 'count': notifications_requested, + "status": "blah", + "count": notifications_requested, } ], - 'created_by': created_by_json( - created_by['id'], - created_by['name'], - created_by['email_address'], + "created_by": created_by_json( + created_by["id"], + created_by["name"], + created_by["email_address"], ), } if scheduled_for: @@ -468,10 +473,10 @@ def notification_json( if template is None: template = template_json(service_id, str(generate_uuid()), type_=template_type) if to is None: - if template_type == 'email': - to = 'example@gsa.gov' + if template_type == "email": + to = "example@gsa.gov" else: - to = '2021234567' + to = "2021234567" if sent_at is None: sent_at = str(datetime.utcnow().time()) if created_at is None: @@ -479,43 +484,46 @@ def notification_json( if updated_at is None: updated_at = str((datetime.utcnow() + timedelta(minutes=1)).time()) if status is None: - status = 'delivered' + status = "delivered" links = {} if with_links: links = { - 'prev': '/service/{}/notifications?page=0'.format(service_id), - 'next': '/service/{}/notifications?page=1'.format(service_id), - 'last': '/service/{}/notifications?page=2'.format(service_id) + "prev": "/service/{}/notifications?page=0".format(service_id), + "next": "/service/{}/notifications?page=1".format(service_id), + "last": "/service/{}/notifications?page=2".format(service_id), } job_payload = None if job: - job_payload = {'id': job['id'], 'original_file_name': job['original_file_name']} + job_payload = {"id": job["id"], "original_file_name": job["original_file_name"]} data = { - 'notifications': [{ - 'id': sample_uuid(), - 'to': to, - 'template': template, - 'job': job_payload, - 'sent_at': sent_at, - 'status': status, - 'created_at': created_at, - 'created_by': None, - 'updated_at': updated_at, - 'job_row_number': job_row_number, - 'service': service_id, - 'template_version': template['version'], - 'personalisation': personalisation or {}, - 'notification_type': template_type, - 'reply_to_text': reply_to_text, - 'client_reference': client_reference, - 'created_by_name': created_by_name, - } for i in range(rows)], - 'total': rows, - 'page_size': 50, - 'links': links + "notifications": [ + { + "id": sample_uuid(), + "to": to, + "template": template, + "job": job_payload, + "sent_at": sent_at, + "status": status, + "created_at": created_at, + "created_by": None, + "updated_at": updated_at, + "job_row_number": job_row_number, + "service": service_id, + "template_version": template["version"], + "personalisation": personalisation or {}, + "notification_type": template_type, + "reply_to_text": reply_to_text, + "client_reference": client_reference, + "created_by_name": created_by_name, + } + for i in range(rows) + ], + "total": rows, + "page_size": 50, + "links": links, } return data @@ -528,7 +536,7 @@ def single_notification_json( sent_at=None, created_at=None, updated_at=None, - notification_type='sms' + notification_type="sms", ): if template is None: template = template_json(service_id, str(generate_uuid())) @@ -539,56 +547,56 @@ def single_notification_json( if updated_at is None: updated_at = str(datetime.utcnow() + timedelta(minutes=1)) if status is None: - status = 'delivered' + status = "delivered" job_payload = None if job: - job_payload = {'id': job['id'], 'original_file_name': job['original_file_name']} + job_payload = {"id": job["id"], "original_file_name": job["original_file_name"]} data = { - 'sent_at': sent_at, - 'to': '2021234567', - 'billable_units': 1, - 'status': status, - 'created_at': created_at, - 'reference': None, - 'updated_at': updated_at, - 'template_version': 5, - 'service': service_id, - 'id': '29441662-17ce-4ffe-9502-fcaed73b2826', - 'template': template, - 'job_row_number': 0, - 'notification_type': notification_type, - 'api_key': None, - 'job': job_payload, - 'sent_by': 'mmg' + "sent_at": sent_at, + "to": "2021234567", + "billable_units": 1, + "status": status, + "created_at": created_at, + "reference": None, + "updated_at": updated_at, + "template_version": 5, + "service": service_id, + "id": "29441662-17ce-4ffe-9502-fcaed73b2826", + "template": template, + "job_row_number": 0, + "notification_type": notification_type, + "api_key": None, + "job": job_payload, + "sent_by": "mmg", } return data -def validate_route_permission(mocker, - notify_admin, - method, - response_code, - route, - permissions, - usr, - service, - session=None): - usr['permissions'][str(service['id'])] = permissions - usr['services'] = [service['id']] +def validate_route_permission( + mocker, + notify_admin, + method, + response_code, + route, + permissions, + usr, + service, + session=None, +): + usr["permissions"][str(service["id"])] = permissions + usr["services"] = [service["id"]] + mocker.patch("app.user_api_client.check_verify_code", return_value=(True, "")) + mocker.patch("app.service_api_client.get_services", return_value={"data": []}) + mocker.patch("app.service_api_client.update_service", return_value=service) mocker.patch( - 'app.user_api_client.check_verify_code', - return_value=(True, '')) - mocker.patch( - 'app.service_api_client.get_services', - return_value={'data': []}) - mocker.patch('app.service_api_client.update_service', return_value=service) - mocker.patch('app.service_api_client.update_service_with_properties', return_value=service) - mocker.patch('app.user_api_client.get_user', return_value=usr) - mocker.patch('app.user_api_client.get_user_by_email', return_value=usr) - mocker.patch('app.service_api_client.get_service', return_value={'data': service}) - mocker.patch('app.models.user.Users.client_method', return_value=[usr]) - mocker.patch('app.job_api_client.has_jobs', return_value=False) + "app.service_api_client.update_service_with_properties", return_value=service + ) + mocker.patch("app.user_api_client.get_user", return_value=usr) + mocker.patch("app.user_api_client.get_user_by_email", return_value=usr) + mocker.patch("app.service_api_client.get_service", return_value={"data": service}) + mocker.patch("app.models.user.Users.client_method", return_value=[usr]) + mocker.patch("app.job_api_client.has_jobs", return_value=False) with notify_admin.test_request_context(): with notify_admin.test_client() as client: client.login(usr) @@ -597,9 +605,9 @@ def validate_route_permission(mocker, for k, v in session.items(): session_[k] = v resp = None - if method == 'GET': + if method == "GET": resp = client.get(route) - elif method == 'POST': + elif method == "POST": resp = client.post(route) else: pytest.fail("Invalid method call {}".format(method)) @@ -608,33 +616,26 @@ def validate_route_permission(mocker, return resp -def validate_route_permission_with_client(mocker, - client, - method, - response_code, - route, - permissions, - usr, - service): - usr['permissions'][str(service['id'])] = permissions +def validate_route_permission_with_client( + mocker, client, method, response_code, route, permissions, usr, service +): + usr["permissions"][str(service["id"])] = permissions + mocker.patch("app.user_api_client.check_verify_code", return_value=(True, "")) + mocker.patch("app.service_api_client.get_services", return_value={"data": []}) + mocker.patch("app.service_api_client.update_service", return_value=service) mocker.patch( - 'app.user_api_client.check_verify_code', - return_value=(True, '')) - mocker.patch( - 'app.service_api_client.get_services', - return_value={'data': []}) - mocker.patch('app.service_api_client.update_service', return_value=service) - mocker.patch('app.service_api_client.update_service_with_properties', return_value=service) - mocker.patch('app.user_api_client.get_user', return_value=usr) - mocker.patch('app.user_api_client.get_user_by_email', return_value=usr) - mocker.patch('app.service_api_client.get_service', return_value={'data': service}) - mocker.patch('app.user_api_client.get_users_for_service', return_value=[usr]) - mocker.patch('app.job_api_client.has_jobs', return_value=False) + "app.service_api_client.update_service_with_properties", return_value=service + ) + mocker.patch("app.user_api_client.get_user", return_value=usr) + mocker.patch("app.user_api_client.get_user_by_email", return_value=usr) + mocker.patch("app.service_api_client.get_service", return_value={"data": service}) + mocker.patch("app.user_api_client.get_users_for_service", return_value=[usr]) + mocker.patch("app.job_api_client.has_jobs", return_value=False) client.login(usr) resp = None - if method == 'GET': + if method == "GET": resp = client.get_response_from_url(route, _expected_status=response_code) - elif method == 'POST': + elif method == "POST": resp = client.post_response_from_url(route, _expected_status=response_code) else: pytest.fail("Invalid method call {}".format(method)) @@ -647,25 +648,18 @@ def assert_url_expected(actual, expected): actual_parts = urlparse(actual) expected_parts = urlparse(expected) for attribute in actual_parts._fields: - if attribute == 'query': + if attribute == "query": # query string ordering can be non-deterministic # so we need to parse it first, which gives us a # dictionary of keys and values, not a # serialized string - assert parse_qs( - expected_parts.query - ) == parse_qs( - actual_parts.query - ) + assert parse_qs(expected_parts.query) == parse_qs(actual_parts.query) else: - assert getattr( - actual_parts, attribute - ) == getattr( + assert getattr(actual_parts, attribute) == getattr( expected_parts, attribute - ), ( - 'Expected redirect: {}\n' - 'Actual redirect: {}' - ).format(expected, actual) + ), ("Expected redirect: {}\n" "Actual redirect: {}").format( + expected, actual + ) def find_element_by_tag_and_partial_text(page, tag, string): diff --git a/tests/app/main/forms/test_choose_time_form.py b/tests/app/main/forms/test_choose_time_form.py index 2a4ce95d7..f3d402e8f 100644 --- a/tests/app/main/forms/test_choose_time_form.py +++ b/tests/app/main/forms/test_choose_time_form.py @@ -6,42 +6,42 @@ from app.main.forms import ChooseTimeForm @freeze_time("2016-01-01 11:09:00.061258") def test_form_contains_next_24h(notify_admin): - choices = ChooseTimeForm().scheduled_for.choices # Friday - assert choices[0] == ('', 'Now') - assert choices[1] == ('2016-01-01T12:00:00', 'Today at noon UTC') - assert choices[13] == ('2016-01-02T00:00:00', 'Today at midnight UTC') + assert choices[0] == ("", "Now") + assert choices[1] == ("2016-01-01T12:00:00", "Today at noon UTC") + assert choices[13] == ("2016-01-02T00:00:00", "Today at midnight UTC") # Saturday - assert choices[14] == ('2016-01-02T01:00:00', 'Tomorrow at 1am UTC') - assert choices[37] == ('2016-01-03T00:00:00', 'Tomorrow at midnight UTC') + assert choices[14] == ("2016-01-02T01:00:00", "Tomorrow at 1am UTC") + assert choices[37] == ("2016-01-03T00:00:00", "Tomorrow at midnight UTC") # Sunday - assert choices[38] == ('2016-01-03T01:00:00', 'Sunday at 1am UTC') + assert choices[38] == ("2016-01-03T01:00:00", "Sunday at 1am UTC") # Monday - assert choices[62] == ('2016-01-04T01:00:00', 'Monday at 1am UTC') - assert choices[80] == ('2016-01-04T19:00:00', 'Monday at 7pm UTC') - assert choices[84] == ('2016-01-04T23:00:00', 'Monday at 11pm UTC') - assert choices[85] == ('2016-01-05T00:00:00', 'Monday at midnight UTC') + assert choices[62] == ("2016-01-04T01:00:00", "Monday at 1am UTC") + assert choices[80] == ("2016-01-04T19:00:00", "Monday at 7pm UTC") + assert choices[84] == ("2016-01-04T23:00:00", "Monday at 11pm UTC") + assert choices[85] == ("2016-01-05T00:00:00", "Monday at midnight UTC") with pytest.raises(IndexError): assert choices[ - 12 + # hours left in the day - (3 * 24) + # 3 days - 2 # magic number + 12 + (3 * 24) + 2 # hours left in the day # 3 days # magic number ] @freeze_time("2016-01-01 11:09:00.061258") def test_form_defaults_to_now(notify_admin): - assert ChooseTimeForm().scheduled_for.data == '' + assert ChooseTimeForm().scheduled_for.data == "" @freeze_time("2016-01-01 11:09:00.061258") def test_form_contains_next_three_days(notify_admin): assert ChooseTimeForm().scheduled_for.categories == [ - 'Later today', 'Tomorrow', 'Sunday', 'Monday' + "Later today", + "Tomorrow", + "Sunday", + "Monday", ] diff --git a/tests/app/main/forms/test_create_key_form.py b/tests/app/main/forms/test_create_key_form.py index 6e6b8d128..0768c271a 100644 --- a/tests/app/main/forms/test_create_key_form.py +++ b/tests/app/main/forms/test_create_key_form.py @@ -4,10 +4,13 @@ from werkzeug.datastructures import MultiDict from app.main.forms import CreateKeyForm -@pytest.mark.parametrize('expiry_date, expected_errors', ( - (None, ['A key with this name already exists']), - ('2001-01-01 01:01:01', None), -)) +@pytest.mark.parametrize( + "expiry_date, expected_errors", + ( + (None, ["A key with this name already exists"]), + ("2001-01-01 01:01:01", None), + ), +) def test_return_validation_error_when_key_name_exists( client_request, expiry_date, @@ -15,36 +18,32 @@ def test_return_validation_error_when_key_name_exists( ): _existing_keys = [ { - 'name': 'some key', - 'expiry_date': expiry_date, + "name": "some key", + "expiry_date": expiry_date, }, { - 'name': 'another key', - 'expiry_date': None, + "name": "another key", + "expiry_date": None, }, ] - form = CreateKeyForm( - _existing_keys, - formdata=MultiDict([('key_name', 'Some key')]) - ) + form = CreateKeyForm(_existing_keys, formdata=MultiDict([("key_name", "Some key")])) - form.key_type.choices = [('a', 'a'), ('b', 'b')] + form.key_type.choices = [("a", "a"), ("b", "b")] form.validate() - assert form.errors.get('key_name') == expected_errors + assert form.errors.get("key_name") == expected_errors @pytest.mark.parametrize( - 'key_type, expected_error', [ - ('', 'Select the type of key'), - ('invalid', 'Select the type of key') - ] + "key_type, expected_error", + [("", "Select the type of key"), ("invalid", "Select the type of key")], ) -def test_return_validation_error_when_key_type_not_chosen(client_request, key_type, expected_error): - +def test_return_validation_error_when_key_type_not_chosen( + client_request, key_type, expected_error +): form = CreateKeyForm( - [], - formdata=MultiDict([('key_name', 'Some key'), ('key_type', key_type)])) - form.key_type.choices = [('a', 'a'), ('b', 'b')] + [], formdata=MultiDict([("key_name", "Some key"), ("key_type", key_type)]) + ) + form.key_type.choices = [("a", "a"), ("b", "b")] form.validate() - assert form.errors['key_type'] == [expected_error] + assert form.errors["key_type"] == [expected_error] diff --git a/tests/app/main/forms/test_placeholder_form.py b/tests/app/main/forms/test_placeholder_form.py index 30e2d9ae3..babd5c640 100644 --- a/tests/app/main/forms/test_placeholder_form.py +++ b/tests/app/main/forms/test_placeholder_form.py @@ -4,46 +4,59 @@ from app.main.forms import get_placeholder_form_instance def test_form_class_not_mutated(notify_admin): - with notify_admin.test_request_context( - method='POST', - data={'placeholder_value': ''} + method="POST", data={"placeholder_value": ""} ): - form1 = get_placeholder_form_instance('name', {}, 'sms') - form2 = get_placeholder_form_instance('city', {}, 'sms') + form1 = get_placeholder_form_instance("name", {}, "sms") + form2 = get_placeholder_form_instance("city", {}, "sms") assert not form1.validate_on_submit() assert not form2.validate_on_submit() - assert str(form1.placeholder_value.label) == '' - assert str(form2.placeholder_value.label) == '' + assert ( + str(form1.placeholder_value.label) + == '' + ) + assert ( + str(form2.placeholder_value.label) + == '' + ) -@pytest.mark.parametrize('service_can_send_international_sms, placeholder_name, template_type, value, expected_error', [ - - (False, 'email address', 'email', '', 'Cannot be empty'), - (False, 'email address', 'email', '12345', 'Enter a valid email address'), - (False, 'email address', 'email', '“bad”@email-address.com', 'Enter a valid email address'), - (False, 'email address', 'email', 'test@example.com', None), - (False, 'email address', 'email', 'test@example.gsa.gov', None), - - (False, 'phone number', 'sms', '', 'Cannot be empty'), - (False, 'phone number', 'sms', '+(44) 7700-900 855', 'Not a US number'), - (False, 'phone number', 'sms', '2028675309', None), - (False, 'phone number', 'sms', '+1 (202) 867-5309', None), - - (True, 'phone number', 'sms', '+123', 'Not enough digits'), - (True, 'phone number', 'sms', '+44(0)7900 900-123', None), - (True, 'phone number', 'sms', '+1-2345-678890', None), - - (False, 'anything else', 'sms', '', 'Cannot be empty'), - (False, 'anything else', 'email', '', 'Cannot be empty'), - - (True, 'phone number', 'sms', 'invalid', 'The string supplied did not seem to be a phone number.'), - (True, 'phone number', 'email', 'invalid', None), - (True, 'email address', 'sms', 'invalid', None), - -]) +@pytest.mark.parametrize( + "service_can_send_international_sms, placeholder_name, template_type, value, expected_error", + [ + (False, "email address", "email", "", "Cannot be empty"), + (False, "email address", "email", "12345", "Enter a valid email address"), + ( + False, + "email address", + "email", + "“bad”@email-address.com", + "Enter a valid email address", + ), + (False, "email address", "email", "test@example.com", None), + (False, "email address", "email", "test@example.gsa.gov", None), + (False, "phone number", "sms", "", "Cannot be empty"), + (False, "phone number", "sms", "+(44) 7700-900 855", "Not a US number"), + (False, "phone number", "sms", "2028675309", None), + (False, "phone number", "sms", "+1 (202) 867-5309", None), + (True, "phone number", "sms", "+123", "Not enough digits"), + (True, "phone number", "sms", "+44(0)7900 900-123", None), + (True, "phone number", "sms", "+1-2345-678890", None), + (False, "anything else", "sms", "", "Cannot be empty"), + (False, "anything else", "email", "", "Cannot be empty"), + ( + True, + "phone number", + "sms", + "invalid", + "The string supplied did not seem to be a phone number.", + ), + (True, "phone number", "email", "invalid", None), + (True, "email address", "sms", "invalid", None), + ], +) def test_validates_recipients( notify_admin, placeholder_name, @@ -53,8 +66,7 @@ def test_validates_recipients( expected_error, ): with notify_admin.test_request_context( - method='POST', - data={'placeholder_value': value} + method="POST", data={"placeholder_value": value} ): form = get_placeholder_form_instance( placeholder_name, diff --git a/tests/app/main/forms/test_register_user_form.py b/tests/app/main/forms/test_register_user_form.py index c56120fd6..fc6b6a055 100644 --- a/tests/app/main/forms/test_register_user_form.py +++ b/tests/app/main/forms/test_register_user_form.py @@ -3,31 +3,29 @@ import pytest from app.main.forms import RegisterUserForm -@pytest.mark.parametrize('password', [ - 'usnotify' -]) +@pytest.mark.parametrize("password", ["usnotify"]) def test_should_raise_validation_error_for_password( client_request, mock_get_user_by_email, password, ): form = RegisterUserForm() - form.name.data = 'test' - form.email_address.data = 'teset@gsa.gov' - form.mobile_number.data = '2021231231' + form.name.data = "test" + form.email_address.data = "teset@gsa.gov" + form.mobile_number.data = "2021231231" form.password.data = password form.validate() - assert 'Choose a password that’s harder to guess' in form.errors['password'] + assert "Choose a password that’s harder to guess" in form.errors["password"] def test_valid_email_not_in_valid_domains( client_request, mock_get_organizations, ): - form = RegisterUserForm(email_address="test@test.com", mobile_number='2021231231') + form = RegisterUserForm(email_address="test@test.com", mobile_number="2021231231") assert not form.validate() - assert "Enter a public sector email address" in form.errors['email_address'][0] + assert "Enter a public sector email address" in form.errors["email_address"][0] def test_valid_email_in_valid_domains( @@ -36,8 +34,9 @@ def test_valid_email_in_valid_domains( form = RegisterUserForm( name="test", email_address="test@gsa.gov", - mobile_number='2028675309', - password='an uncommon password') + mobile_number="2028675309", + password="an uncommon password", + ) form.validate() assert form.errors == {} @@ -49,13 +48,15 @@ def test_invalid_email_address_error_message( form = RegisterUserForm( name="test", email_address="test.com", - mobile_number='2028675309', - password='1234567890') + mobile_number="2028675309", + password="1234567890", + ) assert not form.validate() form = RegisterUserForm( name="test", email_address="test.com", - mobile_number='+12028675309', - password='1234567890') + mobile_number="+12028675309", + password="1234567890", + ) assert not form.validate() diff --git a/tests/app/main/forms/test_service_contact_details_form.py b/tests/app/main/forms/test_service_contact_details_form.py index 84213936f..02695cb28 100644 --- a/tests/app/main/forms/test_service_contact_details_form.py +++ b/tests/app/main/forms/test_service_contact_details_form.py @@ -4,43 +4,49 @@ from app.main.forms import ServiceContactDetailsForm def test_form_fails_validation_with_no_radio_buttons_selected(notify_admin): - with notify_admin.test_request_context(method='POST', data={}): + with notify_admin.test_request_context(method="POST", data={}): form = ServiceContactDetailsForm() assert not form.validate_on_submit() assert len(form.errors) == 1 - assert form.errors['contact_details_type'] == ['Select an option'] + assert form.errors["contact_details_type"] == ["Select an option"] -@pytest.mark.parametrize('selected_radio_button, selected_text_box, text_box_data', [ - ('email_address', 'url', 'http://www.example.com'), - ('phone_number', 'url', 'http://www.example.com'), - ('url', 'email_address', 'user@example.com'), - ('phone_number', 'email_address', 'user@example.com'), - ('url', 'phone_number', '202 867 5309'), - ('email_address', 'phone_number', '202 867 5309'), -]) +@pytest.mark.parametrize( + "selected_radio_button, selected_text_box, text_box_data", + [ + ("email_address", "url", "http://www.example.com"), + ("phone_number", "url", "http://www.example.com"), + ("url", "email_address", "user@example.com"), + ("phone_number", "email_address", "user@example.com"), + ("url", "phone_number", "202 867 5309"), + ("email_address", "phone_number", "202 867 5309"), + ], +) def test_form_fails_validation_when_radio_button_selected_and_text_box_filled_in_do_not_match( - notify_admin, - selected_radio_button, - selected_text_box, - text_box_data + notify_admin, selected_radio_button, selected_text_box, text_box_data ): - data = {'contact_details_type': selected_radio_button, selected_text_box: text_box_data} + data = { + "contact_details_type": selected_radio_button, + selected_text_box: text_box_data, + } - with notify_admin.test_request_context(method='POST', data=data): + with notify_admin.test_request_context(method="POST", data=data): form = ServiceContactDetailsForm() assert not form.validate_on_submit() assert len(form.errors) == 1 - assert form.errors[selected_radio_button] == ['This field is required.'] + assert form.errors[selected_radio_button] == ["This field is required."] -@pytest.mark.parametrize('selected_field, url, email_address, phone_number', [ - ('url', 'http://www.example.com', 'invalid-email.com', 'phone'), - ('email_address', 'www.invalid-url.com', 'me@example.com', 'phone'), - ('phone_number', 'www.invalid-url.com', 'invalid-email.com', '202 867 5309'), -]) +@pytest.mark.parametrize( + "selected_field, url, email_address, phone_number", + [ + ("url", "http://www.example.com", "invalid-email.com", "phone"), + ("email_address", "www.invalid-url.com", "me@example.com", "phone"), + ("phone_number", "www.invalid-url.com", "invalid-email.com", "202 867 5309"), + ], +) def test_form_only_validates_the_field_which_matches_the_selected_radio_button( notify_admin, selected_field, @@ -48,45 +54,49 @@ def test_form_only_validates_the_field_which_matches_the_selected_radio_button( email_address, phone_number, ): - data = {'contact_details_type': selected_field, - 'url': url, - 'email_address': email_address, - 'phone_number': phone_number} + data = { + "contact_details_type": selected_field, + "url": url, + "email_address": email_address, + "phone_number": phone_number, + } - with notify_admin.test_request_context(method='POST', data=data): + with notify_admin.test_request_context(method="POST", data=data): form = ServiceContactDetailsForm() assert form.validate_on_submit() def test_form_url_validation_fails_with_invalid_url_field(notify_admin): - data = {'contact_details_type': 'url', 'url': 'www.example.com'} + data = {"contact_details_type": "url", "url": "www.example.com"} - with notify_admin.test_request_context(method='POST', data=data): + with notify_admin.test_request_context(method="POST", data=data): form = ServiceContactDetailsForm() assert not form.validate_on_submit() assert len(form.errors) == 1 - assert len(form.errors['url']) == 1 + assert len(form.errors["url"]) == 1 def test_form_email_validation_fails_with_invalid_email_address_field(notify_admin): - data = {'contact_details_type': 'email_address', 'email_address': '1@co'} + data = {"contact_details_type": "email_address", "email_address": "1@co"} - with notify_admin.test_request_context(method='POST', data=data): + with notify_admin.test_request_context(method="POST", data=data): form = ServiceContactDetailsForm() assert not form.validate_on_submit() assert len(form.errors) == 1 - assert len(form.errors['email_address']) == 2 + assert len(form.errors["email_address"]) == 2 -def test_form_phone_number_validation_fails_with_invalid_phone_number_field(notify_admin): - data = {'contact_details_type': 'phone_number', 'phone_number': '1235 A'} +def test_form_phone_number_validation_fails_with_invalid_phone_number_field( + notify_admin, +): + data = {"contact_details_type": "phone_number", "phone_number": "1235 A"} - with notify_admin.test_request_context(method='POST', data=data): + with notify_admin.test_request_context(method="POST", data=data): form = ServiceContactDetailsForm() assert not form.validate_on_submit() assert len(form.errors) == 1 - assert form.errors['phone_number'] == ['Must be a valid phone number'] + assert form.errors["phone_number"] == ["Must be a valid phone number"] diff --git a/tests/app/main/forms/test_service_sms_senders_form.py b/tests/app/main/forms/test_service_sms_senders_form.py index 04389228e..fff10c5ca 100644 --- a/tests/app/main/forms/test_service_sms_senders_form.py +++ b/tests/app/main/forms/test_service_sms_senders_form.py @@ -6,24 +6,20 @@ from app.main.forms import ServiceSmsSenderForm @pytest.mark.parametrize( "sms_sender,error_expected,error_message", [ - ('', True, 'Cannot be empty'), - ('22', True, 'Enter 3 characters or more'), - ('333', False, None), - ('elevenchars', False, None), # 11 chars - ('twelvecharas', True, 'Enter 11 characters or fewer'), # 12 chars - ('###', True, 'Use letters and numbers only'), - ('00111222333', True, 'Cannot start with 00'), - ('UK_GOV', False, None), # Underscores are allowed - ('UK.GOV', False, None), # Full stops are allowed + ("", True, "Cannot be empty"), + ("22", True, "Enter 3 characters or more"), + ("333", False, None), + ("elevenchars", False, None), # 11 chars + ("twelvecharas", True, "Enter 11 characters or fewer"), # 12 chars + ("###", True, "Use letters and numbers only"), + ("00111222333", True, "Cannot start with 00"), + ("UK_GOV", False, None), # Underscores are allowed + ("UK.GOV", False, None), # Full stops are allowed ("'UC'", False, None), # Straight single quotes are allowed - ] + ], ) def test_sms_sender_form_validation( - client_request, - mock_get_user_by_email, - sms_sender, - error_expected, - error_message + client_request, mock_get_user_by_email, sms_sender, error_expected, error_message ): form = ServiceSmsSenderForm() form.sms_sender.data = sms_sender @@ -32,6 +28,6 @@ def test_sms_sender_form_validation( if error_expected: assert form.errors - assert error_message == form.errors['sms_sender'][0] + assert error_message == form.errors["sms_sender"][0] else: assert not form.errors diff --git a/tests/app/main/forms/test_strip_whitespace.py b/tests/app/main/forms/test_strip_whitespace.py index 00e407ceb..12aa6cff0 100644 --- a/tests/app/main/forms/test_strip_whitespace.py +++ b/tests/app/main/forms/test_strip_whitespace.py @@ -5,28 +5,34 @@ from app.main.forms import StripWhitespaceForm, StripWhitespaceStringField class ExampleForm(StripWhitespaceForm): - foo = StringField('Foo') + foo = StringField("Foo") class ExampleFormSpecialField(Form): - foo = StripWhitespaceStringField('foo') + foo = StripWhitespaceStringField("foo") -@pytest.mark.parametrize('submitted_data', [ - 'bar', - ' bar ', - """ +@pytest.mark.parametrize( + "submitted_data", + [ + "bar", + " bar ", + """ \t bar """, - ' \u180E\u200B \u200C bar \u200D \u2060\uFEFF ', -]) -@pytest.mark.parametrize('form', [ - ExampleForm, - ExampleFormSpecialField, -]) + " \u180E\u200B \u200C bar \u200D \u2060\uFEFF ", + ], +) +@pytest.mark.parametrize( + "form", + [ + ExampleForm, + ExampleFormSpecialField, + ], +) def test_form_strips_all_whitespace( notify_admin, form, submitted_data, ): - assert form(foo=submitted_data).foo.data == 'bar' + assert form(foo=submitted_data).foo.data == "bar" diff --git a/tests/app/main/forms/test_two_factor_form.py b/tests/app/main/forms/test_two_factor_form.py index 6426b6212..54eb1f6b7 100644 --- a/tests/app/main/forms/test_two_factor_form.py +++ b/tests/app/main/forms/test_two_factor_form.py @@ -5,76 +5,82 @@ from app.main.forms import TwoFactorForm def _check_code(code): - return user_api_client.check_verify_code('1', code, "sms") + return user_api_client.check_verify_code("1", code, "sms") -@pytest.mark.parametrize('post_data', [ - {'sms_code': '123456'}, - {'sms_code': ' 123456 '}, - {'sms_code': '12 34 56'}, - {'sms_code': '1-23-45-6'}, -]) +@pytest.mark.parametrize( + "post_data", + [ + {"sms_code": "123456"}, + {"sms_code": " 123456 "}, + {"sms_code": "12 34 56"}, + {"sms_code": "1-23-45-6"}, + ], +) def test_form_is_valid_returns_no_errors( notify_admin, mock_check_verify_code, post_data, ): - with notify_admin.test_request_context(method='POST', data=post_data): + with notify_admin.test_request_context(method="POST", data=post_data): form = TwoFactorForm(_check_code) assert form.validate() is True assert form.errors == {} - mock_check_verify_code.assert_called_once_with('1', '123456', 'sms') + mock_check_verify_code.assert_called_once_with("1", "123456", "sms") -@pytest.mark.parametrize('post_data, expected_error', ( +@pytest.mark.parametrize( + "post_data, expected_error", ( - {'sms_code': '1234'}, - 'Not enough numbers', + ( + {"sms_code": "1234"}, + "Not enough numbers", + ), + ( + {"sms_code": "1234567"}, + "Too many numbers", + ), + ( + {}, + "Cannot be empty", + ), + ( + {"sms_code": "12E456"}, + "Numbers only", + ), + ( + {"sms_code": " ! 2 3 4 5 6"}, + "Numbers only", + ), ), - ( - {'sms_code': '1234567'}, - 'Too many numbers', - ), - ( - {}, - 'Cannot be empty', - ), - ( - {'sms_code': '12E456'}, - 'Numbers only', - ), - ( - {'sms_code': ' ! 2 3 4 5 6'}, - 'Numbers only', - ), -)) +) def test_check_verify_code_returns_errors( notify_admin, post_data, expected_error, mock_check_verify_code, ): - with notify_admin.test_request_context(method='POST', data=post_data): + with notify_admin.test_request_context(method="POST", data=post_data): form = TwoFactorForm(_check_code) assert form.validate() is False - assert form.errors == {'sms_code': [expected_error]} + assert form.errors == {"sms_code": [expected_error]} def test_check_verify_code_returns_error_when_code_has_expired( notify_admin, mock_check_verify_code_code_expired, ): - with notify_admin.test_request_context(method='POST', data={'sms_code': '999999'}): + with notify_admin.test_request_context(method="POST", data={"sms_code": "999999"}): form = TwoFactorForm(_check_code) assert form.validate() is False - assert form.errors == {'sms_code': ['Code has expired']} + assert form.errors == {"sms_code": ["Code has expired"]} def test_check_verify_code_returns_error_when_code_was_not_found( notify_admin, mock_check_verify_code_code_not_found, ): - with notify_admin.test_request_context(method='POST', data={'sms_code': '999999'}): + with notify_admin.test_request_context(method="POST", data={"sms_code": "999999"}): form = TwoFactorForm(_check_code) assert form.validate() is False - assert form.errors == {'sms_code': ['Code not found']} + assert form.errors == {"sms_code": ["Code not found"]} diff --git a/tests/app/main/test_asset_fingerprinter.py b/tests/app/main/test_asset_fingerprinter.py index 50513dd4f..5d0a577aa 100644 --- a/tests/app/main/test_asset_fingerprinter.py +++ b/tests/app/main/test_asset_fingerprinter.py @@ -5,102 +5,112 @@ from app.asset_fingerprinter import AssetFingerprinter class TestAssetFingerprint(object): def test_url_format(self, mocker): - get_file_content_mock = mocker.patch.object(AssetFingerprinter, 'get_asset_file_contents') + get_file_content_mock = mocker.patch.object( + AssetFingerprinter, "get_asset_file_contents" + ) get_file_content_mock.return_value = """ body { font-family: nta; } - """.encode('utf-8') - asset_fingerprinter = AssetFingerprinter( - asset_root='/suppliers/static/' + """.encode( + "utf-8" + ) + asset_fingerprinter = AssetFingerprinter(asset_root="/suppliers/static/") + assert ( + asset_fingerprinter.get_url("application.css") + == "/suppliers/static/application.css?418e6f4a6cdf1142e45c072ed3e1c90a" # noqa ) assert ( - asset_fingerprinter.get_url('application.css') == - '/suppliers/static/application.css?418e6f4a6cdf1142e45c072ed3e1c90a' # noqa - ) - assert ( - asset_fingerprinter.get_url('application-ie6.css') == - '/suppliers/static/application-ie6.css?418e6f4a6cdf1142e45c072ed3e1c90a' # noqa + asset_fingerprinter.get_url("application-ie6.css") + == "/suppliers/static/application-ie6.css?418e6f4a6cdf1142e45c072ed3e1c90a" # noqa ) def test_building_file_path(self, mocker): - get_file_content_mock = mocker.patch.object(AssetFingerprinter, 'get_asset_file_contents') + get_file_content_mock = mocker.patch.object( + AssetFingerprinter, "get_asset_file_contents" + ) get_file_content_mock.return_value = """ document.write('Hello world!'); - """.encode('utf-8') + """.encode( + "utf-8" + ) fingerprinter = AssetFingerprinter() - fingerprinter.get_url('javascripts/application.js') + fingerprinter.get_url("javascripts/application.js") fingerprinter.get_asset_file_contents.assert_called_with( - 'app/static/javascripts/application.js' + "app/static/javascripts/application.js" ) def test_hashes_are_consistent(self, mocker): - get_file_content_mock = mocker.patch.object(AssetFingerprinter, 'get_asset_file_contents') - get_file_content_mock.return_value = """ - body { - font-family: nta; - } - """.encode('utf-8') - asset_fingerprinter = AssetFingerprinter() - assert ( - asset_fingerprinter.get_asset_fingerprint('application.css') == - asset_fingerprinter.get_asset_fingerprint('same_contents.css') + get_file_content_mock = mocker.patch.object( + AssetFingerprinter, "get_asset_file_contents" ) + get_file_content_mock.return_value = """ + body { + font-family: nta; + } + """.encode( + "utf-8" + ) + asset_fingerprinter = AssetFingerprinter() + assert asset_fingerprinter.get_asset_fingerprint( + "application.css" + ) == asset_fingerprinter.get_asset_fingerprint("same_contents.css") - def test_hashes_are_different_for_different_files( - self, mocker - ): - get_file_content_mock = mocker.patch.object(AssetFingerprinter, 'get_asset_file_contents') + def test_hashes_are_different_for_different_files(self, mocker): + get_file_content_mock = mocker.patch.object( + AssetFingerprinter, "get_asset_file_contents" + ) asset_fingerprinter = AssetFingerprinter() get_file_content_mock.return_value = """ body { font-family: nta; } - """.encode('utf-8') - css_hash = asset_fingerprinter.get_asset_fingerprint('application.css') + """.encode( + "utf-8" + ) + css_hash = asset_fingerprinter.get_asset_fingerprint("application.css") get_file_content_mock.return_value = """ document.write('Hello world!'); - """.encode('utf-8') - js_hash = asset_fingerprinter.get_asset_fingerprint('application.js') - assert ( - js_hash != css_hash + """.encode( + "utf-8" ) + js_hash = asset_fingerprinter.get_asset_fingerprint("application.js") + assert js_hash != css_hash def test_hash_gets_cached(self, mocker): - get_file_content_mock = mocker.patch.object(AssetFingerprinter, 'get_asset_file_contents') + get_file_content_mock = mocker.patch.object( + AssetFingerprinter, "get_asset_file_contents" + ) get_file_content_mock.return_value = """ body { font-family: nta; } - """.encode('utf-8') + """.encode( + "utf-8" + ) fingerprinter = AssetFingerprinter() assert ( - fingerprinter.get_url('application.css') == - '/static/application.css?418e6f4a6cdf1142e45c072ed3e1c90a' - ) - fingerprinter._cache[ - 'application.css' - ] = 'a1a1a1' - assert ( - fingerprinter.get_url('application.css') == - 'a1a1a1' + fingerprinter.get_url("application.css") + == "/static/application.css?418e6f4a6cdf1142e45c072ed3e1c90a" ) + fingerprinter._cache["application.css"] = "a1a1a1" + assert fingerprinter.get_url("application.css") == "a1a1a1" fingerprinter.get_asset_file_contents.assert_called_once_with( - 'app/static/application.css' + "app/static/application.css" ) def test_without_hash_if_requested(self, mocker): fingerprinter = AssetFingerprinter() assert fingerprinter.get_url( - 'application.css', + "application.css", with_querystring_hash=False, - ) == ( - '/static/application.css' - ) + ) == ("/static/application.css") assert fingerprinter._cache == {} class TestAssetFingerprintWithUnicode(object): def test_can_read_self(self): - 'Ralph’s apostrophe is a string containing a unicode character' - AssetFingerprinter(filesystem_path='tests/app/main/').get_url('test_asset_fingerprinter.py') + "Ralph’s apostrophe is a string containing a unicode character" + AssetFingerprinter(filesystem_path="tests/app/main/").get_url( + "test_asset_fingerprinter.py" + ) diff --git a/tests/app/main/test_errorhandlers.py b/tests/app/main/test_errorhandlers.py index b6ec8c927..a544b4ab9 100644 --- a/tests/app/main/test_errorhandlers.py +++ b/tests/app/main/test_errorhandlers.py @@ -6,68 +6,77 @@ from notifications_python_client.errors import HTTPError def test_bad_url_returns_page_not_found(client_request): page = client_request.get_url( - '/bad_url', + "/bad_url", _expected_status=404, ) - assert page.h1.string.strip() == 'Page not found' - assert page.title.string.strip() == 'Page not found – Notify.gov' + assert page.h1.string.strip() == "Page not found" + assert page.title.string.strip() == "Page not found – Notify.gov" def test_load_service_before_request_handles_404(client_request, mocker): - exc = HTTPError(Response(status=404), 'Not found') - get_service = mocker.patch('app.service_api_client.get_service', side_effect=exc) + exc = HTTPError(Response(status=404), "Not found") + get_service = mocker.patch("app.service_api_client.get_service", side_effect=exc) client_request.get( - 'main.service_dashboard', - service_id='00000000-0000-0000-0000-000000000000', - _expected_status=404 + "main.service_dashboard", + service_id="00000000-0000-0000-0000-000000000000", + _expected_status=404, ) - get_service.assert_called_once_with('00000000-0000-0000-0000-000000000000') + get_service.assert_called_once_with("00000000-0000-0000-0000-000000000000") -@pytest.mark.parametrize('url', [ - '/new-password/MALFORMED_TOKEN', - '/user-profile/email/confirm/MALFORMED_TOKEN', - '/verify-email/MALFORMED_TOKEN' -]) +@pytest.mark.parametrize( + "url", + [ + "/new-password/MALFORMED_TOKEN", + "/user-profile/email/confirm/MALFORMED_TOKEN", + "/verify-email/MALFORMED_TOKEN", + ], +) def test_malformed_token_returns_page_not_found(client_request, url): page = client_request.get_url(url, _expected_status=404) - assert page.h1.string.strip() == 'Page not found' - flash_banner = page.find('div', class_='banner-dangerous').string.strip() + assert page.h1.string.strip() == "Page not found" + flash_banner = page.find("div", class_="banner-dangerous").string.strip() assert flash_banner == "There’s something wrong with the link you’ve used." - assert page.title.string.strip() == 'Page not found – Notify.gov' + assert page.title.string.strip() == "Page not found – Notify.gov" def test_csrf_returns_400(client_request, mocker): # we turn off CSRF handling for tests, so fake a CSRF response here. - csrf_err = CSRFError('400 Bad Request: The CSRF tokens do not match.') - mocker.patch('app.main.views.index.render_template', side_effect=csrf_err) + csrf_err = CSRFError("400 Bad Request: The CSRF tokens do not match.") + mocker.patch("app.main.views.index.render_template", side_effect=csrf_err) page = client_request.get_url( - '/privacy', + "/privacy", _expected_status=400, _test_page_title=False, ) - assert page.h1.string.strip() == 'Sorry, there’s a problem with Notify.gov' - assert page.title.string.strip() == 'Sorry, there’s a problem with the service – Notify.gov' + assert page.h1.string.strip() == "Sorry, there’s a problem with Notify.gov" + assert ( + page.title.string.strip() + == "Sorry, there’s a problem with the service – Notify.gov" + ) def test_csrf_redirects_to_sign_in_page_if_not_signed_in(client_request, mocker): - csrf_err = CSRFError('400 Bad Request: The CSRF tokens do not match.') - mocker.patch('app.main.views.index.render_template', side_effect=csrf_err) + csrf_err = CSRFError("400 Bad Request: The CSRF tokens do not match.") + mocker.patch("app.main.views.index.render_template", side_effect=csrf_err) client_request.logout() client_request.get_url( - '/privacy', - _expected_redirect=url_for('main.sign_in', next='/privacy'), + "/privacy", + _expected_redirect=url_for("main.sign_in", next="/privacy"), ) def test_405_returns_something_went_wrong_page(client_request, mocker): - page = client_request.post_url('/', _expected_status=405) + page = client_request.post_url("/", _expected_status=405) - assert page.h1.string.strip() == 'Sorry, there’s a problem with Notify.gov' - assert page.title.string.strip() == 'Sorry, there’s a problem with the service – Notify.gov' + assert page.h1.string.strip() == "Sorry, there’s a problem with Notify.gov" + assert ( + page.title.string.strip() + == "Sorry, there’s a problem with the service – Notify.gov" + ) diff --git a/tests/app/main/test_formatters.py b/tests/app/main/test_formatters.py index 89d8016da..a77b182e4 100644 --- a/tests/app/main/test_formatters.py +++ b/tests/app/main/test_formatters.py @@ -15,114 +15,150 @@ from app.formatters import ( ) -@pytest.mark.parametrize('status, notification_type, expected', ( - # Successful statuses aren’t linked - ('created', 'email', lambda: None), - ('sending', 'email', lambda: None), - ('delivered', 'email', lambda: None), - # Failures are linked to the channel-specific page - ('temporary-failure', 'email', partial(url_for, 'main.message_status', _anchor='email-statuses')), - ('permanent-failure', 'email', partial(url_for, 'main.message_status', _anchor='email-statuses')), - ('technical-failure', 'email', partial(url_for, 'main.message_status', _anchor='email-statuses')), - ('temporary-failure', 'sms', partial(url_for, 'main.message_status', _anchor='text-message-statuses')), - ('permanent-failure', 'sms', partial(url_for, 'main.message_status', _anchor='text-message-statuses')), - ('technical-failure', 'sms', partial(url_for, 'main.message_status', _anchor='text-message-statuses')), -)) +@pytest.mark.parametrize( + "status, notification_type, expected", + ( + # Successful statuses aren’t linked + ("created", "email", lambda: None), + ("sending", "email", lambda: None), + ("delivered", "email", lambda: None), + # Failures are linked to the channel-specific page + ( + "temporary-failure", + "email", + partial(url_for, "main.message_status", _anchor="email-statuses"), + ), + ( + "permanent-failure", + "email", + partial(url_for, "main.message_status", _anchor="email-statuses"), + ), + ( + "technical-failure", + "email", + partial(url_for, "main.message_status", _anchor="email-statuses"), + ), + ( + "temporary-failure", + "sms", + partial(url_for, "main.message_status", _anchor="text-message-statuses"), + ), + ( + "permanent-failure", + "sms", + partial(url_for, "main.message_status", _anchor="text-message-statuses"), + ), + ( + "technical-failure", + "sms", + partial(url_for, "main.message_status", _anchor="text-message-statuses"), + ), + ), +) def test_format_notification_status_as_url( client_request, status, notification_type, expected, ): - assert format_notification_status_as_url( - status, notification_type - ) == expected() + assert format_notification_status_as_url(status, notification_type) == expected() -@pytest.mark.parametrize('input_number, formatted_number', [ - (0, '0p'), - (0.01, '1p'), - (0.5, '50p'), - (1, '£1.00'), - (1.01, '£1.01'), - (1.006, '£1.01'), - (5.25, '£5.25'), - (5.7, '£5.70'), - (381, '£381.00'), - (144820, '£144,820.00'), -]) +@pytest.mark.parametrize( + "input_number, formatted_number", + [ + (0, "0p"), + (0.01, "1p"), + (0.5, "50p"), + (1, "£1.00"), + (1.01, "£1.01"), + (1.006, "£1.01"), + (5.25, "£5.25"), + (5.7, "£5.70"), + (381, "£381.00"), + (144820, "£144,820.00"), + ], +) def test_format_number_in_pounds_as_currency(input_number, formatted_number): assert format_number_in_pounds_as_currency(input_number) == formatted_number -@pytest.mark.parametrize('time, human_readable_datetime', [ - # incoming in UTC, outgoing in "human formatted" UTC - ('2018-03-14 09:00', '14 March at 09:00 UTC'), - ('2018-03-14 19:00', '14 March at 19:00 UTC'), - - ('2018-03-15 09:00', '15 March at 09:00 UTC'), - ('2018-03-15 19:00', '15 March at 19:00 UTC'), - - ('2018-03-19 09:00', '19 March at 09:00 UTC'), - ('2018-03-19 19:00', '19 March at 19:00 UTC'), - ('2018-03-19 23:59', '19 March at 23:59 UTC'), - - ('2018-03-20 00:00', '19 March at 00:00 UTC'), # we specifically refer to 00:00 as belonging to the day before. - ('2018-03-20 04:01', 'yesterday at 04:01 UTC'), - ('2018-03-20 09:00', 'yesterday at 09:00 UTC'), - ('2018-03-20 19:00', 'yesterday at 19:00 UTC'), - ('2018-03-20 23:59', 'yesterday at 23:59 UTC'), - - ('2018-03-21 00:00', 'yesterday at 00:00 UTC'), # we specifically refer to 00:00 as belonging to the day before. - ('2018-03-21 04:01', 'today at 04:01 UTC'), - ('2018-03-21 09:00', 'today at 09:00 UTC'), - ('2018-03-21 12:00', 'today at 12:00 UTC'), - ('2018-03-21 19:00', 'today at 19:00 UTC'), - ('2018-03-21 23:59', 'today at 23:59 UTC'), - - ('2018-03-22 00:00', 'today at 00:00 UTC'), - ('2018-03-22 04:01', 'tomorrow at 04:01 UTC'), - ('2018-03-22 09:00', 'tomorrow at 09:00 UTC'), - ('2018-03-22 19:00', 'tomorrow at 19:00 UTC'), - ('2018-03-22 23:59', 'tomorrow at 23:59 UTC'), - - ('2018-03-23 04:01', '23 March at 04:01 UTC'), - ('2018-03-23 09:00', '23 March at 09:00 UTC'), - ('2018-03-23 19:00', '23 March at 19:00 UTC'), - -]) +@pytest.mark.parametrize( + "time, human_readable_datetime", + [ + # incoming in UTC, outgoing in "human formatted" UTC + ("2018-03-14 09:00", "14 March at 09:00 UTC"), + ("2018-03-14 19:00", "14 March at 19:00 UTC"), + ("2018-03-15 09:00", "15 March at 09:00 UTC"), + ("2018-03-15 19:00", "15 March at 19:00 UTC"), + ("2018-03-19 09:00", "19 March at 09:00 UTC"), + ("2018-03-19 19:00", "19 March at 19:00 UTC"), + ("2018-03-19 23:59", "19 March at 23:59 UTC"), + ( + "2018-03-20 00:00", + "19 March at 00:00 UTC", + ), # we specifically refer to 00:00 as belonging to the day before. + ("2018-03-20 04:01", "yesterday at 04:01 UTC"), + ("2018-03-20 09:00", "yesterday at 09:00 UTC"), + ("2018-03-20 19:00", "yesterday at 19:00 UTC"), + ("2018-03-20 23:59", "yesterday at 23:59 UTC"), + ( + "2018-03-21 00:00", + "yesterday at 00:00 UTC", + ), # we specifically refer to 00:00 as belonging to the day before. + ("2018-03-21 04:01", "today at 04:01 UTC"), + ("2018-03-21 09:00", "today at 09:00 UTC"), + ("2018-03-21 12:00", "today at 12:00 UTC"), + ("2018-03-21 19:00", "today at 19:00 UTC"), + ("2018-03-21 23:59", "today at 23:59 UTC"), + ("2018-03-22 00:00", "today at 00:00 UTC"), + ("2018-03-22 04:01", "tomorrow at 04:01 UTC"), + ("2018-03-22 09:00", "tomorrow at 09:00 UTC"), + ("2018-03-22 19:00", "tomorrow at 19:00 UTC"), + ("2018-03-22 23:59", "tomorrow at 23:59 UTC"), + ("2018-03-23 04:01", "23 March at 04:01 UTC"), + ("2018-03-23 09:00", "23 March at 09:00 UTC"), + ("2018-03-23 19:00", "23 March at 19:00 UTC"), + ], +) def test_format_datetime_relative(time, human_readable_datetime): - with freeze_time('2018-03-21 12:00'): + with freeze_time("2018-03-21 12:00"): assert format_datetime_relative(time) == human_readable_datetime -@pytest.mark.parametrize('value, significant_figures, expected_result', ( - (0, 1, 0), - (0, 2, 0), - (12_345, 1, 10_000), - (12_345, 2, 12_000), - (12_345, 3, 12_300), - (12_345, 9, 12_345), - (12_345.6789, 1, 10_000), - (12_345.6789, 9, 12_345), - (-12_345, 1, -10_000), -)) +@pytest.mark.parametrize( + "value, significant_figures, expected_result", + ( + (0, 1, 0), + (0, 2, 0), + (12_345, 1, 10_000), + (12_345, 2, 12_000), + (12_345, 3, 12_300), + (12_345, 9, 12_345), + (12_345.6789, 1, 10_000), + (12_345.6789, 9, 12_345), + (-12_345, 1, -10_000), + ), +) def test_round_to_significant_figures(value, significant_figures, expected_result): assert round_to_significant_figures(value, significant_figures) == expected_result -@pytest.mark.parametrize('service_name, safe_email', [ - ('name with spaces', 'name.with.spaces'), - ('singleword', 'singleword'), - ('UPPER CASE', 'upper.case'), - ('Service - with dash', 'service.with.dash'), - ('lots of spaces', 'lots.of.spaces'), - ('name.with.dots', 'name.with.dots'), - ('name-with-other-delimiters', 'namewithotherdelimiters'), - ('.leading', 'leading'), - ('trailing.', 'trailing'), - ('üńïçödë wördś', 'unicode.words'), -]) +@pytest.mark.parametrize( + "service_name, safe_email", + [ + ("name with spaces", "name.with.spaces"), + ("singleword", "singleword"), + ("UPPER CASE", "upper.case"), + ("Service - with dash", "service.with.dash"), + ("lots of spaces", "lots.of.spaces"), + ("name.with.dots", "name.with.dots"), + ("name-with-other-delimiters", "namewithotherdelimiters"), + (".leading", "leading"), + ("trailing.", "trailing"), + ("üńïçödë wördś", "unicode.words"), + ], +) def test_email_safe_return_dot_separated_email_domain(service_name, safe_email): assert email_safe(service_name) == safe_email diff --git a/tests/app/main/test_permissions.py b/tests/app/main/test_permissions.py index 0cd12d0e7..f2f015e27 100644 --- a/tests/app/main/test_permissions.py +++ b/tests/app/main/test_permissions.py @@ -15,7 +15,7 @@ from tests.conftest import ( @pytest.mark.parametrize( - 'user_services, user_organizations, expected_status, organization_checked', + "user_services, user_organizations, expected_status, organization_checked", ( ([SERVICE_ONE_ID], [], 200, False), ([SERVICE_ONE_ID, SERVICE_TWO_ID], [], 200, False), @@ -27,7 +27,7 @@ from tests.conftest import ( ([SERVICE_ONE_ID, SERVICE_TWO_ID], [ORGANISATION_ID], 200, False), ([], [ORGANISATION_TWO_ID], 403, True), ([], [ORGANISATION_ID, ORGANISATION_TWO_ID], 200, True), - ) + ), ) def test_services_pages_that_org_users_are_allowed_to_see( client_request, @@ -48,22 +48,21 @@ def test_services_pages_that_org_users_are_allowed_to_see( expected_status, organization_checked, ): - api_user_active['services'] = user_services - api_user_active['organizations'] = user_organizations - api_user_active['permissions'] = { - service_id: ['manage_users', 'manage_settings'] - for service_id in user_services + api_user_active["services"] = user_services + api_user_active["organizations"] = user_organizations + api_user_active["permissions"] = { + service_id: ["manage_users", "manage_settings"] for service_id in user_services } service = service_json( - name='SERVICE WITH ORG', + name="SERVICE WITH ORG", id_=SERVICE_ONE_ID, - users=[api_user_active['id']], + users=[api_user_active["id"]], organization_id=ORGANISATION_ID, ) mock_get_service = mocker.patch( - 'app.notify_client.service_api_client.service_api_client.get_service', - return_value={'data': service} + "app.notify_client.service_api_client.service_api_client.get_service", + return_value={"data": service}, ) client_request.login( api_user_active, @@ -71,8 +70,8 @@ def test_services_pages_that_org_users_are_allowed_to_see( ) endpoints = ( - 'main.usage', - 'main.manage_users', + "main.usage", + "main.manage_users", ) for endpoint in endpoints: @@ -98,51 +97,49 @@ def test_service_navigation_for_org_user( mock_get_users_by_service, mock_get_organization, ): - api_user_active['services'] = [] - api_user_active['organizations'] = [ORGANISATION_ID] + api_user_active["services"] = [] + api_user_active["organizations"] = [ORGANISATION_ID] service = service_json( id_=SERVICE_ONE_ID, organization_id=ORGANISATION_ID, ) - mocker.patch( - 'app.service_api_client.get_service', - return_value={'data': service} - ) + mocker.patch("app.service_api_client.get_service", return_value={"data": service}) client_request.login(api_user_active, service=service) page = client_request.get( - 'main.usage', + "main.usage", service_id=SERVICE_ONE_ID, ) - assert [ - item.text.strip() for item in page.select('nav.nav a') - ] == [ - 'Usage', - 'Team members', + assert [item.text.strip() for item in page.select("nav.nav a")] == [ + "Usage", + "Team members", ] -@pytest.mark.parametrize('user_organizations, expected_menu_items, expected_status', [ - ( - [], +@pytest.mark.parametrize( + "user_organizations, expected_menu_items, expected_status", + [ ( - 'Send messages', - 'Sent messages', - 'Team members', + [], + ( + "Send messages", + "Sent messages", + "Team members", + ), + 403, ), - 403, - ), - ( - [ORGANISATION_ID], ( - 'Send messages', - 'Sent messages', - 'Team members', - 'Usage', + [ORGANISATION_ID], + ( + "Send messages", + "Sent messages", + "Team members", + "Usage", + ), + 200, ), - 200, - ), -]) + ], +) def test_service_user_without_manage_service_permission_can_see_usage_page_when_org_user( client_request, mocker, @@ -163,27 +160,25 @@ def test_service_user_without_manage_service_permission_can_see_usage_page_when_ expected_status, expected_menu_items, ): - active_caseworking_user['services'] = [SERVICE_ONE_ID] - active_caseworking_user['organizations'] = user_organizations + active_caseworking_user["services"] = [SERVICE_ONE_ID] + active_caseworking_user["organizations"] = user_organizations service = service_json( id_=SERVICE_ONE_ID, organization_id=ORGANISATION_ID, ) - mocker.patch( - 'app.service_api_client.get_service', - return_value={'data': service} - ) + mocker.patch("app.service_api_client.get_service", return_value={"data": service}) client_request.login(active_caseworking_user, service=service) page = client_request.get( - 'main.choose_template', + "main.choose_template", service_id=SERVICE_ONE_ID, ) - assert tuple( - item.text.strip() for item in page.select('nav.nav a') - ) == expected_menu_items + assert ( + tuple(item.text.strip() for item in page.select("nav.nav a")) + == expected_menu_items + ) client_request.get( - 'main.usage', + "main.usage", service_id=SERVICE_ONE_ID, _expected_status=expected_status, ) @@ -196,7 +191,7 @@ def get_name_of_decorator_from_ast_node(node): return get_name_of_decorator_from_ast_node(node.func) if isinstance(node, ast.Attribute): return node.value.id - return '{}.{}'.format(node.func.value.id, node.func.attr) + return "{}.{}".format(node.func.value.id, node.func.attr) def get_decorators_for_function(function): @@ -206,99 +201,96 @@ def get_decorators_for_function(function): yield get_name_of_decorator_from_ast_node(decorator) -SERVICE_ID_ARGUMENT = 'service_id' -ORGANISATION_ID_ARGUMENT = 'org_id' +SERVICE_ID_ARGUMENT = "service_id" +ORGANISATION_ID_ARGUMENT = "org_id" def get_routes_and_decorators(argument_name=None): import app.main.views as views + for module_name, module in inspect.getmembers(views): for function_name, function in inspect.getmembers(module): if inspect.isfunction(function): decorators = list(get_decorators_for_function(function)) - if 'main.route' in decorators and ( - not argument_name or - argument_name in inspect.signature(function).parameters.keys() + if "main.route" in decorators and ( + not argument_name + or argument_name in inspect.signature(function).parameters.keys() ): - yield '{}.{}'.format(module_name, function_name), decorators + yield "{}.{}".format(module_name, function_name), decorators def format_decorators(decorators, indent=8): - return '\n'.join( - '{}@{}'.format(' ' * indent, decorator) - for decorator in decorators + return "\n".join( + "{}@{}".format(" " * indent, decorator) for decorator in decorators ) def test_code_to_extract_decorators_works_with_known_examples(): assert ( - 'templates.choose_template', - ['main.route', 'main.route', 'main.route', 'main.route', 'main.route', 'main.route', 'user_has_permissions'], - ) in list( - get_routes_and_decorators(SERVICE_ID_ARGUMENT) - ) + "templates.choose_template", + [ + "main.route", + "main.route", + "main.route", + "main.route", + "main.route", + "main.route", + "user_has_permissions", + ], + ) in list(get_routes_and_decorators(SERVICE_ID_ARGUMENT)) assert ( - 'organizations.organization_dashboard', - ['main.route', 'user_has_permissions'], - ) in list( - get_routes_and_decorators(ORGANISATION_ID_ARGUMENT) - ) + "organizations.organization_dashboard", + ["main.route", "user_has_permissions"], + ) in list(get_routes_and_decorators(ORGANISATION_ID_ARGUMENT)) assert ( - 'platform_admin.platform_admin', - ['main.route', 'user_is_platform_admin'], - ) in list( - get_routes_and_decorators() - ) + "platform_admin.platform_admin", + ["main.route", "user_is_platform_admin"], + ) in list(get_routes_and_decorators()) def test_routes_have_permissions_decorators(): + for endpoint, decorators in list( + get_routes_and_decorators(SERVICE_ID_ARGUMENT) + ) + list(get_routes_and_decorators(ORGANISATION_ID_ARGUMENT)): + file, function = endpoint.split(".") - for endpoint, decorators in ( - list(get_routes_and_decorators(SERVICE_ID_ARGUMENT)) + - list(get_routes_and_decorators(ORGANISATION_ID_ARGUMENT)) - ): - file, function = endpoint.split('.') - - assert 'user_is_logged_in' not in decorators, ( - '@user_is_logged_in used on service or organization specific endpoint\n' - 'Use @user_has_permissions() or @user_is_platform_admin only\n' - 'app/main/views/{}.py::{}\n' + assert "user_is_logged_in" not in decorators, ( + "@user_is_logged_in used on service or organization specific endpoint\n" + "Use @user_has_permissions() or @user_is_platform_admin only\n" + "app/main/views/{}.py::{}\n" ).format(file, function) - if 'user_is_platform_admin' in decorators: + if "user_is_platform_admin" in decorators: continue - assert 'user_has_permissions' in decorators, ( - 'Missing @user_has_permissions decorator\n' - 'Use @user_has_permissions() or @user_is_platform_admin instead\n' - 'app/main/views/{}.py::{}\n' + assert "user_has_permissions" in decorators, ( + "Missing @user_has_permissions decorator\n" + "Use @user_has_permissions() or @user_is_platform_admin instead\n" + "app/main/views/{}.py::{}\n" ).format(file, function) for _endpoint, decorators in get_routes_and_decorators(): - - assert 'login_required' not in decorators, ( - '@login_required found\n' - 'For consistency, use @user_is_logged_in() instead (from app.utils)\n' - 'app/main/views/{}.py::{}\n' + assert "login_required" not in decorators, ( + "@login_required found\n" + "For consistency, use @user_is_logged_in() instead (from app.utils)\n" + "app/main/views/{}.py::{}\n" ).format(file, function) - if 'user_is_platform_admin' in decorators: - assert 'user_has_permissions' not in decorators, ( - '@user_has_permissions and @user_is_platform_admin decorating same function\n' - 'You can only use one of these at a time\n' - 'app/main/views/{}.py::{}\n' + if "user_is_platform_admin" in decorators: + assert "user_has_permissions" not in decorators, ( + "@user_has_permissions and @user_is_platform_admin decorating same function\n" + "You can only use one of these at a time\n" + "app/main/views/{}.py::{}\n" ).format(file, function) - assert 'user_is_logged_in' not in decorators, ( - '@user_is_logged_in used with @user_is_platform_admin\n' - 'Use @user_is_platform_admin only\n' - 'app/main/views/{}.py::{}\n' + assert "user_is_logged_in" not in decorators, ( + "@user_is_logged_in used with @user_is_platform_admin\n" + "Use @user_is_platform_admin only\n" + "app/main/views/{}.py::{}\n" ).format(file, function) def test_routes_require_uuids(client_request): for rule in current_app.url_map.iter_rules(): - for param in re.findall('<([^>]*)>', rule.rule): - if '_id' in param and not param.startswith('uuid:'): - pytest.fail(( - 'Should be in {}' - ).format(param, rule.rule)) + for param in re.findall("<([^>]*)>", rule.rule): + if "_id" in param and not param.startswith("uuid:"): + pytest.fail(("Should be in {}").format(param, rule.rule)) diff --git a/tests/app/main/test_request_header.py b/tests/app/main/test_request_header.py index 7bd6c6f09..665ed689e 100644 --- a/tests/app/main/test_request_header.py +++ b/tests/app/main/test_request_header.py @@ -3,24 +3,31 @@ import pytest from tests.conftest import set_config_values -@pytest.mark.parametrize('check_proxy_header,header_value,expected_code', [ - (True, 'key_1', 200), - (True, 'wrong_key', 403), - (False, 'wrong_key', 200), - (False, 'key_1', 200), -]) -def test_route_correct_secret_key(notify_admin, check_proxy_header, header_value, expected_code): - with set_config_values(notify_admin, { - 'ROUTE_SECRET_KEY_1': 'key_1', - 'ROUTE_SECRET_KEY_2': '', - 'CHECK_PROXY_HEADER': check_proxy_header, - }): - +@pytest.mark.parametrize( + "check_proxy_header,header_value,expected_code", + [ + (True, "key_1", 200), + (True, "wrong_key", 403), + (False, "wrong_key", 200), + (False, "key_1", 200), + ], +) +def test_route_correct_secret_key( + notify_admin, check_proxy_header, header_value, expected_code +): + with set_config_values( + notify_admin, + { + "ROUTE_SECRET_KEY_1": "key_1", + "ROUTE_SECRET_KEY_2": "", + "CHECK_PROXY_HEADER": check_proxy_header, + }, + ): with notify_admin.test_client() as client: response = client.get( - path='/_status?elb=True', + path="/_status?elb=True", headers=[ - ('X-Custom-forwarder', header_value), - ] + ("X-Custom-forwarder", header_value), + ], ) assert response.status_code == expected_code diff --git a/tests/app/main/test_validators.py b/tests/app/main/test_validators.py index d1b046d33..d5962110e 100644 --- a/tests/app/main/test_validators.py +++ b/tests/app/main/test_validators.py @@ -15,11 +15,14 @@ def _gen_mock_field(x): return Mock(data=x) -@pytest.mark.parametrize("email", [ # TODO: update with email_domains.txt - 'test@gsa.gov', - 'test@abc.gov', - 'test@xyz.gov' -]) +@pytest.mark.parametrize( + "email", + [ # TODO: update with email_domains.txt + "test@gsa.gov", + "test@abc.gov", + "test@xyz.gov", + ], +) def test_valid_list_of_white_list_email_domains( client_request, email, @@ -28,13 +31,16 @@ def test_valid_list_of_white_list_email_domains( email_domain_validators(None, _gen_mock_field(email)) -@pytest.mark.parametrize("email", [ # TODO: update with email_domains.txt - 'test@gov.gsa', - 'test@gmail.co', - 'test@mail.co', - 'test@amazonses.co', - 'test@amazon.com' -]) +@pytest.mark.parametrize( + "email", + [ # TODO: update with email_domains.txt + "test@gov.gsa", + "test@gmail.co", + "test@mail.co", + "test@amazonses.co", + "test@amazon.com", + ], +) def test_invalid_list_of_white_list_email_domains( client_request, email, @@ -49,35 +55,38 @@ def test_for_commas_in_placeholders( client_request, ): with pytest.raises(ValidationError) as error: - NoCommasInPlaceHolders()(None, _gen_mock_field('Hello ((name,date))')) - assert str(error.value) == 'You cannot put commas between double brackets' - NoCommasInPlaceHolders()(None, _gen_mock_field('Hello ((name))')) + NoCommasInPlaceHolders()(None, _gen_mock_field("Hello ((name,date))")) + assert str(error.value) == "You cannot put commas between double brackets" + NoCommasInPlaceHolders()(None, _gen_mock_field("Hello ((name))")) -@pytest.mark.parametrize('msg', ['The quick brown fox', 'Thé “quick” bröwn fox\u200B']) +@pytest.mark.parametrize("msg", ["The quick brown fox", "Thé “quick” bröwn fox\u200B"]) def test_sms_character_validation(client_request, msg): - OnlySMSCharacters(template_type='sms')(None, _gen_mock_field(msg)) + OnlySMSCharacters(template_type="sms")(None, _gen_mock_field(msg)) -@pytest.mark.parametrize('data, err_msg', [ - ( - '∆ abc 📲 def 📵 ghi', +@pytest.mark.parametrize( + "data, err_msg", + [ ( - 'You cannot use ∆, 📲 or 📵 in text messages. ' - 'They will not show up properly on everyone’s phones.' - ) - ), - ( - '📵', + "∆ abc 📲 def 📵 ghi", + ( + "You cannot use ∆, 📲 or 📵 in text messages. " + "They will not show up properly on everyone’s phones." + ), + ), ( - 'You cannot use 📵 in text messages. ' - 'It will not show up properly on everyone’s phones.' - ) - ), -]) + "📵", + ( + "You cannot use 📵 in text messages. " + "It will not show up properly on everyone’s phones." + ), + ), + ], +) def test_non_sms_character_validation(data, err_msg, client_request): with pytest.raises(ValidationError) as error: - OnlySMSCharacters(template_type='sms')(None, _gen_mock_field(data)) + OnlySMSCharacters(template_type="sms")(None, _gen_mock_field(data)) assert str(error.value) == err_msg diff --git a/tests/app/main/views/accounts/test_choose_accounts.py b/tests/app/main/views/accounts/test_choose_accounts.py index f0c177ca0..801feff77 100644 --- a/tests/app/main/views/accounts/test_choose_accounts.py +++ b/tests/app/main/views/accounts/test_choose_accounts.py @@ -9,69 +9,69 @@ from tests.conftest import SERVICE_ONE_ID, SERVICE_TWO_ID, normalize_spaces OS1, OS2, OS3, S1, S2, S3 = repeat(uuid.uuid4(), 6) SAMPLE_DATA = { - 'organizations': [ + "organizations": [ { - 'name': 'org_1', - 'id': 'o1', - 'count_of_live_services': 1, + "name": "org_1", + "id": "o1", + "count_of_live_services": 1, }, { - 'name': 'org_2', - 'id': 'o2', - 'count_of_live_services': 2, + "name": "org_2", + "id": "o2", + "count_of_live_services": 2, }, { - 'name': 'org_3', - 'id': 'o3', - 'count_of_live_services': 0, - } + "name": "org_3", + "id": "o3", + "count_of_live_services": 0, + }, ], - 'services': [ + "services": [ { - 'name': 'org_service_1', - 'id': OS1, - 'restricted': False, - 'organization': 'o1', + "name": "org_service_1", + "id": OS1, + "restricted": False, + "organization": "o1", }, { - 'name': 'org_service_2', - 'id': OS2, - 'restricted': False, - 'organization': 'o1', + "name": "org_service_2", + "id": OS2, + "restricted": False, + "organization": "o1", }, { - 'name': 'org_service_3', - 'id': OS3, - 'restricted': True, - 'organization': 'o1', + "name": "org_service_3", + "id": OS3, + "restricted": True, + "organization": "o1", }, { - 'name': 'service_1', - 'id': S1, - 'restricted': False, - 'organization': None, + "name": "service_1", + "id": S1, + "restricted": False, + "organization": None, }, { - 'name': 'service_2', - 'id': S2, - 'restricted': False, - 'organization': None, + "name": "service_2", + "id": S2, + "restricted": False, + "organization": None, }, { - 'name': 'service_3', - 'id': S3, - 'restricted': True, - 'organization': None, + "name": "service_3", + "id": S3, + "restricted": True, + "organization": None, }, - ] + ], } @pytest.fixture def mock_get_orgs_and_services(mocker): return mocker.patch( - 'app.user_api_client.get_organizations_and_services_for_user', - return_value=SAMPLE_DATA + "app.user_api_client.get_organizations_and_services_for_user", + return_value=SAMPLE_DATA, ) @@ -80,59 +80,79 @@ def test_choose_account_should_show_choose_accounts_page( mock_get_non_empty_organizations_and_services_for_user, mock_get_organization, ): - resp = client_request.get('main.choose_account') - page = resp.find('main', {'id': 'main-content'}) + resp = client_request.get("main.choose_account") + page = resp.find("main", {"id": "main-content"}) - assert normalize_spaces(page.h1.text) == 'Choose service' - outer_list_items = page.select('nav ul')[0].select('li') - headings = page.select('main h2') + assert normalize_spaces(page.h1.text) == "Choose service" + outer_list_items = page.select("nav ul")[0].select("li") + headings = page.select("main h2") assert len(outer_list_items) == 8 - assert normalize_spaces(headings[0].text) == 'Live services' + assert normalize_spaces(headings[0].text) == "Live services" # first org - assert outer_list_items[0].a.text == 'Org 1' - assert outer_list_items[0].a['href'] == url_for('.organization_dashboard', org_id='o1') - assert normalize_spaces(outer_list_items[0].select_one('.browse-list-hint').text) == ( - '1 live service' + assert outer_list_items[0].a.text == "Org 1" + assert outer_list_items[0].a["href"] == url_for( + ".organization_dashboard", org_id="o1" ) + assert normalize_spaces( + outer_list_items[0].select_one(".browse-list-hint").text + ) == ("1 live service") # second org - assert outer_list_items[1].a.text == 'Org 2' - assert outer_list_items[1].a['href'] == url_for('.organization_dashboard', org_id='o2') - assert normalize_spaces(outer_list_items[1].select_one('.browse-list-hint').text) == ( - '2 live services' + assert outer_list_items[1].a.text == "Org 2" + assert outer_list_items[1].a["href"] == url_for( + ".organization_dashboard", org_id="o2" ) + assert normalize_spaces( + outer_list_items[1].select_one(".browse-list-hint").text + ) == ("2 live services") # third org - assert outer_list_items[2].a.text == 'Org 3' - assert outer_list_items[2].a['href'] == url_for('.organization_dashboard', org_id='o3') - assert normalize_spaces(outer_list_items[2].select_one('.browse-list-hint').text) == ( - '0 live services' + assert outer_list_items[2].a.text == "Org 3" + assert outer_list_items[2].a["href"] == url_for( + ".organization_dashboard", org_id="o3" ) + assert normalize_spaces( + outer_list_items[2].select_one(".browse-list-hint").text + ) == ("0 live services") # live services - assert outer_list_items[3].a.text == 'Service 1' - assert outer_list_items[3].a['href'] == url_for('.service_dashboard', service_id=SERVICE_TWO_ID) - assert outer_list_items[4].a.text == 'Service 2' - assert outer_list_items[4].a['href'] == url_for('.service_dashboard', service_id=SERVICE_TWO_ID) - assert outer_list_items[5].a.text == 'service one' - assert outer_list_items[5].a['href'] == url_for('.service_dashboard', service_id='12345') - assert outer_list_items[6].a.text == 'service one (org 2)' - assert outer_list_items[6].a['href'] == url_for('.service_dashboard', service_id='12345') - assert outer_list_items[7].a.text == 'service two (org 2)' - assert outer_list_items[7].a['href'] == url_for('.service_dashboard', service_id='67890') + assert outer_list_items[3].a.text == "Service 1" + assert outer_list_items[3].a["href"] == url_for( + ".service_dashboard", service_id=SERVICE_TWO_ID + ) + assert outer_list_items[4].a.text == "Service 2" + assert outer_list_items[4].a["href"] == url_for( + ".service_dashboard", service_id=SERVICE_TWO_ID + ) + assert outer_list_items[5].a.text == "service one" + assert outer_list_items[5].a["href"] == url_for( + ".service_dashboard", service_id="12345" + ) + assert outer_list_items[6].a.text == "service one (org 2)" + assert outer_list_items[6].a["href"] == url_for( + ".service_dashboard", service_id="12345" + ) + assert outer_list_items[7].a.text == "service two (org 2)" + assert outer_list_items[7].a["href"] == url_for( + ".service_dashboard", service_id="67890" + ) - assert normalize_spaces(headings[1].text) == 'Trial mode services' + assert normalize_spaces(headings[1].text) == "Trial mode services" # trial services - trial_services_list_items = page.select('nav ul')[1].select('li') + trial_services_list_items = page.select("nav ul")[1].select("li") assert len(trial_services_list_items) == 3 - assert trial_services_list_items[0].a.text == 'service three' - assert trial_services_list_items[0].a['href'] == url_for('.service_dashboard', service_id='abcde') - assert trial_services_list_items[1].a.text == 'service three' - assert trial_services_list_items[1].a['href'] == url_for('.service_dashboard', service_id='abcde') + assert trial_services_list_items[0].a.text == "service three" + assert trial_services_list_items[0].a["href"] == url_for( + ".service_dashboard", service_id="abcde" + ) + assert trial_services_list_items[1].a.text == "service three" + assert trial_services_list_items[1].a["href"] == url_for( + ".service_dashboard", service_id="abcde" + ) assert mock_get_organization.call_args_list == [] @@ -143,59 +163,72 @@ def test_choose_account_should_show_choose_accounts_page_if_no_services( mock_get_organization, mock_get_organization_services, ): - mock_get_orgs_and_services.return_value = { - 'organizations': [], - 'services': [] - } - resp = client_request.get('main.choose_account') - page = resp.find('main', {'id': 'main-content'}) + mock_get_orgs_and_services.return_value = {"organizations": [], "services": []} + resp = client_request.get("main.choose_account") + page = resp.find("main", {"id": "main-content"}) - links = page.find_all('a') + links = page.find_all("a") assert len(links) == 1 add_service_link = links[0] - assert normalize_spaces(page.h1.text) == 'Choose service' - assert normalize_spaces(add_service_link.text) == 'Add a new service' - assert not page.select('main h2') - assert add_service_link['href'] == url_for('main.add_service') + assert normalize_spaces(page.h1.text) == "Choose service" + assert normalize_spaces(add_service_link.text) == "Add a new service" + assert not page.select("main h2") + assert add_service_link["href"] == url_for("main.add_service") -@pytest.mark.parametrize('orgs_and_services, expected_headings', ( - ({ - 'organizations': [], - 'services': [] - }, [ - 'Platform admin', - ]), - (SAMPLE_DATA, [ - 'Platform admin', - 'Live services', - 'Trial mode services', - ]), - ({ - 'organizations': [], - 'services': [{ - 'name': 'Live service', - 'id': OS2, - 'restricted': False, - 'organization': None, - }], - }, [ - 'Platform admin', - 'Live services', - ]), - ({ - 'organizations': [], - 'services': [{ - 'name': 'Trial service', - 'id': OS2, - 'restricted': True, - 'organization': None, - }], - }, [ - 'Platform admin', - 'Trial mode services', - ]), -)) +@pytest.mark.parametrize( + "orgs_and_services, expected_headings", + ( + ( + {"organizations": [], "services": []}, + [ + "Platform admin", + ], + ), + ( + SAMPLE_DATA, + [ + "Platform admin", + "Live services", + "Trial mode services", + ], + ), + ( + { + "organizations": [], + "services": [ + { + "name": "Live service", + "id": OS2, + "restricted": False, + "organization": None, + } + ], + }, + [ + "Platform admin", + "Live services", + ], + ), + ( + { + "organizations": [], + "services": [ + { + "name": "Trial service", + "id": OS2, + "restricted": True, + "organization": None, + } + ], + }, + [ + "Platform admin", + "Trial mode services", + ], + ), + ), +) def test_choose_account_should_should_organizations_link_for_platform_admin( client_request, platform_admin_user, @@ -209,17 +242,17 @@ def test_choose_account_should_should_organizations_link_for_platform_admin( mock_get_orgs_and_services.return_value = orgs_and_services client_request.login(platform_admin_user) - page = client_request.get('main.choose_account') + page = client_request.get("main.choose_account") - first_item = page.select_one('.browse-list-item') - first_link = first_item.select_one('a') - first_hint = first_item.select_one('.browse-list-hint') - assert first_link.text == 'All organizations' - assert first_link['href'] == url_for('main.organizations') - assert normalize_spaces(first_hint.text) == '3 organizations, 9,999 live services' + first_item = page.select_one(".browse-list-item") + first_link = first_item.select_one("a") + first_hint = first_item.select_one(".browse-list-hint") + assert first_link.text == "All organizations" + assert first_link["href"] == url_for("main.organizations") + assert normalize_spaces(first_hint.text) == "3 organizations, 9,999 live services" assert [ - normalize_spaces(h2.text) for h2 in page.select('main h2') + normalize_spaces(h2.text) for h2 in page.select("main h2") ] == expected_headings @@ -229,13 +262,15 @@ def test_choose_account_should_show_back_to_service_link( mock_get_organization, mock_get_organization_services, ): - resp = client_request.get('main.choose_account') + resp = client_request.get("main.choose_account") - service_navigation = resp.find('div', {'class': 'navigation-service usa-breadcrumb'}) + service_navigation = resp.find( + "div", {"class": "navigation-service usa-breadcrumb"} + ) back_to_service_link = service_navigation.a - assert back_to_service_link['href'] == url_for('main.show_accounts_or_dashboard') - assert back_to_service_link.text == 'Back to service one' + assert back_to_service_link["href"] == url_for("main.show_accounts_or_dashboard") + assert back_to_service_link.text == "Back to service one" def test_choose_account_should_not_show_back_to_service_link_if_no_service_in_session( @@ -245,10 +280,10 @@ def test_choose_account_should_not_show_back_to_service_link_if_no_service_in_se mock_get_organization_services, ): with client_request.session_transaction() as session: - session['service_id'] = None - page = client_request.get('main.choose_account') + session["service_id"] = None + page = client_request.get("main.choose_account") - assert len(page.select('.navigation-service usa-breadcrumb a')) == 0 + assert len(page.select(".navigation-service usa-breadcrumb a")) == 0 def test_choose_account_should_not_show_back_to_service_link_if_not_signed_in( @@ -258,17 +293,20 @@ def test_choose_account_should_not_show_back_to_service_link_if_not_signed_in( client_request.logout() with client_request.session_transaction() as session: - session['service_id'] = SERVICE_ONE_ID - page = client_request.get('main.sign_in') + session["service_id"] = SERVICE_ONE_ID + page = client_request.get("main.sign_in") - assert page.select_one('h1').text == 'Sign in' # We’re not signed in - assert page.select_one('.navigation-service usa-breadcrumb a') is None + assert page.select_one("h1").text == "Sign in" # We’re not signed in + assert page.select_one(".navigation-service usa-breadcrumb a") is None -@pytest.mark.parametrize('active', ( - False, - pytest.param(True), -)) +@pytest.mark.parametrize( + "active", + ( + False, + pytest.param(True), + ), +) def test_choose_account_should_not_show_back_to_service_link_if_service_archived( client_request, service_one, @@ -277,16 +315,16 @@ def test_choose_account_should_not_show_back_to_service_link_if_service_archived mock_get_organization_services, active, ): - service_one['active'] = active + service_one["active"] = active with client_request.session_transaction() as session: - session['service_id'] = service_one['id'] - page = client_request.get('main.choose_account') + session["service_id"] = service_one["id"] + page = client_request.get("main.choose_account") - assert normalize_spaces(page.select_one('h1').text) == 'Choose service' + assert normalize_spaces(page.select_one("h1").text) == "Choose service" if active: - assert page.select_one('.navigation-service a') is not None + assert page.select_one(".navigation-service a") is not None else: - assert page.select_one('.navigation-service a') is None + assert page.select_one(".navigation-service a") is None def test_should_not_show_back_to_service_if_user_doesnt_belong_to_service( @@ -298,20 +336,18 @@ def test_should_not_show_back_to_service_if_user_doesnt_belong_to_service( mock_get_service.return_value = service_two expected_page_text = ( # Page has no ‘back to’ link - 'You’re not allowed to see this page ' - 'To check your permissions, speak to a member of your team who can manage settings, team and usage.' + "You’re not allowed to see this page " + "To check your permissions, speak to a member of your team who can manage settings, team and usage." ) page = client_request.get( - 'main.view_template', - service_id=mock_get_service.return_value['id'], + "main.view_template", + service_id=mock_get_service.return_value["id"], template_id=fake_uuid, _expected_status=403, _test_page_title=False, ) - assert normalize_spaces( - page.select_one('.govuk-grid-row').text - ).startswith( + assert normalize_spaces(page.select_one(".govuk-grid-row").text).startswith( normalize_spaces(expected_page_text) ) @@ -325,22 +361,16 @@ def test_should_show_back_to_service_if_user_belongs_to_service( ): mock_get_service.return_value = service_one expected_page_text = ( - 'Test Service Switch service ' - '' - 'Dashboard ' - 'Send messages ' - 'Team members' + "Test Service Switch service " "" "Dashboard " "Send messages " "Team members" ) # TODO: set sidebar variables in common test module page = client_request.get( - 'main.view_template', - service_id=mock_get_service.return_value['id'], + "main.view_template", + service_id=mock_get_service.return_value["id"], template_id=fake_uuid, _test_page_title=False, ) assert normalize_spaces( - page.select_one('header + .grid-container').text - ).startswith( - normalize_spaces(expected_page_text) - ) + page.select_one("header + .grid-container").text + ).startswith(normalize_spaces(expected_page_text)) diff --git a/tests/app/main/views/accounts/test_show_accounts_or_dashboard.py b/tests/app/main/views/accounts/test_show_accounts_or_dashboard.py index f47d863d7..a57b582d2 100644 --- a/tests/app/main/views/accounts/test_show_accounts_or_dashboard.py +++ b/tests/app/main/views/accounts/test_show_accounts_or_dashboard.py @@ -6,53 +6,58 @@ from tests import user_json def user_with_orgs_and_services(num_orgs, num_services, platform_admin=False): return user_json( - name='leo', - organizations=['org{}'.format(i) for i in range(1, num_orgs + 1)], - services=['service{}'.format(i) for i in range(1, num_services + 1)], - platform_admin=platform_admin + name="leo", + organizations=["org{}".format(i) for i in range(1, num_orgs + 1)], + services=["service{}".format(i) for i in range(1, num_services + 1)], + platform_admin=platform_admin, ) -@pytest.mark.parametrize('num_orgs,num_services,endpoint,endpoint_kwargs', [ - (0, 0, '.choose_account', {}), - (0, 2, '.choose_account', {}), - - # assumption is that live service is part of user’s organization - # – real users shouldn’t have orphaned live services, or access to - # services belonging to other organizations - (1, 1, '.organization_dashboard', {'org_id': 'org1'}), - - (2, 0, '.choose_account', {}), - (0, 1, '.service_dashboard', {'service_id': 'service1'}), - (1, 0, '.organization_dashboard', {'org_id': 'org1'}), -]) +@pytest.mark.parametrize( + "num_orgs,num_services,endpoint,endpoint_kwargs", + [ + (0, 0, ".choose_account", {}), + (0, 2, ".choose_account", {}), + # assumption is that live service is part of user’s organization + # – real users shouldn’t have orphaned live services, or access to + # services belonging to other organizations + (1, 1, ".organization_dashboard", {"org_id": "org1"}), + (2, 0, ".choose_account", {}), + (0, 1, ".service_dashboard", {"service_id": "service1"}), + (1, 0, ".organization_dashboard", {"org_id": "org1"}), + ], +) def test_show_accounts_or_dashboard_redirects_to_choose_account_or_service_dashboard( client_request, mock_get_organizations_and_services_for_user, num_orgs, num_services, endpoint, - endpoint_kwargs + endpoint_kwargs, ): - client_request.login(user_with_orgs_and_services(num_orgs=num_orgs, num_services=num_services)) + client_request.login( + user_with_orgs_and_services(num_orgs=num_orgs, num_services=num_services) + ) client_request.get( - 'main.show_accounts_or_dashboard', - _expected_redirect=url_for(endpoint, **endpoint_kwargs) + "main.show_accounts_or_dashboard", + _expected_redirect=url_for(endpoint, **endpoint_kwargs), ) -def test_show_accounts_or_dashboard_redirects_if_service_in_session(client_request, mock_get_service): +def test_show_accounts_or_dashboard_redirects_if_service_in_session( + client_request, mock_get_service +): client_request.login(user_with_orgs_and_services(num_orgs=1, num_services=1)) with client_request.session_transaction() as session: - session['service_id'] = 'service1' - session['organization_id'] = None + session["service_id"] = "service1" + session["organization_id"] = None client_request.get( - '.show_accounts_or_dashboard', + ".show_accounts_or_dashboard", _expected_redirect=url_for( - 'main.service_dashboard', - service_id='service1', + "main.service_dashboard", + service_id="service1", ), ) @@ -60,31 +65,29 @@ def test_show_accounts_or_dashboard_redirects_if_service_in_session(client_reque def test_show_accounts_or_dashboard_redirects_if_org_in_session(client_request): client_request.login(user_with_orgs_and_services(num_orgs=1, num_services=1)) with client_request.session_transaction() as session: - session['service_id'] = None - session['organization_id'] = 'org1' + session["service_id"] = None + session["organization_id"] = "org1" client_request.get( - '.show_accounts_or_dashboard', + ".show_accounts_or_dashboard", _expected_redirect=url_for( - 'main.organization_dashboard', - org_id='org1', + "main.organization_dashboard", + org_id="org1", ), ) def test_show_accounts_or_dashboard_doesnt_redirect_to_service_dashboard_if_user_not_part_of_service_in_session( - client_request, - mock_get_organizations_and_services_for_user, - mock_get_service + client_request, mock_get_organizations_and_services_for_user, mock_get_service ): client_request.login(user_with_orgs_and_services(num_orgs=1, num_services=1)) with client_request.session_transaction() as session: - session['service_id'] = 'service2' - session['organization_id'] = None + session["service_id"] = "service2" + session["organization_id"] = None client_request.get( - '.show_accounts_or_dashboard', - _expected_redirect=url_for('main.organization_dashboard', org_id='org1') + ".show_accounts_or_dashboard", + _expected_redirect=url_for("main.organization_dashboard", org_id="org1"), ) @@ -94,12 +97,12 @@ def test_show_accounts_or_dashboard_doesnt_redirect_to_org_dashboard_if_user_not ): client_request.login(user_with_orgs_and_services(num_orgs=1, num_services=1)) with client_request.session_transaction() as session: - session['service_id'] = None - session['organization_id'] = 'org2' + session["service_id"] = None + session["organization_id"] = "org2" client_request.get( - '.show_accounts_or_dashboard', - _expected_redirect=url_for('main.organization_dashboard', org_id='org1') + ".show_accounts_or_dashboard", + _expected_redirect=url_for("main.organization_dashboard", org_id="org1"), ) @@ -109,26 +112,26 @@ def test_show_accounts_or_dashboard_redirects_if_not_logged_in( ): client_request.logout() client_request.get( - 'main.show_accounts_or_dashboard', - _expected_redirect=url_for('main.index'), + "main.show_accounts_or_dashboard", + _expected_redirect=url_for("main.index"), ) def test_show_accounts_or_dashboard_redirects_to_service_dashboard_if_platform_admin( - client_request, - mocker, - mock_get_service + client_request, mocker, mock_get_service ): - client_request.login(user_with_orgs_and_services(num_orgs=1, num_services=1, platform_admin=True)) + client_request.login( + user_with_orgs_and_services(num_orgs=1, num_services=1, platform_admin=True) + ) with client_request.session_transaction() as session: - session['service_id'] = 'service2' - session['organization_id'] = None + session["service_id"] = "service2" + session["organization_id"] = None client_request.get( - '.show_accounts_or_dashboard', + ".show_accounts_or_dashboard", _expected_redirect=url_for( - 'main.service_dashboard', - service_id='service2', + "main.service_dashboard", + service_id="service2", ), ) @@ -136,15 +139,17 @@ def test_show_accounts_or_dashboard_redirects_to_service_dashboard_if_platform_a def test_show_accounts_or_dashboard_redirects_to_org_dashboard_if_platform_admin( client_request, ): - client_request.login(user_with_orgs_and_services(num_orgs=1, num_services=1, platform_admin=True)) + client_request.login( + user_with_orgs_and_services(num_orgs=1, num_services=1, platform_admin=True) + ) with client_request.session_transaction() as session: - session['service_id'] = None - session['organization_id'] = 'org2' + session["service_id"] = None + session["organization_id"] = "org2" client_request.get( - '.show_accounts_or_dashboard', + ".show_accounts_or_dashboard", _expected_redirect=url_for( - 'main.organization_dashboard', - org_id='org2', + "main.organization_dashboard", + org_id="org2", ), ) diff --git a/tests/app/main/views/organizations/test_organization_invites.py b/tests/app/main/views/organizations/test_organization_invites.py index 2003d39d6..ac2e7fb56 100644 --- a/tests/app/main/views/organizations/test_organization_invites.py +++ b/tests/app/main/views/organizations/test_organization_invites.py @@ -15,22 +15,21 @@ def test_invite_org_user( mock_get_organization, sample_org_invite, ): - mock_invite_org_user = mocker.patch( - 'app.org_invite_api_client.create_invite', + "app.org_invite_api_client.create_invite", return_value=sample_org_invite, ) client_request.post( - '.invite_org_user', + ".invite_org_user", org_id=ORGANISATION_ID, - _data={'email_address': 'test@example.gsa.gov'} + _data={"email_address": "test@example.gsa.gov"}, ) mock_invite_org_user.assert_called_once_with( - sample_org_invite['invited_by'], - '{}'.format(ORGANISATION_ID), - 'test@example.gsa.gov', + sample_org_invite["invited_by"], + "{}".format(ORGANISATION_ID), + "test@example.gsa.gov", ) @@ -41,23 +40,25 @@ def test_invite_org_user_errors_when_same_email_as_inviter( sample_org_invite, ): new_org_user_data = { - 'email_address': 'test@user.gsa.gov', + "email_address": "test@user.gsa.gov", } mock_invite_org_user = mocker.patch( - 'app.org_invite_api_client.create_invite', + "app.org_invite_api_client.create_invite", return_value=sample_org_invite, ) page = client_request.post( - '.invite_org_user', + ".invite_org_user", org_id=ORGANISATION_ID, _data=new_org_user_data, - _follow_redirects=True + _follow_redirects=True, ) assert mock_invite_org_user.called is False - assert 'You cannot send an invitation to yourself' in normalize_spaces(page.select_one('.usa-error-message').text) + assert "You cannot send an invitation to yourself" in normalize_spaces( + page.select_one(".usa-error-message").text + ) def test_cancel_invited_org_user_cancels_user_invitations( @@ -68,38 +69,42 @@ def test_cancel_invited_org_user_cancels_user_invitations( mock_get_users_for_organization, mocker, ): - mock_cancel = mocker.patch('app.org_invite_api_client.cancel_invited_user') - mocker.patch('app.org_invite_api_client.get_invited_user_for_org', return_value=sample_org_invite) + mock_cancel = mocker.patch("app.org_invite_api_client.cancel_invited_user") + mocker.patch( + "app.org_invite_api_client.get_invited_user_for_org", + return_value=sample_org_invite, + ) page = client_request.get( - 'main.cancel_invited_org_user', + "main.cancel_invited_org_user", org_id=ORGANISATION_ID, - invited_user_id=sample_org_invite['id'], - _follow_redirects=True + invited_user_id=sample_org_invite["id"], + _follow_redirects=True, ) - assert normalize_spaces(page.h1.text) == 'Team members' + assert normalize_spaces(page.h1.text) == "Team members" flash_banner = normalize_spaces( - page.find('div', class_='banner-default-with-tick').text + page.find("div", class_="banner-default-with-tick").text + ) + assert ( + flash_banner == f"Invitation cancelled for {sample_org_invite['email_address']}" ) - assert flash_banner == f"Invitation cancelled for {sample_org_invite['email_address']}" mock_cancel.assert_called_once_with( org_id=ORGANISATION_ID, - invited_user_id=sample_org_invite['id'], + invited_user_id=sample_org_invite["id"], ) def test_accepted_invite_when_other_user_already_logged_in( - client_request, - mock_check_org_invite_token + client_request, mock_check_org_invite_token ): page = client_request.get( - 'main.accept_org_invite', - token='thisisnotarealtoken', + "main.accept_org_invite", + token="thisisnotarealtoken", follow_redirects=True, _expected_status=403, ) - assert 'This invite is for another email address.' in normalize_spaces( - page.select_one('.banner-dangerous').text + assert "This invite is for another email address." in normalize_spaces( + page.select_one(".banner-dangerous").text ) @@ -109,47 +114,51 @@ def test_cancelled_invite_opened_by_user( api_user_active, mock_check_org_cancelled_invite_token, mock_get_organization, - fake_uuid + fake_uuid, ): client_request.logout() - mock_get_user = mocker.patch('app.user_api_client.get_user', return_value=api_user_active) + mock_get_user = mocker.patch( + "app.user_api_client.get_user", return_value=api_user_active + ) page = client_request.get( - 'main.accept_org_invite', - token='thisisnotarealtoken', + "main.accept_org_invite", + token="thisisnotarealtoken", _follow_redirects=True, ) - assert normalize_spaces( - page.select_one('h1').text - ) == 'The invitation you were sent has been cancelled' - assert normalize_spaces( - page.select('main p')[0].text - ) == 'Test User decided to cancel this invitation.' - assert normalize_spaces( - page.select('main p')[1].text - ) == 'If you need access to Test organization, you’ll have to ask them to invite you again.' + assert ( + normalize_spaces(page.select_one("h1").text) + == "The invitation you were sent has been cancelled" + ) + assert ( + normalize_spaces(page.select("main p")[0].text) + == "Test User decided to cancel this invitation." + ) + assert ( + normalize_spaces(page.select("main p")[1].text) + == "If you need access to Test organization, you’ll have to ask them to invite you again." + ) mock_get_user.assert_called_once_with(fake_uuid) mock_get_organization.assert_called_once_with(ORGANISATION_ID) def test_user_invite_already_accepted( - client_request, - mock_check_org_accepted_invite_token + client_request, mock_check_org_accepted_invite_token ): client_request.logout() client_request.get( - 'main.accept_org_invite', - token='thisisnotarealtoken', + "main.accept_org_invite", + token="thisisnotarealtoken", _expected_redirect=url_for( - 'main.organization_dashboard', + "main.organization_dashboard", org_id=ORGANISATION_ID, ), ) -@freeze_time('2021-12-12 12:12:12') +@freeze_time("2021-12-12 12:12:12") def test_existing_user_invite_already_is_member_of_organization( client_request, mock_check_org_invite_token, @@ -164,25 +173,25 @@ def test_existing_user_invite_already_is_member_of_organization( client_request.logout() mock_update_user_attribute.reset_mock() client_request.get( - 'main.accept_org_invite', - token='thisisnotarealtoken', + "main.accept_org_invite", + token="thisisnotarealtoken", _expected_redirect=url_for( - 'main.organization_dashboard', + "main.organization_dashboard", org_id=ORGANISATION_ID, ), ) - mock_check_org_invite_token.assert_called_once_with('thisisnotarealtoken') + mock_check_org_invite_token.assert_called_once_with("thisisnotarealtoken") mock_accept_org_invite.assert_called_once_with(ORGANISATION_ID, ANY) - mock_get_user_by_email.assert_called_once_with('invited_user@test.gsa.gov') + mock_get_user_by_email.assert_called_once_with("invited_user@test.gsa.gov") mock_get_users_for_organization.assert_called_once_with(ORGANISATION_ID) mock_update_user_attribute.assert_called_once_with( - api_user_active['id'], - email_access_validated_at='2021-12-12T12:12:12', + api_user_active["id"], + email_access_validated_at="2021-12-12T12:12:12", ) -@freeze_time('2021-12-12 12:12:12') +@freeze_time("2021-12-12 12:12:12") def test_existing_user_invite_not_a_member_of_organization( client_request, api_user_active, @@ -196,25 +205,25 @@ def test_existing_user_invite_not_a_member_of_organization( client_request.logout() mock_update_user_attribute.reset_mock() client_request.get( - 'main.accept_org_invite', - token='thisisnotarealtoken', + "main.accept_org_invite", + token="thisisnotarealtoken", _expected_redirect=url_for( - 'main.organization_dashboard', + "main.organization_dashboard", org_id=ORGANISATION_ID, ), ) - mock_check_org_invite_token.assert_called_once_with('thisisnotarealtoken') + mock_check_org_invite_token.assert_called_once_with("thisisnotarealtoken") mock_accept_org_invite.assert_called_once_with(ORGANISATION_ID, ANY) - mock_get_user_by_email.assert_called_once_with('invited_user@test.gsa.gov') + mock_get_user_by_email.assert_called_once_with("invited_user@test.gsa.gov") mock_get_users_for_organization.assert_called_once_with(ORGANISATION_ID) mock_add_user_to_organization.assert_called_once_with( ORGANISATION_ID, - api_user_active['id'], + api_user_active["id"], ) mock_update_user_attribute.assert_called_once_with( - mock_get_user_by_email.side_effect(None)['id'], - email_access_validated_at='2021-12-12T12:12:12', + mock_get_user_by_email.side_effect(None)["id"], + email_access_validated_at="2021-12-12T12:12:12", ) @@ -226,13 +235,13 @@ def test_user_accepts_invite( ): client_request.logout() client_request.get( - 'main.accept_org_invite', - token='thisisnotarealtoken', - _expected_redirect=url_for('main.register_from_org_invite') + "main.accept_org_invite", + token="thisisnotarealtoken", + _expected_redirect=url_for("main.register_from_org_invite"), ) - mock_check_org_invite_token.assert_called_once_with('thisisnotarealtoken') - mock_dont_get_user_by_email.assert_called_once_with('invited_user@test.gsa.gov') + mock_check_org_invite_token.assert_called_once_with("thisisnotarealtoken") + mock_dont_get_user_by_email.assert_called_once_with("invited_user@test.gsa.gov") mock_get_users_for_organization.assert_called_once_with(ORGANISATION_ID) @@ -241,23 +250,32 @@ def test_registration_from_org_invite_404s_if_user_not_in_session( ): client_request.logout() client_request.get( - 'main.register_from_org_invite', + "main.register_from_org_invite", _expected_status=404, ) -@pytest.mark.parametrize('data, error', [ - [{ - 'name': 'Bad Mobile', - 'mobile_number': 'not good', - 'password': 'validPassword!' - }, 'The string supplied did not seem to be a phone number'], - [{ - 'name': 'Bad Password', - 'mobile_number': '+12021234123', - 'password': 'password' - }, 'Choose a password that’s harder to guess'], -]) +@pytest.mark.parametrize( + "data, error", + [ + [ + { + "name": "Bad Mobile", + "mobile_number": "not good", + "password": "validPassword!", + }, + "The string supplied did not seem to be a phone number", + ], + [ + { + "name": "Bad Password", + "mobile_number": "+12021234123", + "password": "password", + }, + "Choose a password that’s harder to guess", + ], + ], +) def test_registration_from_org_invite_has_bad_data( client_request, sample_org_invite, @@ -268,10 +286,10 @@ def test_registration_from_org_invite_has_bad_data( client_request.logout() with client_request.session_transaction() as session: - session['invited_org_user_id'] = sample_org_invite['id'] + session["invited_org_user_id"] = sample_org_invite["id"] page = client_request.post( - 'main.register_from_org_invite', + "main.register_from_org_invite", _data=data, _expected_status=200, ) @@ -279,11 +297,10 @@ def test_registration_from_org_invite_has_bad_data( assert error in page.text -@pytest.mark.parametrize('diff_data', [ - ['email_address'], - ['organization'], - ['email_address', 'organization'] -]) +@pytest.mark.parametrize( + "diff_data", + [["email_address"], ["organization"], ["email_address", "organization"]], +) def test_registration_from_org_invite_has_different_email_or_organization( client_request, sample_org_invite, @@ -292,20 +309,20 @@ def test_registration_from_org_invite_has_different_email_or_organization( ): client_request.logout() with client_request.session_transaction() as session: - session['invited_org_user_id'] = sample_org_invite['id'] + session["invited_org_user_id"] = sample_org_invite["id"] data = { - 'name': 'Test User', - 'mobile_number': '+12024900460', - 'password': 'validPassword!', - 'email_address': sample_org_invite['email_address'], - 'organization': sample_org_invite['organization'] + "name": "Test User", + "mobile_number": "+12024900460", + "password": "validPassword!", + "email_address": sample_org_invite["email_address"], + "organization": sample_org_invite["organization"], } for field in diff_data: - data[field] = 'different' + data[field] = "different" client_request.post( - 'main.register_from_org_invite', + "main.register_from_org_invite", _data=data, _expected_status=400, ) @@ -323,23 +340,21 @@ def test_org_user_registers_with_email_already_in_use( ): client_request.logout() with client_request.session_transaction() as session: - session['invited_org_user_id'] = sample_org_invite['id'] + session["invited_org_user_id"] = sample_org_invite["id"] client_request.post( - 'main.register_from_org_invite', + "main.register_from_org_invite", _data={ - 'name': 'Test User', - 'mobile_number': '+12024900460', - 'password': 'validPassword!', - 'email_address': sample_org_invite['email_address'], - 'organization': sample_org_invite['organization'], + "name": "Test User", + "mobile_number": "+12024900460", + "password": "validPassword!", + "email_address": sample_org_invite["email_address"], + "organization": sample_org_invite["organization"], }, - _expected_redirect=url_for('main.verify'), + _expected_redirect=url_for("main.verify"), ) - mock_get_user_by_email.assert_called_once_with( - sample_org_invite['email_address'] - ) + mock_get_user_by_email.assert_called_once_with(sample_org_invite["email_address"]) assert mock_register_user.called is False assert mock_send_already_registered_email.called is False @@ -358,34 +373,34 @@ def test_org_user_registration( ): client_request.logout() with client_request.session_transaction() as session: - session['invited_org_user_id'] = sample_org_invite['id'] + session["invited_org_user_id"] = sample_org_invite["id"] client_request.post( - 'main.register_from_org_invite', + "main.register_from_org_invite", _data={ - 'name': 'Test User', - 'email_address': sample_org_invite['email_address'], - 'mobile_number': '+12024900460', - 'password': 'validPassword!', - 'organization': sample_org_invite['organization'], + "name": "Test User", + "email_address": sample_org_invite["email_address"], + "mobile_number": "+12024900460", + "password": "validPassword!", + "organization": sample_org_invite["organization"], }, - _expected_redirect=url_for('main.verify') + _expected_redirect=url_for("main.verify"), ) assert mock_get_user_by_email.called is False mock_register_user.assert_called_once_with( - 'Test User', - sample_org_invite['email_address'], - '+12024900460', - 'validPassword!', - 'sms_auth' + "Test User", + sample_org_invite["email_address"], + "+12024900460", + "validPassword!", + "sms_auth", ) mock_send_verify_code.assert_called_once_with( - '6ce466d0-fd6a-11e5-82f5-e0accb9d11a6', - 'sms', - '+12024900460', + "6ce466d0-fd6a-11e5-82f5-e0accb9d11a6", + "sms", + "+12024900460", ) - mock_get_invited_org_user_by_id.assert_called_once_with(sample_org_invite['id']) + mock_get_invited_org_user_by_id.assert_called_once_with(sample_org_invite["id"]) def test_verified_org_user_redirects_to_dashboard( @@ -399,15 +414,18 @@ def test_verified_org_user_redirects_to_dashboard( client_request.logout() invited_org_user = InvitedOrgUser(sample_org_invite).serialize() with client_request.session_transaction() as session: - session['expiry_date'] = str(datetime.utcnow() + timedelta(hours=1)) - session['user_details'] = {"email": invited_org_user['email_address'], "id": invited_org_user['id']} - session['organization_id'] = invited_org_user['organization'] + session["expiry_date"] = str(datetime.utcnow() + timedelta(hours=1)) + session["user_details"] = { + "email": invited_org_user["email_address"], + "id": invited_org_user["id"], + } + session["organization_id"] = invited_org_user["organization"] client_request.post( - 'main.verify', - _data={'sms_code': '123456'}, + "main.verify", + _data={"sms_code": "123456"}, _expected_redirect=url_for( - 'main.organization_dashboard', - org_id=invited_org_user['organization'], + "main.organization_dashboard", + org_id=invited_org_user["organization"], ), ) diff --git a/tests/app/main/views/organizations/test_organizations.py b/tests/app/main/views/organizations/test_organizations.py index 57fc14cfd..00ddee7a1 100644 --- a/tests/app/main/views/organizations/test_organizations.py +++ b/tests/app/main/views/organizations/test_organizations.py @@ -15,79 +15,78 @@ from tests.conftest import ( def test_organization_page_shows_all_organizations( - client_request, - platform_admin_user, - mocker + client_request, platform_admin_user, mocker ): orgs = [ - {'id': 'A3', 'name': 'Test 3', 'active': True, 'count_of_live_services': 0}, - {'id': 'B1', 'name': 'Test 1', 'active': True, 'count_of_live_services': 1}, - {'id': 'C2', 'name': 'Test 2', 'active': False, 'count_of_live_services': 2}, + {"id": "A3", "name": "Test 3", "active": True, "count_of_live_services": 0}, + {"id": "B1", "name": "Test 1", "active": True, "count_of_live_services": 1}, + {"id": "C2", "name": "Test 2", "active": False, "count_of_live_services": 2}, ] get_organizations = mocker.patch( - 'app.models.organization.AllOrganizations.client_method', return_value=orgs + "app.models.organization.AllOrganizations.client_method", return_value=orgs ) client_request.login(platform_admin_user) - page = client_request.get('.organizations') + page = client_request.get(".organizations") - assert normalize_spaces( - page.select_one('h1').text - ) == "Organizations" + assert normalize_spaces(page.select_one("h1").text) == "Organizations" assert [ ( normalize_spaces(link.text), normalize_spaces(hint.text), - link['href'], - ) for link, hint in zip( - page.select('.browse-list-item a'), - page.select('.browse-list-item .browse-list-hint'), + link["href"], + ) + for link, hint in zip( + page.select(".browse-list-item a"), + page.select(".browse-list-item .browse-list-hint"), ) ] == [ - ('Test 1', '1 live service', url_for( - 'main.organization_dashboard', org_id='B1' - )), - ('Test 2', '2 live services', url_for( - 'main.organization_dashboard', org_id='C2' - )), - ('Test 3', '0 live services', url_for( - 'main.organization_dashboard', org_id='A3' - )), + ( + "Test 1", + "1 live service", + url_for("main.organization_dashboard", org_id="B1"), + ), + ( + "Test 2", + "2 live services", + url_for("main.organization_dashboard", org_id="C2"), + ), + ( + "Test 3", + "0 live services", + url_for("main.organization_dashboard", org_id="A3"), + ), ] - archived = page.select_one('.table-field-status-default.heading-medium') - assert normalize_spaces(archived.text) == '- archived' - assert normalize_spaces(archived.parent.text) == 'Test 2 - archived 2 live services' + archived = page.select_one(".table-field-status-default.heading-medium") + assert normalize_spaces(archived.text) == "- archived" + assert normalize_spaces(archived.parent.text) == "Test 2 - archived 2 live services" - assert normalize_spaces( - page.select_one('a.govuk-button--secondary').text - ) == 'New organization' + assert ( + normalize_spaces(page.select_one("a.govuk-button--secondary").text) + == "New organization" + ) get_organizations.assert_called_once_with() -def test_view_organization_shows_the_correct_organization( - client_request, - mocker -): - org = {'id': ORGANISATION_ID, 'name': 'Test 1', 'active': True} +def test_view_organization_shows_the_correct_organization(client_request, mocker): + org = {"id": ORGANISATION_ID, "name": "Test 1", "active": True} + mocker.patch("app.organizations_client.get_organization", return_value=org) mocker.patch( - 'app.organizations_client.get_organization', return_value=org - ) - mocker.patch( - 'app.organizations_client.get_services_and_usage', return_value={'services': {}} + "app.organizations_client.get_services_and_usage", return_value={"services": {}} ) page = client_request.get( - '.organization_dashboard', + ".organization_dashboard", org_id=ORGANISATION_ID, ) - assert normalize_spaces(page.select_one('h1').text) == 'Usage' - assert normalize_spaces(page.select_one('.govuk-hint').text) == ( - 'Test 1 has no live services on Notify.gov' + assert normalize_spaces(page.select_one("h1").text) == "Usage" + assert normalize_spaces(page.select_one(".govuk-hint").text) == ( + "Test 1 has no live services on Notify.gov" ) - assert not page.select('a[download]') + assert not page.select("a[download]") def test_page_to_create_new_organization( @@ -96,22 +95,22 @@ def test_page_to_create_new_organization( mocker, ): client_request.login(platform_admin_user) - page = client_request.get('.add_organization') + page = client_request.get(".add_organization") assert [ - (input['type'], input['name'], input.get('value')) - for input in page.select('input') + (input["type"], input["name"], input.get("value")) + for input in page.select("input") ] == [ - ('text', 'name', None), - ('radio', 'organization_type', 'federal'), - ('radio', 'organization_type', 'state'), + ("text", "name", None), + ("radio", "organization_type", "federal"), + ("radio", "organization_type", "state"), # ('radio', 'organization_type', 'nhs_central'), # ('radio', 'organization_type', 'nhs_local'), # ('radio', 'organization_type', 'nhs_gp'), # ('radio', 'organization_type', 'emergency_service'), # ('radio', 'organization_type', 'school_or_college'), - ('radio', 'organization_type', 'other'), - ('hidden', 'csrf_token', mocker.ANY), + ("radio", "organization_type", "other"), + ("hidden", "csrf_token", mocker.ANY), ] @@ -121,26 +120,26 @@ def test_create_new_organization( mocker, ): mock_create_organization = mocker.patch( - 'app.organizations_client.create_organization', + "app.organizations_client.create_organization", return_value=organization_json(ORGANISATION_ID), ) client_request.login(platform_admin_user) client_request.post( - '.add_organization', + ".add_organization", _data={ - 'name': 'new name', - 'organization_type': 'federal', + "name": "new name", + "organization_type": "federal", }, _expected_redirect=url_for( - 'main.organization_settings', + "main.organization_settings", org_id=ORGANISATION_ID, ), ) mock_create_organization.assert_called_once_with( - name='new name', - organization_type='federal', + name="new name", + organization_type="federal", ) @@ -150,29 +149,32 @@ def test_create_new_organization_validates( mocker, ): mock_create_organization = mocker.patch( - 'app.organizations_client.create_organization' + "app.organizations_client.create_organization" ) client_request.login(platform_admin_user) page = client_request.post( - '.add_organization', + ".add_organization", _expected_status=200, ) assert [ - (error['data-error-label'], normalize_spaces(error.text)) - for error in page.select('.usa-error-message') + (error["data-error-label"], normalize_spaces(error.text)) + for error in page.select(".usa-error-message") ] == [ - ('name', 'Error: Cannot be empty'), - ('organization_type', 'Error: Select the type of organization'), + ("name", "Error: Cannot be empty"), + ("organization_type", "Error: Select the type of organization"), ] assert mock_create_organization.called is False -@pytest.mark.parametrize('name, error_message', [ - ('', 'Cannot be empty'), - ('a', 'at least two alphanumeric characters'), - ('a' * 256, 'Organization name must be 255 characters or fewer'), -]) +@pytest.mark.parametrize( + "name, error_message", + [ + ("", "Cannot be empty"), + ("a", "at least two alphanumeric characters"), + ("a" * 256, "Organization name must be 255 characters or fewer"), + ], +) def test_create_new_organization_fails_with_incorrect_input( client_request, platform_admin_user, @@ -181,20 +183,20 @@ def test_create_new_organization_fails_with_incorrect_input( error_message, ): mock_create_organization = mocker.patch( - 'app.organizations_client.create_organization' + "app.organizations_client.create_organization" ) client_request.login(platform_admin_user) page = client_request.post( - '.add_organization', + ".add_organization", _data={ - 'name': name, - 'organization_type': 'local', + "name": name, + "organization_type": "local", }, _expected_status=200, ) assert mock_create_organization.called is False - assert error_message in page.select_one('.usa-error-message').text + assert error_message in page.select_one(".usa-error-message").text def test_create_new_organization_fails_with_duplicate_name( @@ -203,36 +205,38 @@ def test_create_new_organization_fails_with_duplicate_name( mocker, ): def _create(**_kwargs): - json_mock = mocker.Mock(return_value={'message': 'Organization name already exists'}) + json_mock = mocker.Mock( + return_value={"message": "Organization name already exists"} + ) resp_mock = mocker.Mock(status_code=400, json=json_mock) http_error = HTTPError(response=resp_mock, message="Default message") raise http_error - mocker.patch( - 'app.organizations_client.create_organization', - side_effect=_create - ) + mocker.patch("app.organizations_client.create_organization", side_effect=_create) client_request.login(platform_admin_user) page = client_request.post( - '.add_organization', + ".add_organization", _data={ - 'name': 'Existing org', - 'organization_type': 'federal', + "name": "Existing org", + "organization_type": "federal", }, _expected_status=200, ) - error_message = 'This organization name is already in use' - assert error_message in page.select_one('.usa-error-message').text + error_message = "This organization name is already in use" + assert error_message in page.select_one(".usa-error-message").text -@pytest.mark.parametrize('organization_type, organization, expected_status', ( - ('nhs_gp', None, 200), - ('central', None, 403), - ('nhs_gp', organization_json(organization_type='nhs_gp'), 403), -)) -@pytest.mark.skip(reason='Update for TTS') +@pytest.mark.parametrize( + "organization_type, organization, expected_status", + ( + ("nhs_gp", None, 200), + ("central", None, 403), + ("nhs_gp", organization_json(organization_type="nhs_gp"), 403), + ), +) +@pytest.mark.skip(reason="Update for TTS") def test_gps_can_create_own_organizations( client_request, mocker, @@ -242,11 +246,11 @@ def test_gps_can_create_own_organizations( organization, expected_status, ): - mocker.patch('app.organizations_client.get_organization', return_value=organization) - service_one['organization_type'] = organization_type + mocker.patch("app.organizations_client.get_organization", return_value=organization) + service_one["organization_type"] = organization_type page = client_request.get( - '.add_organization_from_gp_service', + ".add_organization_from_gp_service", service_id=SERVICE_ONE_ID, _expected_status=expected_status, ) @@ -254,31 +258,32 @@ def test_gps_can_create_own_organizations( if expected_status == 403: return - assert page.select_one('input[type=text]')['name'] == 'name' - assert normalize_spaces( - page.select_one('label[for=name]').text - ) == ( - 'What’s your practice called?' + assert page.select_one("input[type=text]")["name"] == "name" + assert normalize_spaces(page.select_one("label[for=name]").text) == ( + "What’s your practice called?" ) -@pytest.mark.parametrize('data, expected_service_name', ( +@pytest.mark.parametrize( + "data, expected_service_name", ( - { - 'same_as_service_name': False, - 'name': 'Dr. Example', - }, - 'Dr. Example', + ( + { + "same_as_service_name": False, + "name": "Dr. Example", + }, + "Dr. Example", + ), + ( + { + "same_as_service_name": True, + "name": "This is ignored", + }, + "service one", + ), ), - ( - { - 'same_as_service_name': True, - 'name': 'This is ignored', - }, - 'service one', - ), -)) -@pytest.mark.skip(reason='Update for TTS') +) +@pytest.mark.skip(reason="Update for TTS") def test_gps_can_name_their_organization( client_request, mocker, @@ -287,14 +292,14 @@ def test_gps_can_name_their_organization( data, expected_service_name, ): - service_one['organization_type'] = 'nhs_gp' + service_one["organization_type"] = "nhs_gp" mock_create_organization = mocker.patch( - 'app.organizations_client.create_organization', + "app.organizations_client.create_organization", return_value=organization_json(ORGANISATION_ID), ) client_request.post( - '.add_organization_from_gp_service', + ".add_organization_from_gp_service", service_id=SERVICE_ONE_ID, _data=data, _expected_status=302, @@ -302,27 +307,32 @@ def test_gps_can_name_their_organization( mock_create_organization.assert_called_once_with( name=expected_service_name, - organization_type='nhs_gp', + organization_type="nhs_gp", + ) + mock_update_service_organization.assert_called_once_with( + SERVICE_ONE_ID, ORGANISATION_ID ) - mock_update_service_organization.assert_called_once_with(SERVICE_ONE_ID, ORGANISATION_ID) -@pytest.mark.parametrize('data, expected_error', ( +@pytest.mark.parametrize( + "data, expected_error", ( - { - 'name': 'Dr. Example', - }, - 'Select yes or no', + ( + { + "name": "Dr. Example", + }, + "Select yes or no", + ), + ( + { + "same_as_service_name": False, + "name": "", + }, + "Cannot be empty", + ), ), - ( - { - 'same_as_service_name': False, - 'name': '', - }, - 'Cannot be empty', - ), -)) -@pytest.mark.skip(reason='Update for TTS') +) +@pytest.mark.skip(reason="Update for TTS") def test_validation_of_gps_creating_organizations( client_request, mocker, @@ -330,14 +340,14 @@ def test_validation_of_gps_creating_organizations( data, expected_error, ): - service_one['organization_type'] = 'nhs_gp' + service_one["organization_type"] = "nhs_gp" page = client_request.post( - '.add_organization_from_gp_service', + ".add_organization_from_gp_service", service_id=SERVICE_ONE_ID, _data=data, _expected_status=200, ) - assert expected_error in page.select_one('.usa-error-message, .error-message').text + assert expected_error in page.select_one(".usa-error-message, .error-message").text @freeze_time("2020-02-20 20:20") @@ -349,41 +359,61 @@ def test_organization_services_shows_live_services_and_usage( fake_uuid, ): mock = mocker.patch( - 'app.organizations_client.get_services_and_usage', - return_value={"services": [ - {'service_id': SERVICE_ONE_ID, 'service_name': '1', 'chargeable_billable_sms': 250122, 'emails_sent': 13000, - 'free_sms_limit': 250000, 'sms_billable_units': 122, 'sms_cost': 0, - 'sms_remainder': None}, - {'service_id': SERVICE_TWO_ID, 'service_name': '5', 'chargeable_billable_sms': 0, 'emails_sent': 20000, - 'free_sms_limit': 250000, 'sms_billable_units': 2500, 'sms_cost': 42.0, - 'sms_remainder': None} - ]} + "app.organizations_client.get_services_and_usage", + return_value={ + "services": [ + { + "service_id": SERVICE_ONE_ID, + "service_name": "1", + "chargeable_billable_sms": 250122, + "emails_sent": 13000, + "free_sms_limit": 250000, + "sms_billable_units": 122, + "sms_cost": 0, + "sms_remainder": None, + }, + { + "service_id": SERVICE_TWO_ID, + "service_name": "5", + "chargeable_billable_sms": 0, + "emails_sent": 20000, + "free_sms_limit": 250000, + "sms_billable_units": 2500, + "sms_cost": 42.0, + "sms_remainder": None, + }, + ] + }, ) client_request.login(active_user_with_permissions) - page = client_request.get('.organization_dashboard', org_id=ORGANISATION_ID) + page = client_request.get(".organization_dashboard", org_id=ORGANISATION_ID) mock.assert_called_once_with(ORGANISATION_ID, 2019) - services = page.select('main h3') - usage_rows = page.select('main .govuk-grid-column-one-half') + services = page.select("main h3") + usage_rows = page.select("main .govuk-grid-column-one-half") assert len(services) == 2 # Totals assert normalize_spaces(usage_rows[0].text) == "Emails 33,000 sent" assert normalize_spaces(usage_rows[1].text) == "Text messages $42.00 spent" - assert normalize_spaces(services[0].text) == '1' - assert normalize_spaces(services[1].text) == '5' - assert services[0].find('a')['href'] == url_for('main.usage', service_id=SERVICE_ONE_ID) + assert normalize_spaces(services[0].text) == "1" + assert normalize_spaces(services[1].text) == "5" + assert services[0].find("a")["href"] == url_for( + "main.usage", service_id=SERVICE_ONE_ID + ) assert normalize_spaces(usage_rows[2].text) == "13,000 emails sent" assert normalize_spaces(usage_rows[3].text) == "122 free text messages sent" - assert services[1].find('a')['href'] == url_for('main.usage', service_id=SERVICE_TWO_ID) + assert services[1].find("a")["href"] == url_for( + "main.usage", service_id=SERVICE_TWO_ID + ) assert normalize_spaces(usage_rows[4].text) == "20,000 emails sent" assert normalize_spaces(usage_rows[5].text) == "$42.00 spent on text messages" # Ensure there’s no ‘this org has no services message’ - assert not page.select('.govuk-hint') + assert not page.select(".govuk-hint") @freeze_time("2020-02-20 20:20") @@ -395,18 +425,27 @@ def test_organization_services_shows_live_services_and_usage_with_count_of_1( fake_uuid, ): mocker.patch( - 'app.organizations_client.get_services_and_usage', - return_value={"services": [ - {'service_id': SERVICE_ONE_ID, 'service_name': '1', 'chargeable_billable_sms': 1, 'emails_sent': 1, - 'free_sms_limit': 250000, 'sms_billable_units': 1, 'sms_cost': 0, - 'sms_remainder': None}, - ]} + "app.organizations_client.get_services_and_usage", + return_value={ + "services": [ + { + "service_id": SERVICE_ONE_ID, + "service_name": "1", + "chargeable_billable_sms": 1, + "emails_sent": 1, + "free_sms_limit": 250000, + "sms_billable_units": 1, + "sms_cost": 0, + "sms_remainder": None, + }, + ] + }, ) client_request.login(active_user_with_permissions) - page = client_request.get('.organization_dashboard', org_id=ORGANISATION_ID) + page = client_request.get(".organization_dashboard", org_id=ORGANISATION_ID) - usage_rows = page.select('main .govuk-grid-column-one-half') + usage_rows = page.select("main .govuk-grid-column-one-half") # Totals assert normalize_spaces(usage_rows[0].text) == "Emails 1 sent" @@ -417,11 +456,14 @@ def test_organization_services_shows_live_services_and_usage_with_count_of_1( @freeze_time("2020-02-20 20:20") -@pytest.mark.parametrize('financial_year, expected_selected', ( - (2017, '2017 to 2018 fiscal year'), - (2018, '2018 to 2019 fiscal year'), - (2019, '2019 to 2020 fiscal year'), -)) +@pytest.mark.parametrize( + "financial_year, expected_selected", + ( + (2017, "2017 to 2018 fiscal year"), + (2018, "2018 to 2019 fiscal year"), + (2019, "2019 to 2020 fiscal year"), + ), +) def test_organization_services_filters_by_financial_year( client_request, mock_get_organization, @@ -432,21 +474,20 @@ def test_organization_services_filters_by_financial_year( expected_selected, ): mock = mocker.patch( - 'app.organizations_client.get_services_and_usage', - return_value={"services": []} + "app.organizations_client.get_services_and_usage", return_value={"services": []} ) page = client_request.get( - '.organization_dashboard', + ".organization_dashboard", org_id=ORGANISATION_ID, year=financial_year, ) mock.assert_called_once_with(ORGANISATION_ID, financial_year) - assert normalize_spaces(page.select_one('.pill').text) == ( - '2019 to 2020 fiscal year ' - '2018 to 2019 fiscal year ' - '2017 to 2018 fiscal year' + assert normalize_spaces(page.select_one(".pill").text) == ( + "2019 to 2020 fiscal year " + "2018 to 2019 fiscal year " + "2017 to 2018 fiscal year" ) - assert normalize_spaces(page.select_one('.pill-item--selected').text) == ( + assert normalize_spaces(page.select_one(".pill-item--selected").text) == ( expected_selected ) @@ -460,40 +501,43 @@ def test_organization_services_shows_search_bar( fake_uuid, ): mocker.patch( - 'app.organizations_client.get_services_and_usage', - return_value={"services": [ - { - 'service_id': SERVICE_ONE_ID, - 'service_name': 'Service 1', - 'chargeable_billable_sms': 250122, - 'emails_sent': 13000, - 'free_sms_limit': 250000, - 'sms_billable_units': 122, - 'sms_cost': 1.93, - 'sms_remainder': None - }, - ] * 8} + "app.organizations_client.get_services_and_usage", + return_value={ + "services": [ + { + "service_id": SERVICE_ONE_ID, + "service_name": "Service 1", + "chargeable_billable_sms": 250122, + "emails_sent": 13000, + "free_sms_limit": 250000, + "sms_billable_units": 122, + "sms_cost": 1.93, + "sms_remainder": None, + }, + ] + * 8 + }, ) client_request.login(active_user_with_permissions) - page = client_request.get('.organization_dashboard', org_id=ORGANISATION_ID) + page = client_request.get(".organization_dashboard", org_id=ORGANISATION_ID) - services = page.select('.organization-service') + services = page.select(".organization-service") assert len(services) == 8 - assert page.select_one('.live-search')['data-targets'] == '.organization-service' + assert page.select_one(".live-search")["data-targets"] == ".organization-service" assert [ normalize_spaces(service_name.text) - for service_name in page.select('.live-search-relevant') + for service_name in page.select(".live-search-relevant") ] == [ - 'Service 1', - 'Service 1', - 'Service 1', - 'Service 1', - 'Service 1', - 'Service 1', - 'Service 1', - 'Service 1', + "Service 1", + "Service 1", + "Service 1", + "Service 1", + "Service 1", + "Service 1", + "Service 1", + "Service 1", ] @@ -506,27 +550,30 @@ def test_organization_services_hides_search_bar_for_7_or_fewer_services( fake_uuid, ): mocker.patch( - 'app.organizations_client.get_services_and_usage', - return_value={"services": [ - { - 'service_id': SERVICE_ONE_ID, - 'service_name': 'Service 1', - 'chargeable_billable_sms': 250122, - 'emails_sent': 13000, - 'free_sms_limit': 250000, - 'sms_billable_units': 122, - 'sms_cost': 1.93, - 'sms_remainder': None - }, - ] * 7} + "app.organizations_client.get_services_and_usage", + return_value={ + "services": [ + { + "service_id": SERVICE_ONE_ID, + "service_name": "Service 1", + "chargeable_billable_sms": 250122, + "emails_sent": 13000, + "free_sms_limit": 250000, + "sms_billable_units": 122, + "sms_cost": 1.93, + "sms_remainder": None, + }, + ] + * 7 + }, ) client_request.login(active_user_with_permissions) - page = client_request.get('.organization_dashboard', org_id=ORGANISATION_ID) + page = client_request.get(".organization_dashboard", org_id=ORGANISATION_ID) - services = page.select('.organization-service') + services = page.select(".organization-service") assert len(services) == 7 - assert not page.select_one('.live-search') + assert not page.select_one(".live-search") @freeze_time("2021-11-12 11:09:00.061258") @@ -538,29 +585,32 @@ def test_organization_services_links_to_downloadable_report( fake_uuid, ): mocker.patch( - 'app.organizations_client.get_services_and_usage', - return_value={"services": [ - { - 'service_id': SERVICE_ONE_ID, - 'service_name': 'Service 1', - 'chargeable_billable_sms': 250122, - 'emails_sent': 13000, - 'free_sms_limit': 250000, - 'sms_billable_units': 122, - 'sms_cost': 1.93, - 'sms_remainder': None - }, - ] * 2} + "app.organizations_client.get_services_and_usage", + return_value={ + "services": [ + { + "service_id": SERVICE_ONE_ID, + "service_name": "Service 1", + "chargeable_billable_sms": 250122, + "emails_sent": 13000, + "free_sms_limit": 250000, + "sms_billable_units": 122, + "sms_cost": 1.93, + "sms_remainder": None, + }, + ] + * 2 + }, ) client_request.login(active_user_with_permissions) - page = client_request.get('.organization_dashboard', org_id=ORGANISATION_ID) + page = client_request.get(".organization_dashboard", org_id=ORGANISATION_ID) - link_to_report = page.select_one('a[download]') - assert normalize_spaces(link_to_report.text) == 'Download this report (CSV)' + link_to_report = page.select_one("a[download]") + assert normalize_spaces(link_to_report.text) == "Download this report (CSV)" assert link_to_report.attrs["href"] == url_for( - '.download_organization_usage_report', + ".download_organization_usage_report", org_id=ORGANISATION_ID, - selected_year=2021 + selected_year=2021, ) @@ -573,36 +623,38 @@ def test_download_organization_usage_report( fake_uuid, ): mocker.patch( - 'app.organizations_client.get_services_and_usage', - return_value={"services": [ - { - 'service_id': SERVICE_ONE_ID, - 'service_name': 'Service 1', - 'chargeable_billable_sms': 22, - 'emails_sent': 13000, - 'free_sms_limit': 100, - 'sms_billable_units': 122, - 'sms_cost': 1.934, - 'sms_remainder': 0 - }, - { - 'service_id': SERVICE_TWO_ID, - 'service_name': 'Service 1', - 'chargeable_billable_sms': 222, - 'emails_sent': 23000, - 'free_sms_limit': 250000, - 'sms_billable_units': 322, - 'sms_cost': 3.935, - 'sms_remainder': 0 - }, - ]} + "app.organizations_client.get_services_and_usage", + return_value={ + "services": [ + { + "service_id": SERVICE_ONE_ID, + "service_name": "Service 1", + "chargeable_billable_sms": 22, + "emails_sent": 13000, + "free_sms_limit": 100, + "sms_billable_units": 122, + "sms_cost": 1.934, + "sms_remainder": 0, + }, + { + "service_id": SERVICE_TWO_ID, + "service_name": "Service 1", + "chargeable_billable_sms": 222, + "emails_sent": 23000, + "free_sms_limit": 250000, + "sms_billable_units": 322, + "sms_cost": 3.935, + "sms_remainder": 0, + }, + ] + }, ) client_request.login(active_user_with_permissions) csv_report = client_request.get( - '.download_organization_usage_report', + ".download_organization_usage_report", org_id=ORGANISATION_ID, selected_year=2021, - _test_page_title=False + _test_page_title=False, ) assert csv_report.string == ( @@ -621,28 +673,32 @@ def test_organization_trial_mode_services_shows_all_non_live_services( fake_uuid, ): mocker.patch( - 'app.organizations_client.get_organization_services', + "app.organizations_client.get_organization_services", return_value=[ - service_json(id_='1', name='1', restricted=False, active=True), # live - service_json(id_='2', name='2', restricted=True, active=True), # trial - service_json(id_='3', name='3', restricted=False, active=False), # archived - ] + service_json(id_="1", name="1", restricted=False, active=True), # live + service_json(id_="2", name="2", restricted=True, active=True), # trial + service_json(id_="3", name="3", restricted=False, active=False), # archived + ], ) client_request.login(platform_admin_user) page = client_request.get( - '.organization_trial_mode_services', + ".organization_trial_mode_services", org_id=ORGANISATION_ID, - _test_page_title=False + _test_page_title=False, ) - services = page.select('.browse-list-item') + services = page.select(".browse-list-item") assert len(services) == 2 - assert normalize_spaces(services[0].text) == '2' - assert normalize_spaces(services[1].text) == '3' - assert services[0].find('a')['href'] == url_for('main.service_dashboard', service_id='2') - assert services[1].find('a')['href'] == url_for('main.service_dashboard', service_id='3') + assert normalize_spaces(services[0].text) == "2" + assert normalize_spaces(services[1].text) == "3" + assert services[0].find("a")["href"] == url_for( + "main.service_dashboard", service_id="2" + ) + assert services[1].find("a")["href"] == url_for( + "main.service_dashboard", service_id="3" + ) def test_organization_trial_mode_services_doesnt_work_if_not_platform_admin( @@ -650,9 +706,9 @@ def test_organization_trial_mode_services_doesnt_work_if_not_platform_admin( mock_get_organization, ): client_request.get( - '.organization_trial_mode_services', + ".organization_trial_mode_services", org_id=ORGANISATION_ID, - _expected_status=403 + _expected_status=403, ) @@ -663,30 +719,41 @@ def test_manage_org_users_shows_correct_link_next_to_each_user( mock_get_invited_users_for_organization, ): page = client_request.get( - '.manage_org_users', + ".manage_org_users", org_id=ORGANISATION_ID, ) # No banner confirming a user to be deleted shown - assert not page.select_one('.banner-dangerous') + assert not page.select_one(".banner-dangerous") - users = page.find_all(class_='user-list-item') + users = page.find_all(class_="user-list-item") # The first user is an invited user, so has the link to cancel the invitation. # The second two users are active users, so have the link to be removed from the org - assert normalize_spaces( - users[0].text - ) == 'invited_user@test.gsa.gov (invited) Cancel invitation for invited_user@test.gsa.gov' - assert normalize_spaces(users[1].text) == 'Test User 1 test@gsa.gov Remove Test User 1 test@gsa.gov' - assert normalize_spaces(users[2].text) == 'Test User 2 testt@gsa.gov Remove Test User 2 testt@gsa.gov' - - assert users[0].a['href'] == url_for( - '.cancel_invited_org_user', - org_id=ORGANISATION_ID, - invited_user_id='73616d70-6c65-4f6f-b267-5f696e766974' + assert ( + normalize_spaces(users[0].text) + == "invited_user@test.gsa.gov (invited) Cancel invitation for invited_user@test.gsa.gov" + ) + assert ( + normalize_spaces(users[1].text) + == "Test User 1 test@gsa.gov Remove Test User 1 test@gsa.gov" + ) + assert ( + normalize_spaces(users[2].text) + == "Test User 2 testt@gsa.gov Remove Test User 2 testt@gsa.gov" + ) + + assert users[0].a["href"] == url_for( + ".cancel_invited_org_user", + org_id=ORGANISATION_ID, + invited_user_id="73616d70-6c65-4f6f-b267-5f696e766974", + ) + assert users[1].a["href"] == url_for( + ".edit_organization_user", org_id=ORGANISATION_ID, user_id="1234" + ) + assert users[2].a["href"] == url_for( + ".edit_organization_user", org_id=ORGANISATION_ID, user_id="5678" ) - assert users[1].a['href'] == url_for('.edit_organization_user', org_id=ORGANISATION_ID, user_id='1234') - assert users[2].a['href'] == url_for('.edit_organization_user', org_id=ORGANISATION_ID, user_id='5678') def test_manage_org_users_shows_no_link_for_cancelled_users( @@ -696,23 +763,32 @@ def test_manage_org_users_shows_no_link_for_cancelled_users( sample_org_invite, mocker, ): - sample_org_invite['status'] = 'cancelled' - mocker.patch('app.models.user.OrganizationInvitedUsers.client_method', return_value=[sample_org_invite]) + sample_org_invite["status"] = "cancelled" + mocker.patch( + "app.models.user.OrganizationInvitedUsers.client_method", + return_value=[sample_org_invite], + ) page = client_request.get( - '.manage_org_users', + ".manage_org_users", org_id=ORGANISATION_ID, ) - users = page.find_all(class_='user-list-item') + users = page.find_all(class_="user-list-item") - assert normalize_spaces(users[0].text) == 'invited_user@test.gsa.gov (cancelled invite)' + assert ( + normalize_spaces(users[0].text) + == "invited_user@test.gsa.gov (cancelled invite)" + ) assert not users[0].a -@pytest.mark.parametrize('number_of_users', ( - pytest.param(8), - pytest.param(800), -)) +@pytest.mark.parametrize( + "number_of_users", + ( + pytest.param(8), + pytest.param(800), + ), +) def test_manage_org_users_should_show_live_search_if_more_than_7_users( client_request, mocker, @@ -721,43 +797,45 @@ def test_manage_org_users_should_show_live_search_if_more_than_7_users( number_of_users, ): mocker.patch( - 'app.models.user.OrganizationInvitedUsers.client_method', + "app.models.user.OrganizationInvitedUsers.client_method", return_value=[], ) mocker.patch( - 'app.models.user.OrganizationUsers.client_method', + "app.models.user.OrganizationUsers.client_method", return_value=[active_user_with_permissions] * number_of_users, ) page = client_request.get( - '.manage_org_users', + ".manage_org_users", org_id=ORGANISATION_ID, ) - assert page.select_one('div[data-module=live-search]')['data-targets'] == ( + assert page.select_one("div[data-module=live-search]")["data-targets"] == ( ".user-list-item" ) - assert len(page.select('.user-list-item')) == number_of_users + assert len(page.select(".user-list-item")) == number_of_users - textbox = page.select_one('[data-module=autofocus] .usa-input') - assert 'value' not in textbox - assert textbox['name'] == 'search' + textbox = page.select_one("[data-module=autofocus] .usa-input") + assert "value" not in textbox + assert textbox["name"] == "search" # data-module=autofocus is set on a containing element so it # shouldn’t also be set on the textbox itself - assert 'data-module' not in textbox - assert not page.select_one('[data-force-focus]') - assert textbox['class'] == [ - 'usa-input' - ] - assert normalize_spaces( - page.select_one('label[for=search]').text - ) == 'Search by name or email address' + assert "data-module" not in textbox + assert not page.select_one("[data-force-focus]") + assert textbox["class"] == ["usa-input"] + assert ( + normalize_spaces(page.select_one("label[for=search]").text) + == "Search by name or email address" + ) -@pytest.mark.parametrize('number_of_users', ( - pytest.param(3), - pytest.param(7), -)) +@pytest.mark.parametrize( + "number_of_users", + ( + pytest.param(3), + pytest.param(7), + ), +) def test_manage_org_users_should_show_live_search_if_7_users_or_less( client_request, mocker, @@ -766,21 +844,21 @@ def test_manage_org_users_should_show_live_search_if_7_users_or_less( number_of_users, ): mocker.patch( - 'app.models.user.OrganizationInvitedUsers.client_method', + "app.models.user.OrganizationInvitedUsers.client_method", return_value=[], ) mocker.patch( - 'app.models.user.OrganizationUsers.client_method', + "app.models.user.OrganizationUsers.client_method", return_value=[active_user_with_permissions] * number_of_users, ) page = client_request.get( - '.manage_org_users', + ".manage_org_users", org_id=ORGANISATION_ID, ) with pytest.raises(expected_exception=TypeError): - assert page.select_one('div[data-module=live-search]')['data-targets'] == ( + assert page.select_one("div[data-module=live-search]")["data-targets"] == ( ".user-list-item" ) @@ -793,19 +871,21 @@ def test_edit_organization_user_shows_the_delete_confirmation_banner( active_user_with_permissions, ): page = client_request.get( - '.edit_organization_user', + ".edit_organization_user", org_id=ORGANISATION_ID, - user_id=active_user_with_permissions['id'] + user_id=active_user_with_permissions["id"], ) - assert normalize_spaces(page.h1) == 'Team members' + assert normalize_spaces(page.h1) == "Team members" - banner = page.select_one('.banner-dangerous') - assert "Are you sure you want to remove Test User?" in normalize_spaces(banner.contents[0]) - assert banner.form.attrs['action'] == url_for( - 'main.remove_user_from_organization', + banner = page.select_one(".banner-dangerous") + assert "Are you sure you want to remove Test User?" in normalize_spaces( + banner.contents[0] + ) + assert banner.form.attrs["action"] == url_for( + "main.remove_user_from_organization", org_id=ORGANISATION_ID, - user_id=active_user_with_permissions['id'] + user_id=active_user_with_permissions["id"], ) @@ -815,14 +895,16 @@ def test_remove_user_from_organization_makes_api_request_to_remove_user( mock_get_organization, fake_uuid, ): - mock_remove_user = mocker.patch('app.organizations_client.remove_user_from_organization') + mock_remove_user = mocker.patch( + "app.organizations_client.remove_user_from_organization" + ) client_request.post( - '.remove_user_from_organization', + ".remove_user_from_organization", org_id=ORGANISATION_ID, user_id=fake_uuid, _expected_redirect=url_for( - 'main.show_accounts_or_dashboard', + "main.show_accounts_or_dashboard", ), ) @@ -830,65 +912,63 @@ def test_remove_user_from_organization_makes_api_request_to_remove_user( def test_organization_settings_platform_admin_only( - client_request, - mock_get_organization, - organization_one + client_request, mock_get_organization, organization_one ): client_request.get( - '.organization_settings', - org_id=organization_one['id'], + ".organization_settings", + org_id=organization_one["id"], _expected_status=403, ) def test_organization_settings_for_platform_admin( - client_request, - platform_admin_user, - mock_get_organization, - organization_one + client_request, platform_admin_user, mock_get_organization, organization_one ): expected_rows = [ - 'Label Value Action', - 'Name Test organization Change organization name', - 'Sector Federal government Change sector for the organization', - 'Request to go live notes None Change go live notes for the organization', - 'Billing details None Change billing details for the organization', - 'Notes None Change the notes for the organization', - 'Default email branding GOV.UK Change default email branding for the organization', - 'Known email domains None Change known email domains for the organization', + "Label Value Action", + "Name Test organization Change organization name", + "Sector Federal government Change sector for the organization", + "Request to go live notes None Change go live notes for the organization", + "Billing details None Change billing details for the organization", + "Notes None Change the notes for the organization", + "Default email branding GOV.UK Change default email branding for the organization", + "Known email domains None Change known email domains for the organization", ] client_request.login(platform_admin_user) - page = client_request.get('.organization_settings', org_id=organization_one['id']) + page = client_request.get(".organization_settings", org_id=organization_one["id"]) - assert page.find('h1').text == 'Settings' - rows = page.select('tr') + assert page.find("h1").text == "Settings" + rows = page.select("tr") assert len(rows) == len(expected_rows) for index, row in enumerate(expected_rows): assert row == " ".join(rows[index].text.split()) - mock_get_organization.assert_called_with(organization_one['id']) + mock_get_organization.assert_called_with(organization_one["id"]) -@pytest.mark.parametrize('endpoint, expected_options, expected_selected', ( +@pytest.mark.parametrize( + "endpoint, expected_options, expected_selected", ( - '.edit_organization_type', ( - {'value': 'federal', 'label': 'Federal government'}, - {'value': 'state', 'label': 'State government'}, - {'value': 'other', 'label': 'Other'}, + ".edit_organization_type", + ( + {"value": "federal", "label": "Federal government"}, + {"value": "state", "label": "State government"}, + {"value": "other", "label": "Other"}, + ), + "federal", ), - 'federal', ), -)) -@pytest.mark.parametrize('user', ( - pytest.param( - create_platform_admin_user(), +) +@pytest.mark.parametrize( + "user", + ( + pytest.param( + create_platform_admin_user(), + ), + pytest.param(create_active_user_with_permissions(), marks=pytest.mark.xfail), ), - pytest.param( - create_active_user_with_permissions(), - marks=pytest.mark.xfail - ), -)) +) def test_view_organization_settings( client_request, fake_uuid, @@ -901,46 +981,57 @@ def test_view_organization_settings( ): client_request.login(user) - page = client_request.get(endpoint, org_id=organization_one['id']) + page = client_request.get(endpoint, org_id=organization_one["id"]) - radios = page.select('input[type=radio]') + radios = page.select("input[type=radio]") for index, option in enumerate(expected_options): option_values = { - 'value': radios[index]['value'], - 'label': normalize_spaces(page.select_one('label[for={}]'.format(radios[index]['id'])).text) + "value": radios[index]["value"], + "label": normalize_spaces( + page.select_one("label[for={}]".format(radios[index]["id"])).text + ), } - if 'hint' in option: - option_values['hint'] = normalize_spaces( - page.select_one('label[for={}] + .govuk-hint'.format(radios[index]['id'])).text) + if "hint" in option: + option_values["hint"] = normalize_spaces( + page.select_one( + "label[for={}] + .govuk-hint".format(radios[index]["id"]) + ).text + ) assert option_values == option if expected_selected: - assert page.select_one('input[checked]')['value'] == expected_selected + assert page.select_one("input[checked]")["value"] == expected_selected else: - assert not page.select_one('input[checked]') + assert not page.select_one("input[checked]") -@pytest.mark.parametrize('endpoint, post_data, expected_persisted', ( +@pytest.mark.parametrize( + "endpoint, post_data, expected_persisted", ( - '.edit_organization_type', - {'organization_type': 'federal'}, - {'cached_service_ids': [], 'organization_type': 'federal'}, + ( + ".edit_organization_type", + {"organization_type": "federal"}, + {"cached_service_ids": [], "organization_type": "federal"}, + ), + ( + ".edit_organization_type", + {"organization_type": "state"}, + {"cached_service_ids": [], "organization_type": "state"}, + ), ), +) +@pytest.mark.parametrize( + "user", ( - '.edit_organization_type', - {'organization_type': 'state'}, - {'cached_service_ids': [], 'organization_type': 'state'}, + pytest.param( + create_platform_admin_user(), + ), + pytest.param( + create_active_user_with_permissions(), + ), ), -)) -@pytest.mark.parametrize('user', ( - pytest.param( - create_platform_admin_user(), - ), - pytest.param( - create_active_user_with_permissions(), - ), -)) +) def test_update_organization_settings( mocker, client_request, @@ -953,29 +1044,29 @@ def test_update_organization_settings( expected_persisted, user, ): - mocker.patch('app.organizations_client.get_organization_services', return_value=[]) + mocker.patch("app.organizations_client.get_organization_services", return_value=[]) client_request.login(user) - if user['email_address'] == 'platform@admin.gsa.gov': + if user["email_address"] == "platform@admin.gsa.gov": expected_status = 302 expected_redirect = url_for( - 'main.organization_settings', - org_id=organization_one['id'], + "main.organization_settings", + org_id=organization_one["id"], ) else: expected_status = 403 expected_redirect = None client_request.post( endpoint, - org_id=organization_one['id'], + org_id=organization_one["id"], _data=post_data, _expected_status=expected_status, _expected_redirect=expected_redirect, ) - if user['email_address'] == 'platform@admin.gsa.gov': + if user["email_address"] == "platform@admin.gsa.gov": mock_update_organization.assert_called_once_with( - organization_one['id'], + organization_one["id"], **expected_persisted, ) @@ -991,32 +1082,32 @@ def test_update_organization_sector_sends_service_id_data_to_api_client( client_request.login(platform_admin_user) client_request.post( - 'main.edit_organization_type', - org_id=organization_one['id'], - _data={'organization_type': 'federal'}, + "main.edit_organization_type", + org_id=organization_one["id"], + _data={"organization_type": "federal"}, _expected_status=302, _expected_redirect=url_for( - 'main.organization_settings', - org_id=organization_one['id'], + "main.organization_settings", + org_id=organization_one["id"], ), ) mock_update_organization.assert_called_once_with( - organization_one['id'], - cached_service_ids=['12345', '67890', SERVICE_ONE_ID], - organization_type='federal' + organization_one["id"], + cached_service_ids=["12345", "67890", SERVICE_ONE_ID], + organization_type="federal", ) -@pytest.mark.parametrize('user', ( - pytest.param( - create_platform_admin_user(), +@pytest.mark.parametrize( + "user", + ( + pytest.param( + create_platform_admin_user(), + ), + pytest.param(create_active_user_with_permissions(), marks=pytest.mark.xfail), ), - pytest.param( - create_active_user_with_permissions(), - marks=pytest.mark.xfail - ), -)) +) def test_view_organization_domains( mocker, client_request, @@ -1026,22 +1117,22 @@ def test_view_organization_domains( client_request.login(user) mocker.patch( - 'app.organizations_client.get_organization', + "app.organizations_client.get_organization", side_effect=lambda org_id: organization_json( org_id, - 'Org 1', - domains=['example.gsa.gov', 'test.example.gsa.gov'], - ) + "Org 1", + domains=["example.gsa.gov", "test.example.gsa.gov"], + ), ) page = client_request.get( - 'main.edit_organization_domains', + "main.edit_organization_domains", org_id=ORGANISATION_ID, ) - assert [textbox.get('value') for textbox in page.select('input[type=text]')] == [ - 'example.gsa.gov', - 'test.example.gsa.gov', + assert [textbox.get("value") for textbox in page.select("input[type=text]")] == [ + "example.gsa.gov", + "test.example.gsa.gov", None, None, None, @@ -1063,40 +1154,44 @@ def test_view_organization_domains( ] -@pytest.mark.parametrize('post_data, expected_persisted', ( +@pytest.mark.parametrize( + "post_data, expected_persisted", ( - { - 'domains-0': 'example.gsa.gov', - 'domains-2': 'example.gsa.gov', - 'domains-3': 'EXAMPLE.gsa.gov', - 'domains-5': 'test.gsa.gov', - }, - { - 'domains': [ - 'example.gsa.gov', - 'test.gsa.gov', - ] - } + ( + { + "domains-0": "example.gsa.gov", + "domains-2": "example.gsa.gov", + "domains-3": "EXAMPLE.gsa.gov", + "domains-5": "test.gsa.gov", + }, + { + "domains": [ + "example.gsa.gov", + "test.gsa.gov", + ] + }, + ), + ( + { + "domains-0": "", + "domains-1": "", + "domains-2": "", + }, + {"domains": []}, + ), ), +) +@pytest.mark.parametrize( + "user", ( - { - 'domains-0': '', - 'domains-1': '', - 'domains-2': '', - }, - { - 'domains': [] - } + pytest.param( + create_platform_admin_user(), + ), + pytest.param( + create_active_user_with_permissions(), + ), ), -)) -@pytest.mark.parametrize('user', ( - pytest.param( - create_platform_admin_user(), - ), - pytest.param( - create_active_user_with_permissions(), - ), -)) +) def test_update_organization_domains( client_request, fake_uuid, @@ -1108,25 +1203,25 @@ def test_update_organization_domains( user, ): client_request.login(user) - if user['email_address'] == 'platform@admin.gsa.gov': + if user["email_address"] == "platform@admin.gsa.gov": expected_status = 302 expected_redirect = url_for( - 'main.organization_settings', - org_id=organization_one['id'], + "main.organization_settings", + org_id=organization_one["id"], ) else: expected_status = 403 expected_redirect = None client_request.post( - 'main.edit_organization_domains', + "main.edit_organization_domains", org_id=ORGANISATION_ID, _data=post_data, _expected_status=expected_status, _expected_redirect=expected_redirect, ) - if user['email_address'] == 'platform@admin.gsa.gov': + if user["email_address"] == "platform@admin.gsa.gov": mock_update_organization.assert_called_once_with( ORGANISATION_ID, **expected_persisted, @@ -1143,26 +1238,32 @@ def test_update_organization_domains_when_domain_already_exists( user = create_platform_admin_user() client_request.login(user) - mocker.patch('app.organizations_client.update_organization', side_effect=HTTPError( - response=mocker.Mock( - status_code=400, - json={'result': 'error', 'message': 'Domain already exists'} + mocker.patch( + "app.organizations_client.update_organization", + side_effect=HTTPError( + response=mocker.Mock( + status_code=400, + json={"result": "error", "message": "Domain already exists"}, + ), + message="Domain already exists", ), - message="Domain already exists") ) response = client_request.post( - 'main.edit_organization_domains', + "main.edit_organization_domains", org_id=ORGANISATION_ID, _data={ - 'domains': [ - 'example.gsa.gov', + "domains": [ + "example.gsa.gov", ] }, _expected_status=200, ) - assert response.find("div", class_="banner-dangerous").text.strip() == "This domain is already in use" + assert ( + response.find("div", class_="banner-dangerous").text.strip() + == "This domain is already in use" + ) def test_update_organization_name( @@ -1174,42 +1275,45 @@ def test_update_organization_name( ): client_request.login(platform_admin_user) client_request.post( - '.edit_organization_name', + ".edit_organization_name", org_id=fake_uuid, - _data={'name': 'TestNewOrgName'}, + _data={"name": "TestNewOrgName"}, _expected_redirect=url_for( - '.organization_settings', + ".organization_settings", org_id=fake_uuid, - ) + ), ) mock_update_organization.assert_called_once_with( fake_uuid, - name='TestNewOrgName', + name="TestNewOrgName", cached_service_ids=None, ) -@pytest.mark.parametrize('name, error_message', [ - ('', 'Cannot be empty'), - ('a', 'at least two alphanumeric characters'), - ('a' * 256, 'Organization name must be 255 characters or fewer'), -]) +@pytest.mark.parametrize( + "name, error_message", + [ + ("", "Cannot be empty"), + ("a", "at least two alphanumeric characters"), + ("a" * 256, "Organization name must be 255 characters or fewer"), + ], +) def test_update_organization_with_incorrect_input( client_request, platform_admin_user, organization_one, mock_get_organization, name, - error_message + error_message, ): client_request.login(platform_admin_user) page = client_request.post( - '.edit_organization_name', - org_id=organization_one['id'], - _data={'name': name}, + ".edit_organization_name", + org_id=organization_one["id"], + _data={"name": name}, _expected_status=200, ) - assert error_message in page.select_one('.usa-error-message').text + assert error_message in page.select_one(".usa-error-message").text def test_update_organization_with_non_unique_name( @@ -1220,24 +1324,27 @@ def test_update_organization_with_non_unique_name( mocker, ): mocker.patch( - 'app.organizations_client.update_organization', + "app.organizations_client.update_organization", side_effect=HTTPError( response=mocker.Mock( status_code=400, - json={'result': 'error', 'message': 'Organization name already exists'} + json={"result": "error", "message": "Organization name already exists"}, ), - message='Organization name already exists', - ) + message="Organization name already exists", + ), ) client_request.login(platform_admin_user) page = client_request.post( - '.edit_organization_name', + ".edit_organization_name", org_id=fake_uuid, - _data={'name': 'TestNewOrgName'}, + _data={"name": "TestNewOrgName"}, _expected_status=200, ) - assert 'This organization name is already in use' in page.select_one('.usa-error-message').text + assert ( + "This organization name is already in use" + in page.select_one(".usa-error-message").text + ) def test_get_edit_organization_go_live_notes_page( @@ -1248,16 +1355,15 @@ def test_get_edit_organization_go_live_notes_page( ): client_request.login(platform_admin_user) page = client_request.get( - '.edit_organization_go_live_notes', - org_id=organization_one['id'], + ".edit_organization_go_live_notes", + org_id=organization_one["id"], ) - assert page.find('textarea', id='request_to_go_live_notes') + assert page.find("textarea", id="request_to_go_live_notes") -@pytest.mark.parametrize('input_note,saved_note', [ - ('Needs permission', 'Needs permission'), - (' ', None) -]) +@pytest.mark.parametrize( + "input_note,saved_note", [("Needs permission", "Needs permission"), (" ", None)] +) def test_post_edit_organization_go_live_notes_updates_go_live_notes( client_request, platform_admin_user, @@ -1269,17 +1375,16 @@ def test_post_edit_organization_go_live_notes_updates_go_live_notes( ): client_request.login(platform_admin_user) client_request.post( - '.edit_organization_go_live_notes', - org_id=organization_one['id'], - _data={'request_to_go_live_notes': input_note}, + ".edit_organization_go_live_notes", + org_id=organization_one["id"], + _data={"request_to_go_live_notes": input_note}, _expected_redirect=url_for( - '.organization_settings', - org_id=organization_one['id'], + ".organization_settings", + org_id=organization_one["id"], ), ) mock_update_organization.assert_called_once_with( - organization_one['id'], - request_to_go_live_notes=saved_note + organization_one["id"], request_to_go_live_notes=saved_note ) @@ -1291,88 +1396,95 @@ def test_organization_settings_links_to_edit_organization_notes_page( platform_admin_user, ): client_request.login(platform_admin_user) - page = client_request.get( - '.organization_settings', org_id=organization_one['id'] + page = client_request.get(".organization_settings", org_id=organization_one["id"]) + assert ( + len( + page.find_all( + "a", + attrs={ + "href": "/organizations/{}/settings/notes".format( + organization_one["id"] + ) + }, + ) + ) + == 1 ) - assert len(page.find_all( - 'a', attrs={'href': '/organizations/{}/settings/notes'.format(organization_one['id'])} - )) == 1 def test_view_edit_organization_notes( - client_request, - platform_admin_user, - organization_one, - mock_get_organization, + client_request, + platform_admin_user, + organization_one, + mock_get_organization, ): client_request.login(platform_admin_user) page = client_request.get( - 'main.edit_organization_notes', - org_id=organization_one['id'], + "main.edit_organization_notes", + org_id=organization_one["id"], ) - assert page.select_one('h1').text == "Edit organization notes" - assert page.find('label', class_="usa-label").text.strip() == "Notes" - assert page.find('textarea').attrs["name"] == "notes" + assert page.select_one("h1").text == "Edit organization notes" + assert page.find("label", class_="usa-label").text.strip() == "Notes" + assert page.find("textarea").attrs["name"] == "notes" def test_update_organization_notes( - client_request, - platform_admin_user, - organization_one, - mock_get_organization, - mock_update_organization, + client_request, + platform_admin_user, + organization_one, + mock_get_organization, + mock_update_organization, ): client_request.login(platform_admin_user) client_request.post( - 'main.edit_organization_notes', - org_id=organization_one['id'], - _data={'notes': "Very fluffy"}, + "main.edit_organization_notes", + org_id=organization_one["id"], + _data={"notes": "Very fluffy"}, _expected_redirect=url_for( - 'main.organization_settings', - org_id=organization_one['id'], + "main.organization_settings", + org_id=organization_one["id"], ), ) mock_update_organization.assert_called_with( - organization_one['id'], - cached_service_ids=None, - notes="Very fluffy" + organization_one["id"], cached_service_ids=None, notes="Very fluffy" ) def test_update_organization_notes_errors_when_user_not_platform_admin( - client_request, - organization_one, - mock_get_organization, - mock_update_organization, + client_request, + organization_one, + mock_get_organization, + mock_update_organization, ): client_request.post( - 'main.edit_organization_notes', - org_id=organization_one['id'], - _data={'notes': "Very fluffy"}, + "main.edit_organization_notes", + org_id=organization_one["id"], + _data={"notes": "Very fluffy"}, _expected_status=403, ) def test_update_organization_notes_doesnt_call_api_when_notes_dont_change( - client_request, - platform_admin_user, - organization_one, - mock_update_organization, - mocker + client_request, + platform_admin_user, + organization_one, + mock_update_organization, + mocker, ): - mocker.patch('app.organizations_client.get_organization', return_value=organization_json( - id_=organization_one['id'], - name="Test Org", - notes="Very fluffy" - )) + mocker.patch( + "app.organizations_client.get_organization", + return_value=organization_json( + id_=organization_one["id"], name="Test Org", notes="Very fluffy" + ), + ) client_request.login(platform_admin_user) client_request.post( - 'main.edit_organization_notes', - org_id=organization_one['id'], - _data={'notes': "Very fluffy"}, + "main.edit_organization_notes", + org_id=organization_one["id"], + _data={"notes": "Very fluffy"}, _expected_redirect=url_for( - 'main.organization_settings', - org_id=organization_one['id'], + "main.organization_settings", + org_id=organization_one["id"], ), ) assert not mock_update_organization.called @@ -1386,94 +1498,102 @@ def test_organization_settings_links_to_edit_organization_billing_details_page( platform_admin_user, ): client_request.login(platform_admin_user) - page = client_request.get( - '.organization_settings', org_id=organization_one['id'] + page = client_request.get(".organization_settings", org_id=organization_one["id"]) + assert ( + len( + page.find_all( + "a", + attrs={ + "href": "/organizations/{}/settings/edit-billing-details".format( + organization_one["id"] + ) + }, + ) + ) + == 1 ) - assert len(page.find_all( - 'a', attrs={'href': '/organizations/{}/settings/edit-billing-details'.format(organization_one['id'])} - )) == 1 def test_view_edit_organization_billing_details( - client_request, - platform_admin_user, - organization_one, - mock_get_organization, + client_request, + platform_admin_user, + organization_one, + mock_get_organization, ): client_request.login(platform_admin_user) page = client_request.get( - 'main.edit_organization_billing_details', - org_id=organization_one['id'], + "main.edit_organization_billing_details", + org_id=organization_one["id"], ) - assert page.select_one('h1').text == "Edit organization billing details" - labels = page.find_all('label', class_="usa-label") + assert page.select_one("h1").text == "Edit organization billing details" + labels = page.find_all("label", class_="usa-label") labels_list = [ - 'Contact email addresses', - 'Contact names', - 'Reference', - 'Purchase order number', - 'Notes' + "Contact email addresses", + "Contact names", + "Reference", + "Purchase order number", + "Notes", ] for label in labels: assert label.text.strip() in labels_list - textbox_names = page.find_all('input', class_='govuk-input ') + textbox_names = page.find_all("input", class_="govuk-input ") names_list = [ - 'billing_contact_email_addresses', - 'billing_contact_names', - 'billing_reference', - 'purchase_order_number', + "billing_contact_email_addresses", + "billing_contact_names", + "billing_reference", + "purchase_order_number", ] for name in textbox_names: assert name.attrs["name"] in names_list - assert page.find('textarea').attrs["name"] == "notes" + assert page.find("textarea").attrs["name"] == "notes" def test_update_organization_billing_details( - client_request, - platform_admin_user, - organization_one, - mock_get_organization, - mock_update_organization, + client_request, + platform_admin_user, + organization_one, + mock_get_organization, + mock_update_organization, ): client_request.login(platform_admin_user) client_request.post( - 'main.edit_organization_billing_details', - org_id=organization_one['id'], + "main.edit_organization_billing_details", + org_id=organization_one["id"], _data={ - 'billing_contact_email_addresses': 'accounts@fluff.gsa.gov', - 'billing_contact_names': 'Flannellette von Fluff', - 'billing_reference': '', - 'purchase_order_number': 'PO1234', - 'notes': 'very fluffy, give extra allowance' + "billing_contact_email_addresses": "accounts@fluff.gsa.gov", + "billing_contact_names": "Flannellette von Fluff", + "billing_reference": "", + "purchase_order_number": "PO1234", + "notes": "very fluffy, give extra allowance", }, _expected_redirect=url_for( - 'main.organization_settings', - org_id=organization_one['id'], - ) + "main.organization_settings", + org_id=organization_one["id"], + ), ) mock_update_organization.assert_called_with( - organization_one['id'], + organization_one["id"], cached_service_ids=None, - billing_contact_email_addresses='accounts@fluff.gsa.gov', - billing_contact_names='Flannellette von Fluff', - billing_reference='', - purchase_order_number='PO1234', - notes='very fluffy, give extra allowance' + billing_contact_email_addresses="accounts@fluff.gsa.gov", + billing_contact_names="Flannellette von Fluff", + billing_reference="", + purchase_order_number="PO1234", + notes="very fluffy, give extra allowance", ) def test_update_organization_billing_details_errors_when_user_not_platform_admin( - client_request, - organization_one, - mock_get_organization, - mock_update_organization, + client_request, + organization_one, + mock_get_organization, + mock_update_organization, ): client_request.post( - 'main.edit_organization_billing_details', - org_id=organization_one['id'], - _data={'notes': "Very fluffy"}, + "main.edit_organization_billing_details", + org_id=organization_one["id"], + _data={"notes": "Very fluffy"}, _expected_status=403, ) @@ -1483,7 +1603,5 @@ def test_organization_billing_page_not_accessible_if_not_platform_admin( mock_get_organization, ): client_request.get( - '.organization_billing', - org_id=ORGANISATION_ID, - _expected_status=403 + ".organization_billing", org_id=ORGANISATION_ID, _expected_status=403 ) diff --git a/tests/app/main/views/service_settings/test_email_branding_requests.py b/tests/app/main/views/service_settings/test_email_branding_requests.py index 90a77dedb..55e7df8ee 100644 --- a/tests/app/main/views/service_settings/test_email_branding_requests.py +++ b/tests/app/main/views/service_settings/test_email_branding_requests.py @@ -8,11 +8,17 @@ from tests import sample_uuid from tests.conftest import ORGANISATION_ID, SERVICE_ONE_ID, normalize_spaces -@pytest.mark.parametrize('organization_type, expected_options', ( - ('other', [ - ('something_else', 'Something else'), - ]), -)) +@pytest.mark.parametrize( + "organization_type, expected_options", + ( + ( + "other", + [ + ("something_else", "Something else"), + ], + ), + ), +) def test_email_branding_request_page_when_no_branding_is_set( service_one, client_request, @@ -21,33 +27,33 @@ def test_email_branding_request_page_when_no_branding_is_set( organization_type, expected_options, ): - service_one['email_branding'] = None - service_one['organization_type'] = organization_type + service_one["email_branding"] = None + service_one["organization_type"] = organization_type mocker.patch( - 'app.models.service.Service.email_branding_id', + "app.models.service.Service.email_branding_id", new_callable=PropertyMock, return_value=None, ) - page = client_request.get( - '.email_branding_request', service_id=SERVICE_ONE_ID - ) + page = client_request.get(".email_branding_request", service_id=SERVICE_ONE_ID) assert mock_get_email_branding.called is False - assert page.find_all('iframe')[1]['src'] == url_for('main.email_template', branding_style='__NONE__') + assert page.find_all("iframe")[1]["src"] == url_for( + "main.email_template", branding_style="__NONE__" + ) - button_text = normalize_spaces(page.select_one('.page-footer button').text) + button_text = normalize_spaces(page.select_one(".page-footer button").text) assert [ ( - radio['value'], - page.select_one('label[for={}]'.format(radio['id'])).text.strip() + radio["value"], + page.select_one("label[for={}]".format(radio["id"])).text.strip(), ) - for radio in page.select('input[type=radio]') + for radio in page.select("input[type=radio]") ] == expected_options - assert button_text == 'Continue' + assert button_text == "Continue" def test_email_branding_request_page_shows_branding_if_set( @@ -58,52 +64,53 @@ def test_email_branding_request_page_shows_branding_if_set( mock_get_service_organization, ): mocker.patch( - 'app.models.service.Service.email_branding_id', + "app.models.service.Service.email_branding_id", new_callable=PropertyMock, - return_value='some-random-branding', + return_value="some-random-branding", ) - page = client_request.get( - '.email_branding_request', service_id=SERVICE_ONE_ID + page = client_request.get(".email_branding_request", service_id=SERVICE_ONE_ID) + assert page.find_all("iframe")[1]["src"] == url_for( + "main.email_template", branding_style="some-random-branding" ) - assert page.find_all('iframe')[1]['src'] == url_for('main.email_template', branding_style='some-random-branding') def test_email_branding_request_page_back_link( client_request, ): - page = client_request.get( - '.email_branding_request', service_id=SERVICE_ONE_ID - ) + page = client_request.get(".email_branding_request", service_id=SERVICE_ONE_ID) - back_link = page.select_one('a.usa-back-link') + back_link = page.select_one("a.usa-back-link") assert len(back_link) > 0, "No back link found on the page" - assert back_link['href'] == url_for('.service_settings', service_id=SERVICE_ONE_ID) + assert back_link["href"] == url_for(".service_settings", service_id=SERVICE_ONE_ID) -@pytest.mark.parametrize('data, org_type, endpoint', ( +@pytest.mark.parametrize( + "data, org_type, endpoint", ( - { - 'options': 'govuk', - }, - 'federal', - 'main.email_branding_govuk', + ( + { + "options": "govuk", + }, + "federal", + "main.email_branding_govuk", + ), + ( + { + "options": "govuk_and_org", + }, + "federal", + "main.email_branding_govuk_and_org", + ), + ( + { + "options": "something_else", + }, + "federal", + "main.email_branding_something_else", + ), ), - ( - { - 'options': 'govuk_and_org', - }, - 'federal', - 'main.email_branding_govuk_and_org', - ), - ( - { - 'options': 'something_else', - }, - 'federal', - 'main.email_branding_something_else', - ), -)) +) def test_email_branding_request_submit( client_request, service_one, @@ -114,24 +121,24 @@ def test_email_branding_request_submit( org_type, endpoint, ): - organization_one['organization_type'] = org_type - service_one['email_branding'] = sample_uuid() - service_one['organization'] = organization_one + organization_one["organization_type"] = org_type + service_one["email_branding"] = sample_uuid() + service_one["organization"] = organization_one mocker.patch( - 'app.organizations_client.get_organization', + "app.organizations_client.get_organization", return_value=organization_one, ) client_request.post( - '.email_branding_request', + ".email_branding_request", service_id=SERVICE_ONE_ID, _data=data, _expected_status=302, _expected_redirect=url_for( endpoint, service_id=SERVICE_ONE_ID, - ) + ), ) @@ -140,20 +147,26 @@ def test_email_branding_request_submit_when_no_radio_button_is_selected( service_one, mock_get_email_branding, ): - service_one['email_branding'] = sample_uuid() + service_one["email_branding"] = sample_uuid() page = client_request.post( - '.email_branding_request', service_id=SERVICE_ONE_ID, - _data={'options': ''}, + ".email_branding_request", + service_id=SERVICE_ONE_ID, + _data={"options": ""}, _follow_redirects=True, ) - assert page.h1.text == 'Change email branding' - assert normalize_spaces(page.select_one('.error-message').text) == 'Select an option' + assert page.h1.text == "Change email branding" + assert ( + normalize_spaces(page.select_one(".error-message").text) == "Select an option" + ) -@pytest.mark.parametrize('endpoint, expected_heading', [ - ('main.email_branding_govuk_and_org', 'Before you request new branding'), -]) +@pytest.mark.parametrize( + "endpoint, expected_heading", + [ + ("main.email_branding_govuk_and_org", "Before you request new branding"), + ], +) def test_email_branding_description_pages_for_org_branding( client_request, mocker, @@ -163,11 +176,11 @@ def test_email_branding_description_pages_for_org_branding( endpoint, expected_heading, ): - service_one['email_branding'] = sample_uuid() - service_one['organization'] = organization_one + service_one["email_branding"] = sample_uuid() + service_one["organization"] = organization_one mocker.patch( - 'app.organizations_client.get_organization', + "app.organizations_client.get_organization", return_value=organization_one, ) @@ -176,14 +189,20 @@ def test_email_branding_description_pages_for_org_branding( service_id=SERVICE_ONE_ID, ) assert page.h1.text == expected_heading - assert normalize_spaces(page.select_one('.page-footer button').text) == 'Request new branding' + assert ( + normalize_spaces(page.select_one(".page-footer button").text) + == "Request new branding" + ) -@pytest.mark.parametrize('endpoint, service_org_type, branding_preview_id', [ - ('main.email_branding_govuk', 'central', '__NONE__'), - # ('main.email_branding_nhs', 'nhs_local', NHS_EMAIL_BRANDING_ID), -]) -@pytest.mark.skip(reason='Update for TTS') +@pytest.mark.parametrize( + "endpoint, service_org_type, branding_preview_id", + [ + ("main.email_branding_govuk", "central", "__NONE__"), + # ('main.email_branding_nhs', 'nhs_local', NHS_EMAIL_BRANDING_ID), + ], +) +@pytest.mark.skip(reason="Update for TTS") def test_email_branding_govuk_and_nhs_pages( client_request, mocker, @@ -194,12 +213,12 @@ def test_email_branding_govuk_and_nhs_pages( service_org_type, branding_preview_id, ): - organization_one['organization_type'] = service_org_type - service_one['email_branding'] = sample_uuid() - service_one['organization'] = organization_one + organization_one["organization_type"] = service_org_type + service_one["email_branding"] = sample_uuid() + service_one["organization"] = organization_one mocker.patch( - 'app.organizations_client.get_organization', + "app.organizations_client.get_organization", return_value=organization_one, ) @@ -207,60 +226,71 @@ def test_email_branding_govuk_and_nhs_pages( endpoint, service_id=SERVICE_ONE_ID, ) - assert page.h1.text == 'Check your new branding' - assert 'Emails from service one will look like this' in normalize_spaces(page.text) - assert page.find('iframe')['src'] == url_for('main.email_template', branding_style=branding_preview_id) - assert normalize_spaces(page.select_one('.page-footer button').text) == 'Use this branding' + assert page.h1.text == "Check your new branding" + assert "Emails from service one will look like this" in normalize_spaces(page.text) + assert page.find("iframe")["src"] == url_for( + "main.email_template", branding_style=branding_preview_id + ) + assert ( + normalize_spaces(page.select_one(".page-footer button").text) + == "Use this branding" + ) -@pytest.mark.skip(reason='Update for TTS') +@pytest.mark.skip(reason="Update for TTS") def test_email_branding_something_else_page(client_request, service_one): # expect to have a "NHS" option as well as the # fallback, so back button goes to choices page - service_one['organization_type'] = 'nhs_central' + service_one["organization_type"] = "nhs_central" page = client_request.get( - 'main.email_branding_something_else', + "main.email_branding_something_else", service_id=SERVICE_ONE_ID, ) - assert normalize_spaces(page.h1.text) == 'Describe the branding you want' - assert page.select_one('textarea')['name'] == ('something_else') - assert normalize_spaces(page.select_one('.page-footer button').text) == 'Request new branding' - assert page.select_one('.usa-back-link')['href'] == url_for( - 'main.email_branding_request', service_id=SERVICE_ONE_ID, + assert normalize_spaces(page.h1.text) == "Describe the branding you want" + assert page.select_one("textarea")["name"] == ("something_else") + assert ( + normalize_spaces(page.select_one(".page-footer button").text) + == "Request new branding" + ) + assert page.select_one(".usa-back-link")["href"] == url_for( + "main.email_branding_request", + service_id=SERVICE_ONE_ID, ) -def test_get_email_branding_something_else_page_is_only_option(client_request, service_one): +def test_get_email_branding_something_else_page_is_only_option( + client_request, service_one +): # should only have a "something else" option # so back button goes back to settings page - service_one['organization_type'] = 'other' + service_one["organization_type"] = "other" page = client_request.get( - 'main.email_branding_something_else', + "main.email_branding_something_else", service_id=SERVICE_ONE_ID, ) - assert page.select_one('.usa-back-link')['href'] == url_for( - 'main.service_settings', service_id=SERVICE_ONE_ID, + assert page.select_one(".usa-back-link")["href"] == url_for( + "main.service_settings", + service_id=SERVICE_ONE_ID, ) -@pytest.mark.parametrize('endpoint', [ - ('main.email_branding_govuk'), - ('main.email_branding_govuk_and_org'), - ('main.email_branding_organization'), -]) +@pytest.mark.parametrize( + "endpoint", + [ + ("main.email_branding_govuk"), + ("main.email_branding_govuk_and_org"), + ("main.email_branding_organization"), + ], +) def test_email_branding_pages_give_404_if_selected_branding_not_allowed( client_request, endpoint, ): # The only email branding allowed is 'something_else', so trying to visit any of the other # endpoints gives a 404 status code. - client_request.get( - endpoint, - service_id=SERVICE_ONE_ID, - _expected_status=404 - ) + client_request.get(endpoint, service_id=SERVICE_ONE_ID, _expected_status=404) def test_email_branding_govuk_submit( @@ -274,18 +304,18 @@ def test_email_branding_govuk_submit( mock_update_service, ): mocker.patch( - 'app.organizations_client.get_organization', + "app.organizations_client.get_organization", return_value=organization_one, ) mocker.patch( - 'app.models.service.Service.organization_id', + "app.models.service.Service.organization_id", new_callable=PropertyMock, return_value=ORGANISATION_ID, ) - service_one['email_branding'] = sample_uuid() + service_one["email_branding"] = sample_uuid() page = client_request.post( - '.email_branding_govuk', + ".email_branding_govuk", service_id=SERVICE_ONE_ID, _follow_redirects=True, ) @@ -294,11 +324,14 @@ def test_email_branding_govuk_submit( SERVICE_ONE_ID, email_branding=None, ) - assert page.h1.text == 'Settings' - assert normalize_spaces(page.select_one('.banner-default').text) == 'You’ve updated your email branding' + assert page.h1.text == "Settings" + assert ( + normalize_spaces(page.select_one(".banner-default").text) + == "You’ve updated your email branding" + ) -@pytest.mark.skip(reason='Update for TTS') +@pytest.mark.skip(reason="Update for TTS") def test_email_branding_govuk_and_org_submit( mocker, client_request, @@ -309,55 +342,57 @@ def test_email_branding_govuk_and_org_submit( single_sms_sender, ): mocker.patch( - 'app.organizations_client.get_organization', + "app.organizations_client.get_organization", return_value=organization_one, ) mocker.patch( - 'app.models.service.Service.organization_id', + "app.models.service.Service.organization_id", new_callable=PropertyMock, return_value=ORGANISATION_ID, ) - service_one['email_branding'] = sample_uuid() + service_one["email_branding"] = sample_uuid() - mock_create_ticket = mocker.spy(NotifySupportTicket, '__init__') + mock_create_ticket = mocker.spy(NotifySupportTicket, "__init__") mock_send_ticket_to_zendesk = mocker.patch( - 'app.main.views.service_settings.zendesk_client.send_ticket_to_zendesk', + "app.main.views.service_settings.zendesk_client.send_ticket_to_zendesk", autospec=True, ) page = client_request.post( - '.email_branding_govuk_and_org', + ".email_branding_govuk_and_org", service_id=SERVICE_ONE_ID, _follow_redirects=True, ) mock_create_ticket.assert_called_once_with( ANY, - message='\n'.join([ - 'Organization: organization one', - 'Service: service one', - 'http://localhost/services/596364a0-858e-42c8-9062-a8fe822260eb', - '', - '---', - 'Current branding: Organization name', - 'Branding requested: GOV.UK and organization one\n', - ]), - subject='Email branding request - service one', - ticket_type='question', - user_name='Test User', - user_email='test@user.gsa.gov', + message="\n".join( + [ + "Organization: organization one", + "Service: service one", + "http://localhost/services/596364a0-858e-42c8-9062-a8fe822260eb", + "", + "---", + "Current branding: Organization name", + "Branding requested: GOV.UK and organization one\n", + ] + ), + subject="Email branding request - service one", + ticket_type="question", + user_name="Test User", + user_email="test@user.gsa.gov", org_id=ORGANISATION_ID, - org_type='central', - service_id=SERVICE_ONE_ID + org_type="central", + service_id=SERVICE_ONE_ID, ) mock_send_ticket_to_zendesk.assert_called_once() - assert normalize_spaces(page.select_one('.banner-default').text) == ( - 'Thanks for your branding request. We’ll get back to you ' - 'within one working day.' + assert normalize_spaces(page.select_one(".banner-default").text) == ( + "Thanks for your branding request. We’ll get back to you " + "within one working day." ) -@pytest.mark.skip(reason='Update for TTS') +@pytest.mark.skip(reason="Update for TTS") def test_email_branding_organization_submit( mocker, client_request, @@ -368,51 +403,53 @@ def test_email_branding_organization_submit( single_sms_sender, ): mocker.patch( - 'app.organizations_client.get_organization', + "app.organizations_client.get_organization", return_value=organization_one, ) mocker.patch( - 'app.models.service.Service.organization_id', + "app.models.service.Service.organization_id", new_callable=PropertyMock, return_value=ORGANISATION_ID, ) - service_one['email_branding'] = sample_uuid() + service_one["email_branding"] = sample_uuid() - mock_create_ticket = mocker.spy(NotifySupportTicket, '__init__') + mock_create_ticket = mocker.spy(NotifySupportTicket, "__init__") mock_send_ticket_to_zendesk = mocker.patch( - 'app.main.views.service_settings.zendesk_client.send_ticket_to_zendesk', + "app.main.views.service_settings.zendesk_client.send_ticket_to_zendesk", autospec=True, ) page = client_request.post( - '.email_branding_organization', + ".email_branding_organization", service_id=SERVICE_ONE_ID, _follow_redirects=True, ) mock_create_ticket.assert_called_once_with( ANY, - message='\n'.join([ - 'Organization: organization one', - 'Service: service one', - 'http://localhost/services/596364a0-858e-42c8-9062-a8fe822260eb', - '', - '---', - 'Current branding: Organization name', - 'Branding requested: organization one\n', - ]), - subject='Email branding request - service one', - ticket_type='question', - user_name='Test User', - user_email='test@user.gsa.gov', + message="\n".join( + [ + "Organization: organization one", + "Service: service one", + "http://localhost/services/596364a0-858e-42c8-9062-a8fe822260eb", + "", + "---", + "Current branding: Organization name", + "Branding requested: organization one\n", + ] + ), + subject="Email branding request - service one", + ticket_type="question", + user_name="Test User", + user_email="test@user.gsa.gov", org_id=ORGANISATION_ID, - org_type='central', - service_id=SERVICE_ONE_ID + org_type="central", + service_id=SERVICE_ONE_ID, ) mock_send_ticket_to_zendesk.assert_called_once() - assert normalize_spaces(page.select_one('.banner-default').text) == ( - 'Thanks for your branding request. We’ll get back to you ' - 'within one working day.' + assert normalize_spaces(page.select_one(".banner-default").text) == ( + "Thanks for your branding request. We’ll get back to you " + "within one working day." ) @@ -424,46 +461,48 @@ def test_email_branding_something_else_submit( mock_get_email_branding, single_sms_sender, ): - service_one['email_branding'] = sample_uuid() - service_one['organization_type'] = 'nhs_local' + service_one["email_branding"] = sample_uuid() + service_one["organization_type"] = "nhs_local" - mock_create_ticket = mocker.spy(NotifySupportTicket, '__init__') + mock_create_ticket = mocker.spy(NotifySupportTicket, "__init__") mock_send_ticket_to_zendesk = mocker.patch( - 'app.main.views.service_settings.zendesk_client.send_ticket_to_zendesk', + "app.main.views.service_settings.zendesk_client.send_ticket_to_zendesk", autospec=True, ) page = client_request.post( - '.email_branding_something_else', + ".email_branding_something_else", service_id=SERVICE_ONE_ID, - _data={'something_else': 'Homer Simpson'}, + _data={"something_else": "Homer Simpson"}, _follow_redirects=True, ) mock_create_ticket.assert_called_once_with( ANY, - message='\n'.join([ - 'Organization: Can’t tell (domain is user.gsa.gov)', - 'Service: service one', - 'http://localhost/services/596364a0-858e-42c8-9062-a8fe822260eb', - '', - '---', - 'Current branding: Organization name', - 'Branding requested: Something else\n', - 'Homer Simpson\n' - ]), - subject='Email branding request - service one', - ticket_type='question', - user_name='Test User', - user_email='test@user.gsa.gov', + message="\n".join( + [ + "Organization: Can’t tell (domain is user.gsa.gov)", + "Service: service one", + "http://localhost/services/596364a0-858e-42c8-9062-a8fe822260eb", + "", + "---", + "Current branding: Organization name", + "Branding requested: Something else\n", + "Homer Simpson\n", + ] + ), + subject="Email branding request - service one", + ticket_type="question", + user_name="Test User", + user_email="test@user.gsa.gov", org_id=None, - org_type='nhs_local', - service_id=SERVICE_ONE_ID + org_type="nhs_local", + service_id=SERVICE_ONE_ID, ) mock_send_ticket_to_zendesk.assert_called_once() - assert normalize_spaces(page.select_one('.banner-default').text) == ( - 'Thanks for your branding request. We’ll get back to you ' - 'within one working day.' + assert normalize_spaces(page.select_one(".banner-default").text) == ( + "Thanks for your branding request. We’ll get back to you " + "within one working day." ) @@ -471,10 +510,13 @@ def test_email_branding_something_else_submit_shows_error_if_textbox_is_empty( client_request, ): page = client_request.post( - '.email_branding_something_else', + ".email_branding_something_else", service_id=SERVICE_ONE_ID, - _data={'something_else': ''}, + _data={"something_else": ""}, _follow_redirects=True, ) - assert normalize_spaces(page.h1.text) == 'Describe the branding you want' - assert normalize_spaces(page.select_one('.usa-error-message').text) == 'Error: Cannot be empty' + assert normalize_spaces(page.h1.text) == "Describe the branding you want" + assert ( + normalize_spaces(page.select_one(".usa-error-message").text) + == "Error: Cannot be empty" + ) diff --git a/tests/app/main/views/service_settings/test_inbound_sms_setting.py b/tests/app/main/views/service_settings/test_inbound_sms_setting.py index c4ac86de5..ee74382b1 100644 --- a/tests/app/main/views/service_settings/test_inbound_sms_setting.py +++ b/tests/app/main/views/service_settings/test_inbound_sms_setting.py @@ -7,15 +7,15 @@ def test_set_inbound_sms_sets_a_number_for_service( multiple_available_inbound_numbers, fake_uuid, mock_no_inbound_number_for_service, - mocker + mocker, ): - mocker.patch('app.service_api_client.update_service') + mocker.patch("app.service_api_client.update_service") data = { "inbound_number": "781d9c60-7a7e-46b7-9896-7b045b992fa5", } client_request.post( - 'main.service_set_inbound_number', + "main.service_set_inbound_number", service_id=SERVICE_ONE_ID, _data=data, _expected_status=302, @@ -25,23 +25,25 @@ def test_set_inbound_sms_sets_a_number_for_service( SERVICE_ONE_ID, sms_sender="781d9c60-7a7e-46b7-9896-7b045b992fa5", is_default=True, - inbound_number_id="781d9c60-7a7e-46b7-9896-7b045b992fa5" + inbound_number_id="781d9c60-7a7e-46b7-9896-7b045b992fa5", ) def test_set_inbound_sms_when_no_available_inbound_numbers( - client_request, - service_one, - no_available_inbound_numbers, - mock_no_inbound_number_for_service, - mocker + client_request, + service_one, + no_available_inbound_numbers, + mock_no_inbound_number_for_service, + mocker, ): page = client_request.get( - 'main.service_set_inbound_number', - service_id=service_one['id'] + "main.service_set_inbound_number", service_id=service_one["id"] ) - assert normalize_spaces(page.select_one('main p').text) == "No available inbound numbers" + assert ( + normalize_spaces(page.select_one("main p").text) + == "No available inbound numbers" + ) def test_set_inbound_sms_when_service_already_has_sms( @@ -50,13 +52,14 @@ def test_set_inbound_sms_when_service_already_has_sms( multiple_available_inbound_numbers, mock_get_inbound_number_for_service, ): - page = client_request.get( - 'main.service_set_inbound_number', - service_id=service_one['id'] + "main.service_set_inbound_number", service_id=service_one["id"] ) - assert normalize_spaces(page.select_one('main p').text) == "This service already has an inbound number" + assert ( + normalize_spaces(page.select_one("main p").text) + == "This service already has an inbound number" + ) def test_set_inbound_sms_when_service_does_not_have_sms( @@ -65,10 +68,8 @@ def test_set_inbound_sms_when_service_does_not_have_sms( multiple_available_inbound_numbers, mock_no_inbound_number_for_service, ): - page = client_request.get( - 'main.service_set_inbound_number', - service_id=service_one['id'] + "main.service_set_inbound_number", service_id=service_one["id"] ) - assert normalize_spaces(page.select_one('input')['name']) == "inbound_number" + assert normalize_spaces(page.select_one("input")["name"]) == "inbound_number" diff --git a/tests/app/main/views/service_settings/test_service_setting_permissions.py b/tests/app/main/views/service_settings/test_service_setting_permissions.py index dd6780355..f2673cb15 100644 --- a/tests/app/main/views/service_settings/test_service_setting_permissions.py +++ b/tests/app/main/views/service_settings/test_service_setting_permissions.py @@ -20,7 +20,9 @@ def get_service_settings_page( mock_get_service_data_retention, ): client_request.login(platform_admin_user) - return functools.partial(client_request.get, 'main.service_settings', service_id=service_one['id']) + return functools.partial( + client_request.get, "main.service_settings", service_id=service_one["id"] + ) def test_service_set_permission_requires_platform_admin( @@ -30,38 +32,43 @@ def test_service_set_permission_requires_platform_admin( mock_get_inbound_number_for_service, ): client_request.post( - 'main.service_set_permission', service_id=service_one['id'], permission='email_auth', - _data={'enabled': 'True'}, - _expected_status=403 + "main.service_set_permission", + service_id=service_one["id"], + permission="email_auth", + _data={"enabled": "True"}, + _expected_status=403, ) -@pytest.mark.parametrize('initial_permissions, permission, form_data, expected_update', [ - ( - [], - 'inbound_sms', - 'True', - ['inbound_sms'], - ), - ( - ['inbound_sms'], - 'inbound_sms', - 'False', - [], - ), - ( - [], - 'email_auth', - 'True', - ['email_auth'], - ), - ( - ['email_auth'], - 'email_auth', - 'False', - [], - ), -]) +@pytest.mark.parametrize( + "initial_permissions, permission, form_data, expected_update", + [ + ( + [], + "inbound_sms", + "True", + ["inbound_sms"], + ), + ( + ["inbound_sms"], + "inbound_sms", + "False", + [], + ), + ( + [], + "email_auth", + "True", + ["email_auth"], + ), + ( + ["email_auth"], + "email_auth", + "False", + [], + ), + ], +) def test_service_set_permission( mocker, client_request, @@ -74,32 +81,49 @@ def test_service_set_permission( form_data, expected_update, ): - service_one['permissions'] = initial_permissions - mock_update_service = mocker.patch('app.service_api_client.update_service') + service_one["permissions"] = initial_permissions + mock_update_service = mocker.patch("app.service_api_client.update_service") client_request.login(platform_admin_user) client_request.post( - 'main.service_set_permission', - service_id=service_one['id'], + "main.service_set_permission", + service_id=service_one["id"], permission=permission, - _data={'enabled': form_data}, + _data={"enabled": form_data}, _expected_redirect=url_for( - 'main.service_settings', - service_id=service_one['id'], - ) + "main.service_settings", + service_id=service_one["id"], + ), ) - assert mock_update_service.call_args[0][0] == service_one['id'] - new_permissions = mock_update_service.call_args[1]['permissions'] + assert mock_update_service.call_args[0][0] == service_one["id"] + new_permissions = mock_update_service.call_args[1]["permissions"] assert len(new_permissions) == len(set(new_permissions)) assert set(new_permissions) == set(expected_update) -@pytest.mark.parametrize('service_fields, endpoint, kwargs, text', [ - ({'restricted': True}, '.service_switch_live', {}, 'Live Off Change service status'), - ({'restricted': False}, '.service_switch_live', {}, 'Live On Change service status'), - ({'permissions': ['sms']}, '.service_set_inbound_number', {}, - 'Receive inbound SMS Off Change your settings for Receive inbound SMS'), -]) +@pytest.mark.parametrize( + "service_fields, endpoint, kwargs, text", + [ + ( + {"restricted": True}, + ".service_switch_live", + {}, + "Live Off Change service status", + ), + ( + {"restricted": False}, + ".service_switch_live", + {}, + "Live On Change service status", + ), + ( + {"permissions": ["sms"]}, + ".service_set_inbound_number", + {}, + "Receive inbound SMS Off Change your settings for Receive inbound SMS", + ), + ], +) def test_service_setting_toggles_show( mocker, mock_get_service_organization, @@ -110,19 +134,27 @@ def test_service_setting_toggles_show( kwargs, text, ): - link_url = url_for(endpoint, **kwargs, service_id=service_one['id']) + link_url = url_for(endpoint, **kwargs, service_id=service_one["id"]) service_one.update(service_fields) page = get_service_settings_page() - assert normalize_spaces(page.find('a', {'href': link_url}).find_parent('tr').text.strip()) == text + assert ( + normalize_spaces( + page.find("a", {"href": link_url}).find_parent("tr").text.strip() + ) + == text + ) -@pytest.mark.parametrize('service_fields, endpoint, index, text', [ - ({'active': True}, '.archive_service', 0, 'Delete this service'), - ({'active': True}, '.suspend_service', 1, 'Suspend service'), - ({'active': True}, '.history', 2, 'Service history'), - ({'active': False}, '.resume_service', 0, 'Resume service'), - ({'active': False}, '.history', 1, 'Service history'), -]) +@pytest.mark.parametrize( + "service_fields, endpoint, index, text", + [ + ({"active": True}, ".archive_service", 0, "Delete this service"), + ({"active": True}, ".suspend_service", 1, "Suspend service"), + ({"active": True}, ".history", 2, "Service history"), + ({"active": False}, ".resume_service", 0, "Resume service"), + ({"active": False}, ".history", 1, "Service history"), + ], +) def test_service_setting_link_toggles( get_service_settings_page, service_one, @@ -131,19 +163,25 @@ def test_service_setting_link_toggles( index, text, ): - link_url = url_for(endpoint, service_id=service_one['id']) + link_url = url_for(endpoint, service_id=service_one["id"]) service_one.update(service_fields) page = get_service_settings_page() - link = page.select('.page-footer-link a')[index] + link = page.select(".page-footer-link a")[index] assert normalize_spaces(link.text) == text - assert link['href'] == link_url + assert link["href"] == link_url -@pytest.mark.parametrize('service_fields, endpoint, index, text', [ - pytest.param( - {'active': False}, '.archive_service', 2, 'Resume service', - ) -]) +@pytest.mark.parametrize( + "service_fields, endpoint, index, text", + [ + pytest.param( + {"active": False}, + ".archive_service", + 2, + "Resume service", + ) + ], +) def test_service_setting_link_toggles_index_error( get_service_settings_page, service_one, @@ -153,41 +191,45 @@ def test_service_setting_link_toggles_index_error( text, ): with pytest.raises(expected_exception=IndexError): - url_for(endpoint, service_id=service_one['id']) + url_for(endpoint, service_id=service_one["id"]) service_one.update(service_fields) page = get_service_settings_page() - page.select('.page-footer-link a')[index] + page.select(".page-footer-link a")[index] -@pytest.mark.parametrize('permissions,permissions_text,visible', [ - ('sms', 'inbound SMS', True), - ('inbound_sms', 'inbound SMS', False), # no sms parent permission - # also test no permissions set - ('', 'inbound SMS', False), -]) +@pytest.mark.parametrize( + "permissions,permissions_text,visible", + [ + ("sms", "inbound SMS", True), + ("inbound_sms", "inbound SMS", False), # no sms parent permission + # also test no permissions set + ("", "inbound SMS", False), + ], +) def test_service_settings_doesnt_show_option_if_parent_permission_disabled( - get_service_settings_page, - service_one, - permissions, - permissions_text, - visible + get_service_settings_page, service_one, permissions, permissions_text, visible ): - service_one['permissions'] = [permissions] + service_one["permissions"] = [permissions] page = get_service_settings_page() - cells = page.find_all('td') + cells = page.find_all("td") assert any(cell for cell in cells if permissions_text in cell.text) is visible -@pytest.mark.parametrize('service_fields, link_text', [ - # can't archive or suspend inactive service. Can't resume active service. - ({'active': False}, 'Archive service'), - ({'active': False}, 'Suspend service'), - ({'active': True}, 'Resume service'), -]) -def test_service_setting_toggles_dont_show(get_service_settings_page, service_one, service_fields, link_text): +@pytest.mark.parametrize( + "service_fields, link_text", + [ + # can't archive or suspend inactive service. Can't resume active service. + ({"active": False}, "Archive service"), + ({"active": False}, "Suspend service"), + ({"active": True}, "Resume service"), + ], +) +def test_service_setting_toggles_dont_show( + get_service_settings_page, service_one, service_fields, link_text +): service_one.update(service_fields) page = get_service_settings_page() - toggles = page.find_all('a', {'class': 'usa-link'}) + toggles = page.find_all("a", {"class": "usa-link"}) assert not any(link for link in toggles if link_text in link.text) @@ -199,10 +241,13 @@ def test_normal_user_doesnt_see_any_platform_admin_settings( single_sms_sender, mock_get_inbound_number_for_service, mock_get_free_sms_fragment_limit, - mock_get_service_data_retention + mock_get_service_data_retention, ): - page = client_request.get('main.service_settings', service_id=service_one['id']) - platform_admin_settings = [permission['title'] for permission in PLATFORM_ADMIN_SERVICE_PERMISSIONS.values()] + page = client_request.get("main.service_settings", service_id=service_one["id"]) + platform_admin_settings = [ + permission["title"] + for permission in PLATFORM_ADMIN_SERVICE_PERMISSIONS.values() + ] for permission in platform_admin_settings: assert permission not in page diff --git a/tests/app/main/views/service_settings/test_service_settings.py b/tests/app/main/views/service_settings/test_service_settings.py index ccc5414fd..907777355 100644 --- a/tests/app/main/views/service_settings/test_service_settings.py +++ b/tests/app/main/views/service_settings/test_service_settings.py @@ -48,68 +48,74 @@ def mock_get_service_settings_page_common( return -@pytest.mark.parametrize('user, expected_rows', [ - (create_active_user_with_permissions(), [ - - 'Label Value Action', - 'Service name Test Service Change service name', - 'Sign-in method Text message code Change sign-in method', - 'Send text messages On Change your settings for sending text messages', - 'Start text messages with service name On Change your settings for starting text messages with service name', - 'Send international text messages Off Change your settings for sending international text messages', - - ]), - (create_platform_admin_user(), [ - - 'Label Value Action', - 'Service name Test Service Change service name', - 'Sign-in method Text message code Change sign-in method', - 'Send text messages On Change your settings for sending text messages', - 'Text message senders (Only visible to Platform Admins) GOVUK Manage text message senders', - 'Start text messages with service name On Change your settings for starting text messages with service name', - 'Send international text messages Off Change your settings for sending international text messages', - - - 'Label Value Action', - 'Live Off Change service status', - 'Count in list of live services Yes Change if service is counted in list of live services', - 'Billing details None Change billing details for service', - 'Notes None Change the notes for the service', - 'Organization Test organization Federal government Change organization for service', - 'Rate limit 3,000 per minute Change rate limit', - 'Message limit 1,000 per day Change daily message limit', - 'Free text message allowance 250,000 per year Change free text message allowance', - 'Email branding GOV.UK Change email branding (admin view)', - 'Custom data retention Email – 7 days Change data retention', - 'Receive inbound SMS Off Change your settings for Receive inbound SMS', - 'Email authentication Off Change your settings for Email authentication', - ]), -]) +@pytest.mark.parametrize( + "user, expected_rows", + [ + ( + create_active_user_with_permissions(), + [ + "Label Value Action", + "Service name Test Service Change service name", + "Sign-in method Text message code Change sign-in method", + "Send text messages On Change your settings for sending text messages", + "Start text messages with service name On Change your settings " + "for starting text messages with service name", + "Send international text messages Off Change your settings for sending international text messages", + ], + ), + ( + create_platform_admin_user(), + [ + "Label Value Action", + "Service name Test Service Change service name", + "Sign-in method Text message code Change sign-in method", + "Send text messages On Change your settings for sending text messages", + "Text message senders (Only visible to Platform Admins) GOVUK Manage text message senders", + "Start text messages with service name On Change your settings " + "for starting text messages with service name", + "Send international text messages Off Change your settings for sending international text messages", + "Label Value Action", + "Live Off Change service status", + "Count in list of live services Yes Change if service is counted in list of live services", + "Billing details None Change billing details for service", + "Notes None Change the notes for the service", + "Organization Test organization Federal government Change organization for service", + "Rate limit 3,000 per minute Change rate limit", + "Message limit 1,000 per day Change daily message limit", + "Free text message allowance 250,000 per year Change free text message allowance", + "Email branding GOV.UK Change email branding (admin view)", + "Custom data retention Email – 7 days Change data retention", + "Receive inbound SMS Off Change your settings for Receive inbound SMS", + "Email authentication Off Change your settings for Email authentication", + ], + ), + ], +) def test_should_show_overview( - client_request, - mocker, - api_user_active, - no_reply_to_email_addresses, - single_sms_sender, - user, - expected_rows, - mock_get_service_settings_page_common, + client_request, + mocker, + api_user_active, + no_reply_to_email_addresses, + single_sms_sender, + user, + expected_rows, + mock_get_service_settings_page_common, ): service_one = service_json( SERVICE_ONE_ID, - users=[api_user_active['id']], - permissions=['sms', 'email'], + users=[api_user_active["id"]], + permissions=["sms", "email"], organization_id=ORGANISATION_ID, - contact_link='contact_us@gsa.gov', + contact_link="contact_us@gsa.gov", + ) + mocker.patch( + "app.service_api_client.get_service", return_value={"data": service_one} ) - mocker.patch('app.service_api_client.get_service', return_value={'data': service_one}) client_request.login(user, service_one) - page = client_request.get( - 'main.service_settings', service_id=SERVICE_ONE_ID - ) - assert page.find('h1').text == 'Settings' - rows = page.select('tr') + page = client_request.get("main.service_settings", service_id=SERVICE_ONE_ID) + assert page.find("h1").text == "Settings" + rows = page.select("tr") assert len(rows) == len(expected_rows) for index, row in enumerate(expected_rows): assert row == " ".join(rows[index].text.split()) @@ -124,18 +130,29 @@ def test_no_go_live_link_for_service_without_organization( platform_admin_user, mock_get_service_settings_page_common, ): - mocker.patch('app.organizations_client.get_organization', return_value=None) + mocker.patch("app.organizations_client.get_organization", return_value=None) client_request.login(platform_admin_user) - page = client_request.get('main.service_settings', service_id=SERVICE_ONE_ID) + page = client_request.get("main.service_settings", service_id=SERVICE_ONE_ID) - assert page.find('h1').text == 'Settings' + assert page.find("h1").text == "Settings" - is_live = find_element_by_tag_and_partial_text(page, tag='td', string='Live') - assert normalize_spaces(is_live.find_next_sibling().text) == 'No (organization must be set first)' + is_live = find_element_by_tag_and_partial_text(page, tag="td", string="Live") + assert ( + normalize_spaces(is_live.find_next_sibling().text) + == "No (organization must be set first)" + ) - organization = find_element_by_tag_and_partial_text(page, tag='td', string='Organization') - assert normalize_spaces(organization.find_next_siblings()[0].text) == 'Not set Federal government' - assert normalize_spaces(organization.find_next_siblings()[1].text) == 'Change organization for service' + organization = find_element_by_tag_and_partial_text( + page, tag="td", string="Organization" + ) + assert ( + normalize_spaces(organization.find_next_siblings()[0].text) + == "Not set Federal government" + ) + assert ( + normalize_spaces(organization.find_next_siblings()[1].text) + == "Change organization for service" + ) def test_organization_name_links_to_org_dashboard( @@ -146,27 +163,37 @@ def test_organization_name_links_to_org_dashboard( mock_get_service_settings_page_common, mocker, ): - service_one = service_json(SERVICE_ONE_ID, - permissions=['sms', 'email'], - organization_id=ORGANISATION_ID) - - mocker.patch('app.service_api_client.get_service', return_value={'data': service_one}) - - client_request.login(platform_admin_user, service_one) - response = client_request.get( - 'main.service_settings', service_id=SERVICE_ONE_ID + service_one = service_json( + SERVICE_ONE_ID, permissions=["sms", "email"], organization_id=ORGANISATION_ID ) - org_row = find_element_by_tag_and_partial_text(response, tag='tr', string='Organization') - assert org_row.find('a')['href'] == url_for('main.organization_dashboard', org_id=ORGANISATION_ID) - assert normalize_spaces(org_row.find('a').text) == 'Test organization' + mocker.patch( + "app.service_api_client.get_service", return_value={"data": service_one} + ) + + client_request.login(platform_admin_user, service_one) + response = client_request.get("main.service_settings", service_id=SERVICE_ONE_ID) + + org_row = find_element_by_tag_and_partial_text( + response, tag="tr", string="Organization" + ) + assert org_row.find("a")["href"] == url_for( + "main.organization_dashboard", org_id=ORGANISATION_ID + ) + assert normalize_spaces(org_row.find("a").text) == "Test organization" @pytest.mark.skip(reason="Email currently deactivated") -@pytest.mark.parametrize('service_contact_link,expected_text', [ - ('contact.me@gsa.gov', 'Send files by email contact.me@gsa.gov Manage sending files by email'), - (None, 'Send files by email Not set up Manage sending files by email'), -]) +@pytest.mark.parametrize( + "service_contact_link,expected_text", + [ + ( + "contact.me@gsa.gov", + "Send files by email contact.me@gsa.gov Manage sending files by email", + ), + (None, "Send files by email Not set up Manage sending files by email"), + ], +) def test_send_files_by_email_row_on_settings_page( client_request, platform_admin_user, @@ -175,81 +202,90 @@ def test_send_files_by_email_row_on_settings_page( mock_get_service_settings_page_common, mocker, service_contact_link, - expected_text + expected_text, ): service_one = service_json( SERVICE_ONE_ID, - permissions=['sms', 'email'], + permissions=["sms", "email"], organization_id=ORGANISATION_ID, - contact_link=service_contact_link + contact_link=service_contact_link, ) - mocker.patch('app.service_api_client.get_service', return_value={'data': service_one}) + mocker.patch( + "app.service_api_client.get_service", return_value={"data": service_one} + ) client_request.login(platform_admin_user, service_one) - response = client_request.get( - 'main.service_settings', service_id=SERVICE_ONE_ID - ) + response = client_request.get("main.service_settings", service_id=SERVICE_ONE_ID) - org_row = find_element_by_tag_and_partial_text(response, tag='tr', string='Send files by email') + org_row = find_element_by_tag_and_partial_text( + response, tag="tr", string="Send files by email" + ) assert normalize_spaces(org_row.get_text()) == expected_text -@pytest.mark.parametrize('permissions, expected_rows', [ - (['email', 'sms', 'international_sms'], [ - - 'Service name service one Change service name', - 'Sign-in method Text message code Change sign-in method', - 'Send text messages On Change your settings for sending text messages', - 'Start text messages with service name On Change your settings for starting text messages with service name', - 'Send international text messages On Change your settings for sending international text messages', - - - ]), - (['email', 'sms', 'email_auth'], [ - - 'Service name service one Change service name', - 'Sign-in method Email link or text message code Change sign-in method', - 'Send text messages On Change your settings for sending text messages', - 'Start text messages with service name On Change your settings for starting text messages with service name', - 'Send international text messages Off Change your settings for sending international text messages', - - - ]), -]) +@pytest.mark.parametrize( + "permissions, expected_rows", + [ + ( + ["email", "sms", "international_sms"], + [ + "Service name service one Change service name", + "Sign-in method Text message code Change sign-in method", + "Send text messages On Change your settings for sending text messages", + "Start text messages with service name On Change your settings " + "for starting text messages with service name", + "Send international text messages On Change your settings for sending international text messages", + ], + ), + ( + ["email", "sms", "email_auth"], + [ + "Service name service one Change service name", + "Sign-in method Email link or text message code Change sign-in method", + "Send text messages On Change your settings for sending text messages", + "Start text messages with service name On Change your settings " + "for starting text messages with service name", + "Send international text messages Off Change your settings for sending international text messages", + ], + ), + ], +) def test_should_show_overview_for_service_with_more_things_set( - client_request, - active_user_with_permissions, - mocker, - service_one, - single_reply_to_email_address, - single_sms_sender, - mock_get_email_branding, - mock_get_service_settings_page_common, - permissions, - expected_rows + client_request, + active_user_with_permissions, + mocker, + service_one, + single_reply_to_email_address, + single_sms_sender, + mock_get_email_branding, + mock_get_service_settings_page_common, + permissions, + expected_rows, ): client_request.login(active_user_with_permissions) - service_one['permissions'] = permissions - service_one['email_branding'] = uuid4() - page = client_request.get( - 'main.service_settings', service_id=service_one['id'] - ) + service_one["permissions"] = permissions + service_one["email_branding"] = uuid4() + page = client_request.get("main.service_settings", service_id=service_one["id"]) for index, row in enumerate(expected_rows): - assert row == " ".join(page.find_all('tr')[index + 1].text.split()) + assert row == " ".join(page.find_all("tr")[index + 1].text.split()) def test_should_show_service_name( client_request, ): - page = client_request.get('main.service_name_change', service_id=SERVICE_ONE_ID) - assert page.find('h1').text == 'Change your service name' - assert page.find('input', attrs={"type": "text"})['value'] == 'service one' - assert page.select_one( - 'main .govuk-body' - ).text == 'Your service name should tell users what the message is about as well as who it’s from.' + page = client_request.get("main.service_name_change", service_id=SERVICE_ONE_ID) + assert page.find("h1").text == "Change your service name" + assert page.find("input", attrs={"type": "text"})["value"] == "service one" + assert ( + page.select_one("main .govuk-body").text + == "Your service name should tell users what the message is about as well as who it’s from." + ) - assert "The service name you enter here will appear at the beginning of each text message, unless" in page.text + assert ( + "The service name you enter here will appear at the beginning of each text message, unless" + in page.text + ) app.service_api_client.get_service.assert_called_with(SERVICE_ONE_ID) @@ -260,18 +296,21 @@ def test_should_show_different_change_service_name_page_for_local_services( mocker, ): mocker.patch( - 'app.organizations_client.get_organization_by_domain', - return_value=organization_json(organization_type='local'), + "app.organizations_client.get_organization_by_domain", + return_value=organization_json(organization_type="local"), ) - service_one['organization_type'] = 'local' - page = client_request.get('main.service_name_change', service_id=SERVICE_ONE_ID) - assert page.find('h1').text == 'Change your service name' - assert page.find('input', attrs={"type": "text"})['value'] == 'service one' - assert page.select_one('main .govuk-body').text.strip() == ( - 'Your service name should tell users what the message is about as well as who it’s from. For example:' + service_one["organization_type"] = "local" + page = client_request.get("main.service_name_change", service_id=SERVICE_ONE_ID) + assert page.find("h1").text == "Change your service name" + assert page.find("input", attrs={"type": "text"})["value"] == "service one" + assert page.select_one("main .govuk-body").text.strip() == ( + "Your service name should tell users what the message is about as well as who it’s from. For example:" ) # when no organization on the service object, default org for the user is used for hint - assert "School admissions - Test Org" in page.find_all("ul", class_="govuk-list govuk-list--bullet")[0].text + assert ( + "School admissions - Test Org" + in page.find_all("ul", class_="govuk-list govuk-list--bullet")[0].text + ) app.service_api_client.get_service.assert_called_with(SERVICE_ONE_ID) @@ -282,36 +321,46 @@ def test_should_show_service_org_in_hint_on_change_service_name_page_for_local_s mocker, ): mocker.patch( - 'app.organizations_client.get_organization_by_domain', - return_value=organization_json(organization_type='local'), + "app.organizations_client.get_organization_by_domain", + return_value=organization_json(organization_type="local"), ) - mocker.patch('app.organizations_client.get_organization', return_value=organization_json( - organization_type='local', name="Local Authority") + mocker.patch( + "app.organizations_client.get_organization", + return_value=organization_json( + organization_type="local", name="Local Authority" + ), ) - service_one['organization_type'] = 'local' - service_one['organization'] = '1234' - page = client_request.get('main.service_name_change', service_id=SERVICE_ONE_ID) + service_one["organization_type"] = "local" + service_one["organization"] = "1234" + page = client_request.get("main.service_name_change", service_id=SERVICE_ONE_ID) # when there is organization on the service object, it is used for hint text instead of user default org - assert "School admissions - Local Authority" in page.find_all("ul", class_="govuk-list govuk-list--bullet")[0].text + assert ( + "School admissions - Local Authority" + in page.find_all("ul", class_="govuk-list govuk-list--bullet")[0].text + ) def test_should_show_service_name_with_no_prefixing( client_request, service_one, ): - service_one['prefix_sms'] = False - page = client_request.get('main.service_name_change', service_id=SERVICE_ONE_ID) - assert page.find('h1').text == 'Change your service name' - assert page.select_one( - 'main .govuk-body' - ).text == 'Your service name should tell users what the message is about as well as who it’s from.' + service_one["prefix_sms"] = False + page = client_request.get("main.service_name_change", service_id=SERVICE_ONE_ID) + assert page.find("h1").text == "Change your service name" + assert ( + page.select_one("main .govuk-body").text + == "Your service name should tell users what the message is about as well as who it’s from." + ) -@pytest.mark.parametrize('name, error_message', [ - ('', 'Cannot be empty'), - ('.', 'Must include at least two alphanumeric characters'), - ('a' * 256, 'Service name must be 255 characters or fewer'), -]) +@pytest.mark.parametrize( + "name, error_message", + [ + ("", "Cannot be empty"), + (".", "Must include at least two alphanumeric characters"), + ("a" * 256, "Service name must be 255 characters or fewer"), + ], +) def test_service_name_change_fails_if_new_name_fails_validation( client_request, mock_update_service, @@ -319,27 +368,30 @@ def test_service_name_change_fails_if_new_name_fails_validation( error_message, ): page = client_request.post( - 'main.service_name_change', + "main.service_name_change", service_id=SERVICE_ONE_ID, - _data={'name': name}, + _data={"name": name}, _expected_status=200, ) assert not mock_update_service.called assert error_message in page.find("span", {"class": "usa-error-message"}).text -@pytest.mark.parametrize('user, expected_text, expected_link', [ - ( - create_active_user_with_permissions(), - 'To remove these restrictions, you can send us a request to go live.', - True, - ), - ( - create_active_user_no_settings_permission(), - 'Your service manager can ask to have these restrictions removed.', - False, - ), -]) +@pytest.mark.parametrize( + "user, expected_text, expected_link", + [ + ( + create_active_user_with_permissions(), + "To remove these restrictions, you can send us a request to go live.", + True, + ), + ( + create_active_user_no_settings_permission(), + "Your service manager can ask to have these restrictions removed.", + False, + ), + ], +) def test_show_restricted_service( client_request, service_one, @@ -352,21 +404,21 @@ def test_show_restricted_service( ): client_request.login(user) page = client_request.get( - 'main.service_settings', + "main.service_settings", service_id=SERVICE_ONE_ID, ) - assert page.find('h1').text == 'Settings' - assert page.select('main h2')[0].text == 'Your service is in trial mode' + assert page.find("h1").text == "Settings" + assert page.select("main h2")[0].text == "Your service is in trial mode" - request_to_live = page.select('main p')[1] - request_to_live_link = request_to_live.select_one('a') + request_to_live = page.select("main p")[1] + request_to_live_link = request_to_live.select_one("a") assert normalize_spaces(request_to_live.text) == expected_text if expected_link: - assert request_to_live_link.text.strip() == 'request to go live' - email_address = 'notify-support@gsa.gov' - assert request_to_live_link['href'] == f'mailto:{email_address}' + assert request_to_live_link.text.strip() == "request to go live" + email_address = "notify-support@gsa.gov" + assert request_to_live_link["href"] == f"mailto:{email_address}" else: assert not request_to_live_link @@ -376,24 +428,24 @@ def test_switch_service_to_live( client_request, platform_admin_user, mock_update_service, - mock_get_inbound_number_for_service + mock_get_inbound_number_for_service, ): client_request.login(platform_admin_user) client_request.post( - 'main.service_switch_live', + "main.service_switch_live", service_id=SERVICE_ONE_ID, - _data={'enabled': 'True'}, + _data={"enabled": "True"}, _expected_status=302, _expected_redirect=url_for( - 'main.service_settings', + "main.service_settings", service_id=SERVICE_ONE_ID, - ) + ), ) mock_update_service.assert_called_with( SERVICE_ONE_ID, message_limit=250000, restricted=False, - go_live_at="2017-04-01 11:09:00.061258" + go_live_at="2017-04-01 11:09:00.061258", ) @@ -405,11 +457,11 @@ def test_show_live_service( mock_get_service_settings_page_common, ): page = client_request.get( - 'main.service_settings', + "main.service_settings", service_id=SERVICE_ONE_ID, ) - assert page.find('h1').text.strip() == 'Settings' - assert 'Your service is in trial mode' not in page.text + assert page.find("h1").text.strip() == "Settings" + assert "Your service is in trial mode" not in page.text def test_switch_service_to_restricted( @@ -421,27 +473,27 @@ def test_switch_service_to_restricted( ): client_request.login(platform_admin_user) client_request.post( - 'main.service_switch_live', + "main.service_switch_live", service_id=SERVICE_ONE_ID, - _data={'enabled': 'False'}, + _data={"enabled": "False"}, _expected_status=302, _expected_response=url_for( - 'main.service_settings', + "main.service_settings", service_id=SERVICE_ONE_ID, - ) + ), ) mock_update_service.assert_called_with( - SERVICE_ONE_ID, - message_limit=50, - restricted=True, - go_live_at=None + SERVICE_ONE_ID, message_limit=50, restricted=True, go_live_at=None ) -@pytest.mark.parametrize('count_as_live, selected, labelled', ( - (True, 'True', 'Yes'), - (False, 'False', 'No'), -)) +@pytest.mark.parametrize( + "count_as_live, selected, labelled", + ( + (True, "True", "Yes"), + (False, "False", "No"), + ), +) def test_show_switch_service_to_count_as_live_page( mocker, client_request, @@ -452,26 +504,32 @@ def test_show_switch_service_to_count_as_live_page( labelled, ): mocker.patch( - 'app.models.service.Service.count_as_live', + "app.models.service.Service.count_as_live", create=True, new_callable=PropertyMock, return_value=count_as_live, ) client_request.login(platform_admin_user) page = client_request.get( - 'main.service_switch_count_as_live', + "main.service_switch_count_as_live", service_id=SERVICE_ONE_ID, ) - assert page.select_one('[checked]')['value'] == selected - assert page.select_one('label[for={}]'.format( - page.select_one('[checked]')['id'] - )).text.strip() == labelled + assert page.select_one("[checked]")["value"] == selected + assert ( + page.select_one( + "label[for={}]".format(page.select_one("[checked]")["id"]) + ).text.strip() + == labelled + ) -@pytest.mark.parametrize('post_data, expected_persisted_value', ( - ('True', True), - ('False', False), -)) +@pytest.mark.parametrize( + "post_data, expected_persisted_value", + ( + ("True", True), + ("False", False), + ), +) def test_switch_service_to_count_as_live( client_request, platform_admin_user, @@ -481,14 +539,14 @@ def test_switch_service_to_count_as_live( ): client_request.login(platform_admin_user) client_request.post( - 'main.service_switch_count_as_live', + "main.service_switch_count_as_live", service_id=SERVICE_ONE_ID, - _data={'enabled': post_data}, + _data={"enabled": post_data}, _expected_status=302, _expected_redirect=url_for( - 'main.service_settings', + "main.service_settings", service_id=SERVICE_ONE_ID, - ) + ), ) mock_update_service.assert_called_with( SERVICE_ONE_ID, @@ -502,13 +560,13 @@ def test_should_not_allow_duplicate_service_names( service_one, ): page = client_request.post( - 'main.service_name_change', + "main.service_name_change", service_id=SERVICE_ONE_ID, - _data={'name': "SErvICE TWO"}, + _data={"name": "SErvICE TWO"}, _expected_status=200, ) - assert 'This service name is already in use' in page.text + assert "This service name is already in use" in page.text def test_should_redirect_after_service_name_change( @@ -516,32 +574,33 @@ def test_should_redirect_after_service_name_change( mock_update_service, ): client_request.post( - 'main.service_name_change', + "main.service_name_change", service_id=SERVICE_ONE_ID, - _data={ - 'name': 'New Name' - }, + _data={"name": "New Name"}, _expected_status=302, _expected_redirect=url_for( - 'main.service_settings', + "main.service_settings", service_id=SERVICE_ONE_ID, ), ) mock_update_service.assert_called_once_with( SERVICE_ONE_ID, - name='New Name', - email_from='new.name', + name="New Name", + email_from="new.name", ) -@pytest.mark.parametrize('volumes, consent_to_research, expected_estimated_volumes_item', [ - ((0, 0), None, 'Tell us how many messages you expect to send Not completed'), - ((1, 0), None, 'Tell us how many messages you expect to send Not completed'), - ((1, 0), False, 'Tell us how many messages you expect to send Completed'), - ((1, 0), True, 'Tell us how many messages you expect to send Completed'), - ((9, 99), True, 'Tell us how many messages you expect to send Completed'), -]) +@pytest.mark.parametrize( + "volumes, consent_to_research, expected_estimated_volumes_item", + [ + ((0, 0), None, "Tell us how many messages you expect to send Not completed"), + ((1, 0), None, "Tell us how many messages you expect to send Not completed"), + ((1, 0), False, "Tell us how many messages you expect to send Completed"), + ((1, 0), True, "Tell us how many messages you expect to send Completed"), + ((9, 99), True, "Tell us how many messages you expect to send Completed"), + ], +) def test_should_check_if_estimated_volumes_provided( client_request, mocker, @@ -555,47 +614,45 @@ def test_should_check_if_estimated_volumes_provided( consent_to_research, expected_estimated_volumes_item, ): - - for volume, channel in zip(volumes, ('sms', 'email')): + for volume, channel in zip(volumes, ("sms", "email")): mocker.patch( - 'app.models.service.Service.volume_{}'.format(channel), + "app.models.service.Service.volume_{}".format(channel), create=True, new_callable=PropertyMock, return_value=volume, ) mocker.patch( - 'app.models.service.Service.consent_to_research', + "app.models.service.Service.consent_to_research", create=True, new_callable=PropertyMock, return_value=consent_to_research, ) - page = client_request.get( - 'main.request_to_go_live', service_id=SERVICE_ONE_ID - ) - assert page.h1.text == 'Before you request to go live' + page = client_request.get("main.request_to_go_live", service_id=SERVICE_ONE_ID) + assert page.h1.text == "Before you request to go live" - assert normalize_spaces( - page.select_one('.task-list .task-list-item').text - ) == ( + assert normalize_spaces(page.select_one(".task-list .task-list-item").text) == ( expected_estimated_volumes_item ) -@pytest.mark.parametrize(( - 'volume_email,' - 'count_of_email_templates,' - 'reply_to_email_addresses,' - 'expected_reply_to_checklist_item' -), [ - (None, 1, [], 'Add a reply-to email address Not completed'), - (None, 1, [{}], 'Add a reply-to email address Completed'), - (1, 1, [], 'Add a reply-to email address Not completed'), - (1, 1, [{}], 'Add a reply-to email address Completed'), - (1, 0, [], 'Add a reply-to email address Not completed'), - (1, 0, [{}], 'Add a reply-to email address Completed'), -]) +@pytest.mark.parametrize( + ( + "volume_email," + "count_of_email_templates," + "reply_to_email_addresses," + "expected_reply_to_checklist_item" + ), + [ + (None, 1, [], "Add a reply-to email address Not completed"), + (None, 1, [{}], "Add a reply-to email address Completed"), + (1, 1, [], "Add a reply-to email address Not completed"), + (1, 1, [{}], "Add a reply-to email address Completed"), + (1, 0, [], "Add a reply-to email address Not completed"), + (1, 0, [{}], "Add a reply-to email address Completed"), + ], +) def test_should_check_for_reply_to_on_go_live( client_request, mocker, @@ -610,47 +667,50 @@ def test_should_check_for_reply_to_on_go_live( mock_get_users_by_service, ): mocker.patch( - 'app.service_api_client.get_service_templates', - return_value={'data': [ - create_template(template_type='email') - for _ in range(0, count_of_email_templates) - ]} + "app.service_api_client.get_service_templates", + return_value={ + "data": [ + create_template(template_type="email") + for _ in range(0, count_of_email_templates) + ] + }, ) mock_get_reply_to_email_addresses = mocker.patch( - 'app.main.views.service_settings.service_api_client.get_reply_to_email_addresses', - return_value=reply_to_email_addresses + "app.main.views.service_settings.service_api_client.get_reply_to_email_addresses", + return_value=reply_to_email_addresses, ) - for channel, volume in (('email', volume_email), ('sms', 0)): + for channel, volume in (("email", volume_email), ("sms", 0)): mocker.patch( - 'app.models.service.Service.volume_{}'.format(channel), + "app.models.service.Service.volume_{}".format(channel), create=True, new_callable=PropertyMock, return_value=volume, ) - page = client_request.get( - 'main.request_to_go_live', service_id=SERVICE_ONE_ID - ) - assert page.h1.text == 'Before you request to go live' + page = client_request.get("main.request_to_go_live", service_id=SERVICE_ONE_ID) + assert page.h1.text == "Before you request to go live" - checklist_items = page.select('.task-list .task-list-item') + checklist_items = page.select(".task-list .task-list-item") assert normalize_spaces(checklist_items[3].text) == expected_reply_to_checklist_item if count_of_email_templates: mock_get_reply_to_email_addresses.assert_called_once_with(SERVICE_ONE_ID) -@pytest.mark.parametrize(( - 'volume_email,' - 'count_of_email_templates,' - 'reply_to_email_addresses,' - 'expected_reply_to_checklist_item' -), [ - (None, 0, [], ''), - (0, 0, [], ''), -]) +@pytest.mark.parametrize( + ( + "volume_email," + "count_of_email_templates," + "reply_to_email_addresses," + "expected_reply_to_checklist_item" + ), + [ + (None, 0, [], ""), + (0, 0, [], ""), + ], +) def test_should_check_for_reply_to_on_go_live_index_error( client_request, mocker, @@ -665,50 +725,66 @@ def test_should_check_for_reply_to_on_go_live_index_error( mock_get_users_by_service, ): mocker.patch( - 'app.service_api_client.get_service_templates', - return_value={'data': [ - create_template(template_type='email') - for _ in range(0, count_of_email_templates) - ]} + "app.service_api_client.get_service_templates", + return_value={ + "data": [ + create_template(template_type="email") + for _ in range(0, count_of_email_templates) + ] + }, ) mocker.patch( - 'app.main.views.service_settings.service_api_client.get_reply_to_email_addresses', - return_value=reply_to_email_addresses + "app.main.views.service_settings.service_api_client.get_reply_to_email_addresses", + return_value=reply_to_email_addresses, ) - for channel, volume in (('email', volume_email), ('sms', 0)): + for channel, volume in (("email", volume_email), ("sms", 0)): mocker.patch( - 'app.models.service.Service.volume_{}'.format(channel), + "app.models.service.Service.volume_{}".format(channel), create=True, new_callable=PropertyMock, return_value=volume, ) with pytest.raises(expected_exception=IndexError): - page = client_request.get( - 'main.request_to_go_live', service_id=SERVICE_ONE_ID + page = client_request.get("main.request_to_go_live", service_id=SERVICE_ONE_ID) + assert page.h1.text == "Before you request to go live" + + checklist_items = page.select(".task-list .task-list-item") + assert ( + normalize_spaces(checklist_items[3].text) + == expected_reply_to_checklist_item ) - assert page.h1.text == 'Before you request to go live' - - checklist_items = page.select('.task-list .task-list-item') - assert normalize_spaces(checklist_items[3].text) == expected_reply_to_checklist_item -@pytest.mark.parametrize(( - 'count_of_users_with_manage_service,' - 'count_of_invites_with_manage_service,' - 'expected_user_checklist_item' -), [ - (1, 0, 'Add a team member who can manage settings, team and usage Not completed'), - (2, 0, 'Add a team member who can manage settings, team and usage Completed'), - (1, 1, 'Add a team member who can manage settings, team and usage Completed'), -]) -@pytest.mark.parametrize('count_of_templates, expected_templates_checklist_item', [ - (0, 'Add templates with examples of the content you plan to send Not completed'), - (1, 'Add templates with examples of the content you plan to send Completed'), - (2, 'Add templates with examples of the content you plan to send Completed'), -]) +@pytest.mark.parametrize( + ( + "count_of_users_with_manage_service," + "count_of_invites_with_manage_service," + "expected_user_checklist_item" + ), + [ + ( + 1, + 0, + "Add a team member who can manage settings, team and usage Not completed", + ), + (2, 0, "Add a team member who can manage settings, team and usage Completed"), + (1, 1, "Add a team member who can manage settings, team and usage Completed"), + ], +) +@pytest.mark.parametrize( + "count_of_templates, expected_templates_checklist_item", + [ + ( + 0, + "Add templates with examples of the content you plan to send Not completed", + ), + (1, "Add templates with examples of the content you plan to send Completed"), + (2, "Add templates with examples of the content you plan to send Completed"), + ], +) def test_should_check_for_sending_things_right( client_request, mocker, @@ -725,59 +801,65 @@ def test_should_check_for_sending_things_right( single_reply_to_email_address, ): mocker.patch( - 'app.service_api_client.get_service_templates', - return_value={'data': [ - create_template(template_type='sms') - for _ in range(0, count_of_templates) - ]} + "app.service_api_client.get_service_templates", + return_value={ + "data": [ + create_template(template_type="sms") + for _ in range(0, count_of_templates) + ] + }, ) mock_get_users = mocker.patch( - 'app.models.user.Users.client_method', + "app.models.user.Users.client_method", return_value=( - [active_user_with_permissions] * count_of_users_with_manage_service + - [active_user_no_settings_permission] - ) + [active_user_with_permissions] * count_of_users_with_manage_service + + [active_user_no_settings_permission] + ), + ) + invite_one = invite_json( + id_=uuid4(), + from_user=service_one["users"][0], + service_id=service_one["id"], + email_address="invited_user@test.gsa.gov", + permissions="view_activity,send_messages,manage_service,manage_api_keys", + created_at=datetime.utcnow(), + status="pending", + auth_type="sms_auth", + folder_permissions=[], ) - invite_one = invite_json(id_=uuid4(), - from_user=service_one['users'][0], - service_id=service_one['id'], - email_address='invited_user@test.gsa.gov', - permissions='view_activity,send_messages,manage_service,manage_api_keys', - created_at=datetime.utcnow(), - status='pending', - auth_type='sms_auth', - folder_permissions=[]) invite_two = invite_one.copy() - invite_two['permissions'] = 'view_activity' + invite_two["permissions"] = "view_activity" mock_get_invites = mocker.patch( - 'app.models.user.InvitedUsers.client_method', + "app.models.user.InvitedUsers.client_method", return_value=( - ([invite_one] * count_of_invites_with_manage_service) + - [invite_two] - ) + ([invite_one] * count_of_invites_with_manage_service) + [invite_two] + ), ) - page = client_request.get( - 'main.request_to_go_live', service_id=SERVICE_ONE_ID - ) - assert page.h1.text == 'Before you request to go live' + page = client_request.get("main.request_to_go_live", service_id=SERVICE_ONE_ID) + assert page.h1.text == "Before you request to go live" - checklist_items = page.select('.task-list .task-list-item') + checklist_items = page.select(".task-list .task-list-item") assert normalize_spaces(checklist_items[1].text) == expected_user_checklist_item - assert normalize_spaces(checklist_items[2].text) == expected_templates_checklist_item + assert ( + normalize_spaces(checklist_items[2].text) == expected_templates_checklist_item + ) mock_get_users.assert_called_once_with(SERVICE_ONE_ID) mock_get_invites.assert_called_once_with(SERVICE_ONE_ID) -@pytest.mark.parametrize('checklist_completed, expected_button', ( - (True, True), - (True, True), - (False, False), -)) +@pytest.mark.parametrize( + "checklist_completed, expected_button", + ( + (True, True), + (True, True), + (False, False), + ), +) def test_should_not_show_go_live_button_if_checklist_not_complete( client_request, mocker, @@ -790,125 +872,131 @@ def test_should_not_show_go_live_button_if_checklist_not_complete( expected_button, ): mocker.patch( - 'app.models.service.Service.go_live_checklist_completed', + "app.models.service.Service.go_live_checklist_completed", new_callable=PropertyMock, return_value=checklist_completed, ) - for channel in ('email', 'sms'): + for channel in ("email", "sms"): mocker.patch( - 'app.models.service.Service.volume_{}'.format(channel), + "app.models.service.Service.volume_{}".format(channel), create=True, new_callable=PropertyMock, return_value=0, ) - page = client_request.get( - 'main.request_to_go_live', service_id=SERVICE_ONE_ID - ) - assert page.h1.text == 'Before you request to go live' + page = client_request.get("main.request_to_go_live", service_id=SERVICE_ONE_ID) + assert page.h1.text == "Before you request to go live" if expected_button: - assert page.select_one('form')['method'] == 'post' - assert 'action' not in page.select_one('form') - assert normalize_spaces(page.select('main p')[0].text) == ( - 'When we receive your request we’ll get back to you within one working day.' + assert page.select_one("form")["method"] == "post" + assert "action" not in page.select_one("form") + assert normalize_spaces(page.select("main p")[0].text) == ( + "When we receive your request we’ll get back to you within one working day." ) - assert normalize_spaces(page.select('main p')[1].text) == ( - 'By requesting to go live you’re agreeing to our terms of use.' + assert normalize_spaces(page.select("main p")[1].text) == ( + "By requesting to go live you’re agreeing to our terms of use." + ) + assert page.select_one("main [type=submit]").text.strip() == ( + "Request to go live" ) - assert page.select_one('main [type=submit]').text.strip() == ('Request to go live') else: - assert not page.select('form') - assert not page.select('main [type=submit]') - assert len(page.select('main p')) == 1 - assert normalize_spaces(page.select_one('main p').text) == ( - 'You must complete these steps before you can request to go live.' + assert not page.select("form") + assert not page.select("main [type=submit]") + assert len(page.select("main p")) == 1 + assert normalize_spaces(page.select_one("main p").text) == ( + "You must complete these steps before you can request to go live." ) -@pytest.mark.parametrize('go_live_at, message', [ - (None, '‘service one’ is already live.'), - ('2020-10-09 13:55:20', '‘service one’ went live on 9 October 2020.'), -]) +@pytest.mark.parametrize( + "go_live_at, message", + [ + (None, "‘service one’ is already live."), + ("2020-10-09 13:55:20", "‘service one’ went live on 9 October 2020."), + ], +) def test_request_to_go_live_redirects_if_service_already_live( client_request, service_one, go_live_at, message, ): - service_one['restricted'] = False - service_one['go_live_at'] = go_live_at + service_one["restricted"] = False + service_one["go_live_at"] = go_live_at page = client_request.get( - 'main.request_to_go_live', + "main.request_to_go_live", service_id=SERVICE_ONE_ID, ) - assert page.h1.text == 'Your service is already live' - assert normalize_spaces(page.select_one('main p').text) == message + assert page.h1.text == "Your service is already live" + assert normalize_spaces(page.select_one("main p").text) == message -@pytest.mark.parametrize(( - 'estimated_sms_volume,' - 'organization_type,' - 'count_of_sms_templates,' - 'sms_senders,' - 'expected_sms_sender_checklist_item' -), [ +@pytest.mark.parametrize( ( - 0, - 'state', - 0, - [], - '', + "estimated_sms_volume," + "organization_type," + "count_of_sms_templates," + "sms_senders," + "expected_sms_sender_checklist_item" ), - ( - None, - 'state', - 0, - [{'is_default': True, 'sms_sender': 'GOVUK'}], - '', - ), - ( - 1, - 'federal', - 99, - [{'is_default': True, 'sms_sender': 'GOVUK'}], - '', - ), - ( - None, - 'federal', - 99, - [{'is_default': True, 'sms_sender': 'GOVUK'}], - '', - ), - ( - 1, - 'federal', - 99, - [{'is_default': True, 'sms_sender': 'GOVUK'}], - '', - ), - ( - 1, - 'state', - 1, - [], - 'Change your text message sender name Not completed', - ), - ( - 1, - 'state', - 1, - [ - {'is_default': False, 'sms_sender': 'GOVUK'}, - {'is_default': True, 'sms_sender': 'KUVOG'}, - ], - 'Change your text message sender name Completed', - ), -]) + [ + ( + 0, + "state", + 0, + [], + "", + ), + ( + None, + "state", + 0, + [{"is_default": True, "sms_sender": "GOVUK"}], + "", + ), + ( + 1, + "federal", + 99, + [{"is_default": True, "sms_sender": "GOVUK"}], + "", + ), + ( + None, + "federal", + 99, + [{"is_default": True, "sms_sender": "GOVUK"}], + "", + ), + ( + 1, + "federal", + 99, + [{"is_default": True, "sms_sender": "GOVUK"}], + "", + ), + ( + 1, + "state", + 1, + [], + "Change your text message sender name Not completed", + ), + ( + 1, + "state", + 1, + [ + {"is_default": False, "sms_sender": "GOVUK"}, + {"is_default": True, "sms_sender": "KUVOG"}, + ], + "Change your text message sender name Completed", + ), + ], +) def test_should_check_for_sms_sender_on_go_live( client_request, service_one, @@ -921,42 +1009,45 @@ def test_should_check_for_sms_sender_on_go_live( expected_sms_sender_checklist_item, estimated_sms_volume, ): - service_one['organization_type'] = organization_type + service_one["organization_type"] = organization_type mocker.patch( - 'app.service_api_client.get_service_templates', - return_value={'data': [ - create_template(template_type='sms') - for _ in range(0, count_of_sms_templates) - ]} + "app.service_api_client.get_service_templates", + return_value={ + "data": [ + create_template(template_type="sms") + for _ in range(0, count_of_sms_templates) + ] + }, ) mocker.patch( - 'app.models.service.Service.has_team_members', + "app.models.service.Service.has_team_members", return_value=True, ) mock_get_sms_senders = mocker.patch( - 'app.main.views.service_settings.service_api_client.get_sms_senders', + "app.main.views.service_settings.service_api_client.get_sms_senders", return_value=sms_senders, ) - for channel, volume in (('email', 0), ('sms', estimated_sms_volume)): + for channel, volume in (("email", 0), ("sms", estimated_sms_volume)): mocker.patch( - 'app.models.service.Service.volume_{}'.format(channel), + "app.models.service.Service.volume_{}".format(channel), create=True, new_callable=PropertyMock, return_value=volume, ) with pytest.raises(expected_exception=IndexError): - page = client_request.get( - 'main.request_to_go_live', service_id=SERVICE_ONE_ID - ) - assert page.h1.text == 'Before you request to go live' + page = client_request.get("main.request_to_go_live", service_id=SERVICE_ONE_ID) + assert page.h1.text == "Before you request to go live" - checklist_items = page.select('.task-list .task-list-item') - assert normalize_spaces(checklist_items[3].text) == expected_sms_sender_checklist_item + checklist_items = page.select(".task-list .task-list-item") + assert ( + normalize_spaces(checklist_items[3].text) + == expected_sms_sender_checklist_item + ) mock_get_sms_senders.assert_called_once_with(SERVICE_ONE_ID) @@ -970,48 +1061,52 @@ def test_non_gov_user_is_told_they_cant_go_live( mock_get_organization, ): mocker.patch( - 'app.models.service.Service.has_team_members', + "app.models.service.Service.has_team_members", return_value=False, ) mocker.patch( - 'app.models.service.Service.all_templates', + "app.models.service.Service.all_templates", new_callable=PropertyMock, return_value=[], ) mocker.patch( - 'app.main.views.service_settings.service_api_client.get_sms_senders', + "app.main.views.service_settings.service_api_client.get_sms_senders", return_value=[], ) mocker.patch( - 'app.main.views.service_settings.service_api_client.get_reply_to_email_addresses', + "app.main.views.service_settings.service_api_client.get_reply_to_email_addresses", return_value=[], ) client_request.login(api_nongov_user_active) - page = client_request.get( - 'main.request_to_go_live', service_id=SERVICE_ONE_ID + page = client_request.get("main.request_to_go_live", service_id=SERVICE_ONE_ID) + assert normalize_spaces(page.select_one("main p").text) == ( + "Only team members with a government email address can request to go live." ) - assert normalize_spaces(page.select_one('main p').text) == ( - 'Only team members with a government email address can request to go live.' - ) - assert len(page.select('main form')) == 0 - assert len(page.select('main button')) == 0 + assert len(page.select("main form")) == 0 + assert len(page.select("main button")) == 0 -@pytest.mark.parametrize('consent_to_research, displayed_consent', ( - (None, None), - (True, 'yes'), - (False, 'no'), -)) -@pytest.mark.parametrize('volumes, displayed_volumes', ( +@pytest.mark.parametrize( + "consent_to_research, displayed_consent", ( - (('email', None), ('sms', None)), (None, None), + (True, "yes"), + (False, "no"), ), +) +@pytest.mark.parametrize( + "volumes, displayed_volumes", ( - (('email', 1234), ('sms', 0)), - ('1,234', '0'), + ( + (("email", None), ("sms", None)), + (None, None), + ), + ( + (("email", 1234), ("sms", 0)), + ("1,234", "0"), + ), ), -)) +) def test_should_show_estimate_volumes( mocker, client_request, @@ -1022,56 +1117,61 @@ def test_should_show_estimate_volumes( ): for channel, volume in volumes: mocker.patch( - 'app.models.service.Service.volume_{}'.format(channel), + "app.models.service.Service.volume_{}".format(channel), create=True, new_callable=PropertyMock, return_value=volume, ) mocker.patch( - 'app.models.service.Service.consent_to_research', + "app.models.service.Service.consent_to_research", create=True, new_callable=PropertyMock, return_value=consent_to_research, ) - page = client_request.get( - 'main.estimate_usage', service_id=SERVICE_ONE_ID - ) - assert page.h1.text == 'Tell us how many messages you expect to send' + page = client_request.get("main.estimate_usage", service_id=SERVICE_ONE_ID) + assert page.h1.text == "Tell us how many messages you expect to send" for channel, label, hint, value in ( ( - 'email', - 'How many emails do you expect to send in the next year?', - 'For example, 50,000', + "email", + "How many emails do you expect to send in the next year?", + "For example, 50,000", displayed_volumes[0], ), ( - 'sms', - 'How many text messages do you expect to send in the next year?', - 'For example, 50,000', + "sms", + "How many text messages do you expect to send in the next year?", + "For example, 50,000", displayed_volumes[1], ), ): - assert normalize_spaces( - page.select_one('label[for=volume_{}]'.format(channel)).text - ) == label - assert normalize_spaces( - page.select_one('#volume_{}-hint'.format(channel)).text - ) == hint - assert page.select_one('#volume_{}'.format(channel)).get('value') == value + assert ( + normalize_spaces( + page.select_one("label[for=volume_{}]".format(channel)).text + ) + == label + ) + assert ( + normalize_spaces(page.select_one("#volume_{}-hint".format(channel)).text) + == hint + ) + assert page.select_one("#volume_{}".format(channel)).get("value") == value - assert len(page.select('input[type=radio]')) == 2 + assert len(page.select("input[type=radio]")) == 2 if displayed_consent is None: - assert len(page.select('input[checked]')) == 0 + assert len(page.select("input[checked]")) == 0 else: - assert len(page.select('input[checked]')) == 1 - assert page.select_one('input[checked]')['value'] == displayed_consent + assert len(page.select("input[checked]")) == 1 + assert page.select_one("input[checked]")["value"] == displayed_consent -@pytest.mark.parametrize('consent_to_research, expected_persisted_consent_to_research', ( - ('yes', True), - ('no', False), -)) +@pytest.mark.parametrize( + "consent_to_research, expected_persisted_consent_to_research", + ( + ("yes", True), + ("no", False), + ), +) def test_should_show_persist_estimated_volumes( client_request, mock_update_service, @@ -1079,18 +1179,18 @@ def test_should_show_persist_estimated_volumes( expected_persisted_consent_to_research, ): client_request.post( - 'main.estimate_usage', + "main.estimate_usage", service_id=SERVICE_ONE_ID, _data={ - 'volume_email': '1,234,567', - 'volume_sms': '', - 'consent_to_research': consent_to_research, + "volume_email": "1,234,567", + "volume_sms": "", + "consent_to_research": consent_to_research, }, _expected_status=302, _expected_redirect=url_for( - 'main.request_to_go_live', + "main.request_to_go_live", service_id=SERVICE_ONE_ID, - ) + ), ) mock_update_service.assert_called_once_with( SERVICE_ONE_ID, @@ -1100,26 +1200,29 @@ def test_should_show_persist_estimated_volumes( ) -@pytest.mark.parametrize('data, error_selector, expected_error_message', ( +@pytest.mark.parametrize( + "data, error_selector, expected_error_message", ( - { - 'volume_email': '1234', - 'volume_sms': '2000000001', - 'consent_to_research': 'yes', - }, - '#volume_sms-error', - 'Number of text messages must be 2,000,000,000 or less' + ( + { + "volume_email": "1234", + "volume_sms": "2000000001", + "consent_to_research": "yes", + }, + "#volume_sms-error", + "Number of text messages must be 2,000,000,000 or less", + ), + ( + { + "volume_email": "1 234", + "volume_sms": "0", + "consent_to_research": "", + }, + '[data-error-label="consent_to_research"]', + "Select yes or no", + ), ), - ( - { - 'volume_email': '1 234', - 'volume_sms': '0', - 'consent_to_research': '', - }, - '[data-error-label="consent_to_research"]', - 'Select yes or no' - ), -)) +) def test_should_error_if_bad_estimations_given( client_request, mock_update_service, @@ -1128,7 +1231,7 @@ def test_should_error_if_bad_estimations_given( expected_error_message, ): page = client_request.post( - 'main.estimate_usage', + "main.estimate_usage", service_id=SERVICE_ONE_ID, _data=data, _expected_status=200, @@ -1142,19 +1245,19 @@ def test_should_error_if_all_volumes_zero( mock_update_service, ): page = client_request.post( - 'main.estimate_usage', + "main.estimate_usage", service_id=SERVICE_ONE_ID, _data={ - 'volume_email': '', - 'volume_sms': '0', - 'consent_to_research': 'yes', + "volume_email": "", + "volume_sms": "0", + "consent_to_research": "yes", }, _expected_status=200, ) - assert page.select('input[type=text]')[0].get('value') is None - assert page.select('input[type=text]')[1]['value'] == '0' - assert normalize_spaces(page.select_one('.banner-dangerous').text) == ( - 'Enter the number of messages you expect to send in the next year' + assert page.select("input[type=text]")[0].get("value") is None + assert page.select("input[type=text]")[1]["value"] == "0" + assert normalize_spaces(page.select_one(".banner-dangerous").text) == ( + "Enter the number of messages you expect to send in the next year" ) assert mock_update_service.called is False @@ -1164,20 +1267,21 @@ def test_should_not_default_to_zero_if_some_fields_dont_validate( mock_update_service, ): page = client_request.post( - 'main.estimate_usage', + "main.estimate_usage", service_id=SERVICE_ONE_ID, _data={ - 'volume_email': 'aaaaaaaaaaaaa', - 'volume_sms': '', - 'consent_to_research': 'yes', + "volume_email": "aaaaaaaaaaaaa", + "volume_sms": "", + "consent_to_research": "yes", }, _expected_status=200, ) - assert page.select('input[type=text]')[0]['value'] == 'aaaaaaaaaaaaa' - assert page.select('input[type=text]')[1].get('value') is None - assert normalize_spaces( - page.select_one('#volume_email-error').text - ) == 'Error: Enter the number of emails you expect to send' + assert page.select("input[type=text]")[0]["value"] == "aaaaaaaaaaaaa" + assert page.select("input[type=text]")[1].get("value") is None + assert ( + normalize_spaces(page.select_one("#volume_email-error").text) + == "Error: Enter the number of emails you expect to send" + ) assert mock_update_service.called is False @@ -1188,30 +1292,27 @@ def test_non_gov_users_cant_request_to_go_live( ): client_request.login(api_nongov_user_active) client_request.post( - 'main.request_to_go_live', + "main.request_to_go_live", service_id=SERVICE_ONE_ID, _expected_status=403, ) -@pytest.mark.parametrize('volumes, displayed_volumes, formatted_displayed_volumes', ( +@pytest.mark.parametrize( + "volumes, displayed_volumes, formatted_displayed_volumes", ( - (('email', None), ('sms', None)), - ', ', ( - 'Emails in next year: \n' - 'Text messages in next year: \n' + (("email", None), ("sms", None)), + ", ", + ("Emails in next year: \n" "Text messages in next year: \n"), + ), + ( + (("email", 1234), ("sms", 0)), + "0, 1234", # This is a different order to match the spreadsheet + ("Emails in next year: 1,234\n" "Text messages in next year: 0\n"), ), ), - ( - (('email', 1234), ('sms', 0)), - '0, 1234', # This is a different order to match the spreadsheet - ( - 'Emails in next year: 1,234\n' - 'Text messages in next year: 0\n' - ), - ), -)) +) @freeze_time("2012-12-21 13:12:12.12354") def test_should_redirect_after_request_to_go_live( client_request, @@ -1231,67 +1332,62 @@ def test_should_redirect_after_request_to_go_live( ): for channel, volume in volumes: mocker.patch( - 'app.models.service.Service.volume_{}'.format(channel), + "app.models.service.Service.volume_{}".format(channel), create=True, new_callable=PropertyMock, return_value=volume, ) - mock_create_ticket = mocker.spy(NotifySupportTicket, '__init__') + mock_create_ticket = mocker.spy(NotifySupportTicket, "__init__") mock_send_ticket_to_zendesk = mocker.patch( - 'app.main.views.service_settings.zendesk_client.send_ticket_to_zendesk', + "app.main.views.service_settings.zendesk_client.send_ticket_to_zendesk", autospec=True, ) page = client_request.post( - 'main.request_to_go_live', - service_id=SERVICE_ONE_ID, - _follow_redirects=True + "main.request_to_go_live", service_id=SERVICE_ONE_ID, _follow_redirects=True ) expected_message = ( - 'Service: service one\n' - 'http://localhost/services/{service_id}\n' - '\n' - '---\n' - 'Organization type: Federal government (domain is user.gsa.gov).\n' - '\n' - '{formatted_displayed_volumes}' - '\n' - 'Consent to research: Yes\n' - 'Other live services for that user: No\n' - '\n' - 'Service reply-to address: test@example.com\n' - '\n' - '---\n' - 'Request sent by test@user.gsa.gov\n' - 'Requester’s user page: http://localhost/users/{user_id}\n' + "Service: service one\n" + "http://localhost/services/{service_id}\n" + "\n" + "---\n" + "Organization type: Federal government (domain is user.gsa.gov).\n" + "\n" + "{formatted_displayed_volumes}" + "\n" + "Consent to research: Yes\n" + "Other live services for that user: No\n" + "\n" + "Service reply-to address: test@example.com\n" + "\n" + "---\n" + "Request sent by test@user.gsa.gov\n" + "Requester’s user page: http://localhost/users/{user_id}\n" ).format( service_id=SERVICE_ONE_ID, formatted_displayed_volumes=formatted_displayed_volumes, - user_id=active_user_with_permissions['id'], + user_id=active_user_with_permissions["id"], ) mock_create_ticket.assert_called_once_with( ANY, - subject='Request to go live - service one', + subject="Request to go live - service one", message=expected_message, - ticket_type='question', - user_name=active_user_with_permissions['name'], - user_email=active_user_with_permissions['email_address'], + ticket_type="question", + user_name=active_user_with_permissions["name"], + user_email=active_user_with_permissions["email_address"], requester_sees_message_content=False, org_id=None, - org_type='federal', + org_type="federal", service_id=SERVICE_ONE_ID, ) mock_send_ticket_to_zendesk.assert_called_once() - assert normalize_spaces(page.select_one('.banner-default').text) == ( - 'Thanks for your request to go live. We’ll get back to you within one working day.' - ) - assert normalize_spaces(page.select_one('h1').text) == ( - 'Settings' + assert normalize_spaces(page.select_one(".banner-default").text) == ( + "Thanks for your request to go live. We’ll get back to you within one working day." ) + assert normalize_spaces(page.select_one("h1").text) == ("Settings") mock_update_service.assert_called_once_with( - SERVICE_ONE_ID, - go_live_user=active_user_with_permissions['id'] + SERVICE_ONE_ID, go_live_user=active_user_with_permissions["id"] ) @@ -1309,62 +1405,60 @@ def test_request_to_go_live_displays_go_live_notes_in_zendesk_ticket( mock_update_service, mock_get_invites_without_manage_permission, ): - go_live_note = 'This service is not allowed to go live' + go_live_note = "This service is not allowed to go live" mocker.patch( - 'app.organizations_client.get_organization', + "app.organizations_client.get_organization", side_effect=lambda org_id: organization_json( ORGANISATION_ID, - 'Org 1', + "Org 1", request_to_go_live_notes=go_live_note, - ) + ), ) - mock_create_ticket = mocker.spy(NotifySupportTicket, '__init__') + mock_create_ticket = mocker.spy(NotifySupportTicket, "__init__") mock_send_ticket_to_zendesk = mocker.patch( - 'app.main.views.service_settings.zendesk_client.send_ticket_to_zendesk', + "app.main.views.service_settings.zendesk_client.send_ticket_to_zendesk", autospec=True, ) client_request.post( - 'main.request_to_go_live', - service_id=SERVICE_ONE_ID, - _follow_redirects=True + "main.request_to_go_live", service_id=SERVICE_ONE_ID, _follow_redirects=True ) expected_message = ( - 'Service: service one\n' - 'http://localhost/services/{service_id}\n' - '\n' - '---\n' - 'Organization type: Federal government (organization is Org 1). {go_live_note}\n' - '\n' - 'Emails in next year: 111,111\n' - 'Text messages in next year: 222,222\n' - '\n' - 'Consent to research: Yes\n' - 'Other live services for that user: No\n' - '\n' - 'Service reply-to address: test@example.com\n' - '\n' - '---\n' - 'Request sent by test@user.gsa.gov\n' - 'Requester’s user page: http://localhost/users/{user_id}\n' + "Service: service one\n" + "http://localhost/services/{service_id}\n" + "\n" + "---\n" + "Organization type: Federal government (organization is Org 1). {go_live_note}\n" + "\n" + "Emails in next year: 111,111\n" + "Text messages in next year: 222,222\n" + "\n" + "Consent to research: Yes\n" + "Other live services for that user: No\n" + "\n" + "Service reply-to address: test@example.com\n" + "\n" + "---\n" + "Request sent by test@user.gsa.gov\n" + "Requester’s user page: http://localhost/users/{user_id}\n" ).format( service_id=SERVICE_ONE_ID, go_live_note=go_live_note, - user_id=active_user_with_permissions['id'], + user_id=active_user_with_permissions["id"], ) mock_create_ticket.assert_called_once_with( ANY, - subject='Request to go live - service one', + subject="Request to go live - service one", message=expected_message, - ticket_type='question', - user_name=active_user_with_permissions['name'], - user_email=active_user_with_permissions['email_address'], + ticket_type="question", + user_name=active_user_with_permissions["name"], + user_email=active_user_with_permissions["email_address"], requester_sees_message_content=False, org_id=ORGANISATION_ID, - org_type='federal', - service_id=SERVICE_ONE_ID + org_type="federal", + service_id=SERVICE_ONE_ID, ) mock_send_ticket_to_zendesk.assert_called_once() @@ -1385,33 +1479,31 @@ def test_request_to_go_live_displays_mou_signatories( mock_get_invites_without_manage_permission, ): mocker.patch( - 'app.organizations_client.get_organization', + "app.organizations_client.get_organization", side_effect=lambda org_id: organization_json( ORGANISATION_ID, - 'Org 1', + "Org 1", agreement_signed=True, agreement_signed_by_id=fake_uuid, - agreement_signed_on_behalf_of_email_address='bigdog@example.gsa.gov', - ) + agreement_signed_on_behalf_of_email_address="bigdog@example.gsa.gov", + ), ) mocker.patch( - 'app.main.views.service_settings.zendesk_client.send_ticket_to_zendesk', + "app.main.views.service_settings.zendesk_client.send_ticket_to_zendesk", autospec=True, ) - mock_create_ticket = mocker.spy(NotifySupportTicket, '__init__') + mock_create_ticket = mocker.spy(NotifySupportTicket, "__init__") client_request.post( - 'main.request_to_go_live', - service_id=SERVICE_ONE_ID, - _follow_redirects=True + "main.request_to_go_live", service_id=SERVICE_ONE_ID, _follow_redirects=True ) - assert ( - 'Organization type: Federal government' - ) in mock_create_ticket.call_args[1]['message'] + assert ("Organization type: Federal government") in mock_create_ticket.call_args[1][ + "message" + ] - assert ( - 'Emails in next year: 111,111\n' - ) in mock_create_ticket.call_args[1]['message'] + assert ("Emails in next year: 111,111\n") in mock_create_ticket.call_args[1][ + "message" + ] def test_should_be_able_to_request_to_go_live_with_no_organization( @@ -1426,22 +1518,20 @@ def test_should_be_able_to_request_to_go_live_with_no_organization( mock_update_service, mock_get_invites_without_manage_permission, ): - for channel in {'email', 'sms'}: + for channel in {"email", "sms"}: mocker.patch( - 'app.models.service.Service.volume_{}'.format(channel), + "app.models.service.Service.volume_{}".format(channel), create=True, new_callable=PropertyMock, return_value=1, ) mock_post = mocker.patch( - 'app.main.views.service_settings.zendesk_client.send_ticket_to_zendesk', - autospec=True + "app.main.views.service_settings.zendesk_client.send_ticket_to_zendesk", + autospec=True, ) client_request.post( - 'main.request_to_go_live', - service_id=SERVICE_ONE_ID, - _follow_redirects=True + "main.request_to_go_live", service_id=SERVICE_ONE_ID, _follow_redirects=True ) assert mock_post.called is True @@ -1449,17 +1539,17 @@ def test_should_be_able_to_request_to_go_live_with_no_organization( @pytest.mark.parametrize( ( - 'has_team_members,' - 'has_templates,' - 'has_email_templates,' - 'has_sms_templates,' - 'has_email_reply_to_address,' - 'shouldnt_use_govuk_as_sms_sender,' - 'sms_sender_is_govuk,' - 'volume_email,' - 'volume_sms,' - 'expected_readyness,' - 'agreement_signed,' + "has_team_members," + "has_templates," + "has_email_templates," + "has_sms_templates," + "has_email_reply_to_address," + "shouldnt_use_govuk_as_sms_sender," + "sms_sender_is_govuk," + "volume_email," + "volume_sms," + "expected_readyness," + "agreement_signed," ), ( ( # Just sending email @@ -1470,8 +1560,9 @@ def test_should_be_able_to_request_to_go_live_with_no_organization( True, True, True, - 1, 0, - 'Yes', + 1, + 0, + "Yes", True, ), ( # Needs to set reply to address @@ -1482,8 +1573,9 @@ def test_should_be_able_to_request_to_go_live_with_no_organization( False, True, True, - 1, 0, - 'No', + 1, + 0, + "No", True, ), ( # Just sending SMS @@ -1494,8 +1586,9 @@ def test_should_be_able_to_request_to_go_live_with_no_organization( True, True, False, - 0, 1, - 'Yes', + 0, + 1, + "Yes", True, ), ( # Needs to change SMS sender @@ -1506,8 +1599,9 @@ def test_should_be_able_to_request_to_go_live_with_no_organization( True, True, True, - 0, 1, - 'No', + 0, + 1, + "No", True, ), ( # Needs team members @@ -1518,8 +1612,9 @@ def test_should_be_able_to_request_to_go_live_with_no_organization( True, True, False, - 1, 0, - 'No', + 1, + 0, + "No", True, ), ( # Needs templates @@ -1530,8 +1625,9 @@ def test_should_be_able_to_request_to_go_live_with_no_organization( True, True, False, - 0, 1, - 'No', + 0, + 1, + "No", True, ), ( # Not done anything yet @@ -1542,8 +1638,9 @@ def test_should_be_able_to_request_to_go_live_with_no_organization( False, False, True, - None, None, - 'No', + None, + None, + "No", False, ), ), @@ -1565,164 +1662,184 @@ def test_ready_to_go_live( agreement_signed, ): mocker.patch( - 'app.organizations_client.get_organization', - return_value=organization_json(agreement_signed=agreement_signed) + "app.organizations_client.get_organization", + return_value=organization_json(agreement_signed=agreement_signed), ) for prop in { - 'has_team_members', - 'has_templates', - 'has_email_templates', - 'has_sms_templates', - 'has_email_reply_to_address', - 'shouldnt_use_govuk_as_sms_sender', - 'sms_sender_is_govuk', + "has_team_members", + "has_templates", + "has_email_templates", + "has_sms_templates", + "has_email_reply_to_address", + "shouldnt_use_govuk_as_sms_sender", + "sms_sender_is_govuk", }: mocker.patch( - 'app.models.service.Service.{}'.format(prop), - new_callable=PropertyMock + "app.models.service.Service.{}".format(prop), new_callable=PropertyMock ).return_value = locals()[prop] for channel, volume in ( - ('sms', volume_sms), - ('email', volume_email), + ("sms", volume_sms), + ("email", volume_email), ): mocker.patch( - 'app.models.service.Service.volume_{}'.format(channel), + "app.models.service.Service.volume_{}".format(channel), create=True, new_callable=PropertyMock, return_value=volume, ) - assert app.models.service.Service({ - 'id': SERVICE_ONE_ID - }).go_live_checklist_completed_as_yes_no == expected_readyness + assert ( + app.models.service.Service( + {"id": SERVICE_ONE_ID} + ).go_live_checklist_completed_as_yes_no + == expected_readyness + ) -@pytest.mark.parametrize('route', [ - 'main.service_settings', - 'main.service_name_change', - 'main.request_to_go_live', - 'main.submit_request_to_go_live', - 'main.archive_service' -]) +@pytest.mark.parametrize( + "route", + [ + "main.service_settings", + "main.service_name_change", + "main.request_to_go_live", + "main.submit_request_to_go_live", + "main.archive_service", + ], +) def test_route_permissions( - mocker, - notify_admin, - client_request, - api_user_active, - service_one, - single_reply_to_email_address, - mock_get_invites_for_service, - single_sms_sender, - route, - mock_get_service_settings_page_common, - mock_get_service_templates, + mocker, + notify_admin, + client_request, + api_user_active, + service_one, + single_reply_to_email_address, + mock_get_invites_for_service, + single_sms_sender, + route, + mock_get_service_settings_page_common, + mock_get_service_templates, ): validate_route_permission( mocker, notify_admin, "GET", 200, - url_for(route, service_id=service_one['id']), - ['manage_service'], + url_for(route, service_id=service_one["id"]), + ["manage_service"], api_user_active, service_one, - session={'service_name_change': "New Service Name"} + session={"service_name_change": "New Service Name"}, ) -@pytest.mark.parametrize('route', [ - 'main.service_settings', - 'main.service_name_change', - 'main.request_to_go_live', - 'main.submit_request_to_go_live', - 'main.service_switch_live', - 'main.archive_service', -]) +@pytest.mark.parametrize( + "route", + [ + "main.service_settings", + "main.service_name_change", + "main.request_to_go_live", + "main.submit_request_to_go_live", + "main.service_switch_live", + "main.archive_service", + ], +) def test_route_invalid_permissions( - mocker, - notify_admin, - client_request, - api_user_active, - service_one, - route, - mock_get_service_templates, - mock_get_invites_for_service, + mocker, + notify_admin, + client_request, + api_user_active, + service_one, + route, + mock_get_service_templates, + mock_get_invites_for_service, ): validate_route_permission( mocker, notify_admin, "GET", 403, - url_for(route, service_id=service_one['id']), - ['blah'], + url_for(route, service_id=service_one["id"]), + ["blah"], api_user_active, - service_one) - - -@pytest.mark.parametrize('route', [ - 'main.service_settings', - 'main.service_name_change', - 'main.request_to_go_live', - 'main.submit_request_to_go_live', -]) -def test_route_for_platform_admin( - mocker, - notify_admin, - client_request, - platform_admin_user, service_one, - single_reply_to_email_address, - single_sms_sender, - route, - mock_get_service_settings_page_common, - mock_get_service_templates, - mock_get_invites_for_service, + ) + + +@pytest.mark.parametrize( + "route", + [ + "main.service_settings", + "main.service_name_change", + "main.request_to_go_live", + "main.submit_request_to_go_live", + ], +) +def test_route_for_platform_admin( + mocker, + notify_admin, + client_request, + platform_admin_user, + service_one, + single_reply_to_email_address, + single_sms_sender, + route, + mock_get_service_settings_page_common, + mock_get_service_templates, + mock_get_invites_for_service, ): validate_route_permission( mocker, notify_admin, "GET", 200, - url_for(route, service_id=service_one['id']), + url_for(route, service_id=service_one["id"]), [], platform_admin_user, service_one, - session={'service_name_change': "New Service Name"} + session={"service_name_change": "New Service Name"}, ) @pytest.mark.skip(reason="Email currently deactivated") def test_and_more_hint_appears_on_settings_with_more_than_just_a_single_sender( - client_request, - service_one, - multiple_reply_to_email_addresses, - multiple_sms_senders, - mock_get_service_settings_page_common, + client_request, + service_one, + multiple_reply_to_email_addresses, + multiple_sms_senders, + mock_get_service_settings_page_common, ): - service_one['permissions'] = ['email', 'sms'] + service_one["permissions"] = ["email", "sms"] - page = client_request.get( - 'main.service_settings', - service_id=service_one['id'] - ) + page = client_request.get("main.service_settings", service_id=service_one["id"]) def get_row(page, label): return normalize_spaces( - find_element_by_tag_and_partial_text(page, tag='tr', string=label).text + find_element_by_tag_and_partial_text(page, tag="tr", string=label).text ) - assert get_row(page, 'Reply-to email addresses') == \ - "Reply-to email addresses test@example.com …and 2 more Manage reply-to email addresses" - assert get_row(page, 'Text message senders') == \ - "Text message senders Example …and 2 more Manage text message senders" + assert ( + get_row(page, "Reply-to email addresses") + == "Reply-to email addresses test@example.com …and 2 more Manage reply-to email addresses" + ) + assert ( + get_row(page, "Text message senders") + == "Text message senders Example …and 2 more Manage text message senders" + ) -@pytest.mark.parametrize('sender_list_page, index, expected_output', [ - ('main.service_email_reply_to', 0, 'test@example.com (default) Change test@example.com'), - ('main.service_sms_senders', 0, 'GOVUK (default) Change GOVUK') -]) +@pytest.mark.parametrize( + "sender_list_page, index, expected_output", + [ + ( + "main.service_email_reply_to", + 0, + "test@example.com (default) Change test@example.com", + ), + ("main.service_sms_senders", 0, "GOVUK (default) Change GOVUK"), + ], +) def test_api_ids_dont_show_on_option_pages_with_a_single_sender( client_request, single_reply_to_email_address, @@ -1732,11 +1849,8 @@ def test_api_ids_dont_show_on_option_pages_with_a_single_sender( index, expected_output, ): - rows = client_request.get( - sender_list_page, - service_id=SERVICE_ONE_ID - ).select( - '.user-list-item' + rows = client_request.get(sender_list_page, service_id=SERVICE_ONE_ID).select( + ".user-list-item" ) assert normalize_spaces(rows[index].text) == expected_output @@ -1744,32 +1858,29 @@ def test_api_ids_dont_show_on_option_pages_with_a_single_sender( @pytest.mark.parametrize( - ( - 'sender_list_page,' - 'endpoint_to_mock,' - 'sample_data,' - 'expected_items,' - ), - [( - 'main.service_email_reply_to', - 'app.service_api_client.get_reply_to_email_addresses', - create_multiple_email_reply_to_addresses(), - [ - 'test@example.com (default) Change test@example.com ID: 1234', - 'test2@example.com Change test2@example.com ID: 5678', - 'test3@example.com Change test3@example.com ID: 9457', - ], - ), ( - 'main.service_sms_senders', - 'app.service_api_client.get_sms_senders', - create_multiple_sms_senders(), - [ - 'Example (default and receives replies) Change Example ID: 1234', - 'Example 2 Change Example 2 ID: 5678', - 'Example 3 Change Example 3 ID: 9457', - ], - ), - ] + ("sender_list_page," "endpoint_to_mock," "sample_data," "expected_items,"), + [ + ( + "main.service_email_reply_to", + "app.service_api_client.get_reply_to_email_addresses", + create_multiple_email_reply_to_addresses(), + [ + "test@example.com (default) Change test@example.com ID: 1234", + "test2@example.com Change test2@example.com ID: 5678", + "test3@example.com Change test3@example.com ID: 9457", + ], + ), + ( + "main.service_sms_senders", + "app.service_api_client.get_sms_senders", + create_multiple_sms_senders(), + [ + "Example (default and receives replies) Change Example ID: 1234", + "Example 2 Change Example 2 ID: 5678", + "Example 3 Change Example 3 ID: 9457", + ], + ), + ], ) def test_default_option_shows_for_default_sender( client_request, @@ -1781,77 +1892,75 @@ def test_default_option_shows_for_default_sender( ): mocker.patch(endpoint_to_mock, return_value=sample_data) - rows = client_request.get( - sender_list_page, - service_id=SERVICE_ONE_ID - ).select( - '.user-list-item' + rows = client_request.get(sender_list_page, service_id=SERVICE_ONE_ID).select( + ".user-list-item" ) assert [normalize_spaces(row.text) for row in rows] == expected_items -@pytest.mark.parametrize('sender_list_page, endpoint_to_mock, expected_output', [ - ( - 'main.service_email_reply_to', - 'app.service_api_client.get_reply_to_email_addresses', - 'You have not added any reply-to email addresses yet' - ), - ( - 'main.service_sms_senders', - 'app.service_api_client.get_sms_senders', - 'You have not added any text message senders yet' - ), -]) +@pytest.mark.parametrize( + "sender_list_page, endpoint_to_mock, expected_output", + [ + ( + "main.service_email_reply_to", + "app.service_api_client.get_reply_to_email_addresses", + "You have not added any reply-to email addresses yet", + ), + ( + "main.service_sms_senders", + "app.service_api_client.get_sms_senders", + "You have not added any text message senders yet", + ), + ], +) def test_no_senders_message_shows( - client_request, - sender_list_page, - endpoint_to_mock, - expected_output, - mocker + client_request, sender_list_page, endpoint_to_mock, expected_output, mocker ): mocker.patch(endpoint_to_mock, return_value=[]) - rows = client_request.get( - sender_list_page, - service_id=SERVICE_ONE_ID - ).select( - '.user-list-item' + rows = client_request.get(sender_list_page, service_id=SERVICE_ONE_ID).select( + ".user-list-item" ) assert normalize_spaces(rows[0].text) == expected_output assert len(rows) == 1 -@pytest.mark.parametrize('reply_to_input, expected_error', [ - ('', 'Cannot be empty'), - ('testtest', 'Enter a valid email address'), -]) +@pytest.mark.parametrize( + "reply_to_input, expected_error", + [ + ("", "Cannot be empty"), + ("testtest", "Enter a valid email address"), + ], +) def test_incorrect_reply_to_email_address_input( - reply_to_input, - expected_error, - client_request, - no_reply_to_email_addresses + reply_to_input, expected_error, client_request, no_reply_to_email_addresses ): page = client_request.post( - 'main.service_add_email_reply_to', + "main.service_add_email_reply_to", service_id=SERVICE_ONE_ID, - _data={'email_address': reply_to_input}, - _expected_status=200 + _data={"email_address": reply_to_input}, + _expected_status=200, ) - assert expected_error in normalize_spaces(page.select_one('.usa-error-message').text) + assert expected_error in normalize_spaces( + page.select_one(".usa-error-message").text + ) -@pytest.mark.parametrize('sms_sender_input, expected_error', [ - ('elevenchars', None), - ('11 chars', None), - ('', 'Cannot be empty'), - ('abcdefghijkhgkg', 'Enter 11 characters or fewer'), - (r' ¯\_(ツ)_/¯ ', 'Use letters and numbers only'), - ('blood.co.uk', None), - ('00123', "Cannot start with 00") -]) +@pytest.mark.parametrize( + "sms_sender_input, expected_error", + [ + ("elevenchars", None), + ("11 chars", None), + ("", "Cannot be empty"), + ("abcdefghijkhgkg", "Enter 11 characters or fewer"), + (r" ¯\_(ツ)_/¯ ", "Use letters and numbers only"), + ("blood.co.uk", None), + ("00123", "Cannot start with 00"), + ], +) def test_incorrect_sms_sender_input( sms_sender_input, expected_error, @@ -1860,13 +1969,13 @@ def test_incorrect_sms_sender_input( mock_add_sms_sender, ): page = client_request.post( - 'main.service_add_sms_sender', + "main.service_add_sms_sender", service_id=SERVICE_ONE_ID, - _data={'sms_sender': sms_sender_input}, - _expected_status=(200 if expected_error else 302) + _data={"sms_sender": sms_sender_input}, + _expected_status=(200 if expected_error else 302), ) - error_message = page.select_one('.usa-error-message') + error_message = page.select_one(".usa-error-message") count_of_api_calls = len(mock_add_sms_sender.call_args_list) if not expected_error: @@ -1885,84 +1994,93 @@ def test_incorrect_sms_sender_input_with_multiple_errors_only_shows_the_first( # There are two errors with the SMS sender - the length and characters used. Only one # should be displayed on the page. page = client_request.post( - 'main.service_add_sms_sender', + "main.service_add_sms_sender", service_id=SERVICE_ONE_ID, - _data={'sms_sender': '{}'}, - _expected_status=200 + _data={"sms_sender": "{}"}, + _expected_status=200, ) - error_message = page.select_one('.usa-error-message') + error_message = page.select_one(".usa-error-message") count_of_api_calls = len(mock_add_sms_sender.call_args_list) - assert normalize_spaces(error_message.text) == 'Error: Enter 3 characters or more' + assert normalize_spaces(error_message.text) == "Error: Enter 3 characters or more" assert count_of_api_calls == 0 -@pytest.mark.parametrize('reply_to_addresses, data, api_default_args', [ - ([], {}, True), - (create_multiple_email_reply_to_addresses(), {}, False), - (create_multiple_email_reply_to_addresses(), {"is_default": "y"}, True) -]) +@pytest.mark.parametrize( + "reply_to_addresses, data, api_default_args", + [ + ([], {}, True), + (create_multiple_email_reply_to_addresses(), {}, False), + (create_multiple_email_reply_to_addresses(), {"is_default": "y"}, True), + ], +) def test_add_reply_to_email_address_sends_test_notification( mocker, client_request, reply_to_addresses, data, api_default_args ): - mocker.patch('app.service_api_client.get_reply_to_email_addresses', return_value=reply_to_addresses) - data['email_address'] = "test@example.com" + mocker.patch( + "app.service_api_client.get_reply_to_email_addresses", + return_value=reply_to_addresses, + ) + data["email_address"] = "test@example.com" mock_verify = mocker.patch( - 'app.service_api_client.verify_reply_to_email_address', return_value={"data": {"id": "123"}} + "app.service_api_client.verify_reply_to_email_address", + return_value={"data": {"id": "123"}}, ) client_request.post( - 'main.service_add_email_reply_to', + "main.service_add_email_reply_to", service_id=SERVICE_ONE_ID, _data=data, _expected_status=302, _expected_redirect=url_for( - 'main.service_verify_reply_to_address', + "main.service_verify_reply_to_address", service_id=SERVICE_ONE_ID, notification_id="123", - ) + "?is_default={}".format(api_default_args) + ) + + "?is_default={}".format(api_default_args), ) mock_verify.assert_called_once_with(SERVICE_ONE_ID, "test@example.com") def test_service_add_reply_to_email_address_without_verification_for_platform_admin( - mocker, - client_request, - platform_admin_user + mocker, client_request, platform_admin_user ): client_request.login(platform_admin_user) - mock_update = mocker.patch( - 'app.service_api_client.add_reply_to_email_address' - ) + mock_update = mocker.patch("app.service_api_client.add_reply_to_email_address") mocker.patch( - 'app.service_api_client.get_reply_to_email_addresses', - return_value=[create_reply_to_email_address(is_default=True)] + "app.service_api_client.get_reply_to_email_addresses", + return_value=[create_reply_to_email_address(is_default=True)], ) data = {"is_default": "y", "email_address": "test@example.gsa.gov"} client_request.post( - 'main.service_add_email_reply_to', + "main.service_add_email_reply_to", service_id=SERVICE_ONE_ID, _data=data, _expected_status=302, _expected_redirect=url_for( - 'main.service_email_reply_to', + "main.service_email_reply_to", service_id=SERVICE_ONE_ID, - ) + ), ) mock_update.assert_called_once_with( - SERVICE_ONE_ID, - email_address='test@example.gsa.gov', - is_default=True) + SERVICE_ONE_ID, email_address="test@example.gsa.gov", is_default=True + ) -@pytest.mark.parametrize("is_default,replace,expected_header", [(True, "&replace=123", "Change"), (False, "", "Add")]) -@pytest.mark.parametrize("status,expected_failure,expected_success", [ - ("delivered", 0, 1), - ("sending", 0, 0), - ("permanent-failure", 1, 0), -]) +@pytest.mark.parametrize( + "is_default,replace,expected_header", + [(True, "&replace=123", "Change"), (False, "", "Add")], +) +@pytest.mark.parametrize( + "status,expected_failure,expected_success", + [ + ("delivered", 0, 1), + ("sending", 0, 0), + ("permanent-failure", 1, 0), + ], +) @freeze_time("2018-06-01 11:11:00.061258") def test_service_verify_reply_to_address( mocker, @@ -1974,7 +2092,7 @@ def test_service_verify_reply_to_address( expected_success, is_default, replace, - expected_header + expected_header, ): notification = { "id": fake_uuid, @@ -1983,33 +2101,42 @@ def test_service_verify_reply_to_address( "service_id": SERVICE_ONE_ID, "template_id": TEMPLATE_ONE_ID, "notification_type": "email", - "created_at": '2018-06-01T11:10:52.499230+00:00' + "created_at": "2018-06-01T11:10:52.499230+00:00", } - mocker.patch('app.notification_api_client.get_notification', return_value=notification) - mock_add_reply_to_email_address = mocker.patch('app.service_api_client.add_reply_to_email_address') - mock_update_reply_to_email_address = mocker.patch('app.service_api_client.update_reply_to_email_address') mocker.patch( - 'app.service_api_client.get_reply_to_email_addresses', return_value=[] + "app.notification_api_client.get_notification", return_value=notification ) + mock_add_reply_to_email_address = mocker.patch( + "app.service_api_client.add_reply_to_email_address" + ) + mock_update_reply_to_email_address = mocker.patch( + "app.service_api_client.update_reply_to_email_address" + ) + mocker.patch("app.service_api_client.get_reply_to_email_addresses", return_value=[]) page = client_request.get( - 'main.service_verify_reply_to_address', + "main.service_verify_reply_to_address", service_id=SERVICE_ONE_ID, notification_id=notification["id"], - _optional_args="?is_default={}{}".format(is_default, replace) + _optional_args="?is_default={}{}".format(is_default, replace), ) - assert page.find('h1').text == '{} email reply-to address'.format(expected_header) + assert page.find("h1").text == "{} email reply-to address".format(expected_header) if replace: - assert "/email-reply-to/123/edit" in page.find('a', text="Back").attrs["href"] + assert "/email-reply-to/123/edit" in page.find("a", text="Back").attrs["href"] else: - assert "/email-reply-to/add" in page.find('a', text="Back").attrs["href"] + assert "/email-reply-to/add" in page.find("a", text="Back").attrs["href"] - assert len(page.find_all('div', class_='banner-dangerous')) == expected_failure - assert len(page.find_all('div', class_='banner-default-with-tick')) == expected_success + assert len(page.find_all("div", class_="banner-dangerous")) == expected_failure + assert ( + len(page.find_all("div", class_="banner-default-with-tick")) == expected_success + ) if status == "delivered": if replace: mock_update_reply_to_email_address.assert_called_once_with( - SERVICE_ONE_ID, "123", email_address=notification["to"], is_default=is_default + SERVICE_ONE_ID, + "123", + email_address=notification["to"], + is_default=is_default, ) assert mock_add_reply_to_email_address.called is False else: @@ -2020,11 +2147,13 @@ def test_service_verify_reply_to_address( else: assert mock_add_reply_to_email_address.called is False if status == "permanent-failure": - assert page.find('input', type='email').attrs["value"] == notification["to"] + assert page.find("input", type="email").attrs["value"] == notification["to"] @freeze_time("2018-06-01 11:11:00.061258") -def test_add_reply_to_email_address_fails_if_notification_not_delivered_in_45_sec(mocker, client_request, fake_uuid): +def test_add_reply_to_email_address_fails_if_notification_not_delivered_in_45_sec( + mocker, client_request, fake_uuid +): notification = { "id": fake_uuid, "status": "sending", @@ -2032,78 +2161,81 @@ def test_add_reply_to_email_address_fails_if_notification_not_delivered_in_45_se "service_id": SERVICE_ONE_ID, "template_id": TEMPLATE_ONE_ID, "notification_type": "email", - "created_at": '2018-06-01T11:10:12.499230+00:00' + "created_at": "2018-06-01T11:10:12.499230+00:00", } + mocker.patch("app.service_api_client.get_reply_to_email_addresses", return_value=[]) mocker.patch( - 'app.service_api_client.get_reply_to_email_addresses', return_value=[] + "app.notification_api_client.get_notification", return_value=notification + ) + mock_add_reply_to_email_address = mocker.patch( + "app.service_api_client.add_reply_to_email_address" ) - mocker.patch('app.notification_api_client.get_notification', return_value=notification) - mock_add_reply_to_email_address = mocker.patch('app.service_api_client.add_reply_to_email_address') page = client_request.get( - 'main.service_verify_reply_to_address', + "main.service_verify_reply_to_address", service_id=SERVICE_ONE_ID, notification_id=notification["id"], - _optional_args="?is_default={}".format(False) + _optional_args="?is_default={}".format(False), + ) + expected_banner = page.find_all("div", class_="banner-dangerous")[0] + assert ( + "There’s a problem with your reply-to address" in expected_banner.text.strip() ) - expected_banner = page.find_all('div', class_='banner-dangerous')[0] - assert 'There’s a problem with your reply-to address' in expected_banner.text.strip() assert mock_add_reply_to_email_address.called is False -@pytest.mark.parametrize('sms_senders, data, api_default_args', [ - ([], {}, True), - (create_multiple_sms_senders(), {}, False), - (create_multiple_sms_senders(), {"is_default": "y"}, True) -]) +@pytest.mark.parametrize( + "sms_senders, data, api_default_args", + [ + ([], {}, True), + (create_multiple_sms_senders(), {}, False), + (create_multiple_sms_senders(), {"is_default": "y"}, True), + ], +) def test_add_sms_sender( - sms_senders, - data, - api_default_args, - mocker, - client_request, - mock_add_sms_sender + sms_senders, data, api_default_args, mocker, client_request, mock_add_sms_sender ): - mocker.patch('app.service_api_client.get_sms_senders', return_value=sms_senders) - data['sms_sender'] = "Example" + mocker.patch("app.service_api_client.get_sms_senders", return_value=sms_senders) + data["sms_sender"] = "Example" client_request.post( - 'main.service_add_sms_sender', - service_id=SERVICE_ONE_ID, - _data=data + "main.service_add_sms_sender", service_id=SERVICE_ONE_ID, _data=data ) mock_add_sms_sender.assert_called_once_with( - SERVICE_ONE_ID, - sms_sender="Example", - is_default=api_default_args + SERVICE_ONE_ID, sms_sender="Example", is_default=api_default_args ) -@pytest.mark.parametrize('reply_to_addresses, checkbox_present', [ - ([], False), - (create_multiple_email_reply_to_addresses(), True), -]) +@pytest.mark.parametrize( + "reply_to_addresses, checkbox_present", + [ + ([], False), + (create_multiple_email_reply_to_addresses(), True), + ], +) def test_default_box_doesnt_show_on_first_email_sender( - reply_to_addresses, - mocker, - checkbox_present, - client_request + reply_to_addresses, mocker, checkbox_present, client_request ): - mocker.patch('app.service_api_client.get_reply_to_email_addresses', return_value=reply_to_addresses) + mocker.patch( + "app.service_api_client.get_reply_to_email_addresses", + return_value=reply_to_addresses, + ) page = client_request.get( - 'main.service_add_email_reply_to', - service_id=SERVICE_ONE_ID + "main.service_add_email_reply_to", service_id=SERVICE_ONE_ID ) - assert bool(page.select_one('[name=is_default]')) == checkbox_present + assert bool(page.select_one("[name=is_default]")) == checkbox_present -@pytest.mark.parametrize('reply_to_address, data, api_default_args', [ - (create_reply_to_email_address(is_default=True), {"is_default": "y"}, True), - (create_reply_to_email_address(is_default=True), {}, True), - (create_reply_to_email_address(is_default=False), {}, False), - (create_reply_to_email_address(is_default=False), {"is_default": "y"}, True) -]) +@pytest.mark.parametrize( + "reply_to_address, data, api_default_args", + [ + (create_reply_to_email_address(is_default=True), {"is_default": "y"}, True), + (create_reply_to_email_address(is_default=True), {}, True), + (create_reply_to_email_address(is_default=False), {}, False), + (create_reply_to_email_address(is_default=False), {"is_default": "y"}, True), + ], +) def test_edit_reply_to_email_address_sends_verification_notification_if_address_is_changed( reply_to_address, data, @@ -2113,60 +2245,63 @@ def test_edit_reply_to_email_address_sends_verification_notification_if_address_ client_request, ): mock_verify = mocker.patch( - 'app.service_api_client.verify_reply_to_email_address', return_value={"data": {"id": "123"}} + "app.service_api_client.verify_reply_to_email_address", + return_value={"data": {"id": "123"}}, ) - mocker.patch('app.service_api_client.get_reply_to_email_address', return_value=reply_to_address) - data['email_address'] = "test@example.gsa.gov" + mocker.patch( + "app.service_api_client.get_reply_to_email_address", + return_value=reply_to_address, + ) + data["email_address"] = "test@example.gsa.gov" client_request.post( - 'main.service_edit_email_reply_to', + "main.service_edit_email_reply_to", service_id=SERVICE_ONE_ID, reply_to_email_id=fake_uuid, - _data=data + _data=data, ) mock_verify.assert_called_once_with(SERVICE_ONE_ID, "test@example.gsa.gov") def test_service_edit_email_reply_to_updates_email_address_without_verification_for_platform_admin( - mocker, - fake_uuid, - client_request, - platform_admin_user + mocker, fake_uuid, client_request, platform_admin_user ): client_request.login(platform_admin_user) - mock_update = mocker.patch( - 'app.service_api_client.update_reply_to_email_address' - ) + mock_update = mocker.patch("app.service_api_client.update_reply_to_email_address") mocker.patch( - 'app.service_api_client.get_reply_to_email_address', - return_value=create_reply_to_email_address(is_default=True) + "app.service_api_client.get_reply_to_email_address", + return_value=create_reply_to_email_address(is_default=True), ) data = {"is_default": "y", "email_address": "test@example.gsa.gov"} client_request.post( - 'main.service_edit_email_reply_to', + "main.service_edit_email_reply_to", service_id=SERVICE_ONE_ID, reply_to_email_id=fake_uuid, _data=data, _expected_status=302, _expected_redirect=url_for( - 'main.service_email_reply_to', + "main.service_email_reply_to", service_id=SERVICE_ONE_ID, - ) + ), ) mock_update.assert_called_once_with( SERVICE_ONE_ID, reply_to_email_id=fake_uuid, - email_address='test@example.gsa.gov', - is_default=True) + email_address="test@example.gsa.gov", + is_default=True, + ) -@pytest.mark.parametrize('reply_to_address, data, api_default_args', [ - (create_reply_to_email_address(), {"is_default": "y"}, True), - (create_reply_to_email_address(), {}, True), - (create_reply_to_email_address(is_default=False), {}, False), - (create_reply_to_email_address(is_default=False), {"is_default": "y"}, True) -]) +@pytest.mark.parametrize( + "reply_to_address, data, api_default_args", + [ + (create_reply_to_email_address(), {"is_default": "y"}, True), + (create_reply_to_email_address(), {}, True), + (create_reply_to_email_address(is_default=False), {}, False), + (create_reply_to_email_address(is_default=False), {"is_default": "y"}, True), + ], +) def test_edit_reply_to_email_address_goes_straight_to_update_if_address_not_changed( reply_to_address, data, @@ -2174,79 +2309,91 @@ def test_edit_reply_to_email_address_goes_straight_to_update_if_address_not_chan mocker, fake_uuid, client_request, - mock_update_reply_to_email_address + mock_update_reply_to_email_address, ): - mocker.patch('app.service_api_client.get_reply_to_email_address', return_value=reply_to_address) - mock_verify = mocker.patch('app.service_api_client.verify_reply_to_email_address') - data['email_address'] = "test@example.com" + mocker.patch( + "app.service_api_client.get_reply_to_email_address", + return_value=reply_to_address, + ) + mock_verify = mocker.patch("app.service_api_client.verify_reply_to_email_address") + data["email_address"] = "test@example.com" client_request.post( - 'main.service_edit_email_reply_to', + "main.service_edit_email_reply_to", service_id=SERVICE_ONE_ID, reply_to_email_id=fake_uuid, - _data=data + _data=data, ) mock_update_reply_to_email_address.assert_called_once_with( SERVICE_ONE_ID, reply_to_email_id=fake_uuid, email_address="test@example.com", - is_default=api_default_args + is_default=api_default_args, ) assert mock_verify.called is False -@pytest.mark.parametrize('url', [ - 'main.service_edit_email_reply_to', - 'main.service_add_email_reply_to', -]) +@pytest.mark.parametrize( + "url", + [ + "main.service_edit_email_reply_to", + "main.service_add_email_reply_to", + ], +) def test_add_edit_reply_to_email_address_goes_straight_to_update_if_address_not_changed( - mocker, - fake_uuid, - client_request, - mock_update_reply_to_email_address, - url + mocker, fake_uuid, client_request, mock_update_reply_to_email_address, url ): reply_to_email_address = create_reply_to_email_address() - mocker.patch('app.service_api_client.get_reply_to_email_addresses', return_value=[reply_to_email_address]) - mocker.patch('app.service_api_client.get_reply_to_email_address', return_value=reply_to_email_address) - error_message = 'Your service already uses ‘reply_to@example.com’ as an email reply-to address.' mocker.patch( - 'app.service_api_client.verify_reply_to_email_address', side_effect=[HTTPError( - response=Mock( - status_code=409, - json={ - 'result': 'error', - 'message': error_message - } - ), - message=error_message - )] + "app.service_api_client.get_reply_to_email_addresses", + return_value=[reply_to_email_address], ) - data = {"is_default": "y", 'email_address': "reply_to@example.com"} + mocker.patch( + "app.service_api_client.get_reply_to_email_address", + return_value=reply_to_email_address, + ) + error_message = ( + "Your service already uses ‘reply_to@example.com’ as an email reply-to address." + ) + mocker.patch( + "app.service_api_client.verify_reply_to_email_address", + side_effect=[ + HTTPError( + response=Mock( + status_code=409, json={"result": "error", "message": error_message} + ), + message=error_message, + ) + ], + ) + data = {"is_default": "y", "email_address": "reply_to@example.com"} page = client_request.post( url, service_id=SERVICE_ONE_ID, reply_to_email_id=fake_uuid, _data=data, - _follow_redirects=True + _follow_redirects=True, ) - assert page.find('h1').text == "Reply-to email addresses" - assert error_message in page.find('div', class_='banner-dangerous').text + assert page.find("h1").text == "Reply-to email addresses" + assert error_message in page.find("div", class_="banner-dangerous").text assert mock_update_reply_to_email_address.called is False -@pytest.mark.parametrize('reply_to_address, default_choice_and_delete_link_expected', [ - ( - create_reply_to_email_address(is_default=False), - True, - ), - ( - create_reply_to_email_address(is_default=True), - False, - ), -]) +@pytest.mark.parametrize( + "reply_to_address, default_choice_and_delete_link_expected", + [ + ( + create_reply_to_email_address(is_default=False), + True, + ), + ( + create_reply_to_email_address(is_default=True), + False, + ), + ], +) def test_shows_delete_link_for_get_request_for_edit_email_reply_to_address( mocker, reply_to_address, @@ -2254,51 +2401,49 @@ def test_shows_delete_link_for_get_request_for_edit_email_reply_to_address( fake_uuid, client_request, ): - mocker.patch('app.service_api_client.get_reply_to_email_address', return_value=reply_to_address) + mocker.patch( + "app.service_api_client.get_reply_to_email_address", + return_value=reply_to_address, + ) page = client_request.get( - 'main.service_edit_email_reply_to', + "main.service_edit_email_reply_to", service_id=SERVICE_ONE_ID, reply_to_email_id=sample_uuid(), ) - assert page.select_one('.usa-back-link').text.strip() == 'Back' - assert page.select_one('.usa-back-link')['href'] == url_for( - '.service_email_reply_to', + assert page.select_one(".usa-back-link").text.strip() == "Back" + assert page.select_one(".usa-back-link")["href"] == url_for( + ".service_email_reply_to", service_id=SERVICE_ONE_ID, ) if default_choice_and_delete_link_expected: - link = page.select_one('.page-footer a') - assert normalize_spaces(link.text) == 'Delete' - assert link['href'] == url_for( - 'main.service_confirm_delete_email_reply_to', + link = page.select_one(".page-footer a") + assert normalize_spaces(link.text) == "Delete" + assert link["href"] == url_for( + "main.service_confirm_delete_email_reply_to", service_id=SERVICE_ONE_ID, - reply_to_email_id=sample_uuid() + reply_to_email_id=sample_uuid(), ) - assert not page.select_one('input#is_default').has_attr('checked') + assert not page.select_one("input#is_default").has_attr("checked") else: - assert not page.select('.page-footer a') + assert not page.select(".page-footer a") -@pytest.mark.parametrize('reply_to_address, default_choice_and_delete_link_expected, default_checkbox_checked', [ - ( - create_reply_to_email_address(is_default=False), - True, - False - ), - ( - create_reply_to_email_address(is_default=False), - True, - True - ), - ( - create_reply_to_email_address(is_default=True), - False, - False # not expecting a checkbox to even be shown to be ticked - ), -]) +@pytest.mark.parametrize( + "reply_to_address, default_choice_and_delete_link_expected, default_checkbox_checked", + [ + (create_reply_to_email_address(is_default=False), True, False), + (create_reply_to_email_address(is_default=False), True, True), + ( + create_reply_to_email_address(is_default=True), + False, + False, # not expecting a checkbox to even be shown to be ticked + ), + ], +) def test_shows_delete_link_for_error_on_post_request_for_edit_email_reply_to_address( mocker, reply_to_address, @@ -2307,60 +2452,68 @@ def test_shows_delete_link_for_error_on_post_request_for_edit_email_reply_to_add fake_uuid, client_request, ): - mocker.patch('app.service_api_client.get_reply_to_email_address', return_value=reply_to_address) + mocker.patch( + "app.service_api_client.get_reply_to_email_address", + return_value=reply_to_address, + ) - data = {'email_address': "not a valid email address"} + data = {"email_address": "not a valid email address"} if default_checkbox_checked: data["is_default"] = "y" page = client_request.post( - 'main.service_edit_email_reply_to', + "main.service_edit_email_reply_to", service_id=SERVICE_ONE_ID, reply_to_email_id=sample_uuid(), _data=data, - _expected_status=200 + _expected_status=200, ) - assert page.select_one('.usa-back-link').text.strip() == 'Back' - assert page.select_one('.usa-back-link')['href'] == url_for( - '.service_email_reply_to', + assert page.select_one(".usa-back-link").text.strip() == "Back" + assert page.select_one(".usa-back-link")["href"] == url_for( + ".service_email_reply_to", service_id=SERVICE_ONE_ID, ) - assert page.select_one('.usa-error-message').text.strip() == 'Error: Enter a valid email address' - assert page.select_one('input#email_address').get('value') == 'not a valid email address' + assert ( + page.select_one(".usa-error-message").text.strip() + == "Error: Enter a valid email address" + ) + assert ( + page.select_one("input#email_address").get("value") + == "not a valid email address" + ) if default_choice_and_delete_link_expected: - link = page.select_one('.page-footer a') + link = page.select_one(".page-footer a") assert normalize_spaces(link.text) == "Delete" - assert link['href'] == url_for( - 'main.service_confirm_delete_email_reply_to', + assert link["href"] == url_for( + "main.service_confirm_delete_email_reply_to", service_id=SERVICE_ONE_ID, - reply_to_email_id=sample_uuid() + reply_to_email_id=sample_uuid(), + ) + assert ( + page.select_one("input#is_default").has_attr("checked") + == default_checkbox_checked ) - assert page.select_one('input#is_default').has_attr('checked') == default_checkbox_checked else: - assert not page.select('.page-footer a') + assert not page.select(".page-footer a") def test_confirm_delete_reply_to_email_address( - fake_uuid, - client_request, - get_non_default_reply_to_email_address + fake_uuid, client_request, get_non_default_reply_to_email_address ): - page = client_request.get( - 'main.service_confirm_delete_email_reply_to', + "main.service_confirm_delete_email_reply_to", service_id=SERVICE_ONE_ID, reply_to_email_id=fake_uuid, _test_page_title=False, ) - assert normalize_spaces(page.select_one('.banner-dangerous').text) == ( - 'Are you sure you want to delete this reply-to email address? ' - 'Yes, delete' + assert normalize_spaces(page.select_one(".banner-dangerous").text) == ( + "Are you sure you want to delete this reply-to email address? " "Yes, delete" ) - assert 'action' not in page.select_one('.banner-dangerous form') - assert page.select_one('.banner-dangerous form')['method'] == 'post' + assert "action" not in page.select_one(".banner-dangerous form") + assert page.select_one(".banner-dangerous form")["method"] == "post" def test_delete_reply_to_email_address( @@ -2370,25 +2523,34 @@ def test_delete_reply_to_email_address( get_non_default_reply_to_email_address, mocker, ): - mock_delete = mocker.patch('app.service_api_client.delete_reply_to_email_address') + mock_delete = mocker.patch("app.service_api_client.delete_reply_to_email_address") client_request.post( - '.service_delete_email_reply_to', + ".service_delete_email_reply_to", service_id=SERVICE_ONE_ID, reply_to_email_id=fake_uuid, _expected_redirect=url_for( - 'main.service_email_reply_to', + "main.service_email_reply_to", service_id=SERVICE_ONE_ID, - ) + ), + ) + mock_delete.assert_called_once_with( + service_id=SERVICE_ONE_ID, reply_to_email_id=fake_uuid ) - mock_delete.assert_called_once_with(service_id=SERVICE_ONE_ID, reply_to_email_id=fake_uuid) -@pytest.mark.parametrize('sms_sender, data, api_default_args', [ - (create_sms_sender(), {"is_default": "y", "sms_sender": "test"}, True), - (create_sms_sender(), {"sms_sender": "test"}, True), - (create_sms_sender(is_default=False), {"sms_sender": "test"}, False), - (create_sms_sender(is_default=False), {"is_default": "y", "sms_sender": "test"}, True) -]) +@pytest.mark.parametrize( + "sms_sender, data, api_default_args", + [ + (create_sms_sender(), {"is_default": "y", "sms_sender": "test"}, True), + (create_sms_sender(), {"sms_sender": "test"}, True), + (create_sms_sender(is_default=False), {"sms_sender": "test"}, False), + ( + create_sms_sender(is_default=False), + {"is_default": "y", "sms_sender": "test"}, + True, + ), + ], +) def test_edit_sms_sender( sms_sender, data, @@ -2396,59 +2558,62 @@ def test_edit_sms_sender( mocker, fake_uuid, client_request, - mock_update_sms_sender + mock_update_sms_sender, ): - mocker.patch('app.service_api_client.get_sms_sender', return_value=sms_sender) + mocker.patch("app.service_api_client.get_sms_sender", return_value=sms_sender) client_request.post( - 'main.service_edit_sms_sender', + "main.service_edit_sms_sender", service_id=SERVICE_ONE_ID, sms_sender_id=fake_uuid, - _data=data + _data=data, ) mock_update_sms_sender.assert_called_once_with( SERVICE_ONE_ID, sms_sender_id=fake_uuid, sms_sender="test", - is_default=api_default_args + is_default=api_default_args, ) -@pytest.mark.parametrize('sender_page, endpoint_to_mock, sender_details, default_message, params, checkbox_present', [ - ( - 'main.service_edit_email_reply_to', - 'app.service_api_client.get_reply_to_email_address', - create_reply_to_email_address(is_default=True), - 'This is the default reply-to address for service one emails', - 'reply_to_email_id', - False - ), - ( - 'main.service_edit_email_reply_to', - 'app.service_api_client.get_reply_to_email_address', - create_reply_to_email_address(is_default=False), - 'This is the default reply-to address for service one emails', - 'reply_to_email_id', - True - ), - ( - 'main.service_edit_sms_sender', - 'app.service_api_client.get_sms_sender', - create_sms_sender(is_default=True), - 'This is the default text message sender.', - 'sms_sender_id', - False - ), - ( - 'main.service_edit_sms_sender', - 'app.service_api_client.get_sms_sender', - create_sms_sender(is_default=False), - 'This is the default text message sender.', - 'sms_sender_id', - True - ) -]) +@pytest.mark.parametrize( + "sender_page, endpoint_to_mock, sender_details, default_message, params, checkbox_present", + [ + ( + "main.service_edit_email_reply_to", + "app.service_api_client.get_reply_to_email_address", + create_reply_to_email_address(is_default=True), + "This is the default reply-to address for service one emails", + "reply_to_email_id", + False, + ), + ( + "main.service_edit_email_reply_to", + "app.service_api_client.get_reply_to_email_address", + create_reply_to_email_address(is_default=False), + "This is the default reply-to address for service one emails", + "reply_to_email_id", + True, + ), + ( + "main.service_edit_sms_sender", + "app.service_api_client.get_sms_sender", + create_sms_sender(is_default=True), + "This is the default text message sender.", + "sms_sender_id", + False, + ), + ( + "main.service_edit_sms_sender", + "app.service_api_client.get_sms_sender", + create_sms_sender(is_default=False), + "This is the default text message sender.", + "sms_sender_id", + True, + ), + ], +) def test_default_box_shows_on_non_default_sender_details_while_editing( fake_uuid, mocker, @@ -2458,40 +2623,40 @@ def test_default_box_shows_on_non_default_sender_details_while_editing( client_request, default_message, checkbox_present, - params + params, ): - page_arguments = { - 'service_id': SERVICE_ONE_ID - } + page_arguments = {"service_id": SERVICE_ONE_ID} page_arguments[params] = fake_uuid mocker.patch(endpoint_to_mock, return_value=sender_details) - page = client_request.get( - sender_page, - **page_arguments - ) + page = client_request.get(sender_page, **page_arguments) if checkbox_present: - assert page.select_one('[name=is_default]') + assert page.select_one("[name=is_default]") else: - assert normalize_spaces(page.select_one('form p').text) == ( - default_message - ) + assert normalize_spaces(page.select_one("form p").text) == (default_message) -@pytest.mark.parametrize('sms_sender, expected_link_text, partial_href', [ - ( - create_sms_sender(is_default=False), - 'Delete', - partial(url_for, 'main.service_confirm_delete_sms_sender', sms_sender_id=sample_uuid()), - ), - ( - create_sms_sender(is_default=True), - None, - None, - ), -]) +@pytest.mark.parametrize( + "sms_sender, expected_link_text, partial_href", + [ + ( + create_sms_sender(is_default=False), + "Delete", + partial( + url_for, + "main.service_confirm_delete_sms_sender", + sms_sender_id=sample_uuid(), + ), + ), + ( + create_sms_sender(is_default=True), + None, + None, + ), + ], +) def test_shows_delete_link_for_sms_sender( mocker, sms_sender, @@ -2500,27 +2665,26 @@ def test_shows_delete_link_for_sms_sender( fake_uuid, client_request, ): - - mocker.patch('app.service_api_client.get_sms_sender', return_value=sms_sender) + mocker.patch("app.service_api_client.get_sms_sender", return_value=sms_sender) page = client_request.get( - 'main.service_edit_sms_sender', + "main.service_edit_sms_sender", service_id=SERVICE_ONE_ID, sms_sender_id=sample_uuid(), ) - link = page.select_one('.page-footer a') - back_link = page.select_one('.usa-back-link') + link = page.select_one(".page-footer a") + back_link = page.select_one(".usa-back-link") - assert back_link.text.strip() == 'Back' - assert back_link['href'] == url_for( - '.service_sms_senders', + assert back_link.text.strip() == "Back" + assert back_link["href"] == url_for( + ".service_sms_senders", service_id=SERVICE_ONE_ID, ) if expected_link_text: assert normalize_spaces(link.text) == expected_link_text - assert link['href'] == partial_href(service_id=SERVICE_ONE_ID) + assert link["href"] == partial_href(service_id=SERVICE_ONE_ID) else: assert not link @@ -2530,46 +2694,42 @@ def test_confirm_delete_sms_sender( client_request, get_non_default_sms_sender, ): - page = client_request.get( - 'main.service_confirm_delete_sms_sender', + "main.service_confirm_delete_sms_sender", service_id=SERVICE_ONE_ID, sms_sender_id=fake_uuid, _test_page_title=False, ) - assert normalize_spaces(page.select_one('.banner-dangerous').text) == ( - 'Are you sure you want to delete this text message sender? ' - 'Yes, delete' + assert normalize_spaces(page.select_one(".banner-dangerous").text) == ( + "Are you sure you want to delete this text message sender? " "Yes, delete" ) - assert 'action' not in page.select_one('.banner-dangerous form') - assert page.select_one('.banner-dangerous form')['method'] == 'post' + assert "action" not in page.select_one(".banner-dangerous form") + assert page.select_one(".banner-dangerous form")["method"] == "post" -@pytest.mark.parametrize('sms_sender, expected_link_text', [ - (create_sms_sender(is_default=False, inbound_number_id='1234'), None), - (create_sms_sender(is_default=True), None), - (create_sms_sender(is_default=False), 'Delete'), -]) +@pytest.mark.parametrize( + "sms_sender, expected_link_text", + [ + (create_sms_sender(is_default=False, inbound_number_id="1234"), None), + (create_sms_sender(is_default=True), None), + (create_sms_sender(is_default=False), "Delete"), + ], +) def test_inbound_sms_sender_is_not_deleteable( - client_request, - service_one, - fake_uuid, - sms_sender, - expected_link_text, - mocker + client_request, service_one, fake_uuid, sms_sender, expected_link_text, mocker ): - mocker.patch('app.service_api_client.get_sms_sender', return_value=sms_sender) + mocker.patch("app.service_api_client.get_sms_sender", return_value=sms_sender) page = client_request.get( - '.service_edit_sms_sender', + ".service_edit_sms_sender", service_id=SERVICE_ONE_ID, sms_sender_id=fake_uuid, ) - back_link = page.select_one('.usa-back-link') - footer_link = page.select_one('.page-footer a') - assert normalize_spaces(back_link.text) == 'Back' + back_link = page.select_one(".usa-back-link") + footer_link = page.select_one(".page-footer a") + assert normalize_spaces(back_link.text) == "Back" if expected_link_text: assert normalize_spaces(footer_link.text) == expected_link_text @@ -2584,44 +2744,45 @@ def test_delete_sms_sender( get_non_default_sms_sender, mocker, ): - mock_delete = mocker.patch('app.service_api_client.delete_sms_sender') + mock_delete = mocker.patch("app.service_api_client.delete_sms_sender") client_request.post( - '.service_delete_sms_sender', + ".service_delete_sms_sender", service_id=SERVICE_ONE_ID, sms_sender_id=fake_uuid, _expected_redirect=url_for( - 'main.service_sms_senders', + "main.service_sms_senders", service_id=SERVICE_ONE_ID, - ) + ), + ) + mock_delete.assert_called_once_with( + service_id=SERVICE_ONE_ID, sms_sender_id=fake_uuid ) - mock_delete.assert_called_once_with(service_id=SERVICE_ONE_ID, sms_sender_id=fake_uuid) -@pytest.mark.parametrize('sms_sender, hide_textbox', [ - (create_sms_sender(is_default=False, inbound_number_id='1234'), True), - (create_sms_sender(is_default=True), False), -]) +@pytest.mark.parametrize( + "sms_sender, hide_textbox", + [ + (create_sms_sender(is_default=False, inbound_number_id="1234"), True), + (create_sms_sender(is_default=True), False), + ], +) def test_inbound_sms_sender_is_not_editable( - client_request, - service_one, - fake_uuid, - sms_sender, - hide_textbox, - mocker + client_request, service_one, fake_uuid, sms_sender, hide_textbox, mocker ): - mocker.patch('app.service_api_client.get_sms_sender', return_value=sms_sender) + mocker.patch("app.service_api_client.get_sms_sender", return_value=sms_sender) page = client_request.get( - '.service_edit_sms_sender', + ".service_edit_sms_sender", service_id=SERVICE_ONE_ID, sms_sender_id=fake_uuid, ) - assert bool(page.find('input', attrs={'name': "sms_sender"})) != hide_textbox + assert bool(page.find("input", attrs={"name": "sms_sender"})) != hide_textbox if hide_textbox: - assert normalize_spaces( - page.select_one('form[method="post"] p').text - ) == "GOVUK This phone number receives replies and cannot be changed" + assert ( + normalize_spaces(page.select_one('form[method="post"] p').text) + == "GOVUK This phone number receives replies and cannot be changed" + ) def test_shows_research_mode_indicator( @@ -2632,16 +2793,16 @@ def test_shows_research_mode_indicator( single_sms_sender, mock_get_service_settings_page_common, ): - service_one['research_mode'] = True - mocker.patch('app.service_api_client.update_service', return_value=service_one) + service_one["research_mode"] = True + mocker.patch("app.service_api_client.update_service", return_value=service_one) page = client_request.get( - 'main.service_settings', + "main.service_settings", service_id=SERVICE_ONE_ID, ) - element = page.find('span', {"id": "research-mode"}) - assert element.text == 'research mode' + element = page.find("span", {"id": "research-mode"}) + assert element.text == "research mode" def test_does_not_show_research_mode_indicator( @@ -2651,36 +2812,63 @@ def test_does_not_show_research_mode_indicator( mock_get_service_settings_page_common, ): page = client_request.get( - 'main.service_settings', + "main.service_settings", service_id=SERVICE_ONE_ID, ) - element = page.find('span', {"id": "research-mode"}) + element = page.find("span", {"id": "research-mode"}) assert not element -@pytest.mark.parametrize('current_branding, expected_values, expected_labels', [ - (None, [ - '__NONE__', '1', '2', '3', '4', '5', - ], [ - 'GOV.UK', 'org 1', 'org 2', 'org 3', 'org 4', 'org 5' - ]), - ('5', [ - '5', '__NONE__', '1', '2', '3', '4', - ], [ - 'org 5', 'GOV.UK', 'org 1', 'org 2', 'org 3', 'org 4', - ]), -]) -@pytest.mark.parametrize('endpoint, extra_args', ( +@pytest.mark.parametrize( + "current_branding, expected_values, expected_labels", + [ + ( + None, + [ + "__NONE__", + "1", + "2", + "3", + "4", + "5", + ], + ["GOV.UK", "org 1", "org 2", "org 3", "org 4", "org 5"], + ), + ( + "5", + [ + "5", + "__NONE__", + "1", + "2", + "3", + "4", + ], + [ + "org 5", + "GOV.UK", + "org 1", + "org 2", + "org 3", + "org 4", + ], + ), + ], +) +@pytest.mark.parametrize( + "endpoint, extra_args", ( - 'main.service_set_email_branding', - {'service_id': SERVICE_ONE_ID}, + ( + "main.service_set_email_branding", + {"service_id": SERVICE_ONE_ID}, + ), + ( + "main.edit_organization_email_branding", + {"org_id": ORGANISATION_ID}, + ), ), - ( - 'main.edit_organization_email_branding', - {'org_id': ORGANISATION_ID}, - ), -)) +) def test_should_show_branding_styles( mocker, client_request, @@ -2693,56 +2881,62 @@ def test_should_show_branding_styles( endpoint, extra_args, ): - service_one['email_branding'] = current_branding + service_one["email_branding"] = current_branding mocker.patch( - 'app.organizations_client.get_organization', + "app.organizations_client.get_organization", side_effect=lambda org_id: organization_json( org_id, - 'Org 1', + "Org 1", email_branding_id=current_branding, - ) + ), ) client_request.login(platform_admin_user) page = client_request.get(endpoint, **extra_args) - branding_style_choices = page.find_all('input', attrs={"name": "branding_style"}) + branding_style_choices = page.find_all("input", attrs={"name": "branding_style"}) radio_labels = [ - page.find('label', attrs={"for": branding_style_choices[idx]['id']}).get_text().strip() - for idx, element in enumerate(branding_style_choices)] + page.find("label", attrs={"for": branding_style_choices[idx]["id"]}) + .get_text() + .strip() + for idx, element in enumerate(branding_style_choices) + ] assert len(branding_style_choices) == 6 for index, expected_value in enumerate(expected_values): - assert branding_style_choices[index]['value'] == expected_value + assert branding_style_choices[index]["value"] == expected_value # radios should be in alphabetical order, based on their labels assert radio_labels == expected_labels - assert 'checked' in branding_style_choices[0].attrs - assert 'checked' not in branding_style_choices[1].attrs - assert 'checked' not in branding_style_choices[2].attrs - assert 'checked' not in branding_style_choices[3].attrs - assert 'checked' not in branding_style_choices[4].attrs - assert 'checked' not in branding_style_choices[5].attrs + assert "checked" in branding_style_choices[0].attrs + assert "checked" not in branding_style_choices[1].attrs + assert "checked" not in branding_style_choices[2].attrs + assert "checked" not in branding_style_choices[3].attrs + assert "checked" not in branding_style_choices[4].attrs + assert "checked" not in branding_style_choices[5].attrs app.email_branding_client.get_all_email_branding.assert_called_once_with() - app.service_api_client.get_service.assert_called_once_with(service_one['id']) + app.service_api_client.get_service.assert_called_once_with(service_one["id"]) -@pytest.mark.parametrize('endpoint, extra_args, expected_redirect', ( +@pytest.mark.parametrize( + "endpoint, extra_args, expected_redirect", ( - 'main.service_set_email_branding', - {'service_id': SERVICE_ONE_ID}, - 'main.service_preview_email_branding', + ( + "main.service_set_email_branding", + {"service_id": SERVICE_ONE_ID}, + "main.service_preview_email_branding", + ), + ( + "main.edit_organization_email_branding", + {"org_id": ORGANISATION_ID}, + "main.organization_preview_email_branding", + ), ), - ( - 'main.edit_organization_email_branding', - {'org_id': ORGANISATION_ID}, - 'main.organization_preview_email_branding', - ), -)) +) def test_should_send_branding_and_organizations_to_preview( client_request, platform_admin_user, @@ -2757,32 +2951,28 @@ def test_should_send_branding_and_organizations_to_preview( client_request.login(platform_admin_user) client_request.post( endpoint, - _data={ - 'branding_type': 'org', - 'branding_style': '1' - }, + _data={"branding_type": "org", "branding_style": "1"}, _expected_status=302, - _expected_location=url_for( - expected_redirect, - branding_style='1', - **extra_args - ), - **extra_args + _expected_location=url_for(expected_redirect, branding_style="1", **extra_args), + **extra_args, ) mock_get_all_email_branding.assert_called_once_with() -@pytest.mark.parametrize('endpoint, extra_args', ( +@pytest.mark.parametrize( + "endpoint, extra_args", ( - 'main.service_preview_email_branding', - {'service_id': SERVICE_ONE_ID}, + ( + "main.service_preview_email_branding", + {"service_id": SERVICE_ONE_ID}, + ), + ( + "main.organization_preview_email_branding", + {"org_id": ORGANISATION_ID}, + ), ), - ( - 'main.organization_preview_email_branding', - {'org_id': ORGANISATION_ID}, - ), -)) +) def test_should_preview_email_branding( client_request, platform_admin_user, @@ -2792,38 +2982,41 @@ def test_should_preview_email_branding( ): client_request.login(platform_admin_user) page = client_request.get( - endpoint, - branding_type='org', - branding_style='1', - **extra_args + endpoint, branding_type="org", branding_style="1", **extra_args ) - iframe = page.find('iframe', attrs={"class": "branding-preview"}) - iframeURLComponents = urlparse(iframe['src']) + iframe = page.find("iframe", attrs={"class": "branding-preview"}) + iframeURLComponents = urlparse(iframe["src"]) iframeQString = parse_qs(iframeURLComponents.query) - assert page.find('input', attrs={"id": "branding_style"})['value'] == '1' - assert iframeURLComponents.path == '/_email' - assert iframeQString['branding_style'] == ['1'] + assert page.find("input", attrs={"id": "branding_style"})["value"] == "1" + assert iframeURLComponents.path == "/_email" + assert iframeQString["branding_style"] == ["1"] -@pytest.mark.parametrize('posted_value, submitted_value', ( - ('1', '1'), - ('__NONE__', None), - pytest.param('None', None, marks=pytest.mark.xfail(raises=AssertionError)), -)) -@pytest.mark.parametrize('endpoint, extra_args, expected_redirect', ( +@pytest.mark.parametrize( + "posted_value, submitted_value", ( - 'main.service_preview_email_branding', - {'service_id': SERVICE_ONE_ID}, - 'main.service_settings', + ("1", "1"), + ("__NONE__", None), + pytest.param("None", None, marks=pytest.mark.xfail(raises=AssertionError)), ), +) +@pytest.mark.parametrize( + "endpoint, extra_args, expected_redirect", ( - 'main.organization_preview_email_branding', - {'org_id': ORGANISATION_ID}, - 'main.organization_settings', + ( + "main.service_preview_email_branding", + {"service_id": SERVICE_ONE_ID}, + "main.service_settings", + ), + ( + "main.organization_preview_email_branding", + {"org_id": ORGANISATION_ID}, + "main.organization_settings", + ), ), -)) +) def test_should_set_branding_and_organizations( client_request, platform_admin_user, @@ -2841,31 +3034,26 @@ def test_should_set_branding_and_organizations( client_request.login(platform_admin_user) client_request.post( endpoint, - _data={ - 'branding_style': posted_value - }, + _data={"branding_style": posted_value}, _expected_status=302, - _expected_redirect=url_for( - expected_redirect, - **extra_args - ), - **extra_args + _expected_redirect=url_for(expected_redirect, **extra_args), + **extra_args, ) - if endpoint == 'main.service_preview_email_branding': + if endpoint == "main.service_preview_email_branding": mock_update_service.assert_called_once_with( SERVICE_ONE_ID, email_branding=submitted_value, ) assert mock_update_organization.called is False - elif endpoint == 'main.organization_preview_email_branding': + elif endpoint == "main.organization_preview_email_branding": mock_update_organization.assert_called_once_with( ORGANISATION_ID, email_branding_id=submitted_value, cached_service_ids=[ - '12345', - '67890', - '596364a0-858e-42c8-9062-a8fe822260eb', + "12345", + "67890", + "596364a0-858e-42c8-9062-a8fe822260eb", ], ) assert mock_update_service.called is False @@ -2873,12 +3061,15 @@ def test_should_set_branding_and_organizations( raise Exception -@pytest.mark.parametrize('method', ['get', 'post']) -@pytest.mark.parametrize('endpoint', [ - 'main.set_free_sms_allowance', - 'main.set_message_limit', - 'main.set_rate_limit', -]) +@pytest.mark.parametrize("method", ["get", "post"]) +@pytest.mark.parametrize( + "endpoint", + [ + "main.set_free_sms_allowance", + "main.set_message_limit", + "main.set_rate_limit", + ], +) def test_organization_type_pages_are_platform_admin_only( client_request, method, @@ -2893,26 +3084,27 @@ def test_organization_type_pages_are_platform_admin_only( def test_should_show_page_to_set_sms_allowance( - client_request, - platform_admin_user, - mock_get_free_sms_fragment_limit + client_request, platform_admin_user, mock_get_free_sms_fragment_limit ): client_request.login(platform_admin_user) - page = client_request.get( - 'main.set_free_sms_allowance', - service_id=SERVICE_ONE_ID - ) + page = client_request.get("main.set_free_sms_allowance", service_id=SERVICE_ONE_ID) - assert normalize_spaces(page.select_one('label').text) == 'Numbers of text message fragments per year' + assert ( + normalize_spaces(page.select_one("label").text) + == "Numbers of text message fragments per year" + ) mock_get_free_sms_fragment_limit.assert_called_once_with(SERVICE_ONE_ID) @freeze_time("2017-04-01 11:09:00.061258") -@pytest.mark.parametrize('given_allowance, expected_api_argument', [ - ('0', 0), - ('1', 1), - ('250000', 250000), -]) +@pytest.mark.parametrize( + "given_allowance, expected_api_argument", + [ + ("0", 0), + ("1", 1), + ("250000", 250000), + ], +) def test_should_set_sms_allowance( client_request, platform_admin_user, @@ -2921,30 +3113,31 @@ def test_should_set_sms_allowance( mock_get_free_sms_fragment_limit, mock_create_or_update_free_sms_fragment_limit, ): - client_request.login(platform_admin_user) client_request.post( - 'main.set_free_sms_allowance', + "main.set_free_sms_allowance", service_id=SERVICE_ONE_ID, _data={ - 'free_sms_allowance': given_allowance, + "free_sms_allowance": given_allowance, }, _expected_redirect=url_for( - 'main.service_settings', + "main.service_settings", service_id=SERVICE_ONE_ID, ), ) mock_create_or_update_free_sms_fragment_limit.assert_called_with( - SERVICE_ONE_ID, - expected_api_argument + SERVICE_ONE_ID, expected_api_argument ) @freeze_time("2017-04-01 11:09:00.061258") -@pytest.mark.parametrize('given_allowance, expected_api_argument', [ - pytest.param('foo', 'foo'), -]) +@pytest.mark.parametrize( + "given_allowance, expected_api_argument", + [ + pytest.param("foo", "foo"), + ], +) def test_should_set_sms_allowance_fails( client_request, platform_admin_user, @@ -2953,17 +3146,16 @@ def test_should_set_sms_allowance_fails( mock_get_free_sms_fragment_limit, mock_create_or_update_free_sms_fragment_limit, ): - with pytest.raises(expected_exception=AssertionError): client_request.login(platform_admin_user) client_request.post( - 'main.set_free_sms_allowance', + "main.set_free_sms_allowance", service_id=SERVICE_ONE_ID, _data={ - 'free_sms_allowance': given_allowance, + "free_sms_allowance": given_allowance, }, _expected_redirect=url_for( - 'main.service_settings', + "main.service_settings", service_id=SERVICE_ONE_ID, ), ) @@ -2974,16 +3166,11 @@ def test_should_show_page_to_set_message_limit( platform_admin_user, ): client_request.login(platform_admin_user) - page = client_request.get( - 'main.set_message_limit', - service_id=SERVICE_ONE_ID - ) - assert normalize_spaces(page.select_one('label').text) == ( - 'Number of messages the service is allowed to send each day' - ) - assert normalize_spaces(page.select_one('input[type=text]')['value']) == ( - '1000' + page = client_request.get("main.set_message_limit", service_id=SERVICE_ONE_ID) + assert normalize_spaces(page.select_one("label").text) == ( + "Number of messages the service is allowed to send each day" ) + assert normalize_spaces(page.select_one("input[type=text]")["value"]) == ("1000") def test_should_show_page_to_set_rate_limit( @@ -2991,27 +3178,28 @@ def test_should_show_page_to_set_rate_limit( platform_admin_user, ): client_request.login(platform_admin_user) - page = client_request.get( - 'main.set_rate_limit', - service_id=SERVICE_ONE_ID - ) - assert normalize_spaces(page.select_one('label').text) == ( - 'Number of messages the service can send in a rolling 60 second window' - ) - assert normalize_spaces(page.select_one('input[type=text]')['value']) == ( - '3000' + page = client_request.get("main.set_rate_limit", service_id=SERVICE_ONE_ID) + assert normalize_spaces(page.select_one("label").text) == ( + "Number of messages the service can send in a rolling 60 second window" ) + assert normalize_spaces(page.select_one("input[type=text]")["value"]) == ("3000") -@pytest.mark.parametrize('endpoint, field_name', ( - ('main.set_message_limit', 'message_limit'), - ('main.set_rate_limit', 'rate_limit'), -)) -@pytest.mark.parametrize('new_limit, expected_api_argument', [ - ('1', 1), - ('250000', 250000), - pytest.param('foo', 'foo', marks=pytest.mark.xfail), -]) +@pytest.mark.parametrize( + "endpoint, field_name", + ( + ("main.set_message_limit", "message_limit"), + ("main.set_rate_limit", "rate_limit"), + ), +) +@pytest.mark.parametrize( + "new_limit, expected_api_argument", + [ + ("1", 1), + ("250000", 250000), + pytest.param("foo", "foo", marks=pytest.mark.xfail), + ], +) def test_should_set_message_limit( client_request, platform_admin_user, @@ -3039,50 +3227,53 @@ def test_unknown_channel_404s( client_request, ): client_request.get( - 'main.service_set_channel', + "main.service_set_channel", service_id=SERVICE_ONE_ID, - channel='message-in-a-bottle', + channel="message-in-a-bottle", _expected_status=404, ) -@pytest.mark.parametrize(( - 'channel,' - 'expected_first_para,' - 'expected_legend,' - 'initial_permissions,' - 'expected_initial_value,' - 'posted_value,' - 'expected_updated_permissions' -), [ +@pytest.mark.parametrize( ( - 'sms', - 'You may send up to 250,000 text messages during the pilot period.', - 'Send text messages', - [], - 'False', - 'True', - ['sms'], + "channel," + "expected_first_para," + "expected_legend," + "initial_permissions," + "expected_initial_value," + "posted_value," + "expected_updated_permissions" ), - ( - 'email', - 'It’s free to send emails through Notify.gov.', - 'Send emails', - [], - 'False', - 'True', - ['email'], - ), - ( - 'email', - 'It’s free to send emails through Notify.gov.', - 'Send emails', - ['email', 'sms'], - 'True', - 'True', - ['email', 'sms'], - ), -]) + [ + ( + "sms", + "You may send up to 250,000 text messages during the pilot period.", + "Send text messages", + [], + "False", + "True", + ["sms"], + ), + ( + "email", + "It’s free to send emails through Notify.gov.", + "Send emails", + [], + "False", + "True", + ["email"], + ), + ( + "email", + "It’s free to send emails through Notify.gov.", + "Send emails", + ["email", "sms"], + "True", + "True", + ["email", "sms"], + ), + ], +) def test_switch_service_channels_on_and_off( client_request, service_one, @@ -3096,39 +3287,46 @@ def test_switch_service_channels_on_and_off( posted_value, expected_updated_permissions, ): - mocked_fn = mocker.patch('app.service_api_client.update_service', return_value=service_one) - service_one['permissions'] = initial_permissions + mocked_fn = mocker.patch( + "app.service_api_client.update_service", return_value=service_one + ) + service_one["permissions"] = initial_permissions page = client_request.get( - 'main.service_set_channel', - service_id=service_one['id'], + "main.service_set_channel", + service_id=service_one["id"], channel=channel, ) - assert normalize_spaces(page.select_one('main p').text) == expected_first_para - assert normalize_spaces(page.select_one('legend').text) == expected_legend + assert normalize_spaces(page.select_one("main p").text) == expected_first_para + assert normalize_spaces(page.select_one("legend").text) == expected_legend - assert page.select_one('input[checked]')['value'] == expected_initial_value - assert len(page.select('input[checked]')) == 1 + assert page.select_one("input[checked]")["value"] == expected_initial_value + assert len(page.select("input[checked]")) == 1 client_request.post( - 'main.service_set_channel', - service_id=service_one['id'], + "main.service_set_channel", + service_id=service_one["id"], channel=channel, - _data={'enabled': posted_value}, + _data={"enabled": posted_value}, _expected_redirect=url_for( - 'main.service_settings', - service_id=service_one['id'], - ) + "main.service_settings", + service_id=service_one["id"], + ), ) - assert set(mocked_fn.call_args[1]['permissions']) == set(expected_updated_permissions) - assert mocked_fn.call_args[0][0] == service_one['id'] + assert set(mocked_fn.call_args[1]["permissions"]) == set( + expected_updated_permissions + ) + assert mocked_fn.call_args[0][0] == service_one["id"] -@pytest.mark.parametrize('permission, permissions, expected_checked', [ - ('international_sms', ['international_sms'], 'True'), - ('international_sms', [''], 'False'), -]) +@pytest.mark.parametrize( + "permission, permissions, expected_checked", + [ + ("international_sms", ["international_sms"], "True"), + ("international_sms", [""], "False"), + ], +) def test_show_international_sms_as_radio_button( client_request, service_one, @@ -3137,26 +3335,25 @@ def test_show_international_sms_as_radio_button( permissions, expected_checked, ): - service_one['permissions'] = permissions + service_one["permissions"] = permissions checked_radios = client_request.get( - f'main.service_set_{permission}', - service_id=service_one['id'], - ).select( - '.govuk-radios__item input[checked]' - ) + f"main.service_set_{permission}", + service_id=service_one["id"], + ).select(".govuk-radios__item input[checked]") assert len(checked_radios) == 1 - assert checked_radios[0]['value'] == expected_checked + assert checked_radios[0]["value"] == expected_checked -@pytest.mark.parametrize('permission', ( - 'international_sms', -)) -@pytest.mark.parametrize('post_value, permission_expected_in_api_call', [ - ('True', True), - ('False', False), -]) +@pytest.mark.parametrize("permission", ("international_sms",)) +@pytest.mark.parametrize( + "post_value, permission_expected_in_api_call", + [ + ("True", True), + ("False", False), + ], +) def test_switch_service_enable_international_sms( client_request, service_one, @@ -3165,29 +3362,34 @@ def test_switch_service_enable_international_sms( post_value, permission_expected_in_api_call, ): - mocked_fn = mocker.patch('app.service_api_client.update_service', return_value=service_one) + mocked_fn = mocker.patch( + "app.service_api_client.update_service", return_value=service_one + ) client_request.post( - f'main.service_set_{permission}', - service_id=service_one['id'], - _data={ - 'enabled': post_value - }, - _expected_redirect=url_for('main.service_settings', service_id=service_one['id']) + f"main.service_set_{permission}", + service_id=service_one["id"], + _data={"enabled": post_value}, + _expected_redirect=url_for( + "main.service_settings", service_id=service_one["id"] + ), ) if permission_expected_in_api_call: - assert permission in mocked_fn.call_args[1]['permissions'] + assert permission in mocked_fn.call_args[1]["permissions"] else: - assert permission not in mocked_fn.call_args[1]['permissions'] + assert permission not in mocked_fn.call_args[1]["permissions"] - assert mocked_fn.call_args[0][0] == service_one['id'] + assert mocked_fn.call_args[0][0] == service_one["id"] -@pytest.mark.parametrize('user, is_trial_service', ( - [create_platform_admin_user(), True], - [create_platform_admin_user(), False], - [create_active_user_with_permissions(), True], -)) +@pytest.mark.parametrize( + "user, is_trial_service", + ( + [create_platform_admin_user(), True], + [create_platform_admin_user(), False], + [create_active_user_with_permissions(), True], + ), +) def test_archive_service_after_confirm( client_request, mocker, @@ -3200,34 +3402,45 @@ def test_archive_service_after_confirm( user, is_trial_service, ): - service_one['restricted'] = is_trial_service - mock_api = mocker.patch('app.service_api_client.post') - mock_event = mocker.patch('app.main.views.service_settings.create_archive_service_event') - redis_delete_mock = mocker.patch('app.notify_client.service_api_client.redis_client.delete') - mocker.patch('app.notify_client.service_api_client.redis_client.delete_by_pattern') + service_one["restricted"] = is_trial_service + mock_api = mocker.patch("app.service_api_client.post") + mock_event = mocker.patch( + "app.main.views.service_settings.create_archive_service_event" + ) + redis_delete_mock = mocker.patch( + "app.notify_client.service_api_client.redis_client.delete" + ) + mocker.patch("app.notify_client.service_api_client.redis_client.delete_by_pattern") client_request.login(user) page = client_request.post( - 'main.archive_service', + "main.archive_service", service_id=SERVICE_ONE_ID, _follow_redirects=True, ) - mock_api.assert_called_once_with('/service/{}/archive'.format(SERVICE_ONE_ID), data=None) - mock_event.assert_called_once_with(service_id=SERVICE_ONE_ID, archived_by_id=user['id']) + mock_api.assert_called_once_with( + "/service/{}/archive".format(SERVICE_ONE_ID), data=None + ) + mock_event.assert_called_once_with( + service_id=SERVICE_ONE_ID, archived_by_id=user["id"] + ) - assert normalize_spaces(page.select_one('h1').text) == 'Choose service' - assert normalize_spaces(page.select_one('.banner-default-with-tick').text) == ( - '‘service one’ was deleted' + assert normalize_spaces(page.select_one("h1").text) == "Choose service" + assert normalize_spaces(page.select_one(".banner-default-with-tick").text) == ( + "‘service one’ was deleted" ) # The one user which is part of this service has the sample_uuid as it's user ID assert call(f"user-{sample_uuid()}") in redis_delete_mock.call_args_list -@pytest.mark.parametrize('user, is_trial_service', ( - pytest.param(create_active_user_with_permissions(), False), - pytest.param(create_active_user_no_settings_permission(), True), -)) +@pytest.mark.parametrize( + "user, is_trial_service", + ( + pytest.param(create_active_user_with_permissions(), False), + pytest.param(create_active_user_no_settings_permission(), True), + ), +) def test_archive_service_after_confirm_error( client_request, mocker, @@ -3240,26 +3453,29 @@ def test_archive_service_after_confirm_error( user, is_trial_service, ): - service_one['restricted'] = is_trial_service - mocker.patch('app.service_api_client.post') - mocker.patch('app.main.views.service_settings.create_archive_service_event') - mocker.patch('app.notify_client.service_api_client.redis_client.delete') - mocker.patch('app.notify_client.service_api_client.redis_client.delete_by_pattern') + service_one["restricted"] = is_trial_service + mocker.patch("app.service_api_client.post") + mocker.patch("app.main.views.service_settings.create_archive_service_event") + mocker.patch("app.notify_client.service_api_client.redis_client.delete") + mocker.patch("app.notify_client.service_api_client.redis_client.delete_by_pattern") with pytest.raises(expected_exception=AssertionError): client_request.login(user) client_request.post( - 'main.archive_service', + "main.archive_service", service_id=SERVICE_ONE_ID, _follow_redirects=True, ) -@pytest.mark.parametrize('user, is_trial_service', ( - [create_platform_admin_user(), True], - [create_platform_admin_user(), False], - [create_active_user_with_permissions(), True], -)) +@pytest.mark.parametrize( + "user, is_trial_service", + ( + [create_platform_admin_user(), True], + [create_platform_admin_user(), False], + [create_active_user_with_permissions(), True], + ), +) def test_archive_service_prompts_user( client_request, mocker, @@ -3270,37 +3486,39 @@ def test_archive_service_prompts_user( user, is_trial_service, ): - mock_api = mocker.patch('app.service_api_client.post') - service_one['restricted'] = is_trial_service + mock_api = mocker.patch("app.service_api_client.post") + service_one["restricted"] = is_trial_service client_request.login(user) settings_page = client_request.get( - 'main.archive_service', - service_id=SERVICE_ONE_ID + "main.archive_service", service_id=SERVICE_ONE_ID ) - delete_link = settings_page.select('.page-footer-link a')[0] - assert normalize_spaces(delete_link.text) == 'Delete this service' - assert delete_link['href'] == url_for( - 'main.archive_service', + delete_link = settings_page.select(".page-footer-link a")[0] + assert normalize_spaces(delete_link.text) == "Delete this service" + assert delete_link["href"] == url_for( + "main.archive_service", service_id=SERVICE_ONE_ID, ) delete_page = client_request.get( - 'main.archive_service', + "main.archive_service", service_id=SERVICE_ONE_ID, ) - assert normalize_spaces(delete_page.select_one('.banner-dangerous').text) == ( - 'Are you sure you want to delete ‘service one’? ' - 'There’s no way to undo this. ' - 'Yes, delete' + assert normalize_spaces(delete_page.select_one(".banner-dangerous").text) == ( + "Are you sure you want to delete ‘service one’? " + "There’s no way to undo this. " + "Yes, delete" ) assert mock_api.called is False -@pytest.mark.parametrize('user, is_trial_service', ( - pytest.param(create_active_user_with_permissions(), False), - pytest.param(create_active_user_no_settings_permission(), True), -)) +@pytest.mark.parametrize( + "user, is_trial_service", + ( + pytest.param(create_active_user_with_permissions(), False), + pytest.param(create_active_user_no_settings_permission(), True), + ), +) def test_archive_service_prompts_user_error( client_request, mocker, @@ -3311,15 +3529,12 @@ def test_archive_service_prompts_user_error( user, is_trial_service, ): - mocker.patch('app.service_api_client.post') - service_one['restricted'] = is_trial_service + mocker.patch("app.service_api_client.post") + service_one["restricted"] = is_trial_service client_request.login(user) with pytest.raises(expected_exception=AssertionError): - client_request.get( - 'main.archive_service', - service_id=SERVICE_ONE_ID - ) + client_request.get("main.archive_service", service_id=SERVICE_ONE_ID) def test_cant_archive_inactive_service( @@ -3328,70 +3543,75 @@ def test_cant_archive_inactive_service( service_one, single_reply_to_email_address, single_sms_sender, - mock_get_service_settings_page_common + mock_get_service_settings_page_common, ): - service_one['active'] = False + service_one["active"] = False client_request.login(platform_admin_user) page = client_request.get( - 'main.service_settings', - service_id=service_one['id'], + "main.service_settings", + service_id=service_one["id"], ) - assert 'Delete service' not in {a.text for a in page.find_all('a', class_='button')} + assert "Delete service" not in {a.text for a in page.find_all("a", class_="button")} -@pytest.mark.parametrize('user', ( - create_platform_admin_user(), -)) +@pytest.mark.parametrize("user", (create_platform_admin_user(),)) def test_suspend_service_after_confirm( client_request, user, mocker, ): - mock_api = mocker.patch('app.service_api_client.post') - mock_event = mocker.patch('app.main.views.service_settings.create_suspend_service_event') + mock_api = mocker.patch("app.service_api_client.post") + mock_event = mocker.patch( + "app.main.views.service_settings.create_suspend_service_event" + ) client_request.login(user) client_request.post( - 'main.suspend_service', + "main.suspend_service", service_id=SERVICE_ONE_ID, _expected_redirect=url_for( - 'main.service_settings', + "main.service_settings", service_id=SERVICE_ONE_ID, ), ) - mock_api.assert_called_once_with('/service/{}/suspend'.format(SERVICE_ONE_ID), data=None) - mock_event.assert_called_once_with(service_id=SERVICE_ONE_ID, suspended_by_id=user['id']) + mock_api.assert_called_once_with( + "/service/{}/suspend".format(SERVICE_ONE_ID), data=None + ) + mock_event.assert_called_once_with( + service_id=SERVICE_ONE_ID, suspended_by_id=user["id"] + ) -@pytest.mark.parametrize('user', ( - pytest.param(create_active_user_with_permissions()), -)) +@pytest.mark.parametrize("user", (pytest.param(create_active_user_with_permissions()),)) def test_suspend_service_after_confirm_error( client_request, user, mocker, ): - mocker.patch('app.service_api_client.post') - mocker.patch('app.main.views.service_settings.create_suspend_service_event') + mocker.patch("app.service_api_client.post") + mocker.patch("app.main.views.service_settings.create_suspend_service_event") with pytest.raises(expected_exception=AssertionError): client_request.login(user) client_request.post( - 'main.suspend_service', + "main.suspend_service", service_id=SERVICE_ONE_ID, _expected_redirect=url_for( - 'main.service_settings', + "main.service_settings", service_id=SERVICE_ONE_ID, ), ) -@pytest.mark.parametrize('user', ( - create_platform_admin_user(), - pytest.param(create_active_user_with_permissions()), -)) +@pytest.mark.parametrize( + "user", + ( + create_platform_admin_user(), + pytest.param(create_active_user_with_permissions()), + ), +) def test_suspend_service_prompts_user( client_request, user, @@ -3401,19 +3621,21 @@ def test_suspend_service_prompts_user( single_sms_sender, mock_get_service_settings_page_common, ): - mock_api = mocker.patch('app.service_api_client.post') + mock_api = mocker.patch("app.service_api_client.post") client_request.login(user) - if user['email_address'] != 'platform@admin.gsa.gov': + if user["email_address"] != "platform@admin.gsa.gov": with pytest.raises(expected_exception=AssertionError): - client_request.get('main.suspend_service', service_id=service_one['id']) + client_request.get("main.suspend_service", service_id=service_one["id"]) return - page = client_request.get('main.suspend_service', service_id=service_one['id']) + page = client_request.get("main.suspend_service", service_id=service_one["id"]) - assert 'This will suspend the service and revoke all api keys. Are you sure you want to suspend this service?' in \ - page.find('div', class_='banner-dangerous').text + assert ( + "This will suspend the service and revoke all api keys. Are you sure you want to suspend this service?" + in page.find("div", class_="banner-dangerous").text + ) assert mock_api.called is False @@ -3425,57 +3647,71 @@ def test_cant_suspend_inactive_service( single_sms_sender, mock_get_service_settings_page_common, ): - service_one['active'] = False + service_one["active"] = False client_request.login(platform_admin_user) page = client_request.get( - 'main.service_settings', - service_id=service_one['id'], + "main.service_settings", + service_id=service_one["id"], ) - assert 'Suspend service' not in {a.text for a in page.find_all('a', class_='button')} + assert "Suspend service" not in { + a.text for a in page.find_all("a", class_="button") + } -@pytest.mark.parametrize('user', ( - create_platform_admin_user(), - create_active_user_with_permissions(), -)) +@pytest.mark.parametrize( + "user", + ( + create_platform_admin_user(), + create_active_user_with_permissions(), + ), +) def test_resume_service_after_confirm( mocker, user, service_one, client_request, ): - service_one['active'] = False - mock_api = mocker.patch('app.service_api_client.post') - mock_event = mocker.patch('app.main.views.service_settings.create_resume_service_event') + service_one["active"] = False + mock_api = mocker.patch("app.service_api_client.post") + mock_event = mocker.patch( + "app.main.views.service_settings.create_resume_service_event" + ) client_request.login(user) - if user['email_address'] != "platform@admin.gsa.gov": + if user["email_address"] != "platform@admin.gsa.gov": client_request.post( - 'main.resume_service', + "main.resume_service", service_id=SERVICE_ONE_ID, _expected_status=403, ) return client_request.post( - 'main.resume_service', + "main.resume_service", service_id=SERVICE_ONE_ID, _expected_redirect=url_for( - 'main.service_settings', + "main.service_settings", service_id=SERVICE_ONE_ID, - ) + ), ) - assert mock_api.called_once_with('/service/{}/resume'.format(SERVICE_ONE_ID), data=None) - assert mock_event.called_once_with(service_id=SERVICE_ONE_ID, resumed_by_id=user['id']) + assert mock_api.called_once_with( + "/service/{}/resume".format(SERVICE_ONE_ID), data=None + ) + assert mock_event.called_once_with( + service_id=SERVICE_ONE_ID, resumed_by_id=user["id"] + ) -@pytest.mark.parametrize('user', ( - create_platform_admin_user(), - pytest.param(create_active_user_with_permissions(), marks=pytest.mark.xfail), -)) +@pytest.mark.parametrize( + "user", + ( + create_platform_admin_user(), + pytest.param(create_active_user_with_permissions(), marks=pytest.mark.xfail), + ), +) def test_resume_service_prompts_user( client_request, user, @@ -3485,14 +3721,16 @@ def test_resume_service_prompts_user( mocker, mock_get_service_settings_page_common, ): - service_one['active'] = False - mock_api = mocker.patch('app.service_api_client.post') + service_one["active"] = False + mock_api = mocker.patch("app.service_api_client.post") client_request.login(user) - page = client_request.get('main.resume_service', service_id=service_one['id']) + page = client_request.get("main.resume_service", service_id=service_one["id"]) - assert 'This will resume the service. New api key are required for this service to use the API.' in \ - page.find('div', class_='banner-dangerous').text + assert ( + "This will resume the service. New api key are required for this service to use the API." + in page.find("div", class_="banner-dangerous").text + ) assert mock_api.called is False @@ -3502,41 +3740,52 @@ def test_cant_resume_active_service( service_one, single_reply_to_email_address, single_sms_sender, - mock_get_service_settings_page_common + mock_get_service_settings_page_common, ): client_request.login(platform_admin_user) page = client_request.get( - 'main.service_settings', - service_id=service_one['id'], + "main.service_settings", + service_id=service_one["id"], ) - assert 'Resume service' not in {a.text for a in page.find_all('a', class_='button')} + assert "Resume service" not in {a.text for a in page.find_all("a", class_="button")} -@pytest.mark.parametrize('contact_details_type, contact_details_value', [ - ('url', 'http://example.com/'), - ('email_address', 'me@example.com'), - ('phone_number', '202 867 5309'), -]) +@pytest.mark.parametrize( + "contact_details_type, contact_details_value", + [ + ("url", "http://example.com/"), + ("email_address", "me@example.com"), + ("phone_number", "202 867 5309"), + ], +) def test_send_files_by_email_contact_details_prefills_the_form_with_the_existing_contact_details( client_request, service_one, contact_details_type, contact_details_value, ): - service_one['contact_link'] = contact_details_value + service_one["contact_link"] = contact_details_value page = client_request.get( - 'main.send_files_by_email_contact_details', service_id=SERVICE_ONE_ID + "main.send_files_by_email_contact_details", service_id=SERVICE_ONE_ID + ) + assert page.find( + "input", attrs={"name": "contact_details_type", "value": contact_details_type} + ).has_attr("checked") + assert ( + page.find("input", {"id": contact_details_type}).get("value") + == contact_details_value ) - assert page.find('input', attrs={'name': 'contact_details_type', 'value': contact_details_type}).has_attr('checked') - assert page.find('input', {'id': contact_details_type}).get('value') == contact_details_value -@pytest.mark.parametrize('contact_details_type, old_value, new_value', [ - ('url', 'http://example.com/', 'http://new-link.com/'), - ('email_address', 'old@example.com', 'new@example.com'), - ('phone_number', '2021234567', '2028901234'), -]) +@pytest.mark.parametrize( + "contact_details_type, old_value, new_value", + [ + ("url", "http://example.com/", "http://new-link.com/"), + ("email_address", "old@example.com", "new@example.com"), + ("phone_number", "2021234567", "2028901234"), + ], +) def test_send_files_by_email_contact_details_updates_contact_details_and_redirects_to_settings_page( client_request, service_one, @@ -3548,18 +3797,19 @@ def test_send_files_by_email_contact_details_updates_contact_details_and_redirec old_value, new_value, ): - service_one['contact_link'] = old_value + service_one["contact_link"] = old_value page = client_request.post( - 'main.send_files_by_email_contact_details', service_id=SERVICE_ONE_ID, + "main.send_files_by_email_contact_details", + service_id=SERVICE_ONE_ID, _data={ - 'contact_details_type': contact_details_type, + "contact_details_type": contact_details_type, contact_details_type: new_value, }, - _follow_redirects=True + _follow_redirects=True, ) - assert page.h1.text == 'Settings' + assert page.h1.text == "Settings" mock_update_service.assert_called_once_with(SERVICE_ONE_ID, contact_link=new_value) @@ -3571,67 +3821,95 @@ def test_send_files_by_email_contact_details_uses_the_selected_field_when_multip no_reply_to_email_addresses, single_sms_sender, ): - service_one['contact_link'] = 'http://www.old-url.com' + service_one["contact_link"] = "http://www.old-url.com" page = client_request.post( - 'main.send_files_by_email_contact_details', service_id=SERVICE_ONE_ID, + "main.send_files_by_email_contact_details", + service_id=SERVICE_ONE_ID, _data={ - 'contact_details_type': 'url', - 'url': 'http://www.new-url.com', - 'email_address': 'me@example.com', - 'phone_number': '202 867 5309' + "contact_details_type": "url", + "url": "http://www.new-url.com", + "email_address": "me@example.com", + "phone_number": "202 867 5309", }, - _follow_redirects=True + _follow_redirects=True, ) - assert page.h1.text == 'Settings' - mock_update_service.assert_called_once_with(SERVICE_ONE_ID, contact_link='http://www.new-url.com') + assert page.h1.text == "Settings" + mock_update_service.assert_called_once_with( + SERVICE_ONE_ID, contact_link="http://www.new-url.com" + ) @pytest.mark.parametrize( - 'contact_link, subheader, button_selected', + "contact_link, subheader, button_selected", [ - ('contact.me@gsa.gov', 'Change contact details for the file download page', True), - (None, 'Add contact details to the file download page', False), - ] + ( + "contact.me@gsa.gov", + "Change contact details for the file download page", + True, + ), + (None, "Add contact details to the file download page", False), + ], ) def test_send_files_by_email_contact_details_page( - client_request, service_one, active_user_with_permissions, contact_link, subheader, button_selected + client_request, + service_one, + active_user_with_permissions, + contact_link, + subheader, + button_selected, ): service_one["contact_link"] = contact_link page = client_request.get( - 'main.send_files_by_email_contact_details', service_id=SERVICE_ONE_ID + "main.send_files_by_email_contact_details", service_id=SERVICE_ONE_ID ) - assert normalize_spaces(page.find_all('h2')[0].text) == subheader + assert normalize_spaces(page.find_all("h2")[0].text) == subheader if button_selected: - assert 'checked' in page.find('input', {'name': 'contact_details_type', 'value': 'email_address'}).attrs + assert ( + "checked" + in page.find( + "input", {"name": "contact_details_type", "value": "email_address"} + ).attrs + ) else: - assert 'checked' not in page.find('input', {'name': 'contact_details_type', 'value': 'email_address'}).attrs + assert ( + "checked" + not in page.find( + "input", {"name": "contact_details_type", "value": "email_address"} + ).attrs + ) def test_send_files_by_email_contact_details_displays_error_message_when_no_radio_button_selected( - client_request, - service_one + client_request, service_one ): page = client_request.post( - 'main.send_files_by_email_contact_details', service_id=SERVICE_ONE_ID, + "main.send_files_by_email_contact_details", + service_id=SERVICE_ONE_ID, _data={ - 'contact_details_type': None, - 'url': '', - 'email_address': '', - 'phone_number': '', + "contact_details_type": None, + "url": "", + "email_address": "", + "phone_number": "", }, - _follow_redirects=True + _follow_redirects=True, + ) + assert ( + normalize_spaces(page.find("span", class_="error-message").text) + == "Select an option" ) - assert normalize_spaces(page.find('span', class_='error-message').text) == 'Select an option' assert normalize_spaces(page.h1.text) == "Send files by email" -@pytest.mark.parametrize('contact_details_type, invalid_value, error', [ - ('url', 'invalid.com/', 'Must be a valid URL'), - ('email_address', 'me@co', 'Enter a valid email address'), - ('phone_number', 'abcde', 'Must be a valid phone number'), -]) +@pytest.mark.parametrize( + "contact_details_type, invalid_value, error", + [ + ("url", "invalid.com/", "Must be a valid URL"), + ("email_address", "me@co", "Enter a valid email address"), + ("phone_number", "abcde", "Must be a valid phone number"), + ], +) def test_send_files_by_email_contact_details_does_not_update_invalid_contact_details( mocker, client_request, @@ -3640,38 +3918,34 @@ def test_send_files_by_email_contact_details_does_not_update_invalid_contact_det invalid_value, error, ): - service_one['contact_link'] = 'http://example.com/' - service_one['permissions'].append('upload_document') + service_one["contact_link"] = "http://example.com/" + service_one["permissions"].append("upload_document") page = client_request.post( - 'main.send_files_by_email_contact_details', service_id=SERVICE_ONE_ID, + "main.send_files_by_email_contact_details", + service_id=SERVICE_ONE_ID, _data={ - 'contact_details_type': contact_details_type, + "contact_details_type": contact_details_type, contact_details_type: invalid_value, }, - _follow_redirects=True + _follow_redirects=True, ) - assert error in page.find('span', class_='usa-error-message').text + assert error in page.find("span", class_="usa-error-message").text assert normalize_spaces(page.h1.text) == "Send files by email" -@pytest.mark.parametrize('endpoint, permissions, expected_p', [ - ( - 'main.service_set_auth_type', - [], +@pytest.mark.parametrize( + "endpoint, permissions, expected_p", + [ + ("main.service_set_auth_type", [], ("Text message code")), ( - 'Text message code' - ) - ), - ( - 'main.service_set_auth_type', - ['email_auth'], - ( - 'Email link or text message code' - ) - ), -]) + "main.service_set_auth_type", + ["email_auth"], + ("Email link or text message code"), + ), + ], +) def test_invitation_pages( client_request, service_one, @@ -3681,13 +3955,13 @@ def test_invitation_pages( permissions, expected_p, ): - service_one['permissions'] = permissions + service_one["permissions"] = permissions page = client_request.get( endpoint, service_id=SERVICE_ONE_ID, ) - assert normalize_spaces(page.select('main p')[0].text) == expected_p + assert normalize_spaces(page.select("main p")[0].text) == expected_p def test_service_settings_when_inbound_number_is_not_set( @@ -3700,10 +3974,12 @@ def test_service_settings_when_inbound_number_is_not_set( mock_get_free_sms_fragment_limit, mock_get_service_data_retention, ): - mocker.patch('app.inbound_number_client.get_inbound_sms_number_for_service', - return_value={'data': {}}) + mocker.patch( + "app.inbound_number_client.get_inbound_sms_number_for_service", + return_value={"data": {}}, + ) client_request.get( - 'main.service_settings', + "main.service_settings", service_id=SERVICE_ONE_ID, ) @@ -3715,29 +3991,40 @@ def test_set_inbound_sms_when_inbound_number_is_not_set( single_sms_sender, mocker, ): - mocker.patch('app.inbound_number_client.get_inbound_sms_number_for_service', - return_value={'data': {}}) + mocker.patch( + "app.inbound_number_client.get_inbound_sms_number_for_service", + return_value={"data": {}}, + ) client_request.get( - 'main.service_set_inbound_sms', + "main.service_set_inbound_sms", service_id=SERVICE_ONE_ID, ) -@pytest.mark.parametrize('user, expected_paragraphs', [ - (create_active_user_with_permissions(), [ - 'Your service can receive text messages sent to 2028675309.', - 'You can still send text messages from a sender name if you ' - 'need to, but users will not be able to reply to those messages.', - 'Contact us if you want to switch this feature off.', - 'You can set up callbacks for received text messages on the API integration page.', - ]), - (create_active_user_no_api_key_permission(), [ - 'Your service can receive text messages sent to 2028675309.', - 'You can still send text messages from a sender name if you ' - 'need to, but users will not be able to reply to those messages.', - 'Contact us if you want to switch this feature off.', - ]), -]) +@pytest.mark.parametrize( + "user, expected_paragraphs", + [ + ( + create_active_user_with_permissions(), + [ + "Your service can receive text messages sent to 2028675309.", + "You can still send text messages from a sender name if you " + "need to, but users will not be able to reply to those messages.", + "Contact us if you want to switch this feature off.", + "You can set up callbacks for received text messages on the API integration page.", + ], + ), + ( + create_active_user_no_api_key_permission(), + [ + "Your service can receive text messages sent to 2028675309.", + "You can still send text messages from a sender name if you " + "need to, but users will not be able to reply to those messages.", + "Contact us if you want to switch this feature off.", + ], + ), + ], +) def test_set_inbound_sms_when_inbound_number_is_set( client_request, service_one, @@ -3745,16 +4032,17 @@ def test_set_inbound_sms_when_inbound_number_is_set( user, expected_paragraphs, ): - service_one['permissions'] = ['inbound_sms'] - mocker.patch('app.inbound_number_client.get_inbound_sms_number_for_service', return_value={ - 'data': {'number': '2028675309'} - }) + service_one["permissions"] = ["inbound_sms"] + mocker.patch( + "app.inbound_number_client.get_inbound_sms_number_for_service", + return_value={"data": {"number": "2028675309"}}, + ) client_request.login(user) page = client_request.get( - 'main.service_set_inbound_sms', + "main.service_set_inbound_sms", service_id=SERVICE_ONE_ID, ) - paragraphs = page.select('main p') + paragraphs = page.select("main p") assert len(paragraphs) == len(expected_paragraphs) @@ -3766,36 +4054,39 @@ def test_show_sms_prefixing_setting_page( client_request, mock_update_service, ): - page = client_request.get( - 'main.service_set_sms_prefix', service_id=SERVICE_ONE_ID + page = client_request.get("main.service_set_sms_prefix", service_id=SERVICE_ONE_ID) + assert normalize_spaces(page.select_one("legend").text) == ( + "Start all text messages with ‘service one:’" ) - assert normalize_spaces(page.select_one('legend').text) == ( - 'Start all text messages with ‘service one:’' - ) - radios = page.select('input[type=radio]') + radios = page.select("input[type=radio]") assert len(radios) == 2 - assert radios[0]['value'] == 'True' - assert radios[0]['checked'] == '' - assert radios[1]['value'] == 'False' + assert radios[0]["value"] == "True" + assert radios[0]["checked"] == "" + assert radios[1]["value"] == "False" with pytest.raises(KeyError): - assert radios[1]['checked'] + assert radios[1]["checked"] -@pytest.mark.parametrize('post_value', [ - True, - False, -]) +@pytest.mark.parametrize( + "post_value", + [ + True, + False, + ], +) def test_updates_sms_prefixing( client_request, mock_update_service, post_value, ): client_request.post( - 'main.service_set_sms_prefix', service_id=SERVICE_ONE_ID, - _data={'enabled': post_value}, + "main.service_set_sms_prefix", + service_id=SERVICE_ONE_ID, + _data={"enabled": post_value}, _expected_redirect=url_for( - 'main.service_settings', service_id=SERVICE_ONE_ID, - ) + "main.service_settings", + service_id=SERVICE_ONE_ID, + ), ) mock_update_service.assert_called_once_with( SERVICE_ONE_ID, @@ -3808,38 +4099,34 @@ def test_select_organization( platform_admin_user, service_one, mock_get_organization, - mock_get_organizations + mock_get_organizations, ): client_request.login(platform_admin_user) page = client_request.get( - '.link_service_to_organization', - service_id=service_one['id'], + ".link_service_to_organization", + service_id=service_one["id"], ) - assert len(page.select('.govuk-radios__item')) == 3 + assert len(page.select(".govuk-radios__item")) == 3 for i in range(0, 3): assert normalize_spaces( - page.select('.govuk-radios__item label')[i].text - ) == 'Org {}'.format(i + 1) + page.select(".govuk-radios__item label")[i].text + ) == "Org {}".format(i + 1) def test_select_organization_shows_message_if_no_orgs( - client_request, - platform_admin_user, - service_one, - mock_get_organization, - mocker + client_request, platform_admin_user, service_one, mock_get_organization, mocker ): - mocker.patch('app.organizations_client.get_organizations', return_value=[]) + mocker.patch("app.organizations_client.get_organizations", return_value=[]) client_request.login(platform_admin_user) page = client_request.get( - '.link_service_to_organization', - service_id=service_one['id'], + ".link_service_to_organization", + service_id=service_one["id"], ) - assert normalize_spaces(page.select_one('main p').text) == "No organizations" - assert not page.select_one('main button') + assert normalize_spaces(page.select_one("main p").text) == "No organizations" + assert not page.select_one("main button") def test_update_service_organization( @@ -3852,13 +4139,12 @@ def test_update_service_organization( ): client_request.login(platform_admin_user) client_request.post( - '.link_service_to_organization', - service_id=service_one['id'], - _data={'organizations': '7aa5d4e9-4385-4488-a489-07812ba13384'}, + ".link_service_to_organization", + service_id=service_one["id"], + _data={"organizations": "7aa5d4e9-4385-4488-a489-07812ba13384"}, ) mock_update_service_organization.assert_called_once_with( - service_one['id'], - '7aa5d4e9-4385-4488-a489-07812ba13384' + service_one["id"], "7aa5d4e9-4385-4488-a489-07812ba13384" ) @@ -3871,20 +4157,26 @@ def test_update_service_organization_does_not_update_if_same_value( mock_update_service_organization, ): org_id = "7aa5d4e9-4385-4488-a489-07812ba13383" - service_one['organization'] = org_id + service_one["organization"] = org_id client_request.login(platform_admin_user) client_request.post( - '.link_service_to_organization', - service_id=service_one['id'], - _data={'organizations': org_id}, + ".link_service_to_organization", + service_id=service_one["id"], + _data={"organizations": org_id}, ) assert mock_update_service_organization.called is False @pytest.mark.skip(reason="Email currently deactivated") -@pytest.mark.parametrize('single_branding_option, expected_href', [ - (True, f'/services/{SERVICE_ONE_ID}/service-settings/email-branding/something-else'), -]) +@pytest.mark.parametrize( + "single_branding_option, expected_href", + [ + ( + True, + f"/services/{SERVICE_ONE_ID}/service-settings/email-branding/something-else", + ), + ], +) def test_service_settings_links_to_branding_request_page_for_emails( service_one, client_request, @@ -3896,128 +4188,121 @@ def test_service_settings_links_to_branding_request_page_for_emails( if single_branding_option: # should only have a "something else" option # so we go straight to that form - service_one['organization_type'] = 'other' + service_one["organization_type"] = "other" - page = client_request.get( - '.service_settings', service_id=SERVICE_ONE_ID - ) - assert len(page.find_all('a', attrs={'href': expected_href})) == 1 + page = client_request.get(".service_settings", service_id=SERVICE_ONE_ID) + assert len(page.find_all("a", attrs={"href": expected_href})) == 1 def test_show_service_data_retention( - client_request, - platform_admin_user, - service_one, - mock_get_service_data_retention, - + client_request, + platform_admin_user, + service_one, + mock_get_service_data_retention, ): - - mock_get_service_data_retention.return_value[0]['days_of_retention'] = 5 + mock_get_service_data_retention.return_value[0]["days_of_retention"] = 5 client_request.login(platform_admin_user) page = client_request.get( - 'main.data_retention', - service_id=service_one['id'], + "main.data_retention", + service_id=service_one["id"], ) - rows = page.select('tbody tr') + rows = page.select("tbody tr") assert len(rows) == 1 - assert normalize_spaces(rows[0].text) == 'Email 5 days Change' + assert normalize_spaces(rows[0].text) == "Email 5 days Change" def test_view_add_service_data_retention( - client_request, - platform_admin_user, - service_one, - + client_request, + platform_admin_user, + service_one, ): client_request.login(platform_admin_user) page = client_request.get( - 'main.add_data_retention', - service_id=service_one['id'], + "main.add_data_retention", + service_id=service_one["id"], ) - assert normalize_spaces(page.select_one('input')['value']) == "email" - assert page.find('input', attrs={'name': 'days_of_retention'}) + assert normalize_spaces(page.select_one("input")["value"]) == "email" + assert page.find("input", attrs={"name": "days_of_retention"}) def test_add_service_data_retention( - client_request, - platform_admin_user, - service_one, - mock_create_service_data_retention + client_request, platform_admin_user, service_one, mock_create_service_data_retention ): client_request.login(platform_admin_user) client_request.post( - 'main.add_data_retention', - service_id=service_one['id'], - _data={'notification_type': "email", 'days_of_retention': 5}, + "main.add_data_retention", + service_id=service_one["id"], + _data={"notification_type": "email", "days_of_retention": 5}, _expected_redirect=url_for( - 'main.data_retention', - service_id=service_one['id'], + "main.data_retention", + service_id=service_one["id"], ), ) assert mock_create_service_data_retention.called def test_update_service_data_retention( - client_request, - platform_admin_user, - service_one, - fake_uuid, - mock_get_service_data_retention, - mock_update_service_data_retention, + client_request, + platform_admin_user, + service_one, + fake_uuid, + mock_get_service_data_retention, + mock_update_service_data_retention, ): client_request.login(platform_admin_user) client_request.post( - 'main.edit_data_retention', - service_id=service_one['id'], + "main.edit_data_retention", + service_id=service_one["id"], data_retention_id=str(fake_uuid), - _data={'days_of_retention': 5}, + _data={"days_of_retention": 5}, _expected_redirect=url_for( - 'main.data_retention', - service_id=service_one['id'], + "main.data_retention", + service_id=service_one["id"], ), ) assert mock_update_service_data_retention.called def test_update_service_data_retention_return_validation_error_for_negative_days_of_retention( - client_request, - platform_admin_user, - service_one, - fake_uuid, - mock_get_service_data_retention, - mock_update_service_data_retention, + client_request, + platform_admin_user, + service_one, + fake_uuid, + mock_get_service_data_retention, + mock_update_service_data_retention, ): client_request.login(platform_admin_user) page = client_request.post( - 'main.edit_data_retention', - service_id=service_one['id'], + "main.edit_data_retention", + service_id=service_one["id"], data_retention_id=fake_uuid, - _data={'days_of_retention': -5}, + _data={"days_of_retention": -5}, _expected_status=200, ) - assert 'Must be between 3 and 90' in page.find('span', class_='usa-error-message').text + assert ( + "Must be between 3 and 90" in page.find("span", class_="usa-error-message").text + ) assert mock_get_service_data_retention.called assert not mock_update_service_data_retention.called def test_update_service_data_retention_populates_form( - client_request, - platform_admin_user, - service_one, - fake_uuid, - mock_get_service_data_retention, + client_request, + platform_admin_user, + service_one, + fake_uuid, + mock_get_service_data_retention, ): - - mock_get_service_data_retention.return_value[0]['days_of_retention'] = 5 + mock_get_service_data_retention.return_value[0]["days_of_retention"] = 5 client_request.login(platform_admin_user) page = client_request.get( - 'main.edit_data_retention', - service_id=service_one['id'], + "main.edit_data_retention", + service_id=service_one["id"], data_retention_id=fake_uuid, ) - assert page.find('input', attrs={'name': 'days_of_retention'})['value'] == '5' + assert page.find("input", attrs={"name": "days_of_retention"})["value"] == "5" def test_service_settings_links_to_edit_service_notes_page_for_platform_admins( @@ -4031,43 +4316,46 @@ def test_service_settings_links_to_edit_service_notes_page_for_platform_admins( ): client_request.login(platform_admin_user) page = client_request.get( - '.service_settings', + ".service_settings", service_id=SERVICE_ONE_ID, ) - assert len(page.find_all('a', attrs={'href': '/services/{}/notes'.format(SERVICE_ONE_ID)})) == 1 + assert ( + len( + page.find_all( + "a", attrs={"href": "/services/{}/notes".format(SERVICE_ONE_ID)} + ) + ) + == 1 + ) def test_view_edit_service_notes( - client_request, - platform_admin_user, - service_one, - + client_request, + platform_admin_user, + service_one, ): client_request.login(platform_admin_user) page = client_request.get( - 'main.edit_service_notes', + "main.edit_service_notes", service_id=SERVICE_ONE_ID, ) - assert page.select_one('h1').text == "Edit service notes" - assert page.find('label', class_="usa-label").text.strip() == "Notes" - assert page.find('textarea').attrs["name"] == "notes" + assert page.select_one("h1").text == "Edit service notes" + assert page.find("label", class_="usa-label").text.strip() == "Notes" + assert page.find("textarea").attrs["name"] == "notes" def test_update_service_notes( - client_request, - platform_admin_user, - service_one, - mock_update_service + client_request, platform_admin_user, service_one, mock_update_service ): client_request.login(platform_admin_user) client_request.post( - 'main.edit_service_notes', + "main.edit_service_notes", service_id=SERVICE_ONE_ID, - _data={'notes': "Very fluffy"}, + _data={"notes": "Very fluffy"}, _expected_redirect=url_for( - 'main.service_settings', + "main.service_settings", service_id=SERVICE_ONE_ID, - ) + ), ) mock_update_service.assert_called_with(SERVICE_ONE_ID, notes="Very fluffy") @@ -4083,76 +4371,82 @@ def test_service_settings_links_to_edit_service_billing_details_page_for_platfor ): client_request.login(platform_admin_user) page = client_request.get( - '.service_settings', + ".service_settings", service_id=SERVICE_ONE_ID, ) - assert len(page.find_all('a', attrs={'href': '/services/{}/edit-billing-details'.format(SERVICE_ONE_ID)})) == 1 + assert ( + len( + page.find_all( + "a", + attrs={ + "href": "/services/{}/edit-billing-details".format(SERVICE_ONE_ID) + }, + ) + ) + == 1 + ) def test_view_edit_service_billing_details( - client_request, - platform_admin_user, - service_one, - + client_request, + platform_admin_user, + service_one, ): client_request.login(platform_admin_user) page = client_request.get( - 'main.edit_service_billing_details', + "main.edit_service_billing_details", service_id=SERVICE_ONE_ID, ) - assert page.select_one('h1').text == "Change billing details" - labels = page.find_all('label', class_="form-label") + assert page.select_one("h1").text == "Change billing details" + labels = page.find_all("label", class_="form-label") labels_list = [ - 'Contact email addresses', - 'Contact names', - 'Reference', - 'Purchase order number', - 'Notes' + "Contact email addresses", + "Contact names", + "Reference", + "Purchase order number", + "Notes", ] for label in labels: assert label.text.strip() in labels_list - textbox_names = page.find_all('input', class_='govuk-input ') + textbox_names = page.find_all("input", class_="govuk-input ") names_list = [ - 'billing_contact_email_addresses', - 'billing_contact_names', - 'billing_reference', - 'purchase_order_number', + "billing_contact_email_addresses", + "billing_contact_names", + "billing_reference", + "purchase_order_number", ] for name in textbox_names: assert name.attrs["name"] in names_list - assert page.find('textarea').attrs["name"] == "notes" + assert page.find("textarea").attrs["name"] == "notes" def test_update_service_billing_details( - client_request, - platform_admin_user, - service_one, - mock_update_service + client_request, platform_admin_user, service_one, mock_update_service ): client_request.login(platform_admin_user) client_request.post( - 'main.edit_service_billing_details', + "main.edit_service_billing_details", service_id=SERVICE_ONE_ID, _data={ - 'billing_contact_email_addresses': 'accounts@fluff.gsa.gov', - 'billing_contact_names': 'Flannellette von Fluff', - 'billing_reference': '', - 'purchase_order_number': 'PO1234', - 'notes': 'very fluffy, give extra allowance' + "billing_contact_email_addresses": "accounts@fluff.gsa.gov", + "billing_contact_names": "Flannellette von Fluff", + "billing_reference": "", + "purchase_order_number": "PO1234", + "notes": "very fluffy, give extra allowance", }, _expected_redirect=url_for( - 'main.service_settings', + "main.service_settings", service_id=SERVICE_ONE_ID, ), ) mock_update_service.assert_called_with( SERVICE_ONE_ID, - billing_contact_email_addresses='accounts@fluff.gsa.gov', - billing_contact_names='Flannellette von Fluff', - billing_reference='', - purchase_order_number='PO1234', - notes='very fluffy, give extra allowance' + billing_contact_email_addresses="accounts@fluff.gsa.gov", + billing_contact_names="Flannellette von Fluff", + billing_reference="", + purchase_order_number="PO1234", + notes="very fluffy, give extra allowance", ) diff --git a/tests/app/main/views/test_accept_invite.py b/tests/app/main/views/test_accept_invite.py index b1beff18c..03fd3d920 100644 --- a/tests/app/main/views/test_accept_invite.py +++ b/tests/app/main/views/test_accept_invite.py @@ -17,20 +17,22 @@ from tests.conftest import ( @pytest.fixture() def mock_no_users_for_service(mocker): - mocker.patch('app.models.user.Users.client_method', return_value=[]) + mocker.patch("app.models.user.Users.client_method", return_value=[]) -@pytest.fixture(scope='function') +@pytest.fixture(scope="function") def mock_get_existing_user_by_email(mocker, api_user_active): - return mocker.patch('app.user_api_client.get_user_by_email', return_value=api_user_active) + return mocker.patch( + "app.user_api_client.get_user_by_email", return_value=api_user_active + ) -@pytest.fixture(scope='function') +@pytest.fixture(scope="function") def mock_check_invite_token(mocker, sample_invite): - return mocker.patch('app.invite_api_client.check_token', return_value=sample_invite) + return mocker.patch("app.invite_api_client.check_token", return_value=sample_invite) -@freeze_time('2021-12-12 12:12:12') +@freeze_time("2021-12-12 12:12:12") def test_existing_user_accept_invite_calls_api_and_redirects_to_dashboard( client_request, service_one, @@ -47,21 +49,28 @@ def test_existing_user_accept_invite_calls_api_and_redirects_to_dashboard( mock_update_user_attribute, ): client_request.logout() - expected_service = service_one['id'] - expected_permissions = {'view_activity', 'send_messages', 'manage_service', 'manage_api_keys'} + expected_service = service_one["id"] + expected_permissions = { + "view_activity", + "send_messages", + "manage_service", + "manage_api_keys", + } client_request.get( - 'main.accept_invite', - token='thisisnotarealtoken', - _expected_redirect=url_for('main.service_dashboard', service_id=expected_service), + "main.accept_invite", + token="thisisnotarealtoken", + _expected_redirect=url_for( + "main.service_dashboard", service_id=expected_service + ), ) - mock_check_invite_token.assert_called_with('thisisnotarealtoken') - mock_get_existing_user_by_email.assert_called_with('invited_user@test.gsa.gov') + mock_check_invite_token.assert_called_with("thisisnotarealtoken") + mock_get_existing_user_by_email.assert_called_with("invited_user@test.gsa.gov") assert mock_accept_invite.call_count == 1 mock_add_user_to_service.assert_called_with( expected_service, - api_user_active['id'], + api_user_active["id"], expected_permissions, [], ) @@ -84,21 +93,23 @@ def test_existing_user_with_no_permissions_or_folder_permissions_accept_invite( ): client_request.logout() - expected_service = service_one['id'] - sample_invite['permissions'] = '' + expected_service = service_one["id"] + sample_invite["permissions"] = "" expected_permissions = set() expected_folder_permissions = [] - mocker.patch('app.invite_api_client.accept_invite', return_value=sample_invite) + mocker.patch("app.invite_api_client.accept_invite", return_value=sample_invite) client_request.get( - 'main.accept_invite', - token='thisisnotarealtoken', + "main.accept_invite", + token="thisisnotarealtoken", _expected_status=302, ) - mock_add_user_to_service.assert_called_with(expected_service, - api_user_active['id'], - expected_permissions, - expected_folder_permissions) + mock_add_user_to_service.assert_called_with( + expected_service, + api_user_active["id"], + expected_permissions, + expected_folder_permissions, + ) def test_if_existing_user_accepts_twice_they_redirect_to_sign_in( @@ -112,20 +123,20 @@ def test_if_existing_user_accepts_twice_they_redirect_to_sign_in( client_request.logout() # Logging out updates the current session ID to `None` mock_update_user_attribute.reset_mock() - sample_invite['status'] = 'accepted' + sample_invite["status"] = "accepted" page = client_request.get( - 'main.accept_invite', - token='thisisnotarealtoken', + "main.accept_invite", + token="thisisnotarealtoken", _follow_redirects=True, ) assert ( page.h1.string, - page.select('main p')[0].text.strip(), + page.select("main p")[0].text.strip(), ) == ( - 'You need to sign in again', - 'We signed you out because you have not used Notify for a while.', + "You need to sign in again", + "We signed you out because you have not used Notify for a while.", ) # We don’t let people update `email_access_validated_at` using an # already-accepted invite @@ -144,27 +155,30 @@ def test_invite_goes_in_session( mock_add_user_to_service, mock_accept_invite, ): - sample_invite['email_address'] = 'test@user.gsa.gov' + sample_invite["email_address"] = "test@user.gsa.gov" client_request.get( - 'main.accept_invite', - token='thisisnotarealtoken', + "main.accept_invite", + token="thisisnotarealtoken", _expected_status=302, _expected_redirect=url_for( - 'main.service_dashboard', + "main.service_dashboard", service_id=SERVICE_ONE_ID, ), _follow_redirects=False, ) with client_request.session_transaction() as session: - assert session['invited_user_id'] == sample_invite['id'] + assert session["invited_user_id"] == sample_invite["id"] -@pytest.mark.parametrize('user, landing_page_title', [ - (create_active_user_with_permissions(), 'Dashboard'), - (create_active_caseworking_user(), 'Templates'), -]) +@pytest.mark.parametrize( + "user, landing_page_title", + [ + (create_active_user_with_permissions(), "Dashboard"), + (create_active_caseworking_user(), "Templates"), + ], +) def test_accepting_invite_removes_invite_from_session( client_request, mocker, @@ -190,22 +204,22 @@ def test_accepting_invite_removes_invite_from_session( user, landing_page_title, ): - sample_invite['email_address'] = user['email_address'] + sample_invite["email_address"] = user["email_address"] client_request.login(user) page = client_request.get( - 'main.accept_invite', - token='thisisnotarealtoken', + "main.accept_invite", + token="thisisnotarealtoken", _follow_redirects=True, ) - assert normalize_spaces(page.select_one('h1').text) == landing_page_title + assert normalize_spaces(page.select_one("h1").text) == landing_page_title with client_request.session_transaction() as session: - assert 'invited_user_id' not in session + assert "invited_user_id" not in session -@freeze_time('2021-12-12T12:12:12') +@freeze_time("2021-12-12T12:12:12") def test_existing_user_of_service_get_redirected_to_signin( client_request, mocker, @@ -218,21 +232,21 @@ def test_existing_user_of_service_get_redirected_to_signin( mock_update_user_attribute, ): client_request.logout() - sample_invite['email_address'] = api_user_active['email_address'] - mocker.patch('app.models.user.Users.client_method', return_value=[api_user_active]) + sample_invite["email_address"] = api_user_active["email_address"] + mocker.patch("app.models.user.Users.client_method", return_value=[api_user_active]) page = client_request.get( - 'main.accept_invite', - token='thisisnotarealtoken', + "main.accept_invite", + token="thisisnotarealtoken", _follow_redirects=True, ) assert ( page.h1.string, - page.select('main p')[0].text.strip(), + page.select("main p")[0].text.strip(), ) == ( - 'You need to sign in again', - 'We signed you out because you have not used Notify for a while.', + "You need to sign in again", + "We signed you out because you have not used Notify for a while.", ) assert mock_accept_invite.call_count == 1 @@ -252,22 +266,27 @@ def test_accept_invite_redirects_if_api_raises_an_error_that_they_are_already_pa ): client_request.logout() - mocker.patch('app.user_api_client.add_user_to_service', side_effect=HTTPError( - response=Mock( - status_code=400, - json={ - "result": "error", - "message": {f"User id: {api_user_active['id']} already part of service id: {SERVICE_ONE_ID}"} - }, + mocker.patch( + "app.user_api_client.add_user_to_service", + side_effect=HTTPError( + response=Mock( + status_code=400, + json={ + "result": "error", + "message": { + f"User id: {api_user_active['id']} already part of service id: {SERVICE_ONE_ID}" + }, + }, + ), + message=f"User id: {api_user_active['id']} already part of service id: {SERVICE_ONE_ID}", ), - message=f"User id: {api_user_active['id']} already part of service id: {SERVICE_ONE_ID}" - )) + ) client_request.get( - 'main.accept_invite', - token='thisisnotarealtoken', + "main.accept_invite", + token="thisisnotarealtoken", _follow_redirects=False, - _expected_redirect=url_for('main.service_dashboard', service_id=SERVICE_ONE_ID) + _expected_redirect=url_for("main.service_dashboard", service_id=SERVICE_ONE_ID), ) @@ -288,28 +307,35 @@ def test_existing_signed_out_user_accept_invite_redirects_to_sign_in( mock_update_user_attribute, ): client_request.logout() - expected_service = service_one['id'] - expected_permissions = {'view_activity', 'send_messages', 'manage_service', 'manage_api_keys'} + expected_service = service_one["id"] + expected_permissions = { + "view_activity", + "send_messages", + "manage_service", + "manage_api_keys", + } page = client_request.get( - 'main.accept_invite', - token='thisisnotarealtoken', + "main.accept_invite", + token="thisisnotarealtoken", _follow_redirects=True, ) - mock_check_invite_token.assert_called_with('thisisnotarealtoken') - mock_get_existing_user_by_email.assert_called_with('invited_user@test.gsa.gov') - mock_add_user_to_service.assert_called_with(expected_service, - api_user_active['id'], - expected_permissions, - sample_invite['folder_permissions']) + mock_check_invite_token.assert_called_with("thisisnotarealtoken") + mock_get_existing_user_by_email.assert_called_with("invited_user@test.gsa.gov") + mock_add_user_to_service.assert_called_with( + expected_service, + api_user_active["id"], + expected_permissions, + sample_invite["folder_permissions"], + ) assert mock_accept_invite.call_count == 1 assert ( page.h1.string, - page.select('main p')[0].text.strip(), + page.select("main p")[0].text.strip(), ) == ( - 'You need to sign in again', - 'We signed you out because you have not used Notify for a while.', + "You need to sign in again", + "We signed you out because you have not used Notify for a while.", ) @@ -325,13 +351,13 @@ def test_new_user_accept_invite_calls_api_and_redirects_to_registration( ): client_request.logout() client_request.get( - 'main.accept_invite', - token='thisisnotarealtoken', - _expected_redirect='/register-from-invite', + "main.accept_invite", + token="thisisnotarealtoken", + _expected_redirect="/register-from-invite", ) - mock_check_invite_token.assert_called_with('thisisnotarealtoken') - mock_dont_get_user_by_email.assert_called_with('invited_user@test.gsa.gov') + mock_check_invite_token.assert_called_with("thisisnotarealtoken") + mock_dont_get_user_by_email.assert_called_with("invited_user@test.gsa.gov") def test_new_user_accept_invite_calls_api_and_views_registration_page( @@ -348,34 +374,34 @@ def test_new_user_accept_invite_calls_api_and_views_registration_page( ): client_request.logout() page = client_request.get( - 'main.accept_invite', - token='thisisnotarealtoken', + "main.accept_invite", + token="thisisnotarealtoken", _follow_redirects=True, ) - mock_check_invite_token.assert_called_with('thisisnotarealtoken') - mock_dont_get_user_by_email.assert_called_with('invited_user@test.gsa.gov') - mock_get_invited_user_by_id.assert_called_once_with(sample_invite['id']) + mock_check_invite_token.assert_called_with("thisisnotarealtoken") + mock_dont_get_user_by_email.assert_called_with("invited_user@test.gsa.gov") + mock_get_invited_user_by_id.assert_called_once_with(sample_invite["id"]) - assert page.h1.string.strip() == 'Create an account' + assert page.h1.string.strip() == "Create an account" - assert normalize_spaces(page.select_one('main p').text) == ( - 'Your account will be created with this email address: ' - 'invited_user@test.gsa.gov' + assert normalize_spaces(page.select_one("main p").text) == ( + "Your account will be created with this email address: " + "invited_user@test.gsa.gov" ) - form = page.find('form') - name = form.find('input', id='name') - password = form.find('input', id='password') - service = form.find('input', type='hidden', id='service') - email = form.find('input', type='hidden', id='email_address') + form = page.find("form") + name = form.find("input", id="name") + password = form.find("input", id="password") + service = form.find("input", type="hidden", id="service") + email = form.find("input", type="hidden", id="email_address") assert email - assert email.attrs['value'] == 'invited_user@test.gsa.gov' + assert email.attrs["value"] == "invited_user@test.gsa.gov" assert name assert password assert service - assert service.attrs['value'] == service_one['id'] + assert service.attrs["value"] == service_one["id"] def test_cancelled_invited_user_accepts_invited_redirect_to_cancelled_invitation( @@ -388,21 +414,24 @@ def test_cancelled_invited_user_accepts_invited_redirect_to_cancelled_invitation ): client_request.logout() mock_update_user_attribute.reset_mock() - sample_invite['status'] = 'cancelled' - page = client_request.get('main.accept_invite', token='thisisnotarealtoken') + sample_invite["status"] = "cancelled" + page = client_request.get("main.accept_invite", token="thisisnotarealtoken") - app.invite_api_client.check_token.assert_called_with('thisisnotarealtoken') + app.invite_api_client.check_token.assert_called_with("thisisnotarealtoken") - assert page.h1.string.strip() == 'The invitation you were sent has been cancelled' + assert page.h1.string.strip() == "The invitation you were sent has been cancelled" # We don’t let people update `email_access_validated_at` using an # cancelled invite assert mock_update_user_attribute.called is False -@pytest.mark.parametrize('admin_endpoint, api_endpoint', [ - ('main.accept_invite', 'app.invite_api_client.check_token'), - ('main.accept_org_invite', 'app.org_invite_api_client.check_token'), -]) +@pytest.mark.parametrize( + "admin_endpoint, api_endpoint", + [ + ("main.accept_invite", "app.invite_api_client.check_token"), + ("main.accept_org_invite", "app.org_invite_api_client.check_token"), + ], +) def test_new_user_accept_invite_with_malformed_token( admin_endpoint, api_endpoint, @@ -411,26 +440,34 @@ def test_new_user_accept_invite_with_malformed_token( mocker, ): client_request.logout() - mocker.patch(api_endpoint, side_effect=HTTPError( - response=Mock( - status_code=400, - json={ - 'result': 'error', - 'message': { - 'invitation': { - 'Something’s wrong with this link. Make sure you’ve copied the whole thing.' - } - } - } + mocker.patch( + api_endpoint, + side_effect=HTTPError( + response=Mock( + status_code=400, + json={ + "result": "error", + "message": { + "invitation": { + "Something’s wrong with this link. Make sure you’ve copied the whole thing." + } + }, + }, + ), + message={ + "invitation": "Something’s wrong with this link. Make sure you’ve copied the whole thing." + }, ), - message={'invitation': 'Something’s wrong with this link. Make sure you’ve copied the whole thing.'} - )) + ) - page = client_request.get(admin_endpoint, token='thisisnotarealtoken', _follow_redirects=True) + page = client_request.get( + admin_endpoint, token="thisisnotarealtoken", _follow_redirects=True + ) - assert normalize_spaces( - page.select_one('.banner-dangerous').text - ) == 'Something’s wrong with this link. Make sure you’ve copied the whole thing.' + assert ( + normalize_spaces(page.select_one(".banner-dangerous").text) + == "Something’s wrong with this link. Make sure you’ve copied the whole thing." + ) def test_new_user_accept_invite_completes_new_registration_redirects_to_verify( @@ -451,40 +488,43 @@ def test_new_user_accept_invite_completes_new_registration_redirects_to_verify( mocker, ): client_request.logout() - expected_redirect_location = '/register-from-invite' + expected_redirect_location = "/register-from-invite" client_request.get( - 'main.accept_invite', - token='thisisnotarealtoken', + "main.accept_invite", + token="thisisnotarealtoken", _expected_redirect=expected_redirect_location, ) with client_request.session_transaction() as session: - assert session.get('invited_user_id') == sample_invite['id'] + assert session.get("invited_user_id") == sample_invite["id"] - data = {'service': sample_invite['service'], - 'email_address': sample_invite['email_address'], - 'from_user': sample_invite['from_user'], - 'password': 'longpassword', - 'mobile_number': '+12027890123', - 'name': 'Invited User', - 'auth_type': 'email_auth' - } + data = { + "service": sample_invite["service"], + "email_address": sample_invite["email_address"], + "from_user": sample_invite["from_user"], + "password": "longpassword", + "mobile_number": "+12027890123", + "name": "Invited User", + "auth_type": "email_auth", + } - expected_redirect_location = '/verify' + expected_redirect_location = "/verify" client_request.post( - 'main.register_from_invite', + "main.register_from_invite", _data=data, _expected_redirect=expected_redirect_location, ) - mock_send_verify_code.assert_called_once_with(ANY, 'sms', data['mobile_number']) - mock_get_invited_user_by_id.assert_called_once_with(sample_invite['id']) + mock_send_verify_code.assert_called_once_with(ANY, "sms", data["mobile_number"]) + mock_get_invited_user_by_id.assert_called_once_with(sample_invite["id"]) - mock_register_user.assert_called_with(data['name'], - data['email_address'], - data['mobile_number'], - data['password'], - data['auth_type']) + mock_register_user.assert_called_with( + data["name"], + data["email_address"], + data["mobile_number"], + data["password"], + data["auth_type"], + ) assert mock_accept_invite.call_count == 1 @@ -499,16 +539,18 @@ def test_signed_in_existing_user_cannot_use_anothers_invite( mock_accept_invite, mock_get_service, ): - mocker.patch('app.user_api_client.get_users_for_service', return_value=[api_user_active]) + mocker.patch( + "app.user_api_client.get_users_for_service", return_value=[api_user_active] + ) page = client_request.get( - 'main.accept_invite', - token='thisisnotarealtoken', + "main.accept_invite", + token="thisisnotarealtoken", _follow_redirects=True, _expected_status=403, ) - assert page.h1.string.strip() == 'You’re not allowed to see this page' - flash_banners = page.find_all('div', class_='banner-dangerous') + assert page.h1.string.strip() == "You’re not allowed to see this page" + flash_banners = page.find_all("div", class_="banner-dangerous") assert len(flash_banners) == 1 banner_contents = normalize_spaces(flash_banners[0].text) assert "You’re signed in as test@user.gsa.gov." in banner_contents @@ -524,20 +566,20 @@ def test_accept_invite_does_not_treat_email_addresses_as_case_sensitive( sample_invite, mock_accept_invite, mock_check_invite_token, - mock_get_user_by_email + mock_get_user_by_email, ): # the email address of api_user_active is 'test@user.gsa.gov' - sample_invite['email_address'] = 'TEST@user.gsa.gov' - mocker.patch('app.models.user.Users.client_method', return_value=[api_user_active]) + sample_invite["email_address"] = "TEST@user.gsa.gov" + mocker.patch("app.models.user.Users.client_method", return_value=[api_user_active]) client_request.get( - 'main.accept_invite', - token='thisisnotarealtoken', + "main.accept_invite", + token="thisisnotarealtoken", _expected_status=302, _expected_redirect=url_for( - 'main.service_dashboard', + "main.service_dashboard", service_id=SERVICE_ONE_ID, - ) + ), ) @@ -573,53 +615,63 @@ def test_new_invited_user_verifies_and_added_to_service( # visit accept token page client_request.get( - 'main.accept_invite', - token='thisisnotarealtoken', - _expected_redirect=url_for('main.register_from_invite'), + "main.accept_invite", + token="thisisnotarealtoken", + _expected_redirect=url_for("main.register_from_invite"), ) # get redirected to register from invite data = { - 'service': sample_invite['service'], - 'email_address': sample_invite['email_address'], - 'from_user': sample_invite['from_user'], - 'password': 'longpassword', - 'mobile_number': '+12027890123', - 'name': 'Invited User', - 'auth_type': 'sms_auth' + "service": sample_invite["service"], + "email_address": sample_invite["email_address"], + "from_user": sample_invite["from_user"], + "password": "longpassword", + "mobile_number": "+12027890123", + "name": "Invited User", + "auth_type": "sms_auth", } client_request.post( - 'main.register_from_invite', + "main.register_from_invite", _data=data, - _expected_redirect=url_for('main.verify'), + _expected_redirect=url_for("main.verify"), ) # that sends user on to verify page = client_request.post( - 'main.verify', - _data={'sms_code': '123456'}, + "main.verify", + _data={"sms_code": "123456"}, _follow_redirects=True, ) # when they post codes back to admin user should be added to # service and sent on to dash board - expected_permissions = {'view_activity', 'send_messages', 'manage_service', 'manage_api_keys'} + expected_permissions = { + "view_activity", + "send_messages", + "manage_service", + "manage_api_keys", + } with client_request.session_transaction() as session: - assert 'invited_user_id' not in session - new_user_id = session['user_id'] - mock_add_user_to_service.assert_called_with(data['service'], new_user_id, expected_permissions, []) - mock_accept_invite.assert_called_with(data['service'], sample_invite['id']) - mock_check_verify_code.assert_called_once_with(new_user_id, '123456', 'sms') - assert service_one['id'] == session['service_id'] + assert "invited_user_id" not in session + new_user_id = session["user_id"] + mock_add_user_to_service.assert_called_with( + data["service"], new_user_id, expected_permissions, [] + ) + mock_accept_invite.assert_called_with(data["service"], sample_invite["id"]) + mock_check_verify_code.assert_called_once_with(new_user_id, "123456", "sms") + assert service_one["id"] == session["service_id"] - assert page.find('h1').text == 'Dashboard' + assert page.find("h1").text == "Dashboard" -@pytest.mark.parametrize('service_permissions, trial_mode, expected_endpoint, extra_args', ( - ([], True, 'main.service_dashboard', {}), - ([], False, 'main.service_dashboard', {}), -)) +@pytest.mark.parametrize( + "service_permissions, trial_mode, expected_endpoint, extra_args", + ( + ([], True, "main.service_dashboard", {}), + ([], False, "main.service_dashboard", {}), + ), +) def test_new_invited_user_is_redirected_to_correct_place( mocker, client_request, @@ -638,37 +690,38 @@ def test_new_invited_user_is_redirected_to_correct_place( extra_args, ): client_request.logout() - mocker.patch('app.service_api_client.get_service', return_value={ - 'data': service_json( - sample_invite['service'], - restricted=trial_mode, - permissions=service_permissions, - ) - }) + mocker.patch( + "app.service_api_client.get_service", + return_value={ + "data": service_json( + sample_invite["service"], + restricted=trial_mode, + permissions=service_permissions, + ) + }, + ) client_request.get( - 'main.accept_invite', - token='thisisnotarealtoken', + "main.accept_invite", + token="thisisnotarealtoken", _expected_status=302, ) with client_request.session_transaction() as session: - session['user_details'] = { - 'email': sample_invite['email_address'], - 'id': sample_invite['id'], + session["user_details"] = { + "email": sample_invite["email_address"], + "id": sample_invite["id"], } client_request.post( - 'main.verify', - _data={'sms_code': '123456'}, + "main.verify", + _data={"sms_code": "123456"}, _expected_redirect=url_for( - expected_endpoint, - service_id=sample_invite['service'], - **extra_args - ) + expected_endpoint, service_id=sample_invite["service"], **extra_args + ), ) -@freeze_time('2021-12-12 12:12:12') +@freeze_time("2021-12-12 12:12:12") def test_existing_user_accepts_and_sets_email_auth( client_request, api_user_active, @@ -680,29 +733,33 @@ def test_existing_user_accepts_and_sets_email_auth( mock_check_invite_token, mock_update_user_attribute, mock_add_user_to_service, - mocker + mocker, ): - sample_invite['email_address'] = api_user_active['email_address'] + sample_invite["email_address"] = api_user_active["email_address"] - service_one['permissions'].append('email_auth') - sample_invite['auth_type'] = 'email_auth' + service_one["permissions"].append("email_auth") + sample_invite["auth_type"] = "email_auth" client_request.get( - 'main.accept_invite', - token='thisisnotarealtoken', + "main.accept_invite", + token="thisisnotarealtoken", _expected_status=302, - _expected_redirect=url_for('main.service_dashboard', service_id=service_one['id']), + _expected_redirect=url_for( + "main.service_dashboard", service_id=service_one["id"] + ), ) - mock_get_existing_user_by_email.assert_called_once_with('test@user.gsa.gov') + mock_get_existing_user_by_email.assert_called_once_with("test@user.gsa.gov") assert mock_update_user_attribute.call_args_list == [ - call(api_user_active['id'], email_access_validated_at='2021-12-12T12:12:12'), - call(api_user_active['id'], auth_type='email_auth'), + call(api_user_active["id"], email_access_validated_at="2021-12-12T12:12:12"), + call(api_user_active["id"], auth_type="email_auth"), ] - mock_add_user_to_service.assert_called_once_with(ANY, api_user_active['id'], ANY, ANY) + mock_add_user_to_service.assert_called_once_with( + ANY, api_user_active["id"], ANY, ANY + ) -@freeze_time('2021-12-12 12:12:12') +@freeze_time("2021-12-12 12:12:12") def test_platform_admin_user_accepts_and_preserves_auth( client_request, platform_admin_user, @@ -712,35 +769,39 @@ def test_platform_admin_user_accepts_and_preserves_auth( mock_no_users_for_service, mock_accept_invite, mock_add_user_to_service, - mocker + mocker, ): - sample_invite['email_address'] = platform_admin_user['email_address'] - sample_invite['auth_type'] = 'email_auth' - service_one['permissions'].append('email_auth') + sample_invite["email_address"] = platform_admin_user["email_address"] + sample_invite["auth_type"] = "email_auth" + service_one["permissions"].append("email_auth") - mocker.patch('app.user_api_client.get_user_by_email', return_value=platform_admin_user) + mocker.patch( + "app.user_api_client.get_user_by_email", return_value=platform_admin_user + ) mock_update_user_attribute = mocker.patch( - 'app.user_api_client.update_user_attribute', + "app.user_api_client.update_user_attribute", return_value=platform_admin_user, ) client_request.login(platform_admin_user) client_request.get( - 'main.accept_invite', - token='thisisnotarealtoken', + "main.accept_invite", + token="thisisnotarealtoken", _expected_status=302, - _expected_redirect=url_for('main.service_dashboard', service_id=service_one['id']), + _expected_redirect=url_for( + "main.service_dashboard", service_id=service_one["id"] + ), ) mock_update_user_attribute.assert_called_once_with( - platform_admin_user['id'], - email_access_validated_at='2021-12-12T12:12:12', + platform_admin_user["id"], + email_access_validated_at="2021-12-12T12:12:12", ) assert mock_add_user_to_service.called -@freeze_time('2021-12-12 12:12:12') +@freeze_time("2021-12-12 12:12:12") def test_existing_user_doesnt_get_auth_changed_by_service_without_permission( client_request, api_user_active, @@ -752,28 +813,30 @@ def test_existing_user_doesnt_get_auth_changed_by_service_without_permission( mock_accept_invite, mock_update_user_attribute, mock_add_user_to_service, - mocker + mocker, ): - sample_invite['email_address'] = api_user_active['email_address'] + sample_invite["email_address"] = api_user_active["email_address"] - assert 'email_auth' not in service_one['permissions'] + assert "email_auth" not in service_one["permissions"] - sample_invite['auth_type'] = 'email_auth' + sample_invite["auth_type"] = "email_auth" client_request.get( - 'main.accept_invite', - token='thisisnotarealtoken', + "main.accept_invite", + token="thisisnotarealtoken", _expected_status=302, - _expected_redirect=url_for('main.service_dashboard', service_id=service_one['id']), + _expected_redirect=url_for( + "main.service_dashboard", service_id=service_one["id"] + ), ) mock_update_user_attribute.assert_called_once_with( - api_user_active['id'], - email_access_validated_at='2021-12-12T12:12:12', + api_user_active["id"], + email_access_validated_at="2021-12-12T12:12:12", ) -@freeze_time('2021-12-12 12:12:12') +@freeze_time("2021-12-12 12:12:12") def test_existing_email_auth_user_without_phone_cannot_set_sms_auth( client_request, api_user_active, @@ -784,32 +847,34 @@ def test_existing_email_auth_user_without_phone_cannot_set_sms_auth( mock_accept_invite, mock_update_user_attribute, mock_add_user_to_service, - mocker + mocker, ): - sample_invite['email_address'] = api_user_active['email_address'] + sample_invite["email_address"] = api_user_active["email_address"] - service_one['permissions'].append('email_auth') + service_one["permissions"].append("email_auth") - api_user_active['auth_type'] = 'email_auth' - api_user_active['mobile_number'] = None - sample_invite['auth_type'] = 'sms_auth' + api_user_active["auth_type"] = "email_auth" + api_user_active["mobile_number"] = None + sample_invite["auth_type"] = "sms_auth" - mocker.patch('app.user_api_client.get_user_by_email', return_value=api_user_active) + mocker.patch("app.user_api_client.get_user_by_email", return_value=api_user_active) client_request.get( - 'main.accept_invite', - token='thisisnotarealtoken', + "main.accept_invite", + token="thisisnotarealtoken", _expected_status=302, - _expected_redirect=url_for('main.service_dashboard', service_id=service_one['id']), + _expected_redirect=url_for( + "main.service_dashboard", service_id=service_one["id"] + ), ) mock_update_user_attribute.assert_called_once_with( - api_user_active['id'], - email_access_validated_at='2021-12-12T12:12:12', + api_user_active["id"], + email_access_validated_at="2021-12-12T12:12:12", ) -@freeze_time('2021-12-12 12:12:12') +@freeze_time("2021-12-12 12:12:12") def test_existing_email_auth_user_with_phone_can_set_sms_auth( client_request, api_user_active, @@ -821,21 +886,25 @@ def test_existing_email_auth_user_with_phone_can_set_sms_auth( mock_accept_invite, mock_update_user_attribute, mock_add_user_to_service, - mocker + mocker, ): - sample_invite['email_address'] = api_user_active['email_address'] - service_one['permissions'].append('email_auth') - sample_invite['auth_type'] = 'sms_auth' + sample_invite["email_address"] = api_user_active["email_address"] + service_one["permissions"].append("email_auth") + sample_invite["auth_type"] = "sms_auth" client_request.get( - 'main.accept_invite', - token='thisisnotarealtoken', + "main.accept_invite", + token="thisisnotarealtoken", _expected_status=302, - _expected_redirect=url_for('main.service_dashboard', service_id=service_one['id']), + _expected_redirect=url_for( + "main.service_dashboard", service_id=service_one["id"] + ), ) - mock_get_existing_user_by_email.assert_called_once_with(sample_invite['email_address']) + mock_get_existing_user_by_email.assert_called_once_with( + sample_invite["email_address"] + ) assert mock_update_user_attribute.call_args_list == [ - call(api_user_active['id'], email_access_validated_at='2021-12-12T12:12:12'), - call(api_user_active['id'], auth_type='sms_auth'), + call(api_user_active["id"], email_access_validated_at="2021-12-12T12:12:12"), + call(api_user_active["id"], auth_type="sms_auth"), ] diff --git a/tests/app/main/views/test_activity.py b/tests/app/main/views/test_activity.py index 94dac0593..b6acde247 100644 --- a/tests/app/main/views/test_activity.py +++ b/tests/app/main/views/test_activity.py @@ -20,72 +20,75 @@ from tests.conftest import ( @pytest.mark.parametrize( - "user,extra_args,expected_update_endpoint,expected_limit_days,page_title", [ + "user,extra_args,expected_update_endpoint,expected_limit_days,page_title", + [ ( create_active_user_view_permissions(), - {'message_type': 'email'}, - '/email.json', + {"message_type": "email"}, + "/email.json", 7, - 'Emails', + "Emails", ), ( create_active_user_view_permissions(), - {'message_type': 'sms'}, - '/sms.json', + {"message_type": "sms"}, + "/sms.json", 7, - 'Text messages', + "Text messages", ), ( create_active_caseworking_user(), {}, - '.json', + ".json", None, - 'Sent messages', + "Sent messages", ), - ] + ], ) @pytest.mark.parametrize( - "status_argument, expected_api_call", [ + "status_argument, expected_api_call", + [ ( - '', + "", [ - 'created', 'pending', 'sending', 'delivered', 'sent', 'failed', - 'temporary-failure', 'permanent-failure', 'technical-failure', - 'validation-failed' - ] + "created", + "pending", + "sending", + "delivered", + "sent", + "failed", + "temporary-failure", + "permanent-failure", + "technical-failure", + "validation-failed", + ], ), + ("sending", ["sending", "created", "pending"]), + ("delivered", ["delivered", "sent"]), ( - 'sending', - ['sending', 'created', 'pending'] - ), - ( - 'delivered', - ['delivered', 'sent'] - ), - ( - 'failed', + "failed", [ - 'failed', 'temporary-failure', 'permanent-failure', 'technical-failure', - 'validation-failed' - ] - ) - ] + "failed", + "temporary-failure", + "permanent-failure", + "technical-failure", + "validation-failed", + ], + ), + ], ) @pytest.mark.parametrize( - "page_argument, expected_page_argument", [ - (1, 1), - (22, 22), - (None, 1) - ] + "page_argument, expected_page_argument", [(1, 1), (22, 22), (None, 1)] ) @pytest.mark.parametrize( - "to_argument, expected_to_argument", [ - ('', ''), - ('+12029000123', '+12029000123'), - ('test@example.com', 'test@example.com'), - ] + "to_argument, expected_to_argument", + [ + ("", ""), + ("+12029000123", "+12029000123"), + ("test@example.com", "test@example.com"), + ], ) -@freeze_time('2020-01-01 06:00') +@freeze_time("2020-01-01 06:00") def test_can_show_notifications( client_request, service_one, @@ -110,67 +113,63 @@ def test_can_show_notifications( client_request.login(user) if expected_to_argument: page = client_request.post( - 'main.view_notifications', + "main.view_notifications", service_id=SERVICE_ONE_ID, status=status_argument, page=page_argument, - _data={ - 'to': to_argument - }, + _data={"to": to_argument}, _expected_status=200, **extra_args ) else: page = client_request.get( - 'main.view_notifications', + "main.view_notifications", service_id=SERVICE_ONE_ID, status=status_argument, page=page_argument, **extra_args ) - first_row = page.select_one('tbody tr') + first_row = page.select_one("tbody tr") assert normalize_spaces( - first_row.select_one('a.file-list-filename.usa-link').text + first_row.select_one("a.file-list-filename.usa-link").text ) == ( # Comes from # https://github.com/alphagov/notifications-admin/blob/8faffad508f9a087b0006989c197741c693cc2e2/tests/__init__.py#L436 - '2021234567' + "2021234567" ) assert normalize_spaces( # We’re doing str() here not .text to make sure there’s no extra # HTML sneaking in - str(first_row.select_one('.file-list-hint')) + str(first_row.select_one(".file-list-hint")) ) == ( # Comes from # https://github.com/alphagov/notifications-admin/blob/8faffad508f9a087b0006989c197741c693cc2e2/tests/__init__.py#L271 - 'template content' + "template content" ) or ( # Comes from # https://github.com/alphagov/notifications-admin/blob/8faffad508f9a087b0006989c197741c693cc2e2/tests/__init__.py#L273 - 'template subject' + "template subject" ) assert normalize_spaces( - first_row.select_one('.table-field-right-aligned .align-with-message-body').text - ) == ( - 'Delivered 1 January at 06:01 UTC' - ) + first_row.select_one(".table-field-right-aligned .align-with-message-body").text + ) == ("Delivered 1 January at 06:01 UTC") assert page_title in page.h1.text.strip() - path_to_json = page.find("div", {'data-key': 'notifications'})['data-resource'] + path_to_json = page.find("div", {"data-key": "notifications"})["data-resource"] url = urlparse(path_to_json) - assert url.path == '/services/{}/notifications{}'.format( + assert url.path == "/services/{}/notifications{}".format( SERVICE_ONE_ID, expected_update_endpoint, ) query_dict = parse_qs(url.query) if status_argument: - assert query_dict['status'] == [status_argument] + assert query_dict["status"] == [status_argument] if expected_page_argument: - assert query_dict['page'] == [str(expected_page_argument)] - assert 'to' not in query_dict + assert query_dict["page"] == [str(expected_page_argument)] + assert "to" not in query_dict mock_get_notifications.assert_called_with( limit_days=expected_limit_days, @@ -182,13 +181,17 @@ def test_can_show_notifications( ) json_response = client_request.get_response( - 'main.get_notifications_as_json', - service_id=service_one['id'], + "main.get_notifications_as_json", + service_id=service_one["id"], status=status_argument, **extra_args ) json_content = json.loads(json_response.get_data(as_text=True)) - assert json_content.keys() == {'counts', 'notifications', 'service_data_retention_days'} + assert json_content.keys() == { + "counts", + "notifications", + "service_data_retention_days", + } def test_can_show_notifications_if_data_retention_not_available( @@ -199,55 +202,54 @@ def test_can_show_notifications_if_data_retention_not_available( mock_get_no_api_keys, ): page = client_request.get( - 'main.view_notifications', + "main.view_notifications", service_id=SERVICE_ONE_ID, - status='sending,delivered,failed', + status="sending,delivered,failed", ) - assert page.h1.text.strip() == 'Messages' + assert page.h1.text.strip() == "Messages" -@pytest.mark.parametrize('user, query_parameters, expected_download_link', [ - ( - create_active_user_with_permissions(), - {}, - partial( - url_for, - '.download_notifications_csv', - message_type=None, +@pytest.mark.parametrize( + "user, query_parameters, expected_download_link", + [ + ( + create_active_user_with_permissions(), + {}, + partial( + url_for, + ".download_notifications_csv", + message_type=None, + ), ), - ), - ( - create_active_user_with_permissions(), - {'status': 'failed'}, - partial( - url_for, - '.download_notifications_csv', - status='failed' + ( + create_active_user_with_permissions(), + {"status": "failed"}, + partial(url_for, ".download_notifications_csv", status="failed"), ), - ), - ( - create_active_user_with_permissions(), - {'message_type': 'sms'}, - partial( - url_for, - '.download_notifications_csv', - message_type='sms', + ( + create_active_user_with_permissions(), + {"message_type": "sms"}, + partial( + url_for, + ".download_notifications_csv", + message_type="sms", + ), ), - ), - ( - create_active_user_view_permissions(), - {}, - partial( - url_for, - '.download_notifications_csv', + ( + create_active_user_view_permissions(), + {}, + partial( + url_for, + ".download_notifications_csv", + ), ), - ), - ( - create_active_caseworking_user(), - {}, - lambda service_id: None, - ), -]) + ( + create_active_caseworking_user(), + {}, + lambda service_id: None, + ), + ], +) def test_link_to_download_notifications( client_request, mock_get_notifications, @@ -261,14 +263,12 @@ def test_link_to_download_notifications( ): client_request.login(user) page = client_request.get( - 'main.view_notifications', - service_id=SERVICE_ONE_ID, - **query_parameters + "main.view_notifications", service_id=SERVICE_ONE_ID, **query_parameters + ) + download_link = page.select_one("a[download=download]") + assert (download_link["href"] if download_link else None) == expected_download_link( + service_id=SERVICE_ONE_ID ) - download_link = page.select_one('a[download=download]') - assert ( - download_link['href'] if download_link else None - ) == expected_download_link(service_id=SERVICE_ONE_ID) def test_download_not_available_to_users_without_dashboard( @@ -277,7 +277,7 @@ def test_download_not_available_to_users_without_dashboard( ): client_request.login(active_caseworking_user) client_request.get( - 'main.download_notifications_csv', + "main.download_notifications_csv", service_id=SERVICE_ONE_ID, _expected_status=403, ) @@ -290,61 +290,63 @@ def test_shows_message_when_no_notifications( mock_get_notifications_with_no_notifications, mock_get_no_api_keys, ): - page = client_request.get( - 'main.view_notifications', + "main.view_notifications", service_id=SERVICE_ONE_ID, - message_type='sms', + message_type="sms", ) - assert normalize_spaces(page.select('tbody tr')[0].text) == ( - 'No messages found (messages are kept for 7 days)' + assert normalize_spaces(page.select("tbody tr")[0].text) == ( + "No messages found (messages are kept for 7 days)" ) -@pytest.mark.parametrize(( - 'initial_query_arguments,' - 'form_post_data,' - 'expected_search_box_label,' - 'expected_search_box_contents' -), [ +@pytest.mark.parametrize( ( - {}, - {}, - 'Search by recipient', - None, + "initial_query_arguments," + "form_post_data," + "expected_search_box_label," + "expected_search_box_contents" ), - ( - { - 'message_type': 'sms', - }, - {}, - 'Search by phone number', - None, - ), - ( - { - 'message_type': 'sms', - }, - { - 'to': '+33(0)5-12-34-56-78', - }, - 'Search by phone number', - '+33(0)5-12-34-56-78', - ), - ( - { - 'status': 'failed', - 'message_type': 'email', - 'page': '99', - }, - { - 'to': 'test@example.com', - }, - 'Search by email address', - 'test@example.com', - ), -]) + [ + ( + {}, + {}, + "Search by recipient", + None, + ), + ( + { + "message_type": "sms", + }, + {}, + "Search by phone number", + None, + ), + ( + { + "message_type": "sms", + }, + { + "to": "+33(0)5-12-34-56-78", + }, + "Search by phone number", + "+33(0)5-12-34-56-78", + ), + ( + { + "status": "failed", + "message_type": "email", + "page": "99", + }, + { + "to": "test@example.com", + }, + "Search by email address", + "test@example.com", + ), + ], +) def test_search_recipient_form( client_request, mock_get_notifications, @@ -357,37 +359,39 @@ def test_search_recipient_form( expected_search_box_contents, ): page = client_request.post( - 'main.view_notifications', + "main.view_notifications", service_id=SERVICE_ONE_ID, _data=form_post_data, _expected_status=200, **initial_query_arguments ) - assert page.find("form")['method'] == 'post' - action_url = page.find("form")['action'] + assert page.find("form")["method"] == "post" + action_url = page.find("form")["action"] url = urlparse(action_url) - assert url.path == '/services/{}/notifications/{}'.format( - SERVICE_ONE_ID, - initial_query_arguments.get('message_type', '') - ).rstrip('/') + assert url.path == "/services/{}/notifications/{}".format( + SERVICE_ONE_ID, initial_query_arguments.get("message_type", "") + ).rstrip("/") query_dict = parse_qs(url.query) assert query_dict == {} - assert page.select_one('label[for=to]').text.strip() == expected_search_box_label + assert page.select_one("label[for=to]").text.strip() == expected_search_box_label recipient_inputs = page.select("input[name=to]") - assert (len(recipient_inputs) == 2) + assert len(recipient_inputs) == 2 for field in recipient_inputs: assert field.get("value") == expected_search_box_contents -@pytest.mark.parametrize('message_type, expected_search_box_label', [ - (None, 'Search by recipient or reference'), - ('sms', 'Search by phone number or reference'), - ('email', 'Search by email address or reference'), -]) +@pytest.mark.parametrize( + "message_type, expected_search_box_label", + [ + (None, "Search by recipient or reference"), + ("sms", "Search by phone number or reference"), + ("email", "Search by email address or reference"), + ], +) def test_api_users_are_told_they_can_search_by_reference_when_service_has_api_keys( client_request, mocker, @@ -400,18 +404,21 @@ def test_api_users_are_told_they_can_search_by_reference_when_service_has_api_ke mock_get_api_keys, ): page = client_request.get( - 'main.view_notifications', + "main.view_notifications", service_id=SERVICE_ONE_ID, message_type=message_type, ) - assert page.select_one('label[for=to]').text.strip() == expected_search_box_label + assert page.select_one("label[for=to]").text.strip() == expected_search_box_label -@pytest.mark.parametrize('message_type, expected_search_box_label', [ - (None, 'Search by recipient'), - ('sms', 'Search by phone number'), - ('email', 'Search by email address'), -]) +@pytest.mark.parametrize( + "message_type, expected_search_box_label", + [ + (None, "Search by recipient"), + ("sms", "Search by phone number"), + ("email", "Search by email address"), + ], +) def test_api_users_are_not_told_they_can_search_by_reference_when_service_has_no_api_keys( client_request, mocker, @@ -424,11 +431,11 @@ def test_api_users_are_not_told_they_can_search_by_reference_when_service_has_no mock_get_no_api_keys, ): page = client_request.get( - 'main.view_notifications', + "main.view_notifications", service_id=SERVICE_ONE_ID, message_type=message_type, ) - assert page.select_one('label[for=to]').text.strip() == expected_search_box_label + assert page.select_one("label[for=to]").text.strip() == expected_search_box_label def test_should_show_notifications_for_a_service_with_next_previous( @@ -442,26 +449,36 @@ def test_should_show_notifications_for_a_service_with_next_previous( mocker, ): page = client_request.get( - 'main.view_notifications', - service_id=service_one['id'], - message_type='sms', - page=2 + "main.view_notifications", + service_id=service_one["id"], + message_type="sms", + page=2, ) - next_page_link = page.find('a', {'rel': 'next'}) - prev_page_link = page.find('a', {'rel': 'previous'}) + next_page_link = page.find("a", {"rel": "next"}) + prev_page_link = page.find("a", {"rel": "previous"}) assert ( - url_for('main.view_notifications', service_id=service_one['id'], message_type='sms', page=3) in - next_page_link['href'] + url_for( + "main.view_notifications", + service_id=service_one["id"], + message_type="sms", + page=3, + ) + in next_page_link["href"] ) - assert 'Next page' in next_page_link.text.strip() - assert 'page 3' in next_page_link.text.strip() + assert "Next page" in next_page_link.text.strip() + assert "page 3" in next_page_link.text.strip() assert ( - url_for('main.view_notifications', service_id=service_one['id'], message_type='sms', page=1) in - prev_page_link['href'] + url_for( + "main.view_notifications", + service_id=service_one["id"], + message_type="sms", + page=1, + ) + in prev_page_link["href"] ) - assert 'Previous page' in prev_page_link.text.strip() - assert 'page 1' in prev_page_link.text.strip() + assert "Previous page" in prev_page_link.text.strip() + assert "page 1" in prev_page_link.text.strip() def test_doesnt_show_pagination_with_search_term( @@ -475,70 +492,68 @@ def test_doesnt_show_pagination_with_search_term( mocker, ): page = client_request.post( - 'main.view_notifications', - service_id=service_one['id'], - message_type='sms', + "main.view_notifications", + service_id=service_one["id"], + message_type="sms", _data={ - 'to': 'test@example.com', + "to": "test@example.com", }, _expected_status=200, ) - assert len(page.select('tbody tr')) == 50 - assert not page.find('a', {'rel': 'next'}) - assert not page.find('a', {'rel': 'previous'}) - assert normalize_spaces( - page.select_one('.table-show-more-link').text - ) == ( - 'Only showing the first 50 messages' + assert len(page.select("tbody tr")) == 50 + assert not page.find("a", {"rel": "next"}) + assert not page.find("a", {"rel": "previous"}) + assert normalize_spaces(page.select_one(".table-show-more-link").text) == ( + "Only showing the first 50 messages" ) @pytest.mark.parametrize( - "job_created_at, expected_message", [ + "job_created_at, expected_message", + [ ("2016-01-10 11:09:00.000000+00:00", "Data available for 7 days"), ("2016-01-04 11:09:00.000000+00:00", "Data available for 1 day"), ("2016-01-03 11:09:00.000000+00:00", "Data available for 12 hours"), - ("2016-01-02 23:59:59.000000+00:00", "Data no longer available") - ] + ("2016-01-02 23:59:59.000000+00:00", "Data no longer available"), + ], ) @freeze_time("2016-01-10 12:00:00.000000") def test_time_left(job_created_at, expected_message): assert get_time_left(job_created_at) == expected_message -STATISTICS = { - 'sms': { - 'requested': 6, - 'failed': 2, - 'delivered': 1 - } -} +STATISTICS = {"sms": {"requested": 6, "failed": 2, "delivered": 1}} def test_get_status_filters_calculates_stats(client_request): - ret = get_status_filters(Service({'id': 'foo'}), 'sms', STATISTICS) + ret = get_status_filters(Service({"id": "foo"}), "sms", STATISTICS) assert {label: count for label, _option, _link, count in ret} == { - 'total': 6, - 'pending': 3, - 'failed': 2, - 'delivered': 1 + "total": 6, + "pending": 3, + "failed": 2, + "delivered": 1, } def test_get_status_filters_in_right_order(client_request): - ret = get_status_filters(Service({'id': 'foo'}), 'sms', STATISTICS) + ret = get_status_filters(Service({"id": "foo"}), "sms", STATISTICS) assert [label for label, _option, _link, _count in ret] == [ - 'total', 'pending', 'delivered', 'failed' + "total", + "pending", + "delivered", + "failed", ] def test_get_status_filters_constructs_links(client_request): - ret = get_status_filters(Service({'id': 'foo'}), 'sms', STATISTICS) + ret = get_status_filters(Service({"id": "foo"}), "sms", STATISTICS) link = ret[0][2] - assert link == '/services/foo/notifications/sms?status={}'.format('sending,delivered,failed') + assert link == "/services/foo/notifications/sms?status={}".format( + "sending,delivered,failed" + ) def test_html_contains_notification_id( @@ -552,15 +567,15 @@ def test_html_contains_notification_id( mocker, ): page = client_request.get( - 'main.view_notifications', - service_id=service_one['id'], - message_type='sms', - status='', + "main.view_notifications", + service_id=service_one["id"], + message_type="sms", + status="", ) - notifications = page.tbody.find_all('tr') + notifications = page.tbody.find_all("tr") for tr in notifications: - assert uuid.UUID(tr.attrs['id']) + assert uuid.UUID(tr.attrs["id"]) def test_html_contains_links_for_failed_notifications( @@ -570,29 +585,31 @@ def test_html_contains_links_for_failed_notifications( mock_get_no_api_keys, mocker, ): - notifications = create_notifications(status='technical-failure') - mocker.patch('app.notification_api_client.get_notifications_for_service', return_value=notifications) + notifications = create_notifications(status="technical-failure") + mocker.patch( + "app.notification_api_client.get_notifications_for_service", + return_value=notifications, + ) response = client_request.get( - 'main.view_notifications', + "main.view_notifications", service_id=SERVICE_ONE_ID, - message_type='sms', - status='sending%2Cdelivered%2Cfailed' + message_type="sms", + status="sending%2Cdelivered%2Cfailed", ) - notifications = response.tbody.find_all('tr') + notifications = response.tbody.find_all("tr") for tr in notifications: - link_text = tr.find('div', class_='table-field-status-error').find('a').text - assert normalize_spaces(link_text) == 'Technical failure' + link_text = tr.find("div", class_="table-field-status-error").find("a").text + assert normalize_spaces(link_text) == "Technical failure" -@pytest.mark.parametrize('notification_type, expected_row_contents', ( - ('sms', ( - '2021234567 hello & welcome hidden' - )), - ('email', ( - 'example@gsa.gov hidden, hello & welcome' - )), -)) +@pytest.mark.parametrize( + "notification_type, expected_row_contents", + ( + ("sms", ("2021234567 hello & welcome hidden")), + ("email", ("example@gsa.gov hidden, hello & welcome")), + ), +) def test_redacts_templates_that_should_be_redacted( client_request, mocker, @@ -603,40 +620,59 @@ def test_redacts_templates_that_should_be_redacted( expected_row_contents, ): notifications = create_notifications( - status='technical-failure', - content='hello & welcome ((name))', - subject='((name)), hello & welcome', - personalisation={'name': 'Jo'}, + status="technical-failure", + content="hello & welcome ((name))", + subject="((name)), hello & welcome", + personalisation={"name": "Jo"}, redact_personalisation=True, template_type=notification_type, ) - mocker.patch('app.notification_api_client.get_notifications_for_service', return_value=notifications) + mocker.patch( + "app.notification_api_client.get_notifications_for_service", + return_value=notifications, + ) page = client_request.get( - 'main.view_notifications', + "main.view_notifications", service_id=SERVICE_ONE_ID, message_type=notification_type, ) - assert normalize_spaces(page.select('tbody tr th')[0].text) == ( + assert normalize_spaces(page.select("tbody tr th")[0].text) == ( expected_row_contents ) @freeze_time("2017-09-27 12:30:00.000000") @pytest.mark.parametrize( - "message_type, status, expected_hint_status, single_line", [ - ('email', 'created', 'Sending since 27 September at 12:30 UTC', True), - ('email', 'sending', 'Sending since 27 September at 12:30 UTC', True), - ('email', 'temporary-failure', 'Inbox not accepting messages right now 27 September at 12:31 UTC', False), - ('email', 'permanent-failure', 'Email address does not exist 27 September at 12:31 UTC', False), - ('email', 'delivered', 'Delivered 27 September at 12:31 UTC', True), - ('sms', 'created', 'Sending since 27 September at 12:30 UTC', True), - ('sms', 'sending', 'Sending since 27 September at 12:30 UTC', True), - ('sms', 'temporary-failure', 'Phone not accepting messages right now 27 September at 12:31 UTC', False), - ('sms', 'permanent-failure', 'Not delivered 27 September at 12:31 UTC', False), - ('sms', 'delivered', 'Delivered 27 September at 12:31 UTC', True), - ] + "message_type, status, expected_hint_status, single_line", + [ + ("email", "created", "Sending since 27 September at 12:30 UTC", True), + ("email", "sending", "Sending since 27 September at 12:30 UTC", True), + ( + "email", + "temporary-failure", + "Inbox not accepting messages right now 27 September at 12:31 UTC", + False, + ), + ( + "email", + "permanent-failure", + "Email address does not exist 27 September at 12:31 UTC", + False, + ), + ("email", "delivered", "Delivered 27 September at 12:31 UTC", True), + ("sms", "created", "Sending since 27 September at 12:30 UTC", True), + ("sms", "sending", "Sending since 27 September at 12:30 UTC", True), + ( + "sms", + "temporary-failure", + "Phone not accepting messages right now 27 September at 12:31 UTC", + False, + ), + ("sms", "permanent-failure", "Not delivered 27 September at 12:31 UTC", False), + ("sms", "delivered", "Delivered 27 September at 12:31 UTC", True), + ], ) def test_sending_status_hint_displays_correctly_on_notifications_page( client_request, @@ -648,16 +684,22 @@ def test_sending_status_hint_displays_correctly_on_notifications_page( status, expected_hint_status, single_line, - mocker + mocker, ): notifications = create_notifications(template_type=message_type, status=status) - mocker.patch('app.notification_api_client.get_notifications_for_service', return_value=notifications) - - page = client_request.get( - 'main.view_notifications', - service_id=service_one['id'], - message_type=message_type + mocker.patch( + "app.notification_api_client.get_notifications_for_service", + return_value=notifications, ) - assert normalize_spaces(page.select(".table-field-right-aligned")[0].text) == expected_hint_status - assert bool(page.select('.align-with-message-body')) is single_line + page = client_request.get( + "main.view_notifications", + service_id=service_one["id"], + message_type=message_type, + ) + + assert ( + normalize_spaces(page.select(".table-field-right-aligned")[0].text) + == expected_hint_status + ) + assert bool(page.select(".align-with-message-body")) is single_line diff --git a/tests/app/main/views/test_add_service.py b/tests/app/main/views/test_add_service.py index 25dcc0c80..c1b7bef62 100644 --- a/tests/app/main/views/test_add_service.py +++ b/tests/app/main/views/test_add_service.py @@ -17,39 +17,40 @@ def test_non_gov_user_cannot_see_add_service_button( mock_get_organizations_and_services_for_user, ): client_request.login(api_nongov_user_active) - page = client_request.get('main.choose_account') - assert 'Add a new service' not in page.text + page = client_request.get("main.choose_account") + assert "Add a new service" not in page.text -@pytest.mark.parametrize('org_json', ( - None, - organization_json(organization_type=None), -)) +@pytest.mark.parametrize( + "org_json", + ( + None, + organization_json(organization_type=None), + ), +) def test_get_should_render_add_service_template( client_request, mocker, org_json, ): mocker.patch( - 'app.organizations_client.get_organization_by_domain', + "app.organizations_client.get_organization_by_domain", return_value=org_json, ) - page = client_request.get('main.add_service') - assert page.select_one('h1').text.strip() == 'About your service' - assert page.select_one('input[name=name]').get('value') is None + page = client_request.get("main.add_service") + assert page.select_one("h1").text.strip() == "About your service" + assert page.select_one("input[name=name]").get("value") is None assert [ - label.text.strip() for label in page.select('.govuk-radios__item label') + label.text.strip() for label in page.select(".govuk-radios__item label") ] == [ - 'Federal government', - 'State government', - 'Other', + "Federal government", + "State government", + "Other", ] - assert [ - radio['value'] for radio in page.select('.govuk-radios__item input') - ] == [ - 'federal', - 'state', - 'other', + assert [radio["value"] for radio in page.select(".govuk-radios__item input")] == [ + "federal", + "state", + "other", ] @@ -58,13 +59,13 @@ def test_get_should_not_render_radios_if_org_type_known( mocker, ): mocker.patch( - 'app.organizations_client.get_organization_by_domain', - return_value=organization_json(organization_type='central'), + "app.organizations_client.get_organization_by_domain", + return_value=organization_json(organization_type="central"), ) - page = client_request.get('main.add_service') - assert page.select_one('h1').text.strip() == 'About your service' - assert page.select_one('input[name=name]').get('value') is None - assert not page.select('.multiple-choice') + page = client_request.get("main.add_service") + assert page.select_one("h1").text.strip() == "About your service" + assert page.select_one("input[name=name]").get("value") is None + assert not page.select(".multiple-choice") def test_show_different_page_if_user_org_type_is_local( @@ -72,28 +73,35 @@ def test_show_different_page_if_user_org_type_is_local( mocker, ): mocker.patch( - 'app.organizations_client.get_organization_by_domain', - return_value=organization_json(organization_type='local'), + "app.organizations_client.get_organization_by_domain", + return_value=organization_json(organization_type="local"), + ) + page = client_request.get("main.add_service") + assert page.select_one("h1").text.strip() == "About your service" + assert page.select_one("input[name=name]").get("value") is None + assert page.select_one("main .usa-body").text.strip() == ( + "Give your service a name that tells users what your " + "messages are about, as well as who they’re from. For example:" ) - page = client_request.get('main.add_service') - assert page.select_one('h1').text.strip() == 'About your service' - assert page.select_one('input[name=name]').get('value') is None - assert page.select_one('main .usa-body').text.strip() == ( - 'Give your service a name that tells users what your ' - 'messages are about, as well as who they’re from. For example:') -@pytest.mark.parametrize('email_address', ( - # User’s email address doesn’t matter when the organization is known - 'test@example.gsa.gov', - 'test@anotherexample.gsa.gov', -)) -@pytest.mark.parametrize('inherited, posted, persisted, sms_limit', ( - (None, 'federal', 'federal', 150_000), - # ('federal', None, 'federal', 150_000), - (None, 'state', 'state', 150_000), - # ('state', None, 'state', 150_000), -)) +@pytest.mark.parametrize( + "email_address", + ( + # User’s email address doesn’t matter when the organization is known + "test@example.gsa.gov", + "test@anotherexample.gsa.gov", + ), +) +@pytest.mark.parametrize( + "inherited, posted, persisted, sms_limit", + ( + (None, "federal", "federal", 150_000), + # ('federal', None, 'federal', 150_000), + (None, "state", "state", 150_000), + # ('state', None, 'state', 150_000), + ), +) @freeze_time("2021-01-01") def test_should_add_service_and_redirect_to_tour_when_no_services( mocker, @@ -109,37 +117,37 @@ def test_should_add_service_and_redirect_to_tour_when_no_services( persisted, sms_limit, ): - api_user_active['email_address'] = email_address + api_user_active["email_address"] = email_address client_request.login(api_user_active) mocker.patch( - 'app.organizations_client.get_organization_by_domain', + "app.organizations_client.get_organization_by_domain", return_value=organization_json(organization_type=inherited), ) client_request.post( - 'main.add_service', + "main.add_service", _data={ - 'name': 'testing the post', - 'organization_type': posted, + "name": "testing the post", + "organization_type": posted, }, _expected_status=302, _expected_redirect=url_for( - 'main.begin_tour', + "main.begin_tour", service_id=101, template_id="Example%20text%20message%20template", ), ) assert mock_get_services_with_no_services.called mock_create_service.assert_called_once_with( - service_name='testing the post', + service_name="testing the post", organization_type=persisted, message_limit=50, restricted=True, - user_id=api_user_active['id'], - email_from='testing.the.post', + user_id=api_user_active["id"], + email_from="testing.the.post", ) mock_create_service_template.assert_called_once_with( - 'Example text message template', - 'sms', + "Example text message template", + "sms", ( "Hi, I’m trying out Notify.gov. Today is " "((day of week)) and my favorite color is ((color))." @@ -147,7 +155,7 @@ def test_should_add_service_and_redirect_to_tour_when_no_services( 101, ) with client_request.session_transaction() as session: - assert session['service_id'] == 101 + assert session["service_id"] == 101 def test_add_service_has_to_choose_org_type( @@ -160,65 +168,69 @@ def test_add_service_has_to_choose_org_type( mock_get_all_email_branding, ): mocker.patch( - 'app.organizations_client.get_organization_by_domain', + "app.organizations_client.get_organization_by_domain", return_value=None, ) page = client_request.post( - 'main.add_service', + "main.add_service", _data={ - 'name': 'testing the post', + "name": "testing the post", }, _expected_status=200, ) - assert normalize_spaces(page.select_one('.usa-error-message').text) == ( - 'Error: Select the type of organization' + assert normalize_spaces(page.select_one(".usa-error-message").text) == ( + "Error: Select the type of organization" ) assert mock_create_service.called is False assert mock_create_service_template.called is False -@pytest.mark.parametrize('email_address', ( - 'test@nhs.net', - 'test@nhs.uk', - 'test@example.NhS.uK', - 'test@EXAMPLE.NHS.NET', -)) -@pytest.mark.skip(reason='Update for TTS') +@pytest.mark.parametrize( + "email_address", + ( + "test@nhs.net", + "test@nhs.uk", + "test@example.NhS.uK", + "test@EXAMPLE.NHS.NET", + ), +) +@pytest.mark.skip(reason="Update for TTS") def test_get_should_only_show_nhs_org_types_radios_if_user_has_nhs_email( client_request, mocker, api_user_active, email_address, ): - api_user_active['email_address'] = email_address + api_user_active["email_address"] = email_address client_request.login(api_user_active) mocker.patch( - 'app.organizations_client.get_organization_by_domain', + "app.organizations_client.get_organization_by_domain", return_value=None, ) - page = client_request.get('main.add_service') - assert page.select_one('h1').text.strip() == 'About your service' - assert page.select_one('input[name=name]').get('value') is None + page = client_request.get("main.add_service") + assert page.select_one("h1").text.strip() == "About your service" + assert page.select_one("input[name=name]").get("value") is None assert [ - label.text.strip() for label in page.select('.govuk-radios__item label') + label.text.strip() for label in page.select(".govuk-radios__item label") ] == [ - 'NHS – central government agency or public body', - 'NHS Trust or Clinical Commissioning Group', - 'GP practice', + "NHS – central government agency or public body", + "NHS Trust or Clinical Commissioning Group", + "GP practice", ] - assert [ - radio['value'] for radio in page.select('.govuk-radios__item input') - ] == [ - 'nhs_central', - 'nhs_local', - 'nhs_gp', + assert [radio["value"] for radio in page.select(".govuk-radios__item input")] == [ + "nhs_central", + "nhs_local", + "nhs_gp", ] -@pytest.mark.parametrize('organization_type, free_allowance', [ - ('federal', 150_000), - ('state', 150_000), -]) +@pytest.mark.parametrize( + "organization_type, free_allowance", + [ + ("federal", 150_000), + ("state", 150_000), + ], +) def test_should_add_service_and_redirect_to_dashboard_when_existing_service( notify_admin, mocker, @@ -233,36 +245,39 @@ def test_should_add_service_and_redirect_to_dashboard_when_existing_service( mock_get_all_email_branding, ): client_request.post( - 'main.add_service', + "main.add_service", _data={ - 'name': 'testing the post', - 'organization_type': organization_type, + "name": "testing the post", + "organization_type": organization_type, }, _expected_status=302, _expected_redirect=url_for( - 'main.service_dashboard', + "main.service_dashboard", service_id=101, - ) + ), ) assert mock_get_services.called mock_create_service.assert_called_once_with( - service_name='testing the post', + service_name="testing the post", organization_type=organization_type, - message_limit=notify_admin.config['DEFAULT_SERVICE_LIMIT'], + message_limit=notify_admin.config["DEFAULT_SERVICE_LIMIT"], restricted=True, - user_id=api_user_active['id'], - email_from='testing.the.post', + user_id=api_user_active["id"], + email_from="testing.the.post", ) assert len(mock_create_service_template.call_args_list) == 0 with client_request.session_transaction() as session: - assert session['service_id'] == 101 + assert session["service_id"] == 101 -@pytest.mark.parametrize('name, error_message', [ - ('', 'Cannot be empty'), - ('.', 'Must include at least two alphanumeric characters'), - ('a' * 256, 'Service name must be 255 characters or fewer'), -]) +@pytest.mark.parametrize( + "name, error_message", + [ + ("", "Cannot be empty"), + (".", "Must include at least two alphanumeric characters"), + ("a" * 256, "Service name must be 255 characters or fewer"), + ], +) def test_add_service_fails_if_service_name_fails_validation( client_request, mock_get_organization_by_domain, @@ -270,7 +285,7 @@ def test_add_service_fails_if_service_name_fails_validation( error_message, ): page = client_request.post( - 'main.add_service', + "main.add_service", _data={"name": name}, _expected_status=200, ) @@ -284,25 +299,27 @@ def test_should_return_form_errors_with_duplicate_service_name_regardless_of_cas mocker, ): def _create(**_kwargs): - json_mock = mocker.Mock(return_value={'message': {'name': ["Duplicate service name"]}}) + json_mock = mocker.Mock( + return_value={"message": {"name": ["Duplicate service name"]}} + ) resp_mock = mocker.Mock(status_code=400, json=json_mock) http_error = HTTPError(response=resp_mock, message="Default message") raise http_error - mocker.patch( - 'app.service_api_client.create_service', - side_effect=_create - ) + mocker.patch("app.service_api_client.create_service", side_effect=_create) page = client_request.post( - 'main.add_service', + "main.add_service", _data={ - 'name': 'SERVICE ONE', - 'organization_type': 'federal', + "name": "SERVICE ONE", + "organization_type": "federal", }, _expected_status=200, ) - assert 'This service name is already in use' in page.select_one('.usa-error-message').text.strip() + assert ( + "This service name is already in use" + in page.select_one(".usa-error-message").text.strip() + ) def test_non_government_user_cannot_access_create_service_page( @@ -311,10 +328,10 @@ def test_non_government_user_cannot_access_create_service_page( api_nongov_user_active, mock_get_organizations, ): - assert is_gov_user(api_nongov_user_active['email_address']) is False + assert is_gov_user(api_nongov_user_active["email_address"]) is False client_request.login(api_nongov_user_active) client_request.get( - 'main.add_service', + "main.add_service", _expected_status=403, ) @@ -325,10 +342,10 @@ def test_non_government_user_cannot_create_service( api_nongov_user_active, mock_get_organizations, ): - assert is_gov_user(api_nongov_user_active['email_address']) is False + assert is_gov_user(api_nongov_user_active["email_address"]) is False client_request.login(api_nongov_user_active) client_request.post( - 'main.add_service', - _data={'name': 'SERVICE TWO'}, + "main.add_service", + _data={"name": "SERVICE TWO"}, _expected_status=403, ) diff --git a/tests/app/main/views/test_api_integration.py b/tests/app/main/views/test_api_integration.py index 648bf0199..1577100d2 100644 --- a/tests/app/main/views/test_api_integration.py +++ b/tests/app/main/views/test_api_integration.py @@ -17,17 +17,20 @@ def test_should_show_api_page( api_user_active, mock_get_service, mock_has_permissions, - mock_get_notifications + mock_get_notifications, ): page = client_request.get( - 'main.api_integration', + "main.api_integration", service_id=SERVICE_ONE_ID, ) - assert page.h1.string.strip() == 'API integration' - rows = page.find_all('details') + assert page.h1.string.strip() == "API integration" + rows = page.find_all("details") assert len(rows) == 5 for row in rows: - assert row.select('h3 .govuk-details__summary-text')[0].string.strip() == '2021234567' + assert ( + row.select("h3 .govuk-details__summary-text")[0].string.strip() + == "2021234567" + ) def test_should_show_api_page_with_lots_of_notifications( @@ -36,15 +39,15 @@ def test_should_show_api_page_with_lots_of_notifications( api_user_active, mock_get_service, mock_has_permissions, - mock_get_notifications_with_previous_next + mock_get_notifications_with_previous_next, ): page = client_request.get( - 'main.api_integration', + "main.api_integration", service_id=SERVICE_ONE_ID, ) - rows = page.find_all('div', {'class': 'api-notifications-item'}) - assert ' '.join(rows[len(rows) - 1].text.split()) == ( - 'Only showing the first 50 messages. Notify deletes messages after 7 days.' + rows = page.find_all("div", {"class": "api-notifications-item"}) + assert " ".join(rows[len(rows) - 1].text.split()) == ( + "Only showing the first 50 messages. Notify deletes messages after 7 days." ) @@ -54,14 +57,17 @@ def test_should_show_api_page_with_no_notifications( api_user_active, mock_get_service, mock_has_permissions, - mock_get_notifications_with_no_notifications + mock_get_notifications_with_no_notifications, ): page = client_request.get( - 'main.api_integration', + "main.api_integration", service_id=SERVICE_ONE_ID, ) - rows = page.find_all('div', {'class': 'api-notifications-item'}) - assert 'When you send messages via the API they’ll appear here.' in rows[len(rows) - 1].text.strip() + rows = page.find_all("div", {"class": "api-notifications-item"}) + assert ( + "When you send messages via the API they’ll appear here." + in rows[len(rows) - 1].text.strip() + ) def test_should_show_api_page_for_live_service( @@ -70,28 +76,21 @@ def test_should_show_api_page_for_live_service( api_user_active, mock_get_notifications, mock_get_live_service, - mock_has_permissions + mock_has_permissions, ): - page = client_request.get( - 'main.api_integration', - service_id=uuid.uuid4() - ) - assert 'Your service is in trial mode' not in page.find('main').text + page = client_request.get("main.api_integration", service_id=uuid.uuid4()) + assert "Your service is in trial mode" not in page.find("main").text def test_api_documentation_page_should_redirect( - client_request, - mock_login, - api_user_active, - mock_get_service, - mock_has_permissions + client_request, mock_login, api_user_active, mock_get_service, mock_has_permissions ): client_request.get( - 'main.api_documentation', + "main.api_documentation", service_id=SERVICE_ONE_ID, _expected_status=301, _expected_redirect=url_for( - 'main.documentation', + "main.documentation", ), ) @@ -105,10 +104,10 @@ def test_should_show_empty_api_keys_page( mock_has_permissions, ): client_request.login(api_user_active) - page = client_request.get('main.api_keys', service_id=SERVICE_ONE_ID) + page = client_request.get("main.api_keys", service_id=SERVICE_ONE_ID) - assert 'You have not created any API keys yet' in page.text - assert 'Create an API key' in page.text + assert "You have not created any API keys yet" in page.text + assert "Create an API key" in page.text mock_get_no_api_keys.assert_called_once_with(SERVICE_ONE_ID) @@ -117,17 +116,20 @@ def test_should_show_api_keys_page( mock_get_api_keys, fake_uuid, ): - page = client_request.get('main.api_keys', service_id=SERVICE_ONE_ID) - rows = [normalize_spaces(row.text) for row in page.select('main tr')] - revoke_link = page.select_one('main tr a.usa-link.usa-link--destructive') + page = client_request.get("main.api_keys", service_id=SERVICE_ONE_ID) + rows = [normalize_spaces(row.text) for row in page.select("main tr")] + revoke_link = page.select_one("main tr a.usa-link.usa-link--destructive") - assert rows[0] == 'API keys Action' - assert rows[1] == f"another key name Revoked {format_datetime_short(date.fromtimestamp(0).isoformat())}" - assert rows[2] == 'some key name Revoke some key name' + assert rows[0] == "API keys Action" + assert ( + rows[1] + == f"another key name Revoked {format_datetime_short(date.fromtimestamp(0).isoformat())}" + ) + assert rows[2] == "some key name Revoke some key name" - assert normalize_spaces(revoke_link.text) == 'Revoke some key name' - assert revoke_link['href'] == url_for( - 'main.revoke_api_key', + assert normalize_spaces(revoke_link.text) == "Revoke some key name" + assert revoke_link["href"] == url_for( + "main.revoke_api_key", service_id=SERVICE_ONE_ID, key_id=fake_uuid, ) @@ -135,21 +137,30 @@ def test_should_show_api_keys_page( mock_get_api_keys.assert_called_once_with(SERVICE_ONE_ID) -@pytest.mark.parametrize('restricted, expected_options', [ - (True, [ +@pytest.mark.parametrize( + "restricted, expected_options", + [ ( - 'Live – sends to anyone', - 'Not available because your service is in trial mode' + True, + [ + ( + "Live – sends to anyone", + "Not available because your service is in trial mode", + ), + "Team and guest list – limits who you can send to", + "Test – pretends to send messages", + ], ), - 'Team and guest list – limits who you can send to', - 'Test – pretends to send messages', - ]), - (False, [ - 'Live – sends to anyone', - 'Team and guest list – limits who you can send to', - 'Test – pretends to send messages', - ]), -]) + ( + False, + [ + "Live – sends to anyone", + "Team and guest list – limits who you can send to", + "Test – pretends to send messages", + ], + ), + ], +) def test_should_show_create_api_key_page( client_request, mocker, @@ -159,19 +170,21 @@ def test_should_show_create_api_key_page( expected_options, service_one, ): - service_one['restricted'] = restricted + service_one["restricted"] = restricted - mocker.patch('app.service_api_client.get_service', return_value={'data': service_one}) + mocker.patch( + "app.service_api_client.get_service", return_value={"data": service_one} + ) - page = client_request.get('main.create_api_key', service_id=SERVICE_ONE_ID) + page = client_request.get("main.create_api_key", service_id=SERVICE_ONE_ID) for index, option in enumerate(expected_options): - item = page.select('.govuk-radios__item')[index] + item = page.select(".govuk-radios__item")[index] if type(option) is tuple: - assert normalize_spaces(item.select_one('.usa-label').text) == option[0] - assert normalize_spaces(item.select_one('.usa-hint').text) == option[1] + assert normalize_spaces(item.select_one(".usa-label").text) == option[0] + assert normalize_spaces(item.select_one(".usa-hint").text) == option[1] else: - assert normalize_spaces(item.select_one('.usa-label').text) == option + assert normalize_spaces(item.select_one(".usa-label").text) == option def test_should_create_api_key_with_type_normal( @@ -184,28 +197,31 @@ def test_should_create_api_key_with_type_normal( fake_uuid, mocker, ): - post = mocker.patch('app.notify_client.api_key_api_client.ApiKeyApiClient.post', return_value={'data': fake_uuid}) + post = mocker.patch( + "app.notify_client.api_key_api_client.ApiKeyApiClient.post", + return_value={"data": fake_uuid}, + ) page = client_request.post( - 'main.create_api_key', + "main.create_api_key", service_id=SERVICE_ONE_ID, - _data={ - 'key_name': 'Some default key name 1/2', - 'key_type': 'normal' - }, + _data={"key_name": "Some default key name 1/2", "key_type": "normal"}, _expected_status=200, ) - assert page.select_one('span.copy-to-clipboard__value').text == ( + assert page.select_one("span.copy-to-clipboard__value").text == ( # The text should be exactly this, with no leading or trailing whitespace - f'some_default_key_name_12-{SERVICE_ONE_ID}-{fake_uuid}' + f"some_default_key_name_12-{SERVICE_ONE_ID}-{fake_uuid}" ) - post.assert_called_once_with(url='/service/{}/api-key'.format(SERVICE_ONE_ID), data={ - 'name': 'Some default key name 1/2', - 'key_type': 'normal', - 'created_by': api_user_active['id'] - }) + post.assert_called_once_with( + url="/service/{}/api-key".format(SERVICE_ONE_ID), + data={ + "name": "Some default key name 1/2", + "key_type": "normal", + "created_by": api_user_active["id"], + }, + ) def test_cant_create_normal_api_key_in_trial_mode( @@ -218,15 +234,14 @@ def test_cant_create_normal_api_key_in_trial_mode( fake_uuid, mocker, ): - mock_post = mocker.patch('app.notify_client.api_key_api_client.ApiKeyApiClient.post') + mock_post = mocker.patch( + "app.notify_client.api_key_api_client.ApiKeyApiClient.post" + ) client_request.post( - 'main.create_api_key', + "main.create_api_key", service_id=SERVICE_ONE_ID, - _data={ - 'key_name': 'some default key name', - 'key_type': 'normal' - }, + _data={"key_name": "some default key name", "key_type": "normal"}, _expected_status=400, ) assert mock_post.called is False @@ -238,18 +253,18 @@ def test_should_show_confirm_revoke_api_key( fake_uuid, ): page = client_request.get( - 'main.revoke_api_key', service_id=SERVICE_ONE_ID, key_id=fake_uuid, + "main.revoke_api_key", + service_id=SERVICE_ONE_ID, + key_id=fake_uuid, _test_page_title=False, ) - assert normalize_spaces(page.select('.banner-dangerous')[0].text) == ( - 'Are you sure you want to revoke ‘some key name’? ' - 'You will not be able to use this API key to connect to U.S. Notify. ' - 'Yes, revoke this API key' + assert normalize_spaces(page.select(".banner-dangerous")[0].text) == ( + "Are you sure you want to revoke ‘some key name’? " + "You will not be able to use this API key to connect to U.S. Notify. " + "Yes, revoke this API key" ) assert mock_get_api_keys.call_args_list == [ - call( - '596364a0-858e-42c8-9062-a8fe822260eb' - ), + call("596364a0-858e-42c8-9062-a8fe822260eb"), ] @@ -258,7 +273,9 @@ def test_should_404_for_api_key_that_doesnt_exist( mock_get_api_keys, ): client_request.get( - 'main.revoke_api_key', service_id=SERVICE_ONE_ID, key_id='key-doesn’t-exist', + "main.revoke_api_key", + service_id=SERVICE_ONE_ID, + key_id="key-doesn’t-exist", _expected_status=404, ) @@ -274,24 +291,26 @@ def test_should_redirect_after_revoking_api_key( fake_uuid, ): client_request.post( - 'main.revoke_api_key', + "main.revoke_api_key", service_id=SERVICE_ONE_ID, key_id=fake_uuid, _expected_status=302, _expected_redirect=url_for( - '.api_keys', + ".api_keys", service_id=SERVICE_ONE_ID, ), ) - mock_revoke_api_key.assert_called_once_with(service_id=SERVICE_ONE_ID, key_id=fake_uuid) - mock_get_api_keys.assert_called_once_with(SERVICE_ONE_ID,) + mock_revoke_api_key.assert_called_once_with( + service_id=SERVICE_ONE_ID, key_id=fake_uuid + ) + mock_get_api_keys.assert_called_once_with( + SERVICE_ONE_ID, + ) -@pytest.mark.parametrize('route', [ - 'main.api_keys', - 'main.create_api_key', - 'main.revoke_api_key' -]) +@pytest.mark.parametrize( + "route", ["main.api_keys", "main.create_api_key", "main.revoke_api_key"] +) def test_route_permissions( mocker, notify_admin, @@ -307,17 +326,16 @@ def test_route_permissions( notify_admin, "GET", 200, - url_for(route, service_id=service_one['id'], key_id=fake_uuid), - ['manage_api_keys'], + url_for(route, service_id=service_one["id"], key_id=fake_uuid), + ["manage_api_keys"], api_user_active, - service_one) + service_one, + ) -@pytest.mark.parametrize('route', [ - 'main.api_keys', - 'main.create_api_key', - 'main.revoke_api_key' -]) +@pytest.mark.parametrize( + "route", ["main.api_keys", "main.create_api_key", "main.revoke_api_key"] +) def test_route_invalid_permissions( mocker, notify_admin, @@ -333,10 +351,11 @@ def test_route_invalid_permissions( notify_admin, "GET", 403, - url_for(route, service_id=service_one['id'], key_id=fake_uuid), - ['view_activity'], + url_for(route, service_id=service_one["id"], key_id=fake_uuid), + ["view_activity"], api_user_active, - service_one) + service_one, + ) def test_should_show_guestlist_page( @@ -348,74 +367,82 @@ def test_should_show_guestlist_page( mock_get_guest_list, ): page = client_request.get( - 'main.guest_list', + "main.guest_list", service_id=SERVICE_ONE_ID, ) - textboxes = page.find_all('input', {'class': 'usa-input'}) + textboxes = page.find_all("input", {"class": "usa-input"}) for index, value in enumerate( - ['test@example.com'] + [None] * 4 + ['2028675300'] + [None] * 4 + ["test@example.com"] + [None] * 4 + ["2028675300"] + [None] * 4 ): - assert textboxes[index].get('value') == value + assert textboxes[index].get("value") == value def test_should_update_guestlist( client_request, mock_update_guest_list, ): - data = OrderedDict([ - ('email_addresses-1', 'test@example.com'), - ('email_addresses-3', 'test@example.com'), - ('phone_numbers-0', '2028675300'), - ('phone_numbers-2', '+1800-555-5555'), - ]) + data = OrderedDict( + [ + ("email_addresses-1", "test@example.com"), + ("email_addresses-3", "test@example.com"), + ("phone_numbers-0", "2028675300"), + ("phone_numbers-2", "+1800-555-5555"), + ] + ) client_request.post( - 'main.guest_list', + "main.guest_list", service_id=SERVICE_ONE_ID, _data=data, ) - mock_update_guest_list.assert_called_once_with(SERVICE_ONE_ID, { - 'email_addresses': ['test@example.com', 'test@example.com'], - 'phone_numbers': ['2028675300', '+1800-555-5555']}) + mock_update_guest_list.assert_called_once_with( + SERVICE_ONE_ID, + { + "email_addresses": ["test@example.com", "test@example.com"], + "phone_numbers": ["2028675300", "+1800-555-5555"], + }, + ) def test_should_validate_guestlist_items( client_request, mock_update_guest_list, ): - page = client_request.post( - 'main.guest_list', + "main.guest_list", service_id=SERVICE_ONE_ID, - _data=OrderedDict([ - ('email_addresses-1', 'abc'), - ('phone_numbers-0', '123') - ]), + _data=OrderedDict([("email_addresses-1", "abc"), ("phone_numbers-0", "123")]), _expected_status=200, ) - assert page.h1.string.strip() == 'There was a problem with your guest list' - jump_links = page.select('.banner-dangerous a') + assert page.h1.string.strip() == "There was a problem with your guest list" + jump_links = page.select(".banner-dangerous a") - assert jump_links[0].string.strip() == 'Enter valid email addresses' - assert jump_links[0]['href'] == '#email_addresses' + assert jump_links[0].string.strip() == "Enter valid email addresses" + assert jump_links[0]["href"] == "#email_addresses" - assert jump_links[1].string.strip() == 'Enter valid phone numbers' - assert jump_links[1]['href'] == '#phone_numbers' + assert jump_links[1].string.strip() == "Enter valid phone numbers" + assert jump_links[1]["href"] == "#phone_numbers" assert mock_update_guest_list.called is False -@pytest.mark.parametrize('endpoint', [ - ('main.delivery_status_callback'), - ('main.received_text_messages_callback'), -]) -@pytest.mark.parametrize('url, bearer_token, expected_errors', [ - ("https://example.com", "", "Cannot be empty"), - ("http://not_https.com", "1234567890", "Must be a valid https URL"), - ("https://test.com", "123456789", "Must be at least 10 characters"), -]) +@pytest.mark.parametrize( + "endpoint", + [ + ("main.delivery_status_callback"), + ("main.received_text_messages_callback"), + ], +) +@pytest.mark.parametrize( + "url, bearer_token, expected_errors", + [ + ("https://example.com", "", "Cannot be empty"), + ("http://not_https.com", "1234567890", "Must be a valid https URL"), + ("https://test.com", "123456789", "Must be at least 10 characters"), + ], +) def test_callback_forms_validation( client_request, service_one, @@ -423,10 +450,10 @@ def test_callback_forms_validation( endpoint, url, bearer_token, - expected_errors + expected_errors, ): - if endpoint == 'main.received_text_messages_callback': - service_one['permissions'] = ['inbound_sms'] + if endpoint == "main.received_text_messages_callback": + service_one["permissions"] = ["inbound_sms"] data = { "url": url, @@ -434,27 +461,29 @@ def test_callback_forms_validation( } response = client_request.post( - endpoint, - service_id=service_one['id'], - _data=data, - _expected_status=200 + endpoint, service_id=service_one["id"], _data=data, _expected_status=200 + ) + error_msgs = " ".join( + msg.text.strip() for msg in response.select(".usa-error-message") ) - error_msgs = ' '.join(msg.text.strip() for msg in response.select(".usa-error-message")) assert expected_errors in error_msgs -@pytest.mark.parametrize('bearer_token', ['', 'some-bearer-token']) -@pytest.mark.parametrize('endpoint, expected_delete_url', [ - ( - 'main.delivery_status_callback', - '/service/{}/delivery-receipt-api/{}', - ), - ( - 'main.received_text_messages_callback', - '/service/{}/inbound-api/{}', - ), -]) +@pytest.mark.parametrize("bearer_token", ["", "some-bearer-token"]) +@pytest.mark.parametrize( + "endpoint, expected_delete_url", + [ + ( + "main.delivery_status_callback", + "/service/{}/delivery-receipt-api/{}", + ), + ( + "main.received_text_messages_callback", + "/service/{}/inbound-api/{}", + ), + ], +) def test_callback_forms_can_be_cleared( client_request, service_one, @@ -466,44 +495,45 @@ def test_callback_forms_can_be_cleared( mock_get_valid_service_callback_api, mock_get_valid_service_inbound_api, ): - service_one['service_callback_api'] = [fake_uuid] - service_one['inbound_api'] = [fake_uuid] - service_one['permissions'] = ['inbound_sms'] - mocked_delete = mocker.patch('app.service_api_client.delete') + service_one["service_callback_api"] = [fake_uuid] + service_one["inbound_api"] = [fake_uuid] + service_one["permissions"] = ["inbound_sms"] + mocked_delete = mocker.patch("app.service_api_client.delete") page = client_request.post( endpoint, - service_id=service_one['id'], + service_id=service_one["id"], _data={ - 'url': '', - 'bearer_token': bearer_token, + "url": "", + "bearer_token": bearer_token, }, _expected_redirect=url_for( - 'main.api_callbacks', - service_id=service_one['id'], - ) + "main.api_callbacks", + service_id=service_one["id"], + ), ) assert not page.select(".error-message") mocked_delete.assert_called_once_with( - expected_delete_url.format( - service_one['id'], fake_uuid - ) + expected_delete_url.format(service_one["id"], fake_uuid) ) -@pytest.mark.parametrize('bearer_token', ['', 'some-bearer-token']) -@pytest.mark.parametrize('endpoint, expected_delete_url', [ - ( - 'main.delivery_status_callback', - '/service/{}/delivery-receipt-api/{}', - ), - ( - 'main.received_text_messages_callback', - '/service/{}/inbound-api/{}', - ), -]) +@pytest.mark.parametrize("bearer_token", ["", "some-bearer-token"]) +@pytest.mark.parametrize( + "endpoint, expected_delete_url", + [ + ( + "main.delivery_status_callback", + "/service/{}/delivery-receipt-api/{}", + ), + ( + "main.received_text_messages_callback", + "/service/{}/inbound-api/{}", + ), + ], +) def test_callback_forms_can_be_cleared_when_callback_and_inbound_apis_are_empty( client_request, service_one, @@ -514,48 +544,51 @@ def test_callback_forms_can_be_cleared_when_callback_and_inbound_apis_are_empty( mock_get_empty_service_callback_api, mock_get_empty_service_inbound_api, ): - service_one['permissions'] = ['inbound_sms'] - mocked_delete = mocker.patch('app.service_api_client.delete') + service_one["permissions"] = ["inbound_sms"] + mocked_delete = mocker.patch("app.service_api_client.delete") page = client_request.post( endpoint, - service_id=service_one['id'], + service_id=service_one["id"], _data={ - 'url': '', - 'bearer_token': bearer_token, + "url": "", + "bearer_token": bearer_token, }, _expected_redirect=url_for( - 'main.api_callbacks', - service_id=service_one['id'], - ) + "main.api_callbacks", + service_id=service_one["id"], + ), ) assert not page.select(".error-message") assert mocked_delete.call_args_list == [] -@pytest.mark.parametrize('has_inbound_sms, expected_link', [ - (True, 'main.api_callbacks'), - (False, 'main.delivery_status_callback'), -]) +@pytest.mark.parametrize( + "has_inbound_sms, expected_link", + [ + (True, "main.api_callbacks"), + (False, "main.delivery_status_callback"), + ], +) def test_callbacks_button_links_straight_to_delivery_status_if_service_has_no_inbound_sms( client_request, service_one, mocker, mock_get_notifications, has_inbound_sms, - expected_link + expected_link, ): if has_inbound_sms: - service_one['permissions'] = ['inbound_sms'] + service_one["permissions"] = ["inbound_sms"] page = client_request.get( - 'main.api_integration', - service_id=service_one['id'], + "main.api_integration", + service_id=service_one["id"], ) - assert page.select('.pill-separate-item')[2]['href'] == url_for( - expected_link, service_id=service_one['id'] + assert page.select(".pill-separate-item")[2]["href"] == url_for( + expected_link, service_id=service_one["id"] ) @@ -566,43 +599,48 @@ def test_callbacks_page_redirects_to_delivery_status_if_service_has_no_inbound_s mock_get_valid_service_callback_api, ): page = client_request.get( - 'main.api_callbacks', - service_id=service_one['id'], + "main.api_callbacks", + service_id=service_one["id"], _follow_redirects=True, ) - assert normalize_spaces(page.select_one('h1').text) == "Callbacks for delivery receipts" + assert ( + normalize_spaces(page.select_one("h1").text) + == "Callbacks for delivery receipts" + ) -@pytest.mark.parametrize('has_inbound_sms, expected_link', [ - (True, 'main.api_callbacks'), - (False, 'main.api_integration'), -]) +@pytest.mark.parametrize( + "has_inbound_sms, expected_link", + [ + (True, "main.api_callbacks"), + (False, "main.api_integration"), + ], +) def test_back_link_directs_to_api_integration_from_delivery_callback_if_no_inbound_sms( - client_request, - service_one, - mocker, - has_inbound_sms, - expected_link + client_request, service_one, mocker, has_inbound_sms, expected_link ): if has_inbound_sms: - service_one['permissions'] = ['inbound_sms'] + service_one["permissions"] = ["inbound_sms"] page = client_request.get( - 'main.delivery_status_callback', - service_id=service_one['id'], + "main.delivery_status_callback", + service_id=service_one["id"], _follow_redirects=True, ) - assert page.select_one('.usa-back-link')['href'] == url_for( - expected_link, service_id=service_one['id'] + assert page.select_one(".usa-back-link")["href"] == url_for( + expected_link, service_id=service_one["id"] ) -@pytest.mark.parametrize('endpoint', [ - ('main.delivery_status_callback'), - ('main.received_text_messages_callback'), -]) +@pytest.mark.parametrize( + "endpoint", + [ + ("main.delivery_status_callback"), + ("main.received_text_messages_callback"), + ], +) def test_create_delivery_status_and_receive_text_message_callbacks( client_request, service_one, @@ -613,31 +651,31 @@ def test_create_delivery_status_and_receive_text_message_callbacks( endpoint, fake_uuid, ): - if endpoint == 'main.received_text_messages_callback': - service_one['permissions'] = ['inbound_sms'] + if endpoint == "main.received_text_messages_callback": + service_one["permissions"] = ["inbound_sms"] data = { - 'url': "https://test.url.com/", - 'bearer_token': '1234567890', - 'user_id': fake_uuid + "url": "https://test.url.com/", + "bearer_token": "1234567890", + "user_id": fake_uuid, } client_request.post( endpoint, - service_id=service_one['id'], + service_id=service_one["id"], _data=data, ) - if endpoint == 'main.received_text_messages_callback': + if endpoint == "main.received_text_messages_callback": mock_create_service_inbound_api.assert_called_once_with( - service_one['id'], + service_one["id"], url="https://test.url.com/", bearer_token="1234567890", user_id=fake_uuid, ) else: mock_create_service_callback_api.assert_called_once_with( - service_one['id'], + service_one["id"], url="https://test.url.com/", bearer_token="1234567890", user_id=fake_uuid, @@ -651,26 +689,26 @@ def test_update_delivery_status_callback_details( mock_get_valid_service_callback_api, fake_uuid, ): - service_one['service_callback_api'] = [fake_uuid] + service_one["service_callback_api"] = [fake_uuid] data = { - 'url': "https://test.url.com/", - 'bearer_token': '1234567890', - 'user_id': fake_uuid + "url": "https://test.url.com/", + "bearer_token": "1234567890", + "user_id": fake_uuid, } client_request.post( - 'main.delivery_status_callback', - service_id=service_one['id'], + "main.delivery_status_callback", + service_id=service_one["id"], _data=data, ) mock_update_service_callback_api.assert_called_once_with( - service_one['id'], + service_one["id"], url="https://test.url.com/", bearer_token="1234567890", user_id=fake_uuid, - callback_api_id=fake_uuid + callback_api_id=fake_uuid, ) @@ -681,23 +719,23 @@ def test_update_receive_text_message_callback_details( mock_get_valid_service_inbound_api, fake_uuid, ): - service_one['inbound_api'] = [fake_uuid] - service_one['permissions'] = ['inbound_sms'] + service_one["inbound_api"] = [fake_uuid] + service_one["permissions"] = ["inbound_sms"] data = { - 'url': "https://test.url.com/", - 'bearer_token': '1234567890', - 'user_id': fake_uuid + "url": "https://test.url.com/", + "bearer_token": "1234567890", + "user_id": fake_uuid, } client_request.post( - 'main.received_text_messages_callback', - service_id=service_one['id'], + "main.received_text_messages_callback", + service_id=service_one["id"], _data=data, ) mock_update_service_inbound_api.assert_called_once_with( - service_one['id'], + service_one["id"], url="https://test.url.com/", bearer_token="1234567890", user_id=fake_uuid, @@ -712,12 +750,16 @@ def test_update_delivery_status_callback_without_changes_does_not_update( fake_uuid, mock_get_valid_service_callback_api, ): - service_one['service_callback_api'] = [fake_uuid] - data = {"user_id": fake_uuid, "url": "https://hello2.gsa.gov", "bearer_token": "bearer_token_set"} + service_one["service_callback_api"] = [fake_uuid] + data = { + "user_id": fake_uuid, + "url": "https://hello2.gsa.gov", + "bearer_token": "bearer_token_set", + } client_request.post( - 'main.delivery_status_callback', - service_id=service_one['id'], + "main.delivery_status_callback", + service_id=service_one["id"], _data=data, ) @@ -731,39 +773,45 @@ def test_update_receive_text_message_callback_without_changes_does_not_update( fake_uuid, mock_get_valid_service_inbound_api, ): - service_one['inbound_api'] = [fake_uuid] - service_one['permissions'] = ['inbound_sms'] - data = {"user_id": fake_uuid, "url": "https://hello3.gsa.gov", "bearer_token": "bearer_token_set"} + service_one["inbound_api"] = [fake_uuid] + service_one["permissions"] = ["inbound_sms"] + data = { + "user_id": fake_uuid, + "url": "https://hello3.gsa.gov", + "bearer_token": "bearer_token_set", + } client_request.post( - 'main.received_text_messages_callback', - service_id=service_one['id'], + "main.received_text_messages_callback", + service_id=service_one["id"], _data=data, ) assert mock_update_service_inbound_api.called is False -@pytest.mark.parametrize('service_callback_api, delivery_url, expected_1st_table_row', [ - ( - None, {}, - 'Delivery receipts Not set Change' - ), - ( - sample_uuid(), {'url': 'https://delivery.receipts'}, - 'Delivery receipts https://delivery.receipts Change' - ), -]) -@pytest.mark.parametrize('inbound_api, inbound_url, expected_2nd_table_row', [ - ( - None, {}, - 'Received text messages Not set Change' - ), - ( - sample_uuid(), {'url': 'https://inbound.sms'}, - 'Received text messages https://inbound.sms Change' - ), -]) +@pytest.mark.parametrize( + "service_callback_api, delivery_url, expected_1st_table_row", + [ + (None, {}, "Delivery receipts Not set Change"), + ( + sample_uuid(), + {"url": "https://delivery.receipts"}, + "Delivery receipts https://delivery.receipts Change", + ), + ], +) +@pytest.mark.parametrize( + "inbound_api, inbound_url, expected_2nd_table_row", + [ + (None, {}, "Received text messages Not set Change"), + ( + sample_uuid(), + {"url": "https://inbound.sms"}, + "Received text messages https://inbound.sms Change", + ), + ], +) def test_callbacks_page_works_when_no_apis_set( client_request, service_one, @@ -775,21 +823,25 @@ def test_callbacks_page_works_when_no_apis_set( inbound_url, expected_2nd_table_row, ): - service_one['permissions'] = ['inbound_sms'] - service_one['inbound_api'] = inbound_api - service_one['service_callback_api'] = service_callback_api + service_one["permissions"] = ["inbound_sms"] + service_one["inbound_api"] = inbound_api + service_one["service_callback_api"] = service_callback_api - mocker.patch('app.service_api_client.get_service_callback_api', return_value=delivery_url) - mocker.patch('app.service_api_client.get_service_inbound_api', return_value=inbound_url) + mocker.patch( + "app.service_api_client.get_service_callback_api", return_value=delivery_url + ) + mocker.patch( + "app.service_api_client.get_service_inbound_api", return_value=inbound_url + ) - page = client_request.get('main.api_callbacks', - service_id=service_one['id'], - _follow_redirects=True) + page = client_request.get( + "main.api_callbacks", service_id=service_one["id"], _follow_redirects=True + ) expected_rows = [ expected_1st_table_row, expected_2nd_table_row, ] - rows = page.select('tbody tr') + rows = page.select("tbody tr") assert len(rows) == 2 for index, row in enumerate(expected_rows): assert row == normalize_spaces(rows[index].text) diff --git a/tests/app/main/views/test_code_not_received.py b/tests/app/main/views/test_code_not_received.py index 47d871f9e..28ded7aad 100644 --- a/tests/app/main/views/test_code_not_received.py +++ b/tests/app/main/views/test_code_not_received.py @@ -13,43 +13,51 @@ def test_should_render_email_verification_resend_show_email_address_and_resend_v ): client_request.logout() with client_request.session_transaction() as session: - session['user_details'] = { - 'id': api_user_active['id'], - 'email': api_user_active['email_address']} - page = client_request.get('main.resend_email_verification') + session["user_details"] = { + "id": api_user_active["id"], + "email": api_user_active["email_address"], + } + page = client_request.get("main.resend_email_verification") - assert page.h1.string == 'Check your email' - expected = "A new confirmation email has been sent to {}".format(api_user_active['email_address']) + assert page.h1.string == "Check your email" + expected = "A new confirmation email has been sent to {}".format( + api_user_active["email_address"] + ) - message = page.select('main p')[0].text + message = page.select("main p")[0].text assert message == expected - mock_send_verify_email.assert_called_with(api_user_active['id'], api_user_active['email_address']) + mock_send_verify_email.assert_called_with( + api_user_active["id"], api_user_active["email_address"] + ) -@pytest.mark.parametrize('redirect_url', [ - None, - f'/services/{SERVICE_ONE_ID}/templates', -]) +@pytest.mark.parametrize( + "redirect_url", + [ + None, + f"/services/{SERVICE_ONE_ID}/templates", + ], +) def test_should_render_correct_resend_template_for_active_user( client_request, api_user_active, mock_get_user_by_email, mock_send_verify_code, - redirect_url + redirect_url, ): client_request.logout() with client_request.session_transaction() as session: - session['user_details'] = { - 'id': api_user_active['id'], - 'email': api_user_active['email_address']} - page = client_request.get('main.check_and_resend_text_code', next=redirect_url) + session["user_details"] = { + "id": api_user_active["id"], + "email": api_user_active["email_address"], + } + page = client_request.get("main.check_and_resend_text_code", next=redirect_url) - assert page.h1.string == 'Resend security code' + assert page.h1.string == "Resend security code" # there shouldn't be a form for updating mobile number - assert page.find('form') is None - assert page.find('a', class_="usa-button")['href'] == url_for( - 'main.check_and_resend_verification_code', - next=redirect_url + assert page.find("form") is None + assert page.find("a", class_="usa-button")["href"] == url_for( + "main.check_and_resend_verification_code", next=redirect_url ) @@ -60,30 +68,39 @@ def test_should_render_correct_resend_template_for_pending_user( mock_send_verify_code, ): client_request.logout() - mocker.patch('app.user_api_client.get_user_by_email', return_value=api_user_pending) + mocker.patch("app.user_api_client.get_user_by_email", return_value=api_user_pending) with client_request.session_transaction() as session: - session['user_details'] = { - 'id': api_user_pending['id'], - 'email': api_user_pending['email_address']} - page = client_request.get('main.check_and_resend_text_code') + session["user_details"] = { + "id": api_user_pending["id"], + "email": api_user_pending["email_address"], + } + page = client_request.get("main.check_and_resend_text_code") - assert page.h1.string == 'Check your mobile number' + assert page.h1.string == "Check your mobile number" - expected = 'Check your mobile phone number is correct and then resend the security code.' - message = page.select('main p')[0].text + expected = ( + "Check your mobile phone number is correct and then resend the security code." + ) + message = page.select("main p")[0].text assert message == expected - assert page.find('form').input['value'] == api_user_pending['mobile_number'] + assert page.find("form").input["value"] == api_user_pending["mobile_number"] -@pytest.mark.parametrize('redirect_url', [ - None, - f'/services/{SERVICE_ONE_ID}/templates', -]) -@pytest.mark.parametrize('phone_number_to_register_with', [ - '+12027009004', - '+1800-555-5555', -]) +@pytest.mark.parametrize( + "redirect_url", + [ + None, + f"/services/{SERVICE_ONE_ID}/templates", + ], +) +@pytest.mark.parametrize( + "phone_number_to_register_with", + [ + "+12027009004", + "+1800-555-5555", + ], +) def test_should_resend_verify_code_and_update_mobile_for_pending_user( client_request, mocker, @@ -91,90 +108,102 @@ def test_should_resend_verify_code_and_update_mobile_for_pending_user( mock_update_user_attribute, mock_send_verify_code, phone_number_to_register_with, - redirect_url + redirect_url, ): client_request.logout() mock_update_user_attribute.reset_mock() - mocker.patch('app.user_api_client.get_user_by_email', return_value=api_user_pending) + mocker.patch("app.user_api_client.get_user_by_email", return_value=api_user_pending) with client_request.session_transaction() as session: - session['user_details'] = { - 'id': api_user_pending['id'], - 'email': api_user_pending['email_address']} + session["user_details"] = { + "id": api_user_pending["id"], + "email": api_user_pending["email_address"], + } client_request.post( - 'main.check_and_resend_text_code', + "main.check_and_resend_text_code", next=redirect_url, - _data={'mobile_number': phone_number_to_register_with}, - _expected_redirect=url_for('main.verify', next=redirect_url), + _data={"mobile_number": phone_number_to_register_with}, + _expected_redirect=url_for("main.verify", next=redirect_url), ) mock_update_user_attribute.assert_called_once_with( - api_user_pending['id'], + api_user_pending["id"], mobile_number=phone_number_to_register_with, ) mock_send_verify_code.assert_called_once_with( - api_user_pending['id'], - 'sms', + api_user_pending["id"], + "sms", phone_number_to_register_with, ) -@pytest.mark.parametrize('redirect_url', [ - None, - f'/services/{SERVICE_ONE_ID}/templates', -]) +@pytest.mark.parametrize( + "redirect_url", + [ + None, + f"/services/{SERVICE_ONE_ID}/templates", + ], +) def test_check_and_redirect_to_two_factor_if_user_active( client_request, api_user_active, mock_get_user_by_email, mock_send_verify_code, - redirect_url + redirect_url, ): client_request.logout() with client_request.session_transaction() as session: - session['user_details'] = { - 'id': api_user_active['id'], - 'email': api_user_active['email_address']} + session["user_details"] = { + "id": api_user_active["id"], + "email": api_user_active["email_address"], + } client_request.get( - 'main.check_and_resend_verification_code', + "main.check_and_resend_verification_code", next=redirect_url, - _expected_redirect=url_for('main.two_factor_sms', next=redirect_url) + _expected_redirect=url_for("main.two_factor_sms", next=redirect_url), ) -@pytest.mark.parametrize('redirect_url', [ - None, - f'/services/{SERVICE_ONE_ID}/templates', -]) +@pytest.mark.parametrize( + "redirect_url", + [ + None, + f"/services/{SERVICE_ONE_ID}/templates", + ], +) def test_check_and_redirect_to_verify_if_user_pending( client_request, mocker, api_user_pending, mock_get_user_pending, mock_send_verify_code, - redirect_url + redirect_url, ): client_request.logout() - mocker.patch('app.user_api_client.get_user_by_email', return_value=api_user_pending) + mocker.patch("app.user_api_client.get_user_by_email", return_value=api_user_pending) with client_request.session_transaction() as session: - session['user_details'] = { - 'id': api_user_pending['id'], - 'email': api_user_pending['email_address']} + session["user_details"] = { + "id": api_user_pending["id"], + "email": api_user_pending["email_address"], + } client_request.get( - 'main.check_and_resend_verification_code', + "main.check_and_resend_verification_code", next=redirect_url, - _expected_redirect=url_for('main.verify', next=redirect_url), + _expected_redirect=url_for("main.verify", next=redirect_url), ) -@pytest.mark.parametrize('endpoint', [ - 'main.resend_email_verification', - 'main.check_and_resend_text_code', - 'main.check_and_resend_verification_code', -]) +@pytest.mark.parametrize( + "endpoint", + [ + "main.resend_email_verification", + "main.check_and_resend_text_code", + "main.check_and_resend_verification_code", + ], +) def test_redirect_to_sign_in_if_not_logged_in( client_request, endpoint, @@ -182,29 +211,35 @@ def test_redirect_to_sign_in_if_not_logged_in( client_request.logout() client_request.get( endpoint, - _expected_redirect=url_for('main.sign_in'), + _expected_redirect=url_for("main.sign_in"), ) -@pytest.mark.parametrize('redirect_url', [ - None, - f'/services/{SERVICE_ONE_ID}/templates', -]) +@pytest.mark.parametrize( + "redirect_url", + [ + None, + f"/services/{SERVICE_ONE_ID}/templates", + ], +) def test_should_render_correct_email_not_received_template_for_active_user( client_request, api_user_active, mock_get_user_by_email, mock_send_verify_code, - redirect_url + redirect_url, ): client_request.logout() with client_request.session_transaction() as session: - session['user_details'] = { - 'id': api_user_active['id'], - 'email': api_user_active['email_address']} - page = client_request.get('main.email_not_received', next=redirect_url) + session["user_details"] = { + "id": api_user_active["id"], + "email": api_user_active["email_address"], + } + page = client_request.get("main.email_not_received", next=redirect_url) - assert page.h1.string == 'Resend email link' + assert page.h1.string == "Resend email link" # there shouldn't be a form for updating mobile number - assert page.find('form') is None - assert page.find('a', class_="usa-button")['href'] == url_for('main.resend_email_link', next=redirect_url) + assert page.find("form") is None + assert page.find("a", class_="usa-button")["href"] == url_for( + "main.resend_email_link", next=redirect_url + ) diff --git a/tests/app/main/views/test_conversation.py b/tests/app/main/views/test_conversation.py index f16abc242..c0b82fda1 100644 --- a/tests/app/main/views/test_conversation.py +++ b/tests/app/main/views/test_conversation.py @@ -16,63 +16,60 @@ from tests.conftest import ( normalize_spaces, ) -INV_PARENT_FOLDER_ID = '7e979e79-d970-43a5-ac69-b625a8d147b0' -VIS_PARENT_FOLDER_ID = 'bbbb222b-2b22-2b22-222b-b222b22b2222' +INV_PARENT_FOLDER_ID = "7e979e79-d970-43a5-ac69-b625a8d147b0" +VIS_PARENT_FOLDER_ID = "bbbb222b-2b22-2b22-222b-b222b22b2222" def test_get_user_phone_number_when_only_inbound_exists(mocker): - mock_get_inbound_sms = mocker.patch( - 'app.main.views.conversation.service_api_client.get_inbound_sms_by_id', - return_value={ - 'user_number': '2028675305', - 'notify_number': '2028675309' - } + "app.main.views.conversation.service_api_client.get_inbound_sms_by_id", + return_value={"user_number": "2028675305", "notify_number": "2028675309"}, ) mock_get_notification = mocker.patch( - 'app.main.views.conversation.notification_api_client.get_notification', + "app.main.views.conversation.notification_api_client.get_notification", side_effect=HTTPError(response=Mock(status_code=404)), ) - assert get_user_number('service', 'notification') == '(202) 867-5305' - mock_get_inbound_sms.assert_called_once_with('service', 'notification') + assert get_user_number("service", "notification") == "(202) 867-5305" + mock_get_inbound_sms.assert_called_once_with("service", "notification") assert mock_get_notification.called is False def test_get_user_phone_number_when_only_outbound_exists(mocker): mock_get_inbound_sms = mocker.patch( - 'app.main.views.conversation.service_api_client.get_inbound_sms_by_id', + "app.main.views.conversation.service_api_client.get_inbound_sms_by_id", side_effect=HTTPError(response=Mock(status_code=404)), ) mock_get_notification = mocker.patch( - 'app.main.views.conversation.notification_api_client.get_notification', - return_value={ - 'to': '2028675309' - } + "app.main.views.conversation.notification_api_client.get_notification", + return_value={"to": "2028675309"}, ) - assert get_user_number('service', 'notification') == '(202) 867-5309' - mock_get_inbound_sms.assert_called_once_with('service', 'notification') - mock_get_notification.assert_called_once_with('service', 'notification') + assert get_user_number("service", "notification") == "(202) 867-5309" + mock_get_inbound_sms.assert_called_once_with("service", "notification") + mock_get_notification.assert_called_once_with("service", "notification") def test_get_user_phone_number_raises_if_both_api_requests_fail(mocker): mock_get_inbound_sms = mocker.patch( - 'app.main.views.conversation.service_api_client.get_inbound_sms_by_id', + "app.main.views.conversation.service_api_client.get_inbound_sms_by_id", side_effect=HTTPError(response=Mock(status_code=404)), ) mock_get_notification = mocker.patch( - 'app.main.views.conversation.notification_api_client.get_notification', + "app.main.views.conversation.notification_api_client.get_notification", side_effect=HTTPError(response=Mock(status_code=404)), ) with pytest.raises(HTTPError): - get_user_number('service', 'notification') - mock_get_inbound_sms.assert_called_once_with('service', 'notification') - mock_get_notification.assert_called_once_with('service', 'notification') + get_user_number("service", "notification") + mock_get_inbound_sms.assert_called_once_with("service", "notification") + mock_get_notification.assert_called_once_with("service", "notification") -@pytest.mark.parametrize('outbound_redacted, expected_outbound_content', [ - (True, 'Hello hidden'), - (False, 'Hello Jo'), -]) +@pytest.mark.parametrize( + "outbound_redacted, expected_outbound_content", + [ + (True, "Hello hidden"), + (False, "Hello Jo"), + ], +) @freeze_time("2012-01-01 00:00:00") def test_view_conversation( client_request, @@ -82,96 +79,101 @@ def test_view_conversation( fake_uuid, outbound_redacted, expected_outbound_content, - mock_get_inbound_sms + mock_get_inbound_sms, ): notifications = create_notifications( - content='Hello ((name))', - personalisation={'name': 'Jo'}, + content="Hello ((name))", + personalisation={"name": "Jo"}, redact_personalisation=outbound_redacted, ) - mock = mocker.patch('app.notification_api_client.get_notifications_for_service', return_value=notifications) + mock = mocker.patch( + "app.notification_api_client.get_notifications_for_service", + return_value=notifications, + ) page = client_request.get( - 'main.conversation', + "main.conversation", service_id=SERVICE_ONE_ID, notification_id=fake_uuid, _test_page_title=False, ) - assert normalize_spaces(page.select_one('title').text) == ( - 'Received text message – service one – Notify.gov' - ) - assert normalize_spaces(page.select_one('h1').text) == ( - '2021234567' + assert normalize_spaces(page.select_one("title").text) == ( + "Received text message – service one – Notify.gov" ) + assert normalize_spaces(page.select_one("h1").text) == ("2021234567") - messages = page.select('.sms-message-wrapper') - statuses = page.select('.sms-message-status') + messages = page.select(".sms-message-wrapper") + statuses = page.select(".sms-message-status") assert len(messages) == 13 assert len(statuses) == 13 - for index, expected in enumerate([ - ( - 'message-8', - 'yesterday at 14:59 UTC', - ), - ( - 'message-7', - 'yesterday at 14:59 UTC', - ), - ( - 'message-6', - 'yesterday at 16:59 UTC', - ), - ( - 'message-5', - 'yesterday at 18:59 UTC', - ), - ( - 'message-4', - 'yesterday at 20:59 UTC', - ), - ( - 'message-3', - 'yesterday at 22:59 UTC', - ), - ( - 'message-2', - 'yesterday at 22:59 UTC', - ), - ( - 'message-1', - 'yesterday at 23:00 UTC', - ), - ( - expected_outbound_content, - 'yesterday at 00:00 UTC', - ), - ( - expected_outbound_content, - 'yesterday at 00:00 UTC', - ), - ( - expected_outbound_content, - 'yesterday at 00:00 UTC', - ), - ( - expected_outbound_content, - 'yesterday at 00:00 UTC', - ), - ( - expected_outbound_content, - 'yesterday at 00:00 UTC', - ), - ]): + for index, expected in enumerate( + [ + ( + "message-8", + "yesterday at 14:59 UTC", + ), + ( + "message-7", + "yesterday at 14:59 UTC", + ), + ( + "message-6", + "yesterday at 16:59 UTC", + ), + ( + "message-5", + "yesterday at 18:59 UTC", + ), + ( + "message-4", + "yesterday at 20:59 UTC", + ), + ( + "message-3", + "yesterday at 22:59 UTC", + ), + ( + "message-2", + "yesterday at 22:59 UTC", + ), + ( + "message-1", + "yesterday at 23:00 UTC", + ), + ( + expected_outbound_content, + "yesterday at 00:00 UTC", + ), + ( + expected_outbound_content, + "yesterday at 00:00 UTC", + ), + ( + expected_outbound_content, + "yesterday at 00:00 UTC", + ), + ( + expected_outbound_content, + "yesterday at 00:00 UTC", + ), + ( + expected_outbound_content, + "yesterday at 00:00 UTC", + ), + ] + ): assert ( normalize_spaces(messages[index].text), normalize_spaces(statuses[index].text), ) == expected - mock_get_inbound_sms.assert_called_once_with(SERVICE_ONE_ID, user_number='2021234567') - mock.assert_called_once_with(SERVICE_ONE_ID, to='2021234567', template_type='sms') + mock_get_inbound_sms.assert_called_once_with( + SERVICE_ONE_ID, user_number="2021234567" + ) + mock.assert_called_once_with(SERVICE_ONE_ID, to="2021234567", template_type="sms") def test_view_conversation_updates( @@ -181,25 +183,24 @@ def test_view_conversation_updates( mock_get_inbound_sms_by_id_with_no_messages, mock_get_notification, ): - mocker.patch( - 'app.main.views.conversation.service_api_client.get_inbound_sms_by_id', + "app.main.views.conversation.service_api_client.get_inbound_sms_by_id", side_effect=HTTPError(response=Mock(status_code=404)), ) mock_get_partials = mocker.patch( - 'app.main.views.conversation.get_conversation_partials', - return_value={'messages': 'foo'} + "app.main.views.conversation.get_conversation_partials", + return_value={"messages": "foo"}, ) response = client_request.get_response( - 'main.conversation_updates', + "main.conversation_updates", service_id=SERVICE_ONE_ID, notification_id=fake_uuid, ) - assert json.loads(response.get_data(as_text=True)) == {'messages': 'foo'} + assert json.loads(response.get_data(as_text=True)) == {"messages": "foo"} - mock_get_partials.assert_called_once_with(SERVICE_ONE_ID, '2021234567') + mock_get_partials.assert_called_once_with(SERVICE_ONE_ID, "2021234567") @freeze_time("2012-01-01 00:00:00") @@ -210,30 +211,32 @@ def test_view_conversation_with_empty_inbound( mock_get_inbound_sms_by_id_with_no_messages, mock_get_notification, mock_get_notifications_with_no_notifications, - fake_uuid + fake_uuid, ): mock_get_inbound_sms = mocker.patch( - 'app.main.views.conversation.service_api_client.get_inbound_sms', + "app.main.views.conversation.service_api_client.get_inbound_sms", return_value={ - 'has_next': False, - 'data': [{ - 'user_number': '2028675301', - 'notify_number': '2028675309', - 'content': '', - 'created_at': datetime.utcnow().isoformat(), - 'id': fake_uuid - }] - } + "has_next": False, + "data": [ + { + "user_number": "2028675301", + "notify_number": "2028675309", + "content": "", + "created_at": datetime.utcnow().isoformat(), + "id": fake_uuid, + } + ], + }, ) page = client_request.get( - 'main.conversation', + "main.conversation", service_id=SERVICE_ONE_ID, notification_id=fake_uuid, _test_page_title=False, ) - messages = page.select('.sms-message-wrapper') + messages = page.select(".sms-message-wrapper") assert len(messages) == 1 assert mock_get_inbound_sms.called is True @@ -247,15 +250,15 @@ def test_conversation_links_to_reply( mock_get_inbound_sms, ): page = client_request.get( - 'main.conversation', + "main.conversation", service_id=SERVICE_ONE_ID, notification_id=fake_uuid, _test_page_title=False, ) - assert page.select('main p')[-1].select_one('a')['href'] == ( + assert page.select("main p")[-1].select_one("a")["href"] == ( url_for( - '.conversation_reply', + ".conversation_reply", service_id=SERVICE_ONE_ID, notification_id=fake_uuid, ) @@ -267,21 +270,24 @@ def test_conversation_reply_shows_link_to_add_templates_if_service_has_no_templa fake_uuid, mock_get_service_templates_when_no_templates_exist, mock_get_template_folders, - active_user_with_permissions + active_user_with_permissions, ): page = client_request.get( - 'main.conversation_reply', + "main.conversation_reply", service_id=SERVICE_ONE_ID, notification_id=fake_uuid, ) - page_text = page.find('p', class_='bottom-gutter').text - link = page.find('a', text=re.compile('Add a new template'))['href'] + page_text = page.find("p", class_="bottom-gutter").text + link = page.find("a", text=re.compile("Add a new template"))["href"] - assert normalize_spaces(page_text) == 'You need a template before you can send text messages.' + assert ( + normalize_spaces(page_text) + == "You need a template before you can send text messages." + ) assert link == url_for( - 'main.choose_template', + "main.choose_template", service_id=SERVICE_ONE_ID, - initial_state='add-new-template' + initial_state="add-new-template", ) @@ -291,54 +297,65 @@ def test_conversation_reply_shows_templates( mocker, mock_get_template_folders, active_user_with_permissions, - service_one + service_one, ): - - all_templates = {'data': [ - _template('sms', 'sms_template_one', parent=INV_PARENT_FOLDER_ID), - _template('sms', 'sms_template_two'), - _template('sms', 'sms_template_three', parent=VIS_PARENT_FOLDER_ID), - ]} - mocker.patch('app.service_api_client.get_service_templates', return_value=all_templates) + all_templates = { + "data": [ + _template("sms", "sms_template_one", parent=INV_PARENT_FOLDER_ID), + _template("sms", "sms_template_two"), + _template("sms", "sms_template_three", parent=VIS_PARENT_FOLDER_ID), + ] + } + mocker.patch( + "app.service_api_client.get_service_templates", return_value=all_templates + ) mock_get_template_folders.return_value = [ { - 'name': "Parent 1 - invisible", - 'id': INV_PARENT_FOLDER_ID, - 'parent_id': None, - 'users_with_permission': [] + "name": "Parent 1 - invisible", + "id": INV_PARENT_FOLDER_ID, + "parent_id": None, + "users_with_permission": [], }, { - 'name': "Parent 2 - visible", - 'id': VIS_PARENT_FOLDER_ID, - 'parent_id': None, - 'users_with_permission': [active_user_with_permissions['id']] + "name": "Parent 2 - visible", + "id": VIS_PARENT_FOLDER_ID, + "parent_id": None, + "users_with_permission": [active_user_with_permissions["id"]], }, ] page = client_request.get( - 'main.conversation_reply', + "main.conversation_reply", service_id=SERVICE_ONE_ID, notification_id=fake_uuid, ) - link = page.select('.template-list-item-without-ancestors') + link = page.select(".template-list-item-without-ancestors") assert normalize_spaces(link[0].text) == "Parent 2 - visible 1 template" - assert normalize_spaces(link[1].text) == 'sms_template_two Text message template' + assert normalize_spaces(link[1].text) == "sms_template_two Text message template" - assert link[0].select_one('a')['href'].startswith( - url_for( - 'main.conversation_reply', - service_id=SERVICE_ONE_ID, - notification_id=fake_uuid, - from_folder=VIS_PARENT_FOLDER_ID + assert ( + link[0] + .select_one("a")["href"] + .startswith( + url_for( + "main.conversation_reply", + service_id=SERVICE_ONE_ID, + notification_id=fake_uuid, + from_folder=VIS_PARENT_FOLDER_ID, + ) ) ) - assert link[1].select_one('a')['href'].startswith( - url_for( - 'main.conversation_reply_with_template', - service_id=SERVICE_ONE_ID, - notification_id=fake_uuid, - template_id='', + assert ( + link[1] + .select_one("a")["href"] + .startswith( + url_for( + "main.conversation_reply_with_template", + service_id=SERVICE_ONE_ID, + notification_id=fake_uuid, + template_id="", + ) ) ) @@ -350,12 +367,12 @@ def test_conversation_reply_shows_live_search( mock_get_template_folders, ): page = client_request.get( - 'main.conversation_reply', + "main.conversation_reply", service_id=SERVICE_ONE_ID, notification_id=fake_uuid, ) - assert page.select('.live-search') + assert page.select(".live-search") def test_conversation_reply_redirects_with_phone_number_from_notification( @@ -365,9 +382,8 @@ def test_conversation_reply_redirects_with_phone_number_from_notification( mock_get_notification, mock_get_service_template, ): - page = client_request.get( - 'main.conversation_reply_with_template', + "main.conversation_reply_with_template", service_id=SERVICE_ONE_ID, notification_id=fake_uuid, template_id=fake_uuid, @@ -375,23 +391,23 @@ def test_conversation_reply_redirects_with_phone_number_from_notification( ) for element, expected_text in [ - ('h1', 'Preview of ‘Two week reminder’'), - ('.sms-message-recipient', 'To: 2021234567'), - ('.sms-message-wrapper', 'service one: Template content with & entity'), + ("h1", "Preview of ‘Two week reminder’"), + (".sms-message-recipient", "To: 2021234567"), + ( + ".sms-message-wrapper", + "service one: Template content with & entity", + ), ]: assert normalize_spaces(page.select_one(element).text) == expected_text def test_get_user_phone_number_when_not_a_standard_phone_number(mocker): mocker.patch( - 'app.main.views.conversation.service_api_client.get_inbound_sms_by_id', - return_value={ - 'user_number': 'ALPHANUM3R1C', - 'notify_number': '2028675309' - } + "app.main.views.conversation.service_api_client.get_inbound_sms_by_id", + return_value={"user_number": "ALPHANUM3R1C", "notify_number": "2028675309"}, ) mocker.patch( - 'app.main.views.conversation.notification_api_client.get_notification', + "app.main.views.conversation.notification_api_client.get_notification", side_effect=HTTPError, ) - assert get_user_number('service', 'notification') == 'ALPHANUM3R1C' + assert get_user_number("service", "notification") == "ALPHANUM3R1C" diff --git a/tests/app/main/views/test_dashboard.py b/tests/app/main/views/test_dashboard.py index beb7c87e9..4ac46df1d 100644 --- a/tests/app/main/views/test_dashboard.py +++ b/tests/app/main/views/test_dashboard.py @@ -30,54 +30,59 @@ from tests.conftest import ( stub_template_stats = [ { - 'template_type': 'sms', - 'template_name': 'one', - 'template_id': 'id-1', - 'status': 'created', - 'count': 50, + "template_type": "sms", + "template_name": "one", + "template_id": "id-1", + "status": "created", + "count": 50, }, { - 'template_type': 'email', - 'template_name': 'two', - 'template_id': 'id-2', - 'status': 'created', - 'count': 100, + "template_type": "email", + "template_name": "two", + "template_id": "id-2", + "status": "created", + "count": 100, }, { - 'template_type': 'email', - 'template_name': 'two', - 'template_id': 'id-2', - 'status': 'technical-failure', - 'count': 100, + "template_type": "email", + "template_name": "two", + "template_id": "id-2", + "status": "technical-failure", + "count": 100, }, { - 'template_type': 'sms', - 'template_name': 'one', - 'template_id': 'id-1', - 'status': 'delivered', - 'count': 50, + "template_type": "sms", + "template_name": "one", + "template_id": "id-1", + "status": "delivered", + "count": 50, }, ] -@pytest.mark.parametrize('user', ( - create_active_user_view_permissions(), - create_active_caseworking_user(), -)) +@pytest.mark.parametrize( + "user", + ( + create_active_user_view_permissions(), + create_active_caseworking_user(), + ), +) def test_redirect_from_old_dashboard( client_request, user, mocker, ): - mocker.patch('app.user_api_client.get_user', return_value=user) - expected_location = '/services/{}'.format(SERVICE_ONE_ID) + mocker.patch("app.user_api_client.get_user", return_value=user) + expected_location = "/services/{}".format(SERVICE_ONE_ID) client_request.get_url( - '/services/{}/dashboard'.format(SERVICE_ONE_ID), + "/services/{}/dashboard".format(SERVICE_ONE_ID), _expected_redirect=expected_location, ) - assert expected_location == url_for('main.service_dashboard', service_id=SERVICE_ONE_ID) + assert expected_location == url_for( + "main.service_dashboard", service_id=SERVICE_ONE_ID + ) def test_redirect_caseworkers_to_templates( @@ -87,13 +92,13 @@ def test_redirect_caseworkers_to_templates( ): client_request.login(active_caseworking_user) client_request.get( - 'main.service_dashboard', + "main.service_dashboard", service_id=SERVICE_ONE_ID, _expected_status=302, _expected_redirect=url_for( - 'main.choose_template', + "main.choose_template", service_id=SERVICE_ONE_ID, - ) + ), ) @@ -108,17 +113,19 @@ def test_get_started( mock_get_inbound_sms_summary, ): mocker.patch( - 'app.template_statistics_client.get_template_statistics_for_service', - return_value=copy.deepcopy(stub_template_stats) + "app.template_statistics_client.get_template_statistics_for_service", + return_value=copy.deepcopy(stub_template_stats), ) page = client_request.get( - 'main.service_dashboard', + "main.service_dashboard", service_id=SERVICE_ONE_ID, ) - mock_get_service_templates_when_no_templates_exist.assert_called_once_with(SERVICE_ONE_ID) - assert 'Get started' in page.text + mock_get_service_templates_when_no_templates_exist.assert_called_once_with( + SERVICE_ONE_ID + ) + assert "Get started" in page.text def test_get_started_is_hidden_once_templates_exist( @@ -132,16 +139,16 @@ def test_get_started_is_hidden_once_templates_exist( mock_get_inbound_sms_summary, ): mocker.patch( - 'app.template_statistics_client.get_template_statistics_for_service', - return_value=copy.deepcopy(stub_template_stats) + "app.template_statistics_client.get_template_statistics_for_service", + return_value=copy.deepcopy(stub_template_stats), ) page = client_request.get( - 'main.service_dashboard', + "main.service_dashboard", service_id=SERVICE_ONE_ID, ) mock_get_service_templates.assert_called_once_with(SERVICE_ONE_ID) - assert not page.find('h2', string='Get started') + assert not page.find("h2", string="Get started") def test_inbound_messages_not_visible_to_service_without_permissions( @@ -156,15 +163,14 @@ def test_inbound_messages_not_visible_to_service_without_permissions( mock_get_free_sms_fragment_limit, mock_get_inbound_sms_summary, ): - - service_one['permissions'] = [] + service_one["permissions"] = [] page = client_request.get( - 'main.service_dashboard', + "main.service_dashboard", service_id=SERVICE_ONE_ID, ) - assert not page.select('.big-number-meta-wrapper') + assert not page.select(".big-number-meta-wrapper") assert mock_get_inbound_sms_summary.called is False @@ -181,19 +187,18 @@ def test_inbound_messages_shows_count_of_messages_when_there_are_messages( mock_get_free_sms_fragment_limit, mock_get_inbound_sms_summary, ): - service_one['permissions'] = ['inbound_sms'] + service_one["permissions"] = ["inbound_sms"] page = client_request.get( - 'main.service_dashboard', + "main.service_dashboard", service_id=SERVICE_ONE_ID, ) mock_get_jobs.assert_called_once_with(SERVICE_ONE_ID) - banner = page.select('a.banner-dashboard')[1] - assert normalize_spaces( - banner.text - ) == '9,999 text messages received latest message just now' - assert banner['href'] == url_for( - 'main.inbox', service_id=SERVICE_ONE_ID + banner = page.select("a.banner-dashboard")[1] + assert ( + normalize_spaces(banner.text) + == "9,999 text messages received latest message just now" ) + assert banner["href"] == url_for("main.inbox", service_id=SERVICE_ONE_ID) def test_inbound_messages_shows_count_of_messages_when_there_are_no_messages( @@ -209,29 +214,32 @@ def test_inbound_messages_shows_count_of_messages_when_there_are_no_messages( mock_get_free_sms_fragment_limit, mock_get_inbound_sms_summary_with_no_messages, ): - service_one['permissions'] = ['inbound_sms'] + service_one["permissions"] = ["inbound_sms"] page = client_request.get( - 'main.service_dashboard', + "main.service_dashboard", service_id=SERVICE_ONE_ID, ) mock_get_jobs.assert_called_once_with(SERVICE_ONE_ID) - banner = page.select('a.banner-dashboard')[1] - assert normalize_spaces(banner.text) == '0 text messages received' - assert banner['href'] == url_for( - 'main.inbox', service_id=SERVICE_ONE_ID - ) + banner = page.select("a.banner-dashboard")[1] + assert normalize_spaces(banner.text) == "0 text messages received" + assert banner["href"] == url_for("main.inbox", service_id=SERVICE_ONE_ID) -@pytest.mark.parametrize('index, expected_row', enumerate([ - '(202) 867-5300 message-1 1 hour ago', - '(202) 867-5300 message-2 1 hour ago', - '(202) 867-5300 message-3 1 hour ago', - '(202) 867-5302 message-4 3 hours ago', - '+33 1 12 34 56 78 message-5 5 hours ago', - '(202) 555-0104 message-6 7 hours ago', - '(202) 555-0104 message-7 9 hours ago', - '+682 12345 message-8 9 hours ago', -])) +@pytest.mark.parametrize( + "index, expected_row", + enumerate( + [ + "(202) 867-5300 message-1 1 hour ago", + "(202) 867-5300 message-2 1 hour ago", + "(202) 867-5300 message-3 1 hour ago", + "(202) 867-5302 message-4 3 hours ago", + "+33 1 12 34 56 78 message-5 5 hours ago", + "(202) 555-0104 message-6 7 hours ago", + "(202) 555-0104 message-7 9 hours ago", + "+682 12345 message-8 9 hours ago", + ] + ), +) def test_inbox_showing_inbound_messages( client_request, service_one, @@ -243,19 +251,18 @@ def test_inbox_showing_inbound_messages( index, expected_row, ): - - service_one['permissions'] = ['inbound_sms'] + service_one["permissions"] = ["inbound_sms"] page = client_request.get( - 'main.inbox', + "main.inbox", service_id=SERVICE_ONE_ID, ) - rows = page.select('tbody tr') + rows = page.select("tbody tr") assert len(rows) == 8 assert normalize_spaces(rows[index].text) == expected_row - assert page.select_one('a[download]')['href'] == url_for( - 'main.inbox_download', + assert page.select_one("a[download]")["href"] == url_for( + "main.inbox_download", service_id=SERVICE_ONE_ID, ) @@ -270,16 +277,16 @@ def test_get_inbound_sms_shows_page_links( mock_get_most_recent_inbound_sms, mock_get_inbound_number_for_service, ): - service_one['permissions'] = ['inbound_sms'] + service_one["permissions"] = ["inbound_sms"] page = client_request.get( - 'main.inbox', + "main.inbox", service_id=SERVICE_ONE_ID, page=2, ) - assert 'Next page' in page.find('li', {'class': 'next-page'}).text - assert 'Previous page' in page.find('li', {'class': 'previous-page'}).text + assert "Next page" in page.find("li", {"class": "next-page"}).text + assert "Previous page" in page.find("li", {"class": "previous-page"}).text def test_empty_inbox( @@ -292,32 +299,34 @@ def test_empty_inbox( mock_get_most_recent_inbound_sms_with_no_messages, mock_get_inbound_number_for_service, ): - - service_one['permissions'] = ['inbound_sms'] + service_one["permissions"] = ["inbound_sms"] page = client_request.get( - 'main.inbox', + "main.inbox", service_id=SERVICE_ONE_ID, ) - assert normalize_spaces(page.select('tbody tr')) == ( - 'When users text your service’s phone number (2028675301) you’ll see the messages here' + assert normalize_spaces(page.select("tbody tr")) == ( + "When users text your service’s phone number (2028675301) you’ll see the messages here" ) - assert not page.select('a[download]') - assert not page.select('li.next-page') - assert not page.select('li.previous-page') + assert not page.select("a[download]") + assert not page.select("li.next-page") + assert not page.select("li.previous-page") -@pytest.mark.parametrize('endpoint', [ - 'main.inbox', - 'main.inbox_updates', -]) +@pytest.mark.parametrize( + "endpoint", + [ + "main.inbox", + "main.inbox_updates", + ], +) def test_inbox_not_accessible_to_service_without_permissions( client_request, service_one, endpoint, ): - service_one['permissions'] = [] + service_one["permissions"] = [] client_request.get( endpoint, service_id=SERVICE_ONE_ID, @@ -333,16 +342,15 @@ def test_anyone_can_see_inbox( mock_get_most_recent_inbound_sms_with_no_messages, mock_get_inbound_number_for_service, ): - - service_one['permissions'] = ['inbound_sms'] + service_one["permissions"] = ["inbound_sms"] validate_route_permission_with_client( mocker, client_request, - 'GET', + "GET", 200, - url_for('main.inbox', service_id=service_one['id']), - ['view_activity'], + url_for("main.inbox", service_id=service_one["id"]), + ["view_activity"], api_user_active, service_one, ) @@ -354,18 +362,19 @@ def test_view_inbox_updates( mocker, mock_get_most_recent_inbound_sms_with_no_messages, ): - service_one['permissions'] += ['inbound_sms'] + service_one["permissions"] += ["inbound_sms"] mock_get_partials = mocker.patch( - 'app.main.views.dashboard.get_inbox_partials', - return_value={'messages': 'foo'}, + "app.main.views.dashboard.get_inbox_partials", + return_value={"messages": "foo"}, ) response = client_request.get_response( - 'main.inbox_updates', service_id=SERVICE_ONE_ID, + "main.inbox_updates", + service_id=SERVICE_ONE_ID, ) - assert json.loads(response.get_data(as_text=True)) == {'messages': 'foo'} + assert json.loads(response.get_data(as_text=True)) == {"messages": "foo"} mock_get_partials.assert_called_once_with(SERVICE_ONE_ID) @@ -376,39 +385,38 @@ def test_download_inbox( mock_get_inbound_sms, ): response = client_request.get_response( - 'main.inbox_download', + "main.inbox_download", service_id=SERVICE_ONE_ID, ) - assert response.headers['Content-Type'] == ( - 'text/csv; ' - 'charset=utf-8' - ) - assert response.headers['Content-Disposition'] == ( - 'inline; ' - 'filename="Received text messages 2016-07-01.csv"' + assert response.headers["Content-Type"] == ("text/csv; " "charset=utf-8") + assert response.headers["Content-Disposition"] == ( + "inline; " 'filename="Received text messages 2016-07-01.csv"' ) assert response.get_data(as_text=True) == ( - 'Phone number,Message,Received\r\n' - '(202) 867-5300,message-1,2016-07-01 11:00 UTC\r\n' - '(202) 867-5300,message-2,2016-07-01 10:59 UTC\r\n' - '(202) 867-5300,message-3,2016-07-01 10:59 UTC\r\n' - '(202) 867-5302,message-4,2016-07-01 08:59 UTC\r\n' - '+33 1 12 34 56 78,message-5,2016-07-01 06:59 UTC\r\n' - '(202) 555-0104,message-6,2016-07-01 04:59 UTC\r\n' - '(202) 555-0104,message-7,2016-07-01 02:59 UTC\r\n' - '+682 12345,message-8,2016-07-01 02:59 UTC\r\n' + "Phone number,Message,Received\r\n" + "(202) 867-5300,message-1,2016-07-01 11:00 UTC\r\n" + "(202) 867-5300,message-2,2016-07-01 10:59 UTC\r\n" + "(202) 867-5300,message-3,2016-07-01 10:59 UTC\r\n" + "(202) 867-5302,message-4,2016-07-01 08:59 UTC\r\n" + "+33 1 12 34 56 78,message-5,2016-07-01 06:59 UTC\r\n" + "(202) 555-0104,message-6,2016-07-01 04:59 UTC\r\n" + "(202) 555-0104,message-7,2016-07-01 02:59 UTC\r\n" + "+682 12345,message-8,2016-07-01 02:59 UTC\r\n" ) @freeze_time("2016-07-01 13:00") -@pytest.mark.parametrize('message_content, expected_cell', [ - ('=2+5', '2+5'), - ('==2+5', '2+5'), - ('-2+5', '2+5'), - ('+2+5', '2+5'), - ('@2+5', '2+5'), - ('looks safe,=2+5', '"looks safe,=2+5"'), -]) +@pytest.mark.parametrize( + "message_content, expected_cell", + [ + ("=2+5", "2+5"), + ("==2+5", "2+5"), + ("-2+5", "2+5"), + ("+2+5", "2+5"), + ("@2+5", "2+5"), + ("looks safe,=2+5", '"looks safe,=2+5"'), + ], +) def test_download_inbox_strips_formulae( mocker, client_request, @@ -416,25 +424,26 @@ def test_download_inbox_strips_formulae( message_content, expected_cell, ): - mocker.patch( - 'app.service_api_client.get_inbound_sms', + "app.service_api_client.get_inbound_sms", return_value={ - 'has_next': False, - 'data': [{ - 'user_number': 'elevenchars', - 'notify_number': 'foo', - 'content': message_content, - 'created_at': datetime.utcnow().isoformat(), - 'id': fake_uuid, - }] + "has_next": False, + "data": [ + { + "user_number": "elevenchars", + "notify_number": "foo", + "content": message_content, + "created_at": datetime.utcnow().isoformat(), + "id": fake_uuid, + } + ], }, ) response = client_request.get_response( - 'main.inbox_download', + "main.inbox_download", service_id=SERVICE_ONE_ID, ) - assert expected_cell in response.get_data(as_text=True).split('\r\n')[1] + assert expected_cell in response.get_data(as_text=True).split("\r\n")[1] def test_should_show_recent_templates_on_dashboard( @@ -447,41 +456,48 @@ def test_should_show_recent_templates_on_dashboard( mock_get_free_sms_fragment_limit, mock_get_inbound_sms_summary, ): - mock_template_stats = mocker.patch('app.template_statistics_client.get_template_statistics_for_service', - return_value=copy.deepcopy(stub_template_stats)) + mock_template_stats = mocker.patch( + "app.template_statistics_client.get_template_statistics_for_service", + return_value=copy.deepcopy(stub_template_stats), + ) page = client_request.get( - 'main.service_dashboard', + "main.service_dashboard", service_id=SERVICE_ONE_ID, ) mock_template_stats.assert_called_once_with(SERVICE_ONE_ID, limit_days=7) - headers = [header.text.strip() for header in page.find_all('h2') + page.find_all('h1')] - assert 'In the last seven days' in headers + headers = [ + header.text.strip() for header in page.find_all("h2") + page.find_all("h1") + ] + assert "In the last seven days" in headers - table_rows = page.find_all('tbody')[0].find_all('tr') + table_rows = page.find_all("tbody")[0].find_all("tr") assert len(table_rows) == 2 - assert 'two' in table_rows[0].find_all('th')[0].text - assert 'Email template' in table_rows[0].find_all('th')[0].text - assert '200' in table_rows[0].find_all('td')[0].text + assert "two" in table_rows[0].find_all("th")[0].text + assert "Email template" in table_rows[0].find_all("th")[0].text + assert "200" in table_rows[0].find_all("td")[0].text - assert 'one' in table_rows[1].find_all('th')[0].text - assert 'Text message template' in table_rows[1].find_all('th')[0].text - assert '100' in table_rows[1].find_all('td')[0].text + assert "one" in table_rows[1].find_all("th")[0].text + assert "Text message template" in table_rows[1].find_all("th")[0].text + assert "100" in table_rows[1].find_all("td")[0].text -@pytest.mark.parametrize('stats', ( - pytest.param( - [stub_template_stats[0]], +@pytest.mark.parametrize( + "stats", + ( + pytest.param( + [stub_template_stats[0]], + ), + pytest.param( + [stub_template_stats[0], stub_template_stats[1]], + marks=pytest.mark.xfail(raises=AssertionError), + ), ), - pytest.param( - [stub_template_stats[0], stub_template_stats[1]], - marks=pytest.mark.xfail(raises=AssertionError), - ) -)) +) def test_should_not_show_recent_templates_on_dashboard_if_only_one_template_used( client_request, mocker, @@ -494,39 +510,40 @@ def test_should_not_show_recent_templates_on_dashboard_if_only_one_template_used stats, ): mock_template_stats = mocker.patch( - 'app.template_statistics_client.get_template_statistics_for_service', + "app.template_statistics_client.get_template_statistics_for_service", return_value=stats, ) - page = client_request.get('main.service_dashboard', service_id=SERVICE_ONE_ID) - main = page.select_one('main').text + page = client_request.get("main.service_dashboard", service_id=SERVICE_ONE_ID) + main = page.select_one("main").text mock_template_stats.assert_called_once_with(SERVICE_ONE_ID, limit_days=7) - assert stats[0]['template_name'] == 'one' - assert stats[0]['template_name'] not in main + assert stats[0]["template_name"] == "one" + assert stats[0]["template_name"] not in main # count appears as total, but not per template - expected_count = stats[0]['count'] + expected_count = stats[0]["count"] assert expected_count == 50 - assert normalize_spaces( - page.select_one('#total-sms .big-number-smaller').text - ) == ( - '{} text messages sent'.format(expected_count) + assert normalize_spaces(page.select_one("#total-sms .big-number-smaller").text) == ( + "{} text messages sent".format(expected_count) ) @freeze_time("2016-07-01 12:00") # 4 months into 2016 financial year -@pytest.mark.parametrize('extra_args', [ - {}, - {'year': '2016'}, -]) +@pytest.mark.parametrize( + "extra_args", + [ + {}, + {"year": "2016"}, + ], +) def test_should_show_redirect_from_template_history( client_request, extra_args, ): client_request.get( - 'main.template_history', + "main.template_history", service_id=SERVICE_ONE_ID, _expected_status=301, **extra_args, @@ -534,33 +551,34 @@ def test_should_show_redirect_from_template_history( @freeze_time("2017-01-01 12:00") # 4 months into 2016 financial year -@pytest.mark.parametrize('extra_args', [ - {}, - {'year': '2016'}, -]) +@pytest.mark.parametrize( + "extra_args", + [ + {}, + {"year": "2016"}, + ], +) def test_should_show_monthly_breakdown_of_template_usage( client_request, mock_get_monthly_template_usage, extra_args, ): page = client_request.get( - 'main.template_usage', - service_id=SERVICE_ONE_ID, - **extra_args + "main.template_usage", service_id=SERVICE_ONE_ID, **extra_args ) mock_get_monthly_template_usage.assert_called_once_with(SERVICE_ONE_ID, 2016) - table_rows = page.select('tbody tr') + table_rows = page.select("tbody tr") - assert ' '.join(table_rows[0].text.split()) == ( - 'My first template ' - 'Text message template ' - '2' + assert " ".join(table_rows[0].text.split()) == ( + "My first template " "Text message template " "2" ) - assert len(table_rows) == len(['October']) - assert len(page.select('.table-no-data')) == len(['November', 'December', 'January']) + assert len(table_rows) == len(["October"]) + assert len(page.select(".table-no-data")) == len( + ["November", "December", "January"] + ) def test_anyone_can_see_monthly_breakdown( @@ -573,19 +591,22 @@ def test_anyone_can_see_monthly_breakdown( validate_route_permission_with_client( mocker, client_request, - 'GET', + "GET", 200, - url_for('main.monthly', service_id=service_one['id']), - ['view_activity'], + url_for("main.monthly", service_id=service_one["id"]), + ["view_activity"], api_user_active, service_one, ) -@pytest.mark.parametrize('endpoint', [ - 'main.monthly', - 'main.template_usage', -]) +@pytest.mark.parametrize( + "endpoint", + [ + "main.monthly", + "main.template_usage", + ], +) @freeze_time("2015-01-01 15:15:15.000000") def test_stats_pages_show_last_3_years( client_request, @@ -598,10 +619,10 @@ def test_stats_pages_show_last_3_years( service_id=SERVICE_ONE_ID, ) - assert normalize_spaces(page.select_one('.pill').text) == ( - '2014 to 2015 fiscal year ' - '2013 to 2014 fiscal year ' - '2012 to 2013 fiscal year' + assert normalize_spaces(page.select_one(".pill").text) == ( + "2014 to 2015 fiscal year " + "2013 to 2014 fiscal year " + "2012 to 2013 fiscal year" ) @@ -610,12 +631,9 @@ def test_monthly_has_equal_length_tables( service_one, mock_get_monthly_notification_stats, ): - page = client_request.get( - 'main.monthly', - service_id=service_one['id'] - ) + page = client_request.get("main.monthly", service_id=service_one["id"]) - assert page.select_one('.table-field-headings th').get('width') == "33%" + assert page.select_one(".table-field-headings th").get("width") == "33%" @freeze_time("2016-01-01 1:09:00.061258") @@ -631,28 +649,21 @@ def test_should_show_upcoming_jobs_on_dashboard( mock_get_inbound_sms_summary, ): page = client_request.get( - 'main.service_dashboard', + "main.service_dashboard", service_id=SERVICE_ONE_ID, ) mock_get_jobs.assert_called_once_with(SERVICE_ONE_ID) mock_get_scheduled_job_stats.assert_called_once_with(SERVICE_ONE_ID) - assert normalize_spaces( - page.select_one('main h2').text - ) == ( - 'In the next few days' + assert normalize_spaces(page.select_one("main h2").text) == ("In the next few days") + + assert normalize_spaces(page.select_one("a.banner-dashboard").text) == ( + "2 files waiting to send " "sending starts today at 11:09 UTC" ) - assert normalize_spaces( - page.select_one('a.banner-dashboard').text - ) == ( - '2 files waiting to send ' - 'sending starts today at 11:09 UTC' - ) - - assert page.select_one('a.banner-dashboard')['href'] == url_for( - 'main.uploads', service_id=SERVICE_ONE_ID + assert page.select_one("a.banner-dashboard")["href"] == url_for( + "main.uploads", service_id=SERVICE_ONE_ID ) @@ -667,17 +678,20 @@ def test_should_not_show_upcoming_jobs_on_dashboard_if_count_is_0( mock_get_free_sms_fragment_limit, mock_get_inbound_sms_summary, ): - mocker.patch('app.job_api_client.get_scheduled_job_stats', return_value={ - 'count': 0, - 'soonest_scheduled_for': None, - }) + mocker.patch( + "app.job_api_client.get_scheduled_job_stats", + return_value={ + "count": 0, + "soonest_scheduled_for": None, + }, + ) page = client_request.get( - 'main.service_dashboard', + "main.service_dashboard", service_id=SERVICE_ONE_ID, ) mock_has_jobs.assert_called_once_with(SERVICE_ONE_ID) - assert 'In the next few days' not in page.select_one('main').text - assert 'files waiting to send ' not in page.select_one('main').text + assert "In the next few days" not in page.select_one("main").text + assert "files waiting to send " not in page.select_one("main").text def test_should_not_show_upcoming_jobs_on_dashboard_if_service_has_no_jobs( @@ -693,32 +707,33 @@ def test_should_not_show_upcoming_jobs_on_dashboard_if_service_has_no_jobs( mock_get_inbound_sms_summary, ): page = client_request.get( - 'main.service_dashboard', + "main.service_dashboard", service_id=SERVICE_ONE_ID, ) mock_has_no_jobs.assert_called_once_with(SERVICE_ONE_ID) assert mock_get_scheduled_job_stats.called is False - assert 'In the next few days' not in page.select_one('main').text - assert 'files waiting to send ' not in page.select_one('main').text + assert "In the next few days" not in page.select_one("main").text + assert "files waiting to send " not in page.select_one("main").text -@pytest.mark.parametrize('permissions', ( - ['email', 'sms'], -)) -@pytest.mark.parametrize('totals', [ - ( - { - 'email': {'requested': 0, 'delivered': 0, 'failed': 0}, - 'sms': {'requested': 99999, 'delivered': 0, 'failed': 0}, - }, - ), - ( - { - 'email': {'requested': 0, 'delivered': 0, 'failed': 0}, - 'sms': {'requested': 0, 'delivered': 0, 'failed': 0}, - }, - ), -]) +@pytest.mark.parametrize("permissions", (["email", "sms"],)) +@pytest.mark.parametrize( + "totals", + [ + ( + { + "email": {"requested": 0, "delivered": 0, "failed": 0}, + "sms": {"requested": 99999, "delivered": 0, "failed": 0}, + }, + ), + ( + { + "email": {"requested": 0, "delivered": 0, "failed": 0}, + "sms": {"requested": 0, "delivered": 0, "failed": 0}, + }, + ), + ], +) def test_correct_font_size_for_big_numbers( client_request, mocker, @@ -732,26 +747,24 @@ def test_correct_font_size_for_big_numbers( permissions, totals, ): + service_one["permissions"] = permissions - service_one['permissions'] = permissions - - mocker.patch( - 'app.main.views.dashboard.get_dashboard_totals', - return_value=totals - ) + mocker.patch("app.main.views.dashboard.get_dashboard_totals", return_value=totals) page = client_request.get( - 'main.service_dashboard', - service_id=service_one['id'], + "main.service_dashboard", + service_id=service_one["id"], ) assert ( - len(page.select_one('[data-key=totals]').select('.grid-col-12')) - ) == ( - # len(page.select_one('[data-key=usage]').select('.grid-col-6')) - # ) == ( - len(page.select('.big-number-with-status .big-number-smaller')) - ) == 1 + (len(page.select_one("[data-key=totals]").select(".grid-col-12"))) + == ( + # len(page.select_one('[data-key=usage]').select('.grid-col-6')) + # ) == ( + len(page.select(".big-number-with-status .big-number-smaller")) + ) + == 1 + ) def test_should_not_show_jobs_on_dashboard_for_users_with_uploads_page( @@ -767,7 +780,7 @@ def test_should_not_show_jobs_on_dashboard_for_users_with_uploads_page( mock_get_inbound_sms_summary, ): page = client_request.get( - 'main.service_dashboard', + "main.service_dashboard", service_id=SERVICE_ONE_ID, ) mock_get_jobs.assert_called_once_with(SERVICE_ONE_ID) @@ -777,7 +790,7 @@ def test_should_not_show_jobs_on_dashboard_for_users_with_uploads_page( "applicants.ods", "thisisatest.csv", }: - assert filename not in page.select_one('main').text + assert filename not in page.select_one("main").text @freeze_time("2012-03-31 12:12:12") @@ -789,7 +802,7 @@ def test_usage_page( mock_get_monthly_notification_stats, ): page = client_request.get( - 'main.usage', + "main.usage", service_id=SERVICE_ONE_ID, ) @@ -797,13 +810,16 @@ def test_usage_page( mock_get_annual_usage_for_service.assert_called_once_with(SERVICE_ONE_ID, 2011) mock_get_free_sms_fragment_limit.assert_called_with(SERVICE_ONE_ID, 2011) - nav = page.find('ul', {'class': 'pill'}) - unselected_nav_links = nav.select('a:not(.pill-item--selected)') - assert normalize_spaces(nav.find('a', {'aria-current': 'page'}).text) == '2011 to 2012 fiscal year' - assert normalize_spaces(unselected_nav_links[0].text) == '2010 to 2011 fiscal year' - assert normalize_spaces(unselected_nav_links[1].text) == '2009 to 2010 fiscal year' + nav = page.find("ul", {"class": "pill"}) + unselected_nav_links = nav.select("a:not(.pill-item--selected)") + assert ( + normalize_spaces(nav.find("a", {"aria-current": "page"}).text) + == "2011 to 2012 fiscal year" + ) + assert normalize_spaces(unselected_nav_links[0].text) == "2010 to 2011 fiscal year" + assert normalize_spaces(unselected_nav_links[1].text) == "2009 to 2010 fiscal year" - annual_usage = page.find_all('div', {'class': 'govuk-grid-column-one-half'}) + annual_usage = page.find_all("div", {"class": "govuk-grid-column-one-half"}) # annual stats are shown in two rows, each with three column; email is col 1 # email_column = normalize_spaces(annual_usage[0].text + annual_usage[2].text) @@ -811,13 +827,13 @@ def test_usage_page( # assert '1,000 sent' in email_column sms_column = normalize_spaces(annual_usage[0].text) - assert 'Text messages' in sms_column - assert '251,800 sent' in sms_column - assert '250,000 free allowance' in sms_column - assert '0 free allowance remaining' in sms_column - assert '$29.85 spent' not in sms_column - assert '1,500 at 1.65 pence' in sms_column - assert '300 at 1.70 pence' in sms_column + assert "Text messages" in sms_column + assert "251,800 sent" in sms_column + assert "250,000 free allowance" in sms_column + assert "0 free allowance remaining" in sms_column + assert "$29.85 spent" not in sms_column + assert "1,500 at 1.65 pence" in sms_column + assert "300 at 1.70 pence" in sms_column @freeze_time("2012-03-31 12:12:12") @@ -828,29 +844,32 @@ def test_usage_page_no_sms_spend( mock_get_free_sms_fragment_limit, mock_get_monthly_notification_stats, ): - mocker.patch('app.billing_api_client.get_annual_usage_for_service', return_value=[ - { - "notification_type": "sms", - "chargeable_units": 1000, - "charged_units": 0, - "rate": 0.0165, - "cost": 0 - } - ]) + mocker.patch( + "app.billing_api_client.get_annual_usage_for_service", + return_value=[ + { + "notification_type": "sms", + "chargeable_units": 1000, + "charged_units": 0, + "rate": 0.0165, + "cost": 0, + } + ], + ) page = client_request.get( - 'main.usage', + "main.usage", service_id=SERVICE_ONE_ID, ) - annual_usage = page.find_all('div', {'class': 'govuk-grid-column-one-half'}) + annual_usage = page.find_all("div", {"class": "govuk-grid-column-one-half"}) sms_column = normalize_spaces(annual_usage[0].text) - assert 'Text messages' in sms_column - assert '1,000 sent' in sms_column - assert '250,000 free allowance' in sms_column - assert '249,000 free allowance remaining' in sms_column - assert '$0.00 spent' not in sms_column - assert 'pence per message' not in sms_column + assert "Text messages" in sms_column + assert "1,000 sent" in sms_column + assert "250,000 free allowance" in sms_column + assert "249,000 free allowance remaining" in sms_column + assert "$0.00 spent" not in sms_column + assert "pence per message" not in sms_column @freeze_time("2012-03-31 12:12:12") @@ -862,28 +881,29 @@ def test_usage_page_monthly_breakdown( mock_get_free_sms_fragment_limit, mock_get_monthly_notification_stats, ): - page = client_request.get('main.usage', service_id=SERVICE_ONE_ID) - monthly_breakdown = normalize_spaces(page.find('table').text) + page = client_request.get("main.usage", service_id=SERVICE_ONE_ID) + monthly_breakdown = normalize_spaces(page.find("table").text) - assert 'October' in monthly_breakdown - assert '249,860 free text messages' in monthly_breakdown + assert "October" in monthly_breakdown + assert "249,860 free text messages" in monthly_breakdown - assert 'February' in monthly_breakdown - assert '$16.40' in monthly_breakdown - assert '140 free text messages' in monthly_breakdown - assert '960 text messages at 1.65p' in monthly_breakdown - assert '33 text messages at 1.70p' in monthly_breakdown + assert "February" in monthly_breakdown + assert "$16.40" in monthly_breakdown + assert "140 free text messages" in monthly_breakdown + assert "960 text messages at 1.65p" in monthly_breakdown + assert "33 text messages at 1.70p" in monthly_breakdown - assert 'March' in monthly_breakdown - assert '$20.91' in monthly_breakdown - assert '1,230 text messages at 1.70p' in monthly_breakdown + assert "March" in monthly_breakdown + assert "$20.91" in monthly_breakdown + assert "1,230 text messages at 1.70p" in monthly_breakdown -@ pytest.mark.parametrize( - 'now, expected_number_of_months', [ +@pytest.mark.parametrize( + "now, expected_number_of_months", + [ (freeze_time("2017-03-31 11:09:00.061258"), 6), - (freeze_time("2017-01-01 11:09:00.061258"), 4) - ] + (freeze_time("2017-01-01 11:09:00.061258"), 4), + ], ) def test_usage_page_monthly_breakdown_shows_months_so_far( client_request, @@ -893,11 +913,11 @@ def test_usage_page_monthly_breakdown_shows_months_so_far( mock_get_free_sms_fragment_limit, mock_get_monthly_notification_stats, now, - expected_number_of_months + expected_number_of_months, ): with now: - page = client_request.get('main.usage', service_id=SERVICE_ONE_ID) - rows = page.find('table').find_all('tr', class_='table-row') + page = client_request.get("main.usage", service_id=SERVICE_ONE_ID) + rows = page.find("table").find_all("tr", class_="table-row") assert len(rows) == expected_number_of_months @@ -909,20 +929,20 @@ def test_usage_page_with_0_free_allowance( mock_get_monthly_notification_stats, ): mocker.patch( - 'app.billing_api_client.get_free_sms_fragment_limit_for_year', + "app.billing_api_client.get_free_sms_fragment_limit_for_year", return_value=0, ) page = client_request.get( - 'main.usage', + "main.usage", service_id=SERVICE_ONE_ID, year=2020, ) - annual_usage = page.select('main .govuk-grid-column-one-half') + annual_usage = page.select("main .govuk-grid-column-one-half") sms_column = normalize_spaces(annual_usage[0].text) - assert '0 free allowance' in sms_column - assert 'free allowance remaining' not in sms_column + assert "0 free allowance" in sms_column + assert "free allowance remaining" not in sms_column def test_usage_page_with_year_argument( @@ -933,7 +953,7 @@ def test_usage_page_with_year_argument( mock_get_monthly_notification_stats, ): client_request.get( - 'main.usage', + "main.usage", service_id=SERVICE_ONE_ID, year=2000, ) @@ -947,43 +967,49 @@ def test_usage_page_for_invalid_year( client_request, ): client_request.get( - 'main.usage', + "main.usage", service_id=SERVICE_ONE_ID, - year='abcd', + year="abcd", _expected_status=404, ) -@ freeze_time("2012-03-31 12:12:12") +@freeze_time("2012-03-31 12:12:12") def test_future_usage_page( client_request, mock_get_annual_usage_for_service_in_future, mock_get_monthly_usage_for_service_in_future, mock_get_free_sms_fragment_limit, - mock_get_monthly_notification_stats + mock_get_monthly_notification_stats, ): client_request.get( - 'main.usage', + "main.usage", service_id=SERVICE_ONE_ID, year=2014, ) - mock_get_monthly_usage_for_service_in_future.assert_called_once_with(SERVICE_ONE_ID, 2014) - mock_get_annual_usage_for_service_in_future.assert_called_once_with(SERVICE_ONE_ID, 2014) + mock_get_monthly_usage_for_service_in_future.assert_called_once_with( + SERVICE_ONE_ID, 2014 + ) + mock_get_annual_usage_for_service_in_future.assert_called_once_with( + SERVICE_ONE_ID, 2014 + ) mock_get_free_sms_fragment_limit.assert_called_with(SERVICE_ONE_ID, 2014) mock_get_monthly_notification_stats.assert_called_with(SERVICE_ONE_ID, 2014) def _test_dashboard_menu(client_request, mocker, usr, service, permissions): - usr['permissions'][str(service['id'])] = permissions - usr['services'] = [service['id']] - mocker.patch('app.user_api_client.check_verify_code', return_value=(True, '')) - mocker.patch('app.service_api_client.get_services', return_value={'data': [service]}) - mocker.patch('app.user_api_client.get_user', return_value=usr) - mocker.patch('app.user_api_client.get_user_by_email', return_value=usr) - mocker.patch('app.service_api_client.get_service', return_value={'data': service}) + usr["permissions"][str(service["id"])] = permissions + usr["services"] = [service["id"]] + mocker.patch("app.user_api_client.check_verify_code", return_value=(True, "")) + mocker.patch( + "app.service_api_client.get_services", return_value={"data": [service]} + ) + mocker.patch("app.user_api_client.get_user", return_value=usr) + mocker.patch("app.user_api_client.get_user_by_email", return_value=usr) + mocker.patch("app.service_api_client.get_service", return_value={"data": service}) client_request.login(usr) - return client_request.get('main.service_dashboard', service_id=service['id']) + return client_request.get("main.service_dashboard", service_id=service["id"]) def test_menu_send_messages( @@ -1000,26 +1026,29 @@ def test_menu_send_messages( mock_get_inbound_sms_summary, mock_get_free_sms_fragment_limit, ): - service_one['permissions'] = ['email', 'sms'] + service_one["permissions"] = ["email", "sms"] page = _test_dashboard_menu( client_request, mocker, api_user_active, service_one, - ['view_activity', 'send_texts', 'send_emails'] + ["view_activity", "send_texts", "send_emails"], ) page = str(page) - assert url_for( - 'main.choose_template', - service_id=service_one['id'], - ) in page + assert ( + url_for( + "main.choose_template", + service_id=service_one["id"], + ) + in page + ) # assert url_for('main.uploads', service_id=service_one['id']) in page - assert url_for('main.manage_users', service_id=service_one['id']) in page + assert url_for("main.manage_users", service_id=service_one["id"]) in page - assert url_for('main.service_settings', service_id=service_one['id']) not in page + assert url_for("main.service_settings", service_id=service_one["id"]) not in page # assert url_for('main.api_keys', service_id=service_one['id']) not in page - assert url_for('main.view_providers') not in page + assert url_for("main.view_providers") not in page def test_menu_manage_service( @@ -1040,14 +1069,18 @@ def test_menu_manage_service( mocker, api_user_active, service_one, - ['view_activity', 'manage_templates', 'manage_users', 'manage_settings']) + ["view_activity", "manage_templates", "manage_users", "manage_settings"], + ) page = str(page) - assert url_for( - 'main.choose_template', - service_id=service_one['id'], - ) in page - assert url_for('main.manage_users', service_id=service_one['id']) in page - assert url_for('main.service_settings', service_id=service_one['id']) in page + assert ( + url_for( + "main.choose_template", + service_id=service_one["id"], + ) + in page + ) + assert url_for("main.manage_users", service_id=service_one["id"]) in page + assert url_for("main.service_settings", service_id=service_one["id"]) in page # assert url_for('main.api_keys', service_id=service_one['id']) not in page @@ -1070,14 +1103,21 @@ def test_menu_manage_api_keys( mocker, api_user_active, service_one, - ['view_activity', 'manage_api_keys']) + ["view_activity", "manage_api_keys"], + ) page = str(page) - assert url_for('main.choose_template', service_id=service_one['id'],) in page - assert url_for('main.manage_users', service_id=service_one['id']) in page - assert url_for('main.service_settings', service_id=service_one['id']) in page - assert url_for('main.api_integration', service_id=service_one['id']) in page + assert ( + url_for( + "main.choose_template", + service_id=service_one["id"], + ) + in page + ) + assert url_for("main.manage_users", service_id=service_one["id"]) in page + assert url_for("main.service_settings", service_id=service_one["id"]) in page + assert url_for("main.api_integration", service_id=service_one["id"]) in page def test_menu_all_services_for_platform_admin_user( @@ -1094,17 +1134,19 @@ def test_menu_all_services_for_platform_admin_user( mock_get_free_sms_fragment_limit, ): page = _test_dashboard_menu( - client_request, - mocker, - platform_admin_user, - service_one, - []) + client_request, mocker, platform_admin_user, service_one, [] + ) page = str(page) - assert url_for('main.choose_template', service_id=service_one['id']) in page - assert url_for('main.manage_users', service_id=service_one['id']) in page - assert url_for('main.service_settings', service_id=service_one['id']) in page + assert url_for("main.choose_template", service_id=service_one["id"]) in page + assert url_for("main.manage_users", service_id=service_one["id"]) in page + assert url_for("main.service_settings", service_id=service_one["id"]) in page # assert url_for('main.view_notifications', service_id=service_one['id'], message_type='email') in page - assert url_for('main.view_notifications', service_id=service_one['id'], message_type='sms') in page + assert ( + url_for( + "main.view_notifications", service_id=service_one["id"], message_type="sms" + ) + in page + ) # assert url_for('main.api_keys', service_id=service_one['id']) not in page @@ -1129,30 +1171,31 @@ def test_route_for_service_permissions( notify_admin, "GET", 200, - url_for('main.service_dashboard', service_id=service_one['id']), - ['view_activity'], + url_for("main.service_dashboard", service_id=service_one["id"]), + ["view_activity"], api_user_active, - service_one) + service_one, + ) def test_aggregate_template_stats(): expected = aggregate_template_usage(copy.deepcopy(stub_template_stats)) assert len(expected) == 2 - assert expected[0]['template_name'] == 'two' - assert expected[0]['count'] == 200 - assert expected[0]['template_id'] == 'id-2' - assert expected[0]['template_type'] == 'email' - assert expected[1]['template_name'] == 'one' - assert expected[1]['count'] == 100 - assert expected[1]['template_id'] == 'id-1' - assert expected[1]['template_type'] == 'sms' + assert expected[0]["template_name"] == "two" + assert expected[0]["count"] == 200 + assert expected[0]["template_id"] == "id-2" + assert expected[0]["template_type"] == "email" + assert expected[1]["template_name"] == "one" + assert expected[1]["count"] == 100 + assert expected[1]["template_id"] == "id-1" + assert expected[1]["template_type"] == "sms" def test_aggregate_notifications_stats(): expected = aggregate_notifications_stats(copy.deepcopy(stub_template_stats)) assert expected == { "sms": {"requested": 100, "delivered": 50, "failed": 0}, - "email": {"requested": 200, "delivered": 0, "failed": 100} + "email": {"requested": 200, "delivered": 0, "failed": 100}, } @@ -1167,54 +1210,40 @@ def test_service_dashboard_updates_gets_dashboard_totals( mock_get_free_sms_fragment_limit, mock_get_inbound_sms_summary, ): - mocker.patch('app.main.views.dashboard.get_dashboard_totals', return_value={ - 'email': {'requested': 123, 'delivered': 0, 'failed': 0}, - 'sms': {'requested': 456, 'delivered': 0, 'failed': 0} - }) + mocker.patch( + "app.main.views.dashboard.get_dashboard_totals", + return_value={ + "email": {"requested": 123, "delivered": 0, "failed": 0}, + "sms": {"requested": 456, "delivered": 0, "failed": 0}, + }, + ) page = client_request.get( - 'main.service_dashboard', + "main.service_dashboard", service_id=SERVICE_ONE_ID, ) - numbers = [number.text.strip() for number in page.find_all('span', class_='big-number-number')] + numbers = [ + number.text.strip() + for number in page.find_all("span", class_="big-number-number") + ] # assert '123' in numbers # email is disabled - assert '456' in numbers + assert "456" in numbers def test_get_dashboard_totals_adds_percentages(): stats = { - 'sms': { - 'requested': 3, - 'delivered': 0, - 'failed': 2 - }, - 'email': { - 'requested': 0, - 'delivered': 0, - 'failed': 0 - } + "sms": {"requested": 3, "delivered": 0, "failed": 2}, + "email": {"requested": 0, "delivered": 0, "failed": 0}, } - assert get_dashboard_totals(stats)['sms']['failed_percentage'] == '66.7' - assert get_dashboard_totals(stats)['email']['failed_percentage'] == '0' + assert get_dashboard_totals(stats)["sms"]["failed_percentage"] == "66.7" + assert get_dashboard_totals(stats)["email"]["failed_percentage"] == "0" -@ pytest.mark.parametrize( - 'failures,expected', [ - (2, False), - (3, False), - (4, True) - ] -) +@pytest.mark.parametrize("failures,expected", [(2, False), (3, False), (4, True)]) def test_get_dashboard_totals_adds_warning(failures, expected): - stats = { - 'sms': { - 'requested': 100, - 'delivered': 0, - 'failed': failures - } - } - assert get_dashboard_totals(stats)['sms']['show_warning'] == expected + stats = {"sms": {"requested": 100, "delivered": 0, "failed": failures}} + assert get_dashboard_totals(stats)["sms"]["show_warning"] == expected def test_format_monthly_stats_empty_case(): @@ -1222,76 +1251,82 @@ def test_format_monthly_stats_empty_case(): def test_format_monthly_stats_labels_month(): - resp = format_monthly_stats_to_list({'2016-07': {}}) - assert resp[0]['name'] == 'July' + resp = format_monthly_stats_to_list({"2016-07": {}}) + assert resp[0]["name"] == "July" def test_format_monthly_stats_has_stats_with_failure_rate(): - resp = format_monthly_stats_to_list({ - '2016-07': {'sms': _stats(3, 1, 2)} - }) - assert resp[0]['sms_counts'] == { - 'failed': 2, - 'failed_percentage': '66.7', - 'requested': 3, - 'show_warning': True, + resp = format_monthly_stats_to_list({"2016-07": {"sms": _stats(3, 1, 2)}}) + assert resp[0]["sms_counts"] == { + "failed": 2, + "failed_percentage": "66.7", + "requested": 3, + "show_warning": True, } def test_format_monthly_stats_works_for_email(): - resp = format_monthly_stats_to_list({ - '2016-07': { - 'sms': {}, - 'email': {}, + resp = format_monthly_stats_to_list( + { + "2016-07": { + "sms": {}, + "email": {}, + } } - }) - assert isinstance(resp[0]['sms_counts'], dict) - assert isinstance(resp[0]['email_counts'], dict) + ) + assert isinstance(resp[0]["sms_counts"], dict) + assert isinstance(resp[0]["email_counts"], dict) def _stats(requested, delivered, failed): - return {'requested': requested, 'delivered': delivered, 'failed': failed} + return {"requested": requested, "delivered": delivered, "failed": failed} -@ pytest.mark.parametrize('dict_in, expected_failed, expected_requested', [ - ( - {}, - 0, - 0 - ), - ( - {'temporary-failure': 1, 'permanent-failure': 1, 'technical-failure': 1}, - 3, - 3, - ), - ( - {'created': 1, 'pending': 1, 'sending': 1, 'delivered': 1}, - 0, - 4, - ), -]) +@pytest.mark.parametrize( + "dict_in, expected_failed, expected_requested", + [ + ({}, 0, 0), + ( + {"temporary-failure": 1, "permanent-failure": 1, "technical-failure": 1}, + 3, + 3, + ), + ( + {"created": 1, "pending": 1, "sending": 1, "delivered": 1}, + 0, + 4, + ), + ], +) def test_aggregate_status_types(dict_in, expected_failed, expected_requested): - sms_counts = aggregate_status_types({'sms': dict_in})['sms_counts'] - assert sms_counts['failed'] == expected_failed - assert sms_counts['requested'] == expected_requested + sms_counts = aggregate_status_types({"sms": dict_in})["sms_counts"] + assert sms_counts["failed"] == expected_failed + assert sms_counts["requested"] == expected_requested def test_get_tuples_of_financial_years(): - assert list(get_tuples_of_financial_years( - lambda year: 'http://example.com?year={}'.format(year), - start=2040, - end=2041, - )) == [ - ('fiscal year', 2041, 'http://example.com?year=2041', '2041 to 2042'), - ('fiscal year', 2040, 'http://example.com?year=2040', '2040 to 2041'), + assert list( + get_tuples_of_financial_years( + lambda year: "http://example.com?year={}".format(year), + start=2040, + end=2041, + ) + ) == [ + ("fiscal year", 2041, "http://example.com?year=2041", "2041 to 2042"), + ("fiscal year", 2040, "http://example.com?year=2040", "2040 to 2041"), ] def test_get_tuples_of_financial_years_defaults_to_2015(): - assert 2015 in list(get_tuples_of_financial_years( - lambda year: 'http://example.com?year={}'.format(year), - end=2040, - ))[-1] + assert ( + 2015 + in list( + get_tuples_of_financial_years( + lambda year: "http://example.com?year={}".format(year), + end=2040, + ) + )[-1] + ) def test_org_breadcrumbs_do_not_show_if_service_has_no_org( @@ -1302,9 +1337,9 @@ def test_org_breadcrumbs_do_not_show_if_service_has_no_org( mock_get_annual_usage_for_service, mock_get_free_sms_fragment_limit, ): - page = client_request.get('main.service_dashboard', service_id=SERVICE_ONE_ID) + page = client_request.get("main.service_dashboard", service_id=SERVICE_ONE_ID) - assert not page.select('.navigation-organization-link') + assert not page.select(".navigation-organization-link") def test_org_breadcrumbs_do_not_show_if_user_is_not_an_org_member( @@ -1318,16 +1353,22 @@ def test_org_breadcrumbs_do_not_show_if_user_is_not_an_org_member( ): # active_caseworking_user is not an org member - service_one_json = service_json(SERVICE_ONE_ID, - users=[active_caseworking_user['id']], - restricted=False, - organization_id=ORGANISATION_ID) - mocker.patch('app.service_api_client.get_service', return_value={'data': service_one_json}) + service_one_json = service_json( + SERVICE_ONE_ID, + users=[active_caseworking_user["id"]], + restricted=False, + organization_id=ORGANISATION_ID, + ) + mocker.patch( + "app.service_api_client.get_service", return_value={"data": service_one_json} + ) client_request.login(active_caseworking_user, service=service_one_json) - page = client_request.get('main.service_dashboard', service_id=SERVICE_ONE_ID, _follow_redirects=True) + page = client_request.get( + "main.service_dashboard", service_id=SERVICE_ONE_ID, _follow_redirects=True + ) - assert not page.select('.navigation-organization-link') + assert not page.select(".navigation-organization-link") def test_org_breadcrumbs_show_if_user_is_a_member_of_the_services_org( @@ -1342,19 +1383,26 @@ def test_org_breadcrumbs_show_if_user_is_a_member_of_the_services_org( ): # active_user_with_permissions (used by the client_request) is an org member - service_one_json = service_json(SERVICE_ONE_ID, - users=[active_user_with_permissions['id']], - restricted=False, - organization_id=ORGANISATION_ID) + service_one_json = service_json( + SERVICE_ONE_ID, + users=[active_user_with_permissions["id"]], + restricted=False, + organization_id=ORGANISATION_ID, + ) - mocker.patch('app.service_api_client.get_service', return_value={'data': service_one_json}) - mocker.patch('app.organizations_client.get_organization', return_value=organization_json( - id_=ORGANISATION_ID, - )) + mocker.patch( + "app.service_api_client.get_service", return_value={"data": service_one_json} + ) + mocker.patch( + "app.organizations_client.get_organization", + return_value=organization_json( + id_=ORGANISATION_ID, + ), + ) - page = client_request.get('main.service_dashboard', service_id=SERVICE_ONE_ID) - assert page.select_one('.navigation-organization-link')['href'] == url_for( - 'main.organization_dashboard', + page = client_request.get("main.service_dashboard", service_id=SERVICE_ONE_ID) + assert page.select_one(".navigation-organization-link")["href"] == url_for( + "main.organization_dashboard", org_id=ORGANISATION_ID, ) @@ -1371,16 +1419,20 @@ def test_org_breadcrumbs_do_not_show_if_user_is_a_member_of_the_services_org_but ): # active_user_with_permissions (used by the client_request) is an org member - service_one_json = service_json(SERVICE_ONE_ID, - users=[active_user_with_permissions['id']], - organization_id=ORGANISATION_ID) + service_one_json = service_json( + SERVICE_ONE_ID, + users=[active_user_with_permissions["id"]], + organization_id=ORGANISATION_ID, + ) - mocker.patch('app.service_api_client.get_service', return_value={'data': service_one_json}) - mocker.patch('app.models.service.Organization') + mocker.patch( + "app.service_api_client.get_service", return_value={"data": service_one_json} + ) + mocker.patch("app.models.service.Organization") - page = client_request.get('main.service_dashboard', service_id=SERVICE_ONE_ID) + page = client_request.get("main.service_dashboard", service_id=SERVICE_ONE_ID) - assert not page.select('.navigation-breadcrumb') + assert not page.select(".navigation-breadcrumb") def test_org_breadcrumbs_show_if_user_is_platform_admin( @@ -1393,20 +1445,27 @@ def test_org_breadcrumbs_show_if_user_is_platform_admin( platform_admin_user, client_request, ): - service_one_json = service_json(SERVICE_ONE_ID, - users=[platform_admin_user['id']], - organization_id=ORGANISATION_ID) + service_one_json = service_json( + SERVICE_ONE_ID, + users=[platform_admin_user["id"]], + organization_id=ORGANISATION_ID, + ) - mocker.patch('app.service_api_client.get_service', return_value={'data': service_one_json}) - mocker.patch('app.organizations_client.get_organization', return_value=organization_json( - id_=ORGANISATION_ID, - )) + mocker.patch( + "app.service_api_client.get_service", return_value={"data": service_one_json} + ) + mocker.patch( + "app.organizations_client.get_organization", + return_value=organization_json( + id_=ORGANISATION_ID, + ), + ) client_request.login(platform_admin_user, service_one_json) - page = client_request.get('main.service_dashboard', service_id=SERVICE_ONE_ID) + page = client_request.get("main.service_dashboard", service_id=SERVICE_ONE_ID) - assert page.select_one('.navigation-organization-link')['href'] == url_for( - 'main.organization_dashboard', + assert page.select_one(".navigation-organization-link")["href"] == url_for( + "main.organization_dashboard", org_id=ORGANISATION_ID, ) @@ -1424,18 +1483,18 @@ def test_breadcrumb_shows_if_service_is_suspended( service_one_json = service_json( SERVICE_ONE_ID, active=False, - users=[active_user_with_permissions['id']], + users=[active_user_with_permissions["id"]], ) - mocker.patch('app.service_api_client.get_service', return_value={'data': service_one_json}) - page = client_request.get('main.service_dashboard', service_id=SERVICE_ONE_ID) + mocker.patch( + "app.service_api_client.get_service", return_value={"data": service_one_json} + ) + page = client_request.get("main.service_dashboard", service_id=SERVICE_ONE_ID) - assert 'Suspended' in page.select_one('.navigation-service-name').text + assert "Suspended" in page.select_one(".navigation-service-name").text -@ pytest.mark.parametrize('permissions', ( - ['email', 'sms'], -)) +@pytest.mark.parametrize("permissions", (["email", "sms"],)) def test_service_dashboard_shows_usage( client_request, service_one, @@ -1446,14 +1505,12 @@ def test_service_dashboard_shows_usage( mock_get_free_sms_fragment_limit, permissions, ): - service_one['permissions'] = permissions - page = client_request.get('main.service_dashboard', service_id=SERVICE_ONE_ID) + service_one["permissions"] = permissions + page = client_request.get("main.service_dashboard", service_id=SERVICE_ONE_ID) - assert normalize_spaces( - page.select_one('[data-key=usage]').text - ) == ( - '$29.85 ' - 'spent on text messages' + assert normalize_spaces(page.select_one("[data-key=usage]").text) == ( + "$29.85 " + "spent on text messages" # Disabled for pilot # '0 ' # 'email disabled during SMS pilot' @@ -1469,18 +1526,21 @@ def test_service_dashboard_shows_free_allowance( mock_has_no_jobs, mock_get_free_sms_fragment_limit, ): - mocker.patch('app.billing_api_client.get_annual_usage_for_service', return_value=[ - { - "notification_type": "sms", - "chargeable_units": 1000, - "charged_units": 0, - "rate": 0.0165, - "cost": 0 - } - ]) + mocker.patch( + "app.billing_api_client.get_annual_usage_for_service", + return_value=[ + { + "notification_type": "sms", + "chargeable_units": 1000, + "charged_units": 0, + "rate": 0.0165, + "cost": 0, + } + ], + ) - page = client_request.get('main.service_dashboard', service_id=SERVICE_ONE_ID) + page = client_request.get("main.service_dashboard", service_id=SERVICE_ONE_ID) - usage_text = normalize_spaces(page.select_one('[data-key=usage]').text) - assert 'spent on text messages' not in usage_text - assert '249,000 free text messages left' in usage_text + usage_text = normalize_spaces(page.select_one("[data-key=usage]").text) + assert "spent on text messages" not in usage_text + assert "249,000 free text messages left" in usage_text diff --git a/tests/app/main/views/test_email_branding.py b/tests/app/main/views/test_email_branding.py index 28191ea18..672d41f35 100644 --- a/tests/app/main/views/test_email_branding.py +++ b/tests/app/main/views/test_email_branding.py @@ -10,76 +10,68 @@ from tests.conftest import create_email_branding, normalize_spaces def test_email_branding_page_shows_full_branding_list( - client_request, - platform_admin_user, - mock_get_all_email_branding + client_request, platform_admin_user, mock_get_all_email_branding ): - client_request.login(platform_admin_user) - page = client_request.get('.email_branding') + page = client_request.get(".email_branding") - links = page.select('.message-name a') + links = page.select(".message-name a") brand_names = [normalize_spaces(link.text) for link in links] - hrefs = [link['href'] for link in links] + hrefs = [link["href"] for link in links] - assert normalize_spaces( - page.select_one('h1').text - ) == "Email branding" + assert normalize_spaces(page.select_one("h1").text) == "Email branding" - assert page.select('.grid-col-9 a')[-1]['href'] == url_for('main.create_email_branding') + assert page.select(".grid-col-9 a")[-1]["href"] == url_for( + "main.create_email_branding" + ) assert brand_names == [ - 'org 1', - 'org 2', - 'org 3', - 'org 4', - 'org 5', + "org 1", + "org 2", + "org 3", + "org 4", + "org 5", ] assert hrefs == [ - url_for('.update_email_branding', branding_id=1), - url_for('.update_email_branding', branding_id=2), - url_for('.update_email_branding', branding_id=3), - url_for('.update_email_branding', branding_id=4), - url_for('.update_email_branding', branding_id=5), + url_for(".update_email_branding", branding_id=1), + url_for(".update_email_branding", branding_id=2), + url_for(".update_email_branding", branding_id=3), + url_for(".update_email_branding", branding_id=4), + url_for(".update_email_branding", branding_id=5), ] def test_edit_email_branding_shows_the_correct_branding_info( - client_request, - platform_admin_user, - mock_get_email_branding, - fake_uuid + client_request, platform_admin_user, mock_get_email_branding, fake_uuid ): client_request.login(platform_admin_user) page = client_request.get( - '.update_email_branding', + ".update_email_branding", branding_id=fake_uuid, _test_page_title=False, # TODO: Fix page titles ) - assert page.select_one('#logo-img > img')['src'].endswith('/example.png') - assert page.select_one('#name').attrs.get('value') == 'Organization name' - assert page.select_one('#file').attrs.get('accept') == '.png' - assert page.select_one('#text').attrs.get('value') == 'Organization text' - assert page.select_one('#colour').attrs.get('value') == '#f00' + assert page.select_one("#logo-img > img")["src"].endswith("/example.png") + assert page.select_one("#name").attrs.get("value") == "Organization name" + assert page.select_one("#file").attrs.get("accept") == ".png" + assert page.select_one("#text").attrs.get("value") == "Organization text" + assert page.select_one("#colour").attrs.get("value") == "#f00" def test_create_email_branding_does_not_show_any_branding_info( - client_request, - platform_admin_user, - mock_no_email_branding + client_request, platform_admin_user, mock_no_email_branding ): client_request.login(platform_admin_user) page = client_request.get( - '.create_email_branding', + ".create_email_branding", _test_page_title=False, # TODO: Fix page titles ) - assert page.select_one('#logo-img > img') is None - assert page.select_one('#name').attrs.get('value') is None - assert page.select_one('#file').attrs.get('accept') == '.png' - assert page.select_one('#text').attrs.get('value') is None - assert page.select_one('#colour').attrs.get('value') is None + assert page.select_one("#logo-img > img") is None + assert page.select_one("#name").attrs.get("value") is None + assert page.select_one("#file").attrs.get("accept") == ".png" + assert page.select_one("#text").attrs.get("value") is None + assert page.select_one("#colour").attrs.get("value") is None def test_create_new_email_branding_without_logo( @@ -90,30 +82,30 @@ def test_create_new_email_branding_without_logo( mock_create_email_branding, ): data = { - 'logo': None, - 'colour': '#ff0000', - 'text': 'new text', - 'name': 'new name', - 'brand_type': 'org' + "logo": None, + "colour": "#ff0000", + "text": "new text", + "name": "new name", + "brand_type": "org", } - mock_persist = mocker.patch('app.main.views.email_branding.persist_logo') - mocker.patch('app.main.views.email_branding.delete_email_temp_files_created_by') + mock_persist = mocker.patch("app.main.views.email_branding.persist_logo") + mocker.patch("app.main.views.email_branding.delete_email_temp_files_created_by") client_request.login(platform_admin_user) client_request.post( - '.create_email_branding', - _content_type='multipart/form-data', + ".create_email_branding", + _content_type="multipart/form-data", _data=data, ) assert mock_create_email_branding.called assert mock_create_email_branding.call_args == call( - logo=data['logo'], - name=data['name'], - text=data['text'], - colour=data['colour'], - brand_type=data['brand_type'] + logo=data["logo"], + name=data["name"], + text=data["text"], + colour=data["colour"], + brand_type=data["brand_type"], ) assert mock_persist.call_args_list == [] @@ -124,25 +116,28 @@ def test_create_email_branding_requires_a_name_when_submitting_logo_details( mock_create_email_branding, platform_admin_user, ): - mocker.patch('app.main.views.email_branding.persist_logo') - mocker.patch('app.main.views.email_branding.delete_email_temp_files_created_by') + mocker.patch("app.main.views.email_branding.persist_logo") + mocker.patch("app.main.views.email_branding.delete_email_temp_files_created_by") data = { - 'operation': 'email-branding-details', - 'logo': '', - 'colour': '#ff0000', - 'text': 'new text', - 'name': '', - 'brand_type': 'org', + "operation": "email-branding-details", + "logo": "", + "colour": "#ff0000", + "text": "new text", + "name": "", + "brand_type": "org", } client_request.login(platform_admin_user) page = client_request.post( - '.create_email_branding', - _content_type='multipart/form-data', + ".create_email_branding", + _content_type="multipart/form-data", _data=data, _expected_status=200, ) - assert page.select_one('.usa-error-message').text.strip() == 'Error: This field is required' + assert ( + page.select_one(".usa-error-message").text.strip() + == "Error: This field is required" + ) assert mock_create_email_branding.called is False @@ -151,92 +146,91 @@ def test_create_email_branding_does_not_require_a_name_when_uploading_a_file( mocker, platform_admin_user, ): - mocker.patch('app.main.views.email_branding.upload_email_logo', return_value='temp_filename') + mocker.patch( + "app.main.views.email_branding.upload_email_logo", return_value="temp_filename" + ) data = { - 'file': (BytesIO(''.encode('utf-8')), 'test.png'), - 'colour': '', - 'text': '', - 'name': '', - 'brand_type': 'org', + "file": (BytesIO("".encode("utf-8")), "test.png"), + "colour": "", + "text": "", + "name": "", + "brand_type": "org", } client_request.login(platform_admin_user) page = client_request.post( - '.create_email_branding', - _content_type='multipart/form-data', + ".create_email_branding", + _content_type="multipart/form-data", _data=data, - _follow_redirects=True + _follow_redirects=True, ) - assert not page.find('.error-message') + assert not page.find(".error-message") def test_create_new_email_branding_when_branding_saved( - client_request, - platform_admin_user, - mocker, - mock_create_email_branding, - fake_uuid + client_request, platform_admin_user, mocker, mock_create_email_branding, fake_uuid ): with client_request.session_transaction() as session: user_id = session["user_id"] data = { - 'logo': 'test.png', - 'colour': '#ff0000', - 'text': 'new text', - 'name': 'new name', - 'brand_type': 'org_banner' + "logo": "test.png", + "colour": "#ff0000", + "text": "new text", + "name": "new name", + "brand_type": "org_banner", } temp_filename = EMAIL_LOGO_LOCATION_STRUCTURE.format( temp=TEMP_TAG.format(user_id=user_id), unique_id=fake_uuid, - filename=data['logo'] + filename=data["logo"], ) - mocker.patch('app.main.views.email_branding.persist_logo') - mocker.patch('app.main.views.email_branding.delete_email_temp_files_created_by') + mocker.patch("app.main.views.email_branding.persist_logo") + mocker.patch("app.main.views.email_branding.delete_email_temp_files_created_by") client_request.login(platform_admin_user) client_request.post( - '.create_email_branding', + ".create_email_branding", logo=temp_filename, - _content_type='multipart/form-data', + _content_type="multipart/form-data", _data={ - 'colour': data['colour'], - 'name': data['name'], - 'text': data['text'], - 'cdn_url': 'https://static-logos.cdn.com', - 'brand_type': data['brand_type'] - } + "colour": data["colour"], + "name": data["name"], + "text": data["text"], + "cdn_url": "https://static-logos.cdn.com", + "brand_type": data["brand_type"], + }, ) - updated_logo_name = '{}-{}'.format(fake_uuid, data['logo']) + updated_logo_name = "{}-{}".format(fake_uuid, data["logo"]) assert mock_create_email_branding.called assert mock_create_email_branding.call_args == call( logo=updated_logo_name, - name=data['name'], - text=data['text'], - colour=data['colour'], - brand_type=data['brand_type'] + name=data["name"], + text=data["text"], + colour=data["colour"], + brand_type=data["brand_type"], ) -@pytest.mark.parametrize('endpoint, has_data', [ - ('main.create_email_branding', False), - ('main.update_email_branding', True), -]) +@pytest.mark.parametrize( + "endpoint, has_data", + [ + ("main.create_email_branding", False), + ("main.update_email_branding", True), + ], +) def test_deletes_previous_temp_logo_after_uploading_logo( - client_request, - platform_admin_user, - mocker, - endpoint, - has_data, - fake_uuid + client_request, platform_admin_user, mocker, endpoint, has_data, fake_uuid ): if has_data: - mocker.patch('app.email_branding_client.get_email_branding', return_value=create_email_branding(fake_uuid)) + mocker.patch( + "app.email_branding_client.get_email_branding", + return_value=create_email_branding(fake_uuid), + ) with client_request.session_transaction() as session: user_id = session["user_id"] @@ -244,29 +238,28 @@ def test_deletes_previous_temp_logo_after_uploading_logo( temp_old_filename = EMAIL_LOGO_LOCATION_STRUCTURE.format( temp=TEMP_TAG.format(user_id=user_id), unique_id=fake_uuid, - filename='old_test.png' + filename="old_test.png", ) temp_filename = EMAIL_LOGO_LOCATION_STRUCTURE.format( - temp=TEMP_TAG.format(user_id=user_id), - unique_id=fake_uuid, - filename='test.png' + temp=TEMP_TAG.format(user_id=user_id), unique_id=fake_uuid, filename="test.png" ) mocked_upload_email_logo = mocker.patch( - 'app.main.views.email_branding.upload_email_logo', - return_value=temp_filename + "app.main.views.email_branding.upload_email_logo", return_value=temp_filename ) - mocked_delete_email_temp_file = mocker.patch('app.main.views.email_branding.delete_email_temp_file') + mocked_delete_email_temp_file = mocker.patch( + "app.main.views.email_branding.delete_email_temp_file" + ) client_request.login(platform_admin_user) client_request.post( - 'main.create_email_branding', + "main.create_email_branding", logo=temp_old_filename, branding_id=fake_uuid, - _data={'file': (BytesIO(''.encode('utf-8')), 'test.png')}, - _content_type='multipart/form-data', + _data={"file": (BytesIO("".encode("utf-8")), "test.png")}, + _content_type="multipart/form-data", ) assert mocked_upload_email_logo.called @@ -280,53 +273,53 @@ def test_update_existing_branding( mocker, fake_uuid, mock_get_email_branding, - mock_update_email_branding + mock_update_email_branding, ): with client_request.session_transaction() as session: user_id = session["user_id"] data = { - 'logo': 'test.png', - 'colour': '#0000ff', - 'text': 'new text', - 'name': 'new name', - 'brand_type': 'both' + "logo": "test.png", + "colour": "#0000ff", + "text": "new text", + "name": "new name", + "brand_type": "both", } temp_filename = EMAIL_LOGO_LOCATION_STRUCTURE.format( temp=TEMP_TAG.format(user_id=user_id), unique_id=fake_uuid, - filename=data['logo'] + filename=data["logo"], ) - mocker.patch('app.main.views.email_branding.persist_logo') - mocker.patch('app.main.views.email_branding.delete_email_temp_files_created_by') + mocker.patch("app.main.views.email_branding.persist_logo") + mocker.patch("app.main.views.email_branding.delete_email_temp_files_created_by") client_request.login(platform_admin_user) client_request.post( - '.update_email_branding', + ".update_email_branding", logo=temp_filename, branding_id=fake_uuid, - _content_type='multipart/form-data', + _content_type="multipart/form-data", _data={ - 'colour': data['colour'], - 'name': data['name'], - 'text': data['text'], - 'cdn_url': 'https://static-logos.cdn.com', - 'brand_type': data['brand_type'], + "colour": data["colour"], + "name": data["name"], + "text": data["text"], + "cdn_url": "https://static-logos.cdn.com", + "brand_type": data["brand_type"], }, ) - updated_logo_name = '{}-{}'.format(fake_uuid, data['logo']) + updated_logo_name = "{}-{}".format(fake_uuid, data["logo"]) assert mock_update_email_branding.called assert mock_update_email_branding.call_args == call( branding_id=fake_uuid, logo=updated_logo_name, - name=data['name'], - text=data['text'], - colour=data['colour'], - brand_type=data['brand_type'] + name=data["name"], + text=data["text"], + colour=data["colour"], + brand_type=data["brand_type"], ) @@ -340,47 +333,48 @@ def test_temp_logo_is_shown_after_uploading_logo( user_id = session["user_id"] temp_filename = EMAIL_LOGO_LOCATION_STRUCTURE.format( - temp=TEMP_TAG.format(user_id=user_id), - unique_id=fake_uuid, - filename='test.png' + temp=TEMP_TAG.format(user_id=user_id), unique_id=fake_uuid, filename="test.png" ) - mocker.patch('app.main.views.email_branding.upload_email_logo', return_value=temp_filename) - mocker.patch('app.main.views.email_branding.delete_email_temp_file') + mocker.patch( + "app.main.views.email_branding.upload_email_logo", return_value=temp_filename + ) + mocker.patch("app.main.views.email_branding.delete_email_temp_file") client_request.login(platform_admin_user) page = client_request.post( - 'main.create_email_branding', - _data={'file': (BytesIO(''.encode('utf-8')), 'test.png')}, - _content_type='multipart/form-data', + "main.create_email_branding", + _data={"file": (BytesIO("".encode("utf-8")), "test.png")}, + _content_type="multipart/form-data", _follow_redirects=True, ) - assert page.select_one('#logo-img > img').attrs['src'].endswith(temp_filename) + assert page.select_one("#logo-img > img").attrs["src"].endswith(temp_filename) def test_logo_persisted_when_organization_saved( - client_request, - platform_admin_user, - mock_create_email_branding, - mocker, - fake_uuid + client_request, platform_admin_user, mock_create_email_branding, mocker, fake_uuid ): with client_request.session_transaction() as session: user_id = session["user_id"] temp_filename = EMAIL_LOGO_LOCATION_STRUCTURE.format( - temp=TEMP_TAG.format(user_id=user_id), unique_id=fake_uuid, filename='test.png') + temp=TEMP_TAG.format(user_id=user_id), unique_id=fake_uuid, filename="test.png" + ) - mocked_upload_email_logo = mocker.patch('app.main.views.email_branding.upload_email_logo') - mocked_persist_logo = mocker.patch('app.main.views.email_branding.persist_logo') - mocked_delete_email_temp_files_by = mocker.patch('app.main.views.email_branding.delete_email_temp_files_created_by') + mocked_upload_email_logo = mocker.patch( + "app.main.views.email_branding.upload_email_logo" + ) + mocked_persist_logo = mocker.patch("app.main.views.email_branding.persist_logo") + mocked_delete_email_temp_files_by = mocker.patch( + "app.main.views.email_branding.delete_email_temp_files_created_by" + ) client_request.login(platform_admin_user) client_request.post( - '.create_email_branding', + ".create_email_branding", logo=temp_filename, - _content_type='multipart/form-data', + _content_type="multipart/form-data", ) assert not mocked_upload_email_logo.called @@ -391,27 +385,29 @@ def test_logo_persisted_when_organization_saved( def test_logo_does_not_get_persisted_if_updating_email_branding_client_throws_an_error( - client_request, - platform_admin_user, - mock_create_email_branding, - mocker, - fake_uuid + client_request, platform_admin_user, mock_create_email_branding, mocker, fake_uuid ): with client_request.session_transaction() as session: user_id = session["user_id"] temp_filename = EMAIL_LOGO_LOCATION_STRUCTURE.format( - temp=TEMP_TAG.format(user_id=user_id), unique_id=fake_uuid, filename='test.png') + temp=TEMP_TAG.format(user_id=user_id), unique_id=fake_uuid, filename="test.png" + ) - mocked_persist_logo = mocker.patch('app.main.views.email_branding.persist_logo') - mocked_delete_email_temp_files_by = mocker.patch('app.main.views.email_branding.delete_email_temp_files_created_by') - mocker.patch('app.main.views.email_branding.email_branding_client.create_email_branding', side_effect=HTTPError()) + mocked_persist_logo = mocker.patch("app.main.views.email_branding.persist_logo") + mocked_delete_email_temp_files_by = mocker.patch( + "app.main.views.email_branding.delete_email_temp_files_created_by" + ) + mocker.patch( + "app.main.views.email_branding.email_branding_client.create_email_branding", + side_effect=HTTPError(), + ) client_request.login(platform_admin_user) client_request.post( - '.create_email_branding', + ".create_email_branding", logo=temp_filename, - _content_type='multipart/form-data', + _content_type="multipart/form-data", _expected_status=500, ) @@ -419,11 +415,14 @@ def test_logo_does_not_get_persisted_if_updating_email_branding_client_throws_an assert not mocked_delete_email_temp_files_by.called -@pytest.mark.parametrize('colour_hex, expected_status_code', [ - ('#FF00FF', 302), - ('hello', 200), - ('', 302), -]) +@pytest.mark.parametrize( + "colour_hex, expected_status_code", + [ + ("#FF00FF", 302), + ("hello", 200), + ("", 302), + ], +) def test_colour_regex_validation( client_request, platform_admin_user, @@ -431,22 +430,22 @@ def test_colour_regex_validation( fake_uuid, colour_hex, expected_status_code, - mock_create_email_branding + mock_create_email_branding, ): data = { - 'logo': None, - 'colour': colour_hex, - 'text': 'new text', - 'name': 'new name', - 'brand_type': 'org' + "logo": None, + "colour": colour_hex, + "text": "new text", + "name": "new name", + "brand_type": "org", } - mocker.patch('app.main.views.email_branding.delete_email_temp_files_created_by') + mocker.patch("app.main.views.email_branding.delete_email_temp_files_created_by") client_request.login(platform_admin_user) client_request.post( - '.create_email_branding', - _content_type='multipart/form-data', + ".create_email_branding", + _content_type="multipart/form-data", _data=data, _expected_status=expected_status_code, ) diff --git a/tests/app/main/views/test_email_preview.py b/tests/app/main/views/test_email_preview.py index 180f1c7c8..082394018 100644 --- a/tests/app/main/views/test_email_preview.py +++ b/tests/app/main/views/test_email_preview.py @@ -4,68 +4,92 @@ import pytest @pytest.mark.parametrize( - "query_args, result", [ - ({}, True), - ({'govuk_banner': 'false'}, 'false') - ] + "query_args, result", [({}, True), ({"govuk_banner": "false"}, "false")] ) def test_renders(client_request, mocker, query_args, result): + mocker.patch( + "app.main.views.index.HTMLEmailTemplate.__str__", return_value="rendered" + ) - mocker.patch('app.main.views.index.HTMLEmailTemplate.__str__', return_value='rendered') + response = client_request.get_response("main.email_template", **query_args) - response = client_request.get_response('main.email_template', **query_args) - - assert response.get_data(as_text=True) == 'rendered' + assert response.get_data(as_text=True) == "rendered" -def test_displays_both_branding(client_request, mock_get_email_branding_with_both_brand_type): +def test_displays_both_branding( + client_request, mock_get_email_branding_with_both_brand_type +): + page = client_request.get( + "main.email_template", branding_style="1", _test_page_title=False + ) - page = client_request.get('main.email_template', branding_style="1", _test_page_title=False) - - mock_get_email_branding_with_both_brand_type.assert_called_once_with('1') + mock_get_email_branding_with_both_brand_type.assert_called_once_with("1") assert page.find("img", attrs={"src": re.compile("example.png$")}) - assert page.select("body > table:nth-of-type(3) table > tr:nth-of-type(1) > td:nth-of-type(2)")[0]\ - .get_text().strip() == 'Organization text' # brand text is set + assert ( + page.select( + "body > table:nth-of-type(3) table > tr:nth-of-type(1) > td:nth-of-type(2)" + )[0] + .get_text() + .strip() + == "Organization text" + ) # brand text is set def test_displays_org_branding(client_request, mock_get_email_branding): # mock_get_email_branding has 'brand_type' of 'org' - page = client_request.get('main.email_template', branding_style="1", _test_page_title=False) + page = client_request.get( + "main.email_template", branding_style="1", _test_page_title=False + ) - mock_get_email_branding.assert_called_once_with('1') + mock_get_email_branding.assert_called_once_with("1") assert not page.find("a", attrs={"href": "https://www.gsa.gov"}) assert page.find("img", attrs={"src": re.compile("example.png")}) - assert not page.select("body > table > tr > td[bgcolor='#f00']") # banner colour is not set - assert page.select("body > table:nth-of-type(1) > tr:nth-of-type(1) > td:nth-of-type(2)")[0]\ - .get_text().strip() == 'Organization text' # brand text is set + assert not page.select( + "body > table > tr > td[bgcolor='#f00']" + ) # banner colour is not set + assert ( + page.select( + "body > table:nth-of-type(1) > tr:nth-of-type(1) > td:nth-of-type(2)" + )[0] + .get_text() + .strip() + == "Organization text" + ) # brand text is set def test_displays_org_branding_with_banner( client_request, mock_get_email_branding_with_org_banner_brand_type ): - page = client_request.get('main.email_template', branding_style="1", _test_page_title=False) + page = client_request.get( + "main.email_template", branding_style="1", _test_page_title=False + ) - mock_get_email_branding_with_org_banner_brand_type.assert_called_once_with('1') + mock_get_email_branding_with_org_banner_brand_type.assert_called_once_with("1") assert not page.find("a", attrs={"href": "https://www.gsa.gov"}) assert page.find("img", attrs={"src": re.compile("example.png")}) assert page.select("body > table > tr > td[bgcolor='#f00']") # banner colour is set - assert page.select("body > table table > tr > td > span")[0]\ - .get_text().strip() == 'Organization text' # brand text is set + assert ( + page.select("body > table table > tr > td > span")[0].get_text().strip() + == "Organization text" + ) # brand text is set def test_displays_org_branding_with_banner_without_brand_text( client_request, mock_get_email_branding_without_brand_text ): - # mock_get_email_branding_without_brand_text has 'brand_type' of 'org_banner' - page = client_request.get('main.email_template', branding_style="1", _test_page_title=False) + page = client_request.get( + "main.email_template", branding_style="1", _test_page_title=False + ) - mock_get_email_branding_without_brand_text.assert_called_once_with('1') + mock_get_email_branding_without_brand_text.assert_called_once_with("1") assert not page.find("a", attrs={"href": "https://www.gsa.gov"}) assert page.find("img", attrs={"src": re.compile("example.png")}) assert page.select("body > table > tr > td[bgcolor='#f00']") # banner colour is set - assert not page.select("body > table table > tr > td > span") == 0 # brand text is not set + assert ( + not page.select("body > table table > tr > td > span") == 0 + ) # brand text is not set diff --git a/tests/app/main/views/test_feedback.py b/tests/app/main/views/test_feedback.py index fe3a6996c..89f757bff 100644 --- a/tests/app/main/views/test_feedback.py +++ b/tests/app/main/views/test_feedback.py @@ -23,21 +23,26 @@ def no_redirect(): def test_get_support_index_page( client_request, ): - page = client_request.get('.support') - assert page.select_one('form')['method'] == 'post' - assert 'action' not in page.select_one('form') - assert normalize_spaces(page.select_one('h1').text) == 'Support' - assert normalize_spaces( - page.select_one('form label[for=support_type-0]').text - ) == 'Report a problem' - assert page.select_one('form input#support_type-0')['value'] == 'report-problem' - assert normalize_spaces( - page.select_one('form label[for=support_type-1]').text - ) == 'Ask a question or give feedback' - assert page.select_one('form input#support_type-1')['value'] == 'ask-question-give-feedback' - assert normalize_spaces( - page.select_one('form button[type=submit]').text - ) == 'Continue' + page = client_request.get(".support") + assert page.select_one("form")["method"] == "post" + assert "action" not in page.select_one("form") + assert normalize_spaces(page.select_one("h1").text) == "Support" + assert ( + normalize_spaces(page.select_one("form label[for=support_type-0]").text) + == "Report a problem" + ) + assert page.select_one("form input#support_type-0")["value"] == "report-problem" + assert ( + normalize_spaces(page.select_one("form label[for=support_type-1]").text) + == "Ask a question or give feedback" + ) + assert ( + page.select_one("form input#support_type-1")["value"] + == "ask-question-give-feedback" + ) + assert ( + normalize_spaces(page.select_one("form button[type=submit]").text) == "Continue" + ) @pytest.mark.skip(reason="Not currently using Zendesk") @@ -45,67 +50,64 @@ def test_get_support_index_page_when_signed_out( client_request, ): client_request.logout() - page = client_request.get('.support') - assert page.select_one('form')['method'] == 'post' - assert 'action' not in page.select_one('form') - assert normalize_spaces( - page.select_one('form label[for=who-0]').text - ) == ( - 'I work in the public sector and need to send emails or text messages' + page = client_request.get(".support") + assert page.select_one("form")["method"] == "post" + assert "action" not in page.select_one("form") + assert normalize_spaces(page.select_one("form label[for=who-0]").text) == ( + "I work in the public sector and need to send emails or text messages" ) - assert page.select_one('form input#who-0')['value'] == 'public-sector' - assert normalize_spaces( - page.select_one('form label[for=who-1]').text - ) == ( - 'I’m a member of the public with a question for the government' + assert page.select_one("form input#who-0")["value"] == "public-sector" + assert normalize_spaces(page.select_one("form label[for=who-1]").text) == ( + "I’m a member of the public with a question for the government" + ) + assert page.select_one("form input#who-1")["value"] == "public" + assert ( + normalize_spaces(page.select_one("form button[type=submit]").text) == "Continue" ) - assert page.select_one('form input#who-1')['value'] == 'public' - assert normalize_spaces( - page.select_one('form button[type=submit]').text - ) == 'Continue' -@freeze_time('2016-12-12 12:00:00.000000') -@pytest.mark.parametrize('support_type, expected_h1', [ - (PROBLEM_TICKET_TYPE, 'Report a problem'), - (QUESTION_TICKET_TYPE, 'Ask a question or give feedback'), -]) +@freeze_time("2016-12-12 12:00:00.000000") +@pytest.mark.parametrize( + "support_type, expected_h1", + [ + (PROBLEM_TICKET_TYPE, "Report a problem"), + (QUESTION_TICKET_TYPE, "Ask a question or give feedback"), + ], +) def test_choose_support_type( client_request, mock_get_non_empty_organizations_and_services_for_user, support_type, - expected_h1 + expected_h1, ): page = client_request.post( - 'main.support', - _data={'support_type': support_type}, + "main.support", + _data={"support_type": support_type}, _follow_redirects=True, ) assert page.h1.string.strip() == expected_h1 - assert not page.select_one('input[name=name]') - assert not page.select_one('input[name=email_address]') - assert page.find('form').find('p').text.strip() == ( - 'We’ll reply to test@user.gsa.gov' + assert not page.select_one("input[name=name]") + assert not page.select_one("input[name=email_address]") + assert page.find("form").find("p").text.strip() == ( + "We’ll reply to test@user.gsa.gov" ) -@freeze_time('2016-12-12 12:00:00.000000') +@freeze_time("2016-12-12 12:00:00.000000") def test_get_support_as_someone_in_the_public_sector( client_request, ): client_request.logout() page = client_request.post( - 'main.support', - _data={'who': 'public-sector'}, + "main.support", + _data={"who": "public-sector"}, _follow_redirects=True, ) - assert normalize_spaces(page.select('h1')) == ( - 'Contact Notify.gov support' - ) - assert page.select_one('form textarea[name=feedback]') - assert page.select_one('form input[name=name]') - assert page.select_one('form input[name=email_address]') - assert page.select_one('form button[type=submit]') + assert normalize_spaces(page.select("h1")) == ("Contact Notify.gov support") + assert page.select_one("form textarea[name=feedback]") + assert page.select_one("form input[name=name]") + assert page.select_one("form input[name=email_address]") + assert page.select_one("form button[type=submit]") def test_get_support_as_member_of_public( @@ -113,56 +115,64 @@ def test_get_support_as_member_of_public( ): client_request.logout() page = client_request.post( - 'main.support', - _data={'who': 'public'}, + "main.support", + _data={"who": "public"}, _follow_redirects=True, ) - assert normalize_spaces(page.select('h1')) == ( - 'The Notify.gov service is for people who work in the government' + assert normalize_spaces(page.select("h1")) == ( + "The Notify.gov service is for people who work in the government" ) - assert len(page.select('h2 a')) == 3 - assert not page.select('form') - assert not page.select('input') - assert not page.select('form [type=submit]') + assert len(page.select("h2 a")) == 3 + assert not page.select("form") + assert not page.select("input") + assert not page.select("form [type=submit]") -@freeze_time('2016-12-12 12:00:00.000000') -@pytest.mark.parametrize('ticket_type, expected_status_code', [ - (PROBLEM_TICKET_TYPE, 200), - (QUESTION_TICKET_TYPE, 200), - ('gripe', 404) -]) +@freeze_time("2016-12-12 12:00:00.000000") +@pytest.mark.parametrize( + "ticket_type, expected_status_code", + [(PROBLEM_TICKET_TYPE, 200), (QUESTION_TICKET_TYPE, 200), ("gripe", 404)], +) def test_get_feedback_page(client_request, ticket_type, expected_status_code): client_request.logout() client_request.get( - 'main.feedback', + "main.feedback", ticket_type=ticket_type, _expected_status=expected_status_code, ) -@freeze_time('2016-12-12 12:00:00.000000') -@pytest.mark.parametrize('ticket_type, zendesk_ticket_type', [ - (PROBLEM_TICKET_TYPE, 'incident'), - (QUESTION_TICKET_TYPE, 'question'), - (GENERAL_TICKET_TYPE, 'question'), -]) -def test_passed_non_logged_in_user_details_through_flow(client_request, mocker, ticket_type, zendesk_ticket_type): +@freeze_time("2016-12-12 12:00:00.000000") +@pytest.mark.parametrize( + "ticket_type, zendesk_ticket_type", + [ + (PROBLEM_TICKET_TYPE, "incident"), + (QUESTION_TICKET_TYPE, "question"), + (GENERAL_TICKET_TYPE, "question"), + ], +) +def test_passed_non_logged_in_user_details_through_flow( + client_request, mocker, ticket_type, zendesk_ticket_type +): client_request.logout() - mock_create_ticket = mocker.spy(NotifySupportTicket, '__init__') + mock_create_ticket = mocker.spy(NotifySupportTicket, "__init__") mock_send_ticket_to_zendesk = mocker.patch( - 'app.main.views.feedback.zendesk_client.send_ticket_to_zendesk', + "app.main.views.feedback.zendesk_client.send_ticket_to_zendesk", autospec=True, ) - data = {'feedback': 'blah', 'name': 'Anne Example', 'email_address': 'anne@example.com'} + data = { + "feedback": "blah", + "name": "Anne Example", + "email_address": "anne@example.com", + } client_request.post( - 'main.feedback', + "main.feedback", ticket_type=ticket_type, _data=data, _expected_redirect=url_for( - 'main.thanks', + "main.thanks", out_of_hours_emergency=False, email_address_provided=True, ), @@ -170,112 +180,121 @@ def test_passed_non_logged_in_user_details_through_flow(client_request, mocker, mock_create_ticket.assert_called_once_with( ANY, - subject='Notify feedback', - message='blah\n', + subject="Notify feedback", + message="blah\n", ticket_type=zendesk_ticket_type, p1=False, - user_name='Anne Example', - user_email='anne@example.com', + user_name="Anne Example", + user_email="anne@example.com", org_id=None, org_type=None, - service_id=None + service_id=None, ) mock_send_ticket_to_zendesk.assert_called_once() @freeze_time("2016-12-12 12:00:00.000000") -@pytest.mark.parametrize('data', [ - {'feedback': 'blah'}, - {'feedback': 'blah', 'name': 'Ignored', 'email_address': 'ignored@email.com'} -]) -@pytest.mark.parametrize('ticket_type, zendesk_ticket_type', [ - (PROBLEM_TICKET_TYPE, 'incident'), - (QUESTION_TICKET_TYPE, 'question'), - (GENERAL_TICKET_TYPE, 'question'), -]) +@pytest.mark.parametrize( + "data", + [ + {"feedback": "blah"}, + {"feedback": "blah", "name": "Ignored", "email_address": "ignored@email.com"}, + ], +) +@pytest.mark.parametrize( + "ticket_type, zendesk_ticket_type", + [ + (PROBLEM_TICKET_TYPE, "incident"), + (QUESTION_TICKET_TYPE, "question"), + (GENERAL_TICKET_TYPE, "question"), + ], +) def test_passes_user_details_through_flow( client_request, mock_get_non_empty_organizations_and_services_for_user, mocker, ticket_type, zendesk_ticket_type, - data + data, ): - mock_create_ticket = mocker.spy(NotifySupportTicket, '__init__') + mock_create_ticket = mocker.spy(NotifySupportTicket, "__init__") mock_send_ticket_to_zendesk = mocker.patch( - 'app.main.views.feedback.zendesk_client.send_ticket_to_zendesk', + "app.main.views.feedback.zendesk_client.send_ticket_to_zendesk", autospec=True, ) client_request.post( - 'main.feedback', + "main.feedback", ticket_type=ticket_type, _data=data, _expected_status=302, _expected_redirect=url_for( - 'main.thanks', + "main.thanks", email_address_provided=True, out_of_hours_emergency=False, ), ) mock_create_ticket.assert_called_once_with( ANY, - subject='Notify feedback', + subject="Notify feedback", message=ANY, ticket_type=zendesk_ticket_type, p1=False, - user_name='Test User', - user_email='test@user.gsa.gov', + user_name="Test User", + user_email="test@user.gsa.gov", org_id=None, - org_type='federal', - service_id=SERVICE_ONE_ID + org_type="federal", + service_id=SERVICE_ONE_ID, ) - assert mock_create_ticket.call_args[1]['message'] == '\n'.join([ - 'blah', - 'Service: "service one"', - url_for( - 'main.service_dashboard', - service_id=SERVICE_ONE_ID, - _external=True, - ), - '' - ]) + assert mock_create_ticket.call_args[1]["message"] == "\n".join( + [ + "blah", + 'Service: "service one"', + url_for( + "main.service_dashboard", + service_id=SERVICE_ONE_ID, + _external=True, + ), + "", + ] + ) mock_send_ticket_to_zendesk.assert_called_once() -@freeze_time('2016-12-12 12:00:00.000000') -@pytest.mark.parametrize('data', [ - {'feedback': 'blah', 'name': 'Fred'}, - {'feedback': 'blah'}, -]) -@pytest.mark.parametrize('ticket_type', [ - PROBLEM_TICKET_TYPE, - QUESTION_TICKET_TYPE, -]) +@freeze_time("2016-12-12 12:00:00.000000") +@pytest.mark.parametrize( + "data", + [ + {"feedback": "blah", "name": "Fred"}, + {"feedback": "blah"}, + ], +) +@pytest.mark.parametrize( + "ticket_type", + [ + PROBLEM_TICKET_TYPE, + QUESTION_TICKET_TYPE, + ], +) def test_email_address_required_for_problems_and_questions( client_request, mocker, data, ticket_type, ): - mocker.patch('app.main.views.feedback.zendesk_client') + mocker.patch("app.main.views.feedback.zendesk_client") client_request.logout() page = client_request.post( - 'main.feedback', - ticket_type=ticket_type, - _data=data, - _expected_status=200 + "main.feedback", ticket_type=ticket_type, _data=data, _expected_status=200 ) - assert normalize_spaces(page.select_one('.usa-error-message').text) == ( - 'Error: Cannot be empty' + assert normalize_spaces(page.select_one(".usa-error-message").text) == ( + "Error: Cannot be empty" ) -@freeze_time('2016-12-12 12:00:00.000000') -@pytest.mark.parametrize('ticket_type', ( - PROBLEM_TICKET_TYPE, QUESTION_TICKET_TYPE -)) +@freeze_time("2016-12-12 12:00:00.000000") +@pytest.mark.parametrize("ticket_type", (PROBLEM_TICKET_TYPE, QUESTION_TICKET_TYPE)) def test_email_address_must_be_valid_if_provided_to_support_form( client_request, mocker, @@ -283,37 +302,36 @@ def test_email_address_must_be_valid_if_provided_to_support_form( ): client_request.logout() page = client_request.post( - 'main.feedback', + "main.feedback", ticket_type=ticket_type, _data={ - 'feedback': 'blah', - 'email_address': 'not valid', + "feedback": "blah", + "email_address": "not valid", }, _expected_status=200, ) - assert normalize_spaces(page.select_one('span.usa-error-message').text) == ( - 'Error: Enter a valid email address' + assert normalize_spaces(page.select_one("span.usa-error-message").text) == ( + "Error: Enter a valid email address" ) -@pytest.mark.parametrize('ticket_type, severe, is_in_business_hours, is_out_of_hours_emergency', [ - - # business hours, never an emergency - (PROBLEM_TICKET_TYPE, 'yes', True, False), - (QUESTION_TICKET_TYPE, 'yes', True, False), - (PROBLEM_TICKET_TYPE, 'no', True, False), - (QUESTION_TICKET_TYPE, 'no', True, False), - - # out of hours, if the user says it’s not an emergency - (PROBLEM_TICKET_TYPE, 'no', False, False), - (QUESTION_TICKET_TYPE, 'no', False, False), - - # out of hours, only problems can be emergencies - (PROBLEM_TICKET_TYPE, 'yes', False, True), - (QUESTION_TICKET_TYPE, 'yes', False, False), - -]) +@pytest.mark.parametrize( + "ticket_type, severe, is_in_business_hours, is_out_of_hours_emergency", + [ + # business hours, never an emergency + (PROBLEM_TICKET_TYPE, "yes", True, False), + (QUESTION_TICKET_TYPE, "yes", True, False), + (PROBLEM_TICKET_TYPE, "no", True, False), + (QUESTION_TICKET_TYPE, "no", True, False), + # out of hours, if the user says it’s not an emergency + (PROBLEM_TICKET_TYPE, "no", False, False), + (QUESTION_TICKET_TYPE, "no", False, False), + # out of hours, only problems can be emergencies + (PROBLEM_TICKET_TYPE, "yes", False, True), + (QUESTION_TICKET_TYPE, "yes", False, False), + ], +) def test_urgency( client_request, mock_get_non_empty_organizations_and_services_for_user, @@ -323,59 +341,78 @@ def test_urgency( is_in_business_hours, is_out_of_hours_emergency, ): - mocker.patch('app.main.views.feedback.in_business_hours', return_value=is_in_business_hours) - - mock_ticket = mocker.patch('app.main.views.feedback.NotifySupportTicket') mocker.patch( - 'app.main.views.feedback.zendesk_client.send_ticket_to_zendesk', + "app.main.views.feedback.in_business_hours", return_value=is_in_business_hours + ) + + mock_ticket = mocker.patch("app.main.views.feedback.NotifySupportTicket") + mocker.patch( + "app.main.views.feedback.zendesk_client.send_ticket_to_zendesk", autospec=True, ) client_request.post( - 'main.feedback', + "main.feedback", ticket_type=ticket_type, severe=severe, - _data={'feedback': 'blah', 'email_address': 'test@example.com'}, + _data={"feedback": "blah", "email_address": "test@example.com"}, _expected_status=302, _expected_redirect=url_for( - 'main.thanks', + "main.thanks", out_of_hours_emergency=is_out_of_hours_emergency, email_address_provided=True, ), ) - assert mock_ticket.call_args[1]['p1'] == is_out_of_hours_emergency + assert mock_ticket.call_args[1]["p1"] == is_out_of_hours_emergency -ids, params = zip(*[ - ('non-logged in users always have to triage', ( - GENERAL_TICKET_TYPE, False, False, True, - 302, partial(url_for, 'main.triage', ticket_type=GENERAL_TICKET_TYPE) - )), - ('trial services are never high priority', ( - PROBLEM_TICKET_TYPE, False, True, False, - 200, no_redirect() - )), - ('we can triage in hours', ( - PROBLEM_TICKET_TYPE, True, True, True, - 200, no_redirect() - )), - ('only problems are high priority', ( - QUESTION_TICKET_TYPE, False, True, True, - 200, no_redirect() - )), - ('should triage out of hours', ( - PROBLEM_TICKET_TYPE, False, True, True, - 302, partial(url_for, 'main.triage', ticket_type=PROBLEM_TICKET_TYPE) - )) -]) +ids, params = zip( + *[ + ( + "non-logged in users always have to triage", + ( + GENERAL_TICKET_TYPE, + False, + False, + True, + 302, + partial(url_for, "main.triage", ticket_type=GENERAL_TICKET_TYPE), + ), + ), + ( + "trial services are never high priority", + (PROBLEM_TICKET_TYPE, False, True, False, 200, no_redirect()), + ), + ( + "we can triage in hours", + (PROBLEM_TICKET_TYPE, True, True, True, 200, no_redirect()), + ), + ( + "only problems are high priority", + (QUESTION_TICKET_TYPE, False, True, True, 200, no_redirect()), + ), + ( + "should triage out of hours", + ( + PROBLEM_TICKET_TYPE, + False, + True, + True, + 302, + partial(url_for, "main.triage", ticket_type=PROBLEM_TICKET_TYPE), + ), + ), + ] +) @pytest.mark.parametrize( ( - 'ticket_type, is_in_business_hours, logged_in, has_live_services,' - 'expected_status, expected_redirect' + "ticket_type, is_in_business_hours, logged_in, has_live_services," + "expected_status, expected_redirect" ), - params, ids=ids + params, + ids=ids, ) def test_redirects_to_triage( client_request, @@ -390,96 +427,104 @@ def test_redirects_to_triage( expected_redirect, ): mocker.patch( - 'app.models.user.User.live_services', + "app.models.user.User.live_services", new_callable=PropertyMock, return_value=[{}, {}] if has_live_services else [], ) - mocker.patch('app.main.views.feedback.in_business_hours', return_value=is_in_business_hours) + mocker.patch( + "app.main.views.feedback.in_business_hours", return_value=is_in_business_hours + ) if not logged_in: client_request.logout() client_request.get( - 'main.feedback', + "main.feedback", ticket_type=ticket_type, _expected_status=expected_status, _expected_redirect=expected_redirect(), ) -@pytest.mark.parametrize('ticket_type, expected_h1', ( - (PROBLEM_TICKET_TYPE, 'Report a problem'), - (GENERAL_TICKET_TYPE, 'Contact Notify.gov support'), -)) +@pytest.mark.parametrize( + "ticket_type, expected_h1", + ( + (PROBLEM_TICKET_TYPE, "Report a problem"), + (GENERAL_TICKET_TYPE, "Contact Notify.gov support"), + ), +) def test_options_on_triage_page( client_request, ticket_type, expected_h1, ): - page = client_request.get('main.triage', ticket_type=ticket_type) - assert normalize_spaces(page.select_one('h1').text) == expected_h1 - assert page.select('form input[type=radio]')[0]['value'] == 'yes' - assert page.select('form input[type=radio]')[1]['value'] == 'no' + page = client_request.get("main.triage", ticket_type=ticket_type) + assert normalize_spaces(page.select_one("h1").text) == expected_h1 + assert page.select("form input[type=radio]")[0]["value"] == "yes" + assert page.select("form input[type=radio]")[1]["value"] == "no" def test_doesnt_lose_message_if_post_across_closing( client_request, mocker, ): - - mocker.patch('app.models.user.User.live_services', return_value=True) - mocker.patch('app.main.views.feedback.in_business_hours', return_value=False) + mocker.patch("app.models.user.User.live_services", return_value=True) + mocker.patch("app.main.views.feedback.in_business_hours", return_value=False) page = client_request.post( - 'main.feedback', + "main.feedback", ticket_type=PROBLEM_TICKET_TYPE, - _data={'feedback': 'foo'}, + _data={"feedback": "foo"}, _expected_status=302, - _expected_redirect=url_for('.triage', ticket_type=PROBLEM_TICKET_TYPE), + _expected_redirect=url_for(".triage", ticket_type=PROBLEM_TICKET_TYPE), ) with client_request.session_transaction() as session: - assert session['feedback_message'] == 'foo' + assert session["feedback_message"] == "foo" page = client_request.get( - 'main.feedback', + "main.feedback", ticket_type=PROBLEM_TICKET_TYPE, - severe='yes', + severe="yes", ) with client_request.session_transaction() as session: - assert page.find('textarea', {'name': 'feedback'}).text == '\r\nfoo' - assert 'feedback_message' not in session + assert page.find("textarea", {"name": "feedback"}).text == "\r\nfoo" + assert "feedback_message" not in session -@pytest.mark.parametrize('when, is_in_business_hours', [ - - ('2016-06-06 09:29:59+0100', False), # opening time, summer and winter - ('2016-12-12 09:29:59+0000', False), - ('2016-06-06 09:30:00+0100', True), - ('2016-12-12 09:30:00+0000', True), - - ('2016-12-12 12:00:00+0000', True), # middle of the day - - ('2016-12-12 17:29:59+0000', True), # closing time - ('2016-12-12 17:30:00+0000', False), - - ('2016-12-10 12:00:00+0000', False), # Saturday - ('2016-12-11 12:00:00+0000', False), # Sunday - ('2016-01-01 12:00:00+0000', False), # Bank holiday - -]) +@pytest.mark.parametrize( + "when, is_in_business_hours", + [ + ("2016-06-06 09:29:59+0100", False), # opening time, summer and winter + ("2016-12-12 09:29:59+0000", False), + ("2016-06-06 09:30:00+0100", True), + ("2016-12-12 09:30:00+0000", True), + ("2016-12-12 12:00:00+0000", True), # middle of the day + ("2016-12-12 17:29:59+0000", True), # closing time + ("2016-12-12 17:30:00+0000", False), + ("2016-12-10 12:00:00+0000", False), # Saturday + ("2016-12-11 12:00:00+0000", False), # Sunday + ("2016-01-01 12:00:00+0000", False), # Bank holiday + ], +) def test_in_business_hours(when, is_in_business_hours): with freeze_time(when): assert in_business_hours() == is_in_business_hours -@pytest.mark.parametrize('ticket_type', ( - GENERAL_TICKET_TYPE, - PROBLEM_TICKET_TYPE, -)) -@pytest.mark.parametrize('choice, expected_redirect_param', [ - ('yes', 'yes'), - ('no', 'no'), -]) +@pytest.mark.parametrize( + "ticket_type", + ( + GENERAL_TICKET_TYPE, + PROBLEM_TICKET_TYPE, + ), +) +@pytest.mark.parametrize( + "choice, expected_redirect_param", + [ + ("yes", "yes"), + ("no", "no"), + ], +) def test_triage_redirects_to_correct_url( client_request, ticket_type, @@ -487,37 +532,34 @@ def test_triage_redirects_to_correct_url( expected_redirect_param, ): client_request.post( - 'main.triage', + "main.triage", ticket_type=ticket_type, - _data={'severe': choice}, + _data={"severe": choice}, _expected_status=302, _expected_redirect=url_for( - 'main.feedback', + "main.feedback", ticket_type=ticket_type, severe=expected_redirect_param, ), ) -@pytest.mark.parametrize('extra_args, expected_back_link', [ - ( - {'severe': 'yes'}, - partial(url_for, 'main.triage', ticket_type=PROBLEM_TICKET_TYPE) - ), - ( - {'severe': 'no'}, - partial(url_for, 'main.triage', ticket_type=PROBLEM_TICKET_TYPE) - ), - ( - {'severe': 'foo'}, # hacking the URL - partial(url_for, 'main.support') - ), - ( - {}, - partial(url_for, 'main.support') - ), -]) -@freeze_time('2012-12-12 12:12') +@pytest.mark.parametrize( + "extra_args, expected_back_link", + [ + ( + {"severe": "yes"}, + partial(url_for, "main.triage", ticket_type=PROBLEM_TICKET_TYPE), + ), + ( + {"severe": "no"}, + partial(url_for, "main.triage", ticket_type=PROBLEM_TICKET_TYPE), + ), + ({"severe": "foo"}, partial(url_for, "main.support")), # hacking the URL + ({}, partial(url_for, "main.support")), + ], +) +@freeze_time("2012-12-12 12:12") def test_back_link_from_form( client_request, mock_get_non_empty_organizations_and_services_for_user, @@ -525,58 +567,57 @@ def test_back_link_from_form( expected_back_link, ): page = client_request.get( - 'main.feedback', - ticket_type=PROBLEM_TICKET_TYPE, - **extra_args + "main.feedback", ticket_type=PROBLEM_TICKET_TYPE, **extra_args ) - assert page.select_one('.usa-back-link')['href'] == expected_back_link() - assert normalize_spaces(page.select_one('h1').text) == 'Report a problem' + assert page.select_one(".usa-back-link")["href"] == expected_back_link() + assert normalize_spaces(page.select_one("h1").text) == "Report a problem" @pytest.mark.parametrize( ( - 'is_in_business_hours, severe,' - 'expected_status_code, expected_redirect,' - 'expected_status_code_when_logged_in, expected_redirect_when_logged_in' + "is_in_business_hours, severe," + "expected_status_code, expected_redirect," + "expected_status_code_when_logged_in, expected_redirect_when_logged_in" ), [ + (True, "yes", 200, no_redirect(), 200, no_redirect()), + (True, "no", 200, no_redirect(), 200, no_redirect()), ( - True, 'yes', - 200, no_redirect(), - 200, no_redirect() + False, + "no", + 200, + no_redirect(), + 200, + no_redirect(), ), - ( - True, 'no', - 200, no_redirect(), - 200, no_redirect() - ), - ( - False, 'no', - 200, no_redirect(), - 200, no_redirect(), - ), - # Treat empty query param as mangled URL – ask question again ( - False, '', - 302, partial(url_for, 'main.triage', ticket_type=PROBLEM_TICKET_TYPE), - 302, partial(url_for, 'main.triage', ticket_type=PROBLEM_TICKET_TYPE), + False, + "", + 302, + partial(url_for, "main.triage", ticket_type=PROBLEM_TICKET_TYPE), + 302, + partial(url_for, "main.triage", ticket_type=PROBLEM_TICKET_TYPE), ), - # User hasn’t answered the triage question ( - False, None, - 302, partial(url_for, 'main.triage', ticket_type=PROBLEM_TICKET_TYPE), - 302, partial(url_for, 'main.triage', ticket_type=PROBLEM_TICKET_TYPE), + False, + None, + 302, + partial(url_for, "main.triage", ticket_type=PROBLEM_TICKET_TYPE), + 302, + partial(url_for, "main.triage", ticket_type=PROBLEM_TICKET_TYPE), ), - # Escalation is needed for non-logged-in users ( - False, 'yes', - 302, partial(url_for, 'main.bat_phone'), - 200, no_redirect(), + False, + "yes", + 302, + partial(url_for, "main.bat_phone"), + 200, + no_redirect(), ), - ] + ], ) def test_should_be_shown_the_bat_email( client_request, @@ -591,10 +632,13 @@ def test_should_be_shown_the_bat_email( expected_status_code_when_logged_in, expected_redirect_when_logged_in, ): + mocker.patch( + "app.main.views.feedback.in_business_hours", return_value=is_in_business_hours + ) - mocker.patch('app.main.views.feedback.in_business_hours', return_value=is_in_business_hours) - - feedback_page = url_for('main.feedback', ticket_type=PROBLEM_TICKET_TYPE, severe=severe) + feedback_page = url_for( + "main.feedback", ticket_type=PROBLEM_TICKET_TYPE, severe=severe + ) client_request.logout() client_request.get_url( @@ -614,25 +658,28 @@ def test_should_be_shown_the_bat_email( @pytest.mark.parametrize( ( - 'severe,' - 'expected_status_code, expected_redirect,' - 'expected_status_code_when_logged_in, expected_redirect_when_logged_in' + "severe," + "expected_status_code, expected_redirect," + "expected_status_code_when_logged_in, expected_redirect_when_logged_in" ), [ # User hasn’t answered the triage question ( None, - 302, partial(url_for, 'main.triage', ticket_type=GENERAL_TICKET_TYPE), - 302, partial(url_for, 'main.triage', ticket_type=GENERAL_TICKET_TYPE), + 302, + partial(url_for, "main.triage", ticket_type=GENERAL_TICKET_TYPE), + 302, + partial(url_for, "main.triage", ticket_type=GENERAL_TICKET_TYPE), ), - # Escalation is needed for non-logged-in users ( - 'yes', - 302, partial(url_for, 'main.bat_phone'), - 200, no_redirect(), + "yes", + 302, + partial(url_for, "main.bat_phone"), + 200, + no_redirect(), ), - ] + ], ) def test_should_be_shown_the_bat_email_for_general_questions( client_request, @@ -646,10 +693,11 @@ def test_should_be_shown_the_bat_email_for_general_questions( expected_status_code_when_logged_in, expected_redirect_when_logged_in, ): + mocker.patch("app.main.views.feedback.in_business_hours", return_value=False) - mocker.patch('app.main.views.feedback.in_business_hours', return_value=False) - - feedback_page = url_for('main.feedback', ticket_type=GENERAL_TICKET_TYPE, severe=severe) + feedback_page = url_for( + "main.feedback", ticket_type=GENERAL_TICKET_TYPE, severe=severe + ) client_request.logout() client_request.get_url( @@ -673,59 +721,67 @@ def test_bat_email_page( mocker, service_one, ): - bat_phone_page = 'main.bat_phone' + bat_phone_page = "main.bat_phone" client_request.logout() page = client_request.get(bat_phone_page) - assert page.select_one('.usa-back-link').text == 'Back' - assert page.select_one('.usa-back-link')['href'] == url_for('main.support') - assert page.select('main a')[1].text == 'Fill in this form' - assert page.select('main a')[1]['href'] == url_for('main.feedback', ticket_type=PROBLEM_TICKET_TYPE, severe='no') - next_page = client_request.get_url(page.select('main a')[1]['href']) - assert next_page.h1.text.strip() == 'Report a problem' + assert page.select_one(".usa-back-link").text == "Back" + assert page.select_one(".usa-back-link")["href"] == url_for("main.support") + assert page.select("main a")[1].text == "Fill in this form" + assert page.select("main a")[1]["href"] == url_for( + "main.feedback", ticket_type=PROBLEM_TICKET_TYPE, severe="no" + ) + next_page = client_request.get_url(page.select("main a")[1]["href"]) + assert next_page.h1.text.strip() == "Report a problem" client_request.login(active_user_with_permissions) client_request.get( bat_phone_page, - _expected_redirect=url_for('main.feedback', ticket_type=PROBLEM_TICKET_TYPE) + _expected_redirect=url_for("main.feedback", ticket_type=PROBLEM_TICKET_TYPE), ) -@pytest.mark.parametrize('out_of_hours_emergency, email_address_provided, out_of_hours, message', ( - - # Out of hours emergencies trump everything else +@pytest.mark.parametrize( + "out_of_hours_emergency, email_address_provided, out_of_hours, message", ( - True, True, True, - 'We’ll reply in the next 30 minutes.', + # Out of hours emergencies trump everything else + ( + True, + True, + True, + "We’ll reply in the next 30 minutes.", + ), + ( + True, + False, + False, # Not a real scenario + "We’ll reply in the next 30 minutes.", + ), + # Anonymous tickets don’t promise a reply + ( + False, + False, + False, + "We’ll aim to read your message in the next 30 minutes.", + ), + ( + False, + False, + True, + "We’ll read your message when we’re back in the office.", + ), + # When we look at your ticket depends on whether we’re in normal + # business hours + ( + False, + True, + False, + "We’ll aim to read your message in the next 30 minutes and we’ll reply within one working day.", + ), + (False, True, True, "We’ll reply within one working day."), ), - ( - True, False, False, # Not a real scenario - 'We’ll reply in the next 30 minutes.', - ), - - # Anonymous tickets don’t promise a reply - ( - False, False, False, - 'We’ll aim to read your message in the next 30 minutes.', - ), - ( - False, False, True, - 'We’ll read your message when we’re back in the office.', - ), - - # When we look at your ticket depends on whether we’re in normal - # business hours - ( - False, True, False, - 'We’ll aim to read your message in the next 30 minutes and we’ll reply within one working day.', - ), - ( - False, True, True, - 'We’ll reply within one working day.' - ), - -)) +) def test_thanks( client_request, mocker, @@ -736,10 +792,12 @@ def test_thanks( out_of_hours, message, ): - mocker.patch('app.main.views.feedback.in_business_hours', return_value=(not out_of_hours)) + mocker.patch( + "app.main.views.feedback.in_business_hours", return_value=(not out_of_hours) + ) page = client_request.get( - 'main.thanks', + "main.thanks", out_of_hours_emergency=out_of_hours_emergency, email_address_provided=email_address_provided, ) - assert normalize_spaces(page.find('main').find('p').text) == message + assert normalize_spaces(page.find("main").find("p").text) == message diff --git a/tests/app/main/views/test_find_services.py b/tests/app/main/views/test_find_services.py index 0784ce8fb..f8a6072bf 100644 --- a/tests/app/main/views/test_find_services.py +++ b/tests/app/main/views/test_find_services.py @@ -3,90 +3,103 @@ from flask import url_for from tests import service_json -def test_find_services_by_name_page_loads_correctly(client_request, platform_admin_user): +def test_find_services_by_name_page_loads_correctly( + client_request, platform_admin_user +): client_request.login(platform_admin_user) - document = client_request.get('main.find_services_by_name') + document = client_request.get("main.find_services_by_name") - assert document.h1.text.strip() == 'Find services by name' - assert len(document.find_all('input', {'type': 'search'})) > 0 + assert document.h1.text.strip() == "Find services by name" + assert len(document.find_all("input", {"type": "search"})) > 0 def test_find_services_by_name_displays_services_found( - client_request, - platform_admin_user, - mocker + client_request, platform_admin_user, mocker ): client_request.login(platform_admin_user) get_services = mocker.patch( - 'app.service_api_client.find_services_by_name', - return_value={"data": [service_json()]} + "app.service_api_client.find_services_by_name", + return_value={"data": [service_json()]}, ) document = client_request.post( - 'main.find_services_by_name', + "main.find_services_by_name", _data={"search": "Test Service"}, - _expected_status=200 + _expected_status=200, ) get_services.assert_called_once_with(service_name="Test Service") - result = document.select_one('.browse-list-item a') - assert result.text.strip() == 'Test Service' + result = document.select_one(".browse-list-item a") + assert result.text.strip() == "Test Service" assert result.attrs["href"] == "/services/1234" def test_find_services_by_name_displays_multiple_services( - client_request, - platform_admin_user, - mocker + client_request, platform_admin_user, mocker ): client_request.login(platform_admin_user) mocker.patch( - 'app.service_api_client.find_services_by_name', - return_value={"data": [service_json(name="Tadfield Police"), service_json(name="Tadfield Air Base")]} + "app.service_api_client.find_services_by_name", + return_value={ + "data": [ + service_json(name="Tadfield Police"), + service_json(name="Tadfield Air Base"), + ] + }, + ) + document = client_request.post( + "main.find_services_by_name", _data={"search": "Tadfield"}, _expected_status=200 ) - document = client_request.post('main.find_services_by_name', _data={"search": "Tadfield"}, _expected_status=200) - results = document.find_all('li', {'class': 'browse-list-item'}) + results = document.find_all("li", {"class": "browse-list-item"}) assert len(results) == 2 - assert sorted([result.text.strip() for result in results]) == ["Tadfield Air Base", "Tadfield Police"] + assert sorted([result.text.strip() for result in results]) == [ + "Tadfield Air Base", + "Tadfield Police", + ] def test_find_services_by_name_displays_message_if_no_services_found( - client_request, - platform_admin_user, - mocker + client_request, platform_admin_user, mocker ): client_request.login(platform_admin_user) - mocker.patch('app.service_api_client.find_services_by_name', return_value={"data": []}) + mocker.patch( + "app.service_api_client.find_services_by_name", return_value={"data": []} + ) document = client_request.post( - 'main.find_services_by_name', _data={"search": "Nabuchodonosorian Empire"}, _expected_status=200 + "main.find_services_by_name", + _data={"search": "Nabuchodonosorian Empire"}, + _expected_status=200, ) - assert document.find('p', {'class': 'browse-list-hint'}).text.strip() == 'No services found.' + assert ( + document.find("p", {"class": "browse-list-hint"}).text.strip() + == "No services found." + ) def test_find_services_by_name_validates_against_empty_search_submission( - client_request, - platform_admin_user, - mocker + client_request, platform_admin_user, mocker ): client_request.login(platform_admin_user) - document = client_request.post('main.find_services_by_name', _data={"search": ""}, _expected_status=200) + document = client_request.post( + "main.find_services_by_name", _data={"search": ""}, _expected_status=200 + ) expected_message = "Error: You need to enter full or partial name to search by." - assert document.find('span', {'class': 'usa-error-message'}).text.strip() == expected_message + assert ( + document.find("span", {"class": "usa-error-message"}).text.strip() + == expected_message + ) def test_find_services_by_name_redirects_for_uuid( - client_request, - platform_admin_user, - mocker, - fake_uuid + client_request, platform_admin_user, mocker, fake_uuid ): client_request.login(platform_admin_user) client_request.post( - 'main.find_services_by_name', + "main.find_services_by_name", _data={"search": fake_uuid}, _expected_redirect=url_for( - 'main.service_dashboard', + "main.service_dashboard", service_id=fake_uuid, ), ) diff --git a/tests/app/main/views/test_find_users.py b/tests/app/main/views/test_find_users.py index 762d6ec7d..c5f93cb1c 100644 --- a/tests/app/main/views/test_find_users.py +++ b/tests/app/main/views/test_find_users.py @@ -10,84 +10,105 @@ from tests.conftest import normalize_spaces def test_find_users_by_email_page_loads_correctly(client_request, platform_admin_user): client_request.login(platform_admin_user) - document = client_request.get('main.find_users_by_email') + document = client_request.get("main.find_users_by_email") - assert document.h1.text.strip() == 'Find users by email' - assert len(document.find_all('input', {'type': 'search'})) > 0 + assert document.h1.text.strip() == "Find users by email" + assert len(document.find_all("input", {"type": "search"})) > 0 def test_find_users_by_email_displays_users_found( - client_request, - platform_admin_user, - mocker + client_request, platform_admin_user, mocker ): client_request.login(platform_admin_user) mocker.patch( - 'app.user_api_client.find_users_by_full_or_partial_email', + "app.user_api_client.find_users_by_full_or_partial_email", return_value={"data": [user_json()]}, autospec=True, ) document = client_request.post( - 'main.find_users_by_email', + "main.find_users_by_email", _data={"search": "twilight.sparkle"}, - _expected_status=200 + _expected_status=200, ) - assert any(element.text.strip() == 'test@gsa.gov' for element in document.find_all( - 'a', {'class': 'browse-list-link'}, href=True) + assert any( + element.text.strip() == "test@gsa.gov" + for element in document.find_all("a", {"class": "browse-list-link"}, href=True) + ) + assert any( + element.text.strip() == "Test User" + for element in document.find_all("span", {"class": "browse-list-hint"}) ) - assert any(element.text.strip() == 'Test User' for element in document.find_all( - 'span', {'class': 'browse-list-hint'})) - assert document.find('a', {'class': 'browse-list-link'}).text.strip() == 'test@gsa.gov' - assert document.find('span', {'class': 'browse-list-hint'}).text.strip() == 'Test User' + assert ( + document.find("a", {"class": "browse-list-link"}).text.strip() == "test@gsa.gov" + ) + assert ( + document.find("span", {"class": "browse-list-hint"}).text.strip() == "Test User" + ) def test_find_users_by_email_displays_multiple_users( - client_request, - platform_admin_user, - mocker + client_request, platform_admin_user, mocker ): client_request.login(platform_admin_user) mocker.patch( - 'app.user_api_client.find_users_by_full_or_partial_email', - return_value={"data": [user_json(name="Apple Jack"), user_json(name="Apple Bloom")]}, + "app.user_api_client.find_users_by_full_or_partial_email", + return_value={ + "data": [user_json(name="Apple Jack"), user_json(name="Apple Bloom")] + }, autospec=True, ) - document = client_request.post('main.find_users_by_email', _data={"search": "apple"}, _expected_status=200) + document = client_request.post( + "main.find_users_by_email", _data={"search": "apple"}, _expected_status=200 + ) assert any( - element.text.strip() == 'Apple Jack' for element in document.find_all('span', {'class': 'browse-list-hint'}) + element.text.strip() == "Apple Jack" + for element in document.find_all("span", {"class": "browse-list-hint"}) ) assert any( - element.text.strip() == 'Apple Bloom' for element in document.find_all('span', {'class': 'browse-list-hint'}) + element.text.strip() == "Apple Bloom" + for element in document.find_all("span", {"class": "browse-list-hint"}) ) def test_find_users_by_email_displays_message_if_no_users_found( - client_request, - platform_admin_user, - mocker + client_request, platform_admin_user, mocker ): client_request.login(platform_admin_user) - mocker.patch('app.user_api_client.find_users_by_full_or_partial_email', return_value={"data": []}, autospec=True) + mocker.patch( + "app.user_api_client.find_users_by_full_or_partial_email", + return_value={"data": []}, + autospec=True, + ) document = client_request.post( - 'main.find_users_by_email', _data={"search": "twilight.sparkle"}, _expected_status=200 + "main.find_users_by_email", + _data={"search": "twilight.sparkle"}, + _expected_status=200, ) - assert document.find('p', {'class': 'browse-list-hint'}).text.strip() == 'No users found.' + assert ( + document.find("p", {"class": "browse-list-hint"}).text.strip() + == "No users found." + ) def test_find_users_by_email_validates_against_empty_search_submission( - client_request, - platform_admin_user, - mocker + client_request, platform_admin_user, mocker ): client_request.login(platform_admin_user) - document = client_request.post('main.find_users_by_email', _data={"search": ""}, _expected_status=200) + document = client_request.post( + "main.find_users_by_email", _data={"search": ""}, _expected_status=200 + ) - expected_message = "Error: You need to enter full or partial email address to search by." - assert document.find('span', {'class': 'usa-error-message'}).text.strip() == expected_message + expected_message = ( + "Error: You need to enter full or partial email address to search by." + ) + assert ( + document.find("span", {"class": "usa-error-message"}).text.strip() + == expected_message + ) def test_user_information_page_shows_information_about_user( @@ -99,47 +120,52 @@ def test_user_information_page_shows_information_about_user( client_request.login(platform_admin_user) user_service_one = uuid.uuid4() user_service_two = uuid.uuid4() - mocker.patch('app.user_api_client.get_user', side_effect=[ - user_json(name="Apple Bloom", services=[user_service_one, user_service_two]) - ], autospec=True) + mocker.patch( + "app.user_api_client.get_user", + side_effect=[ + user_json(name="Apple Bloom", services=[user_service_one, user_service_two]) + ], + autospec=True, + ) mocker.patch( - 'app.user_api_client.get_organizations_and_services_for_user', - return_value={'organizations': [], 'services': [ - {"id": user_service_one, "name": "Fresh Orchard Juice", "restricted": True}, - {"id": user_service_two, "name": "Nature Therapy", "restricted": False}, - ]}, - autospec=True + "app.user_api_client.get_organizations_and_services_for_user", + return_value={ + "organizations": [], + "services": [ + { + "id": user_service_one, + "name": "Fresh Orchard Juice", + "restricted": True, + }, + {"id": user_service_two, "name": "Nature Therapy", "restricted": False}, + ], + }, + autospec=True, ) - page = client_request.get('main.user_information', user_id=fake_uuid) + page = client_request.get("main.user_information", user_id=fake_uuid) - assert normalize_spaces(page.select_one('h1').text) == 'Apple Bloom' + assert normalize_spaces(page.select_one("h1").text) == "Apple Bloom" - assert [ - normalize_spaces(p.text) for p in page.select('main p') - ] == [ - 'test@gsa.gov', - '+12028675109', - 'Text message code', - 'Last logged in just now', + assert [normalize_spaces(p.text) for p in page.select("main p")] == [ + "test@gsa.gov", + "+12028675109", + "Text message code", + "Last logged in just now", ] - assert '0 failed login attempts' not in page.text + assert "0 failed login attempts" not in page.text - assert [ - normalize_spaces(h2.text) for h2 in page.select('main h2') - ] == [ - 'Live services', - 'Trial mode services', - 'Authentication', - 'Last login', + assert [normalize_spaces(h2.text) for h2 in page.select("main h2")] == [ + "Live services", + "Trial mode services", + "Authentication", + "Last login", ] - assert [ - normalize_spaces(a.text) for a in page.select('main li a') - ] == [ - 'Nature Therapy', - 'Fresh Orchard Juice', + assert [normalize_spaces(a.text) for a in page.select("main li a")] == [ + "Nature Therapy", + "Fresh Orchard Juice", ] @@ -148,74 +174,80 @@ def test_user_information_page_shows_change_auth_type_link( platform_admin_user, api_user_active, mock_get_organizations_and_services_for_user, - mocker -): - client_request.login(platform_admin_user) - mocker.patch('app.user_api_client.get_user', side_effect=[ - user_json(id_=api_user_active['id'], name="Apple Bloom", auth_type='sms_auth') - ], autospec=True) - - page = client_request.get( - 'main.user_information', user_id=api_user_active['id'] - ) - change_auth_url = url_for('main.change_user_auth', user_id=api_user_active['id']) - - link = page.find('a', {'href': change_auth_url}) - assert normalize_spaces(link.text) == 'Change authentication for this user' - - -@pytest.mark.parametrize('current_auth_type', ['email_auth', 'sms_auth']) -def test_change_user_auth_preselects_current_auth_type( - client_request, - platform_admin_user, - api_user_active, mocker, - current_auth_type +): + client_request.login(platform_admin_user) + mocker.patch( + "app.user_api_client.get_user", + side_effect=[ + user_json( + id_=api_user_active["id"], name="Apple Bloom", auth_type="sms_auth" + ) + ], + autospec=True, + ) + + page = client_request.get("main.user_information", user_id=api_user_active["id"]) + change_auth_url = url_for("main.change_user_auth", user_id=api_user_active["id"]) + + link = page.find("a", {"href": change_auth_url}) + assert normalize_spaces(link.text) == "Change authentication for this user" + + +@pytest.mark.parametrize("current_auth_type", ["email_auth", "sms_auth"]) +def test_change_user_auth_preselects_current_auth_type( + client_request, platform_admin_user, api_user_active, mocker, current_auth_type ): client_request.login(platform_admin_user) - mocker.patch('app.user_api_client.get_user', side_effect=[ - user_json(id_=api_user_active['id'], name="Apple Bloom", auth_type=current_auth_type) - ], autospec=True) + mocker.patch( + "app.user_api_client.get_user", + side_effect=[ + user_json( + id_=api_user_active["id"], + name="Apple Bloom", + auth_type=current_auth_type, + ) + ], + autospec=True, + ) checked_radios = client_request.get( - 'main.change_user_auth', - user_id=api_user_active['id'], - ).select( - '.govuk-radios__item input[checked]' - ) + "main.change_user_auth", + user_id=api_user_active["id"], + ).select(".govuk-radios__item input[checked]") assert len(checked_radios) == 1 - assert checked_radios[0]['value'] == current_auth_type + assert checked_radios[0]["value"] == current_auth_type -def test_change_user_auth( - client_request, - platform_admin_user, - api_user_active, - mocker -): - +def test_change_user_auth(client_request, platform_admin_user, api_user_active, mocker): client_request.login(platform_admin_user) - mocker.patch('app.user_api_client.get_user', side_effect=[ - user_json(id_=api_user_active['id'], name="Apple Bloom", auth_type='sms_auth') - ], autospec=True) + mocker.patch( + "app.user_api_client.get_user", + side_effect=[ + user_json( + id_=api_user_active["id"], name="Apple Bloom", auth_type="sms_auth" + ) + ], + autospec=True, + ) - mock_update = mocker.patch('app.user_api_client.update_user_attribute') + mock_update = mocker.patch("app.user_api_client.update_user_attribute") client_request.post( - 'main.change_user_auth', - user_id=api_user_active['id'], - _data={ - 'auth_type': 'email_auth' - }, - _expected_redirect=url_for('main.user_information', user_id=api_user_active['id']) + "main.change_user_auth", + user_id=api_user_active["id"], + _data={"auth_type": "email_auth"}, + _expected_redirect=url_for( + "main.user_information", user_id=api_user_active["id"] + ), ) mock_update.assert_called_once_with( - api_user_active['id'], - auth_type='email_auth', + api_user_active["id"], + auth_type="email_auth", ) @@ -226,19 +258,21 @@ def test_user_information_page_displays_if_there_are_failed_login_attempts( fake_uuid, ): client_request.login(platform_admin_user) - mocker.patch('app.user_api_client.get_user', side_effect=[ - user_json(name="Apple Bloom", failed_login_count=2) - ], autospec=True) + mocker.patch( + "app.user_api_client.get_user", + side_effect=[user_json(name="Apple Bloom", failed_login_count=2)], + autospec=True, + ) mocker.patch( - 'app.user_api_client.get_organizations_and_services_for_user', - return_value={'organizations': [], 'services': []}, - autospec=True + "app.user_api_client.get_organizations_and_services_for_user", + return_value={"organizations": [], "services": []}, + autospec=True, ) - page = client_request.get('main.user_information', user_id=fake_uuid) + page = client_request.get("main.user_information", user_id=fake_uuid) - assert normalize_spaces(page.select('main p')[-1].text) == ( - '2 failed login attempts' + assert normalize_spaces(page.select("main p")[-1].text) == ( + "2 failed login attempts" ) @@ -249,13 +283,11 @@ def test_user_information_page_shows_archive_link_for_active_users( mock_get_organizations_and_services_for_user, ): client_request.login(platform_admin_user) - page = client_request.get( - 'main.user_information', user_id=api_user_active['id'] - ) - archive_url = url_for('main.archive_user', user_id=api_user_active['id']) + page = client_request.get("main.user_information", user_id=api_user_active["id"]) + archive_url = url_for("main.archive_user", user_id=api_user_active["id"]) - link = page.find('a', {'href': archive_url}) - assert normalize_spaces(link.text) == 'Archive user' + link = page.find("a", {"href": archive_url}) + assert normalize_spaces(link.text) == "Archive user" def test_user_information_page_does_not_show_archive_link_for_inactive_users( @@ -265,16 +297,18 @@ def test_user_information_page_does_not_show_archive_link_for_inactive_users( mock_get_organizations_and_services_for_user, ): inactive_user_id = uuid.uuid4() - inactive_user = user_json(id_=inactive_user_id, state='inactive') + inactive_user = user_json(id_=inactive_user_id, state="inactive") client_request.login(platform_admin_user) - mocker.patch('app.user_api_client.get_user', side_effect=[platform_admin_user, inactive_user], autospec=True) - - page = client_request.get( - 'main.user_information', user_id=inactive_user_id + mocker.patch( + "app.user_api_client.get_user", + side_effect=[platform_admin_user, inactive_user], + autospec=True, ) - archive_url = url_for('main.archive_user', user_id=inactive_user_id) - assert not page.find('a', {'href': archive_url}) + page = client_request.get("main.user_information", user_id=inactive_user_id) + + archive_url = url_for("main.archive_user", user_id=inactive_user_id) + assert not page.find("a", {"href": archive_url}) def test_archive_user_prompts_for_confirmation( @@ -284,11 +318,12 @@ def test_archive_user_prompts_for_confirmation( mock_get_organizations_and_services_for_user, ): client_request.login(platform_admin_user) - page = client_request.get( - 'main.archive_user', user_id=api_user_active['id'] - ) + page = client_request.get("main.archive_user", user_id=api_user_active["id"]) - assert 'Are you sure you want to archive this user?' in page.find('div', class_='banner-dangerous').text + assert ( + "Are you sure you want to archive this user?" + in page.find("div", class_="banner-dangerous").text + ) def test_archive_user_posts_to_user_client( @@ -298,18 +333,21 @@ def test_archive_user_posts_to_user_client( mocker, mock_events, ): - mock_user_client = mocker.patch('app.user_api_client.post') + mock_user_client = mocker.patch("app.user_api_client.post") client_request.login(platform_admin_user) client_request.post( - 'main.archive_user', user_id=api_user_active['id'], + "main.archive_user", + user_id=api_user_active["id"], _expected_redirect=url_for( - 'main.user_information', - user_id=api_user_active['id'], + "main.user_information", + user_id=api_user_active["id"], ), ) - mock_user_client.assert_called_once_with('/user/{}/archive'.format(api_user_active['id']), data=None) + mock_user_client.assert_called_once_with( + "/user/{}/archive".format(api_user_active["id"]), data=None + ) assert mock_events.called @@ -322,30 +360,33 @@ def test_archive_user_shows_error_message_if_user_cannot_be_archived( mock_get_non_empty_organizations_and_services_for_user, ): mocker.patch( - 'app.user_api_client.post', + "app.user_api_client.post", side_effect=HTTPError( response=mocker.Mock( status_code=400, - json={'result': 'error', - 'message': 'User can’t be removed from a service - check all services have another ' - 'team member with manage_settings'} + json={ + "result": "error", + "message": "User can’t be removed from a service - check all services have another " + "team member with manage_settings", + }, ), - message='User can’t be removed from a service - check all services have another team member ' - 'with manage_settings' - ) + message="User can’t be removed from a service - check all services have another team member " + "with manage_settings", + ), ) client_request.login(platform_admin_user) page = client_request.post( - 'main.archive_user', - user_id=api_user_active['id'], + "main.archive_user", + user_id=api_user_active["id"], _follow_redirects=True, ) - assert normalize_spaces(page.find('h1').text) == 'Platform admin user' - assert normalize_spaces( - page.select_one('.banner-dangerous').text - ) == 'User can’t be removed from a service - check all services have another team member with manage_settings' + assert normalize_spaces(page.find("h1").text) == "Platform admin user" + assert ( + normalize_spaces(page.select_one(".banner-dangerous").text) + == "User can’t be removed from a service - check all services have another team member with manage_settings" + ) def test_archive_user_does_not_create_event_if_user_client_raises_unexpected_exception( @@ -360,7 +401,8 @@ def test_archive_user_does_not_create_event_if_user_client_raises_unexpected_exc try: client_request.login(platform_admin_user) client_request.post( - 'main.archive_user', user_id=api_user_active.id, + "main.archive_user", + user_id=api_user_active.id, ) assert 1 == 0 except Exception: diff --git a/tests/app/main/views/test_forgot_password.py b/tests/app/main/views/test_forgot_password.py index 5dc5f2cd8..c23650990 100644 --- a/tests/app/main/views/test_forgot_password.py +++ b/tests/app/main/views/test_forgot_password.py @@ -9,14 +9,13 @@ from tests.conftest import SERVICE_ONE_ID def test_should_render_forgot_password(client_request): client_request.logout() - page = client_request.get('.forgot_password') - assert 'We’ll send you an email to create a new password.' in page.text + page = client_request.get(".forgot_password") + assert "We’ll send you an email to create a new password." in page.text -@pytest.mark.parametrize('email_address', [ - 'test@user.gsa.gov', - 'someuser@notgovernment.com' -]) +@pytest.mark.parametrize( + "email_address", ["test@user.gsa.gov", "someuser@notgovernment.com"] +) def test_should_redirect_to_password_reset_sent_for_valid_email( client_request, fake_uuid, @@ -25,14 +24,16 @@ def test_should_redirect_to_password_reset_sent_for_valid_email( ): client_request.logout() sample_user = user_json(email_address=email_address) - mocker.patch('app.user_api_client.send_reset_password_url', return_value=None) + mocker.patch("app.user_api_client.send_reset_password_url", return_value=None) page = client_request.post( - '.forgot_password', - _data={'email_address': sample_user['email_address']}, + ".forgot_password", + _data={"email_address": sample_user["email_address"]}, _expected_status=200, ) - assert 'Click the link in the email to reset your password.' in page.text - app.user_api_client.send_reset_password_url.assert_called_once_with(sample_user['email_address'], next_string=None) + assert "Click the link in the email to reset your password." in page.text + app.user_api_client.send_reset_password_url.assert_called_once_with( + sample_user["email_address"], next_string=None + ) def test_forgot_password_sends_next_link_with_reset_password_email_request( @@ -41,15 +42,16 @@ def test_forgot_password_sends_next_link_with_reset_password_email_request( mocker, ): client_request.logout() - sample_user = user_json(email_address='test@user.gsa.gov') - mocker.patch('app.user_api_client.send_reset_password_url', return_value=None) + sample_user = user_json(email_address="test@user.gsa.gov") + mocker.patch("app.user_api_client.send_reset_password_url", return_value=None) client_request.post_url( - url_for('.forgot_password') + f"?next=/services/{SERVICE_ONE_ID}/templates", - _data={'email_address': sample_user['email_address']}, + url_for(".forgot_password") + f"?next=/services/{SERVICE_ONE_ID}/templates", + _data={"email_address": sample_user["email_address"]}, _expected_status=200, ) app.user_api_client.send_reset_password_url.assert_called_once_with( - sample_user['email_address'], next_string=f'/services/{SERVICE_ONE_ID}/templates' + sample_user["email_address"], + next_string=f"/services/{SERVICE_ONE_ID}/templates", ) @@ -59,14 +61,16 @@ def test_should_redirect_to_password_reset_sent_for_missing_email( mocker, ): client_request.logout() - mocker.patch('app.user_api_client.send_reset_password_url', side_effect=HTTPError(Response(status=404), - 'Not found')) + mocker.patch( + "app.user_api_client.send_reset_password_url", + side_effect=HTTPError(Response(status=404), "Not found"), + ) page = client_request.post( - '.forgot_password', - _data={'email_address': api_user_active['email_address']}, + ".forgot_password", + _data={"email_address": api_user_active["email_address"]}, _expected_status=200, ) - assert 'Click the link in the email to reset your password.' in page.text + assert "Click the link in the email to reset your password." in page.text app.user_api_client.send_reset_password_url.assert_called_once_with( - api_user_active['email_address'], next_string=None + api_user_active["email_address"], next_string=None ) diff --git a/tests/app/main/views/test_headers.py b/tests/app/main/views/test_headers.py index 5869d3cf0..be845e125 100644 --- a/tests/app/main/views/test_headers.py +++ b/tests/app/main/views/test_headers.py @@ -7,17 +7,17 @@ def test_owasp_useful_headers_set( mock_get_service_and_organization_counts, ): client_request.logout() - response = client_request.get_response('.index') + response = client_request.get_response(".index") - assert response.headers['X-Frame-Options'] == 'deny' - assert response.headers['X-Content-Type-Options'] == 'nosniff' - csp = response.headers['Content-Security-Policy'] + assert response.headers["X-Frame-Options"] == "deny" + assert response.headers["X-Content-Type-Options"] == "nosniff" + csp = response.headers["Content-Security-Policy"] assert search(r"default-src 'self' static\.example\.com;", csp) assert search(r"frame-ancestors 'none';", csp) assert search(r"form-action 'self';", csp) assert search( - r"script-src 'self' static\.example\.com 'unsafe-eval' https:\/\/js-agent\.newrelic\.com https:\/\/gov-bam\.nr-data\.net 'nonce-.*';", # noqa e501 - csp + r"script-src 'self' static\.example\.com 'unsafe-eval' https:\/\/js-agent\.newrelic\.com https:\/\/gov-bam\.nr-data\.net 'nonce-.*';", # noqa e501 + csp, ) assert search(r"connect-src 'self' https:\/\/gov-bam.nr-data\.net;", csp) assert search(r"style-src 'self' static\.example\.com 'nonce-.*';", csp) diff --git a/tests/app/main/views/test_history.py b/tests/app/main/views/test_history.py index 6a0af7b72..2535e24fd 100644 --- a/tests/app/main/views/test_history.py +++ b/tests/app/main/views/test_history.py @@ -4,87 +4,87 @@ from freezegun import freeze_time from tests.conftest import SERVICE_ONE_ID, normalize_spaces -@pytest.mark.parametrize('extra_args, expected_headings_and_events', ( - ({}, [ +@pytest.mark.parametrize( + "extra_args, expected_headings_and_events", + ( ( - '12 December', - ( - 'Test User 18:13 ' - 'Renamed this service from ‘Before lunch’ to ‘After lunch’ ' - 'Test User 17:12 ' - 'Renamed this service from ‘Example service’ to ‘Before lunch’' - ), + {}, + [ + ( + "12 December", + ( + "Test User 18:13 " + "Renamed this service from ‘Before lunch’ to ‘After lunch’ " + "Test User 17:12 " + "Renamed this service from ‘Example service’ to ‘Before lunch’" + ), + ), + ( + "11 November", + ("Test User 17:12 " "Revoked the ‘Bad key’ API key"), + ), + ( + "11 November 2011", + ("Test User 16:11 " "Created an API key called ‘Bad key’"), + ), + ( + "10 October 2010", + ( + "Test User 15:10 " + "Created an API key called ‘Good key’ " + "Test User 14:09 " + "Created an API key called ‘Key event returned in non-chronological order’ " + "Test User 06:01 " + "Created this service and called it ‘Example service’" + ), + ), + ], ), ( - '11 November', - ( - 'Test User 17:12 ' - 'Revoked the ‘Bad key’ API key' - ), + {"selected": "api"}, + [ + ( + "11 November", + ("Test User 17:12 " "Revoked the ‘Bad key’ API key"), + ), + ( + "11 November 2011", + ("Test User 16:11 " "Created an API key called ‘Bad key’"), + ), + ( + "10 October 2010", + ( + "Test User 15:10 " + "Created an API key called ‘Good key’ " + "Test User 14:09 " + "Created an API key called ‘Key event returned in non-chronological order’" + ), + ), + ], ), ( - '11 November 2011', - ( - 'Test User 16:11 ' - 'Created an API key called ‘Bad key’' - ), + {"selected": "service"}, + [ + ( + "12 December", + ( + "Test User 18:13 " + "Renamed this service from ‘Before lunch’ to ‘After lunch’ " + "Test User 17:12 " + "Renamed this service from ‘Example service’ to ‘Before lunch’" + ), + ), + ( + "10 October 2010", + ( + "Test User 06:01 " + "Created this service and called it ‘Example service’" + ), + ), + ], ), - ( - '10 October 2010', - ( - 'Test User 15:10 ' - 'Created an API key called ‘Good key’ ' - 'Test User 14:09 ' - 'Created an API key called ‘Key event returned in non-chronological order’ ' - 'Test User 06:01 ' - 'Created this service and called it ‘Example service’' - ), - ), - ]), - ({'selected': 'api'}, [ - ( - '11 November', - ( - 'Test User 17:12 ' - 'Revoked the ‘Bad key’ API key' - ), - ), - ( - '11 November 2011', - ( - 'Test User 16:11 ' - 'Created an API key called ‘Bad key’' - ), - ), - ( - '10 October 2010', - ( - 'Test User 15:10 ' - 'Created an API key called ‘Good key’ ' - 'Test User 14:09 ' - 'Created an API key called ‘Key event returned in non-chronological order’' - ), - ), - ]), - ({'selected': 'service'}, [ - ( - '12 December', - ( - 'Test User 18:13 ' - 'Renamed this service from ‘Before lunch’ to ‘After lunch’ ' - 'Test User 17:12 ' - 'Renamed this service from ‘Example service’ to ‘Before lunch’' - ), - ), - ( - '10 October 2010', - ( - 'Test User 06:01 ' - 'Created this service and called it ‘Example service’' - ), - ), - ]), -)) + ), +) @freeze_time("2012-01-01 01:01:01") def test_history( client_request, @@ -93,12 +93,12 @@ def test_history( extra_args, expected_headings_and_events, ): - page = client_request.get('main.history', service_id=SERVICE_ONE_ID, **extra_args) + page = client_request.get("main.history", service_id=SERVICE_ONE_ID, **extra_args) - assert page.select_one('h1').text == 'Audit events' + assert page.select_one("h1").text == "Audit events" - headings = page.select('main h2.heading-small') - events = page.select('main ul.bottom-gutter') + headings = page.select("main h2.heading-small") + events = page.select("main ul.bottom-gutter") assert len(headings) == len(events) == len(expected_headings_and_events) diff --git a/tests/app/main/views/test_inbound_sms_admin.py b/tests/app/main/views/test_inbound_sms_admin.py index f6a54273b..fac118485 100644 --- a/tests/app/main/views/test_inbound_sms_admin.py +++ b/tests/app/main/views/test_inbound_sms_admin.py @@ -1,25 +1,34 @@ -sample_inbound_sms = {'data': [{"id": "activated", - "number": "2028675309", - "provider": "provider_one", - "service": {"id": "123234", "name": "Service One"}, - "active": True, - "created_at": "2017-08-15T13:30:30.12312", - "updated_at": "2017-08-15T13:30:30.12312"}, - {"id": "available", - "number": "2028675308", - "provider": "provider_one", - "service": None, - "active": True, - "created_at": "2017-08-15T13:30:30.12312", - "updated_at": None}, - {"id": "deactivated", - "number": "2028675308", - "provider": "provider_one", - "service": None, - "active": True, - "created_at": "2017-08-15T13:30:30.12312", - "updated_at": None} - ]} +sample_inbound_sms = { + "data": [ + { + "id": "activated", + "number": "2028675309", + "provider": "provider_one", + "service": {"id": "123234", "name": "Service One"}, + "active": True, + "created_at": "2017-08-15T13:30:30.12312", + "updated_at": "2017-08-15T13:30:30.12312", + }, + { + "id": "available", + "number": "2028675308", + "provider": "provider_one", + "service": None, + "active": True, + "created_at": "2017-08-15T13:30:30.12312", + "updated_at": None, + }, + { + "id": "deactivated", + "number": "2028675308", + "provider": "provider_one", + "service": None, + "active": True, + "created_at": "2017-08-15T13:30:30.12312", + "updated_at": None, + }, + ] +} def test_inbound_sms_admin( @@ -27,7 +36,10 @@ def test_inbound_sms_admin( platform_admin_user, mocker, ): - mocker.patch("app.inbound_number_client.get_all_inbound_sms_number_service", return_value=sample_inbound_sms) + mocker.patch( + "app.inbound_number_client.get_all_inbound_sms_number_service", + return_value=sample_inbound_sms, + ) client_request.login(platform_admin_user) page = client_request.get("main.inbound_sms_admin") assert page.h1.string.strip() == "Inbound SMS" diff --git a/tests/app/main/views/test_index.py b/tests/app/main/views/test_index.py index c0c2b737c..249acaf44 100644 --- a/tests/app/main/views/test_index.py +++ b/tests/app/main/views/test_index.py @@ -13,19 +13,17 @@ def test_non_logged_in_user_can_see_homepage( mock_get_service_and_organization_counts, ): client_request.logout() - page = client_request.get('main.index', _test_page_title=False) + page = client_request.get("main.index", _test_page_title=False) - assert page.h1.text.strip() == ( - 'Send text messages to your participants' + assert page.h1.text.strip() == ("Send text messages to your participants") + + assert page.select_one("a.usa-button.usa-button--big")["href"] == url_for( + "main.sign_in", ) - assert page.select_one('a.usa-button.usa-button--big')['href'] == url_for( - 'main.sign_in', - ) - - assert page.select_one('meta[name=description]')['content'].strip() == ( - 'Notify.gov lets you send text messages to your users. ' - 'Try it now if you work in federal, state, or local government.' + assert page.select_one("meta[name=description]")["content"].strip() == ( + "Notify.gov lets you send text messages to your users. " + "Try it now if you work in federal, state, or local government." ) # This area is hidden for the pilot @@ -36,7 +34,7 @@ def test_non_logged_in_user_can_see_homepage( # 'There are 111 Organizations and 9,999 Services using Notify.' # ) - assert page.select_one('#whos-using-notify a') is None + assert page.select_one("#whos-using-notify a") is None def test_logged_in_user_redirects_to_choose_account( @@ -47,34 +45,37 @@ def test_logged_in_user_redirects_to_choose_account( mock_login, ): client_request.get( - 'main.index', + "main.index", _expected_status=302, ) client_request.get( - 'main.sign_in', + "main.sign_in", _expected_status=302, - _expected_redirect=url_for('main.show_accounts_or_dashboard') + _expected_redirect=url_for("main.show_accounts_or_dashboard"), ) def test_robots(client_request): - client_request.get_url('/robots.txt', _expected_status=404) + client_request.get_url("/robots.txt", _expected_status=404) -@pytest.mark.parametrize('endpoint, kwargs', ( - ('sign_in', {}), - ('support', {}), - ('support_public', {}), - ('triage', {}), - ('feedback', {'ticket_type': 'ask-question-give-feedback'}), - ('feedback', {'ticket_type': 'general'}), - ('feedback', {'ticket_type': 'report-problem'}), - ('bat_phone', {}), - ('thanks', {}), - ('register', {}), - pytest.param('index', {}, marks=pytest.mark.xfail(raises=AssertionError)), -)) -@freeze_time('2012-12-12 12:12') # So we don’t go out of business hours +@pytest.mark.parametrize( + "endpoint, kwargs", + ( + ("sign_in", {}), + ("support", {}), + ("support_public", {}), + ("triage", {}), + ("feedback", {"ticket_type": "ask-question-give-feedback"}), + ("feedback", {"ticket_type": "general"}), + ("feedback", {"ticket_type": "report-problem"}), + ("bat_phone", {}), + ("thanks", {}), + ("register", {}), + pytest.param("index", {}, marks=pytest.mark.xfail(raises=AssertionError)), + ), +) +@freeze_time("2012-12-12 12:12") # So we don’t go out of business hours def test_hiding_pages_from_search_engines( client_request, mock_get_service_and_organization_counts, @@ -82,103 +83,113 @@ def test_hiding_pages_from_search_engines( kwargs, ): client_request.logout() - response = client_request.get_response(f'main.{endpoint}', **kwargs) - assert 'X-Robots-Tag' in response.headers - assert response.headers['X-Robots-Tag'] == 'noindex' + response = client_request.get_response(f"main.{endpoint}", **kwargs) + assert "X-Robots-Tag" in response.headers + assert response.headers["X-Robots-Tag"] == "noindex" - page = BeautifulSoup(response.data.decode('utf-8'), 'html.parser') - assert page.select_one('meta[name=robots]')['content'] == 'noindex' + page = BeautifulSoup(response.data.decode("utf-8"), "html.parser") + assert page.select_one("meta[name=robots]")["content"] == "noindex" -@pytest.mark.parametrize('view', [ - 'privacy', 'pricing', 'terms', 'roadmap', - 'features', 'documentation', 'security', - 'message_status', 'features_email', 'features_sms', - 'how_to_pay', 'get_started', - 'guidance_index', 'branding_and_customisation', - 'create_and_send_messages', 'edit_and_format_messages', - 'send_files_by_email', - 'billing_details', -]) +@pytest.mark.parametrize( + "view", + [ + "privacy", + "pricing", + "terms", + "roadmap", + "features", + "documentation", + "security", + "message_status", + "features_email", + "features_sms", + "how_to_pay", + "get_started", + "guidance_index", + "branding_and_customisation", + "create_and_send_messages", + "edit_and_format_messages", + "send_files_by_email", + "billing_details", + ], +) def test_static_pages( client_request, mock_get_organization_by_domain, view, ): - request = partial(client_request.get, 'main.{}'.format(view)) + request = partial(client_request.get, "main.{}".format(view)) # Check the page loads when user is signed in page = request() - assert not page.select_one('meta[name=description]') + assert not page.select_one("meta[name=description]") # Check it still works when they don’t have a recent service with client_request.session_transaction() as session: - session['service_id'] = None + session["service_id"] = None request() # Check it redirects to the login screen when they sign out client_request.logout() with client_request.session_transaction() as session: - session['service_id'] = None - session['user_id'] = None + session["service_id"] = None + session["user_id"] = None request( _expected_status=302, - _expected_redirect='/sign-in?next={}'.format( - url_for('main.{}'.format(view)) - ) + _expected_redirect="/sign-in?next={}".format(url_for("main.{}".format(view))), ) def test_guidance_pages_link_to_service_pages_when_signed_in( client_request, ): - request = partial(client_request.get, 'main.edit_and_format_messages') - selector = '.list-number li a' + request = partial(client_request.get, "main.edit_and_format_messages") + selector = ".list-number li a" # Check the page loads when user is signed in page = request() - assert page.select_one(selector)['href'] == url_for( - 'main.choose_template', + assert page.select_one(selector)["href"] == url_for( + "main.choose_template", service_id=SERVICE_ONE_ID, ) # Check it still works when they don’t have a recent service with client_request.session_transaction() as session: - session['service_id'] = None + session["service_id"] = None page = request() assert not page.select_one(selector) # Check it redirects to the login screen when they sign out client_request.logout() with client_request.session_transaction() as session: - session['service_id'] = None - session['user_id'] = None + session["service_id"] = None + session["user_id"] = None page = request(_expected_status=302) assert not page.select_one(selector) -@pytest.mark.parametrize('view, expected_view', [ - ('information_risk_management', 'security'), - ('old_integration_testing', 'integration_testing'), - ('old_roadmap', 'roadmap'), - ('information_risk_management', 'security'), - ('old_terms', 'terms'), - ('information_security', 'using_notify'), - ('old_using_notify', 'using_notify'), - ('delivery_and_failure', 'message_status'), - ('callbacks', 'documentation'), -]) -def test_old_static_pages_redirect( - client_request, - view, - expected_view -): +@pytest.mark.parametrize( + "view, expected_view", + [ + ("information_risk_management", "security"), + ("old_integration_testing", "integration_testing"), + ("old_roadmap", "roadmap"), + ("information_risk_management", "security"), + ("old_terms", "terms"), + ("information_security", "using_notify"), + ("old_using_notify", "using_notify"), + ("delivery_and_failure", "message_status"), + ("callbacks", "documentation"), + ], +) +def test_old_static_pages_redirect(client_request, view, expected_view): client_request.logout() client_request.get( - 'main.{}'.format(view), + "main.{}".format(view), _expected_status=301, _expected_redirect=url_for( - 'main.{}'.format(expected_view), + "main.{}".format(expected_view), ), ) @@ -186,23 +197,25 @@ def test_old_static_pages_redirect( def test_message_status_page_contains_message_status_ids(client_request): # The 'email-statuses' and 'sms-statuses' id are linked to when we display a message status, # so this test ensures we don't accidentally remove them - page = client_request.get('main.message_status') + page = client_request.get("main.message_status") # email-statuses is commented out in view # assert page.find(id='email-statuses') - assert page.find(id='text-message-statuses') + assert page.find(id="text-message-statuses") def test_message_status_page_contains_link_to_support(client_request): - page = client_request.get('main.message_status') - sms_status_table = page.find(id='text-message-statuses').findNext('tbody') + page = client_request.get("main.message_status") + sms_status_table = page.find(id="text-message-statuses").findNext("tbody") - temp_fail_details_cell = sms_status_table.select_one('tr:nth-child(4) > td:nth-child(2)') - assert temp_fail_details_cell.find('a').attrs['href'] == url_for('main.support') + temp_fail_details_cell = sms_status_table.select_one( + "tr:nth-child(4) > td:nth-child(2)" + ) + assert temp_fail_details_cell.find("a").attrs["href"] == url_for("main.support") def test_old_using_notify_page(client_request): - client_request.get('main.using_notify', _expected_status=410) + client_request.get("main.using_notify", _expected_status=410) # def test_old_integration_testing_page( @@ -223,23 +236,23 @@ def test_old_using_notify_page(client_request): def test_terms_page_has_correct_content(client_request): - terms_page = client_request.get('main.terms') - assert normalize_spaces(terms_page.select('main p')[0].text) == ( - 'These terms apply to your service’s use of Notify.gov. ' - 'You must be the service manager to accept them.' + terms_page = client_request.get("main.terms") + assert normalize_spaces(terms_page.select("main p")[0].text) == ( + "These terms apply to your service’s use of Notify.gov. " + "You must be the service manager to accept them." ) def test_css_is_served_from_correct_path(client_request): + page = client_request.get("main.documentation") # easy static page - page = client_request.get('main.documentation') # easy static page + for index, link in enumerate(page.select("link[rel=stylesheet]")): + assert link["href"].startswith( + [ + "https://static.example.com/css/styles.css?", + ][index] + ) - for index, link in enumerate( - page.select('link[rel=stylesheet]') - ): - assert link['href'].startswith([ - 'https://static.example.com/css/styles.css?', - ][index]) # Commenting out until after the pilot when we'll decide on a logo # def test_resources_that_use_asset_path_variable_have_correct_path(client_request): @@ -251,20 +264,23 @@ def test_css_is_served_from_correct_path(client_request): # assert logo_svg_fallback['src'].startswith('https://static.example.com/images/us-notify-color.png') -@pytest.mark.parametrize('extra_args, email_branding_retrieved', ( +@pytest.mark.parametrize( + "extra_args, email_branding_retrieved", ( - {}, - False, + ( + {}, + False, + ), + ( + {"branding_style": "__NONE__"}, + False, + ), + ( + {"branding_style": sample_uuid()}, + True, + ), ), - ( - {'branding_style': '__NONE__'}, - False, - ), - ( - {'branding_style': sample_uuid()}, - True, - ), -)) +) def test_email_branding_preview( client_request, mock_get_email_branding, @@ -272,11 +288,9 @@ def test_email_branding_preview( email_branding_retrieved, ): page = client_request.get( - 'main.email_template', - _test_page_title=False, - **extra_args + "main.email_template", _test_page_title=False, **extra_args ) - assert page.title.text == 'Email branding preview' + assert page.title.text == "Email branding preview" assert mock_get_email_branding.called is email_branding_retrieved @@ -285,20 +299,22 @@ def test_font_preload( mock_get_service_and_organization_counts, ): client_request.logout() - page = client_request.get('main.index', _test_page_title=False) + page = client_request.get("main.index", _test_page_title=False) - preload_tags = page.select('link[rel=preload][as=font][type="font/woff2"][crossorigin]') + preload_tags = page.select( + 'link[rel=preload][as=font][type="font/woff2"][crossorigin]' + ) - assert len(preload_tags) == 4, 'Run `npm run build` to copy fonts into app/static/fonts/' + assert ( + len(preload_tags) == 4 + ), "Run `npm run build` to copy fonts into app/static/fonts/" for element in preload_tags: - assert element['href'].startswith('https://static.example.com/fonts/') - assert element['href'].endswith('.woff2') + assert element["href"].startswith("https://static.example.com/fonts/") + assert element["href"].endswith(".woff2") -@pytest.mark.parametrize('current_date, expected_rate', ( - ('2022-05-01', '1.72'), -)) +@pytest.mark.parametrize("current_date, expected_rate", (("2022-05-01", "1.72"),)) @pytest.mark.skip(reason="Currently hidden for TTS") def test_sms_price( client_request, @@ -309,21 +325,21 @@ def test_sms_price( client_request.logout() with freeze_time(current_date): - home_page = client_request.get('main.index', _test_page_title=False) - pricing_page = client_request.get('main.pricing') + home_page = client_request.get("main.index", _test_page_title=False) + pricing_page = client_request.get("main.pricing") assert normalize_spaces( - home_page.select('.product-page-section')[5].select('.govuk-grid-column-one-half')[1].text + home_page.select(".product-page-section")[5] + .select(".govuk-grid-column-one-half")[1] + .text ) == ( - f'Text messages ' - f'Up to 40,000 free text messages a year, ' - f'then {expected_rate} pence per message' + f"Text messages " + f"Up to 40,000 free text messages a year, " + f"then {expected_rate} pence per message" ) - assert normalize_spaces( - pricing_page.select_one('#text-messages + p + p').text - ) == ( - f'When a service has used its annual allowance, it costs ' - f'{expected_rate} pence (plus VAT) for each text message you ' - f'send.' + assert normalize_spaces(pricing_page.select_one("#text-messages + p + p").text) == ( + f"When a service has used its annual allowance, it costs " + f"{expected_rate} pence (plus VAT) for each text message you " + f"send." ) diff --git a/tests/app/main/views/test_jobs.py b/tests/app/main/views/test_jobs.py index 81bc70aa0..ee12c8ce3 100644 --- a/tests/app/main/views/test_jobs.py +++ b/tests/app/main/views/test_jobs.py @@ -19,46 +19,54 @@ def test_old_jobs_hub_redirects( client_request, ): client_request.get( - 'main.view_jobs', + "main.view_jobs", service_id=SERVICE_ONE_ID, _expected_status=302, _expected_redirect=url_for( - 'main.uploads', + "main.uploads", service_id=SERVICE_ONE_ID, - ) + ), ) -@pytest.mark.parametrize('user', [ - create_active_user_with_permissions(), - create_active_caseworking_user(), -]) @pytest.mark.parametrize( - "status_argument, expected_api_call", [ + "user", + [ + create_active_user_with_permissions(), + create_active_caseworking_user(), + ], +) +@pytest.mark.parametrize( + "status_argument, expected_api_call", + [ ( - '', + "", [ - 'created', 'pending', 'sending', 'delivered', 'sent', 'failed', - 'temporary-failure', 'permanent-failure', 'technical-failure', - 'validation-failed' - ] + "created", + "pending", + "sending", + "delivered", + "sent", + "failed", + "temporary-failure", + "permanent-failure", + "technical-failure", + "validation-failed", + ], ), + ("sending", ["sending", "created", "pending"]), + ("delivered", ["delivered", "sent"]), ( - 'sending', - ['sending', 'created', 'pending'] - ), - ( - 'delivered', - ['delivered', 'sent'] - ), - ( - 'failed', + "failed", [ - 'failed', 'temporary-failure', 'permanent-failure', 'technical-failure', - 'validation-failed' - ] - ) - ] + "failed", + "temporary-failure", + "permanent-failure", + "technical-failure", + "validation-failed", + ], + ), + ], ) @freeze_time("2016-01-01 11:09:00.061258") def test_should_show_page_for_one_job( @@ -73,50 +81,45 @@ def test_should_show_page_for_one_job( expected_api_call, user, ): - page = client_request.get( - 'main.view_job', - service_id=SERVICE_ONE_ID, - job_id=fake_uuid, - status=status_argument - ) - - assert page.h1.text.strip() == 'thisisatest.csv' - assert ' '.join(page.find('tbody').find('tr').text.split()) == ( - '2021234567 template content Delivered 1 January at 11:10 UTC' - ) - assert page.find('div', {'data-key': 'notifications'})['data-resource'] == url_for( - 'main.view_job_updates', + "main.view_job", service_id=SERVICE_ONE_ID, job_id=fake_uuid, status=status_argument, ) - csv_link = page.select_one('a[download]') - assert csv_link['href'] == url_for( - 'main.view_job_csv', + + assert page.h1.text.strip() == "thisisatest.csv" + assert " ".join(page.find("tbody").find("tr").text.split()) == ( + "2021234567 template content Delivered 1 January at 11:10 UTC" + ) + assert page.find("div", {"data-key": "notifications"})["data-resource"] == url_for( + "main.view_job_updates", service_id=SERVICE_ONE_ID, job_id=fake_uuid, - status=status_argument + status=status_argument, ) - assert csv_link.text == 'Download this report (CSV)' - assert page.find('span', {'id': 'time-left'}).text == 'Data available for 7 days' + csv_link = page.select_one("a[download]") + assert csv_link["href"] == url_for( + "main.view_job_csv", + service_id=SERVICE_ONE_ID, + job_id=fake_uuid, + status=status_argument, + ) + assert csv_link.text == "Download this report (CSV)" + assert page.find("span", {"id": "time-left"}).text == "Data available for 7 days" - assert normalize_spaces(page.select_one('tbody tr').text) == normalize_spaces( - '2021234567 ' - 'template content ' - 'Delivered 1 January at 11:10 UTC' + assert normalize_spaces(page.select_one("tbody tr").text) == normalize_spaces( + "2021234567 " "template content " "Delivered 1 January at 11:10 UTC" ) - assert page.select_one('tbody tr a')['href'] == url_for( - 'main.view_notification', + assert page.select_one("tbody tr a")["href"] == url_for( + "main.view_notification", service_id=SERVICE_ONE_ID, notification_id=sample_uuid(), from_job=fake_uuid, ) mock_get_notifications.assert_called_with( - SERVICE_ONE_ID, - fake_uuid, - status=expected_api_call + SERVICE_ONE_ID, fake_uuid, status=expected_api_call ) @@ -131,16 +134,14 @@ def test_should_show_page_for_one_job_with_flexible_data_retention( mock_get_service_data_retention, fake_uuid, ): - - mock_get_service_data_retention.side_effect = [[{"days_of_retention": 10, "notification_type": "sms"}]] + mock_get_service_data_retention.side_effect = [ + [{"days_of_retention": 10, "notification_type": "sms"}] + ] page = client_request.get( - 'main.view_job', - service_id=SERVICE_ONE_ID, - job_id=fake_uuid, - status='delivered' + "main.view_job", service_id=SERVICE_ONE_ID, job_id=fake_uuid, status="delivered" ) - assert page.find('span', {'id': 'time-left'}).text == 'Data available for 10 days' + assert page.find("span", {"id": "time-left"}).text == "Data available for 10 days" assert "Cancel sending these letters" not in page @@ -154,12 +155,15 @@ def test_get_jobs_should_tell_user_if_more_than_one_page( mock_get_service_data_retention, ): page = client_request.get( - 'main.view_job', - service_id=service_one['id'], + "main.view_job", + service_id=service_one["id"], job_id=fake_uuid, - status='', + status="", + ) + assert ( + page.find("p", {"class": "table-show-more-link"}).text.strip() + == "Only showing the first 50 rows" ) - assert page.find('p', {'class': 'table-show-more-link'}).text.strip() == 'Only showing the first 50 rows' def test_should_show_job_in_progress( @@ -174,17 +178,19 @@ def test_should_show_job_in_progress( fake_uuid, ): page = client_request.get( - 'main.view_job', - service_id=service_one['id'], + "main.view_job", + service_id=service_one["id"], job_id=fake_uuid, ) assert [ normalize_spaces(link.text) - for link in page.select('.pill a:not(.pill-item--selected)') + for link in page.select(".pill a:not(.pill-item--selected)") ] == [ - '10 pending text messages', '0 delivered text messages', '0 failed text messages' + "10 pending text messages", + "0 delivered text messages", + "0 failed text messages", ] - assert page.select_one('p.hint').text.strip() == 'Report is 50% complete…' + assert page.select_one("p.hint").text.strip() == "Report is 50% complete…" def test_should_show_job_without_notifications( @@ -199,18 +205,20 @@ def test_should_show_job_without_notifications( fake_uuid, ): page = client_request.get( - 'main.view_job', - service_id=service_one['id'], + "main.view_job", + service_id=service_one["id"], job_id=fake_uuid, ) assert [ normalize_spaces(link.text) - for link in page.select('.pill a:not(.pill-item--selected)') + for link in page.select(".pill a:not(.pill-item--selected)") ] == [ - '10 pending text messages', '0 delivered text messages', '0 failed text messages' + "10 pending text messages", + "0 delivered text messages", + "0 failed text messages", ] - assert page.select_one('p.hint').text.strip() == 'Report is 50% complete…' - assert page.select_one('tbody').text.strip() == 'No messages to show yet…' + assert page.select_one("p.hint").text.strip() == "Report is 50% complete…" + assert page.select_one("tbody").text.strip() == "No messages to show yet…" def test_should_show_job_with_sending_limit_exceeded_status( @@ -224,51 +232,60 @@ def test_should_show_job_with_sending_limit_exceeded_status( fake_uuid, ): page = client_request.get( - 'main.view_job', - service_id=service_one['id'], + "main.view_job", + service_id=service_one["id"], job_id=fake_uuid, ) - assert normalize_spaces(page.select('main p')[1].text) == ( + assert normalize_spaces(page.select("main p")[1].text) == ( "Notify cannot send these messages because you have reached a limit. You can only send 1,000 messages per day and 250,000 messages in total." # noqa ) - assert normalize_spaces(page.select('main p')[2].text) == ( + assert normalize_spaces(page.select("main p")[2].text) == ( "Upload this spreadsheet again tomorrow or contact the Notify.gov team to raise the limit." ) @freeze_time("2020-01-10 1:0:0") -@pytest.mark.parametrize('created_at, processing_started, expected_message', ( - # Recently created, not yet started - (datetime(2020, 1, 10, 0, 0, 0), None, ( - 'No messages to show yet…' - )), - # Just started - (datetime(2020, 1, 10, 0, 0, 0), datetime(2020, 1, 10, 0, 0, 1), ( - 'No messages to show yet…' - )), - # Created a while ago, just started - (datetime(2020, 1, 1, 0, 0, 0), datetime(2020, 1, 10, 0, 0, 1), ( - 'No messages to show yet…' - )), - # Created a while ago, started just within the last 24h - # TODO -- fails locally, should pass, tech debt due to timezone changes, re-evaluate after UTC changes - pytest.param( - datetime(2020, 1, 1, 0, 0, 0), - datetime(2020, 1, 9, 6, 0, 1), - ('No messages to show yet…'), +@pytest.mark.parametrize( + "created_at, processing_started, expected_message", + ( + # Recently created, not yet started + (datetime(2020, 1, 10, 0, 0, 0), None, ("No messages to show yet…")), + # Just started + ( + datetime(2020, 1, 10, 0, 0, 0), + datetime(2020, 1, 10, 0, 0, 1), + ("No messages to show yet…"), + ), + # Created a while ago, just started + ( + datetime(2020, 1, 1, 0, 0, 0), + datetime(2020, 1, 10, 0, 0, 1), + ("No messages to show yet…"), + ), + # Created a while ago, started just within the last 24h + # TODO -- fails locally, should pass, tech debt due to timezone changes, re-evaluate after UTC changes + pytest.param( + datetime(2020, 1, 1, 0, 0, 0), + datetime(2020, 1, 9, 6, 0, 1), + ("No messages to show yet…"), + ), + # Created a while ago, started exactly 24h ago + # --- + # It doesn’t matter that 24h (1 day) and 7 days (the service’s data + # retention) don’t match up. We’re testing the case of no + # notifications existing more than 1 day after the job started + # processing. In this case we assume it’s because the service’s + # data retention has kicked in. + ( + datetime(2020, 1, 1, 0, 0, 0), + datetime(2020, 1, 9, 1, 0, 0), + ( + "These messages have been deleted because they were sent more than 7 days ago" + ), + ), ), - # Created a while ago, started exactly 24h ago - # --- - # It doesn’t matter that 24h (1 day) and 7 days (the service’s data - # retention) don’t match up. We’re testing the case of no - # notifications existing more than 1 day after the job started - # processing. In this case we assume it’s because the service’s - # data retention has kicked in. - (datetime(2020, 1, 1, 0, 0, 0), datetime(2020, 1, 9, 1, 0, 0), ( - 'These messages have been deleted because they were sent more than 7 days ago' - )), -)) +) def test_should_show_old_job( client_request, service_one, @@ -282,34 +299,38 @@ def test_should_show_old_job( processing_started, expected_message, ): - mocker.patch('app.job_api_client.get_job', return_value={ - "data": job_json( - SERVICE_ONE_ID, - active_user_with_permissions, - created_at=created_at.replace(tzinfo=timezone.utc).isoformat(), - processing_started=( - processing_started.replace(tzinfo=timezone.utc).isoformat() - if processing_started else None + mocker.patch( + "app.job_api_client.get_job", + return_value={ + "data": job_json( + SERVICE_ONE_ID, + active_user_with_permissions, + created_at=created_at.replace(tzinfo=timezone.utc).isoformat(), + processing_started=( + processing_started.replace(tzinfo=timezone.utc).isoformat() + if processing_started + else None + ), ), - ), - }) + }, + ) page = client_request.get( - 'main.view_job', + "main.view_job", service_id=SERVICE_ONE_ID, job_id=fake_uuid, ) - assert not page.select('.pill') - assert not page.select('p.hint') - assert not page.select('a[download]') - assert page.select_one('tbody').text.strip() == expected_message + assert not page.select(".pill") + assert not page.select("p.hint") + assert not page.select("a[download]") + assert page.select_one("tbody").text.strip() == expected_message assert [ normalize_spaces(column.text) - for column in page.select('main .govuk-grid-column-one-quarter') + for column in page.select("main .govuk-grid-column-one-quarter") ] == [ - '1 total text messages', - '1 pending', - '0 delivered text messages', - '0 failed text messages', + "1 total text messages", + "1 pending", + "0 delivered text messages", + "0 failed text messages", ] @@ -323,21 +344,21 @@ def test_should_show_scheduled_job( fake_uuid, ): page = client_request.get( - 'main.view_job', + "main.view_job", service_id=SERVICE_ONE_ID, job_id=fake_uuid, ) - assert normalize_spaces(page.select('main p')[1].text) == ( - 'Sending Two week reminder tomorrow at 05:00 UTC' + assert normalize_spaces(page.select("main p")[1].text) == ( + "Sending Two week reminder tomorrow at 05:00 UTC" ) - assert page.select('main p a')[0]['href'] == url_for( - 'main.view_template_version', + assert page.select("main p a")[0]["href"] == url_for( + "main.view_template_version", service_id=SERVICE_ONE_ID, - template_id='5d729fbd-239c-44ab-b498-75a985f3198f', + template_id="5d729fbd-239c-44ab-b498-75a985f3198f", version=1, ) - assert page.select_one('main button[type=submit]').text.strip() == 'Cancel sending' + assert page.select_one("main button[type=submit]").text.strip() == "Cancel sending" def test_should_cancel_job( @@ -347,16 +368,16 @@ def test_should_cancel_job( mock_get_service_template, mocker, ): - mock_cancel = mocker.patch('app.job_api_client.cancel_job') + mock_cancel = mocker.patch("app.job_api_client.cancel_job") client_request.post( - 'main.cancel_job', + "main.cancel_job", service_id=SERVICE_ONE_ID, job_id=fake_uuid, _expected_status=302, _expected_redirect=url_for( - 'main.service_dashboard', + "main.service_dashboard", service_id=SERVICE_ONE_ID, - ) + ), ) mock_cancel.assert_called_once_with(SERVICE_ONE_ID, fake_uuid) @@ -369,7 +390,7 @@ def test_should_not_show_cancelled_job( fake_uuid, ): client_request.get( - 'main.view_job', + "main.view_job", service_id=SERVICE_ONE_ID, job_id=fake_uuid, _expected_status=404, @@ -389,21 +410,21 @@ def test_should_show_updates_for_one_job_as_json( fake_uuid, ): response = client_request.get_response( - 'main.view_job_updates', - service_id=service_one['id'], + "main.view_job_updates", + service_id=service_one["id"], job_id=fake_uuid, ) content = json.loads(response.get_data(as_text=True)) - assert 'pending' in content['counts'] - assert 'delivered' in content['counts'] - assert 'failed' in content['counts'] - assert 'Recipient' in content['notifications'] - assert '2021234567' in content['notifications'] - assert 'Status' in content['notifications'] - assert 'Delivered' in content['notifications'] - assert '05:01' in content['notifications'] - assert 'Sent by Test User on 1 January at 05:00' in content['status'] + assert "pending" in content["counts"] + assert "delivered" in content["counts"] + assert "failed" in content["counts"] + assert "Recipient" in content["notifications"] + assert "2021234567" in content["notifications"] + assert "Status" in content["notifications"] + assert "Delivered" in content["notifications"] + assert "05:01" in content["notifications"] + assert "Sent by Test User on 1 January at 05:00" in content["status"] @freeze_time("2016-01-01 05:00:00.000001") @@ -417,39 +438,45 @@ def test_should_show_updates_for_scheduled_job_as_json( mocker, fake_uuid, ): - mocker.patch('app.job_api_client.get_job', return_value={'data': job_json( - service_one['id'], - created_by=user_json(), - job_id=fake_uuid, - scheduled_for='2016-06-01T18:00:00+00:00', - processing_started='2016-06-01T20:00:00+00:00', - )}) + mocker.patch( + "app.job_api_client.get_job", + return_value={ + "data": job_json( + service_one["id"], + created_by=user_json(), + job_id=fake_uuid, + scheduled_for="2016-06-01T18:00:00+00:00", + processing_started="2016-06-01T20:00:00+00:00", + ) + }, + ) response = client_request.get_response( - 'main.view_job_updates', - service_id=service_one['id'], + "main.view_job_updates", + service_id=service_one["id"], job_id=fake_uuid, ) content = response.json - assert 'pending' in content['counts'] - assert 'delivered' in content['counts'] - assert 'failed' in content['counts'] - assert 'Recipient' in content['notifications'] - assert '2021234567' in content['notifications'] - assert 'Status' in content['notifications'] - assert 'Delivered' in content['notifications'] - assert '05:01' in content['notifications'] - assert 'Sent by Test User on 1 June at 20:00' in content['status'] + assert "pending" in content["counts"] + assert "delivered" in content["counts"] + assert "failed" in content["counts"] + assert "Recipient" in content["notifications"] + assert "2021234567" in content["notifications"] + assert "Status" in content["notifications"] + assert "Delivered" in content["notifications"] + assert "05:01" in content["notifications"] + assert "Sent by Test User on 1 June at 20:00" in content["status"] @pytest.mark.parametrize( - "job_created_at, expected_message", [ + "job_created_at, expected_message", + [ ("2016-01-10 11:09:00.000000+00:00", "Data available for 7 days"), ("2016-01-04 11:09:00.000000+00:00", "Data available for 1 day"), ("2016-01-03 11:09:00.000000+00:00", "Data available for 12 hours"), - ("2016-01-02 23:59:59.000000+00:00", "Data no longer available") - ] + ("2016-01-02 23:59:59.000000+00:00", "Data no longer available"), + ], ) @freeze_time("2016-01-10 12:00:00.000000") def test_time_left(job_created_at, expected_message): diff --git a/tests/app/main/views/test_manage_users.py b/tests/app/main/views/test_manage_users.py index 5a25c12e8..6d7278d07 100644 --- a/tests/app/main/views/test_manage_users.py +++ b/tests/app/main/views/test_manage_users.py @@ -20,114 +20,117 @@ from tests.conftest import ( ) -@pytest.mark.parametrize('user, expected_self_text, expected_coworker_text', [ - ( - create_active_user_with_permissions(), +@pytest.mark.parametrize( + "user, expected_self_text, expected_coworker_text", + [ ( - 'Test User (you) ' - 'Permissions ' - 'Can See dashboard ' - 'Can Send messages ' - 'Can Add and edit templates ' - 'Can Manage settings, team and usage ' - 'Can Manage API integration' + create_active_user_with_permissions(), + ( + "Test User (you) " + "Permissions " + "Can See dashboard " + "Can Send messages " + "Can Add and edit templates " + "Can Manage settings, team and usage " + "Can Manage API integration" + ), + ( + "ZZZZZZZZ zzzzzzz@example.gsa.gov " + "Permissions " + "Can See dashboard " + "Cannot Send messages " + "Cannot Add and edit templates " + "Cannot Manage settings, team and usage " + "Cannot Manage API integration " + "Change details for ZZZZZZZZ zzzzzzz@example.gsa.gov" + ), ), ( - 'ZZZZZZZZ zzzzzzz@example.gsa.gov ' - 'Permissions ' - 'Can See dashboard ' - 'Cannot Send messages ' - 'Cannot Add and edit templates ' - 'Cannot Manage settings, team and usage ' - 'Cannot Manage API integration ' - 'Change details for ZZZZZZZZ zzzzzzz@example.gsa.gov' - ) - ), - ( - create_active_user_empty_permissions(), - ( - 'Test User With Empty Permissions (you) ' - 'Permissions ' - 'Cannot See dashboard ' - 'Cannot Send messages ' - 'Cannot Add and edit templates ' - 'Cannot Manage settings, team and usage ' - 'Cannot Manage API integration' + create_active_user_empty_permissions(), + ( + "Test User With Empty Permissions (you) " + "Permissions " + "Cannot See dashboard " + "Cannot Send messages " + "Cannot Add and edit templates " + "Cannot Manage settings, team and usage " + "Cannot Manage API integration" + ), + ( + "ZZZZZZZZ zzzzzzz@example.gsa.gov " + "Permissions " + "Can See dashboard " + "Cannot Send messages " + "Cannot Add and edit templates " + "Cannot Manage settings, team and usage " + "Cannot Manage API integration" + ), ), ( - 'ZZZZZZZZ zzzzzzz@example.gsa.gov ' - 'Permissions ' - 'Can See dashboard ' - 'Cannot Send messages ' - 'Cannot Add and edit templates ' - 'Cannot Manage settings, team and usage ' - 'Cannot Manage API integration' - ), - ), - ( - create_active_user_view_permissions(), - ( - 'Test User With Permissions (you) ' - 'Permissions ' - 'Can See dashboard ' - 'Cannot Send messages ' - 'Cannot Add and edit templates ' - 'Cannot Manage settings, team and usage ' - 'Cannot Manage API integration' + create_active_user_view_permissions(), + ( + "Test User With Permissions (you) " + "Permissions " + "Can See dashboard " + "Cannot Send messages " + "Cannot Add and edit templates " + "Cannot Manage settings, team and usage " + "Cannot Manage API integration" + ), + ( + "ZZZZZZZZ zzzzzzz@example.gsa.gov " + "Permissions " + "Can See dashboard " + "Cannot Send messages " + "Cannot Add and edit templates " + "Cannot Manage settings, team and usage " + "Cannot Manage API integration" + ), ), ( - 'ZZZZZZZZ zzzzzzz@example.gsa.gov ' - 'Permissions ' - 'Can See dashboard ' - 'Cannot Send messages ' - 'Cannot Add and edit templates ' - 'Cannot Manage settings, team and usage ' - 'Cannot Manage API integration' - ) - ), - ( - create_active_user_manage_template_permissions(), - ( - 'Test User With Permissions (you) ' - 'Permissions ' - 'Can See dashboard ' - 'Cannot Send messages ' - 'Can Add and edit templates ' - 'Cannot Manage settings, team and usage ' - 'Cannot Manage API integration' + create_active_user_manage_template_permissions(), + ( + "Test User With Permissions (you) " + "Permissions " + "Can See dashboard " + "Cannot Send messages " + "Can Add and edit templates " + "Cannot Manage settings, team and usage " + "Cannot Manage API integration" + ), + ( + "ZZZZZZZZ zzzzzzz@example.gsa.gov " + "Permissions " + "Can See dashboard " + "Cannot Send messages " + "Cannot Add and edit templates " + "Cannot Manage settings, team and usage " + "Cannot Manage API integration" + ), ), ( - 'ZZZZZZZZ zzzzzzz@example.gsa.gov ' - 'Permissions ' - 'Can See dashboard ' - 'Cannot Send messages ' - 'Cannot Add and edit templates ' - 'Cannot Manage settings, team and usage ' - 'Cannot Manage API integration' - ) - ), - ( - create_active_user_manage_template_permissions(), - ( - 'Test User With Permissions (you) ' - 'Permissions ' - 'Can See dashboard ' - 'Cannot Send messages ' - 'Can Add and edit templates ' - 'Cannot Manage settings, team and usage ' - 'Cannot Manage API integration' + create_active_user_manage_template_permissions(), + ( + "Test User With Permissions (you) " + "Permissions " + "Can See dashboard " + "Cannot Send messages " + "Can Add and edit templates " + "Cannot Manage settings, team and usage " + "Cannot Manage API integration" + ), + ( + "ZZZZZZZZ zzzzzzz@example.gsa.gov " + "Permissions " + "Can See dashboard " + "Cannot Send messages " + "Cannot Add and edit templates " + "Cannot Manage settings, team and usage " + "Cannot Manage API integration" + ), ), - ( - 'ZZZZZZZZ zzzzzzz@example.gsa.gov ' - 'Permissions ' - 'Can See dashboard ' - 'Cannot Send messages ' - 'Cannot Add and edit templates ' - 'Cannot Manage settings, team and usage ' - 'Cannot Manage API integration' - ) - ), -]) + ], +) def test_should_show_overview_page( client_request, mocker, @@ -142,28 +145,40 @@ def test_should_show_overview_page( ): current_user = user other_user = copy.deepcopy(active_user_view_permissions) - other_user['email_address'] = 'zzzzzzz@example.gsa.gov' - other_user['name'] = 'ZZZZZZZZ' - other_user['id'] = 'zzzzzzzz-zzzz-zzzz-zzzz-zzzzzzzzzzzz' + other_user["email_address"] = "zzzzzzz@example.gsa.gov" + other_user["name"] = "ZZZZZZZZ" + other_user["id"] = "zzzzzzzz-zzzz-zzzz-zzzz-zzzzzzzzzzzz" client_request.login(current_user) - mock_get_users = mocker.patch('app.models.user.Users.client_method', return_value=[ - current_user, - other_user, - ]) + mock_get_users = mocker.patch( + "app.models.user.Users.client_method", + return_value=[ + current_user, + other_user, + ], + ) - page = client_request.get('main.manage_users', service_id=SERVICE_ONE_ID) + page = client_request.get("main.manage_users", service_id=SERVICE_ONE_ID) - assert normalize_spaces(page.select_one('h1').text) == 'Team members' - assert normalize_spaces(page.select('.user-list-item')[0].text) == expected_self_text + assert normalize_spaces(page.select_one("h1").text) == "Team members" + assert ( + normalize_spaces(page.select(".user-list-item")[0].text) == expected_self_text + ) # [1:5] are invited users - assert normalize_spaces(page.select('.user-list-item')[6].text) == expected_coworker_text + assert ( + normalize_spaces(page.select(".user-list-item")[6].text) + == expected_coworker_text + ) mock_get_users.assert_called_once_with(SERVICE_ONE_ID) -@pytest.mark.parametrize('state', ( - 'active', 'pending', -)) +@pytest.mark.parametrize( + "state", + ( + "active", + "pending", + ), +) def test_should_show_change_details_link( client_request, mocker, @@ -177,33 +192,39 @@ def test_should_show_change_details_link( current_user = active_user_with_permissions other_user = active_caseworking_user - other_user['id'] = uuid.uuid4() - other_user['email_address'] = 'zzzzzzz@example.gsa.gov' - other_user['state'] = state + other_user["id"] = uuid.uuid4() + other_user["email_address"] = "zzzzzzz@example.gsa.gov" + other_user["state"] = state - mocker.patch('app.user_api_client.get_user', return_value=current_user) - mocker.patch('app.models.user.Users.client_method', return_value=[ - current_user, - other_user, - ]) + mocker.patch("app.user_api_client.get_user", return_value=current_user) + mocker.patch( + "app.models.user.Users.client_method", + return_value=[ + current_user, + other_user, + ], + ) - page = client_request.get('main.manage_users', service_id=SERVICE_ONE_ID) - link = page.select('.user-list-item')[-1].select_one('a') + page = client_request.get("main.manage_users", service_id=SERVICE_ONE_ID) + link = page.select(".user-list-item")[-1].select_one("a") assert normalize_spaces(link.text) == ( - 'Change details for Test User zzzzzzz@example.gsa.gov' + "Change details for Test User zzzzzzz@example.gsa.gov" ) - assert link['href'] == url_for( - '.edit_user_permissions', + assert link["href"] == url_for( + ".edit_user_permissions", service_id=SERVICE_ONE_ID, - user_id=other_user['id'], + user_id=other_user["id"], ) -@pytest.mark.parametrize('number_of_users', ( - pytest.param(7), - pytest.param(8), -)) +@pytest.mark.parametrize( + "number_of_users", + ( + pytest.param(7), + pytest.param(8), + ), +) def test_should_show_live_search_if_more_than_7_users( client_request, mocker, @@ -214,40 +235,43 @@ def test_should_show_live_search_if_more_than_7_users( active_user_view_permissions, number_of_users, ): - mocker.patch('app.user_api_client.get_user', return_value=active_user_with_permissions) - mocker.patch('app.models.user.InvitedUsers.client_method', return_value=[]) mocker.patch( - 'app.models.user.Users.client_method', - return_value=[active_user_with_permissions] * number_of_users + "app.user_api_client.get_user", return_value=active_user_with_permissions + ) + mocker.patch("app.models.user.InvitedUsers.client_method", return_value=[]) + mocker.patch( + "app.models.user.Users.client_method", + return_value=[active_user_with_permissions] * number_of_users, ) - page = client_request.get('main.manage_users', service_id=SERVICE_ONE_ID) + page = client_request.get("main.manage_users", service_id=SERVICE_ONE_ID) if number_of_users == 7: with pytest.raises(expected_exception=TypeError): - assert page.select_one('div[data-module=live-search]')['data-targets'] == ( + assert page.select_one("div[data-module=live-search]")["data-targets"] == ( ".user-list-item" ) return - assert page.select_one('div[data-module=live-search]')['data-targets'] == ( + assert page.select_one("div[data-module=live-search]")["data-targets"] == ( ".user-list-item" ) - assert len(page.select('.user-list-item')) == number_of_users + assert len(page.select(".user-list-item")) == number_of_users - textbox = page.select_one('[data-module=autofocus] .usa-input') - assert 'value' not in textbox - assert textbox['name'] == 'search' + textbox = page.select_one("[data-module=autofocus] .usa-input") + assert "value" not in textbox + assert textbox["name"] == "search" # data-module=autofocus is set on a containing element so it # shouldn’t also be set on the textbox itself - assert 'data-module' not in textbox - assert not page.select_one('[data-force-focus]') - assert textbox['class'] == [ - 'usa-input', + assert "data-module" not in textbox + assert not page.select_one("[data-force-focus]") + assert textbox["class"] == [ + "usa-input", ] - assert normalize_spaces( - page.select_one('label[for=search]').text - ) == 'Search by name or email address' + assert ( + normalize_spaces(page.select_one("label[for=search]").text) + == "Search by name or email address" + ) def test_should_show_caseworker_on_overview_page( @@ -259,69 +283,55 @@ def test_should_show_caseworker_on_overview_page( active_user_view_permissions, active_caseworking_user, ): - service_one['permissions'].append('caseworking') + service_one["permissions"].append("caseworking") current_user = active_user_view_permissions other_user = active_caseworking_user - other_user['id'] = uuid.uuid4() - other_user['email_address'] = 'zzzzzzz@example.gsa.gov' + other_user["id"] = uuid.uuid4() + other_user["email_address"] = "zzzzzzz@example.gsa.gov" client_request.login(current_user) - mocker.patch('app.models.user.Users.client_method', return_value=[ - current_user, - other_user, - ]) + mocker.patch( + "app.models.user.Users.client_method", + return_value=[ + current_user, + other_user, + ], + ) - page = client_request.get('main.manage_users', service_id=SERVICE_ONE_ID) + page = client_request.get("main.manage_users", service_id=SERVICE_ONE_ID) - assert normalize_spaces(page.select_one('h1').text) == 'Team members' - assert normalize_spaces(page.select('.user-list-item')[0].text) == ( - 'Test User With Permissions (you) ' - 'Permissions ' - 'Can See dashboard ' - 'Cannot Send messages ' - 'Cannot Add and edit templates ' - 'Cannot Manage settings, team and usage ' - 'Cannot Manage API integration' + assert normalize_spaces(page.select_one("h1").text) == "Team members" + assert normalize_spaces(page.select(".user-list-item")[0].text) == ( + "Test User With Permissions (you) " + "Permissions " + "Can See dashboard " + "Cannot Send messages " + "Cannot Add and edit templates " + "Cannot Manage settings, team and usage " + "Cannot Manage API integration" ) # [1:5] are invited users - assert normalize_spaces(page.select('.user-list-item')[6].text) == ( - 'Test User zzzzzzz@example.gsa.gov ' - 'Permissions ' - 'Cannot See dashboard ' - 'Can Send messages ' - 'Cannot Add and edit templates ' - 'Cannot Manage settings, team and usage ' - 'Cannot Manage API integration' + assert normalize_spaces(page.select(".user-list-item")[6].text) == ( + "Test User zzzzzzz@example.gsa.gov " + "Permissions " + "Cannot See dashboard " + "Can Send messages " + "Cannot Add and edit templates " + "Cannot Manage settings, team and usage " + "Cannot Manage API integration" ) -@pytest.mark.parametrize('endpoint, extra_args, service_has_email_auth, auth_options_hidden', [ - ( - 'main.edit_user_permissions', - {'user_id': sample_uuid()}, - True, - False - ), - ( - 'main.edit_user_permissions', - {'user_id': sample_uuid()}, - False, - True - ), - ( - 'main.invite_user', - {}, - True, - False - ), - ( - 'main.invite_user', - {}, - False, - True - ) -]) +@pytest.mark.parametrize( + "endpoint, extra_args, service_has_email_auth, auth_options_hidden", + [ + ("main.edit_user_permissions", {"user_id": sample_uuid()}, True, False), + ("main.edit_user_permissions", {"user_id": sample_uuid()}, False, True), + ("main.invite_user", {}, True, False), + ("main.invite_user", {}, False, True), + ], +) def test_service_with_no_email_auth_hides_auth_type_options( client_request, endpoint, @@ -330,25 +340,30 @@ def test_service_with_no_email_auth_hides_auth_type_options( auth_options_hidden, service_one, mock_get_users_by_service, - mock_get_template_folders + mock_get_template_folders, ): if service_has_email_auth: - service_one['permissions'].append('email_auth') - page = client_request.get(endpoint, service_id=service_one['id'], **extra_args) - assert (page.find('input', attrs={"name": "login_authentication"}) is None) == auth_options_hidden + service_one["permissions"].append("email_auth") + page = client_request.get(endpoint, service_id=service_one["id"], **extra_args) + assert ( + page.find("input", attrs={"name": "login_authentication"}) is None + ) == auth_options_hidden -@pytest.mark.parametrize('service_has_caseworking', (True, False)) -@pytest.mark.parametrize('endpoint, extra_args', [ - ( - 'main.edit_user_permissions', - {'user_id': sample_uuid()}, - ), - ( - 'main.invite_user', - {}, - ), -]) +@pytest.mark.parametrize("service_has_caseworking", (True, False)) +@pytest.mark.parametrize( + "endpoint, extra_args", + [ + ( + "main.edit_user_permissions", + {"user_id": sample_uuid()}, + ), + ( + "main.invite_user", + {}, + ), + ], +) def test_service_without_caseworking_doesnt_show_admin_vs_caseworker( client_request, mock_get_users_by_service, @@ -357,26 +372,21 @@ def test_service_without_caseworking_doesnt_show_admin_vs_caseworker( service_has_caseworking, extra_args, ): - page = client_request.get( - endpoint, - service_id=SERVICE_ONE_ID, - **extra_args - ) - permission_checkboxes = page.select('input[type=checkbox]') + page = client_request.get(endpoint, service_id=SERVICE_ONE_ID, **extra_args) + permission_checkboxes = page.select("input[type=checkbox]") for idx in range(len(permission_checkboxes)): - assert permission_checkboxes[idx]['name'] == 'permissions_field' - assert permission_checkboxes[0]['value'] == 'view_activity' - assert permission_checkboxes[1]['value'] == 'send_messages' - assert permission_checkboxes[2]['value'] == 'manage_templates' - assert permission_checkboxes[3]['value'] == 'manage_service' - assert permission_checkboxes[4]['value'] == 'manage_api_keys' + assert permission_checkboxes[idx]["name"] == "permissions_field" + assert permission_checkboxes[0]["value"] == "view_activity" + assert permission_checkboxes[1]["value"] == "send_messages" + assert permission_checkboxes[2]["value"] == "manage_templates" + assert permission_checkboxes[3]["value"] == "manage_service" + assert permission_checkboxes[4]["value"] == "manage_api_keys" -@pytest.mark.parametrize('service_has_email_auth, displays_auth_type', [ - (True, True), - (False, False) -]) +@pytest.mark.parametrize( + "service_has_email_auth, displays_auth_type", [(True, True), (False, False)] +) def test_manage_users_page_shows_member_auth_type_if_service_has_email_auth_activated( client_request, service_has_email_auth, @@ -384,32 +394,35 @@ def test_manage_users_page_shows_member_auth_type_if_service_has_email_auth_acti mock_get_users_by_service, mock_get_invites_for_service, mock_get_template_folders, - displays_auth_type + displays_auth_type, ): if service_has_email_auth: - service_one['permissions'].append('email_auth') - page = client_request.get('main.manage_users', service_id=service_one['id']) - assert bool(page.select_one('.tick-cross-list-hint')) == displays_auth_type + service_one["permissions"].append("email_auth") + page = client_request.get("main.manage_users", service_id=service_one["id"]) + assert bool(page.select_one(".tick-cross-list-hint")) == displays_auth_type -@pytest.mark.parametrize('sms_option_disabled, mobile_number, expected_label', [ - ( - True, - None, - """ +@pytest.mark.parametrize( + "sms_option_disabled, mobile_number, expected_label", + [ + ( + True, + None, + """ Text message code Not available because this team member has not added a phone number to their profile """, - ), - ( - False, - '202-867-5303', - """ + ), + ( + False, + "202-867-5303", + """ Text message code """, - ), -]) + ), + ], +) def test_user_with_no_mobile_number_cant_be_set_to_sms_auth( client_request, mock_get_users_by_service, @@ -421,48 +434,53 @@ def test_user_with_no_mobile_number_cant_be_set_to_sms_auth( mocker, active_user_with_permissions, ): - active_user_with_permissions['mobile_number'] = mobile_number + active_user_with_permissions["mobile_number"] = mobile_number - service_one['permissions'].append('email_auth') - mocker.patch('app.user_api_client.get_user', return_value=active_user_with_permissions) + service_one["permissions"].append("email_auth") + mocker.patch( + "app.user_api_client.get_user", return_value=active_user_with_permissions + ) page = client_request.get( - 'main.edit_user_permissions', - service_id=service_one['id'], + "main.edit_user_permissions", + service_id=service_one["id"], user_id=sample_uuid(), ) sms_auth_radio_button = page.select_one('input[value="sms_auth"]') assert sms_auth_radio_button.has_attr("disabled") == sms_option_disabled assert normalize_spaces( - page.select_one('label[for=login_authentication-0]').text + page.select_one("label[for=login_authentication-0]").text ) == normalize_spaces(expected_label) -@pytest.mark.parametrize('endpoint, extra_args, expected_checkboxes', [ - ( - 'main.edit_user_permissions', - {'user_id': sample_uuid()}, - [ - ('view_activity', True), - ('send_messages', True), - ('manage_templates', True), - ('manage_service', True), - ('manage_api_keys', True), - ] - ), - ( - 'main.invite_user', - {}, - [ - ('view_activity', False), - ('send_messages', False), - ('manage_templates', False), - ('manage_service', False), - ('manage_api_keys', False), - ] - ), -]) +@pytest.mark.parametrize( + "endpoint, extra_args, expected_checkboxes", + [ + ( + "main.edit_user_permissions", + {"user_id": sample_uuid()}, + [ + ("view_activity", True), + ("send_messages", True), + ("manage_templates", True), + ("manage_service", True), + ("manage_api_keys", True), + ], + ), + ( + "main.invite_user", + {}, + [ + ("view_activity", False), + ("send_messages", False), + ("manage_templates", False), + ("manage_service", False), + ("manage_api_keys", False), + ], + ), + ], +) def test_should_show_page_for_one_user( client_request, mock_get_users_by_service, @@ -472,15 +490,15 @@ def test_should_show_page_for_one_user( expected_checkboxes, ): page = client_request.get(endpoint, service_id=SERVICE_ONE_ID, **extra_args) - checkboxes = page.select('input[type=checkbox]') + checkboxes = page.select("input[type=checkbox]") assert len(checkboxes) == 5 for index, expected in enumerate(expected_checkboxes): expected_input_value, expected_checked = expected - assert checkboxes[index]['name'] == 'permissions_field' - assert checkboxes[index]['value'] == expected_input_value - assert checkboxes[index].has_attr('checked') == expected_checked + assert checkboxes[index]["name"] == "permissions_field" + assert checkboxes[index]["value"] == expected_input_value + assert checkboxes[index].has_attr("checked") == expected_checked def test_invite_user_allows_to_choose_auth( @@ -489,13 +507,13 @@ def test_invite_user_allows_to_choose_auth( mock_get_template_folders, service_one, ): - service_one['permissions'].append('email_auth') - page = client_request.get('main.invite_user', service_id=SERVICE_ONE_ID) + service_one["permissions"].append("email_auth") + page = client_request.get("main.invite_user", service_id=SERVICE_ONE_ID) radio_buttons = page.select("input[name=login_authentication]") values = {button["value"] for button in radio_buttons} - assert values == {'sms_auth', 'email_auth'} + assert values == {"sms_auth", "email_auth"} assert not any(button.has_attr("disabled") for button in radio_buttons) @@ -504,9 +522,11 @@ def test_invite_user_has_correct_email_field( mock_get_users_by_service, mock_get_template_folders, ): - email_field = client_request.get('main.invite_user', service_id=SERVICE_ONE_ID).select_one('#email_address') - assert email_field['spellcheck'] == 'false' - assert 'autocomplete' not in email_field + email_field = client_request.get( + "main.invite_user", service_id=SERVICE_ONE_ID + ).select_one("#email_address") + assert email_field["spellcheck"] == "false" + assert "autocomplete" not in email_field def test_should_not_show_page_for_non_team_member( @@ -514,51 +534,54 @@ def test_should_not_show_page_for_non_team_member( mock_get_users_by_service, ): client_request.get( - 'main.edit_user_permissions', + "main.edit_user_permissions", service_id=SERVICE_ONE_ID, user_id=USER_ONE_ID, _expected_status=404, ) -@pytest.mark.parametrize('submitted_permissions, permissions_sent_to_api', [ - ( - { - 'permissions_field': [ - 'view_activity', - 'send_messages', - 'manage_templates', - 'manage_service', - 'manage_api_keys', - ] - }, - { - 'view_activity', - 'send_messages', - 'manage_service', - 'manage_templates', - 'manage_api_keys', - } - ), - ( - { - 'permissions_field': [ - 'view_activity', - 'send_messages', - 'manage_templates', - ] - }, - { - 'view_activity', - 'send_messages', - 'manage_templates', - } - ), - ( - {}, - set(), - ), -]) +@pytest.mark.parametrize( + "submitted_permissions, permissions_sent_to_api", + [ + ( + { + "permissions_field": [ + "view_activity", + "send_messages", + "manage_templates", + "manage_service", + "manage_api_keys", + ] + }, + { + "view_activity", + "send_messages", + "manage_service", + "manage_templates", + "manage_api_keys", + }, + ), + ( + { + "permissions_field": [ + "view_activity", + "send_messages", + "manage_templates", + ] + }, + { + "view_activity", + "send_messages", + "manage_templates", + }, + ), + ( + {}, + set(), + ), + ], +) def test_edit_user_permissions( client_request, mocker, @@ -571,16 +594,13 @@ def test_edit_user_permissions( permissions_sent_to_api, ): client_request.post( - 'main.edit_user_permissions', + "main.edit_user_permissions", service_id=SERVICE_ONE_ID, user_id=fake_uuid, - _data=dict( - email_address="test@example.com", - **submitted_permissions - ), + _data=dict(email_address="test@example.com", **submitted_permissions), _expected_status=302, _expected_redirect=url_for( - 'main.manage_users', + "main.manage_users", service_id=SERVICE_ONE_ID, ), ) @@ -588,7 +608,7 @@ def test_edit_user_permissions( fake_uuid, SERVICE_ONE_ID, permissions=permissions_sent_to_api, - folder_permissions=[] + folder_permissions=[], ) @@ -603,32 +623,43 @@ def test_edit_user_folder_permissions( fake_uuid, ): mock_get_template_folders.return_value = [ - {'id': 'folder-id-1', 'name': 'folder_one', 'parent_id': None, 'users_with_permission': []}, - {'id': 'folder-id-2', 'name': 'folder_one', 'parent_id': None, 'users_with_permission': []}, - {'id': 'folder-id-3', 'name': 'folder_one', 'parent_id': 'folder-id-1', 'users_with_permission': []}, + { + "id": "folder-id-1", + "name": "folder_one", + "parent_id": None, + "users_with_permission": [], + }, + { + "id": "folder-id-2", + "name": "folder_one", + "parent_id": None, + "users_with_permission": [], + }, + { + "id": "folder-id-3", + "name": "folder_one", + "parent_id": "folder-id-1", + "users_with_permission": [], + }, ] page = client_request.get( - 'main.edit_user_permissions', + "main.edit_user_permissions", service_id=SERVICE_ONE_ID, user_id=fake_uuid, ) assert [ - item['value'] for item in page.select('input[name=folder_permissions]') - ] == [ - 'folder-id-1', 'folder-id-3', 'folder-id-2' - ] + item["value"] for item in page.select("input[name=folder_permissions]") + ] == ["folder-id-1", "folder-id-3", "folder-id-2"] client_request.post( - 'main.edit_user_permissions', + "main.edit_user_permissions", service_id=SERVICE_ONE_ID, user_id=fake_uuid, - _data=dict( - folder_permissions=['folder-id-1', 'folder-id-3'] - ), + _data=dict(folder_permissions=["folder-id-1", "folder-id-3"]), _expected_status=302, _expected_redirect=url_for( - 'main.manage_users', + "main.manage_users", service_id=SERVICE_ONE_ID, ), ) @@ -636,7 +667,7 @@ def test_edit_user_folder_permissions( fake_uuid, SERVICE_ONE_ID, permissions=set(), - folder_permissions=['folder-id-1', 'folder-id-3'] + folder_permissions=["folder-id-1", "folder-id-3"], ) @@ -650,41 +681,61 @@ def test_cant_edit_user_folder_permissions_for_platform_admin_users( mock_get_template_folders, platform_admin_user, ): - service_one['permissions'] = ['edit_folder_permissions'] - mocker.patch( - 'app.user_api_client.get_user', return_value=platform_admin_user - ) + service_one["permissions"] = ["edit_folder_permissions"] + mocker.patch("app.user_api_client.get_user", return_value=platform_admin_user) mock_get_template_folders.return_value = [ - {'id': 'folder-id-1', 'name': 'folder_one', 'parent_id': None, 'users_with_permission': []}, - {'id': 'folder-id-2', 'name': 'folder_one', 'parent_id': None, 'users_with_permission': []}, - {'id': 'folder-id-3', 'name': 'folder_one', 'parent_id': 'folder-id-1', 'users_with_permission': []}, + { + "id": "folder-id-1", + "name": "folder_one", + "parent_id": None, + "users_with_permission": [], + }, + { + "id": "folder-id-2", + "name": "folder_one", + "parent_id": None, + "users_with_permission": [], + }, + { + "id": "folder-id-3", + "name": "folder_one", + "parent_id": "folder-id-1", + "users_with_permission": [], + }, ] page = client_request.get( - 'main.edit_user_permissions', + "main.edit_user_permissions", service_id=SERVICE_ONE_ID, - user_id=platform_admin_user['id'], + user_id=platform_admin_user["id"], ) - assert normalize_spaces(page.select('main .usa-body')[0].text) == 'platform@admin.gsa.gov Change email address' - assert normalize_spaces(page.select('main .usa-body')[1].text) == ( - 'Platform admin users can access all template folders.' + assert ( + normalize_spaces(page.select("main .usa-body")[0].text) + == "platform@admin.gsa.gov Change email address" ) - assert page.select('input[name=folder_permissions]') == [] + assert normalize_spaces(page.select("main .usa-body")[1].text) == ( + "Platform admin users can access all template folders." + ) + assert page.select("input[name=folder_permissions]") == [] client_request.post( - 'main.edit_user_permissions', + "main.edit_user_permissions", service_id=SERVICE_ONE_ID, - user_id=platform_admin_user['id'], + user_id=platform_admin_user["id"], _data={}, _expected_status=302, _expected_redirect=url_for( - 'main.manage_users', + "main.manage_users", service_id=SERVICE_ONE_ID, ), ) mock_set_user_permissions.assert_called_with( - platform_admin_user['id'], + platform_admin_user["id"], SERVICE_ONE_ID, permissions={ - 'manage_api_keys', 'manage_service', 'manage_templates', 'send_messages', 'view_activity', + "manage_api_keys", + "manage_service", + "manage_templates", + "send_messages", + "view_activity", }, folder_permissions=None, ) @@ -697,12 +748,12 @@ def test_cant_edit_non_member_user_permissions( mock_set_user_permissions, ): client_request.post( - 'main.edit_user_permissions', + "main.edit_user_permissions", service_id=SERVICE_ONE_ID, user_id=USER_ONE_ID, _data={ - 'email_address': 'test@example.com', - 'manage_service': 'y', + "email_address": "test@example.com", + "manage_service": "y", }, _expected_status=404, ) @@ -719,44 +770,43 @@ def test_edit_user_permissions_including_authentication_with_email_auth_service( mock_update_user_attribute, mock_get_template_folders, ): - active_user_with_permissions['auth_type'] = 'email_auth' - service_one['permissions'].append('email_auth') + active_user_with_permissions["auth_type"] = "email_auth" + service_one["permissions"].append("email_auth") client_request.post( - 'main.edit_user_permissions', + "main.edit_user_permissions", service_id=SERVICE_ONE_ID, - user_id=active_user_with_permissions['id'], + user_id=active_user_with_permissions["id"], _data={ - 'email_address': active_user_with_permissions['email_address'], - 'permissions_field': [ - 'send_messages', - 'manage_templates', - 'manage_service', - 'manage_api_keys', + "email_address": active_user_with_permissions["email_address"], + "permissions_field": [ + "send_messages", + "manage_templates", + "manage_service", + "manage_api_keys", ], - 'login_authentication': 'sms_auth', + "login_authentication": "sms_auth", }, _expected_status=302, _expected_redirect=url_for( - 'main.manage_users', + "main.manage_users", service_id=SERVICE_ONE_ID, ), ) mock_set_user_permissions.assert_called_with( - str(active_user_with_permissions['id']), + str(active_user_with_permissions["id"]), SERVICE_ONE_ID, permissions={ - 'send_messages', - 'manage_templates', - 'manage_service', - 'manage_api_keys', + "send_messages", + "manage_templates", + "manage_service", + "manage_api_keys", }, - folder_permissions=[] + folder_permissions=[], ) mock_update_user_attribute.assert_called_with( - str(active_user_with_permissions['id']), - auth_type='sms_auth' + str(active_user_with_permissions["id"]), auth_type="sms_auth" ) @@ -767,18 +817,18 @@ def test_edit_user_permissions_shows_authentication_for_email_auth_service( mock_get_template_folders, active_user_with_permissions, ): - service_one['permissions'].append('email_auth') + service_one["permissions"].append("email_auth") page = client_request.get( - 'main.edit_user_permissions', + "main.edit_user_permissions", service_id=SERVICE_ONE_ID, - user_id=active_user_with_permissions['id'], + user_id=active_user_with_permissions["id"], ) radio_buttons = page.select("input[name=login_authentication]") values = {button["value"] for button in radio_buttons} - assert values == {'sms_auth', 'email_auth'} + assert values == {"sms_auth", "email_auth"} assert not any(button.has_attr("disabled") for button in radio_buttons) @@ -789,12 +839,12 @@ def test_should_show_page_for_inviting_user( ): client_request.login(active_user_with_permissions) page = client_request.get( - 'main.invite_user', + "main.invite_user", service_id=SERVICE_ONE_ID, ) - assert 'Invite a team member' in page.find('h1').text.strip() - assert not page.find('div', class_='checkboxes-nested') + assert "Invite a team member" in page.find("h1").text.strip() + assert not page.find("div", class_="checkboxes-nested") def test_should_show_page_for_inviting_user_with_email_prefilled( @@ -809,25 +859,26 @@ def test_should_show_page_for_inviting_user_with_email_prefilled( mock_get_invites_for_service, ): client_request.login(active_user_with_permissions) - service_one['organization'] = ORGANISATION_ID - mocker.patch('app.models.user.user_api_client.get_user', side_effect=[ - active_user_with_permission_to_other_service, - ]) + service_one["organization"] = ORGANISATION_ID + mocker.patch( + "app.models.user.user_api_client.get_user", + side_effect=[ + active_user_with_permission_to_other_service, + ], + ) page = client_request.get( - 'main.invite_user', + "main.invite_user", service_id=SERVICE_ONE_ID, user_id=fake_uuid, # We have the user’s name in the H1 but don’t want it duplicated # in the page title _test_page_title=False, ) - assert normalize_spaces(page.select_one('title').text).startswith( - 'Invite a team member' - ) - assert normalize_spaces(page.select_one('h1').text) == ( - 'Invite Service Two User' + assert normalize_spaces(page.select_one("title").text).startswith( + "Invite a team member" ) + assert normalize_spaces(page.select_one("h1").text) == ("Invite Service Two User") # assert normalize_spaces(page.select_one('main .gov-uk').text) == ( # 'service-two-user@test.gsa.gov' # ) @@ -843,23 +894,26 @@ def test_should_show_page_if_prefilled_user_is_already_a_team_member( active_caseworking_user, ): client_request.login(active_user_with_permissions) - mocker.patch('app.models.user.user_api_client.get_user', side_effect=[ - active_caseworking_user, - ]) + mocker.patch( + "app.models.user.user_api_client.get_user", + side_effect=[ + active_caseworking_user, + ], + ) page = client_request.get( - 'main.invite_user', + "main.invite_user", service_id=SERVICE_ONE_ID, user_id=fake_uuid, ) - assert normalize_spaces(page.select_one('title').text).startswith( - 'This person is already a team member' + assert normalize_spaces(page.select_one("title").text).startswith( + "This person is already a team member" ) - assert normalize_spaces(page.select_one('h1').text) == ( - 'This person is already a team member' + assert normalize_spaces(page.select_one("h1").text) == ( + "This person is already a team member" ) - assert normalize_spaces(page.select_one('main p').text) == ( - 'Test User is already member of ‘service one’.' + assert normalize_spaces(page.select_one("main p").text) == ( + "Test User is already member of ‘service one’." ) assert not page.select("form") @@ -873,28 +927,31 @@ def test_should_show_page_if_prefilled_user_is_already_invited( active_user_with_permission_to_other_service, mock_get_invites_for_service, ): - active_user_with_permission_to_other_service['email_address'] = ( - 'user_1@testnotify.gsa.gov' - ) + active_user_with_permission_to_other_service[ + "email_address" + ] = "user_1@testnotify.gsa.gov" client_request.login(active_user_with_permissions) - mocker.patch('app.models.user.user_api_client.get_user', side_effect=[ - active_user_with_permission_to_other_service, - ]) + mocker.patch( + "app.models.user.user_api_client.get_user", + side_effect=[ + active_user_with_permission_to_other_service, + ], + ) page = client_request.get( - 'main.invite_user', + "main.invite_user", service_id=SERVICE_ONE_ID, user_id=fake_uuid, ) - assert normalize_spaces(page.select_one('title').text).startswith( - 'This person has already received an invite' + assert normalize_spaces(page.select_one("title").text).startswith( + "This person has already received an invite" ) - assert normalize_spaces(page.select_one('h1').text) == ( - 'This person has already received an invite' + assert normalize_spaces(page.select_one("h1").text) == ( + "This person has already received an invite" ) - assert normalize_spaces(page.select_one('main .usa-body').text) == ( - 'Service Two User has not accepted their invitation to ' - '‘service one’ yet. You do not need to do anything.' + assert normalize_spaces(page.select_one("main .usa-body").text) == ( + "Service Two User has not accepted their invitation to " + "‘service one’ yet. You do not need to do anything." ) assert not page.select("form") @@ -910,13 +967,16 @@ def test_should_403_if_trying_to_prefill_email_address_for_user_with_no_organiza mock_get_invites_for_service, mock_get_no_organization_by_domain, ): - service_one['organization'] = ORGANISATION_ID + service_one["organization"] = ORGANISATION_ID client_request.login(active_user_with_permissions) - mocker.patch('app.models.user.user_api_client.get_user', side_effect=[ - active_user_with_permission_to_other_service, - ]) + mocker.patch( + "app.models.user.user_api_client.get_user", + side_effect=[ + active_user_with_permission_to_other_service, + ], + ) client_request.get( - 'main.invite_user', + "main.invite_user", service_id=SERVICE_ONE_ID, user_id=fake_uuid, _expected_status=403, @@ -934,13 +994,16 @@ def test_should_403_if_trying_to_prefill_email_address_for_user_from_other_organ mock_get_invites_for_service, mock_get_organization_by_domain, ): - service_one['organization'] = ORGANISATION_TWO_ID + service_one["organization"] = ORGANISATION_TWO_ID client_request.login(active_user_with_permissions) - mocker.patch('app.models.user.user_api_client.get_user', side_effect=[ - active_user_with_permission_to_other_service, - ]) + mocker.patch( + "app.models.user.user_api_client.get_user", + side_effect=[ + active_user_with_permission_to_other_service, + ], + ) client_request.get( - 'main.invite_user', + "main.invite_user", service_id=SERVICE_ONE_ID, user_id=fake_uuid, _expected_status=403, @@ -948,31 +1011,43 @@ def test_should_403_if_trying_to_prefill_email_address_for_user_from_other_organ def test_should_show_folder_permission_form_if_service_has_folder_permissions_enabled( - client_request, - mocker, - mock_get_template_folders, - service_one + client_request, mocker, mock_get_template_folders, service_one ): mock_get_template_folders.return_value = [ - {'id': 'folder-id-1', 'name': 'folder_one', 'parent_id': None, 'users_with_permission': []}, - {'id': 'folder-id-2', 'name': 'folder_two', 'parent_id': None, 'users_with_permission': []}, - {'id': 'folder-id-3', 'name': 'folder_three', 'parent_id': 'folder-id-1', 'users_with_permission': []}, + { + "id": "folder-id-1", + "name": "folder_one", + "parent_id": None, + "users_with_permission": [], + }, + { + "id": "folder-id-2", + "name": "folder_two", + "parent_id": None, + "users_with_permission": [], + }, + { + "id": "folder-id-3", + "name": "folder_three", + "parent_id": "folder-id-1", + "users_with_permission": [], + }, ] page = client_request.get( - 'main.invite_user', + "main.invite_user", service_id=SERVICE_ONE_ID, ) - assert 'Invite a team member' in page.find('h1').text.strip() + assert "Invite a team member" in page.find("h1").text.strip() - folder_checkboxes = page.find('div', class_='selection-wrapper').find_all('li') + folder_checkboxes = page.find("div", class_="selection-wrapper").find_all("li") assert len(folder_checkboxes) == 3 -@pytest.mark.parametrize('email_address, gov_user', [ - ('test@example.gsa.gov', True), - ('test@example.com', False) -]) +@pytest.mark.parametrize( + "email_address, gov_user", + [("test@example.gsa.gov", True), ("test@example.com", False)], +) def test_invite_user( client_request, active_user_with_permissions, @@ -983,39 +1058,52 @@ def test_invite_user( mock_get_template_folders, mock_get_organizations, ): - sample_invite['email_address'] = email_address + sample_invite["email_address"] = email_address assert is_gov_user(email_address) == gov_user - mocker.patch('app.models.user.InvitedUsers.client_method', return_value=[sample_invite]) - mocker.patch('app.models.user.Users.client_method', return_value=[active_user_with_permissions]) - mocker.patch('app.invite_api_client.create_invite', return_value=sample_invite) + mocker.patch( + "app.models.user.InvitedUsers.client_method", return_value=[sample_invite] + ) + mocker.patch( + "app.models.user.Users.client_method", + return_value=[active_user_with_permissions], + ) + mocker.patch("app.invite_api_client.create_invite", return_value=sample_invite) page = client_request.post( - 'main.invite_user', + "main.invite_user", service_id=SERVICE_ONE_ID, _data={ - 'email_address': email_address, - 'permissions_field': [ - 'view_activity', - 'send_messages', - 'manage_templates', - 'manage_service', - 'manage_api_keys', - ] + "email_address": email_address, + "permissions_field": [ + "view_activity", + "send_messages", + "manage_templates", + "manage_service", + "manage_api_keys", + ], }, _follow_redirects=True, ) - assert page.h1.string.strip() == 'Team members' - flash_banner = page.find('div', class_='banner-default-with-tick').string.strip() + assert page.h1.string.strip() == "Team members" + flash_banner = page.find("div", class_="banner-default-with-tick").string.strip() assert flash_banner == f"Invite sent to {email_address}" - expected_permissions = {'manage_api_keys', 'manage_service', 'manage_templates', 'send_messages', 'view_activity'} + expected_permissions = { + "manage_api_keys", + "manage_service", + "manage_templates", + "send_messages", + "view_activity", + } - app.invite_api_client.create_invite.assert_called_once_with(sample_invite['from_user'], - sample_invite['service'], - email_address, - expected_permissions, - 'sms_auth', - []) + app.invite_api_client.create_invite.assert_called_once_with( + sample_invite["from_user"], + sample_invite["service"], + email_address, + expected_permissions, + "sms_auth", + [], + ) def test_invite_user_when_email_address_is_prefilled( @@ -1030,42 +1118,42 @@ def test_invite_user_when_email_address_is_prefilled( mock_get_invites_for_service, mock_get_organization_by_domain, ): - service_one['organization'] = ORGANISATION_ID + service_one["organization"] = ORGANISATION_ID client_request.login(active_user_with_permissions) - mocker.patch('app.models.user.user_api_client.get_user', side_effect=[ - active_user_with_permission_to_other_service, - ]) - mocker.patch('app.invite_api_client.create_invite', return_value=sample_invite) + mocker.patch( + "app.models.user.user_api_client.get_user", + side_effect=[ + active_user_with_permission_to_other_service, + ], + ) + mocker.patch("app.invite_api_client.create_invite", return_value=sample_invite) client_request.post( - 'main.invite_user', + "main.invite_user", service_id=SERVICE_ONE_ID, user_id=fake_uuid, _data={ # No posted email address - 'permissions_field': [ - 'send_messages', + "permissions_field": [ + "send_messages", ], }, ) app.invite_api_client.create_invite.assert_called_once_with( - active_user_with_permissions['id'], + active_user_with_permissions["id"], SERVICE_ONE_ID, - active_user_with_permission_to_other_service['email_address'], - {'send_messages'}, - 'sms_auth', + active_user_with_permission_to_other_service["email_address"], + {"send_messages"}, + "sms_auth", [], ) -@pytest.mark.parametrize('auth_type', [ - ('sms_auth'), - ('email_auth') -]) -@pytest.mark.parametrize('email_address, gov_user', [ - ('test@example.gsa.gov', True), - ('test@example.com', False) -]) +@pytest.mark.parametrize("auth_type", [("sms_auth"), ("email_auth")]) +@pytest.mark.parametrize( + "email_address, gov_user", + [("test@example.gsa.gov", True), ("test@example.com", False)], +) def test_invite_user_with_email_auth_service( client_request, service_one, @@ -1078,44 +1166,57 @@ def test_invite_user_with_email_auth_service( mock_get_organizations, mock_get_template_folders, ): - service_one['permissions'].append('email_auth') - sample_invite['email_address'] = 'test@example.gsa.gov' + service_one["permissions"].append("email_auth") + sample_invite["email_address"] = "test@example.gsa.gov" assert is_gov_user(email_address) is gov_user - mocker.patch('app.models.user.InvitedUsers.client_method', return_value=[sample_invite]) - mocker.patch('app.models.user.Users.client_method', return_value=[active_user_with_permissions]) - mocker.patch('app.invite_api_client.create_invite', return_value=sample_invite) + mocker.patch( + "app.models.user.InvitedUsers.client_method", return_value=[sample_invite] + ) + mocker.patch( + "app.models.user.Users.client_method", + return_value=[active_user_with_permissions], + ) + mocker.patch("app.invite_api_client.create_invite", return_value=sample_invite) page = client_request.post( - 'main.invite_user', + "main.invite_user", service_id=SERVICE_ONE_ID, _data={ - 'email_address': email_address, - 'permissions_field': [ - 'view_activity', - 'send_messages', - 'manage_templates', - 'manage_service', - 'manage_api_keys', + "email_address": email_address, + "permissions_field": [ + "view_activity", + "send_messages", + "manage_templates", + "manage_service", + "manage_api_keys", ], - 'login_authentication': auth_type, + "login_authentication": auth_type, }, _follow_redirects=True, _expected_status=200, ) - assert page.h1.string.strip() == 'Team members' - flash_banner = page.find('div', class_='banner-default-with-tick').string.strip() - assert flash_banner == 'Invite sent to test@example.gsa.gov' + assert page.h1.string.strip() == "Team members" + flash_banner = page.find("div", class_="banner-default-with-tick").string.strip() + assert flash_banner == "Invite sent to test@example.gsa.gov" - expected_permissions = {'manage_api_keys', 'manage_service', 'manage_templates', 'send_messages', 'view_activity'} + expected_permissions = { + "manage_api_keys", + "manage_service", + "manage_templates", + "send_messages", + "view_activity", + } - app.invite_api_client.create_invite.assert_called_once_with(sample_invite['from_user'], - sample_invite['service'], - email_address, - expected_permissions, - auth_type, - []) + app.invite_api_client.create_invite.assert_called_once_with( + sample_invite["from_user"], + sample_invite["service"], + email_address, + expected_permissions, + auth_type, + [], + ) def test_cancel_invited_user_cancels_user_invitations( @@ -1127,24 +1228,26 @@ def test_cancel_invited_user_cancels_user_invitations( mock_get_template_folders, mocker, ): - mock_cancel = mocker.patch('app.invite_api_client.cancel_invited_user') - mocker.patch('app.invite_api_client.get_invited_user_for_service', return_value=sample_invite) + mock_cancel = mocker.patch("app.invite_api_client.cancel_invited_user") + mocker.patch( + "app.invite_api_client.get_invited_user_for_service", return_value=sample_invite + ) page = client_request.get( - 'main.cancel_invited_user', + "main.cancel_invited_user", service_id=SERVICE_ONE_ID, - invited_user_id=sample_invite['id'], + invited_user_id=sample_invite["id"], _follow_redirects=True, ) - assert normalize_spaces(page.h1.text) == 'Team members' + assert normalize_spaces(page.h1.text) == "Team members" flash_banner = normalize_spaces( - page.find('div', class_='banner-default-with-tick').text + page.find("div", class_="banner-default-with-tick").text ) assert flash_banner == f"Invitation cancelled for {sample_invite['email_address']}" mock_cancel.assert_called_once_with( service_id=SERVICE_ONE_ID, - invited_user_id=sample_invite['id'], + invited_user_id=sample_invite["id"], ) @@ -1153,9 +1256,9 @@ def test_cancel_invited_user_doesnt_work_if_user_not_invited_to_this_service( mock_get_invites_for_service, mocker, ): - mock_cancel = mocker.patch('app.invite_api_client.cancel_invited_user') + mock_cancel = mocker.patch("app.invite_api_client.cancel_invited_user") client_request.get( - 'main.cancel_invited_user', + "main.cancel_invited_user", service_id=SERVICE_ONE_ID, invited_user_id=sample_uuid(), _expected_status=404, @@ -1163,28 +1266,37 @@ def test_cancel_invited_user_doesnt_work_if_user_not_invited_to_this_service( assert mock_cancel.called is False -@pytest.mark.parametrize('invite_status, expected_text', [ - ('pending', ( - 'invited_user@test.gsa.gov (invited) ' - 'Permissions ' - 'Can See dashboard ' - 'Can Send messages ' - 'Cannot Add and edit templates ' - 'Can Manage settings, team and usage ' - 'Can Manage API integration ' - 'Cancel invitation for invited_user@test.gsa.gov' - )), - ('cancelled', ( - 'invited_user@test.gsa.gov (cancelled invite) ' - 'Permissions ' - # all permissions are greyed out - 'Cannot See dashboard ' - 'Cannot Send messages ' - 'Cannot Add and edit templates ' - 'Cannot Manage settings, team and usage ' - 'Cannot Manage API integration' - )), -]) +@pytest.mark.parametrize( + "invite_status, expected_text", + [ + ( + "pending", + ( + "invited_user@test.gsa.gov (invited) " + "Permissions " + "Can See dashboard " + "Can Send messages " + "Cannot Add and edit templates " + "Can Manage settings, team and usage " + "Can Manage API integration " + "Cancel invitation for invited_user@test.gsa.gov" + ), + ), + ( + "cancelled", + ( + "invited_user@test.gsa.gov (cancelled invite) " + "Permissions " + # all permissions are greyed out + "Cannot See dashboard " + "Cannot Send messages " + "Cannot Add and edit templates " + "Cannot Manage settings, team and usage " + "Cannot Manage API integration" + ), + ), + ], +) def test_manage_users_shows_invited_user( client_request, mocker, @@ -1194,13 +1306,18 @@ def test_manage_users_shows_invited_user( invite_status, expected_text, ): - sample_invite['status'] = invite_status - mocker.patch('app.models.user.InvitedUsers.client_method', return_value=[sample_invite]) - mocker.patch('app.models.user.Users.client_method', return_value=[active_user_with_permissions]) + sample_invite["status"] = invite_status + mocker.patch( + "app.models.user.InvitedUsers.client_method", return_value=[sample_invite] + ) + mocker.patch( + "app.models.user.Users.client_method", + return_value=[active_user_with_permissions], + ) - page = client_request.get('main.manage_users', service_id=SERVICE_ONE_ID) - assert page.h1.string.strip() == 'Team members' - assert normalize_spaces(page.select('.user-list-item')[0].text) == expected_text + page = client_request.get("main.manage_users", service_id=SERVICE_ONE_ID) + assert page.h1.string.strip() == "Team members" + assert normalize_spaces(page.select(".user-list-item")[0].text) == expected_text def test_manage_users_does_not_show_accepted_invite( @@ -1211,17 +1328,22 @@ def test_manage_users_does_not_show_accepted_invite( mock_get_template_folders, ): invited_user_id = uuid.uuid4() - sample_invite['id'] = invited_user_id - sample_invite['status'] = 'accepted' - mocker.patch('app.models.user.InvitedUsers.client_method', return_value=[sample_invite]) - mocker.patch('app.models.user.Users.client_method', return_value=[active_user_with_permissions]) + sample_invite["id"] = invited_user_id + sample_invite["status"] = "accepted" + mocker.patch( + "app.models.user.InvitedUsers.client_method", return_value=[sample_invite] + ) + mocker.patch( + "app.models.user.Users.client_method", + return_value=[active_user_with_permissions], + ) - page = client_request.get('main.manage_users', service_id=SERVICE_ONE_ID) + page = client_request.get("main.manage_users", service_id=SERVICE_ONE_ID) - assert page.h1.string.strip() == 'Team members' - user_lists = page.find_all('div', {'class': 'user-list'}) + assert page.h1.string.strip() == "Team members" + user_lists = page.find_all("div", {"class": "user-list"}) assert len(user_lists) == 1 - assert not page.find(text='invited_user@test.gsa.gov') + assert not page.find(text="invited_user@test.gsa.gov") def test_user_cant_invite_themselves( @@ -1232,21 +1354,17 @@ def test_user_cant_invite_themselves( mock_get_template_folders, ): page = client_request.post( - 'main.invite_user', + "main.invite_user", service_id=SERVICE_ONE_ID, _data={ - 'email_address': active_user_with_permissions['email_address'], - 'permissions_field': [ - 'send_messages', - 'manage_service', - 'manage_api_keys' - ] + "email_address": active_user_with_permissions["email_address"], + "permissions_field": ["send_messages", "manage_service", "manage_api_keys"], }, _follow_redirects=True, _expected_status=200, ) - assert page.h1.string.strip() == 'Invite a team member' - form_error = page.find('span', class_='usa-error-message').text.strip() + assert page.h1.string.strip() == "Invite a team member" + form_error = page.find("span", class_="usa-error-message").text.strip() assert form_error == "Error: You cannot send an invitation to yourself" assert not mock_create_invite.called @@ -1260,18 +1378,21 @@ def test_no_permission_manage_users_page( api_user_active, mocker, ): - resp_text = client_request.get('main.manage_users', service_id=service_one['id']) - assert url_for('.invite_user', service_id=service_one['id']) not in resp_text + resp_text = client_request.get("main.manage_users", service_id=service_one["id"]) + assert url_for(".invite_user", service_id=service_one["id"]) not in resp_text assert "Edit permission" not in resp_text assert "Team members" not in resp_text -@pytest.mark.parametrize('folders_user_can_see, expected_message', [ - (3, 'Can see all folders'), - (2, 'Can see 2 folders'), - (1, 'Can see 1 folder'), - (0, 'Cannot see any folders'), -]) +@pytest.mark.parametrize( + "folders_user_can_see, expected_message", + [ + (3, "Can see all folders"), + (2, "Can see 2 folders"), + (1, "Can see 1 folder"), + (0, "Cannot see any folders"), + ], +) def test_manage_user_page_shows_how_many_folders_user_can_view( client_request, service_one, @@ -1280,21 +1401,43 @@ def test_manage_user_page_shows_how_many_folders_user_can_view( mock_get_invites_for_service, api_user_active, folders_user_can_see, - expected_message + expected_message, ): - service_one['permissions'] = ['edit_folder_permissions'] + service_one["permissions"] = ["edit_folder_permissions"] mock_get_template_folders.return_value = [ - {'id': 'folder-id-1', 'name': 'f1', 'parent_id': None, 'users_with_permission': []}, - {'id': 'folder-id-2', 'name': 'f2', 'parent_id': None, 'users_with_permission': []}, - {'id': 'folder-id-3', 'name': 'f3', 'parent_id': None, 'users_with_permission': []}, + { + "id": "folder-id-1", + "name": "f1", + "parent_id": None, + "users_with_permission": [], + }, + { + "id": "folder-id-2", + "name": "f2", + "parent_id": None, + "users_with_permission": [], + }, + { + "id": "folder-id-3", + "name": "f3", + "parent_id": None, + "users_with_permission": [], + }, ] for i in range(folders_user_can_see): - mock_get_template_folders.return_value[i]['users_with_permission'].append(api_user_active['id']) + mock_get_template_folders.return_value[i]["users_with_permission"].append( + api_user_active["id"] + ) - page = client_request.get('main.manage_users', service_id=service_one['id']) + page = client_request.get("main.manage_users", service_id=service_one["id"]) - user_div = page.select_one("h2[title='notify@digital.cabinet-office.gov.uk']").parent - assert user_div.select_one('.tick-cross-list-hint:last-child').text.strip() == expected_message + user_div = page.select_one( + "h2[title='notify@digital.cabinet-office.gov.uk']" + ).parent + assert ( + user_div.select_one(".tick-cross-list-hint:last-child").text.strip() + == expected_message + ) def test_manage_user_page_doesnt_show_folder_hint_if_service_has_no_folders( @@ -1305,13 +1448,15 @@ def test_manage_user_page_doesnt_show_folder_hint_if_service_has_no_folders( mock_get_invites_for_service, api_user_active, ): - service_one['permissions'] = ['edit_folder_permissions'] + service_one["permissions"] = ["edit_folder_permissions"] mock_get_template_folders.return_value = [] - page = client_request.get('main.manage_users', service_id=service_one['id']) + page = client_request.get("main.manage_users", service_id=service_one["id"]) - user_div = page.select_one("h2[title='notify@digital.cabinet-office.gov.uk']").parent - assert user_div.find('.tick-cross-list-hint:last-child') is None + user_div = page.select_one( + "h2[title='notify@digital.cabinet-office.gov.uk']" + ).parent + assert user_div.find(".tick-cross-list-hint:last-child") is None def test_manage_user_page_doesnt_show_folder_hint_if_service_cant_edit_folder_permissions( @@ -1320,17 +1465,24 @@ def test_manage_user_page_doesnt_show_folder_hint_if_service_cant_edit_folder_pe mock_get_template_folders, mock_get_users_by_service, mock_get_invites_for_service, - api_user_active + api_user_active, ): - service_one['permissions'] = [] + service_one["permissions"] = [] mock_get_template_folders.return_value = [ - {'id': 'folder-id-1', 'name': 'f1', 'parent_id': None, 'users_with_permission': [api_user_active['id']]}, + { + "id": "folder-id-1", + "name": "f1", + "parent_id": None, + "users_with_permission": [api_user_active["id"]], + }, ] - page = client_request.get('main.manage_users', service_id=service_one['id']) + page = client_request.get("main.manage_users", service_id=service_one["id"]) - user_div = page.select_one("h2[title='notify@digital.cabinet-office.gov.uk']").parent - assert user_div.find('.tick-cross-list-hint:last-child') is None + user_div = page.select_one( + "h2[title='notify@digital.cabinet-office.gov.uk']" + ).parent + assert user_div.find(".tick-cross-list-hint:last-child") is None def test_remove_user_from_service( @@ -1339,25 +1491,26 @@ def test_remove_user_from_service( api_user_active, service_one, mock_remove_user_from_service, - mocker + mocker, ): - mock_event_handler = mocker.patch('app.main.views.manage_users.create_remove_user_from_service_event') + mock_event_handler = mocker.patch( + "app.main.views.manage_users.create_remove_user_from_service_event" + ) client_request.post( - 'main.remove_user_from_service', - service_id=service_one['id'], - user_id=active_user_with_permissions['id'], - _expected_redirect=url_for('main.manage_users', service_id=service_one['id']) + "main.remove_user_from_service", + service_id=service_one["id"], + user_id=active_user_with_permissions["id"], + _expected_redirect=url_for("main.manage_users", service_id=service_one["id"]), ) mock_remove_user_from_service.assert_called_once_with( - service_one['id'], - str(active_user_with_permissions['id']) + service_one["id"], str(active_user_with_permissions["id"]) ) mock_event_handler.assert_called_once_with( - user_id=active_user_with_permissions['id'], - removed_by_id=api_user_active['id'], - service_id=service_one['id'], + user_id=active_user_with_permissions["id"], + removed_by_id=api_user_active["id"], + service_id=service_one["id"], ) @@ -1370,13 +1523,16 @@ def test_can_invite_user_as_platform_admin( mock_get_template_folders, mocker, ): - mocker.patch('app.models.user.Users.client_method', return_value=[active_user_with_permissions]) + mocker.patch( + "app.models.user.Users.client_method", + return_value=[active_user_with_permissions], + ) page = client_request.get( - 'main.manage_users', + "main.manage_users", service_id=SERVICE_ONE_ID, ) - assert url_for('.invite_user', service_id=service_one['id']) in str(page) + assert url_for(".invite_user", service_id=service_one["id"]) in str(page) def test_edit_user_email_page( @@ -1384,21 +1540,21 @@ def test_edit_user_email_page( active_user_with_permissions, service_one, mock_get_users_by_service, - mocker + mocker, ): user = active_user_with_permissions - mocker.patch('app.user_api_client.get_user', return_value=user) + mocker.patch("app.user_api_client.get_user", return_value=user) page = client_request.get( - 'main.edit_user_email', - service_id=service_one['id'], - user_id=sample_uuid() + "main.edit_user_email", service_id=service_one["id"], user_id=sample_uuid() ) - assert page.find('h1').text == "Change team member’s email address" - assert page.select('p[id=user_name]')[0].text == "This will change the email address for {}.".format(user['name']) - assert page.select('input[type=email]')[0].attrs["value"] == user['email_address'] - assert normalize_spaces(page.select('main button[type=submit]')[0].text) == "Save" + assert page.find("h1").text == "Change team member’s email address" + assert page.select("p[id=user_name]")[ + 0 + ].text == "This will change the email address for {}.".format(user["name"]) + assert page.select("input[type=email]")[0].attrs["value"] == user["email_address"] + assert normalize_spaces(page.select("main button[type=submit]")[0].text) == "Save" def test_edit_user_email_page_404_for_non_team_member( @@ -1406,7 +1562,7 @@ def test_edit_user_email_page_404_for_non_team_member( mock_get_users_by_service, ): client_request.get( - 'main.edit_user_email', + "main.edit_user_email", service_id=SERVICE_ONE_ID, user_id=USER_ONE_ID, _expected_status=404, @@ -1420,20 +1576,23 @@ def test_edit_user_email_redirects_to_confirmation( mock_get_user_by_email_not_found, ): client_request.post( - 'main.edit_user_email', + "main.edit_user_email", service_id=SERVICE_ONE_ID, - user_id=active_user_with_permissions['id'], + user_id=active_user_with_permissions["id"], _expected_status=302, _expected_redirect=url_for( - 'main.confirm_edit_user_email', + "main.confirm_edit_user_email", service_id=SERVICE_ONE_ID, - user_id=active_user_with_permissions['id'], + user_id=active_user_with_permissions["id"], ), ) with client_request.session_transaction() as session: - assert session[ - 'team_member_email_change-{}'.format(active_user_with_permissions['id']) - ] == 'test@user.gsa.gov' + assert ( + session[ + "team_member_email_change-{}".format(active_user_with_permissions["id"]) + ] + == "test@user.gsa.gov" + ) def test_edit_user_email_without_changing_goes_back_to_team_members( @@ -1444,22 +1603,20 @@ def test_edit_user_email_without_changing_goes_back_to_team_members( mock_update_user_attribute, ): client_request.post( - 'main.edit_user_email', + "main.edit_user_email", service_id=SERVICE_ONE_ID, - user_id=active_user_with_permissions['id'], - _data={ - 'email_address': active_user_with_permissions['email_address'] - }, + user_id=active_user_with_permissions["id"], + _data={"email_address": active_user_with_permissions["email_address"]}, _expected_status=302, _expected_redirect=url_for( - 'main.manage_users', + "main.manage_users", service_id=SERVICE_ONE_ID, ), ) assert mock_update_user_attribute.called is False -@pytest.mark.parametrize('original_email_address', ['test@gsa.gov', 'test@example.com']) +@pytest.mark.parametrize("original_email_address", ["test@gsa.gov", "test@example.com"]) def test_edit_user_email_can_change_any_email_address_to_a_gov_email_address( client_request, active_user_with_permissions, @@ -1469,20 +1626,18 @@ def test_edit_user_email_can_change_any_email_address_to_a_gov_email_address( mock_get_organizations, original_email_address, ): - active_user_with_permissions['email_address'] = original_email_address + active_user_with_permissions["email_address"] = original_email_address client_request.post( - 'main.edit_user_email', + "main.edit_user_email", service_id=SERVICE_ONE_ID, - user_id=active_user_with_permissions['id'], - _data={ - 'email_address': 'new-email-address@gsa.gov' - }, + user_id=active_user_with_permissions["id"], + _data={"email_address": "new-email-address@gsa.gov"}, _expected_status=302, _expected_redirect=url_for( - 'main.confirm_edit_user_email', + "main.confirm_edit_user_email", service_id=SERVICE_ONE_ID, - user_id=active_user_with_permissions['id'], + user_id=active_user_with_permissions["id"], ), ) @@ -1495,20 +1650,18 @@ def test_edit_user_email_can_change_a_non_gov_email_address_to_another_non_gov_e mock_update_user_attribute, mock_get_organizations, ): - active_user_with_permissions['email_address'] = 'old@example.com' + active_user_with_permissions["email_address"] = "old@example.com" client_request.post( - 'main.edit_user_email', + "main.edit_user_email", service_id=SERVICE_ONE_ID, - user_id=active_user_with_permissions['id'], - _data={ - 'email_address': 'new@example.com' - }, + user_id=active_user_with_permissions["id"], + _data={"email_address": "new@example.com"}, _expected_status=302, _expected_redirect=url_for( - 'main.confirm_edit_user_email', + "main.confirm_edit_user_email", service_id=SERVICE_ONE_ID, - user_id=active_user_with_permissions['id'], + user_id=active_user_with_permissions["id"], ), ) @@ -1522,17 +1675,21 @@ def test_edit_user_email_cannot_change_a_gov_email_address_to_a_non_gov_email_ad mock_get_organizations, ): page = client_request.post( - 'main.edit_user_email', + "main.edit_user_email", service_id=SERVICE_ONE_ID, - user_id=active_user_with_permissions['id'], - _data={ - 'email_address': 'new_email@example.com' - }, + user_id=active_user_with_permissions["id"], + _data={"email_address": "new_email@example.com"}, _expected_status=200, ) - assert 'Enter a public sector email address' in page.select_one('.usa-error-message').text + assert ( + "Enter a public sector email address" + in page.select_one(".usa-error-message").text + ) with client_request.session_transaction() as session: - assert 'team_member_email_change-{}'.format(active_user_with_permissions['id']) not in session + assert ( + "team_member_email_change-{}".format(active_user_with_permissions["id"]) + not in session + ) def test_confirm_edit_user_email_page( @@ -1541,26 +1698,28 @@ def test_confirm_edit_user_email_page( mock_get_users_by_service, mock_get_user, ): - new_email = 'new_email@gsa.gov' + new_email = "new_email@gsa.gov" with client_request.session_transaction() as session: session[ - 'team_member_email_change-{}'.format(active_user_with_permissions['id']) + "team_member_email_change-{}".format(active_user_with_permissions["id"]) ] = new_email page = client_request.get( - 'main.confirm_edit_user_email', + "main.confirm_edit_user_email", service_id=SERVICE_ONE_ID, - user_id=active_user_with_permissions['id'], + user_id=active_user_with_permissions["id"], ) - assert 'Confirm change of email address' in page.text + assert "Confirm change of email address" in page.text for text in [ - 'New email address:', + "New email address:", new_email, - 'We will send {} an email to tell them about the change.'.format(active_user_with_permissions['name']) + "We will send {} an email to tell them about the change.".format( + active_user_with_permissions["name"] + ), ]: assert text in page.text - assert 'Confirm' in page.text + assert "Confirm" in page.text def test_confirm_edit_user_email_page_redirects_if_session_empty( @@ -1569,12 +1728,12 @@ def test_confirm_edit_user_email_page_redirects_if_session_empty( active_user_with_permissions, ): page = client_request.get( - 'main.confirm_edit_user_email', + "main.confirm_edit_user_email", service_id=SERVICE_ONE_ID, - user_id=active_user_with_permissions['id'], + user_id=active_user_with_permissions["id"], _follow_redirects=True, ) - assert 'Confirm change of email address' not in page.text + assert "Confirm change of email address" not in page.text def test_confirm_edit_user_email_page_404s_for_non_team_member( @@ -1582,7 +1741,7 @@ def test_confirm_edit_user_email_page_404s_for_non_team_member( mock_get_users_by_service, ): client_request.get( - 'main.confirm_edit_user_email', + "main.confirm_edit_user_email", service_id=SERVICE_ONE_ID, user_id=USER_ONE_ID, _expected_status=404, @@ -1599,41 +1758,47 @@ def test_confirm_edit_user_email_changes_user_email( ): # We want active_user_with_permissions (the current user) to update the email address for api_user_active # By default both users would have the same id, so we change the id of api_user_active - api_user_active['id'] = str(uuid.uuid4()) - mocker.patch('app.models.user.Users.client_method', return_value=[api_user_active, active_user_with_permissions]) + api_user_active["id"] = str(uuid.uuid4()) + mocker.patch( + "app.models.user.Users.client_method", + return_value=[api_user_active, active_user_with_permissions], + ) # get_user gets called twice - first to check if current user can see the page, then to see if the team member # whose email address we're changing belongs to the service - mocker.patch('app.user_api_client.get_user', - side_effect=[active_user_with_permissions, api_user_active]) - mock_event_handler = mocker.patch('app.main.views.manage_users.create_email_change_event') + mocker.patch( + "app.user_api_client.get_user", + side_effect=[active_user_with_permissions, api_user_active], + ) + mock_event_handler = mocker.patch( + "app.main.views.manage_users.create_email_change_event" + ) - new_email = 'new_email@gsa.gov' + new_email = "new_email@gsa.gov" with client_request.session_transaction() as session: - session[ - 'team_member_email_change-{}'.format(api_user_active['id']) - ] = new_email + session["team_member_email_change-{}".format(api_user_active["id"])] = new_email client_request.post( - 'main.confirm_edit_user_email', - service_id=service_one['id'], - user_id=api_user_active['id'], + "main.confirm_edit_user_email", + service_id=service_one["id"], + user_id=api_user_active["id"], _expected_status=302, _expected_redirect=url_for( - 'main.manage_users', + "main.manage_users", service_id=SERVICE_ONE_ID, ), ) mock_update_user_attribute.assert_called_once_with( - api_user_active['id'], + api_user_active["id"], email_address=new_email, - updated_by=active_user_with_permissions['id'] + updated_by=active_user_with_permissions["id"], ) mock_event_handler.assert_called_once_with( - user_id=api_user_active['id'], - updated_by_id=active_user_with_permissions['id'], - original_email_address=api_user_active['email_address'], - new_email_address=new_email) + user_id=api_user_active["id"], + updated_by_id=active_user_with_permissions["id"], + original_email_address=api_user_active["email_address"], + new_email_address=new_email, + ) def test_confirm_edit_user_email_doesnt_change_user_email_for_non_team_member( @@ -1641,9 +1806,9 @@ def test_confirm_edit_user_email_doesnt_change_user_email_for_non_team_member( mock_get_users_by_service, ): with client_request.session_transaction() as session: - session['team_member_email_change'] = 'new_email@gsa.gov' + session["team_member_email_change"] = "new_email@gsa.gov" client_request.post( - 'main.confirm_edit_user_email', + "main.confirm_edit_user_email", service_id=SERVICE_ONE_ID, user_id=USER_ONE_ID, _expected_status=404, @@ -1656,20 +1821,22 @@ def test_edit_user_permissions_page_displays_redacted_mobile_number_and_change_l mock_get_users_by_service, mock_get_template_folders, service_one, - mocker + mocker, ): page = client_request.get( - 'main.edit_user_permissions', - service_id=service_one['id'], - user_id=active_user_with_permissions['id'], + "main.edit_user_permissions", + service_id=service_one["id"], + user_id=active_user_with_permissions["id"], ) - assert active_user_with_permissions['name'] in page.find('h1').text - mobile_number_paragraph = page.select('p[id=user_mobile_number]')[0] - assert '202-8 • • • • 303' in mobile_number_paragraph.text + assert active_user_with_permissions["name"] in page.find("h1").text + mobile_number_paragraph = page.select("p[id=user_mobile_number]")[0] + assert "202-8 • • • • 303" in mobile_number_paragraph.text change_link = mobile_number_paragraph.findChild() - assert change_link.attrs['href'] == '/services/{}/users/{}/edit-mobile-number'.format( - service_one['id'], active_user_with_permissions['id'] + assert change_link.attrs[ + "href" + ] == "/services/{}/users/{}/edit-mobile-number".format( + service_one["id"], active_user_with_permissions["id"] ) @@ -1678,21 +1845,21 @@ def test_edit_user_permissions_with_delete_query_shows_banner( active_user_with_permissions, mock_get_users_by_service, mock_get_template_folders, - service_one + service_one, ): page = client_request.get( - 'main.edit_user_permissions', - service_id=service_one['id'], - user_id=active_user_with_permissions['id'], - delete=1 + "main.edit_user_permissions", + service_id=service_one["id"], + user_id=active_user_with_permissions["id"], + delete=1, ) - banner = page.find('div', class_='banner-dangerous') + banner = page.find("div", class_="banner-dangerous") assert banner.contents[0].strip() == "Are you sure you want to remove Test User?" - assert banner.form.attrs['action'] == url_for( - 'main.remove_user_from_service', - service_id=service_one['id'], - user_id=active_user_with_permissions['id'] + assert banner.form.attrs["action"] == url_for( + "main.remove_user_from_service", + service_id=service_one["id"], + user_id=active_user_with_permissions["id"], ) @@ -1701,20 +1868,20 @@ def test_edit_user_mobile_number_page( active_user_with_permissions, mock_get_users_by_service, service_one, - mocker + mocker, ): page = client_request.get( - 'main.edit_user_mobile_number', - service_id=service_one['id'], - user_id=active_user_with_permissions['id'], + "main.edit_user_mobile_number", + service_id=service_one["id"], + user_id=active_user_with_permissions["id"], ) - assert page.find('h1').text == "Change team member’s mobile number" - assert page.select('p[id=user_name]')[0].text == ( + assert page.find("h1").text == "Change team member’s mobile number" + assert page.select("p[id=user_name]")[0].text == ( "This will change the mobile number for {}." - ).format(active_user_with_permissions['name']) - assert page.select('input[name=mobile_number]')[0].attrs["value"] == "202-8••••303" - assert normalize_spaces(page.select('main button[type=submit]')[0].text) == "Save" + ).format(active_user_with_permissions["name"]) + assert page.select("input[name=mobile_number]")[0].attrs["value"] == "202-8••••303" + assert normalize_spaces(page.select("main button[type=submit]")[0].text) == "Save" def test_edit_user_mobile_number_redirects_to_confirmation( @@ -1723,15 +1890,15 @@ def test_edit_user_mobile_number_redirects_to_confirmation( mock_get_users_by_service, ): client_request.post( - 'main.edit_user_mobile_number', + "main.edit_user_mobile_number", service_id=SERVICE_ONE_ID, - user_id=active_user_with_permissions['id'], - _data={'mobile_number': '2028675309'}, + user_id=active_user_with_permissions["id"], + _data={"mobile_number": "2028675309"}, _expected_status=302, _expected_redirect=url_for( - 'main.confirm_edit_user_mobile_number', + "main.confirm_edit_user_mobile_number", service_id=SERVICE_ONE_ID, - user_id=active_user_with_permissions['id'], + user_id=active_user_with_permissions["id"], ), ) @@ -1745,13 +1912,13 @@ def test_edit_user_mobile_number_redirects_to_manage_users_if_number_not_changed mock_get_user, ): client_request.post( - 'main.edit_user_mobile_number', + "main.edit_user_mobile_number", service_id=SERVICE_ONE_ID, - user_id=active_user_with_permissions['id'], - _data={'mobile_number': '202-8••••303'}, + user_id=active_user_with_permissions["id"], + _data={"mobile_number": "202-8••••303"}, _expected_status=302, _expected_redirect=url_for( - 'main.manage_users', + "main.manage_users", service_id=SERVICE_ONE_ID, ), ) @@ -1765,23 +1932,25 @@ def test_confirm_edit_user_mobile_number_page( mocker, mock_get_user, ): - new_number = '2028675309' + new_number = "2028675309" with client_request.session_transaction() as session: - session['team_member_mobile_change'] = new_number + session["team_member_mobile_change"] = new_number page = client_request.get( - 'main.confirm_edit_user_mobile_number', + "main.confirm_edit_user_mobile_number", service_id=SERVICE_ONE_ID, - user_id=active_user_with_permissions['id'], + user_id=active_user_with_permissions["id"], ) - assert 'Confirm change of mobile number' in page.text + assert "Confirm change of mobile number" in page.text for text in [ - 'New mobile number:', + "New mobile number:", new_number, - 'We will send {} a text message to tell them about the change.'.format(active_user_with_permissions['name']) + "We will send {} a text message to tell them about the change.".format( + active_user_with_permissions["name"] + ), ]: assert text in page.text - assert 'Confirm' in page.text + assert "Confirm" in page.text def test_confirm_edit_user_mobile_number_page_redirects_if_session_empty( @@ -1793,12 +1962,12 @@ def test_confirm_edit_user_mobile_number_page_redirects_if_session_empty( mock_get_user, ): page = client_request.get( - 'main.confirm_edit_user_mobile_number', + "main.confirm_edit_user_mobile_number", service_id=SERVICE_ONE_ID, - user_id=active_user_with_permissions['id'], + user_id=active_user_with_permissions["id"], _expected_status=302, ) - assert 'Confirm change of mobile number' not in page.text + assert "Confirm change of mobile number" not in page.text def test_confirm_edit_user_mobile_number_changes_user_mobile_number( @@ -1807,43 +1976,51 @@ def test_confirm_edit_user_mobile_number_changes_user_mobile_number( api_user_active, service_one, mocker, - mock_update_user_attribute + mock_update_user_attribute, ): # We want active_user_with_permissions (the current user) to update the mobile number for api_user_active # By default both users would have the same id, so we change the id of api_user_active - api_user_active['id'] = str(uuid.uuid4()) + api_user_active["id"] = str(uuid.uuid4()) - mocker.patch('app.models.user.Users.client_method', return_value=[api_user_active, active_user_with_permissions]) + mocker.patch( + "app.models.user.Users.client_method", + return_value=[api_user_active, active_user_with_permissions], + ) # get_user gets called twice - first to check if current user can see the page, then to see if the team member # whose mobile number we're changing belongs to the service - mocker.patch('app.user_api_client.get_user', - side_effect=[active_user_with_permissions, api_user_active]) - mock_event_handler = mocker.patch('app.main.views.manage_users.create_mobile_number_change_event') + mocker.patch( + "app.user_api_client.get_user", + side_effect=[active_user_with_permissions, api_user_active], + ) + mock_event_handler = mocker.patch( + "app.main.views.manage_users.create_mobile_number_change_event" + ) - new_number = '2028675309' + new_number = "2028675309" with client_request.session_transaction() as session: - session['team_member_mobile_change'] = new_number + session["team_member_mobile_change"] = new_number client_request.post( - 'main.confirm_edit_user_mobile_number', + "main.confirm_edit_user_mobile_number", service_id=SERVICE_ONE_ID, - user_id=api_user_active['id'], + user_id=api_user_active["id"], _expected_status=302, _expected_redirect=url_for( - 'main.manage_users', + "main.manage_users", service_id=SERVICE_ONE_ID, ), ) mock_update_user_attribute.assert_called_once_with( - api_user_active['id'], + api_user_active["id"], mobile_number=new_number, - updated_by=active_user_with_permissions['id'] + updated_by=active_user_with_permissions["id"], ) mock_event_handler.assert_called_once_with( - user_id=api_user_active['id'], - updated_by_id=active_user_with_permissions['id'], - original_mobile_number=api_user_active['mobile_number'], - new_mobile_number=new_number) + user_id=api_user_active["id"], + updated_by_id=active_user_with_permissions["id"], + original_mobile_number=api_user_active["mobile_number"], + new_mobile_number=new_number, + ) def test_confirm_edit_user_mobile_number_doesnt_change_user_mobile_for_non_team_member( @@ -1851,9 +2028,9 @@ def test_confirm_edit_user_mobile_number_doesnt_change_user_mobile_for_non_team_ mock_get_users_by_service, ): with client_request.session_transaction() as session: - session['team_member_mobile_change'] = '2028675309' + session["team_member_mobile_change"] = "2028675309" client_request.post( - 'main.confirm_edit_user_mobile_number', + "main.confirm_edit_user_mobile_number", service_id=SERVICE_ONE_ID, user_id=USER_ONE_ID, _expected_status=404, diff --git a/tests/app/main/views/test_new_password.py b/tests/app/main/views/test_new_password.py index 0e4fca2e7..3684801de 100644 --- a/tests/app/main/views/test_new_password.py +++ b/tests/app/main/views/test_new_password.py @@ -20,21 +20,25 @@ def test_should_render_new_password_template( ): client_request.logout() user = mock_get_user_by_email_request_password_reset.return_value - user['password_changed_at'] = '2021-01-01 00:00:00' + user["password_changed_at"] = "2021-01-01 00:00:00" mock_update_user_attribute = mocker.patch( - 'app.user_api_client.update_user_attribute', + "app.user_api_client.update_user_attribute", return_value=user, ) - data = json.dumps({'email': user['email_address'], 'created_at': str(datetime.utcnow())}) - token = generate_token(data, notify_admin.config['SECRET_KEY'], - notify_admin.config['DANGEROUS_SALT']) + data = json.dumps( + {"email": user["email_address"], "created_at": str(datetime.utcnow())} + ) + token = generate_token( + data, notify_admin.config["SECRET_KEY"], notify_admin.config["DANGEROUS_SALT"] + ) - page = client_request.get_url(url_for_endpoint_with_token('.new_password', token=token)) - assert 'You can now create a new password for your account.' in page.text + page = client_request.get_url( + url_for_endpoint_with_token(".new_password", token=token) + ) + assert "You can now create a new password for your account." in page.text mock_update_user_attribute.assert_called_once_with( - user['id'], - email_access_validated_at='2021-01-01T11:11:11' + user["id"], email_access_validated_at="2021-01-01T11:11:11" ) @@ -44,18 +48,25 @@ def test_should_return_404_when_email_address_does_not_exist( mock_get_user_by_email_not_found, ): client_request.logout() - data = json.dumps({'email': 'no_user@d.gsa.gov', 'created_at': str(datetime.utcnow())}) - token = generate_token(data, notify_admin.config['SECRET_KEY'], notify_admin.config['DANGEROUS_SALT']) + data = json.dumps( + {"email": "no_user@d.gsa.gov", "created_at": str(datetime.utcnow())} + ) + token = generate_token( + data, notify_admin.config["SECRET_KEY"], notify_admin.config["DANGEROUS_SALT"] + ) client_request.get_url( - url_for_endpoint_with_token('.new_password', token=token), + url_for_endpoint_with_token(".new_password", token=token), _expected_status=404, ) -@pytest.mark.parametrize('redirect_url', [ - None, - f'/services/{SERVICE_ONE_ID}/templates', -]) +@pytest.mark.parametrize( + "redirect_url", + [ + None, + f"/services/{SERVICE_ONE_ID}/templates", + ], +) def test_should_redirect_to_two_factor_when_password_reset_is_successful( notify_admin, client_request, @@ -63,18 +74,24 @@ def test_should_redirect_to_two_factor_when_password_reset_is_successful( mock_login, mock_send_verify_code, mock_reset_failed_login_count, - redirect_url + redirect_url, ): client_request.logout() user = mock_get_user_by_email_request_password_reset.return_value - data = json.dumps({'email': user['email_address'], 'created_at': str(datetime.utcnow())}) - token = generate_token(data, notify_admin.config['SECRET_KEY'], notify_admin.config['DANGEROUS_SALT']) - client_request.post_url( - url_for_endpoint_with_token('.new_password', token=token, next=redirect_url), - _data={'new_password': 'a-new_password'}, - _expected_redirect=url_for('.two_factor_sms', next=redirect_url), + data = json.dumps( + {"email": user["email_address"], "created_at": str(datetime.utcnow())} + ) + token = generate_token( + data, notify_admin.config["SECRET_KEY"], notify_admin.config["DANGEROUS_SALT"] + ) + client_request.post_url( + url_for_endpoint_with_token(".new_password", token=token, next=redirect_url), + _data={"new_password": "a-new_password"}, + _expected_redirect=url_for(".two_factor_sms", next=redirect_url), + ) + mock_get_user_by_email_request_password_reset.assert_called_once_with( + user["email_address"] ) - mock_get_user_by_email_request_password_reset.assert_called_once_with(user['email_address']) def test_should_redirect_index_if_user_has_already_changed_password( @@ -83,33 +100,43 @@ def test_should_redirect_index_if_user_has_already_changed_password( mock_get_user_by_email_user_changed_password, mock_login, mock_send_verify_code, - mock_reset_failed_login_count + mock_reset_failed_login_count, ): client_request.logout() user = mock_get_user_by_email_user_changed_password.return_value - data = json.dumps({'email': user['email_address'], 'created_at': str(datetime.utcnow())}) - token = generate_token(data, notify_admin.config['SECRET_KEY'], notify_admin.config['DANGEROUS_SALT']) - client_request.post_url( - url_for_endpoint_with_token('.new_password', token=token), - _data={'new_password': 'a-new_password'}, - _expected_redirect=url_for('.index'), + data = json.dumps( + {"email": user["email_address"], "created_at": str(datetime.utcnow())} + ) + token = generate_token( + data, notify_admin.config["SECRET_KEY"], notify_admin.config["DANGEROUS_SALT"] + ) + client_request.post_url( + url_for_endpoint_with_token(".new_password", token=token), + _data={"new_password": "a-new_password"}, + _expected_redirect=url_for(".index"), + ) + mock_get_user_by_email_user_changed_password.assert_called_once_with( + user["email_address"] ) - mock_get_user_by_email_user_changed_password.assert_called_once_with(user['email_address']) def test_should_redirect_to_forgot_password_with_flash_message_when_token_is_expired( - notify_admin, - client_request, - mock_login, - mocker + notify_admin, client_request, mock_login, mocker ): client_request.logout() - mocker.patch('app.main.views.new_password.check_token', side_effect=SignatureExpired('expired')) - token = generate_token('foo@bar.com', notify_admin.config['SECRET_KEY'], notify_admin.config['DANGEROUS_SALT']) + mocker.patch( + "app.main.views.new_password.check_token", + side_effect=SignatureExpired("expired"), + ) + token = generate_token( + "foo@bar.com", + notify_admin.config["SECRET_KEY"], + notify_admin.config["DANGEROUS_SALT"], + ) client_request.get_url( - url_for_endpoint_with_token('.new_password', token=token), - _expected_redirect=url_for('.forgot_password'), + url_for_endpoint_with_token(".new_password", token=token), + _expected_redirect=url_for(".forgot_password"), ) @@ -122,26 +149,32 @@ def test_should_sign_in_when_password_reset_is_successful_for_email_auth( mock_login, mock_send_verify_code, mock_reset_failed_login_count, - mock_update_user_password + mock_update_user_password, ): client_request.logout() user = mock_get_user_by_email_request_password_reset.return_value - mock_get_user = mocker.patch('app.user_api_client.get_user', return_value=api_user_active) - user['auth_type'] = 'email_auth' - data = json.dumps({'email': user['email_address'], 'created_at': str(datetime.utcnow())}) - token = generate_token(data, notify_admin.config['SECRET_KEY'], notify_admin.config['DANGEROUS_SALT']) + mock_get_user = mocker.patch( + "app.user_api_client.get_user", return_value=api_user_active + ) + user["auth_type"] = "email_auth" + data = json.dumps( + {"email": user["email_address"], "created_at": str(datetime.utcnow())} + ) + token = generate_token( + data, notify_admin.config["SECRET_KEY"], notify_admin.config["DANGEROUS_SALT"] + ) client_request.post_url( - url_for_endpoint_with_token('.new_password', token=token), - _data={'new_password': 'a-new_password'}, - _expected_redirect=url_for('.show_accounts_or_dashboard'), + url_for_endpoint_with_token(".new_password", token=token), + _data={"new_password": "a-new_password"}, + _expected_redirect=url_for(".show_accounts_or_dashboard"), ) assert mock_get_user_by_email_request_password_reset.called assert mock_reset_failed_login_count.called # the log-in flow makes a couple of calls - mock_get_user.assert_called_once_with(user['id']) - mock_update_user_password.assert_called_once_with(user['id'], 'a-new_password') + mock_get_user.assert_called_once_with(user["id"]) + mock_update_user_password.assert_called_once_with(user["id"], "a-new_password") assert not mock_send_verify_code.called diff --git a/tests/app/main/views/test_notifications.py b/tests/app/main/views/test_notifications.py index 6c6cc06f2..875db4ccc 100644 --- a/tests/app/main/views/test_notifications.py +++ b/tests/app/main/views/test_notifications.py @@ -13,26 +13,39 @@ from tests.conftest import ( ) -@pytest.mark.parametrize('key_type, notification_status, expected_status', [ - (None, 'created', 'Sending'), - (None, 'sending', - "Pending. Messages will remain in pending state until carrier status is received, typically 5 minutes."), - (None, 'delivered', 'Delivered'), - (None, 'failed', 'Failed'), - (None, 'temporary-failure', 'Phone not accepting messages right now'), - (None, 'permanent-failure', 'Not delivered'), - (None, 'technical-failure', 'Technical failure'), - ('team', 'delivered', 'Delivered'), - ('live', 'delivered', 'Delivered'), - ('test', 'sending', - "Pending. Messages will remain in pending state until carrier status is received, typically 5 minutes. (test)"), - ('test', 'delivered', 'Delivered (test)'), - ('test', 'permanent-failure', 'Not delivered (test)'), -]) -@pytest.mark.parametrize('user', [ - create_active_user_with_permissions(), - create_active_caseworking_user(), -]) +@pytest.mark.parametrize( + "key_type, notification_status, expected_status", + [ + (None, "created", "Sending"), + ( + None, + "sending", + "Pending. Messages will remain in pending state until carrier status is received, typically 5 minutes.", + ), + (None, "delivered", "Delivered"), + (None, "failed", "Failed"), + (None, "temporary-failure", "Phone not accepting messages right now"), + (None, "permanent-failure", "Not delivered"), + (None, "technical-failure", "Technical failure"), + ("team", "delivered", "Delivered"), + ("live", "delivered", "Delivered"), + ( + "test", + "sending", + "Pending. Messages will remain in pending state until carrier status " + "is received, typically 5 minutes. (test)", + ), + ("test", "delivered", "Delivered (test)"), + ("test", "permanent-failure", "Not delivered (test)"), + ], +) +@pytest.mark.parametrize( + "user", + [ + create_active_user_with_permissions(), + create_active_caseworking_user(), + ], +) @freeze_time("2016-01-01 11:09:00.061258") def test_notification_status_page_shows_details( client_request, @@ -45,42 +58,45 @@ def test_notification_status_page_shows_details( notification_status, expected_status, ): + mocker.patch("app.user_api_client.get_user", return_value=user) - mocker.patch('app.user_api_client.get_user', return_value=user) - - notification = create_notification(notification_status=notification_status, key_type=key_type) - _mock_get_notification = mocker.patch('app.notification_api_client.get_notification', return_value=notification) + notification = create_notification( + notification_status=notification_status, key_type=key_type + ) + _mock_get_notification = mocker.patch( + "app.notification_api_client.get_notification", return_value=notification + ) page = client_request.get( - 'main.view_notification', - service_id=service_one['id'], - notification_id=fake_uuid + "main.view_notification", + service_id=service_one["id"], + notification_id=fake_uuid, ) - assert normalize_spaces(page.select('.sms-message-recipient')[0].text) == ( - 'To: 2021234567' + assert normalize_spaces(page.select(".sms-message-recipient")[0].text) == ( + "To: 2021234567" ) - assert normalize_spaces(page.select('.sms-message-wrapper')[0].text) == ( - 'service one: hello Jo' + assert normalize_spaces(page.select(".sms-message-wrapper")[0].text) == ( + "service one: hello Jo" ) - assert normalize_spaces(page.select('.ajax-block-container p')[0].text) == ( + assert normalize_spaces(page.select(".ajax-block-container p")[0].text) == ( expected_status ) - _mock_get_notification.assert_called_with( - service_one['id'], - fake_uuid - ) + _mock_get_notification.assert_called_with(service_one["id"], fake_uuid) -@pytest.mark.parametrize('notification_type, notification_status, expected_class', [ - ('sms', 'failed', 'error'), - ('email', 'failed', 'error'), - ('sms', 'sent', 'sent-international'), - ('email', 'sent', None), - ('sms', 'created', 'default'), - ('email', 'created', 'default'), -]) +@pytest.mark.parametrize( + "notification_type, notification_status, expected_class", + [ + ("sms", "failed", "error"), + ("email", "failed", "error"), + ("sms", "sent", "sent-international"), + ("email", "sent", None), + ("sms", "created", "default"), + ("email", "created", "default"), + ], +) @freeze_time("2016-01-01 11:09:00.061258") def test_notification_status_page_formats_email_and_sms_status_correctly( client_request, @@ -93,22 +109,33 @@ def test_notification_status_page_formats_email_and_sms_status_correctly( notification_status, expected_class, ): - mocker.patch('app.user_api_client.get_user', return_value=active_user_with_permissions) - notification = create_notification(notification_status=notification_status, template_type=notification_type) - mocker.patch('app.notification_api_client.get_notification', return_value=notification) + mocker.patch( + "app.user_api_client.get_user", return_value=active_user_with_permissions + ) + notification = create_notification( + notification_status=notification_status, template_type=notification_type + ) + mocker.patch( + "app.notification_api_client.get_notification", return_value=notification + ) page = client_request.get( - 'main.view_notification', - service_id=service_one['id'], - notification_id=fake_uuid + "main.view_notification", + service_id=service_one["id"], + notification_id=fake_uuid, + ) + assert page.select_one( + f".ajax-block-container p.notification-status.{expected_class}" ) - assert page.select_one(f'.ajax-block-container p.notification-status.{expected_class}') -@pytest.mark.parametrize('template_redaction_setting, expected_content', [ - (False, 'service one: hello Jo'), - (True, 'service one: hello hidden'), -]) +@pytest.mark.parametrize( + "template_redaction_setting, expected_content", + [ + (False, "service one: hello Jo"), + (True, "service one: hello hidden"), + ], +) @freeze_time("2016-01-01 11:09:00.061258") def test_notification_status_page_respects_redaction( client_request, @@ -118,48 +145,60 @@ def test_notification_status_page_respects_redaction( template_redaction_setting, expected_content, ): - _mock_get_notification = mocker.patch( - 'app.notification_api_client.get_notification', - return_value=create_notification(redact_personalisation=template_redaction_setting) + "app.notification_api_client.get_notification", + return_value=create_notification( + redact_personalisation=template_redaction_setting + ), ) page = client_request.get( - 'main.view_notification', - service_id=service_one['id'], - notification_id=fake_uuid + "main.view_notification", + service_id=service_one["id"], + notification_id=fake_uuid, ) - assert normalize_spaces(page.select('.sms-message-wrapper')[0].text) == expected_content + assert ( + normalize_spaces(page.select(".sms-message-wrapper")[0].text) + == expected_content + ) _mock_get_notification.assert_called_with( - service_one['id'], + service_one["id"], fake_uuid, ) -@pytest.mark.parametrize('extra_args, expected_back_link', [ - ( - {}, - partial(url_for, 'main.view_notifications', message_type='sms', status='sending,delivered,failed'), - ), - ( - {'from_job': 'job_id'}, - partial(url_for, 'main.view_job', job_id='job_id'), - ), - ( - {'help': '0'}, - None, - ), - ( - {'help': '1'}, - None, - ), - ( - {'help': '2'}, - None, - ), -]) +@pytest.mark.parametrize( + "extra_args, expected_back_link", + [ + ( + {}, + partial( + url_for, + "main.view_notifications", + message_type="sms", + status="sending,delivered,failed", + ), + ), + ( + {"from_job": "job_id"}, + partial(url_for, "main.view_job", job_id="job_id"), + ), + ( + {"help": "0"}, + None, + ), + ( + {"help": "1"}, + None, + ), + ( + {"help": "2"}, + None, + ), + ], +) def test_notification_status_shows_expected_back_link( client_request, mocker, @@ -169,33 +208,40 @@ def test_notification_status_shows_expected_back_link( expected_back_link, ): page = client_request.get( - 'main.view_notification', + "main.view_notification", service_id=SERVICE_ONE_ID, notification_id=fake_uuid, - **extra_args + **extra_args, ) - back_link = page.select_one('.usa-back-link') + back_link = page.select_one(".usa-back-link") if expected_back_link: - assert back_link['href'] == expected_back_link(service_id=SERVICE_ONE_ID) + assert back_link["href"] == expected_back_link(service_id=SERVICE_ONE_ID) else: assert back_link is None -@pytest.mark.parametrize('time_of_viewing_page, expected_message', ( - ('2012-01-01 06:01', ( - "‘sample template’ was sent by Test User today at 06:01 UTC" - )), - ('2012-01-02 06:01', ( - "‘sample template’ was sent by Test User yesterday at 06:01 UTC" - )), - ('2012-01-03 06:01', ( - "‘sample template’ was sent by Test User on 1 January at 06:01 UTC" - )), - ('2013-01-03 06:01', ( - "‘sample template’ was sent by Test User on 1 January 2012 at 06:01 UTC" - )), -)) +@pytest.mark.parametrize( + "time_of_viewing_page, expected_message", + ( + ( + "2012-01-01 06:01", + ("‘sample template’ was sent by Test User today at 06:01 UTC"), + ), + ( + "2012-01-02 06:01", + ("‘sample template’ was sent by Test User yesterday at 06:01 UTC"), + ), + ( + "2012-01-03 06:01", + ("‘sample template’ was sent by Test User on 1 January at 06:01 UTC"), + ), + ( + "2013-01-03 06:01", + ("‘sample template’ was sent by Test User on 1 January 2012 at 06:01 UTC"), + ), + ), +) def test_notification_page_doesnt_link_to_template_in_tour( mocker, client_request, @@ -204,50 +250,58 @@ def test_notification_page_doesnt_link_to_template_in_tour( time_of_viewing_page, expected_message, ): - - with freeze_time('2012-01-01 06:01'): + with freeze_time("2012-01-01 06:01"): notification = create_notification() - mocker.patch('app.notification_api_client.get_notification', return_value=notification) + mocker.patch( + "app.notification_api_client.get_notification", return_value=notification + ) with freeze_time(time_of_viewing_page): page = client_request.get( - 'main.view_notification', + "main.view_notification", service_id=SERVICE_ONE_ID, notification_id=fake_uuid, help=3, ) - assert normalize_spaces(page.select('main p:nth-of-type(1)')[0].text) == ( + assert normalize_spaces(page.select("main p:nth-of-type(1)")[0].text) == ( expected_message ) - assert len(page.select('main p:nth-of-type(1) a')) == 0 + assert len(page.select("main p:nth-of-type(1) a")) == 0 -@pytest.mark.parametrize('notification_type', ['email', 'sms']) -@freeze_time('2016-01-01 15:00') +@pytest.mark.parametrize("notification_type", ["email", "sms"]) +@freeze_time("2016-01-01 15:00") def test_notification_page_does_not_show_cancel_link_for_sms_or_email_notifications( client_request, mocker, fake_uuid, notification_type, ): - notification = create_notification(template_type=notification_type, notification_status='created') - mocker.patch('app.notification_api_client.get_notification', return_value=notification) + notification = create_notification( + template_type=notification_type, notification_status="created" + ) + mocker.patch( + "app.notification_api_client.get_notification", return_value=notification + ) page = client_request.get( - 'main.view_notification', + "main.view_notification", service_id=SERVICE_ONE_ID, notification_id=fake_uuid, ) - assert 'Cancel sending this letter' not in normalize_spaces(page.text) + assert "Cancel sending this letter" not in normalize_spaces(page.text) -@pytest.mark.parametrize('service_permissions, template_type, link_expected', [ - ([], '', False), - (['inbound_sms'], 'email', False), - (['inbound_sms'], 'sms', True), -]) +@pytest.mark.parametrize( + "service_permissions, template_type, link_expected", + [ + ([], "", False), + (["inbound_sms"], "email", False), + (["inbound_sms"], "sms", True), + ], +) def test_notification_page_has_link_to_send_another_for_sms( client_request, mocker, @@ -257,48 +311,53 @@ def test_notification_page_has_link_to_send_another_for_sms( template_type, link_expected, ): - - service_one['permissions'] = service_permissions + service_one["permissions"] = service_permissions notification = create_notification(template_type=template_type) - mocker.patch('app.notification_api_client.get_notification', return_value=notification) + mocker.patch( + "app.notification_api_client.get_notification", return_value=notification + ) page = client_request.get( - 'main.view_notification', + "main.view_notification", service_id=SERVICE_ONE_ID, notification_id=fake_uuid, ) - last_paragraph = page.select('main p')[-1] + last_paragraph = page.select("main p")[-1] conversation_link = url_for( - '.conversation', + ".conversation", service_id=SERVICE_ONE_ID, notification_id=fake_uuid, - _anchor='n{}'.format(fake_uuid), + _anchor="n{}".format(fake_uuid), ) if link_expected: assert normalize_spaces(last_paragraph.text) == ( - 'See all text messages sent to this phone number' + "See all text messages sent to this phone number" ) - assert last_paragraph.select_one('a')['href'] == conversation_link + assert last_paragraph.select_one("a")["href"] == conversation_link else: - assert conversation_link not in str(page.select_one('main')) + assert conversation_link not in str(page.select_one("main")) -@pytest.mark.parametrize('notification_type', ['sms', 'email']) +@pytest.mark.parametrize("notification_type", ["sms", "email"]) def test_should_show_reply_to_from_notification( mocker, fake_uuid, notification_type, client_request, ): - notification = create_notification(reply_to_text='reply to info', template_type=notification_type) - mocker.patch('app.notification_api_client.get_notification', return_value=notification) + notification = create_notification( + reply_to_text="reply to info", template_type=notification_type + ) + mocker.patch( + "app.notification_api_client.get_notification", return_value=notification + ) page = client_request.get( - 'main.view_notification', + "main.view_notification", service_id=SERVICE_ONE_ID, notification_id=fake_uuid, ) - assert 'reply to info' in page.text + assert "reply to info" in page.text diff --git a/tests/app/main/views/test_performance.py b/tests/app/main/views/test_performance.py index 53631edd0..ef2ed06be 100644 --- a/tests/app/main/views/test_performance.py +++ b/tests/app/main/views/test_performance.py @@ -16,81 +16,67 @@ def _get_example_performance_data(): "notifications_by_type": [ { "date": "2021-02-21", - "emails": 1_234_567, "sms": 123_456, + "emails": 1_234_567, + "sms": 123_456, }, { "date": "2021-02-22", - "emails": 1, "sms": 2, + "emails": 1, + "sms": 2, }, { "date": "2021-02-23", - "emails": 1, "sms": 2, + "emails": 1, + "sms": 2, }, { "date": "2021-02-24", - "emails": 1, "sms": 2, + "emails": 1, + "sms": 2, }, { "date": "2021-02-25", - "emails": 1, "sms": 2, + "emails": 1, + "sms": 2, }, { "date": "2021-02-26", - "emails": 1, "sms": 2, + "emails": 1, + "sms": 2, }, { "date": "2021-02-27", - "emails": 1, "sms": 2, + "emails": 1, + "sms": 2, }, ], "processing_time": [ - { - "date": "2021-02-21", - "percentage_under_10_seconds": 99.25 - }, - { - "date": "2021-02-22", - "percentage_under_10_seconds": 95.30 - }, - { - "date": "2021-02-23", - "percentage_under_10_seconds": 95.0 - }, - { - "date": "2021-02-24", - "percentage_under_10_seconds": 100.0 - }, - { - "date": "2021-02-25", - "percentage_under_10_seconds": 99.99 - }, - { - "date": "2021-02-26", - "percentage_under_10_seconds": 100.0 - }, - { - "date": "2021-02-27", - "percentage_under_10_seconds": 98.60 - }, + {"date": "2021-02-21", "percentage_under_10_seconds": 99.25}, + {"date": "2021-02-22", "percentage_under_10_seconds": 95.30}, + {"date": "2021-02-23", "percentage_under_10_seconds": 95.0}, + {"date": "2021-02-24", "percentage_under_10_seconds": 100.0}, + {"date": "2021-02-25", "percentage_under_10_seconds": 99.99}, + {"date": "2021-02-26", "percentage_under_10_seconds": 100.0}, + {"date": "2021-02-27", "percentage_under_10_seconds": 98.60}, ], "services_using_notify": [ { "organization_id": uuid.uuid4(), "organization_name": "Department of Examples and Patterns", "service_id": uuid.uuid4(), - "service_name": "Example service" + "service_name": "Example service", }, { "organization_id": uuid.uuid4(), "organization_name": "Department of Examples and Patterns", "service_id": uuid.uuid4(), - "service_name": "Example service 2" + "service_name": "Example service 2", }, { "organization_id": uuid.uuid4(), "organization_name": "Department of One Service", "service_id": uuid.uuid4(), - "service_name": "Example service 3" + "service_name": "Example service 3", }, { # On production there should be no live services without an @@ -99,66 +85,64 @@ def _get_example_performance_data(): "organization_id": None, "organization_name": None, "service_id": uuid.uuid4(), - "service_name": "Example service 4" + "service_name": "Example service 4", }, ], } -@freeze_time('2021-01-01 12:00') +@freeze_time("2021-01-01 12:00") def test_should_render_performance_page( mocker, client_request, mock_get_service_and_organization_counts, ): mock_get_performance_data = mocker.patch( - 'app.performance_dashboard_api_client.get_performance_dashboard_stats', + "app.performance_dashboard_api_client.get_performance_dashboard_stats", return_value=_get_example_performance_data(), ) - page = client_request.get('main.performance') + page = client_request.get("main.performance") mock_get_performance_data.assert_called_once_with( start_date=date(2020, 12, 25), end_date=date(2021, 1, 1), ) - assert normalize_spaces(page.select_one('main').text) == ( - 'Performance data ' - '' - 'Messages sent since May 2023 ' - '1.8 billion total ' - '1.1 billion emails ' - '987.7 million text messages ' - '' - 'Messages sent since May 2023 ' - 'Date Emails Text messages ' - '27 February 2021 1 2 ' - '26 February 2021 1 2 ' - '25 February 2021 1 2 ' - '24 February 2021 1 2 ' - '23 February 2021 1 2 ' - '22 February 2021 1 2 ' - '21 February 2021 1,234,567 123,456 ' - - - 'Only showing the last 7 days ' - '' - 'Messages sent within 10 seconds ' - '98.31% on average ' - 'Messages sent within 10 seconds ' - 'Date Percentage ' - '27 February 2021 98.60% ' - '26 February 2021 100.00% ' - '25 February 2021 99.99% ' - '24 February 2021 100.00% ' - '23 February 2021 95.00% ' - '22 February 2021 95.30% ' - '21 February 2021 99.25% ' - 'Only showing the last 7 days ' - '' - 'Organizations using Notify ' - 'There are 111 organizations and 9,999 services using Notify. ' - 'Organizations using Notify ' - 'Organization Number of live services ' - 'Department of Examples and Patterns 2 ' - 'Department of One Service 1 ' - 'No organization 1' + assert normalize_spaces(page.select_one("main").text) == ( + "Performance data " + "" + "Messages sent since May 2023 " + "1.8 billion total " + "1.1 billion emails " + "987.7 million text messages " + "" + "Messages sent since May 2023 " + "Date Emails Text messages " + "27 February 2021 1 2 " + "26 February 2021 1 2 " + "25 February 2021 1 2 " + "24 February 2021 1 2 " + "23 February 2021 1 2 " + "22 February 2021 1 2 " + "21 February 2021 1,234,567 123,456 " + "Only showing the last 7 days " + "" + "Messages sent within 10 seconds " + "98.31% on average " + "Messages sent within 10 seconds " + "Date Percentage " + "27 February 2021 98.60% " + "26 February 2021 100.00% " + "25 February 2021 99.99% " + "24 February 2021 100.00% " + "23 February 2021 95.00% " + "22 February 2021 95.30% " + "21 February 2021 99.25% " + "Only showing the last 7 days " + "" + "Organizations using Notify " + "There are 111 organizations and 9,999 services using Notify. " + "Organizations using Notify " + "Organization Number of live services " + "Department of Examples and Patterns 2 " + "Department of One Service 1 " + "No organization 1" ) diff --git a/tests/app/main/views/test_platform_admin.py b/tests/app/main/views/test_platform_admin.py index 907403711..e85303058 100644 --- a/tests/app/main/views/test_platform_admin.py +++ b/tests/app/main/views/test_platform_admin.py @@ -19,28 +19,31 @@ from tests import service_json from tests.conftest import SERVICE_ONE_ID, SERVICE_TWO_ID, normalize_spaces -@pytest.mark.parametrize('endpoint', [ - 'main.platform_admin', - 'main.live_services', - 'main.trial_services', -]) -def test_should_redirect_if_not_logged_in( - client_request, - endpoint -): +@pytest.mark.parametrize( + "endpoint", + [ + "main.platform_admin", + "main.live_services", + "main.trial_services", + ], +) +def test_should_redirect_if_not_logged_in(client_request, endpoint): client_request.logout() client_request.get( endpoint, - _expected_redirect=url_for('main.sign_in', next=url_for(endpoint)), + _expected_redirect=url_for("main.sign_in", next=url_for(endpoint)), ) -@pytest.mark.parametrize('endpoint', [ - 'main.platform_admin', - 'main.platform_admin_splash_page', - 'main.live_services', - 'main.trial_services', -]) +@pytest.mark.parametrize( + "endpoint", + [ + "main.platform_admin", + "main.platform_admin_splash_page", + "main.live_services", + "main.trial_services", + ], +) def test_should_403_if_not_platform_admin( client_request, endpoint, @@ -48,97 +51,110 @@ def test_should_403_if_not_platform_admin( client_request.get(endpoint, _expected_status=403) -@pytest.mark.parametrize('endpoint, expected_services_shown', [ - ('main.live_services', 1), - ('main.trial_services', 1), -]) +@pytest.mark.parametrize( + "endpoint, expected_services_shown", + [ + ("main.live_services", 1), + ("main.trial_services", 1), + ], +) def test_should_render_platform_admin_page( client_request, platform_admin_user, mock_get_detailed_services, endpoint, - expected_services_shown + expected_services_shown, ): client_request.login(platform_admin_user) page = client_request.get(endpoint) assert [ normalize_spaces(column.text) - for column in page.select('tbody tr')[1].select('td') + for column in page.select("tbody tr")[1].select("td") ] == [ - '0 emails sent', '0 text messages sent', + "0 emails sent", + "0 text messages sent", ] - mock_get_detailed_services.assert_called_once_with({'detailed': True, - 'include_from_test_key': True, - 'only_active': False}) + mock_get_detailed_services.assert_called_once_with( + {"detailed": True, "include_from_test_key": True, "only_active": False} + ) -@pytest.mark.parametrize('endpoint', [ - 'main.live_services', - 'main.trial_services', -]) -@pytest.mark.parametrize('partial_url_for, inc', [ - (partial(url_for), True), - (partial(url_for, include_from_test_key='y', start_date='', end_date=''), True), - (partial(url_for, start_date='', end_date=''), False), -]) +@pytest.mark.parametrize( + "endpoint", + [ + "main.live_services", + "main.trial_services", + ], +) +@pytest.mark.parametrize( + "partial_url_for, inc", + [ + (partial(url_for), True), + (partial(url_for, include_from_test_key="y", start_date="", end_date=""), True), + (partial(url_for, start_date="", end_date=""), False), + ], +) def test_live_trial_services_toggle_including_from_test_key( partial_url_for, client_request, platform_admin_user, mock_get_detailed_services, endpoint, - inc + inc, ): client_request.login(platform_admin_user) client_request.get_url(partial_url_for(endpoint)) - mock_get_detailed_services.assert_called_once_with({ - 'detailed': True, - 'only_active': False, - 'include_from_test_key': inc, - }) + mock_get_detailed_services.assert_called_once_with( + { + "detailed": True, + "only_active": False, + "include_from_test_key": inc, + } + ) -@pytest.mark.parametrize('endpoint', [ - 'main.live_services', - 'main.trial_services' -]) +@pytest.mark.parametrize("endpoint", ["main.live_services", "main.trial_services"]) def test_live_trial_services_with_date_filter( - client_request, - platform_admin_user, - mock_get_detailed_services, - endpoint + client_request, platform_admin_user, mock_get_detailed_services, endpoint ): client_request.login(platform_admin_user) page = client_request.get( endpoint, - start_date='2016-12-20', - end_date='2016-12-28', + start_date="2016-12-20", + end_date="2016-12-28", ) - assert 'Platform admin' in page.text - mock_get_detailed_services.assert_called_once_with({ - 'include_from_test_key': False, - 'end_date': datetime.date(2016, 12, 28), - 'start_date': datetime.date(2016, 12, 20), - 'detailed': True, - 'only_active': False, - }) + assert "Platform admin" in page.text + mock_get_detailed_services.assert_called_once_with( + { + "include_from_test_key": False, + "end_date": datetime.date(2016, 12, 28), + "start_date": datetime.date(2016, 12, 20), + "detailed": True, + "only_active": False, + } + ) -@pytest.mark.parametrize('endpoint, expected_big_numbers', [ - ( - 'main.live_services', ( - '55 emails sent 5 failed – 5.0%', - '110 text messages sent 10 failed – 5.0%', +@pytest.mark.parametrize( + "endpoint, expected_big_numbers", + [ + ( + "main.live_services", + ( + "55 emails sent 5 failed – 5.0%", + "110 text messages sent 10 failed – 5.0%", + ), ), - ), - ( - 'main.trial_services', ( - '6 emails sent 1 failed – 10.0%', - '11 text messages sent 1 failed – 5.0%', + ( + "main.trial_services", + ( + "6 emails sent 1 failed – 10.0%", + "11 text messages sent 1 failed – 5.0%", + ), ), - ), -]) + ], +) def test_should_show_total_on_live_trial_services_pages( client_request, platform_admin_user, @@ -148,10 +164,10 @@ def test_should_show_total_on_live_trial_services_pages( expected_big_numbers, ): services = [ - service_json(fake_uuid, 'My Service 1', [], restricted=False), - service_json(fake_uuid, 'My Service 2', [], restricted=True), + service_json(fake_uuid, "My Service 1", [], restricted=False), + service_json(fake_uuid, "My Service 2", [], restricted=True), ] - services[0]['statistics'] = create_stats( + services[0]["statistics"] = create_stats( emails_requested=100, emails_delivered=50, emails_failed=5, @@ -160,7 +176,7 @@ def test_should_show_total_on_live_trial_services_pages( sms_failed=10, ) - services[1]['statistics'] = create_stats( + services[1]["statistics"] = create_stats( emails_requested=10, emails_delivered=5, emails_failed=1, @@ -169,29 +185,26 @@ def test_should_show_total_on_live_trial_services_pages( sms_failed=1, ) - mock_get_detailed_services.return_value = {'data': services} + mock_get_detailed_services.return_value = {"data": services} client_request.login(platform_admin_user) page = client_request.get(endpoint) assert ( - normalize_spaces(page.select('.big-number-with-status')[0].text), - normalize_spaces(page.select('.big-number-with-status')[1].text), + normalize_spaces(page.select(".big-number-with-status")[0].text), + normalize_spaces(page.select(".big-number-with-status")[1].text), # normalize_spaces(page.select('.big-number-with-status')[2].text), ) == expected_big_numbers def test_create_global_stats_sets_failure_rates(fake_uuid): - services = [ - service_json(fake_uuid, 'a', []), - service_json(fake_uuid, 'b', []) - ] - services[0]['statistics'] = create_stats( + services = [service_json(fake_uuid, "a", []), service_json(fake_uuid, "b", [])] + services[0]["statistics"] = create_stats( emails_requested=1, emails_delivered=1, emails_failed=0, ) - services[1]['statistics'] = create_stats( + services[1]["statistics"] = create_stats( emails_requested=2, emails_delivered=1, emails_failed=1, @@ -199,18 +212,8 @@ def test_create_global_stats_sets_failure_rates(fake_uuid): stats = create_global_stats(services) assert stats == { - 'email': { - 'delivered': 2, - 'failed': 1, - 'requested': 3, - 'failure_rate': '33.3' - }, - 'sms': { - 'delivered': 0, - 'failed': 0, - 'requested': 0, - 'failure_rate': '0' - }, + "email": {"delivered": 2, "failed": 1, "requested": 3, "failure_rate": "33.3"}, + "sms": {"delivered": 0, "failed": 0, "requested": 0, "failure_rate": "0"}, } @@ -223,22 +226,22 @@ def create_stats( sms_failed=0, ): return { - 'sms': { - 'requested': sms_requested, - 'delivered': sms_delivered, - 'failed': sms_failed, + "sms": { + "requested": sms_requested, + "delivered": sms_delivered, + "failed": sms_failed, }, - 'email': { - 'requested': emails_requested, - 'delivered': emails_delivered, - 'failed': emails_failed, + "email": { + "requested": emails_requested, + "delivered": emails_delivered, + "failed": emails_failed, }, } def test_format_stats_by_service_returns_correct_values(fake_uuid): - services = [service_json(fake_uuid, 'a', [])] - services[0]['statistics'] = create_stats( + services = [service_json(fake_uuid, "a", [])] + services[0]["statistics"] = create_stats( emails_requested=10, emails_delivered=3, emails_failed=5, @@ -250,19 +253,19 @@ def test_format_stats_by_service_returns_correct_values(fake_uuid): ret = list(format_stats_by_service(services)) assert len(ret) == 1 - assert ret[0]['stats']['email']['requested'] == 10 - assert ret[0]['stats']['email']['delivered'] == 3 - assert ret[0]['stats']['email']['failed'] == 5 + assert ret[0]["stats"]["email"]["requested"] == 10 + assert ret[0]["stats"]["email"]["delivered"] == 3 + assert ret[0]["stats"]["email"]["failed"] == 5 - assert ret[0]['stats']['sms']['requested'] == 50 - assert ret[0]['stats']['sms']['delivered'] == 7 - assert ret[0]['stats']['sms']['failed'] == 11 + assert ret[0]["stats"]["sms"]["requested"] == 50 + assert ret[0]["stats"]["sms"]["delivered"] == 7 + assert ret[0]["stats"]["sms"]["failed"] == 11 -@pytest.mark.parametrize('endpoint, restricted, research_mode', [ - ('main.trial_services', True, False), - ('main.live_services', False, False) -]) +@pytest.mark.parametrize( + "endpoint, restricted, research_mode", + [("main.trial_services", True, False), ("main.live_services", False, False)], +) def test_should_show_email_and_sms_stats_for_all_service_types( endpoint, restricted, @@ -272,37 +275,46 @@ def test_should_show_email_and_sms_stats_for_all_service_types( mock_get_detailed_services, fake_uuid, ): - services = [service_json(fake_uuid, 'My Service', [], restricted=restricted, research_mode=research_mode)] - services[0]['statistics'] = create_stats( + services = [ + service_json( + fake_uuid, + "My Service", + [], + restricted=restricted, + research_mode=research_mode, + ) + ] + services[0]["statistics"] = create_stats( emails_requested=10, emails_delivered=3, emails_failed=5, sms_requested=50, sms_delivered=7, - sms_failed=11 + sms_failed=11, ) - mock_get_detailed_services.return_value = {'data': services} + mock_get_detailed_services.return_value = {"data": services} client_request.login(platform_admin_user) page = client_request.get(endpoint) - mock_get_detailed_services.assert_called_once_with({'detailed': True, - 'include_from_test_key': True, - 'only_active': ANY}) + mock_get_detailed_services.assert_called_once_with( + {"detailed": True, "include_from_test_key": True, "only_active": ANY} + ) - table_body = page.find_all('table')[0].find_all('tbody')[0] - service_row_group = table_body.find_all('tbody')[0].find_all('tr') - email_stats = service_row_group[1].select('.big-number-number')[0] - sms_stats = service_row_group[1].select('.big-number-number')[1] + table_body = page.find_all("table")[0].find_all("tbody")[0] + service_row_group = table_body.find_all("tbody")[0].find_all("tr") + email_stats = service_row_group[1].select(".big-number-number")[0] + sms_stats = service_row_group[1].select(".big-number-number")[1] - assert normalize_spaces(email_stats.text) == '10' - assert normalize_spaces(sms_stats.text) == '50' + assert normalize_spaces(email_stats.text) == "10" + assert normalize_spaces(sms_stats.text) == "50" -@pytest.mark.parametrize('endpoint, restricted', [ - ('main.live_services', False), - ('main.trial_services', True) -], ids=['live', 'trial']) +@pytest.mark.parametrize( + "endpoint, restricted", + [("main.live_services", False), ("main.trial_services", True)], + ids=["live", "trial"], +) def test_should_show_archived_services_last( endpoint, client_request, @@ -311,34 +323,49 @@ def test_should_show_archived_services_last( restricted, ): services = [ - service_json(name='C', restricted=restricted, active=False, created_at='2002-02-02 12:00:00'), - service_json(name='B', restricted=restricted, active=True, created_at='2001-01-01 12:00:00'), - service_json(name='A', restricted=restricted, active=True, created_at='2003-03-03 12:00:00'), + service_json( + name="C", + restricted=restricted, + active=False, + created_at="2002-02-02 12:00:00", + ), + service_json( + name="B", + restricted=restricted, + active=True, + created_at="2001-01-01 12:00:00", + ), + service_json( + name="A", + restricted=restricted, + active=True, + created_at="2003-03-03 12:00:00", + ), ] - services[0]['statistics'] = create_stats() - services[1]['statistics'] = create_stats() - services[2]['statistics'] = create_stats() + services[0]["statistics"] = create_stats() + services[1]["statistics"] = create_stats() + services[2]["statistics"] = create_stats() - mock_get_detailed_services.return_value = {'data': services} + mock_get_detailed_services.return_value = {"data": services} client_request.login(platform_admin_user) page = client_request.get(endpoint) - mock_get_detailed_services.assert_called_once_with({'detailed': True, - 'include_from_test_key': True, - 'only_active': ANY}) + mock_get_detailed_services.assert_called_once_with( + {"detailed": True, "include_from_test_key": True, "only_active": ANY} + ) - table_body = page.find_all('table')[0].find_all('tbody')[0] - services = [service.tr for service in table_body.find_all('tbody')] + table_body = page.find_all("table")[0].find_all("tbody")[0] + services = [service.tr for service in table_body.find_all("tbody")] assert len(services) == 3 - assert normalize_spaces(services[0].td.text) == 'A' - assert normalize_spaces(services[1].td.text) == 'B' - assert normalize_spaces(services[2].td.text) == 'C Archived' + assert normalize_spaces(services[0].td.text) == "A" + assert normalize_spaces(services[1].td.text) == "B" + assert normalize_spaces(services[2].td.text) == "C Archived" -@pytest.mark.parametrize('endpoint, restricted, research_mode', [ - ('main.trial_services', True, False), - ('main.live_services', False, False) -]) +@pytest.mark.parametrize( + "endpoint, restricted, research_mode", + [("main.trial_services", True, False), ("main.live_services", False, False)], +) def test_should_order_services_by_usage_with_inactive_last( endpoint, restricted, @@ -349,103 +376,118 @@ def test_should_order_services_by_usage_with_inactive_last( fake_uuid, ): services = [ - service_json(fake_uuid, 'My Service 1', [], restricted=restricted, research_mode=research_mode), - service_json(fake_uuid, 'My Service 2', [], restricted=restricted, research_mode=research_mode), - service_json(fake_uuid, 'My Service 3', [], restricted=restricted, research_mode=research_mode, active=False) + service_json( + fake_uuid, + "My Service 1", + [], + restricted=restricted, + research_mode=research_mode, + ), + service_json( + fake_uuid, + "My Service 2", + [], + restricted=restricted, + research_mode=research_mode, + ), + service_json( + fake_uuid, + "My Service 3", + [], + restricted=restricted, + research_mode=research_mode, + active=False, + ), ] - services[0]['statistics'] = create_stats( + services[0]["statistics"] = create_stats( emails_requested=100, emails_delivered=25, emails_failed=25, sms_requested=100, sms_delivered=25, - sms_failed=25 + sms_failed=25, ) - services[1]['statistics'] = create_stats( + services[1]["statistics"] = create_stats( emails_requested=200, emails_delivered=50, emails_failed=50, sms_requested=200, sms_delivered=50, - sms_failed=50 + sms_failed=50, ) - services[2]['statistics'] = create_stats( + services[2]["statistics"] = create_stats( emails_requested=200, emails_delivered=50, emails_failed=50, sms_requested=200, sms_delivered=50, - sms_failed=50 + sms_failed=50, ) - mock_get_detailed_services.return_value = {'data': services} + mock_get_detailed_services.return_value = {"data": services} client_request.login(platform_admin_user) page = client_request.get(endpoint) - mock_get_detailed_services.assert_called_once_with({'detailed': True, - 'include_from_test_key': True, - 'only_active': ANY}) + mock_get_detailed_services.assert_called_once_with( + {"detailed": True, "include_from_test_key": True, "only_active": ANY} + ) - table_body = page.find_all('table')[0].find_all('tbody')[0] - services = [service.tr for service in table_body.find_all('tbody')] + table_body = page.find_all("table")[0].find_all("tbody")[0] + services = [service.tr for service in table_body.find_all("tbody")] assert len(services) == 3 - assert normalize_spaces(services[0].td.text) == 'My Service 2' - assert normalize_spaces(services[1].td.text) == 'My Service 1' - assert normalize_spaces(services[2].td.text) == 'My Service 3 Archived' + assert normalize_spaces(services[0].td.text) == "My Service 2" + assert normalize_spaces(services[1].td.text) == "My Service 1" + assert normalize_spaces(services[2].td.text) == "My Service 3 Archived" def test_sum_service_usage_is_sum_of_all_activity(fake_uuid): - service = service_json(fake_uuid, 'My Service 1') - service['statistics'] = create_stats( + service = service_json(fake_uuid, "My Service 1") + service["statistics"] = create_stats( emails_requested=100, emails_delivered=25, emails_failed=25, sms_requested=100, sms_delivered=25, - sms_failed=25 + sms_failed=25, ) assert sum_service_usage(service) == 200 def test_sum_service_usage_with_zeros(fake_uuid): - service = service_json(fake_uuid, 'My Service 1') - service['statistics'] = create_stats( + service = service_json(fake_uuid, "My Service 1") + service["statistics"] = create_stats( emails_requested=0, emails_delivered=0, emails_failed=25, sms_requested=0, sms_delivered=0, - sms_failed=0 + sms_failed=0, ) assert sum_service_usage(service) == 0 -def test_platform_admin_list_complaints( - client_request, - platform_admin_user, - mocker -): +def test_platform_admin_list_complaints(client_request, platform_admin_user, mocker): complaint = { - 'id': str(uuid.uuid4()), - 'notification_id': str(uuid.uuid4()), - 'service_id': str(uuid.uuid4()), - 'service_name': 'Sample service', - 'ses_feedback_id': 'Some ses id', - 'complaint_type': 'abuse', - 'complaint_date': '2018-06-05T13:50:30.012354', - 'created_at': '2018-06-05T13:50:30.012354', + "id": str(uuid.uuid4()), + "notification_id": str(uuid.uuid4()), + "service_id": str(uuid.uuid4()), + "service_name": "Sample service", + "ses_feedback_id": "Some ses id", + "complaint_type": "abuse", + "complaint_date": "2018-06-05T13:50:30.012354", + "created_at": "2018-06-05T13:50:30.012354", } - mock = mocker.patch('app.complaint_api_client.get_all_complaints', - return_value={'complaints': [complaint], 'links': {}}) - - client_request.login(platform_admin_user) - page = client_request.get( - 'main.platform_admin_list_complaints' + mock = mocker.patch( + "app.complaint_api_client.get_all_complaints", + return_value={"complaints": [complaint], "links": {}}, ) - assert 'Email complaints' in page.text + client_request.login(platform_admin_user) + page = client_request.get("main.platform_admin_list_complaints") + + assert "Email complaints" in page.text assert mock.called @@ -456,35 +498,48 @@ def test_should_show_complaints_with_next_previous( service_one, fake_uuid, ): - api_response = { - 'complaints': [{'complaint_date': None, - 'complaint_type': None, - 'created_at': '2017-12-18T05:00:00.000000Z', - 'id': fake_uuid, - 'notification_id': fake_uuid, - 'service_id': service_one['id'], - 'service_name': service_one['name'], - 'ses_feedback_id': 'None'}], - 'links': {'last': '/complaint?page=3', 'next': '/complaint?page=3', 'prev': '/complaint?page=1'} + "complaints": [ + { + "complaint_date": None, + "complaint_type": None, + "created_at": "2017-12-18T05:00:00.000000Z", + "id": fake_uuid, + "notification_id": fake_uuid, + "service_id": service_one["id"], + "service_name": service_one["name"], + "ses_feedback_id": "None", + } + ], + "links": { + "last": "/complaint?page=3", + "next": "/complaint?page=3", + "prev": "/complaint?page=1", + }, } - mocker.patch('app.complaint_api_client.get_all_complaints', return_value=api_response) + mocker.patch( + "app.complaint_api_client.get_all_complaints", return_value=api_response + ) client_request.login(platform_admin_user) page = client_request.get( - 'main.platform_admin_list_complaints', + "main.platform_admin_list_complaints", page=2, ) - next_page_link = page.find('a', {'rel': 'next'}) - prev_page_link = page.find('a', {'rel': 'previous'}) - assert (url_for('main.platform_admin_list_complaints', page=3) in next_page_link['href']) - assert 'Next page' in next_page_link.text.strip() - assert 'page 3' in next_page_link.text.strip() - assert (url_for('main.platform_admin_list_complaints', page=1) in prev_page_link['href']) - assert 'Previous page' in prev_page_link.text.strip() - assert 'page 1' in prev_page_link.text.strip() + next_page_link = page.find("a", {"rel": "next"}) + prev_page_link = page.find("a", {"rel": "previous"}) + assert ( + url_for("main.platform_admin_list_complaints", page=3) in next_page_link["href"] + ) + assert "Next page" in next_page_link.text.strip() + assert "page 3" in next_page_link.text.strip() + assert ( + url_for("main.platform_admin_list_complaints", page=1) in prev_page_link["href"] + ) + assert "Previous page" in prev_page_link.text.strip() + assert "page 1" in prev_page_link.text.strip() def test_platform_admin_list_complaints_returns_404_with_invalid_page( @@ -492,50 +547,57 @@ def test_platform_admin_list_complaints_returns_404_with_invalid_page( platform_admin_user, mocker, ): - - mocker.patch('app.complaint_api_client.get_all_complaints', return_value={'complaints': [], 'links': {}}) + mocker.patch( + "app.complaint_api_client.get_all_complaints", + return_value={"complaints": [], "links": {}}, + ) client_request.login(platform_admin_user) client_request.get( - 'main.platform_admin_list_complaints', - page='invalid', + "main.platform_admin_list_complaints", + page="invalid", _expected_status=404, ) -@pytest.mark.parametrize('number, total, threshold, result', [ - (0, 0, 0, False), - (1, 1, 0, True), - (2, 3, 66, True), - (2, 3, 67, False), -]) +@pytest.mark.parametrize( + "number, total, threshold, result", + [ + (0, 0, 0, False), + (1, 1, 0, True), + (2, 3, 66, True), + (2, 3, 67, False), + ], +) def test_is_over_threshold(number, total, threshold, result): assert is_over_threshold(number, total, threshold) is result def test_get_tech_failure_status_box_data_removes_percentage_data(): stats = { - 'failures': - {'permanent-failure': 0, 'technical-failure': 0, 'temporary-failure': 1}, - 'test-key': 0, - 'total': 5589 + "failures": { + "permanent-failure": 0, + "technical-failure": 0, + "temporary-failure": 1, + }, + "test-key": 0, + "total": 5589, } tech_failure_data = get_tech_failure_status_box_data(stats) - assert 'percentage' not in tech_failure_data + assert "percentage" not in tech_failure_data def test_platform_admin_splash_doesnt_talk_to_api( client_request, platform_admin_user, ): - client_request.login(platform_admin_user) - page = client_request.get('main.platform_admin_splash_page') + page = client_request.get("main.platform_admin_splash_page") - assert page.select_one('main .govuk-body a')['href'] == url_for( - 'main.platform_admin', + assert page.select_one("main .govuk-body a")["href"] == url_for( + "main.platform_admin", ) @@ -544,41 +606,55 @@ def test_platform_admin_with_start_and_end_dates_provided( client_request, platform_admin_user, ): - start_date = '2018-01-01' - end_date = '2018-06-01' - api_args = {'start_date': datetime.date(2018, 1, 1), 'end_date': datetime.date(2018, 6, 1)} + start_date = "2018-01-01" + end_date = "2018-06-01" + api_args = { + "start_date": datetime.date(2018, 1, 1), + "end_date": datetime.date(2018, 6, 1), + } - mocker.patch('app.main.views.platform_admin.make_columns') + mocker.patch("app.main.views.platform_admin.make_columns") aggregate_stats_mock = mocker.patch( - 'app.main.views.platform_admin.platform_stats_api_client.get_aggregate_platform_stats') - complaint_count_mock = mocker.patch('app.main.views.platform_admin.complaint_api_client.get_complaint_count') + "app.main.views.platform_admin.platform_stats_api_client.get_aggregate_platform_stats" + ) + complaint_count_mock = mocker.patch( + "app.main.views.platform_admin.complaint_api_client.get_complaint_count" + ) client_request.login(platform_admin_user) client_request.get( - 'main.platform_admin', start_date=start_date, end_date=end_date, + "main.platform_admin", + start_date=start_date, + end_date=end_date, ) aggregate_stats_mock.assert_called_with(api_args) complaint_count_mock.assert_called_with(api_args) -@freeze_time('2018-6-11') +@freeze_time("2018-6-11") def test_platform_admin_with_only_a_start_date_provided( mocker, client_request, platform_admin_user, ): - start_date = '2018-01-01' - api_args = {'start_date': datetime.date(2018, 1, 1), 'end_date': datetime.datetime.utcnow().date()} + start_date = "2018-01-01" + api_args = { + "start_date": datetime.date(2018, 1, 1), + "end_date": datetime.datetime.utcnow().date(), + } - mocker.patch('app.main.views.platform_admin.make_columns') + mocker.patch("app.main.views.platform_admin.make_columns") aggregate_stats_mock = mocker.patch( - 'app.main.views.platform_admin.platform_stats_api_client.get_aggregate_platform_stats') - complaint_count_mock = mocker.patch('app.main.views.platform_admin.complaint_api_client.get_complaint_count') + "app.main.views.platform_admin.platform_stats_api_client.get_aggregate_platform_stats" + ) + complaint_count_mock = mocker.patch( + "app.main.views.platform_admin.complaint_api_client.get_complaint_count" + ) client_request.login(platform_admin_user) client_request.get( - 'main.platform_admin', + "main.platform_admin", start_date=start_date, ) @@ -593,13 +669,16 @@ def test_platform_admin_without_dates_provided( ): api_args = {} - mocker.patch('app.main.views.platform_admin.make_columns') + mocker.patch("app.main.views.platform_admin.make_columns") aggregate_stats_mock = mocker.patch( - 'app.main.views.platform_admin.platform_stats_api_client.get_aggregate_platform_stats') - complaint_count_mock = mocker.patch('app.main.views.platform_admin.complaint_api_client.get_complaint_count') + "app.main.views.platform_admin.platform_stats_api_client.get_aggregate_platform_stats" + ) + complaint_count_mock = mocker.patch( + "app.main.views.platform_admin.complaint_api_client.get_complaint_count" + ) client_request.login(platform_admin_user) - client_request.get('main.platform_admin') + client_request.get("main.platform_admin") aggregate_stats_mock.assert_called_with(api_args) complaint_count_mock.assert_called_with(api_args) @@ -611,35 +690,54 @@ def test_platform_admin_displays_stats_in_right_boxes_and_with_correct_styling( platform_admin_user, ): platform_stats = { - 'email': {'failures': - {'permanent-failure': 3, 'technical-failure': 0, 'temporary-failure': 0}, - 'test-key': 0, - 'total': 145}, - 'sms': {'failures': - {'permanent-failure': 0, 'technical-failure': 1, 'temporary-failure': 0}, - 'test-key': 5, - 'total': 168}, + "email": { + "failures": { + "permanent-failure": 3, + "technical-failure": 0, + "temporary-failure": 0, + }, + "test-key": 0, + "total": 145, + }, + "sms": { + "failures": { + "permanent-failure": 0, + "technical-failure": 1, + "temporary-failure": 0, + }, + "test-key": 5, + "total": 168, + }, } - mocker.patch('app.main.views.platform_admin.platform_stats_api_client.get_aggregate_platform_stats', - return_value=platform_stats) - mocker.patch('app.main.views.platform_admin.complaint_api_client.get_complaint_count', return_value=15) + mocker.patch( + "app.main.views.platform_admin.platform_stats_api_client.get_aggregate_platform_stats", + return_value=platform_stats, + ) + mocker.patch( + "app.main.views.platform_admin.complaint_api_client.get_complaint_count", + return_value=15, + ) client_request.login(platform_admin_user) - page = client_request.get('main.platform_admin') + page = client_request.get("main.platform_admin") # Email permanent failure status box - number is correct - assert '3 permanent failures' in page.find_all( - 'div', class_='govuk-grid-column-one-half' - )[0].find(string=re.compile('permanent')) + assert "3 permanent failures" in page.find_all( + "div", class_="govuk-grid-column-one-half" + )[0].find(string=re.compile("permanent")) # Email complaints status box - link exists and number is correct - assert page.find('a', string='15 complaints') + assert page.find("a", string="15 complaints") # SMS total box - number is correct - assert page.find_all('span', class_='big-number-number')[1].text.strip() == '168' + assert page.find_all("span", class_="big-number-number")[1].text.strip() == "168" # Test SMS box - number is correct - assert '5' in page.find_all('div', class_='govuk-grid-column-one-half')[3].text + assert "5" in page.find_all("div", class_="govuk-grid-column-one-half")[3].text # SMS technical failure status box - number is correct and failure class is used - assert '1 technical failures' in page.find_all('div', class_='govuk-grid-column-one-half')[1].find( - 'div', class_='big-number-status-failing').text + assert ( + "1 technical failures" + in page.find_all("div", class_="govuk-grid-column-one-half")[1] + .find("div", class_="big-number-status-failing") + .text + ) def test_clear_cache_shows_form( @@ -647,41 +745,56 @@ def test_clear_cache_shows_form( platform_admin_user, mocker, ): - redis = mocker.patch('app.main.views.platform_admin.redis_client') + redis = mocker.patch("app.main.views.platform_admin.redis_client") client_request.login(platform_admin_user) - page = client_request.get('main.clear_cache') + page = client_request.get("main.clear_cache") assert not redis.delete_by_pattern.called - radios = {el['value'] for el in page.select('input[type=checkbox]')} + radios = {el["value"] for el in page.select("input[type=checkbox]")} assert radios == { - 'user', - 'service', - 'template', - 'email_branding', - 'organization', + "user", + "service", + "template", + "email_branding", + "organization", } -@pytest.mark.parametrize('model_type, expected_calls, expected_confirmation', ( - ('template', [ - call('service-????????-????-????-????-????????????-templates'), - call('service-????????-????-????-????-????????????-template-????????-????-????-????-????????????-version-*'), - call('service-????????-????-????-????-????????????-template-????????-????-????-????-????????????-versions'), - ], 'Removed 6 objects across 3 key formats for template'), - (['service', 'organization'], [ - call('has_jobs-????????-????-????-????-????????????'), - call('service-????????-????-????-????-????????????'), - call('service-????????-????-????-????-????????????-templates'), - call('service-????????-????-????-????-????????????-data-retention'), - call('service-????????-????-????-????-????????????-template-folders'), - call('organizations'), - call('domains'), - call('live-service-and-organization-counts'), - call('organization-????????-????-????-????-????????????-name'), - ], 'Removed 18 objects across 9 key formats for service, organization'), -)) +@pytest.mark.parametrize( + "model_type, expected_calls, expected_confirmation", + ( + ( + "template", + [ + call("service-????????-????-????-????-????????????-templates"), + call( + "service-????????-????-????-????-????????????-template-????????-????-????-????-????????????-version-*" # noqa + ), + call( + "service-????????-????-????-????-????????????-template-????????-????-????-????-????????????-versions" # noqa + ), + ], + "Removed 6 objects across 3 key formats for template", + ), + ( + ["service", "organization"], + [ + call("has_jobs-????????-????-????-????-????????????"), + call("service-????????-????-????-????-????????????"), + call("service-????????-????-????-????-????????????-templates"), + call("service-????????-????-????-????-????????????-data-retention"), + call("service-????????-????-????-????-????????????-template-folders"), + call("organizations"), + call("domains"), + call("live-service-and-organization-counts"), + call("organization-????????-????-????-????-????????????-name"), + ], + "Removed 18 objects across 9 key formats for service, organization", + ), + ), +) def test_clear_cache_submits_and_tells_you_how_many_things_were_deleted( client_request, platform_admin_user, @@ -690,19 +803,17 @@ def test_clear_cache_submits_and_tells_you_how_many_things_were_deleted( expected_calls, expected_confirmation, ): - redis = mocker.patch('app.main.views.platform_admin.redis_client') + redis = mocker.patch("app.main.views.platform_admin.redis_client") redis.delete_by_pattern.return_value = 2 client_request.login(platform_admin_user) page = client_request.post( - 'main.clear_cache', - _data={'model_type': model_type}, - _expected_status=200 + "main.clear_cache", _data={"model_type": model_type}, _expected_status=200 ) assert redis.delete_by_pattern.call_args_list == expected_calls - flash_banner = page.find('div', class_='banner-default') + flash_banner = page.find("div", class_="banner-default") assert flash_banner.text.strip() == expected_confirmation @@ -711,12 +822,15 @@ def test_clear_cache_requires_option( platform_admin_user, mocker, ): - redis = mocker.patch('app.main.views.platform_admin.redis_client') + redis = mocker.patch("app.main.views.platform_admin.redis_client") client_request.login(platform_admin_user) - page = client_request.post('main.clear_cache', _data={}, _expected_status=200) + page = client_request.post("main.clear_cache", _data={}, _expected_status=200) - assert normalize_spaces(page.find('span', class_='usa-error-message').text) == 'Error: Select at least one option' + assert ( + normalize_spaces(page.find("span", class_="usa-error-message").text) + == "Error: Select at least one option" + ) assert not redis.delete_by_pattern.called @@ -725,15 +839,16 @@ def test_reports_page( platform_admin_user, ): client_request.login(platform_admin_user) - page = client_request.get('main.platform_admin_reports') + page = client_request.get("main.platform_admin_reports") - assert page.find( - 'a', text="Download live services csv report" - ).attrs['href'] == '/platform-admin/reports/live-services.csv' + assert ( + page.find("a", text="Download live services csv report").attrs["href"] + == "/platform-admin/reports/live-services.csv" + ) - assert page.find( - 'a', text="Monthly notification statuses for live services" - ).attrs['href'] == url_for('main.notifications_sent_by_service') + assert page.find("a", text="Monthly notification statuses for live services").attrs[ + "href" + ] == url_for("main.notifications_sent_by_service") def test_get_live_services_report( @@ -741,38 +856,57 @@ def test_get_live_services_report( platform_admin_user, mocker, ): - mocker.patch( - 'app.service_api_client.get_live_services_data', - return_value={'data': [ - {'service_id': 1, 'service_name': 'jessie the oak tree', 'organization_name': 'Forest', - 'consent_to_research': True, 'contact_name': 'Forest fairy', 'organization_type': 'Ecosystem', - 'contact_email': 'forest.fairy@digital.cabinet-office.gov.uk', 'contact_mobile': '+12028675109', - 'live_date': 'Sat, 29 Mar 2014 00:00:00 GMT', 'sms_volume_intent': 100, 'email_volume_intent': 50, - 'sms_totals': 300, 'email_totals': 1200, - 'free_sms_fragment_limit': 100}, - {'service_id': 2, 'service_name': 'james the pine tree', 'organization_name': 'Forest', - 'consent_to_research': None, 'contact_name': None, 'organization_type': 'Ecosystem', - 'contact_email': None, 'contact_mobile': None, - 'live_date': None, 'sms_volume_intent': None, 'email_volume_intent': 60, - 'sms_totals': 0, 'email_totals': 0, - 'free_sms_fragment_limit': 200}, - ]} + "app.service_api_client.get_live_services_data", + return_value={ + "data": [ + { + "service_id": 1, + "service_name": "jessie the oak tree", + "organization_name": "Forest", + "consent_to_research": True, + "contact_name": "Forest fairy", + "organization_type": "Ecosystem", + "contact_email": "forest.fairy@digital.cabinet-office.gov.uk", + "contact_mobile": "+12028675109", + "live_date": "Sat, 29 Mar 2014 00:00:00 GMT", + "sms_volume_intent": 100, + "email_volume_intent": 50, + "sms_totals": 300, + "email_totals": 1200, + "free_sms_fragment_limit": 100, + }, + { + "service_id": 2, + "service_name": "james the pine tree", + "organization_name": "Forest", + "consent_to_research": None, + "contact_name": None, + "organization_type": "Ecosystem", + "contact_email": None, + "contact_mobile": None, + "live_date": None, + "sms_volume_intent": None, + "email_volume_intent": 60, + "sms_totals": 0, + "email_totals": 0, + "free_sms_fragment_limit": 200, + }, + ] + }, ) client_request.login(platform_admin_user) response = client_request.get_response( - 'main.live_services_csv', + "main.live_services_csv", ) report = response.get_data(as_text=True) assert report.strip() == ( - 'Service ID,Organization,Organization type,Service name,Consent to research,Main contact,Contact email,' + - 'Contact mobile,Live date,SMS volume intent,Email volume intent,SMS sent this year,' + - 'Emails sent this year,Free sms allowance\r\n' + - - '1,Forest,Ecosystem,jessie the oak tree,True,Forest fairy,forest.fairy@digital.cabinet-office.gov.uk,' + - '+12028675109,29-03-2014,100,50,300,1200,100\r\n' + - - '2,Forest,Ecosystem,james the pine tree,,,,,,,60,0,0,200' + "Service ID,Organization,Organization type,Service name,Consent to research,Main contact,Contact email," + + "Contact mobile,Live date,SMS volume intent,Email volume intent,SMS sent this year," + + "Emails sent this year,Free sms allowance\r\n" + + "1,Forest,Ecosystem,jessie the oak tree,True,Forest fairy,forest.fairy@digital.cabinet-office.gov.uk," + + "+12028675109,29-03-2014,100,50,300,1200,100\r\n" + + "2,Forest,Ecosystem,james the pine tree,,,,,,,60,0,0,200" ) @@ -781,15 +915,15 @@ def test_get_notifications_sent_by_service_shows_date_form( platform_admin_user, ): client_request.login(platform_admin_user) - page = client_request.get('main.notifications_sent_by_service') + page = client_request.get("main.notifications_sent_by_service") assert [ - (input['type'], input['name'], input.get('value')) - for input in page.select('input') + (input["type"], input["name"], input.get("value")) + for input in page.select("input") ] == [ - ('text', 'start_date', None), - ('text', 'end_date', None), - ('hidden', 'csrf_token', ANY) + ("text", "start_date", None), + ("text", "end_date", None), + ("hidden", "csrf_token", ANY), ] @@ -798,86 +932,94 @@ def test_get_notifications_sent_by_service_validates_form( client_request, platform_admin_user, ): - mock_get_stats_from_api = mocker.patch('app.main.views.platform_admin.notification_api_client') + mock_get_stats_from_api = mocker.patch( + "app.main.views.platform_admin.notification_api_client" + ) client_request.login(platform_admin_user) page = client_request.post( - 'main.notifications_sent_by_service', + "main.notifications_sent_by_service", _expected_status=200, - _data={'start_date': '', 'end_date': '20190101'} + _data={"start_date": "", "end_date": "20190101"}, ) - errors = page.select('.usa-error-message') + errors = page.select(".usa-error-message") assert len(errors) == 2 for error in errors: - assert 'Not a valid date value' in error.text + assert "Not a valid date value" in error.text assert mock_get_stats_from_api.called is False -def test_get_billing_report_when_no_results_for_date(client_request, platform_admin_user, mocker): +def test_get_billing_report_when_no_results_for_date( + client_request, platform_admin_user, mocker +): client_request.login(platform_admin_user) - mocker.patch("app.main.views.platform_admin.billing_api_client.get_data_for_billing_report", - return_value=[]) + mocker.patch( + "app.main.views.platform_admin.billing_api_client.get_data_for_billing_report", + return_value=[], + ) - page = client_request.post('main.get_billing_report', - _expected_status=200, - _data={'start_date': '2019-01-01', 'end_date': '2019-03-31'}) + page = client_request.post( + "main.get_billing_report", + _expected_status=200, + _data={"start_date": "2019-01-01", "end_date": "2019-03-31"}, + ) - error = page.select_one('.banner-dangerous') - assert normalize_spaces(error.text) == 'No results for dates' + error = page.select_one(".banner-dangerous") + assert normalize_spaces(error.text) == "No results for dates" def test_get_billing_report_calls_api_and_download_data( - client_request, - platform_admin_user, - mocker + client_request, platform_admin_user, mocker ): mocker.patch( "app.main.views.platform_admin.billing_api_client.get_data_for_billing_report", - return_value=[{ - 'organization_id': '7832a1be-a1f0-4f2a-982f-05adfd3d6354', - 'organization_name': 'Org for a - with sms', - 'service_id': '48e82ac0-c8c4-4e46-8712-c83c35a94006', - 'service_name': 'a - with sms', - 'sms_cost': 0, - 'sms_chargeable_units': 0, - 'purchase_order_number': 'PO1234', - 'contact_names': 'Anne, Marie, Josh', - 'contact_email_addresses': 'billing@example.com, accounts@example.com', - 'billing_reference': 'Notify2020' - }] + return_value=[ + { + "organization_id": "7832a1be-a1f0-4f2a-982f-05adfd3d6354", + "organization_name": "Org for a - with sms", + "service_id": "48e82ac0-c8c4-4e46-8712-c83c35a94006", + "service_name": "a - with sms", + "sms_cost": 0, + "sms_chargeable_units": 0, + "purchase_order_number": "PO1234", + "contact_names": "Anne, Marie, Josh", + "contact_email_addresses": "billing@example.com, accounts@example.com", + "billing_reference": "Notify2020", + } + ], ) client_request.login(platform_admin_user) response = client_request.post_response( - 'main.get_billing_report', - _data={'start_date': '2019-01-01', 'end_date': '2019-03-31'}, + "main.get_billing_report", + _data={"start_date": "2019-01-01", "end_date": "2019-03-31"}, _expected_status=200, ) - assert response.content_type == 'text/csv; charset=utf-8' - assert response.headers['Content-Disposition'] == ( - 'attachment; filename="Billing Report from {} to {}.csv"'.format('2019-01-01', '2019-03-31') + assert response.content_type == "text/csv; charset=utf-8" + assert response.headers["Content-Disposition"] == ( + 'attachment; filename="Billing Report from {} to {}.csv"'.format( + "2019-01-01", "2019-03-31" + ) ) assert response.get_data(as_text=True) == ( - 'organization_id,organization_name,service_id,service_name,sms_cost,sms_chargeable_units,' + - 'purchase_order_number,contact_names,contact_email_addresses,' + - 'billing_reference\r\n' + - - '7832a1be-a1f0-4f2a-982f-05adfd3d6354,' + - 'Org for a - with sms,' + - '48e82ac0-c8c4-4e46-8712-c83c35a94006,' + - 'a - with sms,' + - '0,' + - '0,' + - 'PO1234,"Anne, Marie, Josh","billing@example.com, accounts@example.com",Notify2020' + - - '\r\n' + "organization_id,organization_name,service_id,service_name,sms_cost,sms_chargeable_units," + + "purchase_order_number,contact_names,contact_email_addresses," + + "billing_reference\r\n" + + "7832a1be-a1f0-4f2a-982f-05adfd3d6354," + + "Org for a - with sms," + + "48e82ac0-c8c4-4e46-8712-c83c35a94006," + + "a - with sms," + + "0," + + "0," + + 'PO1234,"Anne, Marie, Josh","billing@example.com, accounts@example.com",Notify2020' + + "\r\n" ) @@ -889,203 +1031,223 @@ def test_get_notifications_sent_by_service_calls_api_and_downloads_data( service_two, ): api_data = [ - ['2019-01-01', SERVICE_ONE_ID, service_one['name'], 'email', 191, 0, 0, 14, 0, 0], - ['2019-01-01', SERVICE_ONE_ID, service_one['name'], 'sms', 42, 0, 0, 8, 0, 0], - ['2019-01-01', SERVICE_TWO_ID, service_two['name'], 'email', 3, 1, 0, 2, 0, 0], + [ + "2019-01-01", + SERVICE_ONE_ID, + service_one["name"], + "email", + 191, + 0, + 0, + 14, + 0, + 0, + ], + ["2019-01-01", SERVICE_ONE_ID, service_one["name"], "sms", 42, 0, 0, 8, 0, 0], + ["2019-01-01", SERVICE_TWO_ID, service_two["name"], "email", 3, 1, 0, 2, 0, 0], ] - mocker.patch('app.main.views.platform_admin.notification_api_client.get_notification_status_by_service', - return_value=api_data) + mocker.patch( + "app.main.views.platform_admin.notification_api_client.get_notification_status_by_service", + return_value=api_data, + ) start_date = datetime.date(2019, 1, 1) end_date = datetime.date(2019, 1, 31) client_request.login(platform_admin_user) response = client_request.post_response( - 'main.notifications_sent_by_service', - _data={'start_date': start_date, 'end_date': end_date}, + "main.notifications_sent_by_service", + _data={"start_date": start_date, "end_date": end_date}, _expected_status=200, ) - assert response.content_type == 'text/csv; charset=utf-8' - assert response.headers['Content-Disposition'] == ( - 'attachment; filename="{} to {} notification status per service report.csv"'.format(start_date, end_date) + assert response.content_type == "text/csv; charset=utf-8" + assert response.headers["Content-Disposition"] == ( + 'attachment; filename="{} to {} notification status per service report.csv"'.format( + start_date, end_date + ) ) assert response.get_data(as_text=True) == ( - 'date_created,service_id,service_name,notification_type,count_sending,count_delivered,' + - 'count_technical_failure,count_temporary_failure,count_permanent_failure,count_sent\r\n' + - - '2019-01-01,596364a0-858e-42c8-9062-a8fe822260eb,service one,email,191,0,0,14,0,0\r\n' + - '2019-01-01,596364a0-858e-42c8-9062-a8fe822260eb,service one,sms,42,0,0,8,0,0\r\n' + - '2019-01-01,147ad62a-2951-4fa1-9ca0-093cd1a52c52,service two,email,3,1,0,2,0,0\r\n' + "date_created,service_id,service_name,notification_type,count_sending,count_delivered," + + "count_technical_failure,count_temporary_failure,count_permanent_failure,count_sent\r\n" + + "2019-01-01,596364a0-858e-42c8-9062-a8fe822260eb,service one,email,191,0,0,14,0,0\r\n" + + "2019-01-01,596364a0-858e-42c8-9062-a8fe822260eb,service one,sms,42,0,0,8,0,0\r\n" + + "2019-01-01,147ad62a-2951-4fa1-9ca0-093cd1a52c52,service two,email,3,1,0,2,0,0\r\n" ) def test_get_volumes_by_service_report_calls_api_and_download_data( - client_request, - platform_admin_user, - mocker + client_request, platform_admin_user, mocker ): mocker.patch( "app.main.views.platform_admin.billing_api_client.get_data_for_volumes_by_service_report", - return_value=[{ - "organization_id": "7832a1be-a1f0-4f2a-982f-05adfd3d6354", - "organization_name": "Org name", - "service_id": "48e82ac0-c8c4-4e46-8712-c83c35a94006", - "service_name": "service name", - "free_allowance": 10000, - "sms_notifications": 10, - "sms_chargeable_units": 20, - "email_totals": 8, - }] + return_value=[ + { + "organization_id": "7832a1be-a1f0-4f2a-982f-05adfd3d6354", + "organization_name": "Org name", + "service_id": "48e82ac0-c8c4-4e46-8712-c83c35a94006", + "service_name": "service name", + "free_allowance": 10000, + "sms_notifications": 10, + "sms_chargeable_units": 20, + "email_totals": 8, + } + ], ) client_request.login(platform_admin_user) response = client_request.post_response( - 'main.get_volumes_by_service', - _data={'start_date': '2019-01-01', 'end_date': '2019-03-31'}, + "main.get_volumes_by_service", + _data={"start_date": "2019-01-01", "end_date": "2019-03-31"}, _expected_status=200, ) - assert response.content_type == 'text/csv; charset=utf-8' - assert response.headers['Content-Disposition'] == ( - 'attachment; filename="Volumes by service report from {} to {}.csv"'.format('2019-01-01', '2019-03-31') + assert response.content_type == "text/csv; charset=utf-8" + assert response.headers["Content-Disposition"] == ( + 'attachment; filename="Volumes by service report from {} to {}.csv"'.format( + "2019-01-01", "2019-03-31" + ) ) assert response.get_data(as_text=True) == ( - "organization id,organization name,service id,service name,free allowance,sms notifications," + - "sms chargeable units,email totals\r\n" + - - '7832a1be-a1f0-4f2a-982f-05adfd3d6354,' + - 'Org name,' + - '48e82ac0-c8c4-4e46-8712-c83c35a94006,' + - 'service name,' + - '10000,' + - '10,' + - '20,' + - '8' + - - '\r\n' + "organization id,organization name,service id,service name,free allowance,sms notifications," + + "sms chargeable units,email totals\r\n" + + "7832a1be-a1f0-4f2a-982f-05adfd3d6354," + + "Org name," + + "48e82ac0-c8c4-4e46-8712-c83c35a94006," + + "service name," + + "10000," + + "10," + + "20," + + "8" + + "\r\n" ) def test_get_daily_volumes_report_calls_api_and_download_data( - client_request, - platform_admin_user, - mocker + client_request, platform_admin_user, mocker ): mocker.patch( "app.main.views.platform_admin.billing_api_client.get_data_for_daily_volumes_report", - return_value=[{ - "day": '2019-01-01', - "sms_totals": 20, - "sms_fragment_totals": 40, - "sms_chargeable_units": 60, - "email_totals": 100, - }] + return_value=[ + { + "day": "2019-01-01", + "sms_totals": 20, + "sms_fragment_totals": 40, + "sms_chargeable_units": 60, + "email_totals": 100, + } + ], ) client_request.login(platform_admin_user) response = client_request.post_response( - 'main.get_daily_volumes', - _data={'start_date': '2019-01-01', 'end_date': '2019-03-31'}, + "main.get_daily_volumes", + _data={"start_date": "2019-01-01", "end_date": "2019-03-31"}, _expected_status=200, ) - assert response.content_type == 'text/csv; charset=utf-8' - assert response.headers['Content-Disposition'] == ( - 'attachment; filename="Daily volumes report from {} to {}.csv"'.format('2019-01-01', '2019-03-31') + assert response.content_type == "text/csv; charset=utf-8" + assert response.headers["Content-Disposition"] == ( + 'attachment; filename="Daily volumes report from {} to {}.csv"'.format( + "2019-01-01", "2019-03-31" + ) ) assert response.get_data(as_text=True) == ( - "day,sms totals,sms fragment totals,sms chargeable units,email totals\r\n" + - - '2019-01-01,' + - '20,' + - '40,' + - '60,' + - '100' + - - '\r\n' + "day,sms totals,sms fragment totals,sms chargeable units,email totals\r\n" + + "2019-01-01," + + "20," + + "40," + + "60," + + "100" + + "\r\n" ) -def test_get_users_report( - client_request, - platform_admin_user, - mocker -): +def test_get_users_report(client_request, platform_admin_user, mocker): mocker.patch( "app.main.views.platform_admin.user_api_client.get_all_users", - return_value=[{ - 'name': 'Johnny Sokko', - 'organizations': [], - 'password_changed_at': '2023-07-21 14:12:54.832850', 'permissions': { - 'test service': [ - 'manage_users', 'manage_templates', 'manage_settings', 'send_texts', - 'send_emails', 'manage_api_keys', 'view_activity']}, - 'platform_admin': True, 'services': ['test service'], 'state': 'active'} - ] - - + return_value=[ + { + "name": "Johnny Sokko", + "organizations": [], + "password_changed_at": "2023-07-21 14:12:54.832850", + "permissions": { + "test service": [ + "manage_users", + "manage_templates", + "manage_settings", + "send_texts", + "send_emails", + "manage_api_keys", + "view_activity", + ] + }, + "platform_admin": True, + "services": ["test service"], + "state": "active", + } + ], ) client_request.login(platform_admin_user) response = client_request.post_response( - 'main.get_users_report', + "main.get_users_report", _data={}, _expected_status=200, ) - assert response.content_type == 'text/csv; charset=utf-8' - assert 'attachment' in response.headers['Content-Disposition'] - assert 'filename' in response.headers['Content-Disposition'] - assert 'User Report' in response.headers['Content-Disposition'] + assert response.content_type == "text/csv; charset=utf-8" + assert "attachment" in response.headers["Content-Disposition"] + assert "filename" in response.headers["Content-Disposition"] + assert "User Report" in response.headers["Content-Disposition"] my_response = response.get_data(as_text=True) - assert 'Johnny Sokko' in my_response - assert 'manage_users' in my_response - assert 'test service' in my_response - assert 'active' in my_response + assert "Johnny Sokko" in my_response + assert "manage_users" in my_response + assert "test service" in my_response + assert "active" in my_response def test_get_daily_sms_provider_volumes_report_calls_api_and_download_data( - client_request, - platform_admin_user, - mocker + client_request, platform_admin_user, mocker ): mocker.patch( "app.main.views.platform_admin.billing_api_client.get_data_for_daily_sms_provider_volumes_report", - return_value=[{ - "day": '2019-01-01', - "provider": 'foo', - "sms_totals": 20, - "sms_fragment_totals": 40, - "sms_chargeable_units": 60, - "sms_cost": 80, - }] + return_value=[ + { + "day": "2019-01-01", + "provider": "foo", + "sms_totals": 20, + "sms_fragment_totals": 40, + "sms_chargeable_units": 60, + "sms_cost": 80, + } + ], ) client_request.login(platform_admin_user) response = client_request.post_response( - 'main.get_daily_sms_provider_volumes', - _data={'start_date': '2019-01-01', 'end_date': '2019-03-31'}, + "main.get_daily_sms_provider_volumes", + _data={"start_date": "2019-01-01", "end_date": "2019-03-31"}, _expected_status=200, ) - assert response.content_type == 'text/csv; charset=utf-8' - assert response.headers['Content-Disposition'] == ( - 'attachment; filename="Daily SMS provider volumes report from {} to {}.csv"'.format('2019-01-01', '2019-03-31') + assert response.content_type == "text/csv; charset=utf-8" + assert response.headers["Content-Disposition"] == ( + 'attachment; filename="Daily SMS provider volumes report from {} to {}.csv"'.format( + "2019-01-01", "2019-03-31" + ) ) assert response.get_data(as_text=True) == ( - "day,provider,sms totals,sms fragment totals,sms chargeable units,sms cost\r\n" + - - '2019-01-01,' + - 'foo,' + - '20,' + - '40,' + - '60,' + - '80' + - - '\r\n' + "day,provider,sms totals,sms fragment totals,sms chargeable units,sms cost\r\n" + + "2019-01-01," + + "foo," + + "20," + + "40," + + "60," + + "80" + + "\r\n" ) diff --git a/tests/app/main/views/test_providers.py b/tests/app/main/views/test_providers.py index af36aaad8..482e120e4 100644 --- a/tests/app/main/views/test_providers.py +++ b/tests/app/main/views/test_providers.py @@ -9,17 +9,17 @@ from app.main.views.providers import add_monthly_traffic def provider_json(overrides): provider = { - 'id': 'override-me', - 'active': True, - 'priority': 20, - 'display_name': 'Provider', - 'identifier': 'override-me', - 'notification_type': 'sms', - 'updated_at': None, - 'version': 1, - 'created_by_name': None, - 'supports_international': False, - 'current_month_billable_sms': 0, + "id": "override-me", + "active": True, + "priority": 20, + "display_name": "Provider", + "identifier": "override-me", + "notification_type": "sms", + "updated_at": None, + "version": 1, + "created_by_name": None, + "supports_international": False, + "current_month_billable_sms": 0, } provider.update(**overrides) @@ -28,72 +28,84 @@ def provider_json(overrides): @pytest.fixture def sms_provider_1(): - return provider_json({ - 'id': 'sms_provider_1-id', - 'priority': 20, - 'display_name': 'SMS Provider 1', - 'identifier': 'sms_provider_1', - 'notification_type': 'sms', - 'updated_at': datetime(2017, 1, 16, 15, 20, 40).isoformat(), - 'created_by_name': 'Test User', - 'current_month_billable_sms': 5020, - }) + return provider_json( + { + "id": "sms_provider_1-id", + "priority": 20, + "display_name": "SMS Provider 1", + "identifier": "sms_provider_1", + "notification_type": "sms", + "updated_at": datetime(2017, 1, 16, 15, 20, 40).isoformat(), + "created_by_name": "Test User", + "current_month_billable_sms": 5020, + } + ) @pytest.fixture def sms_provider_2(): - return provider_json({ - 'id': 'sms_provider_2-id', - 'priority': 10, - 'display_name': 'SMS Provider 2', - 'identifier': 'sms_provider_2', - 'notification_type': 'sms', - 'current_month_billable_sms': 6891, - }) + return provider_json( + { + "id": "sms_provider_2-id", + "priority": 10, + "display_name": "SMS Provider 2", + "identifier": "sms_provider_2", + "notification_type": "sms", + "current_month_billable_sms": 6891, + } + ) @pytest.fixture def email_provider_1(): - return provider_json({ - 'id': 'email_provider_1-id', - 'display_name': 'Email Provider 1', - 'identifier': 'email_provider_1', - 'notification_type': 'email', - }) + return provider_json( + { + "id": "email_provider_1-id", + "display_name": "Email Provider 1", + "identifier": "email_provider_1", + "notification_type": "email", + } + ) @pytest.fixture def email_provider_2(): - return provider_json({ - 'id': 'email_provider_2-id', - 'display_name': 'Email Provider 2', - 'identifier': 'email_provider_2', - 'notification_type': 'email', - }) + return provider_json( + { + "id": "email_provider_2-id", + "display_name": "Email Provider 2", + "identifier": "email_provider_2", + "notification_type": "email", + } + ) @pytest.fixture def sms_provider_intl_1(): - return provider_json({ - 'id': 'sms_provider_intl_1-id', - 'active': False, - 'display_name': 'SMS Provider Intl 1', - 'identifier': 'sms_provider_intl_1', - 'notification_type': 'sms', - 'supports_international': True, - }) + return provider_json( + { + "id": "sms_provider_intl_1-id", + "active": False, + "display_name": "SMS Provider Intl 1", + "identifier": "sms_provider_intl_1", + "notification_type": "sms", + "supports_international": True, + } + ) @pytest.fixture def sms_provider_intl_2(): - return provider_json({ - 'id': 'sms_provider_intl_2-id', - 'active': False, - 'display_name': 'SMS Provider Intl 2', - 'identifier': 'sms_provider_intl_2', - 'notification_type': 'sms', - 'supports_international': True, - }) + return provider_json( + { + "id": "sms_provider_intl_2-id", + "active": False, + "display_name": "SMS Provider Intl 2", + "identifier": "sms_provider_intl_2", + "notification_type": "sms", + "supports_international": True, + } + ) @pytest.fixture @@ -106,7 +118,7 @@ def stub_providers( sms_provider_intl_2, ): return { - 'provider_details': [ + "provider_details": [ sms_provider_1, sms_provider_2, email_provider_1, @@ -120,35 +132,35 @@ def stub_providers( @pytest.fixture def stub_provider_history(): return { - 'data': [ + "data": [ { - 'id': 'f9af1ec7-58ef-4f7d-a6f4-5fe7e48644cb', - 'active': True, - 'priority': 20, - 'display_name': 'Foo', - 'identifier': 'foo', - 'notification_type': 'sms', - 'updated_at': None, - 'version': 2, - 'created_by': { - 'email_address': 'test@foo.bar', - 'name': 'Test User', - 'id': '7cc1dddb-bcbc-4739-8fc1-61bedde3332a' + "id": "f9af1ec7-58ef-4f7d-a6f4-5fe7e48644cb", + "active": True, + "priority": 20, + "display_name": "Foo", + "identifier": "foo", + "notification_type": "sms", + "updated_at": None, + "version": 2, + "created_by": { + "email_address": "test@foo.bar", + "name": "Test User", + "id": "7cc1dddb-bcbc-4739-8fc1-61bedde3332a", }, - 'supports_international': False + "supports_international": False, }, { - 'id': 'f9af1ec7-58ef-4f7d-a6f4-5fe7e48644cb', - 'active': True, - 'priority': 10, - 'display_name': 'Bar', - 'identifier': 'bar', - 'notification_type': 'sms', - 'updated_at': None, - 'version': 1, - 'created_by': None, - 'supports_international': False - } + "id": "f9af1ec7-58ef-4f7d-a6f4-5fe7e48644cb", + "active": True, + "priority": 10, + "display_name": "Bar", + "identifier": "bar", + "notification_type": "sms", + "updated_at": None, + "version": 1, + "created_by": None, + "supports_international": False, + }, ] } @@ -159,31 +171,31 @@ def test_view_providers_shows_all_providers( mocker, stub_providers, ): - mocker.patch('app.provider_client.get_all_providers', return_value=stub_providers) + mocker.patch("app.provider_client.get_all_providers", return_value=stub_providers) client_request.login(platform_admin_user) - page = client_request.get('main.view_providers') + page = client_request.get("main.view_providers") - h1 = [header.text.strip() for header in page.find_all('h1')] + h1 = [header.text.strip() for header in page.find_all("h1")] - assert 'Providers' in h1 + assert "Providers" in h1 - h2 = [header.text.strip() for header in page.find_all('h2')] + h2 = [header.text.strip() for header in page.find_all("h2")] - assert 'Email' in h2 - assert 'SMS' in h2 + assert "Email" in h2 + assert "SMS" in h2 - tables = page.find_all('table') + tables = page.find_all("table") assert len(tables) == 3 domestic_sms_table = tables[0] domestic_email_table = tables[1] international_sms_table = tables[2] - domestic_sms_first_row = domestic_sms_table.tbody.find_all('tr')[0] - table_data = domestic_sms_first_row.find_all('td') + domestic_sms_first_row = domestic_sms_table.tbody.find_all("tr")[0] + table_data = domestic_sms_first_row.find_all("td") - assert table_data[0].find_all("a")[0]['href'] == '/provider/sms_provider_1-id' + assert table_data[0].find_all("a")[0]["href"] == "/provider/sms_provider_1-id" assert table_data[0].text.strip() == "SMS Provider 1" assert table_data[1].text.strip() == "20" assert table_data[2].text.strip() == "42" @@ -191,10 +203,10 @@ def test_view_providers_shows_all_providers( assert table_data[4].text.strip() == "16 January at 15:20 UTC" assert table_data[5].text.strip() == "Test User" - domestic_sms_second_row = domestic_sms_table.tbody.find_all('tr')[1] - table_data = domestic_sms_second_row.find_all('td') + domestic_sms_second_row = domestic_sms_table.tbody.find_all("tr")[1] + table_data = domestic_sms_second_row.find_all("td") - assert table_data[0].find_all("a")[0]['href'] == '/provider/sms_provider_2-id' + assert table_data[0].find_all("a")[0]["href"] == "/provider/sms_provider_2-id" assert table_data[0].text.strip() == "SMS Provider 2" assert table_data[1].text.strip() == "10" assert table_data[2].text.strip() == "58" @@ -202,28 +214,34 @@ def test_view_providers_shows_all_providers( assert table_data[4].text.strip() == "None" assert table_data[5].text.strip() == "None" - domestic_email_first_row = domestic_email_table.tbody.find_all('tr')[0] - domestic_email_table_data = domestic_email_first_row.find_all('td') + domestic_email_first_row = domestic_email_table.tbody.find_all("tr")[0] + domestic_email_table_data = domestic_email_first_row.find_all("td") - assert domestic_email_table_data[0].find_all("a")[0]['href'] == '/provider/email_provider_1-id' + assert ( + domestic_email_table_data[0].find_all("a")[0]["href"] + == "/provider/email_provider_1-id" + ) assert domestic_email_table_data[0].text.strip() == "Email Provider 1" assert domestic_email_table_data[1].text.strip() == "True" assert domestic_email_table_data[2].text.strip() == "None" assert domestic_email_table_data[3].text.strip() == "None" - domestic_email_second_row = domestic_email_table.tbody.find_all('tr')[1] - domestic_email_table_data = domestic_email_second_row.find_all('td') + domestic_email_second_row = domestic_email_table.tbody.find_all("tr")[1] + domestic_email_table_data = domestic_email_second_row.find_all("td") - assert domestic_email_table_data[0].find_all("a")[0]['href'] == '/provider/email_provider_2-id' + assert ( + domestic_email_table_data[0].find_all("a")[0]["href"] + == "/provider/email_provider_2-id" + ) assert domestic_email_table_data[0].text.strip() == "Email Provider 2" assert domestic_email_table_data[1].text.strip() == "True" assert domestic_email_table_data[2].text.strip() == "None" assert domestic_email_table_data[3].text.strip() == "None" - international_sms_first_row = international_sms_table.tbody.find_all('tr')[0] - table_data = international_sms_first_row.find_all('td') + international_sms_first_row = international_sms_table.tbody.find_all("tr")[0] + table_data = international_sms_first_row.find_all("td") - assert table_data[0].find_all("a")[0]['href'] == '/provider/sms_provider_intl_1-id' + assert table_data[0].find_all("a")[0]["href"] == "/provider/sms_provider_intl_1-id" assert table_data[0].text.strip() == "SMS Provider Intl 1" assert table_data[1].text.strip() == "False" assert table_data[2].text.strip() == "None" @@ -231,54 +249,60 @@ def test_view_providers_shows_all_providers( def test_add_monthly_traffic(): - domestic_sms_providers = [{ - 'identifier': 'mmg', - 'current_month_billable_sms': 27, - }, { - 'identifier': 'firetext', - 'current_month_billable_sms': 5, - }, { - 'identifier': 'loadtesting', - 'current_month_billable_sms': 0, - }] + domestic_sms_providers = [ + { + "identifier": "mmg", + "current_month_billable_sms": 27, + }, + { + "identifier": "firetext", + "current_month_billable_sms": 5, + }, + { + "identifier": "loadtesting", + "current_month_billable_sms": 0, + }, + ] add_monthly_traffic(domestic_sms_providers) - assert domestic_sms_providers == [{ - 'identifier': 'mmg', - 'current_month_billable_sms': 27, - 'monthly_traffic': 84 - }, { - 'identifier': 'firetext', - 'current_month_billable_sms': 5, - 'monthly_traffic': 16 - }, { - 'identifier': 'loadtesting', - 'current_month_billable_sms': 0, - 'monthly_traffic': 0 - }] + assert domestic_sms_providers == [ + {"identifier": "mmg", "current_month_billable_sms": 27, "monthly_traffic": 84}, + { + "identifier": "firetext", + "current_month_billable_sms": 5, + "monthly_traffic": 16, + }, + { + "identifier": "loadtesting", + "current_month_billable_sms": 0, + "monthly_traffic": 0, + }, + ] def test_view_provider_shows_version_history( - client_request, - platform_admin_user, - mocker, - stub_provider_history + client_request, platform_admin_user, mocker, stub_provider_history ): - mocker.patch('app.provider_client.get_provider_versions', return_value=stub_provider_history) + mocker.patch( + "app.provider_client.get_provider_versions", return_value=stub_provider_history + ) client_request.login(platform_admin_user) page = client_request.get( - 'main.view_provider', provider_id=stub_provider_history['data'][0]['id'] + "main.view_provider", provider_id=stub_provider_history["data"][0]["id"] ) - table = page.find('table') - table_rows = table.find_all('tr') - table_headings = table_rows[0].find_all('th') - first_row = table_rows[1].find_all('td') - second_row = table_rows[2].find_all('td') + table = page.find("table") + table_rows = table.find_all("tr") + table_headings = table_rows[0].find_all("th") + first_row = table_rows[1].find_all("td") + second_row = table_rows[2].find_all("td") - assert page.find_all('h1')[0].text.strip() == stub_provider_history['data'][0]["display_name"] + assert ( + page.find_all("h1")[0].text.strip() + == stub_provider_history["data"][0]["display_name"] + ) assert len(table_rows) == 3 assert table_headings[0].text.strip() == "Version" @@ -301,27 +325,20 @@ def test_view_provider_shows_version_history( def test_edit_sms_provider_provider_ratio( - client_request, - platform_admin_user, - mocker, - stub_providers, - sms_provider_1 + client_request, platform_admin_user, mocker, stub_providers, sms_provider_1 ): - mocker.patch( - 'app.provider_client.get_all_providers', - return_value=stub_providers - ) + mocker.patch("app.provider_client.get_all_providers", return_value=stub_providers) client_request.login(platform_admin_user) page = client_request.get( - '.edit_sms_provider_ratio', + ".edit_sms_provider_ratio", ) inputs = page.select('.usa-input[type="text"]') assert len(inputs) == 2 first_input = page.select_one('.usa-input[name="sms_provider_1"]') - assert first_input.attrs['value'] == str(sms_provider_1['priority']) + assert first_input.attrs["value"] == str(sms_provider_1["priority"]) def test_edit_sms_provider_provider_ratio_only_shows_active_providers( @@ -331,44 +348,41 @@ def test_edit_sms_provider_provider_ratio_only_shows_active_providers( stub_providers, sms_provider_1, ): - sms_provider_1['active'] = False + sms_provider_1["active"] = False mocker.patch( - 'app.provider_client.get_all_providers', + "app.provider_client.get_all_providers", return_value=stub_providers, ) client_request.login(platform_admin_user) page = client_request.get( - '.edit_sms_provider_ratio', + ".edit_sms_provider_ratio", ) inputs = page.select('.usa-input[type="text"]') assert len(inputs) == 1 -@pytest.mark.parametrize('post_data, expected_calls', [ - ( - { - 'sms_provider_1': 10, - 'sms_provider_2': 90 - }, - [ - call('sms_provider_1-id', 10), - call('sms_provider_2-id', 90), - ], - ), - ( - { - 'sms_provider_1': 80, - 'sms_provider_2': 20 - }, - [ - call('sms_provider_1-id', 80), - call('sms_provider_2-id', 20), - ], - ), -]) +@pytest.mark.parametrize( + "post_data, expected_calls", + [ + ( + {"sms_provider_1": 10, "sms_provider_2": 90}, + [ + call("sms_provider_1-id", 10), + call("sms_provider_2-id", 90), + ], + ), + ( + {"sms_provider_1": 80, "sms_provider_2": 20}, + [ + call("sms_provider_1-id", 80), + call("sms_provider_2-id", 20), + ], + ), + ], +) def test_edit_sms_provider_ratio_submit( client_request, platform_admin_user, @@ -377,42 +391,28 @@ def test_edit_sms_provider_ratio_submit( expected_calls, stub_providers, ): - mocker.patch( - 'app.provider_client.get_all_providers', - return_value=stub_providers - ) - mock_update_provider = mocker.patch( - 'app.provider_client.update_provider' - ) + mocker.patch("app.provider_client.get_all_providers", return_value=stub_providers) + mock_update_provider = mocker.patch("app.provider_client.update_provider") client_request.login(platform_admin_user) client_request.post( - '.edit_sms_provider_ratio', + ".edit_sms_provider_ratio", _data=post_data, _expected_redirect=url_for( - '.view_providers', + ".view_providers", ), ) assert mock_update_provider.call_args_list == expected_calls -@pytest.mark.parametrize('post_data, expected_error', [ - ( - { - 'sms_provider_1': 90, - 'sms_provider_2': 20 - }, - "Must add up to 100%" - ), - ( - { - 'sms_provider_1': 101, - 'sms_provider_2': 20 - }, - "Must be between 0 and 100" - ), -]) +@pytest.mark.parametrize( + "post_data, expected_error", + [ + ({"sms_provider_1": 90, "sms_provider_2": 20}, "Must add up to 100%"), + ({"sms_provider_1": 101, "sms_provider_2": 20}, "Must be between 0 and 100"), + ], +) def test_edit_sms_provider_submit_invalid_percentages( client_request, platform_admin_user, @@ -421,20 +421,13 @@ def test_edit_sms_provider_submit_invalid_percentages( expected_error, stub_providers, ): - mocker.patch( - 'app.provider_client.get_all_providers', - return_value=stub_providers - ) - mock_update_provider = mocker.patch( - 'app.provider_client.update_provider' - ) + mocker.patch("app.provider_client.get_all_providers", return_value=stub_providers) + mock_update_provider = mocker.patch("app.provider_client.update_provider") client_request.login(platform_admin_user) page = client_request.post( - '.edit_sms_provider_ratio', - _data=post_data, - _follow_redirects=True + ".edit_sms_provider_ratio", _data=post_data, _follow_redirects=True ) - assert expected_error in page.select_one('.usa-error-message').text + assert expected_error in page.select_one(".usa-error-message").text mock_update_provider.assert_not_called() diff --git a/tests/app/main/views/test_register.py b/tests/app/main/views/test_register.py index 641bb2ee2..bc6a7b4a6 100644 --- a/tests/app/main/views/test_register.py +++ b/tests/app/main/views/test_register.py @@ -11,33 +11,39 @@ def test_render_register_returns_template_with_form( client_request, ): client_request.logout() - page = client_request.get_url('/register') + page = client_request.get_url("/register") - assert page.find('input', attrs={'name': 'auth_type'}).attrs['value'] == 'sms_auth' - assert page.select_one('#email_address')['spellcheck'] == 'false' - assert page.select_one('#email_address')['autocomplete'] == 'email' - assert page.select_one('#password')['autocomplete'] == 'new-password' - assert 'Create an account' in page.text + assert page.find("input", attrs={"name": "auth_type"}).attrs["value"] == "sms_auth" + assert page.select_one("#email_address")["spellcheck"] == "false" + assert page.select_one("#email_address")["autocomplete"] == "email" + assert page.select_one("#password")["autocomplete"] == "new-password" + assert "Create an account" in page.text def test_logged_in_user_redirects_to_account( client_request, ): client_request.get( - 'main.register', + "main.register", _expected_status=302, - _expected_redirect=url_for('main.show_accounts_or_dashboard'), + _expected_redirect=url_for("main.show_accounts_or_dashboard"), ) -@pytest.mark.parametrize('phone_number_to_register_with', [ - '+12023900460', - '+1800-555-5555', -]) -@pytest.mark.parametrize('password', [ - 'the quick brown fox', - ' the quick brown fox ', -]) +@pytest.mark.parametrize( + "phone_number_to_register_with", + [ + "+12023900460", + "+1800-555-5555", + ], +) +@pytest.mark.parametrize( + "password", + [ + "the quick brown fox", + " the quick brown fox ", + ], +) def test_register_creates_new_user_and_redirects_to_continue_page( client_request, mock_send_verify_code, @@ -50,27 +56,33 @@ def test_register_creates_new_user_and_redirects_to_continue_page( password, ): client_request.logout() - user_data = {'name': 'Some One Valid', - 'email_address': 'notfound@example.gsa.gov', - 'mobile_number': phone_number_to_register_with, - 'password': password, - 'auth_type': 'sms_auth' - } + user_data = { + "name": "Some One Valid", + "email_address": "notfound@example.gsa.gov", + "mobile_number": phone_number_to_register_with, + "password": password, + "auth_type": "sms_auth", + } page = client_request.post( - 'main.register', + "main.register", _data=user_data, _follow_redirects=True, ) - assert page.select('main p')[0].text == 'An email has been sent to notfound@example.gsa.gov.' + assert ( + page.select("main p")[0].text + == "An email has been sent to notfound@example.gsa.gov." + ) - mock_send_verify_email.assert_called_with(ANY, user_data['email_address']) - mock_register_user.assert_called_with(user_data['name'], - user_data['email_address'], - user_data['mobile_number'], - user_data['password'], - user_data['auth_type']) + mock_send_verify_email.assert_called_with(ANY, user_data["email_address"]) + mock_register_user.assert_called_with( + user_data["name"], + user_data["email_address"], + user_data["mobile_number"], + user_data["password"], + user_data["auth_type"], + ) def test_register_continue_handles_missing_session_sensibly( @@ -79,8 +91,8 @@ def test_register_continue_handles_missing_session_sensibly( client_request.logout() # session is not set client_request.get( - 'main.registration_continue', - _expected_redirect=url_for('main.show_accounts_or_dashboard'), + "main.registration_continue", + _expected_redirect=url_for("main.show_accounts_or_dashboard"), ) @@ -92,17 +104,17 @@ def test_process_register_returns_200_when_mobile_number_is_invalid( ): client_request.logout() page = client_request.post( - 'main.register', + "main.register", _data={ - 'name': 'Bad Mobile', - 'email_address': 'bad_mobile@example.gsa.gov', - 'mobile_number': 'not good', - 'password': 'validPassword!', + "name": "Bad Mobile", + "email_address": "bad_mobile@example.gsa.gov", + "mobile_number": "not good", + "password": "validPassword!", }, _expected_status=200, ) - assert 'The string supplied did not seem to be a phone number' in page.text + assert "The string supplied did not seem to be a phone number" in page.text def test_should_return_200_when_email_is_not_gov_uk( @@ -111,28 +123,30 @@ def test_should_return_200_when_email_is_not_gov_uk( ): client_request.logout() page = client_request.post( - 'main.register', + "main.register", _data={ - 'name': 'Firstname Lastname', - 'email_address': 'bad_mobile@example.not.right', - 'mobile_number': '2020900123', - 'password': 'validPassword!' + "name": "Firstname Lastname", + "email_address": "bad_mobile@example.not.right", + "mobile_number": "2020900123", + "password": "validPassword!", }, _expected_status=200, ) - assert 'Enter a public sector email address or find out who can use Notify' in normalize_spaces( - page.select_one('.usa-error-message').text - ) - assert page.select_one('.usa-error-message a')['href'] == url_for( - 'main.features' + assert ( + "Enter a public sector email address or find out who can use Notify" + in normalize_spaces(page.select_one(".usa-error-message").text) ) + assert page.select_one(".usa-error-message a")["href"] == url_for("main.features") -@pytest.mark.parametrize('email_address', ( - 'notfound@example.gsa.gov', - 'example@lsquo.net', -)) +@pytest.mark.parametrize( + "email_address", + ( + "notfound@example.gsa.gov", + "example@lsquo.net", + ), +) def test_should_add_user_details_to_session( client_request, mocker, @@ -147,16 +161,16 @@ def test_should_add_user_details_to_session( ): client_request.logout() client_request.post( - 'main.register', + "main.register", _data={ - 'name': 'Test Codes', - 'email_address': email_address, - 'mobile_number': '+12023123123', - 'password': 'validPassword!' - } + "name": "Test Codes", + "email_address": email_address, + "mobile_number": "+12023123123", + "password": "validPassword!", + }, ) with client_request.session_transaction() as session: - assert session['user_details']['email'] == email_address + assert session["user_details"]["email"] == email_address def test_should_return_200_if_password_is_on_list_of_commonly_used_passwords( @@ -166,17 +180,17 @@ def test_should_return_200_if_password_is_on_list_of_commonly_used_passwords( ): client_request.logout() page = client_request.post( - 'main.register', + "main.register", _data={ - 'name': 'Bad Mobile', - 'email_address': 'bad_mobile@example.gsa.gov', - 'mobile_number': '+12021234123', - 'password': 'password', + "name": "Bad Mobile", + "email_address": "bad_mobile@example.gsa.gov", + "mobile_number": "+12021234123", + "password": "password", }, _expected_status=200, ) - assert 'Choose a password that’s harder to guess' in page.text + assert "Choose a password that’s harder to guess" in page.text def test_register_with_existing_email_sends_emails( @@ -187,38 +201,41 @@ def test_register_with_existing_email_sends_emails( ): client_request.logout() user_data = { - 'name': 'Already Hasaccount', - 'email_address': api_user_active['email_address'], - 'mobile_number': '+12025900460', - 'password': 'validPassword!' + "name": "Already Hasaccount", + "email_address": api_user_active["email_address"], + "mobile_number": "+12025900460", + "password": "validPassword!", } client_request.post( - 'main.register', + "main.register", _data=user_data, - _expected_redirect=url_for('main.registration_continue'), + _expected_redirect=url_for("main.registration_continue"), ) -@pytest.mark.parametrize('email_address, expected_value', [ - ("first.last@example.com", "First Last"), - ("first.middle.last@example.com", "First Middle Last"), - ("first.m.last@example.com", "First Last"), - ("first.last-last@example.com", "First Last-Last"), - ("first.o'last@example.com", "First O’Last"), - ("first.last+testing@example.com", "First Last"), - ("first.last+testing+testing@example.com", "First Last"), - ("first.last6@example.com", "First Last"), - ("first.last.212@example.com", "First Last"), - ("first.2.last@example.com", "First Last"), - ("first.2b.last@example.com", "First Last"), - ("first.1.2.3.last@example.com", "First Last"), - ("first.last.1.2.3@example.com", "First Last"), - # Instances where we can’t make a good-enough guess: - ("example123@example.com", None), - ("f.last@example.com", None), - ("f.m.last@example.com", None), -]) +@pytest.mark.parametrize( + "email_address, expected_value", + [ + ("first.last@example.com", "First Last"), + ("first.middle.last@example.com", "First Middle Last"), + ("first.m.last@example.com", "First Last"), + ("first.last-last@example.com", "First Last-Last"), + ("first.o'last@example.com", "First O’Last"), + ("first.last+testing@example.com", "First Last"), + ("first.last+testing+testing@example.com", "First Last"), + ("first.last6@example.com", "First Last"), + ("first.last.212@example.com", "First Last"), + ("first.2.last@example.com", "First Last"), + ("first.2b.last@example.com", "First Last"), + ("first.1.2.3.last@example.com", "First Last"), + ("first.last.1.2.3@example.com", "First Last"), + # Instances where we can’t make a good-enough guess: + ("example123@example.com", None), + ("f.last@example.com", None), + ("f.m.last@example.com", None), + ], +) def test_shows_name_on_registration_page_from_invite( client_request, fake_uuid, @@ -227,12 +244,12 @@ def test_shows_name_on_registration_page_from_invite( sample_invite, mock_get_invited_user_by_id, ): - sample_invite['email_address'] = email_address + sample_invite["email_address"] = email_address with client_request.session_transaction() as session: - session['invited_user_id'] = sample_invite + session["invited_user_id"] = sample_invite - page = client_request.get('main.register_from_invite') - assert page.select_one('input[name=name]').get('value') == expected_value + page = client_request.get("main.register_from_invite") + assert page.select_one("input[name=name]").get("value") == expected_value def test_shows_hidden_email_address_on_registration_page_from_invite( @@ -241,35 +258,37 @@ def test_shows_hidden_email_address_on_registration_page_from_invite( sample_invite, mock_get_invited_user_by_id, ): - with client_request.session_transaction() as session: - session['invited_user_id'] = sample_invite + session["invited_user_id"] = sample_invite - page = client_request.get('main.register_from_invite') - assert normalize_spaces(page.select_one('main p').text) == ( - 'Your account will be created with this email address: invited_user@test.gsa.gov' + page = client_request.get("main.register_from_invite") + assert normalize_spaces(page.select_one("main p").text) == ( + "Your account will be created with this email address: invited_user@test.gsa.gov" ) - hidden_input = page.select_one('form .usa-sr-only input') + hidden_input = page.select_one("form .usa-sr-only input") for attr, value in ( - ('type', 'email'), - ('name', 'username'), - ('id', 'username'), - ('value', 'invited_user@test.gsa.gov'), - ('disabled', "disabled"), - ('tabindex', '-1'), - ('aria-hidden', 'true'), - ('autocomplete', 'username'), + ("type", "email"), + ("name", "username"), + ("id", "username"), + ("value", "invited_user@test.gsa.gov"), + ("disabled", "disabled"), + ("tabindex", "-1"), + ("aria-hidden", "true"), + ("autocomplete", "username"), ): assert hidden_input[attr] == value -@pytest.mark.parametrize('extra_data', ( - {}, - # The username field is present in the page but the POST request - # should ignore it - {'username': 'invited@user.com'}, - {'username': 'anythingelse@example.com'}, -)) +@pytest.mark.parametrize( + "extra_data", + ( + {}, + # The username field is present in the page but the POST request + # should ignore it + {"username": "invited@user.com"}, + {"username": "anythingelse@example.com"}, + ), +) def test_register_from_invite( client_request, fake_uuid, @@ -283,28 +302,28 @@ def test_register_from_invite( ): client_request.logout() with client_request.session_transaction() as session: - session['invited_user_id'] = sample_invite['id'] + session["invited_user_id"] = sample_invite["id"] client_request.post( - 'main.register_from_invite', + "main.register_from_invite", _data=dict( - name='Registered in another Browser', - email_address=sample_invite['email_address'], - mobile_number='+12024900460', - service=sample_invite['service'], - password='somreallyhardthingtoguess', - auth_type='sms_auth', + name="Registered in another Browser", + email_address=sample_invite["email_address"], + mobile_number="+12024900460", + service=sample_invite["service"], + password="somreallyhardthingtoguess", + auth_type="sms_auth", **extra_data ), - _expected_redirect=url_for('main.verify'), + _expected_redirect=url_for("main.verify"), ) mock_register_user.assert_called_once_with( - 'Registered in another Browser', - sample_invite['email_address'], - '+12024900460', - 'somreallyhardthingtoguess', - 'sms_auth', + "Registered in another Browser", + sample_invite["email_address"], + "+12024900460", + "somreallyhardthingtoguess", + "sms_auth", ), - mock_get_invited_user_by_id.assert_called_once_with(sample_invite['id']) + mock_get_invited_user_by_id.assert_called_once_with(sample_invite["id"]) def test_register_from_invite_when_user_registers_in_another_browser( @@ -316,24 +335,26 @@ def test_register_from_invite_when_user_registers_in_another_browser( sample_invite, ): client_request.logout() - sample_invite['email_address'] = api_user_active['email_address'] + sample_invite["email_address"] = api_user_active["email_address"] with client_request.session_transaction() as session: - session['invited_user_id'] = sample_invite['id'] + session["invited_user_id"] = sample_invite["id"] client_request.post( - 'main.register_from_invite', + "main.register_from_invite", _data={ - 'name': 'Registered in another Browser', - 'email_address': api_user_active['email_address'], - 'mobile_number': api_user_active['mobile_number'], - 'service': sample_invite['service'], - 'password': 'somreallyhardthingtoguess', - 'auth_type': 'sms_auth' + "name": "Registered in another Browser", + "email_address": api_user_active["email_address"], + "mobile_number": api_user_active["mobile_number"], + "service": sample_invite["service"], + "password": "somreallyhardthingtoguess", + "auth_type": "sms_auth", }, - _expected_redirect=url_for('main.verify'), + _expected_redirect=url_for("main.verify"), ) -@pytest.mark.parametrize('invite_email_address', ['gov-user@gsa.gov', 'non-gov-user@example.com']) +@pytest.mark.parametrize( + "invite_email_address", ["gov-user@gsa.gov", "non-gov-user@example.com"] +) def test_register_from_email_auth_invite( client_request, sample_invite, @@ -353,29 +374,29 @@ def test_register_from_email_auth_invite( mocker, ): client_request.logout() - mock_login_user = mocker.patch('app.models.user.login_user') - sample_invite['auth_type'] = 'email_auth' - sample_invite['email_address'] = invite_email_address + mock_login_user = mocker.patch("app.models.user.login_user") + sample_invite["auth_type"] = "email_auth" + sample_invite["email_address"] = invite_email_address with client_request.session_transaction() as session: - session['invited_user_id'] = sample_invite['id'] + session["invited_user_id"] = sample_invite["id"] # Prove that the user isn’t already signed in - assert 'user_id' not in session + assert "user_id" not in session data = { - 'name': 'invited user', - 'email_address': sample_invite['email_address'], - 'mobile_number': '2028675301', - 'password': 'FSLKAJHFNvdzxgfyst', - 'service': sample_invite['service'], - 'auth_type': 'email_auth', + "name": "invited user", + "email_address": sample_invite["email_address"], + "mobile_number": "2028675301", + "password": "FSLKAJHFNvdzxgfyst", + "service": sample_invite["service"], + "auth_type": "email_auth", } client_request.post( - 'main.register_from_invite', + "main.register_from_invite", _data=data, _expected_redirect=url_for( - 'main.service_dashboard', - service_id=sample_invite['service'], + "main.service_dashboard", + service_id=sample_invite["service"], ), ) @@ -384,33 +405,39 @@ def test_register_from_email_auth_invite( assert not mock_send_verify_code.called # creates user with email_auth set mock_register_user.assert_called_once_with( - data['name'], - data['email_address'], - data['mobile_number'], - data['password'], - data['auth_type'] + data["name"], + data["email_address"], + data["mobile_number"], + data["password"], + data["auth_type"], ) # this is actually called twice, at the beginning of the function and then by the activate_user function - mock_get_invited_user_by_id.assert_called_with(sample_invite['id']) - mock_accept_invite.assert_called_once_with(sample_invite['service'], sample_invite['id']) + mock_get_invited_user_by_id.assert_called_with(sample_invite["id"]) + mock_accept_invite.assert_called_once_with( + sample_invite["service"], sample_invite["id"] + ) # just logs them in - mock_login_user.assert_called_once_with(User({ - 'id': fake_uuid, # This ID matches the return value of mock_register_user - 'platform_admin': False - })) + mock_login_user.assert_called_once_with( + User( + { + "id": fake_uuid, # This ID matches the return value of mock_register_user + "platform_admin": False, + } + ) + ) mock_add_user_to_service.assert_called_once_with( - sample_invite['service'], + sample_invite["service"], fake_uuid, # This ID matches the return value of mock_register_user - {'manage_api_keys', 'manage_service', 'send_messages', 'view_activity'}, + {"manage_api_keys", "manage_service", "send_messages", "view_activity"}, [], ) with client_request.session_transaction() as session: # The user is signed in - assert 'user_id' in session + assert "user_id" in session # invited user details are still there so they can get added to the service - assert session['invited_user_id'] == sample_invite['id'] + assert session["invited_user_id"] == sample_invite["id"] def test_can_register_email_auth_without_phone_number( @@ -428,34 +455,30 @@ def test_can_register_email_auth_without_phone_number( mock_get_invited_user_by_id, ): client_request.logout() - sample_invite['auth_type'] = 'email_auth' + sample_invite["auth_type"] = "email_auth" with client_request.session_transaction() as session: - session['invited_user_id'] = sample_invite['id'] + session["invited_user_id"] = sample_invite["id"] data = { - 'name': 'invited user', - 'email_address': sample_invite['email_address'], - 'mobile_number': '', - 'password': 'FSLKAJHFNvdzxgfyst', - 'service': sample_invite['service'], - 'auth_type': 'email_auth' + "name": "invited user", + "email_address": sample_invite["email_address"], + "mobile_number": "", + "password": "FSLKAJHFNvdzxgfyst", + "service": sample_invite["service"], + "auth_type": "email_auth", } client_request.post( - 'main.register_from_invite', + "main.register_from_invite", _data=data, _expected_redirect=url_for( - 'main.service_dashboard', - service_id=sample_invite['service'], + "main.service_dashboard", + service_id=sample_invite["service"], ), ) mock_register_user.assert_called_once_with( - ANY, - ANY, - None, # mobile_number - ANY, - ANY + ANY, ANY, None, ANY, ANY # mobile_number ) @@ -467,18 +490,18 @@ def test_cannot_register_with_sms_auth_and_missing_mobile_number( ): client_request.logout() page = client_request.post( - 'main.register', + "main.register", _data={ - 'name': 'Missing Mobile', - 'email_address': 'missing_mobile@example.gsa.gov', - 'password': 'validPassword!', + "name": "Missing Mobile", + "email_address": "missing_mobile@example.gsa.gov", + "password": "validPassword!", }, _expected_status=200, ) - err = page.select_one('.usa-error-message') - assert err.text.strip() == 'Error: Cannot be empty' - assert err.attrs['data-error-label'] == 'mobile_number' + err = page.select_one(".usa-error-message") + assert err.text.strip() == "Error: Cannot be empty" + assert err.attrs["data-error-label"] == "mobile_number" def test_register_from_invite_form_doesnt_show_mobile_number_field_if_email_auth( @@ -487,11 +510,13 @@ def test_register_from_invite_form_doesnt_show_mobile_number_field_if_email_auth mock_get_invited_user_by_id, ): client_request.logout() - sample_invite['auth_type'] = 'email_auth' + sample_invite["auth_type"] = "email_auth" with client_request.session_transaction() as session: - session['invited_user_id'] = sample_invite['id'] + session["invited_user_id"] = sample_invite["id"] - page = client_request.get('main.register_from_invite') + page = client_request.get("main.register_from_invite") - assert page.find('input', attrs={'name': 'auth_type'}).attrs['value'] == 'email_auth' - assert page.find('input', attrs={'name': 'mobile_number'}) is None + assert ( + page.find("input", attrs={"name": "auth_type"}).attrs["value"] == "email_auth" + ) + assert page.find("input", attrs={"name": "mobile_number"}) is None diff --git a/tests/app/main/views/test_security_policy.py b/tests/app/main/views/test_security_policy.py index 71785e775..61620516d 100644 --- a/tests/app/main/views/test_security_policy.py +++ b/tests/app/main/views/test_security_policy.py @@ -1,10 +1,13 @@ import pytest -@pytest.mark.parametrize('url', [ - '/security.txt', - '/.well-known/security.txt', -]) +@pytest.mark.parametrize( + "url", + [ + "/security.txt", + "/.well-known/security.txt", + ], +) def test_security_policy_redirects_to_policy(client_request, url): client_request.get_url( url, diff --git a/tests/app/main/views/test_send.py b/tests/app/main/views/test_send.py index 7f4e4d7b5..01318d940 100644 --- a/tests/app/main/views/test_send.py +++ b/tests/app/main/views/test_send.py @@ -30,13 +30,13 @@ from tests.conftest import ( normalize_spaces, ) -template_types = ['email', 'sms'] +template_types = ["email", "sms"] unchanging_fake_uuid = uuid.uuid4() # The * ignores hidden files, eg .DS_Store -test_spreadsheet_files = glob(path.join('tests', 'spreadsheet_files', '*')) -test_non_spreadsheet_files = glob(path.join('tests', 'non_spreadsheet_files', '*')) +test_spreadsheet_files = glob(path.join("tests", "spreadsheet_files", "*")) +test_non_spreadsheet_files = glob(path.join("tests", "non_spreadsheet_files", "*")) def test_show_correct_title_and_description_for_email_sender_type( @@ -46,12 +46,13 @@ def test_show_correct_title_and_description_for_email_sender_type( multiple_reply_to_email_addresses, ): page = client_request.get( - '.set_sender', - service_id=SERVICE_ONE_ID, - template_id=fake_uuid + ".set_sender", service_id=SERVICE_ONE_ID, template_id=fake_uuid ) - assert page.select_one('.usa-fieldset h1').text.strip() == 'Where should replies come back to?' + assert ( + page.select_one(".usa-fieldset h1").text.strip() + == "Where should replies come back to?" + ) def test_show_correct_title_and_description_for_sms_sender_type( @@ -61,12 +62,13 @@ def test_show_correct_title_and_description_for_sms_sender_type( multiple_sms_senders, ): page = client_request.get( - '.set_sender', - service_id=SERVICE_ONE_ID, - template_id=fake_uuid + ".set_sender", service_id=SERVICE_ONE_ID, template_id=fake_uuid ) - assert page.select_one('.usa-fieldset h1').text.strip() == 'Who should the message come from?' + assert ( + page.select_one(".usa-fieldset h1").text.strip() + == "Who should the message come from?" + ) def test_default_email_sender_is_checked_and_has_hint( @@ -76,14 +78,14 @@ def test_default_email_sender_is_checked_and_has_hint( multiple_reply_to_email_addresses, ): page = client_request.get( - '.set_sender', - service_id=SERVICE_ONE_ID, - template_id=fake_uuid + ".set_sender", service_id=SERVICE_ONE_ID, template_id=fake_uuid ) - assert page.select('.govuk-radios input')[0].has_attr('checked') - assert normalize_spaces(page.select_one('.govuk-radios .usa-hint').text) == "(Default)" - assert not page.select('.govuk-radios input')[1].has_attr('checked') + assert page.select(".govuk-radios input")[0].has_attr("checked") + assert ( + normalize_spaces(page.select_one(".govuk-radios .usa-hint").text) == "(Default)" + ) + assert not page.select(".govuk-radios input")[1].has_attr("checked") def test_default_sms_sender_is_checked_and_has_hint( @@ -93,14 +95,14 @@ def test_default_sms_sender_is_checked_and_has_hint( multiple_sms_senders_with_diff_default, ): page = client_request.get( - '.set_sender', - service_id=SERVICE_ONE_ID, - template_id=fake_uuid + ".set_sender", service_id=SERVICE_ONE_ID, template_id=fake_uuid ) - assert page.select('.govuk-radios input')[0].has_attr('checked') - assert normalize_spaces(page.select_one('.govuk-radios .usa-hint').text) == "(Default)" - assert not page.select('.govuk-radios input')[1].has_attr('checked') + assert page.select(".govuk-radios input")[0].has_attr("checked") + assert ( + normalize_spaces(page.select_one(".govuk-radios .usa-hint").text) == "(Default)" + ) + assert not page.select(".govuk-radios input")[1].has_attr("checked") def test_default_sms_sender_is_checked_and_has_hint_when_there_are_no_inbound_numbers( @@ -110,14 +112,14 @@ def test_default_sms_sender_is_checked_and_has_hint_when_there_are_no_inbound_nu multiple_sms_senders_no_inbound, ): page = client_request.get( - '.set_sender', - service_id=SERVICE_ONE_ID, - template_id=fake_uuid + ".set_sender", service_id=SERVICE_ONE_ID, template_id=fake_uuid ) - assert page.select('.govuk-radios input')[0].has_attr('checked') - assert normalize_spaces(page.select_one('.govuk-radios .usa-hint').text) == "(Default)" - assert not page.select('.govuk-radios input')[1].has_attr('checked') + assert page.select(".govuk-radios input")[0].has_attr("checked") + assert ( + normalize_spaces(page.select_one(".govuk-radios .usa-hint").text) == "(Default)" + ) + assert not page.select(".govuk-radios input")[1].has_attr("checked") def test_default_inbound_sender_is_checked_and_has_hint_with_default_and_receives_text( @@ -125,19 +127,19 @@ def test_default_inbound_sender_is_checked_and_has_hint_with_default_and_receive service_one, fake_uuid, mock_get_service_template, - multiple_sms_senders + multiple_sms_senders, ): page = client_request.get( - '.set_sender', - service_id=service_one['id'], - template_id=fake_uuid + ".set_sender", service_id=service_one["id"], template_id=fake_uuid ) - assert page.select('.govuk-radios input')[0].has_attr('checked') - assert normalize_spaces( - page.select_one('.govuk-radios .usa-hint').text) == "(Default and receives replies)" - assert not page.select('.govuk-radios input')[1].has_attr('checked') - assert not page.select('.govuk-radios input')[2].has_attr('checked') + assert page.select(".govuk-radios input")[0].has_attr("checked") + assert ( + normalize_spaces(page.select_one(".govuk-radios .usa-hint").text) + == "(Default and receives replies)" + ) + assert not page.select(".govuk-radios input")[1].has_attr("checked") + assert not page.select(".govuk-radios input")[2].has_attr("checked") def test_sms_sender_has_receives_replies_hint( @@ -145,56 +147,57 @@ def test_sms_sender_has_receives_replies_hint( service_one, fake_uuid, mock_get_service_template, - multiple_sms_senders + multiple_sms_senders, ): page = client_request.get( - '.set_sender', - service_id=service_one['id'], - template_id=fake_uuid + ".set_sender", service_id=service_one["id"], template_id=fake_uuid ) - assert page.select('.govuk-radios input')[0].has_attr('checked') - assert normalize_spaces( - page.select_one('.govuk-radios .usa-hint').text) == "(Default and receives replies)" - assert not page.select('.govuk-radios input')[1].has_attr('checked') - assert not page.select('.govuk-radios input')[2].has_attr('checked') - - -@pytest.mark.parametrize('template_type, sender_data', [ - ( - 'email', - create_multiple_email_reply_to_addresses(), - ), - ( - 'sms', - create_multiple_sms_senders() + assert page.select(".govuk-radios input")[0].has_attr("checked") + assert ( + normalize_spaces(page.select_one(".govuk-radios .usa-hint").text) + == "(Default and receives replies)" ) -]) + assert not page.select(".govuk-radios input")[1].has_attr("checked") + assert not page.select(".govuk-radios input")[2].has_attr("checked") + + +@pytest.mark.parametrize( + "template_type, sender_data", + [ + ( + "email", + create_multiple_email_reply_to_addresses(), + ), + ("sms", create_multiple_sms_senders()), + ], +) def test_sender_session_is_present_after_selected( - client_request, - service_one, - fake_uuid, - template_type, - sender_data, - mocker + client_request, service_one, fake_uuid, template_type, sender_data, mocker ): template_data = create_template(template_type=template_type) - mocker.patch('app.service_api_client.get_service_template', return_value={'data': template_data}) + mocker.patch( + "app.service_api_client.get_service_template", + return_value={"data": template_data}, + ) - if template_type == 'email': - mocker.patch('app.service_api_client.get_reply_to_email_addresses', return_value=sender_data) + if template_type == "email": + mocker.patch( + "app.service_api_client.get_reply_to_email_addresses", + return_value=sender_data, + ) else: - mocker.patch('app.service_api_client.get_sms_senders', return_value=sender_data) + mocker.patch("app.service_api_client.get_sms_senders", return_value=sender_data) client_request.post( - '.set_sender', - service_id=service_one['id'], + ".set_sender", + service_id=service_one["id"], template_id=fake_uuid, - _data={'sender': '1234'}, + _data={"sender": "1234"}, ) with client_request.session_transaction() as session: - assert session['sender_id'] == '1234' + assert session["sender_id"] == "1234" def test_set_sender_redirects_if_no_reply_to_email_addresses( @@ -204,15 +207,15 @@ def test_set_sender_redirects_if_no_reply_to_email_addresses( no_reply_to_email_addresses, ): client_request.get( - '.set_sender', + ".set_sender", service_id=SERVICE_ONE_ID, template_id=fake_uuid, _expected_status=302, _expected_url=url_for( - '.send_one_off', + ".send_one_off", service_id=SERVICE_ONE_ID, template_id=fake_uuid, - ) + ), ) @@ -223,15 +226,15 @@ def test_set_sender_redirects_if_no_sms_senders( no_sms_senders, ): client_request.get( - '.set_sender', + ".set_sender", service_id=SERVICE_ONE_ID, template_id=fake_uuid, _expected_status=302, _expected_url=url_for( - '.send_one_off', + ".send_one_off", service_id=SERVICE_ONE_ID, template_id=fake_uuid, - ) + ), ) @@ -242,19 +245,19 @@ def test_set_sender_redirects_if_one_email_sender( single_reply_to_email_address, ): client_request.get( - '.set_sender', + ".set_sender", service_id=SERVICE_ONE_ID, template_id=fake_uuid, _expected_status=302, _expected_url=url_for( - '.send_one_off', + ".send_one_off", service_id=SERVICE_ONE_ID, template_id=fake_uuid, - ) + ), ) with client_request.session_transaction() as session: - assert session['sender_id'] == '1234' + assert session["sender_id"] == "1234" def test_set_sender_redirects_if_one_sms_sender( @@ -264,19 +267,19 @@ def test_set_sender_redirects_if_one_sms_sender( single_sms_sender, ): client_request.get( - '.set_sender', + ".set_sender", service_id=SERVICE_ONE_ID, template_id=fake_uuid, _expected_status=302, _expected_url=url_for( - '.send_one_off', + ".send_one_off", service_id=SERVICE_ONE_ID, template_id=fake_uuid, - ) + ), ) with client_request.session_transaction() as session: - assert session['sender_id'] == '1234' + assert session["sender_id"] == "1234" def test_that_test_files_exist(): @@ -291,21 +294,24 @@ def test_should_not_allow_files_to_be_uploaded_without_the_correct_permission( fake_uuid, ): template_id = fake_uuid - service_one['permissions'] = [] + service_one["permissions"] = [] page = client_request.get( - '.send_messages', + ".send_messages", service_id=SERVICE_ONE_ID, template_id=template_id, _follow_redirects=True, _expected_status=403, ) - assert page.select('main p')[0].text.strip() == "Sending text messages has been disabled for your service." + assert ( + page.select("main p")[0].text.strip() + == "Sending text messages has been disabled for your service." + ) assert page.select(".usa-back-link")[0].text == "Back" - assert page.select(".usa-back-link")[0]['href'] == url_for( - '.view_template', - service_id=service_one['id'], + assert page.select(".usa-back-link")[0]["href"] == url_for( + ".view_template", + service_id=service_one["id"], template_id=template_id, ) @@ -315,26 +321,24 @@ def test_example_spreadsheet( mock_get_service_template_with_placeholders_same_as_recipient, fake_uuid, ): - page = client_request.get( - '.send_messages', - service_id=SERVICE_ONE_ID, - template_id=fake_uuid + ".send_messages", service_id=SERVICE_ONE_ID, template_id=fake_uuid ) - assert normalize_spaces( - page.select_one('tbody tr').text - ) == ( - '1 phone number name date' + assert normalize_spaces(page.select_one("tbody tr").text) == ( + "1 phone number name date" + ) + assert page.select_one("input[type=file]").has_attr("accept") + assert ( + page.select_one("input[type=file]")["accept"] + == ".csv,.xlsx,.xls,.ods,.xlsm,.tsv" ) - assert page.select_one('input[type=file]').has_attr('accept') - assert page.select_one('input[type=file]')['accept'] == '.csv,.xlsx,.xls,.ods,.xlsm,.tsv' @pytest.mark.parametrize( "filename, acceptable_file, expected_status", - list(zip(test_spreadsheet_files, repeat(True), repeat(302))) + - list(zip(test_non_spreadsheet_files, repeat(False), repeat(200))) + list(zip(test_spreadsheet_files, repeat(True), repeat(302))) + + list(zip(test_non_spreadsheet_files, repeat(False), repeat(200))), ) def test_upload_files_in_different_formats( filename, @@ -348,32 +352,30 @@ def test_upload_files_in_different_formats( mock_s3_upload, fake_uuid, ): - with open(filename, 'rb') as uploaded: + with open(filename, "rb") as uploaded: page = client_request.post( - 'main.send_messages', - service_id=service_one['id'], + "main.send_messages", + service_id=service_one["id"], template_id=fake_uuid, - _data={'file': (BytesIO(uploaded.read()), filename)}, - _content_type='multipart/form-data', + _data={"file": (BytesIO(uploaded.read()), filename)}, + _content_type="multipart/form-data", _expected_status=expected_status, ) if acceptable_file: - assert mock_s3_upload.call_args[0][1]['data'].strip() == ( + assert mock_s3_upload.call_args[0][1]["data"].strip() == ( "phone number,name,favourite colour,fruit\r\n" "202 946 8050,Pete,Coral,tomato\r\n" "202 712 5974,Not Pete,Magenta,Avacado\r\n" "202 205 8823,Still Not Pete,Crimson,Pear" ) mock_s3_set_metadata.assert_called_once_with( - SERVICE_ONE_ID, - fake_uuid, - original_file_name=filename + SERVICE_ONE_ID, fake_uuid, original_file_name=filename ) else: assert not mock_s3_upload.called - assert normalize_spaces(page.select_one('.banner-dangerous').text) == ( - 'Could not read {}. Try using a different file format.'.format(filename) + assert normalize_spaces(page.select_one(".banner-dangerous").text) == ( + "Could not read {}. Try using a different file format.".format(filename) ) @@ -392,48 +394,68 @@ def test_send_messages_sanitises_and_truncates_file_name_for_metadata( filename = f"😁{'a' * 2000}.csv" client_request.post( - 'main.send_messages', - service_id=service_one['id'], + "main.send_messages", + service_id=service_one["id"], template_id=fake_uuid, - _data={'file': (BytesIO(''.encode('utf-8')), filename)}, - _content_type='multipart/form-data', + _data={"file": (BytesIO("".encode("utf-8")), filename)}, + _content_type="multipart/form-data", _follow_redirects=False, ) - assert len( - mock_s3_set_metadata.call_args_list[0][1]['original_file_name'] - ) < len(filename) + assert len(mock_s3_set_metadata.call_args_list[0][1]["original_file_name"]) < len( + filename + ) - assert mock_s3_set_metadata.call_args_list[0][1]['original_file_name'].startswith('?') + assert mock_s3_set_metadata.call_args_list[0][1]["original_file_name"].startswith( + "?" + ) -@pytest.mark.parametrize('exception, expected_error_message', [ - (partial(UnicodeDecodeError, 'codec', b'', 1, 2, 'reason'), ( - 'Could not read example.xlsx. Try using a different file format.' - )), - (BadZipFile, ( - 'Could not read example.xlsx. Try using a different file format.' - )), - (XLRDError, ( - 'Could not read example.xlsx. Try using a different file format.' - )), - (XLDateError, ( - 'example.xlsx contains numbers or dates that Notify cannot understand. ' - 'Try formatting all columns as ‘text’ or export your file as CSV.' - )), - (XLDateNegative, ( - 'example.xlsx contains numbers or dates that Notify cannot understand. ' - 'Try formatting all columns as ‘text’ or export your file as CSV.' - )), - (XLDateAmbiguous, ( - 'example.xlsx contains numbers or dates that Notify cannot understand. ' - 'Try formatting all columns as ‘text’ or export your file as CSV.' - )), - (XLDateTooLarge, ( - 'example.xlsx contains numbers or dates that Notify cannot understand. ' - 'Try formatting all columns as ‘text’ or export your file as CSV.' - )), -]) +@pytest.mark.parametrize( + "exception, expected_error_message", + [ + ( + partial(UnicodeDecodeError, "codec", b"", 1, 2, "reason"), + ("Could not read example.xlsx. Try using a different file format."), + ), + ( + BadZipFile, + ("Could not read example.xlsx. Try using a different file format."), + ), + ( + XLRDError, + ("Could not read example.xlsx. Try using a different file format."), + ), + ( + XLDateError, + ( + "example.xlsx contains numbers or dates that Notify cannot understand. " + "Try formatting all columns as ‘text’ or export your file as CSV." + ), + ), + ( + XLDateNegative, + ( + "example.xlsx contains numbers or dates that Notify cannot understand. " + "Try formatting all columns as ‘text’ or export your file as CSV." + ), + ), + ( + XLDateAmbiguous, + ( + "example.xlsx contains numbers or dates that Notify cannot understand. " + "Try formatting all columns as ‘text’ or export your file as CSV." + ), + ), + ( + XLDateTooLarge, + ( + "example.xlsx contains numbers or dates that Notify cannot understand. " + "Try formatting all columns as ‘text’ or export your file as CSV." + ), + ), + ], +) def test_shows_error_if_parsing_exception( client_request, mocker, @@ -442,25 +464,24 @@ def test_shows_error_if_parsing_exception( expected_error_message, fake_uuid, ): - def _raise_exception_or_partial_exception(file_content, filename): raise exception() mocker.patch( - 'app.main.views.send.Spreadsheet.from_file', - side_effect=_raise_exception_or_partial_exception + "app.main.views.send.Spreadsheet.from_file", + side_effect=_raise_exception_or_partial_exception, ) page = client_request.post( - 'main.send_messages', + "main.send_messages", service_id=SERVICE_ONE_ID, template_id=fake_uuid, - _data={'file': (BytesIO(b'example'), 'example.xlsx')}, - _content_type='multipart/form-data', + _data={"file": (BytesIO(b"example"), "example.xlsx")}, + _content_type="multipart/form-data", _expected_status=200, ) - assert normalize_spaces(page.select_one('.banner-dangerous').text) == ( + assert normalize_spaces(page.select_one(".banner-dangerous").text) == ( expected_error_message ) @@ -479,35 +500,37 @@ def test_upload_csv_file_with_errors_shows_check_page_with_errors( mock_get_jobs, fake_uuid, ): - mocker.patch( - 'app.main.views.send.s3download', + "app.main.views.send.s3download", return_value=""" phone number,name +12028675109 +12028675109 - """ + """, ) page = client_request.post( - 'main.send_messages', - service_id=service_one['id'], + "main.send_messages", + service_id=service_one["id"], template_id=fake_uuid, - _data={'file': (BytesIO(''.encode('utf-8')), 'example.csv')}, - _content_type='multipart/form-data', + _data={"file": (BytesIO("".encode("utf-8")), "example.csv")}, + _content_type="multipart/form-data", _follow_redirects=True, ) with client_request.session_transaction() as session: - assert 'file_uploads' not in session + assert "file_uploads" not in session - assert page.select_one('input[type=file]').has_attr('accept') - assert page.select_one('input[type=file]')['accept'] == '.csv,.xlsx,.xls,.ods,.xlsm,.tsv' + assert page.select_one("input[type=file]").has_attr("accept") + assert ( + page.select_one("input[type=file]")["accept"] + == ".csv,.xlsx,.xls,.ods,.xlsm,.tsv" + ) - assert 'There’s a problem with example.csv' in page.text - assert '+12028675109' in page.text - assert 'Missing' in page.text - assert 'Upload your file again' in page.text + assert "There’s a problem with example.csv" in page.text + assert "+12028675109" in page.text + assert "Missing" in page.text + assert "Upload your file again" in page.text def test_upload_csv_file_with_empty_message_shows_check_page_with_errors( @@ -524,47 +547,42 @@ def test_upload_csv_file_with_empty_message_shows_check_page_with_errors( mock_get_jobs, fake_uuid, ): - mocker.patch( - 'app.main.views.send.s3download', + "app.main.views.send.s3download", return_value=""" phone number, show_placeholder +12028675109, yes +12028675109, no - """ + """, ) page = client_request.post( - 'main.send_messages', - service_id=service_one['id'], + "main.send_messages", + service_id=service_one["id"], template_id=fake_uuid, - _data={'file': (BytesIO(''.encode('utf-8')), 'example.csv')}, - _content_type='multipart/form-data', + _data={"file": (BytesIO("".encode("utf-8")), "example.csv")}, + _content_type="multipart/form-data", _follow_redirects=True, ) with client_request.session_transaction() as session: - assert 'file_uploads' not in session + assert "file_uploads" not in session - assert normalize_spaces( - page.select_one('.banner-dangerous').text - ) == ( - 'There’s a problem with example.csv ' - 'You need to check you have content for the empty message in 1 row.' + assert normalize_spaces(page.select_one(".banner-dangerous").text) == ( + "There’s a problem with example.csv " + "You need to check you have content for the empty message in 1 row." ) - assert [ - normalize_spaces(row.text) for row in page.select('tbody tr') - ] == [ - '3 No content for this message', - '+12028675109 no', + assert [normalize_spaces(row.text) for row in page.select("tbody tr")] == [ + "3 No content for this message", + "+12028675109 no", ] - assert normalize_spaces(page.select_one('.table-field-index').text) == '3' - assert page.select_one('.table-field-index')['rowspan'] == '2' - assert normalize_spaces(page.select('tbody tr td')[0].text) == '3' - assert normalize_spaces(page.select('tbody tr td')[1].text) == ( - 'No content for this message' + assert normalize_spaces(page.select_one(".table-field-index").text) == "3" + assert page.select_one(".table-field-index")["rowspan"] == "2" + assert normalize_spaces(page.select("tbody tr td")[0].text) == "3" + assert normalize_spaces(page.select("tbody tr td")[1].text) == ( + "No content for this message" ) - assert page.select('tbody tr td')[1]['colspan'] == '2' + assert page.select("tbody tr td")[1]["colspan"] == "2" def test_upload_csv_file_with_very_long_placeholder_shows_check_page_with_errors( @@ -581,132 +599,128 @@ def test_upload_csv_file_with_very_long_placeholder_shows_check_page_with_errors mock_get_jobs, fake_uuid, ): - big_placeholder = ' '.join(['not ok'] * 402) + big_placeholder = " ".join(["not ok"] * 402) mocker.patch( - 'app.main.views.send.s3download', + "app.main.views.send.s3download", return_value=f""" phone number, name +12028675109, {big_placeholder} +12027700900, {big_placeholder} - """ + """, ) page = client_request.post( - 'main.send_messages', - service_id=service_one['id'], + "main.send_messages", + service_id=service_one["id"], template_id=fake_uuid, - _data={'file': (BytesIO(''.encode('utf-8')), 'example.csv')}, - _content_type='multipart/form-data', - _follow_redirects=True + _data={"file": (BytesIO("".encode("utf-8")), "example.csv")}, + _content_type="multipart/form-data", + _follow_redirects=True, ) with client_request.session_transaction() as session: - assert 'file_uploads' not in session + assert "file_uploads" not in session - assert normalize_spaces( - page.select_one('.banner-dangerous').text - ) == ( - 'There’s a problem with example.csv ' - 'You need to shorten the messages in 2 rows.' + assert normalize_spaces(page.select_one(".banner-dangerous").text) == ( + "There’s a problem with example.csv " + "You need to shorten the messages in 2 rows." ) - assert [ - normalize_spaces(row.text) for row in page.select('tbody tr') - ] == [ - '2 Message is too long', - f'+12028675109 {big_placeholder}', - '3 Message is too long', - f'+12027700900 {big_placeholder}', + assert [normalize_spaces(row.text) for row in page.select("tbody tr")] == [ + "2 Message is too long", + f"+12028675109 {big_placeholder}", + "3 Message is too long", + f"+12027700900 {big_placeholder}", ] - assert normalize_spaces(page.select_one('.table-field-index').text) == '2' - assert page.select_one('.table-field-index')['rowspan'] == '2' - assert normalize_spaces(page.select('tbody tr td')[0].text) == '2' - assert normalize_spaces(page.select('tbody tr td')[1].text) == ( - 'Message is too long' + assert normalize_spaces(page.select_one(".table-field-index").text) == "2" + assert page.select_one(".table-field-index")["rowspan"] == "2" + assert normalize_spaces(page.select("tbody tr td")[0].text) == "2" + assert normalize_spaces(page.select("tbody tr td")[1].text) == ( + "Message is too long" ) - assert page.select('tbody tr td')[1]['colspan'] == '2' + assert page.select("tbody tr td")[1]["colspan"] == "2" -@pytest.mark.parametrize('file_contents, expected_error,', [ - ( - """ +@pytest.mark.parametrize( + "file_contents, expected_error,", + [ + ( + """ telephone,name +12028675109 """, + ( + "There’s a problem with your column names " + "Your file needs a column called ‘phone number’. " + "Right now it has columns called ‘telephone’ and ‘name’." + ), + ), ( - 'There’s a problem with your column names ' - 'Your file needs a column called ‘phone number’. ' - 'Right now it has columns called ‘telephone’ and ‘name’.' - ) - ), - ( - """ + """ phone number +12028675109 """, + ( + "Your column names need to match the double brackets in your template " + "Your file is missing a column called ‘name’." + ), + ), ( - 'Your column names need to match the double brackets in your template ' - 'Your file is missing a column called ‘name’.' - ) - ), - ( - """ + """ phone number, phone number, PHONE_NUMBER +12027900111,+12027900222,+12027900333, """, + ( + "There’s a problem with your column names " + "We found more than one column called ‘phone number’ or ‘PHONE_NUMBER’. " + "Delete or rename one of these columns and try again." + ), + ), ( - 'There’s a problem with your column names ' - 'We found more than one column called ‘phone number’ or ‘PHONE_NUMBER’. ' - 'Delete or rename one of these columns and try again.' - ) - ), - ( - """ + """ phone number, name """, + ("Your file is missing some rows " "It needs at least one row of data."), + ), ( - 'Your file is missing some rows ' - 'It needs at least one row of data.' - ) - ), - ( - "+12028675109", + "+12028675109", + ( + "Your file is missing some rows " + "It needs at least one row of data, and columns called ‘name’ and ‘phone number’." + ), + ), ( - 'Your file is missing some rows ' - 'It needs at least one row of data, and columns called ‘name’ and ‘phone number’.' - ) - ), - ( - "", + "", + ( + "Your file is missing some rows " + "It needs at least one row of data, and columns called ‘name’ and ‘phone number’." + ), + ), ( - 'Your file is missing some rows ' - 'It needs at least one row of data, and columns called ‘name’ and ‘phone number’.' - ) - ), - ( - """ + """ phone number, name +12028675109, example , example +12028675109, example """, + ( + "There’s a problem with example.csv " + "You need to enter missing data in 1 row." + ), + ), ( - 'There’s a problem with example.csv ' - 'You need to enter missing data in 1 row.' - ) - ), - ( - """ + """ phone number, name +12028675109, example +12028675109, +12028675109, example """, - ( - 'There’s a problem with example.csv ' - 'You need to enter missing data in 1 row.' - ) - ), -]) + ( + "There’s a problem with example.csv " + "You need to enter missing data in 1 row." + ), + ), + ], +) def test_upload_csv_file_with_missing_columns_shows_error( client_request, mocker, @@ -723,21 +737,25 @@ def test_upload_csv_file_with_missing_columns_shows_error( file_contents, expected_error, ): - - mocker.patch('app.main.views.send.s3download', return_value=file_contents) + mocker.patch("app.main.views.send.s3download", return_value=file_contents) page = client_request.post( - 'main.send_messages', service_id=service_one['id'], template_id=fake_uuid, - _data={'file': (BytesIO(''.encode('utf-8')), 'example.csv')}, + "main.send_messages", + service_id=service_one["id"], + template_id=fake_uuid, + _data={"file": (BytesIO("".encode("utf-8")), "example.csv")}, _follow_redirects=True, ) with client_request.session_transaction() as session: - assert 'file_uploads' not in session + assert "file_uploads" not in session - assert page.select_one('input[type=file]').has_attr('accept') - assert page.select_one('input[type=file]')['accept'] == '.csv,.xlsx,.xls,.ods,.xlsm,.tsv' - assert normalize_spaces(page.select('.banner-dangerous')[0].text) == expected_error + assert page.select_one("input[type=file]").has_attr("accept") + assert ( + page.select_one("input[type=file]")["accept"] + == ".csv,.xlsx,.xls,.ods,.xlsm,.tsv" + ) + assert normalize_spaces(page.select(".banner-dangerous")[0].text) == expected_error def test_upload_csv_invalid_extension( @@ -747,13 +765,12 @@ def test_upload_csv_invalid_extension( mock_get_service_template, fake_uuid, ): - page = client_request.post( - 'main.send_messages', - service_id=service_one['id'], + "main.send_messages", + service_id=service_one["id"], template_id=fake_uuid, - _data={'file': (BytesIO('contents'.encode('utf-8')), 'invalid.txt')}, - _content_type='multipart/form-data', + _data={"file": (BytesIO("contents".encode("utf-8")), "invalid.txt")}, + _content_type="multipart/form-data", _follow_redirects=True, ) @@ -767,13 +784,12 @@ def test_upload_csv_size_too_big( mock_get_service_template, fake_uuid, ): - page = client_request.post( - 'main.send_messages', - service_id=service_one['id'], + "main.send_messages", + service_id=service_one["id"], template_id=fake_uuid, - _data={'file': (BytesIO(randbytes(11_000_000)), 'invalid.csv')}, - _content_type='multipart/form-data', + _data={"file": (BytesIO(randbytes(11_000_000)), "invalid.csv")}, + _content_type="multipart/form-data", _follow_redirects=True, ) @@ -788,11 +804,13 @@ def test_upload_valid_csv_redirects_to_check_page( fake_uuid, ): client_request.post( - 'main.send_messages', service_id=SERVICE_ONE_ID, template_id=fake_uuid, - _data={'file': (BytesIO(''.encode('utf-8')), 'valid.csv')}, + "main.send_messages", + service_id=SERVICE_ONE_ID, + template_id=fake_uuid, + _data={"file": (BytesIO("".encode("utf-8")), "valid.csv")}, _expected_status=302, _expected_redirect=url_for( - 'main.check_messages', + "main.check_messages", service_id=SERVICE_ONE_ID, template_id=fake_uuid, upload_id=fake_uuid, @@ -800,26 +818,29 @@ def test_upload_valid_csv_redirects_to_check_page( ) -@pytest.mark.parametrize('extra_args, expected_link_in_first_row, expected_recipient, expected_message', [ - ( - {}, - None, - 'To: 2028675301', - 'Test Service: A, Template content with & entity', - ), - ( - {'row_index': 2}, - None, - 'To: 2028675301', - 'Test Service: A, Template content with & entity', - ), - ( - {'row_index': 4}, - True, - 'To: 2028675303', - 'Test Service: C, Template content with & entity', - ), -]) +@pytest.mark.parametrize( + "extra_args, expected_link_in_first_row, expected_recipient, expected_message", + [ + ( + {}, + None, + "To: 2028675301", + "Test Service: A, Template content with & entity", + ), + ( + {"row_index": 2}, + None, + "To: 2028675301", + "Test Service: A, Template content with & entity", + ), + ( + {"row_index": 4}, + True, + "To: 2028675303", + "Test Service: C, Template content with & entity", + ), + ], +) def test_upload_valid_csv_shows_preview_and_table( client_request, mocker, @@ -837,25 +858,25 @@ def test_upload_valid_csv_shows_preview_and_table( expected_recipient, expected_message, ): - with client_request.session_transaction() as session: - session['file_uploads'] = { - fake_uuid: {'template_id': fake_uuid} - } + session["file_uploads"] = {fake_uuid: {"template_id": fake_uuid}} - mocker.patch('app.main.views.send.s3download', return_value=""" + mocker.patch( + "app.main.views.send.s3download", + return_value=""" phone number,name,thing,thing,thing 2028675301, A, foo, foo, foo 2028675302, B, foo, foo, foo 2028675303, C, foo, foo, - """) + """, + ) page = client_request.get( - 'main.check_messages', + "main.check_messages", service_id=SERVICE_ONE_ID, template_id=fake_uuid, upload_id=fake_uuid, - **extra_args + **extra_args, ) mock_s3_set_metadata.assert_called_once_with( @@ -864,72 +885,74 @@ def test_upload_valid_csv_shows_preview_and_table( notification_count=3, template_id=fake_uuid, valid=True, - original_file_name='example.csv', + original_file_name="example.csv", ) - assert page.h1.text.strip() == 'Preview of Two week reminder' - assert page.select_one('.sms-message-recipient').text.strip() == expected_recipient - assert page.select_one('.sms-message-wrapper').text.strip() == expected_message + assert page.h1.text.strip() == "Preview of Two week reminder" + assert page.select_one(".sms-message-recipient").text.strip() == expected_recipient + assert page.select_one(".sms-message-wrapper").text.strip() == expected_message - assert page.select_one('.table-field-index').text.strip() == '2' + assert page.select_one(".table-field-index").text.strip() == "2" if expected_link_in_first_row: - assert page.select_one('.table-field-index a')['href'] == url_for( - 'main.check_messages', + assert page.select_one(".table-field-index a")["href"] == url_for( + "main.check_messages", service_id=SERVICE_ONE_ID, template_id=fake_uuid, upload_id=fake_uuid, row_index=2, - original_file_name='example.csv', + original_file_name="example.csv", ) else: - assert not page.select_one('.table-field-index').select_one('a') + assert not page.select_one(".table-field-index").select_one("a") - for row_index, row in enumerate([ - ( - '
2028675301
', - '
A
', + for row_index, row in enumerate( + [ ( - ' ' - '
' - '
    ' - '
  • foo
  • foo
  • foo
  • ' - '
' - '
' - '' - ) - ), - ( - '
2028675302
', - '
B
', + '
2028675301
', + '
A
', + ( + ' ' + '
' + "
    " + "
  • foo
  • foo
  • foo
  • " + "
" + "
" + "" + ), + ), ( - ' ' - '
' - '
    ' - '
  • foo
  • foo
  • foo
  • ' - '
' - '
' - '' - ) - ), - ( - '
2028675303
', - '
C
', + '
2028675302
', + '
B
', + ( + ' ' + '
' + "
    " + "
  • foo
  • foo
  • foo
  • " + "
" + "
" + "" + ), + ), ( - ' ' - '
' - '
    ' - '
  • foo
  • foo
  • ' - '
' - '
' - '' - ) - ), - ]): + '
2028675303
', + '
C
', + ( + ' ' + '
' + "
    " + "
  • foo
  • foo
  • " + "
" + "
" + "" + ), + ), + ] + ): for index, cell in enumerate(row): - row = page.select('table tbody tr')[row_index] - assert 'id' not in row - assert normalize_spaces(str(row.select('td')[index + 1])) == cell + row = page.select("table tbody tr")[row_index] + assert "id" not in row + assert normalize_spaces(str(row.select("td")[index + 1])) == cell def test_show_all_columns_if_there_are_duplicate_recipient_columns( @@ -944,41 +967,44 @@ def test_show_all_columns_if_there_are_duplicate_recipient_columns( fake_uuid, mock_s3_get_metadata, ): - with client_request.session_transaction() as session: - session['file_uploads'] = { - fake_uuid: {'template_id': fake_uuid} - } + session["file_uploads"] = {fake_uuid: {"template_id": fake_uuid}} - mocker.patch('app.main.views.send.s3download', return_value=""" + mocker.patch( + "app.main.views.send.s3download", + return_value=""" phone number, phone_number, PHONENUMBER 2028675301, 2028675302, 2028675303 - """) + """, + ) page = client_request.get( - 'main.check_messages', + "main.check_messages", service_id=SERVICE_ONE_ID, template_id=fake_uuid, upload_id=fake_uuid, _test_page_title=False, ) - assert normalize_spaces(page.select_one('thead').text) == ( - 'Row in file1 phone number phone_number PHONENUMBER' + assert normalize_spaces(page.select_one("thead").text) == ( + "Row in file1 phone number phone_number PHONENUMBER" ) - assert normalize_spaces(page.select_one('tbody').text) == ( - '2 2028675303 2028675303 2028675303' + assert normalize_spaces(page.select_one("tbody").text) == ( + "2 2028675303 2028675303 2028675303" ) -@pytest.mark.parametrize('row_index, expected_status', [ - (0, 404), - (1, 404), - (2, 200), - (3, 200), - (4, 200), - (5, 404), -]) +@pytest.mark.parametrize( + "row_index, expected_status", + [ + (0, 404), + (1, 404), + (2, 200), + (3, 200), + (4, 200), + (5, 404), + ], +) def test_404_for_previewing_a_row_out_of_range( client_request, mocker, @@ -994,21 +1020,21 @@ def test_404_for_previewing_a_row_out_of_range( row_index, expected_status, ): - with client_request.session_transaction() as session: - session['file_uploads'] = { - fake_uuid: {'template_id': fake_uuid} - } + session["file_uploads"] = {fake_uuid: {"template_id": fake_uuid}} - mocker.patch('app.main.views.send.s3download', return_value=""" + mocker.patch( + "app.main.views.send.s3download", + return_value=""" phone number,name,thing,thing,thing 2028675301, A, foo, foo, foo 2028675302, B, foo, foo, foo 2028675303, C, foo, foo, foo - """) + """, + ) client_request.get( - 'main.check_messages', + "main.check_messages", service_id=SERVICE_ONE_ID, template_id=fake_uuid, upload_id=fake_uuid, @@ -1017,7 +1043,7 @@ def test_404_for_previewing_a_row_out_of_range( ) -@pytest.mark.parametrize('template_type', ['sms', 'email']) +@pytest.mark.parametrize("template_type", ["sms", "email"]) def test_send_one_off_step_redirects_to_start_if_session_not_setup( mocker, client_request, @@ -1027,24 +1053,27 @@ def test_send_one_off_step_redirects_to_start_if_session_not_setup( fake_uuid, template_type, ): - template_data = create_template(template_type=template_type, content='Hi ((name))') - mocker.patch('app.service_api_client.get_service_template', return_value={'data': template_data}) + template_data = create_template(template_type=template_type, content="Hi ((name))") + mocker.patch( + "app.service_api_client.get_service_template", + return_value={"data": template_data}, + ) with client_request.session_transaction() as session: - assert 'recipient' not in session - assert 'placeholders' not in session + assert "recipient" not in session + assert "placeholders" not in session client_request.get( - 'main.send_one_off_step', + "main.send_one_off_step", service_id=SERVICE_ONE_ID, template_id=fake_uuid, step_index=0, _expected_status=302, _expected_redirect=url_for( - 'main.send_one_off', + "main.send_one_off", service_id=SERVICE_ONE_ID, template_id=fake_uuid, - ) + ), ) @@ -1055,29 +1084,35 @@ def test_send_one_off_does_not_send_without_the_correct_permissions( fake_uuid, ): template_id = fake_uuid - service_one['permissions'] = [] + service_one["permissions"] = [] page = client_request.get( - '.send_one_off', + ".send_one_off", service_id=SERVICE_ONE_ID, template_id=template_id, _follow_redirects=True, _expected_status=403, ) - assert page.select('main p')[0].text.strip() == "Sending text messages has been disabled for your service." + assert ( + page.select("main p")[0].text.strip() + == "Sending text messages has been disabled for your service." + ) assert page.select(".usa-back-link")[0].text == "Back" - assert page.select(".usa-back-link")[0]['href'] == url_for( - '.view_template', - service_id=service_one['id'], + assert page.select(".usa-back-link")[0]["href"] == url_for( + ".view_template", + service_id=service_one["id"], template_id=template_id, ) -@pytest.mark.parametrize('user', ( - create_active_user_with_permissions(), - create_active_caseworking_user(), -)) +@pytest.mark.parametrize( + "user", + ( + create_active_user_with_permissions(), + create_active_caseworking_user(), + ), +) def test_send_one_off_has_correct_page_title( client_request, service_one, @@ -1087,38 +1122,46 @@ def test_send_one_off_has_correct_page_title( user, ): client_request.login(user) - template_data = create_template(template_type='sms', name='Two week reminder', content='Hi there ((name))') - mocker.patch('app.service_api_client.get_service_template', return_value={'data': template_data}) + template_data = create_template( + template_type="sms", name="Two week reminder", content="Hi there ((name))" + ) + mocker.patch( + "app.service_api_client.get_service_template", + return_value={"data": template_data}, + ) page = client_request.get( - 'main.send_one_off', - service_id=service_one['id'], + "main.send_one_off", + service_id=service_one["id"], template_id=fake_uuid, step_index=0, _follow_redirects=True, ) assert page.h1.text.strip() == "Send ‘Two week reminder’" - assert len(page.select('.banner-tour')) == 0 + assert len(page.select(".banner-tour")) == 0 -@pytest.mark.parametrize('step_index, prefilled, expected_field_label', [ - ( - 0, - {}, - 'phone number', - ), - ( - 1, - {'phone number': '2020900123'}, - 'one', - ), - ( - 2, - {'phone number': '2020900123', 'one': 'one'}, - 'two', - ), -]) +@pytest.mark.parametrize( + "step_index, prefilled, expected_field_label", + [ + ( + 0, + {}, + "phone number", + ), + ( + 1, + {"phone number": "2020900123"}, + "one", + ), + ( + 2, + {"phone number": "2020900123", "one": "one"}, + "two", + ), + ], +) def test_send_one_off_shows_placeholders_in_correct_order( client_request, fake_uuid, @@ -1129,38 +1172,37 @@ def test_send_one_off_shows_placeholders_in_correct_order( expected_field_label, ): with client_request.session_transaction() as session: - session['recipient'] = None - session['placeholders'] = prefilled + session["recipient"] = None + session["placeholders"] = prefilled page = client_request.get( - 'main.send_one_off_step', + "main.send_one_off_step", service_id=SERVICE_ONE_ID, template_id=fake_uuid, step_index=step_index, ) - assert normalize_spaces(page.select_one('label').text) == expected_field_label + assert normalize_spaces(page.select_one("label").text) == expected_field_label -@pytest.mark.parametrize('user, template_type, expected_link_text, expected_link_url', [ - ( - create_active_user_with_permissions(), - 'sms', - 'Use my phone number', - partial(url_for, 'main.send_one_off_to_myself') - ), - ( - create_active_user_with_permissions(), - 'email', - 'Use my email address', - partial(url_for, 'main.send_one_off_to_myself') - ), - ( - create_active_caseworking_user(), - 'sms', - None, None - ), -]) +@pytest.mark.parametrize( + "user, template_type, expected_link_text, expected_link_url", + [ + ( + create_active_user_with_permissions(), + "sms", + "Use my phone number", + partial(url_for, "main.send_one_off_to_myself"), + ), + ( + create_active_user_with_permissions(), + "email", + "Use my email address", + partial(url_for, "main.send_one_off_to_myself"), + ), + (create_active_caseworking_user(), "sms", None, None), + ], +) def test_send_one_off_has_skip_link( client_request, service_one, @@ -1175,22 +1217,25 @@ def test_send_one_off_has_skip_link( ): client_request.login(user) template_data = create_template(template_id=fake_uuid, template_type=template_type) - mocker.patch('app.service_api_client.get_service_template', return_value={'data': template_data}) + mocker.patch( + "app.service_api_client.get_service_template", + return_value={"data": template_data}, + ) page = client_request.get( - 'main.send_one_off_step', + "main.send_one_off_step", service_id=SERVICE_ONE_ID, template_id=fake_uuid, step_index=0, _follow_redirects=True, ) - skip_links = page.select('form a') + skip_links = page.select("form a") if expected_link_text and expected_link_url: assert skip_links[1].text.strip() == expected_link_text - assert skip_links[1]['href'] == expected_link_url( - service_id=service_one['id'], + assert skip_links[1]["href"] == expected_link_url( + service_id=service_one["id"], template_id=fake_uuid, ) else: @@ -1198,10 +1243,13 @@ def test_send_one_off_has_skip_link( skip_links[1] -@pytest.mark.parametrize('template_type, expected_sticky', [ - ('sms', False), - ('email', True), -]) +@pytest.mark.parametrize( + "template_type, expected_sticky", + [ + ("sms", False), + ("email", True), + ], +) def test_send_one_off_has_sticky_header_for_email( mocker, client_request, @@ -1210,24 +1258,30 @@ def test_send_one_off_has_sticky_header_for_email( template_type, expected_sticky, ): - template_data = create_template(template_type=template_type, content='((body))') - mocker.patch('app.service_api_client.get_service_template', return_value={'data': template_data}) + template_data = create_template(template_type=template_type, content="((body))") + mocker.patch( + "app.service_api_client.get_service_template", + return_value={"data": template_data}, + ) page = client_request.get( - 'main.send_one_off_step', + "main.send_one_off_step", service_id=SERVICE_ONE_ID, template_id=fake_uuid, step_index=0, _follow_redirects=True, ) - assert bool(page.select('.js-stick-at-top-when-scrolling')) == expected_sticky + assert bool(page.select(".js-stick-at-top-when-scrolling")) == expected_sticky -@pytest.mark.parametrize('user', ( - create_active_user_with_permissions(), - create_active_caseworking_user(), -)) +@pytest.mark.parametrize( + "user", + ( + create_active_user_with_permissions(), + create_active_caseworking_user(), + ), +) def test_skip_link_will_not_show_on_sms_one_off_if_service_has_no_mobile_number( client_request, service_one, @@ -1237,27 +1291,30 @@ def test_skip_link_will_not_show_on_sms_one_off_if_service_has_no_mobile_number( mocker, user, ): - user['mobile_number'] = None + user["mobile_number"] = None client_request.login(user) with client_request.session_transaction() as session: - session['recipient'] = None - session['placeholders'] = {} + session["recipient"] = None + session["placeholders"] = {} page = client_request.get( - 'main.send_one_off_step', + "main.send_one_off_step", service_id=SERVICE_ONE_ID, template_id=fake_uuid, step_index=0, ) - skip_link = page.findAll('a', text='Use my phone number') + skip_link = page.findAll("a", text="Use my phone number") assert not skip_link -@pytest.mark.parametrize('user', ( - create_active_user_with_permissions(), - create_active_caseworking_user(), -)) +@pytest.mark.parametrize( + "user", + ( + create_active_user_with_permissions(), + create_active_caseworking_user(), + ), +) def test_send_one_off_offers_link_to_upload( client_request, fake_uuid, @@ -1268,19 +1325,19 @@ def test_send_one_off_offers_link_to_upload( client_request.login(user) page = client_request.get( - 'main.send_one_off', + "main.send_one_off", service_id=SERVICE_ONE_ID, template_id=fake_uuid, _follow_redirects=True, ) - back_link = page.select_one('.usa-back-link') - link = page.select_one('form a') + back_link = page.select_one(".usa-back-link") + link = page.select_one("form a") - assert back_link.text.strip() == 'Back' + assert back_link.text.strip() == "Back" - assert link.text.strip() == 'Upload a list of phone numbers' - assert link['href'] == url_for( - 'main.send_messages', + assert link.text.strip() == "Upload a list of phone numbers" + assert link["href"] == url_for( + "main.send_messages", service_id=SERVICE_ONE_ID, template_id=fake_uuid, ) @@ -1293,27 +1350,25 @@ def test_send_one_off_has_link_to_use_existing_list( fake_uuid, ): page = client_request.get( - 'main.send_one_off', + "main.send_one_off", service_id=SERVICE_ONE_ID, template_id=fake_uuid, _follow_redirects=True, ) - assert [ - (link.text, link['href']) for link in page.select('form a') - ] == [ + assert [(link.text, link["href"]) for link in page.select("form a")] == [ ( - 'Upload a list of phone numbers', + "Upload a list of phone numbers", url_for( - 'main.send_messages', + "main.send_messages", service_id=SERVICE_ONE_ID, template_id=fake_uuid, ), ), ( - 'Use my phone number', + "Use my phone number", url_for( - 'main.send_one_off_to_myself', + "main.send_one_off_to_myself", service_id=SERVICE_ONE_ID, template_id=fake_uuid, ), @@ -1321,42 +1376,48 @@ def test_send_one_off_has_link_to_use_existing_list( ] -@pytest.mark.parametrize('user', ( - create_active_user_with_permissions(), - create_active_caseworking_user(), -)) +@pytest.mark.parametrize( + "user", + ( + create_active_user_with_permissions(), + create_active_caseworking_user(), + ), +) def test_link_to_upload_not_offered_when_entering_personalisation( client_request, fake_uuid, mock_get_service_template_with_placeholders, mock_has_jobs, - user + user, ): client_request.login(user) with client_request.session_transaction() as session: - session['recipient'] = '2029009009' - session['placeholders'] = {'phone number': '2029009009'} + session["recipient"] = "2029009009" + session["placeholders"] = {"phone number": "2029009009"} page = client_request.get( - 'main.send_one_off_step', + "main.send_one_off_step", service_id=SERVICE_ONE_ID, template_id=fake_uuid, step_index=1, ) # We’re entering personalization - assert page.select_one('input[type=text]')['name'] == 'placeholder_value' - assert page.select_one('label[for=placeholder_value]').text.strip() == 'name' + assert page.select_one("input[type=text]")["name"] == "placeholder_value" + assert page.select_one("label[for=placeholder_value]").text.strip() == "name" # No ‘Upload’ link shown - assert len(page.select('main a')) == 0 - assert 'Upload' not in page.select_one('main').text + assert len(page.select("main a")) == 0 + assert "Upload" not in page.select_one("main").text -@pytest.mark.parametrize('user', ( - create_active_user_with_permissions(), - create_active_caseworking_user(), -)) +@pytest.mark.parametrize( + "user", + ( + create_active_user_with_permissions(), + create_active_caseworking_user(), + ), +) def test_send_one_off_redirects_to_end_if_step_out_of_bounds( client_request, mock_has_no_jobs, @@ -1368,27 +1429,30 @@ def test_send_one_off_redirects_to_end_if_step_out_of_bounds( client_request.login(user) with client_request.session_transaction() as session: - session['recipient'] = '2020900123' - session['placeholders'] = {'name': 'foo', 'phone number': '2020900123'} + session["recipient"] = "2020900123" + session["placeholders"] = {"name": "foo", "phone number": "2020900123"} client_request.get( - 'main.send_one_off_step', + "main.send_one_off_step", service_id=SERVICE_ONE_ID, template_id=fake_uuid, step_index=999, _expected_status=302, _expected_redirect=url_for( - 'main.check_notification', + "main.check_notification", service_id=SERVICE_ONE_ID, template_id=fake_uuid, - ) + ), ) -@pytest.mark.parametrize('user', ( - create_active_user_with_permissions(), - create_active_caseworking_user(), -)) +@pytest.mark.parametrize( + "user", + ( + create_active_user_with_permissions(), + create_active_caseworking_user(), + ), +) def test_send_one_off_redirects_to_start_if_you_skip_steps( client_request, service_one, @@ -1401,26 +1465,29 @@ def test_send_one_off_redirects_to_start_if_you_skip_steps( user, ): with client_request.session_transaction() as session: - session['placeholders'] = {'address_line_1': 'foo'} + session["placeholders"] = {"address_line_1": "foo"} client_request.login(user) client_request.get( - 'main.send_one_off_step', - service_id=service_one['id'], + "main.send_one_off_step", + service_id=service_one["id"], template_id=fake_uuid, step_index=7, _expected_redirect=url_for( - 'main.send_one_off', - service_id=service_one['id'], + "main.send_one_off", + service_id=service_one["id"], template_id=fake_uuid, - ) + ), ) -@pytest.mark.parametrize('user', ( - create_active_user_with_permissions(), - create_active_caseworking_user(), -)) +@pytest.mark.parametrize( + "user", + ( + create_active_user_with_permissions(), + create_active_caseworking_user(), + ), +) def test_send_one_off_redirects_to_start_if_index_out_of_bounds_and_some_placeholders_empty( client_request, service_one, @@ -1435,26 +1502,29 @@ def test_send_one_off_redirects_to_start_if_index_out_of_bounds_and_some_placeho ): client_request.login(user) with client_request.session_transaction() as session: - session['placeholders'] = {'name': 'foo'} + session["placeholders"] = {"name": "foo"} client_request.get( - 'main.send_one_off_step', + "main.send_one_off_step", service_id=SERVICE_ONE_ID, template_id=fake_uuid, step_index=999, _expected_status=302, _expected_redirect=url_for( - 'main.send_one_off', + "main.send_one_off", service_id=SERVICE_ONE_ID, template_id=fake_uuid, ), ) -@pytest.mark.parametrize('user', ( - create_active_user_with_permissions(), - create_active_caseworking_user(), -)) +@pytest.mark.parametrize( + "user", + ( + create_active_user_with_permissions(), + create_active_caseworking_user(), + ), +) def test_send_one_off_sms_message_redirects( client_request, mocker, @@ -1463,27 +1533,30 @@ def test_send_one_off_sms_message_redirects( user, ): client_request.login(user) - template = {'data': {'template_type': 'sms', 'folder': None}} - mocker.patch('app.service_api_client.get_service_template', return_value=template) + template = {"data": {"template_type": "sms", "folder": None}} + mocker.patch("app.service_api_client.get_service_template", return_value=template) client_request.get( - 'main.send_one_off', + "main.send_one_off", service_id=SERVICE_ONE_ID, template_id=fake_uuid, _expected_status=302, _expected_response=url_for( - 'main.send_one_off_step', + "main.send_one_off_step", service_id=SERVICE_ONE_ID, template_id=fake_uuid, step_index=0, - ) + ), ) -@pytest.mark.parametrize('user', ( - create_active_user_with_permissions(), - create_active_caseworking_user(), -)) +@pytest.mark.parametrize( + "user", + ( + create_active_user_with_permissions(), + create_active_caseworking_user(), + ), +) def test_send_one_off_email_to_self_without_placeholders_redirects_to_check_page( client_request, mocker, @@ -1499,37 +1572,40 @@ def test_send_one_off_email_to_self_without_placeholders_redirects_to_check_page client_request.login(user) with client_request.session_transaction() as session: - session['recipient'] = 'foo@bar.com' - session['placeholders'] = {'email address': 'foo@bar.com'} + session["recipient"] = "foo@bar.com" + session["placeholders"] = {"email address": "foo@bar.com"} page = client_request.get( - 'main.send_one_off_step', + "main.send_one_off_step", step_index=1, service_id=SERVICE_ONE_ID, template_id=fake_uuid, _follow_redirects=True, ) - assert page.select('h1')[0].text.strip() == 'Preview of ‘Two week reminder’' + assert page.select("h1")[0].text.strip() == "Preview of ‘Two week reminder’" -@pytest.mark.parametrize('permissions, expected_back_link_endpoint, extra_args', ( +@pytest.mark.parametrize( + "permissions, expected_back_link_endpoint, extra_args", ( - {'send_messages', 'manage_templates'}, - 'main.view_template', - {'template_id': unchanging_fake_uuid} + ( + {"send_messages", "manage_templates"}, + "main.view_template", + {"template_id": unchanging_fake_uuid}, + ), + ( + {"send_messages"}, + "main.choose_template", + {}, + ), + ( + {"send_messages", "view_activity"}, + "main.choose_template", + {}, + ), ), - ( - {'send_messages'}, - 'main.choose_template', - {}, - ), - ( - {'send_messages', 'view_activity'}, - 'main.choose_template', - {}, - ), -)) +) def test_send_one_off_step_0_back_link( client_request, active_user_with_permissions, @@ -1541,24 +1617,22 @@ def test_send_one_off_step_0_back_link( expected_back_link_endpoint, extra_args, ): - active_user_with_permissions['permissions'][SERVICE_ONE_ID] = permissions + active_user_with_permissions["permissions"][SERVICE_ONE_ID] = permissions client_request.login(active_user_with_permissions) with client_request.session_transaction() as session: - session['placeholders'] = {} - session['recipient'] = None + session["placeholders"] = {} + session["recipient"] = None page = client_request.get( - 'main.send_one_off_step', + "main.send_one_off_step", service_id=SERVICE_ONE_ID, template_id=unchanging_fake_uuid, - step_index=0 + step_index=0, ) - assert page.select('.usa-back-link')[0]['href'] == url_for( - expected_back_link_endpoint, - service_id=SERVICE_ONE_ID, - **extra_args + assert page.select(".usa-back-link")[0]["href"] == url_for( + expected_back_link_endpoint, service_id=SERVICE_ONE_ID, **extra_args ) @@ -1568,18 +1642,18 @@ def test_send_one_off_sms_message_back_link_with_multiple_placeholders( mock_has_no_jobs, ): with client_request.session_transaction() as session: - session['recipient'] = '2020900123' - session['placeholders'] = {'phone number': '2020900123', 'one': 'bar'} + session["recipient"] = "2020900123" + session["placeholders"] = {"phone number": "2020900123", "one": "bar"} page = client_request.get( - 'main.send_one_off_step', + "main.send_one_off_step", service_id=SERVICE_ONE_ID, template_id=unchanging_fake_uuid, step_index=2, ) - assert page.select_one('.usa-back-link')['href'] == url_for( - 'main.send_one_off_step', + assert page.select_one(".usa-back-link")["href"] == url_for( + "main.send_one_off_step", service_id=SERVICE_ONE_ID, template_id=unchanging_fake_uuid, step_index=1, @@ -1595,20 +1669,19 @@ def test_send_one_off_populates_field_from_session( mock_get_service_template_with_placeholders, fake_uuid, ): - with client_request.session_transaction() as session: - session['recipient'] = None - session['placeholders'] = {} - session['placeholders']['name'] = 'Jo' + session["recipient"] = None + session["placeholders"] = {} + session["placeholders"]["name"] = "Jo" page = client_request.get( - 'main.send_one_off_step', + "main.send_one_off_step", service_id=SERVICE_ONE_ID, template_id=fake_uuid, step_index=1, ) - assert page.select('input')[0]['value'] == 'Jo' + assert page.select("input")[0]["value"] == "Jo" def test_send_one_off_sms_message_puts_submitted_data_in_session( @@ -1620,26 +1693,26 @@ def test_send_one_off_sms_message_puts_submitted_data_in_session( fake_uuid, ): with client_request.session_transaction() as session: - session['recipient'] = '202-867-5303' - session['placeholders'] = {'phone number': '202-867-5303'} + session["recipient"] = "202-867-5303" + session["placeholders"] = {"phone number": "202-867-5303"} client_request.post( - 'main.send_one_off_step', - service_id=service_one['id'], + "main.send_one_off_step", + service_id=service_one["id"], template_id=fake_uuid, step_index=1, - _data={'placeholder_value': 'Jo'}, + _data={"placeholder_value": "Jo"}, _expected_status=302, _expected_redirect=url_for( - 'main.check_notification', - service_id=service_one['id'], + "main.check_notification", + service_id=service_one["id"], template_id=fake_uuid, - ) + ), ) with client_request.session_transaction() as session: - assert session['recipient'] == '202-867-5303' - assert session['placeholders'] == {'phone number': '202-867-5303', 'name': 'Jo'} + assert session["recipient"] == "202-867-5303" + assert session["placeholders"] == {"phone number": "202-867-5303", "name": "Jo"} def test_send_one_off_clears_session( @@ -1648,23 +1721,23 @@ def test_send_one_off_clears_session( service_one, fake_uuid, ): - template = {'data': {'template_type': 'sms', 'folder': None}} - mocker.patch('app.service_api_client.get_service_template', return_value=template) + template = {"data": {"template_type": "sms", "folder": None}} + mocker.patch("app.service_api_client.get_service_template", return_value=template) with client_request.session_transaction() as session: - session['recipient'] = '2028675301' - session['placeholders'] = {'foo': 'bar'} + session["recipient"] = "2028675301" + session["placeholders"] = {"foo": "bar"} client_request.get( - 'main.send_one_off', + "main.send_one_off", service_id=SERVICE_ONE_ID, template_id=fake_uuid, _expected_status=302, ) with client_request.session_transaction() as session: - assert session['recipient'] is None - assert session['placeholders'] == {} + assert session["recipient"] is None + assert session["placeholders"] == {} def test_download_example_csv( @@ -1675,19 +1748,18 @@ def test_download_example_csv( mock_get_service, mock_get_service_template_with_placeholders_same_as_recipient, mock_has_permissions, - fake_uuid + fake_uuid, ): response = client_request.get_response( - 'main.get_example_csv', + "main.get_example_csv", service_id=fake_uuid, template_id=fake_uuid, follow_redirects=True, ) assert response.get_data(as_text=True) == ( - 'phone number,name,date\r\n' - '12223334444,example,example\r\n' + "phone number,name,date\r\n" "12223334444,example,example\r\n" ) - assert 'text/csv' in response.headers['Content-Type'] + assert "text/csv" in response.headers["Content-Type"] def test_upload_csvfile_with_valid_phone_shows_all_numbers( @@ -1704,30 +1776,30 @@ def test_upload_csvfile_with_valid_phone_shows_all_numbers( mock_s3_upload, mocker, ): - mocker.patch( - 'app.main.views.send.s3download', - return_value='\n'.join(['phone number'] + [ - '202 867 07{0:02d}'.format(final_two) for final_two in range(0, 53) - ]) + "app.main.views.send.s3download", + return_value="\n".join( + ["phone number"] + + ["202 867 07{0:02d}".format(final_two) for final_two in range(0, 53)] + ), + ) + mock_get_notification_count = mocker.patch( + "app.service_api_client.get_notification_count", return_value=0 ) - mock_get_notification_count = mocker.patch('app.service_api_client.get_notification_count', return_value=0) page = client_request.post( - 'main.send_messages', - service_id=service_one['id'], + "main.send_messages", + service_id=service_one["id"], template_id=fake_uuid, - _data={'file': (BytesIO(''.encode('utf-8')), 'example.csv')}, - _content_type='multipart/form-data', + _data={"file": (BytesIO("".encode("utf-8")), "example.csv")}, + _content_type="multipart/form-data", _follow_redirects=True, ) with client_request.session_transaction() as session: - assert 'file_uploads' not in session + assert "file_uploads" not in session assert mock_s3_set_metadata.call_count == 2 assert mock_s3_set_metadata.call_args_list[0] == mocker.call( - SERVICE_ONE_ID, - fake_uuid, - original_file_name='example.csv' + SERVICE_ONE_ID, fake_uuid, original_file_name="example.csv" ) assert mock_s3_set_metadata.call_args_list[1] == mocker.call( SERVICE_ONE_ID, @@ -1735,21 +1807,24 @@ def test_upload_csvfile_with_valid_phone_shows_all_numbers( notification_count=53, template_id=fake_uuid, valid=True, - original_file_name='example.csv', + original_file_name="example.csv", ) - assert '202 867 0701' in page.text - assert '202 867 0749' in page.text - assert '202 867 0750' not in page.text - assert 'Only showing the first 50 rows' in page.text + assert "202 867 0701" in page.text + assert "202 867 0749" in page.text + assert "202 867 0750" not in page.text + assert "Only showing the first 50 rows" in page.text - mock_get_notification_count.assert_called_with(service_one['id']) + mock_get_notification_count.assert_called_with(service_one["id"]) -@pytest.mark.parametrize('international_sms_permission, should_allow_international', [ - (False, False), - (True, True), -]) +@pytest.mark.parametrize( + "international_sms_permission, should_allow_international", + [ + (False, False), + (True, True), + ], +) def test_upload_csvfile_with_international_validates( mocker, api_user_active, @@ -1769,27 +1844,32 @@ def test_upload_csvfile_with_international_validates( service_one, ): if international_sms_permission: - service_one['permissions'] += ('sms', 'international_sms') - mocker.patch('app.service_api_client.get_service', return_value={'data': service_one}) + service_one["permissions"] += ("sms", "international_sms") + mocker.patch( + "app.service_api_client.get_service", return_value={"data": service_one} + ) - mocker.patch('app.main.views.send.s3download', return_value='') + mocker.patch("app.main.views.send.s3download", return_value="") mock_recipients = mocker.patch( - 'app.main.views.send.RecipientCSV', - return_value=RecipientCSV("", template=SMSPreviewTemplate( - {'content': 'foo', 'template_type': 'sms'} - )), + "app.main.views.send.RecipientCSV", + return_value=RecipientCSV( + "", template=SMSPreviewTemplate({"content": "foo", "template_type": "sms"}) + ), ) client_request.post( - 'main.send_messages', + "main.send_messages", service_id=fake_uuid, template_id=fake_uuid, - _data={'file': (BytesIO(''.encode('utf-8')), 'example.csv')}, - _content_type='multipart/form-data', + _data={"file": (BytesIO("".encode("utf-8")), "example.csv")}, + _content_type="multipart/form-data", _follow_redirects=True, ) - assert mock_recipients.call_args[1]['allow_international_sms'] == should_allow_international + assert ( + mock_recipients.call_args[1]["allow_international_sms"] + == should_allow_international + ) def test_test_message_can_only_be_sent_now( @@ -1804,14 +1884,14 @@ def test_test_message_can_only_be_sent_now( mock_get_jobs, mock_s3_get_metadata, mock_s3_set_metadata, - fake_uuid + fake_uuid, ): content = client_request.get( - 'main.check_messages', - service_id=service_one['id'], + "main.check_messages", + service_id=service_one["id"], upload_id=fake_uuid, template_id=fake_uuid, - from_test=True + from_test=True, ) assert 'name="scheduled_for"' not in content @@ -1829,28 +1909,25 @@ def test_send_button_is_correctly_labelled( fake_uuid, mock_s3_get_metadata, ): - mocker.patch('app.main.views.send.s3download', return_value='\n'.join( - ['phone_number'] + (['2028670123'] * 1000) - )) - mocker.patch('app.main.views.send.set_metadata_on_csv_upload') + mocker.patch( + "app.main.views.send.s3download", + return_value="\n".join(["phone_number"] + (["2028670123"] * 1000)), + ) + mocker.patch("app.main.views.send.set_metadata_on_csv_upload") page = client_request.get( - 'main.check_messages', + "main.check_messages", service_id=SERVICE_ONE_ID, upload_id=fake_uuid, template_id=fake_uuid, ) - assert normalize_spaces( - page.select_one('main [type=submit]').text - ) == ( - 'Send 1,000 text messages' + assert normalize_spaces(page.select_one("main [type=submit]").text) == ( + "Send 1,000 text messages" ) -@pytest.mark.parametrize('when', [ - '', '2016-08-25T13:04:21.767198' -]) +@pytest.mark.parametrize("when", ["", "2016-08-25T13:04:21.767198"]) def test_create_job_should_call_api( client_request, mock_create_job, @@ -1862,27 +1939,27 @@ def test_create_job_should_call_api( fake_uuid, when, ): - data = mock_get_job(SERVICE_ONE_ID, fake_uuid)['data'] - job_id = data['id'] - original_file_name = data['original_file_name'] - template_id = data['template'] - notification_count = data['notification_count'] + data = mock_get_job(SERVICE_ONE_ID, fake_uuid)["data"] + job_id = data["id"] + original_file_name = data["original_file_name"] + template_id = data["template"] + notification_count = data["notification_count"] with client_request.session_transaction() as session: - session['file_uploads'] = { + session["file_uploads"] = { fake_uuid: { - 'template_id': template_id, - 'notification_count': notification_count, - 'valid': True + "template_id": template_id, + "notification_count": notification_count, + "valid": True, } } page = client_request.post( - 'main.start_job', + "main.start_job", service_id=SERVICE_ONE_ID, upload_id=job_id, original_file_name=original_file_name, _data={ - 'scheduled_for': when, + "scheduled_for": when, }, _follow_redirects=True, _expected_status=200, @@ -1897,11 +1974,14 @@ def test_create_job_should_call_api( ) -@pytest.mark.parametrize('route, response_code', [ - ('main.send_messages', 200), - ('main.get_example_csv', 200), - ('main.send_one_off', 302) -]) +@pytest.mark.parametrize( + "route, response_code", + [ + ("main.send_messages", 200), + ("main.get_example_csv", 200), + ("main.send_one_off", 302), + ], +) def test_route_permissions( mocker, notify_admin, @@ -1923,20 +2003,17 @@ def test_route_permissions( notify_admin, "GET", response_code, - url_for( - route, - service_id=service_one['id'], - template_id=fake_uuid - ), - ['view_activity', 'send_messages'], + url_for(route, service_id=service_one["id"], template_id=fake_uuid), + ["view_activity", "send_messages"], api_user_active, - service_one) + service_one, + ) -@pytest.mark.parametrize('route, response_code, method', [ - ('main.check_notification', 200, 'GET'), - ('main.send_notification', 302, 'POST') -]) +@pytest.mark.parametrize( + "route, response_code, method", + [("main.check_notification", 200, "GET"), ("main.send_notification", 302, "POST")], +) def test_route_permissions_send_check_notifications( mocker, notify_admin, @@ -1948,32 +2025,31 @@ def test_route_permissions_send_check_notifications( fake_uuid, route, response_code, - method + method, ): with client_request.session_transaction() as session: - session['recipient'] = '2028675301' - session['placeholders'] = {'name': 'a'} + session["recipient"] = "2028675301" + session["placeholders"] = {"name": "a"} validate_route_permission_with_client( mocker, client_request, method, response_code, - url_for( - route, - service_id=service_one['id'], - template_id=fake_uuid - ), - ['send_messages'], + url_for(route, service_id=service_one["id"], template_id=fake_uuid), + ["send_messages"], api_user_active, - service_one + service_one, ) -@pytest.mark.parametrize('route, expected_status', [ - ('main.send_messages', 403), - ('main.get_example_csv', 403), - ('main.send_one_off', 403), -]) +@pytest.mark.parametrize( + "route, expected_status", + [ + ("main.send_messages", 403), + ("main.get_example_csv", 403), + ("main.send_one_off", 403), + ], +) def test_route_permissions_sending( mocker, notify_admin, @@ -1996,36 +2072,23 @@ def test_route_permissions_sending( expected_status, url_for( route, - service_id=service_one['id'], - template_type='sms', - template_id=fake_uuid), - ['blah'], + service_id=service_one["id"], + template_type="sms", + template_id=fake_uuid, + ), + ["blah"], api_user_active, - service_one) + service_one, + ) @pytest.mark.parametrize( - 'template_type, has_placeholders, extra_args, expected_url', + "template_type, has_placeholders, extra_args, expected_url", [ - ( - 'sms', - False, - dict(), - partial(url_for, '.send_messages') - ), - ( - 'sms', - True, - dict(), - partial(url_for, '.send_messages') - ), - ( - 'sms', - True, - dict(from_test=True), - partial(url_for, '.send_one_off') - ) - ] + ("sms", False, dict(), partial(url_for, ".send_messages")), + ("sms", True, dict(), partial(url_for, ".send_messages")), + ("sms", True, dict(from_test=True), partial(url_for, ".send_one_off")), + ], ) def test_check_messages_back_link( client_request, @@ -2043,42 +2106,51 @@ def test_check_messages_back_link( template_type, has_placeholders, extra_args, - expected_url + expected_url, ): - content = 'Hi there ((name))' if has_placeholders else 'Hi there' - template_data = create_template(template_id=fake_uuid, template_type=template_type, content=content) - mocker.patch('app.service_api_client.get_service_template', return_value={'data': template_data}) + content = "Hi there ((name))" if has_placeholders else "Hi there" + template_data = create_template( + template_id=fake_uuid, template_type=template_type, content=content + ) + mocker.patch( + "app.service_api_client.get_service_template", + return_value={"data": template_data}, + ) with client_request.session_transaction() as session: - session['file_uploads'] = { + session["file_uploads"] = { fake_uuid: { - 'original_file_name': 'valid.csv', - 'template_id': fake_uuid, - 'notification_count': 1, - 'valid': True + "original_file_name": "valid.csv", + "template_id": fake_uuid, + "notification_count": 1, + "valid": True, } } page = client_request.get( - 'main.check_messages', + "main.check_messages", service_id=SERVICE_ONE_ID, upload_id=fake_uuid, template_id=fake_uuid, _test_page_title=False, - **extra_args + **extra_args, ) - assert ( - page.find_all('a', {'class': 'usa-back-link'})[0]['href'] - ) == expected_url(service_id=SERVICE_ONE_ID, template_id=fake_uuid) + assert (page.find_all("a", {"class": "usa-back-link"})[0]["href"]) == expected_url( + service_id=SERVICE_ONE_ID, template_id=fake_uuid + ) -@pytest.mark.parametrize('num_requested,expected_msg', [ - (None, '‘example.csv’ contains 1,234 phone numbers.'), - ("0", '‘example.csv’ contains 1,234 phone numbers.'), - # This used to trigger the too many messages errors but we removed the daily limit - ("1", '‘example.csv’ contains 1,234 phone numbers.') -], ids=['none_sent', 'none_sent', 'some_sent']) +@pytest.mark.parametrize( + "num_requested,expected_msg", + [ + (None, "‘example.csv’ contains 1,234 phone numbers."), + ("0", "‘example.csv’ contains 1,234 phone numbers."), + # This used to trigger the too many messages errors but we removed the daily limit + ("1", "‘example.csv’ contains 1,234 phone numbers."), + ], + ids=["none_sent", "none_sent", "some_sent"], +) def test_check_messages_shows_too_many_messages_errors( mocker, client_request, @@ -2090,37 +2162,46 @@ def test_check_messages_shows_too_many_messages_errors( fake_uuid, num_requested, expected_msg, - mock_s3_get_metadata + mock_s3_get_metadata, ): # csv with 100 phone numbers - mocker.patch('app.main.views.send.s3download', return_value=',\n'.join( - ['phone number'] + ([mock_get_users_by_service(None)[0]['mobile_number']] * 1234) - )) - mocker.patch('app.extensions.redis_client.get', return_value=num_requested) + mocker.patch( + "app.main.views.send.s3download", + return_value=",\n".join( + ["phone number"] + + ([mock_get_users_by_service(None)[0]["mobile_number"]] * 1234) + ), + ) + mocker.patch("app.extensions.redis_client.get", return_value=num_requested) with client_request.session_transaction() as session: - session['file_uploads'] = { + session["file_uploads"] = { fake_uuid: { - 'template_id': fake_uuid, - 'notification_count': 1, - 'valid': True + "template_id": fake_uuid, + "notification_count": 1, + "valid": True, } } page = client_request.get( - 'main.check_messages', + "main.check_messages", service_id=SERVICE_ONE_ID, template_id=fake_uuid, upload_id=fake_uuid, _test_page_title=False, ) - assert page.find('h1').text.strip() == 'Too many recipients' - assert page.find('div', class_='banner-dangerous').find('a').text.strip() == 'trial mode' + assert page.find("h1").text.strip() == "Too many recipients" + assert ( + page.find("div", class_="banner-dangerous").find("a").text.strip() + == "trial mode" + ) # remove excess whitespace from element - details = page.find('div', class_='banner-dangerous').find_all('p')[1] - details = ' '.join([line.strip() for line in details.text.split('\n') if line.strip() != '']) + details = page.find("div", class_="banner-dangerous").find_all("p")[1] + details = " ".join( + [line.strip() for line in details.text.split("\n") if line.strip() != ""] + ) assert details == expected_msg @@ -2134,39 +2215,41 @@ def test_check_messages_shows_trial_mode_error( mock_get_job_doesnt_exist, mock_get_jobs, fake_uuid, - mocker + mocker, ): - mocker.patch('app.main.views.send.s3download', return_value=( - 'phone number,\n2028675209' # Not in team - )) + mocker.patch( + "app.main.views.send.s3download", + return_value=("phone number,\n2028675209"), # Not in team + ) with client_request.session_transaction() as session: - session['file_uploads'] = { + session["file_uploads"] = { fake_uuid: { - 'template_id': '', + "template_id": "", } } page = client_request.get( - 'main.check_messages', + "main.check_messages", service_id=SERVICE_ONE_ID, template_id=fake_uuid, upload_id=fake_uuid, _test_page_title=False, ) - assert ' '.join( - page.find('div', class_='banner-dangerous').text.split() - ) == ( - 'You cannot send to this phone number ' - 'In trial mode you can only send to yourself and members of your team' + assert " ".join(page.find("div", class_="banner-dangerous").text.split()) == ( + "You cannot send to this phone number " + "In trial mode you can only send to yourself and members of your team" ) -@pytest.mark.parametrize('uploaded_file_name', ( - pytest.param('applicants.ods'), # normal job - pytest.param('send_me_later.csv'), # should look at scheduled job -)) +@pytest.mark.parametrize( + "uploaded_file_name", + ( + pytest.param("applicants.ods"), # normal job + pytest.param("send_me_later.csv"), # should look at scheduled job + ), +) def test_warns_if_file_sent_already( client_request, mock_get_users_by_service, @@ -2180,15 +2263,15 @@ def test_warns_if_file_sent_already( mocker, uploaded_file_name, ): - mocker.patch('app.main.views.send.s3download', return_value=( - 'phone number,\n2028675209' - )) mocker.patch( - 'app.main.views.send.get_csv_metadata', - return_value={'original_file_name': uploaded_file_name}, + "app.main.views.send.s3download", return_value=("phone number,\n2028675209") + ) + mocker.patch( + "app.main.views.send.get_csv_metadata", + return_value={"original_file_name": uploaded_file_name}, ) page = client_request.get( - 'main.check_messages', + "main.check_messages", service_id=SERVICE_ONE_ID, template_id="5d729fbd-239c-44ab-b498-75a985f3198f", upload_id=fake_uuid, @@ -2196,20 +2279,21 @@ def test_warns_if_file_sent_already( _test_page_title=False, ) - assert normalize_spaces( - page.select_one('.banner-dangerous').text - ) == ( - 'These messages have already been sent today ' - 'If you need to resend them, rename the file and upload it again.' + assert normalize_spaces(page.select_one(".banner-dangerous").text) == ( + "These messages have already been sent today " + "If you need to resend them, rename the file and upload it again." ) mock_get_jobs.assert_called_once_with(SERVICE_ONE_ID, limit_days=0) -@pytest.mark.parametrize('uploaded_file_name', ( - pytest.param('thisisatest.csv'), # different template version - pytest.param('full_of_regret.csv'), # job is cancelled -)) +@pytest.mark.parametrize( + "uploaded_file_name", + ( + pytest.param("thisisatest.csv"), # different template version + pytest.param("full_of_regret.csv"), # job is cancelled + ), +) def test_warns_if_file_sent_already_errors( client_request, mock_get_users_by_service, @@ -2223,17 +2307,17 @@ def test_warns_if_file_sent_already_errors( mocker, uploaded_file_name, ): - mocker.patch('app.main.views.send.s3download', return_value=( - 'phone number,\n2028675209' - )) mocker.patch( - 'app.main.views.send.get_csv_metadata', - return_value={'original_file_name': uploaded_file_name}, + "app.main.views.send.s3download", return_value=("phone number,\n2028675209") + ) + mocker.patch( + "app.main.views.send.get_csv_metadata", + return_value={"original_file_name": uploaded_file_name}, ) # Should be botocore.errorfactory.NoSuchKey but for some reason can't use that with pytest.raises(expected_exception=Exception): page = client_request.get( - 'main.check_messages', + "main.check_messages", service_id=SERVICE_ONE_ID, template_id="5d729fbd-239c-44ab-b498-75a985f3198f", upload_id=fake_uuid, @@ -2241,11 +2325,9 @@ def test_warns_if_file_sent_already_errors( _test_page_title=False, ) - assert normalize_spaces( - page.select_one('.banner-dangerous').text - ) == ( - 'These messages have already been sent today ' - 'If you need to resend them, rename the file and upload it again.' + assert normalize_spaces(page.select_one(".banner-dangerous").text) == ( + "These messages have already been sent today " + "If you need to resend them, rename the file and upload it again." ) mock_get_jobs.assert_called_once_with(SERVICE_ONE_ID, limit_days=0) @@ -2263,32 +2345,34 @@ def test_check_messages_column_error_doesnt_show_optional_columns( mock_get_jobs, mock_s3_get_metadata, ): - - mocker.patch('app.main.views.send.s3download', return_value='\n'.join( - ['address_line_1,address_line_2,foo'] + - ['First Lastname,1 Example Road,SW1 1AA'] - )) + mocker.patch( + "app.main.views.send.s3download", + return_value="\n".join( + ["address_line_1,address_line_2,foo"] + + ["First Lastname,1 Example Road,SW1 1AA"] + ), + ) with client_request.session_transaction() as session: - session['file_uploads'] = { + session["file_uploads"] = { fake_uuid: { - 'template_id': '', - 'original_file_name': '', + "template_id": "", + "original_file_name": "", } } page = client_request.get( - 'main.check_messages', + "main.check_messages", service_id=SERVICE_ONE_ID, template_id=fake_uuid, upload_id=fake_uuid, _test_page_title=False, ) - assert normalize_spaces(page.select_one('.banner-dangerous').text) == ( - 'There’s a problem with your column names ' - 'Your file needs a column called ‘phone number’. ' - 'Right now it has columns called ‘address_line_1’, ‘address_line_2’ and ‘foo’.' + assert normalize_spaces(page.select_one(".banner-dangerous").text) == ( + "There’s a problem with your column names " + "Your file needs a column called ‘phone number’. " + "Right now it has columns called ‘address_line_1’, ‘address_line_2’ and ‘foo’." ) @@ -2305,19 +2389,17 @@ def test_check_messages_adds_sender_id_in_session_to_metadata( mock_s3_set_metadata, fake_uuid, ): - mocker.patch('app.main.views.send.s3download', return_value=( - 'phone number,\n2028675209' - )) - mocker.patch('app.main.views.send.get_sms_sender_from_session') + mocker.patch( + "app.main.views.send.s3download", return_value=("phone number,\n2028675209") + ) + mocker.patch("app.main.views.send.get_sms_sender_from_session") with client_request.session_transaction() as session: - session['file_uploads'] = { - fake_uuid: {'template_id': fake_uuid} - } - session['sender_id'] = 'fake-sender' + session["file_uploads"] = {fake_uuid: {"template_id": fake_uuid}} + session["sender_id"] = "fake-sender" client_request.get( - 'main.check_messages', + "main.check_messages", service_id=SERVICE_ONE_ID, template_id=fake_uuid, upload_id=fake_uuid, @@ -2329,9 +2411,9 @@ def test_check_messages_adds_sender_id_in_session_to_metadata( fake_uuid, notification_count=1, template_id=fake_uuid, - sender_id='fake-sender', + sender_id="fake-sender", valid=True, - original_file_name='example.csv', + original_file_name="example.csv", ) @@ -2346,118 +2428,110 @@ def test_check_messages_shows_over_max_row_error( mock_s3_get_metadata, mock_s3_download, fake_uuid, - mocker + mocker, ): - mock_recipients = mocker.patch('app.main.views.send.RecipientCSV').return_value + mock_recipients = mocker.patch("app.main.views.send.RecipientCSV").return_value mock_recipients.max_rows = 11111 mock_recipients.__len__.return_value = 99999 mock_recipients.too_many_rows.return_value = True with client_request.session_transaction() as session: - session['file_uploads'] = { + session["file_uploads"] = { fake_uuid: { - 'template_id': fake_uuid, + "template_id": fake_uuid, } } page = client_request.get( - 'main.check_messages', + "main.check_messages", service_id=SERVICE_ONE_ID, template_id=fake_uuid, upload_id=fake_uuid, _test_page_title=False, ) - assert ' '.join( - page.find('div', class_='banner-dangerous').text.split() - ) == ( - 'Your file has too many rows ' - 'Notify can process up to 11,111 rows at once. ' - 'Your file has 99,999 rows.' + assert " ".join(page.find("div", class_="banner-dangerous").text.split()) == ( + "Your file has too many rows " + "Notify can process up to 11,111 rows at once. " + "Your file has 99,999 rows." ) -@pytest.mark.parametrize('existing_session_items', [ - {}, - {'recipient': '2028675301'}, - {'name': 'Jo'} -]) +@pytest.mark.parametrize( + "existing_session_items", [{}, {"recipient": "2028675301"}, {"name": "Jo"}] +) def test_check_notification_redirects_if_session_not_populated( client_request, service_one, fake_uuid, existing_session_items, - mock_get_service_template_with_placeholders + mock_get_service_template_with_placeholders, ): with client_request.session_transaction() as session: session.update(existing_session_items) client_request.get( - 'main.check_notification', + "main.check_notification", service_id=SERVICE_ONE_ID, template_id=fake_uuid, _expected_status=301, _expected_redirect=url_for( - 'main.send_one_off_step', + "main.send_one_off_step", service_id=SERVICE_ONE_ID, template_id=fake_uuid, step_index=1, - ) + ), ) def test_check_notification_shows_preview( - client_request, - service_one, - fake_uuid, - mock_get_service_template + client_request, service_one, fake_uuid, mock_get_service_template ): with client_request.session_transaction() as session: - session['recipient'] = '2028675301' - session['placeholders'] = {} + session["recipient"] = "2028675301" + session["placeholders"] = {} page = client_request.get( - 'main.check_notification', - service_id=service_one['id'], - template_id=fake_uuid + "main.check_notification", service_id=service_one["id"], template_id=fake_uuid ) - assert page.h1.text.strip() == 'Preview of ‘Two week reminder’' - assert ( - page.find_all('a', {'class': 'usa-back-link'})[0]['href'] - ) == url_for( - 'main.send_one_off_step', - service_id=service_one['id'], + assert page.h1.text.strip() == "Preview of ‘Two week reminder’" + assert (page.find_all("a", {"class": "usa-back-link"})[0]["href"]) == url_for( + "main.send_one_off_step", + service_id=service_one["id"], template_id=fake_uuid, step_index=0, ) # assert tour not visible - assert not page.select('.banner-tour') + assert not page.select(".banner-tour") # post to send_notification with help=0 to ensure no back link is then shown - assert page.form.attrs['action'] == url_for( - 'main.send_notification', - service_id=service_one['id'], + assert page.form.attrs["action"] == url_for( + "main.send_notification", + service_id=service_one["id"], template_id=fake_uuid, - help='0' + help="0", ) -@pytest.mark.parametrize('template, recipient, placeholders, expected_personalisation', ( +@pytest.mark.parametrize( + "template, recipient, placeholders, expected_personalisation", ( - mock_get_service_template, - '2028675301', - {'a': 'b'}, - {'a': 'b'}, + ( + mock_get_service_template, + "2028675301", + {"a": "b"}, + {"a": "b"}, + ), + ( + mock_get_service_email_template, + "test@example.com", + {}, + {}, + ), ), - ( - mock_get_service_email_template, - 'test@example.com', - {}, - {}, - ), -)) +) def test_send_notification_submits_data( client_request, fake_uuid, @@ -2469,13 +2543,11 @@ def test_send_notification_submits_data( expected_personalisation, ): with client_request.session_transaction() as session: - session['recipient'] = recipient - session['placeholders'] = placeholders + session["recipient"] = recipient + session["placeholders"] = placeholders client_request.post( - 'main.send_notification', - service_id=SERVICE_ONE_ID, - template_id=fake_uuid + "main.send_notification", service_id=SERVICE_ONE_ID, template_id=fake_uuid ) mock_send_notification.assert_called_once_with( @@ -2483,7 +2555,7 @@ def test_send_notification_submits_data( template_id=fake_uuid, recipient=recipient, personalisation=expected_personalisation, - sender_id=None + sender_id=None, ) @@ -2495,25 +2567,26 @@ def test_send_notification_clears_session( mock_get_service_template, ): with client_request.session_transaction() as session: - session['recipient'] = '2028675301' - session['placeholders'] = {'a': 'b'} + session["recipient"] = "2028675301" + session["placeholders"] = {"a": "b"} client_request.post( - 'main.send_notification', - service_id=service_one['id'], - template_id=fake_uuid + "main.send_notification", service_id=service_one["id"], template_id=fake_uuid ) with client_request.session_transaction() as session: - assert 'recipient' not in session - assert 'placeholders' not in session + assert "recipient" not in session + assert "placeholders" not in session -@pytest.mark.parametrize('session_data', [ - {'placeholders': {'a': 'b'}}, # missing recipient - {'recipient': '123'}, # missing placeholders - {'placeholders': {}, 'recipient': ''}, # missing address -]) +@pytest.mark.parametrize( + "session_data", + [ + {"placeholders": {"a": "b"}}, # missing recipient + {"recipient": "123"}, # missing placeholders + {"placeholders": {}, "recipient": ""}, # missing address + ], +) def test_send_notification_redirects_if_missing_data( client_request, fake_uuid, @@ -2523,74 +2596,76 @@ def test_send_notification_redirects_if_missing_data( session.update(session_data) client_request.post( - 'main.send_notification', + "main.send_notification", service_id=SERVICE_ONE_ID, template_id=fake_uuid, _expected_status=302, _expected_redirect=url_for( - '.send_one_off', + ".send_one_off", service_id=SERVICE_ONE_ID, template_id=fake_uuid, ), ) -@pytest.mark.parametrize('extra_args, extra_redirect_args', [ - ({}, {}), - ({'help': '3'}, {'help': '3'}) -]) +@pytest.mark.parametrize( + "extra_args, extra_redirect_args", [({}, {}), ({"help": "3"}, {"help": "3"})] +) def test_send_notification_redirects_to_view_page( client_request, fake_uuid, mock_send_notification, mock_get_service_template, extra_args, - extra_redirect_args + extra_redirect_args, ): with client_request.session_transaction() as session: - session['recipient'] = '2028675301' - session['placeholders'] = {'a': 'b'} + session["recipient"] = "2028675301" + session["placeholders"] = {"a": "b"} client_request.post( - 'main.send_notification', + "main.send_notification", service_id=SERVICE_ONE_ID, template_id=fake_uuid, _expected_status=302, _expected_redirect=url_for( - '.view_notification', + ".view_notification", service_id=SERVICE_ONE_ID, notification_id=fake_uuid, - **extra_redirect_args + **extra_redirect_args, ), - **extra_args + **extra_args, ) TRIAL_MODE_MSG = ( - 'Cannot send to this recipient when service is in trial mode – ' - 'see https://www.notifications.service.gov.uk/trial-mode' + "Cannot send to this recipient when service is in trial mode – " + "see https://www.notifications.service.gov.uk/trial-mode" ) -TOO_LONG_MSG = 'Text messages cannot be longer than 918 characters. Your message is 954 characters.' -SERVICE_DAILY_LIMIT_MSG = 'Exceeded send limits (1000) for today' +TOO_LONG_MSG = "Text messages cannot be longer than 918 characters. Your message is 954 characters." +SERVICE_DAILY_LIMIT_MSG = "Exceeded send limits (1000) for today" -@pytest.mark.parametrize('exception_msg, expected_h1, expected_err_details', [ - ( - TRIAL_MODE_MSG, - 'You cannot send to this phone number', - 'In trial mode you can only send to yourself and members of your team' - ), - ( - TOO_LONG_MSG, - 'Message too long', - 'Text messages cannot be longer than 918 characters. Your message is 954 characters.' - ), - ( - SERVICE_DAILY_LIMIT_MSG, - 'Daily limit reached', - 'You can only send 1,000 messages per day in trial mode.' - ), -]) +@pytest.mark.parametrize( + "exception_msg, expected_h1, expected_err_details", + [ + ( + TRIAL_MODE_MSG, + "You cannot send to this phone number", + "In trial mode you can only send to yourself and members of your team", + ), + ( + TOO_LONG_MSG, + "Message too long", + "Text messages cannot be longer than 918 characters. Your message is 954 characters.", + ), + ( + SERVICE_DAILY_LIMIT_MSG, + "Daily limit reached", + "You can only send 1,000 messages per day in trial mode.", + ), + ], +) def test_send_notification_shows_error_if_400( client_request, service_one, @@ -2599,30 +2674,32 @@ def test_send_notification_shows_error_if_400( mock_get_service_template_with_placeholders, exception_msg, expected_h1, - expected_err_details + expected_err_details, ): - class MockHTTPError(HTTPError): message = exception_msg mocker.patch( - 'app.notification_api_client.send_notification', + "app.notification_api_client.send_notification", side_effect=MockHTTPError(), ) with client_request.session_transaction() as session: - session['recipient'] = '2028675301' - session['placeholders'] = {'name': 'a' * 900} + session["recipient"] = "2028675301" + session["placeholders"] = {"name": "a" * 900} page = client_request.post( - 'main.send_notification', - service_id=service_one['id'], + "main.send_notification", + service_id=service_one["id"], template_id=fake_uuid, - _expected_status=200 + _expected_status=200, ) - assert normalize_spaces(page.select('.banner-dangerous h1')[0].text) == expected_h1 - assert normalize_spaces(page.select('.banner-dangerous p')[0].text) == expected_err_details - assert not page.find('input[type=submit]') + assert normalize_spaces(page.select(".banner-dangerous h1")[0].text) == expected_h1 + assert ( + normalize_spaces(page.select(".banner-dangerous p")[0].text) + == expected_err_details + ) + assert not page.find("input[type=submit]") def test_send_notification_shows_email_error_in_trial_mode( @@ -2636,40 +2713,42 @@ def test_send_notification_shows_email_error_in_trial_mode( status_code = 400 mocker.patch( - 'app.notification_api_client.send_notification', + "app.notification_api_client.send_notification", side_effect=MockHTTPError(), ) with client_request.session_transaction() as session: - session['recipient'] = 'test@example.com' - session['placeholders'] = {'date': 'foo', 'thing': 'bar'} + session["recipient"] = "test@example.com" + session["placeholders"] = {"date": "foo", "thing": "bar"} page = client_request.post( - 'main.send_notification', + "main.send_notification", service_id=SERVICE_ONE_ID, template_id=fake_uuid, _expected_status=200, ) - assert normalize_spaces(page.select('.banner-dangerous h1')[0].text) == ( - 'You cannot send to this email address' + assert normalize_spaces(page.select(".banner-dangerous h1")[0].text) == ( + "You cannot send to this email address" ) - assert normalize_spaces(page.select('.banner-dangerous p')[0].text) == ( - 'In trial mode you can only send to yourself and members of your team' + assert normalize_spaces(page.select(".banner-dangerous p")[0].text) == ( + "In trial mode you can only send to yourself and members of your team" ) -@pytest.mark.parametrize('endpoint, extra_args', [ - ('main.check_messages', { - 'template_id': uuid4(), 'upload_id': uuid4() - }), - ('main.send_one_off_step', { - 'template_id': uuid4(), 'step_index': 0 - }), -]) -@pytest.mark.parametrize('reply_to_address', [ - None, - uuid4(), -]) +@pytest.mark.parametrize( + "endpoint, extra_args", + [ + ("main.check_messages", {"template_id": uuid4(), "upload_id": uuid4()}), + ("main.send_one_off_step", {"template_id": uuid4(), "step_index": 0}), + ], +) +@pytest.mark.parametrize( + "reply_to_address", + [ + None, + uuid4(), + ], +) def test_reply_to_is_previewed_if_chosen( client_request, mocker, @@ -2687,41 +2766,44 @@ def test_reply_to_is_previewed_if_chosen( extra_args, reply_to_address, ): - mocker.patch('app.main.views.send.s3download', return_value=""" + mocker.patch( + "app.main.views.send.s3download", + return_value=""" email_address,date,thing notify@digital.cabinet-office.gov.uk,foo,bar - """) - - with client_request.session_transaction() as session: - session['recipient'] = 'notify@digital.cabinet-office.gov.uk' - session['placeholders'] = {} - session['file_uploads'] = { - fake_uuid: {'template_id': fake_uuid} - } - session['sender_id'] = reply_to_address - - page = client_request.get( - endpoint, - service_id=SERVICE_ONE_ID, - **extra_args + """, ) - email_meta = page.select_one('.email-message-meta').text + with client_request.session_transaction() as session: + session["recipient"] = "notify@digital.cabinet-office.gov.uk" + session["placeholders"] = {} + session["file_uploads"] = {fake_uuid: {"template_id": fake_uuid}} + session["sender_id"] = reply_to_address + + page = client_request.get(endpoint, service_id=SERVICE_ONE_ID, **extra_args) + + email_meta = page.select_one(".email-message-meta").text if reply_to_address: - assert 'test@example.com' in email_meta + assert "test@example.com" in email_meta else: - assert 'test@example.com' not in email_meta + assert "test@example.com" not in email_meta -@pytest.mark.parametrize('endpoint, extra_args', [ - ('main.check_messages', {'template_id': uuid4(), 'upload_id': uuid4()}), - ('main.send_one_off_step', {'template_id': uuid4(), 'step_index': 0}), -]) -@pytest.mark.parametrize('sms_sender', [ - None, - uuid4(), -]) +@pytest.mark.parametrize( + "endpoint, extra_args", + [ + ("main.check_messages", {"template_id": uuid4(), "upload_id": uuid4()}), + ("main.send_one_off_step", {"template_id": uuid4(), "step_index": 0}), + ], +) +@pytest.mark.parametrize( + "sms_sender", + [ + None, + uuid4(), + ], +) def test_sms_sender_is_previewed( client_request, mocker, @@ -2739,34 +2821,32 @@ def test_sms_sender_is_previewed( extra_args, sms_sender, ): - - mocker.patch('app.main.views.send.s3download', return_value=""" + mocker.patch( + "app.main.views.send.s3download", + return_value=""" phone number,date,thing 2028675109,foo,bar - """) - - with client_request.session_transaction() as session: - session['recipient'] = '2028675109' - session['placeholders'] = {} - session['file_uploads'] = { - fake_uuid: { - 'template_id': fake_uuid, - 'notification_count': 1, - 'valid': True - } - } - session['sender_id'] = sms_sender - - page = client_request.get( - endpoint, - service_id=SERVICE_ONE_ID, - **extra_args + """, ) - sms_sender_on_page = page.select_one('.sms-message-sender') + with client_request.session_transaction() as session: + session["recipient"] = "2028675109" + session["placeholders"] = {} + session["file_uploads"] = { + fake_uuid: { + "template_id": fake_uuid, + "notification_count": 1, + "valid": True, + } + } + session["sender_id"] = sms_sender + + page = client_request.get(endpoint, service_id=SERVICE_ONE_ID, **extra_args) + + sms_sender_on_page = page.select_one(".sms-message-sender") if sms_sender: - assert sms_sender_on_page.text.strip() == 'From: GOVUK' + assert sms_sender_on_page.text.strip() == "From: GOVUK" else: assert not sms_sender_on_page @@ -2777,19 +2857,18 @@ def test_redirects_to_template_if_job_exists_already( mock_get_job, fake_uuid, ): - client_request.get( - 'main.check_messages', + "main.check_messages", service_id=SERVICE_ONE_ID, template_id=fake_uuid, upload_id=fake_uuid, - original_file_name='example.csv', + original_file_name="example.csv", _expected_status=301, _expected_redirect=url_for( - 'main.send_messages', + "main.send_messages", service_id=SERVICE_ONE_ID, template_id=fake_uuid, - ) + ), ) @@ -2797,47 +2876,47 @@ def test_send_to_myself_sets_placeholder_and_redirects_for_email( mocker, client_request, fake_uuid, mock_get_service_email_template ): with client_request.session_transaction() as session: - session['recipient'] = None - session['placeholders'] = {} + session["recipient"] = None + session["placeholders"] = {} client_request.get( - 'main.send_one_off_to_myself', + "main.send_one_off_to_myself", service_id=SERVICE_ONE_ID, template_id=fake_uuid, _expected_status=302, _expected_url=url_for( - 'main.send_one_off_step', + "main.send_one_off_step", service_id=SERVICE_ONE_ID, template_id=fake_uuid, step_index=1, - ) + ), ) with client_request.session_transaction() as session: - assert session['recipient'] == 'test@user.gsa.gov' - assert session['placeholders'] == {'email address': 'test@user.gsa.gov'} + assert session["recipient"] == "test@user.gsa.gov" + assert session["placeholders"] == {"email address": "test@user.gsa.gov"} def test_send_to_myself_sets_placeholder_and_redirects_for_sms( mocker, client_request, fake_uuid, mock_get_service_template ): with client_request.session_transaction() as session: - session['recipient'] = None - session['placeholders'] = {} + session["recipient"] = None + session["placeholders"] = {} client_request.get( - 'main.send_one_off_to_myself', + "main.send_one_off_to_myself", service_id=SERVICE_ONE_ID, template_id=fake_uuid, _expected_status=302, _expected_url=url_for( - 'main.send_one_off_step', + "main.send_one_off_step", service_id=SERVICE_ONE_ID, template_id=fake_uuid, step_index=1, - ) + ), ) with client_request.session_transaction() as session: - assert session['recipient'] == '202-867-5303' - assert session['placeholders'] == {'phone number': '202-867-5303'} + assert session["recipient"] == "202-867-5303" + assert session["placeholders"] == {"phone number": "202-867-5303"} diff --git a/tests/app/main/views/test_sign_in.py b/tests/app/main/views/test_sign_in.py index b2929573b..9d4e2bddc 100644 --- a/tests/app/main/views/test_sign_in.py +++ b/tests/app/main/views/test_sign_in.py @@ -7,56 +7,59 @@ from app.models.user import User from tests.conftest import SERVICE_ONE_ID, normalize_spaces -def test_render_sign_in_template_for_new_user( - client_request -): +def test_render_sign_in_template_for_new_user(client_request): client_request.logout() - page = client_request.get('main.sign_in') - assert normalize_spaces(page.select_one('h1').text) == 'Sign in' - assert normalize_spaces(page.select('label')[0].text) == 'Email address' - assert page.select_one('#email_address').get('value') is None - assert page.select_one('#email_address')['autocomplete'] == 'email' - assert normalize_spaces(page.select('label')[1].text) == 'Password' - assert page.select_one('#password').get('value') is None - assert page.select_one('#password')['autocomplete'] == 'current-password' + page = client_request.get("main.sign_in") + assert normalize_spaces(page.select_one("h1").text) == "Sign in" + assert normalize_spaces(page.select("label")[0].text) == "Email address" + assert page.select_one("#email_address").get("value") is None + assert page.select_one("#email_address")["autocomplete"] == "email" + assert normalize_spaces(page.select("label")[1].text) == "Password" + assert page.select_one("#password").get("value") is None + assert page.select_one("#password")["autocomplete"] == "current-password" # Removing for the pilot # assert page.select('main a')[0].text == 'create one now' # assert page.select('main a')[0]['href'] == url_for('main.register') - assert page.select('main a')[0].text == 'Forgot your password?' - assert page.select('main a')[0]['href'] == url_for('main.forgot_password') - assert 'Sign in again' not in normalize_spaces(page.text) + assert page.select("main a")[0].text == "Forgot your password?" + assert page.select("main a")[0]["href"] == url_for("main.forgot_password") + assert "Sign in again" not in normalize_spaces(page.text) -def test_render_sign_in_template_with_next_link_for_password_reset( - client_request -): +def test_render_sign_in_template_with_next_link_for_password_reset(client_request): client_request.logout() page = client_request.get( - 'main.sign_in', + "main.sign_in", _optional_args=f"?next=/services/{SERVICE_ONE_ID}/templates", - _test_page_title=False + _test_page_title=False, + ) + forgot_password_link = page.find("a", class_="usa-link") + assert forgot_password_link.text == "Forgot your password?" + assert forgot_password_link["href"] == url_for( + "main.forgot_password", next=f"/services/{SERVICE_ONE_ID}/templates" ) - forgot_password_link = page.find('a', class_="usa-link") - assert forgot_password_link.text == 'Forgot your password?' - assert forgot_password_link['href'] == url_for('main.forgot_password', next=f'/services/{SERVICE_ONE_ID}/templates') def test_sign_in_explains_session_timeout(client_request): client_request.logout() - page = client_request.get('main.sign_in', next='/foo') - assert 'We signed you out because you have not used Notify for a while.' in page.text + page = client_request.get("main.sign_in", next="/foo") + assert ( + "We signed you out because you have not used Notify for a while." in page.text + ) def test_sign_in_explains_other_browser(client_request, api_user_active, mocker): - api_user_active['current_session_id'] = str(uuid.UUID(int=1)) - mocker.patch('app.user_api_client.get_user', return_value=api_user_active) + api_user_active["current_session_id"] = str(uuid.UUID(int=1)) + mocker.patch("app.user_api_client.get_user", return_value=api_user_active) with client_request.session_transaction() as session: - session['current_session_id'] = str(uuid.UUID(int=2)) + session["current_session_id"] = str(uuid.UUID(int=2)) - page = client_request.get('main.sign_in', next='/foo') + page = client_request.get("main.sign_in", next="/foo") - assert 'We signed you out because you logged in to Notify on another device' in page.text + assert ( + "We signed you out because you logged in to Notify on another device" + in page.text + ) def test_doesnt_redirect_to_sign_in_if_no_session_info( @@ -64,79 +67,84 @@ def test_doesnt_redirect_to_sign_in_if_no_session_info( api_user_active, mock_get_organization_by_domain, ): - api_user_active['current_session_id'] = str(uuid.UUID(int=1)) + api_user_active["current_session_id"] = str(uuid.UUID(int=1)) with client_request.session_transaction() as session: - session['current_session_id'] = None + session["current_session_id"] = None - client_request.get('main.add_service') + client_request.get("main.add_service") -@pytest.mark.parametrize('db_sess_id, cookie_sess_id', [ - (None, None), - (None, uuid.UUID(int=1)), # BAD - cookie doesn't match db - (uuid.UUID(int=1), None), # BAD - has used other browsers before but this is a brand new browser with no cookie - (uuid.UUID(int=1), uuid.UUID(int=2)), # BAD - this person has just signed in on a different browser -]) +@pytest.mark.parametrize( + "db_sess_id, cookie_sess_id", + [ + (None, None), + (None, uuid.UUID(int=1)), # BAD - cookie doesn't match db + ( + uuid.UUID(int=1), + None, + ), # BAD - has used other browsers before but this is a brand new browser with no cookie + ( + uuid.UUID(int=1), + uuid.UUID(int=2), + ), # BAD - this person has just signed in on a different browser + ], +) def test_redirect_to_sign_in_if_logged_in_from_other_browser( - client_request, - api_user_active, - mocker, - db_sess_id, - cookie_sess_id + client_request, api_user_active, mocker, db_sess_id, cookie_sess_id ): - api_user_active['current_session_id'] = db_sess_id - mocker.patch('app.user_api_client.get_user', return_value=api_user_active) + api_user_active["current_session_id"] = db_sess_id + mocker.patch("app.user_api_client.get_user", return_value=api_user_active) with client_request.session_transaction() as session: - session['current_session_id'] = str(cookie_sess_id) + session["current_session_id"] = str(cookie_sess_id) client_request.get( - 'main.choose_account', + "main.choose_account", _expected_status=302, - _expected_redirect=url_for('main.sign_in', next='/accounts'), + _expected_redirect=url_for("main.sign_in", next="/accounts"), ) -def test_logged_in_user_redirects_to_account( - client_request -): +def test_logged_in_user_redirects_to_account(client_request): client_request.get( - 'main.sign_in', + "main.sign_in", _expected_status=302, - _expected_redirect=url_for('main.show_accounts_or_dashboard'), + _expected_redirect=url_for("main.show_accounts_or_dashboard"), ) -def test_logged_in_user_redirects_to_next_url( - client_request -): +def test_logged_in_user_redirects_to_next_url(client_request): client_request.get( - 'main.sign_in', - next='/user-profile', + "main.sign_in", + next="/user-profile", _expected_status=302, - _expected_redirect=url_for('main.user_profile'), + _expected_redirect=url_for("main.user_profile"), ) -def test_logged_in_user_doesnt_do_evil_redirect( - client_request -): +def test_logged_in_user_doesnt_do_evil_redirect(client_request): client_request.get( - 'main.sign_in', - next='http://www.evil.com', + "main.sign_in", + next="http://www.evil.com", _expected_status=302, - _expected_redirect=url_for('main.show_accounts_or_dashboard'), + _expected_redirect=url_for("main.show_accounts_or_dashboard"), ) -@pytest.mark.parametrize('redirect_url', [ - None, - f'/services/{SERVICE_ONE_ID}/templates', -]) -@pytest.mark.parametrize('email_address, password', [ - ('valid@example.gsa.gov', 'val1dPassw0rd!'), - (' valid@example.gsa.gov ', ' val1dPassw0rd! '), -]) +@pytest.mark.parametrize( + "redirect_url", + [ + None, + f"/services/{SERVICE_ONE_ID}/templates", + ], +) +@pytest.mark.parametrize( + "email_address, password", + [ + ("valid@example.gsa.gov", "val1dPassw0rd!"), + (" valid@example.gsa.gov ", " val1dPassw0rd! "), + ], +) def test_process_sms_auth_sign_in_return_2fa_template( client_request, api_user_active, @@ -146,50 +154,61 @@ def test_process_sms_auth_sign_in_return_2fa_template( mock_verify_password, email_address, password, - redirect_url + redirect_url, ): client_request.logout() client_request.post( - 'main.sign_in', + "main.sign_in", next=redirect_url, _data={ - 'email_address': email_address, - 'password': password, + "email_address": email_address, + "password": password, }, - _expected_redirect=url_for('.two_factor_sms', next=redirect_url), + _expected_redirect=url_for(".two_factor_sms", next=redirect_url), ) - mock_verify_password.assert_called_with(api_user_active['id'], password) - mock_get_user_by_email.assert_called_with('valid@example.gsa.gov') + mock_verify_password.assert_called_with(api_user_active["id"], password) + mock_get_user_by_email.assert_called_with("valid@example.gsa.gov") -@pytest.mark.parametrize('redirect_url', [ - None, - f'/services/{SERVICE_ONE_ID}/templates', -]) +@pytest.mark.parametrize( + "redirect_url", + [ + None, + f"/services/{SERVICE_ONE_ID}/templates", + ], +) def test_process_email_auth_sign_in_return_2fa_template( client_request, api_user_active_email_auth, mock_send_verify_code, mock_verify_password, mocker, - redirect_url + redirect_url, ): client_request.logout() - mocker.patch('app.user_api_client.get_user', return_value=api_user_active_email_auth) - mocker.patch('app.user_api_client.get_user_by_email', return_value=api_user_active_email_auth) - - client_request.post( - 'main.sign_in', - next=redirect_url, - _data={ - 'email_address': 'valid@example.gsa.gov', - 'password': 'val1dPassw0rd!', - }, - _expected_redirect=url_for('.two_factor_email_sent', next=redirect_url), + mocker.patch( + "app.user_api_client.get_user", return_value=api_user_active_email_auth + ) + mocker.patch( + "app.user_api_client.get_user_by_email", return_value=api_user_active_email_auth ) - mock_send_verify_code.assert_called_with(api_user_active_email_auth['id'], 'email', None, redirect_url) - mock_verify_password.assert_called_with(api_user_active_email_auth['id'], 'val1dPassw0rd!') + client_request.post( + "main.sign_in", + next=redirect_url, + _data={ + "email_address": "valid@example.gsa.gov", + "password": "val1dPassw0rd!", + }, + _expected_redirect=url_for(".two_factor_email_sent", next=redirect_url), + ) + + mock_send_verify_code.assert_called_with( + api_user_active_email_auth["id"], "email", None, redirect_url + ) + mock_verify_password.assert_called_with( + api_user_active_email_auth["id"], "val1dPassw0rd!" + ) def test_should_return_locked_out_true_when_user_is_locked( @@ -198,14 +217,14 @@ def test_should_return_locked_out_true_when_user_is_locked( ): client_request.logout() page = client_request.post( - 'main.sign_in', + "main.sign_in", _data={ - 'email_address': 'valid@example.gsa.gov', - 'password': 'whatIsMyPassword!', + "email_address": "valid@example.gsa.gov", + "password": "whatIsMyPassword!", }, _expected_status=200, ) - assert 'The email address or password you entered is incorrect' in page.text + assert "The email address or password you entered is incorrect" in page.text def test_should_return_200_when_user_does_not_exist( @@ -214,15 +233,12 @@ def test_should_return_200_when_user_does_not_exist( ): client_request.logout() page = client_request.post( - 'main.sign_in', - _data={ - 'email_address': 'notfound@gsa.gov', - 'password': 'doesNotExist!' - }, + "main.sign_in", + _data={"email_address": "notfound@gsa.gov", "password": "doesNotExist!"}, _expected_status=200, ) - assert 'The email address or password you entered is incorrect' in page.text + assert "The email address or password you entered is incorrect" in page.text def test_should_return_redirect_when_user_is_pending( @@ -233,39 +249,39 @@ def test_should_return_redirect_when_user_is_pending( ): client_request.logout() client_request.post( - 'main.sign_in', + "main.sign_in", _data={ - 'email_address': 'pending_user@example.gsa.gov', - 'password': 'val1dPassw0rd!' + "email_address": "pending_user@example.gsa.gov", + "password": "val1dPassw0rd!", }, - _expected_redirect=url_for('main.resend_email_verification'), + _expected_redirect=url_for("main.resend_email_verification"), ) with client_request.session_transaction() as s: - assert s['user_details'] == { - 'email': api_user_pending['email_address'], - 'id': api_user_pending['id'] + assert s["user_details"] == { + "email": api_user_pending["email_address"], + "id": api_user_pending["id"], } -@pytest.mark.parametrize('redirect_url', [ - None, - f'/services/{SERVICE_ONE_ID}/templates', -]) +@pytest.mark.parametrize( + "redirect_url", + [ + None, + f"/services/{SERVICE_ONE_ID}/templates", + ], +) def test_should_attempt_redirect_when_user_is_pending( - client_request, - mock_get_user_by_email_pending, - mock_verify_password, - redirect_url + client_request, mock_get_user_by_email_pending, mock_verify_password, redirect_url ): client_request.logout() client_request.post( - 'main.sign_in', + "main.sign_in", next=redirect_url, _data={ - 'email_address': 'pending_user@example.gsa.gov', - 'password': 'val1dPassw0rd!' + "email_address": "pending_user@example.gsa.gov", + "password": "val1dPassw0rd!", }, - _expected_redirect=url_for('main.resend_email_verification', next=redirect_url) + _expected_redirect=url_for("main.resend_email_verification", next=redirect_url), ) @@ -280,27 +296,24 @@ def test_email_address_is_treated_case_insensitively_when_signing_in_as_invited_ mock_get_invited_user_by_id, ): client_request.logout() - sample_invite['email_address'] = 'TEST@user.gsa.gov' + sample_invite["email_address"] = "TEST@user.gsa.gov" mocker.patch( - 'app.models.user.User.from_email_address_and_password_or_none', + "app.models.user.User.from_email_address_and_password_or_none", return_value=User(api_user_active), ) with client_request.session_transaction() as session: - session['invited_user_id'] = sample_invite['id'] + session["invited_user_id"] = sample_invite["id"] client_request.post( - 'main.sign_in', - _data={ - 'email_address': 'test@user.gsa.gov', - 'password': 'val1dPassw0rd!' - }, + "main.sign_in", + _data={"email_address": "test@user.gsa.gov", "password": "val1dPassw0rd!"}, ) assert mock_accept_invite.called assert mock_send_verify_code.called - mock_get_invited_user_by_id.assert_called_once_with(sample_invite['id']) + mock_get_invited_user_by_id.assert_called_once_with(sample_invite["id"]) def test_when_signing_in_as_invited_user_you_cannot_accept_an_invite_for_another_email_address( @@ -313,27 +326,27 @@ def test_when_signing_in_as_invited_user_you_cannot_accept_an_invite_for_another mock_send_verify_code, mock_get_invited_user_by_id, ): - sample_invite['email_address'] = 'some_other_user@user.gsa.gov' + sample_invite["email_address"] = "some_other_user@user.gsa.gov" mocker.patch( - 'app.models.user.User.from_email_address_and_password_or_none', + "app.models.user.User.from_email_address_and_password_or_none", return_value=User(api_user_active), ) client_request.logout() with client_request.session_transaction() as session: - session['invited_user_id'] = sample_invite['id'] + session["invited_user_id"] = sample_invite["id"] page = client_request.post( - 'main.sign_in', - _data={ - 'email_address': 'test@user.gsa.gov', - 'password': 'val1dPassw0rd!' - }, - _expected_status=403 + "main.sign_in", + _data={"email_address": "test@user.gsa.gov", "password": "val1dPassw0rd!"}, + _expected_status=403, ) assert mock_accept_invite.called is False assert mock_send_verify_code.called is False - assert page.select_one('.banner-dangerous').text.strip() == 'You cannot accept an invite for another person.' + assert ( + page.select_one(".banner-dangerous").text.strip() + == "You cannot accept an invite for another person." + ) diff --git a/tests/app/main/views/test_sign_out.py b/tests/app/main/views/test_sign_out.py index 2e3c4fb29..ad63b0556 100644 --- a/tests/app/main/views/test_sign_out.py +++ b/tests/app/main/views/test_sign_out.py @@ -3,16 +3,14 @@ from flask import url_for from tests.conftest import SERVICE_ONE_ID -def test_render_sign_out_redirects_to_sign_in( - client_request -): +def test_render_sign_out_redirects_to_sign_in(client_request): with client_request.session_transaction() as session: assert session client_request.get( - 'main.sign_out', + "main.sign_out", _expected_redirect=url_for( - 'main.index', - ) + "main.index", + ), ) with client_request.session_transaction() as session: assert not session @@ -35,33 +33,31 @@ def test_sign_out_user( mock_get_inbound_sms_summary, ): with client_request.session_transaction() as session: - assert session.get('user_id') is not None + assert session.get("user_id") is not None # Check we are logged in client_request.get( - 'main.service_dashboard', + "main.service_dashboard", service_id=SERVICE_ONE_ID, ) client_request.get( - 'main.sign_out', + "main.sign_out", _expected_status=302, _expected_redirect=url_for( - 'main.index', - ) + "main.index", + ), ) with client_request.session_transaction() as session: - assert session.get('user_id') is None + assert session.get("user_id") is None -def test_sign_out_of_two_sessions( - client_request -): +def test_sign_out_of_two_sessions(client_request): client_request.get( - 'main.sign_out', + "main.sign_out", _expected_status=302, ) with client_request.session_transaction() as session: assert not session client_request.get( - 'main.sign_out', + "main.sign_out", _expected_status=302, ) diff --git a/tests/app/main/views/test_template_folders.py b/tests/app/main/views/test_template_folders.py index a4570502a..4dfaf3627 100644 --- a/tests/app/main/views/test_template_folders.py +++ b/tests/app/main/views/test_template_folders.py @@ -17,333 +17,343 @@ from tests.conftest import ( normalize_spaces, ) -ROOT_FOLDER_ID = '__NONE__' -PARENT_FOLDER_ID = '7e979e79-d970-43a5-ac69-b625a8d147b0' -CHILD_FOLDER_ID = '92ee1ee0-e4ee-4dcc-b1a7-a5da9ebcfa2b' -GRANDCHILD_FOLDER_ID = 'fafe723f-1d39-4a10-865f-e551e03d8886' -FOLDER_TWO_ID = 'bbbb222b-2b22-2b22-222b-b222b22b2222' -FOLDER_B_ID = 'dddb222b-2b22-2b22-222b-b222b22b6789' -FOLDER_C_ID = 'ccbb222b-2b22-2b22-222b-b222b22b2345' +ROOT_FOLDER_ID = "__NONE__" +PARENT_FOLDER_ID = "7e979e79-d970-43a5-ac69-b625a8d147b0" +CHILD_FOLDER_ID = "92ee1ee0-e4ee-4dcc-b1a7-a5da9ebcfa2b" +GRANDCHILD_FOLDER_ID = "fafe723f-1d39-4a10-865f-e551e03d8886" +FOLDER_TWO_ID = "bbbb222b-2b22-2b22-222b-b222b22b2222" +FOLDER_B_ID = "dddb222b-2b22-2b22-222b-b222b22b6789" +FOLDER_C_ID = "ccbb222b-2b22-2b22-222b-b222b22b2345" def _folder(name, folder_id=None, parent=None, users_with_permission=None): return { - 'name': name, - 'id': folder_id or str(uuid.uuid4()), - 'parent_id': parent, - 'users_with_permission': users_with_permission if users_with_permission is not None else [sample_uuid()], + "name": name, + "id": folder_id or str(uuid.uuid4()), + "parent_id": parent, + "users_with_permission": users_with_permission + if users_with_permission is not None + else [sample_uuid()], } @pytest.mark.parametrize( ( - 'expected_title_tag,' - 'expected_page_title,' - 'expected_parent_link_args,' - 'extra_args,' - 'expected_nav_links,' - 'expected_items, ' - 'expected_displayed_items, ' - 'expected_searchable_text, ' - 'expected_empty_message ' + "expected_title_tag," + "expected_page_title," + "expected_parent_link_args," + "extra_args," + "expected_nav_links," + "expected_items, " + "expected_displayed_items, " + "expected_searchable_text, " + "expected_empty_message " ), [ ( - 'Templates – service one – Notify.gov', - 'Templates', + "Templates – service one – Notify.gov", + "Templates", [], {}, - ['Email', 'Text message'], + ["Email", "Text message"], [ - 'folder_one folder_one 2 folders', - ('folder_one folder_one_one ' - 'folder_one folder_one_one ' - '1 folder'), - ('folder_one folder_one_one folder_one_one_one ' - 'folder_one folder_one_one folder_one_one_one ' - '1 template'), - ('folder_one folder_one_one folder_one_one_one sms_template_nested ' - 'folder_one folder_one_one folder_one_one_one sms_template_nested ' - 'Text message template'), - ('folder_one folder_one_two ' - 'folder_one folder_one_two ' - 'Empty'), - 'folder_two folder_two Empty', - ('sms_template_one ' - 'sms_template_one ' - 'Text message template'), - ('sms_template_two ' - 'sms_template_two ' - 'Text message template'), - 'email_template_one email_template_one Email template', - 'email_template_two email_template_two Email template', + "folder_one folder_one 2 folders", + ("folder_one folder_one_one " "folder_one folder_one_one " "1 folder"), + ( + "folder_one folder_one_one folder_one_one_one " + "folder_one folder_one_one folder_one_one_one " + "1 template" + ), + ( + "folder_one folder_one_one folder_one_one_one sms_template_nested " + "folder_one folder_one_one folder_one_one_one sms_template_nested " + "Text message template" + ), + ("folder_one folder_one_two " "folder_one folder_one_two " "Empty"), + "folder_two folder_two Empty", + ("sms_template_one " "sms_template_one " "Text message template"), + ("sms_template_two " "sms_template_two " "Text message template"), + "email_template_one email_template_one Email template", + "email_template_two email_template_two Email template", ], [ - 'folder_one folder_one 2 folders', - 'folder_two folder_two Empty', - 'sms_template_one sms_template_one Text message template', - 'sms_template_two sms_template_two Text message template', - 'email_template_one email_template_one Email template', - 'email_template_two email_template_two Email template', + "folder_one folder_one 2 folders", + "folder_two folder_two Empty", + "sms_template_one sms_template_one Text message template", + "sms_template_two sms_template_two Text message template", + "email_template_one email_template_one Email template", + "email_template_two email_template_two Email template", ], [ - 'folder_one', - 'folder_one_one', - 'folder_one_one_one', - 'sms_template_nested', - 'folder_one_two', - 'folder_two', - 'sms_template_one', - 'sms_template_two', - 'email_template_one', - 'email_template_two', + "folder_one", + "folder_one_one", + "folder_one_one_one", + "sms_template_nested", + "folder_one_two", + "folder_two", + "sms_template_one", + "sms_template_two", + "email_template_one", + "email_template_two", ], None, ), ( - 'Templates – service one – Notify.gov', - 'Templates', + "Templates – service one – Notify.gov", + "Templates", [], - {'template_type': 'all'}, - ['Email', 'Text message'], + {"template_type": "all"}, + ["Email", "Text message"], [ - 'folder_one folder_one 2 folders', - ('folder_one folder_one_one ' - 'folder_one folder_one_one ' - '1 folder'), - ('folder_one folder_one_one folder_one_one_one ' - 'folder_one folder_one_one folder_one_one_one ' - '1 template'), - ('folder_one folder_one_one folder_one_one_one sms_template_nested ' - 'folder_one folder_one_one folder_one_one_one sms_template_nested ' - 'Text message template'), - ('folder_one folder_one_two ' - 'folder_one folder_one_two ' - 'Empty'), - 'folder_two folder_two Empty', - 'sms_template_one sms_template_one Text message template', - 'sms_template_two sms_template_two Text message template', - 'email_template_one email_template_one Email template', - 'email_template_two email_template_two Email template', + "folder_one folder_one 2 folders", + ("folder_one folder_one_one " "folder_one folder_one_one " "1 folder"), + ( + "folder_one folder_one_one folder_one_one_one " + "folder_one folder_one_one folder_one_one_one " + "1 template" + ), + ( + "folder_one folder_one_one folder_one_one_one sms_template_nested " + "folder_one folder_one_one folder_one_one_one sms_template_nested " + "Text message template" + ), + ("folder_one folder_one_two " "folder_one folder_one_two " "Empty"), + "folder_two folder_two Empty", + "sms_template_one sms_template_one Text message template", + "sms_template_two sms_template_two Text message template", + "email_template_one email_template_one Email template", + "email_template_two email_template_two Email template", ], [ - 'folder_one folder_one 2 folders', - 'folder_two folder_two Empty', - 'sms_template_one sms_template_one Text message template', - 'sms_template_two sms_template_two Text message template', - 'email_template_one email_template_one Email template', - 'email_template_two email_template_two Email template', + "folder_one folder_one 2 folders", + "folder_two folder_two Empty", + "sms_template_one sms_template_one Text message template", + "sms_template_two sms_template_two Text message template", + "email_template_one email_template_one Email template", + "email_template_two email_template_two Email template", ], [ - 'folder_one', - 'folder_one_one', - 'folder_one_one_one', - 'sms_template_nested', - 'folder_one_two', - 'folder_two', - 'sms_template_one', - 'sms_template_two', - 'email_template_one', - 'email_template_two', + "folder_one", + "folder_one_one", + "folder_one_one_one", + "sms_template_nested", + "folder_one_two", + "folder_two", + "sms_template_one", + "sms_template_two", + "email_template_one", + "email_template_two", ], None, ), ( - 'Templates – service one – Notify.gov', - 'Templates', + "Templates – service one – Notify.gov", + "Templates", [], - {'template_type': 'sms'}, - ['All', 'Email'], + {"template_type": "sms"}, + ["All", "Email"], [ - 'folder_one folder_one 1 folder', - ('folder_one folder_one_one ' - 'folder_one folder_one_one ' - '1 folder'), - ('folder_one folder_one_one folder_one_one_one ' - 'folder_one folder_one_one folder_one_one_one ' - '1 template'), - ('folder_one folder_one_one folder_one_one_one sms_template_nested ' - 'folder_one folder_one_one folder_one_one_one sms_template_nested ' - 'Text message template'), - 'sms_template_one sms_template_one Text message template', - 'sms_template_two sms_template_two Text message template', + "folder_one folder_one 1 folder", + ("folder_one folder_one_one " "folder_one folder_one_one " "1 folder"), + ( + "folder_one folder_one_one folder_one_one_one " + "folder_one folder_one_one folder_one_one_one " + "1 template" + ), + ( + "folder_one folder_one_one folder_one_one_one sms_template_nested " + "folder_one folder_one_one folder_one_one_one sms_template_nested " + "Text message template" + ), + "sms_template_one sms_template_one Text message template", + "sms_template_two sms_template_two Text message template", ], [ - 'folder_one folder_one 1 folder', - 'sms_template_one sms_template_one Text message template', - 'sms_template_two sms_template_two Text message template', + "folder_one folder_one 1 folder", + "sms_template_one sms_template_one Text message template", + "sms_template_two sms_template_two Text message template", ], [ - 'folder_one', - 'folder_one_one', - 'folder_one_one_one', - 'sms_template_nested', - 'sms_template_one', - 'sms_template_two', + "folder_one", + "folder_one_one", + "folder_one_one_one", + "sms_template_nested", + "sms_template_one", + "sms_template_two", ], None, ), ( - 'folder_one – Templates – service one – Notify.gov', - 'Templates folder_one', - [{'template_type': 'all'}], - {'template_folder_id': PARENT_FOLDER_ID}, - ['Email', 'Text message'], + "folder_one – Templates – service one – Notify.gov", + "Templates folder_one", + [{"template_type": "all"}], + {"template_folder_id": PARENT_FOLDER_ID}, + ["Email", "Text message"], [ - 'folder_one_one folder_one_one 1 folder', - ('folder_one_one folder_one_one_one ' - 'folder_one_one folder_one_one_one ' - '1 template'), - ('folder_one_one folder_one_one_one sms_template_nested ' - 'folder_one_one folder_one_one_one sms_template_nested ' - 'Text message template'), - 'folder_one_two folder_one_two Empty', + "folder_one_one folder_one_one 1 folder", + ( + "folder_one_one folder_one_one_one " + "folder_one_one folder_one_one_one " + "1 template" + ), + ( + "folder_one_one folder_one_one_one sms_template_nested " + "folder_one_one folder_one_one_one sms_template_nested " + "Text message template" + ), + "folder_one_two folder_one_two Empty", ], [ - 'folder_one_one folder_one_one 1 folder', - 'folder_one_two folder_one_two Empty', + "folder_one_one folder_one_one 1 folder", + "folder_one_two folder_one_two Empty", ], [ - 'folder_one_one', - 'folder_one_one_one', - 'sms_template_nested', - 'folder_one_two', + "folder_one_one", + "folder_one_one_one", + "sms_template_nested", + "folder_one_two", ], None, ), ( - 'folder_one – Templates – service one – Notify.gov', - 'Templates folder_one', - [{'template_type': 'sms'}], - {'template_type': 'sms', 'template_folder_id': PARENT_FOLDER_ID}, - ['All', 'Email'], + "folder_one – Templates – service one – Notify.gov", + "Templates folder_one", + [{"template_type": "sms"}], + {"template_type": "sms", "template_folder_id": PARENT_FOLDER_ID}, + ["All", "Email"], [ - 'folder_one_one folder_one_one 1 folder', - ('folder_one_one folder_one_one_one ' - 'folder_one_one folder_one_one_one ' - '1 template'), - ('folder_one_one folder_one_one_one sms_template_nested ' - 'folder_one_one folder_one_one_one sms_template_nested ' - 'Text message template'), + "folder_one_one folder_one_one 1 folder", + ( + "folder_one_one folder_one_one_one " + "folder_one_one folder_one_one_one " + "1 template" + ), + ( + "folder_one_one folder_one_one_one sms_template_nested " + "folder_one_one folder_one_one_one sms_template_nested " + "Text message template" + ), ], [ - 'folder_one_one folder_one_one 1 folder', + "folder_one_one folder_one_one 1 folder", ], [ - 'folder_one_one', - 'folder_one_one_one', - 'sms_template_nested', + "folder_one_one", + "folder_one_one_one", + "sms_template_nested", ], None, ), ( - 'folder_one – Templates – service one – Notify.gov', - 'Templates folder_one', - [{'template_type': 'email'}], - {'template_type': 'email', 'template_folder_id': PARENT_FOLDER_ID}, - ['All', 'Text message'], + "folder_one – Templates – service one – Notify.gov", + "Templates folder_one", + [{"template_type": "email"}], + {"template_type": "email", "template_folder_id": PARENT_FOLDER_ID}, + ["All", "Text message"], [], [], [], - 'There are no email templates in this folder', + "There are no email templates in this folder", ), ( - 'folder_one_one – folder_one – Templates – service one – Notify.gov', - 'Templates folder_one folder_one_one', + "folder_one_one – folder_one – Templates – service one – Notify.gov", + "Templates folder_one folder_one_one", [ - {'template_type': 'all'}, - {'template_type': 'all', 'template_folder_id': PARENT_FOLDER_ID}, + {"template_type": "all"}, + {"template_type": "all", "template_folder_id": PARENT_FOLDER_ID}, ], - {'template_folder_id': CHILD_FOLDER_ID}, - ['Email', 'Text message'], + {"template_folder_id": CHILD_FOLDER_ID}, + ["Email", "Text message"], [ - 'folder_one_one_one folder_one_one_one 1 template', - ('folder_one_one_one sms_template_nested ' - 'folder_one_one_one sms_template_nested ' - 'Text message template'), + "folder_one_one_one folder_one_one_one 1 template", + ( + "folder_one_one_one sms_template_nested " + "folder_one_one_one sms_template_nested " + "Text message template" + ), ], [ - 'folder_one_one_one folder_one_one_one 1 template', + "folder_one_one_one folder_one_one_one 1 template", ], [ - 'folder_one_one_one', - 'sms_template_nested', + "folder_one_one_one", + "sms_template_nested", ], None, ), ( - 'folder_one_one_one – folder_one_one – folder_one – Templates – service one – Notify.gov', - 'Templates folder_one folder_one_one folder_one_one_one', + "folder_one_one_one – folder_one_one – folder_one – Templates – service one – Notify.gov", + "Templates folder_one folder_one_one folder_one_one_one", [ - {'template_type': 'all'}, - {'template_type': 'all', 'template_folder_id': PARENT_FOLDER_ID}, - {'template_type': 'all', 'template_folder_id': CHILD_FOLDER_ID}, + {"template_type": "all"}, + {"template_type": "all", "template_folder_id": PARENT_FOLDER_ID}, + {"template_type": "all", "template_folder_id": CHILD_FOLDER_ID}, ], - {'template_folder_id': GRANDCHILD_FOLDER_ID}, - ['Email', 'Text message'], + {"template_folder_id": GRANDCHILD_FOLDER_ID}, + ["Email", "Text message"], [ - 'sms_template_nested sms_template_nested Text message template', + "sms_template_nested sms_template_nested Text message template", ], [ - 'sms_template_nested sms_template_nested Text message template', + "sms_template_nested sms_template_nested Text message template", ], [ - 'sms_template_nested', + "sms_template_nested", ], None, ), ( - 'folder_one_one_one – folder_one_one – folder_one – Templates – service one – Notify.gov', - 'Templates folder_one folder_one_one folder_one_one_one', + "folder_one_one_one – folder_one_one – folder_one – Templates – service one – Notify.gov", + "Templates folder_one folder_one_one folder_one_one_one", [ - {'template_type': 'email'}, - {'template_type': 'email', 'template_folder_id': PARENT_FOLDER_ID}, - {'template_type': 'email', 'template_folder_id': CHILD_FOLDER_ID}, + {"template_type": "email"}, + {"template_type": "email", "template_folder_id": PARENT_FOLDER_ID}, + {"template_type": "email", "template_folder_id": CHILD_FOLDER_ID}, ], { - 'template_type': 'email', - 'template_folder_id': GRANDCHILD_FOLDER_ID, + "template_type": "email", + "template_folder_id": GRANDCHILD_FOLDER_ID, }, - ['All', 'Text message'], + ["All", "Text message"], [], [], [], - 'There are no email templates in this folder', + "There are no email templates in this folder", ), ( - 'folder_two – Templates – service one – Notify.gov', - 'Templates folder_two', - [{'template_type': 'all'}], - {'template_folder_id': FOLDER_TWO_ID}, - ['Email', 'Text message'], + "folder_two – Templates – service one – Notify.gov", + "Templates folder_two", + [{"template_type": "all"}], + {"template_folder_id": FOLDER_TWO_ID}, + ["Email", "Text message"], [], [], [], - 'This folder is empty', + "This folder is empty", ), ( - 'folder_two – Templates – service one – Notify.gov', - 'Templates folder_two', - [{'template_type': 'sms'}], - {'template_folder_id': FOLDER_TWO_ID, 'template_type': 'sms'}, - ['All', 'Email'], + "folder_two – Templates – service one – Notify.gov", + "Templates folder_two", + [{"template_type": "sms"}], + {"template_folder_id": FOLDER_TWO_ID, "template_type": "sms"}, + ["All", "Email"], [], [], [], - 'This folder is empty', + "This folder is empty", ), ( - 'folder_two – Templates – service one – Notify.gov', - 'Templates folder_two', - [{'template_type': 'all'}], - {'template_folder_id': FOLDER_TWO_ID, 'template_type': 'all'}, - ['Email', 'Text message'], + "folder_two – Templates – service one – Notify.gov", + "Templates folder_two", + [{"template_type": "all"}], + {"template_folder_id": FOLDER_TWO_ID, "template_type": "all"}, + ["Email", "Text message"], [], [], [], - 'This folder is empty', + "This folder is empty", ), - ] + ], ) def test_should_show_templates_folder_page( client_request, @@ -363,56 +373,60 @@ def test_should_show_templates_folder_page( expected_empty_message, ): mock_get_template_folders.return_value = [ - _folder('folder_two', FOLDER_TWO_ID), - _folder('folder_one', PARENT_FOLDER_ID), - _folder('folder_one_two', parent=PARENT_FOLDER_ID), - _folder('folder_one_one', CHILD_FOLDER_ID, parent=PARENT_FOLDER_ID), - _folder('folder_one_one_one', GRANDCHILD_FOLDER_ID, parent=CHILD_FOLDER_ID), + _folder("folder_two", FOLDER_TWO_ID), + _folder("folder_one", PARENT_FOLDER_ID), + _folder("folder_one_two", parent=PARENT_FOLDER_ID), + _folder("folder_one_one", CHILD_FOLDER_ID, parent=PARENT_FOLDER_ID), + _folder("folder_one_one_one", GRANDCHILD_FOLDER_ID, parent=CHILD_FOLDER_ID), ] mock_get_service_templates = mocker.patch( - 'app.service_api_client.get_service_templates', - return_value={'data': [ - _template('sms', 'sms_template_one'), - _template('sms', 'sms_template_two'), - _template('email', 'email_template_one'), - _template('email', 'email_template_two'), - _template('sms', 'sms_template_nested', parent=GRANDCHILD_FOLDER_ID), - ]} + "app.service_api_client.get_service_templates", + return_value={ + "data": [ + _template("sms", "sms_template_one"), + _template("sms", "sms_template_two"), + _template("email", "email_template_one"), + _template("email", "email_template_two"), + _template("sms", "sms_template_nested", parent=GRANDCHILD_FOLDER_ID), + ] + }, ) page = client_request.get( - 'main.choose_template', + "main.choose_template", service_id=SERVICE_ONE_ID, _test_page_title=False, - **extra_args + **extra_args, ) - assert normalize_spaces(page.select_one('title').text) == expected_title_tag - assert normalize_spaces(page.select_one('h1').text) == expected_page_title + assert normalize_spaces(page.select_one("title").text) == expected_title_tag + assert normalize_spaces(page.select_one("h1").text) == expected_page_title - assert len(page.select('h1 a')) == len(expected_parent_link_args) + assert len(page.select("h1 a")) == len(expected_parent_link_args) - for index, parent_link in enumerate(page.select('h1 a')): - assert parent_link['href'] == url_for( - 'main.choose_template', + for index, parent_link in enumerate(page.select("h1 a")): + assert parent_link["href"] == url_for( + "main.choose_template", service_id=SERVICE_ONE_ID, - **expected_parent_link_args[index] + **expected_parent_link_args[index], ) - links_in_page = page.select('.pill a:not(.pill-item--selected)') + links_in_page = page.select(".pill a:not(.pill-item--selected)") assert len(links_in_page) == len(expected_nav_links) for index, expected_link in enumerate(expected_nav_links): assert links_in_page[index].text.strip() == expected_link - all_page_items = page.select('.template-list-item') - all_page_items_styled_with_checkboxes = page.select('.usa-checkbox.template-list-item') + all_page_items = page.select(".template-list-item") + all_page_items_styled_with_checkboxes = page.select( + ".usa-checkbox.template-list-item" + ) assert len(all_page_items) == len(all_page_items_styled_with_checkboxes) - checkboxes = page.select('input[name=templates_and_folders]') - unique_checkbox_values = set(item['value'] for item in checkboxes) + checkboxes = page.select("input[name=templates_and_folders]") + unique_checkbox_values = set(item["value"] for item in checkboxes) assert len(all_page_items) == len(expected_items) assert len(checkboxes) == len(expected_items) assert len(unique_checkbox_values) == len(expected_items) @@ -420,29 +434,33 @@ def test_should_show_templates_folder_page( for index, expected_item in enumerate(expected_items): assert normalize_spaces(all_page_items[index].text) == expected_item - displayed_page_items = page.find_all(lambda tag: ( - tag.has_attr('class') - and 'template-list-item' in tag['class'] - and 'template-list-item-hidden-by-default' not in tag['class'] - )) + displayed_page_items = page.find_all( + lambda tag: ( + tag.has_attr("class") + and "template-list-item" in tag["class"] + and "template-list-item-hidden-by-default" not in tag["class"] + ) + ) assert len(displayed_page_items) == len(expected_displayed_items) for index, expected_item in enumerate(expected_displayed_items): - assert '/' not in expected_item # Yo dawg I heard you like tests… + assert "/" not in expected_item # Yo dawg I heard you like tests… assert normalize_spaces(displayed_page_items[index].text) == expected_item - all_searchable_text = page.select('#template-list .template-list-item .live-search-relevant') + all_searchable_text = page.select( + "#template-list .template-list-item .live-search-relevant" + ) assert len(all_searchable_text) == len(expected_searchable_text) for index, expected_item in enumerate(expected_searchable_text): assert normalize_spaces(all_searchable_text[index].text) == expected_item if expected_empty_message: - assert normalize_spaces(page.select_one('.template-list-empty').text) == ( + assert normalize_spaces(page.select_one(".template-list-empty").text) == ( expected_empty_message ) else: - assert not page.select('.template-list-empty') + assert not page.select(".template-list-empty") mock_get_service_templates.assert_called_once_with(SERVICE_ONE_ID) @@ -454,23 +472,24 @@ def test_template_id_is_searchable_for_services_with_api_keys( mock_get_api_keys, service_one, mocker, - ): mock_get_template_folders.return_value = [ - _folder('folder one', PARENT_FOLDER_ID), + _folder("folder one", PARENT_FOLDER_ID), ] - template_1 = _template('sms', 'template one') - template_2 = _template('sms', 'template two', parent=PARENT_FOLDER_ID) + template_1 = _template("sms", "template one") + template_2 = _template("sms", "template two", parent=PARENT_FOLDER_ID) mocker.patch( - 'app.service_api_client.get_service_templates', - return_value={'data': [ - template_1, - template_2, - ]} + "app.service_api_client.get_service_templates", + return_value={ + "data": [ + template_1, + template_2, + ] + }, ) page = client_request.get( - 'main.choose_template', + "main.choose_template", service_id=SERVICE_ONE_ID, _test_page_title=False, ) @@ -479,10 +498,10 @@ def test_template_id_is_searchable_for_services_with_api_keys( normalize_spaces(item.text) for item in page.select( # Elements which the live search will filter by - '.template-list-item .live-search-relevant' + ".template-list-item .live-search-relevant" ) ] == [ - 'folder one', + "folder one", f'{template_2["id"]} template two', f'{template_1["id"]} template one', ] @@ -491,10 +510,10 @@ def test_template_id_is_searchable_for_services_with_api_keys( normalize_spaces(item.text) for item in page.select( # Elements the user will see when first loading the page - '.template-list-item:not(.template-list-item-hidden-by-default)' + ".template-list-item:not(.template-list-item-hidden-by-default)" ) ] == [ - 'folder one folder one 1 template', + "folder one folder one 1 template", f'template one {template_1["id"]} template one Text message template', ] @@ -502,7 +521,7 @@ def test_template_id_is_searchable_for_services_with_api_keys( normalize_spaces(item.text) for item in page.select( # Text which should be hidden from all users - r'.template-list-item .govuk-\!-display-none' + r".template-list-item .govuk-\!-display-none" ) ] == [ template_2["id"], @@ -513,33 +532,36 @@ def test_template_id_is_searchable_for_services_with_api_keys( def test_can_create_email_template_with_parent_folder( - client_request, - mock_create_service_template + client_request, mock_create_service_template ): data = { - 'name': "new name", - 'subject': "Food incoming!", - 'template_content': "here's a burrito 🌯", - 'template_type': 'email', - 'service': SERVICE_ONE_ID, - 'parent_folder_id': PARENT_FOLDER_ID + "name": "new name", + "subject": "Food incoming!", + "template_content": "here's a burrito 🌯", + "template_type": "email", + "service": SERVICE_ONE_ID, + "parent_folder_id": PARENT_FOLDER_ID, } - client_request.post('.add_service_template', - service_id=SERVICE_ONE_ID, - template_type='email', - template_folder_id=PARENT_FOLDER_ID, - _data=data, - _expected_redirect=url_for("main.view_template", - service_id=SERVICE_ONE_ID, - template_id="new%20name",) - ) + client_request.post( + ".add_service_template", + service_id=SERVICE_ONE_ID, + template_type="email", + template_folder_id=PARENT_FOLDER_ID, + _data=data, + _expected_redirect=url_for( + "main.view_template", + service_id=SERVICE_ONE_ID, + template_id="new%20name", + ), + ) mock_create_service_template.assert_called_once_with( - data['name'], - data['template_type'], - data['template_content'], + data["name"], + data["template_type"], + data["template_content"], SERVICE_ONE_ID, - data['subject'], - data['parent_folder_id']) + data["subject"], + data["parent_folder_id"], + ) def test_get_manage_folder_page( @@ -547,28 +569,30 @@ def test_get_manage_folder_page( active_user_with_permissions, service_one, mock_get_template_folders, - mocker + mocker, ): folder_id = str(uuid.uuid4()) mock_get_template_folders.return_value = [ - _folder('folder_two', folder_id, None, [active_user_with_permissions['id']]), + _folder("folder_two", folder_id, None, [active_user_with_permissions["id"]]), ] mocker.patch( - 'app.models.user.Users.client_method', + "app.models.user.Users.client_method", return_value=[active_user_with_permissions], ) page = client_request.get( - 'main.manage_template_folder', - service_id=service_one['id'], + "main.manage_template_folder", + service_id=service_one["id"], template_folder_id=folder_id, _test_page_title=False, ) - assert normalize_spaces(page.select_one('title').text) == ( - 'folder_two – Templates – service one – Notify.gov' + assert normalize_spaces(page.select_one("title").text) == ( + "folder_two – Templates – service one – Notify.gov" + ) + assert page.select_one("input[name=name]")["value"] == "folder_two" + delete_link = page.find("a", string="Delete this folder") + expected_delete_url = "/services/{}/templates/folders/{}/delete".format( + service_one["id"], folder_id ) - assert page.select_one('input[name=name]')['value'] == 'folder_two' - delete_link = page.find('a', string="Delete this folder") - expected_delete_url = "/services/{}/templates/folders/{}/delete".format(service_one['id'], folder_id) assert expected_delete_url in delete_link["href"] @@ -578,41 +602,51 @@ def test_get_manage_folder_viewing_permissions_for_users( service_one, mock_get_template_folders, mock_get_users_by_service, - mocker + mocker, ): folder_id = str(uuid.uuid4()) team_member = create_active_user_view_permissions(with_unique_id=True) team_member_2 = create_active_user_view_permissions(with_unique_id=True) mock_get_template_folders.return_value = [ - _folder('folder_two', folder_id, None, [active_user_with_permissions['id'], team_member_2['id']]), + _folder( + "folder_two", + folder_id, + None, + [active_user_with_permissions["id"], team_member_2["id"]], + ), ] mocker.patch( - 'app.models.user.Users.client_method', + "app.models.user.Users.client_method", return_value=[active_user_with_permissions, team_member, team_member_2], ) page = client_request.get( - 'main.manage_template_folder', - service_id=service_one['id'], + "main.manage_template_folder", + service_id=service_one["id"], template_folder_id=folder_id, _test_page_title=False, ) - assert normalize_spaces(page.select_one('title').text) == ( - 'folder_two – Templates – service one – Notify.gov' + assert normalize_spaces(page.select_one("title").text) == ( + "folder_two – Templates – service one – Notify.gov" ) - form_labels = page.select('legend.usa-legend') - assert normalize_spaces(form_labels[0].text) == "Team members who can see this folder" - checkboxes = page.select('input[name=users_with_permission]') + form_labels = page.select("legend.usa-legend") + assert ( + normalize_spaces(form_labels[0].text) == "Team members who can see this folder" + ) + checkboxes = page.select("input[name=users_with_permission]") assert len(checkboxes) == 2 - assert checkboxes[0]['value'] == team_member['id'] + assert checkboxes[0]["value"] == team_member["id"] assert "checked" not in checkboxes[0].attrs - assert checkboxes[1]['value'] == team_member_2['id'] + assert checkboxes[1]["value"] == team_member_2["id"] assert "checked" in checkboxes[1].attrs - assert "Test User" in page.find_all('label', {'for': 'users_with_permission-1'})[0].text + assert ( + "Test User" + in page.find_all("label", {"for": "users_with_permission-1"})[0].text + ) def test_get_manage_folder_viewing_permissions_for_users_not_visible_when_no_manage_settings_permission( @@ -620,42 +654,48 @@ def test_get_manage_folder_viewing_permissions_for_users_not_visible_when_no_man active_user_with_permissions, service_one, mock_get_template_folders, - mocker + mocker, ): - active_user_with_permissions['permissions'][SERVICE_ONE_ID] = [ - 'send_texts', - 'send_emails', - 'manage_templates', - 'manage_api_keys', - 'view_activity', + active_user_with_permissions["permissions"][SERVICE_ONE_ID] = [ + "send_texts", + "send_emails", + "manage_templates", + "manage_api_keys", + "view_activity", ] folder_id = str(uuid.uuid4()) team_member = create_active_user_view_permissions(with_unique_id=True) team_member_2 = create_active_user_view_permissions(with_unique_id=True) service_one["permissions"] += ["edit_folder_permissions"] mock_get_template_folders.return_value = [ - {'id': folder_id, 'name': 'folder_two', 'parent_id': None, 'users_with_permission': [ - active_user_with_permissions['id'], team_member_2['id'] - ]}, + { + "id": folder_id, + "name": "folder_two", + "parent_id": None, + "users_with_permission": [ + active_user_with_permissions["id"], + team_member_2["id"], + ], + }, ] mocker.patch( - 'app.models.user.Users.client_method', + "app.models.user.Users.client_method", return_value=[active_user_with_permissions, team_member, team_member_2], ) client_request.login(active_user_with_permissions) page = client_request.get( - 'main.manage_template_folder', - service_id=service_one['id'], + "main.manage_template_folder", + service_id=service_one["id"], template_folder_id=folder_id, _test_page_title=False, ) - assert normalize_spaces(page.select_one('title').text) == ( - 'folder_two – Templates – service one – Notify.gov' + assert normalize_spaces(page.select_one("title").text) == ( + "folder_two – Templates – service one – Notify.gov" ) - form_labels = page.select('legend[class=form-label]') + form_labels = page.select("legend[class=form-label]") assert len(form_labels) == 0 - checkboxes = page.select('input[name=users_with_permission]') + checkboxes = page.select("input[name=users_with_permission]") assert len(checkboxes) == 0 @@ -664,37 +704,42 @@ def test_get_manage_folder_viewing_permissions_for_users_not_visible_for_service active_user_with_permissions, service_one, mock_get_template_folders, - mocker + mocker, ): folder_id = str(uuid.uuid4()) service_one["permissions"] += ["edit_folder_permissions"] mock_get_template_folders.return_value = [ - {'id': folder_id, 'name': 'folder_two', 'parent_id': None, 'users_with_permission': [ - active_user_with_permissions['id'] - ]}, + { + "id": folder_id, + "name": "folder_two", + "parent_id": None, + "users_with_permission": [active_user_with_permissions["id"]], + }, ] mocker.patch( - 'app.models.user.Users.client_method', + "app.models.user.Users.client_method", return_value=[active_user_with_permissions], ) page = client_request.get( - 'main.manage_template_folder', - service_id=service_one['id'], + "main.manage_template_folder", + service_id=service_one["id"], template_folder_id=folder_id, _test_page_title=False, ) - assert normalize_spaces(page.select_one('title').text) == ( - 'folder_two – Templates – service one – Notify.gov' + assert normalize_spaces(page.select_one("title").text) == ( + "folder_two – Templates – service one – Notify.gov" ) - form_labels = page.select('legend[class=form-label]') + form_labels = page.select("legend[class=form-label]") assert len(form_labels) == 0 -def test_manage_folder_page_404s(client_request, service_one, mock_get_template_folders): +def test_manage_folder_page_404s( + client_request, service_one, mock_get_template_folders +): client_request.get( - 'main.manage_template_folder', - service_id=service_one['id'], + "main.manage_template_folder", + service_id=service_one["id"], template_folder_id=str(uuid.uuid4()), _expected_status=404, ) @@ -706,35 +751,37 @@ def test_get_manage_folder_page_no_permissions( ): client_request.login(active_user_view_permissions) client_request.get( - 'main.manage_template_folder', + "main.manage_template_folder", service_id=SERVICE_ONE_ID, template_folder_id=PARENT_FOLDER_ID, - _expected_status=403 + _expected_status=403, ) -@pytest.mark.parametrize('endpoint', [ - 'main.manage_template_folder', - 'main.delete_template_folder', -]) +@pytest.mark.parametrize( + "endpoint", + [ + "main.manage_template_folder", + "main.delete_template_folder", + ], +) def test_user_access_denied_to_template_folder_actions_without_folder_permission( client_request, active_user_with_permissions, service_one, mock_get_template_folders, mocker, - endpoint + endpoint, ): - mock = mocker.patch( - 'app.models.service.Service.get_template_folder_with_user_permission_or_403', - side_effect=lambda *args: abort(403) + "app.models.service.Service.get_template_folder_with_user_permission_or_403", + side_effect=lambda *args: abort(403), ) folder_id = str(uuid.uuid4()) client_request.get( endpoint, - service_id=service_one['id'], + service_id=service_one["id"], template_folder_id=folder_id, _expected_status=403, _test_page_title=False, @@ -743,33 +790,31 @@ def test_user_access_denied_to_template_folder_actions_without_folder_permission mock.assert_called_once_with(folder_id, User(active_user_with_permissions)) -@pytest.mark.parametrize('endpoint', [ - 'main.check_notification', - 'main.confirm_redact_template', - 'main.delete_service_template', - 'main.edit_service_template', - 'main.send_messages', - 'main.send_one_off', - 'main.set_sender', - 'main.set_template_sender', -]) +@pytest.mark.parametrize( + "endpoint", + [ + "main.check_notification", + "main.confirm_redact_template", + "main.delete_service_template", + "main.edit_service_template", + "main.send_messages", + "main.send_one_off", + "main.set_sender", + "main.set_template_sender", + ], +) def test_user_access_denied_to_template_actions_without_folder_permission( - client_request, - active_user_with_permissions, - service_one, - mocker, - endpoint + client_request, active_user_with_permissions, service_one, mocker, endpoint ): - mock = mocker.patch( - 'app.models.service.Service.get_template_with_user_permission_or_403', - side_effect=lambda *args: abort(403) + "app.models.service.Service.get_template_with_user_permission_or_403", + side_effect=lambda *args: abort(403), ) template_id = str(uuid.uuid4()) client_request.get( endpoint, - service_id=service_one['id'], + service_id=service_one["id"], template_id=template_id, _expected_status=403, _test_page_title=False, @@ -778,106 +823,137 @@ def test_user_access_denied_to_template_actions_without_folder_permission( mock.assert_called_once_with(template_id, User(active_user_with_permissions)) -def test_rename_folder(client_request, active_user_with_permissions, service_one, mock_get_template_folders, mocker): - mock_update = mocker.patch('app.template_folder_api_client.update_template_folder') +def test_rename_folder( + client_request, + active_user_with_permissions, + service_one, + mock_get_template_folders, + mocker, +): + mock_update = mocker.patch("app.template_folder_api_client.update_template_folder") folder_id = str(uuid.uuid4()) mock_get_template_folders.return_value = [ - _folder('folder_two', folder_id, None, [active_user_with_permissions['id']]) + _folder("folder_two", folder_id, None, [active_user_with_permissions["id"]]) ] mocker.patch( - 'app.models.user.Users.client_method', + "app.models.user.Users.client_method", return_value=[active_user_with_permissions], ) client_request.post( - 'main.manage_template_folder', - service_id=service_one['id'], + "main.manage_template_folder", + service_id=service_one["id"], template_folder_id=folder_id, _data={"name": "new beautiful name", "users_with_permission": []}, - _expected_redirect=url_for("main.choose_template", - service_id=service_one['id'], - template_folder_id=folder_id,) + _expected_redirect=url_for( + "main.choose_template", + service_id=service_one["id"], + template_folder_id=folder_id, + ), ) mock_update.assert_called_once_with( - service_one['id'], + service_one["id"], folder_id, name="new beautiful name", - users_with_permission=None + users_with_permission=None, ) def test_manage_folder_users( - client_request, active_user_with_permissions, service_one, mock_get_template_folders, mocker + client_request, + active_user_with_permissions, + service_one, + mock_get_template_folders, + mocker, ): team_member = create_active_user_view_permissions(with_unique_id=True) - mock_update = mocker.patch('app.template_folder_api_client.update_template_folder') + mock_update = mocker.patch("app.template_folder_api_client.update_template_folder") folder_id = str(uuid.uuid4()) mock_get_template_folders.return_value = [ - _folder('folder_two', folder_id, None, [active_user_with_permissions['id'], team_member['id']]) + _folder( + "folder_two", + folder_id, + None, + [active_user_with_permissions["id"], team_member["id"]], + ) ] mocker.patch( - 'app.models.user.Users.client_method', + "app.models.user.Users.client_method", return_value=[active_user_with_permissions, team_member], ) client_request.post( - 'main.manage_template_folder', - service_id=service_one['id'], + "main.manage_template_folder", + service_id=service_one["id"], template_folder_id=folder_id, _data={"name": "new beautiful name", "users_with_permission": []}, - _expected_redirect=url_for("main.choose_template", - service_id=service_one['id'], - template_folder_id=folder_id,) + _expected_redirect=url_for( + "main.choose_template", + service_id=service_one["id"], + template_folder_id=folder_id, + ), ) mock_update.assert_called_once_with( - service_one['id'], + service_one["id"], folder_id, name="new beautiful name", - users_with_permission=[active_user_with_permissions['id']] + users_with_permission=[active_user_with_permissions["id"]], ) def test_manage_folder_users_doesnt_change_permissions_current_user_cannot_manage_users( - client_request, active_user_with_permissions, service_one, mock_get_template_folders, mocker + client_request, + active_user_with_permissions, + service_one, + mock_get_template_folders, + mocker, ): - active_user_with_permissions['permissions'][SERVICE_ONE_ID] = [ - 'send_texts', - 'send_emails', - 'manage_templates', - 'manage_api_keys', - 'view_activity', + active_user_with_permissions["permissions"][SERVICE_ONE_ID] = [ + "send_texts", + "send_emails", + "manage_templates", + "manage_api_keys", + "view_activity", ] team_member = create_active_user_view_permissions(with_unique_id=True) - mock_update = mocker.patch('app.template_folder_api_client.update_template_folder') + mock_update = mocker.patch("app.template_folder_api_client.update_template_folder") folder_id = str(uuid.uuid4()) mock_get_template_folders.return_value = [ - {'id': folder_id, 'name': 'folder_two', 'parent_id': None, 'users_with_permission': [ - active_user_with_permissions['id'], team_member['id'] - ]} + { + "id": folder_id, + "name": "folder_two", + "parent_id": None, + "users_with_permission": [ + active_user_with_permissions["id"], + team_member["id"], + ], + } ] mocker.patch( - 'app.models.user.Users.client_method', + "app.models.user.Users.client_method", return_value=[active_user_with_permissions, team_member], ) client_request.login(active_user_with_permissions) client_request.post( - 'main.manage_template_folder', - service_id=service_one['id'], + "main.manage_template_folder", + service_id=service_one["id"], template_folder_id=folder_id, _data={"name": "new beautiful name", "users_with_permission": []}, - _expected_redirect=url_for("main.choose_template", - service_id=service_one['id'], - template_folder_id=folder_id,) + _expected_redirect=url_for( + "main.choose_template", + service_id=service_one["id"], + template_folder_id=folder_id, + ), ) mock_update.assert_called_once_with( - service_one['id'], + service_one["id"], folder_id, name="new beautiful name", - users_with_permission=None + users_with_permission=None, ) @@ -888,69 +964,73 @@ def test_delete_template_folder_should_request_confirmation( mocker, mock_get_service_templates_when_no_templates_exist, ): - mocker.patch('app.models.service.Service.active_users', []) + mocker.patch("app.models.service.Service.active_users", []) folder_id = str(uuid.uuid4()) - mock_get_template_folders.side_effect = [[ - _folder('sacrifice', folder_id, None), - ], []] + mock_get_template_folders.side_effect = [ + [ + _folder("sacrifice", folder_id, None), + ], + [], + ] page = client_request.get( - 'main.delete_template_folder', service_id=service_one['id'], + "main.delete_template_folder", + service_id=service_one["id"], template_folder_id=folder_id, _test_page_title=False, ) - assert normalize_spaces(page.select('.banner-dangerous')[0].text) == ( - 'Are you sure you want to delete the ‘sacrifice’ folder? ' - 'Yes, delete' + assert normalize_spaces(page.select(".banner-dangerous")[0].text) == ( + "Are you sure you want to delete the ‘sacrifice’ folder? " "Yes, delete" ) - assert page.select_one('input[name=name]')['value'] == 'sacrifice' + assert page.select_one("input[name=name]")["value"] == "sacrifice" - assert len(page.select('main form')) == 2 - assert len(page.select('main button')) == 2 + assert len(page.select("main form")) == 2 + assert len(page.select("main button")) == 2 - assert 'action' not in page.select('main form')[0] - assert normalize_spaces(page.select('main form button')[0].text) == 'Yes, delete' + assert "action" not in page.select("main form")[0] + assert normalize_spaces(page.select("main form button")[0].text) == "Yes, delete" - assert page.select('main form')[1]['action'] == url_for( - 'main.manage_template_folder', - service_id=service_one['id'], + assert page.select("main form")[1]["action"] == url_for( + "main.manage_template_folder", + service_id=service_one["id"], template_folder_id=folder_id, ) - assert normalize_spaces(page.select('main form button')[1].text) == 'Save' + assert normalize_spaces(page.select("main form button")[1].text) == "Save" def test_delete_template_folder_should_detect_non_empty_folder_on_get( - client_request, - service_one, - mock_get_template_folders, - mocker + client_request, service_one, mock_get_template_folders, mocker ): folder_id = str(uuid.uuid4()) mock_get_template_folders.side_effect = [ [_folder("can't touch me", folder_id, None)], - [] + [], ] mocker.patch( - 'app.service_api_client.get_service_templates', - return_value={'data': [create_template(folder=folder_id)]} + "app.service_api_client.get_service_templates", + return_value={"data": [create_template(folder=folder_id)]}, ) client_request.get( - 'main.delete_template_folder', service_id=service_one['id'], + "main.delete_template_folder", + service_id=service_one["id"], template_folder_id=folder_id, _expected_redirect=url_for( "main.choose_template", template_type="all", - service_id=service_one['id'], + service_id=service_one["id"], template_folder_id=folder_id, ), - _expected_status=302 + _expected_status=302, ) -@pytest.mark.parametrize('parent_folder_id', ( - None, - PARENT_FOLDER_ID, -)) +@pytest.mark.parametrize( + "parent_folder_id", + ( + None, + PARENT_FOLDER_ID, + ), +) def test_delete_folder( client_request, service_one, @@ -959,31 +1039,35 @@ def test_delete_folder( parent_folder_id, mock_get_service_templates_when_no_templates_exist, ): - mock_delete = mocker.patch('app.template_folder_api_client.delete_template_folder') + mock_delete = mocker.patch("app.template_folder_api_client.delete_template_folder") folder_id = str(uuid.uuid4()) - mock_get_template_folders.side_effect = [[ - _folder('sacrifice', folder_id, parent_folder_id), - ], []] + mock_get_template_folders.side_effect = [ + [ + _folder("sacrifice", folder_id, parent_folder_id), + ], + [], + ] client_request.post( - 'main.delete_template_folder', - service_id=service_one['id'], + "main.delete_template_folder", + service_id=service_one["id"], template_folder_id=folder_id, _expected_redirect=url_for( "main.choose_template", - service_id=service_one['id'], + service_id=service_one["id"], template_folder_id=parent_folder_id, - ) + ), ) - mock_delete.assert_called_once_with(service_one['id'], folder_id) + mock_delete.assert_called_once_with(service_one["id"], folder_id) -@pytest.mark.parametrize('user', [ - pytest.param( - create_active_user_with_permissions() - ), -]) +@pytest.mark.parametrize( + "user", + [ + pytest.param(create_active_user_with_permissions()), + ], +) def test_should_show_checkboxes_for_selecting_templates( client_request, mocker, @@ -997,29 +1081,32 @@ def test_should_show_checkboxes_for_selecting_templates( client_request.login(user) page = client_request.get( - 'main.choose_template', + "main.choose_template", service_id=SERVICE_ONE_ID, ) - checkboxes = page.select('input[name=templates_and_folders]') + checkboxes = page.select("input[name=templates_and_folders]") assert len(checkboxes) == 4 - assert checkboxes[0]['value'] == TEMPLATE_ONE_ID - assert checkboxes[0]['id'] == 'templates-or-folder-{}'.format(TEMPLATE_ONE_ID) + assert checkboxes[0]["value"] == TEMPLATE_ONE_ID + assert checkboxes[0]["id"] == "templates-or-folder-{}".format(TEMPLATE_ONE_ID) for index in (1, 2, 3): - assert checkboxes[index]['value'] != TEMPLATE_ONE_ID - assert TEMPLATE_ONE_ID not in checkboxes[index]['id'] + assert checkboxes[index]["value"] != TEMPLATE_ONE_ID + assert TEMPLATE_ONE_ID not in checkboxes[index]["id"] -@pytest.mark.parametrize('user', [ - pytest.param( - create_active_user_view_permissions(), - ), - pytest.param( - create_active_caseworking_user(), - ), -]) +@pytest.mark.parametrize( + "user", + [ + pytest.param( + create_active_user_view_permissions(), + ), + pytest.param( + create_active_caseworking_user(), + ), + ], +) def test_should_show_checkboxes_for_selecting_templates_assertion_error( client_request, mocker, @@ -1034,29 +1121,32 @@ def test_should_show_checkboxes_for_selecting_templates_assertion_error( client_request.login(user) page = client_request.get( - 'main.choose_template', + "main.choose_template", service_id=SERVICE_ONE_ID, ) - checkboxes = page.select('input[name=templates_and_folders]') + checkboxes = page.select("input[name=templates_and_folders]") assert len(checkboxes) == 4 - assert checkboxes[0]['value'] == TEMPLATE_ONE_ID - assert checkboxes[0]['id'] == 'templates-or-folder-{}'.format(TEMPLATE_ONE_ID) + assert checkboxes[0]["value"] == TEMPLATE_ONE_ID + assert checkboxes[0]["id"] == "templates-or-folder-{}".format(TEMPLATE_ONE_ID) for index in (1, 2, 3): - assert checkboxes[index]['value'] != TEMPLATE_ONE_ID - assert TEMPLATE_ONE_ID not in checkboxes[index]['id'] + assert checkboxes[index]["value"] != TEMPLATE_ONE_ID + assert TEMPLATE_ONE_ID not in checkboxes[index]["id"] -@pytest.mark.parametrize('user', [ - create_active_user_view_permissions(), - create_active_caseworking_user(), - pytest.param( - create_active_user_with_permissions(), - marks=pytest.mark.xfail(raises=AssertionError), - ), -]) +@pytest.mark.parametrize( + "user", + [ + create_active_user_view_permissions(), + create_active_caseworking_user(), + pytest.param( + create_active_user_with_permissions(), + marks=pytest.mark.xfail(raises=AssertionError), + ), + ], +) def test_should_not_show_radios_and_buttons_for_move_destination_if_incorrect_permissions( client_request, mocker, @@ -1070,16 +1160,16 @@ def test_should_not_show_radios_and_buttons_for_move_destination_if_incorrect_pe client_request.login(user) page = client_request.get( - 'main.choose_template', + "main.choose_template", service_id=SERVICE_ONE_ID, ) - radios = page.select('input[type=radio]') - radio_div = page.find('div', {'id': 'move_to_folder_radios'}) - assert radios == page.select('input[name=move_to]') + radios = page.select("input[type=radio]") + radio_div = page.find("div", {"id": "move_to_folder_radios"}) + assert radios == page.select("input[name=move_to]") assert not radios assert not radio_div - assert page.find_all('button', {'name': 'operation'}) == [] + assert page.find_all("button", {"name": "operation"}) == [] def test_should_show_radios_and_buttons_for_move_destination_if_correct_permissions( @@ -1091,38 +1181,46 @@ def test_should_show_radios_and_buttons_for_move_destination_if_correct_permissi mock_has_no_jobs, mock_get_no_api_keys, fake_uuid, - active_user_with_permissions + active_user_with_permissions, ): client_request.login(active_user_with_permissions) FOLDER_TWO_ID = str(uuid.uuid4()) FOLDER_ONE_TWO_ID = str(uuid.uuid4()) mock_get_template_folders.return_value = [ - _folder('folder_one', PARENT_FOLDER_ID, None), - _folder('folder_two', FOLDER_TWO_ID, None), - _folder('folder_one_one', CHILD_FOLDER_ID, PARENT_FOLDER_ID), - _folder('folder_one_two', FOLDER_ONE_TWO_ID, PARENT_FOLDER_ID), + _folder("folder_one", PARENT_FOLDER_ID, None), + _folder("folder_two", FOLDER_TWO_ID, None), + _folder("folder_one_one", CHILD_FOLDER_ID, PARENT_FOLDER_ID), + _folder("folder_one_two", FOLDER_ONE_TWO_ID, PARENT_FOLDER_ID), ] page = client_request.get( - 'main.choose_template', + "main.choose_template", service_id=SERVICE_ONE_ID, ) - radios = page.select('#move_to_folder_radios input[type=radio]') - radio_div = page.find('div', {'id': 'move_to_folder_radios'}) - assert radios == page.select('input[name=move_to]') + radios = page.select("#move_to_folder_radios input[type=radio]") + radio_div = page.find("div", {"id": "move_to_folder_radios"}) + assert radios == page.select("input[name=move_to]") - assert [x['value'] for x in radios] == [ - ROOT_FOLDER_ID, PARENT_FOLDER_ID, CHILD_FOLDER_ID, FOLDER_ONE_TWO_ID, FOLDER_TWO_ID, + assert [x["value"] for x in radios] == [ + ROOT_FOLDER_ID, + PARENT_FOLDER_ID, + CHILD_FOLDER_ID, + FOLDER_ONE_TWO_ID, + FOLDER_TWO_ID, ] - assert [x.text.strip() for x in radio_div.select('label')] == [ - 'Templates', 'folder_one', 'folder_one_one', 'folder_one_two', 'folder_two', + assert [x.text.strip() for x in radio_div.select("label")] == [ + "Templates", + "folder_one", + "folder_one_one", + "folder_one_two", + "folder_two", ] - assert set(x['value'] for x in page.find_all('button', {'name': 'operation'})) == { - 'unknown', - 'move-to-existing-folder', - 'move-to-new-folder', - 'add-new-folder', - 'add-new-template', + assert set(x["value"] for x in page.find_all("button", {"name": "operation"})) == { + "unknown", + "move-to-existing-folder", + "move-to-new-folder", + "add-new-folder", + "add-new-template", } @@ -1135,20 +1233,20 @@ def test_move_to_shouldnt_select_a_folder_by_default( mock_has_no_jobs, mock_get_no_api_keys, fake_uuid, - active_user_with_permissions + active_user_with_permissions, ): client_request.login(active_user_with_permissions) FOLDER_TWO_ID = str(uuid.uuid4()) mock_get_template_folders.return_value = [ - _folder('folder_one', PARENT_FOLDER_ID, None), - _folder('folder_two', FOLDER_TWO_ID, None), + _folder("folder_one", PARENT_FOLDER_ID, None), + _folder("folder_two", FOLDER_TWO_ID, None), ] page = client_request.get( - 'main.choose_template', + "main.choose_template", service_id=SERVICE_ONE_ID, ) - checked_radio = page.find('input', attrs={'name': 'move_to', 'checked': 'checked'}) + checked_radio = page.find("input", attrs={"name": "move_to", "checked": "checked"}) assert checked_radio is None @@ -1161,23 +1259,23 @@ def test_should_be_able_to_move_to_existing_folder( ): FOLDER_TWO_ID = str(uuid.uuid4()) mock_get_template_folders.return_value = [ - _folder('folder_one', PARENT_FOLDER_ID, None), - _folder('folder_two', FOLDER_TWO_ID, None), + _folder("folder_one", PARENT_FOLDER_ID, None), + _folder("folder_two", FOLDER_TWO_ID, None), ] client_request.post( - 'main.choose_template', + "main.choose_template", service_id=SERVICE_ONE_ID, _data={ - 'operation': 'move-to-existing-folder', - 'move_to': PARENT_FOLDER_ID, - 'templates_and_folders': [ + "operation": "move-to-existing-folder", + "move_to": PARENT_FOLDER_ID, + "templates_and_folders": [ FOLDER_TWO_ID, TEMPLATE_ONE_ID, ], }, _expected_status=302, _expected_redirect=url_for( - 'main.choose_template', + "main.choose_template", service_id=SERVICE_ONE_ID, _external=True, ), @@ -1190,10 +1288,13 @@ def test_should_be_able_to_move_to_existing_folder( ) -@pytest.mark.parametrize('user, expected_status, expected_called', [ - (create_active_user_view_permissions(), 403, False), - (create_active_user_with_permissions(), 302, True), -]) +@pytest.mark.parametrize( + "user, expected_status, expected_called", + [ + (create_active_user_view_permissions(), 403, False), + (create_active_user_with_permissions(), 302, True), + ], +) def test_should_not_be_able_to_move_to_existing_folder_if_dont_have_permission( client_request, service_one, @@ -1208,21 +1309,21 @@ def test_should_not_be_able_to_move_to_existing_folder_if_dont_have_permission( client_request.login(user) FOLDER_TWO_ID = str(uuid.uuid4()) mock_get_template_folders.return_value = [ - _folder('folder_one', PARENT_FOLDER_ID, None), - _folder('folder_two', FOLDER_TWO_ID, None), + _folder("folder_one", PARENT_FOLDER_ID, None), + _folder("folder_two", FOLDER_TWO_ID, None), ] client_request.post( - 'main.choose_template', + "main.choose_template", service_id=SERVICE_ONE_ID, _data={ - 'operation': 'move-to-existing-folder', - 'move_to': PARENT_FOLDER_ID, - 'templates_and_folders': [ + "operation": "move-to-existing-folder", + "move_to": PARENT_FOLDER_ID, + "templates_and_folders": [ FOLDER_TWO_ID, TEMPLATE_ONE_ID, ], }, - _expected_status=expected_status + _expected_status=expected_status, ) assert mock_move_to_template_folder.called is expected_called @@ -1235,24 +1336,24 @@ def test_move_folder_form_shows_current_folder_hint_when_in_a_folder( mock_get_no_api_keys, ): mock_get_template_folders.return_value = [ - _folder('parent_folder', PARENT_FOLDER_ID, None), - _folder('child_folder', CHILD_FOLDER_ID, PARENT_FOLDER_ID), + _folder("parent_folder", PARENT_FOLDER_ID, None), + _folder("child_folder", CHILD_FOLDER_ID, PARENT_FOLDER_ID), ] page = client_request.get( - 'main.choose_template', + "main.choose_template", service_id=SERVICE_ONE_ID, template_folder_id=PARENT_FOLDER_ID, - _test_page_title=False + _test_page_title=False, ) page.find("input", attrs={"name": "move_to", "value": PARENT_FOLDER_ID}) - move_form_labels = page.find('div', id='move_to_folder_radios').find_all('label') + move_form_labels = page.find("div", id="move_to_folder_radios").find_all("label") assert len(move_form_labels) == 3 - assert normalize_spaces(move_form_labels[0].text) == 'Templates' - assert normalize_spaces(move_form_labels[1].text) == 'parent_folder current folder' - assert normalize_spaces(move_form_labels[2].text) == 'child_folder' + assert normalize_spaces(move_form_labels[0].text) == "Templates" + assert normalize_spaces(move_form_labels[1].text) == "parent_folder current folder" + assert normalize_spaces(move_form_labels[2].text) == "child_folder" def test_move_folder_form_does_not_show_current_folder_hint_at_the_top_level( @@ -1263,23 +1364,21 @@ def test_move_folder_form_does_not_show_current_folder_hint_at_the_top_level( mock_get_no_api_keys, ): mock_get_template_folders.return_value = [ - _folder('parent_folder', PARENT_FOLDER_ID, None), - _folder('child_folder', CHILD_FOLDER_ID, PARENT_FOLDER_ID), + _folder("parent_folder", PARENT_FOLDER_ID, None), + _folder("child_folder", CHILD_FOLDER_ID, PARENT_FOLDER_ID), ] page = client_request.get( - 'main.choose_template', - service_id=SERVICE_ONE_ID, - _test_page_title=False + "main.choose_template", service_id=SERVICE_ONE_ID, _test_page_title=False ) page.find("input", attrs={"name": "move_to", "value": PARENT_FOLDER_ID}) - move_form_labels = page.find('div', id='move_to_folder_radios').find_all('label') + move_form_labels = page.find("div", id="move_to_folder_radios").find_all("label") assert len(move_form_labels) == 3 - assert normalize_spaces(move_form_labels[0].text) == 'Templates' - assert normalize_spaces(move_form_labels[1].text) == 'parent_folder' - assert normalize_spaces(move_form_labels[2].text) == 'child_folder' + assert normalize_spaces(move_form_labels[0].text) == "Templates" + assert normalize_spaces(move_form_labels[1].text) == "parent_folder" + assert normalize_spaces(move_form_labels[2].text) == "child_folder" def test_should_be_able_to_move_a_sub_item( @@ -1292,18 +1391,18 @@ def test_should_be_able_to_move_a_sub_item( ): GRANDCHILD_FOLDER_ID = str(uuid.uuid4()) mock_get_template_folders.return_value = [ - _folder('folder_one', PARENT_FOLDER_ID, None), - _folder('folder_one_one', CHILD_FOLDER_ID, PARENT_FOLDER_ID), - _folder('folder_one_one_one', GRANDCHILD_FOLDER_ID, CHILD_FOLDER_ID), + _folder("folder_one", PARENT_FOLDER_ID, None), + _folder("folder_one_one", CHILD_FOLDER_ID, PARENT_FOLDER_ID), + _folder("folder_one_one_one", GRANDCHILD_FOLDER_ID, CHILD_FOLDER_ID), ] client_request.post( - 'main.choose_template', + "main.choose_template", service_id=SERVICE_ONE_ID, template_folder_id=PARENT_FOLDER_ID, _data={ - 'operation': 'move-to-existing-folder', - 'move_to': ROOT_FOLDER_ID, - 'templates_and_folders': [GRANDCHILD_FOLDER_ID], + "operation": "move-to-existing-folder", + "move_to": ROOT_FOLDER_ID, + "templates_and_folders": [GRANDCHILD_FOLDER_ID], }, _expected_status=302, ) @@ -1315,56 +1414,59 @@ def test_should_be_able_to_move_a_sub_item( ) -@pytest.mark.parametrize('data', [ - # move to existing, but add new folder name given - { - 'operation': 'move-to-existing-folder', - 'templates_and_folders': [], - 'add_new_folder_name': 'foo', - 'move_to': PARENT_FOLDER_ID - }, - # move to existing, but move to new folder name given - { - 'operation': 'move-to-existing-folder', - 'templates_and_folders': [TEMPLATE_ONE_ID], - 'move_to_new_folder_name': 'foo', - 'move_to': PARENT_FOLDER_ID - }, - # move to existing, but no templates to move - { - 'operation': 'move-to-existing-folder', - 'templates_and_folders': [], - 'move_to_new_folder_name': '', - 'move_to': PARENT_FOLDER_ID - }, - # move to new, but nothing selected to move - { - 'operation': 'move-to-new-folder', - 'templates_and_folders': [], - 'move_to_new_folder_name': 'foo', - 'move_to': None - }, - # add a new template, but also select move destination - { - 'operation': 'add-new-template', - 'templates_and_folders': [], - 'move_to_new_folder_name': '', - 'move_to': PARENT_FOLDER_ID, - 'add_template_by_template_type': 'email', - }, - # add a new template, but also move to root folder - { - 'operation': 'add-new-template', - 'templates_and_folders': [], - 'move_to_new_folder_name': '', - 'move_to': ROOT_FOLDER_ID, - 'add_template_by_template_type': 'email', - }, - # add a new template, but don't select anything - { - 'operation': 'add-new-template', - }, -]) +@pytest.mark.parametrize( + "data", + [ + # move to existing, but add new folder name given + { + "operation": "move-to-existing-folder", + "templates_and_folders": [], + "add_new_folder_name": "foo", + "move_to": PARENT_FOLDER_ID, + }, + # move to existing, but move to new folder name given + { + "operation": "move-to-existing-folder", + "templates_and_folders": [TEMPLATE_ONE_ID], + "move_to_new_folder_name": "foo", + "move_to": PARENT_FOLDER_ID, + }, + # move to existing, but no templates to move + { + "operation": "move-to-existing-folder", + "templates_and_folders": [], + "move_to_new_folder_name": "", + "move_to": PARENT_FOLDER_ID, + }, + # move to new, but nothing selected to move + { + "operation": "move-to-new-folder", + "templates_and_folders": [], + "move_to_new_folder_name": "foo", + "move_to": None, + }, + # add a new template, but also select move destination + { + "operation": "add-new-template", + "templates_and_folders": [], + "move_to_new_folder_name": "", + "move_to": PARENT_FOLDER_ID, + "add_template_by_template_type": "email", + }, + # add a new template, but also move to root folder + { + "operation": "add-new-template", + "templates_and_folders": [], + "move_to_new_folder_name": "", + "move_to": ROOT_FOLDER_ID, + "add_template_by_template_type": "email", + }, + # add a new template, but don't select anything + { + "operation": "add-new-template", + }, + ], +) def test_no_action_if_user_fills_in_ambiguous_fields( client_request, service_one, @@ -1376,12 +1478,12 @@ def test_no_action_if_user_fills_in_ambiguous_fields( data, ): mock_get_template_folders.return_value = [ - _folder('parent_folder', PARENT_FOLDER_ID, None), - _folder('folder_two', FOLDER_TWO_ID, None), + _folder("parent_folder", PARENT_FOLDER_ID, None), + _folder("folder_two", FOLDER_TWO_ID, None), ] page = client_request.post( - 'main.choose_template', + "main.choose_template", service_id=SERVICE_ONE_ID, _data=data, _expected_status=200, @@ -1391,15 +1493,15 @@ def test_no_action_if_user_fills_in_ambiguous_fields( assert mock_move_to_template_folder.called is False assert mock_create_template_folder.called is False - assert page.select_one('button[value={}]'.format(data['operation'])) + assert page.select_one("button[value={}]".format(data["operation"])) assert [ # 'email', - 'sms', - 'copy-existing', + "sms", + "copy-existing", ] == [ - radio['value'] - for radio in page.select('#add_new_template_form input[type=radio]') + radio["value"] + for radio in page.select("#add_new_template_form input[type=radio]") ] assert [ @@ -1407,8 +1509,8 @@ def test_no_action_if_user_fills_in_ambiguous_fields( FOLDER_TWO_ID, PARENT_FOLDER_ID, ] == [ - radio['value'] - for radio in page.select('#move_to_folder_radios input[type=radio]') + radio["value"] + for radio in page.select("#move_to_folder_radios input[type=radio]") ] @@ -1418,22 +1520,22 @@ def test_new_folder_is_created_if_only_new_folder_is_filled_out( mock_get_service_templates, mock_get_template_folders, mock_move_to_template_folder, - mock_create_template_folder + mock_create_template_folder, ): data = { - 'move_to_new_folder_name': '', - 'add_new_folder_name': 'new folder', - 'operation': 'add-new-folder' + "move_to_new_folder_name": "", + "add_new_folder_name": "new folder", + "operation": "add-new-folder", } client_request.post( - 'main.choose_template', + "main.choose_template", service_id=SERVICE_ONE_ID, _data=data, _expected_status=302, _expected_redirect=url_for( - 'main.choose_template', - service_id=service_one['id'], + "main.choose_template", + service_id=service_one["id"], template_folder_id=None, _external=True, ), @@ -1441,9 +1543,7 @@ def test_new_folder_is_created_if_only_new_folder_is_filled_out( assert mock_move_to_template_folder.called is False mock_create_template_folder.assert_called_once_with( - SERVICE_ONE_ID, - name='new folder', - parent_id=None + SERVICE_ONE_ID, name="new folder", parent_id=None ) @@ -1458,30 +1558,30 @@ def test_should_be_able_to_move_to_new_folder( new_folder_id = mock_create_template_folder.return_value FOLDER_TWO_ID = str(uuid.uuid4()) mock_get_template_folders.return_value = [ - _folder('parent_folder', PARENT_FOLDER_ID, None), - _folder('folder_two', FOLDER_TWO_ID, None), + _folder("parent_folder", PARENT_FOLDER_ID, None), + _folder("folder_two", FOLDER_TWO_ID, None), ] client_request.post( - 'main.choose_template', + "main.choose_template", service_id=SERVICE_ONE_ID, template_folder_id=None, _data={ - 'operation': 'move-to-new-folder', - 'move_to_new_folder_name': 'new folder', - 'templates_and_folders': [ + "operation": "move-to-new-folder", + "move_to_new_folder_name": "new folder", + "templates_and_folders": [ FOLDER_TWO_ID, TEMPLATE_ONE_ID, ], }, _expected_status=302, - _expected_redirect=url_for('main.choose_template', service_id=SERVICE_ONE_ID, _external=True), + _expected_redirect=url_for( + "main.choose_template", service_id=SERVICE_ONE_ID, _external=True + ), ) mock_create_template_folder.assert_called_once_with( - SERVICE_ONE_ID, - name='new folder', - parent_id=None + SERVICE_ONE_ID, name="new folder", parent_id=None ) mock_move_to_template_folder.assert_called_once_with( service_id=SERVICE_ONE_ID, @@ -1501,9 +1601,9 @@ def test_radio_button_with_no_value_shows_custom_error_message( mock_get_no_api_keys, ): page = client_request.post( - 'main.choose_template', + "main.choose_template", service_id=SERVICE_ONE_ID, - _data={'operation': 'add-new-template'}, + _data={"operation": "add-new-template"}, _expected_status=200, _expected_redirect=None, ) @@ -1511,154 +1611,206 @@ def test_radio_button_with_no_value_shows_custom_error_message( assert mock_move_to_template_folder.called is False assert mock_create_template_folder.called is False - assert page.select_one('span.error-message').text.strip() == 'Select the type of template you want to add' - - -@pytest.mark.parametrize('data, error_msg', [ - # nothing selected when moving - ( - {'operation': 'move-to-new-folder', 'templates_and_folders': [], 'move_to_new_folder_name': 'foo'}, - 'Select at least one template or folder' - ), - ( - {'operation': 'move-to-existing-folder', 'templates_and_folders': [], 'move_to': PARENT_FOLDER_ID}, - 'Select at least one template or folder' - ), - # api error (eg moving folder to itself) - ( - {'operation': 'move-to-existing-folder', 'templates_and_folders': [FOLDER_TWO_ID], 'move_to': FOLDER_TWO_ID}, - 'Some api error msg' + assert ( + page.select_one("span.error-message").text.strip() + == "Select the type of template you want to add" ) -]) + +@pytest.mark.parametrize( + "data, error_msg", + [ + # nothing selected when moving + ( + { + "operation": "move-to-new-folder", + "templates_and_folders": [], + "move_to_new_folder_name": "foo", + }, + "Select at least one template or folder", + ), + ( + { + "operation": "move-to-existing-folder", + "templates_and_folders": [], + "move_to": PARENT_FOLDER_ID, + }, + "Select at least one template or folder", + ), + # api error (eg moving folder to itself) + ( + { + "operation": "move-to-existing-folder", + "templates_and_folders": [FOLDER_TWO_ID], + "move_to": FOLDER_TWO_ID, + }, + "Some api error msg", + ), + ], +) def test_show_custom_error_message( - client_request, - service_one, - mock_get_service_templates, - mock_get_template_folders, - mock_move_to_template_folder, - mock_create_template_folder, - mock_get_no_api_keys, - data, - error_msg, + client_request, + service_one, + mock_get_service_templates, + mock_get_template_folders, + mock_move_to_template_folder, + mock_create_template_folder, + mock_get_no_api_keys, + data, + error_msg, ): mock_get_template_folders.return_value = [ - _folder('folder_one', PARENT_FOLDER_ID, None), - _folder('folder_two', FOLDER_TWO_ID, None), + _folder("folder_one", PARENT_FOLDER_ID, None), + _folder("folder_two", FOLDER_TWO_ID, None), ] - mock_move_to_template_folder.side_effect = HTTPError(message='Some api error msg') + mock_move_to_template_folder.side_effect = HTTPError(message="Some api error msg") page = client_request.post( - 'main.choose_template', + "main.choose_template", service_id=SERVICE_ONE_ID, _data=data, _expected_status=200, _expected_redirect=None, ) - assert page.select_one('div.banner-dangerous').text.strip() == error_msg + assert page.select_one("div.banner-dangerous").text.strip() == error_msg @pytest.mark.parametrize( ( - 'extra_args,' - 'expected_displayed_items, ' - 'expected_items, ' - 'expected_empty_message ' + "extra_args," + "expected_displayed_items, " + "expected_items, " + "expected_empty_message " ), [ ( {}, [ - ['folder_A', 'folder_A', '1 template, 2 folders'], - ['folder_E folder_F folder_G', - 'folder_E', 'folder_F', 'folder_G', - '1 template'], - ['email_template_root', 'email_template_root', 'Email template'], + ["folder_A", "folder_A", "1 template, 2 folders"], + [ + "folder_E folder_F folder_G", + "folder_E", + "folder_F", + "folder_G", + "1 template", + ], + ["email_template_root", "email_template_root", "Email template"], ], [ - ['folder_A', 'folder_A', '1 template, 2 folders'], - ['folder_A folder_C', - 'folder_A', 'folder_C', - '1 template'], - ['folder_A folder_C sms_template_C', - 'folder_A', 'folder_C', 'sms_template_C', - 'Text message template'], - ['folder_A folder_D', - 'folder_A', 'folder_D', - 'Empty'], - ['folder_A sms_template_A', - 'folder_A', 'sms_template_A', - 'Text message template'], - ['folder_E folder_F folder_G', - 'folder_E', 'folder_F', 'folder_G', - '1 template'], - ['folder_E folder_F folder_G email_template_G', - 'folder_E', 'folder_F', 'folder_G', 'email_template_G', - 'Email template'], - ['email_template_root', 'email_template_root', 'Email template'], + ["folder_A", "folder_A", "1 template, 2 folders"], + ["folder_A folder_C", "folder_A", "folder_C", "1 template"], + [ + "folder_A folder_C sms_template_C", + "folder_A", + "folder_C", + "sms_template_C", + "Text message template", + ], + ["folder_A folder_D", "folder_A", "folder_D", "Empty"], + [ + "folder_A sms_template_A", + "folder_A", + "sms_template_A", + "Text message template", + ], + [ + "folder_E folder_F folder_G", + "folder_E", + "folder_F", + "folder_G", + "1 template", + ], + [ + "folder_E folder_F folder_G email_template_G", + "folder_E", + "folder_F", + "folder_G", + "email_template_G", + "Email template", + ], + ["email_template_root", "email_template_root", "Email template"], ], None, ), ( - {'template_type': 'email'}, + {"template_type": "email"}, [ - ['folder_E folder_F folder_G', - 'folder_E', 'folder_F', 'folder_G', - '1 template'], - ['email_template_root', 'email_template_root', 'Email template'], + [ + "folder_E folder_F folder_G", + "folder_E", + "folder_F", + "folder_G", + "1 template", + ], + ["email_template_root", "email_template_root", "Email template"], ], [ - ['folder_E folder_F folder_G', - 'folder_E', 'folder_F', 'folder_G', - '1 template'], - ['folder_E folder_F folder_G email_template_G', - 'folder_E', 'folder_F', 'folder_G', 'email_template_G', - 'Email template'], - ['email_template_root', 'email_template_root', 'Email template'], + [ + "folder_E folder_F folder_G", + "folder_E", + "folder_F", + "folder_G", + "1 template", + ], + [ + "folder_E folder_F folder_G email_template_G", + "folder_E", + "folder_F", + "folder_G", + "email_template_G", + "Email template", + ], + ["email_template_root", "email_template_root", "Email template"], ], None, ), ( - {'template_type': 'sms'}, + {"template_type": "sms"}, [ - ['folder_A', 'folder_A', '1 template, 1 folder'], + ["folder_A", "folder_A", "1 template, 1 folder"], ], [ - ['folder_A', 'folder_A', '1 template, 1 folder'], - ['folder_A folder_C', - 'folder_A', 'folder_C', - '1 template'], - ['folder_A folder_C sms_template_C', - 'folder_A', 'folder_C', 'sms_template_C', - 'Text message template'], - ['folder_A sms_template_A', 'folder_A', 'sms_template_A', - 'Text message template'], + ["folder_A", "folder_A", "1 template, 1 folder"], + ["folder_A folder_C", "folder_A", "folder_C", "1 template"], + [ + "folder_A folder_C sms_template_C", + "folder_A", + "folder_C", + "sms_template_C", + "Text message template", + ], + [ + "folder_A sms_template_A", + "folder_A", + "sms_template_A", + "Text message template", + ], ], None, ), ( { - 'template_type': 'email', - 'template_folder_id': FOLDER_C_ID, + "template_type": "email", + "template_folder_id": FOLDER_C_ID, }, [], [], - 'There are no email templates in this folder', + "There are no email templates in this folder", ), ( - {'template_folder_id': CHILD_FOLDER_ID}, + {"template_folder_id": CHILD_FOLDER_ID}, [], [], - 'This folder is empty', + "This folder is empty", ), ( - {'template_folder_id': CHILD_FOLDER_ID, 'template_type': 'sms'}, + {"template_folder_id": CHILD_FOLDER_ID, "template_type": "sms"}, [], [], - 'This folder is empty', + "This folder is empty", ), - ] + ], ) def test_should_filter_templates_folder_page_based_on_user_permissions( client_request, @@ -1674,51 +1826,61 @@ def test_should_filter_templates_folder_page_based_on_user_permissions( expected_empty_message, ): mock_get_template_folders.return_value = [ - _folder('folder_A', FOLDER_TWO_ID, None, [active_user_with_permissions['id']]), - _folder('folder_B', FOLDER_B_ID, FOLDER_TWO_ID, []), - _folder('folder_C', FOLDER_C_ID, FOLDER_TWO_ID, [active_user_with_permissions['id']]), - _folder('folder_D', None, FOLDER_TWO_ID, [active_user_with_permissions['id']]), - _folder('folder_E', PARENT_FOLDER_ID, users_with_permission=[]), - _folder('folder_F', CHILD_FOLDER_ID, PARENT_FOLDER_ID, []), - _folder('folder_G', GRANDCHILD_FOLDER_ID, CHILD_FOLDER_ID, [active_user_with_permissions['id']]), + _folder("folder_A", FOLDER_TWO_ID, None, [active_user_with_permissions["id"]]), + _folder("folder_B", FOLDER_B_ID, FOLDER_TWO_ID, []), + _folder( + "folder_C", FOLDER_C_ID, FOLDER_TWO_ID, [active_user_with_permissions["id"]] + ), + _folder("folder_D", None, FOLDER_TWO_ID, [active_user_with_permissions["id"]]), + _folder("folder_E", PARENT_FOLDER_ID, users_with_permission=[]), + _folder("folder_F", CHILD_FOLDER_ID, PARENT_FOLDER_ID, []), + _folder( + "folder_G", + GRANDCHILD_FOLDER_ID, + CHILD_FOLDER_ID, + [active_user_with_permissions["id"]], + ), ] mocker.patch( - 'app.service_api_client.get_service_templates', - return_value={'data': [ - _template('email', 'email_template_root'), - _template('sms', 'sms_template_A', parent=FOLDER_TWO_ID), - _template('sms', 'sms_template_C', parent=FOLDER_C_ID), - _template('email', 'email_template_B', parent=FOLDER_B_ID), - _template('email', 'email_template_G', parent=GRANDCHILD_FOLDER_ID), - ]} + "app.service_api_client.get_service_templates", + return_value={ + "data": [ + _template("email", "email_template_root"), + _template("sms", "sms_template_A", parent=FOLDER_TWO_ID), + _template("sms", "sms_template_C", parent=FOLDER_C_ID), + _template("email", "email_template_B", parent=FOLDER_B_ID), + _template("email", "email_template_G", parent=GRANDCHILD_FOLDER_ID), + ] + }, ) page = client_request.get( - 'main.choose_template', + "main.choose_template", service_id=SERVICE_ONE_ID, _test_page_title=False, - **extra_args + **extra_args, ) - displayed_page_items = page.find_all(lambda tag: ( - tag.has_attr('class') - and 'template-list-item' in tag['class'] - and 'template-list-item-hidden-by-default' not in tag['class'] - )) + displayed_page_items = page.find_all( + lambda tag: ( + tag.has_attr("class") + and "template-list-item" in tag["class"] + and "template-list-item-hidden-by-default" not in tag["class"] + ) + ) assert [ [i.strip() for i in e.text.split("\n") if i.strip()] for e in displayed_page_items ] == expected_displayed_items - all_page_items = page.select('.template-list-item') + all_page_items = page.select(".template-list-item") assert [ - [i.strip() for i in e.text.split("\n") if i.strip()] - for e in all_page_items + [i.strip() for i in e.text.split("\n") if i.strip()] for e in all_page_items ] == expected_items if expected_empty_message: - assert normalize_spaces(page.select_one('.template-list-empty').text) == ( + assert normalize_spaces(page.select_one(".template-list-empty").text) == ( expected_empty_message ) else: - assert not page.select('.template-list-empty') + assert not page.select(".template-list-empty") diff --git a/tests/app/main/views/test_template_history.py b/tests/app/main/views/test_template_history.py index f04ad5121..f0627b749 100644 --- a/tests/app/main/views/test_template_history.py +++ b/tests/app/main/views/test_template_history.py @@ -16,26 +16,20 @@ def test_view_template_version( template_id = fake_uuid version = 1 all_versions_link = url_for( - 'main.view_template_versions', - service_id=service_id, - template_id=template_id + "main.view_template_versions", service_id=service_id, template_id=template_id ) page = client_request.get( - '.view_template_version', + ".view_template_version", service_id=service_id, template_id=template_id, version=version, ) template = mock_get_template_version(service_id, template_id, version) - assert api_user_active['name'] in page.text - assert template['data']['content'] in page.text + assert api_user_active["name"] in page.text + assert template["data"]["content"] in page.text assert all_versions_link in str(page) - mock_get_template_version.assert_called_with( - service_id, - template_id, - version - ) + mock_get_template_version.assert_called_with(service_id, template_id, version) def test_view_template_versions( @@ -53,15 +47,12 @@ def test_view_template_versions( service_id = fake_uuid template_id = fake_uuid page = client_request.get( - '.view_template_versions', + ".view_template_versions", service_id=service_id, template_id=template_id, ) versions = mock_get_template_versions(service_id, template_id) - assert api_user_active['name'] in page.text - assert versions['data'][0]['content'] in page.text - mock_get_template_versions.assert_called_with( - service_id, - template_id - ) + assert api_user_active["name"] in page.text + assert versions["data"][0]["content"] in page.text + mock_get_template_versions.assert_called_with(service_id, template_id) diff --git a/tests/app/main/views/test_templates.py b/tests/app/main/views/test_templates.py index 4e9b9367f..370d5b5f3 100644 --- a/tests/app/main/views/test_templates.py +++ b/tests/app/main/views/test_templates.py @@ -27,20 +27,32 @@ from tests.conftest import ( ) -@pytest.mark.parametrize('permissions, expected_message', ( - (['email'], ( - 'Every message starts with a template. You can change it later. ' - 'You need a template before you can send messages.' - )), - (['sms'], ( - 'Every message starts with a template. You can change it later. ' - 'You need a template before you can send messages.' - )), - (['email', 'sms'], ( - 'Every message starts with a template. You can change it later. ' - 'You need a template before you can send messages.' - )), -)) +@pytest.mark.parametrize( + "permissions, expected_message", + ( + ( + ["email"], + ( + "Every message starts with a template. You can change it later. " + "You need a template before you can send messages." + ), + ), + ( + ["sms"], + ( + "Every message starts with a template. You can change it later. " + "You need a template before you can send messages." + ), + ), + ( + ["email", "sms"], + ( + "Every message starts with a template. You can change it later. " + "You need a template before you can send messages." + ), + ), + ), +) def test_should_show_empty_page_when_no_templates( client_request, service_one, @@ -50,22 +62,17 @@ def test_should_show_empty_page_when_no_templates( permissions, expected_message, ): - - service_one['permissions'] = permissions + service_one["permissions"] = permissions page = client_request.get( - 'main.choose_template', - service_id=service_one['id'], + "main.choose_template", + service_id=service_one["id"], ) - assert normalize_spaces(page.select_one('h1').text) == ( - 'Templates' - ) - assert normalize_spaces(page.select_one('main p').text) == ( - expected_message - ) - assert page.select_one('#add_new_folder_form') - assert page.select_one('#add_new_template_form') + assert normalize_spaces(page.select_one("h1").text) == ("Templates") + assert normalize_spaces(page.select_one("main p").text) == (expected_message) + assert page.select_one("#add_new_folder_form") + assert page.select_one("#add_new_template_form") def test_should_show_add_template_form_if_service_has_folder_permission( @@ -76,75 +83,71 @@ def test_should_show_add_template_form_if_service_has_folder_permission( mock_get_no_api_keys, ): page = client_request.get( - 'main.choose_template', - service_id=service_one['id'], + "main.choose_template", + service_id=service_one["id"], ) - assert normalize_spaces(page.select_one('h1').text) == ( - 'Templates' + assert normalize_spaces(page.select_one("h1").text) == ("Templates") + assert normalize_spaces(page.select_one("main p").text) == ( + "Every message starts with a template. You can change it later. " + "You need a template before you can send messages." ) - assert normalize_spaces(page.select_one('main p').text) == ( - 'Every message starts with a template. You can change it later. ' - 'You need a template before you can send messages.' - ) - assert [ - (item['name'], item['value']) for item in page.select('[type=radio]') - ] == [ + assert [(item["name"], item["value"]) for item in page.select("[type=radio]")] == [ # ('add_template_by_template_type', 'email'), - ('add_template_by_template_type', 'sms'), + ("add_template_by_template_type", "sms"), ] - assert not page.select('main a') + assert not page.select("main a") @pytest.mark.parametrize( - 'user, expected_page_title, extra_args, expected_nav_links, expected_templates', + "user, expected_page_title, extra_args, expected_nav_links, expected_templates", [ ( create_active_user_view_permissions(), - 'Templates', + "Templates", {}, - ['Email', 'Text message'], + ["Email", "Text message"], [ - 'sms_template_one', - 'sms_template_two', - 'email_template_one', - 'email_template_two', - ] + "sms_template_one", + "sms_template_two", + "email_template_one", + "email_template_two", + ], ), ( create_active_user_view_permissions(), - 'Templates', - {'template_type': 'sms'}, - ['All', 'Email'], - ['sms_template_one', 'sms_template_two'], + "Templates", + {"template_type": "sms"}, + ["All", "Email"], + ["sms_template_one", "sms_template_two"], ), ( create_active_user_view_permissions(), - 'Templates', - {'template_type': 'email'}, - ['All', 'Text message'], - ['email_template_one', 'email_template_two'], + "Templates", + {"template_type": "email"}, + ["All", "Text message"], + ["email_template_one", "email_template_two"], ), ( create_active_caseworking_user(), - 'Templates', + "Templates", {}, - ['Email', 'Text message'], + ["Email", "Text message"], [ - 'sms_template_one', - 'sms_template_two', - 'email_template_one', - 'email_template_two', + "sms_template_one", + "sms_template_two", + "email_template_one", + "email_template_two", ], ), ( create_active_caseworking_user(), - 'Templates', - {'template_type': 'email'}, - ['All', 'Text message'], - ['email_template_one', 'email_template_two'], + "Templates", + {"template_type": "email"}, + ["All", "Text message"], + ["email_template_one", "email_template_two"], ), - ] + ], ) def test_should_show_page_for_choosing_a_template( client_request, @@ -163,21 +166,19 @@ def test_should_show_page_for_choosing_a_template( client_request.login(user) page = client_request.get( - 'main.choose_template', - service_id=service_one['id'], - **extra_args + "main.choose_template", service_id=service_one["id"], **extra_args ) - assert normalize_spaces(page.select_one('h1').text) == expected_page_title + assert normalize_spaces(page.select_one("h1").text) == expected_page_title - links_in_page = page.select('.pill a:not(.pill-item--selected)') + links_in_page = page.select(".pill a:not(.pill-item--selected)") assert len(links_in_page) == len(expected_nav_links) for index, expected_link in enumerate(expected_nav_links): assert links_in_page[index].text.strip() == expected_link - template_links = page.select('#template-list a, .template-list-item a') + template_links = page.select("#template-list a, .template-list-item a") assert len(template_links) == len(expected_templates) @@ -195,13 +196,13 @@ def test_choose_template_can_pass_through_an_initial_state_to_templates_and_fold mock_get_no_api_keys, ): page = client_request.get( - 'main.choose_template', + "main.choose_template", service_id=SERVICE_ONE_ID, - initial_state='add-new-template' + initial_state="add-new-template", ) - templates_and_folders_form = page.find('form') - assert templates_and_folders_form['data-prev-state'] == 'add-new-template' + templates_and_folders_form = page.find("form") + assert templates_and_folders_form["data-prev-state"] == "add-new-template" def test_should_not_show_template_nav_if_only_one_type_of_template( @@ -210,13 +211,12 @@ def test_should_not_show_template_nav_if_only_one_type_of_template( mock_get_service_templates_with_only_one_template, mock_get_no_api_keys, ): - page = client_request.get( - 'main.choose_template', + "main.choose_template", service_id=SERVICE_ONE_ID, ) - assert not page.select('.pill') + assert not page.select(".pill") def test_should_not_show_live_search_if_list_of_templates_fits_onscreen( @@ -225,13 +225,12 @@ def test_should_not_show_live_search_if_list_of_templates_fits_onscreen( mock_get_service_templates, mock_get_no_api_keys, ): - page = client_request.get( - 'main.choose_template', + "main.choose_template", service_id=SERVICE_ONE_ID, ) - assert not page.select('.live-search') + assert not page.select(".live-search") def test_should_show_live_search_if_list_of_templates_taller_than_screen( @@ -240,21 +239,22 @@ def test_should_show_live_search_if_list_of_templates_taller_than_screen( mock_get_more_service_templates_than_can_fit_onscreen, mock_get_no_api_keys, ): - page = client_request.get( - 'main.choose_template', + "main.choose_template", service_id=SERVICE_ONE_ID, ) - search = page.select_one('.live-search') + search = page.select_one(".live-search") - assert search['data-module'] == 'live-search' - assert search['data-targets'] == '#template-list .template-list-item' - assert normalize_spaces(search.select_one('label').text) == ( - 'Search by name' + assert search["data-module"] == "live-search" + assert search["data-targets"] == "#template-list .template-list-item" + assert normalize_spaces(search.select_one("label").text) == ("Search by name") + + assert ( + len(page.select(search["data-targets"])) + == len(page.select("#template-list .usa-checkbox")) + == 20 ) - assert len(page.select(search['data-targets'])) == len(page.select('#template-list .usa-checkbox')) == 20 - def test_should_label_search_by_id_for_services_with_api_keys( client_request, @@ -263,11 +263,11 @@ def test_should_label_search_by_id_for_services_with_api_keys( mock_get_api_keys, ): page = client_request.get( - 'main.choose_template', + "main.choose_template", service_id=SERVICE_ONE_ID, ) - assert normalize_spaces(page.select_one('.live-search label').text) == ( - 'Search by name or ID' + assert normalize_spaces(page.select_one(".live-search label").text) == ( + "Search by name or ID" ) @@ -277,48 +277,58 @@ def test_should_show_live_search_if_service_has_lots_of_folders( mock_get_service_templates, # returns 4 templates mock_get_no_api_keys, ): - mock_get_template_folders.return_value = [ - _folder('one', PARENT_FOLDER_ID), - _folder('two', None, parent=PARENT_FOLDER_ID), - _folder('three', None, parent=PARENT_FOLDER_ID), - _folder('four', None, parent=PARENT_FOLDER_ID), + _folder("one", PARENT_FOLDER_ID), + _folder("two", None, parent=PARENT_FOLDER_ID), + _folder("three", None, parent=PARENT_FOLDER_ID), + _folder("four", None, parent=PARENT_FOLDER_ID), ] page = client_request.get( - 'main.choose_template', + "main.choose_template", service_id=SERVICE_ONE_ID, ) - count_of_templates_and_folders = len(page.select('#template-list .usa-checkbox')) - count_of_folders = len(page.select('.template-list-folder:first-of-type')) + count_of_templates_and_folders = len(page.select("#template-list .usa-checkbox")) + count_of_folders = len(page.select(".template-list-folder:first-of-type")) count_of_templates = count_of_templates_and_folders - count_of_folders - assert len(page.select('.live-search')) == 1 + assert len(page.select(".live-search")) == 1 assert count_of_folders == 4 assert count_of_templates == 4 -@pytest.mark.parametrize('service_permissions, expected_values, expected_labels', ( - pytest.param(['email', 'sms'], [ - # 'email', - 'sms', - 'copy-existing', - ], [ - # 'Email', - 'Text message', - 'Copy an existing template', - ]), - pytest.param(['email', 'sms'], [ - # 'email', - 'sms', - 'copy-existing', - ], [ - # 'Email', - 'Text message', - 'Copy an existing template', - ]), -)) +@pytest.mark.parametrize( + "service_permissions, expected_values, expected_labels", + ( + pytest.param( + ["email", "sms"], + [ + # 'email', + "sms", + "copy-existing", + ], + [ + # 'Email', + "Text message", + "Copy an existing template", + ], + ), + pytest.param( + ["email", "sms"], + [ + # 'email', + "sms", + "copy-existing", + ], + [ + # 'Email', + "Text message", + "Copy an existing template", + ], + ), + ), +) def test_should_show_new_template_choices_if_service_has_folder_permission( client_request, service_one, @@ -329,32 +339,37 @@ def test_should_show_new_template_choices_if_service_has_folder_permission( expected_values, expected_labels, ): - service_one['permissions'] = service_permissions + service_one["permissions"] = service_permissions page = client_request.get( - 'main.choose_template', + "main.choose_template", service_id=SERVICE_ONE_ID, ) - if not page.select('#add_new_template_form'): + if not page.select("#add_new_template_form"): raise ElementNotFound() - assert normalize_spaces(page.select_one('#add_new_template_form fieldset legend').text) == ( - 'New template' - ) + assert normalize_spaces( + page.select_one("#add_new_template_form fieldset legend").text + ) == ("New template") assert [ - choice['value'] for choice in page.select('#add_new_template_form input[type=radio]') + choice["value"] + for choice in page.select("#add_new_template_form input[type=radio]") ] == expected_values assert [ - normalize_spaces(choice.text) for choice in page.select('#add_new_template_form label') + normalize_spaces(choice.text) + for choice in page.select("#add_new_template_form label") ] == expected_labels -@pytest.mark.parametrize("permissions,are_data_attrs_added", [ - (['sms'], True), - (['email'], True), - (['sms', 'email'], False), -]) +@pytest.mark.parametrize( + "permissions,are_data_attrs_added", + [ + (["sms"], True), + (["email"], True), + (["sms", "email"], False), + ], +) def test_should_add_data_attributes_for_services_that_only_allow_one_type_of_notifications( client_request, service_one, @@ -362,24 +377,30 @@ def test_should_add_data_attributes_for_services_that_only_allow_one_type_of_not mock_get_template_folders, mock_get_no_api_keys, permissions, - are_data_attrs_added + are_data_attrs_added, ): - service_one['permissions'] = permissions + service_one["permissions"] = permissions page = client_request.get( - 'main.choose_template', + "main.choose_template", service_id=SERVICE_ONE_ID, ) - if not page.select('#add_new_template_form'): + if not page.select("#add_new_template_form"): raise ElementNotFound() if are_data_attrs_added: - assert page.find(id='add_new_template_form').attrs['data-channel'] == permissions[0] - assert page.find(id='add_new_template_form').attrs['data-service'] == SERVICE_ONE_ID + assert ( + page.find(id="add_new_template_form").attrs["data-channel"] + == permissions[0] + ) + assert ( + page.find(id="add_new_template_form").attrs["data-service"] + == SERVICE_ONE_ID + ) else: - assert page.find(id='add_new_template_form').attrs.get('data-channel') is None - assert page.find(id='add_new_template_form').attrs.get('data-service') is None + assert page.find(id="add_new_template_form").attrs.get("data-channel") is None + assert page.find(id="add_new_template_form").attrs.get("data-service") is None def test_should_show_page_for_one_template( @@ -389,40 +410,34 @@ def test_should_show_page_for_one_template( ): template_id = fake_uuid page = client_request.get( - '.edit_service_template', + ".edit_service_template", service_id=SERVICE_ONE_ID, template_id=template_id, ) - assert page.select_one('input[type=text]')['value'] == "Two week reminder" + assert page.select_one("input[type=text]")["value"] == "Two week reminder" assert "Template <em>content</em> with & entity" in str( - page.select_one('textarea') + page.select_one("textarea") ) - assert page.select_one('textarea')['data-module'] == 'enhanced-textbox' - assert page.select_one('textarea')['data-highlight-placeholders'] == 'true' - assert "priority" not in str(page.select_one('main')) + assert page.select_one("textarea")["data-module"] == "enhanced-textbox" + assert page.select_one("textarea")["data-highlight-placeholders"] == "true" + assert "priority" not in str(page.select_one("main")) assert ( - page.select_one('[data-module=update-status]')['data-target'] - ) == ( - page.select_one('textarea')['id'] - ) == ( - 'template_content' + (page.select_one("[data-module=update-status]")["data-target"]) + == (page.select_one("textarea")["id"]) + == ("template_content") ) assert ( - page.select_one('[data-module=update-status]')['data-updates-url'] + page.select_one("[data-module=update-status]")["data-updates-url"] ) == url_for( - '.count_content_length', + ".count_content_length", service_id=SERVICE_ONE_ID, - template_type='sms', + template_type="sms", ) - assert ( - page.select_one('[data-module=update-status]')['aria-live'] - ) == ( - 'polite' - ) + assert (page.select_one("[data-module=update-status]")["aria-live"]) == ("polite") mock_get_service_template.assert_called_with(SERVICE_ONE_ID, template_id, None) @@ -435,23 +450,22 @@ def test_caseworker_redirected_to_set_sender_for_one_off( fake_uuid, active_caseworking_user, ): - client_request.login(active_caseworking_user) client_request.get( - 'main.view_template', + "main.view_template", service_id=SERVICE_ONE_ID, template_id=fake_uuid, _expected_status=302, _expected_redirect=url_for( - 'main.set_sender', + "main.set_sender", service_id=SERVICE_ONE_ID, template_id=fake_uuid, ), ) -@freeze_time('2020-01-01 15:00') +@freeze_time("2020-01-01 15:00") def test_caseworker_sees_template_page_if_template_is_deleted( client_request, mock_get_deleted_template, @@ -459,20 +473,25 @@ def test_caseworker_sees_template_page_if_template_is_deleted( mocker, active_caseworking_user, ): - - mocker.patch('app.user_api_client.get_user', return_value=active_caseworking_user) + mocker.patch("app.user_api_client.get_user", return_value=active_caseworking_user) template_id = fake_uuid page = client_request.get( - '.view_template', + ".view_template", service_id=SERVICE_ONE_ID, template_id=template_id, _test_page_title=False, ) content = str(page) - assert url_for("main.send_one_off", service_id=SERVICE_ONE_ID, template_id=fake_uuid) not in content - assert page.select('p.hint')[0].text.strip() == 'This template was deleted today at 15:00 UTC.' + assert ( + url_for("main.send_one_off", service_id=SERVICE_ONE_ID, template_id=fake_uuid) + not in content + ) + assert ( + page.select("p.hint")[0].text.strip() + == "This template was deleted today at 15:00 UTC." + ) mock_get_deleted_template.assert_called_with(SERVICE_ONE_ID, template_id, None) @@ -485,51 +504,54 @@ def test_user_with_only_send_and_view_redirected_to_set_sender_for_one_off( mocker, fake_uuid, ): - active_user_with_permissions['permissions'][SERVICE_ONE_ID] = [ - 'send_messages', - 'view_activity', + active_user_with_permissions["permissions"][SERVICE_ONE_ID] = [ + "send_messages", + "view_activity", ] client_request.login(active_user_with_permissions) client_request.get( - 'main.view_template', + "main.view_template", service_id=SERVICE_ONE_ID, template_id=fake_uuid, _expected_status=302, _expected_redirect=url_for( - 'main.set_sender', + "main.set_sender", service_id=SERVICE_ONE_ID, template_id=fake_uuid, ), ) -@pytest.mark.parametrize('permissions, links_to_be_shown, permissions_warning_to_be_shown', [ - ( - ['view_activity'], - [], - 'If you need to send this text message or edit this template, contact your manager.' - ), - ( - ['manage_api_keys'], - [], - None, - ), - ( - ['manage_templates'], - [ - ('.edit_service_template', 'Edit'), - ], - None, - ), - ( - ['send_messages', 'manage_templates'], - [ - ('.set_sender', 'Prepare to send a message using this template'), - ('.edit_service_template', 'Edit'), - ], - None, - ), -]) +@pytest.mark.parametrize( + "permissions, links_to_be_shown, permissions_warning_to_be_shown", + [ + ( + ["view_activity"], + [], + "If you need to send this text message or edit this template, contact your manager.", + ), + ( + ["manage_api_keys"], + [], + None, + ), + ( + ["manage_templates"], + [ + (".edit_service_template", "Edit"), + ], + None, + ), + ( + ["send_messages", "manage_templates"], + [ + (".set_sender", "Prepare to send a message using this template"), + (".edit_service_template", "Edit"), + ], + None, + ), + ], +) def test_should_be_able_to_view_a_template_with_links( client_request, mock_get_service_template, @@ -540,41 +562,46 @@ def test_should_be_able_to_view_a_template_with_links( links_to_be_shown, permissions_warning_to_be_shown, ): - active_user_with_permissions['permissions'][SERVICE_ONE_ID] = permissions + ['view_activity'] + active_user_with_permissions["permissions"][SERVICE_ONE_ID] = permissions + [ + "view_activity" + ] client_request.login(active_user_with_permissions) page = client_request.get( - '.view_template', + ".view_template", service_id=SERVICE_ONE_ID, template_id=fake_uuid, _test_page_title=False, ) - assert normalize_spaces(page.select_one('h1').text) == ( - 'Review your message' - ) - assert normalize_spaces(page.select_one('title').text) == ( - 'Two week reminder – Templates – service one – Notify.gov' + assert normalize_spaces(page.select_one("h1").text) == ("Review your message") + assert normalize_spaces(page.select_one("title").text) == ( + "Two week reminder – Templates – service one – Notify.gov" ) assert [ - (link['href'], normalize_spaces(link.text)) - for link in page.select('.usa-pill-separate-item') + (link["href"], normalize_spaces(link.text)) + for link in page.select(".usa-pill-separate-item") ] == [ - (url_for( - endpoint, - service_id=SERVICE_ONE_ID, - template_id=fake_uuid, - ), text) + ( + url_for( + endpoint, + service_id=SERVICE_ONE_ID, + template_id=fake_uuid, + ), + text, + ) for endpoint, text in links_to_be_shown ] - assert normalize_spaces(page.select_one('main p').text) == ( - 'To: phone number' or permissions_warning_to_be_shown + assert normalize_spaces(page.select_one("main p").text) == ( + "To: phone number" or permissions_warning_to_be_shown ) -@pytest.mark.skip(reason="Hiding the copy-to-clipboard widget until API access is opened up") +@pytest.mark.skip( + reason="Hiding the copy-to-clipboard widget until API access is opened up" +) def test_should_show_template_id_on_template_page( client_request, mock_get_service_template, @@ -582,12 +609,12 @@ def test_should_show_template_id_on_template_page( fake_uuid, ): page = client_request.get( - '.view_template', + ".view_template", service_id=SERVICE_ONE_ID, template_id=fake_uuid, _test_page_title=False, ) - assert fake_uuid in page.select('.copy-to-clipboard__value')[0].text + assert fake_uuid in page.select(".copy-to-clipboard__value")[0].text def test_should_show_sms_template_with_downgraded_unicode_characters( @@ -597,16 +624,20 @@ def test_should_show_sms_template_with_downgraded_unicode_characters( mock_get_template_folders, fake_uuid, ): - msg = 'here:\tare some “fancy quotes” and zero\u200Bwidth\u200Bspaces' + msg = "here:\tare some “fancy quotes” and zero\u200Bwidth\u200Bspaces" rendered_msg = 'here: are some "fancy quotes" and zerowidthspaces' mocker.patch( - 'app.service_api_client.get_service_template', - return_value={'data': template_json(service_one['id'], fake_uuid, type_='sms', content=msg)} + "app.service_api_client.get_service_template", + return_value={ + "data": template_json( + service_one["id"], fake_uuid, type_="sms", content=msg + ) + }, ) page = client_request.get( - '.view_template', + ".view_template", service_id=SERVICE_ONE_ID, template_id=fake_uuid, _test_page_title=False, @@ -615,10 +646,9 @@ def test_should_show_sms_template_with_downgraded_unicode_characters( assert rendered_msg in page.text -@pytest.mark.parametrize('prefix_sms', [ - True, - pytest.param(False, marks=pytest.mark.xfail()) -]) +@pytest.mark.parametrize( + "prefix_sms", [True, pytest.param(False, marks=pytest.mark.xfail())] +) def test_should_show_message_with_prefix_hint_if_enabled_for_service( client_request, mocker, @@ -626,17 +656,17 @@ def test_should_show_message_with_prefix_hint_if_enabled_for_service( mock_get_users_by_service, service_one, fake_uuid, - prefix_sms + prefix_sms, ): - service_one['prefix_sms'] = prefix_sms + service_one["prefix_sms"] = prefix_sms page = client_request.get( - '.edit_service_template', - service_id=service_one['id'], + ".edit_service_template", + service_id=service_one["id"], template_id=fake_uuid, ) - assert 'Your service name will be added to the start of your message' in page.text + assert "Your service name will be added to the start of your message" in page.text def test_should_show_page_template_with_priority_select_if_platform_admin( @@ -647,19 +677,23 @@ def test_should_show_page_template_with_priority_select_if_platform_admin( service_one, fake_uuid, ): - mocker.patch('app.user_api_client.get_users_for_service', return_value=[platform_admin_user]) + mocker.patch( + "app.user_api_client.get_users_for_service", return_value=[platform_admin_user] + ) template_id = fake_uuid client_request.login(platform_admin_user) page = client_request.get( - '.edit_service_template', - service_id=service_one['id'], + ".edit_service_template", + service_id=service_one["id"], template_id=template_id, ) - assert page.select_one('input[name=name]')['value'] == "Two week reminder" - assert "Template <em>content</em> with & entity" in str(page.select_one('textarea')) + assert page.select_one("input[name=name]")["value"] == "Two week reminder" + assert "Template <em>content</em> with & entity" in str( + page.select_one("textarea") + ) assert "Use priority queue?" not in page.text - mock_get_service_template.assert_called_with(service_one['id'], template_id, None) + mock_get_service_template.assert_called_with(service_one["id"], template_id, None) def test_choosing_to_copy_redirects( @@ -669,15 +703,15 @@ def test_choosing_to_copy_redirects( mock_get_template_folders, ): client_request.post( - 'main.choose_template', + "main.choose_template", service_id=SERVICE_ONE_ID, _data={ - 'operation': 'add-new-template', - 'add_template_by_template_type': 'copy-existing' + "operation": "add-new-template", + "add_template_by_template_type": "copy-existing", }, _expected_status=302, _expected_redirect=url_for( - 'main.choose_template_to_copy', + "main.choose_template_to_copy", service_id=SERVICE_ONE_ID, ), ) @@ -691,74 +725,44 @@ def test_choose_a_template_to_copy( mock_get_just_services_for_user, ): page = client_request.get( - 'main.choose_template_to_copy', + "main.choose_template_to_copy", service_id=SERVICE_ONE_ID, ) - assert page.select('.folder-heading') == [] + assert page.select(".folder-heading") == [] expected = [ - ( - 'Service 1 ' - '4 templates' - ), - ( - 'Service 1 sms_template_one ' - 'Text message template' - ), - ( - 'Service 1 sms_template_two ' - 'Text message template' - ), - ( - 'Service 1 email_template_one ' - 'Email template' - ), - ( - 'Service 1 email_template_two ' - 'Email template' - ), - ( - 'Service 2 ' - '4 templates' - ), - ( - 'Service 2 sms_template_one ' - 'Text message template' - ), - ( - 'Service 2 sms_template_two ' - 'Text message template' - ), - ( - 'Service 2 email_template_one ' - 'Email template' - ), - ( - 'Service 2 email_template_two ' - 'Email template' - ), + ("Service 1 " "4 templates"), + ("Service 1 sms_template_one " "Text message template"), + ("Service 1 sms_template_two " "Text message template"), + ("Service 1 email_template_one " "Email template"), + ("Service 1 email_template_two " "Email template"), + ("Service 2 " "4 templates"), + ("Service 2 sms_template_one " "Text message template"), + ("Service 2 sms_template_two " "Text message template"), + ("Service 2 email_template_one " "Email template"), + ("Service 2 email_template_two " "Email template"), ] - actual = page.select('.template-list-item') + actual = page.select(".template-list-item") assert len(actual) == len(expected) for actual, expected in zip(actual, expected): # noqa: B020 assert normalize_spaces(actual.text) == expected - links = page.select('main nav a') - assert links[0]['href'] == url_for( - 'main.choose_template_to_copy', + links = page.select("main nav a") + assert links[0]["href"] == url_for( + "main.choose_template_to_copy", service_id=SERVICE_ONE_ID, from_service=SERVICE_TWO_ID, ) - assert links[1]['href'] == url_for( - 'main.choose_template_to_copy', + assert links[1]["href"] == url_for( + "main.choose_template_to_copy", service_id=SERVICE_ONE_ID, from_service=SERVICE_TWO_ID, ) - assert links[2]['href'] == url_for( - 'main.copy_template', + assert links[2]["href"] == url_for( + "main.copy_template", service_id=SERVICE_ONE_ID, template_id=TEMPLATE_ONE_ID, from_service=SERVICE_TWO_ID, @@ -773,39 +777,27 @@ def test_choose_a_template_to_copy_when_user_has_one_service( mock_get_empty_organizations_and_one_service_for_user, ): page = client_request.get( - 'main.choose_template_to_copy', + "main.choose_template_to_copy", service_id=SERVICE_ONE_ID, ) - assert page.select('.folder-heading') == [] + assert page.select(".folder-heading") == [] expected = [ - ( - 'sms_template_one ' - 'Text message template' - ), - ( - 'sms_template_two ' - 'Text message template' - ), - ( - 'email_template_one ' - 'Email template' - ), - ( - 'email_template_two ' - 'Email template' - ), + ("sms_template_one " "Text message template"), + ("sms_template_two " "Text message template"), + ("email_template_one " "Email template"), + ("email_template_two " "Email template"), ] - actual = page.select('.template-list-item') + actual = page.select(".template-list-item") assert len(actual) == len(expected) for actual, expected in zip(actual, expected): # noqa: B020 assert normalize_spaces(actual.text) == expected - assert page.select('main nav a')[0]['href'] == url_for( - 'main.copy_template', + assert page.select("main nav a")[0]["href"] == url_for( + "main.copy_template", service_id=SERVICE_ONE_ID, template_id=TEMPLATE_ONE_ID, from_service=SERVICE_TWO_ID, @@ -820,125 +812,115 @@ def test_choose_a_template_to_copy_from_folder_within_service( mock_get_no_api_keys, ): mock_get_template_folders.return_value = [ - _folder('Parent folder', PARENT_FOLDER_ID), - _folder('Child folder empty', CHILD_FOLDER_ID, parent=PARENT_FOLDER_ID), - _folder('Child folder non-empty', FOLDER_TWO_ID, parent=PARENT_FOLDER_ID), + _folder("Parent folder", PARENT_FOLDER_ID), + _folder("Child folder empty", CHILD_FOLDER_ID, parent=PARENT_FOLDER_ID), + _folder("Child folder non-empty", FOLDER_TWO_ID, parent=PARENT_FOLDER_ID), ] mocker.patch( - 'app.service_api_client.get_service_templates', - return_value={'data': [ - _template( - 'sms', - 'Should not appear in list (at service root)', - ), - _template( - 'sms', - 'Should appear in list (at same level)', - parent=PARENT_FOLDER_ID, - ), - _template( - 'sms', - 'Should appear in list (nested)', - parent=FOLDER_TWO_ID, - template_id=TEMPLATE_ONE_ID, - ), - ]} + "app.service_api_client.get_service_templates", + return_value={ + "data": [ + _template( + "sms", + "Should not appear in list (at service root)", + ), + _template( + "sms", + "Should appear in list (at same level)", + parent=PARENT_FOLDER_ID, + ), + _template( + "sms", + "Should appear in list (nested)", + parent=FOLDER_TWO_ID, + template_id=TEMPLATE_ONE_ID, + ), + ] + }, ) page = client_request.get( - 'main.choose_template_to_copy', + "main.choose_template_to_copy", service_id=SERVICE_ONE_ID, from_service=SERVICE_ONE_ID, from_folder=PARENT_FOLDER_ID, ) - assert normalize_spaces(page.select_one('.folder-heading').text) == ( - 'service one Parent folder' + assert normalize_spaces(page.select_one(".folder-heading").text) == ( + "service one Parent folder" ) - breadcrumb_links = page.select('.folder-heading a') + breadcrumb_links = page.select(".folder-heading a") assert len(breadcrumb_links) == 1 - assert breadcrumb_links[0]['href'] == url_for( - 'main.choose_template_to_copy', + assert breadcrumb_links[0]["href"] == url_for( + "main.choose_template_to_copy", service_id=SERVICE_ONE_ID, from_service=SERVICE_ONE_ID, ) expected = [ + ("Child folder empty " "Empty"), + ("Child folder non-empty " "1 template"), ( - 'Child folder empty ' - 'Empty' - ), - ( - 'Child folder non-empty ' - '1 template' - ), - ( - 'Child folder non-empty Should appear in list (nested) ' - 'Text message template' - ), - ( - 'Should appear in list (at same level) ' - 'Text message template' + "Child folder non-empty Should appear in list (nested) " + "Text message template" ), + ("Should appear in list (at same level) " "Text message template"), ] - actual = page.select('.template-list-item') + actual = page.select(".template-list-item") assert len(actual) == len(expected) for actual, expected in zip(actual, expected): # noqa: B020 assert normalize_spaces(actual.text) == expected - links = page.select('main nav a') - assert links[0]['href'] == url_for( - 'main.choose_template_to_copy', + links = page.select("main nav a") + assert links[0]["href"] == url_for( + "main.choose_template_to_copy", service_id=SERVICE_ONE_ID, from_service=SERVICE_ONE_ID, from_folder=CHILD_FOLDER_ID, ) - assert links[1]['href'] == url_for( - 'main.choose_template_to_copy', + assert links[1]["href"] == url_for( + "main.choose_template_to_copy", service_id=SERVICE_ONE_ID, from_service=SERVICE_ONE_ID, from_folder=FOLDER_TWO_ID, ) - assert links[2]['href'] == url_for( - 'main.choose_template_to_copy', + assert links[2]["href"] == url_for( + "main.choose_template_to_copy", service_id=SERVICE_ONE_ID, from_folder=FOLDER_TWO_ID, ) - assert links[3]['href'] == url_for( - 'main.copy_template', + assert links[3]["href"] == url_for( + "main.copy_template", service_id=SERVICE_ONE_ID, template_id=TEMPLATE_ONE_ID, from_service=SERVICE_ONE_ID, ) -@pytest.mark.parametrize('existing_template_names, expected_name', ( +@pytest.mark.parametrize( + "existing_template_names, expected_name", ( - ['Two week reminder'], - 'Two week reminder (copy)' + (["Two week reminder"], "Two week reminder (copy)"), + (["Two week reminder (copy)"], "Two week reminder (copy 2)"), + ( + ["Two week reminder", "Two week reminder (copy)"], + "Two week reminder (copy 2)", + ), + ( + ["Two week reminder (copy 8)", "Two week reminder (copy 9)"], + "Two week reminder (copy 10)", + ), + ( + ["Two week reminder (copy)", "Two week reminder (copy 9)"], + "Two week reminder (copy 10)", + ), + ( + ["Two week reminder (copy)", "Two week reminder (copy 10)"], + "Two week reminder (copy 2)", + ), ), - ( - ['Two week reminder (copy)'], - 'Two week reminder (copy 2)' - ), - ( - ['Two week reminder', 'Two week reminder (copy)'], - 'Two week reminder (copy 2)' - ), - ( - ['Two week reminder (copy 8)', 'Two week reminder (copy 9)'], - 'Two week reminder (copy 10)' - ), - ( - ['Two week reminder (copy)', 'Two week reminder (copy 9)'], - 'Two week reminder (copy 10)' - ), - ( - ['Two week reminder (copy)', 'Two week reminder (copy 10)'], - 'Two week reminder (copy 2)' - ), -)) +) def test_load_edit_template_with_copy_of_template( client_request, active_user_with_permission_to_two_services, @@ -948,26 +930,24 @@ def test_load_edit_template_with_copy_of_template( existing_template_names, expected_name, ): - mock_get_service_templates.side_effect = lambda service_id: {'data': [ - {'name': existing_template_name, 'template_type': 'sms'} - for existing_template_name in existing_template_names - ]} + mock_get_service_templates.side_effect = lambda service_id: { + "data": [ + {"name": existing_template_name, "template_type": "sms"} + for existing_template_name in existing_template_names + ] + } client_request.login(active_user_with_permission_to_two_services) page = client_request.get( - 'main.copy_template', + "main.copy_template", service_id=SERVICE_ONE_ID, template_id=TEMPLATE_ONE_ID, from_service=SERVICE_TWO_ID, ) - assert page.select_one('form')['method'] == 'post' + assert page.select_one("form")["method"] == "post" - assert page.select_one('input')['value'] == ( - expected_name - ) - assert page.select_one('textarea').text == ( - '\r\nYour ((thing)) is due soon' - ) + assert page.select_one("input")["value"] == (expected_name) + assert page.select_one("textarea").text == ("\r\nYour ((thing)) is due soon") mock_get_service_email_template.assert_called_once_with( SERVICE_TWO_ID, TEMPLATE_ONE_ID, @@ -979,33 +959,29 @@ def test_copy_template_loads_template_from_within_subfolder( active_user_with_permission_to_two_services, mock_get_service_templates, mock_get_non_empty_organizations_and_services_for_user, - mocker + mocker, ): template = template_json( - SERVICE_TWO_ID, - TEMPLATE_ONE_ID, - name='foo', - folder=PARENT_FOLDER_ID + SERVICE_TWO_ID, TEMPLATE_ONE_ID, name="foo", folder=PARENT_FOLDER_ID ) mock_get_service_template = mocker.patch( - 'app.service_api_client.get_service_template', - return_value={'data': template} + "app.service_api_client.get_service_template", return_value={"data": template} ) mock_get_template_folder = mocker.patch( - 'app.template_folder_api_client.get_template_folder', - return_value=_folder('Parent folder', PARENT_FOLDER_ID), + "app.template_folder_api_client.get_template_folder", + return_value=_folder("Parent folder", PARENT_FOLDER_ID), ) client_request.login(active_user_with_permission_to_two_services) page = client_request.get( - 'main.copy_template', + "main.copy_template", service_id=SERVICE_ONE_ID, template_id=TEMPLATE_ONE_ID, from_service=SERVICE_TWO_ID, ) - assert page.select_one('input')['value'] == 'foo (copy)' + assert page.select_one("input")["value"] == "foo (copy)" mock_get_service_template.assert_called_once_with(SERVICE_TWO_ID, TEMPLATE_ONE_ID) mock_get_template_folder.assert_called_once_with(SERVICE_TWO_ID, PARENT_FOLDER_ID) @@ -1016,7 +992,7 @@ def test_cant_copy_template_from_non_member_service( mock_get_organizations_and_services_for_user, ): client_request.get( - 'main.copy_template', + "main.copy_template", service_id=SERVICE_ONE_ID, template_id=TEMPLATE_ONE_ID, from_service=SERVICE_TWO_ID, @@ -1025,16 +1001,19 @@ def test_cant_copy_template_from_non_member_service( assert mock_get_service_email_template.call_args_list == [] -@pytest.mark.parametrize('service_permissions, data, expected_error', ( +@pytest.mark.parametrize( + "service_permissions, data, expected_error", ( - ['email'], - { - 'operation': 'add-new-template', - 'add_template_by_template_type': 'sms', - }, - "Sending text messages has been disabled for your service." + ( + ["email"], + { + "operation": "add-new-template", + "add_template_by_template_type": "sms", + }, + "Sending text messages has been disabled for your service.", + ), ), -)) +) def test_should_not_allow_creation_of_template_through_form_without_correct_permission( client_request, service_one, @@ -1045,27 +1024,30 @@ def test_should_not_allow_creation_of_template_through_form_without_correct_perm expected_error, fake_uuid, ): - service_one['permissions'] = service_permissions + service_one["permissions"] = service_permissions page = client_request.post( - 'main.choose_template', + "main.choose_template", service_id=SERVICE_ONE_ID, _data=data, _follow_redirects=True, _expected_status=403, ) - assert normalize_spaces(page.select('main p')[0].text) == expected_error + assert normalize_spaces(page.select("main p")[0].text) == expected_error assert page.select(".usa-back-link")[0].text == "Back" - assert page.select(".usa-back-link")[0]['href'] == url_for( - '.choose_template', + assert page.select(".usa-back-link")[0]["href"] == url_for( + ".choose_template", service_id=SERVICE_ONE_ID, ) -@pytest.mark.parametrize('method', ('get', 'post')) -@pytest.mark.parametrize('type_of_template, expected_error', [ - ('email', 'Sending emails has been disabled for your service.'), - ('sms', 'Sending text messages has been disabled for your service.'), -]) +@pytest.mark.parametrize("method", ("get", "post")) +@pytest.mark.parametrize( + "type_of_template, expected_error", + [ + ("email", "Sending emails has been disabled for your service."), + ("sms", "Sending text messages has been disabled for your service."), + ], +) def test_should_not_allow_creation_of_a_template_without_correct_permission( client_request, service_one, @@ -1074,20 +1056,20 @@ def test_should_not_allow_creation_of_a_template_without_correct_permission( type_of_template, expected_error, ): - service_one['permissions'] = [] + service_one["permissions"] = [] page = getattr(client_request, method)( - '.add_service_template', + ".add_service_template", service_id=SERVICE_ONE_ID, template_type=type_of_template, _follow_redirects=True, _expected_status=403, ) - assert page.select('main p')[0].text.strip() == expected_error + assert page.select("main p")[0].text.strip() == expected_error assert page.select(".usa-back-link")[0].text == "Back" - assert page.select(".usa-back-link")[0]['href'] == url_for( - '.choose_template', - service_id=service_one['id'], + assert page.select(".usa-back-link")[0]["href"] == url_for( + ".choose_template", + service_id=service_one["id"], ) @@ -1101,25 +1083,25 @@ def test_should_redirect_when_saving_a_template( name = "new name" content = "template content with & entity" client_request.post( - '.edit_service_template', + ".edit_service_template", service_id=SERVICE_ONE_ID, template_id=fake_uuid, _data={ - 'id': fake_uuid, - 'name': name, - 'template_content': content, - 'template_type': 'sms', - 'service': SERVICE_ONE_ID, + "id": fake_uuid, + "name": name, + "template_content": content, + "template_type": "sms", + "service": SERVICE_ONE_ID, }, _expected_status=302, _expected_redirect=url_for( - '.view_template', + ".view_template", service_id=SERVICE_ONE_ID, template_id=fake_uuid, ), ) mock_update_service_template.assert_called_with( - fake_uuid, name, 'sms', content, SERVICE_ONE_ID, None + fake_uuid, name, "sms", content, SERVICE_ONE_ID, None ) @@ -1130,30 +1112,30 @@ def test_should_edit_content_when_process_type_is_priority_not_platform_admin( fake_uuid, ): client_request.post( - '.edit_service_template', + ".edit_service_template", service_id=SERVICE_ONE_ID, template_id=fake_uuid, _data={ - 'id': fake_uuid, - 'name': "new name", - 'template_content': "new template content with & entity", - 'template_type': 'sms', - 'service': SERVICE_ONE_ID, + "id": fake_uuid, + "name": "new name", + "template_content": "new template content with & entity", + "template_type": "sms", + "service": SERVICE_ONE_ID, }, _expected_status=302, _expected_redirect=url_for( - '.view_template', + ".view_template", service_id=SERVICE_ONE_ID, template_id=fake_uuid, - ) + ), ) mock_update_service_template.assert_called_with( fake_uuid, "new name", - 'sms', + "sms", "new template content with & entity", SERVICE_ONE_ID, - None + None, ) @@ -1163,20 +1145,23 @@ def test_should_not_allow_template_edits_without_correct_permission( service_one, fake_uuid, ): - service_one['permissions'] = ['email'] + service_one["permissions"] = ["email"] page = client_request.get( - '.edit_service_template', + ".edit_service_template", service_id=SERVICE_ONE_ID, template_id=fake_uuid, _follow_redirects=True, _expected_status=403, ) - assert page.select('main p')[0].text.strip() == "Sending text messages has been disabled for your service." + assert ( + page.select("main p")[0].text.strip() + == "Sending text messages has been disabled for your service." + ) assert page.select(".usa-back-link")[0].text == "Back" - assert page.select(".usa-back-link")[0]['href'] == url_for( - '.view_template', + assert page.select(".usa-back-link")[0]["href"] == url_for( + ".view_template", service_id=SERVICE_ONE_ID, template_id=fake_uuid, ) @@ -1191,21 +1176,24 @@ def test_should_403_when_edit_template_with_process_type_of_priority_for_non_pla fake_uuid, service_one, ): - service_one['users'] = [active_user_with_permissions] + service_one["users"] = [active_user_with_permissions] client_request.login(active_user_with_permissions) - mocker.patch('app.user_api_client.get_users_for_service', return_value=[active_user_with_permissions]) + mocker.patch( + "app.user_api_client.get_users_for_service", + return_value=[active_user_with_permissions], + ) template_id = fake_uuid data = { - 'id': template_id, - 'name': "new name", - 'template_content': "template content with & entity", - 'template_type': 'sms', - 'service': service_one['id'], - 'process_type': 'priority' + "id": template_id, + "name": "new name", + "template_content": "template content with & entity", + "template_type": "sms", + "service": service_one["id"], + "process_type": "priority", } client_request.post( - '.edit_service_template', - service_id=service_one['id'], + ".edit_service_template", + service_id=service_one["id"], template_id=template_id, _data=data, _expected_status=403, @@ -1222,47 +1210,53 @@ def test_should_403_when_create_template_with_process_type_of_priority_for_non_p fake_uuid, service_one, ): - service_one['users'] = [active_user_with_permissions] + service_one["users"] = [active_user_with_permissions] client_request.login(active_user_with_permissions, service_one) - mocker.patch('app.user_api_client.get_users_for_service', return_value=[active_user_with_permissions]) + mocker.patch( + "app.user_api_client.get_users_for_service", + return_value=[active_user_with_permissions], + ) template_id = fake_uuid data = { - 'id': template_id, - 'name': "new name", - 'template_content': "template content with & entity", - 'template_type': 'sms', - 'service': service_one['id'], - 'process_type': 'priority' + "id": template_id, + "name": "new name", + "template_content": "template content with & entity", + "template_type": "sms", + "service": service_one["id"], + "process_type": "priority", } client_request.post( - '.add_service_template', - service_id=service_one['id'], - template_type='sms', + ".add_service_template", + service_id=service_one["id"], + template_type="sms", _data=data, _expected_status=403, ) assert mock_update_service_template.called is False -@pytest.mark.parametrize('old_content, new_content, expected_paragraphs', [ - ( - "my favorite color is blue", - "my favorite color is ((color))", - [ - 'You added ((color))', - 'Before you send any messages, make sure your API calls include color.', - ] - ), - ( - "hello ((name))", - "hello ((first name)) ((middle name)) ((last name))", - [ - 'You removed ((name))', - 'You added ((first name)) ((middle name)) and ((last name))', - 'Before you send any messages, make sure your API calls include first name, middle name and last name.', - ] - ), -]) +@pytest.mark.parametrize( + "old_content, new_content, expected_paragraphs", + [ + ( + "my favorite color is blue", + "my favorite color is ((color))", + [ + "You added ((color))", + "Before you send any messages, make sure your API calls include color.", + ], + ), + ( + "hello ((name))", + "hello ((first name)) ((middle name)) ((last name))", + [ + "You removed ((name))", + "You added ((first name)) ((middle name)) and ((last name))", + "Before you send any messages, make sure your API calls include first name, middle name and last name.", + ], + ), + ], +) def test_should_show_interstitial_when_making_breaking_change( client_request, mock_update_service_template, @@ -1276,24 +1270,27 @@ def test_should_show_interstitial_when_making_breaking_change( ): email_template = create_template( template_id=fake_uuid, - template_type='email', + template_type="email", subject="Your ((thing)) is due soon", - content=old_content + content=old_content, + ) + mocker.patch( + "app.service_api_client.get_service_template", + return_value={"data": email_template}, ) - mocker.patch('app.service_api_client.get_service_template', return_value={'data': email_template}) data = { - 'id': fake_uuid, - 'name': "new name", - 'template_content': new_content, - 'template_type': 'email', - 'subject': 'reminder \'" & ((thing))', - 'service': SERVICE_ONE_ID, - 'process_type': 'normal' + "id": fake_uuid, + "name": "new name", + "template_content": new_content, + "template_type": "email", + "subject": "reminder '\" & ((thing))", + "service": SERVICE_ONE_ID, + "process_type": "normal", } page = client_request.post( - '.edit_service_template', + ".edit_service_template", service_id=SERVICE_ONE_ID, template_id=fake_uuid, _data=data, @@ -1301,26 +1298,26 @@ def test_should_show_interstitial_when_making_breaking_change( ) assert page.h1.string.strip() == "Confirm changes" - assert page.find('a', {'class': 'usa-back-link'})['href'] == url_for( + assert page.find("a", {"class": "usa-back-link"})["href"] == url_for( ".edit_service_template", service_id=SERVICE_ONE_ID, template_id=fake_uuid, ) assert [ - normalize_spaces(paragraph.text) for paragraph in page.select('main p') + normalize_spaces(paragraph.text) for paragraph in page.select("main p") ] == expected_paragraphs for key, value in { - 'name': 'new name', - 'subject': 'reminder \'" & ((thing))', - 'template_content': new_content, - 'confirm': 'true' + "name": "new name", + "subject": "reminder '\" & ((thing))", + "template_content": new_content, + "confirm": "true", }.items(): - assert page.find('input', {'name': key})['value'] == value + assert page.find("input", {"name": key})["value"] == value # BeautifulSoup returns the value attribute as unencoded, let’s make # sure that it is properly encoded in the HTML - assert str(page.find('input', {'name': 'subject'})) == ( + assert str(page.find("input", {"name": "subject"})) == ( """""" ) @@ -1331,19 +1328,19 @@ def test_removing_placeholders_is_not_a_breaking_change( mock_update_service_template, fake_uuid, ): - existing_template = mock_get_service_email_template(0, 0)['data'] + existing_template = mock_get_service_email_template(0, 0)["data"] client_request.post( - '.edit_service_template', + ".edit_service_template", service_id=SERVICE_ONE_ID, template_id=fake_uuid, _data={ - 'name': existing_template['name'], - 'template_content': "no placeholders", - 'subject': existing_template['subject'], + "name": existing_template["name"], + "template_content": "no placeholders", + "subject": existing_template["subject"], }, _expected_status=302, _expected_redirect=url_for( - 'main.view_template', + "main.view_template", service_id=SERVICE_ONE_ID, template_id=fake_uuid, ), @@ -1358,15 +1355,15 @@ def test_should_not_create_too_big_template( fake_uuid, ): page = client_request.post( - '.add_service_template', + ".add_service_template", service_id=SERVICE_ONE_ID, - template_type='sms', + template_type="sms", _data={ - 'name': "new name", - 'template_content': "template content", - 'template_type': 'sms', - 'service': SERVICE_ONE_ID, - 'process_type': 'normal' + "name": "new name", + "template_content": "template content", + "template_type": "sms", + "service": SERVICE_ONE_ID, + "process_type": "normal", }, _expected_status=200, ) @@ -1380,16 +1377,16 @@ def test_should_not_update_too_big_template( fake_uuid, ): page = client_request.post( - '.edit_service_template', + ".edit_service_template", service_id=SERVICE_ONE_ID, template_id=fake_uuid, _data={ - 'id': fake_uuid, - 'name': "new name", - 'template_content': "template content", - 'service': SERVICE_ONE_ID, - 'template_type': 'sms', - 'process_type': 'normal', + "id": fake_uuid, + "name": "new name", + "template_content": "template content", + "service": SERVICE_ONE_ID, + "template_type": "sms", + "process_type": "normal", }, _expected_status=200, ) @@ -1407,26 +1404,26 @@ def test_should_redirect_when_saving_a_template_email( content = "template content with & entity ((thing)) ((date))" subject = "subject & entity" client_request.post( - '.edit_service_template', + ".edit_service_template", service_id=SERVICE_ONE_ID, template_id=fake_uuid, _data={ - 'id': fake_uuid, - 'name': name, - 'template_content': content, - 'template_type': 'email', - 'service': SERVICE_ONE_ID, - 'subject': subject, + "id": fake_uuid, + "name": name, + "template_content": content, + "template_type": "email", + "service": SERVICE_ONE_ID, + "subject": subject, }, _expected_status=302, _expected_redirect=url_for( - '.view_template', + ".view_template", service_id=SERVICE_ONE_ID, template_id=fake_uuid, ), ) mock_update_service_template.assert_called_with( - fake_uuid, name, 'email', content, SERVICE_ONE_ID, subject + fake_uuid, name, "email", content, SERVICE_ONE_ID, subject ) @@ -1435,24 +1432,29 @@ def test_should_show_delete_template_page_with_time_block( mock_get_service_template, mock_get_template_folders, mocker, - fake_uuid + fake_uuid, ): - mocker.patch('app.template_statistics_client.get_last_used_date_for_template', - return_value='2012-01-01 12:00:00') + mocker.patch( + "app.template_statistics_client.get_last_used_date_for_template", + return_value="2012-01-01 12:00:00", + ) - with freeze_time('2012-01-01 12:10:00'): + with freeze_time("2012-01-01 12:10:00"): page = client_request.get( - '.delete_service_template', + ".delete_service_template", service_id=SERVICE_ONE_ID, template_id=fake_uuid, _test_page_title=False, ) - assert "Are you sure you want to delete ‘Two week reminder’?" in page.select('.banner-dangerous')[0].text - assert normalize_spaces(page.select('.banner-dangerous p')[0].text) == ( - 'This template was last used 10 minutes ago.' + assert ( + "Are you sure you want to delete ‘Two week reminder’?" + in page.select(".banner-dangerous")[0].text ) - assert normalize_spaces(page.select('.sms-message-wrapper')[0].text) == ( - 'service one: Template content with & entity' + assert normalize_spaces(page.select(".banner-dangerous p")[0].text) == ( + "This template was last used 10 minutes ago." + ) + assert normalize_spaces(page.select(".sms-message-wrapper")[0].text) == ( + "service one: Template content with & entity" ) mock_get_service_template.assert_called_with(SERVICE_ONE_ID, fake_uuid, None) @@ -1462,24 +1464,29 @@ def test_should_show_delete_template_page_with_time_block_for_empty_notification mock_get_service_template, mock_get_template_folders, mocker, - fake_uuid + fake_uuid, ): - mocker.patch('app.template_statistics_client.get_last_used_date_for_template', - return_value=None) + mocker.patch( + "app.template_statistics_client.get_last_used_date_for_template", + return_value=None, + ) - with freeze_time('2012-01-01 11:00:00'): + with freeze_time("2012-01-01 11:00:00"): page = client_request.get( - '.delete_service_template', + ".delete_service_template", service_id=SERVICE_ONE_ID, template_id=fake_uuid, _test_page_title=False, ) - assert "Are you sure you want to delete ‘Two week reminder’?" in page.select('.banner-dangerous')[0].text - assert normalize_spaces(page.select('.banner-dangerous p')[0].text) == ( - 'This template has never been used.' + assert ( + "Are you sure you want to delete ‘Two week reminder’?" + in page.select(".banner-dangerous")[0].text ) - assert normalize_spaces(page.select('.sms-message-wrapper')[0].text) == ( - 'service one: Template content with & entity' + assert normalize_spaces(page.select(".banner-dangerous p")[0].text) == ( + "This template has never been used." + ) + assert normalize_spaces(page.select(".sms-message-wrapper")[0].text) == ( + "service one: Template content with & entity" ) mock_get_service_template.assert_called_with(SERVICE_ONE_ID, fake_uuid, None) @@ -1492,26 +1499,29 @@ def test_should_show_delete_template_page_with_never_used_block( mocker, ): mocker.patch( - 'app.template_statistics_client.get_last_used_date_for_template', - side_effect=HTTPError(response=Mock(status_code=404), message="Default message") + "app.template_statistics_client.get_last_used_date_for_template", + side_effect=HTTPError( + response=Mock(status_code=404), message="Default message" + ), ) page = client_request.get( - '.delete_service_template', + ".delete_service_template", service_id=SERVICE_ONE_ID, template_id=fake_uuid, _test_page_title=False, ) - assert "Are you sure you want to delete ‘Two week reminder’?" in page.select('.banner-dangerous')[0].text - assert not page.select('.banner-dangerous p') - assert normalize_spaces(page.select('.sms-message-wrapper')[0].text) == ( - 'service one: Template content with & entity' + assert ( + "Are you sure you want to delete ‘Two week reminder’?" + in page.select(".banner-dangerous")[0].text + ) + assert not page.select(".banner-dangerous p") + assert normalize_spaces(page.select(".sms-message-wrapper")[0].text) == ( + "service one: Template content with & entity" ) mock_get_service_template.assert_called_with(SERVICE_ONE_ID, fake_uuid, None) -@pytest.mark.parametrize('parent', ( - PARENT_FOLDER_ID, None -)) +@pytest.mark.parametrize("parent", (PARENT_FOLDER_ID, None)) def test_should_redirect_when_deleting_a_template( mocker, client_request, @@ -1519,38 +1529,42 @@ def test_should_redirect_when_deleting_a_template( mock_get_template_folders, parent, ): - mock_get_template_folders.return_value = [ - {'id': PARENT_FOLDER_ID, 'name': 'Folder', 'parent': None, 'users_with_permission': [ANY]} + { + "id": PARENT_FOLDER_ID, + "name": "Folder", + "parent": None, + "users_with_permission": [ANY], + } ] mock_get_service_template = mocker.patch( - 'app.service_api_client.get_service_template', - return_value={'data': _template( - 'sms', 'Hello', parent=parent, - )}, + "app.service_api_client.get_service_template", + return_value={ + "data": _template( + "sms", + "Hello", + parent=parent, + ) + }, ) client_request.post( - '.delete_service_template', + ".delete_service_template", service_id=SERVICE_ONE_ID, template_id=TEMPLATE_ONE_ID, _expected_status=302, _expected_redirect=url_for( - '.choose_template', + ".choose_template", service_id=SERVICE_ONE_ID, template_folder_id=parent, - ) + ), ) - mock_get_service_template.assert_called_with( - SERVICE_ONE_ID, TEMPLATE_ONE_ID, None - ) - mock_delete_service_template.assert_called_with( - SERVICE_ONE_ID, TEMPLATE_ONE_ID - ) + mock_get_service_template.assert_called_with(SERVICE_ONE_ID, TEMPLATE_ONE_ID, None) + mock_delete_service_template.assert_called_with(SERVICE_ONE_ID, TEMPLATE_ONE_ID) -@freeze_time('2016-01-01T15:00') +@freeze_time("2016-01-01T15:00") def test_should_show_page_for_a_deleted_template( client_request, mock_get_template_folders, @@ -1562,26 +1576,42 @@ def test_should_show_page_for_a_deleted_template( ): template_id = fake_uuid page = client_request.get( - '.view_template', + ".view_template", service_id=SERVICE_ONE_ID, template_id=template_id, _test_page_title=False, ) content = str(page) - assert url_for("main.edit_service_template", service_id=SERVICE_ONE_ID, template_id=fake_uuid) not in content - assert url_for("main.send_one_off", service_id=SERVICE_ONE_ID, template_id=fake_uuid) not in content - assert page.select('p.hint')[0].text.strip() == 'This template was deleted today at 15:00 UTC.' - assert 'Delete this template' not in page.select_one('main').text + assert ( + url_for( + "main.edit_service_template", + service_id=SERVICE_ONE_ID, + template_id=fake_uuid, + ) + not in content + ) + assert ( + url_for("main.send_one_off", service_id=SERVICE_ONE_ID, template_id=fake_uuid) + not in content + ) + assert ( + page.select("p.hint")[0].text.strip() + == "This template was deleted today at 15:00 UTC." + ) + assert "Delete this template" not in page.select_one("main").text mock_get_deleted_template.assert_called_with(SERVICE_ONE_ID, template_id, None) -@pytest.mark.parametrize('route', [ - 'main.add_service_template', - 'main.edit_service_template', - 'main.delete_service_template' -]) +@pytest.mark.parametrize( + "route", + [ + "main.add_service_template", + "main.edit_service_template", + "main.delete_service_template", + ], +) def test_route_permissions( route, mocker, @@ -1593,8 +1623,10 @@ def test_route_permissions( mock_get_template_folders, fake_uuid, ): - mocker.patch('app.template_statistics_client.get_last_used_date_for_template', - return_value='2012-01-01 12:00:00') + mocker.patch( + "app.template_statistics_client.get_last_used_date_for_template", + return_value="2012-01-01 12:00:00", + ) validate_route_permission( mocker, notify_admin, @@ -1602,12 +1634,14 @@ def test_route_permissions( 200, url_for( route, - service_id=service_one['id'], - template_type='sms', - template_id=fake_uuid), - ['manage_templates'], + service_id=service_one["id"], + template_type="sms", + template_id=fake_uuid, + ), + ["manage_templates"], api_user_active, - service_one) + service_one, + ) def test_route_permissions_for_choose_template( @@ -1620,26 +1654,30 @@ def test_route_permissions_for_choose_template( mock_get_service_templates, mock_get_no_api_keys, ): - mocker.patch('app.job_api_client.get_job') + mocker.patch("app.job_api_client.get_job") validate_route_permission( mocker, notify_admin, "GET", 200, url_for( - 'main.choose_template', - service_id=service_one['id'], + "main.choose_template", + service_id=service_one["id"], ), [], api_user_active, - service_one) + service_one, + ) -@pytest.mark.parametrize('route', [ - 'main.add_service_template', - 'main.edit_service_template', - 'main.delete_service_template' -]) +@pytest.mark.parametrize( + "route", + [ + "main.add_service_template", + "main.edit_service_template", + "main.delete_service_template", + ], +) def test_route_invalid_permissions( route, mocker, @@ -1657,59 +1695,61 @@ def test_route_invalid_permissions( 403, url_for( route, - service_id=service_one['id'], - template_type='sms', - template_id=fake_uuid), - ['view_activity'], + service_id=service_one["id"], + template_type="sms", + template_id=fake_uuid, + ), + ["view_activity"], api_user_active, - service_one) + service_one, + ) -@pytest.mark.parametrize('template_type, expected', ( - ('email', 'New email template'), - ('sms', 'New text message template'), -)) +@pytest.mark.parametrize( + "template_type, expected", + ( + ("email", "New email template"), + ("sms", "New text message template"), + ), +) def test_add_template_page_title( client_request, service_one, template_type, expected, ): - service_one['permissions'] += [template_type] + service_one["permissions"] += [template_type] page = client_request.get( - '.add_service_template', + ".add_service_template", service_id=SERVICE_ONE_ID, template_type=template_type, ) - assert normalize_spaces(page.select_one('h1').text) == expected + assert normalize_spaces(page.select_one("h1").text) == expected def test_can_create_email_template_with_emoji( - client_request, - mock_create_service_template + client_request, mock_create_service_template ): client_request.post( - '.add_service_template', + ".add_service_template", service_id=SERVICE_ONE_ID, - template_type='email', + template_type="email", _data={ - 'name': "new name", - 'subject': "Food incoming!", - 'template_content': "here's a burrito 🌯", - 'template_type': 'email', - 'service': SERVICE_ONE_ID, - 'process_type': 'normal' + "name": "new name", + "subject": "Food incoming!", + "template_content": "here's a burrito 🌯", + "template_type": "email", + "service": SERVICE_ONE_ID, + "process_type": "normal", }, _expected_status=302, ) assert mock_create_service_template.called is True -@pytest.mark.parametrize('template_type, expected_error', ( - ('sms', ( - 'You cannot use 🍜 in text messages.' - )), -)) +@pytest.mark.parametrize( + "template_type, expected_error", (("sms", ("You cannot use 🍜 in text messages.")),) +) def test_should_not_create_sms_template_with_emoji( client_request, service_one, @@ -1717,17 +1757,17 @@ def test_should_not_create_sms_template_with_emoji( template_type, expected_error, ): - service_one['permissions'] += [template_type] + service_one["permissions"] += [template_type] page = client_request.post( - '.add_service_template', + ".add_service_template", service_id=SERVICE_ONE_ID, template_type=template_type, _data={ - 'name': "new name", - 'template_content': "here are some noodles 🍜", - 'template_type': 'sms', - 'service': SERVICE_ONE_ID, - 'process_type': 'normal', + "name": "new name", + "template_content": "here are some noodles 🍜", + "template_type": "sms", + "service": SERVICE_ONE_ID, + "process_type": "normal", }, _expected_status=200, ) @@ -1735,11 +1775,9 @@ def test_should_not_create_sms_template_with_emoji( assert mock_create_service_template.called is False -@pytest.mark.parametrize('template_type, expected_error', ( - ('sms', ( - 'You cannot use 🍔 in text messages.' - )), -)) +@pytest.mark.parametrize( + "template_type, expected_error", (("sms", ("You cannot use 🍔 in text messages.")),) +) def test_should_not_update_sms_template_with_emoji( mocker, client_request, @@ -1750,9 +1788,9 @@ def test_should_not_update_sms_template_with_emoji( template_type, expected_error, ): - service_one['permissions'] += [template_type] + service_one["permissions"] += [template_type] return mocker.patch( - 'app.service_api_client.get_service_template', + "app.service_api_client.get_service_template", return_value=template_json( SERVICE_ONE_ID, fake_uuid, @@ -1760,16 +1798,16 @@ def test_should_not_update_sms_template_with_emoji( ), ) page = client_request.post( - '.edit_service_template', + ".edit_service_template", service_id=SERVICE_ONE_ID, template_id=fake_uuid, _data={ - 'id': fake_uuid, - 'name': "new name", - 'template_content': "here's a burger 🍔", - 'service': SERVICE_ONE_ID, - 'template_type': template_type, - 'process_type': 'normal' + "id": fake_uuid, + "name": "new name", + "template_content": "here's a burger 🍔", + "service": SERVICE_ONE_ID, + "template_type": template_type, + "process_type": "normal", }, _expected_status=200, ) @@ -1777,28 +1815,26 @@ def test_should_not_update_sms_template_with_emoji( assert mock_update_service_template.called is False -@pytest.mark.parametrize('template_type', ( - 'sms', -)) +@pytest.mark.parametrize("template_type", ("sms",)) def test_should_create_sms_template_without_downgrading_unicode_characters( client_request, service_one, mock_create_service_template, template_type, ): - service_one['permissions'] += [template_type] + service_one["permissions"] += [template_type] - msg = 'here:\tare some “fancy quotes” and non\u200Bbreaking\u200Bspaces' + msg = "here:\tare some “fancy quotes” and non\u200Bbreaking\u200Bspaces" client_request.post( - '.add_service_template', + ".add_service_template", service_id=SERVICE_ONE_ID, - template_type='sms', + template_type="sms", _data={ - 'name': "new name", - 'template_content': msg, - 'template_type': template_type, - 'service': SERVICE_ONE_ID, + "name": "new name", + "template_content": msg, + "template_type": template_type, + "service": SERVICE_ONE_ID, }, expected_status=302, ) @@ -1809,7 +1845,7 @@ def test_should_create_sms_template_without_downgrading_unicode_characters( msg, # content ANY, # service_id ANY, # subject - ANY # parent_folder_id + ANY, # parent_folder_id ) @@ -1819,22 +1855,21 @@ def test_should_show_message_before_redacting_template( service_one, fake_uuid, ): - page = client_request.get( - 'main.redact_template', + "main.redact_template", service_id=SERVICE_ONE_ID, template_id=fake_uuid, _test_page_title=False, ) assert ( - 'Are you sure you want to hide personalization after sending?' - ) in page.select('.banner-dangerous')[0].text + "Are you sure you want to hide personalization after sending?" + ) in page.select(".banner-dangerous")[0].text - form = page.select('.banner-dangerous form')[0] + form = page.select(".banner-dangerous form")[0] - assert 'action' not in form - assert form['method'] == 'post' + assert "action" not in form + assert form["method"] == "post" def test_should_show_redact_template( @@ -1845,16 +1880,15 @@ def test_should_show_redact_template( service_one, fake_uuid, ): - page = client_request.post( - 'main.redact_template', + "main.redact_template", service_id=SERVICE_ONE_ID, template_id=fake_uuid, _follow_redirects=True, ) - assert normalize_spaces(page.select('.banner-default-with-tick')[0].text) == ( - 'Personalized content will be hidden for messages sent with this template' + assert normalize_spaces(page.select(".banner-default-with-tick")[0].text) == ( + "Personalized content will be hidden for messages sent with this template" ) mock_redact_template.assert_called_once_with(SERVICE_ONE_ID, fake_uuid) @@ -1867,17 +1901,21 @@ def test_should_show_hint_once_template_redacted( mock_get_template_folders, fake_uuid, ): - template = create_template(template_type='email', content='hi ((name))', redact_personalisation=True) - mocker.patch('app.service_api_client.get_service_template', return_value={'data': template}) + template = create_template( + template_type="email", content="hi ((name))", redact_personalisation=True + ) + mocker.patch( + "app.service_api_client.get_service_template", return_value={"data": template} + ) page = client_request.get( - 'main.view_template', + "main.view_template", service_id=SERVICE_ONE_ID, template_id=fake_uuid, _test_page_title=False, ) - assert page.select('.hint')[0].text == 'Personalization is hidden after sending' + assert page.select(".hint")[0].text == "Personalization is hidden after sending" def test_set_template_sender( @@ -1885,14 +1923,14 @@ def test_set_template_sender( fake_uuid, mock_update_service_template_sender, mock_get_service_template, - single_sms_sender + single_sms_sender, ): data = { - 'sender': '1234', + "sender": "1234", } client_request.post( - 'main.set_template_sender', + "main.set_template_sender", service_id=SERVICE_ONE_ID, template_id=fake_uuid, _data=data, @@ -1901,100 +1939,133 @@ def test_set_template_sender( mock_update_service_template_sender.assert_called_once_with( SERVICE_ONE_ID, fake_uuid, - '1234', + "1234", ) @pytest.mark.parametrize( - 'template_type, prefix_sms, content, expected_message, expected_class', ( + "template_type, prefix_sms, content, expected_message, expected_class", + ( ( - 'sms', False, '', - 'Will be charged as 1 text message', + "sms", + False, + "", + "Will be charged as 1 text message", None, ), ( - 'sms', False, 'a' * 160, - 'Will be charged as 1 text message', + "sms", + False, + "a" * 160, + "Will be charged as 1 text message", None, ), ( - 'sms', False, 'a' * 161, - 'Will be charged as 2 text messages', + "sms", + False, + "a" * 161, + "Will be charged as 2 text messages", None, ), ( # service name takes 13 characters, 147 + 13 = 160 - 'sms', True, 'a' * 147, - 'Will be charged as 1 text message', + "sms", + True, + "a" * 147, + "Will be charged as 1 text message", None, ), ( # service name takes 13 characters, 148 + 13 = 161 - 'sms', True, 'a' * 148, - 'Will be charged as 2 text messages', + "sms", + True, + "a" * 148, + "Will be charged as 2 text messages", None, ), ( - 'sms', False, 'a' * 918, - 'Will be charged as 6 text messages', + "sms", + False, + "a" * 918, + "Will be charged as 6 text messages", None, ), ( # Service name increases fragment count but doesn’t count # against total character limit - 'sms', True, 'a' * 918, - 'Will be charged as 7 text messages', + "sms", + True, + "a" * 918, + "Will be charged as 7 text messages", None, ), ( # Can’t make a 7 fragment text template from content alone - 'sms', False, 'a' * 919, - 'You have 1 character too many', - 'usa-error-message', + "sms", + False, + "a" * 919, + "You have 1 character too many", + "usa-error-message", ), ( # Service name increases content count but character count # is based on content alone - 'sms', True, 'a' * 919, - 'You have 1 character too many', - 'usa-error-message', + "sms", + True, + "a" * 919, + "You have 1 character too many", + "usa-error-message", ), ( # Service name increases content count but character count # is based on content alone - 'sms', True, 'a' * 920, - 'You have 2 characters too many', - 'usa-error-message', + "sms", + True, + "a" * 920, + "You have 2 characters too many", + "usa-error-message", ), ( - 'sms', False, 'Ẅ' * 70, - 'Will be charged as 1 text message', + "sms", + False, + "Ẅ" * 70, + "Will be charged as 1 text message", None, ), ( - 'sms', False, 'Ẅ' * 71, - 'Will be charged as 2 text messages', + "sms", + False, + "Ẅ" * 71, + "Will be charged as 2 text messages", None, ), ( - 'sms', False, 'Ẅ' * 918, - 'Will be charged as 14 text messages', + "sms", + False, + "Ẅ" * 918, + "Will be charged as 14 text messages", None, ), ( - 'sms', False, 'Ẅ' * 919, - 'You have 1 character too many', - 'usa-error-message', + "sms", + False, + "Ẅ" * 919, + "You have 1 character too many", + "usa-error-message", ), ( - 'sms', False, 'Hello ((name))', - 'Will be charged as 1 text message (not including personalization)', + "sms", + False, + "Hello ((name))", + "Will be charged as 1 text message (not including personalization)", None, ), ( # Length of placeholder body doesn’t count towards fragment count - 'sms', False, f'Hello (( {"a" * 999} ))', - 'Will be charged as 1 text message (not including personalization)', + "sms", + False, + f'Hello (( {"a" * 999} ))', + "Will be charged as 1 text message (not including personalization)", None, ), ), @@ -2008,39 +2079,43 @@ def test_content_count_json_endpoint( expected_message, expected_class, ): - service_one['prefix_sms'] = prefix_sms + service_one["prefix_sms"] = prefix_sms response = client_request.post_response( - 'main.count_content_length', + "main.count_content_length", service_id=SERVICE_ONE_ID, template_type=template_type, _data={ - 'template_content': content, + "template_content": content, }, _expected_status=200, ) - html = json.loads(response.get_data(as_text=True))['html'] - snippet = BeautifulSoup(html, 'html.parser').select_one('span') + html = json.loads(response.get_data(as_text=True))["html"] + snippet = BeautifulSoup(html, "html.parser").select_one("span") assert normalize_spaces(snippet.text) == expected_message - if snippet.has_attr('class'): - assert snippet['class'] == [expected_class] + if snippet.has_attr("class"): + assert snippet["class"] == [expected_class] else: assert expected_class is None -@pytest.mark.parametrize('template_type', ( - 'email', 'banana', -)) +@pytest.mark.parametrize( + "template_type", + ( + "email", + "banana", + ), +) def test_content_count_json_endpoint_for_unsupported_template_types( client_request, template_type, ): client_request.post( - 'main.count_content_length', + "main.count_content_length", service_id=SERVICE_ONE_ID, template_type=template_type, - content='foo', + content="foo", _expected_status=404, ) diff --git a/tests/app/main/views/test_tour.py b/tests/app/main/views/test_tour.py index 1bc9f5d7f..c26673bab 100644 --- a/tests/app/main/views/test_tour.py +++ b/tests/app/main/views/test_tour.py @@ -13,34 +13,28 @@ def test_should_200_for_tour_start( fake_uuid, ): page = client_request.get( - 'main.begin_tour', + "main.begin_tour", service_id=SERVICE_ONE_ID, template_id=fake_uuid, ) - assert normalize_spaces( - page.select('.banner-tour .heading-medium')[0].text - ) == ( - 'Try sending yourself this example' + assert normalize_spaces(page.select(".banner-tour .heading-medium")[0].text) == ( + "Try sending yourself this example" ) - selected_hint = page.select('.banner-tour .govuk-grid-row')[0] + selected_hint = page.select(".banner-tour .govuk-grid-row")[0] selected_hint_text = normalize_spaces(selected_hint.select(".govuk-body")[0].text) assert "greyed-out-step" not in selected_hint["class"] - assert selected_hint_text == 'Every message is sent from a template' + assert selected_hint_text == "Every message is sent from a template" - assert normalize_spaces( - page.select('.sms-message-recipient')[0].text - ) == ( - 'To: 202-867-5303' + assert normalize_spaces(page.select(".sms-message-recipient")[0].text) == ( + "To: 202-867-5303" ) - assert normalize_spaces( - page.select('.sms-message-wrapper')[0].text - ) == ( - 'service one: ((one)) ((two)) ((three))' + assert normalize_spaces(page.select(".sms-message-wrapper")[0].text) == ( + "service one: ((one)) ((two)) ((three))" ) - assert page.select('a.usa-button')[0]['href'] == url_for( - '.tour_step', service_id=SERVICE_ONE_ID, template_id=fake_uuid, step_index=1 + assert page.select("a.usa-button")[0]["href"] == url_for( + ".tour_step", service_id=SERVICE_ONE_ID, template_id=fake_uuid, step_index=1 ) @@ -51,19 +45,19 @@ def test_should_clear_session_on_tour_start( fake_uuid, ): with client_request.session_transaction() as session: - session['placeholders'] = {'one': 'hello', 'phone number': '202-867-5303'} + session["placeholders"] = {"one": "hello", "phone number": "202-867-5303"} client_request.get( - 'main.begin_tour', + "main.begin_tour", service_id=SERVICE_ONE_ID, template_id=fake_uuid, ) with client_request.session_transaction() as session: - assert session['placeholders'] == {} + assert session["placeholders"] == {} -@pytest.mark.parametrize('template_type', ['email']) +@pytest.mark.parametrize("template_type", ["email"]) def test_should_404_if_non_sms_template_for_tour_start( client_request, fake_uuid, @@ -71,12 +65,12 @@ def test_should_404_if_non_sms_template_for_tour_start( template_type, ): mocker.patch( - 'app.service_api_client.get_service_template', - return_value={'data': create_template(template_type=template_type)} + "app.service_api_client.get_service_template", + return_value={"data": create_template(template_type=template_type)}, ) client_request.get( - 'main.begin_tour', + "main.begin_tour", service_id=SERVICE_ONE_ID, template_id=fake_uuid, _expected_status=404, @@ -88,12 +82,12 @@ def test_should_404_if_no_mobile_number_for_tour_start( mock_get_service_template_with_multiple_placeholders, service_one, fake_uuid, - active_user_with_permissions_no_mobile + active_user_with_permissions_no_mobile, ): client_request.login(active_user_with_permissions_no_mobile) assert current_user.mobile_number is None client_request.get( - 'main.begin_tour', + "main.begin_tour", service_id=SERVICE_ONE_ID, template_id=fake_uuid, _expected_status=404, @@ -115,13 +109,14 @@ def test_should_403_if_user_does_not_have_send_permissions_for_tour_start( "GET", 403, url_for( - 'main.begin_tour', + "main.begin_tour", service_id=SERVICE_ONE_ID, template_id=fake_uuid, ), - ['view_activity'], + ["view_activity"], api_user_active, - service_one) + service_one, + ) def test_should_200_for_get_tour_step( @@ -131,36 +126,27 @@ def test_should_200_for_get_tour_step( fake_uuid, ): with client_request.session_transaction() as session: - session['placeholders'] = {} + session["placeholders"] = {} page = client_request.get( - 'main.tour_step', - service_id=SERVICE_ONE_ID, - template_id=fake_uuid, - step_index=1 + "main.tour_step", service_id=SERVICE_ONE_ID, template_id=fake_uuid, step_index=1 ) - assert 'Example text message' in normalize_spaces(page.select_one('title').text) - assert normalize_spaces( - page.select('.banner-tour .heading-medium')[0].text - ) == ( - 'Try sending yourself this example' + assert "Example text message" in normalize_spaces(page.select_one("title").text) + assert normalize_spaces(page.select(".banner-tour .heading-medium")[0].text) == ( + "Try sending yourself this example" ) - selected_hint = page.select('.banner-tour .govuk-grid-row')[1] + selected_hint = page.select(".banner-tour .govuk-grid-row")[1] selected_hint_text = normalize_spaces(selected_hint.select(".govuk-body")[0].text) assert "greyed-out-step" not in selected_hint["class"] - assert selected_hint_text == 'The template pulls in the data you provide' + assert selected_hint_text == "The template pulls in the data you provide" - assert normalize_spaces( - page.select('.sms-message-recipient')[0].text - ) == ( - 'To: 202-867-5303' + assert normalize_spaces(page.select(".sms-message-recipient")[0].text) == ( + "To: 202-867-5303" ) - assert normalize_spaces( - page.select('.sms-message-wrapper')[0].text - ) == ( - 'service one: ((one)) ((two)) ((three))' + assert normalize_spaces(page.select(".sms-message-wrapper")[0].text) == ( + "service one: ((one)) ((two)) ((three))" ) @@ -171,27 +157,26 @@ def test_should_show_empty_text_box( fake_uuid, ): with client_request.session_transaction() as session: - session['placeholders'] = {'phone number': '202-867-5303'} + session["placeholders"] = {"phone number": "202-867-5303"} page = client_request.get( - 'main.tour_step', - service_id=SERVICE_ONE_ID, - template_id=fake_uuid, - step_index=1 + "main.tour_step", service_id=SERVICE_ONE_ID, template_id=fake_uuid, step_index=1 ) - textbox = page.select_one('[data-module=autofocus][data-force-focus=True] .usa-input') - assert 'value' not in textbox - assert textbox['name'] == 'placeholder_value' - assert textbox['class'] == [ - 'usa-input', + textbox = page.select_one( + "[data-module=autofocus][data-force-focus=True] .usa-input" + ) + assert "value" not in textbox + assert textbox["name"] == "placeholder_value" + assert textbox["class"] == [ + "usa-input", ] # data-module=autofocus is set on a containing element so it # shouldn’t also be set on the textbox itself - assert 'data-module' not in textbox - assert normalize_spaces( - page.select_one('label[for=placeholder_value]').text - ) == 'one' + assert "data-module" not in textbox + assert ( + normalize_spaces(page.select_one("label[for=placeholder_value]").text) == "one" + ) def test_should_prefill_answers_for_get_tour_step( @@ -201,38 +186,34 @@ def test_should_prefill_answers_for_get_tour_step( fake_uuid, ): with client_request.session_transaction() as session: - session['placeholders'] = session['placeholders'] = {'one': 'hello', 'phone number': '202-867-5303'} + session["placeholders"] = session["placeholders"] = { + "one": "hello", + "phone number": "202-867-5303", + } page = client_request.get( - 'main.tour_step', - service_id=SERVICE_ONE_ID, - template_id=fake_uuid, - step_index=1 + "main.tour_step", service_id=SERVICE_ONE_ID, template_id=fake_uuid, step_index=1 ) - assert page.select('.usa-input')[0]['value'] == 'hello' + assert page.select(".usa-input")[0]["value"] == "hello" -@pytest.mark.parametrize('template_type', ['email']) -@pytest.mark.parametrize('method', ['get', 'post']) +@pytest.mark.parametrize("template_type", ["email"]) +@pytest.mark.parametrize("method", ["get", "post"]) def test_should_404_if_non_sms_template_for_tour_step( - client_request, - fake_uuid, - mocker, - template_type, - method + client_request, fake_uuid, mocker, template_type, method ): mocker.patch( - 'app.service_api_client.get_service_template', - return_value={'data': create_template(template_type=template_type)} + "app.service_api_client.get_service_template", + return_value={"data": create_template(template_type=template_type)}, ) getattr(client_request, method)( - 'main.tour_step', + "main.tour_step", service_id=SERVICE_ONE_ID, template_id=fake_uuid, step_index=1, - _expected_status=404 + _expected_status=404, ) @@ -243,18 +224,18 @@ def test_should_404_for_get_tour_step_0( fake_uuid, ): with client_request.session_transaction() as session: - session['placeholders'] = {} + session["placeholders"] = {} client_request.get( - 'main.tour_step', + "main.tour_step", service_id=SERVICE_ONE_ID, template_id=fake_uuid, step_index=0, - _expected_status=404 + _expected_status=404, ) -@pytest.mark.parametrize('method', ['GET', 'POST']) +@pytest.mark.parametrize("method", ["GET", "POST"]) def test_should_403_if_user_does_not_have_send_permissions_for_tour_step( mocker, notify_admin, @@ -263,7 +244,7 @@ def test_should_403_if_user_does_not_have_send_permissions_for_tour_step( mock_get_service_template_with_multiple_placeholders, service_one, fake_uuid, - method + method, ): validate_route_permission( mocker, @@ -271,14 +252,14 @@ def test_should_403_if_user_does_not_have_send_permissions_for_tour_step( method, 403, url_for( - 'main.tour_step', + "main.tour_step", service_id=SERVICE_ONE_ID, template_id=fake_uuid, - step_index=1 + step_index=1, ), - ['view_activity'], + ["view_activity"], api_user_active, - service_one + service_one, ) @@ -289,16 +270,16 @@ def test_tour_step_redirects_to_tour_start_if_placeholders_doesnt_exist_in_sessi fake_uuid, ): with client_request.session_transaction() as session: - assert 'placeholders' not in session + assert "placeholders" not in session client_request.get( - 'main.tour_step', + "main.tour_step", service_id=SERVICE_ONE_ID, template_id=fake_uuid, step_index=1, _expected_status=302, _expected_redirect=url_for( - 'main.begin_tour', + "main.begin_tour", service_id=SERVICE_ONE_ID, template_id=fake_uuid, ), @@ -312,19 +293,14 @@ def test_back_link_from_first_get_tour_step_points_to_tour_start( fake_uuid, ): with client_request.session_transaction() as session: - session['placeholders'] = {} + session["placeholders"] = {} page = client_request.get( - 'main.tour_step', - service_id=SERVICE_ONE_ID, - template_id=fake_uuid, - step_index=1 + "main.tour_step", service_id=SERVICE_ONE_ID, template_id=fake_uuid, step_index=1 ) - assert page.select('.usa-back-link')[0]['href'] == url_for( - "main.begin_tour", - service_id=SERVICE_ONE_ID, - template_id=fake_uuid + assert page.select(".usa-back-link")[0]["href"] == url_for( + "main.begin_tour", service_id=SERVICE_ONE_ID, template_id=fake_uuid ) @@ -335,20 +311,14 @@ def test_back_link_from_get_tour_step_points_to_previous_step( fake_uuid, ): with client_request.session_transaction() as session: - session['placeholders'] = {} + session["placeholders"] = {} page = client_request.get( - 'main.tour_step', - service_id=SERVICE_ONE_ID, - template_id=fake_uuid, - step_index=2 + "main.tour_step", service_id=SERVICE_ONE_ID, template_id=fake_uuid, step_index=2 ) - assert page.select('.usa-back-link')[0]['href'] == url_for( - 'main.tour_step', - service_id=SERVICE_ONE_ID, - template_id=fake_uuid, - step_index=1 + assert page.select(".usa-back-link")[0]["href"] == url_for( + "main.tour_step", service_id=SERVICE_ONE_ID, template_id=fake_uuid, step_index=1 ) @@ -359,17 +329,17 @@ def test_post_tour_step_saves_data_and_redirects_to_next_step( fake_uuid, ): with client_request.session_transaction() as session: - session['placeholders'] = {} + session["placeholders"] = {} client_request.post( - 'main.tour_step', + "main.tour_step", service_id=SERVICE_ONE_ID, template_id=fake_uuid, step_index=1, - _data={'placeholder_value': 'hello'}, + _data={"placeholder_value": "hello"}, _expected_status=302, _expected_redirect=url_for( - 'main.tour_step', + "main.tour_step", service_id=SERVICE_ONE_ID, template_id=fake_uuid, step_index=2, @@ -377,7 +347,10 @@ def test_post_tour_step_saves_data_and_redirects_to_next_step( ) with client_request.session_transaction() as session: - assert session['placeholders'] == {'one': 'hello', 'phone number': '202-867-5303'} + assert session["placeholders"] == { + "one": "hello", + "phone number": "202-867-5303", + } def test_post_tour_step_adds_data_to_saved_data_and_redirects_to_next_step( @@ -387,17 +360,17 @@ def test_post_tour_step_adds_data_to_saved_data_and_redirects_to_next_step( fake_uuid, ): with client_request.session_transaction() as session: - session['placeholders'] = {'one': 'hello', 'phone number': '202-867-5303'} + session["placeholders"] = {"one": "hello", "phone number": "202-867-5303"} client_request.post( - 'main.tour_step', + "main.tour_step", service_id=SERVICE_ONE_ID, template_id=fake_uuid, step_index=2, - _data={'placeholder_value': 'is it me you are looking for'}, + _data={"placeholder_value": "is it me you are looking for"}, _expected_status=302, _expected_redirect=url_for( - 'main.tour_step', + "main.tour_step", service_id=SERVICE_ONE_ID, template_id=fake_uuid, step_index=3, @@ -405,8 +378,10 @@ def test_post_tour_step_adds_data_to_saved_data_and_redirects_to_next_step( ) with client_request.session_transaction() as session: - assert session['placeholders'] == { - 'one': 'hello', 'two': 'is it me you are looking for', 'phone number': '202-867-5303' + assert session["placeholders"] == { + "one": "hello", + "two": "is it me you are looking for", + "phone number": "202-867-5303", } @@ -417,37 +392,31 @@ def test_post_tour_step_raises_validation_error_for_form_error( fake_uuid, ): with client_request.session_transaction() as session: - session['placeholders'] = {'one': 'hi', 'phone number': '202-867-5303'} + session["placeholders"] = {"one": "hi", "phone number": "202-867-5303"} page = client_request.post( - 'main.tour_step', + "main.tour_step", service_id=SERVICE_ONE_ID, template_id=fake_uuid, step_index=2, - _data={'placeholder_value': ''}, + _data={"placeholder_value": ""}, _expected_status=200, # should this be 400 ) - assert normalize_spaces( - page.select('.usa-error-message')[0].text - ) == ( - 'Error: Cannot be empty' + assert normalize_spaces(page.select(".usa-error-message")[0].text) == ( + "Error: Cannot be empty" ) - assert normalize_spaces( - page.select('.sms-message-recipient')[0].text - ) == ( - 'To: 202-867-5303' + assert normalize_spaces(page.select(".sms-message-recipient")[0].text) == ( + "To: 202-867-5303" ) - assert normalize_spaces( - page.select('.sms-message-wrapper')[0].text - ) == ( - 'service one: hi ((two)) ((three))' + assert normalize_spaces(page.select(".sms-message-wrapper")[0].text) == ( + "service one: hi ((two)) ((three))" ) with client_request.session_transaction() as session: - assert session['placeholders'] == {'one': 'hi', 'phone number': '202-867-5303'} + assert session["placeholders"] == {"one": "hi", "phone number": "202-867-5303"} def test_post_final_tour_step_saves_data_and_redirects_to_check_notification( @@ -457,25 +426,32 @@ def test_post_final_tour_step_saves_data_and_redirects_to_check_notification( fake_uuid, ): with client_request.session_transaction() as session: - session['placeholders'] = {'one': 'hello', 'two': 'hi', 'phone number': '202-867-5303'} + session["placeholders"] = { + "one": "hello", + "two": "hi", + "phone number": "202-867-5303", + } client_request.post( - 'main.tour_step', + "main.tour_step", service_id=SERVICE_ONE_ID, template_id=fake_uuid, step_index=3, - _data={'placeholder_value': 'howdy'}, + _data={"placeholder_value": "howdy"}, _expected_status=302, _expected_redirect=url_for( - 'main.check_tour_notification', + "main.check_tour_notification", service_id=SERVICE_ONE_ID, template_id=fake_uuid, ), ) with client_request.session_transaction() as session: - assert session['placeholders'] == { - 'one': 'hello', 'two': 'hi', 'three': 'howdy', 'phone number': '202-867-5303' + assert session["placeholders"] == { + "one": "hello", + "two": "hi", + "three": "howdy", + "phone number": "202-867-5303", } @@ -486,16 +462,16 @@ def test_get_test_step_out_of_index_redirects_to_first_step( fake_uuid, ): with client_request.session_transaction() as session: - session['placeholders'] = {} + session["placeholders"] = {} client_request.get( - 'main.tour_step', + "main.tour_step", service_id=SERVICE_ONE_ID, template_id=fake_uuid, step_index=4, _expected_status=302, _expected_redirect=url_for( - 'main.tour_step', + "main.tour_step", service_id=SERVICE_ONE_ID, template_id=fake_uuid, step_index=1, @@ -510,16 +486,21 @@ def test_get_test_step_out_of_index_redirects_to_check_notification_if_all_place fake_uuid, ): with client_request.session_transaction() as session: - session['placeholders'] = {'one': 'hello', 'two': 'hi', 'three': 'howdy', 'phone number': '202-867-5303'} + session["placeholders"] = { + "one": "hello", + "two": "hi", + "three": "howdy", + "phone number": "202-867-5303", + } client_request.get( - 'main.tour_step', + "main.tour_step", service_id=SERVICE_ONE_ID, template_id=fake_uuid, step_index=4, _expected_status=302, _expected_redirect=url_for( - 'main.check_tour_notification', + "main.check_tour_notification", service_id=SERVICE_ONE_ID, template_id=fake_uuid, ), @@ -533,41 +514,40 @@ def test_should_200_for_check_tour_notification( fake_uuid, ): with client_request.session_transaction() as session: - session['placeholders'] = {'one': 'hello', 'two': 'hi', 'three': 'howdy', 'phone number': '202-867-5303'} + session["placeholders"] = { + "one": "hello", + "two": "hi", + "three": "howdy", + "phone number": "202-867-5303", + } page = client_request.get( - 'main.check_tour_notification', + "main.check_tour_notification", service_id=SERVICE_ONE_ID, template_id=fake_uuid, ) - assert normalize_spaces( - page.select('.banner-tour .heading-medium')[0].text - ) == ( - 'Try sending yourself this example' + assert normalize_spaces(page.select(".banner-tour .heading-medium")[0].text) == ( + "Try sending yourself this example" ) - selected_hint = page.select('.banner-tour .govuk-grid-row')[1] + selected_hint = page.select(".banner-tour .govuk-grid-row")[1] selected_hint_text = normalize_spaces(selected_hint.select(".govuk-body")[0].text) assert "greyed-out-step" not in selected_hint["class"] - assert selected_hint_text == 'The template pulls in the data you provide' + assert selected_hint_text == "The template pulls in the data you provide" - assert normalize_spaces( - page.select('.sms-message-recipient')[0].text - ) == ( - 'To: 202-867-5303' + assert normalize_spaces(page.select(".sms-message-recipient")[0].text) == ( + "To: 202-867-5303" ) - assert normalize_spaces( - page.select('.sms-message-wrapper')[0].text - ) == ( - 'service one: hello hi howdy' + assert normalize_spaces(page.select(".sms-message-wrapper")[0].text) == ( + "service one: hello hi howdy" ) # post to send_notification keeps help argument - assert page.form.attrs['action'] == url_for( - 'main.send_notification', + assert page.form.attrs["action"] == url_for( + "main.send_notification", service_id=SERVICE_ONE_ID, template_id=fake_uuid, - help='3' + help="3", ) @@ -578,19 +558,21 @@ def test_back_link_from_check_tour_notification_points_to_last_tour_step( fake_uuid, ): with client_request.session_transaction() as session: - session['placeholders'] = {'one': 'hello', 'two': 'hi', 'three': 'howdy', 'phone number': '202-867-5303'} + session["placeholders"] = { + "one": "hello", + "two": "hi", + "three": "howdy", + "phone number": "202-867-5303", + } page = client_request.get( - 'main.check_tour_notification', + "main.check_tour_notification", service_id=SERVICE_ONE_ID, template_id=fake_uuid, ) - assert page.select('.usa-back-link')[0]['href'] == url_for( - "main.tour_step", - service_id=SERVICE_ONE_ID, - template_id=fake_uuid, - step_index=3 + assert page.select(".usa-back-link")[0]["href"] == url_for( + "main.tour_step", service_id=SERVICE_ONE_ID, template_id=fake_uuid, step_index=3 ) @@ -601,16 +583,16 @@ def test_check_tour_notification_redirects_to_tour_start_if_placeholders_doesnt_ fake_uuid, ): with client_request.session_transaction() as session: - assert 'placeholders' not in session + assert "placeholders" not in session client_request.get( - 'main.check_tour_notification', + "main.check_tour_notification", service_id=SERVICE_ONE_ID, template_id=fake_uuid, step_index=1, _expected_status=302, _expected_redirect=url_for( - 'main.begin_tour', + "main.begin_tour", service_id=SERVICE_ONE_ID, template_id=fake_uuid, ), @@ -624,15 +606,19 @@ def test_check_tour_notification_redirects_to_first_step_if_not_all_placeholders fake_uuid, ): with client_request.session_transaction() as session: - session['placeholders'] = {'one': 'hello', 'two': 'hi', 'phone number': '202-867-5303'} + session["placeholders"] = { + "one": "hello", + "two": "hi", + "phone number": "202-867-5303", + } client_request.get( - 'main.check_tour_notification', + "main.check_tour_notification", service_id=SERVICE_ONE_ID, template_id=fake_uuid, _expected_status=302, _expected_redirect=url_for( - 'main.tour_step', + "main.tour_step", service_id=SERVICE_ONE_ID, template_id=fake_uuid, step_index=1, @@ -645,18 +631,17 @@ def test_shows_link_to_end_tour( mock_get_notification, fake_uuid, ): - page = client_request.get( - 'main.view_notification', + "main.view_notification", service_id=SERVICE_ONE_ID, notification_id=fake_uuid, help=3, ) - assert page.select(".banner-tour a")[0]['href'] == url_for( - 'main.go_to_dashboard_after_tour', + assert page.select(".banner-tour a")[0]["href"] == url_for( + "main.go_to_dashboard_after_tour", service_id=SERVICE_ONE_ID, - example_template_id='5407f4db-51c7-4150-8758-35412d42186a', + example_template_id="5407f4db-51c7-4150-8758-35412d42186a", ) @@ -668,16 +653,16 @@ def test_go_to_dashboard_after_tour_link( mock_get_service, mock_has_permissions, mock_delete_service_template, - fake_uuid + fake_uuid, ): client_request.get( - 'main.go_to_dashboard_after_tour', + "main.go_to_dashboard_after_tour", service_id=fake_uuid, example_template_id=fake_uuid, _expected_redirect=url_for( "main.service_dashboard", service_id=fake_uuid, - ) + ), ) mock_delete_service_template.assert_called_once_with(fake_uuid, fake_uuid) diff --git a/tests/app/main/views/test_two_factor.py b/tests/app/main/views/test_two_factor.py index 8144faa19..b51fbf39a 100644 --- a/tests/app/main/views/test_two_factor.py +++ b/tests/app/main/views/test_two_factor.py @@ -11,70 +11,71 @@ from tests.conftest import ( @pytest.fixture def mock_email_validated_recently(mocker): - return mocker.patch('app.main.views.two_factor.email_needs_revalidating', return_value=False) + return mocker.patch( + "app.main.views.two_factor.email_needs_revalidating", return_value=False + ) -@pytest.mark.parametrize('request_url', ['two_factor_email_sent', 'revalidate_email_sent']) -@pytest.mark.parametrize('redirect_url', [None, f'/services/{SERVICE_ONE_ID}/templates']) -@pytest.mark.parametrize('email_resent, page_title', [ - (None, 'Check your email'), - (True, 'Email resent') -]) +@pytest.mark.parametrize( + "request_url", ["two_factor_email_sent", "revalidate_email_sent"] +) +@pytest.mark.parametrize( + "redirect_url", [None, f"/services/{SERVICE_ONE_ID}/templates"] +) +@pytest.mark.parametrize( + "email_resent, page_title", [(None, "Check your email"), (True, "Email resent")] +) def test_two_factor_email_sent_page( - client_request, - email_resent, - page_title, - redirect_url, - request_url + client_request, email_resent, page_title, redirect_url, request_url ): client_request.logout() page = client_request.get( - f'main.{request_url}', + f"main.{request_url}", next=redirect_url, email_resent=email_resent, ) assert page.h1.string == page_title # there shouldn't be a form for updating mobile number - assert page.find('form') is None - resend_email_link = page.find('a', class_="usa-link") - assert resend_email_link.text == 'Not received an email?' - assert resend_email_link['href'] == url_for('main.email_not_received', next=redirect_url) + assert page.find("form") is None + resend_email_link = page.find("a", class_="usa-link") + assert resend_email_link.text == "Not received an email?" + assert resend_email_link["href"] == url_for( + "main.email_not_received", next=redirect_url + ) -@pytest.mark.parametrize('redirect_url', [ - None, - f'/services/{SERVICE_ONE_ID}/templates', -]) +@pytest.mark.parametrize( + "redirect_url", + [ + None, + f"/services/{SERVICE_ONE_ID}/templates", + ], +) def test_should_render_two_factor_page( - client_request, - api_user_active, - mock_get_user_by_email, - mocker, - redirect_url + client_request, api_user_active, mock_get_user_by_email, mocker, redirect_url ): client_request.logout() # TODO this lives here until we work out how to # reassign the session after it is lost mid register process with client_request.session_transaction() as session: - session['user_details'] = { - 'id': api_user_active['id'], - 'email': api_user_active['email_address']} - mocker.patch('app.user_api_client.get_user', return_value=api_user_active) - page = client_request.get('main.two_factor_sms', next=redirect_url) + session["user_details"] = { + "id": api_user_active["id"], + "email": api_user_active["email_address"], + } + mocker.patch("app.user_api_client.get_user", return_value=api_user_active) + page = client_request.get("main.two_factor_sms", next=redirect_url) - assert page.select_one('main p').text.strip() == ( - 'We’ve sent you a text message with a security code.' + assert page.select_one("main p").text.strip() == ( + "We’ve sent you a text message with a security code." ) - assert page.select_one('label').text.strip() == ( - 'Text message code' - ) - assert page.select_one('input')['type'] == 'tel' - assert page.select_one('input')['pattern'] == '[0-9]*' + assert page.select_one("label").text.strip() == ("Text message code") + assert page.select_one("input")["type"] == "tel" + assert page.select_one("input")["pattern"] == "[0-9]*" - assert page.select_one( - 'a:contains("Not received a text message?")' - )['href'] == url_for('main.check_and_resend_text_code', next=redirect_url) + assert page.select_one('a:contains("Not received a text message?")')[ + "href" + ] == url_for("main.check_and_resend_text_code", next=redirect_url) def test_should_login_user_and_should_redirect_to_next_url( @@ -89,16 +90,17 @@ def test_should_login_user_and_should_redirect_to_next_url( client_request.logout() with client_request.session_transaction() as session: - session['user_details'] = { - 'id': api_user_active['id'], - 'email': api_user_active['email_address']} + session["user_details"] = { + "id": api_user_active["id"], + "email": api_user_active["email_address"], + } client_request.post( - 'main.two_factor_sms', - next='/services/{}'.format(SERVICE_ONE_ID), - _data={'sms_code': '123456'}, + "main.two_factor_sms", + next="/services/{}".format(SERVICE_ONE_ID), + _data={"sms_code": "123456"}, _expected_redirect=url_for( - 'main.service_dashboard', + "main.service_dashboard", service_id=SERVICE_ONE_ID, ), ) @@ -111,27 +113,31 @@ def test_should_send_email_and_redirect_to_info_page_if_user_needs_to_revalidate mock_check_verify_code, mock_create_event, mock_send_verify_code, - mocker + mocker, ): client_request.logout() - mocker.patch('app.user_api_client.get_user', return_value=api_user_active) - mocker.patch('app.main.views.two_factor.email_needs_revalidating', return_value=True) + mocker.patch("app.user_api_client.get_user", return_value=api_user_active) + mocker.patch( + "app.main.views.two_factor.email_needs_revalidating", return_value=True + ) with client_request.session_transaction() as session: - session['user_details'] = { - 'id': api_user_active['id'], - 'email': api_user_active['email_address']} + session["user_details"] = { + "id": api_user_active["id"], + "email": api_user_active["email_address"], + } client_request.post( - 'main.two_factor_sms', - next=f'/services/{SERVICE_ONE_ID}', - _data={'sms_code': '123456'}, + "main.two_factor_sms", + next=f"/services/{SERVICE_ONE_ID}", + _data={"sms_code": "123456"}, _expected_redirect=url_for( - 'main.revalidate_email_sent', - next=f'/services/{SERVICE_ONE_ID}' + "main.revalidate_email_sent", next=f"/services/{SERVICE_ONE_ID}" ), ) - mock_send_verify_code.assert_called_with(api_user_active['id'], 'email', None, mocker.ANY) + mock_send_verify_code.assert_called_with( + api_user_active["id"], "email", None, mocker.ANY + ) def test_should_login_user_and_not_redirect_to_external_url( @@ -147,21 +153,26 @@ def test_should_login_user_and_not_redirect_to_external_url( client_request.logout() with client_request.session_transaction() as session: - session['user_details'] = { - 'id': api_user_active['id'], - 'email': api_user_active['email_address']} + session["user_details"] = { + "id": api_user_active["id"], + "email": api_user_active["email_address"], + } client_request.post( - 'main.two_factor_sms', - next='http://www.google.com', - _data={'sms_code': '123456'}, - _expected_redirect=url_for('main.show_accounts_or_dashboard') + "main.two_factor_sms", + next="http://www.google.com", + _data={"sms_code": "123456"}, + _expected_redirect=url_for("main.show_accounts_or_dashboard"), ) -@pytest.mark.parametrize('platform_admin', ( - True, False, -)) +@pytest.mark.parametrize( + "platform_admin", + ( + True, + False, + ), +) def test_should_login_user_and_redirect_to_show_accounts( client_request, api_user_active, @@ -175,15 +186,16 @@ def test_should_login_user_and_redirect_to_show_accounts( client_request.logout() with client_request.session_transaction() as session: - session['user_details'] = { - 'id': api_user_active['id'], - 'email': api_user_active['email_address']} - api_user_active['platform_admin'] = platform_admin + session["user_details"] = { + "id": api_user_active["id"], + "email": api_user_active["email_address"], + } + api_user_active["platform_admin"] = platform_admin client_request.post( - 'main.two_factor_sms', - _data={'sms_code': '123456'}, - _expected_redirect=url_for('main.show_accounts_or_dashboard') + "main.two_factor_sms", + _data={"sms_code": "123456"}, + _expected_redirect=url_for("main.show_accounts_or_dashboard"), ) @@ -192,22 +204,23 @@ def test_should_return_200_with_sms_code_error_when_sms_code_is_wrong( api_user_active, mock_get_user_by_email, mock_check_verify_code_code_not_found, - mocker + mocker, ): client_request.logout() with client_request.session_transaction() as session: - session['user_details'] = { - 'id': api_user_active['id'], - 'email': api_user_active['email_address']} - mocker.patch('app.user_api_client.get_user', return_value=api_user_active) + session["user_details"] = { + "id": api_user_active["id"], + "email": api_user_active["email_address"], + } + mocker.patch("app.user_api_client.get_user", return_value=api_user_active) page = client_request.post( - 'main.two_factor_sms', - _data={'sms_code': '234567'}, + "main.two_factor_sms", + _data={"sms_code": "234567"}, _expected_status=200, ) - assert 'Code not found' in page.text + assert "Code not found" in page.text def test_should_login_user_when_multiple_valid_codes_exist( @@ -223,13 +236,14 @@ def test_should_login_user_when_multiple_valid_codes_exist( client_request.logout() with client_request.session_transaction() as session: - session['user_details'] = { - 'id': api_user_active['id'], - 'email': api_user_active['email_address']} + session["user_details"] = { + "id": api_user_active["id"], + "email": api_user_active["email_address"], + } client_request.post( - 'main.two_factor_sms', - _data={'sms_code': '234567'}, + "main.two_factor_sms", + _data={"sms_code": "234567"}, _expected_status=302, ) @@ -247,19 +261,21 @@ def test_two_factor_sms_should_set_password_when_new_password_exists_in_session( client_request.logout() with client_request.session_transaction() as session: - session['user_details'] = { - 'id': api_user_active['id'], - 'email': api_user_active['email_address'], - 'password': 'changedpassword'} + session["user_details"] = { + "id": api_user_active["id"], + "email": api_user_active["email_address"], + "password": "changedpassword", + } client_request.post( - 'main.two_factor_sms', - _data={'sms_code': '123456'}, - _expected_redirect=url_for('main.show_accounts_or_dashboard'), + "main.two_factor_sms", + _data={"sms_code": "123456"}, + _expected_redirect=url_for("main.show_accounts_or_dashboard"), ) mock_update_user_password.assert_called_once_with( - api_user_active['id'], 'changedpassword', + api_user_active["id"], + "changedpassword", ) @@ -268,30 +284,30 @@ def test_two_factor_sms_returns_error_when_user_is_locked( api_user_locked, mock_get_locked_user, mock_check_verify_code_code_not_found, - mock_get_services_with_one_service + mock_get_services_with_one_service, ): client_request.logout() with client_request.session_transaction() as session: - session['user_details'] = { - 'id': api_user_locked['id'], - 'email': api_user_locked['email_address'], + session["user_details"] = { + "id": api_user_locked["id"], + "email": api_user_locked["email_address"], } page = client_request.post( - 'main.two_factor_sms', - _data={'sms_code': '123456'}, + "main.two_factor_sms", + _data={"sms_code": "123456"}, _expected_status=200, ) - assert 'Code not found' in page.text + assert "Code not found" in page.text def test_two_factor_sms_post_should_redirect_to_sign_in_if_user_not_in_session( client_request, ): client_request.post( - 'main.two_factor_sms', - _data={'sms_code': '123456'}, - _expected_redirect=url_for('main.sign_in') + "main.two_factor_sms", + _data={"sms_code": "123456"}, + _expected_redirect=url_for("main.sign_in"), ) @@ -305,22 +321,22 @@ def test_two_factor_sms_should_activate_pending_user( mock_email_validated_recently, ): client_request.logout() - mocker.patch('app.user_api_client.get_user', return_value=api_user_pending) - mocker.patch('app.service_api_client.get_services', return_value={'data': []}) + mocker.patch("app.user_api_client.get_user", return_value=api_user_pending) + mocker.patch("app.service_api_client.get_services", return_value={"data": []}) with client_request.session_transaction() as session: - session['user_details'] = { - 'id': api_user_pending['id'], - 'email_address': api_user_pending['email_address'] + session["user_details"] = { + "id": api_user_pending["id"], + "email_address": api_user_pending["email_address"], } - client_request.post('main.two_factor_sms', _data={'sms_code': '123456'}) + client_request.post("main.two_factor_sms", _data={"sms_code": "123456"}) assert mock_activate_user.called -@pytest.mark.parametrize('extra_args, expected_encoded_next_arg', ( - ({}, ''), - ({'next': 'https://example.com'}, '?next=https://example.com') -)) +@pytest.mark.parametrize( + "extra_args, expected_encoded_next_arg", + (({}, ""), ({"next": "https://example.com"}, "?next=https://example.com")), +) def test_valid_two_factor_email_link_shows_interstitial( client_request, valid_token, @@ -328,30 +344,28 @@ def test_valid_two_factor_email_link_shows_interstitial( extra_args, expected_encoded_next_arg, ): - mock_check_code = mocker.patch('app.user_api_client.check_verify_code') - encoded_token = valid_token.replace('%2E', '.') + mock_check_code = mocker.patch("app.user_api_client.check_verify_code") + encoded_token = valid_token.replace("%2E", ".") token_url = url_for( - 'main.two_factor_email_interstitial', - token=encoded_token, - **extra_args + "main.two_factor_email_interstitial", token=encoded_token, **extra_args ) # This must match the URL we put in the emails - assert token_url == f'/email-auth/{encoded_token}{expected_encoded_next_arg}' + assert token_url == f"/email-auth/{encoded_token}{expected_encoded_next_arg}" client_request.logout() page = client_request.get_url(token_url) - assert normalize_spaces(page.select_one('main').text) == ( - 'Click below to complete email re-verification and finish signing in. ' - 'Verify email' + assert normalize_spaces(page.select_one("main").text) == ( + "Click below to complete email re-verification and finish signing in. " + "Verify email" ) - form = page.select_one('form') - expected_form_id = 'use-email-auth' - assert 'action' not in form - assert form['method'] == 'post' - assert form['id'] == expected_form_id + form = page.select_one("form") + expected_form_id = "use-email-auth" + assert "action" not in form + assert form["method"] == "post" + assert form["id"] == expected_form_id assert mock_check_code.called is False @@ -364,106 +378,115 @@ def test_valid_two_factor_email_link_logs_in_user( mocker, mock_create_event, ): - mocker.patch('app.user_api_client.check_verify_code', return_value=(True, '')) + mocker.patch("app.user_api_client.check_verify_code", return_value=(True, "")) client_request.post_url( - url_for_endpoint_with_token('main.two_factor_email', token=valid_token), - _expected_redirect=url_for('main.show_accounts_or_dashboard') + url_for_endpoint_with_token("main.two_factor_email", token=valid_token), + _expected_redirect=url_for("main.show_accounts_or_dashboard"), ) -@pytest.mark.parametrize('redirect_url', [ - None, - f'/services/{SERVICE_ONE_ID}/templates', -]) +@pytest.mark.parametrize( + "redirect_url", + [ + None, + f"/services/{SERVICE_ONE_ID}/templates", + ], +) def test_two_factor_email_link_has_expired( notify_admin, valid_token, client_request, mock_send_verify_code, fake_uuid, - redirect_url + redirect_url, ): client_request.logout() - with set_config(notify_admin, 'EMAIL_EXPIRY_SECONDS', -1): + with set_config(notify_admin, "EMAIL_EXPIRY_SECONDS", -1): page = client_request.post_url( - url_for_endpoint_with_token('main.two_factor_email', token=valid_token, next=redirect_url), + url_for_endpoint_with_token( + "main.two_factor_email", token=valid_token, next=redirect_url + ), _follow_redirects=True, ) - assert page.h1.text.strip() == 'This link has expired' - assert page.select_one('a:contains("Sign in again")')['href'] == url_for('main.sign_in', next=redirect_url) + assert page.h1.text.strip() == "This link has expired" + assert page.select_one('a:contains("Sign in again")')["href"] == url_for( + "main.sign_in", next=redirect_url + ) assert mock_send_verify_code.called is False -def test_two_factor_email_link_is_invalid( - client_request -): +def test_two_factor_email_link_is_invalid(client_request): client_request.logout() token = 12345 page = client_request.post( - 'main.two_factor_email', + "main.two_factor_email", token=token, _follow_redirects=True, _expected_status=404, ) - assert normalize_spaces( - page.select_one('.banner-dangerous').text - ) == "There’s something wrong with the link you’ve used." + assert ( + normalize_spaces(page.select_one(".banner-dangerous").text) + == "There’s something wrong with the link you’ve used." + ) -@pytest.mark.parametrize('redirect_url', [ - None, - f'/services/{SERVICE_ONE_ID}/templates', -]) +@pytest.mark.parametrize( + "redirect_url", + [ + None, + f"/services/{SERVICE_ONE_ID}/templates", + ], +) def test_two_factor_email_link_is_already_used( - client_request, - valid_token, - mocker, - mock_send_verify_code, - redirect_url - + client_request, valid_token, mocker, mock_send_verify_code, redirect_url ): client_request.logout() - mocker.patch('app.user_api_client.check_verify_code', return_value=(False, 'Code has expired')) + mocker.patch( + "app.user_api_client.check_verify_code", + return_value=(False, "Code has expired"), + ) page = client_request.post_url( - url_for_endpoint_with_token('main.two_factor_email', token=valid_token, next=redirect_url), + url_for_endpoint_with_token( + "main.two_factor_email", token=valid_token, next=redirect_url + ), _follow_redirects=True, ) - assert page.h1.text.strip() == 'This link has expired' - assert page.select_one('a:contains("Sign in again")')['href'] == url_for('main.sign_in', next=redirect_url) + assert page.h1.text.strip() == "This link has expired" + assert page.select_one('a:contains("Sign in again")')["href"] == url_for( + "main.sign_in", next=redirect_url + ) assert mock_send_verify_code.called is False def test_two_factor_email_link_when_user_is_locked_out( - client_request, - valid_token, - mocker, - mock_send_verify_code + client_request, valid_token, mocker, mock_send_verify_code ): client_request.logout() - mocker.patch('app.user_api_client.check_verify_code', return_value=(False, 'Code not found')) + mocker.patch( + "app.user_api_client.check_verify_code", return_value=(False, "Code not found") + ) page = client_request.post_url( - url_for_endpoint_with_token('main.two_factor_email', token=valid_token), + url_for_endpoint_with_token("main.two_factor_email", token=valid_token), _follow_redirects=True, ) - assert page.h1.text.strip() == 'This link has expired' + assert page.h1.text.strip() == "This link has expired" assert mock_send_verify_code.called is False def test_two_factor_email_link_used_when_user_already_logged_in( - client_request, - valid_token + client_request, valid_token ): client_request.post_url( - url_for_endpoint_with_token('main.two_factor_email', token=valid_token), - _expected_redirect=url_for('main.show_accounts_or_dashboard'), + url_for_endpoint_with_token("main.two_factor_email", token=valid_token), + _expected_redirect=url_for("main.show_accounts_or_dashboard"), ) diff --git a/tests/app/main/views/test_user_profile.py b/tests/app/main/views/test_user_profile.py index ca2f4cb2f..9b171a173 100644 --- a/tests/app/main/views/test_user_profile.py +++ b/tests/app/main/views/test_user_profile.py @@ -16,29 +16,28 @@ from tests.conftest import ( def test_should_show_overview_page( client_request, ): - page = client_request.get('main.user_profile') - assert page.select_one('h1').text.strip() == 'Your profile' - assert 'Use platform admin view' not in page - assert 'Security keys' not in page + page = client_request.get("main.user_profile") + assert page.select_one("h1").text.strip() == "Your profile" + assert "Use platform admin view" not in page + assert "Security keys" not in page def test_overview_page_shows_disable_for_platform_admin( - client_request, - platform_admin_user + client_request, platform_admin_user ): client_request.login(platform_admin_user) - page = client_request.get('main.user_profile') - assert page.select_one('h1').text.strip() == 'Your profile' - disable_platform_admin_row = page.select_one('#disable-platform-admin') - assert ' '.join(disable_platform_admin_row.text.split()) == \ - 'Use platform admin view Yes Change whether to use platform admin view' + page = client_request.get("main.user_profile") + assert page.select_one("h1").text.strip() == "Your profile" + disable_platform_admin_row = page.select_one("#disable-platform-admin") + assert ( + " ".join(disable_platform_admin_row.text.split()) + == "Use platform admin view Yes Change whether to use platform admin view" + ) -def test_should_show_name_page( - client_request -): - page = client_request.get(('main.user_profile_name')) - assert page.select_one('h1').text.strip() == 'Change your name' +def test_should_show_name_page(client_request): + page = client_request.get(("main.user_profile_name")) + assert page.select_one("h1").text.strip() == "Change your name" def test_should_redirect_after_name_change( @@ -46,10 +45,10 @@ def test_should_redirect_after_name_change( mock_update_user_attribute, ): client_request.post( - 'main.user_profile_name', - _data={'new_name': 'New Name'}, + "main.user_profile_name", + _data={"new_name": "New Name"}, _expected_status=302, - _expected_redirect=url_for('main.user_profile'), + _expected_redirect=url_for("main.user_profile"), ) assert mock_update_user_attribute.called is True @@ -57,12 +56,10 @@ def test_should_redirect_after_name_change( def test_should_show_email_page( client_request, ): - page = client_request.get( - 'main.user_profile_email' - ) - assert page.select_one('h1').text.strip() == 'Change your email address' + page = client_request.get("main.user_profile_email") + assert page.select_one("h1").text.strip() == "Change your email address" # template is shared with "Change your mobile number" but we don't want to show Delete mobile number link - assert 'Delete your number' not in page.text + assert "Delete your number" not in page.text def test_should_redirect_after_email_change( @@ -71,21 +68,30 @@ def test_should_redirect_after_email_change( mock_email_is_not_already_in_use, ): client_request.post( - 'main.user_profile_email', - _data={'email_address': 'new_notify@notify.gsa.gov'}, + "main.user_profile_email", + _data={"email_address": "new_notify@notify.gsa.gov"}, _expected_status=302, _expected_redirect=url_for( - 'main.user_profile_email_authenticate', - ) + "main.user_profile_email_authenticate", + ), ) assert mock_email_is_not_already_in_use.called -@pytest.mark.parametrize('email_address,error_message', [ - ('me@example.com', 'Enter a public sector email address or find out who can use Notify'), - ('not_valid', 'Enter a valid email address') # 2 errors with email address, only first error shown -]) +@pytest.mark.parametrize( + "email_address,error_message", + [ + ( + "me@example.com", + "Enter a public sector email address or find out who can use Notify", + ), + ( + "not_valid", + "Enter a valid email address", + ), # 2 errors with email address, only first error shown + ], +) def test_should_show_errors_if_new_email_address_does_not_validate( client_request, mock_email_is_not_already_in_use, @@ -94,12 +100,15 @@ def test_should_show_errors_if_new_email_address_does_not_validate( error_message, ): page = client_request.post( - 'main.user_profile_email', - _data={'email_address': email_address}, + "main.user_profile_email", + _data={"email_address": email_address}, _expected_status=200, ) - assert normalize_spaces(page.find('span', class_='usa-error-message').text) == f'Error: {error_message}' + assert ( + normalize_spaces(page.find("span", class_="usa-error-message").text) + == f"Error: {error_message}" + ) # We only call API to check if the email address is already in use if there are no other errors assert not mock_email_is_not_already_in_use.called @@ -108,12 +117,12 @@ def test_should_show_authenticate_after_email_change( client_request, ): with client_request.session_transaction() as session: - session['new-email'] = 'new_notify@notify.gsa.gov' + session["new-email"] = "new_notify@notify.gsa.gov" - page = client_request.get('main.user_profile_email_authenticate') + page = client_request.get("main.user_profile_email_authenticate") - assert 'Change your email address' in page.text - assert 'Confirm' in page.text + assert "Change your email address" in page.text + assert "Confirm" in page.text def test_should_render_change_email_continue_after_authenticate_email( @@ -122,13 +131,16 @@ def test_should_render_change_email_continue_after_authenticate_email( mock_send_change_email_verification, ): with client_request.session_transaction() as session: - session['new-email'] = 'new_notify@notify.gsa.gov' + session["new-email"] = "new_notify@notify.gsa.gov" page = client_request.post( - 'main.user_profile_email_authenticate', - _data={'password': '12345'}, + "main.user_profile_email_authenticate", + _data={"password": "12345"}, _expected_status=200, ) - assert 'Click the link in the email to confirm the change to your email address.' in page.text + assert ( + "Click the link in the email to confirm the change to your email address." + in page.text + ) def test_should_redirect_to_user_profile_when_user_confirms_email_link( @@ -137,125 +149,116 @@ def test_should_redirect_to_user_profile_when_user_confirms_email_link( api_user_active, mock_update_user_attribute, ): - - token = generate_token(payload=json.dumps({'user_id': api_user_active['id'], 'email': 'new_email@gsa.gov'}), - secret=notify_admin.config['SECRET_KEY'], salt=notify_admin.config['DANGEROUS_SALT']) + token = generate_token( + payload=json.dumps( + {"user_id": api_user_active["id"], "email": "new_email@gsa.gov"} + ), + secret=notify_admin.config["SECRET_KEY"], + salt=notify_admin.config["DANGEROUS_SALT"], + ) client_request.get_url( url_for_endpoint_with_token( - 'main.user_profile_email_confirm', + "main.user_profile_email_confirm", token=token, ), - _expected_redirect=url_for('main.user_profile'), + _expected_redirect=url_for("main.user_profile"), ) def test_should_show_mobile_number_page( client_request, ): - page = client_request.get(('main.user_profile_mobile_number')) - assert 'Change your mobile number' in page.text - assert 'Delete your number' not in page.text + page = client_request.get(("main.user_profile_mobile_number")) + assert "Change your mobile number" in page.text + assert "Delete your number" not in page.text def test_change_your_mobile_number_page_shows_delete_link_if_user_on_email_auth( - client_request, - api_user_active_email_auth, - mocker + client_request, api_user_active_email_auth, mocker ): client_request.login(api_user_active_email_auth) - page = client_request.get(('main.user_profile_mobile_number')) - assert 'Change your mobile number' in page.text - assert 'Delete your number' in page.text + page = client_request.get(("main.user_profile_mobile_number")) + assert "Change your mobile number" in page.text + assert "Delete your number" in page.text def test_change_your_mobile_number_page_doesnt_show_delete_link_if_user_has_no_mobile_number( - client_request, - fake_uuid, - mocker + client_request, fake_uuid, mocker ): - user = create_user( - id=fake_uuid, - auth_type='email_auth', - mobile_number=None - ) + user = create_user(id=fake_uuid, auth_type="email_auth", mobile_number=None) client_request.login(user) - page = client_request.get(('main.user_profile_mobile_number')) - assert 'Change your mobile number' in page.text - assert 'Delete your number' not in page.text + page = client_request.get(("main.user_profile_mobile_number")) + assert "Change your mobile number" in page.text + assert "Delete your number" not in page.text def test_confirm_delete_mobile_number( - client_request, - api_user_active_email_auth, - mocker + client_request, api_user_active_email_auth, mocker ): client_request.login(api_user_active_email_auth) page = client_request.get( - '.user_profile_confirm_delete_mobile_number', + ".user_profile_confirm_delete_mobile_number", _test_page_title=False, ) - assert normalize_spaces(page.select_one('.banner-dangerous').text) == ( - 'Are you sure you want to delete your mobile number from Notify? ' - 'Yes, delete' + assert normalize_spaces(page.select_one(".banner-dangerous").text) == ( + "Are you sure you want to delete your mobile number from Notify? " "Yes, delete" ) - assert 'action' not in page.select_one('.banner-dangerous form') - assert page.select_one('.banner-dangerous form')['method'] == 'post' + assert "action" not in page.select_one(".banner-dangerous form") + assert page.select_one(".banner-dangerous form")["method"] == "post" -def test_delete_mobile_number( - client_request, - api_user_active_email_auth, - mocker -): +def test_delete_mobile_number(client_request, api_user_active_email_auth, mocker): client_request.login(api_user_active_email_auth) - mock_delete = mocker.patch('app.user_api_client.update_user_attribute') + mock_delete = mocker.patch("app.user_api_client.update_user_attribute") client_request.post( - '.user_profile_mobile_number_delete', + ".user_profile_mobile_number_delete", _expected_redirect=url_for( - '.user_profile', - ) + ".user_profile", + ), ) mock_delete.assert_called_once_with( - api_user_active_email_auth["id"], - mobile_number=None + api_user_active_email_auth["id"], mobile_number=None ) -@pytest.mark.parametrize('phone_number_to_register_with', [ - '+12024900460', - '+1800-555-5555', -]) +@pytest.mark.parametrize( + "phone_number_to_register_with", + [ + "+12024900460", + "+1800-555-5555", + ], +) def test_should_redirect_after_mobile_number_change( client_request, phone_number_to_register_with, ): client_request.post( - 'main.user_profile_mobile_number', - _data={'mobile_number': phone_number_to_register_with}, + "main.user_profile_mobile_number", + _data={"mobile_number": phone_number_to_register_with}, _expected_status=302, _expected_redirect=url_for( - 'main.user_profile_mobile_number_authenticate', - ) + "main.user_profile_mobile_number_authenticate", + ), ) with client_request.session_transaction() as session: - assert session['new-mob'] == phone_number_to_register_with + assert session["new-mob"] == phone_number_to_register_with def test_should_show_authenticate_after_mobile_number_change( client_request, ): with client_request.session_transaction() as session: - session['new-mob'] = '+12021234123' + session["new-mob"] = "+12021234123" page = client_request.get( - 'main.user_profile_mobile_number_authenticate', + "main.user_profile_mobile_number_authenticate", ) - assert 'Change your mobile number' in page.text - assert 'Confirm' in page.text + assert "Change your mobile number" in page.text + assert "Confirm" in page.text def test_should_redirect_after_mobile_number_authenticate( @@ -264,15 +267,15 @@ def test_should_redirect_after_mobile_number_authenticate( mock_send_verify_code, ): with client_request.session_transaction() as session: - session['new-mob'] = '+12021234123' + session["new-mob"] = "+12021234123" client_request.post( - 'main.user_profile_mobile_number_authenticate', - _data={'password': '12345667'}, + "main.user_profile_mobile_number_authenticate", + _data={"password": "12345667"}, _expected_status=302, _expected_redirect=url_for( - 'main.user_profile_mobile_number_confirm', - ) + "main.user_profile_mobile_number_confirm", + ), ) @@ -280,19 +283,20 @@ def test_should_show_confirm_after_mobile_number_change( client_request, ): with client_request.session_transaction() as session: - session['new-mob-password-confirmed'] = True - page = client_request.get( - 'main.user_profile_mobile_number_confirm' - ) + session["new-mob-password-confirmed"] = True + page = client_request.get("main.user_profile_mobile_number_confirm") - assert 'Change your mobile number' in page.text - assert 'Confirm' in page.text + assert "Change your mobile number" in page.text + assert "Confirm" in page.text -@pytest.mark.parametrize('phone_number_to_register_with', [ - '+12020900460', - '+1800-555-555', -]) +@pytest.mark.parametrize( + "phone_number_to_register_with", + [ + "+12020900460", + "+1800-555-555", + ], +) def test_should_redirect_after_mobile_number_confirm( client_request, mocker, @@ -302,37 +306,37 @@ def test_should_redirect_after_mobile_number_confirm( ): user_before = create_api_user_active(with_unique_id=True) user_after = create_api_user_active(with_unique_id=True) - user_before['current_session_id'] = str(uuid.UUID(int=1)) - user_after['current_session_id'] = str(uuid.UUID(int=2)) + user_before["current_session_id"] = str(uuid.UUID(int=1)) + user_after["current_session_id"] = str(uuid.UUID(int=2)) client_request.login(user_before) - mocker.patch('app.user_api_client.get_user', side_effect=[user_after]) + mocker.patch("app.user_api_client.get_user", side_effect=[user_after]) with client_request.session_transaction() as session: - session['new-mob-password-confirmed'] = True - session['new-mob'] = phone_number_to_register_with - session['current_session_id'] = user_before['current_session_id'] + session["new-mob-password-confirmed"] = True + session["new-mob"] = phone_number_to_register_with + session["current_session_id"] = user_before["current_session_id"] client_request.post( - 'main.user_profile_mobile_number_confirm', - _data={'sms_code': '123456'}, + "main.user_profile_mobile_number_confirm", + _data={"sms_code": "123456"}, _expected_status=302, _expected_redirect=url_for( - 'main.user_profile', - ) + "main.user_profile", + ), ) # make sure the current_session_id has changed to what the API returned with client_request.session_transaction() as session: - assert session['current_session_id'] == user_after['current_session_id'] + assert session["current_session_id"] == user_after["current_session_id"] def test_should_show_password_page( client_request, ): - page = client_request.get(('main.user_profile_password')) + page = client_request.get(("main.user_profile_password")) - assert page.select_one('h1').text.strip() == 'Change your password' + assert page.select_one("h1").text.strip() == "Change your password" def test_should_redirect_after_password_change( @@ -341,14 +345,14 @@ def test_should_redirect_after_password_change( mock_verify_password, ): client_request.post( - 'main.user_profile_password', + "main.user_profile_password", _data={ - 'new_password': 'the new password', - 'old_password': 'the old password', + "new_password": "the new password", + "old_password": "the old password", }, _expected_status=302, _expected_redirect=url_for( - 'main.user_profile', + "main.user_profile", ), ) @@ -359,9 +363,9 @@ def test_non_gov_user_cannot_see_change_email_link( mock_get_organizations, ): client_request.login(api_nongov_user_active) - page = client_request.get('main.user_profile') - assert not page.find('a', {'href': url_for('main.user_profile_email')}) - assert page.select_one('h1').text.strip() == 'Your profile' + page = client_request.get("main.user_profile") + assert not page.find("a", {"href": url_for("main.user_profile_email")}) + assert page.select_one("h1").text.strip() == "Your profile" def test_non_gov_user_cannot_access_change_email_page( @@ -370,50 +374,54 @@ def test_non_gov_user_cannot_access_change_email_page( mock_get_organizations, ): client_request.login(api_nongov_user_active) - client_request.get('main.user_profile_email', _expected_status=403) + client_request.get("main.user_profile_email", _expected_status=403) def test_normal_user_doesnt_see_disable_platform_admin(client_request): - client_request.get('main.user_profile_disable_platform_admin_view', _expected_status=403) + client_request.get( + "main.user_profile_disable_platform_admin_view", _expected_status=403 + ) -def test_platform_admin_can_see_disable_platform_admin_page(client_request, platform_admin_user): +def test_platform_admin_can_see_disable_platform_admin_page( + client_request, platform_admin_user +): client_request.login(platform_admin_user) - page = client_request.get('main.user_profile_disable_platform_admin_view') + page = client_request.get("main.user_profile_disable_platform_admin_view") - assert page.select_one('h1').text.strip() == 'Use platform admin view' - assert page.select_one('input[checked]')['value'] == 'True' + assert page.select_one("h1").text.strip() == "Use platform admin view" + assert page.select_one("input[checked]")["value"] == "True" def test_can_disable_platform_admin(client_request, platform_admin_user): client_request.login(platform_admin_user) with client_request.session_transaction() as session: - assert 'disable_platform_admin_view' not in session + assert "disable_platform_admin_view" not in session client_request.post( - 'main.user_profile_disable_platform_admin_view', - _data={'enabled': False}, + "main.user_profile_disable_platform_admin_view", + _data={"enabled": False}, _expected_status=302, - _expected_redirect=url_for('main.user_profile'), + _expected_redirect=url_for("main.user_profile"), ) with client_request.session_transaction() as session: - assert session['disable_platform_admin_view'] is True + assert session["disable_platform_admin_view"] is True def test_can_reenable_platform_admin(client_request, platform_admin_user): client_request.login(platform_admin_user) with client_request.session_transaction() as session: - session['disable_platform_admin_view'] = True + session["disable_platform_admin_view"] = True client_request.post( - 'main.user_profile_disable_platform_admin_view', - _data={'enabled': True}, + "main.user_profile_disable_platform_admin_view", + _data={"enabled": True}, _expected_status=302, - _expected_redirect=url_for('main.user_profile'), + _expected_redirect=url_for("main.user_profile"), ) with client_request.session_transaction() as session: - assert session['disable_platform_admin_view'] is False + assert session["disable_platform_admin_view"] is False diff --git a/tests/app/main/views/test_verify.py b/tests/app/main/views/test_verify.py index 862ab31b9..461935d22 100644 --- a/tests/app/main/views/test_verify.py +++ b/tests/app/main/views/test_verify.py @@ -20,11 +20,14 @@ def test_should_return_verify_template( # TODO this lives here until we work out how to # reassign the session after it is lost mid register process with client_request.session_transaction() as session: - session['user_details'] = {'email_address': api_user_active['email_address'], 'id': api_user_active['id']} - page = client_request.get('main.verify') + session["user_details"] = { + "email_address": api_user_active["email_address"], + "id": api_user_active["id"], + } + page = client_request.get("main.verify") - assert page.h1.text == 'Check your phone' - message = page.select('main p')[0].text + assert page.h1.text == "Check your phone" + message = page.select("main p")[0].text assert message == "We’ve sent you a text message with a security code." @@ -37,25 +40,30 @@ def test_should_redirect_to_add_service_when_sms_code_is_correct( mock_create_event, fake_uuid, ): - api_user_active['current_session_id'] = str(uuid.UUID(int=1)) - mocker.patch('app.user_api_client.get_user', return_value=api_user_active) + api_user_active["current_session_id"] = str(uuid.UUID(int=1)) + mocker.patch("app.user_api_client.get_user", return_value=api_user_active) with client_request.session_transaction() as session: - session['user_details'] = {'email_address': api_user_active['email_address'], 'id': api_user_active['id']} + session["user_details"] = { + "email_address": api_user_active["email_address"], + "id": api_user_active["id"], + } # user's only just created their account so no session in the cookie - session.pop('current_session_id', None) + session.pop("current_session_id", None) client_request.post( - 'main.verify', - _data={'sms_code': '123456'}, - _expected_redirect=url_for('main.add_service', first='first'), + "main.verify", + _data={"sms_code": "123456"}, + _expected_redirect=url_for("main.add_service", first="first"), ) # make sure the current_session_id has changed to what the API returned with client_request.session_transaction() as session: - assert session['current_session_id'] == str(uuid.UUID(int=1)) + assert session["current_session_id"] == str(uuid.UUID(int=1)) - mock_check_verify_code.assert_called_once_with(api_user_active['id'], '123456', 'sms') + mock_check_verify_code.assert_called_once_with( + api_user_active["id"], "123456", "sms" + ) def test_should_activate_user_after_verify( @@ -68,13 +76,13 @@ def test_should_activate_user_after_verify( mock_activate_user, ): client_request.logout() - mocker.patch('app.user_api_client.get_user', return_value=api_user_pending) + mocker.patch("app.user_api_client.get_user", return_value=api_user_pending) with client_request.session_transaction() as session: - session['user_details'] = { - 'email_address': api_user_pending['email_address'], - 'id': api_user_pending['id'] + session["user_details"] = { + "email_address": api_user_pending["email_address"], + "id": api_user_pending["id"], } - client_request.post('main.verify', _data={'sms_code': '123456'}) + client_request.post("main.verify", _data={"sms_code": "123456"}) assert mock_activate_user.called @@ -84,19 +92,19 @@ def test_should_return_200_when_sms_code_is_wrong( mock_check_verify_code_code_not_found, ): with client_request.session_transaction() as session: - session['user_details'] = { - 'email_address': api_user_active['email_address'], - 'id': api_user_active['id'], + session["user_details"] = { + "email_address": api_user_active["email_address"], + "id": api_user_active["id"], } page = client_request.post( - 'main.verify', - _data={'sms_code': '123456'}, + "main.verify", + _data={"sms_code": "123456"}, _expected_status=200, ) - assert len(page.select('.usa-error-message')) == 1 - assert 'Code not found' in page.select_one('.usa-error-message').text + assert len(page.select(".usa-error-message")) == 1 + assert "Code not found" in page.select_one(".usa-error-message").text def test_verify_email_redirects_to_verify_if_token_valid( @@ -107,20 +115,27 @@ def test_verify_email_redirects_to_verify_if_token_valid( mock_send_verify_code, mock_check_verify_code, ): - token_data = {"user_id": api_user_pending['id'], "secret_code": 'UNUSED'} - mocker.patch('app.main.views.verify.check_token', return_value=json.dumps(token_data)) + token_data = {"user_id": api_user_pending["id"], "secret_code": "UNUSED"} + mocker.patch( + "app.main.views.verify.check_token", return_value=json.dumps(token_data) + ) client_request.get( - 'main.verify_email', - token='notreal', - _expected_redirect=url_for('main.verify'), + "main.verify_email", + token="notreal", + _expected_redirect=url_for("main.verify"), ) assert not mock_check_verify_code.called - mock_send_verify_code.assert_called_once_with(api_user_pending['id'], 'sms', api_user_pending['mobile_number']) + mock_send_verify_code.assert_called_once_with( + api_user_pending["id"], "sms", api_user_pending["mobile_number"] + ) with client_request.session_transaction() as session: - assert session['user_details'] == {'email': api_user_pending['email_address'], 'id': api_user_pending['id']} + assert session["user_details"] == { + "email": api_user_pending["email_address"], + "id": api_user_pending["id"], + } def test_verify_email_doesnt_verify_sms_if_user_on_email_auth( @@ -131,26 +146,35 @@ def test_verify_email_doesnt_verify_sms_if_user_on_email_auth( mock_activate_user, fake_uuid, ): - pending_user_with_email_auth = create_user(auth_type='email_auth', state='pending', id=fake_uuid) + pending_user_with_email_auth = create_user( + auth_type="email_auth", state="pending", id=fake_uuid + ) - mocker.patch('app.user_api_client.get_user', return_value=pending_user_with_email_auth) - token_data = {"user_id": pending_user_with_email_auth['id'], "secret_code": 'UNUSED'} - mocker.patch('app.main.views.verify.check_token', return_value=json.dumps(token_data)) + mocker.patch( + "app.user_api_client.get_user", return_value=pending_user_with_email_auth + ) + token_data = { + "user_id": pending_user_with_email_auth["id"], + "secret_code": "UNUSED", + } + mocker.patch( + "app.main.views.verify.check_token", return_value=json.dumps(token_data) + ) client_request.get( - 'main.verify_email', - token='notreal', - _expected_redirect=url_for('main.add_service', first='first'), + "main.verify_email", + token="notreal", + _expected_redirect=url_for("main.add_service", first="first"), ) assert not mock_check_verify_code.called assert not mock_send_verify_code.called - mock_activate_user.assert_called_once_with(pending_user_with_email_auth['id']) + mock_activate_user.assert_called_once_with(pending_user_with_email_auth["id"]) # user is logged in with client_request.session_transaction() as session: - assert session['user_id'] == pending_user_with_email_auth['id'] + assert session["user_id"] == pending_user_with_email_auth["id"] def test_verify_email_redirects_to_email_sent_if_token_expired( @@ -159,12 +183,14 @@ def test_verify_email_redirects_to_email_sent_if_token_expired( api_user_pending, ): client_request.logout() - mocker.patch('app.main.views.verify.check_token', side_effect=SignatureExpired('expired')) + mocker.patch( + "app.main.views.verify.check_token", side_effect=SignatureExpired("expired") + ) client_request.get( - 'main.verify_email', - token='notreal', - _expected_redirect=url_for('main.resend_email_verification'), + "main.verify_email", + token="notreal", + _expected_redirect=url_for("main.resend_email_verification"), ) @@ -177,23 +203,25 @@ def test_verify_email_redirects_to_sign_in_if_user_active( mock_check_verify_code, ): client_request.logout() - token_data = {"user_id": api_user_active['id'], "secret_code": 12345} - mocker.patch('app.main.views.verify.check_token', return_value=json.dumps(token_data)) + token_data = {"user_id": api_user_active["id"], "secret_code": 12345} + mocker.patch( + "app.main.views.verify.check_token", return_value=json.dumps(token_data) + ) - page = client_request.get('main.verify_email', token='notreal', _follow_redirects=True) + page = client_request.get( + "main.verify_email", token="notreal", _follow_redirects=True + ) - assert page.h1.text == 'Sign in' - flash_banner = page.find('div', class_='banner-dangerous').string.strip() + assert page.h1.text == "Sign in" + flash_banner = page.find("div", class_="banner-dangerous").string.strip() assert flash_banner == "That verification link has expired." -def test_verify_redirects_to_sign_in_if_not_logged_in( - client_request -): +def test_verify_redirects_to_sign_in_if_not_logged_in(client_request): client_request.logout() client_request.get( - 'main.verify', - _expected_redirect=url_for('main.sign_in'), + "main.verify", + _expected_redirect=url_for("main.sign_in"), ) @@ -207,22 +235,29 @@ def test_activate_user_redirects_to_service_dashboard_if_user_already_belongs_to mock_get_service, mock_get_invited_user_by_id, ): - mocker.patch('app.user_api_client.add_user_to_service', side_effect=HTTPError( - response=Mock( - status_code=400, - json={ - "result": "error", - "message": {f"User id: {api_user_active['id']} already part of service id: {service_one['id']}"} - }, + mocker.patch( + "app.user_api_client.add_user_to_service", + side_effect=HTTPError( + response=Mock( + status_code=400, + json={ + "result": "error", + "message": { + f"User id: {api_user_active['id']} already part of service id: {service_one['id']}" + }, + }, + ), + message=f"User id: {api_user_active['id']} already part of service id: {service_one['id']}", ), - message=f"User id: {api_user_active['id']} already part of service id: {service_one['id']}" - )) + ) # Can't use `with client.session_transaction()...` here since activate_session is not a view function - flask_session['invited_user_id'] = sample_invite['id'] + flask_session["invited_user_id"] = sample_invite["id"] - response = activate_user(api_user_active['id']) + response = activate_user(api_user_active["id"]) - assert response.location == url_for('main.service_dashboard', service_id=service_one['id']) + assert response.location == url_for( + "main.service_dashboard", service_id=service_one["id"] + ) - flask_session.pop('invited_user_id') + flask_session.pop("invited_user_id") diff --git a/tests/app/main/views/uploads/test_upload_hub.py b/tests/app/main/views/uploads/test_upload_hub.py index 4cf2acb6d..96223e660 100644 --- a/tests/app/main/views/uploads/test_upload_hub.py +++ b/tests/app/main/views/uploads/test_upload_hub.py @@ -9,11 +9,10 @@ from tests.conftest import ( ) -@pytest.mark.parametrize('extra_permissions, expected_empty_message', ( - ([], ( - 'You have not uploaded any files recently.' - )), -)) +@pytest.mark.parametrize( + "extra_permissions, expected_empty_message", + (([], ("You have not uploaded any files recently.")),), +) def test_get_upload_hub_with_no_uploads( mocker, client_request, @@ -22,44 +21,48 @@ def test_get_upload_hub_with_no_uploads( extra_permissions, expected_empty_message, ): - mocker.patch('app.job_api_client.get_jobs', return_value={'data': []}) - service_one['permissions'] += extra_permissions - page = client_request.get('main.uploads', service_id=SERVICE_ONE_ID) - assert normalize_spaces(' '.join( - paragraph.text for paragraph in page.select('main p') - )) == expected_empty_message - assert not page.select('.file-list-filename') + mocker.patch("app.job_api_client.get_jobs", return_value={"data": []}) + service_one["permissions"] += extra_permissions + page = client_request.get("main.uploads", service_id=SERVICE_ONE_ID) + assert ( + normalize_spaces( + " ".join(paragraph.text for paragraph in page.select("main p")) + ) + == expected_empty_message + ) + assert not page.select(".file-list-filename") -@freeze_time('2017-10-10 10:10:10') +@freeze_time("2017-10-10 10:10:10") def test_get_upload_hub_page( mocker, client_request, service_one, mock_get_uploads, ): - mocker.patch('app.job_api_client.get_jobs', return_value={'data': []}) - page = client_request.get('main.uploads', service_id=SERVICE_ONE_ID) - assert page.find('h1').text == 'Uploads' + mocker.patch("app.job_api_client.get_jobs", return_value={"data": []}) + page = client_request.get("main.uploads", service_id=SERVICE_ONE_ID) + assert page.find("h1").text == "Uploads" - uploads = page.select('tbody tr') + uploads = page.select("tbody tr") assert len(uploads) == 1 assert normalize_spaces(uploads[0].text.strip()) == ( - 'some.csv ' - 'Sent 1 January 2016 at 11:09 UTC ' - '0 pending 8 delivered 2 failed' + "some.csv " "Sent 1 January 2016 at 11:09 UTC " "0 pending 8 delivered 2 failed" ) - assert uploads[0].select_one('a.file-list-filename-large')['href'] == ( - '/services/{}/jobs/job_id_1'.format(SERVICE_ONE_ID) + assert uploads[0].select_one("a.file-list-filename-large")["href"] == ( + "/services/{}/jobs/job_id_1".format(SERVICE_ONE_ID) ) -@pytest.mark.parametrize('user', ( - create_active_caseworking_user(), - create_active_user_with_permissions(), -)) +@pytest.mark.parametrize( + "user", + ( + create_active_caseworking_user(), + create_active_user_with_permissions(), + ), +) @freeze_time("2012-12-12 12:12") def test_uploads_page_shows_scheduled_jobs( mocker, @@ -69,23 +72,19 @@ def test_uploads_page_shows_scheduled_jobs( user, ): client_request.login(user) - page = client_request.get('main.uploads', service_id=SERVICE_ONE_ID) + page = client_request.get("main.uploads", service_id=SERVICE_ONE_ID) - assert [ - normalize_spaces(row.text) for row in page.select('tr') - ] == [ + assert [normalize_spaces(row.text) for row in page.select("tr")] == [ + ("File Status"), ( - 'File Status' + "even_later.csv " + "Sending 1 January 2016 at 23:09 UTC " + "1 text message waiting to send" ), ( - 'even_later.csv ' - 'Sending 1 January 2016 at 23:09 UTC ' - '1 text message waiting to send' - ), - ( - 'send_me_later.csv ' - 'Sending 1 January 2016 at 11:09 UTC ' - '1 text message waiting to send' + "send_me_later.csv " + "Sending 1 January 2016 at 11:09 UTC " + "1 text message waiting to send" ), ] - assert not page.select('.table-empty-message') + assert not page.select(".table-empty-message") diff --git a/tests/app/models/test_base_model.py b/tests/app/models/test_base_model.py index 46ea027b3..00c97b361 100644 --- a/tests/app/models/test_base_model.py +++ b/tests/app/models/test_base_model.py @@ -4,35 +4,34 @@ from app.models import JSONModel def test_looks_up_from_dict(): - class Custom(JSONModel): - ALLOWED_PROPERTIES = {'foo'} + ALLOWED_PROPERTIES = {"foo"} - assert Custom({'foo': 'bar'}).foo == 'bar' + assert Custom({"foo": "bar"}).foo == "bar" def test_raises_when_overriding_custom_properties(): - class Custom(JSONModel): - - ALLOWED_PROPERTIES = {'foo'} + ALLOWED_PROPERTIES = {"foo"} @property def foo(self): pass with pytest.raises(AttributeError) as e: - Custom({'foo': 'NOPE'}) + Custom({"foo": "NOPE"}) assert str(e.value) == "can't set attribute" -@pytest.mark.parametrize('json_response', ( - {}, - {'foo': 'bar'}, # Should still raise an exception -)) +@pytest.mark.parametrize( + "json_response", + ( + {}, + {"foo": "bar"}, # Should still raise an exception + ), +) def test_model_raises_for_unknown_attributes(json_response): - class Custom(JSONModel): ALLOWED_PROPERTIES = set() @@ -46,9 +45,8 @@ def test_model_raises_for_unknown_attributes(json_response): def test_model_raises_keyerror_if_item_missing_from_dict(): - class Custom(JSONModel): - ALLOWED_PROPERTIES = {'foo'} + ALLOWED_PROPERTIES = {"foo"} with pytest.raises(AttributeError) as e: Custom({}).foo @@ -56,30 +54,31 @@ def test_model_raises_keyerror_if_item_missing_from_dict(): assert str(e.value) == "'Custom' object has no attribute 'foo'" -@pytest.mark.parametrize('json_response', ( - {}, - {'foo': 'bar'}, # Should be ignored -)) +@pytest.mark.parametrize( + "json_response", + ( + {}, + {"foo": "bar"}, # Should be ignored + ), +) def test_model_doesnt_swallow_attribute_errors(json_response): - class Custom(JSONModel): ALLOWED_PROPERTIES = set() @property def foo(self): - raise AttributeError('Something has gone wrong') + raise AttributeError("Something has gone wrong") with pytest.raises(AttributeError) as e: Custom(json_response).foo - assert str(e.value) == 'Something has gone wrong' + assert str(e.value) == "Something has gone wrong" def test_dynamic_properties_are_introspectable(): - class Custom(JSONModel): - ALLOWED_PROPERTIES = {'foo', 'bar', 'baz'} + ALLOWED_PROPERTIES = {"foo", "bar", "baz"} - model = Custom({'foo': None, 'bar': None, 'baz': None}) + model = Custom({"foo": None, "bar": None, "baz": None}) - assert dir(model)[-3:] == ['bar', 'baz', 'foo'] + assert dir(model)[-3:] == ["bar", "baz", "foo"] diff --git a/tests/app/models/test_event.py b/tests/app/models/test_event.py index 943985535..f8dbd29ca 100644 --- a/tests/app/models/test_event.py +++ b/tests/app/models/test_event.py @@ -4,62 +4,74 @@ from app.models.event import ServiceEvent from tests.conftest import sample_uuid -@pytest.mark.parametrize('key, value_from, value_to, expected', ( - ('restricted', True, False, ( - 'Made this service live' - )), - ('restricted', False, True, ( - 'Put this service back into trial mode' - )), - ('active', False, True, ( - 'Unsuspended this service' - )), - ('active', True, False, ( - 'Deleted this service' - )), - ('contact_link', 'x', 'y', ( - 'Set the contact details for this service to ‘y’' - )), - ('email_branding', 'foo', 'bar', ( - 'Updated this service’s email branding' - )), - ('inbound_api', 'foo', 'bar', ( - 'Updated the callback for received text messages' - )), - ('message_limit', 1, 2, ( - 'Increased this service’s daily message limit from 1 to 2' - )), - ('message_limit', 2, 1, ( - 'Reduced this service’s daily message limit from 2 to 1' - )), - ('name', 'Old', 'New', ( - 'Renamed this service from ‘Old’ to ‘New’' - )), - ('permissions', ['a', 'b', 'c'], ['a', 'b', 'c', 'd'], ( - 'Added ‘d’ to this service’s permissions' - )), - ('permissions', ['a', 'b', 'c'], ['a', 'b'], ( - 'Removed ‘c’ from this service’s permissions' - )), - ('permissions', ['a', 'b', 'c'], ['c', 'd', 'e'], ( - 'Removed ‘a’ and ‘b’ from this service’s permissions, added ‘d’ and ‘e’' - )), - ('prefix_sms', True, False, ( - 'Set text messages to not start with the name of this service' - )), - ('prefix_sms', False, True, ( - 'Set text messages to start with the name of this service' - )), - ('research_mode', True, False, ( - 'Took this service out of research mode' - )), - ('research_mode', False, True, ( - 'Put this service into research mode' - )), - ('service_callback_api', 'foo', 'bar', ( - 'Updated the callback for delivery receipts' - )), -)) +@pytest.mark.parametrize( + "key, value_from, value_to, expected", + ( + ("restricted", True, False, ("Made this service live")), + ("restricted", False, True, ("Put this service back into trial mode")), + ("active", False, True, ("Unsuspended this service")), + ("active", True, False, ("Deleted this service")), + ("contact_link", "x", "y", ("Set the contact details for this service to ‘y’")), + ("email_branding", "foo", "bar", ("Updated this service’s email branding")), + ( + "inbound_api", + "foo", + "bar", + ("Updated the callback for received text messages"), + ), + ( + "message_limit", + 1, + 2, + ("Increased this service’s daily message limit from 1 to 2"), + ), + ( + "message_limit", + 2, + 1, + ("Reduced this service’s daily message limit from 2 to 1"), + ), + ("name", "Old", "New", ("Renamed this service from ‘Old’ to ‘New’")), + ( + "permissions", + ["a", "b", "c"], + ["a", "b", "c", "d"], + ("Added ‘d’ to this service’s permissions"), + ), + ( + "permissions", + ["a", "b", "c"], + ["a", "b"], + ("Removed ‘c’ from this service’s permissions"), + ), + ( + "permissions", + ["a", "b", "c"], + ["c", "d", "e"], + ("Removed ‘a’ and ‘b’ from this service’s permissions, added ‘d’ and ‘e’"), + ), + ( + "prefix_sms", + True, + False, + ("Set text messages to not start with the name of this service"), + ), + ( + "prefix_sms", + False, + True, + ("Set text messages to start with the name of this service"), + ), + ("research_mode", True, False, ("Took this service out of research mode")), + ("research_mode", False, True, ("Put this service into research mode")), + ( + "service_callback_api", + "foo", + "bar", + ("Updated the callback for delivery receipts"), + ), + ), +) def test_service_event( key, value_from, @@ -68,9 +80,9 @@ def test_service_event( ): event = ServiceEvent( { - 'created_at': 'foo', - 'updated_at': 'bar', - 'created_by_id': sample_uuid(), + "created_at": "foo", + "updated_at": "bar", + "created_by_id": sample_uuid(), }, key, value_from, diff --git a/tests/app/models/test_job.py b/tests/app/models/test_job.py index e43d8c08f..986f1ebdb 100644 --- a/tests/app/models/test_job.py +++ b/tests/app/models/test_job.py @@ -6,26 +6,23 @@ from tests.conftest import SERVICE_ONE_ID @pytest.mark.parametrize( - 'job_status, num_notifications_created, expected_still_processing', + "job_status, num_notifications_created, expected_still_processing", [ - ('scheduled', 0, True), - ('cancelled', 10, True), - ('finished', 5, True), - ('finished', 10, False), - ] + ("scheduled", 0, True), + ("cancelled", 10, True), + ("finished", 5, True), + ("finished", 10, False), + ], ) def test_still_processing( - notify_admin, - job_status, - num_notifications_created, - expected_still_processing + notify_admin, job_status, num_notifications_created, expected_still_processing ): json = job_json( service_id=SERVICE_ONE_ID, created_by=user_json(), notification_count=10, notifications_requested=num_notifications_created, - job_status=job_status + job_status=job_status, ) job = Job(json) assert job.still_processing == expected_still_processing diff --git a/tests/app/models/test_organization.py b/tests/app/models/test_organization.py index de9841497..42ee05906 100644 --- a/tests/app/models/test_organization.py +++ b/tests/app/models/test_organization.py @@ -4,10 +4,12 @@ from app.models.organization import Organization from tests import organization_json -@pytest.mark.parametrize("purchase_order_number,expected_result", [ - [None, None], - ["PO1234", [None, None, None, "PO1234"]] -]) +@pytest.mark.parametrize( + "purchase_order_number,expected_result", + [[None, None], ["PO1234", [None, None, None, "PO1234"]]], +) def test_organization_billing_details(purchase_order_number, expected_result): - organization = Organization(organization_json(purchase_order_number=purchase_order_number)) + organization = Organization( + organization_json(purchase_order_number=purchase_order_number) + ) assert organization.billing_details == expected_result diff --git a/tests/app/models/test_service.py b/tests/app/models/test_service.py index 5cc257230..39f3f8f3b 100644 --- a/tests/app/models/test_service.py +++ b/tests/app/models/test_service.py @@ -6,68 +6,72 @@ from tests import organization_json, service_json from tests.conftest import ORGANISATION_ID, create_folder, create_template -def test_organization_type_when_services_organization_has_no_org_type(mocker, service_one): +def test_organization_type_when_services_organization_has_no_org_type( + mocker, service_one +): service = Service(service_one) - service._dict['organization_id'] = ORGANISATION_ID + service._dict["organization_id"] = ORGANISATION_ID org = organization_json(organization_type=None) - mocker.patch('app.organizations_client.get_organization', return_value=org) + mocker.patch("app.organizations_client.get_organization", return_value=org) - assert not org['organization_type'] - assert service.organization_type == 'federal' + assert not org["organization_type"] + assert service.organization_type == "federal" -def test_organization_type_when_service_and_its_org_both_have_an_org_type(mocker, service_one): +def test_organization_type_when_service_and_its_org_both_have_an_org_type( + mocker, service_one +): # service_one has an organization_type of 'central' service = Service(service_one) - service._dict['organization'] = ORGANISATION_ID - org = organization_json(organization_type='local') - mocker.patch('app.organizations_client.get_organization', return_value=org) + service._dict["organization"] = ORGANISATION_ID + org = organization_json(organization_type="local") + mocker.patch("app.organizations_client.get_organization", return_value=org) - assert service.organization_type == 'local' + assert service.organization_type == "local" def test_organization_name_comes_from_cache(mocker, service_one): mock_redis_get = mocker.patch( - 'app.extensions.RedisClient.get', + "app.extensions.RedisClient.get", return_value=b'"Borchester Council"', ) - mock_get_organization = mocker.patch('app.organizations_client.get_organization') + mock_get_organization = mocker.patch("app.organizations_client.get_organization") service = Service(service_one) - service._dict['organization'] = ORGANISATION_ID + service._dict["organization"] = ORGANISATION_ID - assert service.organization_name == 'Borchester Council' - mock_redis_get.assert_called_once_with(f'organization-{ORGANISATION_ID}-name') + assert service.organization_name == "Borchester Council" + mock_redis_get.assert_called_once_with(f"organization-{ORGANISATION_ID}-name") assert mock_get_organization.called is False def test_organization_name_goes_into_cache(mocker, service_one): mocker.patch( - 'app.extensions.RedisClient.get', + "app.extensions.RedisClient.get", return_value=None, ) mock_redis_set = mocker.patch( - 'app.extensions.RedisClient.set', + "app.extensions.RedisClient.set", ) mocker.patch( - 'app.organizations_client.get_organization', + "app.organizations_client.get_organization", return_value=organization_json(), ) service = Service(service_one) - service._dict['organization'] = ORGANISATION_ID + service._dict["organization"] = ORGANISATION_ID - assert service.organization_name == 'Test Organization' + assert service.organization_name == "Test Organization" mock_redis_set.assert_called_once_with( - f'organization-{ORGANISATION_ID}-name', + f"organization-{ORGANISATION_ID}-name", '"Test Organization"', ex=604800, ) def test_service_without_organization_doesnt_need_org_api(mocker, service_one): - mock_redis_get = mocker.patch('app.extensions.RedisClient.get') - mock_get_organization = mocker.patch('app.organizations_client.get_organization') + mock_redis_get = mocker.patch("app.extensions.RedisClient.get") + mock_get_organization = mocker.patch("app.organizations_client.get_organization") service = Service(service_one) - service._dict['organization'] = None + service._dict["organization"] = None assert service.organization_id is None assert service.organization_name is None @@ -79,17 +83,17 @@ def test_service_without_organization_doesnt_need_org_api(mocker, service_one): def test_bad_permission_raises(service_one): with pytest.raises(KeyError) as e: - Service(service_one).has_permission('foo') + Service(service_one).has_permission("foo") assert str(e.value) == "'foo is not a service permission'" -@pytest.mark.parametrize("purchase_order_number,expected_result", [ - [None, None], - ["PO1234", [None, None, None, "PO1234"]] -]) +@pytest.mark.parametrize( + "purchase_order_number,expected_result", + [[None, None], ["PO1234", [None, None, None, "PO1234"]]], +) def test_service_billing_details(purchase_order_number, expected_result): service = Service(service_json(purchase_order_number=purchase_order_number)) - service._dict['purchase_order_number'] = purchase_order_number + service._dict["purchase_order_number"] = purchase_order_number assert service.billing_details == expected_result @@ -99,15 +103,15 @@ def test_has_templates_of_type_includes_folders( mock_get_template_folders, ): mocker.patch( - 'app.service_api_client.get_service_templates', - return_value={'data': [create_template( - folder='something', template_type='sms' - )]} + "app.service_api_client.get_service_templates", + return_value={ + "data": [create_template(folder="something", template_type="sms")] + }, ) mocker.patch( - 'app.template_folder_api_client.get_template_folders', - return_value=[create_folder(id='something')] + "app.template_folder_api_client.get_template_folders", + return_value=[create_folder(id="something")], ) - assert Service(service_one).has_templates_of_type('sms') + assert Service(service_one).has_templates_of_type("sms") diff --git a/tests/app/models/test_spreadsheet.py b/tests/app/models/test_spreadsheet.py index 54fa925f6..1e921f180 100644 --- a/tests/app/models/test_spreadsheet.py +++ b/tests/app/models/test_spreadsheet.py @@ -7,36 +7,43 @@ from app.models.spreadsheet import Spreadsheet def test_can_create_spreadsheet_from_large_excel_file(): - with open(str(Path.cwd() / 'tests' / 'spreadsheet_files' / 'excel 2007.xlsx'), 'rb') as xl: - ret = Spreadsheet.from_file(xl, filename='xl.xlsx') + with open( + str(Path.cwd() / "tests" / "spreadsheet_files" / "excel 2007.xlsx"), "rb" + ) as xl: + ret = Spreadsheet.from_file(xl, filename="xl.xlsx") assert ret.as_csv_data def test_can_create_spreadsheet_from_dict(): - assert Spreadsheet.from_dict(OrderedDict( - foo='bar', - name='Jane', - )).as_csv_data == ( - "foo,name\r\n" - "bar,Jane\r\n" - ) + assert Spreadsheet.from_dict( + OrderedDict( + foo="bar", + name="Jane", + ) + ).as_csv_data == ("foo,name\r\n" "bar,Jane\r\n") def test_can_create_spreadsheet_from_dict_with_filename(): - assert Spreadsheet.from_dict({}, filename='empty.csv').as_dict['file_name'] == "empty.csv" + assert ( + Spreadsheet.from_dict({}, filename="empty.csv").as_dict["file_name"] + == "empty.csv" + ) -@pytest.mark.parametrize('args, kwargs', ( +@pytest.mark.parametrize( + "args, kwargs", ( - ('hello', ['hello']), - {}, + ( + ("hello", ["hello"]), + {}, + ), + ((), {"csv_data": "hello", "rows": ["hello"]}), ), - ( - (), - {'csv_data': 'hello', 'rows': ['hello']} - ), -)) +) def test_spreadsheet_checks_for_bad_arguments(args, kwargs): with pytest.raises(TypeError) as exception: Spreadsheet(*args, **kwargs) - assert str(exception.value) == 'Spreadsheet must be created from either rows or CSV data' + assert ( + str(exception.value) + == "Spreadsheet must be created from either rows or CSV data" + ) diff --git a/tests/app/models/test_template_list.py b/tests/app/models/test_template_list.py index 119bf2180..959ed4f70 100644 --- a/tests/app/models/test_template_list.py +++ b/tests/app/models/test_template_list.py @@ -6,71 +6,70 @@ from app.models.service import Service from app.models.template_list import TemplateList from app.models.user import User -INV_PARENT_FOLDER_ID = '7e979e79-d970-43a5-ac69-b625a8d147b0' -INV_CHILD_1_FOLDER_ID = '92ee1ee0-e4ee-4dcc-b1a7-a5da9ebcfa2b' -VIS_PARENT_FOLDER_ID = 'bbbb222b-2b22-2b22-222b-b222b22b2222' -INV_CHILD_2_FOLDER_ID = 'fafe723f-1d39-4a10-865f-e551e03d8886' +INV_PARENT_FOLDER_ID = "7e979e79-d970-43a5-ac69-b625a8d147b0" +INV_CHILD_1_FOLDER_ID = "92ee1ee0-e4ee-4dcc-b1a7-a5da9ebcfa2b" +VIS_PARENT_FOLDER_ID = "bbbb222b-2b22-2b22-222b-b222b22b2222" +INV_CHILD_2_FOLDER_ID = "fafe723f-1d39-4a10-865f-e551e03d8886" @pytest.fixture def mock_get_hierarchy_of_folders( - mock_get_template_folders, - active_user_with_permissions + mock_get_template_folders, active_user_with_permissions ): mock_get_template_folders.return_value = [ { - 'name': "Invisible folder", - 'id': str(uuid.uuid4()), - 'parent_id': None, - 'users_with_permission': [] + "name": "Invisible folder", + "id": str(uuid.uuid4()), + "parent_id": None, + "users_with_permission": [], }, { - 'name': "Parent 1 - invisible", - 'id': INV_PARENT_FOLDER_ID, - 'parent_id': None, - 'users_with_permission': [] + "name": "Parent 1 - invisible", + "id": INV_PARENT_FOLDER_ID, + "parent_id": None, + "users_with_permission": [], }, { - 'name': "1's Visible child", - 'id': str(uuid.uuid4()), - 'parent_id': INV_PARENT_FOLDER_ID, - 'users_with_permission': [active_user_with_permissions['id']], + "name": "1's Visible child", + "id": str(uuid.uuid4()), + "parent_id": INV_PARENT_FOLDER_ID, + "users_with_permission": [active_user_with_permissions["id"]], }, { - 'name': "1's Invisible child", - 'id': INV_CHILD_1_FOLDER_ID, - 'parent_id': INV_PARENT_FOLDER_ID, - 'users_with_permission': [] + "name": "1's Invisible child", + "id": INV_CHILD_1_FOLDER_ID, + "parent_id": INV_PARENT_FOLDER_ID, + "users_with_permission": [], }, { - 'name': "1's Visible grandchild", - 'id': str(uuid.uuid4()), - 'parent_id': INV_CHILD_1_FOLDER_ID, - 'users_with_permission': [active_user_with_permissions['id']], + "name": "1's Visible grandchild", + "id": str(uuid.uuid4()), + "parent_id": INV_CHILD_1_FOLDER_ID, + "users_with_permission": [active_user_with_permissions["id"]], }, { - 'name': "Parent 2 - visible", - 'id': VIS_PARENT_FOLDER_ID, - 'parent_id': None, - 'users_with_permission': [active_user_with_permissions['id']], + "name": "Parent 2 - visible", + "id": VIS_PARENT_FOLDER_ID, + "parent_id": None, + "users_with_permission": [active_user_with_permissions["id"]], }, { - 'name': "2's Visible child", - 'id': str(uuid.uuid4()), - 'parent_id': VIS_PARENT_FOLDER_ID, - 'users_with_permission': [active_user_with_permissions['id']], + "name": "2's Visible child", + "id": str(uuid.uuid4()), + "parent_id": VIS_PARENT_FOLDER_ID, + "users_with_permission": [active_user_with_permissions["id"]], }, { - 'name': "2's Invisible child", - 'id': INV_CHILD_2_FOLDER_ID, - 'parent_id': VIS_PARENT_FOLDER_ID, - 'users_with_permission': [] + "name": "2's Invisible child", + "id": INV_CHILD_2_FOLDER_ID, + "parent_id": VIS_PARENT_FOLDER_ID, + "users_with_permission": [], }, { - 'name': "2's Visible grandchild", - 'id': str(uuid.uuid4()), - 'parent_id': INV_CHILD_2_FOLDER_ID, - 'users_with_permission': [active_user_with_permissions['id']], + "name": "2's Visible grandchild", + "id": str(uuid.uuid4()), + "parent_id": INV_CHILD_2_FOLDER_ID, + "users_with_permission": [active_user_with_permissions["id"]], }, ] @@ -85,8 +84,8 @@ def test_template_list_yields_folders_visible_to_user( user = User(active_user_with_permissions) result_folder_names = tuple( - result.name for result in - TemplateList(service=service, user=user) + result.name + for result in TemplateList(service=service, user=user) if result.is_folder ) @@ -107,9 +106,7 @@ def test_template_list_yields_all_folders_without_user( service = Service(service_one) result_folder_names = tuple( - result.name for result in - TemplateList(service=service) - if result.is_folder + result.name for result in TemplateList(service=service) if result.is_folder ) assert result_folder_names == ( diff --git a/tests/app/models/test_user.py b/tests/app/models/test_user.py index ae3e36cb2..88df63442 100644 --- a/tests/app/models/test_user.py +++ b/tests/app/models/test_user.py @@ -14,21 +14,22 @@ def test_anonymous_user(notify_admin): def test_user(notify_admin): - user_data = {'id': 1, - 'name': 'Test User', - 'email_address': 'test@user.gsa.gov', - 'mobile_number': '+12021231234', - 'state': 'pending', - 'failed_login_count': 0, - 'platform_admin': False, - } + user_data = { + "id": 1, + "name": "Test User", + "email_address": "test@user.gsa.gov", + "mobile_number": "+12021231234", + "state": "pending", + "failed_login_count": 0, + "platform_admin": False, + } user = User(user_data) assert user.id == 1 - assert user.name == 'Test User' - assert user.email_address == 'test@user.gsa.gov' - assert user.mobile_number == '+12021231234' - assert user.state == 'pending' + assert user.name == "Test User" + assert user.email_address == "test@user.gsa.gov" + assert user.mobile_number == "+12021231234" + assert user.state == "pending" # user has ten failed logins before being locked assert user.MAX_FAILED_LOGIN_COUNT == 10 @@ -40,37 +41,45 @@ def test_user(notify_admin): assert user.locked is True with pytest.raises(TypeError): - user.has_permissions('to_do_bad_things') + user.has_permissions("to_do_bad_things") def test_activate_user(notify_admin, api_user_pending, mock_activate_user): assert User(api_user_pending).activate() == User(api_user_pending) - mock_activate_user.assert_called_once_with(api_user_pending['id']) + mock_activate_user.assert_called_once_with(api_user_pending["id"]) -def test_activate_user_already_active(notify_admin, api_user_active, mock_activate_user): +def test_activate_user_already_active( + notify_admin, api_user_active, mock_activate_user +): assert User(api_user_active).activate() == User(api_user_active) assert mock_activate_user.called is False -@pytest.mark.parametrize('is_platform_admin, value_in_session, expected_result', [ - (True, True, False), - (True, False, True), - (True, None, True), - (False, True, False), - (False, False, False), - (False, None, False), -]) +@pytest.mark.parametrize( + "is_platform_admin, value_in_session, expected_result", + [ + (True, True, False), + (True, False, True), + (True, None, True), + (False, True, False), + (False, False, False), + (False, None, False), + ], +) def test_platform_admin_flag_set_in_session( client_request, mocker, is_platform_admin, value_in_session, expected_result ): session_dict = {} if value_in_session is not None: - session_dict['disable_platform_admin_view'] = value_in_session + session_dict["disable_platform_admin_view"] = value_in_session - mocker.patch.dict('app.models.user.session', values=session_dict, clear=True) + mocker.patch.dict("app.models.user.session", values=session_dict, clear=True) - assert User({'id': 1, 'platform_admin': is_platform_admin}).platform_admin == expected_result + assert ( + User({"id": 1, "platform_admin": is_platform_admin}).platform_admin + == expected_result + ) def test_has_live_services( @@ -78,10 +87,12 @@ def test_has_live_services( mock_get_non_empty_organizations_and_services_for_user, fake_uuid, ): - user = User({ - 'id': fake_uuid, - 'platform_admin': False, - }) + user = User( + { + "id": fake_uuid, + "platform_admin": False, + } + ) assert len(user.live_services) == 5 for service in user.live_services: assert service.live @@ -92,10 +103,15 @@ def test_has_live_services_when_there_are_no_services( mock_get_organizations_and_services_for_user, fake_uuid, ): - assert User({ - 'id': fake_uuid, - 'platform_admin': False, - }).live_services == [] + assert ( + User( + { + "id": fake_uuid, + "platform_admin": False, + } + ).live_services + == [] + ) def test_has_live_services_when_service_is_not_live( @@ -103,49 +119,62 @@ def test_has_live_services_when_service_is_not_live( mock_get_empty_organizations_and_one_service_for_user, fake_uuid, ): - assert User({ - 'id': fake_uuid, - 'platform_admin': False, - }).live_services == [] + assert ( + User( + { + "id": fake_uuid, + "platform_admin": False, + } + ).live_services + == [] + ) -def test_invited_user_from_session_uses_id(client_request, mocker, mock_get_invited_user_by_id): - session_dict = {'invited_user_id': USER_ONE_ID} - mocker.patch.dict('app.models.user.session', values=session_dict, clear=True) +def test_invited_user_from_session_uses_id( + client_request, mocker, mock_get_invited_user_by_id +): + session_dict = {"invited_user_id": USER_ONE_ID} + mocker.patch.dict("app.models.user.session", values=session_dict, clear=True) assert InvitedUser.from_session().id == USER_ONE_ID mock_get_invited_user_by_id.assert_called_once_with(USER_ONE_ID) -def test_invited_user_from_session_returns_none_if_nothing_present(client_request, mocker): - mocker.patch.dict('app.models.user.session', values={}, clear=True) +def test_invited_user_from_session_returns_none_if_nothing_present( + client_request, mocker +): + mocker.patch.dict("app.models.user.session", values={}, clear=True) assert InvitedUser.from_session() is None def test_invited_org_user_from_session_uses_id( client_request, mocker, mock_get_invited_org_user_by_id, sample_org_invite ): - session_dict = {'invited_org_user_id': sample_org_invite['id']} - mocker.patch.dict('app.models.user.session', values=session_dict, clear=True) + session_dict = {"invited_org_user_id": sample_org_invite["id"]} + mocker.patch.dict("app.models.user.session", values=session_dict, clear=True) - assert InvitedOrgUser.from_session().id == sample_org_invite['id'] + assert InvitedOrgUser.from_session().id == sample_org_invite["id"] - mock_get_invited_org_user_by_id.assert_called_once_with(sample_org_invite['id']) + mock_get_invited_org_user_by_id.assert_called_once_with(sample_org_invite["id"]) -def test_invited_org_user_from_session_returns_none_if_nothing_present(client_request, mocker): - mocker.patch.dict('app.models.user.session', values={}, clear=True) +def test_invited_org_user_from_session_returns_none_if_nothing_present( + client_request, mocker +): + mocker.patch.dict("app.models.user.session", values={}, clear=True) assert InvitedOrgUser.from_session() is None -def test_set_permissions(client_request, mocker, active_user_view_permissions, fake_uuid): - mock_api = mocker.patch('app.models.user.user_api_client.set_user_permissions') - mock_event = mocker.patch('app.models.user.create_set_user_permissions_event') +def test_set_permissions( + client_request, mocker, active_user_view_permissions, fake_uuid +): + mock_api = mocker.patch("app.models.user.user_api_client.set_user_permissions") + mock_event = mocker.patch("app.models.user.create_set_user_permissions_event") User(active_user_view_permissions).set_permissions( service_id=SERVICE_ONE_ID, - permissions={'manage_templates'}, + permissions={"manage_templates"}, folder_permissions=[], set_by_id=fake_uuid, ) @@ -153,20 +182,20 @@ def test_set_permissions(client_request, mocker, active_user_view_permissions, f mock_api.assert_called_once() mock_event.assert_called_once_with( service_id=SERVICE_ONE_ID, - user_id=active_user_view_permissions['id'], - original_ui_permissions={'view_activity'}, - new_ui_permissions={'manage_templates'}, + user_id=active_user_view_permissions["id"], + original_ui_permissions={"view_activity"}, + new_ui_permissions={"manage_templates"}, set_by_id=fake_uuid, ) def test_add_to_service(client_request, mocker, api_user_active, fake_uuid): - mock_api = mocker.patch('app.models.user.user_api_client.add_user_to_service') - mock_event = mocker.patch('app.models.user.create_add_user_to_service_event') + mock_api = mocker.patch("app.models.user.user_api_client.add_user_to_service") + mock_event = mocker.patch("app.models.user.create_add_user_to_service_event") User(api_user_active).add_to_service( service_id=SERVICE_ONE_ID, - permissions={'manage_templates'}, + permissions={"manage_templates"}, folder_permissions=[], invited_by_id=fake_uuid, ) @@ -174,7 +203,7 @@ def test_add_to_service(client_request, mocker, api_user_active, fake_uuid): mock_api.assert_called_once() mock_event.assert_called_once_with( service_id=SERVICE_ONE_ID, - user_id=api_user_active['id'], + user_id=api_user_active["id"], invited_by_id=fake_uuid, - ui_permissions={'manage_templates'}, + ui_permissions={"manage_templates"}, ) diff --git a/tests/app/notify_client/test_billing_client.py b/tests/app/notify_client/test_billing_client.py index d657ab258..8486064d9 100644 --- a/tests/app/notify_client/test_billing_client.py +++ b/tests/app/notify_client/test_billing_client.py @@ -7,56 +7,77 @@ from app.notify_client.billing_api_client import BillingAPIClient def test_get_free_sms_fragment_limit_for_year_correct_endpoint(mocker, api_user_active): service_id = uuid.uuid4() - expected_url = '/service/{}/billing/free-sms-fragment-limit'.format(service_id) + expected_url = "/service/{}/billing/free-sms-fragment-limit".format(service_id) client = BillingAPIClient() - mock_get = mocker.patch('app.notify_client.billing_api_client.BillingAPIClient.get') + mock_get = mocker.patch("app.notify_client.billing_api_client.BillingAPIClient.get") client.get_free_sms_fragment_limit_for_year(service_id, year=1999) - mock_get.assert_called_once_with(expected_url, params={'financial_year_start': 1999}) + mock_get.assert_called_once_with( + expected_url, params={"financial_year_start": 1999} + ) -def test_post_free_sms_fragment_limit_for_current_year_endpoint(mocker, api_user_active): +def test_post_free_sms_fragment_limit_for_current_year_endpoint( + mocker, api_user_active +): service_id = uuid.uuid4() - sms_limit_data = {'free_sms_fragment_limit': 1111, 'financial_year_start': None} - mock_post = mocker.patch('app.notify_client.billing_api_client.BillingAPIClient.post') + sms_limit_data = {"free_sms_fragment_limit": 1111, "financial_year_start": None} + mock_post = mocker.patch( + "app.notify_client.billing_api_client.BillingAPIClient.post" + ) client = BillingAPIClient() - client.create_or_update_free_sms_fragment_limit(service_id=service_id, free_sms_fragment_limit=1111) + client.create_or_update_free_sms_fragment_limit( + service_id=service_id, free_sms_fragment_limit=1111 + ) mock_post.assert_called_once_with( - url='/service/{}/billing/free-sms-fragment-limit'.format(service_id), - data=sms_limit_data + url="/service/{}/billing/free-sms-fragment-limit".format(service_id), + data=sms_limit_data, ) def test_post_free_sms_fragment_limit_for_year_endpoint(mocker, api_user_active): service_id = uuid.uuid4() - sms_limit_data = {'free_sms_fragment_limit': 1111, 'financial_year_start': 2017} - mock_post = mocker.patch('app.notify_client.billing_api_client.BillingAPIClient.post') + sms_limit_data = {"free_sms_fragment_limit": 1111, "financial_year_start": 2017} + mock_post = mocker.patch( + "app.notify_client.billing_api_client.BillingAPIClient.post" + ) client = BillingAPIClient() - client.create_or_update_free_sms_fragment_limit(service_id=service_id, - free_sms_fragment_limit=1111, - year=2017) + client.create_or_update_free_sms_fragment_limit( + service_id=service_id, free_sms_fragment_limit=1111, year=2017 + ) mock_post.assert_called_once_with( - url='/service/{}/billing/free-sms-fragment-limit'.format(service_id), - data=sms_limit_data + url="/service/{}/billing/free-sms-fragment-limit".format(service_id), + data=sms_limit_data, ) -@pytest.mark.parametrize('func, expected_url', [ - (BillingAPIClient.get_data_for_volumes_by_service_report, '/platform-stats/volumes-by-service'), - (BillingAPIClient.get_data_for_daily_volumes_report, '/platform-stats/daily-volumes-report'), - ( - BillingAPIClient.get_data_for_daily_sms_provider_volumes_report, - '/platform-stats/daily-sms-provider-volumes-report' - ), -]) +@pytest.mark.parametrize( + "func, expected_url", + [ + ( + BillingAPIClient.get_data_for_volumes_by_service_report, + "/platform-stats/volumes-by-service", + ), + ( + BillingAPIClient.get_data_for_daily_volumes_report, + "/platform-stats/daily-volumes-report", + ), + ( + BillingAPIClient.get_data_for_daily_sms_provider_volumes_report, + "/platform-stats/daily-sms-provider-volumes-report", + ), + ], +) def test_get_data_for_volume_reports(mocker, api_user_active, func, expected_url): - mock_get = mocker.patch('app.notify_client.billing_api_client.BillingAPIClient.get') + mock_get = mocker.patch("app.notify_client.billing_api_client.BillingAPIClient.get") client = BillingAPIClient() - func(client, '2022-03-01', '2022-03-31') + func(client, "2022-03-01", "2022-03-31") - mock_get.assert_called_once_with(url=expected_url, params={'start_date': '2022-03-01', 'end_date': '2022-03-31'}) + mock_get.assert_called_once_with( + url=expected_url, params={"start_date": "2022-03-01", "end_date": "2022-03-31"} + ) diff --git a/tests/app/notify_client/test_compliant_client.py b/tests/app/notify_client/test_compliant_client.py index 21a99a632..08b6ee602 100644 --- a/tests/app/notify_client/test_compliant_client.py +++ b/tests/app/notify_client/test_compliant_client.py @@ -4,25 +4,25 @@ from app.notify_client.complaint_api_client import ComplaintApiClient def test_get_all_complaints(mocker): client = ComplaintApiClient() - mock = mocker.patch('app.notify_client.complaint_api_client.ComplaintApiClient.get') + mock = mocker.patch("app.notify_client.complaint_api_client.ComplaintApiClient.get") client.get_all_complaints() - mock.assert_called_once_with('/complaint', params={'page': 1}) + mock.assert_called_once_with("/complaint", params={"page": 1}) def test_get_all_complaints_with_a_page_number_specified(mocker): client = ComplaintApiClient() - mock = mocker.patch('app.notify_client.complaint_api_client.ComplaintApiClient.get') + mock = mocker.patch("app.notify_client.complaint_api_client.ComplaintApiClient.get") client.get_all_complaints(page=3) - mock.assert_called_once_with('/complaint', params={'page': 3}) + mock.assert_called_once_with("/complaint", params={"page": 3}) def test_get_complaint_count(mocker): client = ComplaintApiClient() - mock = mocker.patch.object(client, 'get') - params_dict = {'start_date': '2018-06-01', 'end_date': '2018-06-15'} + mock = mocker.patch.object(client, "get") + params_dict = {"start_date": "2018-06-01", "end_date": "2018-06-15"} client.get_complaint_count(params_dict=params_dict) - mock.assert_called_once_with('/complaint/count-by-date-range', params=params_dict) + mock.assert_called_once_with("/complaint/count-by-date-range", params=params_dict) diff --git a/tests/app/notify_client/test_email_branding_client.py b/tests/app/notify_client/test_email_branding_client.py index 86953e201..85b2c12c9 100644 --- a/tests/app/notify_client/test_email_branding_client.py +++ b/tests/app/notify_client/test_email_branding_client.py @@ -5,23 +5,21 @@ from app.notify_client.email_branding_client import EmailBrandingClient def test_get_email_branding(mocker, fake_uuid): mock_get = mocker.patch( - 'app.notify_client.email_branding_client.EmailBrandingClient.get', - return_value={'foo': 'bar'} + "app.notify_client.email_branding_client.EmailBrandingClient.get", + return_value={"foo": "bar"}, ) mock_redis_get = mocker.patch( - 'app.extensions.RedisClient.get', + "app.extensions.RedisClient.get", return_value=None, ) mock_redis_set = mocker.patch( - 'app.extensions.RedisClient.set', + "app.extensions.RedisClient.set", ) EmailBrandingClient().get_email_branding(fake_uuid) - mock_get.assert_called_once_with( - url='/email-branding/{}'.format(fake_uuid) - ) - mock_redis_get.assert_called_once_with('email_branding-{}'.format(fake_uuid)) + mock_get.assert_called_once_with(url="/email-branding/{}".format(fake_uuid)) + mock_redis_get.assert_called_once_with("email_branding-{}".format(fake_uuid)) mock_redis_set.assert_called_once_with( - 'email_branding-{}'.format(fake_uuid), + "email_branding-{}".format(fake_uuid), '{"foo": "bar"}', ex=604800, ) @@ -29,62 +27,78 @@ def test_get_email_branding(mocker, fake_uuid): def test_get_all_email_branding(mocker): mock_get = mocker.patch( - 'app.notify_client.email_branding_client.EmailBrandingClient.get', - return_value={'email_branding': [1, 2, 3]} + "app.notify_client.email_branding_client.EmailBrandingClient.get", + return_value={"email_branding": [1, 2, 3]}, ) mock_redis_get = mocker.patch( - 'app.extensions.RedisClient.get', + "app.extensions.RedisClient.get", return_value=None, ) mock_redis_set = mocker.patch( - 'app.extensions.RedisClient.set', + "app.extensions.RedisClient.set", ) EmailBrandingClient().get_all_email_branding() - mock_get.assert_called_once_with( - url='/email-branding' - ) - mock_redis_get.assert_called_once_with('email_branding') + mock_get.assert_called_once_with(url="/email-branding") + mock_redis_get.assert_called_once_with("email_branding") mock_redis_set.assert_called_once_with( - 'email_branding', - '[1, 2, 3]', + "email_branding", + "[1, 2, 3]", ex=604800, ) def test_create_email_branding(mocker): - org_data = {'logo': 'test.png', 'name': 'test name', 'text': 'test name', 'colour': 'red', - 'brand_type': 'org'} + org_data = { + "logo": "test.png", + "name": "test name", + "text": "test name", + "colour": "red", + "brand_type": "org", + } - mock_post = mocker.patch('app.notify_client.email_branding_client.EmailBrandingClient.post') - mock_redis_delete = mocker.patch('app.extensions.RedisClient.delete') + mock_post = mocker.patch( + "app.notify_client.email_branding_client.EmailBrandingClient.post" + ) + mock_redis_delete = mocker.patch("app.extensions.RedisClient.delete") EmailBrandingClient().create_email_branding( - logo=org_data['logo'], name=org_data['name'], text=org_data['text'], colour=org_data['colour'], - brand_type='org' + logo=org_data["logo"], + name=org_data["name"], + text=org_data["text"], + colour=org_data["colour"], + brand_type="org", ) - mock_post.assert_called_once_with( - url='/email-branding', - data=org_data - ) + mock_post.assert_called_once_with(url="/email-branding", data=org_data) - mock_redis_delete.assert_called_once_with('email_branding') + mock_redis_delete.assert_called_once_with("email_branding") def test_update_email_branding(mocker, fake_uuid): - org_data = {'logo': 'test.png', 'name': 'test name', 'text': 'test name', 'colour': 'red', - 'brand_type': 'org'} + org_data = { + "logo": "test.png", + "name": "test name", + "text": "test name", + "colour": "red", + "brand_type": "org", + } - mock_post = mocker.patch('app.notify_client.email_branding_client.EmailBrandingClient.post') - mock_redis_delete = mocker.patch('app.extensions.RedisClient.delete') + mock_post = mocker.patch( + "app.notify_client.email_branding_client.EmailBrandingClient.post" + ) + mock_redis_delete = mocker.patch("app.extensions.RedisClient.delete") EmailBrandingClient().update_email_branding( - branding_id=fake_uuid, logo=org_data['logo'], name=org_data['name'], text=org_data['text'], - colour=org_data['colour'], brand_type='org') + branding_id=fake_uuid, + logo=org_data["logo"], + name=org_data["name"], + text=org_data["text"], + colour=org_data["colour"], + brand_type="org", + ) mock_post.assert_called_once_with( - url='/email-branding/{}'.format(fake_uuid), - data=org_data + url="/email-branding/{}".format(fake_uuid), data=org_data ) assert mock_redis_delete.call_args_list == [ - call('email_branding-{}'.format(fake_uuid)), - call('email_branding'), + call("email_branding-{}".format(fake_uuid)), + call("email_branding"), ] diff --git a/tests/app/notify_client/test_events_client.py b/tests/app/notify_client/test_events_client.py index 6868bdc3d..9b6f8951f 100644 --- a/tests/app/notify_client/test_events_client.py +++ b/tests/app/notify_client/test_events_client.py @@ -2,15 +2,14 @@ from app.notify_client.events_api_client import EventsApiClient def test_events_client_calls_correct_api_endpoint(mocker): - - expected_url = '/events' - event_type = 'anything' - event_data = {'does_not': 'matter'} - expected_data = {'event_type': event_type, 'data': event_data} + expected_url = "/events" + event_type = "anything" + event_data = {"does_not": "matter"} + expected_data = {"event_type": event_type, "data": event_data} client = EventsApiClient() - mock_post = mocker.patch('app.notify_client.events_api_client.EventsApiClient.post') + mock_post = mocker.patch("app.notify_client.events_api_client.EventsApiClient.post") client.create_event(event_type, event_data) diff --git a/tests/app/notify_client/test_invite_client.py b/tests/app/notify_client/test_invite_client.py index a416556fe..d12832c2f 100644 --- a/tests/app/notify_client/test_invite_client.py +++ b/tests/app/notify_client/test_invite_client.py @@ -9,49 +9,61 @@ def test_client_creates_invite( fake_uuid, sample_invite, ): - - mocker.patch('app.notify_client.current_user') + mocker.patch("app.notify_client.current_user") mock_post = mocker.patch( - 'app.invite_api_client.post', - return_value={'data': dict.fromkeys({ - 'id', 'service', 'from_user', 'email_address', - 'permissions', 'status', 'created_at', 'auth_type', 'folder_permissions' - })} + "app.invite_api_client.post", + return_value={ + "data": dict.fromkeys( + { + "id", + "service", + "from_user", + "email_address", + "permissions", + "status", + "created_at", + "auth_type", + "folder_permissions", + } + ) + }, ) invite_api_client.create_invite( - '12345', '67890', 'test@example.com', {'send_messages'}, 'sms_auth', [fake_uuid] + "12345", "67890", "test@example.com", {"send_messages"}, "sms_auth", [fake_uuid] ) mock_post.assert_called_once_with( - url='/service/{}/invite'.format('67890'), + url="/service/{}/invite".format("67890"), data={ - 'auth_type': 'sms_auth', - 'email_address': 'test@example.com', - 'from_user': '12345', - 'service': '67890', - 'created_by': ANY, - 'permissions': 'send_emails,send_texts', - 'invite_link_host': 'http://localhost:6012', - 'folder_permissions': [fake_uuid] - } + "auth_type": "sms_auth", + "email_address": "test@example.com", + "from_user": "12345", + "service": "67890", + "created_by": ANY, + "permissions": "send_emails,send_texts", + "invite_link_host": "http://localhost:6012", + "folder_permissions": [fake_uuid], + }, ) def test_client_returns_invite(mocker, sample_invite): + sample_invite["status"] = "pending" + service_id = sample_invite["service"] - sample_invite['status'] = 'pending' - service_id = sample_invite['service'] + expected_data = {"data": [sample_invite]} - expected_data = {'data': [sample_invite]} + expected_url = "/service/{}/invite".format(service_id) - expected_url = '/service/{}/invite'.format(service_id) - - mock_get = mocker.patch('app.notify_client.invite_api_client.InviteApiClient.get', return_value=expected_data) + mock_get = mocker.patch( + "app.notify_client.invite_api_client.InviteApiClient.get", + return_value=expected_data, + ) invites = invite_api_client.get_invites_for_service(service_id) mock_get.assert_called_once_with(expected_url) assert len(invites) == 1 - assert invites[0]['status'] == 'pending' + assert invites[0]["status"] == "pending" diff --git a/tests/app/notify_client/test_job_client.py b/tests/app/notify_client/test_job_client.py index b3bffbbe2..99164ed54 100644 --- a/tests/app/notify_client/test_job_client.py +++ b/tests/app/notify_client/test_job_client.py @@ -10,54 +10,48 @@ from app.notify_client.job_api_client import JobApiClient def test_client_creates_job_data_correctly(mocker, fake_uuid): job_id = fake_uuid service_id = fake_uuid - mocker.patch('app.notify_client.current_user', id='1') - mock_redis_set = mocker.patch('app.extensions.RedisClient.set') + mocker.patch("app.notify_client.current_user", id="1") + mock_redis_set = mocker.patch("app.extensions.RedisClient.set") - expected_data = { - "id": job_id, - "created_by": '1' - } + expected_data = {"id": job_id, "created_by": "1"} - expected_url = '/service/{}/job'.format(service_id) + expected_url = "/service/{}/job".format(service_id) client = JobApiClient() mock_post = mocker.patch( - 'app.notify_client.job_api_client.JobApiClient.post', - return_value={'data': dict(statistics=[], **expected_data)} + "app.notify_client.job_api_client.JobApiClient.post", + return_value={"data": dict(statistics=[], **expected_data)}, ) client.create_job(service_id, job_id) mock_post.assert_called_once_with(url=expected_url, data=expected_data) mock_redis_set.assert_called_once_with( - 'has_jobs-{}'.format(service_id), - b'true', + "has_jobs-{}".format(service_id), + b"true", ex=604800, ) def test_client_schedules_job(mocker, fake_uuid): + mocker.patch("app.notify_client.current_user", id="1") - mocker.patch('app.notify_client.current_user', id='1') + mock_post = mocker.patch("app.notify_client.job_api_client.JobApiClient.post") - mock_post = mocker.patch('app.notify_client.job_api_client.JobApiClient.post') + when = "2016-08-25T13:04:21.767198" - when = '2016-08-25T13:04:21.767198' + JobApiClient().create_job(fake_uuid, 1, scheduled_for=when) - JobApiClient().create_job( - fake_uuid, 1, scheduled_for=when - ) - - assert mock_post.call_args[1]['data']['scheduled_for'] == when + assert mock_post.call_args[1]["data"]["scheduled_for"] == when def test_client_gets_job_by_service_and_job(mocker): - service_id = 'service_id' - job_id = 'job_id' + service_id = "service_id" + job_id = "job_id" - expected_url = '/service/{}/job/{}'.format(service_id, job_id) + expected_url = "/service/{}/job/{}".format(service_id, job_id) client = JobApiClient() - mock_get = mocker.patch('app.notify_client.job_api_client.JobApiClient.get') + mock_get = mocker.patch("app.notify_client.job_api_client.JobApiClient.get") client.get_job(service_id, job_id) @@ -65,56 +59,60 @@ def test_client_gets_job_by_service_and_job(mocker): def test_client_gets_jobs_with_status_filter(mocker): - mock_get = mocker.patch('app.notify_client.job_api_client.JobApiClient.get') + mock_get = mocker.patch("app.notify_client.job_api_client.JobApiClient.get") - JobApiClient().get_jobs(uuid.uuid4(), statuses=['foo', 'bar']) + JobApiClient().get_jobs(uuid.uuid4(), statuses=["foo", "bar"]) - mock_get.assert_called_once_with(url=ANY, params={'page': 1, 'statuses': 'foo,bar'}) + mock_get.assert_called_once_with(url=ANY, params={"page": 1, "statuses": "foo,bar"}) def test_client_gets_jobs_with_page_parameter(mocker): client = JobApiClient() - mock_get = mocker.patch('app.notify_client.job_api_client.JobApiClient.get') + mock_get = mocker.patch("app.notify_client.job_api_client.JobApiClient.get") - client.get_jobs('foo', page=2) + client.get_jobs("foo", page=2) - mock_get.assert_called_once_with(url=ANY, params={'page': 2}) + mock_get.assert_called_once_with(url=ANY, params={"page": 2}) def test_client_parses_job_stats(mocker): - service_id = 'service_id' - job_id = 'job_id' - expected_data = {'data': { - 'status': 'finished', - 'template_version': 3, - 'id': job_id, - 'updated_at': '2016-08-24T08:29:28.332972+00:00', - 'service': service_id, - 'processing_finished': '2016-08-24T08:11:48.676365+00:00', - 'statistics': [ - {'status': 'failed', 'count': 10}, - {'status': 'technical-failure', 'count': 10}, - {'status': 'temporary-failure', 'count': 10}, - {'status': 'permanent-failure', 'count': 10}, - {'status': 'created', 'count': 10}, - {'status': 'sending', 'count': 10}, - {'status': 'pending', 'count': 10}, - {'status': 'delivered', 'count': 10} - ], - 'original_file_name': 'test-notify-email.csv', - 'created_by': { - 'name': 'test-user@digital.cabinet-office.gov.uk', - 'id': '3571f2ae-7a39-4fb4-9ad7-8453f5257072' - }, - 'created_at': '2016-08-24T08:09:56.371073+00:00', - 'template': 'c0309261-9c9e-4530-8fed-5f67b02260d2', - 'notification_count': 80, - 'processing_started': '2016-08-24T08:09:57.661246+00:00' - }} + service_id = "service_id" + job_id = "job_id" + expected_data = { + "data": { + "status": "finished", + "template_version": 3, + "id": job_id, + "updated_at": "2016-08-24T08:29:28.332972+00:00", + "service": service_id, + "processing_finished": "2016-08-24T08:11:48.676365+00:00", + "statistics": [ + {"status": "failed", "count": 10}, + {"status": "technical-failure", "count": 10}, + {"status": "temporary-failure", "count": 10}, + {"status": "permanent-failure", "count": 10}, + {"status": "created", "count": 10}, + {"status": "sending", "count": 10}, + {"status": "pending", "count": 10}, + {"status": "delivered", "count": 10}, + ], + "original_file_name": "test-notify-email.csv", + "created_by": { + "name": "test-user@digital.cabinet-office.gov.uk", + "id": "3571f2ae-7a39-4fb4-9ad7-8453f5257072", + }, + "created_at": "2016-08-24T08:09:56.371073+00:00", + "template": "c0309261-9c9e-4530-8fed-5f67b02260d2", + "notification_count": 80, + "processing_started": "2016-08-24T08:09:57.661246+00:00", + } + } - expected_url = '/service/{}/job/{}'.format(service_id, job_id) + expected_url = "/service/{}/job/{}".format(service_id, job_id) - mock_get = mocker.patch('app.notify_client.job_api_client.JobApiClient.get', return_value=expected_data) + mock_get = mocker.patch( + "app.notify_client.job_api_client.JobApiClient.get", return_value=expected_data + ) result = Job.from_id(job_id, service_id=service_id) @@ -126,30 +124,34 @@ def test_client_parses_job_stats(mocker): def test_client_parses_empty_job_stats(mocker): - service_id = 'service_id' - job_id = 'job_id' - expected_data = {'data': { - 'status': 'finished', - 'template_version': 3, - 'id': job_id, - 'updated_at': '2016-08-24T08:29:28.332972+00:00', - 'service': service_id, - 'processing_finished': '2016-08-24T08:11:48.676365+00:00', - 'statistics': [], - 'original_file_name': 'test-notify-email.csv', - 'created_by': { - 'name': 'test-user@digital.cabinet-office.gov.uk', - 'id': '3571f2ae-7a39-4fb4-9ad7-8453f5257072' - }, - 'created_at': '2016-08-24T08:09:56.371073+00:00', - 'template': 'c0309261-9c9e-4530-8fed-5f67b02260d2', - 'notification_count': 80, - 'processing_started': '2016-08-24T08:09:57.661246+00:00' - }} + service_id = "service_id" + job_id = "job_id" + expected_data = { + "data": { + "status": "finished", + "template_version": 3, + "id": job_id, + "updated_at": "2016-08-24T08:29:28.332972+00:00", + "service": service_id, + "processing_finished": "2016-08-24T08:11:48.676365+00:00", + "statistics": [], + "original_file_name": "test-notify-email.csv", + "created_by": { + "name": "test-user@digital.cabinet-office.gov.uk", + "id": "3571f2ae-7a39-4fb4-9ad7-8453f5257072", + }, + "created_at": "2016-08-24T08:09:56.371073+00:00", + "template": "c0309261-9c9e-4530-8fed-5f67b02260d2", + "notification_count": 80, + "processing_started": "2016-08-24T08:09:57.661246+00:00", + } + } - expected_url = '/service/{}/job/{}'.format(service_id, job_id) + expected_url = "/service/{}/job/{}".format(service_id, job_id) - mock_get = mocker.patch('app.notify_client.job_api_client.JobApiClient.get', return_value=expected_data) + mock_get = mocker.patch( + "app.notify_client.job_api_client.JobApiClient.get", return_value=expected_data + ) result = Job.from_id(job_id, service_id=service_id) @@ -161,70 +163,79 @@ def test_client_parses_empty_job_stats(mocker): def test_client_parses_job_stats_for_service(mocker): - service_id = 'service_id' - job_1_id = 'job_id_1' - job_2_id = 'job_id_2' - expected_data = {'data': [{ - 'status': 'finished', - 'template_version': 3, - 'id': job_1_id, - 'updated_at': '2016-08-24T08:29:28.332972+00:00', - 'service': service_id, - 'processing_finished': '2016-08-24T08:11:48.676365+00:00', - 'statistics': [ - {'status': 'failed', 'count': 10}, - {'status': 'technical-failure', 'count': 10}, - {'status': 'temporary-failure', 'count': 10}, - {'status': 'permanent-failure', 'count': 10}, - {'status': 'created', 'count': 10}, - {'status': 'sending', 'count': 10}, - {'status': 'pending', 'count': 10}, - {'status': 'delivered', 'count': 10} - ], - 'original_file_name': 'test-notify-email.csv', - 'created_by': { - 'name': 'test-user@digital.cabinet-office.gov.uk', - 'id': '3571f2ae-7a39-4fb4-9ad7-8453f5257072' - }, - 'created_at': '2016-08-24T08:09:56.371073+00:00', - 'template': 'c0309261-9c9e-4530-8fed-5f67b02260d2', - 'notification_count': 80, - 'processing_started': '2016-08-24T08:09:57.661246+00:00' - }, { - 'status': 'finished', - 'template_version': 3, - 'id': job_2_id, - 'updated_at': '2016-08-24T08:29:28.332972+00:00', - 'service': service_id, - 'processing_finished': '2016-08-24T08:11:48.676365+00:00', - 'statistics': [ - {'status': 'failed', 'count': 5}, - {'status': 'technical-failure', 'count': 5}, - {'status': 'temporary-failure', 'count': 5}, - {'status': 'permanent-failure', 'count': 5}, - {'status': 'created', 'count': 5}, - {'status': 'sending', 'count': 5}, - {'status': 'pending', 'count': 5}, - {'status': 'delivered', 'count': 5} - ], - 'original_file_name': 'test-notify-email.csv', - 'created_by': { - 'name': 'test-user@digital.cabinet-office.gov.uk', - 'id': '3571f2ae-7a39-4fb4-9ad7-8453f5257072' - }, - 'created_at': '2016-08-24T08:09:56.371073+00:00', - 'template': 'c0309261-9c9e-4530-8fed-5f67b02260d2', - 'notification_count': 40, - 'processing_started': '2016-08-24T08:09:57.661246+00:00' - }]} + service_id = "service_id" + job_1_id = "job_id_1" + job_2_id = "job_id_2" + expected_data = { + "data": [ + { + "status": "finished", + "template_version": 3, + "id": job_1_id, + "updated_at": "2016-08-24T08:29:28.332972+00:00", + "service": service_id, + "processing_finished": "2016-08-24T08:11:48.676365+00:00", + "statistics": [ + {"status": "failed", "count": 10}, + {"status": "technical-failure", "count": 10}, + {"status": "temporary-failure", "count": 10}, + {"status": "permanent-failure", "count": 10}, + {"status": "created", "count": 10}, + {"status": "sending", "count": 10}, + {"status": "pending", "count": 10}, + {"status": "delivered", "count": 10}, + ], + "original_file_name": "test-notify-email.csv", + "created_by": { + "name": "test-user@digital.cabinet-office.gov.uk", + "id": "3571f2ae-7a39-4fb4-9ad7-8453f5257072", + }, + "created_at": "2016-08-24T08:09:56.371073+00:00", + "template": "c0309261-9c9e-4530-8fed-5f67b02260d2", + "notification_count": 80, + "processing_started": "2016-08-24T08:09:57.661246+00:00", + }, + { + "status": "finished", + "template_version": 3, + "id": job_2_id, + "updated_at": "2016-08-24T08:29:28.332972+00:00", + "service": service_id, + "processing_finished": "2016-08-24T08:11:48.676365+00:00", + "statistics": [ + {"status": "failed", "count": 5}, + {"status": "technical-failure", "count": 5}, + {"status": "temporary-failure", "count": 5}, + {"status": "permanent-failure", "count": 5}, + {"status": "created", "count": 5}, + {"status": "sending", "count": 5}, + {"status": "pending", "count": 5}, + {"status": "delivered", "count": 5}, + ], + "original_file_name": "test-notify-email.csv", + "created_by": { + "name": "test-user@digital.cabinet-office.gov.uk", + "id": "3571f2ae-7a39-4fb4-9ad7-8453f5257072", + }, + "created_at": "2016-08-24T08:09:56.371073+00:00", + "template": "c0309261-9c9e-4530-8fed-5f67b02260d2", + "notification_count": 40, + "processing_started": "2016-08-24T08:09:57.661246+00:00", + }, + ] + } - expected_url = '/service/{}/job'.format(service_id) + expected_url = "/service/{}/job".format(service_id) - mock_get = mocker.patch('app.notify_client.job_api_client.JobApiClient.get', return_value=expected_data) + mock_get = mocker.patch( + "app.notify_client.job_api_client.JobApiClient.get", return_value=expected_data + ) result = PaginatedJobs(service_id) - mock_get.assert_called_once_with(url=expected_url, params={'page': 1, 'statuses': ANY}) + mock_get.assert_called_once_with( + url=expected_url, params={"page": 1, "statuses": ANY} + ) assert result[0].id == job_1_id assert result[0].notifications_requested == 80 assert result[0].notifications_sent == 50 @@ -238,52 +249,61 @@ def test_client_parses_job_stats_for_service(mocker): def test_client_parses_empty_job_stats_for_service(mocker): - service_id = 'service_id' - job_1_id = 'job_id_1' - job_2_id = 'job_id_2' - expected_data = {'data': [{ - 'status': 'finished', - 'template_version': 3, - 'id': job_1_id, - 'updated_at': '2016-08-24T08:29:28.332972+00:00', - 'service': service_id, - 'processing_finished': '2016-08-24T08:11:48.676365+00:00', - 'statistics': [], - 'original_file_name': 'test-notify-email.csv', - 'created_by': { - 'name': 'test-user@digital.cabinet-office.gov.uk', - 'id': '3571f2ae-7a39-4fb4-9ad7-8453f5257072' - }, - 'created_at': '2016-08-24T08:09:56.371073+00:00', - 'template': 'c0309261-9c9e-4530-8fed-5f67b02260d2', - 'notification_count': 80, - 'processing_started': '2016-08-24T08:09:57.661246+00:00' - }, { - 'status': 'finished', - 'template_version': 3, - 'id': job_2_id, - 'updated_at': '2016-08-24T08:29:28.332972+00:00', - 'service': service_id, - 'processing_finished': '2016-08-24T08:11:48.676365+00:00', - 'statistics': [], - 'original_file_name': 'test-notify-email.csv', - 'created_by': { - 'name': 'test-user@digital.cabinet-office.gov.uk', - 'id': '3571f2ae-7a39-4fb4-9ad7-8453f5257072' - }, - 'created_at': '2016-08-24T08:09:56.371073+00:00', - 'template': 'c0309261-9c9e-4530-8fed-5f67b02260d2', - 'notification_count': 40, - 'processing_started': '2016-08-24T08:09:57.661246+00:00' - }]} + service_id = "service_id" + job_1_id = "job_id_1" + job_2_id = "job_id_2" + expected_data = { + "data": [ + { + "status": "finished", + "template_version": 3, + "id": job_1_id, + "updated_at": "2016-08-24T08:29:28.332972+00:00", + "service": service_id, + "processing_finished": "2016-08-24T08:11:48.676365+00:00", + "statistics": [], + "original_file_name": "test-notify-email.csv", + "created_by": { + "name": "test-user@digital.cabinet-office.gov.uk", + "id": "3571f2ae-7a39-4fb4-9ad7-8453f5257072", + }, + "created_at": "2016-08-24T08:09:56.371073+00:00", + "template": "c0309261-9c9e-4530-8fed-5f67b02260d2", + "notification_count": 80, + "processing_started": "2016-08-24T08:09:57.661246+00:00", + }, + { + "status": "finished", + "template_version": 3, + "id": job_2_id, + "updated_at": "2016-08-24T08:29:28.332972+00:00", + "service": service_id, + "processing_finished": "2016-08-24T08:11:48.676365+00:00", + "statistics": [], + "original_file_name": "test-notify-email.csv", + "created_by": { + "name": "test-user@digital.cabinet-office.gov.uk", + "id": "3571f2ae-7a39-4fb4-9ad7-8453f5257072", + }, + "created_at": "2016-08-24T08:09:56.371073+00:00", + "template": "c0309261-9c9e-4530-8fed-5f67b02260d2", + "notification_count": 40, + "processing_started": "2016-08-24T08:09:57.661246+00:00", + }, + ] + } - expected_url = '/service/{}/job'.format(service_id) + expected_url = "/service/{}/job".format(service_id) - mock_get = mocker.patch('app.notify_client.job_api_client.JobApiClient.get', return_value=expected_data) + mock_get = mocker.patch( + "app.notify_client.job_api_client.JobApiClient.get", return_value=expected_data + ) result = PaginatedJobs(service_id) - mock_get.assert_called_once_with(url=expected_url, params={'page': 1, 'statuses': ANY}) + mock_get.assert_called_once_with( + url=expected_url, params={"page": 1, "statuses": ANY} + ) assert result[0].id == job_1_id assert result[0].notifications_requested == 0 assert result[0].notifications_sent == 0 @@ -297,26 +317,28 @@ def test_client_parses_empty_job_stats_for_service(mocker): def test_cancel_job(mocker): - mock_post = mocker.patch('app.notify_client.job_api_client.JobApiClient.post') + mock_post = mocker.patch("app.notify_client.job_api_client.JobApiClient.post") - JobApiClient().cancel_job('service_id', 'job_id') + JobApiClient().cancel_job("service_id", "job_id") mock_post.assert_called_once_with( - url='/service/{}/job/{}/cancel'.format('service_id', 'job_id'), - data={} + url="/service/{}/job/{}/cancel".format("service_id", "job_id"), data={} ) -@pytest.mark.parametrize('job_data, expected_cache_value', [ - ( - [{'data': [1, 2, 3], 'statistics': []}], - 'true', - ), - ( - [], - 'false', - ), -]) +@pytest.mark.parametrize( + "job_data, expected_cache_value", + [ + ( + [{"data": [1, 2, 3], "statistics": []}], + "true", + ), + ( + [], + "false", + ), + ], +) def test_has_jobs_sets_cache( mocker, fake_uuid, @@ -324,44 +346,42 @@ def test_has_jobs_sets_cache( expected_cache_value, ): mock_get = mocker.patch( - 'app.notify_client.job_api_client.JobApiClient.get', - return_value={'data': job_data} + "app.notify_client.job_api_client.JobApiClient.get", + return_value={"data": job_data}, ) - mock_redis_set = mocker.patch('app.extensions.RedisClient.set') + mock_redis_set = mocker.patch("app.extensions.RedisClient.set") JobApiClient().has_jobs(fake_uuid) mock_get.assert_called_once_with( - url='/service/{}/job'.format(fake_uuid), - params={'page': 1} + url="/service/{}/job".format(fake_uuid), params={"page": 1} ) mock_redis_set.assert_called_once_with( - 'has_jobs-{}'.format(fake_uuid), + "has_jobs-{}".format(fake_uuid), expected_cache_value, ex=604800, ) -@pytest.mark.parametrize('cache_value, return_value', [ - (b'true', True), - (b'false', False), -]) +@pytest.mark.parametrize( + "cache_value, return_value", + [ + (b"true", True), + (b"false", False), + ], +) def test_has_jobs_returns_from_cache( mocker, fake_uuid, cache_value, return_value, ): - mock_get = mocker.patch( - 'app.notify_client.job_api_client.JobApiClient.get' - ) + mock_get = mocker.patch("app.notify_client.job_api_client.JobApiClient.get") mock_redis_get = mocker.patch( - 'app.extensions.RedisClient.get', + "app.extensions.RedisClient.get", return_value=cache_value, ) assert JobApiClient().has_jobs(fake_uuid) is return_value assert not mock_get.called - mock_redis_get.assert_called_once_with( - 'has_jobs-{}'.format(fake_uuid) - ) + mock_redis_get.assert_called_once_with("has_jobs-{}".format(fake_uuid)) diff --git a/tests/app/notify_client/test_notification_client.py b/tests/app/notify_client/test_notification_client.py index be9af8f46..2eee3b2fd 100644 --- a/tests/app/notify_client/test_notification_client.py +++ b/tests/app/notify_client/test_notification_client.py @@ -3,107 +3,149 @@ import pytest from app.notify_client.notification_api_client import NotificationApiClient -@pytest.mark.parametrize("arguments,expected_call", [ - ( - {}, - {'url': '/service/abcd1234/notifications', 'params': {}} - ), - ( - {'page': 99}, - {'url': '/service/abcd1234/notifications', 'params': {'page': 99}} - ), - ( - {'include_jobs': False}, - {'url': '/service/abcd1234/notifications', 'params': {'include_jobs': False}} - ), - ( - {'include_from_test_key': True}, - {'url': '/service/abcd1234/notifications', 'params': {'include_from_test_key': True}} - ), - ( - {'page': 48, 'limit_days': 3}, - {'url': '/service/abcd1234/notifications', 'params': {'page': 48, 'limit_days': 3}} - ), - ( - {'job_id': 'efgh5678'}, - {'url': '/service/abcd1234/job/efgh5678/notifications', 'params': {}} - ), - ( - {'job_id': 'efgh5678', 'page': 48}, - {'url': '/service/abcd1234/job/efgh5678/notifications', 'params': {'page': 48}} - ), - ( - {'job_id': 'efgh5678', 'page': 48, 'limit_days': 3}, - {'url': '/service/abcd1234/job/efgh5678/notifications', 'params': {'page': 48}} - ), -]) -def test_client_gets_notifications_for_service_and_job_by_page(mocker, arguments, expected_call): - - mock_get = mocker.patch('app.notify_client.notification_api_client.NotificationApiClient.get') - NotificationApiClient().get_notifications_for_service('abcd1234', **arguments) +@pytest.mark.parametrize( + "arguments,expected_call", + [ + ({}, {"url": "/service/abcd1234/notifications", "params": {}}), + ( + {"page": 99}, + {"url": "/service/abcd1234/notifications", "params": {"page": 99}}, + ), + ( + {"include_jobs": False}, + { + "url": "/service/abcd1234/notifications", + "params": {"include_jobs": False}, + }, + ), + ( + {"include_from_test_key": True}, + { + "url": "/service/abcd1234/notifications", + "params": {"include_from_test_key": True}, + }, + ), + ( + {"page": 48, "limit_days": 3}, + { + "url": "/service/abcd1234/notifications", + "params": {"page": 48, "limit_days": 3}, + }, + ), + ( + {"job_id": "efgh5678"}, + {"url": "/service/abcd1234/job/efgh5678/notifications", "params": {}}, + ), + ( + {"job_id": "efgh5678", "page": 48}, + { + "url": "/service/abcd1234/job/efgh5678/notifications", + "params": {"page": 48}, + }, + ), + ( + {"job_id": "efgh5678", "page": 48, "limit_days": 3}, + { + "url": "/service/abcd1234/job/efgh5678/notifications", + "params": {"page": 48}, + }, + ), + ], +) +def test_client_gets_notifications_for_service_and_job_by_page( + mocker, arguments, expected_call +): + mock_get = mocker.patch( + "app.notify_client.notification_api_client.NotificationApiClient.get" + ) + NotificationApiClient().get_notifications_for_service("abcd1234", **arguments) mock_get.assert_called_once_with(**expected_call) -@pytest.mark.parametrize("arguments,expected_call", [ - ( - {'to': "2028675309"}, - {'url': '/service/abcd1234/notifications', 'data': {'to': "2028675309"}} - ), - ( - {'to': "2028675309", 'job_id': 'efgh5678'}, - {'url': '/service/abcd1234/job/efgh5678/notifications', 'data': {'to': "2028675309"}} - ), - ( - {'to': "2028675309", 'page': 99}, - {'url': '/service/abcd1234/notifications', 'data': {'to': "2028675309", 'page': 99}} - ), - ( - {'to': "2028675309", 'limit_days': 3}, - {'url': '/service/abcd1234/notifications', 'data': {'to': "2028675309", 'limit_days': 3}} - ), - ( - {'to': "2028675309", 'job_id': 'efgh5678', 'limit_days': 3}, - {'url': '/service/abcd1234/job/efgh5678/notifications', 'data': {'to': "2028675309"}} - ), -]) -def test_client_gets_notifications_for_service_and_job_by_page_posts_for_to(mocker, arguments, expected_call): - - mock_post = mocker.patch('app.notify_client.notification_api_client.NotificationApiClient.post') - NotificationApiClient().get_notifications_for_service('abcd1234', **arguments) +@pytest.mark.parametrize( + "arguments,expected_call", + [ + ( + {"to": "2028675309"}, + {"url": "/service/abcd1234/notifications", "data": {"to": "2028675309"}}, + ), + ( + {"to": "2028675309", "job_id": "efgh5678"}, + { + "url": "/service/abcd1234/job/efgh5678/notifications", + "data": {"to": "2028675309"}, + }, + ), + ( + {"to": "2028675309", "page": 99}, + { + "url": "/service/abcd1234/notifications", + "data": {"to": "2028675309", "page": 99}, + }, + ), + ( + {"to": "2028675309", "limit_days": 3}, + { + "url": "/service/abcd1234/notifications", + "data": {"to": "2028675309", "limit_days": 3}, + }, + ), + ( + {"to": "2028675309", "job_id": "efgh5678", "limit_days": 3}, + { + "url": "/service/abcd1234/job/efgh5678/notifications", + "data": {"to": "2028675309"}, + }, + ), + ], +) +def test_client_gets_notifications_for_service_and_job_by_page_posts_for_to( + mocker, arguments, expected_call +): + mock_post = mocker.patch( + "app.notify_client.notification_api_client.NotificationApiClient.post" + ) + NotificationApiClient().get_notifications_for_service("abcd1234", **arguments) mock_post.assert_called_once_with(**expected_call) def test_send_notification(mocker, client_request, active_user_with_permissions): - mock_post = mocker.patch('app.notify_client.notification_api_client.NotificationApiClient.post') + mock_post = mocker.patch( + "app.notify_client.notification_api_client.NotificationApiClient.post" + ) NotificationApiClient().send_notification( - 'foo', - template_id='bar', - recipient='2028675301', + "foo", + template_id="bar", + recipient="2028675301", personalisation=None, - sender_id=None + sender_id=None, ) mock_post.assert_called_once_with( - url='/service/foo/send-notification', + url="/service/foo/send-notification", data={ - 'template_id': 'bar', - 'to': '2028675301', - 'personalisation': None, - 'created_by': active_user_with_permissions['id'] - } + "template_id": "bar", + "to": "2028675301", + "personalisation": None, + "created_by": active_user_with_permissions["id"], + }, ) def test_get_notification(mocker): - mock_get = mocker.patch('app.notify_client.notification_api_client.NotificationApiClient.get') - NotificationApiClient().get_notification('foo', 'bar') - mock_get.assert_called_once_with( - url='/service/foo/notifications/bar' + mock_get = mocker.patch( + "app.notify_client.notification_api_client.NotificationApiClient.get" ) + NotificationApiClient().get_notification("foo", "bar") + mock_get.assert_called_once_with(url="/service/foo/notifications/bar") def test_get_notification_count_for_job_id(mocker): - mock_get = mocker.patch('app.notify_client.notification_api_client.NotificationApiClient.get') - NotificationApiClient().get_notification_count_for_job_id(service_id='foo', job_id='bar') - mock_get.assert_called_once_with( - url='/service/foo/job/bar/notification_count', + mock_get = mocker.patch( + "app.notify_client.notification_api_client.NotificationApiClient.get" + ) + NotificationApiClient().get_notification_count_for_job_id( + service_id="foo", job_id="bar" + ) + mock_get.assert_called_once_with( + url="/service/foo/job/bar/notification_count", ) diff --git a/tests/app/notify_client/test_notify_admin_api_client.py b/tests/app/notify_client/test_notify_admin_api_client.py index 3036d8b69..e68d4669f 100644 --- a/tests/app/notify_client/test_notify_admin_api_client.py +++ b/tests/app/notify_client/test_notify_admin_api_client.py @@ -15,19 +15,18 @@ from tests.conftest import ( ) -@pytest.mark.parametrize('method', [ - 'put', - 'post', - 'delete' -]) -@pytest.mark.parametrize('user', [ - create_api_user_active(), - create_platform_admin_user(), -], ids=['api_user', 'platform_admin']) -@pytest.mark.parametrize('service', [ - service_json(active=True), - None -], ids=['active_service', 'no_service']) +@pytest.mark.parametrize("method", ["put", "post", "delete"]) +@pytest.mark.parametrize( + "user", + [ + create_api_user_active(), + create_platform_admin_user(), + ], + ids=["api_user", "platform_admin"], +) +@pytest.mark.parametrize( + "service", [service_json(active=True), None], ids=["active_service", "no_service"] +) def test_active_service_can_be_modified(notify_admin, method, user, service): api_client = NotifyAdminAPIClient() @@ -35,46 +34,42 @@ def test_active_service_can_be_modified(notify_admin, method, user, service): client.login(user) request_context.service = Service(service) - with patch.object(api_client, 'request') as request: - ret = getattr(api_client, method)('url', 'data') + with patch.object(api_client, "request") as request: + ret = getattr(api_client, method)("url", "data") assert request.called assert ret == request.return_value -@pytest.mark.parametrize('method', [ - 'put', - 'post', - 'delete' -]) -def test_inactive_service_cannot_be_modified_by_normal_user(notify_admin, api_user_active, method): +@pytest.mark.parametrize("method", ["put", "post", "delete"]) +def test_inactive_service_cannot_be_modified_by_normal_user( + notify_admin, api_user_active, method +): api_client = NotifyAdminAPIClient() with notify_admin.test_request_context() as request_context, notify_admin.test_client() as client: client.login(api_user_active) request_context.service = Service(service_json(active=False)) - with patch.object(api_client, 'request') as request: + with patch.object(api_client, "request") as request: with pytest.raises(werkzeug.exceptions.Forbidden): - getattr(api_client, method)('url', 'data') + getattr(api_client, method)("url", "data") assert not request.called -@pytest.mark.parametrize('method', [ - 'put', - 'post', - 'delete' -]) -def test_inactive_service_can_be_modified_by_platform_admin(notify_admin, platform_admin_user, method): +@pytest.mark.parametrize("method", ["put", "post", "delete"]) +def test_inactive_service_can_be_modified_by_platform_admin( + notify_admin, platform_admin_user, method +): api_client = NotifyAdminAPIClient() with notify_admin.test_request_context() as request_context, notify_admin.test_client() as client: client.login(platform_admin_user) request_context.service = Service(service_json(active=False)) - with patch.object(api_client, 'request') as request: - ret = getattr(api_client, method)('url', 'data') + with patch.object(api_client, "request") as request: + ret = getattr(api_client, method)("url", "data") assert request.called assert ret == request.return_value @@ -82,17 +77,22 @@ def test_inactive_service_can_be_modified_by_platform_admin(notify_admin, platfo def test_generate_headers_sets_standard_headers(notify_admin): api_client = NotifyAdminAPIClient() - with set_config(notify_admin, 'ROUTE_SECRET_KEY_1', 'proxy-secret'): + with set_config(notify_admin, "ROUTE_SECRET_KEY_1", "proxy-secret"): api_client.init_app(notify_admin) # with patch('app.notify_client.has_request_context', return_value=False): - headers = api_client.generate_headers('api_token') + headers = api_client.generate_headers("api_token") - assert set(headers.keys()) == {'Authorization', 'Content-type', 'User-agent', 'X-Custom-Forwarder'} - assert headers['Authorization'] == 'Bearer api_token' - assert headers['Content-type'] == 'application/json' - assert headers['User-agent'].startswith('NOTIFY-API-PYTHON-CLIENT') - assert headers['X-Custom-Forwarder'] == 'proxy-secret' + assert set(headers.keys()) == { + "Authorization", + "Content-type", + "User-agent", + "X-Custom-Forwarder", + } + assert headers["Authorization"] == "Bearer api_token" + assert headers["Content-type"] == "application/json" + assert headers["User-agent"].startswith("NOTIFY-API-PYTHON-CLIENT") + assert headers["X-Custom-Forwarder"] == "proxy-secret" def test_generate_headers_sets_request_id_if_in_request_context(notify_admin): @@ -100,23 +100,28 @@ def test_generate_headers_sets_request_id_if_in_request_context(notify_admin): api_client.init_app(notify_admin) with notify_admin.test_request_context() as request_context: - headers = api_client.generate_headers('api_token') + headers = api_client.generate_headers("api_token") assert set(headers.keys()) == { - 'Authorization', 'Content-type', 'User-agent', 'X-Custom-Forwarder', 'X-B3-TraceId', 'X-B3-SpanId', + "Authorization", + "Content-type", + "User-agent", + "X-Custom-Forwarder", + "X-B3-TraceId", + "X-B3-SpanId", } - assert headers['X-B3-TraceId'] == request_context.request.request_id - assert headers['X-B3-SpanId'] == request_context.request.span_id + assert headers["X-B3-TraceId"] == request_context.request.request_id + assert headers["X-B3-SpanId"] == request_context.request.span_id def test_get_notification_status_by_service(mocker): - mock_get = mocker.patch.object(notification_api_client, 'get') + mock_get = mocker.patch.object(notification_api_client, "get") start_date = date(2019, 4, 1) end_date = date(2019, 4, 30) notification_api_client.get_notification_status_by_service(start_date, end_date) mock_get.assert_called_once_with( - url='service/monthly-data-by-service', - params={'start_date': '2019-04-01', 'end_date': '2019-04-30'} + url="service/monthly-data-by-service", + params={"start_date": "2019-04-01", "end_date": "2019-04-30"}, ) diff --git a/tests/app/notify_client/test_organization_client.py b/tests/app/notify_client/test_organization_client.py index f6e67311e..cb63cb3e2 100644 --- a/tests/app/notify_client/test_organization_client.py +++ b/tests/app/notify_client/test_organization_client.py @@ -7,18 +7,18 @@ from app import organizations_client @pytest.mark.parametrize( ( - 'client_method,' - 'expected_cache_get_calls,' - 'cache_value,' - 'expected_api_calls,' - 'expected_cache_set_calls,' - 'expected_return_value,' + "client_method," + "expected_cache_get_calls," + "cache_value," + "expected_api_calls," + "expected_cache_set_calls," + "expected_return_value," ), [ ( - 'get_domains', + "get_domains", [ - call('domains'), + call("domains"), ], b""" [ @@ -28,36 +28,30 @@ from app import organizations_client """, [], [], - ['a', 'b', 'c', 'd', 'e'], + ["a", "b", "c", "d", "e"], ), ( - 'get_domains', + "get_domains", [ - call('domains'), - call('organizations'), + call("domains"), + call("organizations"), ], None, - [ - call(url='/organizations') - ], + [call(url="/organizations")], [ call( - 'organizations', + "organizations", '[{"domains": ["x", "y", "z"]}]', ex=604800, ), - call( - 'domains', - '["x", "y", "z"]', - ex=604800 - ), + call("domains", '["x", "y", "z"]', ex=604800), ], - 'from api', + "from api", ), ( - 'get_organizations', + "get_organizations", [ - call('organizations'), + call("organizations"), ], b""" [ @@ -69,28 +63,26 @@ from app import organizations_client [], [ {"name": "org 1", "domains": ["a", "b", "c"]}, - {"name": "org 2", "domains": ["c", "d", "e"]} + {"name": "org 2", "domains": ["c", "d", "e"]}, ], ), ( - 'get_organizations', + "get_organizations", [ - call('organizations'), + call("organizations"), ], None, - [ - call(url='/organizations') - ], + [call(url="/organizations")], [ call( - 'organizations', + "organizations", '[{"domains": ["x", "y", "z"]}]', ex=604800, ), ], - 'from api', + "from api", ), - ] + ], ) def test_returns_value_from_cache( notify_admin, @@ -102,19 +94,16 @@ def test_returns_value_from_cache( expected_api_calls, expected_cache_set_calls, ): - mock_redis_get = mocker.patch( - 'app.extensions.RedisClient.get', + "app.extensions.RedisClient.get", return_value=cache_value, ) mock_api_get = mocker.patch( - 'app.notify_client.NotifyAdminAPIClient.get', - return_value=[ - {'domains': ['x', 'y', 'z']} - ], + "app.notify_client.NotifyAdminAPIClient.get", + return_value=[{"domains": ["x", "y", "z"]}], ) mock_redis_set = mocker.patch( - 'app.extensions.RedisClient.set', + "app.extensions.RedisClient.set", ) getattr(organizations_client, client_method)() @@ -130,118 +119,137 @@ def test_deletes_domain_cache( mocker, fake_uuid, ): - mocker.patch('app.notify_client.current_user', id='1') - mock_redis_delete = mocker.patch('app.extensions.RedisClient.delete') - mock_request = mocker.patch('notifications_python_client.base.BaseAPIClient.request') + mocker.patch("app.notify_client.current_user", id="1") + mock_redis_delete = mocker.patch("app.extensions.RedisClient.delete") + mock_request = mocker.patch( + "notifications_python_client.base.BaseAPIClient.request" + ) - organizations_client.update_organization(fake_uuid, foo='bar') + organizations_client.update_organization(fake_uuid, foo="bar") - assert call('domains') in mock_redis_delete.call_args_list + assert call("domains") in mock_redis_delete.call_args_list assert len(mock_request.call_args_list) == 1 -@pytest.mark.parametrize('post_data, expected_cache_delete_calls', ( - ({'foo': 'bar'}, [ - call('organizations'), - call('domains'), - ]), - ({'name': 'new name'}, [ - call('organization-6ce466d0-fd6a-11e5-82f5-e0accb9d11a6-name'), - call('organizations'), - call('domains'), - ]), -)) +@pytest.mark.parametrize( + "post_data, expected_cache_delete_calls", + ( + ( + {"foo": "bar"}, + [ + call("organizations"), + call("domains"), + ], + ), + ( + {"name": "new name"}, + [ + call("organization-6ce466d0-fd6a-11e5-82f5-e0accb9d11a6-name"), + call("organizations"), + call("domains"), + ], + ), + ), +) def test_update_organization_when_not_updating_org_type( mocker, fake_uuid, post_data, expected_cache_delete_calls, ): - - mock_redis_delete = mocker.patch('app.extensions.RedisClient.delete') - mock_post = mocker.patch('app.notify_client.organizations_api_client.OrganizationsClient.post') + mock_redis_delete = mocker.patch("app.extensions.RedisClient.delete") + mock_post = mocker.patch( + "app.notify_client.organizations_api_client.OrganizationsClient.post" + ) organizations_client.update_organization(fake_uuid, **post_data) mock_post.assert_called_with( - url='/organizations/{}'.format(fake_uuid), - data=post_data + url="/organizations/{}".format(fake_uuid), data=post_data ) assert mock_redis_delete.call_args_list == expected_cache_delete_calls -def test_update_organization_when_updating_org_type_and_org_has_services(mocker, fake_uuid): - mock_redis_delete = mocker.patch('app.extensions.RedisClient.delete') - mock_post = mocker.patch('app.notify_client.organizations_api_client.OrganizationsClient.post') +def test_update_organization_when_updating_org_type_and_org_has_services( + mocker, fake_uuid +): + mock_redis_delete = mocker.patch("app.extensions.RedisClient.delete") + mock_post = mocker.patch( + "app.notify_client.organizations_api_client.OrganizationsClient.post" + ) organizations_client.update_organization( fake_uuid, - cached_service_ids=['a', 'b', 'c'], - organization_type='central', + cached_service_ids=["a", "b", "c"], + organization_type="central", ) mock_post.assert_called_with( - url='/organizations/{}'.format(fake_uuid), - data={'organization_type': 'central'} + url="/organizations/{}".format(fake_uuid), data={"organization_type": "central"} ) assert mock_redis_delete.call_args_list == [ - call('service-a', 'service-b', 'service-c'), - call('organizations'), - call('domains'), + call("service-a", "service-b", "service-c"), + call("organizations"), + call("domains"), ] -def test_update_organization_when_updating_org_type_but_org_has_no_services(mocker, fake_uuid): - mock_redis_delete = mocker.patch('app.extensions.RedisClient.delete') - mock_post = mocker.patch('app.notify_client.organizations_api_client.OrganizationsClient.post') +def test_update_organization_when_updating_org_type_but_org_has_no_services( + mocker, fake_uuid +): + mock_redis_delete = mocker.patch("app.extensions.RedisClient.delete") + mock_post = mocker.patch( + "app.notify_client.organizations_api_client.OrganizationsClient.post" + ) organizations_client.update_organization( fake_uuid, cached_service_ids=[], - organization_type='central', + organization_type="central", ) mock_post.assert_called_with( - url='/organizations/{}'.format(fake_uuid), - data={'organization_type': 'central'} + url="/organizations/{}".format(fake_uuid), data={"organization_type": "central"} ) assert mock_redis_delete.call_args_list == [ - call('organizations'), - call('domains'), + call("organizations"), + call("domains"), ] def test_update_service_organization_deletes_cache(mocker, fake_uuid): - mock_redis_delete = mocker.patch('app.extensions.RedisClient.delete') - mock_post = mocker.patch('app.notify_client.organizations_api_client.OrganizationsClient.post') + mock_redis_delete = mocker.patch("app.extensions.RedisClient.delete") + mock_post = mocker.patch( + "app.notify_client.organizations_api_client.OrganizationsClient.post" + ) organizations_client.update_service_organization( - service_id=fake_uuid, - org_id=fake_uuid + service_id=fake_uuid, org_id=fake_uuid ) assert sorted(mock_redis_delete.call_args_list) == [ - call('live-service-and-organization-counts'), - call('organizations'), - call('service-{}'.format(fake_uuid)), + call("live-service-and-organization-counts"), + call("organizations"), + call("service-{}".format(fake_uuid)), ] mock_post.assert_called_with( - url='/organizations/{}/service'.format(fake_uuid), - data=ANY + url="/organizations/{}/service".format(fake_uuid), data=ANY ) def test_remove_user_from_organization_deletes_user_cache(mocker): - mock_redis_delete = mocker.patch('app.extensions.RedisClient.delete') - mock_delete = mocker.patch('app.notify_client.organizations_api_client.OrganizationsClient.delete') + mock_redis_delete = mocker.patch("app.extensions.RedisClient.delete") + mock_delete = mocker.patch( + "app.notify_client.organizations_api_client.OrganizationsClient.delete" + ) - org_id = 'abcd-1234' - user_id = 'efgh-5678' + org_id = "abcd-1234" + user_id = "efgh-5678" organizations_client.remove_user_from_organization( org_id=org_id, user_id=user_id, ) - assert mock_redis_delete.call_args_list == [call(f'user-{user_id}')] - mock_delete.assert_called_with(f'/organizations/{org_id}/users/{user_id}') + assert mock_redis_delete.call_args_list == [call(f"user-{user_id}")] + mock_delete.assert_called_with(f"/organizations/{org_id}/users/{user_id}") diff --git a/tests/app/notify_client/test_performance_platform_api_client.py b/tests/app/notify_client/test_performance_platform_api_client.py index 3c9b55e73..6b3b05c0e 100644 --- a/tests/app/notify_client/test_performance_platform_api_client.py +++ b/tests/app/notify_client/test_performance_platform_api_client.py @@ -6,47 +6,48 @@ from app.notify_client.performance_dashboard_api_client import ( def test_get_aggregate_platform_stats(mocker): - mocker.patch('app.extensions.RedisClient.get', return_value=None) + mocker.patch("app.extensions.RedisClient.get", return_value=None) client = PerformanceDashboardAPIClient() - mock = mocker.patch.object(client, 'get', return_value={}) + mock = mocker.patch.object(client, "get", return_value={}) client.get_performance_dashboard_stats( start_date=date(2021, 3, 1), end_date=date(2021, 3, 31), ) - mock.assert_called_once_with('/performance-dashboard', params={ - 'start_date': '2021-03-01', - 'end_date': '2021-03-31' - }) + mock.assert_called_once_with( + "/performance-dashboard", + params={"start_date": "2021-03-01", "end_date": "2021-03-31"}, + ) def test_sets_value_in_cache(mocker): client = PerformanceDashboardAPIClient() mock_redis_get = mocker.patch( - 'app.extensions.RedisClient.get', + "app.extensions.RedisClient.get", return_value=None, ) mock_api_get = mocker.patch( - 'app.notify_client.NotifyAdminAPIClient.get', - return_value={'data_from': 'api'}, + "app.notify_client.NotifyAdminAPIClient.get", + return_value={"data_from": "api"}, ) mock_redis_set = mocker.patch( - 'app.extensions.RedisClient.set', + "app.extensions.RedisClient.set", ) assert client.get_performance_dashboard_stats( start_date=date(2021, 1, 1), end_date=date(2022, 2, 2), - ) == {'data_from': 'api'} + ) == {"data_from": "api"} - mock_redis_get.assert_called_once_with('performance-stats-2021-01-01-to-2022-02-02') - mock_api_get.assert_called_once_with('/performance-dashboard', params={ - 'start_date': '2021-01-01', 'end_date': '2022-02-02' - }) + mock_redis_get.assert_called_once_with("performance-stats-2021-01-01-to-2022-02-02") + mock_api_get.assert_called_once_with( + "/performance-dashboard", + params={"start_date": "2021-01-01", "end_date": "2022-02-02"}, + ) mock_redis_set.assert_called_once_with( - 'performance-stats-2021-01-01-to-2022-02-02', + "performance-stats-2021-01-01-to-2022-02-02", '{"data_from": "api"}', ex=3600, ) @@ -56,21 +57,21 @@ def test_returns_value_from_cache(mocker): client = PerformanceDashboardAPIClient() mock_redis_get = mocker.patch( - 'app.extensions.RedisClient.get', + "app.extensions.RedisClient.get", return_value=b'{"data_from": "cache"}', ) mock_api_get = mocker.patch( - 'app.notify_client.NotifyAdminAPIClient.get', + "app.notify_client.NotifyAdminAPIClient.get", ) mock_redis_set = mocker.patch( - 'app.extensions.RedisClient.set', + "app.extensions.RedisClient.set", ) assert client.get_performance_dashboard_stats( start_date=date(2021, 1, 1), end_date=date(2022, 2, 2), - ) == {'data_from': 'cache'} + ) == {"data_from": "cache"} - mock_redis_get.assert_called_once_with('performance-stats-2021-01-01-to-2022-02-02') + mock_redis_get.assert_called_once_with("performance-stats-2021-01-01-to-2022-02-02") assert mock_api_get.called is False assert mock_redis_set.called is False diff --git a/tests/app/notify_client/test_platform_stats_api_client.py b/tests/app/notify_client/test_platform_stats_api_client.py index e0f510c86..df8cb81e0 100644 --- a/tests/app/notify_client/test_platform_stats_api_client.py +++ b/tests/app/notify_client/test_platform_stats_api_client.py @@ -3,8 +3,8 @@ from app.notify_client.platform_stats_api_client import PlatformStatsAPIClient def test_get_aggregate_platform_stats(mocker): client = PlatformStatsAPIClient() - mock = mocker.patch.object(client, 'get') - params_dict = {'start_date': '2018-06-01', 'end_date': '2018-06-15'} + mock = mocker.patch.object(client, "get") + params_dict = {"start_date": "2018-06-01", "end_date": "2018-06-15"} client.get_aggregate_platform_stats(params_dict=params_dict) - mock.assert_called_once_with('/platform-stats', params=params_dict) + mock.assert_called_once_with("/platform-stats", params=params_dict) diff --git a/tests/app/notify_client/test_service_api_client.py b/tests/app/notify_client/test_service_api_client.py index 1f285e632..75d813d31 100644 --- a/tests/app/notify_client/test_service_api_client.py +++ b/tests/app/notify_client/test_service_api_client.py @@ -11,48 +11,56 @@ FAKE_TEMPLATE_ID = uuid4() def test_client_posts_archived_true_when_deleting_template(mocker): - mocker.patch('app.notify_client.current_user', id='1') - mock_redis_delete_by_pattern = mocker.patch('app.extensions.RedisClient.delete_by_pattern') - expected_data = { - 'archived': True, - 'created_by': '1' - } - expected_url = '/service/{}/template/{}'.format(SERVICE_ONE_ID, FAKE_TEMPLATE_ID) + mocker.patch("app.notify_client.current_user", id="1") + mock_redis_delete_by_pattern = mocker.patch( + "app.extensions.RedisClient.delete_by_pattern" + ) + expected_data = {"archived": True, "created_by": "1"} + expected_url = "/service/{}/template/{}".format(SERVICE_ONE_ID, FAKE_TEMPLATE_ID) client = ServiceAPIClient() - mock_post = mocker.patch('app.notify_client.service_api_client.ServiceAPIClient.post') - mocker.patch('app.notify_client.service_api_client.ServiceAPIClient.get', - return_value={'data': {'id': str(FAKE_TEMPLATE_ID)}}) + mock_post = mocker.patch( + "app.notify_client.service_api_client.ServiceAPIClient.post" + ) + mocker.patch( + "app.notify_client.service_api_client.ServiceAPIClient.get", + return_value={"data": {"id": str(FAKE_TEMPLATE_ID)}}, + ) client.delete_service_template(SERVICE_ONE_ID, FAKE_TEMPLATE_ID) mock_post.assert_called_once_with(expected_url, data=expected_data) - assert call(f'service-{SERVICE_ONE_ID}-template-*') in mock_redis_delete_by_pattern.call_args_list + assert ( + call(f"service-{SERVICE_ONE_ID}-template-*") + in mock_redis_delete_by_pattern.call_args_list + ) def test_client_gets_service(mocker): client = ServiceAPIClient() - mock_get = mocker.patch.object(client, 'get', return_value={}) + mock_get = mocker.patch.object(client, "get", return_value={}) - client.get_service('foo') - mock_get.assert_called_once_with('/service/foo') + client.get_service("foo") + mock_get.assert_called_once_with("/service/foo") -@pytest.mark.parametrize('limit_days', [None, 30]) +@pytest.mark.parametrize("limit_days", [None, 30]) def test_client_gets_service_statistics(mocker, limit_days): client = ServiceAPIClient() - mock_get = mocker.patch.object(client, 'get', return_value={'data': {'a': 'b'}}) + mock_get = mocker.patch.object(client, "get", return_value={"data": {"a": "b"}}) - ret = client.get_service_statistics('foo', limit_days) + ret = client.get_service_statistics("foo", limit_days) - assert ret == {'a': 'b'} - mock_get.assert_called_once_with('/service/foo/statistics', params={'limit_days': limit_days}) + assert ret == {"a": "b"} + mock_get.assert_called_once_with( + "/service/foo/statistics", params={"limit_days": limit_days} + ) def test_client_only_updates_allowed_attributes(mocker): - mocker.patch('app.notify_client.current_user', id='1') + mocker.patch("app.notify_client.current_user", id="1") with pytest.raises(TypeError) as error: - ServiceAPIClient().update_service('service_id', foo='bar') - assert str(error.value) == 'Not allowed to update service attributes: foo' + ServiceAPIClient().update_service("service_id", foo="bar") + assert str(error.value) == "Not allowed to update service attributes: foo" def test_client_creates_service_with_correct_data( @@ -61,71 +69,76 @@ def test_client_creates_service_with_correct_data( fake_uuid, ): client = ServiceAPIClient() - mock_post = mocker.patch.object(client, 'post', return_value={'data': {'id': None}}) - mocker.patch('app.notify_client.current_user', id='123') + mock_post = mocker.patch.object(client, "post", return_value={"data": {"id": None}}) + mocker.patch("app.notify_client.current_user", id="123") client.create_service( - 'My first service', - 'central_government', + "My first service", + "central_government", 1, True, fake_uuid, - 'test@example.com', + "test@example.com", ) mock_post.assert_called_once_with( - '/service', + "/service", dict( # Autogenerated arguments - created_by='123', + created_by="123", active=True, # ‘service_name’ argument is coerced to ‘name’ - name='My first service', + name="My first service", # The rest pass through with the same names - organization_type='central_government', + organization_type="central_government", message_limit=1, restricted=True, user_id=fake_uuid, - email_from='test@example.com', + email_from="test@example.com", ), ) def test_get_precompiled_template(mocker): client = ServiceAPIClient() - mock_get = mocker.patch.object(client, 'get') + mock_get = mocker.patch.object(client, "get") client.get_precompiled_template(SERVICE_ONE_ID) - mock_get.assert_called_once_with('/service/{}/template/precompiled'.format(SERVICE_ONE_ID)) + mock_get.assert_called_once_with( + "/service/{}/template/precompiled".format(SERVICE_ONE_ID) + ) -@pytest.mark.parametrize('template_data, extra_args, expected_count', ( +@pytest.mark.parametrize( + "template_data, extra_args, expected_count", ( - [], - {}, - 0, + ( + [], + {}, + 0, + ), + ( + [], + {"template_type": "email"}, + 0, + ), + ( + [ + {"template_type": "email"}, + {"template_type": "sms"}, + ], + {}, + 2, + ), + ( + [ + {"template_type": "email"}, + {"template_type": "sms"}, + ], + {"template_type": "email"}, + 1, + ), ), - ( - [], - {'template_type': 'email'}, - 0, - ), - ( - [ - {'template_type': 'email'}, - {'template_type': 'sms'}, - ], - {}, - 2, - ), - ( - [ - {'template_type': 'email'}, - {'template_type': 'sms'}, - ], - {'template_type': 'email'}, - 1, - ), -)) +) def test_client_returns_count_of_service_templates( notify_admin, mocker, @@ -133,179 +146,203 @@ def test_client_returns_count_of_service_templates( extra_args, expected_count, ): - mocker.patch( - 'app.service_api_client.get_service_templates', - return_value={'data': template_data} + "app.service_api_client.get_service_templates", + return_value={"data": template_data}, ) - assert service_api_client.count_service_templates( - SERVICE_ONE_ID, **extra_args - ) == expected_count + assert ( + service_api_client.count_service_templates(SERVICE_ONE_ID, **extra_args) + == expected_count + ) @pytest.mark.parametrize( ( - 'client_method,' - 'extra_args,' - 'expected_cache_get_calls,' - 'cache_value,' - 'expected_api_calls,' - 'expected_cache_set_calls,' - 'expected_return_value,' + "client_method," + "extra_args," + "expected_cache_get_calls," + "cache_value," + "expected_api_calls," + "expected_cache_set_calls," + "expected_return_value," ), [ ( service_api_client.get_service, [SERVICE_ONE_ID], - [ - call('service-{}'.format(SERVICE_ONE_ID)) - ], + [call("service-{}".format(SERVICE_ONE_ID))], b'{"data_from": "cache"}', [], [], - {'data_from': 'cache'}, + {"data_from": "cache"}, ), ( service_api_client.get_service, [SERVICE_ONE_ID], - [ - call('service-{}'.format(SERVICE_ONE_ID)) - ], + [call("service-{}".format(SERVICE_ONE_ID))], None, - [ - call('/service/{}'.format(SERVICE_ONE_ID)) - ], + [call("/service/{}".format(SERVICE_ONE_ID))], [ call( - 'service-{}'.format(SERVICE_ONE_ID), + "service-{}".format(SERVICE_ONE_ID), '{"data_from": "api"}', ex=604800, ) ], - {'data_from': 'api'}, + {"data_from": "api"}, ), ( service_api_client.get_service_template, [SERVICE_ONE_ID, FAKE_TEMPLATE_ID], [ - call('service-{}-template-{}-version-None'.format(SERVICE_ONE_ID, FAKE_TEMPLATE_ID)) + call( + "service-{}-template-{}-version-None".format( + SERVICE_ONE_ID, FAKE_TEMPLATE_ID + ) + ) ], b'{"data_from": "cache"}', [], [], - {'data_from': 'cache'}, + {"data_from": "cache"}, ), ( service_api_client.get_service_template, [SERVICE_ONE_ID, FAKE_TEMPLATE_ID], [ - call('service-{}-template-{}-version-None'.format(SERVICE_ONE_ID, FAKE_TEMPLATE_ID)), + call( + "service-{}-template-{}-version-None".format( + SERVICE_ONE_ID, FAKE_TEMPLATE_ID + ) + ), ], None, - [ - call('/service/{}/template/{}'.format(SERVICE_ONE_ID, FAKE_TEMPLATE_ID)) - ], + [call("/service/{}/template/{}".format(SERVICE_ONE_ID, FAKE_TEMPLATE_ID))], [ call( - 'service-{}-template-{}-version-None'.format(SERVICE_ONE_ID, FAKE_TEMPLATE_ID), + "service-{}-template-{}-version-None".format( + SERVICE_ONE_ID, FAKE_TEMPLATE_ID + ), '{"data_from": "api"}', ex=604800, ), ], - {'data_from': 'api'}, + {"data_from": "api"}, ), ( service_api_client.get_service_template, [SERVICE_ONE_ID, FAKE_TEMPLATE_ID, 1], [ - call('service-{}-template-{}-version-1'.format(SERVICE_ONE_ID, FAKE_TEMPLATE_ID)) + call( + "service-{}-template-{}-version-1".format( + SERVICE_ONE_ID, FAKE_TEMPLATE_ID + ) + ) ], b'{"data_from": "cache"}', [], [], - {'data_from': 'cache'}, + {"data_from": "cache"}, ), ( service_api_client.get_service_template, [SERVICE_ONE_ID, FAKE_TEMPLATE_ID, 1], [ - call('service-{}-template-{}-version-1'.format(SERVICE_ONE_ID, FAKE_TEMPLATE_ID)), + call( + "service-{}-template-{}-version-1".format( + SERVICE_ONE_ID, FAKE_TEMPLATE_ID + ) + ), ], None, [ - call('/service/{}/template/{}/version/1'.format(SERVICE_ONE_ID, FAKE_TEMPLATE_ID)) + call( + "/service/{}/template/{}/version/1".format( + SERVICE_ONE_ID, FAKE_TEMPLATE_ID + ) + ) ], [ call( - 'service-{}-template-{}-version-1'.format(SERVICE_ONE_ID, FAKE_TEMPLATE_ID), + "service-{}-template-{}-version-1".format( + SERVICE_ONE_ID, FAKE_TEMPLATE_ID + ), '{"data_from": "api"}', ex=604800, ), ], - {'data_from': 'api'}, + {"data_from": "api"}, ), ( service_api_client.get_service_templates, [SERVICE_ONE_ID], - [ - call('service-{}-templates'.format(SERVICE_ONE_ID)) - ], + [call("service-{}-templates".format(SERVICE_ONE_ID))], b'{"data_from": "cache"}', [], [], - {'data_from': 'cache'}, + {"data_from": "cache"}, ), ( service_api_client.get_service_templates, [SERVICE_ONE_ID], - [ - call('service-{}-templates'.format(SERVICE_ONE_ID)) - ], + [call("service-{}-templates".format(SERVICE_ONE_ID))], None, - [ - call('/service/{}/template?detailed=False'.format(SERVICE_ONE_ID)) - ], + [call("/service/{}/template?detailed=False".format(SERVICE_ONE_ID))], [ call( - 'service-{}-templates'.format(SERVICE_ONE_ID), + "service-{}-templates".format(SERVICE_ONE_ID), '{"data_from": "api"}', ex=604800, ) ], - {'data_from': 'api'}, + {"data_from": "api"}, ), ( service_api_client.get_service_template_versions, [SERVICE_ONE_ID, FAKE_TEMPLATE_ID], [ - call('service-{}-template-{}-versions'.format(SERVICE_ONE_ID, FAKE_TEMPLATE_ID)) + call( + "service-{}-template-{}-versions".format( + SERVICE_ONE_ID, FAKE_TEMPLATE_ID + ) + ) ], b'{"data_from": "cache"}', [], [], - {'data_from': 'cache'}, + {"data_from": "cache"}, ), ( service_api_client.get_service_template_versions, [SERVICE_ONE_ID, FAKE_TEMPLATE_ID], [ - call('service-{}-template-{}-versions'.format(SERVICE_ONE_ID, FAKE_TEMPLATE_ID)), + call( + "service-{}-template-{}-versions".format( + SERVICE_ONE_ID, FAKE_TEMPLATE_ID + ) + ), ], None, [ - call('/service/{}/template/{}/versions'.format(SERVICE_ONE_ID, FAKE_TEMPLATE_ID)) + call( + "/service/{}/template/{}/versions".format( + SERVICE_ONE_ID, FAKE_TEMPLATE_ID + ) + ) ], [ call( - 'service-{}-template-{}-versions'.format(SERVICE_ONE_ID, FAKE_TEMPLATE_ID), + "service-{}-template-{}-versions".format( + SERVICE_ONE_ID, FAKE_TEMPLATE_ID + ), '{"data_from": "api"}', ex=604800, ), ], - {'data_from': 'api'}, + {"data_from": "api"}, ), - ] + ], ) def test_returns_value_from_cache( mocker, @@ -317,17 +354,16 @@ def test_returns_value_from_cache( expected_api_calls, expected_cache_set_calls, ): - mock_redis_get = mocker.patch( - 'app.extensions.RedisClient.get', + "app.extensions.RedisClient.get", return_value=cache_value, ) mock_api_get = mocker.patch( - 'app.notify_client.NotifyAdminAPIClient.get', - return_value={'data_from': 'api'}, + "app.notify_client.NotifyAdminAPIClient.get", + return_value={"data_from": "api"}, ) mock_redis_set = mocker.patch( - 'app.extensions.RedisClient.set', + "app.extensions.RedisClient.set", ) assert client_method(*extra_args) == expected_return_value @@ -337,27 +373,60 @@ def test_returns_value_from_cache( assert mock_redis_set.call_args_list == expected_cache_set_calls -@pytest.mark.parametrize('client, method, extra_args, extra_kwargs', [ - (service_api_client, 'update_service', [SERVICE_ONE_ID], {'name': 'foo'}), - (service_api_client, 'update_service_with_properties', [SERVICE_ONE_ID], {'properties': {}}), - (service_api_client, 'archive_service', [SERVICE_ONE_ID, []], {}), - (service_api_client, 'suspend_service', [SERVICE_ONE_ID], {}), - (service_api_client, 'resume_service', [SERVICE_ONE_ID], {}), - (service_api_client, 'remove_user_from_service', [SERVICE_ONE_ID, ''], {}), - (service_api_client, 'update_guest_list', [SERVICE_ONE_ID, {}], {}), - (service_api_client, 'create_service_inbound_api', [SERVICE_ONE_ID] + [''] * 3, {}), - (service_api_client, 'update_service_inbound_api', [SERVICE_ONE_ID] + [''] * 4, {}), - (service_api_client, 'add_reply_to_email_address', [SERVICE_ONE_ID, ''], {}), - (service_api_client, 'update_reply_to_email_address', [SERVICE_ONE_ID] + [''] * 2, {}), - (service_api_client, 'delete_reply_to_email_address', [SERVICE_ONE_ID, ''], {}), - (service_api_client, 'add_sms_sender', [SERVICE_ONE_ID, ''], {}), - (service_api_client, 'update_sms_sender', [SERVICE_ONE_ID] + [''] * 2, {}), - (service_api_client, 'delete_sms_sender', [SERVICE_ONE_ID, ''], {}), - (service_api_client, 'update_service_callback_api', [SERVICE_ONE_ID] + [''] * 4, {}), - (service_api_client, 'create_service_callback_api', [SERVICE_ONE_ID] + [''] * 3, {}), - (user_api_client, 'add_user_to_service', [SERVICE_ONE_ID, uuid4(), [], []], {}), - (invite_api_client, 'accept_invite', [SERVICE_ONE_ID, uuid4()], {}), -]) +@pytest.mark.parametrize( + "client, method, extra_args, extra_kwargs", + [ + (service_api_client, "update_service", [SERVICE_ONE_ID], {"name": "foo"}), + ( + service_api_client, + "update_service_with_properties", + [SERVICE_ONE_ID], + {"properties": {}}, + ), + (service_api_client, "archive_service", [SERVICE_ONE_ID, []], {}), + (service_api_client, "suspend_service", [SERVICE_ONE_ID], {}), + (service_api_client, "resume_service", [SERVICE_ONE_ID], {}), + (service_api_client, "remove_user_from_service", [SERVICE_ONE_ID, ""], {}), + (service_api_client, "update_guest_list", [SERVICE_ONE_ID, {}], {}), + ( + service_api_client, + "create_service_inbound_api", + [SERVICE_ONE_ID] + [""] * 3, + {}, + ), + ( + service_api_client, + "update_service_inbound_api", + [SERVICE_ONE_ID] + [""] * 4, + {}, + ), + (service_api_client, "add_reply_to_email_address", [SERVICE_ONE_ID, ""], {}), + ( + service_api_client, + "update_reply_to_email_address", + [SERVICE_ONE_ID] + [""] * 2, + {}, + ), + (service_api_client, "delete_reply_to_email_address", [SERVICE_ONE_ID, ""], {}), + (service_api_client, "add_sms_sender", [SERVICE_ONE_ID, ""], {}), + (service_api_client, "update_sms_sender", [SERVICE_ONE_ID] + [""] * 2, {}), + (service_api_client, "delete_sms_sender", [SERVICE_ONE_ID, ""], {}), + ( + service_api_client, + "update_service_callback_api", + [SERVICE_ONE_ID] + [""] * 4, + {}, + ), + ( + service_api_client, + "create_service_callback_api", + [SERVICE_ONE_ID] + [""] * 3, + {}, + ), + (user_api_client, "add_user_to_service", [SERVICE_ONE_ID, uuid4(), [], []], {}), + (invite_api_client, "accept_invite", [SERVICE_ONE_ID, uuid4()], {}), + ], +) def test_deletes_service_cache( notify_admin, mock_get_user, @@ -368,37 +437,66 @@ def test_deletes_service_cache( extra_args, extra_kwargs, ): - mocker.patch('app.notify_client.current_user', id='1') - mock_redis_delete = mocker.patch('app.extensions.RedisClient.delete') - mock_request = mocker.patch('notifications_python_client.base.BaseAPIClient.request') + mocker.patch("app.notify_client.current_user", id="1") + mock_redis_delete = mocker.patch("app.extensions.RedisClient.delete") + mock_request = mocker.patch( + "notifications_python_client.base.BaseAPIClient.request" + ) getattr(client, method)(*extra_args, **extra_kwargs) - assert call('service-{}'.format(SERVICE_ONE_ID)) in mock_redis_delete.call_args_list + assert call("service-{}".format(SERVICE_ONE_ID)) in mock_redis_delete.call_args_list assert len(mock_request.call_args_list) == 1 -@pytest.mark.parametrize('method, extra_args, expected_cache_deletes', [ - ('create_service_template', ['name', 'type_', 'content', SERVICE_ONE_ID], [ - 'service-{}-templates'.format(SERVICE_ONE_ID), - ]), - ('update_service_template', [FAKE_TEMPLATE_ID, 'foo', 'sms', 'bar', SERVICE_ONE_ID], [ - 'service-{}-templates'.format(SERVICE_ONE_ID), - ]), - ('redact_service_template', [SERVICE_ONE_ID, FAKE_TEMPLATE_ID], [ - 'service-{}-templates'.format(SERVICE_ONE_ID), - ]), - ('update_service_template_sender', [SERVICE_ONE_ID, FAKE_TEMPLATE_ID, 'foo'], [ - 'service-{}-templates'.format(SERVICE_ONE_ID), - ]), - ('delete_service_template', [SERVICE_ONE_ID, FAKE_TEMPLATE_ID], [ - 'service-{}-templates'.format(SERVICE_ONE_ID), - ]), - ('archive_service', [SERVICE_ONE_ID, []], [ - 'service-{}-templates'.format(SERVICE_ONE_ID), - 'service-{}'.format(SERVICE_ONE_ID), - ]), -]) +@pytest.mark.parametrize( + "method, extra_args, expected_cache_deletes", + [ + ( + "create_service_template", + ["name", "type_", "content", SERVICE_ONE_ID], + [ + "service-{}-templates".format(SERVICE_ONE_ID), + ], + ), + ( + "update_service_template", + [FAKE_TEMPLATE_ID, "foo", "sms", "bar", SERVICE_ONE_ID], + [ + "service-{}-templates".format(SERVICE_ONE_ID), + ], + ), + ( + "redact_service_template", + [SERVICE_ONE_ID, FAKE_TEMPLATE_ID], + [ + "service-{}-templates".format(SERVICE_ONE_ID), + ], + ), + ( + "update_service_template_sender", + [SERVICE_ONE_ID, FAKE_TEMPLATE_ID, "foo"], + [ + "service-{}-templates".format(SERVICE_ONE_ID), + ], + ), + ( + "delete_service_template", + [SERVICE_ONE_ID, FAKE_TEMPLATE_ID], + [ + "service-{}-templates".format(SERVICE_ONE_ID), + ], + ), + ( + "archive_service", + [SERVICE_ONE_ID, []], + [ + "service-{}-templates".format(SERVICE_ONE_ID), + "service-{}".format(SERVICE_ONE_ID), + ], + ), + ], +) def test_deletes_caches_when_modifying_templates( notify_admin, mock_get_user, @@ -407,54 +505,72 @@ def test_deletes_caches_when_modifying_templates( extra_args, expected_cache_deletes, ): - mocker.patch('app.notify_client.current_user', id='1') - mock_redis_delete = mocker.patch('app.extensions.RedisClient.delete') - mock_redis_delete_by_pattern = mocker.patch('app.extensions.RedisClient.delete_by_pattern') - mock_request = mocker.patch('notifications_python_client.base.BaseAPIClient.request') + mocker.patch("app.notify_client.current_user", id="1") + mock_redis_delete = mocker.patch("app.extensions.RedisClient.delete") + mock_redis_delete_by_pattern = mocker.patch( + "app.extensions.RedisClient.delete_by_pattern" + ) + mock_request = mocker.patch( + "notifications_python_client.base.BaseAPIClient.request" + ) getattr(service_api_client, method)(*extra_args) assert mock_redis_delete.call_args_list == [call(x) for x in expected_cache_deletes] assert len(mock_request.call_args_list) == 1 - if method != 'create_service_template': + if method != "create_service_template": # no deletes for template cach on create_service_template assert len(mock_redis_delete_by_pattern.call_args_list) == 1 - assert mock_redis_delete_by_pattern.call_args_list[0] == call(f'service-{SERVICE_ONE_ID}-template-*') + assert mock_redis_delete_by_pattern.call_args_list[0] == call( + f"service-{SERVICE_ONE_ID}-template-*" + ) -def test_deletes_cached_users_when_archiving_service(mocker, mock_get_service_templates): - mock_redis_delete = mocker.patch('app.extensions.RedisClient.delete') - mock_redis_delete_by_pattern = mocker.patch('app.extensions.RedisClient.delete_by_pattern') +def test_deletes_cached_users_when_archiving_service( + mocker, mock_get_service_templates +): + mock_redis_delete = mocker.patch("app.extensions.RedisClient.delete") + mock_redis_delete_by_pattern = mocker.patch( + "app.extensions.RedisClient.delete_by_pattern" + ) - mocker.patch('notifications_python_client.base.BaseAPIClient.request', return_value={'data': ""}) + mocker.patch( + "notifications_python_client.base.BaseAPIClient.request", + return_value={"data": ""}, + ) service_api_client.archive_service(SERVICE_ONE_ID, ["my-user-id1", "my-user-id2"]) - assert call('user-my-user-id1', 'user-my-user-id2') in mock_redis_delete.call_args_list - assert call(f'service-{SERVICE_ONE_ID}-template-*') in mock_redis_delete_by_pattern.call_args_list + assert ( + call("user-my-user-id1", "user-my-user-id2") in mock_redis_delete.call_args_list + ) + assert ( + call(f"service-{SERVICE_ONE_ID}-template-*") + in mock_redis_delete_by_pattern.call_args_list + ) def test_client_gets_guest_list(mocker): client = ServiceAPIClient() - mock_get = mocker.patch.object(client, 'get', return_value=['a', 'b', 'c']) + mock_get = mocker.patch.object(client, "get", return_value=["a", "b", "c"]) - response = client.get_guest_list('foo') + response = client.get_guest_list("foo") - assert response == ['a', 'b', 'c'] + assert response == ["a", "b", "c"] mock_get.assert_called_once_with( - url='/service/foo/guest-list', + url="/service/foo/guest-list", ) def test_client_updates_guest_list(mocker): client = ServiceAPIClient() - mock_put = mocker.patch.object(client, 'put') + mock_put = mocker.patch.object(client, "put") - client.update_guest_list('foo', data=['a', 'b', 'c']) + client.update_guest_list("foo", data=["a", "b", "c"]) mock_put.assert_called_once_with( - url='/service/foo/guest-list', - data=['a', 'b', 'c'], + url="/service/foo/guest-list", + data=["a", "b", "c"], ) @@ -462,79 +578,85 @@ def test_client_doesnt_delete_service_template_cache_when_none_exist( notify_admin, mock_get_user, mock_get_service_templates_when_no_templates_exist, - mocker + mocker, ): - mocker.patch('app.notify_client.current_user', id='1') - mocker.patch('notifications_python_client.base.BaseAPIClient.request') - mock_redis_delete = mocker.patch('app.extensions.RedisClient.delete') - mock_redis_delete_by_pattern = mocker.patch('app.extensions.RedisClient.delete_by_pattern') + mocker.patch("app.notify_client.current_user", id="1") + mocker.patch("notifications_python_client.base.BaseAPIClient.request") + mock_redis_delete = mocker.patch("app.extensions.RedisClient.delete") + mock_redis_delete_by_pattern = mocker.patch( + "app.extensions.RedisClient.delete_by_pattern" + ) - service_api_client.update_reply_to_email_address(SERVICE_ONE_ID, uuid4(), 'foo@bar.com') + service_api_client.update_reply_to_email_address( + SERVICE_ONE_ID, uuid4(), "foo@bar.com" + ) assert len(mock_redis_delete.call_args_list) == 1 - assert mock_redis_delete.call_args_list[0] == call('service-{}'.format(SERVICE_ONE_ID)) + assert mock_redis_delete.call_args_list[0] == call( + "service-{}".format(SERVICE_ONE_ID) + ) assert len(mock_redis_delete_by_pattern.call_args_list) == 1 def test_client_deletes_service_template_cache_when_service_is_updated( - notify_admin, - mock_get_user, - mocker + notify_admin, mock_get_user, mocker ): - mocker.patch('app.notify_client.current_user', id='1') - mocker.patch('notifications_python_client.base.BaseAPIClient.request') - mock_redis_delete = mocker.patch('app.extensions.RedisClient.delete') - mock_redis_delete_by_pattern = mocker.patch('app.extensions.RedisClient.delete_by_pattern') + mocker.patch("app.notify_client.current_user", id="1") + mocker.patch("notifications_python_client.base.BaseAPIClient.request") + mock_redis_delete = mocker.patch("app.extensions.RedisClient.delete") + mock_redis_delete_by_pattern = mocker.patch( + "app.extensions.RedisClient.delete_by_pattern" + ) - service_api_client.update_reply_to_email_address(SERVICE_ONE_ID, uuid4(), 'foo@bar.com') + service_api_client.update_reply_to_email_address( + SERVICE_ONE_ID, uuid4(), "foo@bar.com" + ) assert len(mock_redis_delete.call_args_list) == 1 - assert mock_redis_delete.call_args_list[0] == call(f'service-{SERVICE_ONE_ID}') - assert mock_redis_delete_by_pattern.call_args_list[0] == call(f'service-{SERVICE_ONE_ID}-template-*') + assert mock_redis_delete.call_args_list[0] == call(f"service-{SERVICE_ONE_ID}") + assert mock_redis_delete_by_pattern.call_args_list[0] == call( + f"service-{SERVICE_ONE_ID}-template-*" + ) def test_client_updates_service_with_allowed_attributes( mocker, ): client = ServiceAPIClient() - mock_post = mocker.patch.object(client, 'post', return_value={'data': {'id': None}}) - mocker.patch('app.notify_client.current_user', id='123') + mock_post = mocker.patch.object(client, "post", return_value={"data": {"id": None}}) + mocker.patch("app.notify_client.current_user", id="123") allowed_attributes = [ - 'active', - 'consent_to_research', - 'contact_link', - 'count_as_live', - 'email_branding', - 'email_from', - 'free_sms_fragment_limit', - 'go_live_at', - 'go_live_user', - 'message_limit', - 'name', - 'notes', - 'organization_type', - 'permissions', - 'prefix_sms', - 'rate_limit', - 'reply_to_email_address', - 'research_mode', - 'restricted', - 'sms_sender', - 'volume_email', - 'volume_sms', + "active", + "consent_to_research", + "contact_link", + "count_as_live", + "email_branding", + "email_from", + "free_sms_fragment_limit", + "go_live_at", + "go_live_user", + "message_limit", + "name", + "notes", + "organization_type", + "permissions", + "prefix_sms", + "rate_limit", + "reply_to_email_address", + "research_mode", + "restricted", + "sms_sender", + "volume_email", + "volume_sms", ] attrs_dict = {} for attr in allowed_attributes: attrs_dict[attr] = "value" - client.update_service( - SERVICE_ONE_ID, - **attrs_dict - ) + client.update_service(SERVICE_ONE_ID, **attrs_dict) mock_post.assert_called_once_with( - f'/service/{SERVICE_ONE_ID}', - {**{'created_by': '123'}, **attrs_dict} + f"/service/{SERVICE_ONE_ID}", {**{"created_by": "123"}, **attrs_dict} ) diff --git a/tests/app/notify_client/test_status_api_client.py b/tests/app/notify_client/test_status_api_client.py index 907fd6b29..f13e3fd0c 100644 --- a/tests/app/notify_client/test_status_api_client.py +++ b/tests/app/notify_client/test_status_api_client.py @@ -2,38 +2,35 @@ from app.notify_client.status_api_client import StatusApiClient def test_get_count_of_live_services_and_organizations(mocker): - mocker.patch('app.extensions.RedisClient.get', return_value=None) + mocker.patch("app.extensions.RedisClient.get", return_value=None) client = StatusApiClient() - mock = mocker.patch.object(client, 'get', return_value={}) + mock = mocker.patch.object(client, "get", return_value={}) client.get_count_of_live_services_and_organizations() - mock.assert_called_once_with(url='/_status/live-service-and-organization-counts') + mock.assert_called_once_with(url="/_status/live-service-and-organization-counts") def test_sets_value_in_cache(mocker): client = StatusApiClient() - mock_redis_get = mocker.patch( - 'app.extensions.RedisClient.get', - return_value=None - ) + mock_redis_get = mocker.patch("app.extensions.RedisClient.get", return_value=None) mock_api_get = mocker.patch( - 'app.notify_client.NotifyAdminAPIClient.get', - return_value={'data_from': 'api'}, + "app.notify_client.NotifyAdminAPIClient.get", + return_value={"data_from": "api"}, ) mock_redis_set = mocker.patch( - 'app.extensions.RedisClient.set', + "app.extensions.RedisClient.set", ) - assert client.get_count_of_live_services_and_organizations() == {'data_from': 'api'} + assert client.get_count_of_live_services_and_organizations() == {"data_from": "api"} - mock_redis_get.assert_called_once_with('live-service-and-organization-counts') - mock_api_get.assert_called_once_with(url='/_status/live-service-and-organization-counts') + mock_redis_get.assert_called_once_with("live-service-and-organization-counts") + mock_api_get.assert_called_once_with( + url="/_status/live-service-and-organization-counts" + ) mock_redis_set.assert_called_once_with( - 'live-service-and-organization-counts', - '{"data_from": "api"}', - ex=3600 + "live-service-and-organization-counts", '{"data_from": "api"}', ex=3600 ) @@ -41,19 +38,21 @@ def test_returns_value_from_cache(mocker): client = StatusApiClient() mock_redis_get = mocker.patch( - 'app.extensions.RedisClient.get', + "app.extensions.RedisClient.get", return_value=b'{"data_from": "cache"}', ) mock_api_get = mocker.patch( - 'app.notify_client.NotifyAdminAPIClient.get', + "app.notify_client.NotifyAdminAPIClient.get", ) mock_redis_set = mocker.patch( - 'app.extensions.RedisClient.set', + "app.extensions.RedisClient.set", ) - assert client.get_count_of_live_services_and_organizations() == {'data_from': 'cache'} + assert client.get_count_of_live_services_and_organizations() == { + "data_from": "cache" + } - mock_redis_get.assert_called_once_with('live-service-and-organization-counts') + mock_redis_get.assert_called_once_with("live-service-and-organization-counts") assert mock_api_get.called is False assert mock_redis_set.called is False diff --git a/tests/app/notify_client/test_template_folder_client.py b/tests/app/notify_client/test_template_folder_client.py index 275bb8a4e..6e5aa2c55 100644 --- a/tests/app/notify_client/test_template_folder_client.py +++ b/tests/app/notify_client/test_template_folder_client.py @@ -7,41 +7,45 @@ from orderedset import OrderedSet from app.notify_client.template_folder_api_client import TemplateFolderAPIClient -@pytest.mark.parametrize('parent_id', [uuid.uuid4(), None]) +@pytest.mark.parametrize("parent_id", [uuid.uuid4(), None]) def test_create_template_folder_calls_correct_api_endpoint(mocker, parent_id): - mock_redis_delete = mocker.patch('app.extensions.RedisClient.delete') + mock_redis_delete = mocker.patch("app.extensions.RedisClient.delete") some_service_id = uuid.uuid4() - expected_url = '/service/{}/template-folder'.format(some_service_id) - data = {'name': 'foo', 'parent_id': parent_id} + expected_url = "/service/{}/template-folder".format(some_service_id) + data = {"name": "foo", "parent_id": parent_id} client = TemplateFolderAPIClient() - mock_post = mocker.patch('app.notify_client.template_folder_api_client.TemplateFolderAPIClient.post') + mock_post = mocker.patch( + "app.notify_client.template_folder_api_client.TemplateFolderAPIClient.post" + ) - client.create_template_folder(some_service_id, name='foo', parent_id=parent_id) + client.create_template_folder(some_service_id, name="foo", parent_id=parent_id) mock_post.assert_called_once_with(expected_url, data) - mock_redis_delete.assert_called_once_with('service-{}-template-folders'.format(some_service_id)) + mock_redis_delete.assert_called_once_with( + "service-{}-template-folders".format(some_service_id) + ) def test_get_template_folders_calls_correct_api_endpoint(mocker): - mock_redis_get = mocker.patch('app.extensions.RedisClient.get', return_value=None) - mock_redis_set = mocker.patch('app.extensions.RedisClient.set') + mock_redis_get = mocker.patch("app.extensions.RedisClient.get", return_value=None) + mock_redis_set = mocker.patch("app.extensions.RedisClient.set") mock_api_get = mocker.patch( - 'app.notify_client.NotifyAdminAPIClient.get', - return_value={'template_folders': {'a': 'b'}} + "app.notify_client.NotifyAdminAPIClient.get", + return_value={"template_folders": {"a": "b"}}, ) some_service_id = uuid.uuid4() - expected_url = '/service/{}/template-folder'.format(some_service_id) - redis_key = 'service-{}-template-folders'.format(some_service_id) + expected_url = "/service/{}/template-folder".format(some_service_id) + redis_key = "service-{}-template-folders".format(some_service_id) client = TemplateFolderAPIClient() ret = client.get_template_folders(some_service_id) - assert ret == {'a': 'b'} + assert ret == {"a": "b"} mock_redis_get.assert_called_once_with(redis_key) mock_api_get.assert_called_once_with(expected_url) @@ -49,9 +53,8 @@ def test_get_template_folders_calls_correct_api_endpoint(mocker): def test_move_templates_and_folders(mocker): - - mock_redis_delete = mocker.patch('app.extensions.RedisClient.delete') - mock_api_post = mocker.patch('app.notify_client.NotifyAdminAPIClient.post') + mock_redis_delete = mocker.patch("app.extensions.RedisClient.delete") + mock_api_post = mocker.patch("app.notify_client.NotifyAdminAPIClient.post") some_service_id = uuid.uuid4() some_folder_id = uuid.uuid4() @@ -59,82 +62,98 @@ def test_move_templates_and_folders(mocker): TemplateFolderAPIClient().move_to_folder( some_service_id, some_folder_id, - template_ids=OrderedSet(('a', 'b', 'c')), - folder_ids=OrderedSet(('1', '2', '3')), + template_ids=OrderedSet(("a", "b", "c")), + folder_ids=OrderedSet(("1", "2", "3")), ) mock_api_post.assert_called_once_with( - '/service/{}/template-folder/{}/contents'.format( + "/service/{}/template-folder/{}/contents".format( some_service_id, some_folder_id ), { - 'folders': ['1', '2', '3'], - 'templates': ['a', 'b', 'c'], + "folders": ["1", "2", "3"], + "templates": ["a", "b", "c"], }, ) assert mock_redis_delete.call_args_list == [ call( - f'service-{some_service_id}-template-a-version-None', - f'service-{some_service_id}-template-b-version-None', - f'service-{some_service_id}-template-c-version-None', + f"service-{some_service_id}-template-a-version-None", + f"service-{some_service_id}-template-b-version-None", + f"service-{some_service_id}-template-c-version-None", ), - call('service-{}-templates'.format(some_service_id)), - call('service-{}-template-folders'.format(some_service_id)), + call("service-{}-templates".format(some_service_id)), + call("service-{}-template-folders".format(some_service_id)), ] def test_move_templates_and_folders_to_root(mocker): - - mock_api_post = mocker.patch('app.notify_client.NotifyAdminAPIClient.post') + mock_api_post = mocker.patch("app.notify_client.NotifyAdminAPIClient.post") some_service_id = uuid.uuid4() TemplateFolderAPIClient().move_to_folder( some_service_id, None, - template_ids=OrderedSet(('a', 'b', 'c')), - folder_ids=OrderedSet(('1', '2', '3')), + template_ids=OrderedSet(("a", "b", "c")), + folder_ids=OrderedSet(("1", "2", "3")), ) mock_api_post.assert_called_once_with( - '/service/{}/template-folder/contents'.format(some_service_id), + "/service/{}/template-folder/contents".format(some_service_id), { - 'folders': ['1', '2', '3'], - 'templates': ['a', 'b', 'c'], + "folders": ["1", "2", "3"], + "templates": ["a", "b", "c"], }, ) def test_update_template_folder_calls_correct_api_endpoint(mocker): - mock_redis_delete = mocker.patch('app.extensions.RedisClient.delete') + mock_redis_delete = mocker.patch("app.extensions.RedisClient.delete") some_service_id = uuid.uuid4() template_folder_id = uuid.uuid4() - expected_url = '/service/{}/template-folder/{}'.format(some_service_id, template_folder_id) - data = {'name': 'foo', 'users_with_permission': ['some_id']} + expected_url = "/service/{}/template-folder/{}".format( + some_service_id, template_folder_id + ) + data = {"name": "foo", "users_with_permission": ["some_id"]} client = TemplateFolderAPIClient() - mock_post = mocker.patch('app.notify_client.template_folder_api_client.TemplateFolderAPIClient.post') + mock_post = mocker.patch( + "app.notify_client.template_folder_api_client.TemplateFolderAPIClient.post" + ) - client.update_template_folder(some_service_id, template_folder_id, name='foo', users_with_permission=['some_id']) + client.update_template_folder( + some_service_id, + template_folder_id, + name="foo", + users_with_permission=["some_id"], + ) mock_post.assert_called_once_with(expected_url, data) - mock_redis_delete.assert_called_once_with('service-{}-template-folders'.format(some_service_id)) + mock_redis_delete.assert_called_once_with( + "service-{}-template-folders".format(some_service_id) + ) def test_delete_template_folder_calls_correct_api_endpoint(mocker): - mock_redis_delete = mocker.patch('app.extensions.RedisClient.delete') + mock_redis_delete = mocker.patch("app.extensions.RedisClient.delete") some_service_id = uuid.uuid4() template_folder_id = uuid.uuid4() - expected_url = '/service/{}/template-folder/{}'.format(some_service_id, template_folder_id) + expected_url = "/service/{}/template-folder/{}".format( + some_service_id, template_folder_id + ) client = TemplateFolderAPIClient() - mock_delete = mocker.patch('app.notify_client.template_folder_api_client.TemplateFolderAPIClient.delete') + mock_delete = mocker.patch( + "app.notify_client.template_folder_api_client.TemplateFolderAPIClient.delete" + ) client.delete_template_folder(some_service_id, template_folder_id) mock_delete.assert_called_once_with(expected_url, {}) - mock_redis_delete.assert_called_once_with('service-{}-template-folders'.format(some_service_id)) + mock_redis_delete.assert_called_once_with( + "service-{}-template-folders".format(some_service_id) + ) diff --git a/tests/app/notify_client/test_template_statistics_client.py b/tests/app/notify_client/test_template_statistics_client.py index 9f24c19a6..dcacce313 100644 --- a/tests/app/notify_client/test_template_statistics_client.py +++ b/tests/app/notify_client/test_template_statistics_client.py @@ -3,27 +3,36 @@ import uuid from app.notify_client.template_statistics_api_client import TemplateStatisticsApiClient -def test_template_statistics_client_calls_correct_api_endpoint_for_service(mocker, api_user_active): - +def test_template_statistics_client_calls_correct_api_endpoint_for_service( + mocker, api_user_active +): some_service_id = uuid.uuid4() - expected_url = '/service/{}/template-statistics'.format(some_service_id) + expected_url = "/service/{}/template-statistics".format(some_service_id) client = TemplateStatisticsApiClient() - mock_get = mocker.patch('app.notify_client.template_statistics_api_client.TemplateStatisticsApiClient.get') + mock_get = mocker.patch( + "app.notify_client.template_statistics_api_client.TemplateStatisticsApiClient.get" + ) client.get_template_statistics_for_service(some_service_id) mock_get.assert_called_once_with(url=expected_url, params={}) -def test_template_statistics_client_calls_correct_api_endpoint_for_template(mocker, api_user_active): +def test_template_statistics_client_calls_correct_api_endpoint_for_template( + mocker, api_user_active +): some_service_id = uuid.uuid4() some_template_id = uuid.uuid4() - expected_url = '/service/{}/template-statistics/last-used/{}'.format(some_service_id, some_template_id) + expected_url = "/service/{}/template-statistics/last-used/{}".format( + some_service_id, some_template_id + ) client = TemplateStatisticsApiClient() - mock_get = mocker.patch('app.notify_client.template_statistics_api_client.TemplateStatisticsApiClient.get') + mock_get = mocker.patch( + "app.notify_client.template_statistics_api_client.TemplateStatisticsApiClient.get" + ) client.get_last_used_date_for_template(some_service_id, some_template_id) diff --git a/tests/app/notify_client/test_user_client.py b/tests/app/notify_client/test_user_client.py index 519dd862d..c2f635626 100644 --- a/tests/app/notify_client/test_user_client.py +++ b/tests/app/notify_client/test_user_client.py @@ -14,59 +14,63 @@ def test_client_gets_all_users_for_service( mocker, fake_uuid, ): - user_api_client.max_failed_login_count = 99 # doesn't matter for this test mock_get = mocker.patch( - 'app.notify_client.user_api_client.UserApiClient.get', - return_value={'data': [ - {'id': fake_uuid}, - ]} + "app.notify_client.user_api_client.UserApiClient.get", + return_value={ + "data": [ + {"id": fake_uuid}, + ] + }, ) users = user_api_client.get_users_for_service(SERVICE_ONE_ID) - mock_get.assert_called_once_with('/service/{}/users'.format(SERVICE_ONE_ID)) + mock_get.assert_called_once_with("/service/{}/users".format(SERVICE_ONE_ID)) assert len(users) == 1 - assert users[0]['id'] == fake_uuid + assert users[0]["id"] == fake_uuid def test_client_uses_correct_find_by_email(mocker, api_user_active): - - expected_url = '/user/email' - expected_data = {'email': api_user_active['email_address']} + expected_url = "/user/email" + expected_data = {"email": api_user_active["email_address"]} user_api_client.max_failed_login_count = 1 # doesn't matter for this test - mock_post = mocker.patch('app.notify_client.user_api_client.UserApiClient.post') + mock_post = mocker.patch("app.notify_client.user_api_client.UserApiClient.post") - user_api_client.get_user_by_email(api_user_active['email_address']) + user_api_client.get_user_by_email(api_user_active["email_address"]) mock_post.assert_called_once_with(expected_url, data=expected_data) def test_client_only_updates_allowed_attributes(mocker): - mocker.patch('app.notify_client.current_user', id='1') + mocker.patch("app.notify_client.current_user", id="1") with pytest.raises(TypeError) as error: - user_api_client.update_user_attribute('user_id', id='1') - assert str(error.value) == 'Not allowed to update user attributes: id' + user_api_client.update_user_attribute("user_id", id="1") + assert str(error.value) == "Not allowed to update user attributes: id" def test_client_updates_password_separately(mocker, api_user_active): - expected_url = '/user/{}/update-password'.format(api_user_active['id']) - expected_params = {'_password': 'newpassword'} + expected_url = "/user/{}/update-password".format(api_user_active["id"]) + expected_params = {"_password": "newpassword"} user_api_client.max_failed_login_count = 1 # doesn't matter for this test - mock_update_password = mocker.patch('app.notify_client.user_api_client.UserApiClient.post') + mock_update_password = mocker.patch( + "app.notify_client.user_api_client.UserApiClient.post" + ) - user_api_client.update_password(api_user_active['id'], expected_params['_password']) + user_api_client.update_password(api_user_active["id"], expected_params["_password"]) mock_update_password.assert_called_once_with(expected_url, data=expected_params) def test_client_activates_if_pending(mocker, api_user_pending): - mock_post = mocker.patch('app.notify_client.user_api_client.UserApiClient.post') + mock_post = mocker.patch("app.notify_client.user_api_client.UserApiClient.post") user_api_client.max_failed_login_count = 1 # doesn't matter for this test - user_api_client.activate_user(api_user_pending['id']) + user_api_client.activate_user(api_user_pending["id"]) - mock_post.assert_called_once_with('/user/{}/activate'.format(api_user_pending['id']), data=None) + mock_post.assert_called_once_with( + "/user/{}/activate".format(api_user_pending["id"]), data=None + ) def test_client_passes_admin_url_when_sending_email_auth( @@ -74,82 +78,91 @@ def test_client_passes_admin_url_when_sending_email_auth( mocker, fake_uuid, ): - mock_post = mocker.patch('app.notify_client.user_api_client.UserApiClient.post') + mock_post = mocker.patch("app.notify_client.user_api_client.UserApiClient.post") - user_api_client.send_verify_code(fake_uuid, 'email', 'ignored@example.com') + user_api_client.send_verify_code(fake_uuid, "email", "ignored@example.com") mock_post.assert_called_once_with( - '/user/{}/email-code'.format(fake_uuid), + "/user/{}/email-code".format(fake_uuid), data={ - 'to': 'ignored@example.com', - 'email_auth_link_host': 'http://localhost:6012', - } + "to": "ignored@example.com", + "email_auth_link_host": "http://localhost:6012", + }, ) -def test_client_converts_admin_permissions_to_db_permissions_on_edit(notify_admin, mocker): - mock_post = mocker.patch('app.notify_client.user_api_client.UserApiClient.post') +def test_client_converts_admin_permissions_to_db_permissions_on_edit( + notify_admin, mocker +): + mock_post = mocker.patch("app.notify_client.user_api_client.UserApiClient.post") - user_api_client.set_user_permissions('user_id', 'service_id', permissions={'send_messages', 'view_activity'}) + user_api_client.set_user_permissions( + "user_id", "service_id", permissions={"send_messages", "view_activity"} + ) - assert sorted(mock_post.call_args[1]['data']['permissions'], key=lambda x: x['permission']) == sorted([ - {'permission': 'send_texts'}, - {'permission': 'send_emails'}, - {'permission': 'view_activity'}, - ], key=lambda x: x['permission']) + assert sorted( + mock_post.call_args[1]["data"]["permissions"], key=lambda x: x["permission"] + ) == sorted( + [ + {"permission": "send_texts"}, + {"permission": "send_emails"}, + {"permission": "view_activity"}, + ], + key=lambda x: x["permission"], + ) -def test_client_converts_admin_permissions_to_db_permissions_on_add_to_service(notify_admin, mocker): - mock_post = mocker.patch('app.notify_client.user_api_client.UserApiClient.post', return_value={'data': {}}) +def test_client_converts_admin_permissions_to_db_permissions_on_add_to_service( + notify_admin, mocker +): + mock_post = mocker.patch( + "app.notify_client.user_api_client.UserApiClient.post", + return_value={"data": {}}, + ) - user_api_client.add_user_to_service('service_id', - 'user_id', - permissions={'send_messages', 'view_activity'}, - folder_permissions=[]) + user_api_client.add_user_to_service( + "service_id", + "user_id", + permissions={"send_messages", "view_activity"}, + folder_permissions=[], + ) - assert sorted(mock_post.call_args[1]['data']['permissions'], key=lambda x: x['permission']) == sorted([ - {'permission': 'send_texts'}, - {'permission': 'send_emails'}, - {'permission': 'view_activity'}, - ], key=lambda x: x['permission']) + assert sorted( + mock_post.call_args[1]["data"]["permissions"], key=lambda x: x["permission"] + ) == sorted( + [ + {"permission": "send_texts"}, + {"permission": "send_emails"}, + {"permission": "view_activity"}, + ], + key=lambda x: x["permission"], + ) @pytest.mark.parametrize( ( - 'expected_cache_get_calls,' - 'cache_value,' - 'expected_api_calls,' - 'expected_cache_set_calls,' - 'expected_return_value,' + "expected_cache_get_calls," + "cache_value," + "expected_api_calls," + "expected_cache_set_calls," + "expected_return_value," ), [ ( - [ - call('user-{}'.format(user_id)) - ], + [call("user-{}".format(user_id))], b'{"data": "from cache"}', [], [], - 'from cache', + "from cache", ), ( - [ - call('user-{}'.format(user_id)) - ], + [call("user-{}".format(user_id))], None, - [ - call('/user/{}'.format(user_id)) - ], - [ - call( - 'user-{}'.format(user_id), - '{"data": "from api"}', - ex=604800 - ) - ], - 'from api', + [call("/user/{}".format(user_id))], + [call("user-{}".format(user_id), '{"data": "from api"}', ex=604800)], + "from api", ), - ] + ], ) def test_returns_value_from_cache( notify_admin, @@ -160,17 +173,16 @@ def test_returns_value_from_cache( expected_api_calls, expected_cache_set_calls, ): - mock_redis_get = mocker.patch( - 'app.extensions.RedisClient.get', + "app.extensions.RedisClient.get", return_value=cache_value, ) mock_api_get = mocker.patch( - 'app.notify_client.NotifyAdminAPIClient.get', - return_value={'data': 'from api'}, + "app.notify_client.NotifyAdminAPIClient.get", + return_value={"data": "from api"}, ) mock_redis_set = mocker.patch( - 'app.extensions.RedisClient.set', + "app.extensions.RedisClient.set", ) user_api_client.get_user(user_id) @@ -180,23 +192,36 @@ def test_returns_value_from_cache( assert mock_redis_set.call_args_list == expected_cache_set_calls -@pytest.mark.parametrize('client, method, extra_args, extra_kwargs', [ - (user_api_client, 'add_user_to_service', [SERVICE_ONE_ID, sample_uuid(), [], []], {}), - (user_api_client, 'update_user_attribute', [user_id], {}), - (user_api_client, 'reset_failed_login_count', [user_id], {}), - (user_api_client, 'update_user_attribute', [user_id], {}), - (user_api_client, 'update_password', [user_id, 'hunter2'], {}), - (user_api_client, 'verify_password', [user_id, 'hunter2'], {}), - (user_api_client, 'check_verify_code', [user_id, '', ''], {}), - (user_api_client, 'add_user_to_service', [SERVICE_ONE_ID, user_id, [], []], {}), - (user_api_client, 'add_user_to_organization', [sample_uuid(), user_id], {}), - (user_api_client, 'set_user_permissions', [user_id, SERVICE_ONE_ID, []], {}), - (user_api_client, 'activate_user', [user_id], {}), - (user_api_client, 'archive_user', [user_id], {}), - (service_api_client, 'remove_user_from_service', [SERVICE_ONE_ID, user_id], {}), - (service_api_client, 'create_service', ['', '', 0, False, user_id, sample_uuid()], {}), - (invite_api_client, 'accept_invite', [SERVICE_ONE_ID, user_id], {}), -]) +@pytest.mark.parametrize( + "client, method, extra_args, extra_kwargs", + [ + ( + user_api_client, + "add_user_to_service", + [SERVICE_ONE_ID, sample_uuid(), [], []], + {}, + ), + (user_api_client, "update_user_attribute", [user_id], {}), + (user_api_client, "reset_failed_login_count", [user_id], {}), + (user_api_client, "update_user_attribute", [user_id], {}), + (user_api_client, "update_password", [user_id, "hunter2"], {}), + (user_api_client, "verify_password", [user_id, "hunter2"], {}), + (user_api_client, "check_verify_code", [user_id, "", ""], {}), + (user_api_client, "add_user_to_service", [SERVICE_ONE_ID, user_id, [], []], {}), + (user_api_client, "add_user_to_organization", [sample_uuid(), user_id], {}), + (user_api_client, "set_user_permissions", [user_id, SERVICE_ONE_ID, []], {}), + (user_api_client, "activate_user", [user_id], {}), + (user_api_client, "archive_user", [user_id], {}), + (service_api_client, "remove_user_from_service", [SERVICE_ONE_ID, user_id], {}), + ( + service_api_client, + "create_service", + ["", "", 0, False, user_id, sample_uuid()], + {}, + ), + (invite_api_client, "accept_invite", [SERVICE_ONE_ID, user_id], {}), + ], +) def test_deletes_user_cache( notify_admin, mock_get_user, @@ -206,35 +231,37 @@ def test_deletes_user_cache( extra_args, extra_kwargs, ): - mocker.patch('app.notify_client.current_user', id='1') - mock_redis_delete = mocker.patch('app.extensions.RedisClient.delete') - mock_request = mocker.patch('notifications_python_client.base.BaseAPIClient.request') + mocker.patch("app.notify_client.current_user", id="1") + mock_redis_delete = mocker.patch("app.extensions.RedisClient.delete") + mock_request = mocker.patch( + "notifications_python_client.base.BaseAPIClient.request" + ) getattr(client, method)(*extra_args, **extra_kwargs) - assert call('user-{}'.format(user_id)) in mock_redis_delete.call_args_list + assert call("user-{}".format(user_id)) in mock_redis_delete.call_args_list assert len(mock_request.call_args_list) == 1 def test_add_user_to_service_calls_correct_endpoint_and_deletes_keys_from_cache(mocker): - mock_redis_delete = mocker.patch('app.extensions.RedisClient.delete') + mock_redis_delete = mocker.patch("app.extensions.RedisClient.delete") service_id = uuid.uuid4() user_id = uuid.uuid4() folder_id = uuid.uuid4() - expected_url = '/service/{}/users/{}'.format(service_id, user_id) - data = {'permissions': [], 'folder_permissions': [folder_id]} + expected_url = "/service/{}/users/{}".format(service_id, user_id) + data = {"permissions": [], "folder_permissions": [folder_id]} - mock_post = mocker.patch('app.notify_client.user_api_client.UserApiClient.post') + mock_post = mocker.patch("app.notify_client.user_api_client.UserApiClient.post") user_api_client.add_user_to_service(service_id, user_id, [], [folder_id]) mock_post.assert_called_once_with(expected_url, data=data) assert mock_redis_delete.call_args_list == [ - call('user-{user_id}'.format(user_id=user_id)), - call('service-{service_id}-template-folders'.format(service_id=service_id)), - call('service-{service_id}'.format(service_id=service_id)), + call("user-{user_id}".format(user_id=user_id)), + call("service-{service_id}-template-folders".format(service_id=service_id)), + call("service-{service_id}".format(service_id=service_id)), ] @@ -242,17 +269,15 @@ def test_reset_password( mocker, fake_uuid, ): - mock_post = mocker.patch( - 'app.notify_client.user_api_client.UserApiClient.post' - ) + mock_post = mocker.patch("app.notify_client.user_api_client.UserApiClient.post") - user_api_client.send_reset_password_url('test@example.com') + user_api_client.send_reset_password_url("test@example.com") mock_post.assert_called_once_with( - '/user/reset-password', + "/user/reset-password", data={ - 'email': 'test@example.com', - 'admin_base_url': 'http://localhost:6012', + "email": "test@example.com", + "admin_base_url": "http://localhost:6012", }, ) @@ -261,16 +286,14 @@ def test_send_registration_email( mocker, fake_uuid, ): - mock_post = mocker.patch( - 'app.notify_client.user_api_client.UserApiClient.post' - ) + mock_post = mocker.patch("app.notify_client.user_api_client.UserApiClient.post") - user_api_client.send_verify_email(fake_uuid, 'test@example.com') + user_api_client.send_verify_email(fake_uuid, "test@example.com") mock_post.assert_called_once_with( - f'/user/{fake_uuid}/email-verification', + f"/user/{fake_uuid}/email-verification", data={ - 'to': 'test@example.com', - 'admin_base_url': 'http://localhost:6012', + "to": "test@example.com", + "admin_base_url": "http://localhost:6012", }, ) diff --git a/tests/app/s3_client/test_s3_csv_client.py b/tests/app/s3_client/test_s3_csv_client.py index 4a099d73d..dbf26ea47 100644 --- a/tests/app/s3_client/test_s3_csv_client.py +++ b/tests/app/s3_client/test_s3_csv_client.py @@ -4,18 +4,20 @@ from app.s3_client.s3_csv_client import set_metadata_on_csv_upload def test_sets_metadata(client_request, mocker): - mocked_s3_object = Mock(bucket_name='test-csv-upload', key='service-1234-notify/5678.csv') + mocked_s3_object = Mock( + bucket_name="test-csv-upload", key="service-1234-notify/5678.csv" + ) mocked_get_s3_object = mocker.patch( - 'app.s3_client.s3_csv_client.get_csv_upload', + "app.s3_client.s3_csv_client.get_csv_upload", return_value=mocked_s3_object, ) - set_metadata_on_csv_upload('1234', '5678', foo='bar', baz=True) + set_metadata_on_csv_upload("1234", "5678", foo="bar", baz=True) - mocked_get_s3_object.assert_called_once_with('1234', '5678') + mocked_get_s3_object.assert_called_once_with("1234", "5678") mocked_s3_object.copy_from.assert_called_once_with( - CopySource='test-csv-upload/service-1234-notify/5678.csv', - Metadata={'baz': 'True', 'foo': 'bar'}, - MetadataDirective='REPLACE', - ServerSideEncryption='AES256', + CopySource="test-csv-upload/service-1234-notify/5678.csv", + Metadata={"baz": "True", "foo": "bar"}, + MetadataDirective="REPLACE", + ServerSideEncryption="AES256", ) diff --git a/tests/app/s3_client/test_s3_logo_client.py b/tests/app/s3_client/test_s3_logo_client.py index 00358100f..2041d54c1 100644 --- a/tests/app/s3_client/test_s3_logo_client.py +++ b/tests/app/s3_client/test_s3_logo_client.py @@ -13,76 +13,88 @@ from app.s3_client.s3_logo_client import ( upload_email_logo, ) -data = {'data': 'some_data'} -filename = 'test.png' -svg_filename = 'test.svg' -upload_id = 'test_uuid' +data = {"data": "some_data"} +filename = "test.png" +svg_filename = "test.svg" +upload_id = "test_uuid" @pytest.fixture def upload_filename(fake_uuid): return EMAIL_LOGO_LOCATION_STRUCTURE.format( - temp=TEMP_TAG.format(user_id=fake_uuid), unique_id=upload_id, filename=filename) + temp=TEMP_TAG.format(user_id=fake_uuid), unique_id=upload_id, filename=filename + ) @pytest.fixture def bucket_credentials(notify_admin): - return notify_admin.config['LOGO_UPLOAD_BUCKET'] + return notify_admin.config["LOGO_UPLOAD_BUCKET"] -def test_upload_email_logo_calls_correct_args(client_request, mocker, bucket_credentials, fake_uuid, upload_filename): - mocker.patch('uuid.uuid4', return_value=upload_id) - mocked_s3_upload = mocker.patch('app.s3_client.s3_logo_client.utils_s3upload') +def test_upload_email_logo_calls_correct_args( + client_request, mocker, bucket_credentials, fake_uuid, upload_filename +): + mocker.patch("uuid.uuid4", return_value=upload_id) + mocked_s3_upload = mocker.patch("app.s3_client.s3_logo_client.utils_s3upload") upload_email_logo(filename=filename, user_id=fake_uuid, filedata=data) mocked_s3_upload.assert_called_once_with( filedata=data, - region=bucket_credentials['region'], + region=bucket_credentials["region"], file_location=upload_filename, - bucket_name=bucket_credentials['bucket'], - content_type='image/png', - access_key=bucket_credentials['access_key_id'], - secret_key=bucket_credentials['secret_access_key'], + bucket_name=bucket_credentials["bucket"], + content_type="image/png", + access_key=bucket_credentials["access_key_id"], + secret_key=bucket_credentials["secret_access_key"], ) -def test_persist_logo(client_request, bucket_credentials, mocker, fake_uuid, upload_filename): - mocked_get_s3_object = mocker.patch('app.s3_client.s3_logo_client.get_s3_object') - mocked_delete_s3_object = mocker.patch('app.s3_client.s3_logo_client.delete_s3_object') +def test_persist_logo( + client_request, bucket_credentials, mocker, fake_uuid, upload_filename +): + mocked_get_s3_object = mocker.patch("app.s3_client.s3_logo_client.get_s3_object") + mocked_delete_s3_object = mocker.patch( + "app.s3_client.s3_logo_client.delete_s3_object" + ) new_filename = permanent_email_logo_name(upload_filename, fake_uuid) persist_logo(upload_filename, new_filename) mocked_get_s3_object.assert_called_once_with( - bucket_credentials['bucket'], + bucket_credentials["bucket"], new_filename, - bucket_credentials['access_key_id'], - bucket_credentials['secret_access_key'], - bucket_credentials['region']) + bucket_credentials["access_key_id"], + bucket_credentials["secret_access_key"], + bucket_credentials["region"], + ) mocked_delete_s3_object.assert_called_once_with(upload_filename) def test_persist_logo_returns_if_not_temp(client_request, mocker, fake_uuid): - filename = 'logo.png' + filename = "logo.png" persist_logo(filename, filename) - mocked_get_s3_object = mocker.patch('app.s3_client.s3_logo_client.get_s3_object') - mocked_delete_s3_object = mocker.patch('app.s3_client.s3_logo_client.delete_s3_object') + mocked_get_s3_object = mocker.patch("app.s3_client.s3_logo_client.get_s3_object") + mocked_delete_s3_object = mocker.patch( + "app.s3_client.s3_logo_client.delete_s3_object" + ) assert mocked_get_s3_object.called is False assert mocked_delete_s3_object.called is False -def test_permanent_email_logo_name_removes_TEMP_TAG_from_filename(upload_filename, fake_uuid): +def test_permanent_email_logo_name_removes_TEMP_TAG_from_filename( + upload_filename, fake_uuid +): new_name = permanent_email_logo_name(upload_filename, fake_uuid) - assert new_name == 'test_uuid-test.png' + assert new_name == "test_uuid-test.png" def test_permanent_email_logo_name_does_not_change_filenames_with_no_TEMP_TAG(): - filename = 'logo.png' + filename = "logo.png" new_name = permanent_email_logo_name(filename, filename) assert new_name == filename @@ -90,10 +102,15 @@ def test_permanent_email_logo_name_does_not_change_filenames_with_no_TEMP_TAG(): def test_delete_email_temp_files_created_by_user(client_request, mocker, fake_uuid): obj = namedtuple("obj", ["key"]) - objs = [obj(key='test1'), obj(key='test2')] + objs = [obj(key="test1"), obj(key="test2")] - mocker.patch('app.s3_client.s3_logo_client.get_s3_objects_filter_by_prefix', return_value=objs) - mocked_delete_s3_object = mocker.patch('app.s3_client.s3_logo_client.delete_s3_object') + mocker.patch( + "app.s3_client.s3_logo_client.get_s3_objects_filter_by_prefix", + return_value=objs, + ) + mocked_delete_s3_object = mocker.patch( + "app.s3_client.s3_logo_client.delete_s3_object" + ) delete_email_temp_files_created_by(fake_uuid) @@ -102,7 +119,9 @@ def test_delete_email_temp_files_created_by_user(client_request, mocker, fake_uu def test_delete_single_email_temp_file(client_request, mocker, upload_filename): - mocked_delete_s3_object = mocker.patch('app.s3_client.s3_logo_client.delete_s3_object') + mocked_delete_s3_object = mocker.patch( + "app.s3_client.s3_logo_client.delete_s3_object" + ) delete_email_temp_file(upload_filename) @@ -110,11 +129,13 @@ def test_delete_single_email_temp_file(client_request, mocker, upload_filename): def test_does_not_delete_non_temp_email_file(client_request, mocker): - filename = 'logo.png' - mocked_delete_s3_object = mocker.patch('app.s3_client.s3_logo_client.delete_s3_object') + filename = "logo.png" + mocked_delete_s3_object = mocker.patch( + "app.s3_client.s3_logo_client.delete_s3_object" + ) with pytest.raises(ValueError) as error: delete_email_temp_file(filename) assert mocked_delete_s3_object.called is False - assert str(error.value) == 'Not a temp file: {}'.format(filename) + assert str(error.value) == "Not a temp file: {}".format(filename) diff --git a/tests/app/test_accessibility_statement.py b/tests/app/test_accessibility_statement.py index dd2f53f95..b76ca45af 100644 --- a/tests/app/test_accessibility_statement.py +++ b/tests/app/test_accessibility_statement.py @@ -7,16 +7,21 @@ def test_last_review_date(): statement_file_path = "app/templates/views/accessibility_statement.html" # test local changes against master for a full diff of what will be merged - statement_diff = subprocess.run([f"git diff --exit-code origin/master -- {statement_file_path}"], - stdout=subprocess.PIPE, shell=True) + statement_diff = subprocess.run( + [f"git diff --exit-code origin/master -- {statement_file_path}"], + stdout=subprocess.PIPE, + shell=True, + ) # if statement has changed, test the review date was part of those changes if statement_diff.returncode == 1: - raw_diff = statement_diff.stdout.decode('utf-8') - today = datetime.now().strftime('%d %B %Y') - with open(statement_file_path, 'r') as statement_file: - current_review_date = re.search((r'"Last updated": "(\d{1,2} [A-Z]{1}[a-z]+ \d{4})"'), - statement_file.read()).group(1) + raw_diff = statement_diff.stdout.decode("utf-8") + today = datetime.now().strftime("%d %B %Y") + with open(statement_file_path, "r") as statement_file: + current_review_date = re.search( + (r'"Last updated": "(\d{1,2} [A-Z]{1}[a-z]+ \d{4})"'), + statement_file.read(), + ).group(1) # guard against changes that don't need to update the review date if current_review_date != today: diff --git a/tests/app/test_assets.py b/tests/app/test_assets.py index 39514abda..f2311dd98 100644 --- a/tests/app/test_assets.py +++ b/tests/app/test_assets.py @@ -1,5 +1,5 @@ def test_static_404s_return(client_request): client_request.get_response_from_url( - '/static/images/some-image-that-doesnt-exist.png', + "/static/images/some-image-that-doesnt-exist.png", _expected_status=404, ) diff --git a/tests/app/test_cloudfoundry_config.py b/tests/app/test_cloudfoundry_config.py index e6af9da06..433f2c73e 100644 --- a/tests/app/test_cloudfoundry_config.py +++ b/tests/app/test_cloudfoundry_config.py @@ -6,66 +6,65 @@ import pytest from app.cloudfoundry_config import CloudfoundryConfig bucket_credentials = { - 'access_key_id': 'contact-list-access', - 'bucket': 'contact-list-bucket', - 'region': 'us-gov-west-1', - 'secret_access_key': 'contact-list-secret' + "access_key_id": "contact-list-access", + "bucket": "contact-list-bucket", + "region": "us-gov-west-1", + "secret_access_key": "contact-list-secret", } @pytest.fixture def vcap_services(): return { - 'aws-elasticache-redis': [{ - 'credentials': { - 'uri': 'redis://xxx:6379' - } - }], - 's3': [ + "aws-elasticache-redis": [{"credentials": {"uri": "redis://xxx:6379"}}], + "s3": [ { - 'name': 'notify-api-csv-upload-bucket-test', - 'credentials': { - 'access_key_id': 'csv-access', - 'bucket': 'csv-upload-bucket', - 'region': 'us-gov-west-1', - 'secret_access_key': 'csv-secret' - } + "name": "notify-api-csv-upload-bucket-test", + "credentials": { + "access_key_id": "csv-access", + "bucket": "csv-upload-bucket", + "region": "us-gov-west-1", + "secret_access_key": "csv-secret", + }, }, { - 'name': 'notify-api-contact-list-bucket-test', - 'credentials': bucket_credentials - } + "name": "notify-api-contact-list-bucket-test", + "credentials": bucket_credentials, + }, ], } def test_redis_url(vcap_services): - os.environ['VCAP_SERVICES'] = json.dumps(vcap_services) + os.environ["VCAP_SERVICES"] = json.dumps(vcap_services) - assert CloudfoundryConfig().redis_url == 'rediss://xxx:6379' + assert CloudfoundryConfig().redis_url == "rediss://xxx:6379" def test_redis_url_falls_back_to_REDIS_URL(): - expected = 'rediss://yyy:6379' - os.environ['REDIS_URL'] = expected - os.environ['VCAP_SERVICES'] = "" + expected = "rediss://yyy:6379" + os.environ["REDIS_URL"] = expected + os.environ["VCAP_SERVICES"] = "" assert CloudfoundryConfig().redis_url == expected def test_s3_bucket_credentials(vcap_services): - os.environ['VCAP_SERVICES'] = json.dumps(vcap_services) + os.environ["VCAP_SERVICES"] = json.dumps(vcap_services) - assert CloudfoundryConfig().s3_credentials('notify-api-contact-list-bucket-test') == bucket_credentials + assert ( + CloudfoundryConfig().s3_credentials("notify-api-contact-list-bucket-test") + == bucket_credentials + ) def test_s3_bucket_credentials_falls_back_to_empty_creds(): - os.environ['VCAP_SERVICES'] = "" + os.environ["VCAP_SERVICES"] = "" expected = { - 'bucket': '', - 'access_key_id': '', - 'secret_access_key': '', - 'region': '' + "bucket": "", + "access_key_id": "", + "secret_access_key": "", + "region": "", } - assert CloudfoundryConfig().s3_credentials('bucket') == expected + assert CloudfoundryConfig().s3_credentials("bucket") == expected diff --git a/tests/app/test_event_handlers.py b/tests/app/test_event_handlers.py index 3230b1877..0f188a95f 100644 --- a/tests/app/test_event_handlers.py +++ b/tests/app/test_event_handlers.py @@ -18,30 +18,37 @@ from app.models.user import User def event_dict(**extra): return { - 'browser_fingerprint': {'browser': ANY, 'version': ANY, 'platform': ANY, 'user_agent_string': ''}, - 'ip_address': ANY, - **extra + "browser_fingerprint": { + "browser": ANY, + "version": ANY, + "platform": ANY, + "user_agent_string": "", + }, + "ip_address": ANY, + **extra, } -def test_on_user_logged_in_calls_events_api(client_request, api_user_active, mock_events): - on_user_logged_in('_notify_admin', User(api_user_active)) +def test_on_user_logged_in_calls_events_api( + client_request, api_user_active, mock_events +): + on_user_logged_in("_notify_admin", User(api_user_active)) - mock_events.assert_called_with('sucessful_login', event_dict( - user_id=str(api_user_active['id']) - )) + mock_events.assert_called_with( + "sucessful_login", event_dict(user_id=str(api_user_active["id"])) + ) def test_create_email_change_event_calls_events_api(client_request, mock_events): kwargs = { "user_id": str(uuid.uuid4()), "updated_by_id": str(uuid.uuid4()), - "original_email_address": 'original@example.com', - "new_email_address": 'new@example.com' + "original_email_address": "original@example.com", + "new_email_address": "new@example.com", } create_email_change_event(**kwargs) - mock_events.assert_called_with('update_user_email', event_dict(**kwargs)) + mock_events.assert_called_with("update_user_email", event_dict(**kwargs)) def test_create_add_user_to_service_event_calls_events_api(client_request, mock_events): @@ -49,74 +56,66 @@ def test_create_add_user_to_service_event_calls_events_api(client_request, mock_ "user_id": str(uuid.uuid4()), "invited_by_id": str(uuid.uuid4()), "service_id": str(uuid.uuid4()), - "ui_permissions": {'manage_templates'}, + "ui_permissions": {"manage_templates"}, } create_add_user_to_service_event(**kwargs) - mock_events.assert_called_with('add_user_to_service', event_dict(**kwargs)) + mock_events.assert_called_with("add_user_to_service", event_dict(**kwargs)) -def test_create_remove_user_from_service_event_calls_events_api(client_request, mock_events): +def test_create_remove_user_from_service_event_calls_events_api( + client_request, mock_events +): kwargs = { "user_id": str(uuid.uuid4()), "removed_by_id": str(uuid.uuid4()), - "service_id": str(uuid.uuid4()) + "service_id": str(uuid.uuid4()), } create_remove_user_from_service_event(**kwargs) - mock_events.assert_called_with('remove_user_from_service', event_dict(**kwargs)) + mock_events.assert_called_with("remove_user_from_service", event_dict(**kwargs)) -def test_create_mobile_number_change_event_calls_events_api(client_request, mock_events): +def test_create_mobile_number_change_event_calls_events_api( + client_request, mock_events +): kwargs = { "user_id": str(uuid.uuid4()), "updated_by_id": str(uuid.uuid4()), - "original_mobile_number": '2028675304', - "new_mobile_number": '2028675309' + "original_mobile_number": "2028675304", + "new_mobile_number": "2028675309", } create_mobile_number_change_event(**kwargs) - mock_events.assert_called_with('update_user_mobile_number', event_dict(**kwargs)) + mock_events.assert_called_with("update_user_mobile_number", event_dict(**kwargs)) def test_create_archive_user_event_calls_events_api(client_request, mock_events): - kwargs = { - "user_id": str(uuid.uuid4()), - "archived_by_id": str(uuid.uuid4()) - } + kwargs = {"user_id": str(uuid.uuid4()), "archived_by_id": str(uuid.uuid4())} create_archive_user_event(**kwargs) - mock_events.assert_called_with('archive_user', event_dict(**kwargs)) + mock_events.assert_called_with("archive_user", event_dict(**kwargs)) def test_suspend_service(client_request, mock_events): - kwargs = { - "service_id": str(uuid.uuid4()), - "suspended_by_id": str(uuid.uuid4()) - } + kwargs = {"service_id": str(uuid.uuid4()), "suspended_by_id": str(uuid.uuid4())} create_suspend_service_event(**kwargs) - mock_events.assert_called_with('suspend_service', event_dict(**kwargs)) + mock_events.assert_called_with("suspend_service", event_dict(**kwargs)) def test_archive_service(client_request, mock_events): - kwargs = { - "service_id": str(uuid.uuid4()), - "archived_by_id": str(uuid.uuid4()) - } + kwargs = {"service_id": str(uuid.uuid4()), "archived_by_id": str(uuid.uuid4())} create_archive_service_event(**kwargs) - mock_events.assert_called_with('archive_service', event_dict(**kwargs)) + mock_events.assert_called_with("archive_service", event_dict(**kwargs)) def test_resume_service(client_request, mock_events): - kwargs = { - "service_id": str(uuid.uuid4()), - "resumed_by_id": str(uuid.uuid4()) - } + kwargs = {"service_id": str(uuid.uuid4()), "resumed_by_id": str(uuid.uuid4())} create_resume_service_event(**kwargs) - mock_events.assert_called_with('resume_service', event_dict(**kwargs)) + mock_events.assert_called_with("resume_service", event_dict(**kwargs)) def test_set_user_permissions(client_request, mock_events): @@ -129,4 +128,4 @@ def test_set_user_permissions(client_request, mock_events): } create_set_user_permissions_event(**kwargs) - mock_events.assert_called_with('set_user_permissions', event_dict(**kwargs)) + mock_events.assert_called_with("set_user_permissions", event_dict(**kwargs)) diff --git a/tests/app/test_navigation.py b/tests/app/test_navigation.py index ab1b3b036..59cb0f8f9 100644 --- a/tests/app/test_navigation.py +++ b/tests/app/test_navigation.py @@ -13,265 +13,270 @@ from app.navigation import ( # from tests.conftest import ORGANISATION_ID, SERVICE_ONE_ID, normalize_spaces from tests.conftest import SERVICE_ONE_ID, normalize_spaces -EXCLUDED_ENDPOINTS = tuple(map(Navigation.get_endpoint_with_blueprint, { - 'accept_invite', - 'accept_org_invite', - 'accessibility_statement', - 'action_blocked', - 'add_data_retention', - 'add_organization', - 'add_service', - 'add_service_template', - 'api_callbacks', - 'api_documentation', - 'api_integration', - 'api_keys', - 'archive_service', - 'archive_user', - 'bat_phone', - 'begin_tour', - 'billing_details', - 'branding_and_customisation', - 'callbacks', - 'cancel_invited_org_user', - 'cancel_invited_user', - 'cancel_job', - 'change_user_auth', - 'check_and_resend_text_code', - 'check_and_resend_verification_code', - 'check_messages', - 'check_notification', - 'check_tour_notification', - 'choose_account', - 'choose_service', - 'choose_template', - 'choose_template_to_copy', - 'clear_cache', - 'confirm_edit_user_email', - 'confirm_edit_user_mobile_number', - 'confirm_redact_template', - 'conversation', - 'conversation_reply', - 'conversation_reply_with_template', - 'conversation_updates', - 'copy_template', - 'count_content_length', - 'create_and_send_messages', - 'create_api_key', - 'create_email_branding', - 'data_retention', - 'delete_service_template', - 'delete_template_folder', - 'delivery_and_failure', - 'delivery_status_callback', - 'design_content', - 'documentation', - 'download_notifications_csv', - 'download_organization_usage_report', - 'edit_and_format_messages', - 'edit_data_retention', - 'edit_organization_billing_details', - 'edit_organization_domains', - 'edit_organization_email_branding', - 'edit_organization_go_live_notes', - 'edit_organization_name', - 'edit_organization_notes', - 'edit_organization_type', - 'edit_organization_user', - 'edit_service_billing_details', - 'edit_service_notes', - 'edit_service_template', - 'edit_sms_provider_ratio', - 'edit_user_email', - 'edit_user_mobile_number', - 'edit_user_permissions', - 'email_branding', - 'email_branding_govuk', - 'email_branding_govuk_and_org', - 'email_branding_organization', - 'email_branding_request', - 'email_branding_something_else', - 'email_not_received', - 'email_template', - 'error', - 'estimate_usage', - 'features', - 'features_email', - 'features_sms', - 'feedback', - 'find_services_by_name', - 'find_users_by_email', - 'forgot_password', - 'get_billing_report', - 'get_users_report', - 'get_daily_volumes', - 'get_daily_sms_provider_volumes', - 'get_volumes_by_service', - 'get_example_csv', - 'get_notifications_as_json', - 'get_started', - 'get_started_old', - 'go_to_dashboard_after_tour', - 'guest_list', - 'guidance_index', - 'history', - 'how_to_pay', - 'inbound_sms_admin', - 'inbox', - 'inbox_download', - 'inbox_updates', - 'index', - 'information_risk_management', - 'information_security', - 'integration_testing', - 'invite_org_user', - 'invite_user', - 'link_service_to_organization', - 'live_services', - 'live_services_csv', - 'manage_org_users', - 'manage_template_folder', - 'manage_users', - 'message_status', - 'monthly', - 'new_password', - 'notifications_sent_by_service', - 'old_guest_list', - 'old_integration_testing', - 'old_roadmap', - 'old_service_dashboard', - 'old_terms', - 'old_using_notify', - 'organization_billing', - 'organization_dashboard', - 'organization_preview_email_branding', - 'organization_settings', - 'organization_trial_mode_services', - 'organizations', - 'performance', - 'platform_admin', - 'platform_admin_list_complaints', - 'platform_admin_reports', - 'platform_admin_splash_page', - 'pricing', - 'privacy', - 'received_text_messages_callback', - 'redact_template', - 'register', - 'register_from_invite', - 'register_from_org_invite', - 'registration_continue', - 'remove_user_from_organization', - 'remove_user_from_service', - 'request_to_go_live', - 'resend_email_link', - 'resend_email_verification', - 'resume_service', - 'revalidate_email_sent', - 'revoke_api_key', - 'roadmap', - 'security', - 'security_policy', - 'send_files_by_email', - 'send_files_by_email_contact_details', - 'send_messages', - 'send_notification', - 'send_one_off', - 'send_one_off_step', - 'send_one_off_to_myself', - 'service_add_email_reply_to', - 'service_add_sms_sender', - 'service_confirm_delete_email_reply_to', - 'service_confirm_delete_sms_sender', - 'service_dashboard', - 'service_dashboard_updates', - 'service_delete_email_reply_to', - 'service_delete_sms_sender', - 'service_edit_email_reply_to', - 'service_edit_sms_sender', - 'service_email_reply_to', - 'service_name_change', - 'service_preview_email_branding', - 'service_set_auth_type', - 'service_set_channel', - 'service_set_email_branding', - 'service_set_inbound_number', - 'service_set_inbound_sms', - 'service_set_international_sms', - 'service_set_permission', - 'service_set_reply_to_email', - 'service_set_sms_prefix', - 'service_settings', - 'service_sms_senders', - 'service_switch_count_as_live', - 'service_switch_live', - 'service_verify_reply_to_address', - 'service_verify_reply_to_address_updates', - 'services_or_dashboard', - 'set_free_sms_allowance', - 'set_message_limit', - 'set_rate_limit', - 'set_sender', - 'set_template_sender', - 'show_accounts_or_dashboard', - 'sign_in', - 'sign_out', - 'start_job', - 'submit_request_to_go_live', - 'support', - 'support_public', - 'suspend_service', - 'template_history', - 'template_usage', - 'terms', - 'thanks', - 'tour_step', - 'triage', - 'trial_mode', - 'trial_mode_new', - 'trial_services', - 'two_factor_sms', - 'two_factor_email', - 'two_factor_email_interstitial', - 'two_factor_email_sent', - 'update_email_branding', - 'uploads', - 'usage', - 'user_information', - 'user_profile', - 'user_profile_confirm_delete_mobile_number', - 'user_profile_disable_platform_admin_view', - 'user_profile_email', - 'user_profile_email_authenticate', - 'user_profile_email_confirm', - 'user_profile_mobile_number', - 'user_profile_mobile_number_authenticate', - 'user_profile_mobile_number_confirm', - 'user_profile_mobile_number_delete', - 'user_profile_name', - 'user_profile_password', - 'using_notify', - 'verify', - 'verify_email', - 'view_job', - 'view_job_csv', - 'view_job_updates', - 'view_jobs', - 'view_notification', - 'view_notification_updates', - 'view_notifications', - 'view_notifications_csv', - 'view_provider', - 'view_providers', - 'view_template', - 'view_template_version', - 'view_template_versions', - 'who_its_for', -})) +EXCLUDED_ENDPOINTS = tuple( + map( + Navigation.get_endpoint_with_blueprint, + { + "accept_invite", + "accept_org_invite", + "accessibility_statement", + "action_blocked", + "add_data_retention", + "add_organization", + "add_service", + "add_service_template", + "api_callbacks", + "api_documentation", + "api_integration", + "api_keys", + "archive_service", + "archive_user", + "bat_phone", + "begin_tour", + "billing_details", + "branding_and_customisation", + "callbacks", + "cancel_invited_org_user", + "cancel_invited_user", + "cancel_job", + "change_user_auth", + "check_and_resend_text_code", + "check_and_resend_verification_code", + "check_messages", + "check_notification", + "check_tour_notification", + "choose_account", + "choose_service", + "choose_template", + "choose_template_to_copy", + "clear_cache", + "confirm_edit_user_email", + "confirm_edit_user_mobile_number", + "confirm_redact_template", + "conversation", + "conversation_reply", + "conversation_reply_with_template", + "conversation_updates", + "copy_template", + "count_content_length", + "create_and_send_messages", + "create_api_key", + "create_email_branding", + "data_retention", + "delete_service_template", + "delete_template_folder", + "delivery_and_failure", + "delivery_status_callback", + "design_content", + "documentation", + "download_notifications_csv", + "download_organization_usage_report", + "edit_and_format_messages", + "edit_data_retention", + "edit_organization_billing_details", + "edit_organization_domains", + "edit_organization_email_branding", + "edit_organization_go_live_notes", + "edit_organization_name", + "edit_organization_notes", + "edit_organization_type", + "edit_organization_user", + "edit_service_billing_details", + "edit_service_notes", + "edit_service_template", + "edit_sms_provider_ratio", + "edit_user_email", + "edit_user_mobile_number", + "edit_user_permissions", + "email_branding", + "email_branding_govuk", + "email_branding_govuk_and_org", + "email_branding_organization", + "email_branding_request", + "email_branding_something_else", + "email_not_received", + "email_template", + "error", + "estimate_usage", + "features", + "features_email", + "features_sms", + "feedback", + "find_services_by_name", + "find_users_by_email", + "forgot_password", + "get_billing_report", + "get_users_report", + "get_daily_volumes", + "get_daily_sms_provider_volumes", + "get_volumes_by_service", + "get_example_csv", + "get_notifications_as_json", + "get_started", + "get_started_old", + "go_to_dashboard_after_tour", + "guest_list", + "guidance_index", + "history", + "how_to_pay", + "inbound_sms_admin", + "inbox", + "inbox_download", + "inbox_updates", + "index", + "information_risk_management", + "information_security", + "integration_testing", + "invite_org_user", + "invite_user", + "link_service_to_organization", + "live_services", + "live_services_csv", + "manage_org_users", + "manage_template_folder", + "manage_users", + "message_status", + "monthly", + "new_password", + "notifications_sent_by_service", + "old_guest_list", + "old_integration_testing", + "old_roadmap", + "old_service_dashboard", + "old_terms", + "old_using_notify", + "organization_billing", + "organization_dashboard", + "organization_preview_email_branding", + "organization_settings", + "organization_trial_mode_services", + "organizations", + "performance", + "platform_admin", + "platform_admin_list_complaints", + "platform_admin_reports", + "platform_admin_splash_page", + "pricing", + "privacy", + "received_text_messages_callback", + "redact_template", + "register", + "register_from_invite", + "register_from_org_invite", + "registration_continue", + "remove_user_from_organization", + "remove_user_from_service", + "request_to_go_live", + "resend_email_link", + "resend_email_verification", + "resume_service", + "revalidate_email_sent", + "revoke_api_key", + "roadmap", + "security", + "security_policy", + "send_files_by_email", + "send_files_by_email_contact_details", + "send_messages", + "send_notification", + "send_one_off", + "send_one_off_step", + "send_one_off_to_myself", + "service_add_email_reply_to", + "service_add_sms_sender", + "service_confirm_delete_email_reply_to", + "service_confirm_delete_sms_sender", + "service_dashboard", + "service_dashboard_updates", + "service_delete_email_reply_to", + "service_delete_sms_sender", + "service_edit_email_reply_to", + "service_edit_sms_sender", + "service_email_reply_to", + "service_name_change", + "service_preview_email_branding", + "service_set_auth_type", + "service_set_channel", + "service_set_email_branding", + "service_set_inbound_number", + "service_set_inbound_sms", + "service_set_international_sms", + "service_set_permission", + "service_set_reply_to_email", + "service_set_sms_prefix", + "service_settings", + "service_sms_senders", + "service_switch_count_as_live", + "service_switch_live", + "service_verify_reply_to_address", + "service_verify_reply_to_address_updates", + "services_or_dashboard", + "set_free_sms_allowance", + "set_message_limit", + "set_rate_limit", + "set_sender", + "set_template_sender", + "show_accounts_or_dashboard", + "sign_in", + "sign_out", + "start_job", + "submit_request_to_go_live", + "support", + "support_public", + "suspend_service", + "template_history", + "template_usage", + "terms", + "thanks", + "tour_step", + "triage", + "trial_mode", + "trial_mode_new", + "trial_services", + "two_factor_sms", + "two_factor_email", + "two_factor_email_interstitial", + "two_factor_email_sent", + "update_email_branding", + "uploads", + "usage", + "user_information", + "user_profile", + "user_profile_confirm_delete_mobile_number", + "user_profile_disable_platform_admin_view", + "user_profile_email", + "user_profile_email_authenticate", + "user_profile_email_confirm", + "user_profile_mobile_number", + "user_profile_mobile_number_authenticate", + "user_profile_mobile_number_confirm", + "user_profile_mobile_number_delete", + "user_profile_name", + "user_profile_password", + "using_notify", + "verify", + "verify_email", + "view_job", + "view_job_csv", + "view_job_updates", + "view_jobs", + "view_notification", + "view_notification_updates", + "view_notifications", + "view_notifications_csv", + "view_provider", + "view_providers", + "view_template", + "view_template_version", + "view_template_versions", + "who_its_for", + }, + ) +) def flask_app(): - app = Flask('app') + app = Flask("app") create_app(app) ctx = app.app_context() @@ -280,9 +285,7 @@ def flask_app(): yield app -all_endpoints = [ - rule.endpoint for rule in next(flask_app()).url_map.iter_rules() -] +all_endpoints = [rule.endpoint for rule in next(flask_app()).url_map.iter_rules()] navigation_instances = ( MainNavigation(), @@ -293,23 +296,21 @@ navigation_instances = ( @pytest.mark.parametrize( - 'navigation_instance', + "navigation_instance", navigation_instances, - ids=(x.__class__.__name__ for x in navigation_instances) + ids=(x.__class__.__name__ for x in navigation_instances), ) def test_navigation_items_are_properly_defined(navigation_instance): for endpoint in navigation_instance.endpoints_with_navigation: assert ( endpoint in all_endpoints - ), '{} is not a real endpoint (in {}.mapping)'.format( - endpoint, - type(navigation_instance).__name__ + ), "{} is not a real endpoint (in {}.mapping)".format( + endpoint, type(navigation_instance).__name__ ) assert ( navigation_instance.endpoints_with_navigation.count(endpoint) == 1 - ), '{} found more than once in {}.mapping'.format( - endpoint, - type(navigation_instance).__name__ + ), "{} found more than once in {}.mapping".format( + endpoint, type(navigation_instance).__name__ ) @@ -317,45 +318,46 @@ def test_excluded_navigation_items_are_properly_defined(): for endpoint in EXCLUDED_ENDPOINTS: assert ( endpoint in all_endpoints - ), f'{endpoint} is not a real endpoint (in EXCLUDED_ENDPOINTS)' + ), f"{endpoint} is not a real endpoint (in EXCLUDED_ENDPOINTS)" assert ( EXCLUDED_ENDPOINTS.count(endpoint) == 1 - ), f'{endpoint} found more than once in EXCLUDED_ENDPOINTS' + ), f"{endpoint} found more than once in EXCLUDED_ENDPOINTS" @pytest.mark.parametrize( - 'navigation_instance', + "navigation_instance", navigation_instances, - ids=(x.__class__.__name__ for x in navigation_instances) + ids=(x.__class__.__name__ for x in navigation_instances), ) def test_all_endpoints_are_covered(navigation_instance): covered_endpoints = ( - navigation_instance.endpoints_with_navigation + - EXCLUDED_ENDPOINTS + - ('static', 'status.show_status', 'status.show_redis_status', 'metrics') + navigation_instance.endpoints_with_navigation + + EXCLUDED_ENDPOINTS + + ("static", "status.show_status", "status.show_redis_status", "metrics") ) for endpoint in all_endpoints: - assert endpoint in covered_endpoints, f'{endpoint} is not listed or excluded' + assert endpoint in covered_endpoints, f"{endpoint} is not listed or excluded" @pytest.mark.parametrize( - 'navigation_instance', + "navigation_instance", navigation_instances, - ids=(x.__class__.__name__ for x in navigation_instances) + ids=(x.__class__.__name__ for x in navigation_instances), ) -def test_raises_on_invalid_navigation_item( - client_request, navigation_instance -): +def test_raises_on_invalid_navigation_item(client_request, navigation_instance): with pytest.raises(expected_exception=KeyError): - navigation_instance.is_selected('foo') + navigation_instance.is_selected("foo") -@pytest.mark.parametrize('endpoint, selected_nav_item', [ - ('main.choose_template', 'Send messages'), - ('main.manage_users', 'Team members'), -]) +@pytest.mark.parametrize( + "endpoint, selected_nav_item", + [ + ("main.choose_template", "Send messages"), + ("main.manage_users", "Team members"), + ], +) def test_a_page_should_nave_selected_navigation_item( client_request, mock_get_service_templates, @@ -367,10 +369,11 @@ def test_a_page_should_nave_selected_navigation_item( selected_nav_item, ): page = client_request.get(endpoint, service_id=SERVICE_ONE_ID) - selected_nav_items = page.select('nav.nav li.usa-sidenav__item a.usa-current') + selected_nav_items = page.select("nav.nav li.usa-sidenav__item a.usa-current") assert len(selected_nav_items) == 1 assert selected_nav_items[0].text.strip() == selected_nav_item + # Hiding nav for the pilot, will uncomment after # @pytest.mark.parametrize('endpoint, selected_nav_item', [ @@ -388,10 +391,13 @@ def test_a_page_should_nave_selected_navigation_item( # assert selected_nav_items[0].text.strip() == selected_nav_item -@pytest.mark.parametrize('endpoint, selected_nav_item', [ - ('main.organization_dashboard', 'Usage'), - ('main.manage_org_users', 'Team members'), -]) +@pytest.mark.parametrize( + "endpoint, selected_nav_item", + [ + ("main.organization_dashboard", "Usage"), + ("main.manage_org_users", "Team members"), + ], +) def test_a_page_should_nave_selected_org_navigation_item( client_request, mock_get_organization, @@ -399,10 +405,10 @@ def test_a_page_should_nave_selected_org_navigation_item( mock_get_invited_users_for_organization, endpoint, selected_nav_item, - mocker + mocker, ): mocker.patch( - 'app.organizations_client.get_services_and_usage', return_value={'services': {}} + "app.organizations_client.get_services_and_usage", return_value={"services": {}} ) # page = client_request.get(endpoint, org_id=ORGANISATION_ID) # selected_nav_item_element = page.select_one('.usa-sidenav a.usa-current') @@ -416,15 +422,13 @@ def test_navigation_urls( mock_get_template_folders, mock_get_api_keys, ): - page = client_request.get('main.choose_template', service_id=SERVICE_ONE_ID) - assert [ - a['href'] for a in page.select('.nav.margin-bottom-5 a') - ] == [ - '/services/{}'.format(SERVICE_ONE_ID), - '/services/{}/templates'.format(SERVICE_ONE_ID), - '/services/{}/users'.format(SERVICE_ONE_ID), - '/services/{}/usage'.format(SERVICE_ONE_ID), - '/services/{}/service-settings'.format(SERVICE_ONE_ID), + page = client_request.get("main.choose_template", service_id=SERVICE_ONE_ID) + assert [a["href"] for a in page.select(".nav.margin-bottom-5 a")] == [ + "/services/{}".format(SERVICE_ONE_ID), + "/services/{}/templates".format(SERVICE_ONE_ID), + "/services/{}/users".format(SERVICE_ONE_ID), + "/services/{}/usage".format(SERVICE_ONE_ID), + "/services/{}/service-settings".format(SERVICE_ONE_ID), # '/services/{}/api'.format(SERVICE_ONE_ID), ] @@ -438,9 +442,9 @@ def test_caseworkers_get_caseworking_navigation( active_caseworking_user, ): client_request.login(active_caseworking_user) - page = client_request.get('main.choose_template', service_id=SERVICE_ONE_ID) - assert normalize_spaces(page.select_one('header + .grid-container nav').text) == ( - 'Send messages Sent messages Team members' + page = client_request.get("main.choose_template", service_id=SERVICE_ONE_ID) + assert normalize_spaces(page.select_one("header + .grid-container nav").text) == ( + "Send messages Sent messages Team members" ) @@ -453,7 +457,7 @@ def test_caseworkers_see_jobs_nav_if_jobs_exist( mock_get_api_keys, ): client_request.login(active_caseworking_user) - page = client_request.get('main.choose_template', service_id=SERVICE_ONE_ID) - assert normalize_spaces(page.select_one('header + .grid-container nav').text) == ( - 'Send messages Sent messages Team members' + page = client_request.get("main.choose_template", service_id=SERVICE_ONE_ID) + assert normalize_spaces(page.select_one("header + .grid-container nav").text) == ( + "Send messages Sent messages Team members" ) diff --git a/tests/app/test_statistics_utils.py b/tests/app/test_statistics_utils.py index 91e5f5313..1e2b585ee 100644 --- a/tests/app/test_statistics_utils.py +++ b/tests/app/test_statistics_utils.py @@ -4,145 +4,155 @@ from app.models.job import Job from app.statistics_utils import add_rates_to, statistics_by_state, sum_of_statistics -@pytest.mark.parametrize('delivery_statistics', [ - None, - [{}], - [{'emails_requested': 0}, {'emails_requested': 0}] -]) +@pytest.mark.parametrize( + "delivery_statistics", + [None, [{}], [{"emails_requested": 0}, {"emails_requested": 0}]], +) def test_sum_of_statistics_puts_in_defaults_of_zero(delivery_statistics): resp = sum_of_statistics(delivery_statistics) assert resp == { - 'emails_delivered': 0, - 'emails_requested': 0, - 'emails_failed': 0, - 'sms_requested': 0, - 'sms_delivered': 0, - 'sms_failed': 0 + "emails_delivered": 0, + "emails_requested": 0, + "emails_failed": 0, + "sms_requested": 0, + "sms_delivered": 0, + "sms_failed": 0, } def test_sum_of_statistics_sums_inputs(): delivery_statistics = [ { - 'emails_delivered': 1, - 'emails_requested': 2, - 'emails_failed': 3, - 'sms_requested': 4, - 'sms_delivered': 5, - 'sms_failed': 6 + "emails_delivered": 1, + "emails_requested": 2, + "emails_failed": 3, + "sms_requested": 4, + "sms_delivered": 5, + "sms_failed": 6, }, { - 'emails_delivered': 10, - 'emails_requested': 20, - 'emails_failed': 30, - 'sms_requested': 40, - 'sms_delivered': 50, - 'sms_failed': 60 - } + "emails_delivered": 10, + "emails_requested": 20, + "emails_failed": 30, + "sms_requested": 40, + "sms_delivered": 50, + "sms_failed": 60, + }, ] resp = sum_of_statistics(delivery_statistics) assert resp == { - 'emails_delivered': 11, - 'emails_requested': 22, - 'emails_failed': 33, - 'sms_requested': 44, - 'sms_delivered': 55, - 'sms_failed': 66 + "emails_delivered": 11, + "emails_requested": 22, + "emails_failed": 33, + "sms_requested": 44, + "sms_delivered": 55, + "sms_failed": 66, } -@pytest.mark.parametrize('emails_failed,emails_requested,expected_failure_rate', [ - (0, 0, '0'), - (0, 1, '0.0'), - (1, 3, '33.3') -]) -def test_add_rates_sets_email_failure_rate(emails_failed, emails_requested, expected_failure_rate): - resp = add_rates_to({ - 'emails_failed': emails_failed, - 'emails_requested': emails_requested, - 'sms_failed': 0, - 'sms_requested': 0 - }) +@pytest.mark.parametrize( + "emails_failed,emails_requested,expected_failure_rate", + [(0, 0, "0"), (0, 1, "0.0"), (1, 3, "33.3")], +) +def test_add_rates_sets_email_failure_rate( + emails_failed, emails_requested, expected_failure_rate +): + resp = add_rates_to( + { + "emails_failed": emails_failed, + "emails_requested": emails_requested, + "sms_failed": 0, + "sms_requested": 0, + } + ) - assert resp['emails_failure_rate'] == expected_failure_rate + assert resp["emails_failure_rate"] == expected_failure_rate -@pytest.mark.parametrize('sms_failed,sms_requested,expected_failure_rate', [ - (0, 0, '0'), - (0, 1, '0.0'), - (1, 3, '33.3') -]) -def test_add_rates_sets_sms_failure_rate(sms_failed, sms_requested, expected_failure_rate): - resp = add_rates_to({ - 'emails_failed': 0, - 'emails_requested': 0, - 'sms_failed': sms_failed, - 'sms_requested': sms_requested - }) +@pytest.mark.parametrize( + "sms_failed,sms_requested,expected_failure_rate", + [(0, 0, "0"), (0, 1, "0.0"), (1, 3, "33.3")], +) +def test_add_rates_sets_sms_failure_rate( + sms_failed, sms_requested, expected_failure_rate +): + resp = add_rates_to( + { + "emails_failed": 0, + "emails_requested": 0, + "sms_failed": sms_failed, + "sms_requested": sms_requested, + } + ) - assert resp['sms_failure_rate'] == expected_failure_rate + assert resp["sms_failure_rate"] == expected_failure_rate def test_add_rates_keeps_original_raw_data(): - resp = add_rates_to({ - 'emails_failed': 1, - 'emails_requested': 2, - 'sms_failed': 3, - 'sms_requested': 4 - }) + resp = add_rates_to( + {"emails_failed": 1, "emails_requested": 2, "sms_failed": 3, "sms_requested": 4} + ) - assert resp['emails_failed'] == 1 - assert resp['emails_requested'] == 2 - assert resp['sms_failed'] == 3 - assert resp['sms_requested'] == 4 + assert resp["emails_failed"] == 1 + assert resp["emails_requested"] == 2 + assert resp["sms_failed"] == 3 + assert resp["sms_requested"] == 4 def test_service_statistics_by_state(): - resp = statistics_by_state({ - 'emails_requested': 3, - 'emails_failed': 1, - 'emails_delivered': 1, - 'sms_requested': 3, - 'sms_failed': 1, - 'sms_delivered': 1 - }) + resp = statistics_by_state( + { + "emails_requested": 3, + "emails_failed": 1, + "emails_delivered": 1, + "sms_requested": 3, + "sms_failed": 1, + "sms_delivered": 1, + } + ) - for message_type in ['email', 'sms']: - assert resp[message_type]['processed'] == 3 - assert resp[message_type]['sending'] == 1 - assert resp[message_type]['delivered'] == 1 - assert resp[message_type]['failed'] == 1 + for message_type in ["email", "sms"]: + assert resp[message_type]["processed"] == 3 + assert resp[message_type]["sending"] == 1 + assert resp[message_type]["delivered"] == 1 + assert resp[message_type]["failed"] == 1 -@pytest.mark.parametrize('failed, delivered, expected_failure_rate', [ - (0, 0, 0), - (0, 1, 0), - (1, 1, 50), - (1, 0, 100), - (1, 4, 20) -]) +@pytest.mark.parametrize( + "failed, delivered, expected_failure_rate", + [(0, 0, 0), (0, 1, 0), (1, 1, 50), (1, 0, 100), (1, 4, 20)], +) def test_add_rate_to_job_calculates_rate(failed, delivered, expected_failure_rate): - resp = Job({ - 'statistics': [ - {'status': 'failed', 'count': failed}, - {'status': 'delivered', 'count': delivered}, - ], - 'id': 'foo', - }) + resp = Job( + { + "statistics": [ + {"status": "failed", "count": failed}, + {"status": "delivered", "count": delivered}, + ], + "id": "foo", + } + ) assert resp.failure_rate == expected_failure_rate def test_add_rate_to_job_preserves_initial_fields(): - resp = Job({ - 'statistics': [ - {'status': 'failed', 'count': 0}, - {'status': 'delivered', 'count': 0}, - ], - 'id': 'foo', - }) + resp = Job( + { + "statistics": [ + {"status": "failed", "count": 0}, + {"status": "delivered", "count": 0}, + ], + "id": "foo", + } + ) - assert resp.notifications_failed == resp.notifications_delivered == resp.failure_rate == 0 - assert resp.id == 'foo' + assert ( + resp.notifications_failed + == resp.notifications_delivered + == resp.failure_rate + == 0 + ) + assert resp.id == "foo" diff --git a/tests/app/test_utils.py b/tests/app/test_utils.py index 9fa05118b..9473fce00 100644 --- a/tests/app/test_utils.py +++ b/tests/app/test_utils.py @@ -3,36 +3,49 @@ import pytest from app.utils import merge_jsonlike -@pytest.mark.parametrize("source_object, destination_object, expected_result", [ - # simple dicts: - ({"a": "b"}, {"c": "d"}, {"a": "b", "c": "d"}), - # dicts with nested dict, both under same key, additive behaviour: - ({"a": {"b": "c"}}, {"a": {"e": "f"}}, {"a": {"b": "c", "e": "f"}}), - # same key in both dicts, value is a string, destination supercedes source: - ({"a": "b"}, {"a": "c"}, {"a": "c"}), - # nested dict added to new key of dict, additive behaviour: - ({"a": "b"}, {"c": {"d": "e"}}, {"a": "b", "c": {"d": "e"}}), - # lists with same length but different items, destination supercedes source: - (["b", "c", "d"], ["b", "e", "f"], ["b", "e", "f"]), - # lists in dicts behave as top level lists - ({"a": ["b", "c", "d"]}, {"a": ["b", "e", "f"]}, {"a": ["b", "e", "f"]}), - # lists with same string in both, at different positions, result in duplicates keeping their positions - (["a", "b", "c", "d"], ["d", "e", "f"], ["d", "e", "f", "d"]), - # lists with same dict in both result in a list with one instance of that dict - ([{"b": "c"}], [{"b": "c"}], [{"b": "c"}]), - # if dicts in lists have different values, they are not merged - ([{"b": "c"}], [{"b": "e"}], [{"b": "e"}]), - # if nested dicts in lists have different keys, additive behaviour - ([{"b": "c"}], [{"d": {"e": "f"}}], [{"b": "c", "d": {"e": "f"}}]), - # if dicts in destination list but not source, they just get added to end of source - ([{"a": "b"}], [{"a": "b"}, {"a": "b"}, {"c": "d"}], [{"a": "b"}, {"a": "b"}, {"c": "d"}]), - # merge a dict with a null object returns that dict (does not work the other way round) - ({"a": {"b": "c"}}, None, {"a": {"b": "c"}}), - # double nested dicts, new adds new Boolean key: value, additive behaviour - ({"a": {"b": {"c": "d"}}}, {"a": {"b": {"e": True}}}, {"a": {"b": {"c": "d", "e": True}}}), - # double nested dicts, both have same key, different values, destination supercedes source - ({"a": {"b": {"c": "d"}}}, {"a": {"b": {"c": "e"}}}, {"a": {"b": {"c": "e"}}}) -]) -def test_merge_jsonlike_merges_jsonlike_objects_correctly(source_object, destination_object, expected_result): +@pytest.mark.parametrize( + "source_object, destination_object, expected_result", + [ + # simple dicts: + ({"a": "b"}, {"c": "d"}, {"a": "b", "c": "d"}), + # dicts with nested dict, both under same key, additive behaviour: + ({"a": {"b": "c"}}, {"a": {"e": "f"}}, {"a": {"b": "c", "e": "f"}}), + # same key in both dicts, value is a string, destination supercedes source: + ({"a": "b"}, {"a": "c"}, {"a": "c"}), + # nested dict added to new key of dict, additive behaviour: + ({"a": "b"}, {"c": {"d": "e"}}, {"a": "b", "c": {"d": "e"}}), + # lists with same length but different items, destination supercedes source: + (["b", "c", "d"], ["b", "e", "f"], ["b", "e", "f"]), + # lists in dicts behave as top level lists + ({"a": ["b", "c", "d"]}, {"a": ["b", "e", "f"]}, {"a": ["b", "e", "f"]}), + # lists with same string in both, at different positions, result in duplicates keeping their positions + (["a", "b", "c", "d"], ["d", "e", "f"], ["d", "e", "f", "d"]), + # lists with same dict in both result in a list with one instance of that dict + ([{"b": "c"}], [{"b": "c"}], [{"b": "c"}]), + # if dicts in lists have different values, they are not merged + ([{"b": "c"}], [{"b": "e"}], [{"b": "e"}]), + # if nested dicts in lists have different keys, additive behaviour + ([{"b": "c"}], [{"d": {"e": "f"}}], [{"b": "c", "d": {"e": "f"}}]), + # if dicts in destination list but not source, they just get added to end of source + ( + [{"a": "b"}], + [{"a": "b"}, {"a": "b"}, {"c": "d"}], + [{"a": "b"}, {"a": "b"}, {"c": "d"}], + ), + # merge a dict with a null object returns that dict (does not work the other way round) + ({"a": {"b": "c"}}, None, {"a": {"b": "c"}}), + # double nested dicts, new adds new Boolean key: value, additive behaviour + ( + {"a": {"b": {"c": "d"}}}, + {"a": {"b": {"e": True}}}, + {"a": {"b": {"c": "d", "e": True}}}, + ), + # double nested dicts, both have same key, different values, destination supercedes source + ({"a": {"b": {"c": "d"}}}, {"a": {"b": {"c": "e"}}}, {"a": {"b": {"c": "e"}}}), + ], +) +def test_merge_jsonlike_merges_jsonlike_objects_correctly( + source_object, destination_object, expected_result +): merge_jsonlike(source_object, destination_object) assert source_object == expected_result diff --git a/tests/app/utils/test_branding.py b/tests/app/utils/test_branding.py index 62b85a5b2..4704c7ac9 100644 --- a/tests/app/utils/test_branding.py +++ b/tests/app/utils/test_branding.py @@ -8,48 +8,58 @@ from tests import organization_json from tests.conftest import create_email_branding -@pytest.mark.parametrize('function', [get_email_choices]) -@pytest.mark.parametrize('org_type, expected_options', [ - ('federal', []), - ('state', []), -]) +@pytest.mark.parametrize("function", [get_email_choices]) +@pytest.mark.parametrize( + "org_type, expected_options", + [ + ("federal", []), + ("state", []), + ], +) def test_get_choices_service_not_assigned_to_org( service_one, function, org_type, expected_options, ): - service_one['organization_type'] = org_type + service_one["organization_type"] = org_type service = Service(service_one) options = function(service) assert list(options) == expected_options -@pytest.mark.parametrize('org_type, branding_id, expected_options', [ - ('federal', None, [ - ('govuk_and_org', 'GOV.UK and Test Organization'), - ('organization', 'Test Organization'), - ]), - ('federal', 'some-branding-id', [ - ('govuk', 'GOV.UK'), # central orgs can switch back to gsa.gov - ('govuk_and_org', 'GOV.UK and Test Organization'), - ('organization', 'Test Organization'), - ]), - ('state', None, [ - ('organization', 'Test Organization') - ]), - ('state', 'some-branding-id', [ - ('organization', 'Test Organization') - ]), - # ('nhs_central', None, [ - # ('nhs', 'NHS') - # ]), - # ('nhs_central', NHS_EMAIL_BRANDING_ID, [ - # # don't show NHS if it's the current branding - # ]), -]) -@pytest.mark.skip(reason='Update for TTS') +@pytest.mark.parametrize( + "org_type, branding_id, expected_options", + [ + ( + "federal", + None, + [ + ("govuk_and_org", "GOV.UK and Test Organization"), + ("organization", "Test Organization"), + ], + ), + ( + "federal", + "some-branding-id", + [ + ("govuk", "GOV.UK"), # central orgs can switch back to gsa.gov + ("govuk_and_org", "GOV.UK and Test Organization"), + ("organization", "Test Organization"), + ], + ), + ("state", None, [("organization", "Test Organization")]), + ("state", "some-branding-id", [("organization", "Test Organization")]), + # ('nhs_central', None, [ + # ('nhs', 'NHS') + # ]), + # ('nhs_central', NHS_EMAIL_BRANDING_ID, [ + # # don't show NHS if it's the current branding + # ]), + ], +) +@pytest.mark.skip(reason="Update for TTS") def test_get_email_choices_service_assigned_to_org( mocker, service_one, @@ -57,37 +67,52 @@ def test_get_email_choices_service_assigned_to_org( branding_id, expected_options, mock_get_service_organization, - mock_get_email_branding + mock_get_email_branding, ): service = Service(service_one) mocker.patch( - 'app.organizations_client.get_organization', - return_value=organization_json(organization_type=org_type) + "app.organizations_client.get_organization", + return_value=organization_json(organization_type=org_type), ) mocker.patch( - 'app.models.service.Service.email_branding_id', + "app.models.service.Service.email_branding_id", new_callable=PropertyMock, - return_value=branding_id + return_value=branding_id, ) options = get_email_choices(service) assert list(options) == expected_options -@pytest.mark.parametrize('org_type, branding_id, expected_options', [ - ('federal', 'some-branding-id', [ - # don't show gsa.gov options as org default supersedes it - ('organization', 'Test Organization'), - ]), - ('federal', 'org-branding-id', [ - # also don't show org option if it's the current branding - ]), - ('state', 'org-branding-id', [ - # don't show org option if it's the current branding - ]), -]) -@pytest.mark.skip(reason='Update for TTS') +@pytest.mark.parametrize( + "org_type, branding_id, expected_options", + [ + ( + "federal", + "some-branding-id", + [ + # don't show gsa.gov options as org default supersedes it + ("organization", "Test Organization"), + ], + ), + ( + "federal", + "org-branding-id", + [ + # also don't show org option if it's the current branding + ], + ), + ( + "state", + "org-branding-id", + [ + # don't show org option if it's the current branding + ], + ), + ], +) +@pytest.mark.skip(reason="Update for TTS") def test_get_email_choices_org_has_default_branding( mocker, service_one, @@ -95,39 +120,47 @@ def test_get_email_choices_org_has_default_branding( branding_id, expected_options, mock_get_service_organization, - mock_get_email_branding + mock_get_email_branding, ): service = Service(service_one) mocker.patch( - 'app.organizations_client.get_organization', + "app.organizations_client.get_organization", return_value=organization_json( - organization_type=org_type, - email_branding_id='org-branding-id' - ) + organization_type=org_type, email_branding_id="org-branding-id" + ), ) mocker.patch( - 'app.models.service.Service.email_branding_id', + "app.models.service.Service.email_branding_id", new_callable=PropertyMock, - return_value=branding_id + return_value=branding_id, ) options = get_email_choices(service) assert list(options) == expected_options -@pytest.mark.parametrize('branding_name, expected_options', [ - ('gsa.gov and something else', [ - ('govuk', 'GOV.UK'), - ('govuk_and_org', 'GOV.UK and Test Organization'), - ('organization', 'Test Organization'), - ]), - ('gsa.gov and test OrganisatioN', [ - ('govuk', 'GOV.UK'), - ('organization', 'Test Organization'), - ]) -]) -@pytest.mark.skip(reason='Update for TTS') +@pytest.mark.parametrize( + "branding_name, expected_options", + [ + ( + "gsa.gov and something else", + [ + ("govuk", "GOV.UK"), + ("govuk_and_org", "GOV.UK and Test Organization"), + ("organization", "Test Organization"), + ], + ), + ( + "gsa.gov and test OrganisatioN", + [ + ("govuk", "GOV.UK"), + ("organization", "Test Organization"), + ], + ), + ], +) +@pytest.mark.skip(reason="Update for TTS") def test_get_email_choices_branding_name_in_use( mocker, service_one, @@ -138,17 +171,17 @@ def test_get_email_choices_branding_name_in_use( service = Service(service_one) mocker.patch( - 'app.organizations_client.get_organization', - return_value=organization_json(organization_type='central') + "app.organizations_client.get_organization", + return_value=organization_json(organization_type="central"), ) mocker.patch( - 'app.models.service.Service.email_branding_id', + "app.models.service.Service.email_branding_id", new_callable=PropertyMock, - return_value='some-branding-id', + return_value="some-branding-id", ) mocker.patch( - 'app.email_branding_client.get_email_branding', - return_value=create_email_branding('_id', {'name': branding_name}) + "app.email_branding_client.get_email_branding", + return_value=create_email_branding("_id", {"name": branding_name}), ) options = get_email_choices(service) diff --git a/tests/app/utils/test_csv.py b/tests/app/utils/test_csv.py index 18e74a8f0..55ea638d9 100644 --- a/tests/app/utils/test_csv.py +++ b/tests/app/utils/test_csv.py @@ -10,19 +10,18 @@ from tests.conftest import fake_uuid def _get_notifications_csv( row_number=1, - recipient='foo@bar.com', - template_name='foo', - template_type='sms', - job_name='bar.csv', - status='Delivered', - created_at='1943-04-19 12:00:00', + recipient="foo@bar.com", + template_name="foo", + template_type="sms", + job_name="bar.csv", + status="Delivered", + created_at="1943-04-19 12:00:00", rows=1, with_links=False, job_id=fake_uuid, created_by_name=None, created_by_email_address=None, ): - def _get( service_id, page=1, @@ -32,30 +31,33 @@ def _get_notifications_csv( links = {} if with_links: links = { - 'prev': '/service/{}/notifications?page=0'.format(service_id), - 'next': '/service/{}/notifications?page=1'.format(service_id), - 'last': '/service/{}/notifications?page=2'.format(service_id) + "prev": "/service/{}/notifications?page=0".format(service_id), + "next": "/service/{}/notifications?page=1".format(service_id), + "last": "/service/{}/notifications?page=2".format(service_id), } data = { - 'notifications': [{ - "row_number": row_number + i, - "to": recipient, - "recipient": recipient, - "client_reference": 'ref 1234', - "template_name": template_name, - "template_type": template_type, - "template": {"name": template_name, "template_type": template_type}, - "job_name": job_name, - "status": status, - "created_at": created_at, - "updated_at": None, - "created_by_name": created_by_name, - "created_by_email_address": created_by_email_address, - } for i in range(rows)], - 'total': rows, - 'page_size': 50, - 'links': links + "notifications": [ + { + "row_number": row_number + i, + "to": recipient, + "recipient": recipient, + "client_reference": "ref 1234", + "template_name": template_name, + "template_type": template_type, + "template": {"name": template_name, "template_type": template_type}, + "job_name": job_name, + "status": status, + "created_at": created_at, + "updated_at": None, + "created_by_name": created_by_name, + "created_by_email_address": created_by_email_address, + } + for i in range(rows) + ], + "total": rows, + "page_size": 50, + "links": links, } return data @@ -63,31 +65,36 @@ def _get_notifications_csv( return _get -@pytest.fixture(scope='function') +@pytest.fixture(scope="function") def _get_notifications_csv_mock( mocker, api_user_active, ): return mocker.patch( - 'app.notification_api_client.get_notifications_for_service', - side_effect=_get_notifications_csv() + "app.notification_api_client.get_notifications_for_service", + side_effect=_get_notifications_csv(), ) -@pytest.mark.parametrize('created_by_name, expected_content', [ - ( - None, [ - 'Recipient,Template,Type,Sent by,Job,Status,Time\n', - 'foo@bar.com,foo,sms,,,Delivered,1943-04-19 12:00:00\r\n', - ] - ), - ( - 'Anne Example', [ - 'Recipient,Template,Type,Sent by,Job,Status,Time\n', - 'foo@bar.com,foo,sms,Anne Example,,Delivered,1943-04-19 12:00:00\r\n', - ] - ), -]) +@pytest.mark.parametrize( + "created_by_name, expected_content", + [ + ( + None, + [ + "Recipient,Template,Type,Sent by,Job,Status,Time\n", + "foo@bar.com,foo,sms,,,Delivered,1943-04-19 12:00:00\r\n", + ], + ), + ( + "Anne Example", + [ + "Recipient,Template,Type,Sent by,Job,Status,Time\n", + "foo@bar.com,foo,sms,Anne Example,,Delivered,1943-04-19 12:00:00\r\n", + ], + ), + ], +) def test_generate_notifications_csv_without_job( notify_admin, mocker, @@ -95,43 +102,98 @@ def test_generate_notifications_csv_without_job( expected_content, ): mocker.patch( - 'app.notification_api_client.get_notifications_for_service', + "app.notification_api_client.get_notifications_for_service", side_effect=_get_notifications_csv( created_by_name=created_by_name, created_by_email_address="sender@email.gsa.gov", job_id=None, - job_name=None - ) + job_name=None, + ), ) assert list(generate_notifications_csv(service_id=fake_uuid)) == expected_content -@pytest.mark.parametrize('original_file_contents, expected_column_headers, expected_1st_row', [ - ( - """ +@pytest.mark.parametrize( + "original_file_contents, expected_column_headers, expected_1st_row", + [ + ( + """ phone_number 2028675309 """, - ['Row number', 'phone_number', 'Template', 'Type', 'Job', 'Status', 'Time'], - ['1', '2028675309', 'foo', 'sms', 'bar.csv', 'Delivered', '1943-04-19 12:00:00'], - ), - ( - """ + ["Row number", "phone_number", "Template", "Type", "Job", "Status", "Time"], + [ + "1", + "2028675309", + "foo", + "sms", + "bar.csv", + "Delivered", + "1943-04-19 12:00:00", + ], + ), + ( + """ phone_number, a, b, c 2028675309, 🐜,🐝,🦀 """, - ['Row number', 'phone_number', 'a', 'b', 'c', 'Template', 'Type', 'Job', 'Status', 'Time'], - ['1', '2028675309', '🐜', '🐝', '🦀', 'foo', 'sms', 'bar.csv', 'Delivered', '1943-04-19 12:00:00'], - ), - ( - """ + [ + "Row number", + "phone_number", + "a", + "b", + "c", + "Template", + "Type", + "Job", + "Status", + "Time", + ], + [ + "1", + "2028675309", + "🐜", + "🐝", + "🦀", + "foo", + "sms", + "bar.csv", + "Delivered", + "1943-04-19 12:00:00", + ], + ), + ( + """ "phone_number", "a", "b", "c" "2028675309","🐜,🐜","🐝,🐝","🦀" """, - ['Row number', 'phone_number', 'a', 'b', 'c', 'Template', 'Type', 'Job', 'Status', 'Time'], - ['1', '2028675309', '🐜,🐜', '🐝,🐝', '🦀', 'foo', 'sms', 'bar.csv', 'Delivered', '1943-04-19 12:00:00'], - ), -]) + [ + "Row number", + "phone_number", + "a", + "b", + "c", + "Template", + "Type", + "Job", + "Status", + "Time", + ], + [ + "1", + "2028675309", + "🐜,🐜", + "🐝,🐝", + "🦀", + "foo", + "sms", + "bar.csv", + "Delivered", + "1943-04-19 12:00:00", + ], + ), + ], +) def test_generate_notifications_csv_returns_correct_csv_file( notify_admin, mocker, @@ -141,11 +203,13 @@ def test_generate_notifications_csv_returns_correct_csv_file( expected_1st_row, ): mocker.patch( - 'app.s3_client.s3_csv_client.s3download', + "app.s3_client.s3_csv_client.s3download", return_value=original_file_contents, ) - csv_content = generate_notifications_csv(service_id='1234', job_id=fake_uuid, template_type='sms') - csv_file = DictReader(StringIO('\n'.join(csv_content))) + csv_content = generate_notifications_csv( + service_id="1234", job_id=fake_uuid, template_type="sms" + ) + csv_file = DictReader(StringIO("\n".join(csv_content))) assert csv_file.fieldnames == expected_column_headers assert next(csv_file) == dict(zip(expected_column_headers, expected_1st_row)) @@ -154,7 +218,7 @@ def test_generate_notifications_csv_only_calls_once_if_no_next_link( notify_admin, _get_notifications_csv_mock, ): - list(generate_notifications_csv(service_id='1234')) + list(generate_notifications_csv(service_id="1234")) assert _get_notifications_csv_mock.call_count == 1 @@ -165,9 +229,8 @@ def test_generate_notifications_csv_calls_twice_if_next_link( mocker, job_id, ): - mocker.patch( - 'app.s3_client.s3_csv_client.s3download', + "app.s3_client.s3_csv_client.s3download", return_value=""" phone_number 2028675304 @@ -180,45 +243,47 @@ def test_generate_notifications_csv_calls_twice_if_next_link( 2028675307 2028675308 2028675309 - """ + """, ) - service_id = '1234' + service_id = "1234" response_with_links = _get_notifications_csv(rows=7, with_links=True) - response_with_no_links = _get_notifications_csv(rows=3, row_number=8, with_links=False) + response_with_no_links = _get_notifications_csv( + rows=3, row_number=8, with_links=False + ) mock_get_notifications = mocker.patch( - 'app.notification_api_client.get_notifications_for_service', + "app.notification_api_client.get_notifications_for_service", side_effect=[ response_with_links(service_id), response_with_no_links(service_id), - ] + ], ) csv_content = generate_notifications_csv( service_id=service_id, job_id=job_id or fake_uuid, - template_type='sms', + template_type="sms", ) - csv = list(DictReader(StringIO('\n'.join(csv_content)))) + csv = list(DictReader(StringIO("\n".join(csv_content)))) assert len(csv) == 10 - assert csv[0]['phone_number'] == '2028675304' - assert csv[9]['phone_number'] == '2028675309' + assert csv[0]["phone_number"] == "2028675304" + assert csv[9]["phone_number"] == "2028675309" assert mock_get_notifications.call_count == 2 # mock_calls[0][2] is the kwargs from first call - assert mock_get_notifications.mock_calls[0][2]['page'] == 1 - assert mock_get_notifications.mock_calls[1][2]['page'] == 2 + assert mock_get_notifications.mock_calls[0][2]["page"] == 1 + assert mock_get_notifications.mock_calls[1][2]["page"] == 2 MockRecipients = namedtuple( - 'RecipientCSV', + "RecipientCSV", [ - 'rows_with_bad_recipients', - 'rows_with_missing_data', - 'rows_with_message_too_long', - 'rows_with_empty_message' - ] + "rows_with_bad_recipients", + "rows_with_missing_data", + "rows_with_message_too_long", + "rows_with_empty_message", + ], ) @@ -226,85 +291,64 @@ MockRecipients = namedtuple( "rows_with_bad_recipients, rows_with_missing_data, " "rows_with_message_too_long, rows_with_empty_message, template_type, expected_errors", [ + ([], [], [], [], "sms", []), + ({2}, [], [], [], "sms", ["fix 1 phone number"]), + ({2, 4, 6}, [], [], [], "sms", ["fix 3 phone numbers"]), + ({1}, [], [], [], "email", ["fix 1 email address"]), + ({2, 4, 6}, [], [], [], "email", ["fix 3 email addresses"]), ( - [], [], [], [], - 'sms', - [] + {2}, + {3}, + [], + [], + "sms", + ["fix 1 phone number", "enter missing data in 1 row"], ), ( - {2}, [], [], [], - 'sms', - ['fix 1 phone number'] + {2, 4, 6, 8}, + {3, 6, 9, 12}, + [], + [], + "sms", + ["fix 4 phone numbers", "enter missing data in 4 rows"], + ), + ({}, {}, {3}, [], "sms", ["shorten the message in 1 row"]), + ({}, {}, {3, 12}, [], "sms", ["shorten the messages in 2 rows"]), + ( + {}, + {}, + {}, + {2}, + "sms", + ["check you have content for the empty message in 1 row"], ), ( - {2, 4, 6}, [], [], [], - 'sms', - ['fix 3 phone numbers'] + {}, + {}, + {}, + {2, 4, 8}, + "sms", + ["check you have content for the empty messages in 3 rows"], ), - ( - {1}, [], [], [], - 'email', - ['fix 1 email address'] - ), - ( - {2, 4, 6}, [], [], [], - 'email', - ['fix 3 email addresses'] - ), - ( - {2}, {3}, [], [], - 'sms', - [ - 'fix 1 phone number', - 'enter missing data in 1 row' - ] - ), - ( - {2, 4, 6, 8}, {3, 6, 9, 12}, [], [], - 'sms', - [ - 'fix 4 phone numbers', - 'enter missing data in 4 rows' - ] - ), - ( - {}, {}, {3}, [], - 'sms', - [ - 'shorten the message in 1 row' - ] - ), - ( - {}, {}, {3, 12}, [], - 'sms', - [ - 'shorten the messages in 2 rows' - ] - ), - ( - {}, {}, {}, {2}, - 'sms', - [ - 'check you have content for the empty message in 1 row' - ] - ), - ( - {}, {}, {}, {2, 4, 8}, - 'sms', - [ - 'check you have content for the empty messages in 3 rows' - ] - ), - ] + ], ) def test_get_errors_for_csv( - rows_with_bad_recipients, rows_with_missing_data, rows_with_message_too_long, rows_with_empty_message, + rows_with_bad_recipients, + rows_with_missing_data, + rows_with_message_too_long, + rows_with_empty_message, template_type, - expected_errors + expected_errors, ): - assert get_errors_for_csv( - MockRecipients( - rows_with_bad_recipients, rows_with_missing_data, rows_with_message_too_long, rows_with_empty_message - ), - template_type - ) == expected_errors + assert ( + get_errors_for_csv( + MockRecipients( + rows_with_bad_recipients, + rows_with_missing_data, + rows_with_message_too_long, + rows_with_empty_message, + ), + template_type, + ) + == expected_errors + ) diff --git a/tests/app/utils/test_login.py b/tests/app/utils/test_login.py index c376561eb..fdcd3c437 100644 --- a/tests/app/utils/test_login.py +++ b/tests/app/utils/test_login.py @@ -5,15 +5,18 @@ from app.models.user import User from app.utils.login import email_needs_revalidating -@freeze_time('2020-11-27T12:00:00') -@pytest.mark.parametrize(('email_access_validated_at', 'expected_result'), ( - ('2020-10-01T11:35:21.726132Z', False), - ('2020-07-23T11:35:21.726132Z', True), -)) +@freeze_time("2020-11-27T12:00:00") +@pytest.mark.parametrize( + ("email_access_validated_at", "expected_result"), + ( + ("2020-10-01T11:35:21.726132Z", False), + ("2020-07-23T11:35:21.726132Z", True), + ), +) def test_email_needs_revalidating( api_user_active, email_access_validated_at, expected_result, ): - api_user_active['email_access_validated_at'] = email_access_validated_at + api_user_active["email_access_validated_at"] = email_access_validated_at assert email_needs_revalidating(User(api_user_active)) == expected_result diff --git a/tests/app/utils/test_pagination.py b/tests/app/utils/test_pagination.py index 2db08209c..4a98a1913 100644 --- a/tests/app/utils/test_pagination.py +++ b/tests/app/utils/test_pagination.py @@ -2,19 +2,21 @@ from app.utils.pagination import generate_next_dict, generate_previous_dict def test_generate_previous_dict(client_request): - result = generate_previous_dict('main.view_jobs', 'foo', 2, {}) - assert 'page=1' in result['url'] - assert result['title'] == 'Previous page' - assert result['label'] == 'page 1' + result = generate_previous_dict("main.view_jobs", "foo", 2, {}) + assert "page=1" in result["url"] + assert result["title"] == "Previous page" + assert result["label"] == "page 1" def test_generate_next_dict(client_request): - result = generate_next_dict('main.view_jobs', 'foo', 2, {}) - assert 'page=3' in result['url'] - assert result['title'] == 'Next page' - assert result['label'] == 'page 3' + result = generate_next_dict("main.view_jobs", "foo", 2, {}) + assert "page=3" in result["url"] + assert result["title"] == "Next page" + assert result["label"] == "page 3" def test_generate_previous_next_dict_adds_other_url_args(client_request): - result = generate_next_dict('main.view_notifications', 'foo', 2, {'message_type': 'blah'}) - assert 'notifications/blah' in result['url'] + result = generate_next_dict( + "main.view_notifications", "foo", 2, {"message_type": "blah"} + ) + assert "notifications/blah" in result["url"] diff --git a/tests/app/utils/test_time.py b/tests/app/utils/test_time.py index d2fa56d81..1ee6bdd24 100644 --- a/tests/app/utils/test_time.py +++ b/tests/app/utils/test_time.py @@ -4,22 +4,28 @@ from freezegun import freeze_time from app.utils.time import get_current_financial_year, is_less_than_days_ago -@pytest.mark.parametrize("date_from_db, expected_result", [ - ('2019-11-17T11:35:21.726132Z', True), - ('2019-11-16T11:35:21.726132Z', False), - ('2019-11-16T11:35:21+0000', False), -]) -@freeze_time('2020-02-14T12:00:00') +@pytest.mark.parametrize( + "date_from_db, expected_result", + [ + ("2019-11-17T11:35:21.726132Z", True), + ("2019-11-16T11:35:21.726132Z", False), + ("2019-11-16T11:35:21+0000", False), + ], +) +@freeze_time("2020-02-14T12:00:00") def test_is_less_than_days_ago(date_from_db, expected_result): assert is_less_than_days_ago(date_from_db, 90) == expected_result -@pytest.mark.parametrize('datetime_string, financial_year', ( - ('2021-01-01T00:00:00+00:00', 2020), # Start of 2021 - ('2021-04-01T03:59:59+00:00', 2020), # One minute before midnight (BST) - ('2021-10-01T04:05:00+00:00', 2021), # Midnight (BST) - ('2021-12-12T12:12:12+01:00', 2021), # Later in the year -)) +@pytest.mark.parametrize( + "datetime_string, financial_year", + ( + ("2021-01-01T00:00:00+00:00", 2020), # Start of 2021 + ("2021-04-01T03:59:59+00:00", 2020), # One minute before midnight (BST) + ("2021-10-01T04:05:00+00:00", 2021), # Midnight (BST) + ("2021-12-12T12:12:12+01:00", 2021), # Later in the year + ), +) def test_get_financial_year(datetime_string, financial_year): with freeze_time(datetime_string): assert get_current_financial_year() == financial_year diff --git a/tests/app/utils/test_user.py b/tests/app/utils/test_user.py index b79e89e9d..e6dde3576 100644 --- a/tests/app/utils/test_user.py +++ b/tests/app/utils/test_user.py @@ -5,32 +5,39 @@ from werkzeug.exceptions import Forbidden from app.utils.user import user_has_permissions -@pytest.mark.parametrize('permissions', ( - [ - # Route has one of the permissions which the user has - 'manage_service' - ], - [ - # Route has more than one of the permissions which the user has - 'manage_templates', 'manage_service' - ], - [ - # Route has one of the permissions which the user has, and one they do not - 'manage_service', 'send_messages', - ], - [ - # Route has no specific permissions required - ], -)) +@pytest.mark.parametrize( + "permissions", + ( + [ + # Route has one of the permissions which the user has + "manage_service" + ], + [ + # Route has more than one of the permissions which the user has + "manage_templates", + "manage_service", + ], + [ + # Route has one of the permissions which the user has, and one they do not + "manage_service", + "send_messages", + ], + [ + # Route has no specific permissions required + ], + ), +) def test_permissions( client_request, permissions, api_user_active, ): - request.view_args.update({'service_id': 'foo'}) + request.view_args.update({"service_id": "foo"}) - api_user_active['permissions'] = {'foo': ['manage_users', 'manage_templates', 'manage_settings']} - api_user_active['services'] = ['foo', 'bar'] + api_user_active["permissions"] = { + "foo": ["manage_users", "manage_templates", "manage_settings"] + } + api_user_active["services"] = ["foo", "bar"] client_request.login(api_user_active) @@ -41,21 +48,26 @@ def test_permissions( index() -@pytest.mark.parametrize('permissions', ( - [ - # Route has a permission which the user doesn’t have - 'send_messages' - ], -)) +@pytest.mark.parametrize( + "permissions", + ( + [ + # Route has a permission which the user doesn’t have + "send_messages" + ], + ), +) def test_permissions_forbidden( client_request, permissions, api_user_active, ): - request.view_args.update({'service_id': 'foo'}) + request.view_args.update({"service_id": "foo"}) - api_user_active['permissions'] = {'foo': ['manage_users', 'manage_templates', 'manage_settings']} - api_user_active['services'] = ['foo', 'bar'] + api_user_active["permissions"] = { + "foo": ["manage_users", "manage_templates", "manage_settings"] + } + api_user_active["services"] = ["foo", "bar"] client_request.login(api_user_active) @@ -71,7 +83,7 @@ def test_restrict_admin_usage( client_request, platform_admin_user, ): - request.view_args.update({'service_id': 'foo'}) + request.view_args.update({"service_id": "foo"}) client_request.login(platform_admin_user) @user_has_permissions(restrict_admin_usage=True) @@ -82,9 +94,7 @@ def test_restrict_admin_usage( index() -def test_no_user_returns_redirect_to_sign_in( - client_request -): +def test_no_user_returns_redirect_to_sign_in(client_request): client_request.logout() @user_has_permissions() @@ -93,17 +103,17 @@ def test_no_user_returns_redirect_to_sign_in( response = index() assert response.status_code == 302 - assert response.location.startswith('/sign-in?next=') + assert response.location.startswith("/sign-in?next=") def test_user_has_permissions_for_organization( client_request, api_user_active, ): - api_user_active['organizations'] = ['org_1', 'org_2'] + api_user_active["organizations"] = ["org_1", "org_2"] client_request.login(api_user_active) - request.view_args = {'org_id': 'org_2'} + request.view_args = {"org_id": "org_2"} @user_has_permissions() def index(): @@ -116,10 +126,10 @@ def test_platform_admin_can_see_orgs_they_dont_have( client_request, platform_admin_user, ): - platform_admin_user['organizations'] = [] + platform_admin_user["organizations"] = [] client_request.login(platform_admin_user) - request.view_args = {'org_id': 'org_2'} + request.view_args = {"org_id": "org_2"} @user_has_permissions() def index(): @@ -148,10 +158,10 @@ def test_user_doesnt_have_permissions_for_organization( client_request, api_user_active, ): - api_user_active['organizations'] = ['org_1', 'org_2'] + api_user_active["organizations"] = ["org_1", "org_2"] client_request.login(api_user_active) - request.view_args = {'org_id': 'org_3'} + request.view_args = {"org_id": "org_3"} @user_has_permissions() def index(): @@ -165,10 +175,12 @@ def test_user_with_no_permissions_to_service_goes_to_templates( client_request, api_user_active, ): - api_user_active['permissions'] = {'foo': ['manage_users', 'manage_templates', 'manage_settings']} - api_user_active['services'] = ['foo', 'bar'] + api_user_active["permissions"] = { + "foo": ["manage_users", "manage_templates", "manage_settings"] + } + api_user_active["services"] = ["foo", "bar"] client_request.login(api_user_active) - request.view_args = {'service_id': 'bar'} + request.view_args = {"service_id": "bar"} @user_has_permissions() def index(): diff --git a/tests/app/utils/test_user_permissions.py b/tests/app/utils/test_user_permissions.py index 34b57fb0f..594b62a27 100644 --- a/tests/app/utils/test_user_permissions.py +++ b/tests/app/utils/test_user_permissions.py @@ -6,16 +6,24 @@ from app.utils.user_permissions import ( ) -@pytest.mark.parametrize('db_permissions,expected_ui_permissions', [ - ( - ['manage_templates'], - {'manage_templates'}, - ), - ( - ['send_texts', 'send_emails', 'manage_templates', 'some_unknown_permission'], - {'send_messages', 'manage_templates', 'some_unknown_permission'}, - ), -]) +@pytest.mark.parametrize( + "db_permissions,expected_ui_permissions", + [ + ( + ["manage_templates"], + {"manage_templates"}, + ), + ( + [ + "send_texts", + "send_emails", + "manage_templates", + "some_unknown_permission", + ], + {"send_messages", "manage_templates", "some_unknown_permission"}, + ), + ], +) def test_translate_permissions_from_db_to_ui( db_permissions, expected_ui_permissions, @@ -25,9 +33,12 @@ def test_translate_permissions_from_db_to_ui( def test_translate_permissions_from_ui_to_db(): - ui_permissions = ['send_messages', 'manage_templates', 'some_unknown_permission'] + ui_permissions = ["send_messages", "manage_templates", "some_unknown_permission"] db_permissions = translate_permissions_from_ui_to_db(ui_permissions) assert db_permissions == { - 'send_texts', 'send_emails', 'manage_templates', 'some_unknown_permission' + "send_texts", + "send_emails", + "manage_templates", + "some_unknown_permission", } diff --git a/tests/conftest.py b/tests/conftest.py index 866b26f42..30b5fe780 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -38,9 +38,9 @@ class ElementNotFound(Exception): pass -@pytest.fixture(scope='session') +@pytest.fixture(scope="session") def notify_admin(): - app = Flask('app') + app = Flask("app") create_app(app) ctx = app.app_context() @@ -50,357 +50,399 @@ def notify_admin(): yield app -@pytest.fixture(scope='function') +@pytest.fixture(scope="function") def service_one(api_user_active): - return service_json(SERVICE_ONE_ID, 'service one', [api_user_active['id']]) + return service_json(SERVICE_ONE_ID, "service one", [api_user_active["id"]]) -@pytest.fixture(scope='function') +@pytest.fixture(scope="function") def service_two(api_user_active): - return service_json(SERVICE_TWO_ID, 'service two', [api_user_active['id']]) + return service_json(SERVICE_TWO_ID, "service two", [api_user_active["id"]]) -@pytest.fixture(scope='function') +@pytest.fixture(scope="function") def multiple_reply_to_email_addresses(mocker): def _get(service_id): return [ { - 'id': '1234', - 'service_id': service_id, - 'email_address': 'test@example.com', - 'is_default': True, - 'created_at': datetime.utcnow(), - 'updated_at': None - }, { - 'id': '5678', - 'service_id': service_id, - 'email_address': 'test2@example.com', - 'is_default': False, - 'created_at': datetime.utcnow(), - 'updated_at': None - }, { - 'id': '9457', - 'service_id': service_id, - 'email_address': 'test3@example.com', - 'is_default': False, - 'created_at': datetime.utcnow(), - 'updated_at': None - } + "id": "1234", + "service_id": service_id, + "email_address": "test@example.com", + "is_default": True, + "created_at": datetime.utcnow(), + "updated_at": None, + }, + { + "id": "5678", + "service_id": service_id, + "email_address": "test2@example.com", + "is_default": False, + "created_at": datetime.utcnow(), + "updated_at": None, + }, + { + "id": "9457", + "service_id": service_id, + "email_address": "test3@example.com", + "is_default": False, + "created_at": datetime.utcnow(), + "updated_at": None, + }, ] return mocker.patch( - 'app.service_api_client.get_reply_to_email_addresses', + "app.service_api_client.get_reply_to_email_addresses", side_effect=_get, ) -@pytest.fixture(scope='function') +@pytest.fixture(scope="function") def no_reply_to_email_addresses(mocker): def _get(service_id): return [] - return mocker.patch('app.service_api_client.get_reply_to_email_addresses', side_effect=_get) + return mocker.patch( + "app.service_api_client.get_reply_to_email_addresses", side_effect=_get + ) -@pytest.fixture(scope='function') +@pytest.fixture(scope="function") def single_reply_to_email_address(mocker): def _get(service_id): return [ { - 'id': '1234', - 'service_id': service_id, - 'email_address': 'test@example.com', - 'is_default': True, - 'created_at': datetime.utcnow(), - 'updated_at': None + "id": "1234", + "service_id": service_id, + "email_address": "test@example.com", + "is_default": True, + "created_at": datetime.utcnow(), + "updated_at": None, } ] - return mocker.patch('app.service_api_client.get_reply_to_email_addresses', side_effect=_get) + return mocker.patch( + "app.service_api_client.get_reply_to_email_addresses", side_effect=_get + ) -@pytest.fixture(scope='function') +@pytest.fixture(scope="function") def get_default_reply_to_email_address(mocker): def _get(service_id, reply_to_email_id): return { - 'id': '1234', - 'service_id': service_id, - 'email_address': 'test@example.com', - 'is_default': True, - 'created_at': datetime.utcnow(), - 'updated_at': None + "id": "1234", + "service_id": service_id, + "email_address": "test@example.com", + "is_default": True, + "created_at": datetime.utcnow(), + "updated_at": None, } - return mocker.patch('app.service_api_client.get_reply_to_email_address', side_effect=_get) + return mocker.patch( + "app.service_api_client.get_reply_to_email_address", side_effect=_get + ) -@pytest.fixture(scope='function') +@pytest.fixture(scope="function") def get_non_default_reply_to_email_address(mocker): def _get(service_id, reply_to_email_id): return { - 'id': '1234', - 'service_id': service_id, - 'email_address': 'test@example.com', - 'is_default': False, - 'created_at': datetime.utcnow(), - 'updated_at': None + "id": "1234", + "service_id": service_id, + "email_address": "test@example.com", + "is_default": False, + "created_at": datetime.utcnow(), + "updated_at": None, } - return mocker.patch('app.service_api_client.get_reply_to_email_address', side_effect=_get) + return mocker.patch( + "app.service_api_client.get_reply_to_email_address", side_effect=_get + ) -@pytest.fixture(scope='function') +@pytest.fixture(scope="function") def mock_add_reply_to_email_address(mocker): def _add_reply_to(service_id, email_address, is_default=False): return - return mocker.patch('app.service_api_client.add_reply_to_email_address', side_effect=_add_reply_to) + return mocker.patch( + "app.service_api_client.add_reply_to_email_address", side_effect=_add_reply_to + ) -@pytest.fixture(scope='function') +@pytest.fixture(scope="function") def mock_update_reply_to_email_address(mocker): - def _update_reply_to(service_id, reply_to_email_id, email_address=None, active=None, is_default=False): + def _update_reply_to( + service_id, reply_to_email_id, email_address=None, active=None, is_default=False + ): return - return mocker.patch('app.service_api_client.update_reply_to_email_address', side_effect=_update_reply_to) + return mocker.patch( + "app.service_api_client.update_reply_to_email_address", + side_effect=_update_reply_to, + ) -@pytest.fixture(scope='function') +@pytest.fixture(scope="function") def multiple_sms_senders(mocker): def _get(service_id): return [ { - 'id': '1234', - 'service_id': service_id, - 'sms_sender': 'Example', - 'is_default': True, - 'created_at': datetime.utcnow(), - 'inbound_number_id': '1234', - 'updated_at': None - }, { - 'id': '5678', - 'service_id': service_id, - 'sms_sender': 'Example 2', - 'is_default': False, - 'created_at': datetime.utcnow(), - 'inbound_number_id': None, - 'updated_at': None - }, { - 'id': '9457', - 'service_id': service_id, - 'sms_sender': 'Example 3', - 'is_default': False, - 'created_at': datetime.utcnow(), - 'inbound_number_id': None, - 'updated_at': None - } + "id": "1234", + "service_id": service_id, + "sms_sender": "Example", + "is_default": True, + "created_at": datetime.utcnow(), + "inbound_number_id": "1234", + "updated_at": None, + }, + { + "id": "5678", + "service_id": service_id, + "sms_sender": "Example 2", + "is_default": False, + "created_at": datetime.utcnow(), + "inbound_number_id": None, + "updated_at": None, + }, + { + "id": "9457", + "service_id": service_id, + "sms_sender": "Example 3", + "is_default": False, + "created_at": datetime.utcnow(), + "inbound_number_id": None, + "updated_at": None, + }, ] - return mocker.patch('app.service_api_client.get_sms_senders', side_effect=_get) + return mocker.patch("app.service_api_client.get_sms_senders", side_effect=_get) -@pytest.fixture(scope='function') +@pytest.fixture(scope="function") def multiple_sms_senders_with_diff_default(mocker): def _get(service_id): return [ { - 'id': '1234', - 'service_id': service_id, - 'sms_sender': 'Example', - 'is_default': True, - 'created_at': datetime.utcnow(), - 'inbound_number_id': None, - 'updated_at': None - }, { - 'id': '5678', - 'service_id': service_id, - 'sms_sender': 'Example 2', - 'is_default': False, - 'created_at': datetime.utcnow(), - 'inbound_number_id': None, - 'updated_at': None - }, { - 'id': '9457', - 'service_id': service_id, - 'sms_sender': 'Example 3', - 'is_default': False, - 'created_at': datetime.utcnow(), - 'inbound_number_id': '12354', - 'updated_at': None - } + "id": "1234", + "service_id": service_id, + "sms_sender": "Example", + "is_default": True, + "created_at": datetime.utcnow(), + "inbound_number_id": None, + "updated_at": None, + }, + { + "id": "5678", + "service_id": service_id, + "sms_sender": "Example 2", + "is_default": False, + "created_at": datetime.utcnow(), + "inbound_number_id": None, + "updated_at": None, + }, + { + "id": "9457", + "service_id": service_id, + "sms_sender": "Example 3", + "is_default": False, + "created_at": datetime.utcnow(), + "inbound_number_id": "12354", + "updated_at": None, + }, ] - return mocker.patch('app.service_api_client.get_sms_senders', side_effect=_get) + return mocker.patch("app.service_api_client.get_sms_senders", side_effect=_get) -@pytest.fixture(scope='function') +@pytest.fixture(scope="function") def multiple_sms_senders_no_inbound(mocker): def _get(service_id): return [ { - 'id': '1234', - 'service_id': service_id, - 'sms_sender': 'Example', - 'is_default': True, - 'created_at': datetime.utcnow(), - 'inbound_number_id': None, - 'updated_at': None - }, { - 'id': '5678', - 'service_id': service_id, - 'sms_sender': 'Example 2', - 'is_default': False, - 'created_at': datetime.utcnow(), - 'inbound_number_id': None, - 'updated_at': None - } + "id": "1234", + "service_id": service_id, + "sms_sender": "Example", + "is_default": True, + "created_at": datetime.utcnow(), + "inbound_number_id": None, + "updated_at": None, + }, + { + "id": "5678", + "service_id": service_id, + "sms_sender": "Example 2", + "is_default": False, + "created_at": datetime.utcnow(), + "inbound_number_id": None, + "updated_at": None, + }, ] - return mocker.patch('app.service_api_client.get_sms_senders', side_effect=_get) + return mocker.patch("app.service_api_client.get_sms_senders", side_effect=_get) -@pytest.fixture(scope='function') +@pytest.fixture(scope="function") def no_sms_senders(mocker): def _get(service_id): return [] - return mocker.patch('app.service_api_client.get_sms_senders', side_effect=_get) + return mocker.patch("app.service_api_client.get_sms_senders", side_effect=_get) -@pytest.fixture(scope='function') +@pytest.fixture(scope="function") def single_sms_sender(mocker): def _get(service_id): return [ { - 'id': '1234', - 'service_id': service_id, - 'sms_sender': 'GOVUK', - 'is_default': True, - 'created_at': datetime.utcnow(), - 'inbound_number_id': None, - 'updated_at': None + "id": "1234", + "service_id": service_id, + "sms_sender": "GOVUK", + "is_default": True, + "created_at": datetime.utcnow(), + "inbound_number_id": None, + "updated_at": None, } ] - return mocker.patch('app.service_api_client.get_sms_senders', side_effect=_get) + return mocker.patch("app.service_api_client.get_sms_senders", side_effect=_get) -@pytest.fixture(scope='function') +@pytest.fixture(scope="function") def get_default_sms_sender(mocker): def _get(service_id, sms_sender_id): return { - 'id': '1234', - 'service_id': service_id, - 'sms_sender': 'GOVUK', - 'is_default': True, - 'created_at': datetime.utcnow(), - 'inbound_number_id': None, - 'updated_at': None + "id": "1234", + "service_id": service_id, + "sms_sender": "GOVUK", + "is_default": True, + "created_at": datetime.utcnow(), + "inbound_number_id": None, + "updated_at": None, } - return mocker.patch('app.service_api_client.get_sms_sender', side_effect=_get) + return mocker.patch("app.service_api_client.get_sms_sender", side_effect=_get) -@pytest.fixture(scope='function') +@pytest.fixture(scope="function") def get_non_default_sms_sender(mocker): def _get(service_id, sms_sender_id): return { - 'id': '1234', - 'service_id': service_id, - 'sms_sender': 'GOVUK', - 'is_default': False, - 'created_at': datetime.utcnow(), - 'inbound_number_id': None, - 'updated_at': None + "id": "1234", + "service_id": service_id, + "sms_sender": "GOVUK", + "is_default": False, + "created_at": datetime.utcnow(), + "inbound_number_id": None, + "updated_at": None, } - return mocker.patch('app.service_api_client.get_sms_sender', side_effect=_get) + return mocker.patch("app.service_api_client.get_sms_sender", side_effect=_get) -@pytest.fixture(scope='function') +@pytest.fixture(scope="function") def mock_add_sms_sender(mocker): - def _add_sms_sender(service_id, sms_sender, is_default=False, inbound_number_id=None): + def _add_sms_sender( + service_id, sms_sender, is_default=False, inbound_number_id=None + ): return - return mocker.patch('app.service_api_client.add_sms_sender', side_effect=_add_sms_sender) + return mocker.patch( + "app.service_api_client.add_sms_sender", side_effect=_add_sms_sender + ) -@pytest.fixture(scope='function') +@pytest.fixture(scope="function") def mock_update_sms_sender(mocker): - def _update_sms_sender(service_id, sms_sender_id, sms_sender=None, active=None, is_default=False): + def _update_sms_sender( + service_id, sms_sender_id, sms_sender=None, active=None, is_default=False + ): return - return mocker.patch('app.service_api_client.update_sms_sender', side_effect=_update_sms_sender) + return mocker.patch( + "app.service_api_client.update_sms_sender", side_effect=_update_sms_sender + ) -@pytest.fixture(scope='function') +@pytest.fixture(scope="function") def multiple_available_inbound_numbers(mocker): def _get(): - return {'data': [ - { - 'active': True, - 'created_at': '2017-10-18T16:57:14.154185Z', - 'id': '781d9c60-7a7e-46b7-9896-7b045b992fa7', - 'number': '2021212124', - 'provider': 'sns', - 'service': None, - 'updated_at': None - }, { - 'active': True, - 'created_at': '2017-10-18T16:57:22.585806Z', - 'id': '781d9c60-7a7e-46b7-9896-7b045b992fa5', - 'number': '2021212125', - 'provider': 'sns', - 'service': None, - 'updated_at': None - }, { - 'active': True, - 'created_at': '2017-10-18T16:57:38.585806Z', - 'id': '781d9c61-7a7e-46b7-9896-7b045b992fa5', - 'number': '2021212126', - 'provider': 'sns', - 'service': None, - 'updated_at': None - } - ]} + return { + "data": [ + { + "active": True, + "created_at": "2017-10-18T16:57:14.154185Z", + "id": "781d9c60-7a7e-46b7-9896-7b045b992fa7", + "number": "2021212124", + "provider": "sns", + "service": None, + "updated_at": None, + }, + { + "active": True, + "created_at": "2017-10-18T16:57:22.585806Z", + "id": "781d9c60-7a7e-46b7-9896-7b045b992fa5", + "number": "2021212125", + "provider": "sns", + "service": None, + "updated_at": None, + }, + { + "active": True, + "created_at": "2017-10-18T16:57:38.585806Z", + "id": "781d9c61-7a7e-46b7-9896-7b045b992fa5", + "number": "2021212126", + "provider": "sns", + "service": None, + "updated_at": None, + }, + ] + } - return mocker.patch('app.inbound_number_client.get_available_inbound_sms_numbers', side_effect=_get) + return mocker.patch( + "app.inbound_number_client.get_available_inbound_sms_numbers", side_effect=_get + ) -@pytest.fixture(scope='function') +@pytest.fixture(scope="function") def no_available_inbound_numbers(mocker): def _get(): - return {'data': []} + return {"data": []} - return mocker.patch('app.inbound_number_client.get_available_inbound_sms_numbers', side_effect=_get) + return mocker.patch( + "app.inbound_number_client.get_available_inbound_sms_numbers", side_effect=_get + ) -@pytest.fixture(scope='function') +@pytest.fixture(scope="function") def fake_uuid(): return sample_uuid() -@pytest.fixture(scope='function') +@pytest.fixture(scope="function") def mock_get_service(mocker, api_user_active): def _get(service_id): - service = service_json(service_id, users=[api_user_active['id']], message_limit=50) - return {'data': service} + service = service_json( + service_id, users=[api_user_active["id"]], message_limit=50 + ) + return {"data": service} - return mocker.patch('app.service_api_client.get_service', side_effect=_get) + return mocker.patch("app.service_api_client.get_service", side_effect=_get) -@pytest.fixture(scope='function') +@pytest.fixture(scope="function") def mock_get_service_statistics(mocker, api_user_active): def _get(service_id, limit_days=None): return { - 'email': {'requested': 0, 'delivered': 0, 'failed': 0}, - 'sms': {'requested': 0, 'delivered': 0, 'failed': 0}, + "email": {"requested": 0, "delivered": 0, "failed": 0}, + "sms": {"requested": 0, "delivered": 0, "failed": 0}, } - return mocker.patch('app.service_api_client.get_service_statistics', side_effect=_get) + return mocker.patch( + "app.service_api_client.get_service_statistics", side_effect=_get + ) -@pytest.fixture(scope='function') +@pytest.fixture(scope="function") def mock_get_detailed_services(mocker, fake_uuid): service_one = service_json( id_=SERVICE_ONE_ID, @@ -418,34 +460,31 @@ def mock_get_detailed_services(mocker, fake_uuid): active=True, restricted=True, ) - service_one['statistics'] = { - 'email': {'requested': 0, 'delivered': 0, 'failed': 0}, - 'sms': {'requested': 0, 'delivered': 0, 'failed': 0}, - + service_one["statistics"] = { + "email": {"requested": 0, "delivered": 0, "failed": 0}, + "sms": {"requested": 0, "delivered": 0, "failed": 0}, } - service_two['statistics'] = { - 'email': {'requested': 0, 'delivered': 0, 'failed': 0}, - 'sms': {'requested': 0, 'delivered': 0, 'failed': 0}, - + service_two["statistics"] = { + "email": {"requested": 0, "delivered": 0, "failed": 0}, + "sms": {"requested": 0, "delivered": 0, "failed": 0}, } - services = {'data': [service_one, service_two]} + services = {"data": [service_one, service_two]} - return mocker.patch('app.service_api_client.get_services', return_value=services) + return mocker.patch("app.service_api_client.get_services", return_value=services) -@pytest.fixture(scope='function') +@pytest.fixture(scope="function") def mock_get_live_service(mocker, api_user_active): def _get(service_id): service = service_json( - service_id, - users=[api_user_active['id']], - restricted=False) - return {'data': service} + service_id, users=[api_user_active["id"]], restricted=False + ) + return {"data": service} - return mocker.patch('app.service_api_client.get_service', side_effect=_get) + return mocker.patch("app.service_api_client.get_service", side_effect=_get) -@pytest.fixture(scope='function') +@pytest.fixture(scope="function") def mock_create_service(mocker): def _create( service_name, @@ -456,48 +495,61 @@ def mock_create_service(mocker): email_from, ): service = service_json( - 101, service_name, [user_id], message_limit=message_limit, restricted=restricted, email_from=email_from) - return service['id'] + 101, + service_name, + [user_id], + message_limit=message_limit, + restricted=restricted, + email_from=email_from, + ) + return service["id"] - return mocker.patch( - 'app.service_api_client.create_service', side_effect=_create) + return mocker.patch("app.service_api_client.create_service", side_effect=_create) -@pytest.fixture(scope='function') +@pytest.fixture(scope="function") def mock_update_service(mocker): def _update(service_id, **kwargs): service = service_json( service_id, - **{key: kwargs[key] for key in kwargs if key in [ - 'name', - 'users', - 'message_limit', - 'active', - 'restricted', - 'email_from', - 'sms_sender', - 'permissions' - ]} + **{ + key: kwargs[key] + for key in kwargs + if key + in [ + "name", + "users", + "message_limit", + "active", + "restricted", + "email_from", + "sms_sender", + "permissions", + ] + } ) - return {'data': service} + return {"data": service} return mocker.patch( - 'app.service_api_client.update_service', side_effect=_update, autospec=True) + "app.service_api_client.update_service", side_effect=_update, autospec=True + ) -@pytest.fixture(scope='function') +@pytest.fixture(scope="function") def mock_update_service_raise_httperror_duplicate_name(mocker): - def _update( - service_id, - **kwargs - ): - json_mock = Mock(return_value={'message': {'name': ["Duplicate service name '{}'".format(kwargs.get('name'))]}}) + def _update(service_id, **kwargs): + json_mock = Mock( + return_value={ + "message": { + "name": ["Duplicate service name '{}'".format(kwargs.get("name"))] + } + } + ) resp_mock = Mock(status_code=400, json=json_mock) http_error = HTTPError(response=resp_mock, message="Default message") raise http_error - return mocker.patch( - 'app.service_api_client.update_service', side_effect=_update) + return mocker.patch("app.service_api_client.update_service", side_effect=_update) SERVICE_ONE_ID = "596364a0-858e-42c8-9062-a8fe822260eb" @@ -508,71 +560,99 @@ TEMPLATE_ONE_ID = "b22d7d94-2197-4a7d-a8e7-fd5f9770bf48" USER_ONE_ID = "7b395b52-c6c1-469c-9d61-54166461c1ab" -@pytest.fixture(scope='function') +@pytest.fixture(scope="function") def mock_get_services(mocker, active_user_with_permissions): def _get_services(params_dict=None): service_one = service_json( - SERVICE_ONE_ID, "service_one", [active_user_with_permissions['id']], 1000, True, False) + SERVICE_ONE_ID, + "service_one", + [active_user_with_permissions["id"]], + 1000, + True, + False, + ) service_two = service_json( - SERVICE_TWO_ID, "service_two", [active_user_with_permissions['id']], 1000, True, False) - return {'data': [service_one, service_two]} + SERVICE_TWO_ID, + "service_two", + [active_user_with_permissions["id"]], + 1000, + True, + False, + ) + return {"data": [service_one, service_two]} return mocker.patch( - 'app.service_api_client.get_services', side_effect=_get_services) + "app.service_api_client.get_services", side_effect=_get_services + ) -@pytest.fixture(scope='function') +@pytest.fixture(scope="function") def mock_get_services_with_no_services(mocker): def _get_services(params_dict=None): - return {'data': []} + return {"data": []} return mocker.patch( - 'app.service_api_client.get_services', side_effect=_get_services) + "app.service_api_client.get_services", side_effect=_get_services + ) -@pytest.fixture(scope='function') +@pytest.fixture(scope="function") def mock_get_services_with_one_service(mocker, api_user_active): def _get_services(params_dict=None): - return {'data': [service_json( - SERVICE_ONE_ID, "service_one", [api_user_active['id']], 1000, True, True - )]} + return { + "data": [ + service_json( + SERVICE_ONE_ID, + "service_one", + [api_user_active["id"]], + 1000, + True, + True, + ) + ] + } return mocker.patch( - 'app.service_api_client.get_services', side_effect=_get_services) + "app.service_api_client.get_services", side_effect=_get_services + ) -@pytest.fixture(scope='function') +@pytest.fixture(scope="function") def mock_get_service_template(mocker): def _get(service_id, template_id, version=None): template = template_json( - service_id, template_id, "Two week reminder", "sms", "Template content with & entity") + service_id, + template_id, + "Two week reminder", + "sms", + "Template content with & entity", + ) if version: - template.update({'version': version}) - return {'data': template} + template.update({"version": version}) + return {"data": template} - return mocker.patch( - 'app.service_api_client.get_service_template', - side_effect=_get - ) + return mocker.patch("app.service_api_client.get_service_template", side_effect=_get) -@pytest.fixture(scope='function') +@pytest.fixture(scope="function") def mock_get_service_template_with_priority(mocker): def _get(service_id, template_id, version=None): template = template_json( - service_id, template_id, "Two week reminder", "sms", "Template content with & entity", - process_type='priority') + service_id, + template_id, + "Two week reminder", + "sms", + "Template content with & entity", + process_type="priority", + ) if version: - template.update({'version': version}) - return {'data': template} + template.update({"version": version}) + return {"data": template} - return mocker.patch( - 'app.service_api_client.get_service_template', - side_effect=_get - ) + return mocker.patch("app.service_api_client.get_service_template", side_effect=_get) -@pytest.fixture(scope='function') +@pytest.fixture(scope="function") def mock_get_deleted_template(mocker): def _get(service_id, template_id, version=None): template = template_json( @@ -581,109 +661,99 @@ def mock_get_deleted_template(mocker): "Two week reminder", "sms", "Template content with & entity", - archived=True + archived=True, ) if version: - template.update({'version': version}) - return {'data': template} + template.update({"version": version}) + return {"data": template} - return mocker.patch( - 'app.service_api_client.get_service_template', - side_effect=_get - ) + return mocker.patch("app.service_api_client.get_service_template", side_effect=_get) -@pytest.fixture(scope='function') +@pytest.fixture(scope="function") def mock_get_template_version(mocker, api_user_active): def _get(service_id, template_id, version): template_version = template_version_json( - service_id, - template_id, - api_user_active, - version=version + service_id, template_id, api_user_active, version=version ) - return {'data': template_version} + return {"data": template_version} - return mocker.patch( - 'app.service_api_client.get_service_template', - side_effect=_get - ) + return mocker.patch("app.service_api_client.get_service_template", side_effect=_get) -@pytest.fixture(scope='function') +@pytest.fixture(scope="function") def mock_get_template_versions(mocker, api_user_active): def _get(service_id, template_id): template_version = template_version_json( - service_id, - template_id, - api_user_active, - version=1 + service_id, template_id, api_user_active, version=1 ) - return {'data': [template_version]} + return {"data": [template_version]} return mocker.patch( - 'app.service_api_client.get_service_template_versions', - side_effect=_get + "app.service_api_client.get_service_template_versions", side_effect=_get ) -@pytest.fixture(scope='function') +@pytest.fixture(scope="function") def mock_get_service_template_with_placeholders(mocker): def _get(service_id, template_id, version=None): template = template_json( - service_id, template_id, "Two week reminder", "sms", "((name)), Template content with & entity" + service_id, + template_id, + "Two week reminder", + "sms", + "((name)), Template content with & entity", ) - return {'data': template} + return {"data": template} - return mocker.patch( - 'app.service_api_client.get_service_template', - side_effect=_get - ) + return mocker.patch("app.service_api_client.get_service_template", side_effect=_get) -@pytest.fixture(scope='function') +@pytest.fixture(scope="function") def mock_get_empty_service_template_with_optional_placeholder(mocker): def _get(service_id, template_id, version=None): template = template_json( - service_id, template_id, name="Optional content", content="((show_placeholder??Some content))" + service_id, + template_id, + name="Optional content", + content="((show_placeholder??Some content))", ) - return {'data': template} + return {"data": template} - return mocker.patch( - 'app.service_api_client.get_service_template', - side_effect=_get - ) + return mocker.patch("app.service_api_client.get_service_template", side_effect=_get) -@pytest.fixture(scope='function') +@pytest.fixture(scope="function") def mock_get_service_template_with_multiple_placeholders(mocker): def _get(service_id, template_id, version=None): template = template_json( - service_id, template_id, "Two week reminder", "sms", "((one)) ((two)) ((three))" + service_id, + template_id, + "Two week reminder", + "sms", + "((one)) ((two)) ((three))", ) - return {'data': template} + return {"data": template} - return mocker.patch( - 'app.service_api_client.get_service_template', - side_effect=_get - ) + return mocker.patch("app.service_api_client.get_service_template", side_effect=_get) -@pytest.fixture(scope='function') +@pytest.fixture(scope="function") def mock_get_service_template_with_placeholders_same_as_recipient(mocker): def _get(service_id, template_id, version=None): template = template_json( - service_id, template_id, "Two week reminder", "sms", "((name)) ((date)) ((PHONENUMBER))" + service_id, + template_id, + "Two week reminder", + "sms", + "((name)) ((date)) ((PHONENUMBER))", ) - return {'data': template} + return {"data": template} - return mocker.patch( - 'app.service_api_client.get_service_template', - side_effect=_get - ) + return mocker.patch("app.service_api_client.get_service_template", side_effect=_get) -@pytest.fixture(scope='function') +@pytest.fixture(scope="function") def mock_get_service_email_template(mocker): def _get(service_id, template_id, version=None): template = template_json( @@ -695,13 +765,12 @@ def mock_get_service_email_template(mocker): "Your ((thing)) is due soon", redact_personalisation=False, ) - return {'data': template} + return {"data": template} - return mocker.patch( - 'app.service_api_client.get_service_template', side_effect=_get) + return mocker.patch("app.service_api_client.get_service_template", side_effect=_get) -@pytest.fixture(scope='function') +@pytest.fixture(scope="function") def mock_get_service_email_template_without_placeholders(mocker): def _get(service_id, template_id, version=None): template = template_json( @@ -713,68 +782,109 @@ def mock_get_service_email_template_without_placeholders(mocker): subject="Your thing is due soon", redact_personalisation=False, ) - return {'data': template} + return {"data": template} - return mocker.patch( - 'app.service_api_client.get_service_template', side_effect=_get) + return mocker.patch("app.service_api_client.get_service_template", side_effect=_get) -@pytest.fixture(scope='function') +@pytest.fixture(scope="function") def mock_create_service_template(mocker, fake_uuid): - def _create(name, type_, content, service, subject=None, process_type=None, parent_folder_id=None): - template = template_json(fake_uuid, name, type_, content, service, process_type, parent_folder_id) - return {'data': template} + def _create( + name, + type_, + content, + service, + subject=None, + process_type=None, + parent_folder_id=None, + ): + template = template_json( + fake_uuid, name, type_, content, service, process_type, parent_folder_id + ) + return {"data": template} return mocker.patch( - 'app.service_api_client.create_service_template', - side_effect=_create) + "app.service_api_client.create_service_template", side_effect=_create + ) -@pytest.fixture(scope='function') +@pytest.fixture(scope="function") def mock_update_service_template(mocker): def _update(id_, name, type_, content, service, subject=None, process_type=None): - template = template_json(service, id_, name, type_, content, subject, process_type) - return {'data': template} + template = template_json( + service, id_, name, type_, content, subject, process_type + ) + return {"data": template} return mocker.patch( - 'app.service_api_client.update_service_template', - side_effect=_update) + "app.service_api_client.update_service_template", side_effect=_update + ) -@pytest.fixture(scope='function') +@pytest.fixture(scope="function") def mock_create_service_template_content_too_big(mocker): - def _create(name, type_, content, service, subject=None, process_type=None, parent_folder_id=None): - json_mock = Mock(return_value={ - 'message': {'content': ["Content has a character count greater than the limit of 459"]}, - 'result': 'error' - }) + def _create( + name, + type_, + content, + service, + subject=None, + process_type=None, + parent_folder_id=None, + ): + json_mock = Mock( + return_value={ + "message": { + "content": [ + "Content has a character count greater than the limit of 459" + ] + }, + "result": "error", + } + ) resp_mock = Mock(status_code=400, json=json_mock) http_error = HTTPError( response=resp_mock, - message={'content': ["Content has a character count greater than the limit of 459"]}) + message={ + "content": [ + "Content has a character count greater than the limit of 459" + ] + }, + ) raise http_error return mocker.patch( - 'app.service_api_client.create_service_template', - side_effect=_create) + "app.service_api_client.create_service_template", side_effect=_create + ) -@pytest.fixture(scope='function') +@pytest.fixture(scope="function") def mock_update_service_template_400_content_too_big(mocker): def _update(id_, name, type_, content, service, subject=None, process_type=None): - json_mock = Mock(return_value={ - 'message': {'content': ["Content has a character count greater than the limit of 459"]}, - 'result': 'error' - }) + json_mock = Mock( + return_value={ + "message": { + "content": [ + "Content has a character count greater than the limit of 459" + ] + }, + "result": "error", + } + ) resp_mock = Mock(status_code=400, json=json_mock) http_error = HTTPError( response=resp_mock, - message={'content': ["Content has a character count greater than the limit of 459"]}) + message={ + "content": [ + "Content has a character count greater than the limit of 459" + ] + }, + ) raise http_error return mocker.patch( - 'app.service_api_client.update_service_template', - side_effect=_update) + "app.service_api_client.update_service_template", side_effect=_update + ) def create_service_templates(service_id, number_of_templates=4): @@ -785,123 +895,136 @@ def create_service_templates(service_id, number_of_templates=4): template_number = "two" if _ % 2 == 0 else "one" template_type = template_types[(_ % 4) - 1] - service_templates.append(template_json( - service_id, - TEMPLATE_ONE_ID if _ == 1 else str(generate_uuid()), - "{}_template_{}".format(template_type, template_number), - template_type, - "{} template {} content".format(template_type, template_number), - subject="{} template {} subject".format(template_type, template_number) - if template_type == "email" else None - )) + service_templates.append( + template_json( + service_id, + TEMPLATE_ONE_ID if _ == 1 else str(generate_uuid()), + "{}_template_{}".format(template_type, template_number), + template_type, + "{} template {} content".format(template_type, template_number), + subject="{} template {} subject".format(template_type, template_number) + if template_type == "email" + else None, + ) + ) - return {'data': service_templates} + return {"data": service_templates} def _template(template_type, name, parent=None, template_id=None): return { - 'id': template_id or str(uuid4()), - 'name': name, - 'template_type': template_type, - 'folder': parent, + "id": template_id or str(uuid4()), + "name": name, + "template_type": template_type, + "folder": parent, } -@pytest.fixture(scope='function') +@pytest.fixture(scope="function") def mock_get_service_templates(mocker): def _create(service_id): return create_service_templates(service_id) return mocker.patch( - 'app.service_api_client.get_service_templates', - side_effect=_create) + "app.service_api_client.get_service_templates", side_effect=_create + ) -@pytest.fixture(scope='function') +@pytest.fixture(scope="function") def mock_get_more_service_templates_than_can_fit_onscreen(mocker): def _create(service_id): return create_service_templates(service_id, number_of_templates=20) return mocker.patch( - 'app.service_api_client.get_service_templates', - side_effect=_create) + "app.service_api_client.get_service_templates", side_effect=_create + ) -@pytest.fixture(scope='function') +@pytest.fixture(scope="function") def mock_get_service_templates_when_no_templates_exist(mocker): - def _create(service_id): - return {'data': []} + return {"data": []} return mocker.patch( - 'app.service_api_client.get_service_templates', - side_effect=_create) + "app.service_api_client.get_service_templates", side_effect=_create + ) -@pytest.fixture(scope='function') +@pytest.fixture(scope="function") def mock_get_service_templates_with_only_one_template(mocker): - def _get(service_id): - return {'data': [ - template_json( - service_id, generate_uuid(), "sms_template_one", "sms", "sms template one content" - ) - ]} + return { + "data": [ + template_json( + service_id, + generate_uuid(), + "sms_template_one", + "sms", + "sms template one content", + ) + ] + } return mocker.patch( - 'app.service_api_client.get_service_templates', - side_effect=_get) + "app.service_api_client.get_service_templates", side_effect=_get + ) -@pytest.fixture(scope='function') +@pytest.fixture(scope="function") def mock_delete_service_template(mocker): def _delete(service_id, template_id): template = template_json( - service_id, template_id, "Template to delete", "sms", "content to be deleted") - return {'data': template} + service_id, + template_id, + "Template to delete", + "sms", + "content to be deleted", + ) + return {"data": template} return mocker.patch( - 'app.service_api_client.delete_service_template', side_effect=_delete) + "app.service_api_client.delete_service_template", side_effect=_delete + ) -@pytest.fixture(scope='function') +@pytest.fixture(scope="function") def mock_redact_template(mocker): - return mocker.patch('app.service_api_client.redact_service_template') + return mocker.patch("app.service_api_client.redact_service_template") -@pytest.fixture(scope='function') +@pytest.fixture(scope="function") def mock_update_service_template_sender(mocker): def _update(service_id, template_id, reply_to): return return mocker.patch( - 'app.service_api_client.update_service_template_sender', - side_effect=_update + "app.service_api_client.update_service_template_sender", side_effect=_update ) -@pytest.fixture(scope='function') +@pytest.fixture(scope="function") def api_user_pending(fake_uuid): - return create_user( - id=fake_uuid, - state='pending' + return create_user(id=fake_uuid, state="pending") + + +@pytest.fixture(scope="function") +def platform_admin_user(fake_uuid): + return create_platform_admin_user( + permissions={ + SERVICE_ONE_ID: [ + "send_texts", + "send_emails", + "manage_users", + "manage_templates", + "manage_settings", + "manage_api_keys", + "view_activity", + ] + } ) -@pytest.fixture(scope='function') -def platform_admin_user(fake_uuid): - return create_platform_admin_user(permissions={SERVICE_ONE_ID: [ - 'send_texts', - 'send_emails', - 'manage_users', - 'manage_templates', - 'manage_settings', - 'manage_api_keys', - 'view_activity' - ]}) - - -@pytest.fixture(scope='function') +@pytest.fixture(scope="function") def platform_admin_user_no_service_permissions(): """ this fixture is for situations where we want to test that platform admin can access @@ -910,20 +1033,17 @@ def platform_admin_user_no_service_permissions(): return create_platform_admin_user() -@pytest.fixture(scope='function') +@pytest.fixture(scope="function") def api_user_active(): return create_api_user_active() -@pytest.fixture(scope='function') +@pytest.fixture(scope="function") def api_user_active_email_auth(fake_uuid): - return create_user( - id=fake_uuid, - auth_type='email_auth' - ) + return create_user(id=fake_uuid, auth_type="email_auth") -@pytest.fixture(scope='function') +@pytest.fixture(scope="function") def active_user_with_permissions_no_mobile(fake_uuid): return create_service_one_admin( id=fake_uuid, @@ -931,29 +1051,29 @@ def active_user_with_permissions_no_mobile(fake_uuid): ) -@pytest.fixture(scope='function') +@pytest.fixture(scope="function") def api_nongov_user_active(fake_uuid): return create_service_one_admin( id=fake_uuid, - email_address='someuser@example.com', + email_address="someuser@example.com", ) -@pytest.fixture(scope='function') +@pytest.fixture(scope="function") def active_user_with_permissions(fake_uuid): return create_active_user_with_permissions() -@pytest.fixture(scope='function') +@pytest.fixture(scope="function") def active_user_with_permission_to_two_services(fake_uuid): permissions = [ - 'send_texts', - 'send_emails', - 'manage_users', - 'manage_templates', - 'manage_settings', - 'manage_api_keys', - 'view_activity', + "send_texts", + "send_emails", + "manage_users", + "manage_templates", + "manage_settings", + "manage_api_keys", + "view_activity", ] return create_user( @@ -967,22 +1087,20 @@ def active_user_with_permission_to_two_services(fake_uuid): ) -@pytest.fixture(scope='function') +@pytest.fixture(scope="function") def active_user_with_permission_to_other_service( - active_user_with_permission_to_two_services + active_user_with_permission_to_two_services, ): - active_user_with_permission_to_two_services['permissions'].pop(SERVICE_ONE_ID) - active_user_with_permission_to_two_services['services'].pop(0) - active_user_with_permission_to_two_services['name'] = ( - 'Service Two User' - ) - active_user_with_permission_to_two_services['email_address'] = ( - 'service-two-user@test.gsa.gov' - ) + active_user_with_permission_to_two_services["permissions"].pop(SERVICE_ONE_ID) + active_user_with_permission_to_two_services["services"].pop(0) + active_user_with_permission_to_two_services["name"] = "Service Two User" + active_user_with_permission_to_two_services[ + "email_address" + ] = "service-two-user@test.gsa.gov" return active_user_with_permission_to_two_services -@pytest.fixture(scope='function') +@pytest.fixture(scope="function") def active_caseworking_user(): return create_active_caseworking_user() @@ -997,7 +1115,7 @@ def active_user_no_settings_permission(): return create_active_user_no_settings_permission() -@pytest.fixture(scope='function') +@pytest.fixture(scope="function") def api_user_locked(fake_uuid): return create_user( id=fake_uuid, @@ -1006,7 +1124,7 @@ def api_user_locked(fake_uuid): ) -@pytest.fixture(scope='function') +@pytest.fixture(scope="function") def api_user_request_password_reset(fake_uuid): return create_user( id=fake_uuid, @@ -1014,7 +1132,7 @@ def api_user_request_password_reset(fake_uuid): ) -@pytest.fixture(scope='function') +@pytest.fixture(scope="function") def api_user_changed_password(fake_uuid): return create_user( id=fake_uuid, @@ -1023,257 +1141,252 @@ def api_user_changed_password(fake_uuid): ) -@pytest.fixture(scope='function') +@pytest.fixture(scope="function") def mock_send_change_email_verification(mocker): - return mocker.patch('app.user_api_client.send_change_email_verification') + return mocker.patch("app.user_api_client.send_change_email_verification") -@pytest.fixture(scope='function') +@pytest.fixture(scope="function") def mock_register_user(mocker, api_user_pending): def _register(name, email_address, mobile_number, password, auth_type): - api_user_pending['name'] = name - api_user_pending['email_address'] = email_address - api_user_pending['mobile_number'] = mobile_number - api_user_pending['password'] = password - api_user_pending['auth_type'] = auth_type + api_user_pending["name"] = name + api_user_pending["email_address"] = email_address + api_user_pending["mobile_number"] = mobile_number + api_user_pending["password"] = password + api_user_pending["auth_type"] = auth_type return api_user_pending - return mocker.patch('app.user_api_client.register_user', side_effect=_register) + return mocker.patch("app.user_api_client.register_user", side_effect=_register) -@pytest.fixture(scope='function') +@pytest.fixture(scope="function") def mock_get_non_govuser(mocker, api_user_active): - api_user_active['email_address'] = 'someuser@example.com' + api_user_active["email_address"] = "someuser@example.com" def _get_user(id_): - api_user_active['id'] = id_ + api_user_active["id"] = id_ return api_user_active - return mocker.patch( - 'app.user_api_client.get_user', side_effect=_get_user) + return mocker.patch("app.user_api_client.get_user", side_effect=_get_user) -@pytest.fixture(scope='function') +@pytest.fixture(scope="function") def mock_get_user(mocker, api_user_active): def _get_user(id_): - api_user_active['id'] = id_ + api_user_active["id"] = id_ return api_user_active - return mocker.patch( - 'app.user_api_client.get_user', side_effect=_get_user) + return mocker.patch("app.user_api_client.get_user", side_effect=_get_user) -@pytest.fixture(scope='function') +@pytest.fixture(scope="function") def mock_get_locked_user(mocker, api_user_locked): def _get_user(id_): - api_user_locked['id'] = id_ + api_user_locked["id"] = id_ return api_user_locked - return mocker.patch( - 'app.user_api_client.get_user', side_effect=_get_user) + return mocker.patch("app.user_api_client.get_user", side_effect=_get_user) -@pytest.fixture(scope='function') +@pytest.fixture(scope="function") def mock_get_user_pending(mocker, api_user_pending): - return mocker.patch( - 'app.user_api_client.get_user', return_value=api_user_pending) + return mocker.patch("app.user_api_client.get_user", return_value=api_user_pending) -@pytest.fixture(scope='function') +@pytest.fixture(scope="function") def mock_get_user_by_email(mocker, api_user_active): def _get_user(email_address): - api_user_active['email_address'] = email_address + api_user_active["email_address"] = email_address return api_user_active - return mocker.patch('app.user_api_client.get_user_by_email', side_effect=_get_user) + return mocker.patch("app.user_api_client.get_user_by_email", side_effect=_get_user) -@pytest.fixture(scope='function') +@pytest.fixture(scope="function") def mock_dont_get_user_by_email(mocker): def _get_user(email_address): return None return mocker.patch( - 'app.user_api_client.get_user_by_email', - side_effect=_get_user, - autospec=True + "app.user_api_client.get_user_by_email", side_effect=_get_user, autospec=True ) -@pytest.fixture(scope='function') -def mock_get_user_by_email_request_password_reset(mocker, api_user_request_password_reset): +@pytest.fixture(scope="function") +def mock_get_user_by_email_request_password_reset( + mocker, api_user_request_password_reset +): return mocker.patch( - 'app.user_api_client.get_user_by_email', - return_value=api_user_request_password_reset) + "app.user_api_client.get_user_by_email", + return_value=api_user_request_password_reset, + ) -@pytest.fixture(scope='function') +@pytest.fixture(scope="function") def mock_get_user_by_email_user_changed_password(mocker, api_user_changed_password): return mocker.patch( - 'app.user_api_client.get_user_by_email', - return_value=api_user_changed_password) + "app.user_api_client.get_user_by_email", return_value=api_user_changed_password + ) -@pytest.fixture(scope='function') +@pytest.fixture(scope="function") def mock_get_user_by_email_locked(mocker, api_user_locked): return mocker.patch( - 'app.user_api_client.get_user_by_email', return_value=api_user_locked) + "app.user_api_client.get_user_by_email", return_value=api_user_locked + ) -@pytest.fixture(scope='function') +@pytest.fixture(scope="function") def mock_get_user_by_email_pending(mocker, api_user_pending): return mocker.patch( - 'app.user_api_client.get_user_by_email', - return_value=api_user_pending) + "app.user_api_client.get_user_by_email", return_value=api_user_pending + ) -@pytest.fixture(scope='function') +@pytest.fixture(scope="function") def mock_get_user_by_email_not_found(mocker, api_user_active): def _get_user(email): - json_mock = Mock(return_value={'message': "Not found", 'result': 'error'}) + json_mock = Mock(return_value={"message": "Not found", "result": "error"}) resp_mock = Mock(status_code=404, json=json_mock) http_error = HTTPError(response=resp_mock, message="Default message") raise http_error - return mocker.patch( - 'app.user_api_client.get_user_by_email', - side_effect=_get_user) + return mocker.patch("app.user_api_client.get_user_by_email", side_effect=_get_user) -@pytest.fixture(scope='function') +@pytest.fixture(scope="function") def mock_verify_password(mocker): def _verify_password(user, password): return True return mocker.patch( - 'app.user_api_client.verify_password', - side_effect=_verify_password) + "app.user_api_client.verify_password", side_effect=_verify_password + ) -@pytest.fixture(scope='function') +@pytest.fixture(scope="function") def mock_update_user_password(mocker, api_user_active): def _update(user_id, password): - api_user_active['id'] = user_id + api_user_active["id"] = user_id return api_user_active - return mocker.patch('app.user_api_client.update_password', side_effect=_update) + return mocker.patch("app.user_api_client.update_password", side_effect=_update) -@pytest.fixture(scope='function') +@pytest.fixture(scope="function") def mock_update_user_attribute(mocker, api_user_active): def _update(user_id, **kwargs): - api_user_active['id'] = user_id + api_user_active["id"] = user_id return api_user_active - return mocker.patch('app.user_api_client.update_user_attribute', side_effect=_update) + return mocker.patch( + "app.user_api_client.update_user_attribute", side_effect=_update + ) @pytest.fixture def mock_activate_user(mocker, api_user_active): def _activate(user_id): - api_user_active['id'] = user_id - return {'data': api_user_active} + api_user_active["id"] = user_id + return {"data": api_user_active} - return mocker.patch('app.user_api_client.activate_user', side_effect=_activate) + return mocker.patch("app.user_api_client.activate_user", side_effect=_activate) -@pytest.fixture(scope='function') +@pytest.fixture(scope="function") def mock_email_is_not_already_in_use(mocker): - return mocker.patch('app.user_api_client.get_user_by_email_or_none', return_value=None) + return mocker.patch( + "app.user_api_client.get_user_by_email_or_none", return_value=None + ) -@pytest.fixture(scope='function') +@pytest.fixture(scope="function") def mock_revoke_api_key(mocker): def _revoke(service_id, key_id): return {} - return mocker.patch( - 'app.api_key_api_client.revoke_api_key', - side_effect=_revoke) + return mocker.patch("app.api_key_api_client.revoke_api_key", side_effect=_revoke) -@pytest.fixture(scope='function') +@pytest.fixture(scope="function") def mock_get_api_keys(mocker, fake_uuid): def _get_keys(service_id, key_id=None): - keys = {'apiKeys': [ - api_key_json(id_=fake_uuid, name='some key name',), - api_key_json(id_='1234567', name='another key name', expiry_date=str(date.fromtimestamp(0))) - ]} + keys = { + "apiKeys": [ + api_key_json( + id_=fake_uuid, + name="some key name", + ), + api_key_json( + id_="1234567", + name="another key name", + expiry_date=str(date.fromtimestamp(0)), + ), + ] + } return keys - return mocker.patch('app.api_key_api_client.get_api_keys', side_effect=_get_keys) + return mocker.patch("app.api_key_api_client.get_api_keys", side_effect=_get_keys) -@pytest.fixture(scope='function') +@pytest.fixture(scope="function") def mock_get_no_api_keys(mocker): def _get_keys(service_id): - keys = {'apiKeys': []} + keys = {"apiKeys": []} return keys - return mocker.patch('app.api_key_api_client.get_api_keys', side_effect=_get_keys) + return mocker.patch("app.api_key_api_client.get_api_keys", side_effect=_get_keys) -@pytest.fixture(scope='function') +@pytest.fixture(scope="function") def mock_login(mocker, mock_get_user, mock_update_user_attribute, mock_events): def _verify_code(user_id, code, code_type): - return True, '' + return True, "" def _no_services(params_dict=None): - return {'data': []} + return {"data": []} return ( - mocker.patch( - 'app.user_api_client.check_verify_code', - side_effect=_verify_code - ), - mocker.patch( - 'app.service_api_client.get_services', - side_effect=_no_services - ) + mocker.patch("app.user_api_client.check_verify_code", side_effect=_verify_code), + mocker.patch("app.service_api_client.get_services", side_effect=_no_services), ) -@pytest.fixture(scope='function') +@pytest.fixture(scope="function") def mock_send_verify_code(mocker): - return mocker.patch('app.user_api_client.send_verify_code') + return mocker.patch("app.user_api_client.send_verify_code") -@pytest.fixture(scope='function') +@pytest.fixture(scope="function") def mock_send_verify_email(mocker): - return mocker.patch('app.user_api_client.send_verify_email') + return mocker.patch("app.user_api_client.send_verify_email") -@pytest.fixture(scope='function') +@pytest.fixture(scope="function") def mock_check_verify_code(mocker): def _verify(user_id, code, code_type): - return True, '' + return True, "" - return mocker.patch( - 'app.user_api_client.check_verify_code', - side_effect=_verify) + return mocker.patch("app.user_api_client.check_verify_code", side_effect=_verify) -@pytest.fixture(scope='function') +@pytest.fixture(scope="function") def mock_check_verify_code_code_not_found(mocker): def _verify(user_id, code, code_type): - return False, 'Code not found' + return False, "Code not found" - return mocker.patch( - 'app.user_api_client.check_verify_code', - side_effect=_verify) + return mocker.patch("app.user_api_client.check_verify_code", side_effect=_verify) -@pytest.fixture(scope='function') +@pytest.fixture(scope="function") def mock_check_verify_code_code_expired(mocker): def _verify(user_id, code, code_type): - return False, 'Code has expired' + return False, "Code has expired" - return mocker.patch( - 'app.user_api_client.check_verify_code', - side_effect=_verify) + return mocker.patch("app.user_api_client.check_verify_code", side_effect=_verify) -@pytest.fixture(scope='function') +@pytest.fixture(scope="function") def mock_create_job(mocker, api_user_active): def _create(job_id, service_id, scheduled_for=None): return job_json( @@ -1282,15 +1395,15 @@ def mock_create_job(mocker, api_user_active): job_id=job_id, ) - return mocker.patch('app.job_api_client.create_job', side_effect=_create) + return mocker.patch("app.job_api_client.create_job", side_effect=_create) -@pytest.fixture(scope='function') +@pytest.fixture(scope="function") def mock_get_job(mocker, api_user_active): def _get_job(service_id, job_id): return {"data": job_json(service_id, api_user_active, job_id=job_id)} - return mocker.patch('app.job_api_client.get_job', side_effect=_get_job) + return mocker.patch("app.job_api_client.get_job", side_effect=_get_job) @pytest.fixture @@ -1298,78 +1411,90 @@ def mock_get_job_doesnt_exist(mocker): def _get_job(service_id, job_id): raise HTTPError(response=Mock(status_code=404, json={}), message={}) - return mocker.patch('app.job_api_client.get_job', side_effect=_get_job) + return mocker.patch("app.job_api_client.get_job", side_effect=_get_job) -@pytest.fixture(scope='function') +@pytest.fixture(scope="function") def mock_get_scheduled_job(mocker, api_user_active): def _get_job(service_id, job_id): - return {"data": job_json( - service_id, - api_user_active, - job_id=job_id, - job_status='scheduled', - scheduled_for='2016-01-02T05:00:00.061258' - )} + return { + "data": job_json( + service_id, + api_user_active, + job_id=job_id, + job_status="scheduled", + scheduled_for="2016-01-02T05:00:00.061258", + ) + } - return mocker.patch('app.job_api_client.get_job', side_effect=_get_job) + return mocker.patch("app.job_api_client.get_job", side_effect=_get_job) -@pytest.fixture(scope='function') +@pytest.fixture(scope="function") def mock_get_cancelled_job(mocker, api_user_active): def _get_job(service_id, job_id): - return {"data": job_json( - service_id, - api_user_active, - job_id=job_id, - job_status='cancelled', - scheduled_for='2016-01-01T00:00:00.061258' - )} + return { + "data": job_json( + service_id, + api_user_active, + job_id=job_id, + job_status="cancelled", + scheduled_for="2016-01-01T00:00:00.061258", + ) + } - return mocker.patch('app.job_api_client.get_job', side_effect=_get_job) + return mocker.patch("app.job_api_client.get_job", side_effect=_get_job) -@pytest.fixture(scope='function') +@pytest.fixture(scope="function") def mock_get_job_in_progress(mocker, api_user_active): def _get_job(service_id, job_id): - return {"data": job_json( - service_id, api_user_active, job_id=job_id, - notification_count=10, - notifications_requested=5, - job_status='processing', - )} + return { + "data": job_json( + service_id, + api_user_active, + job_id=job_id, + notification_count=10, + notifications_requested=5, + job_status="processing", + ) + } - return mocker.patch('app.job_api_client.get_job', side_effect=_get_job) + return mocker.patch("app.job_api_client.get_job", side_effect=_get_job) -@pytest.fixture(scope='function') +@pytest.fixture(scope="function") def mock_get_job_with_sending_limits_exceeded(mocker, api_user_active): def _get_job(service_id, job_id): - return {"data": job_json( - service_id, api_user_active, job_id=job_id, - notification_count=10, - notifications_requested=5, - job_status='sending limits exceeded', - )} + return { + "data": job_json( + service_id, + api_user_active, + job_id=job_id, + notification_count=10, + notifications_requested=5, + job_status="sending limits exceeded", + ) + } - return mocker.patch('app.job_api_client.get_job', side_effect=_get_job) + return mocker.patch("app.job_api_client.get_job", side_effect=_get_job) -@pytest.fixture(scope='function') +@pytest.fixture(scope="function") def mock_has_jobs(mocker): - return mocker.patch('app.job_api_client.has_jobs', return_value=True) + return mocker.patch("app.job_api_client.has_jobs", return_value=True) -@pytest.fixture(scope='function') +@pytest.fixture(scope="function") def mock_has_no_jobs(mocker): - return mocker.patch('app.job_api_client.has_jobs', return_value=False) + return mocker.patch("app.job_api_client.has_jobs", return_value=False) -@pytest.fixture(scope='function') +@pytest.fixture(scope="function") def mock_get_jobs(mocker, api_user_active, fake_uuid): def _get_jobs(service_id, limit_days=None, statuses=None, page=1): if statuses is None: - statuses = ['', 'scheduled', 'pending', 'cancelled', 'finished'] + statuses = ["", "scheduled", "pending", "cancelled", "finished"] jobs = [ job_json( @@ -1383,86 +1508,110 @@ def mock_get_jobs(mocker, api_user_active, fake_uuid): template_name=template_name, ) for filename, scheduled_for, job_status, template_name, template_version in ( - ('full_of_regret.csv', '2016-01-01 23:09:00.061258', 'cancelled', 'Template X', 1), - ('even_later.csv', '2016-01-01 23:09:00.061258', 'scheduled', 'Template Y', 1), - ('send_me_later.csv', '2016-01-01 11:09:00.061258', 'scheduled', 'Template Z', 1), - ('export 1/1/2016.xls', '', 'finished', 'Template A', 1), - ('all email addresses.xlsx', '', 'pending', 'Template B', 1), - ('applicants.ods', '', 'finished', 'Template C', 1), - ('thisisatest.csv', '', 'finished', 'Template D', 2), + ( + "full_of_regret.csv", + "2016-01-01 23:09:00.061258", + "cancelled", + "Template X", + 1, + ), + ( + "even_later.csv", + "2016-01-01 23:09:00.061258", + "scheduled", + "Template Y", + 1, + ), + ( + "send_me_later.csv", + "2016-01-01 11:09:00.061258", + "scheduled", + "Template Z", + 1, + ), + ("export 1/1/2016.xls", "", "finished", "Template A", 1), + ("all email addresses.xlsx", "", "pending", "Template B", 1), + ("applicants.ods", "", "finished", "Template C", 1), + ("thisisatest.csv", "", "finished", "Template D", 2), ) ] return { - 'data': [job for job in jobs if job['job_status'] in statuses], - 'links': { - 'prev': 'services/{}/jobs?page={}'.format(service_id, page - 1), - 'next': 'services/{}/jobs?page={}'.format(service_id, page + 1) - } + "data": [job for job in jobs if job["job_status"] in statuses], + "links": { + "prev": "services/{}/jobs?page={}".format(service_id, page - 1), + "next": "services/{}/jobs?page={}".format(service_id, page + 1), + }, } - return mocker.patch('app.job_api_client.get_jobs', side_effect=_get_jobs) + return mocker.patch("app.job_api_client.get_jobs", side_effect=_get_jobs) -@pytest.fixture(scope='function') +@pytest.fixture(scope="function") def mock_get_scheduled_job_stats(mocker, api_user_active): - return mocker.patch('app.job_api_client.get_scheduled_job_stats', return_value={ - # These values match the return value of `mock_get_jobs` - 'count': 2, - 'soonest_scheduled_for': '2016-01-01 11:09:00' - }) + return mocker.patch( + "app.job_api_client.get_scheduled_job_stats", + return_value={ + # These values match the return value of `mock_get_jobs` + "count": 2, + "soonest_scheduled_for": "2016-01-01 11:09:00", + }, + ) -@pytest.fixture(scope='function') +@pytest.fixture(scope="function") def mock_get_uploads(mocker, api_user_active): def _get_uploads(service_id, limit_days=None, statuses=None, page=1): uploads = [ { - 'id': 'job_id_1', - 'original_file_name': 'some.csv', - 'notification_count': 10, - 'created_at': '2016-01-01 11:09:00.061258', - 'statistics': [ - {'count': 8, 'status': 'delivered'}, - {'count': 2, 'status': 'temporary-failure'} + "id": "job_id_1", + "original_file_name": "some.csv", + "notification_count": 10, + "created_at": "2016-01-01 11:09:00.061258", + "statistics": [ + {"count": 8, "status": "delivered"}, + {"count": 2, "status": "temporary-failure"}, ], - 'upload_type': 'job', - 'template_type': 'sms', - 'recipient': None, + "upload_type": "job", + "template_type": "sms", + "recipient": None, }, ] return { - 'data': uploads, - 'links': { - 'prev': 'services/{}/uploads?page={}'.format(service_id, page - 1), - 'next': 'services/{}/uploads?page={}'.format(service_id, page + 1) - } + "data": uploads, + "links": { + "prev": "services/{}/uploads?page={}".format(service_id, page - 1), + "next": "services/{}/uploads?page={}".format(service_id, page + 1), + }, } + # Why is mocking on the model needed? - return mocker.patch('app.models.job.PaginatedUploads.client_method', side_effect=_get_uploads) + return mocker.patch( + "app.models.job.PaginatedUploads.client_method", side_effect=_get_uploads + ) -@pytest.fixture(scope='function') +@pytest.fixture(scope="function") def mock_get_no_uploads(mocker, api_user_active): mocker.patch( - 'app.models.job.PaginatedUploads.client_method', + "app.models.job.PaginatedUploads.client_method", return_value={ - 'data': [], - } + "data": [], + }, ) -@pytest.fixture(scope='function') +@pytest.fixture(scope="function") def mock_get_no_jobs(mocker, api_user_active): return mocker.patch( - 'app.models.job.PaginatedJobs.client_method', + "app.models.job.PaginatedJobs.client_method", return_value={ - 'data': [], - 'links': {}, - } + "data": [], + "links": {}, + }, ) -@pytest.fixture(scope='function') +@pytest.fixture(scope="function") def mock_get_notifications( mocker, api_user_active, @@ -1504,201 +1653,187 @@ def mock_get_notifications( rows=rows, job=job, with_links=True if count_pages is None else count_pages, - created_by_name='Firstname Lastname', + created_by_name="Firstname Lastname", ) return mocker.patch( - 'app.notification_api_client.get_notifications_for_service', - side_effect=_get_notifications + "app.notification_api_client.get_notifications_for_service", + side_effect=_get_notifications, ) -@pytest.fixture(scope='function') +@pytest.fixture(scope="function") def mock_get_notifications_with_previous_next(mocker): - def _get_notifications(service_id, - job_id=None, - page=1, - count_pages=None, - template_type=None, - status=None, - limit_days=None, - include_jobs=None, - include_from_test_key=None, - to=None, - include_one_off=None - ): - return notification_json(service_id, rows=50, with_links=True if count_pages is None else count_pages) + def _get_notifications( + service_id, + job_id=None, + page=1, + count_pages=None, + template_type=None, + status=None, + limit_days=None, + include_jobs=None, + include_from_test_key=None, + to=None, + include_one_off=None, + ): + return notification_json( + service_id, rows=50, with_links=True if count_pages is None else count_pages + ) return mocker.patch( - 'app.notification_api_client.get_notifications_for_service', - side_effect=_get_notifications + "app.notification_api_client.get_notifications_for_service", + side_effect=_get_notifications, ) -@pytest.fixture(scope='function') +@pytest.fixture(scope="function") def mock_get_notifications_with_no_notifications(mocker): - def _get_notifications(service_id, - job_id=None, - page=1, - count_pages=None, - template_type=None, - status=None, - limit_days=None, - include_jobs=None, - include_from_test_key=None, - to=None, - include_one_off=None - ): + def _get_notifications( + service_id, + job_id=None, + page=1, + count_pages=None, + template_type=None, + status=None, + limit_days=None, + include_jobs=None, + include_from_test_key=None, + to=None, + include_one_off=None, + ): return notification_json(service_id, rows=0) return mocker.patch( - 'app.notification_api_client.get_notifications_for_service', - side_effect=_get_notifications + "app.notification_api_client.get_notifications_for_service", + side_effect=_get_notifications, ) -@pytest.fixture(scope='function') +@pytest.fixture(scope="function") def mock_get_inbound_sms(mocker): - def _get_inbound_sms( - service_id, - user_number=None, - page=1 - ): + def _get_inbound_sms(service_id, user_number=None, page=1): return inbound_sms_json() return mocker.patch( - 'app.service_api_client.get_inbound_sms', + "app.service_api_client.get_inbound_sms", side_effect=_get_inbound_sms, ) @pytest.fixture def mock_get_inbound_sms_by_id_with_no_messages(mocker): - def _get_inbound_sms_by_id( - service_id, - notification_id - ): + def _get_inbound_sms_by_id(service_id, notification_id): raise HTTPError(response=Mock(status_code=404)) return mocker.patch( - 'app.service_api_client.get_inbound_sms_by_id', + "app.service_api_client.get_inbound_sms_by_id", side_effect=_get_inbound_sms_by_id, ) -@pytest.fixture(scope='function') +@pytest.fixture(scope="function") def mock_get_most_recent_inbound_sms(mocker): - def _get_most_recent_inbound_sms( - service_id, - user_number=None, - page=1 - ): + def _get_most_recent_inbound_sms(service_id, user_number=None, page=1): return inbound_sms_json() return mocker.patch( - 'app.service_api_client.get_most_recent_inbound_sms', + "app.service_api_client.get_most_recent_inbound_sms", side_effect=_get_most_recent_inbound_sms, ) -@pytest.fixture(scope='function') +@pytest.fixture(scope="function") def mock_get_most_recent_inbound_sms_with_no_messages(mocker): - def _get_most_recent_inbound_sms( - service_id, - user_number=None, - page=1 - ): - return { - 'has_next': False, - 'data': [] - } + def _get_most_recent_inbound_sms(service_id, user_number=None, page=1): + return {"has_next": False, "data": []} return mocker.patch( - 'app.service_api_client.get_most_recent_inbound_sms', + "app.service_api_client.get_most_recent_inbound_sms", side_effect=_get_most_recent_inbound_sms, ) -@pytest.fixture(scope='function') +@pytest.fixture(scope="function") def mock_get_inbound_sms_summary(mocker): def _get_inbound_sms_summary( service_id, ): - return { - 'count': 9999, - 'most_recent': datetime.utcnow().isoformat() - } + return {"count": 9999, "most_recent": datetime.utcnow().isoformat()} return mocker.patch( - 'app.service_api_client.get_inbound_sms_summary', + "app.service_api_client.get_inbound_sms_summary", side_effect=_get_inbound_sms_summary, ) -@pytest.fixture(scope='function') +@pytest.fixture(scope="function") def mock_get_inbound_sms_summary_with_no_messages(mocker): def _get_inbound_sms_summary( service_id, ): - return { - 'count': 0, - 'latest_message': None - } + return {"count": 0, "latest_message": None} return mocker.patch( - 'app.service_api_client.get_inbound_sms_summary', + "app.service_api_client.get_inbound_sms_summary", side_effect=_get_inbound_sms_summary, ) -@pytest.fixture(scope='function') +@pytest.fixture(scope="function") def mock_get_inbound_number_for_service(mocker): return mocker.patch( - 'app.inbound_number_client.get_inbound_sms_number_for_service', - return_value={'data': {'number': '2028675301'}}) + "app.inbound_number_client.get_inbound_sms_number_for_service", + return_value={"data": {"number": "2028675301"}}, + ) -@pytest.fixture(scope='function') +@pytest.fixture(scope="function") def mock_no_inbound_number_for_service(mocker): return mocker.patch( - 'app.inbound_number_client.get_inbound_sms_number_for_service', - return_value={'data': {}}) + "app.inbound_number_client.get_inbound_sms_number_for_service", + return_value={"data": {}}, + ) -@pytest.fixture(scope='function') +@pytest.fixture(scope="function") def mock_has_permissions(mocker): def _has_permission(*permissions, restrict_admin_usage=False, allow_org_user=False): return True return mocker.patch( - 'app.models.user.User.has_permissions', - side_effect=_has_permission) + "app.models.user.User.has_permissions", side_effect=_has_permission + ) -@pytest.fixture(scope='function') +@pytest.fixture(scope="function") def mock_get_users_by_service(mocker): def _get_users_for_service(service_id): - return [create_service_one_admin( - id=sample_uuid(), - logged_in_at=None, - mobile_number='+12028675109', - email_address='notify@digital.cabinet-office.gov.uk', - )] + return [ + create_service_one_admin( + id=sample_uuid(), + logged_in_at=None, + mobile_number="+12028675109", + email_address="notify@digital.cabinet-office.gov.uk", + ) + ] # You shouldn’t be calling the user API client directly, so it’s the # instance on the model that’s mocked here - return mocker.patch('app.models.user.Users.client_method', side_effect=_get_users_for_service) + return mocker.patch( + "app.models.user.Users.client_method", side_effect=_get_users_for_service + ) -@pytest.fixture(scope='function') +@pytest.fixture(scope="function") def mock_s3_upload(mocker): def _upload(service_id, filedata): return sample_uuid() - return mocker.patch('app.main.views.send.s3upload', side_effect=_upload) + return mocker.patch("app.main.views.send.s3upload", side_effect=_upload) -@pytest.fixture(scope='function') +@pytest.fixture(scope="function") def mock_s3_download(mocker): def _download(service_id, upload_id): return """ @@ -1707,166 +1842,203 @@ def mock_s3_download(mocker): +12028675109,Smith """ - return mocker.patch('app.main.views.send.s3download', side_effect=_download) + return mocker.patch("app.main.views.send.s3download", side_effect=_download) -@pytest.fixture(scope='function') +@pytest.fixture(scope="function") def mock_s3_get_metadata(mocker): def _get_metadata(service_id, upload_id): - return {'original_file_name': 'example.csv'} + return {"original_file_name": "example.csv"} - return mocker.patch('app.main.views.send.get_csv_metadata', side_effect=_get_metadata) + return mocker.patch( + "app.main.views.send.get_csv_metadata", side_effect=_get_metadata + ) -@pytest.fixture(scope='function') +@pytest.fixture(scope="function") def mock_s3_set_metadata(mocker): - return mocker.patch('app.main.views.send.set_metadata_on_csv_upload') + return mocker.patch("app.main.views.send.set_metadata_on_csv_upload") -@pytest.fixture(scope='function') +@pytest.fixture(scope="function") def sample_invite(mocker, service_one): id_ = USER_ONE_ID - from_user = service_one['users'][0] - email_address = 'invited_user@test.gsa.gov' - service_id = service_one['id'] - permissions = 'view_activity,send_emails,send_texts,manage_settings,manage_users,manage_api_keys' + from_user = service_one["users"][0] + email_address = "invited_user@test.gsa.gov" + service_id = service_one["id"] + permissions = "view_activity,send_emails,send_texts,manage_settings,manage_users,manage_api_keys" created_at = str(datetime.utcnow()) - auth_type = 'sms_auth' + auth_type = "sms_auth" folder_permissions = [] return invite_json( - id_, from_user, service_id, email_address, permissions, created_at, 'pending', auth_type, folder_permissions) + id_, + from_user, + service_id, + email_address, + permissions, + created_at, + "pending", + auth_type, + folder_permissions, + ) -@pytest.fixture(scope='function') +@pytest.fixture(scope="function") def mock_create_invite(mocker, sample_invite): - def _create_invite(from_user, service_id, email_address, permissions, folder_permissions): - sample_invite['from_user'] = from_user - sample_invite['service'] = service_id - sample_invite['email_address'] = email_address - sample_invite['status'] = 'pending' - sample_invite['permissions'] = permissions - sample_invite['folder_permissions'] = folder_permissions + def _create_invite( + from_user, service_id, email_address, permissions, folder_permissions + ): + sample_invite["from_user"] = from_user + sample_invite["service"] = service_id + sample_invite["email_address"] = email_address + sample_invite["status"] = "pending" + sample_invite["permissions"] = permissions + sample_invite["folder_permissions"] = folder_permissions return sample_invite - return mocker.patch('app.invite_api_client.create_invite', side_effect=_create_invite) + return mocker.patch( + "app.invite_api_client.create_invite", side_effect=_create_invite + ) -@pytest.fixture(scope='function') +@pytest.fixture(scope="function") def mock_get_invites_for_service(mocker, service_one, sample_invite): def _get_invites(service_id): data = [] for i in range(0, 5): invite = copy.copy(sample_invite) - invite['email_address'] = 'user_{}@testnotify.gsa.gov'.format(i) + invite["email_address"] = "user_{}@testnotify.gsa.gov".format(i) data.append(invite) return data - return mocker.patch('app.models.user.InvitedUsers.client_method', side_effect=_get_invites) + return mocker.patch( + "app.models.user.InvitedUsers.client_method", side_effect=_get_invites + ) -@pytest.fixture(scope='function') +@pytest.fixture(scope="function") def mock_get_invites_without_manage_permission(mocker, service_one, sample_invite): - def _get_invites(service_id): - return [invite_json( - id_=str(sample_uuid()), - from_user=service_one['users'][0], - email_address='invited_user@test.gsa.gov', - service_id=service_one['id'], - permissions='view_activity,send_messages,manage_api_keys', - created_at=str(datetime.utcnow()), - auth_type='sms_auth', - folder_permissions=[], - status='pending', - )] + return [ + invite_json( + id_=str(sample_uuid()), + from_user=service_one["users"][0], + email_address="invited_user@test.gsa.gov", + service_id=service_one["id"], + permissions="view_activity,send_messages,manage_api_keys", + created_at=str(datetime.utcnow()), + auth_type="sms_auth", + folder_permissions=[], + status="pending", + ) + ] - return mocker.patch('app.models.user.InvitedUsers.client_method', side_effect=_get_invites) + return mocker.patch( + "app.models.user.InvitedUsers.client_method", side_effect=_get_invites + ) -@pytest.fixture(scope='function') +@pytest.fixture(scope="function") def mock_accept_invite(mocker, sample_invite): def _accept(service_id, invite_id): return sample_invite - return mocker.patch('app.invite_api_client.accept_invite', side_effect=_accept) + return mocker.patch("app.invite_api_client.accept_invite", side_effect=_accept) -@pytest.fixture(scope='function') +@pytest.fixture(scope="function") def mock_add_user_to_service(mocker, service_one, api_user_active): def _add_user(service_id, user_id, permissions, folder_permissions): return - return mocker.patch('app.user_api_client.add_user_to_service', side_effect=_add_user) + return mocker.patch( + "app.user_api_client.add_user_to_service", side_effect=_add_user + ) -@pytest.fixture(scope='function') +@pytest.fixture(scope="function") def mock_set_user_permissions(mocker): - return mocker.patch('app.user_api_client.set_user_permissions', return_value=None) + return mocker.patch("app.user_api_client.set_user_permissions", return_value=None) -@pytest.fixture(scope='function') +@pytest.fixture(scope="function") def mock_remove_user_from_service(mocker): - return mocker.patch('app.service_api_client.remove_user_from_service', return_value=None) + return mocker.patch( + "app.service_api_client.remove_user_from_service", return_value=None + ) -@pytest.fixture(scope='function') +@pytest.fixture(scope="function") def mock_get_template_statistics(mocker, service_one, fake_uuid): - template = template_json(service_one['id'], fake_uuid, "Test template", "sms", "Something very interesting") + template = template_json( + service_one["id"], + fake_uuid, + "Test template", + "sms", + "Something very interesting", + ) data = { "count": 1, - "template_name": template['name'], - "template_type": template['template_type'], - "template_id": template['id'], - "status": "delivered" + "template_name": template["name"], + "template_type": template["template_type"], + "template_id": template["id"], + "status": "delivered", } def _get_stats(service_id, limit_days=None): return [data] return mocker.patch( - 'app.template_statistics_client.get_template_statistics_for_service', side_effect=_get_stats) + "app.template_statistics_client.get_template_statistics_for_service", + side_effect=_get_stats, + ) -@pytest.fixture(scope='function') +@pytest.fixture(scope="function") def mock_get_monthly_template_usage(mocker, service_one, fake_uuid): def _stats(service_id, year): - return [{ - "template_id": fake_uuid, - "month": 10, - "year": year, - "count": 2, - "name": 'My first template', - "type": 'sms' - }] + return [ + { + "template_id": fake_uuid, + "month": 10, + "year": year, + "count": 2, + "name": "My first template", + "type": "sms", + } + ] + return mocker.patch( - 'app.template_statistics_client.get_monthly_template_usage_for_service', - side_effect=_stats + "app.template_statistics_client.get_monthly_template_usage_for_service", + side_effect=_stats, ) -@pytest.fixture(scope='function') +@pytest.fixture(scope="function") def mock_get_monthly_notification_stats(mocker, service_one, fake_uuid): def _stats(service_id, year): - return {'data': { - datetime.utcnow().strftime('%Y-%m'): { - "email": { - "sending": 1, - "delivered": 1, - }, - "sms": { - "sending": 1, - "delivered": 1, - }, + return { + "data": { + datetime.utcnow().strftime("%Y-%m"): { + "email": { + "sending": 1, + "delivered": 1, + }, + "sms": { + "sending": 1, + "delivered": 1, + }, + } } - }} + } + return mocker.patch( - 'app.service_api_client.get_monthly_notification_stats', - side_effect=_stats + "app.service_api_client.get_monthly_notification_stats", side_effect=_stats ) -@pytest.fixture(scope='function') +@pytest.fixture(scope="function") def mock_get_annual_usage_for_service(mocker, service_one, fake_uuid): def _get_usage(service_id, year=None): return [ @@ -1876,7 +2048,7 @@ def mock_get_annual_usage_for_service(mocker, service_one, fake_uuid): "notifications_sent": 1000, "charged_units": 1000, "rate": 0.00, - "cost": 0 + "cost": 0, }, { "notification_type": "sms", @@ -1884,7 +2056,7 @@ def mock_get_annual_usage_for_service(mocker, service_one, fake_uuid): "notifications_sent": 105000, "charged_units": 1500, "rate": 0.0165, - "cost": 24.75 # 250K free allowance + "cost": 24.75, # 250K free allowance }, { "notification_type": "sms", @@ -1892,125 +2064,133 @@ def mock_get_annual_usage_for_service(mocker, service_one, fake_uuid): "notifications_sent": 300, "charged_units": 300, "rate": 0.017, - "cost": 5.1 + "cost": 5.1, }, ] return mocker.patch( - 'app.billing_api_client.get_annual_usage_for_service', side_effect=_get_usage) + "app.billing_api_client.get_annual_usage_for_service", side_effect=_get_usage + ) -@pytest.fixture(scope='function') +@pytest.fixture(scope="function") def mock_get_monthly_usage_for_service(mocker): def _get_usage(service_id, year): return [ { - 'month': 'March', - 'notification_type': 'sms', - 'rate': 0.017, - 'chargeable_units': 1230, - 'notifications_sent': 1234, - 'charged_units': 1230, - 'free_allowance_used': 0, - 'cost': 20.91, + "month": "March", + "notification_type": "sms", + "rate": 0.017, + "chargeable_units": 1230, + "notifications_sent": 1234, + "charged_units": 1230, + "free_allowance_used": 0, + "cost": 20.91, }, { - 'month': 'February', - 'notification_type': 'sms', - 'rate': 0.017, - 'chargeable_units': 33, - 'notifications_sent': 1234, - 'charged_units': 33, - 'free_allowance_used': 0, - 'cost': 0.561, + "month": "February", + "notification_type": "sms", + "rate": 0.017, + "chargeable_units": 33, + "notifications_sent": 1234, + "charged_units": 33, + "free_allowance_used": 0, + "cost": 0.561, }, { - 'month': 'February', - 'notification_type': 'sms', - 'rate': 0.0165, - 'chargeable_units': 1100, - 'notifications_sent': 1234, - 'charged_units': 960, - 'free_allowance_used': 140, - 'cost': 15.84, + "month": "February", + "notification_type": "sms", + "rate": 0.0165, + "chargeable_units": 1100, + "notifications_sent": 1234, + "charged_units": 960, + "free_allowance_used": 140, + "cost": 15.84, }, { - 'month': 'October', - 'notification_type': 'sms', - 'rate': 0.017, - 'chargeable_units': 249860, - 'notifications_sent': 1234, - 'charged_units': 0, - 'free_allowance_used': 249860, - 'cost': 0, + "month": "October", + "notification_type": "sms", + "rate": 0.017, + "chargeable_units": 249860, + "notifications_sent": 1234, + "charged_units": 0, + "free_allowance_used": 249860, + "cost": 0, }, ] return mocker.patch( - 'app.billing_api_client.get_monthly_usage_for_service', side_effect=_get_usage) + "app.billing_api_client.get_monthly_usage_for_service", side_effect=_get_usage + ) -@pytest.fixture(scope='function') +@pytest.fixture(scope="function") def mock_get_annual_usage_for_service_in_future(mocker, service_one, fake_uuid): def _get_usage(service_id, year=None): return [ { - 'notification_type': 'sms', - 'chargeable_units': 0, - 'notifications_sent': 0, - 'charged_units': 0, - 'rate': 0.0158, - 'cost': 0 + "notification_type": "sms", + "chargeable_units": 0, + "notifications_sent": 0, + "charged_units": 0, + "rate": 0.0158, + "cost": 0, }, { - 'notification_type': 'email', - 'chargeable_units': 0, - 'notifications_sent': 0, - 'charged_units': 0, - 'rate': 0.0, - 'cost': 0 - } + "notification_type": "email", + "chargeable_units": 0, + "notifications_sent": 0, + "charged_units": 0, + "rate": 0.0, + "cost": 0, + }, ] return mocker.patch( - 'app.billing_api_client.get_annual_usage_for_service', side_effect=_get_usage) + "app.billing_api_client.get_annual_usage_for_service", side_effect=_get_usage + ) -@pytest.fixture(scope='function') +@pytest.fixture(scope="function") def mock_get_monthly_usage_for_service_in_future(mocker): def _get_usage(service_id, year): return [] return mocker.patch( - 'app.billing_api_client.get_monthly_usage_for_service', side_effect=_get_usage) + "app.billing_api_client.get_monthly_usage_for_service", side_effect=_get_usage + ) -@pytest.fixture(scope='function') +@pytest.fixture(scope="function") def mock_events(mocker): def _create_event(event_type, event_data): - return {'some': 'data'} + return {"some": "data"} - return mocker.patch('app.events_api_client.create_event', side_effect=_create_event) + return mocker.patch("app.events_api_client.create_event", side_effect=_create_event) -@pytest.fixture(scope='function') +@pytest.fixture(scope="function") def mock_send_already_registered_email(mocker): - return mocker.patch('app.user_api_client.send_already_registered_email') + return mocker.patch("app.user_api_client.send_already_registered_email") -def create_email_brandings(number_of_brandings, non_standard_values=None, shuffle=False): +def create_email_brandings( + number_of_brandings, non_standard_values=None, shuffle=False +): brandings = [ { - 'id': str(idx), - 'name': 'org {}'.format(idx), - 'text': 'org {}'.format(idx), - 'colour': None, - 'logo': 'logo{}.png'.format(idx), - 'brand_type': 'org', - } for idx in range(1, number_of_brandings + 1)] + "id": str(idx), + "name": "org {}".format(idx), + "text": "org {}".format(idx), + "colour": None, + "logo": "logo{}.png".format(idx), + "brand_type": "org", + } + for idx in range(1, number_of_brandings + 1) + ] for idx, row in enumerate(non_standard_values or {}): - brandings[row['idx']].update(non_standard_values[idx]) + brandings[row["idx"]].update(non_standard_values[idx]) if shuffle: brandings.insert(3, brandings.pop(4)) @@ -2018,144 +2198,149 @@ def create_email_brandings(number_of_brandings, non_standard_values=None, shuffl return brandings -@pytest.fixture(scope='function') +@pytest.fixture(scope="function") def mock_get_all_email_branding(mocker): def _get_all_email_branding(sort_key=None): non_standard_values = [ - {'idx': 1, 'colour': 'red'}, - {'idx': 2, 'colour': 'orange'}, - {'idx': 3, 'text': None}, - {'idx': 4, 'colour': 'blue'}, + {"idx": 1, "colour": "red"}, + {"idx": 2, "colour": "orange"}, + {"idx": 3, "text": None}, + {"idx": 4, "colour": "blue"}, ] shuffle = sort_key is None - return create_email_brandings(5, non_standard_values=non_standard_values, shuffle=shuffle) + return create_email_brandings( + 5, non_standard_values=non_standard_values, shuffle=shuffle + ) return mocker.patch( - 'app.notify_client.email_branding_client.email_branding_client.get_all_email_branding', + "app.notify_client.email_branding_client.email_branding_client.get_all_email_branding", side_effect=_get_all_email_branding, ) -@pytest.fixture(scope='function') +@pytest.fixture(scope="function") def mock_no_email_branding(mocker): def _get_email_branding(): return [] return mocker.patch( - 'app.email_branding_client.get_all_email_branding', side_effect=_get_email_branding + "app.email_branding_client.get_all_email_branding", + side_effect=_get_email_branding, ) def create_email_branding(id, non_standard_values=None): branding = { - 'logo': 'example.png', - 'name': 'Organization name', - 'text': 'Organization text', - 'id': id, - 'colour': '#f00', - 'brand_type': 'org', + "logo": "example.png", + "name": "Organization name", + "text": "Organization text", + "id": id, + "colour": "#f00", + "brand_type": "org", } if non_standard_values: branding.update(non_standard_values) - return {'email_branding': branding} + return {"email_branding": branding} -@pytest.fixture(scope='function') +@pytest.fixture(scope="function") def mock_get_email_branding(mocker, fake_uuid): def _get_email_branding(id): return create_email_branding(fake_uuid) return mocker.patch( - 'app.email_branding_client.get_email_branding', side_effect=_get_email_branding + "app.email_branding_client.get_email_branding", side_effect=_get_email_branding ) -@pytest.fixture(scope='function') +@pytest.fixture(scope="function") def mock_get_email_branding_with_govuk_brand_type(mocker, fake_uuid): def _get_email_branding(id): - return create_email_branding(fake_uuid, {'brand_type': 'govuk'}) + return create_email_branding(fake_uuid, {"brand_type": "govuk"}) return mocker.patch( - 'app.email_branding_client.get_email_branding', side_effect=_get_email_branding + "app.email_branding_client.get_email_branding", side_effect=_get_email_branding ) -@pytest.fixture(scope='function') +@pytest.fixture(scope="function") def mock_get_email_branding_with_both_brand_type(mocker, fake_uuid): def _get_email_branding(id): - return create_email_branding(fake_uuid, {'brand_type': 'both'}) + return create_email_branding(fake_uuid, {"brand_type": "both"}) return mocker.patch( - 'app.email_branding_client.get_email_branding', side_effect=_get_email_branding + "app.email_branding_client.get_email_branding", side_effect=_get_email_branding ) -@pytest.fixture(scope='function') +@pytest.fixture(scope="function") def mock_get_email_branding_with_org_banner_brand_type(mocker, fake_uuid): def _get_email_branding(id): - return create_email_branding(fake_uuid, {'brand_type': 'org_banner'}) + return create_email_branding(fake_uuid, {"brand_type": "org_banner"}) return mocker.patch( - 'app.email_branding_client.get_email_branding', side_effect=_get_email_branding + "app.email_branding_client.get_email_branding", side_effect=_get_email_branding ) -@pytest.fixture(scope='function') +@pytest.fixture(scope="function") def mock_get_email_branding_without_brand_text(mocker, fake_uuid): def _get_email_branding_without_brand_text(id): - return create_email_branding(fake_uuid, {'text': '', 'brand_type': 'org_banner'}) + return create_email_branding( + fake_uuid, {"text": "", "brand_type": "org_banner"} + ) return mocker.patch( - 'app.email_branding_client.get_email_branding', - side_effect=_get_email_branding_without_brand_text + "app.email_branding_client.get_email_branding", + side_effect=_get_email_branding_without_brand_text, ) -@pytest.fixture(scope='function') +@pytest.fixture(scope="function") def mock_create_email_branding(mocker): def _create_email_branding(logo, name, text, colour, brand_type): return return mocker.patch( - 'app.email_branding_client.create_email_branding', side_effect=_create_email_branding + "app.email_branding_client.create_email_branding", + side_effect=_create_email_branding, ) -@pytest.fixture(scope='function') +@pytest.fixture(scope="function") def mock_update_email_branding(mocker): def _update_email_branding(branding_id, logo, name, text, colour, brand_type): return return mocker.patch( - 'app.email_branding_client.update_email_branding', side_effect=_update_email_branding + "app.email_branding_client.update_email_branding", + side_effect=_update_email_branding, ) -@pytest.fixture(scope='function') +@pytest.fixture(scope="function") def mock_get_guest_list(mocker): def _get_guest_list(service_id): return { - 'email_addresses': ['test@example.com'], - 'phone_numbers': ['2028675300'] + "email_addresses": ["test@example.com"], + "phone_numbers": ["2028675300"], } return mocker.patch( - 'app.service_api_client.get_guest_list', side_effect=_get_guest_list + "app.service_api_client.get_guest_list", side_effect=_get_guest_list ) -@pytest.fixture(scope='function') +@pytest.fixture(scope="function") def mock_update_guest_list(mocker): - return mocker.patch( - 'app.service_api_client.update_guest_list' - ) + return mocker.patch("app.service_api_client.update_guest_list") -@pytest.fixture(scope='function') +@pytest.fixture(scope="function") def mock_reset_failed_login_count(mocker): - return mocker.patch('app.user_api_client.reset_failed_login_count') + return mocker.patch("app.user_api_client.reset_failed_login_count") @pytest.fixture @@ -2164,31 +2349,28 @@ def mock_get_notification(mocker): service_id, notification_id, ): - noti = notification_json( - service_id, - rows=1, - personalisation={'name': 'Jo'} - )['notifications'][0] + noti = notification_json(service_id, rows=1, personalisation={"name": "Jo"})[ + "notifications" + ][0] - noti['id'] = notification_id - noti['created_by'] = { - 'id': fake_uuid, - 'name': 'Test User', - 'email_address': 'test@user.gsa.gov' + noti["id"] = notification_id + noti["created_by"] = { + "id": fake_uuid, + "name": "Test User", + "email_address": "test@user.gsa.gov", } - noti['template'] = template_json( + noti["template"] = template_json( service_id, - '5407f4db-51c7-4150-8758-35412d42186a', - content='hello ((name))', - subject='blah', + "5407f4db-51c7-4150-8758-35412d42186a", + content="hello ((name))", + subject="blah", redact_personalisation=False, - name='sample template' + name="sample template", ) return noti return mocker.patch( - 'app.notification_api_client.get_notification', - side_effect=_get_notification + "app.notification_api_client.get_notification", side_effect=_get_notification ) @@ -2197,15 +2379,14 @@ def mock_send_notification(mocker, fake_uuid): def _send_notification( service_id, *, template_id, recipient, personalisation, sender_id ): - return {'id': fake_uuid} + return {"id": fake_uuid} return mocker.patch( - 'app.notification_api_client.send_notification', - side_effect=_send_notification + "app.notification_api_client.send_notification", side_effect=_send_notification ) -@pytest.fixture(scope='function') +@pytest.fixture(scope="function") def _client(notify_admin): """ Do not use this fixture directly – use `client_request` instead @@ -2214,13 +2395,9 @@ def _client(notify_admin): yield client -@pytest.fixture(scope='function') +@pytest.fixture(scope="function") def _logged_in_client( - _client, - active_user_with_permissions, - mocker, - service_one, - mock_login + _client, active_user_with_permissions, mocker, service_one, mock_login ): """ Do not use this fixture directly – use `client_request` instead @@ -2242,10 +2419,9 @@ def os_environ(): os.environ[k] = v -@pytest.fixture # noqa (C901 too complex) +@pytest.fixture # noqa (C901 too complex) def client_request(_logged_in_client, mocker, service_one): # noqa (C901 too complex) class ClientRequest: - @staticmethod @contextmanager def session_transaction(): @@ -2303,16 +2479,23 @@ def client_request(_logged_in_client, mocker, service_one): # noqa (C901 too co if _expected_redirect: assert resp.location == _expected_redirect - page = BeautifulSoup(resp.data.decode('utf-8'), 'html.parser') + page = BeautifulSoup(resp.data.decode("utf-8"), "html.parser") if _test_page_title: - count_of_h1s = len(page.select('h1')) + count_of_h1s = len(page.select("h1")) if count_of_h1s != 1: - raise AssertionError('Page should have one H1 ({} found)'.format(count_of_h1s)) + raise AssertionError( + "Page should have one H1 ({} found)".format(count_of_h1s) + ) page_title, h1 = ( - normalize_spaces(page.find(selector).text) for selector in ('title', 'h1') + normalize_spaces(page.find(selector).text) + for selector in ("title", "h1") ) if not normalize_spaces(page_title).startswith(h1): - raise AssertionError('Page title ‘{}’ does not start with H1 ‘{}’'.format(page_title, h1)) + raise AssertionError( + "Page title ‘{}’ does not start with H1 ‘{}’".format( + page_title, h1 + ) + ) return page @staticmethod @@ -2349,23 +2532,17 @@ def client_request(_logged_in_client, mocker, service_one): # noqa (C901 too co if _content_type: post_kwargs.update(content_type=_content_type) resp = _logged_in_client.post( - url, - data=_data, - follow_redirects=_follow_redirects, - **post_kwargs + url, data=_data, follow_redirects=_follow_redirects, **post_kwargs ) assert resp.status_code == _expected_status if _expected_redirect: assert_url_expected(resp.location, _expected_redirect) - return BeautifulSoup(resp.data.decode('utf-8'), 'html.parser') + return BeautifulSoup(resp.data.decode("utf-8"), "html.parser") @staticmethod def get_response( - endpoint, - _expected_status=200, - _optional_args="", - **endpoint_kwargs + endpoint, _expected_status=200, _optional_args="", **endpoint_kwargs ): return ClientRequest.get_response_from_url( url_for(endpoint, **(endpoint_kwargs or {})) + _optional_args, @@ -2407,11 +2584,7 @@ def client_request(_logged_in_client, mocker, service_one): # noqa (C901 too co post_kwargs = {} if _content_type: post_kwargs.update(content_type=_content_type) - resp = _logged_in_client.post( - url, - data=_data, - **post_kwargs - ) + resp = _logged_in_client.post(url, data=_data, **post_kwargs) assert resp.status_code == _expected_status return resp @@ -2420,46 +2593,52 @@ def client_request(_logged_in_client, mocker, service_one): # noqa (C901 too co def normalize_spaces(input): if isinstance(input, str): - return ' '.join(input.split()) - return normalize_spaces(' '.join(item.text for item in input)) + return " ".join(input.split()) + return normalize_spaces(" ".join(item.text for item in input)) -@pytest.fixture(scope='function') +@pytest.fixture(scope="function") def mock_get_service_data_retention(mocker): - data = {"id": str(sample_uuid()), - "service_id": str(sample_uuid()), - "service_name": "service name", - "notification_type": "email", - "days_of_retention": 7, - "created_at": datetime.now(), - "updated_at": None, - } - return mocker.patch('app.service_api_client.get_service_data_retention', - return_value=[data]) + data = { + "id": str(sample_uuid()), + "service_id": str(sample_uuid()), + "service_name": "service name", + "notification_type": "email", + "days_of_retention": 7, + "created_at": datetime.now(), + "updated_at": None, + } + return mocker.patch( + "app.service_api_client.get_service_data_retention", return_value=[data] + ) -@pytest.fixture(scope='function') +@pytest.fixture(scope="function") def mock_create_service_data_retention(mocker): - return mocker.patch('app.service_api_client.create_service_data_retention') + return mocker.patch("app.service_api_client.create_service_data_retention") -@pytest.fixture(scope='function') +@pytest.fixture(scope="function") def mock_update_service_data_retention(mocker): - return mocker.patch('app.service_api_client.update_service_data_retention') + return mocker.patch("app.service_api_client.update_service_data_retention") -@pytest.fixture(scope='function') +@pytest.fixture(scope="function") def mock_get_free_sms_fragment_limit(mocker): sample_limit = 250000 - return mocker.patch('app.billing_api_client.get_free_sms_fragment_limit_for_year', - return_value=sample_limit) + return mocker.patch( + "app.billing_api_client.get_free_sms_fragment_limit_for_year", + return_value=sample_limit, + ) -@pytest.fixture(scope='function') +@pytest.fixture(scope="function") def mock_create_or_update_free_sms_fragment_limit(mocker): sample_limit = 250000 - return mocker.patch('app.billing_api_client.create_or_update_free_sms_fragment_limit', - return_value=sample_limit) + return mocker.patch( + "app.billing_api_client.create_or_update_free_sms_fragment_limit", + return_value=sample_limit, + ) @contextmanager @@ -2484,417 +2663,468 @@ def set_config_values(app, dict): app.config[key] = old_values[key] -@pytest.fixture(scope='function') +@pytest.fixture(scope="function") def valid_token(notify_admin, fake_uuid): return generate_token( - json.dumps({'user_id': fake_uuid, 'secret_code': 'my secret'}), - notify_admin.config['SECRET_KEY'], - notify_admin.config['DANGEROUS_SALT'] + json.dumps({"user_id": fake_uuid, "secret_code": "my secret"}), + notify_admin.config["SECRET_KEY"], + notify_admin.config["DANGEROUS_SALT"], ) -@pytest.fixture(scope='function') +@pytest.fixture(scope="function") def mock_get_valid_service_inbound_api(mocker): def _get(service_id, inbound_api_id): return { - 'created_at': '2017-12-04T10:52:55.289026Z', - 'updated_by_id': fake_uuid, - 'id': inbound_api_id, - 'url': 'https://hello3.gsa.gov', - 'service_id': service_id, - 'updated_at': '2017-12-04T11:28:42.575153Z' + "created_at": "2017-12-04T10:52:55.289026Z", + "updated_by_id": fake_uuid, + "id": inbound_api_id, + "url": "https://hello3.gsa.gov", + "service_id": service_id, + "updated_at": "2017-12-04T11:28:42.575153Z", } - return mocker.patch('app.service_api_client.get_service_inbound_api', side_effect=_get) + return mocker.patch( + "app.service_api_client.get_service_inbound_api", side_effect=_get + ) -@pytest.fixture(scope='function') +@pytest.fixture(scope="function") def mock_get_valid_service_callback_api(mocker): def _get(service_id, callback_api_id): return { - 'created_at': '2017-12-04T10:52:55.289026Z', - 'updated_by_id': fake_uuid, - 'id': callback_api_id, - 'url': 'https://hello2.gsa.gov', - 'service_id': service_id, - 'updated_at': '2017-12-04T11:28:42.575153Z' + "created_at": "2017-12-04T10:52:55.289026Z", + "updated_by_id": fake_uuid, + "id": callback_api_id, + "url": "https://hello2.gsa.gov", + "service_id": service_id, + "updated_at": "2017-12-04T11:28:42.575153Z", } - return mocker.patch('app.service_api_client.get_service_callback_api', side_effect=_get) + return mocker.patch( + "app.service_api_client.get_service_callback_api", side_effect=_get + ) -@pytest.fixture(scope='function') +@pytest.fixture(scope="function") def mock_get_empty_service_inbound_api(mocker): return mocker.patch( - 'app.service_api_client.get_service_inbound_api', + "app.service_api_client.get_service_inbound_api", side_effect=lambda service_id, callback_api_id: None, ) -@pytest.fixture(scope='function') +@pytest.fixture(scope="function") def mock_get_empty_service_callback_api(mocker): return mocker.patch( - 'app.service_api_client.get_service_callback_api', + "app.service_api_client.get_service_callback_api", side_effect=lambda service_id, callback_api_id: None, ) -@pytest.fixture(scope='function') +@pytest.fixture(scope="function") def mock_create_service_inbound_api(mocker): def _create_service_inbound_api(service_id, url, bearer_token, user_id): return - return mocker.patch('app.service_api_client.create_service_inbound_api', side_effect=_create_service_inbound_api) + return mocker.patch( + "app.service_api_client.create_service_inbound_api", + side_effect=_create_service_inbound_api, + ) -@pytest.fixture(scope='function') +@pytest.fixture(scope="function") def mock_update_service_inbound_api(mocker): - def _update_service_inbound_api(service_id, url, bearer_token, user_id, inbound_api_id): + def _update_service_inbound_api( + service_id, url, bearer_token, user_id, inbound_api_id + ): return - return mocker.patch('app.service_api_client.update_service_inbound_api', side_effect=_update_service_inbound_api) + return mocker.patch( + "app.service_api_client.update_service_inbound_api", + side_effect=_update_service_inbound_api, + ) -@pytest.fixture(scope='function') +@pytest.fixture(scope="function") def mock_create_service_callback_api(mocker): def _create_service_callback_api(service_id, url, bearer_token, user_id): return - return mocker.patch('app.service_api_client.create_service_callback_api', side_effect=_create_service_callback_api) + return mocker.patch( + "app.service_api_client.create_service_callback_api", + side_effect=_create_service_callback_api, + ) -@pytest.fixture(scope='function') +@pytest.fixture(scope="function") def mock_update_service_callback_api(mocker): - def _update_service_callback_api(service_id, url, bearer_token, user_id, callback_api_id): + def _update_service_callback_api( + service_id, url, bearer_token, user_id, callback_api_id + ): return - return mocker.patch('app.service_api_client.update_service_callback_api', side_effect=_update_service_callback_api) + return mocker.patch( + "app.service_api_client.update_service_callback_api", + side_effect=_update_service_callback_api, + ) -@pytest.fixture(scope='function') +@pytest.fixture(scope="function") def organization_one(api_user_active): - return organization_json(ORGANISATION_ID, 'organization one', [api_user_active['id']]) + return organization_json( + ORGANISATION_ID, "organization one", [api_user_active["id"]] + ) -@pytest.fixture(scope='function') +@pytest.fixture(scope="function") def mock_get_organizations(mocker): def _get_organizations(): return [ - organization_json('7aa5d4e9-4385-4488-a489-07812ba13383', 'Org 1'), - organization_json('7aa5d4e9-4385-4488-a489-07812ba13384', 'Org 2'), - organization_json('7aa5d4e9-4385-4488-a489-07812ba13385', 'Org 3'), + organization_json("7aa5d4e9-4385-4488-a489-07812ba13383", "Org 1"), + organization_json("7aa5d4e9-4385-4488-a489-07812ba13384", "Org 2"), + organization_json("7aa5d4e9-4385-4488-a489-07812ba13385", "Org 3"), ] mocker.patch( - 'app.models.organization.AllOrganizations.client_method', + "app.models.organization.AllOrganizations.client_method", side_effect=_get_organizations, ) return mocker.patch( - 'app.notify_client.organizations_api_client.organizations_client.get_organizations', + "app.notify_client.organizations_api_client.organizations_client.get_organizations", side_effect=_get_organizations, ) -@pytest.fixture(scope='function') +@pytest.fixture(scope="function") def mock_get_organizations_with_unusual_domains(mocker): def _get_organizations(): return [ - organization_json('7aa5d4e9-4385-4488-a489-07812ba13383', 'Org 1', domains=[ - 'ldquo.net', - 'rdquo.net', - 'lsquo.net', - 'rsquo.net', - ]), + organization_json( + "7aa5d4e9-4385-4488-a489-07812ba13383", + "Org 1", + domains=[ + "ldquo.net", + "rdquo.net", + "lsquo.net", + "rsquo.net", + ], + ), ] - return mocker.patch('app.organizations_client.get_organizations', side_effect=_get_organizations) + return mocker.patch( + "app.organizations_client.get_organizations", side_effect=_get_organizations + ) -@pytest.fixture(scope='function') +@pytest.fixture(scope="function") def mock_get_organization(mocker): def _get_organization(org_id): return organization_json( org_id, { - 'o1': 'Org 1', - 'o2': 'Org 2', - 'o3': 'Org 3', - }.get(org_id, 'Test organization'), + "o1": "Org 1", + "o2": "Org 2", + "o3": "Org 3", + }.get(org_id, "Test organization"), ) - return mocker.patch('app.organizations_client.get_organization', side_effect=_get_organization) + return mocker.patch( + "app.organizations_client.get_organization", side_effect=_get_organization + ) -@pytest.fixture(scope='function') +@pytest.fixture(scope="function") def mock_get_organization_by_domain(mocker): def _get_organization_by_domain(domain): return organization_json(ORGANISATION_ID) return mocker.patch( - 'app.organizations_client.get_organization_by_domain', + "app.organizations_client.get_organization_by_domain", side_effect=_get_organization_by_domain, ) -@pytest.fixture(scope='function') +@pytest.fixture(scope="function") def mock_get_no_organization_by_domain(mocker): return mocker.patch( - 'app.organizations_client.get_organization_by_domain', + "app.organizations_client.get_organization_by_domain", return_value=None, ) -@pytest.fixture(scope='function') +@pytest.fixture(scope="function") def mock_get_service_organization( mocker, mock_get_organization, ): return mocker.patch( - 'app.models.service.Service.organization_id', + "app.models.service.Service.organization_id", new_callable=PropertyMock, return_value=ORGANISATION_ID, ) -@pytest.fixture(scope='function') +@pytest.fixture(scope="function") def mock_update_service_organization(mocker): def _update_service_organization(service_id, org_id): return return mocker.patch( - 'app.organizations_client.update_service_organization', - side_effect=_update_service_organization + "app.organizations_client.update_service_organization", + side_effect=_update_service_organization, ) def _get_organization_services(organization_id): - if organization_id == 'o1': + if organization_id == "o1": return [ - service_json('12345', 'service one', restricted=False), - service_json('67890', 'service two'), - service_json('abcde', 'service three'), + service_json("12345", "service one", restricted=False), + service_json("67890", "service two"), + service_json("abcde", "service three"), ] - if organization_id == 'o2': + if organization_id == "o2": return [ - service_json('12345', 'service one (org 2)', restricted=False), - service_json('67890', 'service two (org 2)', restricted=False), - service_json('abcde', 'service three'), + service_json("12345", "service one (org 2)", restricted=False), + service_json("67890", "service two (org 2)", restricted=False), + service_json("abcde", "service three"), ] return [ - service_json('12345', 'service one'), - service_json('67890', 'service two'), - service_json(SERVICE_ONE_ID, 'service one', [sample_uuid()]) + service_json("12345", "service one"), + service_json("67890", "service two"), + service_json(SERVICE_ONE_ID, "service one", [sample_uuid()]), ] -@pytest.fixture(scope='function') +@pytest.fixture(scope="function") def mock_get_organization_services(mocker, api_user_active): return mocker.patch( - 'app.organizations_client.get_organization_services', - side_effect=_get_organization_services + "app.organizations_client.get_organization_services", + side_effect=_get_organization_services, ) -@pytest.fixture(scope='function') +@pytest.fixture(scope="function") def mock_get_users_for_organization(mocker): def _get_users_for_organization(org_id): return [ - user_json(id_='1234', name='Test User 1'), - user_json(id_='5678', name='Test User 2', email_address='testt@gsa.gov'), + user_json(id_="1234", name="Test User 1"), + user_json(id_="5678", name="Test User 2", email_address="testt@gsa.gov"), ] return mocker.patch( - 'app.models.user.OrganizationUsers.client_method', - side_effect=_get_users_for_organization + "app.models.user.OrganizationUsers.client_method", + side_effect=_get_users_for_organization, ) -@pytest.fixture(scope='function') +@pytest.fixture(scope="function") def mock_get_invited_users_for_organization(mocker, sample_org_invite): def _get_invited_invited_users_for_organization(org_id): - return [ - sample_org_invite - ] + return [sample_org_invite] return mocker.patch( - 'app.models.user.OrganizationInvitedUsers.client_method', - side_effect=_get_invited_invited_users_for_organization + "app.models.user.OrganizationInvitedUsers.client_method", + side_effect=_get_invited_invited_users_for_organization, ) -@pytest.fixture(scope='function') +@pytest.fixture(scope="function") def sample_org_invite(mocker, organization_one): - id_ = str(UUID(bytes=b'sample_org_invit', version=4)) - invited_by = organization_one['users'][0] - email_address = 'invited_user@test.gsa.gov' - organization = organization_one['id'] + id_ = str(UUID(bytes=b"sample_org_invit", version=4)) + invited_by = organization_one["users"][0] + email_address = "invited_user@test.gsa.gov" + organization = organization_one["id"] created_at = str(datetime.utcnow()) - status = 'pending' + status = "pending" - return org_invite_json(id_, invited_by, organization, email_address, created_at, status) + return org_invite_json( + id_, invited_by, organization, email_address, created_at, status + ) -@pytest.fixture(scope='function') +@pytest.fixture(scope="function") def mock_get_invites_for_organization(mocker, sample_org_invite): def _get_org_invites(org_id): data = [] for i in range(0, 5): invite = copy.copy(sample_org_invite) - invite['email_address'] = 'user_{}@testnotify.gsa.gov'.format(i) + invite["email_address"] = "user_{}@testnotify.gsa.gov".format(i) data.append(invite) return data - return mocker.patch('app.models.user.OrganizationInvitedUsers.client_method', side_effect=_get_org_invites) + return mocker.patch( + "app.models.user.OrganizationInvitedUsers.client_method", + side_effect=_get_org_invites, + ) -@pytest.fixture(scope='function') +@pytest.fixture(scope="function") def mock_check_org_invite_token(mocker, sample_org_invite): def _check_org_token(token): return sample_org_invite - return mocker.patch('app.org_invite_api_client.check_token', side_effect=_check_org_token) + return mocker.patch( + "app.org_invite_api_client.check_token", side_effect=_check_org_token + ) -@pytest.fixture(scope='function') +@pytest.fixture(scope="function") def mock_check_org_cancelled_invite_token(mocker, sample_org_invite): def _check_org_token(token): - sample_org_invite['status'] = 'cancelled' + sample_org_invite["status"] = "cancelled" return sample_org_invite - return mocker.patch('app.org_invite_api_client.check_token', side_effect=_check_org_token) + return mocker.patch( + "app.org_invite_api_client.check_token", side_effect=_check_org_token + ) -@pytest.fixture(scope='function') +@pytest.fixture(scope="function") def mock_check_org_accepted_invite_token(mocker, sample_org_invite): - sample_org_invite['status'] = 'accepted' + sample_org_invite["status"] = "accepted" def _check_org_token(token): return sample_org_invite - return mocker.patch('app.org_invite_api_client.check_token', return_value=sample_org_invite) + return mocker.patch( + "app.org_invite_api_client.check_token", return_value=sample_org_invite + ) -@pytest.fixture(scope='function') +@pytest.fixture(scope="function") def mock_accept_org_invite(mocker, sample_org_invite): def _accept(organization_id, invite_id): return sample_org_invite - return mocker.patch('app.org_invite_api_client.accept_invite', side_effect=_accept) + return mocker.patch("app.org_invite_api_client.accept_invite", side_effect=_accept) -@pytest.fixture(scope='function') +@pytest.fixture(scope="function") def mock_add_user_to_organization(mocker, organization_one, api_user_active): def _add_user(organization_id, user_id): return api_user_active - return mocker.patch('app.user_api_client.add_user_to_organization', side_effect=_add_user) + return mocker.patch( + "app.user_api_client.add_user_to_organization", side_effect=_add_user + ) -@pytest.fixture(scope='function') +@pytest.fixture(scope="function") def mock_update_organization(mocker): def _update_org(org, **kwargs): return - return mocker.patch('app.organizations_client.update_organization', side_effect=_update_org) - - -@pytest.fixture -def mock_get_organizations_and_services_for_user(mocker, organization_one, api_user_active): - def _get_orgs_and_services(user_id): - return { - 'organizations': [], - 'services': [] - } - return mocker.patch( - 'app.user_api_client.get_organizations_and_services_for_user', - side_effect=_get_orgs_and_services + "app.organizations_client.update_organization", side_effect=_update_org ) @pytest.fixture -def mock_get_non_empty_organizations_and_services_for_user(mocker, organization_one, api_user_active): +def mock_get_organizations_and_services_for_user( + mocker, organization_one, api_user_active +): + def _get_orgs_and_services(user_id): + return {"organizations": [], "services": []} + return mocker.patch( + "app.user_api_client.get_organizations_and_services_for_user", + side_effect=_get_orgs_and_services, + ) + + +@pytest.fixture +def mock_get_non_empty_organizations_and_services_for_user( + mocker, organization_one, api_user_active +): def _make_services(name, trial_mode=False): - return [{ - 'name': '{} {}'.format(name, i), - 'id': SERVICE_TWO_ID, - 'restricted': trial_mode, - 'organization': None, - } for i in range(1, 3)] + return [ + { + "name": "{} {}".format(name, i), + "id": SERVICE_TWO_ID, + "restricted": trial_mode, + "organization": None, + } + for i in range(1, 3) + ] def _get_orgs_and_services(user_id): return { - 'organizations': [ + "organizations": [ { - 'name': 'Org 1', - 'id': 'o1', - 'count_of_live_services': 1, + "name": "Org 1", + "id": "o1", + "count_of_live_services": 1, }, { - 'name': 'Org 2', - 'id': 'o2', - 'count_of_live_services': 2, + "name": "Org 2", + "id": "o2", + "count_of_live_services": 2, }, { - 'name': 'Org 3', - 'id': 'o3', - 'count_of_live_services': 0, + "name": "Org 3", + "id": "o3", + "count_of_live_services": 0, }, ], - 'services': ( - _get_organization_services('o1') - + _get_organization_services('o2') - + _make_services('Service') - ) + "services": ( + _get_organization_services("o1") + + _get_organization_services("o2") + + _make_services("Service") + ), } return mocker.patch( - 'app.user_api_client.get_organizations_and_services_for_user', - side_effect=_get_orgs_and_services + "app.user_api_client.get_organizations_and_services_for_user", + side_effect=_get_orgs_and_services, ) @pytest.fixture def mock_get_just_services_for_user(mocker, organization_one, api_user_active): - def _make_services(name, trial_mode=False): - return [{ - 'name': '{} {}'.format(name, i + 1), - 'id': id, - 'restricted': trial_mode, - 'organization': None, - } for i, id in enumerate([SERVICE_TWO_ID, SERVICE_ONE_ID])] + return [ + { + "name": "{} {}".format(name, i + 1), + "id": id, + "restricted": trial_mode, + "organization": None, + } + for i, id in enumerate([SERVICE_TWO_ID, SERVICE_ONE_ID]) + ] def _get_orgs_and_services(user_id): return { - 'organizations': [], - 'services': _make_services('Service'), + "organizations": [], + "services": _make_services("Service"), } return mocker.patch( - 'app.user_api_client.get_organizations_and_services_for_user', - side_effect=_get_orgs_and_services + "app.user_api_client.get_organizations_and_services_for_user", + side_effect=_get_orgs_and_services, ) @pytest.fixture -def mock_get_empty_organizations_and_one_service_for_user(mocker, organization_one, api_user_active): - +def mock_get_empty_organizations_and_one_service_for_user( + mocker, organization_one, api_user_active +): def _get_orgs_and_services(user_id): return { - 'organizations': [], - 'services': [{ - 'name': 'Only service', - 'id': SERVICE_TWO_ID, - 'restricted': True, - }] + "organizations": [], + "services": [ + { + "name": "Only service", + "id": SERVICE_TWO_ID, + "restricted": True, + } + ], } return mocker.patch( - 'app.user_api_client.get_organizations_and_services_for_user', - side_effect=_get_orgs_and_services + "app.user_api_client.get_organizations_and_services_for_user", + side_effect=_get_orgs_and_services, ) @@ -2903,91 +3133,103 @@ def mock_create_event(mocker): """ This should be used whenever your code is calling `flask_login.login_user` """ + def _add_event(event_type, event_data): return - return mocker.patch('app.events_api_client.create_event', side_effect=_add_event) + return mocker.patch("app.events_api_client.create_event", side_effect=_add_event) def url_for_endpoint_with_token(endpoint, token, next=None): - token = token.replace('%2E', '.') + token = token.replace("%2E", ".") return url_for(endpoint, token=token, next=next) @pytest.fixture def mock_get_template_folders(mocker): - return mocker.patch('app.template_folder_api_client.get_template_folders', return_value=[]) + return mocker.patch( + "app.template_folder_api_client.get_template_folders", return_value=[] + ) @pytest.fixture def mock_move_to_template_folder(mocker): - return mocker.patch('app.template_folder_api_client.move_to_folder') + return mocker.patch("app.template_folder_api_client.move_to_folder") @pytest.fixture def mock_create_template_folder(mocker): - return mocker.patch('app.template_folder_api_client.create_template_folder', return_value=sample_uuid()) + return mocker.patch( + "app.template_folder_api_client.create_template_folder", + return_value=sample_uuid(), + ) -@pytest.fixture(scope='function') +@pytest.fixture(scope="function") def mock_get_service_and_organization_counts(mocker): - return mocker.patch('app.status_api_client.get_count_of_live_services_and_organizations', return_value={ - 'organizations': 111, - 'services': 9999, - }) + return mocker.patch( + "app.status_api_client.get_count_of_live_services_and_organizations", + return_value={ + "organizations": 111, + "services": 9999, + }, + ) -@pytest.fixture(scope='function') +@pytest.fixture(scope="function") def mock_get_service_history(mocker): - return mocker.patch('app.service_api_client.get_service_history', return_value={ - 'service_history': [ - { - 'name': 'Example service', - 'created_at': '2010-10-10T06:01:01.000000Z', - 'updated_at': None, - 'created_by_id': uuid4(), - }, - { - 'name': 'Before lunch', - 'created_at': '2010-10-10T06:01:01.000000Z', - 'updated_at': '2012-12-12T17:12:12.000000Z', - 'created_by_id': sample_uuid(), - }, - { - 'name': 'After lunch', - 'created_at': '2010-10-10T06:01:01.000000Z', - 'updated_at': '2012-12-12T18:13:13.000000Z', - 'created_by_id': sample_uuid(), - }, - ], - 'api_key_history': [ - { - 'name': 'Good key', - 'updated_at': None, - 'created_at': '2010-10-10T15:10:10.000000Z', - 'created_by_id': sample_uuid(), - }, - { - 'name': 'Bad key', - 'updated_at': '2012-11-11T17:12:12.000000Z', - 'created_at': '2011-11-11T16:11:11.000000Z', - 'created_by_id': sample_uuid(), - }, - { - 'name': 'Bad key', - 'updated_at': None, - 'created_at': '2011-11-11T16:11:11.000000Z', - 'created_by_id': sample_uuid(), - }, - { - 'name': 'Key event returned in non-chronological order', - 'updated_at': None, - 'created_at': '2010-10-10T14:09:09.000000Z', - 'created_by_id': sample_uuid(), - }, - ], - 'events': [], - }) + return mocker.patch( + "app.service_api_client.get_service_history", + return_value={ + "service_history": [ + { + "name": "Example service", + "created_at": "2010-10-10T06:01:01.000000Z", + "updated_at": None, + "created_by_id": uuid4(), + }, + { + "name": "Before lunch", + "created_at": "2010-10-10T06:01:01.000000Z", + "updated_at": "2012-12-12T17:12:12.000000Z", + "created_by_id": sample_uuid(), + }, + { + "name": "After lunch", + "created_at": "2010-10-10T06:01:01.000000Z", + "updated_at": "2012-12-12T18:13:13.000000Z", + "created_by_id": sample_uuid(), + }, + ], + "api_key_history": [ + { + "name": "Good key", + "updated_at": None, + "created_at": "2010-10-10T15:10:10.000000Z", + "created_by_id": sample_uuid(), + }, + { + "name": "Bad key", + "updated_at": "2012-11-11T17:12:12.000000Z", + "created_at": "2011-11-11T16:11:11.000000Z", + "created_by_id": sample_uuid(), + }, + { + "name": "Bad key", + "updated_at": None, + "created_at": "2011-11-11T16:11:11.000000Z", + "created_by_id": sample_uuid(), + }, + { + "name": "Key event returned in non-chronological order", + "updated_at": None, + "created_at": "2010-10-10T14:09:09.000000Z", + "created_by_id": sample_uuid(), + }, + ], + "events": [], + }, + ) def create_api_user_active(with_unique_id=False): @@ -2999,7 +3241,7 @@ def create_api_user_active(with_unique_id=False): def create_active_user_empty_permissions(with_unique_id=False): return create_service_one_user( id=str(uuid4()) if with_unique_id else sample_uuid(), - name='Test User With Empty Permissions', + name="Test User With Empty Permissions", ) @@ -3012,19 +3254,21 @@ def create_active_user_with_permissions(with_unique_id=False): def create_active_user_view_permissions(with_unique_id=False): return create_service_one_user( id=str(uuid4()) if with_unique_id else sample_uuid(), - name='Test User With Permissions', - permissions={SERVICE_ONE_ID: ['view_activity']}, + name="Test User With Permissions", + permissions={SERVICE_ONE_ID: ["view_activity"]}, ) def create_active_caseworking_user(with_unique_id=False): return create_user( id=str(uuid4()) if with_unique_id else sample_uuid(), - email_address='caseworker@example.gsa.gov', - permissions={SERVICE_ONE_ID: [ - 'send_texts', - 'send_emails', - ]}, + email_address="caseworker@example.gsa.gov", + permissions={ + SERVICE_ONE_ID: [ + "send_texts", + "send_emails", + ] + }, services=[SERVICE_ONE_ID], ) @@ -3032,60 +3276,68 @@ def create_active_caseworking_user(with_unique_id=False): def create_active_user_no_api_key_permission(with_unique_id=False): return create_service_one_user( id=str(uuid4()) if with_unique_id else sample_uuid(), - name='Test User With Permissions', - permissions={SERVICE_ONE_ID: [ - 'manage_templates', - 'manage_settings', - 'manage_users', - 'view_activity', - ]}, + name="Test User With Permissions", + permissions={ + SERVICE_ONE_ID: [ + "manage_templates", + "manage_settings", + "manage_users", + "view_activity", + ] + }, ) def create_active_user_no_settings_permission(with_unique_id=False): return create_service_one_user( id=str(uuid4()) if with_unique_id else sample_uuid(), - name='Test User With Permissions', - permissions={SERVICE_ONE_ID: [ - 'manage_templates', - 'manage_api_keys', - 'view_activity', - ]}, + name="Test User With Permissions", + permissions={ + SERVICE_ONE_ID: [ + "manage_templates", + "manage_api_keys", + "view_activity", + ] + }, ) def create_active_user_manage_template_permissions(with_unique_id=False): return create_service_one_user( id=str(uuid4()) if with_unique_id else sample_uuid(), - name='Test User With Permissions', - permissions={SERVICE_ONE_ID: [ - 'manage_templates', - 'view_activity', - ]}, + name="Test User With Permissions", + permissions={ + SERVICE_ONE_ID: [ + "manage_templates", + "view_activity", + ] + }, ) def create_platform_admin_user(with_unique_id=False, permissions=None): return create_user( id=str(uuid4()) if with_unique_id else sample_uuid(), - name='Platform admin user', - email_address='platform@admin.gsa.gov', + name="Platform admin user", + email_address="platform@admin.gsa.gov", permissions=permissions or {}, - platform_admin=True + platform_admin=True, ) def create_service_one_admin(**overrides): user_data = { - 'permissions': {SERVICE_ONE_ID: [ - 'send_texts', - 'send_emails', - 'manage_users', - 'manage_templates', - 'manage_settings', - 'manage_api_keys', - 'view_activity' - ]}, + "permissions": { + SERVICE_ONE_ID: [ + "send_texts", + "send_emails", + "manage_users", + "manage_templates", + "manage_settings", + "manage_api_keys", + "view_activity", + ] + }, } user_data.update(overrides) return create_service_one_user(**user_data) @@ -3093,8 +3345,8 @@ def create_service_one_admin(**overrides): def create_service_one_user(**overrides): user_data = { - 'organizations': [ORGANISATION_ID], - 'services': [SERVICE_ONE_ID], + "organizations": [ORGANISATION_ID], + "services": [SERVICE_ONE_ID], } user_data.update(overrides) return create_user(**user_data) @@ -3102,128 +3354,132 @@ def create_service_one_user(**overrides): def create_user(**overrides): user_data = { - 'name': 'Test User', - 'password': 'somepassword', - 'email_address': 'test@user.gsa.gov', - 'mobile_number': '202-867-5303', - 'state': 'active', - 'failed_login_count': 0, - 'permissions': {}, - 'platform_admin': False, - 'auth_type': 'sms_auth', - 'password_changed_at': str(datetime.utcnow()), - 'services': [], - 'organizations': [], - 'current_session_id': None, - 'logged_in_at': None, - 'email_access_validated_at': None, + "name": "Test User", + "password": "somepassword", + "email_address": "test@user.gsa.gov", + "mobile_number": "202-867-5303", + "state": "active", + "failed_login_count": 0, + "permissions": {}, + "platform_admin": False, + "auth_type": "sms_auth", + "password_changed_at": str(datetime.utcnow()), + "services": [], + "organizations": [], + "current_session_id": None, + "logged_in_at": None, + "email_access_validated_at": None, } user_data.update(overrides) return user_data def create_reply_to_email_address( - id_='1234', - service_id='abcd', - email_address='test@example.com', + id_="1234", + service_id="abcd", + email_address="test@example.com", is_default=True, created_at=None, - updated_at=None + updated_at=None, ): return { - 'id': id_, - 'service_id': service_id, - 'email_address': email_address, - 'is_default': is_default, - 'created_at': created_at, - 'updated_at': updated_at + "id": id_, + "service_id": service_id, + "email_address": email_address, + "is_default": is_default, + "created_at": created_at, + "updated_at": updated_at, } -def create_multiple_email_reply_to_addresses(service_id='abcd'): +def create_multiple_email_reply_to_addresses(service_id="abcd"): return [ { - 'id': '1234', - 'service_id': service_id, - 'email_address': 'test@example.com', - 'is_default': True, - 'created_at': datetime.utcnow(), - 'updated_at': None - }, { - 'id': '5678', - 'service_id': service_id, - 'email_address': 'test2@example.com', - 'is_default': False, - 'created_at': datetime.utcnow(), - 'updated_at': None - }, { - 'id': '9457', - 'service_id': service_id, - 'email_address': 'test3@example.com', - 'is_default': False, - 'created_at': datetime.utcnow(), - 'updated_at': None - } + "id": "1234", + "service_id": service_id, + "email_address": "test@example.com", + "is_default": True, + "created_at": datetime.utcnow(), + "updated_at": None, + }, + { + "id": "5678", + "service_id": service_id, + "email_address": "test2@example.com", + "is_default": False, + "created_at": datetime.utcnow(), + "updated_at": None, + }, + { + "id": "9457", + "service_id": service_id, + "email_address": "test3@example.com", + "is_default": False, + "created_at": datetime.utcnow(), + "updated_at": None, + }, ] def create_sms_sender( - id_='1234', - service_id='abcd', - sms_sender='GOVUK', + id_="1234", + service_id="abcd", + sms_sender="GOVUK", is_default=True, created_at=None, inbound_number_id=None, - updated_at=None + updated_at=None, ): return { - 'id': id_, - 'service_id': service_id, - 'sms_sender': sms_sender, - 'is_default': is_default, - 'created_at': created_at, - 'inbound_number_id': inbound_number_id, - 'updated_at': updated_at + "id": id_, + "service_id": service_id, + "sms_sender": sms_sender, + "is_default": is_default, + "created_at": created_at, + "inbound_number_id": inbound_number_id, + "updated_at": updated_at, } -def create_multiple_sms_senders(service_id='abcd'): +def create_multiple_sms_senders(service_id="abcd"): return [ { - 'id': '1234', - 'service_id': service_id, - 'sms_sender': 'Example', - 'is_default': True, - 'created_at': datetime.utcnow(), - 'inbound_number_id': '1234', - 'updated_at': None - }, { - 'id': '5678', - 'service_id': service_id, - 'sms_sender': 'Example 2', - 'is_default': False, - 'created_at': datetime.utcnow(), - 'inbound_number_id': None, - 'updated_at': None - }, { - 'id': '9457', - 'service_id': service_id, - 'sms_sender': 'Example 3', - 'is_default': False, - 'created_at': datetime.utcnow(), - 'inbound_number_id': None, - 'updated_at': None - } + "id": "1234", + "service_id": service_id, + "sms_sender": "Example", + "is_default": True, + "created_at": datetime.utcnow(), + "inbound_number_id": "1234", + "updated_at": None, + }, + { + "id": "5678", + "service_id": service_id, + "sms_sender": "Example 2", + "is_default": False, + "created_at": datetime.utcnow(), + "inbound_number_id": None, + "updated_at": None, + }, + { + "id": "9457", + "service_id": service_id, + "sms_sender": "Example 3", + "is_default": False, + "created_at": datetime.utcnow(), + "inbound_number_id": None, + "updated_at": None, + }, ] def create_notification( notifification_id=None, - service_id='abcd', - notification_status='delivered', + service_id="abcd", + notification_status="delivered", redact_personalisation=False, template_type=None, - template_name='sample template', + template_name="sample template", key_type=None, sent_one_off=True, reply_to_text=None, @@ -3234,41 +3490,41 @@ def create_notification( status=notification_status, template_type=template_type, reply_to_text=reply_to_text, - )['notifications'][0] + )["notifications"][0] - noti['id'] = notifification_id or sample_uuid() + noti["id"] = notifification_id or sample_uuid() if sent_one_off: - noti['created_by'] = { - 'id': sample_uuid(), - 'name': 'Test User', - 'email_address': 'test@user.gsa.gov' + noti["created_by"] = { + "id": sample_uuid(), + "name": "Test User", + "email_address": "test@user.gsa.gov", } - noti['personalisation'] = {'name': 'Jo'} - noti['template'] = template_json( + noti["personalisation"] = {"name": "Jo"} + noti["template"] = template_json( service_id, - '5407f4db-51c7-4150-8758-35412d42186a', - content='hello ((name))', - subject='blah', + "5407f4db-51c7-4150-8758-35412d42186a", + content="hello ((name))", + subject="blah", redact_personalisation=redact_personalisation, type_=template_type, - name=template_name + name=template_name, ) if key_type: - noti['key_type'] = key_type + noti["key_type"] = key_type return noti def create_notifications( service_id=SERVICE_ONE_ID, - template_type='sms', + template_type="sms", rows=5, status=None, - subject='subject', - content='content', + subject="subject", + content="content", client_reference=None, personalisation=None, redact_personalisation=False, - to=None + to=None, ): template = template_json( service_id, @@ -3287,28 +3543,24 @@ def create_notifications( template_type=template_type, client_reference=client_reference, status=status, - created_by_name='Firstname Lastname', - to=to + created_by_name="Firstname Lastname", + to=to, ) def create_folder(id): - return { - 'id': id, - 'parent_id': None, - 'name': 'My folder' - } + return {"id": id, "parent_id": None, "name": "My folder"} def create_template( service_id=SERVICE_ONE_ID, template_id=None, - template_type='sms', - name='sample template', - content='Template content', - subject='Template subject', + template_type="sms", + name="sample template", + content="Template content", + subject="Template subject", redact_personalisation=False, - folder=None + folder=None, ): return template_json( service_id=service_id, @@ -3324,26 +3576,22 @@ def create_template( @pytest.fixture def mock_get_invited_user_by_id(mocker, sample_invite): - def _get( - invited_user_id - ): + def _get(invited_user_id): return sample_invite return mocker.patch( - 'app.invite_api_client.get_invited_user', + "app.invite_api_client.get_invited_user", side_effect=_get, ) @pytest.fixture def mock_get_invited_org_user_by_id(mocker, sample_org_invite): - def _get( - invited_org_user_id - ): + def _get(invited_org_user_id): return sample_org_invite return mocker.patch( - 'app.org_invite_api_client.get_invited_user', + "app.org_invite_api_client.get_invited_user", side_effect=_get, ) @@ -3352,15 +3600,15 @@ def login_for_end_to_end_testing(browser): # Open a new page and go to the staging site. context = browser.new_context() page = context.new_page() - page.goto(os.getenv('NOTIFY_E2E_TEST_URI')) + page.goto(os.getenv("NOTIFY_E2E_TEST_URI")) - sign_in_button = page.get_by_role('link', name='Sign in') + sign_in_button = page.get_by_role("link", name="Sign in") # Test trying to sign in. sign_in_button.click() # Wait for the next page to fully load. - page.wait_for_load_state('domcontentloaded') + page.wait_for_load_state("domcontentloaded") # Check for the sign in form elements. # NOTE: Playwright cannot find input elements by role and recommends using @@ -3369,17 +3617,17 @@ def login_for_end_to_end_testing(browser): # See https://playwright.dev/python/docs/api/class-page#page-get-by-label # and https://playwright.dev/python/docs/locators#locate-by-css-or-xpath # for more information. - email_address_input = page.get_by_label('Email address') - password_input = page.get_by_label('Password') - continue_button = page.get_by_role('button', name=re.compile('Continue')) + email_address_input = page.get_by_label("Email address") + password_input = page.get_by_label("Password") + continue_button = page.get_by_role("button", name=re.compile("Continue")) # Sign in to the site. - email_address_input.fill(os.getenv('NOTIFY_E2E_TEST_EMAIL')) - password_input.fill(os.getenv('NOTIFY_E2E_TEST_PASSWORD')) + email_address_input.fill(os.getenv("NOTIFY_E2E_TEST_EMAIL")) + password_input.fill(os.getenv("NOTIFY_E2E_TEST_PASSWORD")) continue_button.click() # Wait for the next page to fully load. - page.wait_for_load_state('domcontentloaded') + page.wait_for_load_state("domcontentloaded") # Check for the sign in form elements. # NOTE: Playwright cannot find input elements by role and recommends using @@ -3411,30 +3659,31 @@ def login_for_end_to_end_testing(browser): # context.storage_state(path=auth_state_path) -@pytest.fixture(scope='session') +@pytest.fixture(scope="session") def end_to_end_context(browser): # Create a context with HTTP Authentication credentials for Playwright E2E # tests, if the environment variables exist. - if os.getenv('NOTIFY_E2E_TEST_HTTP_AUTH_USER'): - context = browser.new_context(http_credentials={ - 'username': os.getenv('NOTIFY_E2E_TEST_HTTP_AUTH_USER'), - 'password': os.getenv('NOTIFY_E2E_TEST_HTTP_AUTH_PASSWORD'), - }) + if os.getenv("NOTIFY_E2E_TEST_HTTP_AUTH_USER"): + context = browser.new_context( + http_credentials={ + "username": os.getenv("NOTIFY_E2E_TEST_HTTP_AUTH_USER"), + "password": os.getenv("NOTIFY_E2E_TEST_HTTP_AUTH_PASSWORD"), + } + ) else: context = browser.new_context() yield context -@pytest.fixture(scope='session') +@pytest.fixture(scope="session") def end_to_end_authenticated_context(browser): # Create and load a previously authenticated context for Playwright E2E # tests. login_for_end_to_end_testing(browser) auth_state_path = os.path.join( - os.getenv('NOTIFY_E2E_AUTH_STATE_PATH'), - 'state.json' + os.getenv("NOTIFY_E2E_AUTH_STATE_PATH"), "state.json" ) context = browser.new_context(storage_state=auth_state_path) diff --git a/tests/end_to_end/test_accounts_page.py b/tests/end_to_end/test_accounts_page.py index 2073d203b..35976929e 100644 --- a/tests/end_to_end/test_accounts_page.py +++ b/tests/end_to_end/test_accounts_page.py @@ -6,74 +6,69 @@ import pytest from playwright.sync_api import expect -@pytest.mark.skip(reason='Not authenticating test users.') +@pytest.mark.skip(reason="Not authenticating test users.") def test_accounts_page(end_to_end_authenticated_context): # Open a new page and go to the staging site. page = end_to_end_authenticated_context.new_page() - accounts_uri = '{}accounts'.format(os.getenv('NOTIFY_E2E_TEST_URI')) + accounts_uri = "{}accounts".format(os.getenv("NOTIFY_E2E_TEST_URI")) page.goto(accounts_uri) # Check to make sure that we've arrived at the next page. - page.wait_for_load_state('domcontentloaded') + page.wait_for_load_state("domcontentloaded") # Check to make sure that we've arrived at the next page. # Check the page title exists and matches what we expect. - expect(page).to_have_title(re.compile('Choose service')) + expect(page).to_have_title(re.compile("Choose service")) # Check for the sign in heading. - sign_in_heading = page.get_by_role('heading', name='Choose service') + sign_in_heading = page.get_by_role("heading", name="Choose service") expect(sign_in_heading).to_be_visible() # Retrieve some prominent elements on the page for testing. add_service_button = page.get_by_role( - 'button', - name=re.compile('Add a new service') + "button", name=re.compile("Add a new service") ) expect(add_service_button).to_be_visible() -@pytest.mark.skip(reason='Not authenticating test users.') +@pytest.mark.skip(reason="Not authenticating test users.") def test_add_new_service_workflow(end_to_end_authenticated_context): # Prepare for adding a new service later in the test. current_date_time = datetime.datetime.now() - new_service_name = 'E2E Federal Test Service {now} - {browser_type}'.format( - now=current_date_time.strftime('%m/%d/%Y %H:%M:%S'), - browser_type=end_to_end_authenticated_context.browser.browser_type.name + new_service_name = "E2E Federal Test Service {now} - {browser_type}".format( + now=current_date_time.strftime("%m/%d/%Y %H:%M:%S"), + browser_type=end_to_end_authenticated_context.browser.browser_type.name, ) # Open a new page and go to the staging site. page = end_to_end_authenticated_context.new_page() - accounts_uri = '{}accounts'.format(os.getenv('NOTIFY_E2E_TEST_URI')) + accounts_uri = "{}accounts".format(os.getenv("NOTIFY_E2E_TEST_URI")) page.goto(accounts_uri) # Check to make sure that we've arrived at the next page. - page.wait_for_load_state('domcontentloaded') + page.wait_for_load_state("domcontentloaded") # Check to make sure that we've arrived at the next page. # Check the page title exists and matches what we expect. - expect(page).to_have_title(re.compile('Choose service')) + expect(page).to_have_title(re.compile("Choose service")) # Check for the sign in heading. - sign_in_heading = page.get_by_role('heading', name='Choose service') + sign_in_heading = page.get_by_role("heading", name="Choose service") expect(sign_in_heading).to_be_visible() # Retrieve some prominent elements on the page for testing. add_service_button = page.get_by_role( - 'button', - name=re.compile('Add a new service') + "button", name=re.compile("Add a new service") ) expect(add_service_button).to_be_visible() - existing_service_link = page.get_by_role( - 'link', - name=new_service_name - ) + existing_service_link = page.get_by_role("link", name=new_service_name) # Check to see if the service was already created - if so, we should fail. # TODO: Figure out how to make this truly isolated, and/or work in a @@ -84,10 +79,10 @@ def test_add_new_service_workflow(end_to_end_authenticated_context): add_service_button.click() # Check to make sure that we've arrived at the next page. - page.wait_for_load_state('domcontentloaded') + page.wait_for_load_state("domcontentloaded") # Check for the sign in heading. - about_heading = page.get_by_role('heading', name='About your service') + about_heading = page.get_by_role("heading", name="About your service") expect(about_heading).to_be_visible() # Retrieve some prominent elements on the page for testing. @@ -95,10 +90,7 @@ def test_add_new_service_workflow(end_to_end_authenticated_context): federal_radio_button = page.locator('xpath=//input[@value="federal"]') state_radio_button = page.locator('xpath=//input[@value="state"]') other_radio_button = page.locator('xpath=//input[@value="other"]') - add_service_button = page.get_by_role( - 'button', - name=re.compile('Add service') - ) + add_service_button = page.get_by_role("button", name=re.compile("Add service")) expect(service_name_input).to_be_visible() expect(federal_radio_button).to_be_visible() @@ -114,7 +106,7 @@ def test_add_new_service_workflow(end_to_end_authenticated_context): add_service_button.click() # Check to make sure that we've arrived at the next page. - page.wait_for_load_state('domcontentloaded') + page.wait_for_load_state("domcontentloaded") # Check for the service name title and heading. service_heading = page.get_by_text(new_service_name) diff --git a/tests/end_to_end/test_landing_and_sign_in_pages.py b/tests/end_to_end/test_landing_and_sign_in_pages.py index d8b0008f7..b86581af8 100644 --- a/tests/end_to_end/test_landing_and_sign_in_pages.py +++ b/tests/end_to_end/test_landing_and_sign_in_pages.py @@ -8,24 +8,20 @@ from playwright.sync_api import expect def test_landing_page(end_to_end_context): # Open a new page and go to the staging site. page = end_to_end_context.new_page() - page.goto(os.getenv('NOTIFY_E2E_TEST_URI')) + page.goto(os.getenv("NOTIFY_E2E_TEST_URI")) # Check to make sure that we've arrived at the next page. - page.wait_for_load_state('domcontentloaded') + page.wait_for_load_state("domcontentloaded") # Check the page title exists and matches what we expect. - expect(page).to_have_title(re.compile('Notify.gov')) + expect(page).to_have_title(re.compile("Notify.gov")) # Retrieve some prominent elements on the page for testing. main_header = page.get_by_role( - 'heading', - name='Send text messages to your participants' - ) - sign_in_button = page.get_by_role('link', name='Sign in') - benefits_studio_email = page.get_by_role( - 'link', - name='tts-benefits-studio@gsa.gov' + "heading", name="Send text messages to your participants" ) + sign_in_button = page.get_by_role("link", name="Sign in") + benefits_studio_email = page.get_by_role("link", name="tts-benefits-studio@gsa.gov") # Check to make sure the elements are visible. expect(main_header).to_be_visible() @@ -33,46 +29,45 @@ def test_landing_page(end_to_end_context): expect(benefits_studio_email).to_be_visible() # Check to make sure the sign-in button and email links are correct. - expect(sign_in_button).to_have_attribute('href', '/sign-in') + expect(sign_in_button).to_have_attribute("href", "/sign-in") expect(benefits_studio_email).to_have_attribute( - 'href', - 'mailto:tts-benefits-studio@gsa.gov' + "href", "mailto:tts-benefits-studio@gsa.gov" ) # Retrieve all other main content headers and check that they're # visible. content_headers = [ - 'Control your content', - 'See how your messages perform', - 'No technical integration needed', - 'About the product', + "Control your content", + "See how your messages perform", + "No technical integration needed", + "About the product", ] for content_header in content_headers: expect( - page.get_by_role('heading', name=re.compile(content_header)) + page.get_by_role("heading", name=re.compile(content_header)) ).to_be_visible() -@pytest.mark.skip(reason='Not authenticating test users.') +@pytest.mark.skip(reason="Not authenticating test users.") def test_sign_in_and_mfa_pages(end_to_end_context): # Open a new page and go to the staging site. page = end_to_end_context.new_page() - page.goto(os.getenv('NOTIFY_E2E_TEST_URI')) + page.goto(os.getenv("NOTIFY_E2E_TEST_URI")) - sign_in_button = page.get_by_role('link', name='Sign in') + sign_in_button = page.get_by_role("link", name="Sign in") # Test trying to sign in. sign_in_button.click() # Check to make sure that we've arrived at the next page. - page.wait_for_load_state('domcontentloaded') + page.wait_for_load_state("domcontentloaded") # Check the page title exists and matches what we expect. - expect(page).to_have_title(re.compile('Sign in')) + expect(page).to_have_title(re.compile("Sign in")) # Check for the sign in heading. - sign_in_heading = page.get_by_role('heading', name='Sign in') + sign_in_heading = page.get_by_role("heading", name="Sign in") expect(sign_in_heading).to_be_visible() # Check for the sign in form elements. @@ -82,17 +77,11 @@ def test_sign_in_and_mfa_pages(end_to_end_context): # See https://playwright.dev/python/docs/api/class-page#page-get-by-label # and https://playwright.dev/python/docs/locators#locate-by-css-or-xpath # for more information. - email_address_input = page.get_by_label('Email address') - password_input = page.get_by_label('Password') + email_address_input = page.get_by_label("Email address") + password_input = page.get_by_label("Password") csrf_token = page.locator('xpath=//input[@name="csrf_token"]') - continue_button = page.get_by_role( - 'button', - name=re.compile('Continue') - ) - forgot_password_link = page.get_by_role( - 'link', - name='Forgot your password?' - ) + continue_button = page.get_by_role("button", name=re.compile("Continue")) + forgot_password_link = page.get_by_role("link", name="Forgot your password?") # Make sure form elements are visible and not visible as expected. expect(email_address_input).to_be_visible() @@ -104,33 +93,25 @@ def test_sign_in_and_mfa_pages(end_to_end_context): # Make sure form elements are configured correctly with the right # attributes. - expect(email_address_input).to_have_attribute('type', 'email') - expect(password_input).to_have_attribute('type', 'password') - expect(csrf_token).to_have_attribute('type', 'hidden') - expect(continue_button).to_have_attribute('type', 'submit') - expect(forgot_password_link).to_have_attribute( - 'href', - '/forgot-password' - ) + expect(email_address_input).to_have_attribute("type", "email") + expect(password_input).to_have_attribute("type", "password") + expect(csrf_token).to_have_attribute("type", "hidden") + expect(continue_button).to_have_attribute("type", "submit") + expect(forgot_password_link).to_have_attribute("href", "/forgot-password") # Sign in to the site. - email_address_input.fill( - os.getenv('NOTIFY_E2E_TEST_EMAIL') - ) - password_input.fill(os.getenv('NOTIFY_E2E_TEST_PASSWORD')) + email_address_input.fill(os.getenv("NOTIFY_E2E_TEST_EMAIL")) + password_input.fill(os.getenv("NOTIFY_E2E_TEST_PASSWORD")) continue_button.click() # Wait for the next page to fully load. - page.wait_for_load_state('domcontentloaded') + page.wait_for_load_state("domcontentloaded") # Check the page title exists and matches what we expect. - expect(page).to_have_title(re.compile('Check your phone')) + expect(page).to_have_title(re.compile("Check your phone")) # Check for the sign in heading. - sign_in_heading = page.get_by_role( - 'heading', - name='Check your phone' - ) + sign_in_heading = page.get_by_role("heading", name="Check your phone") expect(sign_in_heading).to_be_visible() # Check for the sign in form elements. @@ -140,15 +121,11 @@ def test_sign_in_and_mfa_pages(end_to_end_context): # See https://playwright.dev/python/docs/api/class-page#page-get-by-label # and https://playwright.dev/python/docs/locators#locate-by-css-or-xpath # for more information. - mfa_input = page.get_by_label('Text message code') + mfa_input = page.get_by_label("Text message code") csrf_token = page.locator('xpath=//input[@name="csrf_token"]') - continue_button = page.get_by_role( - 'button', - name=re.compile('Continue') - ) + continue_button = page.get_by_role("button", name=re.compile("Continue")) not_received_message_link = page.get_by_role( - 'link', - name='Not received a text message?' + "link", name="Not received a text message?" ) # Make sure form elements are visible and not visible as expected. @@ -160,14 +137,11 @@ def test_sign_in_and_mfa_pages(end_to_end_context): # Make sure form elements are configured correctly with the right # attributes. - expect(mfa_input).to_have_attribute('type', 'tel') - expect(mfa_input).to_have_attribute('pattern', '[0-9]*') - expect(csrf_token).to_have_attribute('type', 'hidden') - expect(continue_button).to_have_attribute('type', 'submit') - expect(not_received_message_link).to_have_attribute( - 'href', - '/text-not-received' - ) + expect(mfa_input).to_have_attribute("type", "tel") + expect(mfa_input).to_have_attribute("pattern", "[0-9]*") + expect(csrf_token).to_have_attribute("type", "hidden") + expect(continue_button).to_have_attribute("type", "submit") + expect(not_received_message_link).to_have_attribute("href", "/text-not-received") # Enter MFA code and continue. # TODO: Revisit this at a later point in time.