SPB Git forge

spb/job-ka

Public
226commits 1branches 0releases
37.5 MBsize
maindefault branch
10 h agolast push
HTML 82.1% Python 14.6% TypeScript 1.9% CSS 1% JavaScript 0.5%

[ka6] fix connecteur groupe_bellemare: faux positif de supervision (micro-panne réseau vers api.scrapfly.io) — durcissement du retry de _fetch_listing aux erreurs réseau (njoyn.py) — le statut stale vient d'un unique échec à 04:28 (ConnectTimeout 180 s vers l'API Scrapfly elle-même, seul échec de tout le cycle app : 1105 connecteurs ok), alors que tous les syncs précédents étaient stables à found=34 = médiane exacte ; Scrapfly répond en 0,3 s au moment du diagnostic et le sync rejoué donne found=34 en 8,4 s — connecteur INTACT. Vraie faiblesse corrigée : le retry durci du 2026-09-09 (3 essais, ré-amorçage de session) ne couvrait que les réponses vides/anti-bot ; une requests.ConnectTimeout levée par base.scrapfly() traversait la boucle et avortait le sync au premier essai. _fetch_listing attrape désormais requests.RequestException — même traitement qu'une page vide (nouvelle session Scrapfly = nouveau proxy, backoff 10-20 s), ré-élévation après 3 échecs. Test unitaire ajouté (retry après 2 ConnectTimeout simulés + ré-élévation après 3). Sync rejoué post-patch : found=34, ok. 102 tests pytest verts. pm2 restart job-ka-sync OK, site 200.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Simon-Pierre Boucher committed 11 days ago (Sep 13, 2026) parent 65afc9c

2 changed files +54 −3

modified jobka/connectors/njoyn.py +15 −2
@@ -5,7 +5,7 @@
5 5 # Fichier : jobka/connectors/njoyn.py
6 6 # Rôle : Classe de plateforme Njoyn (CGI) — très répandu au secteur public
7 7 # québécois (santé, villes, sociétés d'État). HTML serveur + jeton.
8 −# Créé : 2026-08-18 Modifié : 2026-09-09
8 +# Créé : 2026-08-18 Modifié : 2026-09-13
9 9 # =============================================================================
10 10 """Plateforme Njoyn (CGI).
11 11
@@ -30,6 +30,8 @@ import os
30 30 import re
31 31 import time
32 32
33 +import requests
34 +
33 35 from ..schema import JobPosting, clean_html
34 36 from .base import BaseConnector
35 37
@@ -312,7 +314,18 @@ class NjoynConnector(BaseConnector):
312 314 if attempt:
313 315 time.sleep(10 * attempt)
314 316 self._sf_session_name = None # nouvelle session = nouveau proxy
315 − page = self._html(self._url("joblisting"))
317 + try:
318 + page = self._html(self._url("joblisting"))
319 + except requests.RequestException:
320 + # Panne réseau transitoire vers api.scrapfly.io ou le site
321 + # (constaté 2026-09-13 04:28 : ConnectTimeout 180 s vers l'API
322 + # Scrapfly sur groupe_bellemare, seul échec de tout le cycle) :
323 + # même traitement qu'une réponse vide — nouvelle session,
324 + # backoff, retry — au lieu de laisser l'exception avorter le
325 + # sync au premier essai.
326 + if attempt == 2:
327 + raise
328 + continue
316 329 if _MAINT_RE.search(page):
317 330 # Maintenance plateforme : inutile de retenter dans 10 s (la
318 331 # fenêtre dure des heures) — échec immédiat et explicite, les
modified tests/test_connectors.py +39 −1
@@ -5,7 +5,7 @@
5 5 # Fichier : tests/test_connectors.py
6 6 # Rôle : Tests de régression des connecteurs (rejeu des fixtures hors ligne)
7 7 # Ré-enregistrer après un changement de site : run.py record <source>
8 −# Créé : 2026-08-17 Modifié : 2026-08-17
8 +# Créé : 2026-08-17 Modifié : 2026-09-13
9 9 # =============================================================================
10 10 import json
11 11 import re
@@ -77,3 +77,41 @@ def test_finalize_is_safe(source_id, parsed):
77 77 assert re.fullmatch(r"\d{4}-\d{2}-\d{2}", f.date_posted)
78 78 if f.lat is not None:
79 79 assert 44.5 <= f.lat <= 63.0 and -80.0 <= f.lng <= -56.0
80 +
81 +
82 +def test_njoyn_listing_retries_network_errors(monkeypatch):
83 + """Une panne réseau transitoire vers l'API Scrapfly (ConnectTimeout, vu le
84 + 2026-09-13 04:28 sur groupe_bellemare) doit engager le retry de
85 + _fetch_listing — nouvelle session Scrapfly, backoff — au lieu d'avorter le
86 + sync au premier essai ; après 3 échecs réseau, l'exception remonte."""
87 + import requests
88 +
89 + from jobka.connectors.njoyn import NjoynConnector
90 +
91 + monkeypatch.setattr("jobka.connectors.njoyn.time.sleep", lambda s: None)
92 +
93 + conn = NjoynConnector()
94 + conn.source_id = "test_njoyn"
95 + conn.USE_SCRAPFLY = True
96 + calls = {"n": 0}
97 +
98 + def flaky_html(url):
99 + calls["n"] += 1
100 + if calls["n"] < 3:
101 + raise requests.ConnectTimeout("api.scrapfly.io injoignable (simulé)")
102 + return "<form action='xweb.asp'></form>"
103 +
104 + monkeypatch.setattr(conn, "_html", flaky_html)
105 + page, items = conn._fetch_listing()
106 + assert calls["n"] == 3 and items == []
107 +
108 + conn2 = NjoynConnector()
109 + conn2.source_id = "test_njoyn"
110 + conn2.USE_SCRAPFLY = True
111 +
112 + def dead_html(url):
113 + raise requests.ConnectTimeout("api.scrapfly.io injoignable (simulé)")
114 +
115 + monkeypatch.setattr(conn2, "_html", dead_html)
116 + with pytest.raises(requests.ConnectTimeout):
117 + conn2._fetch_listing()
80 118