diff --git a/.gitignore b/.gitignore index 26fab617..3d3cdbab 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,7 @@ .coverage .DS_STORE .env +.env.tests .idea/ .mypy_cache/ .tox/ diff --git a/Makefile b/Makefile index 5504b06b..33a2a4d6 100644 --- a/Makefile +++ b/Makefile @@ -81,8 +81,15 @@ start: .env test: .env $(INSTALL_STAMP) COVERAGE_REPORT=1 bin/test.sh +.env.tests: .env setup $(INSTALL_STAMP) + cp tests/integration/basket.env .env.tests + poetry run ctms/bin/client_credentials.py -e master@local.host integration-test --save-file creds.json + echo "CTMS_CLIENT_ID=$(jq -r .client_id creds.json)" >> .env.tests + echo "CTMS_CLIENT_SECRET=$(jq -r .client_secret creds.json)" >> .env.tests + rm creds.json + .PHONY: integration-test -integration-test: .env setup $(INSTALL_STAMP) +integration-test: .env.tests echo "Starting containers..." ${DOCKER_COMPOSE} --profile integration-test up --force-recreate --wait basket echo "Start test suite..." diff --git a/bin/integration-test.sh b/bin/integration-test.sh index 965f0237..96125d5c 100755 --- a/bin/integration-test.sh +++ b/bin/integration-test.sh @@ -14,8 +14,5 @@ export TZ=UTC # Create newsletters in basket cat tests/integration/basket-db-init.sql | $DOCKER_COMPOSE exec -T mysql mariadb -u root -h mysql basket -# Create token in CTMS (will only work with specific CTMS_SECRET, see .sql source) -cat tests/integration/ctms-db-init.sql | $DOCKER_COMPOSE exec -T postgres psql --user postgres -d postgres - # We don't capture tests output to trace retries of failing assertions. $POETRY_RUN pytest --capture=no tests/integration/ diff --git a/ctms/bin/client_credentials.py b/ctms/bin/client_credentials.py index 7bb13949..1b40b152 100755 --- a/ctms/bin/client_credentials.py +++ b/ctms/bin/client_credentials.py @@ -2,6 +2,7 @@ """Generate OAuth2 client credentials.""" import argparse +import json import re from secrets import token_urlsafe @@ -86,6 +87,18 @@ def print_new_credentials( ) +def save_json(output, client_id, client_secret): + with open(output, "w") as f: + json.dump( + { + "client_id": client_id, + "client_secret": client_secret, + }, + f, + ) + print(f"Credentials saved to {output!r}.") + + def main(db, settings, test_args=None): # noqa: PLR0912 """ Process the command line and create or update client credentials @@ -99,6 +112,9 @@ def main(db, settings, test_args=None): # noqa: PLR0912 parser = argparse.ArgumentParser(description="Create or update client credentials.") parser.add_argument("name", help="short name of the client") parser.add_argument("-e", "--email", help="contact email for the client") + parser.add_argument( + "--save-file", help="output credentials into file in JSON format" + ) parser.add_argument( "--enable", action="store_true", help="enable a disabled client" ) @@ -115,6 +131,7 @@ def main(db, settings, test_args=None): # noqa: PLR0912 enable = args.enable disable = args.disable rotate = args.rotate_secret + save_file = args.save_file if not re.match(r"^[-_.a-zA-Z0-9]*$", name): print( @@ -126,10 +143,8 @@ def main(db, settings, test_args=None): # noqa: PLR0912 print("Can only pick one of --enable and --disable") return 1 - if name.startswith("id_"): - client_id = name - else: - client_id = f"id_{name}" + client_id = name if name.startswith("id_") else f"id_{name}" + existing = get_api_client_by_id(db, client_id) if existing: if disable and existing.enabled: @@ -139,10 +154,7 @@ def main(db, settings, test_args=None): # noqa: PLR0912 else: enabled = None - if rotate: - new_secret = create_secret() - else: - new_secret = None + new_secret = create_secret() if rotate else None if new_secret is None and enabled is None and email in (None, existing.email): print(f"Nothing to change for existing credentials for {name}.") @@ -158,6 +170,8 @@ def main(db, settings, test_args=None): # noqa: PLR0912 sample_email=email, enabled=enabled, ) + if save_file: + save_json(save_file, existing.client_id, new_secret) else: print(f"Credentials for {name} are updated.") else: @@ -171,6 +185,8 @@ def main(db, settings, test_args=None): # noqa: PLR0912 print_new_credentials( client_id, client_secret, settings, sample_email=email, enabled=enabled ) + if save_file: + save_json(save_file, client_id, client_secret) return 0 diff --git a/docker-compose.yaml b/docker-compose.yaml index 7cb3b044..1d1fe765 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -47,7 +47,7 @@ services: profiles: [integration-test] image: mozmeao/basket:2024-10-21 env_file: - - ./tests/integration/basket.env + - .env.tests command: ./bin/run-dev.sh ports: @@ -68,7 +68,7 @@ services: profiles: [integration-test] image: mozmeao/basket:2024-10-21 env_file: - - ./tests/integration/basket.env + - .env.tests command: ./bin/run-worker.sh depends_on: diff --git a/tests/integration/basket.env b/tests/integration/basket.env index 322d0756..5b588858 100644 --- a/tests/integration/basket.env +++ b/tests/integration/basket.env @@ -3,9 +3,6 @@ REDIS_URL=redis://redis:6379 CTMS_ENABLED=True CTMS_URL=http://web:8000 -CTMS_CLIENT_ID=id_integration-test -# See `ctms-db-init.sql` -CTMS_CLIENT_SECRET=bogus_MzOWu8UMz5N6M4--2iX9jgJ05JX5MziH6KeH8dI6hrw # pragma: allowlist secret DEBUG=True ALLOWED_HOSTS=* SECRET_KEY=sssssssshhhhhhhhhh # pragma: allowlist secret diff --git a/tests/integration/ctms-db-init.sql b/tests/integration/ctms-db-init.sql deleted file mode 100644 index 65bd5482..00000000 --- a/tests/integration/ctms-db-init.sql +++ /dev/null @@ -1,20 +0,0 @@ -INSERT INTO api_client ( - client_id, - email, - enabled, - hashed_secret, - create_timestamp, - update_timestamp -) VALUES ( - 'id_integration-test', -- client_id - 'master@local.host', -- email - true, -- enabled - -- client_id=id_integration-test - -- secret=bogus_MzOWu8UMz5N6M4--2iX9jgJ05JX5MziH6KeH8dI6hrw pragma: allowlist secret - -- To generate it, we used: - -- $ CTMS_DB_URL="postgresql://ctmsuser:ctmsuser@localhost/ctms" CTMS_SECRET_KEY="some-secret" poetry run ctms/bin/client_credentials.py -e master@local.host integration-test # pragma: allowlist secret - -- Use the same secret key when running CTMS, or integration tests authentication won't work. - '$argon2id$v=19$m=65536,t=3,p=4$zznnfG+NMeZ8D8HYu/f+Hw$edz+Kc8/8MqL9yig2MXFTAlnpJ2vnkUyvnuRe5QERcA', -- hashed_secret - '2023-03-15 16:52:38.542769+01'::timestamp, -- create_timestamp - '2023-03-15 16:52:38.542769+01'::timestamp -- update_timestamp -) ON CONFLICT DO NOTHING; diff --git a/tests/integration/test_basket_waitlist_subscription.py b/tests/integration/test_basket_waitlist_subscription.py index ce207ac9..32d20316 100644 --- a/tests/integration/test_basket_waitlist_subscription.py +++ b/tests/integration/test_basket_waitlist_subscription.py @@ -1,5 +1,5 @@ import logging -import os +from pathlib import Path from uuid import uuid4 import backoff @@ -9,17 +9,16 @@ from tests.conftest import FuzzyAssert -TEST_FOLDER = os.path.dirname(os.path.realpath(__file__)) +ROOT_FOLDER = Path(__file__).parent.parent class Settings(BaseSettings): basket_server_url: str = "http://127.0.0.1:9000" ctms_server_url: str = "http://127.0.0.1:8000" - # We initialize CTMS api client id/secret in `ctms-db-init.sql` - ctms_client_id: str = "id_integration-test" + ctms_client_id: str ctms_client_secret: str model_config = SettingsConfigDict( - env_file=os.path.join(TEST_FOLDER, "basket.env"), extra="ignore" + env_file=str(ROOT_FOLDER / ".env.tests"), extra="ignore" )