ocado-grocy/tests/test_enrichment_retry.py
2026-09-19 11:32:53 +01:00

85 lines
4 KiB
Python

import sqlite3
from types import SimpleNamespace
from click.testing import CliRunner
from ocado_grocy.enrichment_retry import RetryQueue
from ocado_grocy.history import private_json, main
from ocado_grocy import postprocess
from ocado_grocy.order_state import mark_orders
def test_legacy_report_recovery_is_scoped_and_does_not_resurrect_old_errors(tmp_path):
output = tmp_path/'enrichment'/'old'
private_json(output/'summary.json',{'order_ids':['123'],'errors':['Open Food Facts: failed']})
private_json(output/'openfoodfacts.json',{'products':[
{'product_id':1,'status':'error','error':'ConnectionError'},
{'product_id':2,'status':'unchanged'}, {'product_id':99,'status':'error'}]})
queue = RetryQueue(tmp_path/'state.sqlite3','server')
queue.migrate_reports(tmp_path,{1,2})
assert queue.pending('openfoodfacts') == {1}
queue.results('openfoodfacts',[{'product_id':1,'status':'unchanged'}])
queue.migrate_reports(tmp_path,{1,2})
assert not queue.pending('openfoodfacts')
queue.close()
def test_queue_separates_stages_and_servers_and_keeps_only_failures(tmp_path):
queue = RetryQueue(tmp_path/'state.sqlite3','server')
queue.start('images',[{'id':1},{'id':2}])
queue.start('openfoodfacts',[{'id':1}])
queue.results('images',[{'product_id':1,'status':'updated'},{'product_id':2,'status':'error'}])
assert queue.pending('images') == {2}
assert queue.pending('openfoodfacts') == {1}
other = RetryQueue(tmp_path/'state.sqlite3','other')
assert not other.pending('images')
other.close()
queue.close()
def test_retry_targets_only_failed_live_imports_and_clears_success(tmp_path, monkeypatch):
state = tmp_path/'imports.sqlite3'
with sqlite3.connect(state) as db:
db.execute('CREATE TABLE imports(server,product_id,status)')
db.executemany('INSERT INTO imports VALUES (?,?,?)',[('server',1,'done'),('server',2,'done'),('server',3,'done')])
queue = RetryQueue(state,'server')
queue.start('openfoodfacts',[{'id':1},{'id':3}])
queue.close()
class Client:
url = 'server'
def get(self,path):
assert path == '/objects/products'
return [{'id':1},{'id':2}] # 3 was deleted
monkeypatch.setattr(postprocess,'Grocy',lambda *a:Client())
calls = []
def run(client, products, cache, contact):
calls.append(products)
return {'counts':{'unchanged':1},'products':[{'product_id':1,'status':'unchanged'}]}
monkeypatch.setattr(postprocess,'run_openfoodfacts',run)
result = postprocess.retry_enrichment({'openfoodfacts':{'contact':'test@example.com'}},tmp_path/'config.toml',tmp_path)
assert calls == [[{'id':1}]] and not result['errors']
queue = RetryQueue(state,'server')
assert not queue.pending('openfoodfacts')
queue.close()
def test_main_retries_even_with_no_new_orders(tmp_path, monkeypatch):
from ocado_grocy import history
monkeypatch.setattr(history,'cached_orders',lambda *a:([],[]))
calls = []
monkeypatch.setattr(postprocess,'retry_enrichment',lambda *a,**kw:calls.append(True) or {'errors':[]})
result = CliRunner().invoke(main,['--cached','--config',str(tmp_path/'config.toml'),'--archive',str(tmp_path)])
assert result.exit_code == 0 and calls == [True]
assert 'No new delivered orders' in result.output
calls.clear()
result = CliRunner().invoke(main,['--cached','--dry-run','--config',str(tmp_path/'config.toml'),'--archive',str(tmp_path)])
assert result.exit_code == 0 and not calls
def test_retry_failure_keeps_error_exit_even_without_new_orders(tmp_path, monkeypatch):
from ocado_grocy import history
monkeypatch.setattr(history,'cached_orders',lambda *a:([],[]))
monkeypatch.setattr(postprocess,'retry_enrichment',lambda *a,**kw:{'errors':['ConnectionError']})
result = CliRunner().invoke(main,['--cached','--config',str(tmp_path/'config.toml'),'--archive',str(tmp_path)])
assert result.exit_code == 1
assert 'Enrichment retries still need attention' in result.output