Dobry audyt konektora Odoo nie zaczyna się od testów. Zaczyna się od logu i od jednej prawdziwej operacji od początku do końca: zamówienia, które wpływa, przesyłki, która wychodzi, zwrotu, który wraca. Konektor Mirakl przechodził swój zestaw testów na zielono w Odoo 19, 18 i 17, a ukrywał 18 potwierdzonych błędów; inny konektor do marketplace'u dawał 236 z 236 na prawdziwych buildach Odoo.sh i ukrywał 20 błędów kontraktu oraz 12 błędów wersji. Tamten artykuł opowiada o porażce; ten opisuje metodę: dziesięć punktów po kolei, objaw, który zdradza każdy z nich, i test mieszczący się w jednym popołudniu.
Kluczowa myśl: konektor, który dziedziczysz, już sam mówi Ci, co mu jest, w tabeli logu i w wyłączonych cronach.
Co zebrać, zanim usiądziesz
Cztery dane, które nie zależą od tego, kto zbudował konektor. Rzeczywista wersja Odoo: SELECT latest_version FROM ir_module_module WHERE name = 'base';. Wersja API strony trzeciej, którą zakłada kod, prawie zawsze widoczna w bazowym adresie URL. Data ostatniej zmiany mocków: git log -1 --format=%cd -- tests/. I kto dostaje błędy: e-mail, aktywność, plik czy nikt.
Dziesięć punktów, po kolei
1. Czy log naprawdę zapisuje?
Objaw: tabela logu ma zero wierszy po miesiącach działania cronów. Test: SELECT last_value FROM pg_sequences WHERE sequencename = '<tabla_log>_id_seq';. NULL oznacza tam, że ta sekwencja nigdy nie była odczytana: nikt nie usunął wierszy, po prostu żaden nie został zapisany. Tak wyszło na produkcji konektora Mirakl. Funkcja pomocnicza zwracająca model logu zwracała przy sukcesie pusty recordset, a w Odoo pusty recordset jest fałszem, więc if not log_model: return False zawsze się udawało, a każde „to zapisujemy" było operacją bez efektu. Poprawka to if log_model is False.
2. Data graniczna synchronizacji
Objaw: zamówienie utworzone rano i wysłane po południu zostaje w Odoo ze statusem z rana. Test: trzy zamówienia, które wczoraj zmieniły status w marketplace'ie, porównane z write_date i statusem w Odoo. W Mirakl konektor prosił o start_date, które filtruje po dacie utworzenia, zamiast o start_update_date, które filtruje po dacie zmiany.
3. Idempotentność: podwójne zamówienie, podwójny zwrot
Objaw: dwa sale.order z tym samym zewnętrznym odnośnikiem albo zwrot, który pojawia się dwa razy w marketplace'ie. Test: SELECT client_order_ref, count(*) FROM sale_order GROUP BY 1 HAVING count(*) > 1; oraz utworzenie duplikatu z odoo shell, żeby sprawdzić, czy baza naprawdę go odrzuca: ograniczenie zapisane w modelu nie oznacza, że istnieje w Postgresie. W Odoo 19 odziedziczone _sql_constraints niczego już nie tworzy, a ORM zostawia tylko ostrzeżenie w logu, więc tracisz gwarancję, nie wiedząc o tym; w drugą stronę jest głośno, bo models.Constraint nie istnieje w 17 ani w 18 i tam moduł nawet się nie importuje. Jest też klient HTTP: w Mirakl ponawiał przy 5xx i timeoucie dla każdej metody, także dla PUT zwrotu.
4. Błędy HTTP i ponowienia
Objaw: konto budzi się „nieautoryzowane", a cała synchronizacja stoi. Test: wywołaj 401, 429 i, jeśli się da, 403 z powodu limitu. W drugim przypadku 403 z limitu nie miał udokumentowanego formatu błędu i był odczytywany jako brak uprawnień: limit szybkości zatrzymywał wszystko. Co powinno być widać: Retry-After respektowany i z górnym ograniczeniem, 5xx i timeouty ponawiane tylko przy odczytach, 429 także przy zapisach, bo serwer odrzucił żądanie bez przetwarzania, a konto blokowane wyłącznie przy prawdziwym błędzie uwierzytelnienia.
5. Mocki a rzeczywisty kontrakt
Objaw: pole, które zawsze przychodzi puste, filtr, który „nie filtruje". Test: wrzuć aktualny OpenAPI dostawcy do repozytorium i porównaj parametry każdego wrappera, klucze każdego ciała zapisu i pola odczytywane przez każdy parser. W drugim przypadku wyszło tak 16 rozbieżności w 88 operacjach, a cztery kolejne dopiero przez samo wywołanie: względny nagłówek paginacji uniemożliwiał zaimportowanie choćby jednego zamówienia od strony pierwszej wzwyż.
6. Uprawnienia i SSRF przez przekierowanie
Objaw: żaden, i dlatego jest na liście. Test: grep -rn "allow_redirects\|\.sudo()\|has_group" models/. Każde wywołanie wychodzące musi mieć allow_redirects=False i odrzucać 3xx, ponieważ requests domyślnie podąża za przekierowaniami, a przejęty host może skierować Cię na wewnętrzny adres IP. Każde action_* pracujące z sudo() musi sprawdzać grupę po stronie serwera: groups= przycisku to tylko interfejs. W lipcu 2026 zastosowaliśmy to w około szesnastu własnych konektorach.
7. Crony: numbercall i interwały
Objaw: „cron przestał działać we wtorek", bez błędu i bez śladu. Test w Odoo 17: SELECT cron_name, active, numbercall, nextcall FROM ir_cron WHERE cron_name ILIKE '%conector%';. W 17 numbercall ma domyślnie wartość 1, więc cron zadeklarowany bez niego wykonuje się raz i sam się dezaktywuje, tak jak jest w ir_cron.py serii 17.0. Odoo 18 usunęło to pole, a 19 go nie ma, więc kto testował w 18 lub 19, nigdy tego nie zobaczył. Spójrz też na interwał: pełna synchronizacja co dziesięć minut kończy się błędem 429.
8. Wersja Odoo, którą zakłada kod
Objaw: formularz bez chattera, pusta zakładka „payload", zamówienie anulowane w marketplace'ie, które w Odoo zostaje potwierdzone. Test: grep -rn "<chatter/>\|widget=\"json\"\|read_group\|<tree\|<list" views/ models/. W Mirakl wyszły wszystkie cztery naraz: <chatter/> istnieje od 18, widget="json" tylko w 19, read_group jest przestarzałe w 19, a sale.order.action_cancel() zwraca kreator w 17 i 18 dla potwierdzonego zamówienia, więc anulowanie nigdy nie następowało. To lista z modułu, który kompiluje się w Odoo 19 i się nie instaluje.
9. Migracja danych historycznych
Objaw: poprawiasz obliczenie, a stare zamówienia nadal są błędne, bo konektor nie dotyka już istniejącego zamówienia. Test: policz rekordy sprzed poprawki, które łamią nową regułę, i zdecyduj między skryptem migracji a jednorazową, udokumentowaną korektą. W Mirakl było to 18 zamówień z podwojonym VAT w sumie; poprawiono 15 aktywnych, tylko jeśli suma zgadzała się z sumą w marketplace'ie. I pułapka: dane noupdate z base, w tym grupy, nie są stosowane ponownie przy aktualizacji, więc zmiana członkostwa zostawia grupę pustą na produkcji i pełną w Twojej czystej bazie.
10. Dokumentacja strony trzeciej a rzeczywiste zachowanie
Objaw: kod robi to, co mówi PDF, a serwer robi coś innego. Test: jedno prawdziwe wywołanie na każdą rodzinę endpointów i przegląd tego, które endpointy są dziś przestarzałe. W Mirakl model importu z pliku był nadal aktualny, ale komunikacja używała przestarzałego endpointu. W drugim przypadku endpoint przyjmował jedną wartość tam, gdzie dokumentacja mówiła o liście, i ten słownik był pusty od początku. Idzie na końcu, bo jest najdroższy.
Popołudnie w skrócie
| Punkt | Objaw | Test |
|---|---|---|
| Log | Zero wierszy po miesiącach działania cronów | count(*) i pg_sequences.last_value |
| Data graniczna | Zamrożone statusy | Wczorajsze zamówienia a write_date |
| Idempotentność | Duplikaty, podwójny zwrot | GROUP BY HAVING; czy baza odrzuca duplikat? |
| Błędy HTTP | „Nieautoryzowane" po 429/403 | Wywołać 401, 429, 403 |
| Mocki | Pole zawsze puste | Aktualny OpenAPI a wrappery i parsery |
| SSRF i uprawnienia | Żaden | grep allow_redirects, sudo(), has_group |
| Crony | Wyłącza się bez błędu (17) | SELECT active, numbercall FROM ir_cron |
| Wersja Odoo | Pusty chatter lub payload | grep chatter, widget json, read_group, tree/list |
| Historia | Stare dane nadal błędne po poprawce | Policzyć wcześniejsze rekordy łamiące regułę |
| Dokumentacja | Serwer nie robi tego, co mówi PDF | Jedno prawdziwe wywołanie na rodzinę endpointów |
Co daje audyt: raport z ważnością, a nie lista życzeń
Raport porządkuje według szkody: najpierw pieniądze (podwójny zwrot, zduplikowane zamówienie, VAT dwa razy), potem dane (zamrożony status, log, który nie zapisuje, wyłączony cron), na końcu ergonomia. Każde znalezisko ma swoją reprodukcję, a to, czego nie da się odtworzyć, jest skreślane: w audycie Mirakl było 121 kandydatów, 33 obalono w sceptycznym przeglądzie i przetrwało tylko 18.
Drugim efektem jest gate: dotychczasowy zestaw testów, na zielono przed poprawką i po niej, nie dowodzi poprawki. Dowodem jest uruchomienie w trzech seriach na prawdziwych buildach, 163 z 163 w 19, 18 i 17 dla Mirakl, plus weryfikacja na żywo. To odróżnia dobrze zrobiony konektor natywny od warstwy pośredniej, która przetrwa, bo się do niczego nie zobowiązuje.
Kiedy łatać, a kiedy przepisać
Łata się, gdy model danych jest poprawny (jedna tabela na obiekt strony trzeciej, log, unikalny odnośnik zewnętrzny), a błędy tkwią w warstwie HTTP, oknach czasowych i wersjach: mieści się w jednym wydaniu, jak w przypadku Mirakl. Przepisuje się, gdy zawodzi fundament: API wycofane przez dostawcę, brak logu i zewnętrznego odnośnika, zapisy nieidempotentne z założenia albo konektor przywiązany do jednej serii Odoo, gdy potrzebujesz trzech. To decyzja, którą przygotowujemy dla firm doradczych i partnerów technologicznych, które dziedziczą integracje, a także wtedy, gdy konektor dotyczy ich własnego oprogramowania.
Najczęstsze pytania
Dlaczego nie zacząć od zestawu testów?
Bo zielony zestaw testów mówi, że kod zgadza się ze swoim mockiem, a nie z dostawcą ani z produkcją. Zestaw testów idzie na koniec, jako gate poprawki.
Czy potrzebny jest dostęp do produkcji?
Do punktów 1, 2, 3, 7 i 9 tak, albo do kopii: sekwencję logu, zamrożone statusy, duplikaty i wyłączone crony widać tylko na prawdziwych danych.
Co dostarczacie na końcu?
Raport z każdym znaleziskiem udowodnionym i uporządkowanym według pieniędzy, danych i ergonomii; rekomendację łatać czy przepisać; a jeśli zostanie to uzgodnione, poprawkę z jej gate'em w seriach Odoo klienta. Szczegóły są w usługach.
Przydatne linki w FlexigoTech
Co robimy w tej sprawie
Odziedziczyłeś konektor, który zawodzi albo któremu nikt nie ufa?
Napisz nam, co integruje, na jakiej wersji Odoo działa i jaki objaw widzisz. Powiemy Ci, które punkty z listy sprawdzilibyśmy najpierw i co jest potrzebne, żeby to zrobić. Napisz na comercial@flexigobe.com lub zadzwoń pod numer +34 616 809 504.

