"""Checks for REWARDS_ACCOUNTS. Five layers, cheapest first: 1. which accounts a configuration produces 2. the flags each one hands Edge 3. the run loop's ordering, skip-on-failure and exit codes 4. one account failing every way it can, without ending the batch 5. two real Edge profiles holding two independent, persistent identities Layers 1 to 4 are pure and need nothing installed. Layer 4 drives the real run loop with a stand-in for the browser, so the code under test is the shipped one and only selenium is replaced. Layer 5 starts Edge twice and reaches bing.com, so it is opt in: python tests/test_multi_account.py # layers 1-4 python tests/test_multi_account.py --browser # all five Layer 5 is the one that answers "does multi-account work". Two profiles must end up with two different identities, and each must keep its own across a restart, because that is what a per-account sign-in is made of. It uses bing.com's own MUID cookie rather than an injected one: a cookie added through webdriver is not written to the profile the way a Set-Cookie is, so it proves nothing about a sign-in surviving. """ import logging import os import shutil import sys sys.path.insert(0, os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", "src")) FAILURES = [] def check(label, got, want): if got == want: print(f" ok {label}") return True print(f" FAIL {label}\n got {got!r}\n want {want!r}") FAILURES.append(label) return False def accounts_for(value): """configured() under a given REWARDS_ACCOUNTS, or ValueError.""" if value is None: os.environ.pop(accounts.ENV_VAR, None) else: os.environ[accounts.ENV_VAR] = value return accounts.configured() # -------------------------------------------------------------------------- # 1. which accounts a configuration produces # -------------------------------------------------------------------------- def test_configuration(): print("\n[1] account configuration") default = accounts_for(None) check("unset gives one account", [a.name for a in default], ["default"]) check("unset uses the existing profile directory", default[0].user_data_dir, USER_DATA_DIR) check("unset is the default profile", default[0].is_default, True) check("two names, in order", [a.name for a in accounts_for("personal,spare")], ["personal", "spare"]) check("surrounding whitespace ignored", [a.name for a in accounts_for(" personal , spare ")], ["personal", "spare"]) check("empty entries dropped", [a.name for a in accounts_for("personal,,spare,")], ["personal", "spare"]) check("blank value falls back to default", [a.name for a in accounts_for(" ")], ["default"]) check("duplicates collapse, case insensitively", [a.name for a in accounts_for("personal,PERSONAL,spare")], ["personal", "spare"]) # Names become directory names. Anything that resolves outside the profile # directory, or onto a directory another entry already owns, has to be # refused rather than quietly writing somewhere else. refused = [ # relative traversal "..", ".", "../escape", "..\\escape", "a/b", "a\\b", # absolute, drive-relative and UNC "/etc", "\\", "/", "C:", "C:\\Windows", "\\\\server\\share", # expanded elsewhere, not here "~", "%TEMP%", "$HOME", # Win32 strips trailing dots, so these are not the directories they read # as: "personal." is "personal", and "..." is data-dir itself "...", "....", "personal.", "personal..", # shell and filesystem metacharacters "a b", "a:b", "a;b", "a|b", "a*b", "a?b", "a