diff --git a/README.md b/README.md index b8cd421c0..79a376436 100644 --- a/README.md +++ b/README.md @@ -30,7 +30,7 @@ Our other repositories are: ### Common dev work - [Local setup](#local-setup) -- [Testing](./docs/testing.md) +- [Testing](./docs/testing.md), both automated and manual - [Deploying](./docs/deploying.md) - [Running one-off tasks](./docs/one-off-tasks.md) diff --git a/app/commands.py b/app/commands.py index ee3c4065f..0f4e94ed3 100644 --- a/app/commands.py +++ b/app/commands.py @@ -340,33 +340,6 @@ def update_jobs_archived_flag(start_date, end_date): current_app.logger.info('Total archived jobs = {}'.format(total_updated)) -@notify_command(name='update-emails-to-remove-gsi') -@click.option('-s', '--service_id', required=True, help="service id. Update all user.email_address to remove .gsi") -@statsd(namespace="tasks") -def update_emails_to_remove_gsi(service_id): - users_to_update = """SELECT u.id user_id, u.name, email_address, s.id, s.name - FROM users u - JOIN user_to_service us on (u.id = us.user_id) - JOIN services s on (s.id = us.service_id) - WHERE s.id = :service_id - AND u.email_address ilike ('%.gsi.gov.uk%') - """ - results = db.session.execute(users_to_update, {'service_id': service_id}) - print("Updating {} users.".format(results.rowcount)) - - for user in results: - print('User with id {} updated'.format(user.user_id)) - - update_stmt = """ - UPDATE users - SET email_address = replace(replace(email_address, '.gsi.gov.uk', '.gov.uk'), '.GSI.GOV.UK', '.GOV.UK'), - updated_at = now() - WHERE id = :user_id - """ - db.session.execute(update_stmt, {'user_id': str(user.user_id)}) - db.session.commit() - - @notify_command(name='replay-daily-sorted-count-files') @click.option('-f', '--file_extension', required=False, help="File extension to search for, defaults to rs.txt") @statsd(namespace="tasks") @@ -733,3 +706,43 @@ def populate_annual_billing_with_defaults(year, missing_services_only): else: print(f'update service {service.id} with default') set_default_free_allowance_for_service(service, year) + + +def validate_mobile(ctx, param, value): + if (len(''.join(i for i in value if i.isdigit())) != 10): + raise click.BadParameter("mobile number must have 10 digits") + else: + return value + + +@notify_command(name='create-test-user') +@click.option('-n', '--name', required=True, prompt=True) +@click.option('-e', '--email', required=True, prompt=True) # TODO: require valid email +@click.option('-m', '--mobile_number', + required=True, prompt=True, callback=validate_mobile) +@click.option('-p', '--password', + required=True, prompt=True, hide_input=True, confirmation_prompt=True) +@click.option('-a', '--auth_type', default="sms_auth") +@click.option('-s', '--state', default="active") +@click.option('-d', '--admin', default=False, type=bool) +def create_test_user(name, email, mobile_number, password, auth_type, state, admin): + if os.getenv('NOTIFY_ENVIRONMENT', '') not in ['development', 'test']: + current_app.logger.error('Can only be run in development') + return + + data = { + 'name': name, + 'email_address': email, + 'mobile_number': mobile_number, + 'password': password, + 'auth_type': auth_type, + 'state': state, # skip the email verification for our test user + 'platform_admin': admin, + } + user = User(**data) + try: + db.session.add(user) + db.session.commit() + except IntegrityError: + print("duplicate user", user.name) + db.session.rollback() diff --git a/docs/one-off-tasks.md b/docs/one-off-tasks.md index a337eaf01..d107ea849 100644 --- a/docs/one-off-tasks.md +++ b/docs/one-off-tasks.md @@ -2,14 +2,18 @@ For these, we're using Flask commands, which live in [`/app/commands.py`](../app/commands.py). -This includes things that might be one-time operations! Using a command allows the operation to be tested, -both with `pytest` and with trial runs. +This includes things that might be one-time operations! If we're running it on production, it should be a Flask +command Using a command allows the operation to be tested, both with `pytest` and with trial runs in staging. + +To see information about available commands, you can get a list with: + +`pipenv run flask command` + +Appending `--help` to any command will give you more information about parameters. To run a command on cloud.gov, use this format: -``` -cf run-task CLOUD-GOV-APP --commmand "YOUR COMMAND HERE" --name YOUR-COMMAND -``` +`cf run-task CLOUD-GOV-APP --commmand "YOUR COMMAND HERE" --name YOUR-COMMAND` [Here's more documentation](https://docs.cloudfoundry.org/devguide/using-tasks.html) about Cloud Foundry tasks. diff --git a/docs/testing.md b/docs/testing.md index 90c5bdb2d..ad6c7e62e 100644 --- a/docs/testing.md +++ b/docs/testing.md @@ -23,6 +23,14 @@ We're using GitHub Actions. See [/.github](../.github/) for the configuration. In addition to commit-triggered scans, the `daily_checks.yml` workflow runs the relevant dependency audits, static scan, and/or dynamic scans at 10am UTC each day. Developers will be notified of failures in daily scans by GitHub notifications. +## Manual testing + +If you're checking out the system locally, you may want to create a user quickly. + +`pipenv run flask command create-test-user` + +This will run an interactive prompt to create a user, and then mark that user as active. *Use a real mobile number* if you want to log in, as the SMS auth code will be sent here. + ## To run a local OWASP scan 1. Run `make run-flask` from within the dev container. diff --git a/tests/app/test_commands.py b/tests/app/test_commands.py index d29949be8..b2b6abd14 100644 --- a/tests/app/test_commands.py +++ b/tests/app/test_commands.py @@ -1,14 +1,45 @@ import pytest from app.commands import ( + create_test_user, insert_inbound_numbers_from_file, populate_annual_billing_with_defaults, ) from app.dao.inbound_numbers_dao import dao_get_available_inbound_numbers -from app.models import AnnualBilling +from app.models import AnnualBilling, User from tests.app.db import create_annual_billing, create_service +def test_create_test_user_command(notify_db_session, notify_api): + + # number of users before adding ours + user_count = User.query.count() + + # run the command + notify_api.test_cli_runner().invoke( + create_test_user, [ + '--email', 'somebody@fake.gov', + '--mobile_number', '555-555-5555', + '--password', 'correct horse battery staple', + '--name', 'Fake Personson', + # '--auth_type', 'sms_auth', # this is the default + # '--state', 'active', # this is the default + # '--admin', 'False', # this is the default + ] + ) + + # there should be one more user + assert User.query.count() == user_count + 1 + + # that user should be the one we added + user = User.query.filter_by( + name='Fake Personson' + ).first() + assert user.email_address == 'somebody@fake.gov' + assert user.auth_type == 'sms_auth' + assert user.state == 'active' + + def test_insert_inbound_numbers_from_file(notify_db_session, notify_api, tmpdir): numbers_file = tmpdir.join("numbers.txt") numbers_file.write("07700900373\n07700900473\n07700900375\n\n\n\n")