2f6335ec26
Prototype routing so 2–3 owners can share one store URL with different WP application passwords. Device creds live in localStorage; server uses request override → Settings fallback and never writes overrides into the Settings row. Inference stays on shared Settings. See PROTOTYPE.md for UAT with two browsers and known LAN forge risk.
114 lines
4.1 KiB
Python
114 lines
4.1 KiB
Python
"""Prototype: per-request X-Woo-* headers build distinct WooClients without clobbering Settings."""
|
|
|
|
import base64
|
|
import os
|
|
import shutil
|
|
import tempfile
|
|
|
|
import pytest
|
|
from starlette.datastructures import Headers
|
|
|
|
import backend.db as db_mod
|
|
from backend.crypto import encrypt, decrypt
|
|
from backend.woo import (
|
|
WooClient,
|
|
client_for_request,
|
|
request_woo_override,
|
|
settings_woo_creds,
|
|
HEADER_WOO_USERNAME,
|
|
HEADER_WOO_APP_PASSWORD,
|
|
)
|
|
|
|
|
|
@pytest.fixture
|
|
def db_dir():
|
|
tmp = tempfile.mkdtemp(prefix="per_user_woo_")
|
|
os.environ["SLAB_DB_PATH"] = os.path.join(tmp, "t.db")
|
|
os.environ["SLAB_IMAGES_DIR"] = tmp
|
|
os.environ["WOO_BASE_URL"] = "https://store.example.com"
|
|
os.environ["SLAB_AES_KEY"] = base64.b64encode(b"0123456789abcdef0123456789abcdef").decode()
|
|
db_mod._reset_for_tests()
|
|
conn = db_mod.init_db()
|
|
conn.execute(
|
|
"INSERT OR REPLACE INTO settings(key, value, value_enc) VALUES ('woo_wp_username','shop-default',NULL)"
|
|
)
|
|
conn.execute(
|
|
"INSERT OR REPLACE INTO settings(key, value, value_enc) VALUES ('woo_app_password',NULL,?)",
|
|
(encrypt("shop-secret"),),
|
|
)
|
|
conn.commit()
|
|
yield conn
|
|
db_mod._reset_for_tests()
|
|
shutil.rmtree(tmp, ignore_errors=True)
|
|
|
|
|
|
def _auth_pair(client: WooClient) -> tuple[str, str]:
|
|
raw = base64.b64decode(client._auth).decode("utf-8")
|
|
user, password = raw.split(":", 1)
|
|
return user, password
|
|
|
|
|
|
def test_override_headers_produce_distinct_auth_without_clobbering_settings(db_dir):
|
|
conn = db_dir
|
|
before = settings_woo_creds(conn)
|
|
assert before[1] == "shop-default"
|
|
assert decrypt(before[2]) == "shop-secret"
|
|
|
|
h_a = Headers({HEADER_WOO_USERNAME: "alice", HEADER_WOO_APP_PASSWORD: "alice-app-pw"})
|
|
h_b = Headers({HEADER_WOO_USERNAME: "bob", HEADER_WOO_APP_PASSWORD: "bob-app-pw"})
|
|
|
|
client_a = client_for_request(conn, h_a)
|
|
client_b = client_for_request(conn, h_b)
|
|
client_fallback = client_for_request(conn, Headers({}))
|
|
|
|
assert _auth_pair(client_a) == ("alice", "alice-app-pw")
|
|
assert _auth_pair(client_b) == ("bob", "bob-app-pw")
|
|
assert _auth_pair(client_fallback) == ("shop-default", "shop-secret")
|
|
assert client_a._auth != client_b._auth
|
|
|
|
after = settings_woo_creds(conn)
|
|
assert after == before
|
|
assert decrypt(after[2]) == "shop-secret"
|
|
# Explicit: Settings row values unchanged
|
|
row_u = conn.execute("SELECT value FROM settings WHERE key='woo_wp_username'").fetchone()[0]
|
|
row_p = conn.execute("SELECT value_enc FROM settings WHERE key='woo_app_password'").fetchone()[0]
|
|
assert row_u == "shop-default"
|
|
assert decrypt(row_p) == "shop-secret"
|
|
|
|
|
|
def test_partial_headers_fall_back_to_settings(db_dir):
|
|
conn = db_dir
|
|
# Username only — incomplete override
|
|
assert request_woo_override(Headers({HEADER_WOO_USERNAME: "alice"})) is None
|
|
client = client_for_request(conn, Headers({HEADER_WOO_USERNAME: "alice"}))
|
|
assert _auth_pair(client) == ("shop-default", "shop-secret")
|
|
|
|
|
|
def test_test_woo_endpoint_uses_request_override(db_dir, monkeypatch):
|
|
"""API: POST /settings/test-woo with X-Woo-* builds client for that user."""
|
|
from fastapi.testclient import TestClient
|
|
from backend import app as app_mod
|
|
|
|
seen: list[str] = []
|
|
|
|
def fake_sync(client, conn):
|
|
seen.append(_auth_pair(client)[0])
|
|
return {"taxonomy_anchor": "x", "taxonomy_last_sync_at": "2026-01-01T00:00:00"}
|
|
|
|
monkeypatch.setattr(app_mod.taxonomy_mod, "sync", fake_sync)
|
|
tc = TestClient(app_mod.app)
|
|
r1 = tc.post(
|
|
"/api/v1/settings/test-woo",
|
|
headers={HEADER_WOO_USERNAME: "alice", HEADER_WOO_APP_PASSWORD: "pw-a"},
|
|
)
|
|
r2 = tc.post(
|
|
"/api/v1/settings/test-woo",
|
|
headers={HEADER_WOO_USERNAME: "bob", HEADER_WOO_APP_PASSWORD: "pw-b"},
|
|
)
|
|
r3 = tc.post("/api/v1/settings/test-woo")
|
|
assert r1.status_code == 200 and r1.json()["ok"] is True
|
|
assert r2.status_code == 200 and r2.json()["ok"] is True
|
|
assert r3.status_code == 200 and r3.json()["ok"] is True
|
|
assert seen == ["alice", "bob", "shop-default"]
|
|
assert settings_woo_creds(db_dir)[1] == "shop-default"
|