Coverage for tests / test_stationmeta.py: 100%

329 statements  

« prev     ^ index     » next       coverage.py v7.14.0, created at 2026-05-12 12:51 +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 def test_get_special_with_id_and_fields(self, client, db): 

452 response = client.get("/stationmeta/?fields=id,toar2_category&id=1") 

453 expected_status_code = 200 

454 assert response.status_code == expected_status_code 

455 expected_resp = [{'globalmeta': {'toar2_category': '1'}, 'id': 1}] 

456 assert response.json() == expected_resp 

457 

458 

459 def test_get_special_with_ambiguous_field(self, client, db): 

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

461 expected_status_code = 200 

462 assert response.status_code == expected_status_code 

463 expected_resp = {'id': 1} 

464 assert response.json() == expected_resp 

465 

466 

467 def test_get_special_with_fields_station_not_found(self, client, db): 

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

469 expected_status_code = 404 

470 assert response.status_code == expected_status_code 

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

472 assert response.json() == expected_resp 

473 

474 def test_get_all_with_fields(self, client, db): 

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

476 expected_status_code = 200 

477 assert response.status_code == expected_status_code 

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

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

480 'mean_topography_srtm_alt_1km_year1994': -999.0, 

481 'max_topography_srtm_relative_alt_5km_year1994': -999.0, 

482 'min_topography_srtm_relative_alt_5km_year1994': -999.0, 

483 'stddev_topography_srtm_relative_alt_5km_year1994': -999.0, 

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

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

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

487 '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 %', 

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

489 'ecoregion_description_25km_year2017': '', 

490 'distance_to_major_road_year2020': -999.0, 

491 'mean_stable_nightlights_1km_year2013': -999.0, 

492 'mean_stable_nightlights_5km_year2013': -999.0, 

493 'max_stable_nightlights_25km_year2013': -999.0, 

494 'max_stable_nightlights_25km_year1992': -999.0, 

495 'mean_population_density_250m_year2015': -1.0, 

496 'mean_population_density_5km_year2015': -1.0, 

497 'max_population_density_25km_year2015': -1.0, 

498 'mean_population_density_250m_year1990': -1.0, 

499 'mean_population_density_5km_year1990': -1.0, 

500 'max_population_density_25km_year1990': -1.0, 

501 'mean_nox_emissions_10km_year2015': -999.0, 

502 'mean_nox_emissions_10km_year2000': -999.0, 

503 'toar1_category': 'unclassified', 

504 'toar2_category': 'urban' 

505 } 

506 }, 

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

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

509 'mean_topography_srtm_alt_1km_year1994': -999.0, 

510 'max_topography_srtm_relative_alt_5km_year1994': -999.0, 

511 'min_topography_srtm_relative_alt_5km_year1994': -999.0, 

512 'stddev_topography_srtm_relative_alt_5km_year1994': -999.0, 

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

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

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

516 'landcover_description_25km_year2012': '', 

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

518 'ecoregion_description_25km_year2017': '', 

519 'distance_to_major_road_year2020': -999.0, 

520 'mean_stable_nightlights_1km_year2013': -999.0, 

521 'mean_stable_nightlights_5km_year2013': -999.0, 

522 'max_stable_nightlights_25km_year2013': -999.0, 

523 'max_stable_nightlights_25km_year1992': -999.0, 

524 'mean_population_density_250m_year2015': -1.0, 

525 'mean_population_density_5km_year2015': -1.0, 

526 'max_population_density_25km_year2015': -1.0, 

527 'mean_population_density_250m_year1990': -1.0, 

528 'mean_population_density_5km_year1990': -1.0, 

529 'max_population_density_25km_year1990': -1.0, 

530 'mean_nox_emissions_10km_year2015': -999.0, 

531 'mean_nox_emissions_10km_year2000': -999.0, 

532 'toar1_category': 'unclassified', 

533 'toar2_category': 'suburban' 

534 } 

535 }, 

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

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

538 'mean_topography_srtm_alt_1km_year1994': -999.0, 

539 'max_topography_srtm_relative_alt_5km_year1994': -999.0, 

540 'min_topography_srtm_relative_alt_5km_year1994': -999.0, 

541 'stddev_topography_srtm_relative_alt_5km_year1994': -999.0, 

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

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

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

545 'landcover_description_25km_year2012': '', 

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

547 'ecoregion_description_25km_year2017': '', 

548 'distance_to_major_road_year2020': -999.0, 

549 'mean_stable_nightlights_1km_year2013': -999.0, 

550 'mean_stable_nightlights_5km_year2013': -999.0, 

551 'max_stable_nightlights_25km_year2013': -999.0, 

552 'max_stable_nightlights_25km_year1992': -999.0, 

553 'mean_population_density_250m_year2015': -1.0, 

554 'mean_population_density_5km_year2015': -1.0, 

555 'max_population_density_25km_year2015': -1.0, 

556 'mean_population_density_250m_year1990': -1.0, 

557 'mean_population_density_5km_year1990': -1.0, 

558 'max_population_density_25km_year1990': -1.0, 

559 'mean_nox_emissions_10km_year2015': -999.0, 

560 'mean_nox_emissions_10km_year2000': -999.0, 

561 'toar1_category': 'unclassified', 

562 'toar2_category': 'suburban' 

563 } 

564 }] 

565 assert response.json() == expected_resp 

566 

567 

568 def test_get_all_wrong_params(self, client, db): 

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

570 expected_status_code = 400 

571 assert response.status_code == expected_status_code 

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

573 assert response.json() == expected_resp 

574 

575 

576 def test_get_stationmeta_not_found(self, client, db): 

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

578 expected_status_code = 404 

579 assert response.status_code == expected_status_code 

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

581 assert response.json() == expected_resp 

582 

583 

584 def test_get_stationmeta_by_id_not_found(self, client, db): 

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

586 expected_status_code = 404 

587 assert response.status_code == expected_status_code 

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

589 assert response.json() == expected_resp 

590 

591 

592 def test_get_stationmeta_by_id(self, client, db): 

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

594 expected_status_code = 200 

595 assert response.status_code == expected_status_code 

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

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

598 'coordinate_validation_status': 'not checked', 

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

600 'type': 'unknown', 'type_of_area': 'unknown', 

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

602 'roles': [{ 'contact': { 

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

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

605 'country': 'Germany', 

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

607 'id': 1, 

608 'kind': 'government', 

609 'longname': 'Umweltbundesamt', 

610 'name': 'UBA', 

611 'postcode': '06844', 

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

613 }, 

614 'id': 2, 

615 'role': 'resource provider', 

616 'status': 'active' }], 

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

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

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

620 'distance_to_major_road_year2020': -999.0, 

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

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

623 'ecoregion_description_25km_year2017': '', 

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

625 +'herbaceous cover): ' 

626 +'30.7 %, 12 (Cropland, ' 

627 +'rainfed, tree or shrub ' 

628 +'cover): 25.0 %, 210 ' 

629 +'(Water bodies): 16.9 ' 

630 +'%, 130 (Grassland): ' 

631 +'8.6 %, 190 (Urban ' 

632 +'areas): 5.8 %, 100 ' 

633 +'(Mosaic tree and shrub ' 

634 +'(>50%) / herbaceous ' 

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

636 +'40 (Mosaic natural ' 

637 +'vegetation (tree, ' 

638 +'shrub, herbaceous ' 

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

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

641 +'%, 10 (Cropland, ' 

642 +'rainfed): 2.0 %, 30 ' 

643 +'(Mosaic cropland ' 

644 +'(>50%) / natural ' 

645 +'vegetation (tree, ' 

646 +'shrub, herbaceous ' 

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

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

649 'max_stable_nightlights_25km_year1992': -999.0, 

650 'max_stable_nightlights_25km_year2013': -999.0, 

651 'max_population_density_25km_year1990': -1.0, 

652 'max_population_density_25km_year2015': -1.0, 

653 'max_topography_srtm_relative_alt_5km_year1994': -999.0, 

654 'mean_stable_nightlights_1km_year2013': -999.0, 

655 'mean_stable_nightlights_5km_year2013': -999.0, 

656 'mean_nox_emissions_10km_year2000': -999.0, 

657 'mean_nox_emissions_10km_year2015': -999.0, 

658 'mean_population_density_250m_year1990': -1.0, 

659 'mean_population_density_250m_year2015': -1.0, 

660 'mean_population_density_5km_year1990': -1.0, 

661 'mean_population_density_5km_year2015': -1.0, 

662 'mean_topography_srtm_alt_1km_year1994': -999.0, 

663 'mean_topography_srtm_alt_90m_year1994': -999.0, 

664 'min_topography_srtm_relative_alt_5km_year1994': -999.0, 

665 'stddev_topography_srtm_relative_alt_5km_year1994': -999.0, 

666 'toar1_category': 'unclassified', 

667 'toar2_category': 'urban'}, 

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

669 'description': 'station created', 

670 'old_value': '', 

671 'new_value': '', 

672 'station_id': 1, 

673 'author_id': 1, 

674 'type_of_change': 'created' 

675 }]} 

676 assert response.json() == expected_resp 

677 

678 

679 def test_get_stationmeta_changelog(self, client, db): 

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

681 expected_status_code = 200 

682 assert response.status_code == expected_status_code 

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

684 'description': 'station created', 

685 'old_value': '', 

686 'new_value': '', 

687 'station_id': 1, 

688 'author_id': 1, 

689 'type_of_change': 'created' 

690 }] 

691 assert response.json() == expected_resp 

692 

693 # 2. tests creating station metadata 

694 

695 

696 def test_insert_new_wrong_credentials(self, client, db): 

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

698 json={"stationmeta": 

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

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

701 "coordinate_validation_status": "NotChecked", 

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

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

704 "additional_metadata": "{}" } 

705 }, 

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

707 ) 

708 expected_status_code=401 

709 assert response.status_code == expected_status_code 

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

711 assert response.json() == expected_resp 

712 

713 

714 def test_insert_new_without_credentials(self, client, db): 

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

716 json={"stationmeta": 

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

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

719 "coordinate_validation_status": "NotChecked", 

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

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

722 "additional_metadata": "{}" } 

723 }) 

724 expected_status_code=401 

725 assert response.status_code == expected_status_code 

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

727 assert response.json() == expected_resp 

728 

729 

730 def test_insert_new(self, client, db): 

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

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

733 json={"stationmeta": 

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

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

736 "coordinate_validation_status": "NotChecked", 

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

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

739 "additional_metadata": "{}" } 

740 }, 

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

742 ) 

743 expected_status_code = 200 

744 assert response.status_code == expected_status_code 

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

746 'station_id': 4}} 

747 assert response.json() == expected_resp 

748 

749 

750 def test_insert_new_with_roles(self, client, db): 

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

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

753 json={"stationmeta": 

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

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

756 "coordinate_validation_status": "NotChecked", 

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

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

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

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

761 "additional_metadata": "{}" } 

762 }, 

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

764 ) 

765 expected_status_code = 200 

766 assert response.status_code == expected_status_code 

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

768 'station_id': 4}} 

769 assert response.json() == expected_resp 

770 

771 

772 def test_insert_new_with_annotations(self, client, db): 

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

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

775 json={"stationmeta": 

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

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

778 "coordinate_validation_status": "NotChecked", 

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

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

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

782 "text": "some foo", 

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

784 "approved": True, 

785 "contributor_id":1}], 

786 "additional_metadata": "{}" } 

787 }, 

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

789 ) 

790 expected_status_code = 200 

791 assert response.status_code == expected_status_code 

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

793 'station_id': 4}} 

794 assert response.json() == expected_resp 

795 

796 

797 def test_insert_new_same_coordinates(self, client, db): 

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

799 json={"stationmeta": 

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

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

802 "coordinate_validation_status": "NotChecked", 

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

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

805 "additional_metadata":"{}"}, 

806 }, 

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

808 ) 

809 expected_status_code = 444 

810 assert response.status_code == expected_status_code 

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

812 'station_ids': [1, 3], 

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

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

815 assert response.json() == expected_resp 

816 

817 

818 def test_insert_duplicate(self, client, db): 

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

820 json={"stationmeta": 

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

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

823 "coordinate_validation_status": "NotChecked", 

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

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

826 "additional_metadata":"{}"} 

827 }, 

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

829 ) 

830 expected_status_code = 443 

831 assert response.status_code == expected_status_code 

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

833 'station_id': 1}} 

834 assert response.json() == expected_resp 

835 

836 

837 # 3. tests updating station metadata 

838 

839 def test_patch_stationmeta_no_description(self, client, db): 

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

841 json={"stationmeta": 

842 {"name":"TTTT95TTTT"} 

843 }, 

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

845 ) 

846 expected_status_code = 404 

847 assert response.status_code == expected_status_code 

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

849 assert response.json() == expected_resp 

850 

851 

852 def test_patch_stationmeta_not_found1(self, client, db): 

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

854 json={"stationmeta": 

855 {"name":"TTTT95TTTT"} 

856 }, 

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

858 ) 

859 expected_status_code = 404 

860 assert response.status_code == expected_status_code 

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

862 assert response.json() == expected_resp 

863 

864 

865 def test_patch_stationmeta_name(self, client, db): 

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

867 json={"stationmeta": 

868 {"name":"TTTT95TTTT"} 

869 }, 

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

871 ) 

872 expected_status_code = 200 

873 assert response.status_code == expected_status_code 

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

875 response_json = response.json() 

876 assert response_json == expected_resp 

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

878 response_json = response.json() 

879 # just check special changes 

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

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

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

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

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

885 

886 

887 def test_patch_single_stationmeta_global(self, client, db): 

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

889 json={"stationmeta": 

890 {"globalmeta": {"climatic_zone_year2016": "WarmTemperateMoist"}} 

891 }, 

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

893 ) 

894 expected_status_code = 200 

895 assert response.status_code == expected_status_code 

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

897 response_json = response.json() 

898 assert response_json == expected_resp 

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

900 response_json = response.json() 

901 # just check special changes 

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

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

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

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

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

907 

908 

909 def test_patch_multiple_stationmeta_global(self, client, db): 

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

911 json={"stationmeta": 

912 {"globalmeta": {"climatic_zone_year2016": "WarmTemperateMoist", 

913 "toar1_category": "RuralLowElevation", 

914 "toar2_category": "Urban", 

915 "htap_region_tier1_year2010": "HTAPTier1PAN", 

916 "dominant_landcover_year2012": "TreeNeedleleavedEvergreenClosedToOpen", 

917 "landcover_description_25km_year2012": "TreeNeedleleavedEvergreenClosedToOpen: 100 %", 

918 "dominant_ecoregion_year2017": "Guianansavanna", 

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

920 }, 

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

922 ) 

923 expected_status_code = 200 

924 assert response.status_code == expected_status_code 

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

926 response_json = response.json() 

927 assert response_json == expected_resp 

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

929 response_json = response.json() 

930 # just check special changes 

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

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

933 "{'climatic_zone_year2016': 'WarmTemperateDry', " 

934 "'htap_region_tier1_year2010': 'HTAPTier1MDE', " 

935 "'dominant_landcover_year2012': 'CroplandRainfedHerbaceousCover', " 

936 "'landcover_description_25km_year2012': '', " 

937 "'dominant_ecoregion_year2017': 'Undefined', " 

938 "'ecoregion_description_25km_year2017': '', " 

939 "'toar1_category': 'Unclassified', " 

940 "'toar2_category': 'Suburban'}" ) 

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

942 "{'climatic_zone_year2016': 'WarmTemperateMoist', " 

943 "'htap_region_tier1_year2010': 'HTAPTier1PAN', " 

944 "'dominant_landcover_year2012': 'TreeNeedleleavedEvergreenClosedToOpen', " 

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

946 "'dominant_ecoregion_year2017': 'Guianansavanna', " 

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

948 ", 'toar1_category': 'RuralLowElevation'" 

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

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

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

952 

953 

954 def test_patch_stationmeta_roles_and_annotations(self, client, db): 

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

956 json={"stationmeta": 

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

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

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

960 "text": "some annotation text", 

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

962 "approved": True, 

963 "contributor_id":1}] 

964 } 

965 }, 

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

967 ) 

968 expected_status_code = 200 

969 assert response.status_code == expected_status_code 

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

971 response_json = response.json() 

972 assert response_json == expected_resp 

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

974 response_json = response.json() 

975 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}] 

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

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

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

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

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

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

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

983 

984 

985 def test_delete_roles_from_stationmeta(self, client, db): 

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

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

988 expected_status_code = 200 

989 assert response.status_code == expected_status_code 

990 # Database defaults cannot be patched within pytest 

991 patched_response = response.json() 

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

993 expected_resp = {'id': 1, 

994 'codes': ['China11'], 

995 'name': 'Mount Tai', 

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

997 'coordinate_validation_status': 'not checked', 

998 'country': 'Germany', 

999 'state': 'Shandong Sheng', 

1000 'type': 'unknown', 

1001 'type_of_area': 'unknown', 

1002 'timezone': 'Asia/Shanghai', 

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

1004 'aux_images': [], 

1005 'aux_docs': [], 

1006 'aux_urls': [], 

1007 'changelog': [ 

1008 { 

1009 'author_id': 1, 

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

1011 'description': 'station created', 

1012 'new_value': '', 

1013 'old_value': '', 

1014 'station_id': 1, 

1015 'type_of_change': 'created', 

1016 }, 

1017 { 

1018 'author_id': 1, 

1019 'datetime': '', 

1020 'description': 'delete field roles', 

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

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

1023 'station_id': 1, 

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

1025 }, 

1026 ], 

1027 } 

1028 assert patched_response == expected_resp 

1029 

1030 

1031 def test_delete_field_station_not_found(self, client, db): 

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

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

1034 expected_status_code = 404 

1035 assert response.status_code == expected_status_code 

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

1037 assert response.json() == expected_resp 

1038 

1039 

1040 def test_test(self, client, db): 

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

1042 json={"stationmeta": 

1043 {"globalmeta": { "toar2_category": "Rural"}} 

1044 }, 

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

1046 ) 

1047 expected_status_code = 404 

1048 assert response.status_code == expected_status_code 

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

1050 assert response.json() == expected_resp