Skip to content

Commit bafc636

Browse files
author
bosd
committed
Improve test coverage for import flows
- Add test_run_import_fail_mode_no_records: Test fail mode when fail file has no records - Add test_run_import_sort_strategy_already_sorted: Test sort strategy with already sorted file - Add test_run_import_invalid_json_type_context: Test handling of non-dict JSON context - Add test_run_import_with_relational_strategy: Test relational import strategies in Pass 2 - Add test_run_import_fails_without_creating_fail_file: Test failure path without fail file creation These new tests improve coverage for edge cases and error handling in the import flows.
1 parent 9a573d6 commit bafc636

File tree

1 file changed

+220
-4
lines changed

1 file changed

+220
-4
lines changed

tests/test_importer.py

Lines changed: 220 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,16 @@ def test_count_lines(self, tmp_path: Path) -> None:
2525
def test_infer_model_from_filename(self) -> None:
2626
"""Test model name inference from various filename formats."""
2727
assert _infer_model_from_filename("res_partner.csv") == "res.partner"
28-
assert _infer_model_from_filename("sale_order_line.csv") == "sale.order.line"
29-
assert _infer_model_from_filename("x_custom_model.csv") == "x.custom.model"
30-
assert _infer_model_from_filename("res_partner_fail.csv") == "res.partner"
28+
assert (
29+
_infer_model_from_filename("sale_order_line.csv")
30+
== "sale.order.line"
31+
)
32+
assert (
33+
_infer_model_from_filename("x_custom_model.csv") == "x.custom.model"
34+
)
35+
assert (
36+
_infer_model_from_filename("res_partner_fail.csv") == "res.partner"
37+
)
3138
assert _infer_model_from_filename("res_users_123.csv") == "res.users"
3239

3340
def test_get_fail_filename_recovery_mode(self) -> None:
@@ -357,7 +364,10 @@ def preflight_side_effect(*_args: Any, **kwargs: Any) -> bool:
357364
return True
358365

359366
mock_preflight.side_effect = preflight_side_effect
360-
mock_import_data.return_value = (True, {"total_records": 1, "id_map": {"1": 1}})
367+
mock_import_data.return_value = (
368+
True,
369+
{"total_records": 1, "id_map": {"1": 1}},
370+
)
361371

362372
run_import(
363373
config="dummy.conf",
@@ -380,3 +390,209 @@ def preflight_side_effect(*_args: Any, **kwargs: Any) -> bool:
380390
)
381391
mock_import_data.assert_called_once()
382392
mock_relational_import.assert_not_called()
393+
394+
395+
@patch("odoo_data_flow.importer.import_threaded.import_data")
396+
@patch("odoo_data_flow.importer.Console")
397+
def test_run_import_fail_mode_no_records(
398+
mock_console: MagicMock, mock_import_data: MagicMock, tmp_path: Path
399+
) -> None:
400+
"""Test fail mode when the fail file has no records to retry."""
401+
source_file = tmp_path / "source.csv"
402+
source_file.touch()
403+
fail_file = tmp_path / "res_partner_fail.csv"
404+
fail_file.write_text("id,name\n") # Only a header
405+
406+
run_import(
407+
config="dummy.conf",
408+
filename=str(source_file),
409+
model="res.partner",
410+
fail=True,
411+
deferred_fields=None,
412+
unique_id_field=None,
413+
no_preflight_checks=True,
414+
headless=True,
415+
worker=1,
416+
batch_size=100,
417+
skip=0,
418+
separator=";",
419+
ignore=None,
420+
context={},
421+
encoding="utf-8",
422+
o2m=False,
423+
groupby=None,
424+
)
425+
mock_import_data.assert_not_called()
426+
mock_console.return_value.print.assert_called_once()
427+
assert (
428+
"No records to retry"
429+
in mock_console.return_value.print.call_args[0][0].renderable
430+
)
431+
432+
433+
@patch("odoo_data_flow.importer.sort.sort_for_self_referencing")
434+
@patch("odoo_data_flow.importer.import_threaded.import_data")
435+
@patch("odoo_data_flow.importer._run_preflight_checks")
436+
def test_run_import_sort_strategy_already_sorted(
437+
mock_preflight: MagicMock,
438+
mock_import_data: MagicMock,
439+
mock_sort: MagicMock,
440+
tmp_path: Path,
441+
) -> None:
442+
"""Test the sort strategy when the file is already sorted."""
443+
source_file = tmp_path / "source.csv"
444+
source_file.touch()
445+
mock_sort.return_value = True # Indicates file is already sorted
446+
447+
def preflight_side_effect(*args: Any, **kwargs: Any) -> bool:
448+
kwargs["import_plan"]["strategy"] = "sort_and_one_pass_load"
449+
kwargs["import_plan"]["id_column"] = "id"
450+
kwargs["import_plan"]["parent_column"] = "parent_id"
451+
return True
452+
453+
mock_preflight.side_effect = preflight_side_effect
454+
mock_import_data.return_value = (True, {"total_records": 1})
455+
456+
run_import(
457+
config="dummy.conf",
458+
filename=str(source_file),
459+
model="res.partner",
460+
deferred_fields=None,
461+
unique_id_field=None,
462+
no_preflight_checks=False,
463+
headless=True,
464+
worker=1,
465+
batch_size=100,
466+
skip=0,
467+
fail=False,
468+
separator=";",
469+
ignore=None,
470+
context={},
471+
encoding="utf-8",
472+
o2m=False,
473+
groupby=None,
474+
)
475+
mock_sort.assert_called_once()
476+
assert mock_import_data.call_args.kwargs["file_csv"] == str(source_file)
477+
478+
479+
@patch("odoo_data_flow.importer._show_error_panel")
480+
def test_run_import_invalid_json_type_context(
481+
mock_show_error: MagicMock,
482+
) -> None:
483+
"""Test that run_import handles context that is not a JSON dict."""
484+
run_import(
485+
config="dummy.conf",
486+
filename="dummy.csv",
487+
model="res.partner",
488+
context='["not", "a", "dict"]', # Valid JSON, but not a dict
489+
deferred_fields=None,
490+
unique_id_field=None,
491+
no_preflight_checks=True,
492+
headless=True,
493+
worker=1,
494+
batch_size=100,
495+
skip=0,
496+
fail=False,
497+
separator=";",
498+
ignore=None,
499+
encoding="utf-8",
500+
o2m=False,
501+
groupby=None,
502+
)
503+
mock_show_error.assert_called_once()
504+
assert "must be a valid JSON dictionary" in mock_show_error.call_args[0][1]
505+
506+
507+
@patch("odoo_data_flow.importer.cache.save_id_map")
508+
@patch("odoo_data_flow.importer.relational_import.run_direct_relational_import")
509+
@patch("odoo_data_flow.importer.import_threaded.import_data")
510+
@patch("odoo_data_flow.importer._run_preflight_checks")
511+
def test_run_import_with_relational_strategy(
512+
mock_preflight: MagicMock,
513+
mock_import_data: MagicMock,
514+
mock_run_direct_relational: MagicMock,
515+
mock_save_cache: MagicMock,
516+
tmp_path: Path,
517+
) -> None:
518+
"""Test that relational import strategies are called in Pass 2."""
519+
source_file = tmp_path / "source.csv"
520+
source_file.write_text("id,name,tags\np1,Partner 1,tag1,tag2")
521+
522+
def preflight_side_effect(*args: Any, **kwargs: Any) -> bool:
523+
kwargs["import_plan"]["strategies"] = {
524+
"tags": {"strategy": "direct_relational_import"}
525+
}
526+
return True
527+
528+
mock_preflight.side_effect = preflight_side_effect
529+
# Pass 1 successful, returns an id_map
530+
mock_import_data.return_value = (True, {"id_map": {"p1": 1}})
531+
# Pass 2 (from relational) returns None, so no third import call
532+
mock_run_direct_relational.return_value = None
533+
534+
run_import(
535+
config=str(tmp_path / "dummy.conf"),
536+
filename=str(source_file),
537+
model="res.partner",
538+
deferred_fields=None,
539+
unique_id_field=None,
540+
no_preflight_checks=False,
541+
headless=True,
542+
worker=1,
543+
batch_size=100,
544+
skip=0,
545+
fail=False,
546+
separator=",",
547+
ignore=None,
548+
context={},
549+
encoding="utf-8",
550+
o2m=False,
551+
groupby=None,
552+
)
553+
554+
assert mock_import_data.call_count == 1 # Only the first pass
555+
mock_run_direct_relational.assert_called_once()
556+
mock_save_cache.assert_called_once()
557+
558+
559+
@patch("odoo_data_flow.importer._show_error_panel")
560+
@patch("odoo_data_flow.importer._count_lines", return_value=0)
561+
@patch("odoo_data_flow.importer.import_threaded.import_data")
562+
@patch("odoo_data_flow.importer._run_preflight_checks", return_value=True)
563+
def test_run_import_fails_without_creating_fail_file(
564+
mock_preflight: MagicMock,
565+
mock_import_data: MagicMock,
566+
mock_count_lines: MagicMock,
567+
mock_show_error: MagicMock,
568+
tmp_path: Path,
569+
) -> None:
570+
"""Test the failure path where import fails but no fail file is created."""
571+
source_file = tmp_path / "source.csv"
572+
source_file.touch()
573+
# Simulate import_data returning success=False
574+
mock_import_data.return_value = (False, {})
575+
576+
run_import(
577+
config="dummy.conf",
578+
filename=str(source_file),
579+
model="res.partner",
580+
deferred_fields=None,
581+
unique_id_field=None,
582+
no_preflight_checks=False,
583+
headless=True,
584+
worker=1,
585+
batch_size=100,
586+
skip=0,
587+
fail=False,
588+
separator=";",
589+
ignore=None,
590+
context={},
591+
encoding="utf-8",
592+
o2m=False,
593+
groupby=None,
594+
)
595+
596+
mock_import_data.assert_called_once()
597+
mock_show_error.assert_called_once()
598+
assert "Import Failed" in mock_show_error.call_args[0]

0 commit comments

Comments
 (0)