- manifest + icones, service worker (precache, network-first, Background Sync)
- module client FlowOffline (IndexedDB, queue, delta, flush) + hook editeur
- endpoints /api/v2/sync/{delta,batch,status} + moteur de sync (conflits LWW/orpheline/copie offline)
- migrations offline_sync_queue + sync_version (triggers)
- UI offline (banner, badge sync, toasts, icone dirty) + doc /help
- tests pytest (sync, migrations, SW, offline) + E2E Playwright; bump 6.0.0
126 lines
4.7 KiB
Python
126 lines
4.7 KiB
Python
"""FlowDeck — v6.0.0 PWA: offline-sync schema migrations.
|
|
|
|
Validates the ``offline_sync_queue`` table, the ``sync_version`` columns and
|
|
the BEFORE-UPDATE triggers that bump them on every write.
|
|
"""
|
|
from app.db import get_conn
|
|
|
|
|
|
def _insert_page(title="Page"):
|
|
with get_conn() as conn:
|
|
cur = conn.execute(
|
|
"INSERT INTO pages (workspace, title, content, content_format, parent_section) "
|
|
"VALUES ('', ?, '', 'blocks', 'Private')",
|
|
(title,),
|
|
)
|
|
conn.commit()
|
|
return cur.lastrowid
|
|
|
|
|
|
def _insert_collection(name="Coll"):
|
|
with get_conn() as conn:
|
|
cur = conn.execute(
|
|
"INSERT INTO collections (name, schema_json) VALUES (?, '[]')", (name,)
|
|
)
|
|
conn.commit()
|
|
return cur.lastrowid
|
|
|
|
|
|
def _insert_collection_page(coll_id, title="Row"):
|
|
with get_conn() as conn:
|
|
cur = conn.execute(
|
|
"INSERT INTO collection_pages (collection_id, title) VALUES (?, ?)",
|
|
(coll_id, title),
|
|
)
|
|
conn.commit()
|
|
return cur.lastrowid
|
|
|
|
|
|
def test_offline_sync_queue_table_and_indexes(client):
|
|
with get_conn() as conn:
|
|
cols = {r[1] for r in conn.execute("PRAGMA table_info(offline_sync_queue)")}
|
|
for expected in ("id", "user_id", "device_id", "type", "payload",
|
|
"client_timestamp", "server_version", "status",
|
|
"retries", "error", "created_at"):
|
|
assert expected in cols, f"offline_sync_queue missing column {expected}"
|
|
|
|
indexes = {r[1] for r in conn.execute("PRAGMA index_list('offline_sync_queue')")}
|
|
assert "idx_syncqueue_user" in indexes
|
|
assert "idx_syncqueue_device" in indexes
|
|
|
|
|
|
def test_offline_sync_queue_insert(client):
|
|
with get_conn() as conn:
|
|
uid = conn.execute(
|
|
"INSERT INTO users (login, full_name, email) VALUES ('queuetest', 'Queue', '[email protected]')"
|
|
).lastrowid
|
|
cur = conn.execute(
|
|
"INSERT INTO offline_sync_queue (user_id, device_id, type, payload, client_timestamp) "
|
|
"VALUES (?, ?, ?, ?, ?)",
|
|
(uid, "dev-abc", "page_update", '{"page_id": 1}', 1750000000000.0),
|
|
)
|
|
conn.commit()
|
|
qid = cur.lastrowid
|
|
row = conn.execute(
|
|
"SELECT status, server_version, retries FROM offline_sync_queue WHERE id=?",
|
|
(qid,),
|
|
).fetchone()
|
|
assert row["status"] == "pending"
|
|
assert row["server_version"] == 0
|
|
assert row["retries"] == 0
|
|
|
|
|
|
def test_sync_version_column_and_defaults(client):
|
|
pid = _insert_page()
|
|
cid = _insert_collection()
|
|
cpid = _insert_collection_page(cid)
|
|
with get_conn() as conn:
|
|
for table, pk in (("pages", pid), ("collection_pages", cpid), ("collections", cid)):
|
|
row = conn.execute(f"SELECT sync_version FROM {table} WHERE id=?", (pk,)).fetchone()
|
|
assert row is not None, f"sync_version missing on {table}"
|
|
assert row["sync_version"] == 1, f"{table} default sync_version should be 1"
|
|
|
|
|
|
def test_sync_version_increments_on_update(client):
|
|
pid = _insert_page()
|
|
with get_conn() as conn:
|
|
conn.execute("UPDATE pages SET title='Renamed' WHERE id=?", (pid,))
|
|
conn.commit()
|
|
v1 = conn.execute("SELECT sync_version FROM pages WHERE id=?", (pid,)).fetchone()
|
|
conn.execute("UPDATE pages SET content='[...]' WHERE id=?", (pid,))
|
|
conn.commit()
|
|
v2 = conn.execute("SELECT sync_version FROM pages WHERE id=?", (pid,)).fetchone()
|
|
assert v1["sync_version"] == 2
|
|
assert v2["sync_version"] == 3
|
|
|
|
|
|
def test_sync_version_increments_on_soft_delete(client):
|
|
"""deleted_at changes must bump the version too (delta sync sees deletions)."""
|
|
pid = _insert_page()
|
|
with get_conn() as conn:
|
|
conn.execute(
|
|
"UPDATE pages SET deleted_at=CURRENT_TIMESTAMP WHERE id=? AND deleted_at IS NULL",
|
|
(pid,),
|
|
)
|
|
conn.commit()
|
|
row = conn.execute("SELECT sync_version FROM pages WHERE id=?", (pid,)).fetchone()
|
|
assert row["sync_version"] == 2
|
|
|
|
|
|
def test_sync_version_increments_collection_and_rows(client):
|
|
cid = _insert_collection()
|
|
cpid = _insert_collection_page(cid)
|
|
with get_conn() as conn:
|
|
conn.execute("UPDATE collections SET name='Renamed' WHERE id=?", (cid,))
|
|
conn.execute(
|
|
"UPDATE collection_pages SET title='Row2', property_values_json='{}' WHERE id=?",
|
|
(cpid,),
|
|
)
|
|
conn.commit()
|
|
col = conn.execute("SELECT sync_version FROM collections WHERE id=?", (cid,)).fetchone()
|
|
row = conn.execute(
|
|
"SELECT sync_version FROM collection_pages WHERE id=?", (cpid,)
|
|
).fetchone()
|
|
assert col["sync_version"] == 2
|
|
assert row["sync_version"] == 2
|