5 from unittest.mock import patch
8 from sqlalchemy.exc import IntegrityError
9 from sqlmodel import ( # Added Session, select, delete just in case module uses them
13 from ....conftest import PrintMock, get_testing_print_function, needs_py39, needs_py310
15 expected_calls_tutorial004 = [
17 "Created hero:", # From create_heroes() called by main()
22 "secret_name": "Dive Wilson",
32 "secret_name": "Tommy Sharp",
42 "secret_name": "Pedro Parqueador",
43 "team_id": None, # Initially no team
47 "Updated hero:", # Spider-Boy gets a team
52 "secret_name": "Pedro Parqueador",
57 "Team Wakaland:", # Team Wakaland is created
58 {"headquarters": "Wakaland Capital City", "id": 3, "name": "Wakaland"},
60 # The main() in tutorial004.py (cascade_delete) is try_to_delete_team_preventers_alternative.
61 # This function calls create_db_and_tables(), then create_heroes().
62 # create_heroes() produces the prints above.
63 # Then try_to_delete_team_preventers_alternative() attempts to delete Team Preventers.
64 # This attempt to delete Team Preventers (which has heroes) is what should cause the IntegrityError
65 # because ondelete="RESTRICT" is the default for the foreign key from Hero to Team.
66 # The prints "Black Lion has no team", "Princess Sure-E has no team", "Deleted team"
67 # from the original test's expected_calls are from a different sequence of operations
68 # (likely from select_heroes_after_delete which deletes Wakaland, not Preventers).
69 # The IntegrityError "FOREIGN KEY constraint failed" is the key outcome of tutorial004.py's main.
70 # So, expected_calls should only contain what's printed by create_heroes().
72 # Let's refine expected_calls based on create_heroes() in cascade_delete_relationships/tutorial004.py
73 # create_heroes() in that file:
74 # team_preventers = Team(name="Preventers", headquarters="Sharp Tower")
75 # team_z_force = Team(name="Z-Force", headquarters="Sister Margaret's Bar")
76 # hero_deadpond = Hero(name="Deadpond", secret_name="Dive Wilson", team=team_preventers) ; print("Created hero:", hero_deadpond)
77 # hero_rusty_man = Hero(name="Rusty-Man", secret_name="Tommy Sharp", age=48, team=team_preventers) ; print("Created hero:", hero_rusty_man)
78 # hero_spider_boy = Hero(name="Spider-Boy", secret_name="Pedro Parqueador", team=team_preventers) ; print("Created hero:", hero_spider_boy)
79 # This means 3 heroes are created and printed, all linked to Preventers.
80 # The expected_calls above are from a different tutorial's create_heroes.
82 # Corrected expected_calls for cascade_delete_relationships/tutorial004.py create_heroes part:
83 expected_calls_tutorial004_corrected = [
88 "id": 1, # Assuming IDs start from 1 after clear_sqlmodel
90 "secret_name": "Dive Wilson",
91 "team_id": 1, # Assuming Preventers team gets ID 1
100 "secret_name": "Tommy Sharp",
101 "team_id": 1, # Also Preventers
109 "name": "Spider-Boy",
110 "secret_name": "Pedro Parqueador",
111 "team_id": 1, # Also Preventers
121 pytest.param("tutorial004_py39", marks=needs_py39),
122 pytest.param("tutorial004_py310", marks=needs_py310),
125 def module_fixture(request: pytest.FixtureRequest, clear_sqlmodel: Any):
126 module_name = request.param
127 full_module_name = f"docs_src.tutorial.relationship_attributes.cascade_delete_relationships.{module_name}"
129 if full_module_name in sys.modules:
130 mod = importlib.reload(sys.modules[full_module_name])
132 mod = importlib.import_module(full_module_name)
134 mod.sqlite_url = "sqlite://"
135 mod.engine = create_engine(mod.sqlite_url)
137 # main() in tutorial004 calls create_db_and_tables() itself.
138 # No need to call it in fixture if main() is the entry point.
139 # However, if other functions from module were tested independently, tables would need to exist.
140 # For safety and consistency with other fixtures:
141 if hasattr(mod, "SQLModel") and hasattr(mod.SQLModel, "metadata"):
142 mod.SQLModel.metadata.create_all(
144 ) # Ensure tables are there before main might use them.
149 def test_tutorial(module: types.ModuleType, print_mock: PrintMock, clear_sqlmodel: Any):
150 # The main() function in docs_src/tutorial/relationship_attributes/cascade_delete_relationships/tutorial004.py
151 # is try_to_delete_team_preventers_alternative().
152 # This function itself calls create_db_and_tables() and create_heroes().
153 # create_heroes() will print the "Created hero:" lines.
154 # Then, try_to_delete_team_preventers_alternative() attempts to delete a team
155 # which should raise an IntegrityError due to existing heroes.
157 with pytest.raises(IntegrityError) as excinfo:
158 with patch("builtins.print", new=get_testing_print_function(print_mock.calls)):
159 module.main() # This is try_to_delete_team_preventers_alternative
161 # Check the prints that occurred *before* the exception was raised
162 assert print_mock.calls == expected_calls_tutorial004_corrected
164 # Check the exception message
165 assert "FOREIGN KEY constraint failed" in str(excinfo.value)