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