test(slice-3): INV-4 invariants — concurrent-create refusal + many-capable membership schema
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,64 @@
|
|||||||
|
"""INV-4's sharp edges (SD-0001 §6.8): concurrent second-storefront refusal, and the
|
||||||
|
membership schema staying many-capable (the rule is a deletable guard, not a schema law)."""
|
||||||
|
import threading
|
||||||
|
|
||||||
|
import psycopg
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from app.domains import storefronts
|
||||||
|
from app.platform import db
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture()
|
||||||
|
def migrated_url(fresh_db_url):
|
||||||
|
with psycopg.connect(fresh_db_url) as conn:
|
||||||
|
db.migrate(conn)
|
||||||
|
return fresh_db_url
|
||||||
|
|
||||||
|
|
||||||
|
def test_inv_4_concurrent_creates_exactly_one_wins(migrated_url):
|
||||||
|
with psycopg.connect(migrated_url) as setup:
|
||||||
|
acct = setup.execute(
|
||||||
|
"INSERT INTO account (email) VALUES ('racer@example.com') RETURNING id"
|
||||||
|
).fetchone()[0]
|
||||||
|
setup.commit()
|
||||||
|
|
||||||
|
barrier = threading.Barrier(2)
|
||||||
|
results: list[str] = []
|
||||||
|
|
||||||
|
def racer():
|
||||||
|
with psycopg.connect(migrated_url) as conn:
|
||||||
|
barrier.wait()
|
||||||
|
try:
|
||||||
|
storefronts.create_storefront(conn, acct, "racer@example.com", None)
|
||||||
|
results.append("created")
|
||||||
|
except storefronts.AlreadyOwnsStorefront:
|
||||||
|
results.append("refused")
|
||||||
|
|
||||||
|
threads = [threading.Thread(target=racer) for _ in range(2)]
|
||||||
|
for t in threads:
|
||||||
|
t.start()
|
||||||
|
for t in threads:
|
||||||
|
t.join()
|
||||||
|
|
||||||
|
assert sorted(results) == ["created", "refused"]
|
||||||
|
with psycopg.connect(migrated_url) as check:
|
||||||
|
count = check.execute(
|
||||||
|
"SELECT count(*) FROM storefront_membership WHERE account_id = %s", (acct,)
|
||||||
|
).fetchone()[0]
|
||||||
|
assert count == 1
|
||||||
|
|
||||||
|
|
||||||
|
def test_inv_4_schema_stays_many_capable(migrated_url):
|
||||||
|
# No UNIQUE(account_id) anywhere in the storefront path: the only unique constraint on
|
||||||
|
# storefront_membership is its composite PK, so many-per-account stays a deletable
|
||||||
|
# service rule (R-5 mitigation).
|
||||||
|
with psycopg.connect(migrated_url) as conn:
|
||||||
|
uniques = conn.execute(
|
||||||
|
"SELECT i.indkey::text FROM pg_index i"
|
||||||
|
" JOIN pg_class c ON c.oid = i.indrelid"
|
||||||
|
" WHERE c.relname = 'storefront_membership' AND (i.indisunique OR i.indisprimary)"
|
||||||
|
).fetchall()
|
||||||
|
# exactly one unique index (the composite PK over two columns)
|
||||||
|
assert len(uniques) == 1
|
||||||
|
assert len(uniques[0][0].split()) == 2
|
||||||
Reference in New Issue
Block a user