feat(v5.2.0): finalize Infrastructure & Polish (tests isolation, xdist, lint, CI)
FlowDeck CI / lint (push) Successful in 43s
FlowDeck CI / test (push) Successful in 4m2s
FlowDeck CI / lint (pull_request) Successful in 42s
FlowDeck CI / test (pull_request) Successful in 4m3s
FlowDeck CI / docker (push) Successful in 1m2s
FlowDeck CI / docker (pull_request) Successful in 35s

tests/conftest.py: mutate the settings singleton (instead of rebinding) so DB + backup dir are isolated per test -> pytest-xdist safe.
Real backup tests (snapshot/prune/admin API) and OAuth mock tests (Gitea/GitHub/link) replace the previous skips.
init_db() now also creates webhook_subscriptions (full schema without the FastAPI lifespan).
ruff check is clean; .eslintrc.json migrated to eslint.config.mjs (flat config).
CI: lint job (ruff + eslint), parallel tests (-n auto), run on every branch push.
VERSION 5.11.1.
This commit is contained in:
2026-09-11 23:36:53 -04:00
parent 881c3e3e0a
commit ba363eaee9
42 changed files with 566 additions and 206 deletions
+1 -1
View File
@@ -166,7 +166,7 @@ class AgentEngine:
used_model = model or "" # peut être ajusté par un repli de modèle (404/410)
try:
for step in range(settings.agent_max_iterations or MAX_ITERATIONS):
for _step in range(settings.agent_max_iterations or MAX_ITERATIONS):
if self._tokens >= settings.agent_max_tokens_budget:
yield self._event("error", {"message": "Budget de tokens dépassé"})
break
+2 -2
View File
@@ -147,7 +147,7 @@ def embed_src(url: str) -> str | None:
u = urlparse(url if url.startswith("http") else "https://" + url)
if u.scheme not in ("http", "https"):
return None
host = (u.hostname or "").lower().replace("www.", "")
(u.hostname or "").lower().replace("www.", "")
netloc = (u.netloc or "").lower()
params = parse_qs(u.query)
# Google Maps share links encode the query in the path (…&q=/maps/…)
@@ -191,4 +191,4 @@ def embed_html(src: str, *, height: int = 520) -> str:
f'style="width:100%;height:{height}px;border:none;border-radius:8px;background:#000;" '
f'allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; '
f'picture-in-picture" allowfullscreen></iframe>'
)
)
+3 -2
View File
@@ -243,7 +243,8 @@ def _parse_table_at(lines: list[str], i: int, n: int):
rows.append(cells)
j += 1
width = max(len(r) for r in rows)
pad = lambda r: r + [""] * (width - len(r))
def pad(r):
return r + [""] * (width - len(r))
align = (align + ["left"] * width)[:width]
return (
{
@@ -657,7 +658,7 @@ def blocks_to_html(blocks: list) -> str:
url = b.get("src") or ""
emb = (b.get("embed_type") or "")
if emb in ("inline_dbs", "collection"):
parts.append(f'<div class="embed-note">[Embedded content]</div>')
parts.append('<div class="embed-note">[Embedded content]</div>')
elif emb == "download":
parts.append(f'<a href="{_text(url)}" download>⬇ {_text(b.get("file_name") or "Download")}</a>')
elif emb == "pdf" and url:
+1 -1
View File
@@ -94,7 +94,7 @@ def _do_mentions(conn, handles, actor_id, ntype, title, message,
resource_type, resource_id, url, notified):
placeholders = ",".join("?" * len(handles))
rows = conn.execute(
"SELECT id, login, email FROM users WHERE lower(login) IN (%s)" % placeholders,
f"SELECT id, login, email FROM users WHERE lower(login) IN ({placeholders})",
handles,
).fetchall()
for row in rows:
+2 -2
View File
@@ -29,7 +29,7 @@ def _extract_og(html: str) -> dict:
text = html[:400_000] # only scan the beginning — that's where <head> lives
props: dict[str, str] = {}
for m in _META_RE.finditer(text):
groups = m.groups()
m.groups()
content = ""
prop = ""
for s in re.findall(r"(?:content|property|name)=[\"']([^\"']*)[\"']", m.group(0)):
@@ -121,4 +121,4 @@ async def fetch_og_metadata(url: str, timeout: float = 6.0) -> dict:
"image": abs_url(img),
"site_name": site.strip()[:100],
"favicon": favicon,
}
}
+1 -1
View File
@@ -84,4 +84,4 @@ async def trash_purge_scheduler(interval_hours: int = 24):
purge_expired(days=30)
except Exception as exc: # pragma: no cover - defensive only
logger.warning("Trash purge failed: %s", exc)
await asyncio.sleep(interval_hours * 3600)
await asyncio.sleep(interval_hours * 3600)