Coverage for tests/test_stationmeta.py: 100%

323 statements  

« 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 

3 

4import pytest 

5import json 

6from fastapi import Request 

7from sqlalchemy import insert 

8from toardb.stationmeta.models import ( 

9 StationmetaCore, 

10 StationmetaGlobal, 

11 StationmetaGlobalService, 

12 StationmetaRole, 

13 StationmetaChangelog, 

14 stationmeta_core_stationmeta_roles_table 

15) 

16from toardb.toardb import app 

17from toardb.stationmeta import crud 

18from toardb.stationmeta.schemas import ( 

19 get_geom_from_coordinates, 

20 Coordinates, 

21 StationmetaCreate 

22) 

23from toardb.auth_user.models import AuthUser 

24from toardb.contacts.models import Person, Organisation, Contact 

25from toardb.test_base import ( 

26 client, 

27 get_test_db, 

28 override_dependency, 

29 create_test_database, 

30 url, 

31 get_test_engine, 

32 test_db_session as db, 

33) 

34from unittest.mock import patch 

35 

36 

37class TestApps: 

38 def setup(self): 

39 self.application_url = "/stationmeta/" 

40 

41 """Set up all the data before each test 

42 If you want the setup only once (per test module), 

43 the scope argument is not working in the expected way, as discussed here: 

44 https://stackoverflow.com/questions/45817153/py-test-fixture-use-function-fixture-in-scope-fixture 

45 """ 

46 @pytest.fixture(autouse=True) 

47 def setup_db_data(self, db): 

48 # id_seq will not be reset automatically between tests! 

49 _db_conn = get_test_engine() 

50 fake_conn = _db_conn.raw_connection() 

51 fake_cur = fake_conn.cursor() 

52 fake_cur.execute("ALTER SEQUENCE auth_user_id_seq RESTART WITH 1") 

53 fake_conn.commit() 

54 fake_cur.execute("ALTER SEQUENCE stationmeta_core_id_seq RESTART WITH 1") 

55 fake_conn.commit() 

56 fake_cur.execute("ALTER SEQUENCE stationmeta_global_id_seq RESTART WITH 1") 

57 fake_conn.commit() 

58 fake_cur.execute("ALTER SEQUENCE stationmeta_annotations_id_seq RESTART WITH 1") 

59 fake_conn.commit() 

60 fake_cur.execute("ALTER SEQUENCE stationmeta_roles_id_seq RESTART WITH 3") 

61 fake_conn.commit() 

62 fake_cur.execute("ALTER SEQUENCE stationmeta_aux_doc_id_seq RESTART WITH 1") 

63 fake_conn.commit() 

64 fake_cur.execute("ALTER SEQUENCE stationmeta_aux_image_id_seq RESTART WITH 1") 

65 fake_conn.commit() 

66 fake_cur.execute("ALTER SEQUENCE stationmeta_aux_url_id_seq RESTART WITH 1") 

67 fake_conn.commit() 

68 fake_cur.execute("ALTER SEQUENCE persons_id_seq RESTART WITH 1") 

69 fake_conn.commit() 

70 fake_cur.execute("ALTER SEQUENCE organisations_id_seq RESTART WITH 1") 

71 fake_conn.commit() 

72 fake_cur.execute("ALTER SEQUENCE contacts_id_seq RESTART WITH 1") 

73 fake_conn.commit() 

74 infilename = "tests/fixtures/auth_user/auth.json" 

75 with open(infilename) as f: 

76 metajson=json.load(f) 

77 for entry in metajson: 

78 new_auth_user= AuthUser(**entry) 

79 db.add(new_auth_user) 

80 db.commit() 

81 db.refresh(new_auth_user) 

82 infilename = "tests/fixtures/contacts/persons.json" 

83 with open(infilename) as f: 

84 metajson=json.load(f) 

85 for entry in metajson: 

86 new_person = Person(**entry) 

87 db.add(new_person) 

88 db.commit() 

89 db.refresh(new_person) 

90 infilename = "tests/fixtures/contacts/organisations.json" 

91 with open(infilename) as f: 

92 metajson=json.load(f) 

93 for entry in metajson: 

94 new_organisation = Organisation(**entry) 

95 db.add(new_organisation) 

96 db.commit() 

97 db.refresh(new_organisation) 

98 infilename = "tests/fixtures/contacts/contacts.json" 

99 with open(infilename) as f: 

100 metajson=json.load(f) 

101 for entry in metajson: 

102 new_contact = Contact(**entry) 

103 db.add(new_contact) 

104 db.commit() 

105 db.refresh(new_contact) 

106 # I also need to upload tests with nested data!!! 

107 infilename = "tests/fixtures/stationmeta/stationmeta_core.json" 

108 with open(infilename) as f: 

109 metajson=json.load(f) 

110 for entry in metajson: 

111 new_stationmeta_core = StationmetaCore(**entry) 

112 # there's a mismatch with coordinates --> how to automatically switch back and forth?! 

113 tmp_coordinates = new_stationmeta_core.coordinates 

114 new_stationmeta_core.coordinates = get_geom_from_coordinates(Coordinates(**new_stationmeta_core.coordinates)) 

115 # there's also a mismatch with additional_metadata --> BUT: this should not be switched back! 

116 # in upload command, we have now: "additional_metadata": "{}" 

117 # but return from this method gives: "additional_metadata": {} 

118 # ==> there is a mismatch between model(JSONB) and schema(JSON) 

119 new_stationmeta_core.additional_metadata = str(new_stationmeta_core.additional_metadata) 

120 db.add(new_stationmeta_core) 

121 db.commit() 

122 db.refresh(new_stationmeta_core) 

123 infilename = "tests/fixtures/stationmeta/stationmeta_changelog.json" 

124 with open(infilename) as f: 

125 metajson=json.load(f) 

126 for entry in metajson: 

127 new_stationmeta_changelog = StationmetaChangelog(**entry) 

128 db.add(new_stationmeta_changelog) 

129 db.commit() 

130 db.refresh(new_stationmeta_changelog) 

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/stationmeta/stationmeta_global_services.json" 

140 with open(infilename) as f: 

141 metajson=json.load(f) 

142 for entry in metajson: 

143 new_stationmeta_global_service = StationmetaGlobalService(**entry) 

144 db.add(new_stationmeta_global_service) 

145 db.commit() 

146 db.refresh(new_stationmeta_global_service) 

147 infilename = "tests/fixtures/stationmeta/stationmeta_roles.json" 

148 with open(infilename) as f: 

149 metajson=json.load(f) 

150 for entry in metajson: 

151 new_stationmeta_role = StationmetaRole(**entry) 

152 db.add(new_stationmeta_role) 

153 db.commit() 

154 db.refresh(new_stationmeta_role) 

155 infilename = "tests/fixtures/stationmeta/stationmeta_core_stationmeta_roles.json" 

156 with open(infilename) as f: 

157 metajson=json.load(f) 

158 for entry in metajson: 

159 db.execute(insert(stationmeta_core_stationmeta_roles_table).values(station_id=entry["station_id"], role_id=entry["role_id"])) 

160 db.execute("COMMIT") 

161 

162 

163 # 1. tests retrieving station metadata 

164 

165 def test_get_all(self, client, db): 

166 response = client.get("/stationmeta/") 

167 expected_status_code = 200 

168 assert response.status_code == expected_status_code 

169 expected_resp = [{'id': 1, 

170 'codes': ['China11'], 

171 'name': 'Mount Tai', 

172 'coordinates': {'lat': 36.256, 'lng': 117.106, 'alt': 1534.0}, 

173 'coordinate_validation_status': 'not checked', 

174 'country': 'Germany', 

175 'state': 'Shandong Sheng', 

176 'type': 'unknown', 

177 'type_of_area': 'unknown', 

178 'timezone': 'Asia/Shanghai', 

179 'additional_metadata': {'dummy_info': 'Here is some more information about the station'}, 

180 'roles': [{'id': 2, 

181 'role': 'resource provider', 

182 'status': 'active', 

183 'contact': {'id': 1, 

184 'name': 'UBA', 

185 'longname': 'Umweltbundesamt', 

186 'kind': 'government', 

187 'city': 'Dessau-Roßlau', 

188 'postcode': '06844', 

189 'street_address': 'Wörlitzer Platz 1', 

190 'country': 'Germany', 

191 'homepage': 'https://www.umweltbundesamt.de', 

192 'contact_url': 'mailto:immission@uba.de'}}], 

193 'aux_images': [], 

194 'aux_docs': [], 

195 'aux_urls': [], 

196 'globalmeta': {'mean_topography_srtm_alt_90m_year1994': -999.0, 

197 'mean_topography_srtm_alt_1km_year1994': -999.0, 

198 'max_topography_srtm_relative_alt_5km_year1994': -999.0, 

199 'min_topography_srtm_relative_alt_5km_year1994': -999.0, 

200 'stddev_topography_srtm_relative_alt_5km_year1994': -999.0, 

201 'climatic_zone_year2016': '6 (warm temperate dry)', 

202 'htap_region_tier1_year2010': '11 (MDE Middle East: S. ' 

203 'Arabia, Oman, etc, Iran, Iraq)', 

204 'dominant_landcover_year2012': '10 (Cropland, rainfed)', 

205 'landcover_description_25km_year2012': '11 (Cropland, ' 

206 'rainfed, herbaceous ' 

207 'cover): 30.7 %, 12 ' 

208 '(Cropland, rainfed, ' 

209 'tree or shrub cover): ' 

210 '25.0 %, 210 (Water ' 

211 'bodies): 16.9 %, 130 ' 

212 '(Grassland): 8.6 %, ' 

213 '190 (Urban areas): ' 

214 '5.8 %, 100 (Mosaic ' 

215 'tree and shrub (>50%) ' 

216 '/ herbaceous cover ' 

217 '(<50%)): 3.5 %, 40 ' 

218 '(Mosaic natural ' 

219 'vegetation (tree, ' 

220 'shrub, herbaceous ' 

221 'cover) (>50%) / ' 

222 'cropland (<50%)): 2.6 ' 

223 '%, 10 (Cropland, ' 

224 'rainfed): 2.0 %, 30 ' 

225 '(Mosaic cropland ' 

226 '(>50%) / natural ' 

227 'vegetation (tree, ' 

228 'shrub, herbaceous ' 

229 'cover) (<50%)): 1.9 %', 

230 'dominant_ecoregion_year2017': '-1 (undefined)', 

231 'ecoregion_description_25km_year2017': '', 

232 'distance_to_major_road_year2020': -999.0, 

233 'mean_stable_nightlights_1km_year2013': -999.0, 

234 'mean_stable_nightlights_5km_year2013': -999.0, 

235 'max_stable_nightlights_25km_year2013': -999.0, 

236 'max_stable_nightlights_25km_year1992': -999.0, 

237 'mean_population_density_250m_year2015': -1.0, 

238 'mean_population_density_5km_year2015': -1.0, 

239 'max_population_density_25km_year2015': -1.0, 

240 'mean_population_density_250m_year1990': -1.0, 

241 'mean_population_density_5km_year1990': -1.0, 

242 'max_population_density_25km_year1990': -1.0, 

243 'mean_nox_emissions_10km_year2015': -999.0, 

244 'mean_nox_emissions_10km_year2000': -999.0, 

245 'toar1_category': 'unclassified', 

246 'toar2_category': 'urban'}, 

247 'changelog': [{'datetime': '2023-07-05T08:23:04.551645+00:00', 

248 'description': 'station created', 

249 'old_value': '', 

250 'new_value': '', 

251 'station_id': 1, 

252 'author_id': 1, 

253 'type_of_change': 'created'}]}, 

254 {'id': 2, 

255 'codes': ['SDZ54421'], 

256 'name': 'Shangdianzi', 

257 'coordinates': {'lat': 40.65, 'lng': 117.106, 'alt': 293.9}, 

258 'coordinate_validation_status': 'not checked', 

259 'country': 'China', 

260 'state': 'Beijing Shi', 

261 'type': 'unknown', 

262 'type_of_area': 'unknown', 

263 'timezone': 'Asia/Shanghai', 

264 'additional_metadata': {'add_type': 'nature reservation'}, 

265 'roles': [{'id': 1, 

266 'role': 'resource provider', 

267 'status': 'active', 

268 'contact': {'id': 2, 

269 'name': 'FZJ', 

270 'longname': 'Forschungszentrum Jülich', 

271 'kind': 'research', 

272 'city': 'Jülich', 

273 'postcode': '52425', 

274 'street_address': 'Wilhelm-Johnen-Straße', 

275 'country': 'Germany', 

276 'homepage': 'https://www.fz-juelich.de', 

277 'contact_url': 'mailto:toar-data@fz-juelich.de'}}], 

278 'aux_images': [], 

279 'aux_docs': [], 

280 'aux_urls': [], 

281 'globalmeta': {'mean_topography_srtm_alt_90m_year1994': -999.0, 

282 'mean_topography_srtm_alt_1km_year1994': -999.0, 

283 'max_topography_srtm_relative_alt_5km_year1994': -999.0, 

284 'min_topography_srtm_relative_alt_5km_year1994': -999.0, 

285 'stddev_topography_srtm_relative_alt_5km_year1994': -999.0, 

286 'climatic_zone_year2016': '6 (warm temperate dry)', 

287 'htap_region_tier1_year2010': '11 (MDE Middle East: S. ' 

288 'Arabia, Oman, etc, Iran, Iraq)', 

289 'dominant_landcover_year2012': '11 (Cropland, rainfed, herbaceous cover)', 

290 'landcover_description_25km_year2012': '', 

291 'dominant_ecoregion_year2017': '-1 (undefined)', 

292 'ecoregion_description_25km_year2017': '', 

293 'distance_to_major_road_year2020': -999.0, 

294 'mean_stable_nightlights_1km_year2013': -999.0, 

295 'mean_stable_nightlights_5km_year2013': -999.0, 

296 'max_stable_nightlights_25km_year2013': -999.0, 

297 'max_stable_nightlights_25km_year1992': -999.0, 

298 'mean_population_density_250m_year2015': -1.0, 

299 'mean_population_density_5km_year2015': -1.0, 

300 'max_population_density_25km_year2015': -1.0, 

301 'mean_population_density_250m_year1990': -1.0, 

302 'mean_population_density_5km_year1990': -1.0, 

303 'max_population_density_25km_year1990': -1.0, 

304 'mean_nox_emissions_10km_year2015': -999.0, 

305 'mean_nox_emissions_10km_year2000': -999.0, 

306 'toar1_category': 'unclassified', 

307 'toar2_category': 'suburban'}, 

308 'changelog': [{'datetime': '2023-07-15T19:27:09.463245+00:00', 

309 'description': 'station created', 

310 'old_value': '', 

311 'new_value': '', 

312 'station_id': 2, 

313 'author_id': 1, 

314 'type_of_change': 'created'}]}, 

315 {'id': 3, 

316 'codes': ['China_test8'], 

317 'name': 'Test_China', 

318 'coordinates': {'lat': 36.256, 'lng': 117.106, 'alt': 1534.0}, 

319 'coordinate_validation_status': 'not checked', 

320 'country': 'China', 

321 'state': 'Shandong Sheng', 

322 'type': 'unknown', 

323 'type_of_area': 'unknown', 

324 'timezone': 'Asia/Shanghai', 

325 'additional_metadata': {}, 

326 'aux_images': [], 

327 'aux_docs': [], 

328 'aux_urls': [], 

329 'globalmeta': {'mean_topography_srtm_alt_90m_year1994': -999.0, 

330 'mean_topography_srtm_alt_1km_year1994': -999.0, 

331 'max_topography_srtm_relative_alt_5km_year1994': -999.0, 

332 'min_topography_srtm_relative_alt_5km_year1994': -999.0, 

333 'stddev_topography_srtm_relative_alt_5km_year1994': -999.0, 

334 'climatic_zone_year2016': '6 (warm temperate dry)', 

335 'htap_region_tier1_year2010': '10 (SAF Sub Saharan/sub Sahel ' 

336 'Africa)', 

337 'dominant_landcover_year2012': '10 (Cropland, 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': [{'datetime': '2023-08-15T21:16:20.596545+00:00', 

357 'description': 'station created', 

358 'old_value': '', 

359 'new_value': '', 

360 'station_id': 3, 

361 'author_id': 1, 

362 'type_of_change': 'created'}]}] 

363 assert response.json() == expected_resp 

364 

365 

366 def test_get_special_nested(self, client, db): 

367 response = client.get("/stationmeta/China_test8") 

368 expected_status_code = 200 

369 assert response.status_code == expected_status_code 

370 expected_resp = {'id': 3, 'codes': ['China_test8'], 'name': 'Test_China', 

371 'coordinates': {'lat': 36.256, 'lng': 117.106, 'alt': 1534.0}, 

372 'coordinate_validation_status': 'not checked', 

373 'country': 'China', 'state': 'Shandong Sheng', 

374 'type': 'unknown', 

375 'type_of_area': 'unknown', 'timezone': 'Asia/Shanghai', 

376 'additional_metadata': {}, 

377 'aux_images': [], 'aux_docs': [], 

378 'aux_urls': [], 

379 'globalmeta': {'climatic_zone_year2016': '6 (warm temperate dry)', 

380 'distance_to_major_road_year2020': -999.0, 

381 'dominant_ecoregion_year2017': '-1 (undefined)', 

382 'dominant_landcover_year2012': '10 (Cropland, rainfed)', 

383 'ecoregion_description_25km_year2017': '', 

384 'landcover_description_25km_year2012': '', 

385 'htap_region_tier1_year2010': '10 (SAF Sub Saharan/sub Sahel Africa)', 

386 'max_stable_nightlights_25km_year1992': -999.0, 

387 'max_stable_nightlights_25km_year2013': -999.0, 

388 'max_population_density_25km_year1990': -1.0, 

389 'max_population_density_25km_year2015': -1.0, 

390 'max_topography_srtm_relative_alt_5km_year1994': -999.0, 

391 'mean_stable_nightlights_1km_year2013': -999.0, 

392 'mean_stable_nightlights_5km_year2013': -999.0, 

393 'mean_nox_emissions_10km_year2000': -999.0, 

394 'mean_nox_emissions_10km_year2015': -999.0, 

395 'mean_population_density_250m_year1990': -1.0, 

396 'mean_population_density_250m_year2015': -1.0, 

397 'mean_population_density_5km_year1990': -1.0, 

398 'mean_population_density_5km_year2015': -1.0, 

399 'mean_topography_srtm_alt_1km_year1994': -999.0, 

400 'mean_topography_srtm_alt_90m_year1994': -999.0, 

401 'min_topography_srtm_relative_alt_5km_year1994': -999.0, 

402 'stddev_topography_srtm_relative_alt_5km_year1994': -999.0, 

403 'toar1_category': 'unclassified', 

404 'toar2_category': 'suburban'}, 

405 'changelog': [{'datetime': '2023-08-15T21:16:20.596545+00:00', 

406 'description': 'station created', 

407 'old_value': '', 

408 'new_value': '', 

409 'station_id': 3, 

410 'author_id': 1, 

411 'type_of_change': 'created' 

412 }]} 

413 assert response.json() == expected_resp 

414 

415 

416 def test_get_special_with_fields(self, client, db): 

417 response = client.get("/stationmeta/China11?fields=codes,globalmeta") 

418 expected_status_code = 200 

419 assert response.status_code == expected_status_code 

420 expected_resp = {'codes': ['China11'], 

421 'globalmeta': {'mean_topography_srtm_alt_90m_year1994': -999.0, 

422 'mean_topography_srtm_alt_1km_year1994': -999.0, 

423 'max_topography_srtm_relative_alt_5km_year1994': -999.0, 

424 'min_topography_srtm_relative_alt_5km_year1994': -999.0, 

425 'stddev_topography_srtm_relative_alt_5km_year1994': -999.0, 

426 'climatic_zone_year2016': '6 (warm temperate dry)', 

427 'htap_region_tier1_year2010': '11 (MDE Middle East: S. Arabia, Oman, etc, Iran, Iraq)', 

428 'dominant_landcover_year2012': '10 (Cropland, rainfed)', 

429 'landcover_description_25km_year2012': '11 (Cropland, rainfed, herbaceous cover): 30.7 %, 12 (Cropland, rainfed, tree or shrub cover): 25.0 %, 210 (Water bodies): 16.9 %, 130 (Grassland): 8.6 %, 190 (Urban areas): 5.8 %, 100 (Mosaic tree and shrub (>50%) / herbaceous cover (<50%)): 3.5 %, 40 (Mosaic natural vegetation (tree, shrub, herbaceous cover) (>50%) / cropland (<50%)): 2.6 %, 10 (Cropland, rainfed): 2.0 %, 30 (Mosaic cropland (>50%) / natural vegetation (tree, shrub, herbaceous cover) (<50%)): 1.9 %', 

430 'dominant_ecoregion_year2017': '-1 (undefined)', 

431 'ecoregion_description_25km_year2017': '', 

432 'distance_to_major_road_year2020': -999.0, 

433 'mean_stable_nightlights_1km_year2013': -999.0, 

434 'mean_stable_nightlights_5km_year2013': -999.0, 

435 'max_stable_nightlights_25km_year2013': -999.0, 

436 'max_stable_nightlights_25km_year1992': -999.0, 

437 'mean_population_density_250m_year2015': -1.0, 

438 'mean_population_density_5km_year2015': -1.0, 

439 'max_population_density_25km_year2015': -1.0, 

440 'mean_population_density_250m_year1990': -1.0, 

441 'mean_population_density_5km_year1990': -1.0, 

442 'max_population_density_25km_year1990': -1.0, 

443 'mean_nox_emissions_10km_year2015': -999.0, 

444 'mean_nox_emissions_10km_year2000': -999.0, 

445 'toar1_category': 'unclassified', 

446 'toar2_category': 'urban' 

447 } 

448 } 

449 assert response.json() == expected_resp 

450 

451 

452 def test_get_special_with_ambiguous_field(self, client, db): 

453 response = client.get("/stationmeta/China11?fields=id") 

454 expected_status_code = 200 

455 assert response.status_code == expected_status_code 

456 expected_resp = {'id': 1} 

457 assert response.json() == expected_resp 

458 

459 

460 def test_get_special_with_fields_station_not_found(self, client, db): 

461 response = client.get("/stationmeta/DENW078?fields=codes,globalmeta") 

462 expected_status_code = 404 

463 assert response.status_code == expected_status_code 

464 expected_resp = {"detail":"Metadata for station 'DENW078' not found."} 

465 assert response.json() == expected_resp 

466 

467 def test_get_all_with_fields(self, client, db): 

468 response = client.get("/stationmeta/?fields=codes,globalmeta") 

469 expected_status_code = 200 

470 assert response.status_code == expected_status_code 

471 expected_resp = [{'codes': ['China11'], 

472 'globalmeta': {'mean_topography_srtm_alt_90m_year1994': -999.0, 

473 'mean_topography_srtm_alt_1km_year1994': -999.0, 

474 'max_topography_srtm_relative_alt_5km_year1994': -999.0, 

475 'min_topography_srtm_relative_alt_5km_year1994': -999.0, 

476 'stddev_topography_srtm_relative_alt_5km_year1994': -999.0, 

477 'climatic_zone_year2016': '6 (warm temperate dry)', 

478 'htap_region_tier1_year2010': '11 (MDE Middle East: S. Arabia, Oman, etc, Iran, Iraq)', 

479 'dominant_landcover_year2012': '10 (Cropland, rainfed)', 

480 'landcover_description_25km_year2012': '11 (Cropland, rainfed, herbaceous cover): 30.7 %, 12 (Cropland, rainfed, tree or shrub cover): 25.0 %, 210 (Water bodies): 16.9 %, 130 (Grassland): 8.6 %, 190 (Urban areas): 5.8 %, 100 (Mosaic tree and shrub (>50%) / herbaceous cover (<50%)): 3.5 %, 40 (Mosaic natural vegetation (tree, shrub, herbaceous cover) (>50%) / cropland (<50%)): 2.6 %, 10 (Cropland, rainfed): 2.0 %, 30 (Mosaic cropland (>50%) / natural vegetation (tree, shrub, herbaceous cover) (<50%)): 1.9 %', 

481 'dominant_ecoregion_year2017': '-1 (undefined)', 

482 'ecoregion_description_25km_year2017': '', 

483 'distance_to_major_road_year2020': -999.0, 

484 'mean_stable_nightlights_1km_year2013': -999.0, 

485 'mean_stable_nightlights_5km_year2013': -999.0, 

486 'max_stable_nightlights_25km_year2013': -999.0, 

487 'max_stable_nightlights_25km_year1992': -999.0, 

488 'mean_population_density_250m_year2015': -1.0, 

489 'mean_population_density_5km_year2015': -1.0, 

490 'max_population_density_25km_year2015': -1.0, 

491 'mean_population_density_250m_year1990': -1.0, 

492 'mean_population_density_5km_year1990': -1.0, 

493 'max_population_density_25km_year1990': -1.0, 

494 'mean_nox_emissions_10km_year2015': -999.0, 

495 'mean_nox_emissions_10km_year2000': -999.0, 

496 'toar1_category': 'unclassified', 

497 'toar2_category': 'urban' 

498 } 

499 }, 

500 {'codes': ['SDZ54421'], 

501 'globalmeta': {'mean_topography_srtm_alt_90m_year1994': -999.0, 

502 'mean_topography_srtm_alt_1km_year1994': -999.0, 

503 'max_topography_srtm_relative_alt_5km_year1994': -999.0, 

504 'min_topography_srtm_relative_alt_5km_year1994': -999.0, 

505 'stddev_topography_srtm_relative_alt_5km_year1994': -999.0, 

506 'climatic_zone_year2016': '6 (warm temperate dry)', 

507 'htap_region_tier1_year2010': '11 (MDE Middle East: S. Arabia, Oman, etc, Iran, Iraq)', 

508 'dominant_landcover_year2012': '11 (Cropland, rainfed, herbaceous cover)', 

509 'landcover_description_25km_year2012': '', 

510 'dominant_ecoregion_year2017': '-1 (undefined)', 

511 'ecoregion_description_25km_year2017': '', 

512 'distance_to_major_road_year2020': -999.0, 

513 'mean_stable_nightlights_1km_year2013': -999.0, 

514 'mean_stable_nightlights_5km_year2013': -999.0, 

515 'max_stable_nightlights_25km_year2013': -999.0, 

516 'max_stable_nightlights_25km_year1992': -999.0, 

517 'mean_population_density_250m_year2015': -1.0, 

518 'mean_population_density_5km_year2015': -1.0, 

519 'max_population_density_25km_year2015': -1.0, 

520 'mean_population_density_250m_year1990': -1.0, 

521 'mean_population_density_5km_year1990': -1.0, 

522 'max_population_density_25km_year1990': -1.0, 

523 'mean_nox_emissions_10km_year2015': -999.0, 

524 'mean_nox_emissions_10km_year2000': -999.0, 

525 'toar1_category': 'unclassified', 

526 'toar2_category': 'suburban' 

527 } 

528 }, 

529 {'codes': ['China_test8'], 

530 'globalmeta': {'mean_topography_srtm_alt_90m_year1994': -999.0, 

531 'mean_topography_srtm_alt_1km_year1994': -999.0, 

532 'max_topography_srtm_relative_alt_5km_year1994': -999.0, 

533 'min_topography_srtm_relative_alt_5km_year1994': -999.0, 

534 'stddev_topography_srtm_relative_alt_5km_year1994': -999.0, 

535 'climatic_zone_year2016': '6 (warm temperate dry)', 

536 'htap_region_tier1_year2010': '10 (SAF Sub Saharan/sub Sahel Africa)', 

537 'dominant_landcover_year2012': '10 (Cropland, rainfed)', 

538 'landcover_description_25km_year2012': '', 

539 'dominant_ecoregion_year2017': '-1 (undefined)', 

540 'ecoregion_description_25km_year2017': '', 

541 'distance_to_major_road_year2020': -999.0, 

542 'mean_stable_nightlights_1km_year2013': -999.0, 

543 'mean_stable_nightlights_5km_year2013': -999.0, 

544 'max_stable_nightlights_25km_year2013': -999.0, 

545 'max_stable_nightlights_25km_year1992': -999.0, 

546 'mean_population_density_250m_year2015': -1.0, 

547 'mean_population_density_5km_year2015': -1.0, 

548 'max_population_density_25km_year2015': -1.0, 

549 'mean_population_density_250m_year1990': -1.0, 

550 'mean_population_density_5km_year1990': -1.0, 

551 'max_population_density_25km_year1990': -1.0, 

552 'mean_nox_emissions_10km_year2015': -999.0, 

553 'mean_nox_emissions_10km_year2000': -999.0, 

554 'toar1_category': 'unclassified', 

555 'toar2_category': 'suburban' 

556 } 

557 }] 

558 assert response.json() == expected_resp 

559 

560 

561 def test_get_all_wrong_params(self, client, db): 

562 response = client.get("/stationmeta/?altitude=2000&limit=None") 

563 expected_status_code = 400 

564 assert response.status_code == expected_status_code 

565 expected_resp = "'An unknown argument was received: altitude.'" 

566 assert response.json() == expected_resp 

567 

568 

569 def test_get_stationmeta_not_found(self, client, db): 

570 response = client.get("/stationmeta/-1") 

571 expected_status_code = 404 

572 assert response.status_code == expected_status_code 

573 expected_resp = {"detail": "Metadata for station '-1' not found."} 

574 assert response.json() == expected_resp 

575 

576 

577 def test_get_stationmeta_by_id_not_found(self, client, db): 

578 response = client.get("/stationmeta/id/-1") 

579 expected_status_code = 404 

580 assert response.status_code == expected_status_code 

581 expected_resp = {"detail": "Metadata for station with ID '-1' not found."} 

582 assert response.json() == expected_resp 

583 

584 

585 def test_get_stationmeta_by_id(self, client, db): 

586 response = client.get("/stationmeta/id/1") 

587 expected_status_code = 200 

588 assert response.status_code == expected_status_code 

589 expected_resp = {'id': 1, 'codes': ['China11'], 'name': 'Mount Tai', 

590 'coordinates': {'lat': 36.256, 'lng': 117.106, 'alt': 1534.0}, 

591 'coordinate_validation_status': 'not checked', 

592 'country': 'Germany', 'state': 'Shandong Sheng', 

593 'type': 'unknown', 'type_of_area': 'unknown', 

594 'timezone': 'Asia/Shanghai', 'additional_metadata': {}, 

595 'roles': [{ 'contact': { 

596 'city': 'Dessau-Roßlau', 

597 'contact_url': 'mailto:immission@uba.de', 

598 'country': 'Germany', 

599 'homepage': 'https://www.umweltbundesamt.de', 

600 'id': 1, 

601 'kind': 'government', 

602 'longname': 'Umweltbundesamt', 

603 'name': 'UBA', 

604 'postcode': '06844', 

605 'street_address': 'Wörlitzer Platz 1', 

606 }, 

607 'id': 2, 

608 'role': 'resource provider', 

609 'status': 'active' }], 

610 'additional_metadata': {'dummy_info': 'Here is some more information about the station'}, 

611 'aux_images': [], 'aux_docs': [], 'aux_urls': [], 

612 'globalmeta': {'climatic_zone_year2016': '6 (warm temperate dry)', 

613 'distance_to_major_road_year2020': -999.0, 

614 'dominant_ecoregion_year2017': '-1 (undefined)', 

615 'dominant_landcover_year2012': '10 (Cropland, rainfed)', 

616 'ecoregion_description_25km_year2017': '', 

617 'landcover_description_25km_year2012': '11 (Cropland, rainfed, ' 

618 +'herbaceous cover): ' 

619 +'30.7 %, 12 (Cropland, ' 

620 +'rainfed, tree or shrub ' 

621 +'cover): 25.0 %, 210 ' 

622 +'(Water bodies): 16.9 ' 

623 +'%, 130 (Grassland): ' 

624 +'8.6 %, 190 (Urban ' 

625 +'areas): 5.8 %, 100 ' 

626 +'(Mosaic tree and shrub ' 

627 +'(>50%) / herbaceous ' 

628 +'cover (<50%)): 3.5 %, ' 

629 +'40 (Mosaic natural ' 

630 +'vegetation (tree, ' 

631 +'shrub, herbaceous ' 

632 +'cover) (>50%) / ' 

633 +'cropland (<50%)): 2.6 ' 

634 +'%, 10 (Cropland, ' 

635 +'rainfed): 2.0 %, 30 ' 

636 +'(Mosaic cropland ' 

637 +'(>50%) / natural ' 

638 +'vegetation (tree, ' 

639 +'shrub, herbaceous ' 

640 +'cover) (<50%)): 1.9 %', 

641 'htap_region_tier1_year2010': '11 (MDE Middle East: S. Arabia, Oman, etc, Iran, Iraq)', 

642 'max_stable_nightlights_25km_year1992': -999.0, 

643 'max_stable_nightlights_25km_year2013': -999.0, 

644 'max_population_density_25km_year1990': -1.0, 

645 'max_population_density_25km_year2015': -1.0, 

646 'max_topography_srtm_relative_alt_5km_year1994': -999.0, 

647 'mean_stable_nightlights_1km_year2013': -999.0, 

648 'mean_stable_nightlights_5km_year2013': -999.0, 

649 'mean_nox_emissions_10km_year2000': -999.0, 

650 'mean_nox_emissions_10km_year2015': -999.0, 

651 'mean_population_density_250m_year1990': -1.0, 

652 'mean_population_density_250m_year2015': -1.0, 

653 'mean_population_density_5km_year1990': -1.0, 

654 'mean_population_density_5km_year2015': -1.0, 

655 'mean_topography_srtm_alt_1km_year1994': -999.0, 

656 'mean_topography_srtm_alt_90m_year1994': -999.0, 

657 'min_topography_srtm_relative_alt_5km_year1994': -999.0, 

658 'stddev_topography_srtm_relative_alt_5km_year1994': -999.0, 

659 'toar1_category': 'unclassified', 

660 'toar2_category': 'urban'}, 

661 'changelog': [{'datetime': '2023-07-05T08:23:04.551645+00:00', 

662 'description': 'station created', 

663 'old_value': '', 

664 'new_value': '', 

665 'station_id': 1, 

666 'author_id': 1, 

667 'type_of_change': 'created' 

668 }]} 

669 assert response.json() == expected_resp 

670 

671 

672 def test_get_stationmeta_changelog(self, client, db): 

673 response = client.get("/stationmeta_changelog/1") 

674 expected_status_code = 200 

675 assert response.status_code == expected_status_code 

676 expected_resp = [{ 'datetime': '2023-07-05T08:23:04.551645+00:00', 

677 'description': 'station created', 

678 'old_value': '', 

679 'new_value': '', 

680 'station_id': 1, 

681 'author_id': 1, 

682 'type_of_change': 'created' 

683 }] 

684 assert response.json() == expected_resp 

685 

686 # 2. tests creating station metadata 

687 

688 

689 def test_insert_new_wrong_credentials(self, client, db): 

690 response = client.post("/stationmeta/", 

691 json={"stationmeta": 

692 {"codes":["ttt3","ttt4"], 

693 "name":"Test_China","coordinates":{"lat":37.256,"lng":117.106,"alt":1534.0}, 

694 "coordinate_validation_status": "NotChecked", 

695 "country":"CN","state":"Shandong Sheng", 

696 "type":"Unknown","type_of_area":"Unknown","timezone":"Asia/Shanghai", 

697 "additional_metadata": "{}" } 

698 }, 

699 headers={"email": "j.doe@fz-juelich.de"} 

700 ) 

701 expected_status_code=401 

702 assert response.status_code == expected_status_code 

703 expected_resp = {'detail': 'Unauthorized.'} 

704 assert response.json() == expected_resp 

705 

706 

707 def test_insert_new_without_credentials(self, client, db): 

708 response = client.post("/stationmeta/", 

709 json={"stationmeta": 

710 {"codes":["ttt3","ttt4"], 

711 "name":"Test_China","coordinates":{"lat":37.256,"lng":117.106,"alt":1534.0}, 

712 "coordinate_validation_status": "NotChecked", 

713 "country":"CN","state":"Shandong Sheng", 

714 "type":"Unknown","type_of_area":"Unknown","timezone":"Asia/Shanghai", 

715 "additional_metadata": "{}" } 

716 }) 

717 expected_status_code=401 

718 assert response.status_code == expected_status_code 

719 expected_resp = {'detail': 'Unauthorized.'} 

720 assert response.json() == expected_resp 

721 

722 

723 def test_insert_new(self, client, db): 

724 with patch("toardb.stationmeta.crud.determine_stationmeta_global", return_value={}): 

725 response = client.post("/stationmeta/", 

726 json={"stationmeta": 

727 {"codes":["ttt3","ttt4"], 

728 "name":"Test_China","coordinates":{"lat":37.256,"lng":117.106,"alt":1534.0}, 

729 "coordinate_validation_status": "NotChecked", 

730 "country":"CN","state":"Shandong Sheng", 

731 "type":"Unknown","type_of_area":"Unknown","timezone":"Asia/Shanghai", 

732 "additional_metadata": "{}" } 

733 }, 

734 headers={"email": "s.schroeder@fz-juelich.de"} 

735 ) 

736 expected_status_code = 200 

737 assert response.status_code == expected_status_code 

738 expected_resp = {'detail': {'message': "new station: ['ttt3', 'ttt4'],Test_China,{'lat': 37.256, 'lng': 117.106, 'alt': 1534.0}", 

739 'station_id': 4}} 

740 assert response.json() == expected_resp 

741 

742 

743 def test_insert_new_with_roles(self, client, db): 

744 with patch("toardb.stationmeta.crud.determine_stationmeta_global", return_value={}): 

745 response = client.post("/stationmeta/", 

746 json={"stationmeta": 

747 {"codes":["ttt3","ttt4"], 

748 "name":"Test_China","coordinates":{"lat":37.256,"lng":117.106,"alt":1534.0}, 

749 "coordinate_validation_status": "NotChecked", 

750 "country":"CN","state":"Shandong Sheng", 

751 "type":"Unknown","type_of_area":"Unknown","timezone":"Asia/Shanghai", 

752 "roles": [{"role": "PointOfContact", "contact_id": 3, "status": "Active"}, 

753 {"role": "Originator", "contact_id": 1, "status": "Active"}], 

754 "additional_metadata": "{}" } 

755 }, 

756 headers={"email": "s.schroeder@fz-juelich.de"} 

757 ) 

758 expected_status_code = 200 

759 assert response.status_code == expected_status_code 

760 expected_resp = {'detail': {'message': "new station: ['ttt3', 'ttt4'],Test_China,{'lat': 37.256, 'lng': 117.106, 'alt': 1534.0}", 

761 'station_id': 4}} 

762 assert response.json() == expected_resp 

763 

764 

765 def test_insert_new_with_annotations(self, client, db): 

766 with patch("toardb.stationmeta.crud.determine_stationmeta_global", return_value={}): 

767 response = client.post("/stationmeta/", 

768 json={"stationmeta": 

769 {"codes":["ttt3","ttt4"], 

770 "name":"Test_China","coordinates":{"lat":37.256,"lng":117.106,"alt":1534.0}, 

771 "coordinate_validation_status": "NotChecked", 

772 "country":"CN","state":"Shandong Sheng", 

773 "type":"Unknown","type_of_area":"Unknown","timezone":"Asia/Shanghai", 

774 "annotations": [{"kind": "User", 

775 "text": "some foo", 

776 "date_added":"2021-07-27 00:00", 

777 "approved": True, 

778 "contributor_id":1}], 

779 "additional_metadata": "{}" } 

780 }, 

781 headers={"email": "s.schroeder@fz-juelich.de"} 

782 ) 

783 expected_status_code = 200 

784 assert response.status_code == expected_status_code 

785 expected_resp = {'detail': {'message': "new station: ['ttt3', 'ttt4'],Test_China,{'lat': 37.256, 'lng': 117.106, 'alt': 1534.0}", 

786 'station_id': 4}} 

787 assert response.json() == expected_resp 

788 

789 

790 def test_insert_new_same_coordinates(self, client, db): 

791 response = client.post("/stationmeta/", 

792 json={"stationmeta": 

793 {"codes":["ttt3","ttt4"], 

794 "name":"Test_China","coordinates":{"lat":36.256,"lng":117.106,"alt":1534.0}, 

795 "coordinate_validation_status": "NotChecked", 

796 "country":"CN","state":"Shandong Sheng", 

797 "type":"Unknown","type_of_area":"Unknown","timezone":"Asia/Shanghai", 

798 "additional_metadata":"{}"}, 

799 }, 

800 headers={"email": "s.schroeder@fz-juelich.de"} 

801 ) 

802 expected_status_code = 444 

803 assert response.status_code == expected_status_code 

804 expected_resp = {'detail': {'message': 'more than one station falls within the given radius!\nchoose which station record to patch (add station code)', 

805 'station_ids': [1, 3], 

806 'station_records': [[['China11'], 'lat=36.256 lng=117.106 alt=1534.0', 'Shandong Sheng', 84], 

807 [['China_test8'], 'lat=36.256 lng=117.106 alt=1534.0', 'Shandong Sheng', 48]]}} 

808 assert response.json() == expected_resp 

809 

810 

811 def test_insert_duplicate(self, client, db): 

812 response = client.post("/stationmeta/", 

813 json={"stationmeta": 

814 {"codes":["China11"], 

815 "name":"Test_China","coordinates":{"lat":36.256,"lng":117.106,"alt":1534.0}, 

816 "coordinate_validation_status": "NotChecked", 

817 "country":"CN","state":"Shandong Sheng", 

818 "type":"Unknown","type_of_area":"Unknown","timezone":"Asia/Shanghai", 

819 "additional_metadata":"{}"} 

820 }, 

821 headers={"email": "s.schroeder@fz-juelich.de"} 

822 ) 

823 expected_status_code = 443 

824 assert response.status_code == expected_status_code 

825 expected_resp = {'detail' : {'message': 'Station already registered!', 

826 'station_id': 1}} 

827 assert response.json() == expected_resp 

828 

829 

830 # 3. tests updating station metadata 

831 

832 def test_patch_stationmeta_no_description(self, client, db): 

833 response = client.patch("/stationmeta/-1", 

834 json={"stationmeta": 

835 {"name":"TTTT95TTTT"} 

836 }, 

837 headers={"email": "s.schroeder@fz-juelich.de"} 

838 ) 

839 expected_status_code = 404 

840 assert response.status_code == expected_status_code 

841 expected_resp = {"detail": "description text ist missing."} 

842 assert response.json() == expected_resp 

843 

844 

845 def test_patch_stationmeta_not_found1(self, client, db): 

846 response = client.patch("/stationmeta/-1?description=changing station name", 

847 json={"stationmeta": 

848 {"name":"TTTT95TTTT"} 

849 }, 

850 headers={"email": "s.schroeder@fz-juelich.de"} 

851 ) 

852 expected_status_code = 404 

853 assert response.status_code == expected_status_code 

854 expected_resp = {"detail": "Station for patching not found."} 

855 assert response.json() == expected_resp 

856 

857 

858 def test_patch_stationmeta_name(self, client, db): 

859 response = client.patch("/stationmeta/SDZ54421?description=changing station name", 

860 json={"stationmeta": 

861 {"name":"TTTT95TTTT"} 

862 }, 

863 headers={"email": "s.schroeder@fz-juelich.de"} 

864 ) 

865 expected_status_code = 200 

866 assert response.status_code == expected_status_code 

867 expected_resp = {'message': 'patched stationmeta record for station_id 2', 'station_id': 2} 

868 response_json = response.json() 

869 assert response_json == expected_resp 

870 response = client.get(f"/stationmeta/id/{response_json['station_id']}") 

871 response_json = response.json() 

872 # just check special changes 

873 assert response_json['name'] == 'TTTT95TTTT' 

874 assert response_json['changelog'][1]['old_value'] == "{'name': 'Shangdianzi'}" 

875 assert response_json['changelog'][1]['new_value'] == "{'name': 'TTTT95TTTT'}" 

876 assert response_json['changelog'][1]['author_id'] == 1 

877 assert response_json['changelog'][1]['type_of_change'] == 'single value correction in metadata' 

878 

879 

880 def test_patch_single_stationmeta_global(self, client, db): 

881 response = client.patch("/stationmeta/SDZ54421?description=changing global metadata", 

882 json={"stationmeta": 

883 {"globalmeta": {"climatic_zone_year2016": "WarmTemperateMoist"}} 

884 }, 

885 headers={"email": "s.schroeder@fz-juelich.de"} 

886 ) 

887 expected_status_code = 200 

888 assert response.status_code == expected_status_code 

889 expected_resp = {'message': 'patched stationmeta record for station_id 2', 'station_id': 2} 

890 response_json = response.json() 

891 assert response_json == expected_resp 

892 response = client.get(f"/stationmeta/id/{response_json['station_id']}") 

893 response_json = response.json() 

894 # just check special changes 

895 assert response_json['name'] == 'Shangdianzi' 

896 assert response_json['changelog'][1]['old_value'] == "{'climatic_zone_year2016': 'WarmTemperateDry'}" 

897 assert response_json['changelog'][1]['new_value'] == "{'climatic_zone_year2016': 'WarmTemperateMoist'}" 

898 assert response_json['changelog'][1]['author_id'] == 1 

899 assert response_json['changelog'][1]['type_of_change'] == 'single value correction in metadata' 

900 

901 

902 def test_patch_multiple_stationmeta_global(self, client, db): 

903 response = client.patch("/stationmeta/SDZ54421?description=changing global metadata", 

904 json={"stationmeta": 

905 {"globalmeta": {"climatic_zone_year2016": "WarmTemperateMoist", 

906 "toar1_category": "RuralLowElevation", 

907 "toar2_category": "Urban", 

908 "htap_region_tier1_year2010": "HTAPTier1PAN", 

909 "dominant_landcover_year2012": "TreeNeedleleavedEvergreenClosedToOpen", 

910 "landcover_description_25km_year2012": "TreeNeedleleavedEvergreenClosedToOpen: 100 %", 

911 "dominant_ecoregion_year2017": "Guianansavanna", 

912 "ecoregion_description_25km_year2017": "Guianansavanna: 90 %, Miskitopineforests: 10 %"}} 

913 }, 

914 headers={"email": "s.schroeder@fz-juelich.de"} 

915 ) 

916 expected_status_code = 200 

917 assert response.status_code == expected_status_code 

918 expected_resp = {'message': 'patched stationmeta record for station_id 2', 'station_id': 2} 

919 response_json = response.json() 

920 assert response_json == expected_resp 

921 response = client.get(f"/stationmeta/id/{response_json['station_id']}") 

922 response_json = response.json() 

923 # just check special changes 

924 assert response_json['name'] == 'Shangdianzi' 

925 assert response_json['changelog'][1]['old_value'] == ( 

926 "{'climatic_zone_year2016': 'WarmTemperateDry', " 

927 "'htap_region_tier1_year2010': 'HTAPTier1MDE', " 

928 "'dominant_landcover_year2012': 'CroplandRainfedHerbaceousCover', " 

929 "'landcover_description_25km_year2012': '', " 

930 "'dominant_ecoregion_year2017': 'Undefined', " 

931 "'ecoregion_description_25km_year2017': '', " 

932 "'toar1_category': 'Unclassified', " 

933 "'toar2_category': 'Suburban'}" ) 

934 assert response_json['changelog'][1]['new_value'] == ( 

935 "{'climatic_zone_year2016': 'WarmTemperateMoist', " 

936 "'htap_region_tier1_year2010': 'HTAPTier1PAN', " 

937 "'dominant_landcover_year2012': 'TreeNeedleleavedEvergreenClosedToOpen', " 

938 "'landcover_description_25km_year2012': 'TreeNeedleleavedEvergreenClosedToOpen: 100 %', " 

939 "'dominant_ecoregion_year2017': 'Guianansavanna', " 

940 "'ecoregion_description_25km_year2017': 'Guianansavanna: 90 %, Miskitopineforests: 10 %'" 

941 ", 'toar1_category': 'RuralLowElevation'" 

942 ", 'toar2_category': 'Urban'}" ) 

943 assert response_json['changelog'][1]['author_id'] == 1 

944 assert response_json['changelog'][1]['type_of_change'] == 'single value correction in metadata' 

945 

946 

947 def test_patch_stationmeta_roles_and_annotations(self, client, db): 

948 response = client.patch("/stationmeta/SDZ54421?description=adding annotation text", 

949 json={"stationmeta": 

950 {"roles": [{"role": "PointOfContact", "contact_id": 3, "status": "Active"}, 

951 {"role": "Originator", "contact_id": 1, "status": "Active"}], 

952 "annotations": [{"kind": "User", 

953 "text": "some annotation text", 

954 "date_added": "2025-02-10 17:00", 

955 "approved": True, 

956 "contributor_id":1}] 

957 } 

958 }, 

959 headers={"email": "s.schroeder@fz-juelich.de"} 

960 ) 

961 expected_status_code = 200 

962 assert response.status_code == expected_status_code 

963 expected_resp = {'message': 'patched stationmeta record for station_id 2', 'station_id': 2} 

964 response_json = response.json() 

965 assert response_json == expected_resp 

966 response = client.get(f"/stationmeta/id/{response_json['station_id']}") 

967 response_json = response.json() 

968 assert response_json['annotations'] == [{'id': 1, 'kind': 'user comment', 'text': 'some annotation text', 'date_added': '2025-02-10T17:00:00+00:00', 'approved': True, 'contributor_id': 1}] 

969 assert response_json['changelog'][1]['old_value'] == "{'roles': {{'role': 'ResourceProvider', 'status': 'Active', 'contact_id': 5},}" 

970 assert response_json['changelog'][1]['new_value'] == ( 

971 "{'roles': [{'role': 'PointOfContact', 'contact_id': 3, 'status': 'Active'}, " 

972 "{'role': 'Originator', 'contact_id': 1, 'status': 'Active'}], " 

973 "'annotations': [{'kind': 'User', 'text': 'some annotation text', 'date_added': '2025-02-10 17:00', 'approved': True, 'contributor_id': 1}]}" ) 

974 assert response_json['changelog'][1]['author_id'] == 1 

975 assert response_json['changelog'][1]['type_of_change'] == 'comprehensive metadata revision' 

976 

977 

978 def test_delete_roles_from_stationmeta(self, client, db): 

979 response = client.patch("/stationmeta/delete_field/China11?field=roles", 

980 headers={"email": "s.schroeder@fz-juelich.de"}) 

981 expected_status_code = 200 

982 assert response.status_code == expected_status_code 

983 # Database defaults cannot be patched within pytest 

984 patched_response = response.json() 

985 patched_response["changelog"][-1]["datetime"] = "" 

986 expected_resp = {'id': 1, 

987 'codes': ['China11'], 

988 'name': 'Mount Tai', 

989 'coordinates': {'lat': 36.256, 'lng': 117.106, 'alt': 1534.0}, 

990 'coordinate_validation_status': 'not checked', 

991 'country': 'Germany', 

992 'state': 'Shandong Sheng', 

993 'type': 'unknown', 

994 'type_of_area': 'unknown', 

995 'timezone': 'Asia/Shanghai', 

996 'additional_metadata': {'dummy_info': 'Here is some more information about the station'}, 

997 'aux_images': [], 

998 'aux_docs': [], 

999 'aux_urls': [], 

1000 'changelog': [ 

1001 { 

1002 'author_id': 1, 

1003 'datetime': '2023-07-05T08:23:04.551645+00:00', 

1004 'description': 'station created', 

1005 'new_value': '', 

1006 'old_value': '', 

1007 'station_id': 1, 

1008 'type_of_change': 'created', 

1009 }, 

1010 { 

1011 'author_id': 1, 

1012 'datetime': '', 

1013 'description': 'delete field roles', 

1014 'new_value': "'roles': []", 

1015 'old_value': "'roles': '[]'", 

1016 'station_id': 1, 

1017 'type_of_change': 'single value correction in metadata', 

1018 }, 

1019 ], 

1020 } 

1021 assert patched_response == expected_resp 

1022 

1023 

1024 def test_delete_field_station_not_found(self, client, db): 

1025 response = client.patch("/stationmeta/delete_field/China22?field=timezone", 

1026 headers={"email": "s.schroeder@fz-juelich.de"}) 

1027 expected_status_code = 404 

1028 assert response.status_code == expected_status_code 

1029 expected_resp = {'detail': 'Station for deleting field not found.'} 

1030 assert response.json() == expected_resp 

1031 

1032 

1033 def test_test(self, client, db): 

1034 response = client.patch("/stationmeta/SDY54421?description=changing global metadata", 

1035 json={"stationmeta": 

1036 {"globalmeta": { "toar2_category": "Rural"}} 

1037 }, 

1038 headers={"email": "s.schroeder@fz-juelich.de"} 

1039 ) 

1040 expected_status_code = 404 

1041 assert response.status_code == expected_status_code 

1042 expected_resp = {'detail': 'Station for patching not found.'} 

1043 assert response.json() == expected_resp