test_2_0_resource_api.py 35 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050
  1. import pdb
  2. import pytest
  3. from io import BytesIO
  4. from uuid import uuid4
  5. from rdflib import Literal, URIRef
  6. from lakesuperior import env
  7. from lakesuperior.api import resource as rsrc_api
  8. from lakesuperior.dictionaries.namespaces import ns_collection as nsc
  9. from lakesuperior.exceptions import (
  10. IncompatibleLdpTypeError, InvalidResourceError, ResourceNotExistsError,
  11. TombstoneError)
  12. from lakesuperior.model.ldp.ldpr import Ldpr, RES_CREATED, RES_UPDATED
  13. from lakesuperior.model.rdf.graph import Graph, from_rdf
  14. txn_ctx = env.app_globals.rdf_store.txn_ctx
  15. @pytest.fixture(scope='module')
  16. def random_uuid():
  17. return str(uuid.uuid4())
  18. @pytest.fixture
  19. def dc_rdf():
  20. return b'''
  21. PREFIX dcterms: <http://purl.org/dc/terms/>
  22. PREFIX ldp: <http://www.w3.org/ns/ldp#>
  23. <> a ldp:DirectContainer ;
  24. dcterms:title "Direct Container" ;
  25. ldp:membershipResource <info:fcres/member> ;
  26. ldp:hasMemberRelation dcterms:relation .
  27. '''
  28. @pytest.fixture
  29. def ic_rdf():
  30. return b'''
  31. PREFIX dcterms: <http://purl.org/dc/terms/>
  32. PREFIX ldp: <http://www.w3.org/ns/ldp#>
  33. PREFIX ore: <http://www.openarchives.org/ore/terms/>
  34. <> a ldp:IndirectContainer ;
  35. dcterms:title "Indirect Container" ;
  36. ldp:membershipResource <info:fcres/top_container> ;
  37. ldp:hasMemberRelation dcterms:relation ;
  38. ldp:insertedContentRelation ore:proxyFor .
  39. '''
  40. @pytest.mark.usefixtures('db')
  41. class TestResourceCRUD:
  42. '''
  43. Test interaction with the Resource API.
  44. '''
  45. def test_nodes_exist(self):
  46. """
  47. Verify whether nodes exist or not.
  48. """
  49. assert rsrc_api.exists('/') is True
  50. assert rsrc_api.exists('/{}'.format(uuid4())) is False
  51. def test_get_root_node_metadata(self):
  52. """
  53. Get the root node metadata.
  54. The ``dcterms:title`` property should NOT be included.
  55. """
  56. gr = rsrc_api.get_metadata('/')
  57. assert isinstance(gr, Graph)
  58. assert len(gr) == 9
  59. with txn_ctx():
  60. assert gr[gr.uri : nsc['rdf'].type : nsc['ldp'].Resource ]
  61. assert not gr[
  62. gr.uri : nsc['dcterms'].title : Literal("Repository Root")
  63. ]
  64. def test_get_root_node(self):
  65. """
  66. Get the root node.
  67. The ``dcterms:title`` property should be included.
  68. """
  69. rsrc = rsrc_api.get('/')
  70. assert isinstance(rsrc, Ldpr)
  71. gr = rsrc.imr
  72. assert len(gr) == 10
  73. with txn_ctx():
  74. assert gr[gr.uri : nsc['rdf'].type : nsc['ldp'].Resource ]
  75. assert gr[
  76. gr.uri : nsc['dcterms'].title : Literal('Repository Root')]
  77. def test_get_nonexisting_node(self):
  78. """
  79. Get a non-existing node.
  80. """
  81. with pytest.raises(ResourceNotExistsError):
  82. gr = rsrc_api.get('/{}'.format(uuid4()))
  83. def test_create_ldp_rs(self):
  84. """
  85. Create an RDF resource (LDP-RS) from a provided graph.
  86. """
  87. uid = '/rsrc_from_graph'
  88. uri = nsc['fcres'][uid]
  89. with txn_ctx():
  90. gr = from_rdf(
  91. data='<> a <http://ex.org/type#A> .', format='turtle',
  92. publicID=uri)
  93. evt, _ = rsrc_api.create_or_replace(uid, graph=gr)
  94. rsrc = rsrc_api.get(uid)
  95. with txn_ctx():
  96. assert rsrc.imr[
  97. rsrc.uri : nsc['rdf'].type : URIRef('http://ex.org/type#A')]
  98. assert rsrc.imr[
  99. rsrc.uri : nsc['rdf'].type : nsc['ldp'].RDFSource]
  100. def test_create_ldp_rs_literals(self):
  101. """
  102. Create an RDF resource (LDP-RS) containing different literal types.
  103. """
  104. uid = f'/{uuid4()}'
  105. uri = nsc['fcres'][uid]
  106. with txn_ctx():
  107. gr = from_rdf(
  108. data = '''
  109. <>
  110. <urn:p:1> 1 ;
  111. <urn:p:2> "Untyped Literal" ;
  112. <urn:p:3> "Typed Literal"^^<http://www.w3.org/2001/XMLSchema#string> ;
  113. <urn:p:4> "2019-09-26"^^<http://www.w3.org/2001/XMLSchema#date> ;
  114. <urn:p:5> "Lang-tagged Literal"@en-US ;
  115. .
  116. ''', format='turtle',
  117. publicID=uri)
  118. evt, _ = rsrc_api.create_or_replace(uid, graph=gr)
  119. rsrc = rsrc_api.get(uid)
  120. with txn_ctx():
  121. assert rsrc.imr[
  122. rsrc.uri : URIRef('urn:p:1') :
  123. Literal('1', datatype=nsc['xsd'].integer)]
  124. assert rsrc.imr[
  125. rsrc.uri : URIRef('urn:p:2') : Literal('Untyped Literal')]
  126. assert rsrc.imr[
  127. rsrc.uri : URIRef('urn:p:3') :
  128. Literal('Typed Literal', datatype=nsc['xsd'].string)]
  129. assert rsrc.imr[
  130. rsrc.uri : URIRef('urn:p:4') :
  131. Literal('2019-09-26', datatype=nsc['xsd'].date)]
  132. assert rsrc.imr[
  133. rsrc.uri : URIRef('urn:p:5') :
  134. Literal('Lang-tagged Literal', lang='en-US')]
  135. def test_create_ldp_nr(self):
  136. """
  137. Create a non-RDF resource (LDP-NR).
  138. """
  139. uid = '/{}'.format(uuid4())
  140. data = b'Hello. This is some dummy content.'
  141. rsrc_api.create_or_replace(
  142. uid, stream=BytesIO(data), mimetype='text/plain')
  143. rsrc = rsrc_api.get(uid)
  144. with rsrc.imr.store.txn_ctx():
  145. assert rsrc.content.read() == data
  146. def test_replace_rsrc(self):
  147. uid = '/test_replace'
  148. uri = nsc['fcres'][uid]
  149. with txn_ctx():
  150. gr1 = from_rdf(
  151. data='<> a <http://ex.org/type#A> .', format='turtle',
  152. publicID=uri
  153. )
  154. evt, _ = rsrc_api.create_or_replace(uid, graph=gr1)
  155. assert evt == RES_CREATED
  156. rsrc = rsrc_api.get(uid)
  157. with txn_ctx():
  158. assert rsrc.imr[
  159. rsrc.uri : nsc['rdf'].type : URIRef('http://ex.org/type#A')]
  160. assert rsrc.imr[
  161. rsrc.uri : nsc['rdf'].type : nsc['ldp'].RDFSource]
  162. with txn_ctx():
  163. gr2 = from_rdf(
  164. data='<> a <http://ex.org/type#B> .', format='turtle',
  165. publicID=uri
  166. )
  167. #pdb.set_trace()
  168. evt, _ = rsrc_api.create_or_replace(uid, graph=gr2)
  169. assert evt == RES_UPDATED
  170. rsrc = rsrc_api.get(uid)
  171. with txn_ctx():
  172. assert not rsrc.imr[
  173. rsrc.uri : nsc['rdf'].type : URIRef('http://ex.org/type#A')]
  174. assert rsrc.imr[
  175. rsrc.uri : nsc['rdf'].type : URIRef('http://ex.org/type#B')]
  176. assert rsrc.imr[
  177. rsrc.uri : nsc['rdf'].type : nsc['ldp'].RDFSource]
  178. def test_replace_incompatible_type(self):
  179. """
  180. Verify replacing resources with incompatible type.
  181. Replacing a LDP-NR with a LDP-RS, or vice versa, should fail.
  182. """
  183. uid_rs = '/test_incomp_rs'
  184. uid_nr = '/test_incomp_nr'
  185. data = b'mock binary content'
  186. with txn_ctx():
  187. gr = from_rdf(
  188. data='<> a <http://ex.org/type#A> .', format='turtle',
  189. publicID=nsc['fcres'][uid_rs]
  190. )
  191. rsrc_api.create_or_replace(uid_rs, graph=gr)
  192. rsrc_api.create_or_replace(
  193. uid_nr, stream=BytesIO(data), mimetype='text/plain')
  194. with pytest.raises(IncompatibleLdpTypeError):
  195. rsrc_api.create_or_replace(uid_nr, graph=gr)
  196. with pytest.raises(IncompatibleLdpTypeError):
  197. rsrc_api.create_or_replace(
  198. uid_rs, stream=BytesIO(data), mimetype='text/plain')
  199. with pytest.raises(IncompatibleLdpTypeError):
  200. rsrc_api.create_or_replace(uid_nr)
  201. def test_delta_update(self):
  202. """
  203. Update a resource with two sets of add and remove triples.
  204. """
  205. uid = '/test_delta_patch'
  206. uri = nsc['fcres'][uid]
  207. init_trp = {
  208. (URIRef(uri), nsc['rdf'].type, nsc['foaf'].Person),
  209. (URIRef(uri), nsc['foaf'].name, Literal('Joe Bob')),
  210. }
  211. remove_trp = {
  212. (URIRef(uri), nsc['rdf'].type, nsc['foaf'].Person),
  213. }
  214. add_trp = {
  215. (URIRef(uri), nsc['rdf'].type, nsc['foaf'].Organization),
  216. }
  217. with txn_ctx():
  218. gr = Graph(data=init_trp)
  219. rsrc_api.create_or_replace(uid, graph=gr)
  220. rsrc_api.update_delta(uid, remove_trp, add_trp)
  221. rsrc = rsrc_api.get(uid)
  222. with txn_ctx():
  223. assert rsrc.imr[
  224. rsrc.uri : nsc['rdf'].type : nsc['foaf'].Organization]
  225. assert rsrc.imr[rsrc.uri : nsc['foaf'].name : Literal('Joe Bob')]
  226. assert not rsrc.imr[
  227. rsrc.uri : nsc['rdf'].type : nsc['foaf'].Person]
  228. def test_delta_update_wildcard(self):
  229. """
  230. Update a resource using wildcard modifiers.
  231. """
  232. uid = '/test_delta_patch_wc'
  233. uri = nsc['fcres'][uid]
  234. init_trp = {
  235. (URIRef(uri), nsc['rdf'].type, nsc['foaf'].Person),
  236. (URIRef(uri), nsc['foaf'].name, Literal('Joe Bob')),
  237. (URIRef(uri), nsc['foaf'].name, Literal('Joe Average Bob')),
  238. (URIRef(uri), nsc['foaf'].name, Literal('Joe 12oz Bob')),
  239. }
  240. remove_trp = {
  241. (URIRef(uri), nsc['foaf'].name, None),
  242. }
  243. add_trp = {
  244. (URIRef(uri), nsc['foaf'].name, Literal('Joan Knob')),
  245. }
  246. with txn_ctx():
  247. gr = Graph(data=init_trp)
  248. rsrc_api.create_or_replace(uid, graph=gr)
  249. rsrc_api.update_delta(uid, remove_trp, add_trp)
  250. rsrc = rsrc_api.get(uid)
  251. with txn_ctx():
  252. assert rsrc.imr[
  253. rsrc.uri : nsc['rdf'].type : nsc['foaf'].Person]
  254. assert rsrc.imr[rsrc.uri : nsc['foaf'].name : Literal('Joan Knob')]
  255. assert not rsrc.imr[rsrc.uri : nsc['foaf'].name : Literal('Joe Bob')]
  256. assert not rsrc.imr[
  257. rsrc.uri : nsc['foaf'].name : Literal('Joe Average Bob')]
  258. assert not rsrc.imr[
  259. rsrc.uri : nsc['foaf'].name : Literal('Joe 12oz Bob')]
  260. def test_sparql_update(self):
  261. """
  262. Update a resource using a SPARQL Update string.
  263. Use a mix of relative and absolute URIs.
  264. """
  265. uid = '/test_sparql'
  266. rdf_data = b'<> <http://purl.org/dc/terms/title> "Original title." .'
  267. update_str = '''DELETE {
  268. <> <http://purl.org/dc/terms/title> "Original title." .
  269. } INSERT {
  270. <> <http://purl.org/dc/terms/title> "Title #2." .
  271. <info:fcres/test_sparql>
  272. <http://purl.org/dc/terms/title> "Title #3." .
  273. <#h1> <http://purl.org/dc/terms/title> "This is a hash." .
  274. } WHERE {
  275. }'''
  276. rsrc_api.create_or_replace(uid, rdf_data=rdf_data, rdf_fmt='turtle')
  277. ver_uid = rsrc_api.create_version(uid, 'v1').split('fcr:versions/')[-1]
  278. rsrc = rsrc_api.update(uid, update_str)
  279. with txn_ctx():
  280. assert (
  281. (rsrc.uri, nsc['dcterms'].title, Literal('Original title.'))
  282. not in set(rsrc.imr))
  283. assert (
  284. (rsrc.uri, nsc['dcterms'].title, Literal('Title #2.'))
  285. in set(rsrc.imr))
  286. assert (
  287. (rsrc.uri, nsc['dcterms'].title, Literal('Title #3.'))
  288. in set(rsrc.imr))
  289. assert ((
  290. URIRef(str(rsrc.uri) + '#h1'),
  291. nsc['dcterms'].title, Literal('This is a hash.'))
  292. in set(rsrc.imr))
  293. def test_create_ldp_dc_post(self, dc_rdf):
  294. """
  295. Create an LDP Direct Container via POST.
  296. """
  297. rsrc_api.create_or_replace('/member')
  298. dc_rsrc = rsrc_api.create(
  299. '/', 'test_dc_post', rdf_data=dc_rdf, rdf_fmt='turtle')
  300. member_rsrc = rsrc_api.get('/member')
  301. with txn_ctx():
  302. assert nsc['ldp'].Container in dc_rsrc.ldp_types
  303. assert nsc['ldp'].DirectContainer in dc_rsrc.ldp_types
  304. def test_create_ldp_dc_put(self, dc_rdf):
  305. """
  306. Create an LDP Direct Container via PUT.
  307. """
  308. dc_uid = '/test_dc_put01'
  309. _, dc_rsrc = rsrc_api.create_or_replace(
  310. dc_uid, rdf_data=dc_rdf, rdf_fmt='turtle')
  311. member_rsrc = rsrc_api.get('/member')
  312. with txn_ctx():
  313. assert nsc['ldp'].Container in dc_rsrc.ldp_types
  314. assert nsc['ldp'].DirectContainer in dc_rsrc.ldp_types
  315. def test_add_dc_member(self, dc_rdf):
  316. """
  317. Add members to a direct container and verify special properties.
  318. """
  319. dc_uid = '/test_dc_put02'
  320. _, dc_rsrc = rsrc_api.create_or_replace(
  321. dc_uid, rdf_data=dc_rdf, rdf_fmt='turtle')
  322. child_uid = rsrc_api.create(dc_uid).uid
  323. member_rsrc = rsrc_api.get('/member')
  324. with txn_ctx():
  325. assert member_rsrc.imr[
  326. member_rsrc.uri: nsc['dcterms'].relation: nsc['fcres'][child_uid]]
  327. def test_create_ldp_dc_defaults1(self):
  328. """
  329. Create an LDP Direct Container with default values.
  330. """
  331. dc_rdf = b'''
  332. PREFIX dcterms: <http://purl.org/dc/terms/>
  333. PREFIX ldp: <http://www.w3.org/ns/ldp#>
  334. <> a ldp:DirectContainer ;
  335. ldp:membershipResource <info:fcres/member> .
  336. '''
  337. dc_uid = '/test_dc_defaults1'
  338. _, dc_rsrc = rsrc_api.create_or_replace(
  339. dc_uid, rdf_data=dc_rdf, rdf_fmt='turtle')
  340. child_uid = rsrc_api.create(dc_uid).uid
  341. member_rsrc = rsrc_api.get('/member')
  342. with txn_ctx():
  343. assert member_rsrc.imr[
  344. member_rsrc.uri: nsc['ldp'].member: nsc['fcres'][child_uid]
  345. ]
  346. def test_create_ldp_dc_defaults2(self):
  347. """
  348. Create an LDP Direct Container with default values.
  349. """
  350. dc_rdf = b'''
  351. PREFIX dcterms: <http://purl.org/dc/terms/>
  352. PREFIX ldp: <http://www.w3.org/ns/ldp#>
  353. <> a ldp:DirectContainer ;
  354. ldp:hasMemberRelation dcterms:relation .
  355. '''
  356. dc_uid = '/test_dc_defaults2'
  357. _, dc_rsrc = rsrc_api.create_or_replace(
  358. dc_uid, rdf_data=dc_rdf, rdf_fmt='turtle')
  359. child_uid = rsrc_api.create(dc_uid).uid
  360. member_rsrc = rsrc_api.get(dc_uid)
  361. with txn_ctx():
  362. #import pdb; pdb.set_trace()
  363. assert member_rsrc.imr[
  364. member_rsrc.uri: nsc['dcterms'].relation:
  365. nsc['fcres'][child_uid]]
  366. def test_create_ldp_dc_defaults3(self):
  367. """
  368. Create an LDP Direct Container with default values.
  369. """
  370. dc_rdf = b'''
  371. PREFIX dcterms: <http://purl.org/dc/terms/>
  372. PREFIX ldp: <http://www.w3.org/ns/ldp#>
  373. <> a ldp:DirectContainer .
  374. '''
  375. dc_uid = '/test_dc_defaults3'
  376. _, dc_rsrc = rsrc_api.create_or_replace(
  377. dc_uid, rdf_data=dc_rdf, rdf_fmt='turtle')
  378. child_uid = rsrc_api.create(dc_uid, None).uid
  379. member_rsrc = rsrc_api.get(dc_uid)
  380. with txn_ctx():
  381. assert member_rsrc.imr[
  382. member_rsrc.uri: nsc['ldp'].member: nsc['fcres'][child_uid]]
  383. def test_indirect_container(self, ic_rdf):
  384. """
  385. Create an indirect container and verify special properties.
  386. """
  387. cont_uid = '/top_container'
  388. ic_uid = '{}/test_ic'.format(cont_uid)
  389. member_uid = '{}/ic_member'.format(ic_uid)
  390. target_uid = '/ic_target'
  391. ic_member_rdf = b'''
  392. PREFIX ore: <http://www.openarchives.org/ore/terms/>
  393. <> ore:proxyFor <info:fcres/ic_target> .'''
  394. rsrc_api.create_or_replace(cont_uid)
  395. rsrc_api.create_or_replace(target_uid)
  396. rsrc_api.create_or_replace(ic_uid, rdf_data=ic_rdf, rdf_fmt='turtle')
  397. rsrc_api.create_or_replace(
  398. member_uid, rdf_data=ic_member_rdf, rdf_fmt='turtle')
  399. ic_rsrc = rsrc_api.get(ic_uid)
  400. with txn_ctx():
  401. assert nsc['ldp'].Container in ic_rsrc.ldp_types
  402. assert nsc['ldp'].IndirectContainer in ic_rsrc.ldp_types
  403. assert nsc['ldp'].DirectContainer not in ic_rsrc.ldp_types
  404. member_rsrc = rsrc_api.get(member_uid)
  405. top_cont_rsrc = rsrc_api.get(cont_uid)
  406. with txn_ctx():
  407. assert top_cont_rsrc.imr[
  408. top_cont_rsrc.uri: nsc['dcterms'].relation:
  409. nsc['fcres'][target_uid]]
  410. # TODO WIP Complex test of all possible combinations of missing IC triples
  411. # falling back to default values.
  412. #def test_indirect_container_defaults(self):
  413. # """
  414. # Create an indirect container with various default values.
  415. # """
  416. # ic_rdf_base = b'''
  417. # PREFIX dcterms: <http://purl.org/dc/terms/>
  418. # PREFIX ldp: <http://www.w3.org/ns/ldp#>
  419. # PREFIX ore: <http://www.openarchives.org/ore/terms/>
  420. # <> a ldp:IndirectContainer ;
  421. # '''
  422. # ic_rdf_trp1 = '\nldp:membershipResource <info:fcres/top_container> ;'
  423. # ic_rdf_trp2 = '\nldp:hasMemberRelation dcterms:relation ;'
  424. # ic_rdf_trp3 = '\nldp:insertedContentRelation ore:proxyFor ;'
  425. # ic_def_rdf = [
  426. # ic_rdf_base + ic_rdf_trp1 + ic_trp2 + '\n.',
  427. # ic_rdf_base + ic_rdf_trp1 + ic_trp3 + '\n.',
  428. # ic_rdf_base + ic_rdf_trp2 + ic_trp3 + '\n.',
  429. # ic_rdf_base + ic_rdf_trp1 + '\n.',
  430. # ic_rdf_base + ic_rdf_trp2 + '\n.',
  431. # ic_rdf_base + ic_rdf_trp3 + '\n.',
  432. # ic_rdf_base + '\n.',
  433. # ]
  434. # target_uid = '/ic_target_def'
  435. # rsrc_api.create_or_replace(target_uid)
  436. # # Create several sets of indirect containers, each missing one or more
  437. # # triples from the original graph, which should be replaced by default
  438. # # values. All combinations are tried.
  439. # for i, ic_rdf in enumerate(ic_def_rdf):
  440. # cont_uid = f'/top_container_def{i}'
  441. # ic_uid = '{}/test_ic'.format(cont_uid)
  442. # member_uid = '{}/ic_member'.format(ic_uid)
  443. # rsrc_api.create_or_replace(cont_uid)
  444. # rsrc_api.create_or_replace(
  445. # ic_uid, rdf_data=ic_rdf, rdf_fmt='turtle'
  446. # )
  447. # ic_member_p = (
  448. # nsc['ore'].proxyFor if i in (1, 2, 5)
  449. # else nsc['ldp'].memberSubject
  450. # )
  451. # # WIP
  452. # #ic_member_o_uid = (
  453. # # 'ic_target_def' if i in (1, 2, 5)
  454. # # else nsc['ldp'].memberSubject
  455. # #)
  456. # ic_member_rdf = b'''
  457. # PREFIX ore: <http://www.openarchives.org/ore/terms/>
  458. # <> ore:proxyFor <info:fcres/ic_target_def> .'''
  459. # rsrc_api.create_or_replace(
  460. # member_uid, rdf_data=ic_member_rdf, rdf_fmt='turtle')
  461. # ic_rsrc = rsrc_api.get(ic_uid)
  462. # with txn_ctx():
  463. # assert nsc['ldp'].Container in ic_rsrc.ldp_types
  464. # assert nsc['ldp'].IndirectContainer in ic_rsrc.ldp_types
  465. # top_cont_rsrc = rsrc_api.get(cont_uid)
  466. # for i, ic_rdf in enumerate(ic_def_rdf):
  467. # member_rsrc = rsrc_api.get(member_uid)
  468. # with txn_ctx():
  469. # assert top_cont_rsrc.imr[
  470. # top_cont_rsrc.uri: nsc['dcterms'].relation:
  471. # nsc['fcres'][target_uid]]
  472. def test_user_data(self):
  473. '''
  474. Verify that only user-defined data are in user_data.
  475. '''
  476. data = b'''
  477. <> a <urn:t:1> ;
  478. <urn:p:1> "Property 1" ;
  479. <urn:p:2> <urn:o:2> .
  480. '''
  481. uid = f'/{uuid4()}'
  482. uri = nsc['fcres'][uid]
  483. rsrc_api.create_or_replace(uid, rdf_data=data, rdf_fmt='ttl')
  484. rsrc = rsrc_api.get(uid)
  485. with txn_ctx():
  486. ud_data = rsrc.user_data
  487. assert ud_data[uri: nsc['rdf'].type: URIRef('urn:t:1')]
  488. assert ud_data[uri: URIRef('urn:p:1'): Literal('Property 1')]
  489. assert ud_data[uri: URIRef('urn:p:2'): URIRef('urn:o:2')]
  490. assert not ud_data[uri: nsc['rdf'].type: nsc['ldp'].Resource]
  491. def test_types(self):
  492. '''
  493. Test server-managed and user-defined RDF types.
  494. '''
  495. data = b'''
  496. <> a <urn:t:1> , <urn:t:2> .
  497. '''
  498. uid = f'/{uuid4()}'
  499. uri = nsc['fcres'][uid]
  500. rsrc_api.create_or_replace(uid, rdf_data=data, rdf_fmt='ttl')
  501. rsrc = rsrc_api.get(uid)
  502. with txn_ctx():
  503. assert URIRef('urn:t:1') in rsrc.types
  504. assert URIRef('urn:t:1') in rsrc.user_types
  505. assert URIRef('urn:t:1') not in rsrc.ldp_types
  506. assert URIRef('urn:t:2') in rsrc.types
  507. assert URIRef('urn:t:2') in rsrc.user_types
  508. assert URIRef('urn:t:2') not in rsrc.ldp_types
  509. assert nsc['ldp'].Resource in rsrc.types
  510. assert nsc['ldp'].Resource not in rsrc.user_types
  511. assert nsc['ldp'].Resource in rsrc.ldp_types
  512. assert nsc['ldp'].Container in rsrc.types
  513. assert nsc['ldp'].Container not in rsrc.user_types
  514. assert nsc['ldp'].Container in rsrc.ldp_types
  515. def test_inbound_triples_ldprs(self):
  516. """ Test displaying of inbound triples for a LDP_RS. """
  517. src_uid = f'/{uuid4()}'
  518. src_uri = nsc['fcres'][src_uid]
  519. trg_uid = f'/{uuid4()}'
  520. trg_uri = nsc['fcres'][trg_uid]
  521. src_data = f'<> <urn:p:1> <{trg_uri}> .'.encode()
  522. trg_data = b'<> <urn:p:2> <urn:o:1> .'
  523. with txn_ctx(True):
  524. rsrc_api.create_or_replace(
  525. trg_uid, rdf_data=trg_data, rdf_fmt='ttl')
  526. rsrc_api.create_or_replace(
  527. src_uid, rdf_data=src_data, rdf_fmt='ttl')
  528. rsrc = rsrc_api.get(trg_uid, repr_options={'incl_inbound': True})
  529. with txn_ctx():
  530. assert (src_uri, URIRef('urn:p:1'), trg_uri) in rsrc.imr
  531. def test_inbound_triples_ldpnr(self):
  532. """ Test displaying of inbound triples for a LDP_NR. """
  533. src_uid = f'/{uuid4()}'
  534. src_uri = nsc['fcres'][src_uid]
  535. trg_uid = f'/{uuid4()}'
  536. trg_uri = nsc['fcres'][trg_uid]
  537. src_data = f'<> <urn:p:1> <{trg_uri}> .'.encode()
  538. trg_data = b'Some ASCII content.'
  539. with txn_ctx(True):
  540. rsrc_api.create_or_replace(
  541. trg_uid, stream=BytesIO(trg_data), mimetype='text/plain')
  542. rsrc_api.create_or_replace(
  543. src_uid, rdf_data=src_data, rdf_fmt='ttl')
  544. rsrc = rsrc_api.get(trg_uid, repr_options={'incl_inbound': True})
  545. with txn_ctx():
  546. assert (src_uri, URIRef('urn:p:1'), trg_uri) in rsrc.imr
  547. @pytest.mark.usefixtures('db')
  548. class TestRelativeUris:
  549. '''
  550. Test inserting and updating resources with relative URIs.
  551. '''
  552. def test_create_self_uri_rdf(self):
  553. """
  554. Create a resource with empty string ("self") URIs in the RDF body.
  555. """
  556. uid = '/reluri01'
  557. uri = nsc['fcres'][uid]
  558. data = '''
  559. <> a <urn:type:A> .
  560. <http://ex.org/external> <urn:pred:x> <> .
  561. '''
  562. rsrc_api.create_or_replace(uid, rdf_data=data, rdf_fmt='ttl')
  563. rsrc = rsrc_api.get(uid)
  564. with txn_ctx():
  565. assert rsrc.imr[uri: nsc['rdf']['type']: URIRef('urn:type:A')]
  566. assert rsrc.imr[
  567. URIRef('http://ex.org/external'): URIRef('urn:pred:x'): uri]
  568. def test_create_self_uri_graph(self):
  569. """
  570. Create a resource with empty string ("self") URIs in a RDFlib graph.
  571. """
  572. uid = '/reluri02'
  573. uri = nsc['fcres'][uid]
  574. gr = Graph()
  575. with txn_ctx():
  576. gr.add({
  577. (URIRef(''), nsc['rdf']['type'], URIRef('urn:type:A')),
  578. (
  579. URIRef('http://ex.org/external'),
  580. URIRef('urn:pred:x'), URIRef('')
  581. ),
  582. })
  583. rsrc_api.create_or_replace(uid, graph=gr)
  584. rsrc = rsrc_api.get(uid)
  585. with txn_ctx():
  586. assert rsrc.imr[uri: nsc['rdf']['type']: URIRef('urn:type:A')]
  587. assert rsrc.imr[
  588. URIRef('http://ex.org/external'): URIRef('urn:pred:x'): uri]
  589. def test_create_hash_uri_rdf(self):
  590. """
  591. Create a resource with empty string ("self") URIs in the RDF body.
  592. """
  593. uid = '/reluri03'
  594. uri = nsc['fcres'][uid]
  595. data = '''
  596. <#hash1> a <urn:type:A> .
  597. <http://ex.org/external> <urn:pred:x> <#hash2> .
  598. '''
  599. rsrc_api.create_or_replace(uid, rdf_data=data, rdf_fmt='ttl')
  600. rsrc = rsrc_api.get(uid)
  601. with txn_ctx():
  602. assert rsrc.imr[
  603. URIRef(str(uri) + '#hash1'): nsc['rdf'].type:
  604. URIRef('urn:type:A')]
  605. assert rsrc.imr[
  606. URIRef('http://ex.org/external'): URIRef('urn:pred:x'):
  607. URIRef(str(uri) + '#hash2')]
  608. @pytest.mark.skip
  609. def test_create_hash_uri_graph(self):
  610. """
  611. Create a resource with empty string ("self") URIs in a RDFlib graph.
  612. """
  613. uid = '/reluri04'
  614. uri = nsc['fcres'][uid]
  615. gr = Graph()
  616. with txn_ctx():
  617. gr.add({
  618. (URIRef('#hash1'), nsc['rdf']['type'], URIRef('urn:type:A')),
  619. (
  620. URIRef('http://ex.org/external'),
  621. URIRef('urn:pred:x'), URIRef('#hash2')
  622. )
  623. })
  624. rsrc_api.create_or_replace(uid, graph=gr)
  625. rsrc = rsrc_api.get(uid)
  626. with txn_ctx():
  627. assert rsrc.imr[
  628. URIRef(str(uri) + '#hash1'): nsc['rdf']['type']:
  629. URIRef('urn:type:A')]
  630. assert rsrc.imr[
  631. URIRef('http://ex.org/external'): URIRef('urn:pred:x'):
  632. URIRef(str(uri) + '#hash2')]
  633. @pytest.mark.skip(reason='RDFlib bug.')
  634. def test_create_child_uri_rdf(self):
  635. """
  636. Create a resource with empty string ("self") URIs in the RDF body.
  637. """
  638. uid = '/reluri05'
  639. uri = nsc['fcres'][uid]
  640. data = '''
  641. <child1> a <urn:type:A> .
  642. <http://ex.org/external> <urn:pred:x> <child2> .
  643. '''
  644. rsrc_api.create_or_replace(uid, rdf_data=data, rdf_fmt='ttl')
  645. rsrc = rsrc_api.get(uid)
  646. with txn_ctx():
  647. assert rsrc.imr[
  648. URIRef(str(uri) + '/child1'): nsc['rdf'].type:
  649. URIRef('urn:type:A')]
  650. assert rsrc.imr[
  651. URIRef('http://ex.org/external'): URIRef('urn:pred:x'):
  652. URIRef(str(uri) + '/child2')]
  653. @pytest.mark.skip(reason='RDFlib bug.')
  654. def test_create_child_uri_graph(self):
  655. """
  656. Create a resource with empty string ("self") URIs in the RDF body.
  657. """
  658. uid = '/reluri06'
  659. uri = nsc['fcres'][uid]
  660. gr = Graph()
  661. with txn_ctx():
  662. gr.add({
  663. (URIRef('child1'), nsc['rdf']['type'], URIRef('urn:type:A')),
  664. (
  665. URIRef('http://ex.org/external'),
  666. URIRef('urn:pred:x'), URIRef('child22')
  667. )
  668. })
  669. rsrc_api.create_or_replace(uid, graph=gr)
  670. rsrc = rsrc_api.get(uid)
  671. with txn_ctx():
  672. assert rsrc.imr[
  673. URIRef(str(uri) + '/child1'): nsc['rdf'].type:
  674. URIRef('urn:type:A')]
  675. assert rsrc.imr[
  676. URIRef('http://ex.org/external'): URIRef('urn:pred:x'):
  677. URIRef(str(uri) + '/child2')]
  678. @pytest.mark.usefixtures('db')
  679. class TestAdvancedDelete:
  680. '''
  681. Test resource version lifecycle.
  682. '''
  683. def test_soft_delete(self):
  684. """
  685. Soft-delete (bury) a resource.
  686. """
  687. uid = '/test_soft_delete01'
  688. rsrc_api.create_or_replace(uid)
  689. rsrc_api.delete(uid)
  690. with pytest.raises(TombstoneError):
  691. rsrc_api.get(uid)
  692. def test_resurrect(self):
  693. """
  694. Restore (resurrect) a soft-deleted resource.
  695. """
  696. uid = '/test_soft_delete02'
  697. rsrc_api.create_or_replace(uid)
  698. rsrc_api.delete(uid)
  699. rsrc_api.resurrect(uid)
  700. rsrc = rsrc_api.get(uid)
  701. with txn_ctx():
  702. assert nsc['ldp'].Resource in rsrc.ldp_types
  703. def test_hard_delete(self):
  704. """
  705. Hard-delete (forget) a resource.
  706. """
  707. uid = '/test_hard_delete01'
  708. rsrc_api.create_or_replace(uid)
  709. rsrc_api.delete(uid, False)
  710. with pytest.raises(ResourceNotExistsError):
  711. rsrc_api.get(uid)
  712. with pytest.raises(ResourceNotExistsError):
  713. rsrc_api.resurrect(uid)
  714. def test_delete_children(self):
  715. """
  716. Soft-delete a resource with children.
  717. """
  718. uid = '/test_soft_delete_children01'
  719. rsrc_api.create_or_replace(uid)
  720. for i in range(3):
  721. rsrc_api.create_or_replace('{}/child{}'.format(uid, i))
  722. rsrc_api.delete(uid)
  723. with pytest.raises(TombstoneError):
  724. rsrc_api.get(uid)
  725. for i in range(3):
  726. with pytest.raises(TombstoneError):
  727. rsrc_api.get('{}/child{}'.format(uid, i))
  728. # Cannot resurrect children of a tombstone.
  729. with pytest.raises(TombstoneError):
  730. rsrc_api.resurrect('{}/child{}'.format(uid, i))
  731. def test_resurrect_children(self):
  732. """
  733. Resurrect a resource with its children.
  734. This uses fixtures from the previous test.
  735. """
  736. uid = '/test_soft_delete_children01'
  737. rsrc_api.resurrect(uid)
  738. parent_rsrc = rsrc_api.get(uid)
  739. with txn_ctx():
  740. assert nsc['ldp'].Resource in parent_rsrc.ldp_types
  741. for i in range(3):
  742. child_rsrc = rsrc_api.get('{}/child{}'.format(uid, i))
  743. with txn_ctx():
  744. assert nsc['ldp'].Resource in child_rsrc.ldp_types
  745. def test_hard_delete_children(self):
  746. """
  747. Hard-delete (forget) a resource with its children.
  748. This uses fixtures from the previous test.
  749. """
  750. uid = '/test_hard_delete_children01'
  751. rsrc_api.create_or_replace(uid)
  752. for i in range(3):
  753. rsrc_api.create_or_replace('{}/child{}'.format(uid, i))
  754. rsrc_api.delete(uid, False)
  755. with pytest.raises(ResourceNotExistsError):
  756. rsrc_api.get(uid)
  757. with pytest.raises(ResourceNotExistsError):
  758. rsrc_api.resurrect(uid)
  759. for i in range(3):
  760. with pytest.raises(ResourceNotExistsError):
  761. rsrc_api.get('{}/child{}'.format(uid, i))
  762. with pytest.raises(ResourceNotExistsError):
  763. rsrc_api.resurrect('{}/child{}'.format(uid, i))
  764. def test_hard_delete_descendants(self):
  765. """
  766. Forget a resource with all its descendants.
  767. """
  768. uid = '/test_hard_delete_descendants01'
  769. rsrc_api.create_or_replace(uid)
  770. for i in range(1, 4):
  771. rsrc_api.create_or_replace('{}/child{}'.format(uid, i))
  772. for j in range(i):
  773. rsrc_api.create_or_replace('{}/child{}/grandchild{}'.format(
  774. uid, i, j))
  775. rsrc_api.delete(uid, False)
  776. with pytest.raises(ResourceNotExistsError):
  777. rsrc_api.get(uid)
  778. with pytest.raises(ResourceNotExistsError):
  779. rsrc_api.resurrect(uid)
  780. for i in range(1, 4):
  781. with pytest.raises(ResourceNotExistsError):
  782. rsrc_api.get('{}/child{}'.format(uid, i))
  783. with pytest.raises(ResourceNotExistsError):
  784. rsrc_api.resurrect('{}/child{}'.format(uid, i))
  785. for j in range(i):
  786. with pytest.raises(ResourceNotExistsError):
  787. rsrc_api.get('{}/child{}/grandchild{}'.format(
  788. uid, i, j))
  789. with pytest.raises(ResourceNotExistsError):
  790. rsrc_api.resurrect('{}/child{}/grandchild{}'.format(
  791. uid, i, j))
  792. @pytest.mark.usefixtures('db')
  793. class TestResourceVersioning:
  794. '''
  795. Test resource version lifecycle.
  796. '''
  797. def test_create_version(self):
  798. """
  799. Create a version snapshot.
  800. """
  801. uid = '/test_version1'
  802. rdf_data = b'<> <http://purl.org/dc/terms/title> "Original title." .'
  803. update_str = '''DELETE {
  804. <> <http://purl.org/dc/terms/title> "Original title." .
  805. } INSERT {
  806. <> <http://purl.org/dc/terms/title> "Title #2." .
  807. } WHERE {
  808. }'''
  809. rsrc_api.create_or_replace(uid, rdf_data=rdf_data, rdf_fmt='turtle')
  810. ver_uid = rsrc_api.create_version(uid, 'v1').split('fcr:versions/')[-1]
  811. #FIXME Without this, the test fails.
  812. #set(rsrc_api.get_version(uid, ver_uid))
  813. rsrc_api.update(uid, update_str)
  814. current = rsrc_api.get(uid)
  815. with txn_ctx():
  816. assert (
  817. (current.uri, nsc['dcterms'].title, Literal('Title #2.'))
  818. in current.imr)
  819. assert (
  820. (current.uri, nsc['dcterms'].title, Literal('Original title.'))
  821. not in current.imr)
  822. v1 = rsrc_api.get_version(uid, ver_uid)
  823. with txn_ctx():
  824. assert (
  825. (v1.uri, nsc['dcterms'].title, Literal('Original title.'))
  826. in set(v1))
  827. assert (
  828. (v1.uri, nsc['dcterms'].title, Literal('Title #2.'))
  829. not in set(v1))
  830. def test_revert_to_version(self):
  831. """
  832. Test reverting to a previous version.
  833. Uses assets from previous test.
  834. """
  835. uid = '/test_version1'
  836. ver_uid = 'v1'
  837. rsrc_api.revert_to_version(uid, ver_uid)
  838. rev = rsrc_api.get(uid)
  839. with txn_ctx():
  840. assert (
  841. (rev.uri, nsc['dcterms'].title, Literal('Original title.'))
  842. in rev.imr)
  843. def test_versioning_children(self):
  844. """
  845. Test that children are not affected by version restoring.
  846. 1. create parent resource
  847. 2. Create child 1
  848. 3. Version parent
  849. 4. Create child 2
  850. 5. Restore parent to previous version
  851. 6. Verify that restored version still has 2 children
  852. """
  853. uid = '/test_version_children'
  854. ver_uid = 'v1'
  855. ch1_uid = '{}/kid_a'.format(uid)
  856. ch2_uid = '{}/kid_b'.format(uid)
  857. rsrc_api.create_or_replace(uid)
  858. rsrc_api.create_or_replace(ch1_uid)
  859. ver_uid = rsrc_api.create_version(uid, ver_uid).split('fcr:versions/')[-1]
  860. rsrc = rsrc_api.get(uid)
  861. with txn_ctx():
  862. assert nsc['fcres'][ch1_uid] in rsrc.imr[
  863. rsrc.uri : nsc['ldp'].contains]
  864. rsrc_api.create_or_replace(ch2_uid)
  865. rsrc = rsrc_api.get(uid)
  866. with txn_ctx():
  867. assert nsc['fcres'][ch2_uid] in rsrc.imr[
  868. rsrc.uri : nsc['ldp'].contains]
  869. rsrc_api.revert_to_version(uid, ver_uid)
  870. rsrc = rsrc_api.get(uid)
  871. with txn_ctx():
  872. assert nsc['fcres'][ch1_uid] in rsrc.imr[
  873. rsrc.uri : nsc['ldp'].contains]
  874. assert nsc['fcres'][ch2_uid] in rsrc.imr[
  875. rsrc.uri : nsc['ldp'].contains]