Coverage for tests/test_timeseries.py: 100%
406 statements
« prev ^ index » next coverage.py v7.11.0, created at 2025-11-03 20:32 +0000
« prev ^ index » next coverage.py v7.11.0, created at 2025-11-03 20:32 +0000
1# SPDX-FileCopyrightText: 2021 Forschungszentrum Jülich GmbH
2# SPDX-License-Identifier: MIT
4import pytest
5import json
6from sqlalchemy import insert
7from toardb.timeseries.models import Timeseries, timeseries_timeseries_roles_table, \
8 s1_contributors_table
9from toardb.timeseries.models_programme import TimeseriesProgramme
10from toardb.timeseries.models_role import TimeseriesRole
11from toardb.stationmeta.models import StationmetaCore, StationmetaGlobal
12from toardb.stationmeta.schemas import get_geom_from_coordinates, Coordinates
13from toardb.variables.models import Variable
14from toardb.contacts.models import Person, Organisation, Contact
15from toardb.auth_user.models import AuthUser
16# Required imports 'create_test_database'
17from toardb.test_base import (
18 client,
19 get_test_db,
20 override_dependency,
21 create_test_database,
22 url,
23 get_test_engine,
24 test_db_session as db,
25)
26# for mocking datetime.now(timezone.utc)
27from datetime import datetime
28from unittest.mock import patch
31class TestApps:
32 def setup(self):
33 self.application_url = "/timeseries/"
35 """Set up all the data before each test
36 If you want the setup only once (per test module),
37 the scope argument is not working in the expected way, as discussed here:
38 https://stackoverflow.com/questions/45817153/py-test-fixture-use-function-fixture-in-scope-fixture
39 """
40 @pytest.fixture(autouse=True)
41 def setup_db_data(self, db):
42 _db_conn = get_test_engine()
43 # id_seq will not be reset automatically between tests!
44 fake_conn = _db_conn.raw_connection()
45 fake_cur = fake_conn.cursor()
46 fake_cur.execute("ALTER SEQUENCE auth_user_id_seq RESTART WITH 1")
47 fake_conn.commit()
48 fake_cur.execute("ALTER SEQUENCE variables_id_seq RESTART WITH 1")
49 fake_conn.commit()
50 fake_cur.execute("ALTER SEQUENCE stationmeta_core_id_seq RESTART WITH 1")
51 fake_conn.commit()
52 fake_cur.execute("ALTER SEQUENCE stationmeta_global_id_seq RESTART WITH 1")
53 fake_conn.commit()
54 fake_cur.execute("ALTER SEQUENCE stationmeta_roles_id_seq RESTART WITH 1")
55 fake_conn.commit()
56 fake_cur.execute("ALTER SEQUENCE stationmeta_annotations_id_seq RESTART WITH 1")
57 fake_conn.commit()
58 fake_cur.execute("ALTER SEQUENCE stationmeta_aux_doc_id_seq RESTART WITH 1")
59 fake_conn.commit()
60 fake_cur.execute("ALTER SEQUENCE stationmeta_aux_image_id_seq RESTART WITH 1")
61 fake_conn.commit()
62 fake_cur.execute("ALTER SEQUENCE stationmeta_aux_url_id_seq RESTART WITH 1")
63 fake_conn.commit()
64 fake_cur.execute("ALTER SEQUENCE persons_id_seq RESTART WITH 1")
65 fake_conn.commit()
66 fake_cur.execute("ALTER SEQUENCE organisations_id_seq RESTART WITH 1")
67 fake_conn.commit()
68 fake_cur.execute("ALTER SEQUENCE contacts_id_seq RESTART WITH 1")
69 fake_conn.commit()
70 fake_cur.execute("ALTER SEQUENCE timeseries_annotations_id_seq RESTART WITH 1")
71 fake_conn.commit()
72 fake_cur.execute("ALTER SEQUENCE timeseries_roles_id_seq RESTART WITH 4")
73 fake_conn.commit()
74 fake_cur.execute("ALTER SEQUENCE timeseries_programmes_id_seq RESTART WITH 1")
75 fake_conn.commit()
76 fake_cur.execute("ALTER SEQUENCE timeseries_id_seq RESTART WITH 1")
77 fake_conn.commit()
78 fake_cur.execute("ALTER TABLE timeseries_changelog ALTER COLUMN datetime SET DEFAULT '2023-07-28 12:00:00'")
79 fake_conn.commit()
80 infilename = "tests/fixtures/auth_user/auth.json"
81 with open(infilename) as f:
82 metajson=json.load(f)
83 for entry in metajson:
84 new_auth_user = AuthUser(**entry)
85 db.add(new_auth_user)
86 db.commit()
87 db.refresh(new_auth_user)
88 infilename = "tests/fixtures/contacts/persons.json"
89 with open(infilename) as f:
90 metajson=json.load(f)
91 for entry in metajson:
92 new_person = Person(**entry)
93 db.add(new_person)
94 db.commit()
95 db.refresh(new_person)
96 infilename = "tests/fixtures/contacts/organisations.json"
97 with open(infilename) as f:
98 metajson=json.load(f)
99 for entry in metajson:
100 new_organisation = Organisation(**entry)
101 db.add(new_organisation)
102 db.commit()
103 db.refresh(new_organisation)
104 infilename = "tests/fixtures/contacts/contacts.json"
105 with open(infilename) as f:
106 metajson=json.load(f)
107 for entry in metajson:
108 new_contact = Contact(**entry)
109 db.add(new_contact)
110 db.commit()
111 db.refresh(new_contact)
112 infilename = "tests/fixtures/variables/variables.json"
113 with open(infilename) as f:
114 metajson=json.load(f)
115 for entry in metajson:
116 new_variable = Variable(**entry)
117 db.add(new_variable)
118 db.commit()
119 db.refresh(new_variable)
120 infilename = "tests/fixtures/stationmeta/stationmeta_core.json"
121 with open(infilename) as f:
122 metajson=json.load(f)
123 for entry in metajson:
124 new_stationmeta_core = StationmetaCore(**entry)
125 # there's a mismatch with coordinates --> how to automatically switch back and forth?!
126 tmp_coordinates = new_stationmeta_core.coordinates
127 new_stationmeta_core.coordinates = get_geom_from_coordinates(Coordinates(**new_stationmeta_core.coordinates))
128 db.add(new_stationmeta_core)
129 db.commit()
130 db.refresh(new_stationmeta_core)
131 infilename = "tests/fixtures/stationmeta/stationmeta_global.json"
132 with open(infilename) as f:
133 metajson=json.load(f)
134 for entry in metajson:
135 new_stationmeta_global = StationmetaGlobal(**entry)
136 db.add(new_stationmeta_global)
137 db.commit()
138 db.refresh(new_stationmeta_global)
139 infilename = "tests/fixtures/timeseries/timeseries_programmes.json"
140 with open(infilename) as f:
141 metajson=json.load(f)
142 for entry in metajson:
143 new_timeseries_programme = TimeseriesProgramme(**entry)
144 db.add(new_timeseries_programme)
145 db.commit()
146 db.refresh(new_timeseries_programme)
147 infilename = "tests/fixtures/timeseries/timeseries.json"
148 with open(infilename) as f:
149 metajson=json.load(f)
150 for entry in metajson:
151 new_timeseries = Timeseries(**entry)
152 db.add(new_timeseries)
153 db.commit()
154 db.refresh(new_timeseries)
155 infilename = "tests/fixtures/timeseries/timeseries_roles.json"
156 with open(infilename) as f:
157 metajson=json.load(f)
158 for entry in metajson:
159 new_timeseries_role = TimeseriesRole(**entry)
160 db.add(new_timeseries_role)
161 db.commit()
162 db.refresh(new_timeseries_role)
163 infilename = "tests/fixtures/timeseries/timeseries_timeseries_roles.json"
164 with open(infilename) as f:
165 metajson=json.load(f)
166 for entry in metajson:
167 db.execute(insert(timeseries_timeseries_roles_table).values(timeseries_id=entry["timeseries_id"], role_id=entry["role_id"]))
168 db.execute("COMMIT")
169 infilename = "tests/fixtures/timeseries/timeseries_contributors.json"
170 with open(infilename) as f:
171 metajson=json.load(f)
172 for entry in metajson:
173 db.execute(insert(s1_contributors_table).values(request_id=entry["request_id"], timeseries_ids=entry["timeseries_ids"]))
174 db.execute("COMMIT")
177 def test_get_timeseries(self, client, db):
178 response = client.get("/timeseries/")
179 expected_status_code = 200
180 assert response.status_code == expected_status_code
181 expected_resp = [{'id': 1,
182 'label': 'CMA',
183 'order': 1,
184 'sampling_frequency': 'hourly',
185 'aggregation': 'mean',
186 'data_start_date': '2003-09-07T15:30:00+00:00',
187 'data_end_date': '2016-12-31T14:30:00+00:00',
188 'data_origin': 'instrument',
189 'data_origin_type': 'measurement',
190 'provider_version': 'N/A',
191 'sampling_height': 7.0,
192 'additional_metadata': {'original_units': 'ppb'},
193 'doi': '',
194 'coverage': -1.0,
195 'station': {'id': 2,
196 'codes': ['SDZ54421'],
197 'name': 'Shangdianzi',
198 'coordinates': {'lat': 40.65, 'lng': 117.106, 'alt': 293.9},
199 'coordinate_validation_status': 'not checked',
200 'country': 'China',
201 'state': 'Beijing Shi',
202 'type': 'unknown',
203 'type_of_area': 'unknown',
204 'timezone': 'Asia/Shanghai',
205 'additional_metadata': {'add_type': 'nature reservation'},
206 'aux_images': [],
207 'aux_docs': [],
208 'aux_urls': [],
209 'globalmeta': {'mean_topography_srtm_alt_90m_year1994': -999.0,
210 'mean_topography_srtm_alt_1km_year1994': -999.0,
211 'max_topography_srtm_relative_alt_5km_year1994': -999.0,
212 'min_topography_srtm_relative_alt_5km_year1994': -999.0,
213 'stddev_topography_srtm_relative_alt_5km_year1994': -999.0,
214 'climatic_zone_year2016': '6 (warm temperate dry)',
215 'htap_region_tier1_year2010': '11 (MDE Middle '
216 'East: S. Arabia, '
217 'Oman, etc, Iran, '
218 'Iraq)',
219 'dominant_landcover_year2012': '11 (Cropland, '
220 'rainfed, herbaceous cover)',
221 'landcover_description_25km_year2012': '',
222 'dominant_ecoregion_year2017': '-1 (undefined)',
223 'ecoregion_description_25km_year2017': '',
224 'distance_to_major_road_year2020': -999.0,
225 'mean_stable_nightlights_1km_year2013': -999.0,
226 'mean_stable_nightlights_5km_year2013': -999.0,
227 'max_stable_nightlights_25km_year2013': -999.0,
228 'max_stable_nightlights_25km_year1992': -999.0,
229 'mean_population_density_250m_year2015': -1.0,
230 'mean_population_density_5km_year2015': -1.0,
231 'max_population_density_25km_year2015': -1.0,
232 'mean_population_density_250m_year1990': -1.0,
233 'mean_population_density_5km_year1990': -1.0,
234 'max_population_density_25km_year1990': -1.0,
235 'mean_nox_emissions_10km_year2015': -999.0,
236 'mean_nox_emissions_10km_year2000': -999.0,
237 'toar1_category': 'unclassified',
238 'toar2_category': 'suburban'},
239 'changelog': []},
240 'variable': {'name': 'toluene',
241 'longname': 'toluene',
242 'displayname': 'Toluene',
243 'cf_standardname': 'mole_fraction_of_toluene_in_air',
244 'units': 'nmol mol-1',
245 'chemical_formula': 'C7H8',
246 'id': 7},
247 'programme': {'id': 0,
248 'name': '',
249 'longname': '',
250 'homepage': '',
251 'description': ''},
252 'roles': [{'id': 2,
253 'role': 'resource provider',
254 'status': 'active',
255 'contact': {'id': 4,
256 'organisation': {'id': 1,
257 'name': 'UBA',
258 'longname': 'Umweltbundesamt',
259 'kind': 'government',
260 'city': 'Dessau-Roßlau',
261 'postcode': '06844',
262 'street_address': 'Wörlitzer Platz 1',
263 'country': 'Germany',
264 'homepage': 'https://www.umweltbundesamt.de',
265 'contact_url': 'mailto:immission@uba.de'}}},
266 {'id': 3,
267 'role': 'principal investigator',
268 'status': 'active',
269 'contact': {'id': 3,
270 'person': {'email': 's.schroeder@fz-juelich.de',
271 'id': 3,
272 'isprivate': False,
273 'name': 'Sabine Schröder',
274 'orcid': '0000-0002-0309-8010',
275 'phone': '+49-2461-61-6397'}}}]},
276 {'id': 2,
277 'label': 'CMA',
278 'order': 1,
279 'sampling_frequency': 'hourly',
280 'aggregation': 'mean',
281 'data_start_date': '2003-09-07T15:30:00+00:00',
282 'data_end_date': '2016-12-31T14:30:00+00:00',
283 'data_origin': 'instrument',
284 'data_origin_type': 'measurement',
285 'provider_version': 'N/A',
286 'sampling_height': 7.0,
287 'additional_metadata': {'original_units': {'since_19740101000000': 'nmol/mol'},
288 'measurement_method': 'uv_abs',
289 'absorption_cross_section': 'Hearn 1961',
290 'ebas_metadata_19740101000000_29y': {'Submitter': 'Unknown, '
291 'Lady, '
292 'lady.unknown@unknown.com, '
293 'some '
294 'long '
295 'division '
296 'name, '
297 'SHORT, '
298 ', '
299 '111 '
300 'Streetname, '
301 ', '
302 'zipcode, '
303 'Boulder, '
304 'CO, '
305 'USA',
306 'Data level': '2',
307 'Frameworks': 'GAW-WDCRG '
308 'NOAA-ESRL',
309 'Station code': 'XXX',
310 'Station name': 'Secret'}},
311 'doi': '',
312 'coverage': -1.0,
313 'station': {'id': 3,
314 'codes': ['China_test8'],
315 'name': 'Test_China',
316 'coordinates': {'lat': 36.256, 'lng': 117.106, 'alt': 1534.0},
317 'coordinate_validation_status': 'not checked',
318 'country': 'China',
319 'state': 'Shandong Sheng',
320 'type': 'unknown',
321 'type_of_area': 'unknown',
322 'timezone': 'Asia/Shanghai',
323 'additional_metadata': {},
324 'aux_images': [],
325 'aux_docs': [],
326 'aux_urls': [],
327 'globalmeta': {'mean_topography_srtm_alt_90m_year1994': -999.0,
328 'mean_topography_srtm_alt_1km_year1994': -999.0,
329 'max_topography_srtm_relative_alt_5km_year1994': -999.0,
330 'min_topography_srtm_relative_alt_5km_year1994': -999.0,
331 'stddev_topography_srtm_relative_alt_5km_year1994': -999.0,
332 'climatic_zone_year2016': '6 (warm temperate dry)',
333 'htap_region_tier1_year2010': '10 (SAF Sub '
334 'Saharan/sub Sahel '
335 'Africa)',
336 'dominant_landcover_year2012': '10 (Cropland, '
337 'rainfed)',
338 'landcover_description_25km_year2012': '',
339 'dominant_ecoregion_year2017': '-1 (undefined)',
340 'ecoregion_description_25km_year2017': '',
341 'distance_to_major_road_year2020': -999.0,
342 'mean_stable_nightlights_1km_year2013': -999.0,
343 'mean_stable_nightlights_5km_year2013': -999.0,
344 'max_stable_nightlights_25km_year2013': -999.0,
345 'max_stable_nightlights_25km_year1992': -999.0,
346 'mean_population_density_250m_year2015': -1.0,
347 'mean_population_density_5km_year2015': -1.0,
348 'max_population_density_25km_year2015': -1.0,
349 'mean_population_density_250m_year1990': -1.0,
350 'mean_population_density_5km_year1990': -1.0,
351 'max_population_density_25km_year1990': -1.0,
352 'mean_nox_emissions_10km_year2015': -999.0,
353 'mean_nox_emissions_10km_year2000': -999.0,
354 'toar1_category': 'unclassified',
355 'toar2_category': 'suburban'},
356 'changelog': []},
357 'variable': {'name': 'o3',
358 'longname': 'ozone',
359 'displayname': 'Ozone',
360 'cf_standardname': 'mole_fraction_of_ozone_in_air',
361 'units': 'nmol mol-1',
362 'chemical_formula': 'O3',
363 'id': 5},
364 'programme': {'id': 0,
365 'name': '',
366 'longname': '',
367 'homepage': '',
368 'description': ''},
369 'roles': [{'id': 1,
370 'role': 'resource provider',
371 'status': 'active',
372 'contact': {'id': 5,
373 'organisation': {'id': 2,
374 'name': 'FZJ',
375 'longname': 'Forschungszentrum '
376 'Jülich',
377 'kind': 'research',
378 'city': 'Jülich',
379 'postcode': '52425',
380 'street_address': 'Wilhelm-Johnen-Straße',
381 'country': 'Germany',
382 'homepage': 'https://www.fz-juelich.de',
383 'contact_url': 'mailto:toar-data@fz-juelich.de'}}}]},
384 {
385 'additional_metadata': {
386 'original_units': 'ppb',
387 },
388 'aggregation': 'mean',
389 'coverage': -1.0,
390 'data_end_date': '2025-02-25T14:00:00+00:00',
391 'data_origin': 'instrument',
392 'data_origin_type': 'measurement',
393 'data_start_date': '1991-01-01T00:00:00+00:00',
394 'doi': '',
395 'id': 18763,
396 'label': '',
397 'order': 1,
398 'programme': {
399 'description': '',
400 'homepage': '',
401 'id': 0,
402 'longname': '',
403 'name': '',
404 },
405 'provider_version': 'N/A',
406 'roles': [
407 {
408 'contact': {
409 'id': 5,
410 'organisation': {
411 'city': 'Jülich',
412 'contact_url': 'mailto:toar-data@fz-juelich.de',
413 'country': 'Germany',
414 'homepage': 'https://www.fz-juelich.de',
415 'id': 2,
416 'kind': 'research',
417 'longname': 'Forschungszentrum Jülich',
418 'name': 'FZJ',
419 'postcode': '52425',
420 'street_address': 'Wilhelm-Johnen-Straße',
421 },
422 },
423 'id': 1,
424 'role': 'resource provider',
425 'status': 'active',
426 },
427 ],
428 'sampling_frequency': 'hourly',
429 'sampling_height': 7.0,
430 'station': {
431 'additional_metadata': {
432 'add_type': 'nature reservation',
433 },
434 'aux_docs': [],
435 'aux_images': [],
436 'aux_urls': [],
437 'changelog': [],
438 'codes': [
439 'SDZ54421',
440 ],
441 'coordinate_validation_status': 'not checked',
442 'coordinates': {
443 'alt': 293.9,
444 'lat': 40.65,
445 'lng': 117.106,
446 },
447 'country': 'China',
448 'globalmeta': {
449 'climatic_zone_year2016': '6 (warm temperate dry)',
450 'distance_to_major_road_year2020': -999.0,
451 'dominant_ecoregion_year2017': '-1 (undefined)',
452 'dominant_landcover_year2012': '11 (Cropland, rainfed, herbaceous cover)',
453 'ecoregion_description_25km_year2017': '',
454 'htap_region_tier1_year2010': '11 (MDE Middle East: S. Arabia, Oman, etc, Iran, Iraq)',
455 'landcover_description_25km_year2012': '',
456 'max_population_density_25km_year1990': -1.0,
457 'max_population_density_25km_year2015': -1.0,
458 'max_stable_nightlights_25km_year1992': -999.0,
459 'max_stable_nightlights_25km_year2013': -999.0,
460 'max_topography_srtm_relative_alt_5km_year1994': -999.0,
461 'mean_nox_emissions_10km_year2000': -999.0,
462 'mean_nox_emissions_10km_year2015': -999.0,
463 'mean_population_density_250m_year1990': -1.0,
464 'mean_population_density_250m_year2015': -1.0,
465 'mean_population_density_5km_year1990': -1.0,
466 'mean_population_density_5km_year2015': -1.0,
467 'mean_stable_nightlights_1km_year2013': -999.0,
468 'mean_stable_nightlights_5km_year2013': -999.0,
469 'mean_topography_srtm_alt_1km_year1994': -999.0,
470 'mean_topography_srtm_alt_90m_year1994': -999.0,
471 'min_topography_srtm_relative_alt_5km_year1994': -999.0,
472 'stddev_topography_srtm_relative_alt_5km_year1994': -999.0,
473 'toar1_category': 'unclassified',
474 'toar2_category': 'suburban',
475 },
476 'id': 2,
477 'name': 'Shangdianzi',
478 'state': 'Beijing Shi',
479 'timezone': 'Asia/Shanghai',
480 'type': 'unknown',
481 'type_of_area': 'unknown',
482 },
483 'variable': {
484 'cf_standardname': 'mole_fraction_of_ozone_in_air',
485 'chemical_formula': 'O3',
486 'displayname': 'Ozone',
487 'id': 5,
488 'longname': 'ozone',
489 'name': 'o3',
490 'units': 'nmol mol-1',
491 },
492 },
493 {
494 'additional_metadata': {
495 'original_units': 'ppb',
496 },
497 'aggregation': 'mean',
498 'coverage': -1.0,
499 'data_end_date': '2025-02-25T14:00:00+00:00',
500 'data_origin': 'instrument',
501 'data_origin_type': 'measurement',
502 'data_start_date': '1991-01-01T00:00:00+00:00',
503 'doi': '',
504 'id': 30890,
505 'label': '',
506 'order': 2,
507 'programme': {
508 'description': '',
509 'homepage': '',
510 'id': 0,
511 'longname': '',
512 'name': '',
513 },
514 'provider_version': 'N/A',
515 'roles': [
516 {
517 'contact': {
518 'id': 5,
519 'organisation': {
520 'city': 'Jülich',
521 'contact_url': 'mailto:toar-data@fz-juelich.de',
522 'country': 'Germany',
523 'homepage': 'https://www.fz-juelich.de',
524 'id': 2,
525 'kind': 'research',
526 'longname': 'Forschungszentrum Jülich',
527 'name': 'FZJ',
528 'postcode': '52425',
529 'street_address': 'Wilhelm-Johnen-Straße',
530 },
531 },
532 'id': 1,
533 'role': 'resource provider',
534 'status': 'active',
535 },
536 ],
537 'sampling_frequency': 'hourly',
538 'sampling_height': 7.0,
539 'station': {
540 'additional_metadata': {
541 'add_type': 'nature reservation',
542 },
543 'aux_docs': [],
544 'aux_images': [],
545 'aux_urls': [],
546 'changelog': [],
547 'codes': [
548 'SDZ54421',
549 ],
550 'coordinate_validation_status': 'not checked',
551 'coordinates': {
552 'alt': 293.9,
553 'lat': 40.65,
554 'lng': 117.106,
555 },
556 'country': 'China',
557 'globalmeta': {
558 'climatic_zone_year2016': '6 (warm temperate dry)',
559 'distance_to_major_road_year2020': -999.0,
560 'dominant_ecoregion_year2017': '-1 (undefined)',
561 'dominant_landcover_year2012': '11 (Cropland, rainfed, herbaceous cover)',
562 'ecoregion_description_25km_year2017': '',
563 'htap_region_tier1_year2010': '11 (MDE Middle East: S. Arabia, Oman, etc, Iran, Iraq)',
564 'landcover_description_25km_year2012': '',
565 'max_population_density_25km_year1990': -1.0,
566 'max_population_density_25km_year2015': -1.0,
567 'max_stable_nightlights_25km_year1992': -999.0,
568 'max_stable_nightlights_25km_year2013': -999.0,
569 'max_topography_srtm_relative_alt_5km_year1994': -999.0,
570 'mean_nox_emissions_10km_year2000': -999.0,
571 'mean_nox_emissions_10km_year2015': -999.0,
572 'mean_population_density_250m_year1990': -1.0,
573 'mean_population_density_250m_year2015': -1.0,
574 'mean_population_density_5km_year1990': -1.0,
575 'mean_population_density_5km_year2015': -1.0,
576 'mean_stable_nightlights_1km_year2013': -999.0,
577 'mean_stable_nightlights_5km_year2013': -999.0,
578 'mean_topography_srtm_alt_1km_year1994': -999.0,
579 'mean_topography_srtm_alt_90m_year1994': -999.0,
580 'min_topography_srtm_relative_alt_5km_year1994': -999.0,
581 'stddev_topography_srtm_relative_alt_5km_year1994': -999.0,
582 'toar1_category': 'unclassified',
583 'toar2_category': 'suburban',
584 },
585 'id': 2,
586 'name': 'Shangdianzi',
587 'state': 'Beijing Shi',
588 'timezone': 'Asia/Shanghai',
589 'type': 'unknown',
590 'type_of_area': 'unknown',
591 },
592 'variable': {
593 'cf_standardname': 'mole_fraction_of_ozone_in_air',
594 'chemical_formula': 'O3',
595 'displayname': 'Ozone',
596 'id': 5,
597 'longname': 'ozone',
598 'name': 'o3',
599 'units': 'nmol mol-1',
600 },
601 },
602 {
603 'additional_metadata': {
604 'original_units': 'ppb',
605 },
606 'aggregation': 'mean',
607 'coverage': -1.0,
608 'data_end_date': '2025-02-25T14:00:00+00:00',
609 'data_origin': 'instrument',
610 'data_origin_type': 'measurement',
611 'data_start_date': '1991-01-01T00:00:00+00:00',
612 'doi': '',
613 'id': 434870,
614 'label': '',
615 'order': 2,
616 'programme': {
617 'description': '',
618 'homepage': '',
619 'id': 0,
620 'longname': '',
621 'name': '',
622 },
623 'provider_version': 'N/A',
624 'roles': [
625 {
626 'contact': {
627 'id': 4,
628 'organisation': {
629 'city': 'Dessau-Roßlau',
630 'contact_url': 'mailto:immission@uba.de',
631 'country': 'Germany',
632 'homepage': 'https://www.umweltbundesamt.de',
633 'id': 1,
634 'kind': 'government',
635 'longname': 'Umweltbundesamt',
636 'name': 'UBA',
637 'postcode': '06844',
638 'street_address': 'Wörlitzer Platz 1',
639 },
640 },
641 'id': 2,
642 'role': 'resource provider',
643 'status': 'active',
644 },
645 ],
646 'sampling_frequency': 'hourly',
647 'sampling_height': 7.0,
648 'station': {
649 'additional_metadata': {
650 'add_type': 'nature reservation',
651 },
652 'aux_docs': [],
653 'aux_images': [],
654 'aux_urls': [],
655 'changelog': [],
656 'codes': [
657 'SDZ54421',
658 ],
659 'coordinate_validation_status': 'not checked',
660 'coordinates': {
661 'alt': 293.9,
662 'lat': 40.65,
663 'lng': 117.106,
664 },
665 'country': 'China',
666 'globalmeta': {
667 'climatic_zone_year2016': '6 (warm temperate dry)',
668 'distance_to_major_road_year2020': -999.0,
669 'dominant_ecoregion_year2017': '-1 (undefined)',
670 'dominant_landcover_year2012': '11 (Cropland, rainfed, herbaceous cover)',
671 'ecoregion_description_25km_year2017': '',
672 'htap_region_tier1_year2010': '11 (MDE Middle East: S. Arabia, Oman, etc, Iran, Iraq)',
673 'landcover_description_25km_year2012': '',
674 'max_population_density_25km_year1990': -1.0,
675 'max_population_density_25km_year2015': -1.0,
676 'max_stable_nightlights_25km_year1992': -999.0,
677 'max_stable_nightlights_25km_year2013': -999.0,
678 'max_topography_srtm_relative_alt_5km_year1994': -999.0,
679 'mean_nox_emissions_10km_year2000': -999.0,
680 'mean_nox_emissions_10km_year2015': -999.0,
681 'mean_population_density_250m_year1990': -1.0,
682 'mean_population_density_250m_year2015': -1.0,
683 'mean_population_density_5km_year1990': -1.0,
684 'mean_population_density_5km_year2015': -1.0,
685 'mean_stable_nightlights_1km_year2013': -999.0,
686 'mean_stable_nightlights_5km_year2013': -999.0,
687 'mean_topography_srtm_alt_1km_year1994': -999.0,
688 'mean_topography_srtm_alt_90m_year1994': -999.0,
689 'min_topography_srtm_relative_alt_5km_year1994': -999.0,
690 'stddev_topography_srtm_relative_alt_5km_year1994': -999.0,
691 'toar1_category': 'unclassified',
692 'toar2_category': 'suburban',
693 },
694 'id': 2,
695 'name': 'Shangdianzi',
696 'state': 'Beijing Shi',
697 'timezone': 'Asia/Shanghai',
698 'type': 'unknown',
699 'type_of_area': 'unknown',
700 },
701 'variable': {
702 'cf_standardname': 'mole_fraction_of_ozone_in_air',
703 'chemical_formula': 'O3',
704 'displayname': 'Ozone',
705 'id': 5,
706 'longname': 'ozone',
707 'name': 'o3',
708 'units': 'nmol mol-1',
709 },
710 }]
711 assert response.json() == expected_resp
714 def test_get_special(self, client, db):
715 response = client.get("/timeseries/1")
716 expected_status_code = 200
717 assert response.status_code == expected_status_code
718 expected_resp = {'id': 1, 'label': 'CMA', 'order': 1,
719 'sampling_frequency': 'hourly', 'aggregation': 'mean', 'data_origin_type': 'measurement',
720 'data_start_date': '2003-09-07T15:30:00+00:00', 'data_end_date': '2016-12-31T14:30:00+00:00', 'coverage': -1.0,
721 'data_origin': 'instrument', 'sampling_height': 7.0,
722 'provider_version': 'N/A', 'doi': '', 'additional_metadata': {'original_units': 'ppb'},
723 'roles': [{'id': 2, 'role': 'resource provider', 'status': 'active',
724 'contact': {'id': 4, 'organisation': {'id': 1, 'name': 'UBA', 'longname': 'Umweltbundesamt',
725 'kind': 'government', 'city': 'Dessau-Roßlau', 'postcode': '06844', 'street_address': 'Wörlitzer Platz 1',
726 'country': 'Germany', 'homepage': 'https://www.umweltbundesamt.de', 'contact_url': 'mailto:immission@uba.de'}}},
727 {'id': 3, 'role': 'principal investigator', 'status': 'active',
728 'contact': {'id': 3, 'person': {'email': 's.schroeder@fz-juelich.de', 'id': 3, 'isprivate': False,
729 'name': 'Sabine Schröder', 'orcid': '0000-0002-0309-8010', 'phone': '+49-2461-61-6397'}}}],
730 'variable': {'name': 'toluene', 'longname': 'toluene', 'displayname': 'Toluene',
731 'cf_standardname': 'mole_fraction_of_toluene_in_air', 'units': 'nmol mol-1',
732 'chemical_formula': 'C7H8', 'id': 7},
733 'station': {'id': 2, 'codes': ['SDZ54421'], 'name': 'Shangdianzi',
734 'coordinates': {'lat': 40.65, 'lng': 117.106, 'alt': 293.9},
735 'coordinate_validation_status': 'not checked',
736 'country': 'China', 'state': 'Beijing Shi',
737 'type': 'unknown', 'type_of_area': 'unknown',
738 'timezone': 'Asia/Shanghai',
739 'additional_metadata': {'add_type': 'nature reservation'},
740 'aux_images': [], 'aux_docs': [], 'aux_urls': [],
741 'globalmeta': {'climatic_zone_year2016': '6 (warm temperate dry)',
742 'distance_to_major_road_year2020': -999.0,
743 'dominant_ecoregion_year2017': '-1 (undefined)',
744 'dominant_landcover_year2012': '11 (Cropland, rainfed, herbaceous cover)',
745 'ecoregion_description_25km_year2017': '',
746 'htap_region_tier1_year2010': '11 (MDE Middle East: S. Arabia, Oman, etc, Iran, Iraq)',
747 'landcover_description_25km_year2012': '',
748 'max_stable_nightlights_25km_year1992': -999.0,
749 'max_stable_nightlights_25km_year2013': -999.0,
750 'max_population_density_25km_year1990': -1.0,
751 'max_population_density_25km_year2015': -1.0,
752 'max_topography_srtm_relative_alt_5km_year1994': -999.0,
753 'mean_stable_nightlights_1km_year2013': -999.0,
754 'mean_stable_nightlights_5km_year2013': -999.0,
755 'mean_nox_emissions_10km_year2000': -999.0,
756 'mean_nox_emissions_10km_year2015': -999.0,
757 'mean_population_density_250m_year1990': -1.0,
758 'mean_population_density_250m_year2015': -1.0,
759 'mean_population_density_5km_year1990': -1.0,
760 'mean_population_density_5km_year2015': -1.0,
761 'mean_topography_srtm_alt_1km_year1994': -999.0,
762 'mean_topography_srtm_alt_90m_year1994': -999.0,
763 'min_topography_srtm_relative_alt_5km_year1994': -999.0,
764 'stddev_topography_srtm_relative_alt_5km_year1994': -999.0,
765 'toar1_category': 'unclassified',
766 'toar2_category': 'suburban'},
767 'changelog': []},
768 'programme': {'id': 0, 'name': '', 'longname': '', 'homepage': '', 'description': ''}}
769 assert response.json() == expected_resp
772 def test_insert_new_wrong_credentials(self, client, db):
773 response = client.post("/timeseries/",
774 json={"timeseries":
775 {"label": "CMA2", "order": 1,
776 "sampling_frequency": "Hourly", "aggregation": "Mean", "data_origin_type": "Measurement",
777 "data_start_date": "2003-09-07T15:30:00+02:00",
778 "data_end_date": "2016-12-31T14:30:00+01:00",
779 'coverage': -1.0,
780 "data_origin": "Instrument", "sampling_height": 7.0,
781 "station_id": 2, "variable_id": 7,
782 "additional_metadata":"{}"
783 }
784 },
785 headers={"email": "j.doe@fz-juelich.de"}
786 )
787 expected_status_code=401
788 assert response.status_code == expected_status_code
789 expected_resp = {'detail': 'Unauthorized.'}
790 assert response.json() == expected_resp
793 def test_insert_new_without_credentials(self, client, db):
794 response = client.post("/timeseries/",
795 json={"timeseries":
796 {"label": "CMA2", "order": 1,
797 "sampling_frequency": "Hourly", "aggregation": "Mean", "data_origin_type": "Measurement",
798 "data_start_date": "2003-09-07T15:30:00+02:00",
799 "data_end_date": "2016-12-31T14:30:00+01:00",
800 'coverage': -1.0,
801 "data_origin": "Instrument", "sampling_height": 7.0,
802 "station_id": 2, "variable_id": 7,
803 "additional_metadata":"{}"
804 }
805 })
806 expected_status_code=401
807 assert response.status_code == expected_status_code
808 expected_resp = {'detail': 'Unauthorized.'}
809 assert response.json() == expected_resp
812 def test_insert_new_with_roles(self, client, db):
813 add_meta_dict = {"absorption_cross_section": "Hearn1961", "measurement_method": "uv_abs"}
814 response = client.post("/timeseries/",
815 json={"timeseries":
816 {"label": "CMA2", "order": 1,
817 "sampling_frequency": "Hourly", "aggregation": "Mean", "data_origin_type": "Measurement",
818 "data_start_date": "2003-09-07T15:30:00+02:00",
819 "data_end_date": "2016-12-31T14:30:00+01:00",
820 'coverage': -1.0,
821 "data_origin": "Instrument", "sampling_height": 7.0,
822 "station_id": 2, "variable_id": 5,
823 "additional_metadata": json.dumps(add_meta_dict),
824 "roles": [{"role": "PointOfContact", "contact_id": 3, "status": "Active"},
825 {"role": "Originator", "contact_id": 1, "status": "Active"}]
826 }
827 },
828 headers={"email": "s.schroeder@fz-juelich.de"}
829 )
830 expected_status_code = 200
831 assert response.status_code == expected_status_code
832 expected_resp = {'detail': {'message': 'New timeseries created.', 'timeseries_id': 3}}
833 assert response.json() == expected_resp
835 new_response = client.get("/timeseries/3")
836 new_expected_status_code = 200
837 assert new_response.status_code == new_expected_status_code
838 new_response_changelog = new_response.json()["changelog"][-1]
839 expected_changelog = {'datetime': '2023-07-28T12:00:00+00:00', 'description': 'time series created', 'old_value': '', 'new_value': '',
840 'timeseries_id': 3, 'author_id': 1,
841 'type_of_change': 'created'}
842 assert new_response_changelog == expected_changelog
845 def test_insert_new_without_existing_station(self, client, db):
846 response = client.post("/timeseries/",
847 json={"timeseries":
848 {"label": "CMA2", "order": 1,
849 "sampling_frequency": "Hourly", "aggregation": "Mean", "data_origin_type": "Measurement",
850 "data_start_date": "2003-09-07T15:30:00+02:00",
851 "data_end_date": "2016-12-31T14:30:00+01:00",
852 'coverage': -1.0,
853 "data_origin": "Instrument", "sampling_height": 7.0,
854 "station_id": 97, "variable_id": 7,
855 "additional_metadata":"{}",
856 "roles": [{"role": "PointOfContact", "contact_id": 3, "status": "Active"},
857 {"role": "Originator", "contact_id": 1, "status": "Active"}]
858 }
859 },
860 headers={"email": "s.schroeder@fz-juelich.de"}
861 )
862 expected_status_code = 440
863 assert response.status_code == expected_status_code
864 expected_resp = 'Station (station_id: 97) not found in database.'
865 assert response.json() == expected_resp
868 def test_insert_duplicate_no_resource_provider(self, client, db):
869 # one timeseries can be inserted without having a resource provider
870 response = client.post("/timeseries/",
871 json={"timeseries":
872 {"label": "CMA", "order": 1,
873 "sampling_frequency": "Hourly", "aggregation": "Mean", "data_origin_type": "Measurement",
874 "data_start_date": "2003-09-07T15:30:00+02:00",
875 "data_end_date": "2016-12-31T14:30:00+01:00",
876 'coverage': -1.0,
877 "data_origin": "Instrument", "sampling_height": 7.0,
878 "station_id": 2, "variable_id": 7,
879 "additional_metadata":"{}",
880 "roles": [{"role": "PointOfContact", "contact_id": 3, "status": "Active"},
881 {"role": "Originator", "contact_id": 1, "status": "Active"}]
882 }
883 },
884 headers={"email": "s.schroeder@fz-juelich.de"}
885 )
886 expected_status_code = 200
887 assert response.status_code == expected_status_code
888 expected_resp = {'detail': {'message': 'New timeseries created.', 'timeseries_id': 3}}
889 assert response.json() == expected_resp
892 def test_insert_duplicate_wrong_resource_provider(self, client, db):
893 # one timeseries can be inserted without having a resource provider
894 response = client.post("/timeseries/",
895 json={"timeseries":
896 {"label": "CMA", "order": 1,
897 "sampling_frequency": "Hourly", "aggregation": "Mean", "data_origin_type": "Measurement",
898 "data_start_date": "2003-09-07T15:30:00+02:00",
899 "data_end_date": "2016-12-31T14:30:00+01:00",
900 'coverage': -1.0,
901 "data_origin": "Instrument", "sampling_height": 7.0,
902 "station_id": 2, "variable_id": 7,
903 "additional_metadata":"{}",
904 "roles": [{"role": "PointOfContact", "contact_id": 3, "status": "Active"},
905 {"role": "Originator", "contact_id": 1, "status": "Active"},
906 {"role": "ResourceProvider", "contact_id": 1, "status": "Active"}]
907 }
908 },
909 headers={"email": "s.schroeder@fz-juelich.de"}
910 )
911 expected_status_code = 442
912 assert response.status_code == expected_status_code
913 expected_resp = 'Resource provider (contact_id: 1) not found in database.'
914 assert response.json() == expected_resp
917 def test_insert_duplicate(self, client, db):
918 # one timeseries can be inserted without having a resource provider
919 response = client.post("/timeseries/",
920 json={"timeseries":
921 {"label": "CMA", "order": 1,
922 "sampling_frequency": "Hourly", "aggregation": "Mean", "data_origin_type": "Measurement",
923 "data_start_date": "2003-09-07T15:30:00+02:00",
924 "data_end_date": "2016-12-31T14:30:00+01:00",
925 'coverage': -1.0,
926 "data_origin": "Instrument", "sampling_height": 7.0,
927 "station_id": 2, "variable_id": 7,
928 "additional_metadata":"{}",
929 "roles": [{"role": "PointOfContact", "contact_id": 3, "status": "Active"},
930 {"role": "Originator", "contact_id": 1, "status": "Active"},
931 {"role": "ResourceProvider", "contact_id": 4, "status": "Active"}]
932 }
933 },
934 headers={"email": "s.schroeder@fz-juelich.de"}
935 )
936 expected_status_code = 443
937 assert response.status_code == expected_status_code
938 expected_resp = {'detail': {'message': 'Timeseries already registered.', 'timeseries_id': 1}}
939 assert response.json() == expected_resp
941 def test_get_all_timeseries(self, client, db):
942 response = client.get("/timeseries/?limit=1")
943 expected_status_code = 200
944 assert response.status_code == expected_status_code
945 expected_response = [{
946 'id': 1, 'label': 'CMA', 'order': 1, 'sampling_frequency': 'hourly',
947 'aggregation': 'mean', 'data_start_date': '2003-09-07T15:30:00+00:00',
948 'data_end_date': '2016-12-31T14:30:00+00:00', 'coverage': -1.0, 'data_origin': 'instrument', 'data_origin_type': 'measurement',
949 'provider_version': 'N/A', 'doi': '', 'sampling_height': 7.0, 'additional_metadata': {'original_units': 'ppb'},
950 'roles': [{'id': 2, 'role': 'resource provider', 'status': 'active',
951 'contact': {'id': 4, 'organisation': {'id': 1, 'name': 'UBA', 'longname': 'Umweltbundesamt',
952 'kind': 'government', 'city': 'Dessau-Roßlau', 'postcode': '06844', 'street_address': 'Wörlitzer Platz 1',
953 'country': 'Germany', 'homepage': 'https://www.umweltbundesamt.de', 'contact_url': 'mailto:immission@uba.de'}}},
954 {'id': 3, 'role': 'principal investigator', 'status': 'active',
955 'contact': {'id': 3, 'person': {'email': 's.schroeder@fz-juelich.de','id': 3, 'isprivate': False,
956 'name': 'Sabine Schröder', 'orcid': '0000-0002-0309-8010', 'phone': '+49-2461-61-6397'}}}
957 ],
958 'station': {'id': 2, 'codes': ['SDZ54421'], 'name': 'Shangdianzi',
959 'coordinates': {'lat': 40.65, 'lng': 117.106, 'alt': 293.9},
960 'coordinate_validation_status': 'not checked', 'country': 'China',
961 'state': 'Beijing Shi', 'type': 'unknown', 'type_of_area': 'unknown',
962 'timezone': 'Asia/Shanghai',
963 'additional_metadata': {'add_type': 'nature reservation'},
964 'aux_images': [], 'aux_docs': [], 'aux_urls': [],
965 'globalmeta': {'climatic_zone_year2016': '6 (warm temperate dry)',
966 'distance_to_major_road_year2020': -999.0,
967 'dominant_ecoregion_year2017': '-1 (undefined)',
968 'dominant_landcover_year2012': '11 (Cropland, rainfed, herbaceous cover)',
969 'ecoregion_description_25km_year2017': '',
970 'htap_region_tier1_year2010': '11 (MDE Middle East: S. Arabia, Oman, etc, Iran, Iraq)',
971 'landcover_description_25km_year2012': '',
972 'max_stable_nightlights_25km_year1992': -999.0,
973 'max_stable_nightlights_25km_year2013': -999.0,
974 'max_population_density_25km_year1990': -1.0,
975 'max_population_density_25km_year2015': -1.0,
976 'max_topography_srtm_relative_alt_5km_year1994': -999.0,
977 'mean_stable_nightlights_1km_year2013': -999.0,
978 'mean_stable_nightlights_5km_year2013': -999.0,
979 'mean_nox_emissions_10km_year2000': -999.0,
980 'mean_nox_emissions_10km_year2015': -999.0,
981 'mean_population_density_250m_year1990': -1.0,
982 'mean_population_density_250m_year2015': -1.0,
983 'mean_population_density_5km_year1990': -1.0,
984 'mean_population_density_5km_year2015': -1.0,
985 'mean_topography_srtm_alt_1km_year1994': -999.0,
986 'mean_topography_srtm_alt_90m_year1994': -999.0,
987 'min_topography_srtm_relative_alt_5km_year1994': -999.0,
988 'stddev_topography_srtm_relative_alt_5km_year1994': -999.0,
989 'toar1_category': 'unclassified',
990 'toar2_category': 'suburban'},
991 'changelog': []},
992 'variable': {'name': 'toluene', 'longname': 'toluene', 'displayname': 'Toluene',
993 'cf_standardname': 'mole_fraction_of_toluene_in_air', 'units': 'nmol mol-1',
994 'chemical_formula': 'C7H8', 'id': 7},
995 'programme': {'id': 0, 'name': '', 'longname': '', 'homepage': '', 'description': ''}
996 }]
997 assert response.json() == expected_response
1000 def test_get_one_timeseries_with_fields(self, client, db):
1001 response = client.get("/timeseries/1?fields=additional_metadata")
1002 expected_status_code = 200
1003 assert response.status_code == expected_status_code
1004 expected_response = {'additional_metadata': {'original_units': 'ppb'} }
1005 assert response.json() == expected_response
1008 def test_get_all_timeseries_with_fields(self, client, db):
1009 response = client.get("/timeseries/?fields=additional_metadata")
1010 expected_status_code = 200
1011 assert response.status_code == expected_status_code
1012 expected_response = [
1013 {'additional_metadata': {'original_units': 'ppb'}},
1014 {'additional_metadata':
1015 {'absorption_cross_section': 'Hearn 1961',
1016 'ebas_metadata_19740101000000_29y':
1017 {'Data level': '2',
1018 'Frameworks': 'GAW-WDCRG '
1019 'NOAA-ESRL',
1020 'Station code': 'XXX',
1021 'Station name': 'Secret',
1022 'Submitter': 'Unknown, Lady, lady.unknown@unknown.com, some long division name, SHORT, , 111 Streetname, , zipcode, Boulder, CO, USA'
1023 },
1024 'measurement_method': 'uv_abs',
1025 'original_units': {'since_19740101000000': 'nmol/mol'}
1026 }
1027 }
1028 ]
1029 set_expected_response = {json.dumps(item, sort_keys=True) for item in expected_response}
1030 set_response = {json.dumps(item, sort_keys=True) for item in response.json()}
1031 assert set_response == set_expected_response
1034 def test_get_all_timeseries_filter_roles(self, client, db):
1035 response = client.get("/timeseries/?has_role=UBA")
1036 expected_status_code = 200
1037 assert response.status_code == expected_status_code
1038 expected_response = [{'id': 1,
1039 'label': 'CMA',
1040 'order': 1,
1041 'sampling_frequency': 'hourly',
1042 'aggregation': 'mean',
1043 'data_start_date': '2003-09-07T15:30:00+00:00',
1044 'data_end_date': '2016-12-31T14:30:00+00:00',
1045 'data_origin': 'instrument',
1046 'data_origin_type': 'measurement',
1047 'provider_version': 'N/A',
1048 'sampling_height': 7.0,
1049 'additional_metadata': {'original_units': 'ppb'},
1050 'doi': '',
1051 'coverage': -1.0,
1052 'station': {'id': 2,
1053 'codes': ['SDZ54421'],
1054 'name': 'Shangdianzi',
1055 'coordinates': {'lat': 40.65, 'lng': 117.106, 'alt': 293.9},
1056 'coordinate_validation_status': 'not checked',
1057 'country': 'China',
1058 'state': 'Beijing Shi',
1059 'type': 'unknown',
1060 'type_of_area': 'unknown',
1061 'timezone': 'Asia/Shanghai',
1062 'additional_metadata': {'add_type': 'nature reservation'},
1063 'aux_images': [],
1064 'aux_docs': [],
1065 'aux_urls': [],
1066 'globalmeta': {'mean_topography_srtm_alt_90m_year1994': -999.0,
1067 'mean_topography_srtm_alt_1km_year1994': -999.0,
1068 'max_topography_srtm_relative_alt_5km_year1994': -999.0,
1069 'min_topography_srtm_relative_alt_5km_year1994': -999.0,
1070 'stddev_topography_srtm_relative_alt_5km_year1994': -999.0,
1071 'climatic_zone_year2016': '6 (warm temperate dry)',
1072 'htap_region_tier1_year2010': '11 (MDE Middle East: S. Arabia, Oman, etc, Iran, Iraq)',
1073 'dominant_landcover_year2012': '11 (Cropland, rainfed, herbaceous cover)',
1074 'landcover_description_25km_year2012': '',
1075 'dominant_ecoregion_year2017': '-1 (undefined)',
1076 'ecoregion_description_25km_year2017': '',
1077 'distance_to_major_road_year2020': -999.0,
1078 'mean_stable_nightlights_1km_year2013': -999.0,
1079 'mean_stable_nightlights_5km_year2013': -999.0,
1080 'max_stable_nightlights_25km_year2013': -999.0,
1081 'max_stable_nightlights_25km_year1992': -999.0,
1082 'mean_population_density_250m_year2015': -1.0,
1083 'mean_population_density_5km_year2015': -1.0,
1084 'max_population_density_25km_year2015': -1.0,
1085 'mean_population_density_250m_year1990': -1.0,
1086 'mean_population_density_5km_year1990': -1.0,
1087 'max_population_density_25km_year1990': -1.0,
1088 'mean_nox_emissions_10km_year2015': -999.0,
1089 'mean_nox_emissions_10km_year2000': -999.0,
1090 'toar1_category': 'unclassified',
1091 'toar2_category': 'suburban'
1092 },
1093 'changelog': []
1094 },
1095 'variable': {'name': 'toluene',
1096 'longname': 'toluene',
1097 'displayname': 'Toluene',
1098 'cf_standardname': 'mole_fraction_of_toluene_in_air',
1099 'units': 'nmol mol-1',
1100 'chemical_formula': 'C7H8',
1101 'id': 7
1102 },
1103 'programme': {'id': 0,
1104 'name': '',
1105 'longname': '',
1106 'homepage': '',
1107 'description': ''
1108 },
1109 'roles': [{'id': 2,
1110 'role': 'resource provider',
1111 'status': 'active',
1112 'contact': {'id': 4,
1113 'organisation': {'id': 1,
1114 'name': 'UBA',
1115 'longname': 'Umweltbundesamt',
1116 'kind': 'government',
1117 'city': 'Dessau-Roßlau',
1118 'postcode': '06844',
1119 'street_address': 'Wörlitzer Platz 1',
1120 'country': 'Germany',
1121 'homepage': 'https://www.umweltbundesamt.de',
1122 'contact_url': 'mailto:immission@uba.de'
1123 }
1124 }
1125 },
1126 {'id': 3,
1127 'role': 'principal investigator',
1128 'status': 'active',
1129 'contact': {'id': 3,
1130 'person': {'email': 's.schroeder@fz-juelich.de',
1131 'id': 3,
1132 'isprivate': False,
1133 'name': 'Sabine Schröder',
1134 'orcid': '0000-0002-0309-8010',
1135 'phone': '+49-2461-61-6397'}}}
1136 ]
1137 },
1138 {'additional_metadata': {
1139 'original_units': 'ppb',
1140 },
1141 'aggregation': 'mean',
1142 'coverage': -1.0,
1143 'data_end_date': '2025-02-25T14:00:00+00:00',
1144 'data_origin': 'instrument',
1145 'data_origin_type': 'measurement',
1146 'data_start_date': '1991-01-01T00:00:00+00:00',
1147 'doi': '',
1148 'id': 434870,
1149 'label': '',
1150 'order': 2,
1151 'programme': {
1152 'description': '',
1153 'homepage': '',
1154 'id': 0,
1155 'longname': '',
1156 'name': '',
1157 },
1158 'provider_version': 'N/A',
1159 'roles': [
1160 {
1161 'contact': {
1162 'id': 4,
1163 'organisation': {
1164 'city': 'Dessau-Roßlau',
1165 'contact_url': 'mailto:immission@uba.de',
1166 'country': 'Germany',
1167 'homepage': 'https://www.umweltbundesamt.de',
1168 'id': 1,
1169 'kind': 'government',
1170 'longname': 'Umweltbundesamt',
1171 'name': 'UBA',
1172 'postcode': '06844',
1173 'street_address': 'Wörlitzer Platz 1',
1174 },
1175 },
1176 'id': 2,
1177 'role': 'resource provider',
1178 'status': 'active',
1179 },
1180 ],
1181 'sampling_frequency': 'hourly',
1182 'sampling_height': 7.0,
1183 'station': {
1184 'additional_metadata': {
1185 'add_type': 'nature reservation',
1186 },
1187 'aux_docs': [],
1188 'aux_images': [],
1189 'aux_urls': [],
1190 'changelog': [],
1191 'codes': [
1192 'SDZ54421',
1193 ],
1194 'coordinate_validation_status': 'not checked',
1195 'coordinates': {
1196 'alt': 293.9,
1197 'lat': 40.65,
1198 'lng': 117.106,
1199 },
1200 'country': 'China',
1201 'globalmeta': {
1202 'climatic_zone_year2016': '6 (warm temperate dry)',
1203 'distance_to_major_road_year2020': -999.0,
1204 'dominant_ecoregion_year2017': '-1 (undefined)',
1205 'dominant_landcover_year2012': '11 (Cropland, rainfed, herbaceous cover)',
1206 'ecoregion_description_25km_year2017': '',
1207 'htap_region_tier1_year2010': '11 (MDE Middle East: S. Arabia, Oman, etc, Iran, Iraq)',
1208 'landcover_description_25km_year2012': '',
1209 'max_population_density_25km_year1990': -1.0,
1210 'max_population_density_25km_year2015': -1.0,
1211 'max_stable_nightlights_25km_year1992': -999.0,
1212 'max_stable_nightlights_25km_year2013': -999.0,
1213 'max_topography_srtm_relative_alt_5km_year1994': -999.0,
1214 'mean_nox_emissions_10km_year2000': -999.0,
1215 'mean_nox_emissions_10km_year2015': -999.0,
1216 'mean_population_density_250m_year1990': -1.0,
1217 'mean_population_density_250m_year2015': -1.0,
1218 'mean_population_density_5km_year1990': -1.0,
1219 'mean_population_density_5km_year2015': -1.0,
1220 'mean_stable_nightlights_1km_year2013': -999.0,
1221 'mean_stable_nightlights_5km_year2013': -999.0,
1222 'mean_topography_srtm_alt_1km_year1994': -999.0,
1223 'mean_topography_srtm_alt_90m_year1994': -999.0,
1224 'min_topography_srtm_relative_alt_5km_year1994': -999.0,
1225 'stddev_topography_srtm_relative_alt_5km_year1994': -999.0,
1226 'toar1_category': 'unclassified',
1227 'toar2_category': 'suburban',
1228 },
1229 'id': 2,
1230 'name': 'Shangdianzi',
1231 'state': 'Beijing Shi',
1232 'timezone': 'Asia/Shanghai',
1233 'type': 'unknown',
1234 'type_of_area': 'unknown',
1235 },
1236 'variable': {
1237 'cf_standardname': 'mole_fraction_of_ozone_in_air',
1238 'chemical_formula': 'O3',
1239 'displayname': 'Ozone',
1240 'id': 5,
1241 'longname': 'ozone',
1242 'name': 'o3',
1243 'units': 'nmol mol-1',
1244 }
1245 }]
1246 assert response.json() == expected_response
1249 def test_get_all_timeseries_filter_not_roles(self, client, db):
1250 response = client.get("/timeseries/?has_role=~UBA")
1251 expected_status_code = 200
1252 assert response.status_code == expected_status_code
1253 expected_response = [{'id': 2,
1254 'label': 'CMA',
1255 'order': 1,
1256 'sampling_frequency': 'hourly',
1257 'aggregation': 'mean',
1258 'data_start_date': '2003-09-07T15:30:00+00:00',
1259 'data_end_date': '2016-12-31T14:30:00+00:00',
1260 'data_origin': 'instrument',
1261 'data_origin_type': 'measurement',
1262 'provider_version': 'N/A',
1263 'sampling_height': 7.0,
1264 'additional_metadata': {'original_units': {'since_19740101000000': 'nmol/mol'}, 'measurement_method': 'uv_abs', 'absorption_cross_section': 'Hearn 1961', 'ebas_metadata_19740101000000_29y': {'Submitter': 'Unknown, Lady, lady.unknown@unknown.com, some long division name, SHORT, , 111 Streetname, , zipcode, Boulder, CO, USA', 'Data level': '2', 'Frameworks': 'GAW-WDCRG NOAA-ESRL', 'Station code': 'XXX', 'Station name': 'Secret'}},
1265 'doi': '',
1266 'coverage': -1.0,
1267 'station': {'id': 3,
1268 'codes': ['China_test8'],
1269 'name': 'Test_China',
1270 'coordinates': {'lat': 36.256, 'lng': 117.106, 'alt': 1534.0},
1271 'coordinate_validation_status': 'not checked',
1272 'country': 'China',
1273 'state': 'Shandong Sheng',
1274 'type': 'unknown',
1275 'type_of_area': 'unknown',
1276 'timezone': 'Asia/Shanghai',
1277 'additional_metadata': {},
1278 'aux_images': [],
1279 'aux_docs': [],
1280 'aux_urls': [],
1281 'globalmeta': {'mean_topography_srtm_alt_90m_year1994': -999.0,
1282 'mean_topography_srtm_alt_1km_year1994': -999.0,
1283 'max_topography_srtm_relative_alt_5km_year1994': -999.0,
1284 'min_topography_srtm_relative_alt_5km_year1994': -999.0,
1285 'stddev_topography_srtm_relative_alt_5km_year1994': -999.0,
1286 'climatic_zone_year2016': '6 (warm temperate dry)',
1287 'htap_region_tier1_year2010': '10 (SAF Sub Saharan/sub Sahel Africa)',
1288 'dominant_landcover_year2012': '10 (Cropland, rainfed)',
1289 'landcover_description_25km_year2012': '',
1290 'dominant_ecoregion_year2017': '-1 (undefined)',
1291 'ecoregion_description_25km_year2017': '',
1292 'distance_to_major_road_year2020': -999.0,
1293 'mean_stable_nightlights_1km_year2013': -999.0,
1294 'mean_stable_nightlights_5km_year2013': -999.0,
1295 'max_stable_nightlights_25km_year2013': -999.0,
1296 'max_stable_nightlights_25km_year1992': -999.0,
1297 'mean_population_density_250m_year2015': -1.0,
1298 'mean_population_density_5km_year2015': -1.0,
1299 'max_population_density_25km_year2015': -1.0,
1300 'mean_population_density_250m_year1990': -1.0,
1301 'mean_population_density_5km_year1990': -1.0,
1302 'max_population_density_25km_year1990': -1.0,
1303 'mean_nox_emissions_10km_year2015': -999.0,
1304 'mean_nox_emissions_10km_year2000': -999.0,
1305 'toar1_category': 'unclassified',
1306 'toar2_category': 'suburban'},
1307 'changelog': []},
1308 'variable': {'name': 'o3',
1309 'longname': 'ozone',
1310 'displayname': 'Ozone',
1311 'cf_standardname': 'mole_fraction_of_ozone_in_air',
1312 'units': 'nmol mol-1',
1313 'chemical_formula': 'O3',
1314 'id': 5},
1315 'programme': {'id': 0,
1316 'name': '',
1317 'longname': '',
1318 'homepage': '',
1319 'description': ''},
1320 'roles': [{'id': 1,
1321 'role': 'resource provider',
1322 'status': 'active',
1323 'contact': {'id': 5,
1324 'organisation': {'id': 2,
1325 'name': 'FZJ',
1326 'longname': 'Forschungszentrum Jülich',
1327 'kind': 'research',
1328 'city': 'Jülich',
1329 'postcode': '52425',
1330 'street_address': 'Wilhelm-Johnen-Straße',
1331 'country': 'Germany',
1332 'homepage': 'https://www.fz-juelich.de',
1333 'contact_url': 'mailto:toar-data@fz-juelich.de'
1334 }
1335 }
1336 }
1337 ]
1338 },
1339 {
1340 'additional_metadata': {
1341 'original_units': 'ppb',
1342 },
1343 'aggregation': 'mean',
1344 'coverage': -1.0,
1345 'data_end_date': '2025-02-25T14:00:00+00:00',
1346 'data_origin': 'instrument',
1347 'data_origin_type': 'measurement',
1348 'data_start_date': '1991-01-01T00:00:00+00:00',
1349 'doi': '',
1350 'id': 18763,
1351 'label': '',
1352 'order': 1,
1353 'programme': {
1354 'description': '',
1355 'homepage': '',
1356 'id': 0,
1357 'longname': '',
1358 'name': '',
1359 },
1360 'provider_version': 'N/A',
1361 'roles': [
1362 {
1363 'contact': {
1364 'id': 5,
1365 'organisation': {
1366 'city': 'Jülich',
1367 'contact_url': 'mailto:toar-data@fz-juelich.de',
1368 'country': 'Germany',
1369 'homepage': 'https://www.fz-juelich.de',
1370 'id': 2,
1371 'kind': 'research',
1372 'longname': 'Forschungszentrum Jülich',
1373 'name': 'FZJ',
1374 'postcode': '52425',
1375 'street_address': 'Wilhelm-Johnen-Straße',
1376 },
1377 },
1378 'id': 1,
1379 'role': 'resource provider',
1380 'status': 'active',
1381 },
1382 ],
1383 'sampling_frequency': 'hourly',
1384 'sampling_height': 7.0,
1385 'station': {
1386 'additional_metadata': {
1387 'add_type': 'nature reservation',
1388 },
1389 'aux_docs': [],
1390 'aux_images': [],
1391 'aux_urls': [],
1392 'changelog': [],
1393 'codes': [
1394 'SDZ54421',
1395 ],
1396 'coordinate_validation_status': 'not checked',
1397 'coordinates': {
1398 'alt': 293.9,
1399 'lat': 40.65,
1400 'lng': 117.106,
1401 },
1402 'country': 'China',
1403 'globalmeta': {
1404 'climatic_zone_year2016': '6 (warm temperate dry)',
1405 'distance_to_major_road_year2020': -999.0,
1406 'dominant_ecoregion_year2017': '-1 (undefined)',
1407 'dominant_landcover_year2012': '11 (Cropland, rainfed, herbaceous cover)',
1408 'ecoregion_description_25km_year2017': '',
1409 'htap_region_tier1_year2010': '11 (MDE Middle East: S. Arabia, Oman, etc, Iran, Iraq)',
1410 'landcover_description_25km_year2012': '',
1411 'max_population_density_25km_year1990': -1.0,
1412 'max_population_density_25km_year2015': -1.0,
1413 'max_stable_nightlights_25km_year1992': -999.0,
1414 'max_stable_nightlights_25km_year2013': -999.0,
1415 'max_topography_srtm_relative_alt_5km_year1994': -999.0,
1416 'mean_nox_emissions_10km_year2000': -999.0,
1417 'mean_nox_emissions_10km_year2015': -999.0,
1418 'mean_population_density_250m_year1990': -1.0,
1419 'mean_population_density_250m_year2015': -1.0,
1420 'mean_population_density_5km_year1990': -1.0,
1421 'mean_population_density_5km_year2015': -1.0,
1422 'mean_stable_nightlights_1km_year2013': -999.0,
1423 'mean_stable_nightlights_5km_year2013': -999.0,
1424 'mean_topography_srtm_alt_1km_year1994': -999.0,
1425 'mean_topography_srtm_alt_90m_year1994': -999.0,
1426 'min_topography_srtm_relative_alt_5km_year1994': -999.0,
1427 'stddev_topography_srtm_relative_alt_5km_year1994': -999.0,
1428 'toar1_category': 'unclassified',
1429 'toar2_category': 'suburban',
1430 },
1431 'id': 2,
1432 'name': 'Shangdianzi',
1433 'state': 'Beijing Shi',
1434 'timezone': 'Asia/Shanghai',
1435 'type': 'unknown',
1436 'type_of_area': 'unknown',
1437 },
1438 'variable': {
1439 'cf_standardname': 'mole_fraction_of_ozone_in_air',
1440 'chemical_formula': 'O3',
1441 'displayname': 'Ozone',
1442 'id': 5,
1443 'longname': 'ozone',
1444 'name': 'o3',
1445 'units': 'nmol mol-1',
1446 },
1447 },
1448 {
1449 'additional_metadata': {
1450 'original_units': 'ppb',
1451 },
1452 'aggregation': 'mean',
1453 'coverage': -1.0,
1454 'data_end_date': '2025-02-25T14:00:00+00:00',
1455 'data_origin': 'instrument',
1456 'data_origin_type': 'measurement',
1457 'data_start_date': '1991-01-01T00:00:00+00:00',
1458 'doi': '',
1459 'id': 30890,
1460 'label': '',
1461 'order': 2,
1462 'programme': {
1463 'description': '',
1464 'homepage': '',
1465 'id': 0,
1466 'longname': '',
1467 'name': '',
1468 },
1469 'provider_version': 'N/A',
1470 'roles': [
1471 {
1472 'contact': {
1473 'id': 5,
1474 'organisation': {
1475 'city': 'Jülich',
1476 'contact_url': 'mailto:toar-data@fz-juelich.de',
1477 'country': 'Germany',
1478 'homepage': 'https://www.fz-juelich.de',
1479 'id': 2,
1480 'kind': 'research',
1481 'longname': 'Forschungszentrum Jülich',
1482 'name': 'FZJ',
1483 'postcode': '52425',
1484 'street_address': 'Wilhelm-Johnen-Straße',
1485 },
1486 },
1487 'id': 1,
1488 'role': 'resource provider',
1489 'status': 'active',
1490 },
1491 ],
1492 'sampling_frequency': 'hourly',
1493 'sampling_height': 7.0,
1494 'station': {
1495 'additional_metadata': {
1496 'add_type': 'nature reservation',
1497 },
1498 'aux_docs': [],
1499 'aux_images': [],
1500 'aux_urls': [],
1501 'changelog': [],
1502 'codes': [
1503 'SDZ54421',
1504 ],
1505 'coordinate_validation_status': 'not checked',
1506 'coordinates': {
1507 'alt': 293.9,
1508 'lat': 40.65,
1509 'lng': 117.106,
1510 },
1511 'country': 'China',
1512 'globalmeta': {
1513 'climatic_zone_year2016': '6 (warm temperate dry)',
1514 'distance_to_major_road_year2020': -999.0,
1515 'dominant_ecoregion_year2017': '-1 (undefined)',
1516 'dominant_landcover_year2012': '11 (Cropland, rainfed, herbaceous cover)',
1517 'ecoregion_description_25km_year2017': '',
1518 'htap_region_tier1_year2010': '11 (MDE Middle East: S. Arabia, Oman, etc, Iran, Iraq)',
1519 'landcover_description_25km_year2012': '',
1520 'max_population_density_25km_year1990': -1.0,
1521 'max_population_density_25km_year2015': -1.0,
1522 'max_stable_nightlights_25km_year1992': -999.0,
1523 'max_stable_nightlights_25km_year2013': -999.0,
1524 'max_topography_srtm_relative_alt_5km_year1994': -999.0,
1525 'mean_nox_emissions_10km_year2000': -999.0,
1526 'mean_nox_emissions_10km_year2015': -999.0,
1527 'mean_population_density_250m_year1990': -1.0,
1528 'mean_population_density_250m_year2015': -1.0,
1529 'mean_population_density_5km_year1990': -1.0,
1530 'mean_population_density_5km_year2015': -1.0,
1531 'mean_stable_nightlights_1km_year2013': -999.0,
1532 'mean_stable_nightlights_5km_year2013': -999.0,
1533 'mean_topography_srtm_alt_1km_year1994': -999.0,
1534 'mean_topography_srtm_alt_90m_year1994': -999.0,
1535 'min_topography_srtm_relative_alt_5km_year1994': -999.0,
1536 'stddev_topography_srtm_relative_alt_5km_year1994': -999.0,
1537 'toar1_category': 'unclassified',
1538 'toar2_category': 'suburban',
1539 },
1540 'id': 2,
1541 'name': 'Shangdianzi',
1542 'state': 'Beijing Shi',
1543 'timezone': 'Asia/Shanghai',
1544 'type': 'unknown',
1545 'type_of_area': 'unknown',
1546 },
1547 'variable': {
1548 'cf_standardname': 'mole_fraction_of_ozone_in_air',
1549 'chemical_formula': 'O3',
1550 'displayname': 'Ozone',
1551 'id': 5,
1552 'longname': 'ozone',
1553 'name': 'o3',
1554 'units': 'nmol mol-1',
1555 }
1556 }]
1557 assert response.json() == expected_response
1560 def test_get_timeseries_not_found(self, client, db):
1561 response = client.get("/timeseries/-1")
1562 expected_status_code = 404
1563 assert response.status_code == expected_status_code
1564 expected_response = {"detail": "Timeseries not found."}
1565 assert response.json() == expected_response
1568 def test_get_timeseries_invalid_type(self, client, db):
1569 response = client.get("/timeseries/A")
1570 expected_status_code = 422
1571 assert response.status_code == expected_status_code
1572 """expected_response = {"detail": "Timeseries not found."}
1573 assert response.json() == expected_response"""
1575 def test_get_timeseries2_not_found(self, client, db):
1576 response = client.get("/timeseries/id/-1")
1577 expected_status_code = 404
1578 assert response.status_code == expected_status_code
1579 expected_response = {"detail": "Timeseries not found."}
1580 assert response.json() == expected_response
1582 def test_get_timeseries2_invalid_type(self, client, db):
1583 response = client.get("/timeseries/id/A")
1584 expected_status_code = 422
1585 assert response.status_code == expected_status_code
1586 """expected_response = {"detail": "Timeseries not found."}
1587 assert response.json() == expected_response"""
1590# def test_get_unique_timeseries(self, client, db):
1591# response = client.get("/timeseries/unique/?station_id=2&variable_id=7")
1592# expected_status_code = 200
1593# assert response.status_code == expected_status_code
1595# assert response.json() == client.get("/timeseries/1").json()
1598# def test_get_unique_timeseries_not_found(self, client, db):
1599# response = client.get("/timeseries/unique/?station_id=-1&variable_id=-1")
1600# expected_status_code = 404
1601# assert response.status_code == expected_status_code
1602# expected_response = {"detail": "Timeseries not found."}
1603# assert response.json() == expected_response
1606 def test_get_unique_timeseries_invalid_type(self, client, db):
1607 response = client.get("/timeseries/unique/?station_id=A&variable_id=A")
1608 expected_status_code = 422
1609 assert response.status_code == expected_status_code
1610 """expected_response = {"detail": "Timeseries not found."}
1611 assert response.json() == expected_response"""
1614 def test_get_citation(self, client, db):
1615 fixed_time = datetime(2023, 7, 28, 12, 0, 0)
1617 with patch('toardb.timeseries.crud.dt.datetime') as mock_datetime:
1618 mock_datetime.now.return_value = fixed_time
1619 response = client.get("/timeseries/citation/2")
1620 expected_status_code = 200
1621 assert response.status_code == expected_status_code
1622 expected_response = {'attribution': 'Test-Attributions to be announced',
1623 'citation': 'Forschungszentrum Jülich: time series of o3 at Test_China, accessed from the TOAR database on 2023-07-28 12:00:00',
1624 'license': 'This data is published under a Creative Commons Attribution 4.0 International (CC BY 4.0). https://creativecommons.org/licenses/by/4.0/'
1625 }
1626 assert response.json() == expected_response
1629 # test the endpoint needed for TOARgridding
1630 def test_get_contributors_json(self, client, db):
1631 response = client.post("/timeseries/request_contributors?format=json", files={"file": open("tests/fixtures/timeseries/timeseries_id.txt", "rb")})
1632 expected_status_code = 200
1633 assert response.status_code == expected_status_code
1634 expected_response = [
1635 {'contact': {'id': 5,
1636 'organisation': {'city': 'Jülich',
1637 'contact_url': 'mailto:toar-data@fz-juelich.de',
1638 'country': 'Germany',
1639 'homepage': 'https://www.fz-juelich.de',
1640 'id': 2,
1641 'kind': 'research',
1642 'longname': 'Forschungszentrum Jülich',
1643 'name': 'FZJ',
1644 'postcode': '52425',
1645 'street_address': 'Wilhelm-Johnen-Straße'
1646 },
1647 'person': None
1648 },
1649 'id': 1,
1650 'role': 'resource provider',
1651 'status': 'active'
1652 },
1653 {'contact': {'id': 4,
1654 'organisation': {'city': 'Dessau-Roßlau',
1655 'contact_url': 'mailto:immission@uba.de',
1656 'country': 'Germany',
1657 'homepage': 'https://www.umweltbundesamt.de',
1658 'id': 1,
1659 'kind': 'government',
1660 'longname': 'Umweltbundesamt',
1661 'name': 'UBA',
1662 'postcode': '06844',
1663 'street_address': 'Wörlitzer Platz 1'
1664 },
1665 'person': None
1666 },
1667 'id': 2,
1668 'role': 'resource provider',
1669 'status': 'active'
1670 },
1671 {'id': 3,
1672 'role': 'principal investigator',
1673 'status': 'active',
1674 'contact': {'id': 3,
1675 'organisation': None,
1676 'person': {'email': 's.schroeder@fz-juelich.de',
1677 'id': 3,
1678 'isprivate': False,
1679 'name': 'Sabine Schröder',
1680 'orcid': '0000-0002-0309-8010',
1681 'phone': '+49-2461-61-6397'}}}
1682 ]
1683 assert response.json() == expected_response
1686 def test_get_contributors_text(self, client, db):
1687 response = client.post("/timeseries/request_contributors?format=text", files={"file": open("tests/fixtures/timeseries/timeseries_id.txt", "rb")})
1688 expected_status_code = 200
1689 assert response.status_code == expected_status_code
1690 expected_response = 'organisations: Forschungszentrum Jülich;Umweltbundesamt; persons:Sabine Schröder'
1691 assert response.json() == expected_response
1694 def test_get_contributors_default(self, client, db):
1695 response = client.post("/timeseries/request_contributors", files={"file": open("tests/fixtures/timeseries/timeseries_id.txt", "rb")})
1696 expected_status_code = 200
1697 assert response.status_code == expected_status_code
1698 expected_response = 'organisations: Forschungszentrum Jülich;Umweltbundesamt; persons:Sabine Schröder'
1699 assert response.json() == expected_response
1702 def test_get_contributors_unknown_format(self, client, db):
1703 response = client.post("/timeseries/request_contributors?format=CMOR", files={"file": open("tests/fixtures/timeseries/timeseries_id.txt", "rb")})
1704 expected_status_code = 400
1705 assert response.status_code == expected_status_code
1706 expected_response = 'not a valid format: CMOR'
1707 assert response.json() == expected_response
1710 def test_register_contributors_list(self, client, db):
1711 response = client.post("/timeseries/register_timeseries_list_of_contributors/5f0df73a-bd0f-48b9-bb17-d5cd36f89598",
1712 data='''[1,2]''',
1713 headers={"email": "s.schroeder@fz-juelich.de"} )
1714 expected_status_code = 200
1715 assert response.status_code == expected_status_code
1716 expected_response = '5f0df73a-bd0f-48b9-bb17-d5cd36f89598 successfully registered.'
1717 assert response.json() == expected_response
1720 def test_register_duplicate_contributors_list(self, client, db):
1721 response = client.post("/timeseries/register_timeseries_list_of_contributors/7f0df73a-bd0f-48b9-bb17-d5cd36f89598",
1722 data='''[1,2]''',
1723 headers={"email": "s.schroeder@fz-juelich.de"} )
1724 expected_status_code = 443
1725 assert response.status_code == expected_status_code
1726 expected_response = '7f0df73a-bd0f-48b9-bb17-d5cd36f89598 already registered.'
1727 assert response.json() == expected_response
1730 def test_request_registered_contributors_list_json(self, client, db):
1731 response = client.get("/timeseries/request_timeseries_list_of_contributors/7f0df73a-bd0f-48b9-bb17-d5cd36f89598?format=json")
1732 expected_status_code = 200
1733 assert response.status_code == expected_status_code
1734 expected_response = [
1735 {'contact': {'id': 5,
1736 'organisation': {'city': 'Jülich',
1737 'contact_url': 'mailto:toar-data@fz-juelich.de',
1738 'country': 'Germany',
1739 'homepage': 'https://www.fz-juelich.de',
1740 'id': 2,
1741 'kind': 'research',
1742 'longname': 'Forschungszentrum Jülich',
1743 'name': 'FZJ',
1744 'postcode': '52425',
1745 'street_address': 'Wilhelm-Johnen-Straße'
1746 },
1747 'person': None
1748 },
1749 'id': 1,
1750 'role': 'resource provider',
1751 'status': 'active'
1752 },
1753 {'contact': {'id': 4,
1754 'organisation': {'city': 'Dessau-Roßlau',
1755 'contact_url': 'mailto:immission@uba.de',
1756 'country': 'Germany',
1757 'homepage': 'https://www.umweltbundesamt.de',
1758 'id': 1,
1759 'kind': 'government',
1760 'longname': 'Umweltbundesamt',
1761 'name': 'UBA',
1762 'postcode': '06844',
1763 'street_address': 'Wörlitzer Platz 1'
1764 },
1765 'person': None
1766 },
1767 'id': 2,
1768 'role': 'resource provider',
1769 'status': 'active'
1770 },
1771 {'id': 3,
1772 'role': 'principal investigator',
1773 'status': 'active',
1774 'contact': {'id': 3,
1775 'organisation': None,
1776 'person': {'email': 's.schroeder@fz-juelich.de',
1777 'id': 3,
1778 'isprivate': False,
1779 'name': 'Sabine Schröder',
1780 'orcid': '0000-0002-0309-8010',
1781 'phone': '+49-2461-61-6397'}}}
1782 ]
1783 assert response.json() == expected_response
1786 def test_request_registered_contributors_list_text(self, client, db):
1787 response = client.get("/timeseries/request_timeseries_list_of_contributors/7f0df73a-bd0f-48b9-bb17-d5cd36f89598?format=text")
1788 expected_status_code = 200
1789 assert response.status_code == expected_status_code
1790 expected_response = 'organisations: Forschungszentrum Jülich;Umweltbundesamt; persons:Sabine Schröder'
1791 assert response.json() == expected_response
1794 def test_request_registered_contributors_list_unknown_rid(self, client, db):
1795 response = client.get("/timeseries/request_timeseries_list_of_contributors/7f0df73a-bd0f-58b9-bb17-d5cd36f89598?format=text")
1796 expected_status_code = 400
1797 assert response.status_code == expected_status_code
1798 expected_response = 'not a registered request id: 7f0df73a-bd0f-58b9-bb17-d5cd36f89598'
1799 assert response.json() == expected_response
1802 # 3. tests updating timeseries metadata
1804 def test_patch_timeseries_no_description(self, client, db):
1805 response = client.patch("/timeseries/id/1",
1806 json={"timeseries":
1807 {"sampling_frequency":"Daily"}
1808 },
1809 headers={"email": "s.schroeder@fz-juelich.de"}
1810 )
1811 expected_status_code = 404
1812 assert response.status_code == expected_status_code
1813 expected_resp = {"detail": "description text ist missing."}
1814 assert response.json() == expected_resp
1817 def test_patch_timeseries_not_found(self, client, db):
1818 response = client.patch("/timeseries/id/-1?description=changed sampling_frequency",
1819 json={"timeseries":
1820 {"sampling_frequency":"Daily"}
1821 },
1822 headers={"email": "s.schroeder@fz-juelich.de"}
1823 )
1824 expected_status_code = 404
1825 assert response.status_code == expected_status_code
1826 expected_resp = {"detail": "Time series for patching not found."}
1827 assert response.json() == expected_resp
1830 def test_patch_timeseries_single_item(self, client, db):
1831 response = client.patch("/timeseries/id/1?description=changed sampling_frequency",
1832 json={"timeseries":
1833 {"sampling_frequency": "Daily"}
1834 },
1835 headers={"email": "s.schroeder@fz-juelich.de"}
1836 )
1837 expected_status_code = 200
1838 assert response.status_code == expected_status_code
1839 expected_resp = {'detail': { 'message': 'timeseries patched.',
1840 'timeseries_id': 1 }
1841 }
1842 response_json = response.json()
1843 assert response_json == expected_resp
1844 response = client.get(f"/timeseries/id/{response_json['detail']['timeseries_id']}")
1845 response_json = response.json()
1846 # just check special changes
1847 assert response_json['sampling_frequency'] == "daily"
1848 assert response_json['changelog'][0]['old_value'] == "{'sampling_frequency': 'Hourly'}"
1849 assert response_json['changelog'][0]['new_value'] == "{'sampling_frequency': 'Daily'}"
1850 assert response_json['changelog'][0]['author_id'] == 1
1851 assert response_json['changelog'][0]['type_of_change'] == 'single value correction in metadata'
1854 def test_patch_timeseries_multiple_items(self, client, db):
1855 response = client.patch("/timeseries/id/1?description=changed some metadata",
1856 json={"timeseries":
1857 {"sampling_frequency": "Daily",
1858 "aggregation": "MeanOf4Samples",
1859 "data_origin": "COSMOREA6",
1860 "data_origin_type": "Model"}
1861 },
1862 headers={"email": "s.schroeder@fz-juelich.de"}
1863 )
1864 expected_status_code = 200
1865 assert response.status_code == expected_status_code
1866 expected_resp = {'detail': { 'message': 'timeseries patched.',
1867 'timeseries_id': 1 }
1868 }
1869 response_json = response.json()
1870 assert response_json == expected_resp
1871 response = client.get(f"/timeseries/id/{response_json['detail']['timeseries_id']}")
1872 response_json = response.json()
1873 # just check special changes
1874 assert response_json['sampling_frequency'] == "daily"
1875 assert response_json['changelog'][0]['old_value'] == "{'sampling_frequency': 'Hourly', 'aggregation': 'Mean', 'data_origin': 'Instrument', 'data_origin_type': 'Measurement'}"
1876 assert response_json['changelog'][0]['new_value'] == "{'sampling_frequency': 'Daily', 'aggregation': 'MeanOf4Samples', 'data_origin': 'COSMOREA6', 'data_origin_type': 'Model'}"
1877 assert response_json['changelog'][0]['author_id'] == 1
1878 assert response_json['changelog'][0]['type_of_change'] == 'comprehensive metadata revision'
1881 def test_patch_timeseries_roles(self, client, db):
1882 response = client.patch("/timeseries/id/1?description=changed roles",
1883 json={"timeseries":
1884 {"roles": [{"role": "ResourceProvider", "contact_id": 5, "status": "Active"}]
1885 }
1886 },
1887 headers={"email": "s.schroeder@fz-juelich.de"}
1888 )
1889 expected_status_code = 200
1890 assert response.status_code == expected_status_code
1891 expected_resp = {'detail': { 'message': 'timeseries patched.',
1892 'timeseries_id': 1 }
1893 }
1894 response_json = response.json()
1895 assert response_json == expected_resp
1896 response = client.get(f"/timeseries/id/{response_json['detail']['timeseries_id']}")
1897 response_json = response.json()
1898 # just check special changes
1899 response_roles = [{'contact': {'id': 5,
1900 'organisation': {'city': 'Jülich',
1901 'contact_url': 'mailto:toar-data@fz-juelich.de',
1902 'country': 'Germany',
1903 'homepage': 'https://www.fz-juelich.de',
1904 'id': 2,
1905 'kind': 'research',
1906 'longname': 'Forschungszentrum Jülich',
1907 'name': 'FZJ',
1908 'postcode': '52425',
1909 'street_address': 'Wilhelm-Johnen-Straße',
1910 },
1911 },
1912 'id': 1,
1913 'role': 'resource provider',
1914 'status': 'active'},
1915 {'contact': {'id': 4,
1916 'organisation': {'city': 'Dessau-Roßlau',
1917 'contact_url': 'mailto:immission@uba.de',
1918 'country': 'Germany',
1919 'homepage': 'https://www.umweltbundesamt.de',
1920 'id': 1,
1921 'kind': 'government',
1922 'longname': 'Umweltbundesamt',
1923 'name': 'UBA',
1924 'postcode': '06844',
1925 'street_address': 'Wörlitzer Platz 1',
1926 'contact_url': 'mailto:immission@uba.de'}},
1927 'id': 2,
1928 'role': 'resource provider',
1929 'status': 'active'},
1930 {'id': 3,
1931 'role': 'principal investigator',
1932 'status': 'active',
1933 'contact': {'id': 3,
1934 'person': {'email': 's.schroeder@fz-juelich.de',
1935 'id': 3,
1936 'isprivate': False,
1937 'name': 'Sabine Schröder',
1938 'orcid': '0000-0002-0309-8010',
1939 'phone': '+49-2461-61-6397'}}}]
1940 set_expected_response_roles = {json.dumps(item, sort_keys=True) for item in response_roles}
1941 set_response_roles = {json.dumps(item, sort_keys=True) for item in response_json['roles']}
1942 assert set_response_roles == set_expected_response_roles
1943 assert response_json['changelog'][0]['old_value'] == "{'roles': [{'role': 'ResourceProvider', 'status': 'Active', 'contact_id': 4}, {'role': 'PrincipalInvestigator', 'status': 'Active', 'contact_id': 3}]}"
1944 assert response_json['changelog'][0]['new_value'] == "{'roles': [{'role': 'ResourceProvider', 'status': 'Active', 'contact_id': 4}, {'role': 'PrincipalInvestigator', 'status': 'Active', 'contact_id': 3}, {'role': 'ResourceProvider', 'contact_id': 5, 'status': 'Active'}]}"
1945 assert response_json['changelog'][0]['author_id'] == 1
1946 assert response_json['changelog'][0]['type_of_change'] == 'single value correction in metadata'
1949 def test_patch_timeseries_annotations(self, client, db):
1950 response = client.patch("/timeseries/id/1?description=changed annotations",
1951 json={"timeseries":
1952 {"annotations": [{"kind": "User",
1953 "text": "some foo",
1954 "date_added": "2021-07-27 00:00",
1955 "approved": True,
1956 "contributor_id":1}]
1957 }
1958 },
1959 headers={"email": "s.schroeder@fz-juelich.de"}
1960 )
1961 expected_status_code = 200
1962 assert response.status_code == expected_status_code
1963 expected_resp = {'detail': { 'message': 'timeseries patched.',
1964 'timeseries_id': 1 }
1965 }
1966 response_json = response.json()
1967 assert response_json == expected_resp
1968 response = client.get(f"/timeseries/id/{response_json['detail']['timeseries_id']}")
1969 response_json = response.json()
1970 # just check special changes
1971 response_annotations = [{"kind": "user comment",
1972 "text": "some foo",
1973 "date_added": "2021-07-27 00:00",
1974 "approved": True,
1975 "contributor_id":1}
1976 ]
1977 assert response_json['changelog'][0]['old_value'] == "{'annotations': []}"
1978 assert response_json['changelog'][0]['new_value'] == "{'annotations': [{'kind': 'User', 'text': 'some foo', 'date_added': '2021-07-27 00:00', 'approved': True, 'contributor_id': 1}]}"
1979 assert response_json['changelog'][0]['author_id'] == 1
1980 assert response_json['changelog'][0]['type_of_change'] == 'single value correction in metadata'
1983 """def test_get_timeseries_changelog(self, client, db):
1984 response = client.get("/timeseries_changelog/{id}")
1985 expected_status_code = 200
1986 assert response.status_code == expected_status_code
1987 new_response = response.json()["datetime"] = ""
1988 expected_response = [{"datetime":"","description":"fake creation of timeseries","old_value":"","new_value":"","timeseries_id":8,"author_id":1,"type_of_change":0,"period_start":None,"period_end":None,"version":None}]
1989 assert response.json() == expected_response"""