diff options
Diffstat (limited to 'web/tests')
| -rw-r--r-- | web/tests/helpers.py | 26 | ||||
| -rw-r--r-- | web/tests/test_account_and_store.py | 49 |
2 files changed, 60 insertions, 15 deletions
diff --git a/web/tests/helpers.py b/web/tests/helpers.py index 8aca3f6..285b769 100644 --- a/web/tests/helpers.py +++ b/web/tests/helpers.py @@ -1,11 +1,37 @@ """Shared helpers for the Playwright test suite. See README.md for how to run these tests and what needs to already be running. """ +import os import random import string +import subprocess FRONTEND_URL = "http://localhost:4173" +# Buying is a real payment now, so a test cannot get an item by clicking Buy. +# Ownership is granted straight in the database instead, which is what a paid +# webhook would have done. +DATABASE_URL = os.environ.get( + "TYPERPUNK_TEST_DATABASE_URL", + "postgresql://typerpunk:typerpunk@localhost/typerpunk", +) + + +def grant_cosmetics(username, cosmetic_ids): + """Gives an account the named cosmetics without going through checkout.""" + ids = ", ".join(f"'{cid}'" for cid in cosmetic_ids) + sql = ( + "INSERT INTO user_cosmetics (user_id, cosmetic_id, acquired_at) " + "SELECT u.id, c.id, '2026-01-01T00:00:00Z' " + "FROM users u CROSS JOIN cosmetics c " + f"WHERE u.username = '{username}' AND c.id IN ({ids}) " + "ON CONFLICT (user_id, cosmetic_id) DO NOTHING" + ) + subprocess.run( + ["psql", DATABASE_URL, "-v", "ON_ERROR_STOP=1", "-c", sql], + check=True, capture_output=True, + ) + def random_username(prefix="test"): suffix = "".join(random.choices(string.ascii_lowercase + string.digits, k=8)) diff --git a/web/tests/test_account_and_store.py b/web/tests/test_account_and_store.py index f7c219c..9252cdb 100644 --- a/web/tests/test_account_and_store.py +++ b/web/tests/test_account_and_store.py @@ -1,9 +1,13 @@ -"""Account registration, and the cosmetics store's buy/equip flow -- -including that an equipped caret color and flair icon actually show up, -not just that the API calls succeed. See README.md to run. +"""Account registration, and the cosmetics store's buy and equip flow. + +Buying is a real payment now, so clicking Buy leaves for the processor rather +than granting anything. The test asserts that it does not grant, then gives +the account its items the way a paid webhook would, and checks that an +equipped caret colour and flair icon actually show up rather than that the +API calls returned 200. See README.md to run. """ from playwright.sync_api import sync_playwright -from helpers import FRONTEND_URL, random_username, register_and_login +from helpers import FRONTEND_URL, grant_cosmetics, random_username, register_and_login def run(): @@ -20,18 +24,33 @@ def run(): rows = page.locator(".store-item-row") assert rows.count() > 0, "store catalog did not render" - def buy_and_equip(item_name): - row = page.locator(".store-item-row", has=page.locator(".leaderboard-name", has_text=item_name)) - buy_btn = row.locator('[data-action="buy"]') - if buy_btn.count() > 0: - buy_btn.click() - page.wait_for_timeout(500) - row = page.locator(".store-item-row", has=page.locator(".leaderboard-name", has_text=item_name)) - row.locator('[data-action="equip"]').click(timeout=10000) - page.wait_for_timeout(500) + def row_for(item_name): + return page.locator( + ".store-item-row", + has=page.locator(".leaderboard-name", has_text=item_name), + ) - buy_and_equip("Cyan Caret") - buy_and_equip("Bolt") + # Buy must not grant. Before this was a real payment the endpoint + # handed the item over for nothing, so anyone signed in could take + # the catalogue. Clicking Buy leaves for the processor, or reports + # that payments are unconfigured; either way nothing is owned and + # no Equip control appears. + row_for("Cyan Caret").locator('[data-action="buy"]').click() + page.wait_for_timeout(800) + assert row_for("Cyan Caret").locator('[data-action="equip"]').count() == 0, \ + "Buy granted the item without a payment" + + # Granted the way a signature-verified webhook would. + grant_cosmetics(username, ["caret-cyan", "flair-bolt"]) + # A reload drops back to the menu, so the store has to be reopened. + page.reload() + page.wait_for_timeout(600) + page.click('[data-action="store"]') + page.wait_for_selector(".store-item-row", timeout=10000) + + for item_name in ("Cyan Caret", "Bolt"): + row_for(item_name).locator('[data-action="equip"]').click(timeout=10000) + page.wait_for_timeout(500) equipped_rows = page.locator(".store-item-row", has=page.get_by_text("EQUIPPED")) assert equipped_rows.count() >= 2, "expected caret and flair to both show as equipped" |