]> git.openstreetmap.org Git - nominatim.git/blob - test/python/test_cli.py
Improved performance of the postcodes query and some code cleaning
[nominatim.git] / test / python / test_cli.py
1 """
2 Tests for command line interface wrapper.
3
4 These tests just check that the various command line parameters route to the
5 correct functionionality. They use a lot of monkeypatching to avoid executing
6 the actual functions.
7 """
8 import pytest
9
10 import nominatim.db.properties
11 import nominatim.cli
12 import nominatim.clicmd.api
13 import nominatim.clicmd.refresh
14 import nominatim.clicmd.admin
15 import nominatim.clicmd.setup
16 import nominatim.indexer.indexer
17 import nominatim.tools.admin
18 import nominatim.tools.check_database
19 import nominatim.tools.database_import
20 import nominatim.tools.freeze
21 import nominatim.tools.refresh
22 import nominatim.tools.postcodes
23 import nominatim.tokenizer.factory
24
25 from mocks import MockParamCapture
26
27 @pytest.fixture
28 def mock_run_legacy(monkeypatch):
29     mock = MockParamCapture()
30     monkeypatch.setattr(nominatim.cli, 'run_legacy_script', mock)
31     return mock
32
33
34 @pytest.fixture
35 def mock_func_factory(monkeypatch):
36     def get_mock(module, func):
37         mock = MockParamCapture()
38         mock.func_name = func
39         monkeypatch.setattr(module, func, mock)
40         return mock
41
42     return get_mock
43
44
45
46 class TestCli:
47
48     @pytest.fixture(autouse=True)
49     def setup_cli_call(self, cli_call):
50         self.call_nominatim = cli_call
51
52
53     def test_cli_help(self, capsys):
54         """ Running nominatim tool without arguments prints help.
55         """
56         assert self.call_nominatim() == 1
57
58         captured = capsys.readouterr()
59         assert captured.out.startswith('usage:')
60
61
62     @pytest.mark.parametrize("command,script", [
63                              (('add-data', '--file', 'foo.osm'), 'update'),
64                              (('export',), 'export')
65                              ])
66     def test_legacy_commands_simple(self, mock_run_legacy, command, script):
67         assert self.call_nominatim(*command) == 0
68
69         assert mock_run_legacy.called == 1
70         assert mock_run_legacy.last_args[0] == script + '.php'
71
72
73     @pytest.mark.parametrize("params", [('--warm', ),
74                                         ('--warm', '--reverse-only'),
75                                         ('--warm', '--search-only')])
76     def test_admin_command_legacy(self, mock_func_factory, params):
77         mock_run_legacy = mock_func_factory(nominatim.clicmd.admin, 'run_legacy_script')
78
79         assert self.call_nominatim('admin', *params) == 0
80
81         assert mock_run_legacy.called == 1
82
83
84     def test_admin_command_check_database(self, mock_func_factory):
85         mock = mock_func_factory(nominatim.tools.check_database, 'check_database')
86
87         assert self.call_nominatim('admin', '--check-database') == 0
88         assert mock.called == 1
89
90
91     @pytest.mark.parametrize("name,oid", [('file', 'foo.osm'), ('diff', 'foo.osc'),
92                                           ('node', 12), ('way', 8), ('relation', 32)])
93     def test_add_data_command(self, mock_run_legacy, name, oid):
94         assert self.call_nominatim('add-data', '--' + name, str(oid)) == 0
95
96         assert mock_run_legacy.called == 1
97         assert mock_run_legacy.last_args == ('update.php', '--import-' + name, oid)
98
99
100     def test_serve_command(self, mock_func_factory):
101         func = mock_func_factory(nominatim.cli, 'run_php_server')
102
103         self.call_nominatim('serve')
104
105         assert func.called == 1
106
107
108     @pytest.mark.parametrize("params", [('search', '--query', 'new'),
109                                         ('reverse', '--lat', '0', '--lon', '0'),
110                                         ('lookup', '--id', 'N1'),
111                                         ('details', '--node', '1'),
112                                         ('details', '--way', '1'),
113                                         ('details', '--relation', '1'),
114                                         ('details', '--place_id', '10001'),
115                                         ('status',)])
116     def test_api_commands_simple(self, mock_func_factory, params):
117         mock_run_api = mock_func_factory(nominatim.clicmd.api, 'run_api_script')
118
119         assert self.call_nominatim(*params) == 0
120
121         assert mock_run_api.called == 1
122         assert mock_run_api.last_args[0] == params[0]
123
124
125
126 class TestCliWithDb:
127
128     @pytest.fixture(autouse=True)
129     def setup_cli_call(self, cli_call, temp_db):
130         self.call_nominatim = cli_call
131
132
133     @pytest.fixture(autouse=True)
134     def setup_tokenizer_mock(self, monkeypatch):
135         class DummyTokenizer:
136             def __init__(self, *args, **kwargs):
137                 self.update_sql_functions_called = False
138                 self.finalize_import_called = False
139
140             def update_sql_functions(self, *args):
141                 self.update_sql_functions_called = True
142
143             def finalize_import(self, *args):
144                 self.finalize_import_called = True
145
146         tok = DummyTokenizer()
147         monkeypatch.setattr(nominatim.tokenizer.factory, 'get_tokenizer_for_db',
148                             lambda *args: tok)
149         monkeypatch.setattr(nominatim.tokenizer.factory, 'create_tokenizer',
150                             lambda *args: tok)
151
152         self.tokenizer_mock = tok
153
154
155     def test_import_missing_file(self):
156         assert self.call_nominatim('import', '--osm-file', 'sfsafegwedgw.reh.erh') == 1
157
158
159     def test_import_bad_file(self):
160         assert self.call_nominatim('import', '--osm-file', '.') == 1
161
162
163     def test_import_full(self, mock_func_factory):
164         mocks = [
165             mock_func_factory(nominatim.tools.database_import, 'setup_database_skeleton'),
166             mock_func_factory(nominatim.tools.database_import, 'import_osm_data'),
167             mock_func_factory(nominatim.tools.refresh, 'import_wikipedia_articles'),
168             mock_func_factory(nominatim.tools.database_import, 'truncate_data_tables'),
169             mock_func_factory(nominatim.tools.database_import, 'load_data'),
170             mock_func_factory(nominatim.tools.database_import, 'create_tables'),
171             mock_func_factory(nominatim.tools.database_import, 'create_table_triggers'),
172             mock_func_factory(nominatim.tools.database_import, 'create_partition_tables'),
173             mock_func_factory(nominatim.tools.database_import, 'create_search_indices'),
174             mock_func_factory(nominatim.tools.database_import, 'create_country_names'),
175             mock_func_factory(nominatim.tools.refresh, 'load_address_levels_from_file'),
176             mock_func_factory(nominatim.tools.postcodes, 'update_postcodes'),
177             mock_func_factory(nominatim.indexer.indexer.Indexer, 'index_full'),
178             mock_func_factory(nominatim.tools.refresh, 'setup_website'),
179             mock_func_factory(nominatim.db.properties, 'set_property')
180         ]
181
182         cf_mock = mock_func_factory(nominatim.tools.refresh, 'create_functions')
183
184         assert self.call_nominatim('import', '--osm-file', __file__) == 0
185         assert self.tokenizer_mock.finalize_import_called
186
187         assert cf_mock.called > 1
188
189         for mock in mocks:
190             assert mock.called == 1, "Mock '{}' not called".format(mock.func_name)
191
192
193     def test_import_continue_load_data(self, mock_func_factory):
194         mocks = [
195             mock_func_factory(nominatim.tools.database_import, 'truncate_data_tables'),
196             mock_func_factory(nominatim.tools.database_import, 'load_data'),
197             mock_func_factory(nominatim.tools.database_import, 'create_search_indices'),
198             mock_func_factory(nominatim.tools.database_import, 'create_country_names'),
199             mock_func_factory(nominatim.tools.postcodes, 'update_postcodes'),
200             mock_func_factory(nominatim.indexer.indexer.Indexer, 'index_full'),
201             mock_func_factory(nominatim.tools.refresh, 'setup_website'),
202             mock_func_factory(nominatim.db.properties, 'set_property')
203         ]
204
205         assert self.call_nominatim('import', '--continue', 'load-data') == 0
206         assert self.tokenizer_mock.finalize_import_called
207
208         for mock in mocks:
209             assert mock.called == 1, "Mock '{}' not called".format(mock.func_name)
210
211
212     def test_import_continue_indexing(self, mock_func_factory, placex_table,
213                                       temp_db_conn):
214         mocks = [
215             mock_func_factory(nominatim.tools.database_import, 'create_search_indices'),
216             mock_func_factory(nominatim.tools.database_import, 'create_country_names'),
217             mock_func_factory(nominatim.indexer.indexer.Indexer, 'index_full'),
218             mock_func_factory(nominatim.tools.refresh, 'setup_website'),
219             mock_func_factory(nominatim.db.properties, 'set_property')
220         ]
221
222         assert self.call_nominatim('import', '--continue', 'indexing') == 0
223
224         for mock in mocks:
225             assert mock.called == 1, "Mock '{}' not called".format(mock.func_name)
226
227         assert temp_db_conn.index_exists('idx_placex_pendingsector')
228
229         # Calling it again still works for the index
230         assert self.call_nominatim('import', '--continue', 'indexing') == 0
231         assert temp_db_conn.index_exists('idx_placex_pendingsector')
232
233
234     def test_import_continue_postprocess(self, mock_func_factory):
235         mocks = [
236             mock_func_factory(nominatim.tools.database_import, 'create_search_indices'),
237             mock_func_factory(nominatim.tools.database_import, 'create_country_names'),
238             mock_func_factory(nominatim.tools.refresh, 'setup_website'),
239             mock_func_factory(nominatim.db.properties, 'set_property')
240         ]
241
242         assert self.call_nominatim('import', '--continue', 'db-postprocess') == 0
243
244         assert self.tokenizer_mock.finalize_import_called
245
246         for mock in mocks:
247             assert mock.called == 1, "Mock '{}' not called".format(mock.func_name)
248
249
250     def test_freeze_command(self, mock_func_factory):
251         mock_drop = mock_func_factory(nominatim.tools.freeze, 'drop_update_tables')
252         mock_flatnode = mock_func_factory(nominatim.tools.freeze, 'drop_flatnode_file')
253
254         assert self.call_nominatim('freeze') == 0
255
256         assert mock_drop.called == 1
257         assert mock_flatnode.called == 1
258
259
260
261     @pytest.mark.parametrize("func, params", [('analyse_indexing', ('--analyse-indexing', ))])
262     def test_admin_command_tool(self, mock_func_factory, func, params):
263         mock = mock_func_factory(nominatim.tools.admin, func)
264
265         assert self.call_nominatim('admin', *params) == 0
266         assert mock.called == 1
267
268
269     @pytest.mark.parametrize("params,do_bnds,do_ranks", [
270                               ([], 1, 1),
271                               (['--boundaries-only'], 1, 0),
272                               (['--no-boundaries'], 0, 1),
273                               (['--boundaries-only', '--no-boundaries'], 0, 0)])
274     def test_index_command(self, mock_func_factory, table_factory,
275                            params, do_bnds, do_ranks):
276         table_factory('import_status', 'indexed bool')
277         bnd_mock = mock_func_factory(nominatim.indexer.indexer.Indexer, 'index_boundaries')
278         rank_mock = mock_func_factory(nominatim.indexer.indexer.Indexer, 'index_by_rank')
279
280         assert self.call_nominatim('index', *params) == 0
281
282         assert bnd_mock.called == do_bnds
283         assert rank_mock.called == do_ranks
284
285     @pytest.mark.parametrize("no_replace", [(True), (False)])
286     def test_special_phrases_wiki_command(self, mock_func_factory, no_replace):
287         func = mock_func_factory(nominatim.clicmd.special_phrases.SPImporter, 'import_phrases')
288
289         if no_replace:
290             self.call_nominatim('special-phrases', '--import-from-wiki', '--no-replace')
291         else:
292             self.call_nominatim('special-phrases', '--import-from-wiki')
293
294         assert func.called == 1
295
296     @pytest.mark.parametrize("no_replace", [(True), (False)])
297     def test_special_phrases_csv_command(self, src_dir, mock_func_factory, no_replace):
298         func = mock_func_factory(nominatim.clicmd.special_phrases.SPImporter, 'import_phrases')
299         testdata = src_dir / 'test' / 'testdb'
300         csv_path = str((testdata / 'full_en_phrases_test.csv').resolve())
301
302         if no_replace:
303             self.call_nominatim('special-phrases', '--import-from-csv', csv_path, '--no-replace')
304         else:
305             self.call_nominatim('special-phrases', '--import-from-csv', csv_path)
306
307         assert func.called == 1
308
309     @pytest.mark.parametrize("command,func", [
310                              ('word-counts', 'recompute_word_counts'),
311                              ('address-levels', 'load_address_levels_from_file'),
312                              ('wiki-data', 'import_wikipedia_articles'),
313                              ('importance', 'recompute_importance'),
314                              ('website', 'setup_website'),
315                              ])
316     def test_refresh_command(self, mock_func_factory, command, func):
317         func_mock = mock_func_factory(nominatim.tools.refresh, func)
318
319         assert self.call_nominatim('refresh', '--' + command) == 0
320         assert func_mock.called == 1
321
322
323     def test_refresh_postcodes(self, mock_func_factory, place_table):
324         func_mock = mock_func_factory(nominatim.tools.postcodes, 'update_postcodes')
325         idx_mock = mock_func_factory(nominatim.indexer.indexer.Indexer, 'index_postcodes')
326
327         assert self.call_nominatim('refresh', '--postcodes') == 0
328         assert func_mock.called == 1
329         assert idx_mock.called == 1
330
331     def test_refresh_create_functions(self, mock_func_factory):
332         func_mock = mock_func_factory(nominatim.tools.refresh, 'create_functions')
333
334         assert self.call_nominatim('refresh', '--functions') == 0
335         assert func_mock.called == 1
336         assert self.tokenizer_mock.update_sql_functions_called
337
338
339     def test_refresh_importance_computed_after_wiki_import(self, monkeypatch):
340         calls = []
341         monkeypatch.setattr(nominatim.tools.refresh, 'import_wikipedia_articles',
342                             lambda *args, **kwargs: calls.append('import') or 0)
343         monkeypatch.setattr(nominatim.tools.refresh, 'recompute_importance',
344                             lambda *args, **kwargs: calls.append('update'))
345
346         assert self.call_nominatim('refresh', '--importance', '--wiki-data') == 0
347
348         assert calls == ['import', 'update']