31427a8b3b
Alphabet du jeton passé de base62 (a-z A-Z 0-9) à base36 (a-z 0-9), longueur inchangée (6). Lien plus lisible/dictable et cohérent avec le slug déjà en minuscules. Les jetons déjà générés (à casse mixte) restent valides (lookup par match exact).
149 lines
4.6 KiB
Python
149 lines
4.6 KiB
Python
import string
|
|
|
|
from src.auth import db as auth_db
|
|
from src.saved_views import db
|
|
|
|
|
|
def _make_user(email="u@ex.fr"):
|
|
auth_db.init_schema()
|
|
return auth_db.create_user(email, "hash")
|
|
|
|
|
|
def test_init_schema_creates_table(users_db_path):
|
|
db.init_schema()
|
|
conn = auth_db.get_conn()
|
|
tables = {
|
|
row[0]
|
|
for row in conn.execute("SELECT name FROM sqlite_master WHERE type='table'")
|
|
}
|
|
assert "saved_views" in tables
|
|
|
|
|
|
def test_upsert_creates_and_lists(users_db_path):
|
|
db.init_schema()
|
|
uid = _make_user()
|
|
db.upsert(uid, "tableau", "Ma vue", "filtres=foo")
|
|
views = db.list_views(uid, "tableau")
|
|
assert len(views) == 1
|
|
assert views[0]["name"] == "Ma vue"
|
|
assert views[0]["query"] == "filtres=foo"
|
|
|
|
|
|
def test_upsert_same_name_overwrites(users_db_path):
|
|
db.init_schema()
|
|
uid = _make_user()
|
|
db.upsert(uid, "tableau", "Ma vue", "filtres=foo")
|
|
db.upsert(uid, "tableau", "Ma vue", "filtres=bar")
|
|
views = db.list_views(uid, "tableau")
|
|
assert len(views) == 1
|
|
assert views[0]["query"] == "filtres=bar"
|
|
|
|
|
|
def test_list_views_is_isolated_per_user(users_db_path):
|
|
db.init_schema()
|
|
uid1 = _make_user("a@ex.fr")
|
|
uid2 = _make_user("b@ex.fr")
|
|
db.upsert(uid1, "tableau", "Vue A", "filtres=a")
|
|
assert db.list_views(uid2, "tableau") == []
|
|
|
|
|
|
def test_rename_only_affects_owner(users_db_path):
|
|
db.init_schema()
|
|
uid1 = _make_user("a@ex.fr")
|
|
uid2 = _make_user("b@ex.fr")
|
|
db.upsert(uid1, "tableau", "Vue A", "filtres=a")
|
|
view_id = db.list_views(uid1, "tableau")[0]["id"]
|
|
db.rename(view_id, uid2, "Pirate") # mauvais propriétaire → no-op
|
|
assert db.get(view_id, uid1)["name"] == "Vue A"
|
|
db.rename(view_id, uid1, "Vue B")
|
|
assert db.get(view_id, uid1)["name"] == "Vue B"
|
|
|
|
|
|
def test_delete_only_affects_owner(users_db_path):
|
|
db.init_schema()
|
|
uid1 = _make_user("a@ex.fr")
|
|
uid2 = _make_user("b@ex.fr")
|
|
db.upsert(uid1, "tableau", "Vue A", "filtres=a")
|
|
view_id = db.list_views(uid1, "tableau")[0]["id"]
|
|
db.delete(view_id, uid2) # mauvais propriétaire → no-op
|
|
assert db.get(view_id, uid1) is not None
|
|
db.delete(view_id, uid1)
|
|
assert db.get(view_id, uid1) is None
|
|
|
|
|
|
def test_views_deleted_on_user_cascade(users_db_path):
|
|
db.init_schema()
|
|
uid = _make_user()
|
|
db.upsert(uid, "tableau", "Vue A", "filtres=a")
|
|
auth_db.delete_user(uid)
|
|
assert db.list_views(uid, "tableau") == []
|
|
|
|
|
|
def test_generate_token_is_lowercase_base36_and_length_6():
|
|
token = db.generate_token()
|
|
assert len(token) == 6
|
|
alphabet = set(string.ascii_lowercase + string.digits)
|
|
assert set(token) <= alphabet
|
|
# Jamais de majuscule (lien plus lisible/dictable, cohérent avec le slug).
|
|
assert token == token.lower()
|
|
|
|
|
|
def test_upsert_returns_token_on_insert(users_db_path):
|
|
db.init_schema()
|
|
uid = _make_user()
|
|
token = db.upsert(uid, "tableau", "Ma vue", "q1")
|
|
assert token
|
|
assert db.list_views(uid, "tableau")[0]["token"] == token
|
|
|
|
|
|
def test_upsert_preserves_token_on_overwrite(users_db_path):
|
|
db.init_schema()
|
|
uid = _make_user()
|
|
token1 = db.upsert(uid, "tableau", "Ma vue", "q1")
|
|
token2 = db.upsert(uid, "tableau", "Ma vue", "q2")
|
|
assert token2 == token1 # écrasement → lien stable
|
|
assert db.list_views(uid, "tableau")[0]["query"] == "q2"
|
|
|
|
|
|
def test_get_by_token_public_lookup(users_db_path):
|
|
db.init_schema()
|
|
uid = _make_user()
|
|
token = db.upsert(uid, "tableau", "Ma vue", "q1")
|
|
row = db.get_by_token(token)
|
|
assert row is not None
|
|
assert row["name"] == "Ma vue"
|
|
assert db.get_by_token("zzzzzz") is None
|
|
|
|
|
|
def test_tokens_are_unique_across_views(users_db_path):
|
|
db.init_schema()
|
|
uid = _make_user()
|
|
t1 = db.upsert(uid, "tableau", "Vue A", "a")
|
|
t2 = db.upsert(uid, "tableau", "Vue B", "b")
|
|
assert t1 != t2
|
|
|
|
|
|
def test_backfill_assigns_tokens_to_null_rows(users_db_path):
|
|
db.init_schema()
|
|
uid = _make_user()
|
|
conn = auth_db.get_conn()
|
|
# Simule une ligne pré-migration (token NULL) en contournant upsert.
|
|
conn.execute(
|
|
"INSERT INTO saved_views "
|
|
"(user_id, table_name, name, query, token, created_at, updated_at) "
|
|
"VALUES (?, 'tableau', 'Ancienne', 'q', NULL, '', '')",
|
|
(uid,),
|
|
)
|
|
db.init_schema() # doit backfiller
|
|
row = conn.execute(
|
|
"SELECT token FROM saved_views WHERE name = 'Ancienne'"
|
|
).fetchone()
|
|
assert row["token"] and len(row["token"]) == 6
|
|
# Idempotent : un second appel ne change pas le jeton attribué.
|
|
token_after_first = row["token"]
|
|
db.init_schema()
|
|
row2 = conn.execute(
|
|
"SELECT token FROM saved_views WHERE name = 'Ancienne'"
|
|
).fetchone()
|
|
assert row2["token"] == token_after_first
|