Coverage for test / conftest.py: 94%

228 statements  

« prev     ^ index     » next       coverage.py v7.13.4, created at 2026-03-25 18:06 +0000

1# SPDX-FileCopyrightText: 2024 Elia Rizzetto <elia.rizzetto2@unibo.it> 

2# SPDX-FileCopyrightText: 2026 Arcangelo Massari <arcangelo.massari@unibo.it> 

3# 

4# SPDX-License-Identifier: ISC 

5 

6import json as json_module 

7import re 

8from urllib.parse import unquote 

9 

10import pytest 

11import responses 

12 

13from oc_ds_converter.oc_idmanager.oc_data_storage.in_memory_manager import InMemoryStorageManager 

14from oc_ds_converter.oc_idmanager.oc_data_storage.redis_manager import RedisStorageManager 

15from oc_ds_converter.oc_idmanager.oc_data_storage.sqlite_manager import SqliteStorageManager 

16 

17 

18@pytest.fixture(params=["redis", "sqlite", "inmemory"]) 

19def storage_manager(request: pytest.FixtureRequest, tmp_path): 

20 if request.param == "redis": 

21 sm = RedisStorageManager(testing=True) 

22 elif request.param == "sqlite": 

23 sm = SqliteStorageManager(str(tmp_path / "test.db")) 

24 else: 

25 sm = InMemoryStorageManager(str(tmp_path / "test.json")) 

26 yield sm 

27 sm.delete_storage() 

28 

29 

30@pytest.fixture(autouse=True) 

31def mock_http_requests(request): 

32 with responses.RequestsMock(assert_all_requests_are_fired=False) as rsps: 

33 _register_doi_ra_mocks(rsps) 

34 _register_doi_mocks(rsps) 

35 _register_crossref_mocks(rsps) 

36 _register_arxiv_mocks(rsps) 

37 _register_pmid_mocks(rsps) 

38 _register_orcid_mocks(rsps) 

39 _register_openalex_mocks(rsps) 

40 _register_pmc_mocks(rsps) 

41 _register_datacite_mocks(rsps) 

42 _register_jid_mocks(rsps) 

43 _register_ror_mocks(rsps) 

44 _register_viaf_mocks(rsps) 

45 _register_wikidata_mocks(rsps) 

46 _register_wikipedia_mocks(rsps) 

47 _register_medra_mocks(rsps) 

48 _register_url_mocks(rsps) 

49 yield rsps 

50 

51 

52def _register_doi_ra_mocks(rsps: responses.RequestsMock) -> None: 

53 def doi_ra_callback(request): 

54 return (200, {}, json_module.dumps([{"RA": "Crossref"}])) 

55 

56 rsps.add_callback( 

57 responses.GET, 

58 re.compile(r"https://doi\.org/ra/.*"), 

59 callback=doi_ra_callback, 

60 content_type="application/json", 

61 ) 

62 

63 

64def _register_doi_mocks(rsps: responses.RequestsMock) -> None: 

65 valid_dois = { 

66 "10.1108/jd-12-2013-0166", 

67 "10.1130/2015.2513(00)", 

68 "10.1007/s11192-022-04367-w", 

69 "10.1007/s12160-011-9282-0", 

70 "10.5281/zenodo.4725899", 

71 "10.2307/3053861", 

72 "10.1016/0006-2944(75)90147-7", 

73 "10.1016/0006-291x(75)90482-9", 

74 "10.1016/0006-291x(75)90498-2", 

75 "10.1016/0006-291x(75)90506-9", 

76 "10.1016/0006-291x(75)90508-2", 

77 "10.48550/arxiv.1509.08217", 

78 "10.1016/0361-9230(93)90026-8", 

79 "10.1184/r1/12841247.v1", 

80 "10.1038/nature12373", 

81 "10.3390/ijerph15061183", 

82 "10.17605/osf.io/abcde", 

83 "10.13039/100005522", 

84 "10.11578/1367552", 

85 "10.11221/jima.51.86", 

86 "10.11185/imt.8.380", 

87 "10.11230/jsts.17.2_1", 

88 "10.11178/example.1", 

89 "10.11178/example.2", 

90 "10.1016/0022-0248(83)90411-6", 

91 "10.1016/0038-1098(88)91128-3", 

92 "10.11224/cleftpalate1976.23.2_83", 

93 "10.11178/jdsa.8.49", 

94 "10.11178/jdsa.5.47", 

95 "10.11230/jsts.17.1_11", 

96 "10.11450/seitaikogaku1989.10.1", 

97 "10.11230/jsts.18.1_34", 

98 "10.2494/photopolymer.12.209", 

99 "10.1007/bf00354389", 

100 "10.1007/bf00354390", 

101 "10.1088/0954-0083/11/1/013", 

102 "10.1177/0954008303015002003", 

103 "10.1021/ma0201779", 

104 "10.1246/cl.1997.333", 

105 "10.1126/science.2563171", 

106 "10.1016/0032-3861(95)90668-r", 

107 "10.1016/0032-3861(95)90669-s", 

108 "10.1088/0954-0083/11/1/008", 

109 "10.1016/s0032-3861(02)00362-2", 

110 "10.1093/comjnl/4.4.332", 

111 "10.2307/1252042", 

112 "10.1063/1.1656693", 

113 "10.3233/978-1-61499-672-9-227", 

114 "10.3233/ds-170012", 

115 "10.1145/3360901.3364434", 

116 "10.1007/978-3-030-61244-3_6", 

117 "10.1007/978-3-030-62466-8_28", 

118 "10.3897/bdj.3.e5063", 

119 "10.1103/physrevd.84.084046", 

120 "10.48550/arxiv.1107.5979", 

121 "10.1001/10-v4n2-hsf10003", 

122 "10.1001/jama.299.12.1471", 

123 "10.1016/s0196-0644(99)70224-6", 

124 "10.1056/nejmsa021807", 

125 "10.1089/bsp.2008.0020", 

126 "10.1097/01.bcr.0000155527.76205.a2", 

127 "10.1097/01.ccm.0000151067.76074.21", 

128 "10.1097/01.ccm.0000151072.17826.72", 

129 "10.1097/dmp.0b013e31817196bf", 

130 "10.1097/dmp.0b013e318194898d", 

131 "10.1097/dmp.0b013e31819d977c", 

132 "10.1097/dmp.0b013e31819f1ae2", 

133 "10.1177/003335490812300219", 

134 "10.1177/003335490912400218", 

135 "10.1378/chest.07-2693", 

136 "10.2105/ajph.2006.101626", 

137 "10.2105/ajph.2009.162677", 

138 "10.9799/ksfan.2012.25.1.069", 

139 "10.9799/ksfan.2012.25.1.077", 

140 "10.9799/ksfan.2012.25.1.083", 

141 "10.9799/ksfan.2012.25.1.090", 

142 "10.9799/ksfan.2012.25.1.099", 

143 "10.9799/ksfan.2012.25.1.105", 

144 "10.9799/ksfan.2012.25.1.116", 

145 "10.9799/ksfan.2012.25.1.123", 

146 "10.9799/ksfan.2012.25.1.132", 

147 "10.9799/ksfan.2012.25.1.142", 

148 "10.11426/nagare1970.2.3_3", 

149 "10.11426/nagare1970.2.4_1", 

150 "10.11426/nagare1970.3.3_13", 

151 "10.14825/kaseki.68.0_14", 

152 "10.14825/kaseki.68.0_18", 

153 "10.1017/s0022112062000762", 

154 "10.1295/kobunshi.16.842", 

155 "10.1295/kobunshi.16.921", 

156 "10.1002/zamm.19210010401", 

157 "10.1002/zamm.19210010402", 

158 "10.1126/science.235.4793.1156", 

159 "10.1098/rstb.1989.0091", 

160 "10.5575/geosoc.96.265", 

161 "10.1007/s00266-017-1063-0", 

162 "10.1016/s0006-3495(95)79925-8", 

163 "10.1016/s0306-4530(03)00098-2", 

164 "10.1021/bi00230a017", 

165 "10.1080/2162402x.2015.1114203", 

166 "10.1093/infdis/jir563", 

167 "10.1137/1.9781611973730.123", 

168 "10.1137/s0097539793255151", 

169 "10.1172/jci115925", 

170 "10.1186/s12891-018-2177-5", 

171 "10.2466/pr0.1959.5.3.355", 

172 "10.3171/jns.1980.53.6.0765", 

173 "10.3389/fnana.2012.00034", 

174 "10.3389/fnint.2013.00107", 

175 "10.4021/wjon235w", 

176 "10.48550/arxiv.1410.2266", 

177 "10.5812/aapm.19333", 

178 "10.1007/jhep03(2014)050", 

179 "10.1016/0024-3795(84)90143-5", 

180 "10.1093/ajcn/79.5.727", 

181 "10.1109/sfcs.1981.28", 

182 "10.1136/bjo.74.3.130-a", 

183 "10.1186/1477-7827-12-11", 

184 "10.1248/cpb.50.1128", 

185 "10.1371/journal.pone.0060793", 

186 "10.17511/jooo.2020.i06.02", 

187 "10.18632/oncotarget.8868", 

188 "10.2527/1995.7392834x", 

189 "10.3390/ijms19082345", 

190 "10.4103/abr.abr_141_17", 

191 "10.48550/arxiv.1312.6380", 

192 "10.48550/arxiv.1712.08660", 

193 "10.7557/5.5607", 

194 "10.48550/arxiv.1107.5979", 

195 "10.5100/jje.33.1", 

196 "10.11575/jet.v46i3.52198", 

197 "10.11578/1367548", 

198 "10.11578/1372474", 

199 "10.11578/1480643", 

200 "10.11578/dc.20191106.1", 

201 "10.15407/scin11.06.057", 

202 "10.1021/acs.jpclett.7b01097", 

203 "10.1063/1.4973421", 

204 "10.1371/journal.pone.0284601", 

205 "10.1007/978-3-030-00668-6_8", 

206 "10.1234/null-date", 

207 "10.1234/empty-date", 

208 "10.1234/no-dateparts", 

209 "10.1234/html-title", 

210 "10.1234/with-editor", 

211 "10.1001/test.12345", 

212 # DataCite test DOIs 

213 "10.1016/j.archger.2019.103975", 

214 "10.1016/j.archger.2020.104228", 

215 "10.1016/j.clinbiomech.2022.105711", 

216 "10.1016/j.humov.2013.06.005", 

217 "10.1016/j.humov.2020.102588", 

218 "10.1016/j.jbiomech.2014.07.010", 

219 "10.1016/j.jbmt.2023.04.020", 

220 "10.1021/acsami.3c04897", 

221 "10.1080/00222895.2014.916651", 

222 "10.1080/03091902.2022.2043947", 

223 "10.1080/10749357.2022.2130620", 

224 "10.1111/sms.12847", 

225 "10.12678/1089-313x.21.4.151", 

226 "10.1590/s0103-51502010000200003", 

227 "10.2174/1871527315666151111120403", 

228 # NOTE: 10.46979/rbn.v52i4.5546 is intentionally invalid for datacite tests 

229 "10.5007/1980-0037.2014v16n3p287", 

230 "10.5258/soton/d2733", 

231 "10.5281/zenodo.8210025", 

232 "10.5281/zenodo.8232826", 

233 "10.5281/zenodo.8233112", 

234 "10.5281/zenodo.8233113", 

235 "10.5281/zenodo.8265216", 

236 "10.5281/zenodo.8265217", 

237 "10.6061/clinics/2013(11)07", 

238 "10.5281/zenodo.8249952", 

239 "10.5281/zenodo.8249970", 

240 "10.1017/9781009157896", 

241 "10.1017/9781009157896.005", 

242 } 

243 

244 def doi_callback(request): 

245 url_path = request.url.replace("https://doi.org/api/handles/", "") 

246 doi = unquote(url_path).lower() 

247 if doi in valid_dois: 

248 return (200, {}, json_module.dumps({"responseCode": 1, "handle": doi})) 

249 return (404, {}, json_module.dumps({"responseCode": 100})) 

250 

251 rsps.add_callback( 

252 responses.GET, 

253 re.compile(r"https://doi\.org/api/handles/.*"), 

254 callback=doi_callback, 

255 content_type="application/json", 

256 ) 

257 

258 

259def _register_crossref_mocks(rsps: responses.RequestsMock) -> None: 

260 valid_members = { 

261 "297": {"id": 297, "primary-name": "IWA Publishing"}, 

262 "4443": {"id": 4443, "primary-name": "Example Publisher"}, 

263 "78": {"id": 78, "primary-name": "Elsevier BV"}, 

264 "311": {"id": 311, "primary-name": "MDPI"}, 

265 } 

266 

267 def crossref_members_callback(request): 

268 member_id = request.url.split("/")[-1] 

269 if member_id in valid_members: 

270 return ( 

271 200, 

272 {}, 

273 json_module.dumps({"status": "ok", "message": valid_members[member_id]}), 

274 ) 

275 return (404, {}, "") 

276 

277 rsps.add_callback( 

278 responses.GET, 

279 re.compile(r"https://api\.crossref\.org/members/.*"), 

280 callback=crossref_members_callback, 

281 content_type="application/json", 

282 ) 

283 

284 crossref_works_responses = { 

285 "10.1038/nature12373": { 

286 "DOI": "10.1038/nature12373", 

287 "type": "journal-article", 

288 "title": ["Nanometre-scale thermometry in a living cell"], 

289 "author": [ 

290 {"given": "G.", "family": "Kucsko", "sequence": "first"}, 

291 {"given": "P. C.", "family": "Maurer", "sequence": "additional"}, 

292 {"given": "M. D.", "family": "Lukin", "sequence": "additional"} 

293 ], 

294 "container-title": ["Nature"], 

295 "volume": "500", 

296 "issue": "7460", 

297 "page": "54-58", 

298 "issued": {"date-parts": [[2013, 7, 31]]}, 

299 "ISSN": ["0028-0836", "1476-4687"], 

300 "publisher": "Springer Science and Business Media LLC", 

301 "member": "297", 

302 "prefix": "10.1038" 

303 }, 

304 "10.1371/journal.pone.0284601": { 

305 "DOI": "10.1371/journal.pone.0284601", 

306 "type": "journal-article", 

307 "title": ["Biochemical evaluation of vaccination in rats"], 

308 "author": [ 

309 {"given": "Mahsa", "family": "Teymoorzadeh", "sequence": "first"}, 

310 {"given": "Razieh", "family": "Yazdanparast", "sequence": "additional", 

311 "ORCID": "https://orcid.org/0000-0003-0530-4305", "authenticated-orcid": True} 

312 ], 

313 "container-title": ["PLOS ONE"], 

314 "volume": "18", 

315 "issue": "5", 

316 "page": "e0284601", 

317 "issued": {"date-parts": [[2023, 5, 4]]}, 

318 "ISSN": ["1932-6203"], 

319 "publisher": "Public Library of Science (PLoS)" 

320 }, 

321 "10.1007/978-3-030-00668-6_8": { 

322 "DOI": "10.1007/978-3-030-00668-6_8", 

323 "type": "book-chapter", 

324 "title": ["The SPAR Ontologies"], 

325 "author": [ 

326 {"given": "Silvio", "family": "Peroni", "sequence": "first"}, 

327 {"given": "David", "family": "Shotton", "sequence": "additional"} 

328 ], 

329 "container-title": ["Lecture Notes in Computer Science", "The Semantic Web – ISWC 2018"], 

330 "page": "119-136", 

331 "issued": {"date-parts": [[2018]]}, 

332 "ISBN": ["9783030006679", "9783030006686"], 

333 "publisher": "Springer International Publishing" 

334 }, 

335 "10.1234/null-date": { 

336 "DOI": "10.1234/null-date", 

337 "type": "journal-article", 

338 "title": ["Article with null date"], 

339 "issued": {"date-parts": [[None]]} 

340 }, 

341 "10.1234/empty-date": { 

342 "DOI": "10.1234/empty-date", 

343 "type": "journal-article", 

344 "title": ["Article with empty date-parts"], 

345 "issued": {"date-parts": [[]]} 

346 }, 

347 "10.1234/no-dateparts": { 

348 "DOI": "10.1234/no-dateparts", 

349 "type": "journal-article", 

350 "title": ["Article without date-parts key"], 

351 "issued": {} 

352 }, 

353 "10.1234/html-title": { 

354 "DOI": "10.1234/html-title", 

355 "type": "journal-article", 

356 "title": ["A study of <i>Escherichia coli</i> in <b>biofilms</b>"], 

357 "issued": {"date-parts": [[2024, 1, 15]]} 

358 }, 

359 "10.1234/with-editor": { 

360 "DOI": "10.1234/with-editor", 

361 "type": "edited-book", 

362 "title": ["Edited volume test"], 

363 "author": [{"given": "John", "family": "Doe", "sequence": "first"}], 

364 "editor": [{"given": "Jane", "family": "Smith", "sequence": "first"}], 

365 "issued": {"date-parts": [[2024, 6, 20]]} 

366 }, 

367 } 

368 

369 def crossref_works_callback(request): 

370 doi = unquote(request.url.split("/works/")[-1]).lower() 

371 if doi in crossref_works_responses: 

372 return (200, {}, json_module.dumps({"status": "ok", "message": crossref_works_responses[doi]})) 

373 return (200, {}, json_module.dumps({"status": "ok", "message": {"DOI": doi}})) 

374 

375 rsps.add_callback( 

376 responses.GET, 

377 re.compile(r"https://api\.crossref\.org/works/.*"), 

378 callback=crossref_works_callback, 

379 content_type="application/json", 

380 ) 

381 

382 prefix_to_publisher = { 

383 "10.1016": ("Elsevier BV", "78"), 

384 "10.1136": ("BMJ", "239"), 

385 "10.1021": ("American Chemical Society (ACS)", "316"), 

386 "10.1007": ("Springer Science and Business Media LLC", "297"), 

387 "10.1097": ("Ovid Technologies (Wolters Kluwer Health)", "276"), 

388 "10.1378": ("Elsevier BV", "78"), 

389 "10.1055": ("Georg Thieme Verlag KG", "194"), 

390 "10.1080": ("Informa UK Limited", "301"), 

391 "10.1210": ("The Endocrine Society", "13"), 

392 "10.1159": ("S. Karger AG", "32"), 

393 "10.3233": ("IOS Press", "2428"), 

394 } 

395 

396 def crossref_prefixes_callback(request): 

397 prefix = request.url.split("/")[-1] 

398 if prefix in prefix_to_publisher: 

399 name, member_id = prefix_to_publisher[prefix] 

400 return ( 

401 200, 

402 {}, 

403 json_module.dumps({ 

404 "status": "ok", 

405 "message": { 

406 "prefix": prefix, 

407 "name": name, 

408 "member": f"http://id.crossref.org/member/{member_id}" 

409 } 

410 }), 

411 ) 

412 return (404, {}, "") 

413 

414 rsps.add_callback( 

415 responses.GET, 

416 re.compile(r"https://api\.crossref\.org/prefixes/.*"), 

417 callback=crossref_prefixes_callback, 

418 content_type="application/json", 

419 ) 

420 

421 

422def _register_arxiv_mocks(rsps: responses.RequestsMock) -> None: 

423 valid_arxiv_xml = """<?xml version="1.0" encoding="UTF-8"?> 

424<feed xmlns="http://www.w3.org/2005/Atom" xmlns:opensearch="http://a9.com/-/spec/opensearch/1.1/"> 

425 <opensearch:totalResults>1</opensearch:totalResults> 

426 <entry> 

427 <id>http://arxiv.org/abs/{arxiv_id}</id> 

428 <title>Example Paper Title</title> 

429 </entry> 

430</feed>""" 

431 

432 invalid_arxiv_xml = """<?xml version="1.0" encoding="UTF-8"?> 

433<feed xmlns="http://www.w3.org/2005/Atom" xmlns:opensearch="http://a9.com/-/spec/opensearch/1.1/"> 

434 <opensearch:totalResults>0</opensearch:totalResults> 

435</feed>""" 

436 

437 valid_ids = {"2109.05583", "2109.05582", "1509.08217", "1107.5979", "1410.2266", "1712.08660", "1312.6380"} 

438 valid_versions = {"2109.05583v2", "1509.08217v1", "1107.5979v1", "1410.2266v1", "1712.08660v1", "1312.6380v1"} 

439 

440 def arxiv_api_callback(request): 

441 url = request.url 

442 if "search_query=all:" in url: 

443 arxiv_id = url.split("search_query=all:")[-1] 

444 arxiv_id = unquote(arxiv_id) 

445 if arxiv_id in valid_ids: 

446 return (200, {}, valid_arxiv_xml.format(arxiv_id=arxiv_id)) 

447 return (200, {}, invalid_arxiv_xml) 

448 return (404, {}, "") 

449 

450 rsps.add_callback( 

451 responses.GET, 

452 re.compile(r"https://export\.arxiv\.org/api/query.*"), 

453 callback=arxiv_api_callback, 

454 content_type="application/xml", 

455 ) 

456 

457 def arxiv_abs_callback(request): 

458 url = request.url 

459 arxiv_id = url.split("/abs/")[-1] 

460 if arxiv_id in valid_versions: 

461 return (200, {}, "<html><body>ArXiv paper</body></html>") 

462 return (404, {}, "") 

463 

464 rsps.add_callback( 

465 responses.GET, 

466 re.compile(r"https://arxiv\.org/abs/.*"), 

467 callback=arxiv_abs_callback, 

468 content_type="text/html", 

469 ) 

470 

471 

472def _register_pmid_mocks(rsps: responses.RequestsMock) -> None: 

473 valid_pmid_html = """<html><body> 

474<div id="article-details"> 

475PMID- {pmid} 

476TI - Brevetoxin depresses synaptic transmission in guinea pig hippocampal slices. 

477FAU - Adler, M 

478FAU - Sheridan, R E 

479FAU - Apland, J P 

480DP - 1993 

481TA - Brain research bulletin 

482VI - 31 

483IP - 1-2 

484PG - 201-7 

485IS - 0361-9230 

486AID - 10.1016/0361-9230(93)90026-8 [doi] 

487</div></body></html>""" 

488 

489 valid_pmids = { 

490 "2942070", "1509982", "8384044", "6716460", 

491 "20662931", "12345", "5", "1", "2", "3", "4", 

492 "24484640", "21887584", "29890726", "23483834", "2938", 

493 "1056020", "4882249", "14834145", "5509841", "5059118", 

494 "118", "120", "351", "352", "353", "324", 

495 "1387883", "19934524", "20879334", "21949042", "22952459", 

496 "23199016", "25340169", "27429628", "27467927", "29147199", 

497 "30064399", "8527666", "8946776", "11169242", "15113710", 

498 "1581770", "16277675", "2322507", "23577161", "24632350", 

499 "29416127", "29456979", "30096915", "7441336", "28383409", 

500 "8582874", "9488524", "12192153", "12892987", "21533295", 

501 "24363676", "24409128", "24479789", "25893183", "27105522", "29305643", 

502 } 

503 

504 def pmid_callback(request): 

505 url = request.url 

506 match = re.search(r"/(\d+)/", url) 

507 if match: 

508 pmid = match.group(1) 

509 if pmid in valid_pmids: 

510 return (200, {}, valid_pmid_html.format(pmid=pmid)) 

511 return (404, {}, "") 

512 

513 rsps.add_callback( 

514 responses.GET, 

515 re.compile(r"https://pubmed\.ncbi\.nlm\.nih\.gov/.*"), 

516 callback=pmid_callback, 

517 content_type="text/html", 

518 ) 

519 

520 

521def _register_orcid_mocks(rsps: responses.RequestsMock) -> None: 

522 specific_orcids = { 

523 "0000-0001-5506-523X": { 

524 "orcid-identifier": {"path": "0000-0001-5506-523X"}, 

525 "person": { 

526 "name": {"family-name": {"value": "Shotton"}, "given-names": {"value": "David"}}, 

527 "emails": {"email": []}, 

528 "external-identifiers": {"external-identifier": []}, 

529 }, 

530 "history": { 

531 "submission-date": {"value": 1351641600000}, 

532 "last-modified-date": {"value": 1700000000000}, 

533 }, 

534 }, 

535 } 

536 generic_valid_orcids = { 

537 "0000-0003-0530-4305", 

538 "0000-0001-8513-8700", "0000-0002-9286-2630", 

539 "0000-0001-9597-7030", "0000-0002-8210-7076", 

540 "0000-0001-9759-3938", "0000-0003-4082-1500", 

541 "0000-0002-6227-4053", "0000-0002-0861-0511", 

542 "0000-0002-2149-4113", "0000-0001-6946-5074", 

543 "0000-0001-5096-5538", "0000-0001-5115-3099", 

544 "0000-0001-6174-9013", "0000-0001-6293-0113", 

545 "0000-0001-6419-1668", "0000-0001-9831-6254", 

546 "0000-0002-1844-174x", "0000-0002-5266-9991", 

547 "0000-0002-6396-4603", "0000-0003-0585-8228", 

548 "0000-0003-2921-5606", "0000-0003-4409-2261", 

549 "0000-0001-8128-1032", "0000-0002-0743-7905", 

550 "0000-0002-4847-4163", "0000-0002-8454-1159", 

551 "0000-0003-1094-3363", "0000-0003-1223-5934", 

552 "0000-0003-2712-1825", 

553 "0000-0001-6147-9981", "0000-0001-7392-1415", 

554 "0000-0001-7543-3466", "0000-0001-9940-8409", 

555 "0000-0002-0801-0890", "0000-0002-3019-5377", 

556 "0000-0002-5870-1542", "0000-0002-6210-8370", 

557 "0000-0002-6715-3533", "0000-0002-8013-9947", 

558 "0000-0002-9747-4928", "0000-0003-0621-9209", 

559 "0000-0003-1445-0291", "0000-0003-2185-3267", 

560 "0000-0003-2328-5769", "0000-0003-2713-8387", 

561 "0000-0003-4149-9760", 

562 } 

563 base_orcid_data = { 

564 "person": { 

565 "name": {"family-name": {"value": "Test"}, "given-names": {"value": "User"}}, 

566 "emails": {"email": []}, 

567 "external-identifiers": {"external-identifier": []}, 

568 }, 

569 "history": { 

570 "submission-date": {"value": 1351641600000}, 

571 "last-modified-date": {"value": 1700000000000}, 

572 }, 

573 } 

574 

575 def orcid_callback(request): 

576 orcid = request.url.split("/")[-1] 

577 if orcid in specific_orcids: 

578 return (200, {}, json_module.dumps(specific_orcids[orcid])) 

579 if orcid in generic_valid_orcids: 

580 data = dict(base_orcid_data) 

581 data["orcid-identifier"] = {"path": orcid} 

582 return (200, {}, json_module.dumps(data)) 

583 return (404, {}, "") 

584 

585 rsps.add_callback( 

586 responses.GET, 

587 re.compile(r"https://pub\.orcid\.org/v3\.0/.*"), 

588 callback=orcid_callback, 

589 content_type="application/json", 

590 ) 

591 

592 

593def _register_openalex_mocks(rsps: responses.RequestsMock) -> None: 

594 valid_works = { 

595 "W2013228336": {"id": "https://openalex.org/W2013228336", "title": "Example"}, 

596 "W748315831": {"id": "https://openalex.org/W748315831", "title": "Example 2"}, 

597 } 

598 valid_sources = { 

599 "S4210229581": { 

600 "id": "https://openalex.org/S4210229581", 

601 "display_name": "Example Source", 

602 }, 

603 } 

604 

605 def openalex_callback(request): 

606 url = request.url 

607 for work_id, data in valid_works.items(): 

608 if work_id in url: 

609 return (200, {}, json_module.dumps(data)) 

610 for source_id, data in valid_sources.items(): 

611 if source_id in url: 

612 return (200, {}, json_module.dumps(data)) 

613 return (404, {}, "") 

614 

615 rsps.add_callback( 

616 responses.GET, 

617 re.compile(r"https://api\.openalex\.org/.*"), 

618 callback=openalex_callback, 

619 content_type="application/json", 

620 ) 

621 

622 

623def _register_pmc_mocks(rsps: responses.RequestsMock) -> None: 

624 valid_pmcs = { 

625 "PMC8384044": { 

626 "records": [ 

627 {"pmcid": "PMC8384044", "pmid": "34356789", "doi": "10.1234/example1"} 

628 ] 

629 }, 

630 "PMC6716460": { 

631 "records": [ 

632 {"pmcid": "PMC6716460", "pmid": "31456789", "doi": "10.1234/example2"} 

633 ] 

634 }, 

635 "PMC3928621": { 

636 "records": [ 

637 {"pmcid": "PMC3928621", "pmid": "24484640", "doi": "10.2307/3053861"} 

638 ] 

639 }, 

640 "PMC5555555": { 

641 "records": [ 

642 {"pmcid": "PMC5555555", "pmid": "12345678", "doi": "10.1234/example3"} 

643 ] 

644 }, 

645 "PMC2873764": { 

646 "records": [ 

647 {"pmcid": "PMC2873764", "pmid": "20662931", "doi": "10.1186/1471-2164-11-245"} 

648 ] 

649 }, 

650 "PMC1236277": {"records": [{"pmcid": "PMC1236277", "pmid": "16277675"}]}, 

651 "PMC329969": {"records": [{"pmcid": "PMC329969", "pmid": "1387883"}]}, 

652 "PMC3429885": {"records": [{"pmcid": "PMC3429885", "pmid": "22952459"}]}, 

653 "PMC4206050": {"records": [{"pmcid": "PMC4206050", "pmid": "25340169"}]}, 

654 "PMC4634289": {"records": [{"pmcid": "PMC4634289", "pmid": "29147199"}]}, 

655 "PMC4910730": {"records": [{"pmcid": "PMC4910730", "pmid": "27467927"}]}, 

656 "PMC4933747": {"records": [{"pmcid": "PMC4933747", "pmid": "27429628"}]}, 

657 "PMC5649856": {"records": [{"pmcid": "PMC5649856", "pmid": "29147199"}]}, 

658 "PMC6069818": {"records": [{"pmcid": "PMC6069818", "pmid": "30064399"}]}, 

659 "PMC1042030": {"records": [{"pmcid": "PMC1042030", "pmid": "1581770"}]}, 

660 "PMC1042034": {"records": [{"pmcid": "PMC1042034", "pmid": "2322507"}]}, 

661 "PMC1297570": {"records": [{"pmcid": "PMC1297570", "pmid": "16277675"}]}, 

662 "PMC3618337": {"records": [{"pmcid": "PMC3618337", "pmid": "23577161"}]}, 

663 "PMC4005913": {"records": [{"pmcid": "PMC4005913", "pmid": "24632350"}]}, 

664 "PMC5777326": {"records": [{"pmcid": "PMC5777326", "pmid": "29416127"}]}, 

665 "PMC5812102": {"records": [{"pmcid": "PMC5812102", "pmid": "29456979"}]}, 

666 "PMC6025535": {"records": [{"pmcid": "PMC6025535", "pmid": "29890726"}]}, 

667 "PMC6121471": {"records": [{"pmcid": "PMC6121471", "pmid": "30096915"}]}, 

668 "PMC5411193": {"records": [{"pmcid": "PMC5411193", "pmid": "28383409"}]}, 

669 "PMC1191243": {"records": [{"pmcid": "PMC1191243", "pmid": "16277675"}]}, 

670 "PMC3869588": {"records": [{"pmcid": "PMC3869588", "pmid": "24363676"}]}, 

671 "PMC3885986": {"records": [{"pmcid": "PMC3885986", "pmid": "24409128"}]}, 

672 "PMC3922134": {"records": [{"pmcid": "PMC3922134", "pmid": "24479789"}]}, 

673 "PMC4377162": {"records": [{"pmcid": "PMC4377162", "pmid": "25893183"}]}, 

674 "PMC5058700": {"records": [{"pmcid": "PMC5058700", "pmid": "27105522"}]}, 

675 "PMC5840246": {"records": [{"pmcid": "PMC5840246", "pmid": "29305643"}]}, 

676 } 

677 

678 def pmc_callback(request): 

679 url = request.url 

680 for pmc_id, data in valid_pmcs.items(): 

681 if pmc_id in url: 

682 return (200, {}, json_module.dumps(data)) 

683 return (200, {}, json_module.dumps({"records": [{"status": "error", "errmsg": "not found"}]})) 

684 

685 rsps.add_callback( 

686 responses.GET, 

687 re.compile(r".*ncbi\.nlm\.nih\.gov/pmc/utils/idconv/.*"), 

688 callback=pmc_callback, 

689 content_type="application/json", 

690 ) 

691 

692 

693def _register_datacite_mocks(rsps: responses.RequestsMock) -> None: 

694 def datacite_callback(request): 

695 return (404, {}, "") 

696 

697 rsps.add_callback( 

698 responses.GET, 

699 re.compile(r"https://api\.datacite\.org/dois/.*"), 

700 callback=datacite_callback, 

701 content_type="application/json", 

702 ) 

703 

704 

705def _register_jid_mocks(rsps: responses.RequestsMock) -> None: 

706 valid_jids = {"otoljpn1970", "jscej1944b", "japeoj"} 

707 

708 valid_xml = """<?xml version="1.0" encoding="UTF-8"?> 

709<result xmlns="http://www.w3.org/2005/Atom"> 

710 <status>0</status> 

711 <entry><title>Example Journal</title></entry> 

712</result>""" 

713 

714 invalid_xml = """<?xml version="1.0" encoding="UTF-8"?> 

715<result xmlns="http://www.w3.org/2005/Atom"> 

716 <status>ERR_001</status> 

717</result>""" 

718 

719 def jid_api_callback(request): 

720 url = request.url 

721 for jid in valid_jids: 

722 if f"cdjournal={jid}" in url: 

723 return (200, {}, valid_xml) 

724 return (200, {}, invalid_xml) 

725 

726 rsps.add_callback( 

727 responses.GET, 

728 re.compile(r"https://api\.jstage\.jst\.go\.jp/searchapi/.*"), 

729 callback=jid_api_callback, 

730 content_type="application/xml", 

731 ) 

732 

733 def jid_browse_callback(request): 

734 url = request.url 

735 for jid in valid_jids: 

736 if jid in url: 

737 return (200, {}, '<html><div id="page-content">Journal Page</div></html>') 

738 return (404, {}, "") 

739 

740 rsps.add_callback( 

741 responses.GET, 

742 re.compile(r"https://www\.jstage\.jst\.go\.jp/browse/.*"), 

743 callback=jid_browse_callback, 

744 content_type="text/html", 

745 ) 

746 

747 

748def _register_ror_mocks(rsps: responses.RequestsMock) -> None: 

749 valid_rors = { 

750 "040jc3p57", "01111rn36", "0138va192", "00wb4mk85", 

751 "03ztgj037", "03ztgj039", "041qv0h25", "04wxnsj81", "04wxnsj89", 

752 } 

753 

754 def ror_callback(request): 

755 url = request.url 

756 for ror_id in valid_rors: 

757 if ror_id in url: 

758 return (200, {}, json_module.dumps({"id": f"https://ror.org/{ror_id}", "name": "Example Org"})) 

759 return (404, {}, "") 

760 

761 rsps.add_callback( 

762 responses.GET, 

763 re.compile(r"https://api\.ror\.org/organizations/.*"), 

764 callback=ror_callback, 

765 content_type="application/json", 

766 ) 

767 

768 

769def _register_viaf_mocks(rsps: responses.RequestsMock) -> None: 

770 valid_viafs = { 

771 "5604148947771454950004", "234145033", "56752857", 

772 "148463773", "102333412", 

773 } 

774 

775 def viaf_callback(request): 

776 url = request.url 

777 for viaf_id in valid_viafs: 

778 if viaf_id in url: 

779 return (200, {}, json_module.dumps({ 

780 "ns1:VIAFCluster": {"ns1:viafID": viaf_id} 

781 })) 

782 return (404, {}, "") 

783 

784 rsps.add_callback( 

785 responses.GET, 

786 re.compile(r"https?://viaf\.org/viaf/.*"), 

787 callback=viaf_callback, 

788 content_type="application/json", 

789 ) 

790 

791 

792def _register_wikidata_mocks(rsps: responses.RequestsMock) -> None: 

793 valid_wikidata_ids = {"Q34433", "Q24698708", "Q15767074", "Q7842", "Q42"} 

794 

795 def wikidata_callback(request): 

796 url = request.url 

797 for wikidata_id in valid_wikidata_ids: 

798 if wikidata_id in url: 

799 return (200, {}, json_module.dumps({ 

800 "entities": {wikidata_id: {"id": wikidata_id, "type": "item"}} 

801 })) 

802 return (404, {}, "") 

803 

804 rsps.add_callback( 

805 responses.GET, 

806 re.compile(r"https://www\.wikidata\.org/wiki/Special:EntityData/.*"), 

807 callback=wikidata_callback, 

808 content_type="application/json", 

809 ) 

810 

811 

812def _register_wikipedia_mocks(rsps: responses.RequestsMock) -> None: 

813 valid_wikipedia_ids = {"30456", "43744177"} 

814 

815 def wikipedia_callback(request): 

816 url = request.url 

817 for wiki_id in valid_wikipedia_ids: 

818 if f"pageids={wiki_id}" in url or f"pageids%3D{wiki_id}" in url: 

819 return (200, {}, json_module.dumps({ 

820 "query": {"pages": {wiki_id: {"pageid": int(wiki_id), "title": "Example Page"}}} 

821 })) 

822 return (200, {}, json_module.dumps({"query": {"pages": {}}})) 

823 

824 rsps.add_callback( 

825 responses.GET, 

826 re.compile(r"https://en\.wikipedia\.org/w/api\.php.*"), 

827 callback=wikipedia_callback, 

828 content_type="application/json", 

829 ) 

830 

831 

832def _register_medra_mocks(rsps: responses.RequestsMock) -> None: 

833 medra_article_xml = """<?xml version="1.0" encoding="UTF-8"?> 

834<ONIXDOISerialArticleWorkRegistrationMessage xmlns="http://www.editeur.org/onix/DOIMetadata/2.0"> 

835 <Header> 

836 <FromCompany>mEDRA</FromCompany> 

837 <FromEmail>medra@medra.org</FromEmail> 

838 <ToCompany>PublicService</ToCompany> 

839 <SentDate>20260305</SentDate> 

840 </Header> 

841 <DOISerialArticleWork> 

842 <NotificationType>07</NotificationType> 

843 <DOI>10.3233/DS-210053</DOI> 

844 <DOIWebsiteLink>https://www.medra.org/servlet/aliasResolver?alias=iospress&amp;doi=10.3233/DS-210053</DOIWebsiteLink> 

845 <RegistrantName>Default Registrant</RegistrantName> 

846 <RegistrationAuthority>mEDRA</RegistrationAuthority> 

847 <SerialPublication> 

848 <SerialWork> 

849 <WorkIdentifier> 

850 <WorkIDType>16</WorkIDType> 

851 <IDValue>2451-8484</IDValue> 

852 </WorkIdentifier> 

853 <Title> 

854 <TitleType>01</TitleType> 

855 <TitleText>Data Science</TitleText> 

856 </Title> 

857 <Publisher> 

858 <PublishingRole>01</PublishingRole> 

859 <PublisherName>IOS Press</PublisherName> 

860 </Publisher> 

861 <CountryOfPublication>NL</CountryOfPublication> 

862 </SerialWork> 

863 <SerialVersion> 

864 <ProductIdentifier> 

865 <ProductIDType>07</ProductIDType> 

866 <IDValue>24518492</IDValue> 

867 </ProductIdentifier> 

868 <ProductForm>JD</ProductForm> 

869 </SerialVersion> 

870 <SerialVersion> 

871 <ProductIdentifier> 

872 <ProductIDType>07</ProductIDType> 

873 <IDValue>24518484</IDValue> 

874 </ProductIdentifier> 

875 <ProductForm>JB</ProductForm> 

876 </SerialVersion> 

877 </SerialPublication> 

878 <JournalIssue> 

879 <JournalVolumeNumber>5</JournalVolumeNumber> 

880 <JournalIssueNumber>2</JournalIssueNumber> 

881 <JournalIssueDate> 

882 <DateFormat>00</DateFormat> 

883 <Date>20220720</Date> 

884 </JournalIssueDate> 

885 </JournalIssue> 

886 <ContentItem> 

887 <TextItem> 

888 <TextItemType>10</TextItemType> 

889 <PageRun> 

890 <FirstPageNumber>97</FirstPageNumber> 

891 <LastPageNumber>138</LastPageNumber> 

892 </PageRun> 

893 </TextItem> 

894 <Title> 

895 <TitleType>01</TitleType> 

896 <TitleText>Packaging research artefacts with RO-Crate</TitleText> 

897 </Title> 

898 <Contributor> 

899 <SequenceNumber>1</SequenceNumber> 

900 <ContributorRole>A01</ContributorRole> 

901 <NameIdentifier><NameIDType>21</NameIDType><IDValue>https://orcid.org/0000-0001-9842-9718</IDValue></NameIdentifier> 

902 <PersonNameInverted>Soiland-Reyes, Stian</PersonNameInverted> 

903 </Contributor> 

904 <Contributor> 

905 <SequenceNumber>2</SequenceNumber> 

906 <ContributorRole>A01</ContributorRole> 

907 <NameIdentifier><NameIDType>21</NameIDType><IDValue>https://orcid.org/0000-0002-3545-944X</IDValue></NameIdentifier> 

908 <PersonNameInverted>Sefton, Peter</PersonNameInverted> 

909 </Contributor> 

910 <Contributor> 

911 <SequenceNumber>3</SequenceNumber> 

912 <ContributorRole>A01</ContributorRole> 

913 <NameIdentifier><NameIDType>21</NameIDType><IDValue>https://orcid.org/0000-0003-1304-1939</IDValue></NameIdentifier> 

914 <PersonNameInverted>Crosas, Mercè</PersonNameInverted> 

915 </Contributor> 

916 <Contributor> 

917 <SequenceNumber>4</SequenceNumber> 

918 <ContributorRole>A01</ContributorRole> 

919 <NameIdentifier><NameIDType>21</NameIDType><IDValue>https://orcid.org/0000-0003-3986-0510</IDValue></NameIdentifier> 

920 <PersonNameInverted>Castro, Leyla Jael</PersonNameInverted> 

921 </Contributor> 

922 <Contributor> 

923 <SequenceNumber>5</SequenceNumber> 

924 <ContributorRole>A01</ContributorRole> 

925 <NameIdentifier><NameIDType>21</NameIDType><IDValue>https://orcid.org/0000-0001-6565-5145</IDValue></NameIdentifier> 

926 <PersonNameInverted>Coppens, Frederik</PersonNameInverted> 

927 </Contributor> 

928 <Contributor> 

929 <SequenceNumber>6</SequenceNumber> 

930 <ContributorRole>A01</ContributorRole> 

931 <NameIdentifier><NameIDType>21</NameIDType><IDValue>https://orcid.org/0000-0002-4806-5140</IDValue></NameIdentifier> 

932 <PersonNameInverted>Fernández, José M.</PersonNameInverted> 

933 </Contributor> 

934 <Contributor> 

935 <SequenceNumber>7</SequenceNumber> 

936 <ContributorRole>A01</ContributorRole> 

937 <NameIdentifier><NameIDType>21</NameIDType><IDValue>https://orcid.org/0000-0003-0454-7145</IDValue></NameIdentifier> 

938 <PersonNameInverted>Garijo, Daniel</PersonNameInverted> 

939 </Contributor> 

940 <Contributor> 

941 <SequenceNumber>8</SequenceNumber> 

942 <ContributorRole>A01</ContributorRole> 

943 <NameIdentifier><NameIDType>21</NameIDType><IDValue>https://orcid.org/0000-0002-3079-6586</IDValue></NameIdentifier> 

944 <PersonNameInverted>Grüning, Björn</PersonNameInverted> 

945 </Contributor> 

946 <Contributor> 

947 <SequenceNumber>9</SequenceNumber> 

948 <ContributorRole>A01</ContributorRole> 

949 <NameIdentifier><NameIDType>21</NameIDType><IDValue>https://orcid.org/0000-0001-5383-6993</IDValue></NameIdentifier> 

950 <PersonNameInverted>La Rosa, Marco</PersonNameInverted> 

951 </Contributor> 

952 <Contributor> 

953 <SequenceNumber>10</SequenceNumber> 

954 <ContributorRole>A01</ContributorRole> 

955 <NameIdentifier><NameIDType>21</NameIDType><IDValue>https://orcid.org/0000-0001-8271-5429</IDValue></NameIdentifier> 

956 <PersonNameInverted>Leo, Simone</PersonNameInverted> 

957 </Contributor> 

958 <Contributor> 

959 <SequenceNumber>11</SequenceNumber> 

960 <ContributorRole>A01</ContributorRole> 

961 <NameIdentifier><NameIDType>21</NameIDType><IDValue>https://orcid.org/0000-0001-8131-2150</IDValue></NameIdentifier> 

962 <PersonNameInverted>Ó Carragáin, Eoghan</PersonNameInverted> 

963 </Contributor> 

964 <Contributor> 

965 <SequenceNumber>12</SequenceNumber> 

966 <ContributorRole>A01</ContributorRole> 

967 <NameIdentifier><NameIDType>21</NameIDType><IDValue>https://orcid.org/0000-0002-9648-6484</IDValue></NameIdentifier> 

968 <PersonNameInverted>Portier, Marc</PersonNameInverted> 

969 </Contributor> 

970 <Contributor> 

971 <SequenceNumber>13</SequenceNumber> 

972 <ContributorRole>A01</ContributorRole> 

973 <NameIdentifier><NameIDType>21</NameIDType><IDValue>https://orcid.org/0000-0003-1991-0533</IDValue></NameIdentifier> 

974 <PersonNameInverted>Trisovic, Ana</PersonNameInverted> 

975 </Contributor> 

976 <Contributor> 

977 <SequenceNumber>14</SequenceNumber> 

978 <ContributorRole>A01</ContributorRole> 

979 <PersonNameInverted>RO-Crate Community</PersonNameInverted> 

980 </Contributor> 

981 <Contributor> 

982 <SequenceNumber>15</SequenceNumber> 

983 <ContributorRole>A01</ContributorRole> 

984 <NameIdentifier><NameIDType>21</NameIDType><IDValue>https://orcid.org/0000-0003-0183-6910</IDValue></NameIdentifier> 

985 <PersonNameInverted>Groth, Paul</PersonNameInverted> 

986 </Contributor> 

987 <Contributor> 

988 <SequenceNumber>16</SequenceNumber> 

989 <ContributorRole>A01</ContributorRole> 

990 <NameIdentifier><NameIDType>21</NameIDType><IDValue>https://orcid.org/0000-0003-1219-2137</IDValue></NameIdentifier> 

991 <PersonNameInverted>Goble, Carole</PersonNameInverted> 

992 </Contributor> 

993 <Contributor> 

994 <SequenceNumber>17</SequenceNumber> 

995 <ContributorRole>B01</ContributorRole> 

996 <NameIdentifier><NameIDType>21</NameIDType><IDValue>https://orcid.org/0000-0003-0530-4305</IDValue></NameIdentifier> 

997 <PersonNameInverted>Peroni, Silvio</PersonNameInverted> 

998 </Contributor> 

999 <PublicationDate>20220720</PublicationDate> 

1000 </ContentItem> 

1001 </DOISerialArticleWork> 

1002</ONIXDOISerialArticleWorkRegistrationMessage>""" 

1003 

1004 medra_book_xml = """<?xml version="1.0" encoding="UTF-8"?> 

1005<ONIXDOIMonographicProductRegistrationMessage xmlns="http://www.editeur.org/onix/DOIMetadata/2.0"> 

1006 <Header> 

1007 <FromCompany>mEDRA</FromCompany> 

1008 <FromEmail>medra@medra.org</FromEmail> 

1009 <ToCompany>PublicService</ToCompany> 

1010 <SentDate>20260305</SentDate> 

1011 </Header> 

1012 <DOIMonographicProduct> 

1013 <NotificationType>06</NotificationType> 

1014 <DOI>10.23775/20221026</DOI> 

1015 <DOIWebsiteLink>https://perosh.eu/wp-content/uploads/2022/10/Book-of-Abstracts_2nd-PEROSH-PWL-conference_DOI-10.23775-20221026.pdf</DOIWebsiteLink> 

1016 <RegistrantName>PEROSH</RegistrantName> 

1017 <RegistrationAuthority>mEDRA</RegistrationAuthority> 

1018 <ProductForm>DA</ProductForm> 

1019 <Title> 

1020 <TitleType>01</TitleType> 

1021 <TitleText>Book of Abstract: 2nd International PEROSH conference on Prolonging Working Life</TitleText> 

1022 </Title> 

1023 <Contributor> 

1024 <SequenceNumber>1</SequenceNumber> 

1025 <ContributorRole>A01</ContributorRole> 

1026 <CorporateName>PEROSH member institutes</CorporateName> 

1027 </Contributor> 

1028 <Language> 

1029 <LanguageRole>01</LanguageRole> 

1030 <LanguageCode>eng</LanguageCode> 

1031 </Language> 

1032 <Publisher> 

1033 <PublishingRole>01</PublishingRole> 

1034 <PublisherName>PEROSH</PublisherName> 

1035 </Publisher> 

1036 <CountryOfPublication>SE</CountryOfPublication> 

1037 <PublicationDate>202209</PublicationDate> 

1038 </DOIMonographicProduct> 

1039</ONIXDOIMonographicProductRegistrationMessage>""" 

1040 

1041 medra_series_xml = """<?xml version="1.0" encoding="UTF-8"?> 

1042<ONIXDOISerialTitleWorkRegistrationMessage xmlns="http://www.editeur.org/onix/DOIMetadata/2.0"> 

1043 <Header> 

1044 <FromCompany>mEDRA</FromCompany> 

1045 <FromEmail>medra@medra.org</FromEmail> 

1046 <ToCompany>PublicService</ToCompany> 

1047 <SentDate>20260305</SentDate> 

1048 </Header> 

1049 <DOISerialTitleWork> 

1050 <NotificationType>07</NotificationType> 

1051 <DOI>10.17426/58141</DOI> 

1052 <DOIWebsiteLink>https://www.centroricercheroma.it/prodotto/laquila-oltre-i-terremoti-costruzioni-e-ricostruzioni-della-citta/</DOIWebsiteLink> 

1053 <RegistrantName>CROMA - UNIVERSITÀ ROMA TRE</RegistrantName> 

1054 <RegistrationAuthority>mEDRA</RegistrationAuthority> 

1055 <SerialPublication> 

1056 <SerialWork> 

1057 <Title language="ita"> 

1058 <TitleType>01</TitleType> 

1059 <TitleText>L’Aquila oltre i terremoti. Costruzioni e ricostruzioni della città a cura di Simonetta Ciranna e Manuel Vaquero Piñeiro</TitleText> 

1060 </Title> 

1061 <Publisher> 

1062 <PublishingRole>01</PublishingRole> 

1063 <PublisherIdentifier> 

1064 <PublisherIDType>01</PublisherIDType> 

1065 <IDValue>1</IDValue> 

1066 </PublisherIdentifier> 

1067 <PublisherName>CROMA - UNIVERSITÀ ROMA TRE</PublisherName> 

1068 </Publisher> 

1069 <CountryOfPublication>IT</CountryOfPublication> 

1070 </SerialWork> 

1071 </SerialPublication> 

1072 <Language> 

1073 <LanguageRole>01</LanguageRole> 

1074 <LanguageCode>ita</LanguageCode> 

1075 </Language> 

1076 <PublishingStatus>04</PublishingStatus> 

1077 <DateFirstPublished> 

1078 <DateFormat>00</DateFormat> 

1079 <Date>20110708</Date> 

1080 </DateFirstPublished> 

1081 </DOISerialTitleWork> 

1082</ONIXDOISerialTitleWorkRegistrationMessage>""" 

1083 

1084 medra_chapter_xml = """<?xml version="1.0" encoding="UTF-8"?> 

1085<ONIXDOIMonographChapterWorkRegistrationMessage xmlns="http://www.editeur.org/onix/DOIMetadata/2.0"> 

1086 <Header> 

1087 <FromCompany>mEDRA</FromCompany> 

1088 <FromEmail>medra@medra.org</FromEmail> 

1089 <ToCompany>PublicService</ToCompany> 

1090 <SentDate>20260305</SentDate> 

1091 </Header> 

1092 <DOIMonographChapterWork> 

1093 <NotificationType>06</NotificationType> 

1094 <DOI>10.2357/9783739880303-105</DOI> 

1095 <DOIWebsiteLink>https://elibrary.narr.digital/book/99.0000/9783739880303</DOIWebsiteLink> 

1096 <DOIStructuralType>Abstraction</DOIStructuralType> 

1097 <DOIMode>Abstract</DOIMode> 

1098 <RegistrantName>Narr Francke Attempto</RegistrantName> 

1099 <MonographicPublication> 

1100 <MonographicWork> 

1101 <WorkIdentifier> 

1102 <WorkIDType>06</WorkIDType> 

1103 <IDValue>10.2357/9783739880303-105</IDValue> 

1104 </WorkIdentifier> 

1105 <Title language="ger"> 

1106 <TitleType>01</TitleType> 

1107 <TitleText>Bodenseeschifferpatent kompakt</TitleText> 

1108 <Subtitle>Motorboot und Segelboot</Subtitle> 

1109 </Title> 

1110 </MonographicWork> 

1111 </MonographicPublication> 

1112 <ContentItem> 

1113 <Title language="ger"> 

1114 <TitleType>01</TitleType> 

1115 <TitleText>Kapitel 11: Sturmwarndienst und Seenotrettung</TitleText> 

1116 </Title> 

1117 <Contributor> 

1118 <ContributorRole>A01</ContributorRole> 

1119 <PersonNameInverted>Wassermann, Matthias</PersonNameInverted> 

1120 </Contributor> 

1121 <Language> 

1122 <LanguageRole>01</LanguageRole> 

1123 <LanguageCode>ger</LanguageCode> 

1124 </Language> 

1125 <PublicationDate>2020</PublicationDate> 

1126 <CopyrightStatement> 

1127 <CopyrightYear>2020</CopyrightYear> 

1128 <CopyrightOwner> 

1129 <CorporateName>Narr Francke Attempto Verlag GmbH + Co. KG</CorporateName> 

1130 </CopyrightOwner> 

1131 </CopyrightStatement> 

1132 </ContentItem> 

1133 </DOIMonographChapterWork> 

1134</ONIXDOIMonographChapterWorkRegistrationMessage>""" 

1135 

1136 medra_responses = { 

1137 "10.3233/ds-210053": medra_article_xml, 

1138 "10.23775/20221026": medra_book_xml, 

1139 "10.17426/58141": medra_series_xml, 

1140 "10.2357/9783739880303-105": medra_chapter_xml, 

1141 } 

1142 

1143 def medra_callback(request): 

1144 url = request.url.lower() 

1145 for doi, xml in medra_responses.items(): 

1146 if doi.lower() in url: 

1147 return (200, {}, xml) 

1148 return (404, {}, "") 

1149 

1150 rsps.add_callback( 

1151 responses.GET, 

1152 re.compile(r"https://api\.medra\.org/metadata/.*"), 

1153 callback=medra_callback, 

1154 content_type="application/xml", 

1155 ) 

1156 

1157 

1158def _register_url_mocks(rsps: responses.RequestsMock) -> None: 

1159 valid_urls = { 

1160 "datacite.org", 

1161 "opencitations.net", 

1162 "nih.gov", 

1163 "it.wikipedia.org/wiki/Muro%20di%20Berlino", 

1164 "it.wikipedia.org/wiki/Muro di Berlino", 

1165 } 

1166 invalid_paths = {"invalid_url", "not%20a%20real%20page", "not a real page"} 

1167 

1168 def url_callback(request): 

1169 url = request.url.lower() 

1170 for invalid in invalid_paths: 

1171 if invalid in url: 

1172 return (404, {}, "") 

1173 for valid in valid_urls: 

1174 if valid.lower() in url: 

1175 return (200, {}, "<html><body>Valid page</body></html>") 

1176 return (404, {}, "") 

1177 

1178 for domain in ["datacite.org", "opencitations.net", "nih.gov", "it.wikipedia.org"]: 

1179 rsps.add_callback( 

1180 responses.GET, 

1181 re.compile(rf"https?://(www\.)?{re.escape(domain)}.*"), 

1182 callback=url_callback, 

1183 content_type="text/html", 

1184 )