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