44from unittest import mock
55
66import pytest
7- from django .core .management .base import CommandError
87from django .core .management import CommandParser
8+ from django .core .management .base import CommandError
99
10- from metrics .interfaces .management .commands .seed_random import Command , SCALE_CONFIGS
10+ from metrics .interfaces .management .commands .seed_random import SCALE_CONFIGS , Command
1111
1212MODULE_PATH = "metrics.interfaces.management.commands.seed_random"
13+ FULL_BATCH_DAYS = 5000
14+ SMALL_GEO_COUNT = 3
15+ LARGE_GEO_COUNT = 7
1316
1417
1518def _fake_metric_hierarchy () -> SimpleNamespace :
@@ -70,6 +73,7 @@ def test_handle_metrics_dataset(
7073 spy_seed_metrics_data .assert_called_once_with (
7174 scale_config = SCALE_CONFIGS ["small" ],
7275 truncate_first = True ,
76+ progress_callback = mock .ANY ,
7377 )
7478 spy_call_command .assert_not_called ()
7579 spy_print_summary .assert_called_once_with (
@@ -121,81 +125,129 @@ def test_handle_cms_dataset_uses_time_seed_and_builds_cms(
121125
122126 @mock .patch .object (Command , "_truncate_metrics_data" )
123127 @mock .patch .object (Command , "_seed_time_series_rows" )
128+ @mock .patch .object (Command , "_build_geography_seed_values" )
129+ @mock .patch .object (Command , "_build_theme_hierarchy_records" )
124130 @mock .patch .object (Command , "_bulk_create" )
125131 @mock .patch (f"{ MODULE_PATH } .Geography" )
126132 @mock .patch (f"{ MODULE_PATH } .Metric" )
127133 @mock .patch (f"{ MODULE_PATH } .Topic" )
128134 @mock .patch (f"{ MODULE_PATH } .SubTheme" )
129135 @mock .patch (f"{ MODULE_PATH } .Theme" )
136+ @mock .patch (f"{ MODULE_PATH } .GeographyType" )
130137 @mock .patch (f"{ MODULE_PATH } .transaction.atomic" )
131- @mock .patch (f"{ MODULE_PATH } .GeographyType.objects.create" )
132138 @mock .patch (f"{ MODULE_PATH } .Stratum.objects.create" )
133139 @mock .patch (f"{ MODULE_PATH } .Age.objects.create" )
134140 def test_seed_metrics_data_builds_expected_counts_and_calls (
135141 self ,
136142 spy_age_create : mock .MagicMock ,
137143 spy_stratum_create : mock .MagicMock ,
138- spy_geography_type_create : mock .MagicMock ,
139144 spy_atomic : mock .MagicMock ,
145+ spy_geography_type : mock .MagicMock ,
140146 spy_theme : mock .MagicMock ,
141147 spy_sub_theme : mock .MagicMock ,
142148 spy_topic : mock .MagicMock ,
143149 spy_metric : mock .MagicMock ,
144150 spy_geography : mock .MagicMock ,
145151 spy_bulk_create : mock .MagicMock ,
152+ spy_build_theme_hierarchy_records : mock .MagicMock ,
153+ spy_build_geography_seed_values : mock .MagicMock ,
146154 spy_seed_time_series_rows : mock .MagicMock ,
147155 spy_truncate : mock .MagicMock ,
148156 ):
157+ spy_progress_callback = mock .MagicMock ()
149158 spy_atomic .return_value = nullcontext ()
150- spy_theme .side_effect = lambda ** kwargs : SimpleNamespace ( ** kwargs )
151- spy_sub_theme .side_effect = lambda ** kwargs : SimpleNamespace ( ** kwargs )
152- spy_topic .side_effect = lambda ** kwargs : SimpleNamespace ( ** kwargs )
153- spy_metric .side_effect = lambda ** kwargs : SimpleNamespace ( ** kwargs )
154- spy_geography .side_effect = lambda ** kwargs : SimpleNamespace ( ** kwargs )
155- spy_geography_type_create . return_value = SimpleNamespace ( name = "Nation" )
159+ spy_geography_type .side_effect = SimpleNamespace
160+ spy_theme .side_effect = SimpleNamespace
161+ spy_sub_theme .side_effect = SimpleNamespace
162+ spy_topic .side_effect = SimpleNamespace
163+ spy_metric .side_effect = SimpleNamespace
164+ spy_geography . side_effect = SimpleNamespace
156165 spy_stratum_create .return_value = SimpleNamespace (name = "All" )
157166 spy_age_create .return_value = SimpleNamespace (name = "All ages" )
158167 spy_seed_time_series_rows .return_value = (77 , 88 )
168+ spy_build_theme_hierarchy_records .return_value = (
169+ ["infectious_disease" , "climate_and_environment" ],
170+ [
171+ ("respiratory" , "infectious_disease" ),
172+ ("vectors" , "climate_and_environment" ),
173+ ],
174+ [
175+ ("COVID-19" , "respiratory" , "infectious_disease" ),
176+ ("ticks" , "vectors" , "climate_and_environment" ),
177+ ],
178+ )
179+ spy_build_geography_seed_values .return_value = [
180+ {
181+ "name" : "England" ,
182+ "geography_code" : "E92000001" ,
183+ "geography_type" : "Nation" ,
184+ },
185+ {
186+ "name" : "Area 2" ,
187+ "geography_code" : "E09000002" ,
188+ "geography_type" : "Lower Tier Local Authority" ,
189+ },
190+ ]
159191
160- themes = [SimpleNamespace (name = f"Theme { index + 1 } " ) for index in range (3 )]
192+ themes = [
193+ SimpleNamespace (name = "infectious_disease" ),
194+ SimpleNamespace (name = "climate_and_environment" ),
195+ ]
161196 sub_themes = [
162- SimpleNamespace (
163- name = f"SubTheme { index + 1 } " , theme = themes [index % len (themes )]
164- )
165- for index in range (6 )
197+ SimpleNamespace (name = "respiratory" , theme = themes [0 ]),
198+ SimpleNamespace (name = "vectors" , theme = themes [1 ]),
166199 ]
167200 topics = [
168201 SimpleNamespace (
169- name = f"Topic { index + 1 } " ,
170- sub_theme = sub_themes [index % len (sub_themes )],
171- )
172- for index in range (12 )
202+ name = "COVID-19" ,
203+ sub_theme = sub_themes [0 ],
204+ ),
205+ SimpleNamespace (
206+ name = "ticks" ,
207+ sub_theme = sub_themes [1 ],
208+ ),
173209 ]
174210 metrics = [
175211 SimpleNamespace (
176212 name = f"Metric { index + 1 } " , topic = topics [index % len (topics )]
177213 )
178214 for index in range (4 )
179215 ]
216+ geography_types = [
217+ SimpleNamespace (name = "Nation" ),
218+ SimpleNamespace (name = "Lower Tier Local Authority" ),
219+ ]
180220 geographies = [
181221 SimpleNamespace (
182- name = f"Area { index + 1 } " ,
183- geography_code = f"RND{ index + 1 :04d} " ,
184- geography_type = spy_geography_type_create .return_value ,
185- )
186- for index in range (2 )
222+ name = "England" ,
223+ geography_code = "E92000001" ,
224+ geography_type = geography_types [0 ],
225+ ),
226+ SimpleNamespace (
227+ name = "Area 2" ,
228+ geography_code = "E09000002" ,
229+ geography_type = geography_types [1 ],
230+ ),
231+ ]
232+ spy_bulk_create .side_effect = [
233+ themes ,
234+ sub_themes ,
235+ topics ,
236+ metrics ,
237+ geography_types ,
238+ geographies ,
187239 ]
188- spy_bulk_create .side_effect = [themes , sub_themes , topics , metrics , geographies ]
189240
190241 result = Command ._seed_metrics_data (
191242 scale_config = {"geographies" : 2 , "metrics" : 4 , "days" : 9 },
192243 truncate_first = True ,
244+ progress_callback = spy_progress_callback ,
193245 )
194246
195247 assert result == {
196- "Theme" : 3 ,
197- "SubTheme" : 6 ,
198- "Topic" : 12 ,
248+ "Theme" : 2 ,
249+ "SubTheme" : 2 ,
250+ "Topic" : 2 ,
199251 "Metric" : 4 ,
200252 "Geography" : 2 ,
201253 "CoreTimeSeries" : 77 ,
@@ -208,7 +260,12 @@ def test_seed_metrics_data_builds_expected_counts_and_calls(
208260 stratum = spy_stratum_create .return_value ,
209261 age = spy_age_create .return_value ,
210262 days = 9 ,
263+ progress_callback = spy_progress_callback ,
211264 )
265+ spy_progress_callback .assert_any_call (
266+ "Preparing metric taxonomy and geography records..."
267+ )
268+ spy_progress_callback .assert_any_call ("Generating Core/API time series rows..." )
212269
213270 def test_truncate_metrics_data_deletes_from_all_models (self ):
214271 model_names = [
@@ -248,19 +305,24 @@ def test_seed_time_series_rows_flushes_remainder(
248305 ):
249306 spy_core_time_series .side_effect = lambda ** kwargs : kwargs
250307 spy_api_time_series .side_effect = lambda ** kwargs : kwargs
308+ spy_progress_callback = mock .MagicMock ()
251309
252310 core_count , api_count = Command ._seed_time_series_rows (
253311 metrics = [_fake_metric_hierarchy ()],
254312 geographies = [_fake_geography ()],
255313 stratum = SimpleNamespace (name = "All" ),
256314 age = SimpleNamespace (name = "All ages" ),
257315 days = 1 ,
316+ progress_callback = spy_progress_callback ,
258317 )
259318
260319 assert core_count == 1
261320 assert api_count == 1
262321 spy_core_time_series .objects .bulk_create .assert_called_once ()
263322 spy_api_time_series .objects .bulk_create .assert_called_once ()
323+ progress_messages = [call .args [0 ] for call in spy_progress_callback .call_args_list ]
324+ assert any (message .startswith ("Processed 1/1 metrics" ) for message in progress_messages )
325+ assert any (message .startswith ("Inserted " ) for message in progress_messages )
264326
265327 @mock .patch (f"{ MODULE_PATH } .APITimeSeries" )
266328 @mock .patch (f"{ MODULE_PATH } .CoreTimeSeries" )
@@ -277,11 +339,11 @@ def test_seed_time_series_rows_flushes_at_batch_size(
277339 geographies = [_fake_geography ()],
278340 stratum = SimpleNamespace (name = "All" ),
279341 age = SimpleNamespace (name = "All ages" ),
280- days = 5000 ,
342+ days = FULL_BATCH_DAYS ,
281343 )
282344
283- assert core_count == 5000
284- assert api_count == 5000
345+ assert core_count == FULL_BATCH_DAYS
346+ assert api_count == FULL_BATCH_DAYS
285347 spy_core_time_series .objects .bulk_create .assert_called_once ()
286348 spy_api_time_series .objects .bulk_create .assert_called_once ()
287349
@@ -346,3 +408,30 @@ def test_add_arguments_rejects_invalid_dataset_value():
346408
347409 with pytest .raises (CommandError ):
348410 parser .parse_args (["--dataset" , "invalid" ])
411+
412+
413+ def test_build_theme_hierarchy_records_contains_expected_real_values ():
414+ theme_names , sub_theme_rows , topic_rows = Command ._build_theme_hierarchy_records ()
415+
416+ assert "infectious_disease" in theme_names
417+ assert any (sub_theme == "respiratory" for sub_theme , _ in sub_theme_rows )
418+ assert any (topic == "COVID-19" and sub_theme == "respiratory" for topic , sub_theme , _ in topic_rows )
419+
420+
421+ def test_build_geography_seed_values_returns_required_count ():
422+ small_geographies = Command ._build_geography_seed_values (count = SMALL_GEO_COUNT )
423+ larger_geographies = Command ._build_geography_seed_values (count = LARGE_GEO_COUNT )
424+
425+ assert len (small_geographies ) == SMALL_GEO_COUNT
426+ assert len (larger_geographies ) == LARGE_GEO_COUNT
427+ assert small_geographies [0 ]["name" ] == "United Kingdom"
428+ assert larger_geographies [- 1 ]["geography_type" ] in {
429+ "Nation" ,
430+ "Lower Tier Local Authority" ,
431+ }
432+
433+
434+ def test_format_enum_name_replaces_underscores_and_title_cases ():
435+ assert Command ._format_enum_name ("LOWER_TIER_LOCAL_AUTHORITY" ) == (
436+ "Lower Tier Local Authority"
437+ )
0 commit comments