2026-03-08 01:52:03 -05:00
|
|
|
|
from selenium.webdriver.common.action_chains import ActionChains
|
|
|
|
|
|
from selenium.webdriver.common.by import By
|
|
|
|
|
|
|
|
|
|
|
|
from .base import FunctionalTest
|
2026-03-09 16:08:28 -04:00
|
|
|
|
from apps.applets.models import Applet
|
2026-03-08 01:52:03 -05:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class WalletDisplayTest(FunctionalTest):
|
|
|
|
|
|
def setUp(self):
|
|
|
|
|
|
super().setUp()
|
|
|
|
|
|
Applet.objects.get_or_create(slug="wallet", defaults={"name": "Wallet"})
|
2026-03-11 00:58:24 -04:00
|
|
|
|
for slug, name, cols, rows in [
|
|
|
|
|
|
("wallet-balances", "Wallet Balances", 3, 3),
|
|
|
|
|
|
("wallet-tokens", "Wallet Tokens", 3, 3),
|
|
|
|
|
|
("wallet-payment", "Payment Methods", 6, 2),
|
|
|
|
|
|
]:
|
|
|
|
|
|
Applet.objects.get_or_create(
|
|
|
|
|
|
slug=slug,
|
|
|
|
|
|
defaults={"name": name, "grid_cols": cols, "grid_rows": rows, "context": "wallet"},
|
|
|
|
|
|
)
|
2026-03-08 01:52:03 -05:00
|
|
|
|
|
|
|
|
|
|
def test_new_user_wallet_shows_starting_balances(self):
|
|
|
|
|
|
# 1. Log in as new user
|
|
|
|
|
|
self.create_pre_authenticated_session("capman@test.io")
|
|
|
|
|
|
# 2. Navigate to dashboard
|
|
|
|
|
|
self.browser.get(self.live_server_url)
|
|
|
|
|
|
# 3. Find wallet applet summary card
|
|
|
|
|
|
self.wait_for(
|
|
|
|
|
|
lambda: self.browser.find_element(By.ID, "id_applet_wallet")
|
|
|
|
|
|
)
|
|
|
|
|
|
# 4. Click thru to full wallet page via manage link
|
|
|
|
|
|
self.browser.find_element(
|
|
|
|
|
|
By.CSS_SELECTOR, "#id_applet_wallet a.wallet-manage-link"
|
|
|
|
|
|
).click()
|
|
|
|
|
|
# 5. Assert user landed on wallet page
|
|
|
|
|
|
self.wait_for(
|
|
|
|
|
|
lambda: self.assertRegex(self.browser.current_url, r"/dashboard/wallet/$")
|
|
|
|
|
|
)
|
|
|
|
|
|
# 6. Assert writs balance shows 144 (complimentary bundle on signup)
|
|
|
|
|
|
self.wait_for(
|
|
|
|
|
|
lambda: self.assertEqual(
|
|
|
|
|
|
"144",
|
|
|
|
|
|
self.browser.find_element(By.ID, "id_writs_balance").text,
|
|
|
|
|
|
)
|
|
|
|
|
|
)
|
|
|
|
|
|
# 7. Assert esteem balance shows 0
|
|
|
|
|
|
self.assertEqual(
|
|
|
|
|
|
"0",
|
|
|
|
|
|
self.browser.find_element(By.ID, "id_esteem_balance").text,
|
|
|
|
|
|
)
|
|
|
|
|
|
# 8. Assert Coin-on-a-String token element present
|
|
|
|
|
|
coin = self.browser.find_element(By.ID, "id_coin_on_a_string")
|
|
|
|
|
|
# 9. Hover over it; assert tooltip appears w. name, entry text, 'no expiry'
|
|
|
|
|
|
ActionChains(self.browser).move_to_element(coin).perform()
|
|
|
|
|
|
self.wait_for(
|
|
|
|
|
|
lambda: self.assertTrue(
|
2026-03-11 00:58:24 -04:00
|
|
|
|
self.browser.find_element(By.ID, "id_tooltip_portal").is_displayed()
|
2026-03-08 01:52:03 -05:00
|
|
|
|
)
|
|
|
|
|
|
)
|
2026-03-11 00:58:24 -04:00
|
|
|
|
coin_tooltip = self.browser.find_element(By.ID, "id_tooltip_portal").text
|
2026-03-08 01:52:03 -05:00
|
|
|
|
self.assertIn("Coin-on-a-String", coin_tooltip)
|
|
|
|
|
|
self.assertIn("Admit 1 Entry", coin_tooltip)
|
|
|
|
|
|
self.assertIn("no expiry", coin_tooltip)
|
|
|
|
|
|
# 10. Assert ×1 Free Token present (complimentary on signup)
|
|
|
|
|
|
free_token = self.browser.find_element(By.ID, "id_free_token_0")
|
|
|
|
|
|
# 11. Hover over it; assert tooltip shows name, entry text, expiry date
|
|
|
|
|
|
ActionChains(self.browser).move_to_element(free_token).perform()
|
|
|
|
|
|
self.wait_for(
|
|
|
|
|
|
lambda: self.assertTrue(
|
2026-03-11 00:58:24 -04:00
|
|
|
|
self.browser.find_element(By.ID, "id_tooltip_portal").is_displayed()
|
2026-03-08 01:52:03 -05:00
|
|
|
|
)
|
|
|
|
|
|
)
|
2026-03-11 00:58:24 -04:00
|
|
|
|
free_tooltip = self.browser.find_element(By.ID, "id_tooltip_portal").text
|
2026-03-08 01:52:03 -05:00
|
|
|
|
self.assertIn("Free Token", free_tooltip)
|
|
|
|
|
|
self.assertIn("Admit 1 Entry", free_tooltip)
|
|
|
|
|
|
self.assertIn("Expires", free_tooltip)
|
|
|
|
|
|
|
|
|
|
|
|
def test_wallet_payment_section_renders(self):
|
|
|
|
|
|
# 1. Log in, navigate directly to wallet page
|
|
|
|
|
|
self.create_pre_authenticated_session("capman@test.io")
|
|
|
|
|
|
self.browser.get(self.live_server_url + "/dashboard/wallet/")
|
|
|
|
|
|
# 2. Assert saved payment methods section present
|
|
|
|
|
|
self.wait_for(
|
|
|
|
|
|
lambda: self.browser.find_element(By.ID, "id_payment_methods")
|
|
|
|
|
|
)
|
|
|
|
|
|
# 3. Assert the add-payment-method button is visible
|
|
|
|
|
|
self.browser.find_element(By.ID, "id_add_payment_method")
|
|
|
|
|
|
# 4. Assert Stripe Payment Element mount point exists
|
|
|
|
|
|
self.browser.find_element(By.ID, "id_stripe_payment_element")
|
|
|
|
|
|
|
|
|
|
|
|
def test_user_can_save_a_payment_method(self):
|
|
|
|
|
|
# 1. Log in, navigate to wallet page
|
|
|
|
|
|
self.create_pre_authenticated_session("capman@test.io")
|
|
|
|
|
|
self.browser.get(self.live_server_url + "/dashboard/wallet/")
|
|
|
|
|
|
# 2. Click add-payment-method btn
|
|
|
|
|
|
self.browser.find_element(By.ID, "id_add_payment_method").click()
|
|
|
|
|
|
# 3. Wait for Stripe Payment Element iframe to appear inside mount point
|
|
|
|
|
|
self.wait_for(
|
|
|
|
|
|
lambda: self.browser.find_element(
|
|
|
|
|
|
By.CSS_SELECTOR, "#id_stripe_payment_element iframe"
|
|
|
|
|
|
)
|
|
|
|
|
|
)
|
|
|
|
|
|
# 4. Switch into Stripe iframe to interact w. card fields
|
|
|
|
|
|
stripe_frame = self.browser.find_element(
|
|
|
|
|
|
By.CSS_SELECTOR, "#id_stripe_payment_element iframe"
|
|
|
|
|
|
)
|
|
|
|
|
|
self.browser.switch_to.frame(stripe_frame)
|
2026-03-09 01:07:16 -04:00
|
|
|
|
# 4a. Wait for card inputs to render inside iframe
|
|
|
|
|
|
self.wait_for(
|
|
|
|
|
|
lambda: self.browser.find_element(
|
|
|
|
|
|
By.CSS_SELECTOR, 'input[placeholder="1234 1234 1234 1234"]'
|
|
|
|
|
|
)
|
|
|
|
|
|
)
|
2026-03-08 01:52:03 -05:00
|
|
|
|
# 5. Fill in Stripe test card details
|
|
|
|
|
|
self.browser.find_element(
|
|
|
|
|
|
By.CSS_SELECTOR, 'input[placeholder="1234 1234 1234 1234"]'
|
|
|
|
|
|
).send_keys("4242424242424242")
|
|
|
|
|
|
self.browser.find_element(
|
|
|
|
|
|
By.CSS_SELECTOR, 'input[placeholder="MM / YY"]'
|
|
|
|
|
|
).send_keys("12 / 26")
|
|
|
|
|
|
self.browser.find_element(
|
|
|
|
|
|
By.CSS_SELECTOR, 'input[placeholder="CVC"]'
|
|
|
|
|
|
).send_keys("424")
|
|
|
|
|
|
self.browser.find_element(
|
2026-03-09 01:07:16 -04:00
|
|
|
|
By.CSS_SELECTOR, 'input[placeholder="12345"]'
|
2026-03-08 01:52:03 -05:00
|
|
|
|
).send_keys("42424")
|
|
|
|
|
|
# 6. Return to main doc & submit form
|
|
|
|
|
|
self.browser.switch_to.default_content()
|
2026-03-11 00:58:24 -04:00
|
|
|
|
self.wait_for(
|
|
|
|
|
|
lambda: self.assertFalse(
|
|
|
|
|
|
self.browser.find_element(By.ID, "id_save_payment_method").get_attribute("hidden")
|
|
|
|
|
|
)
|
|
|
|
|
|
)
|
2026-03-08 01:52:03 -05:00
|
|
|
|
self.browser.find_element(By.ID, "id_save_payment_method").click()
|
|
|
|
|
|
# 7. Wait for saved card to appear in payment methods list
|
2026-03-11 00:58:24 -04:00
|
|
|
|
# Assert last 4 digits shown (Stripe confirmSetup + server round-trip can be slow)
|
|
|
|
|
|
self.wait_for_slow(
|
2026-03-08 01:52:03 -05:00
|
|
|
|
lambda: self.assertIn(
|
|
|
|
|
|
"4242",
|
|
|
|
|
|
self.browser.find_element(By.ID, "id_payment_methods").text,
|
|
|
|
|
|
)
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
def test_user_can_purchase_tithe_token_bundle(self):
|
|
|
|
|
|
# 1. Log in, navigate to wallet page
|
|
|
|
|
|
self.create_pre_authenticated_session("capman@test.io")
|
|
|
|
|
|
self.browser.get(self.live_server_url + "/dashboard/wallet/")
|
|
|
|
|
|
# 2. Assert Tithe Token purchase section present
|
|
|
|
|
|
self.wait_for(
|
|
|
|
|
|
lambda: self.browser.find_element(By.ID, "id_tithe_token_shop")
|
|
|
|
|
|
)
|
|
|
|
|
|
# 3. Assert min. +1 bundle option is visible
|
|
|
|
|
|
bundle = self.browser.find_element(
|
|
|
|
|
|
By.CSS_SELECTOR, "#id_tithe_token_shop .token-bundle"
|
|
|
|
|
|
)
|
|
|
|
|
|
# 4. Assert ea. bundle shows token count & writ bonus placeholder
|
|
|
|
|
|
self.assertIn("Tithe Token", bundle.text)
|
|
|
|
|
|
self.assertIn("Writ", bundle.text)
|
|
|
|
|
|
# 5. (Placeholder) Purchase flow via Stripe not driven in this FT:
|
|
|
|
|
|
# Full charge assertion deferred until Stripe webhook handling implemented
|