rdf-ldp 0.9.2 → 0.9.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -87,9 +87,11 @@ module RDF::LDP
87
87
  predicate = inserted_content_relation
88
88
  return resource.to_uri if predicate == MEMBER_SUBJECT_URI
89
89
 
90
- raise(NotAcceptable, "#{resource.to_uri} is an LDP-NR; cannot add " \
91
- 'it to an IndirectContainer with a content ' \
92
- 'relation.') if resource.non_rdf_source?
90
+ if resource.non_rdf_source?
91
+ raise(NotAcceptable, "#{resource.to_uri} is an LDP-NR; cannot add " \
92
+ 'it to an IndirectContainer with a content ' \
93
+ 'relation.')
94
+ end
93
95
 
94
96
  target = transaction || resource.graph
95
97
  statements = target.query([resource.subject_uri, predicate, :o])
@@ -1,14 +1,16 @@
1
1
  module RDF
2
2
  module LDP
3
+ ##
4
+ # Provides an interaction model registry.
3
5
  class InteractionModel
4
6
  class << self
5
7
  ##
6
8
  # Interaction models are in reverse order of preference for POST/PUT
7
- # requests; e.g. if a client sends a request with Resource, RDFSource, and
8
- # BasicContainer headers, the server gives a basic container.
9
+ # requests; e.g. if a client sends a request with Resource, RDFSource,
10
+ # oand BasicContainer headers, the server gives a basic container.
9
11
  #
10
- # Interaction models are initialized in the correct order, but with no classed
11
- # registered to handle them.
12
+ # Interaction models are initialized in the correct order, but with no
13
+ # class registered to handle them.
12
14
  @@interaction_models = {
13
15
  RDF::LDP::RDFSource.to_uri => nil,
14
16
  RDF::LDP::Container.to_uri => nil,
@@ -17,43 +19,47 @@ module RDF
17
19
  RDF::LDP::IndirectContainer.to_uri => nil,
18
20
  RDF::LDP::NonRDFSource.to_uri => nil
19
21
  }
20
-
22
+
21
23
  ##
22
- # Register a new interaction model for one or more Link header URIs. klass.to_uri
23
- # will automatically be registered.
24
+ # Register a new interaction model for one or more Link header URIs.
25
+ # klass.to_uri will automatically be registered.
24
26
  #
25
- # @param [RDF::LDP::Resource] klass the implementation class to register
27
+ # @param [RDF::LDP::Resource] klass the implementation class to
28
+ # register
26
29
  # @param [Hash <Symbol, *>] opts registration options:
27
- # :default [true, false] if true, klass will become the new default klass for
28
- # unrecognized Link headers
29
- # :for [RDF::URI, Array<RDF::URI>] additional URIs for which klass should become
30
- # the interaction model
30
+ # :default [true, false] if true, klass will become the new default
31
+ # klass for unrecognized Link headers
32
+ # :for [RDF::URI, Array<RDF::URI>] additional URIs for which klass
33
+ # should become the interaction model
31
34
  #
32
35
  # @return [RDF::LDP::Resource] klass
33
- def register(klass, opts={})
36
+ def register(klass, opts = {})
34
37
  unless klass.ancestors.include?(RDF::LDP::Resource)
35
- raise ArgumentError, "Interaction models must subclass `RDF::LDP::Resource`"
38
+ raise ArgumentError,
39
+ 'Interaction models must subclass `RDF::LDP::Resource`'
36
40
  end
37
- @@default = klass if opts[:default] or @@default.nil?
41
+ @@default = klass if opts[:default] || @@default.nil?
38
42
  @@interaction_models[klass.to_uri] = klass
39
43
  Array(opts[:for]).each do |model|
40
44
  @@interaction_models[model] = klass
41
45
  end
42
46
  klass
43
47
  end
44
-
48
+
45
49
  ##
46
- # Find the appropriate interaction model given a set of Link header URIs.
50
+ # Find the appropriate interaction model given a set of Link header
51
+ # URIs.
47
52
  #
48
53
  # @param [Array<RDF::URI>] uris
49
54
  #
50
- # @return [Class] a subclass of {RDF::LDP::Resource} that most narrowly matches the
51
- # supplied `uris`, or the default interaction model if nothing matches
55
+ # @return [Class] a subclass of {RDF::LDP::Resource} that most narrowly
56
+ # matches the supplied `uris`, or the default interaction model if
57
+ # nothing matches
52
58
  def find(uris)
53
- match = @@interaction_models.keys.reverse.find { |u| uris.include? u }
59
+ match = @@interaction_models.keys.reverse.find { |u| uris.include? u }
54
60
  self.for(match) || @@default
55
61
  end
56
-
62
+
57
63
  ##
58
64
  # Find the interaction model registered for a given uri
59
65
  #
@@ -63,23 +69,27 @@ module RDF
63
69
  def for(uri)
64
70
  @@interaction_models[uri]
65
71
  end
66
-
72
+
67
73
  ##
68
74
  # The default registered interaction model
69
75
  def default
70
76
  @@default
71
77
  end
72
-
78
+
73
79
  ##
74
- # Test an array of URIs to see if their interaction models are compatible (e.g., all of the URIs
75
- # refer either to RDF models or non-RDF models, but not a combination of both).
80
+ # Test an array of URIs to see if their interaction models are
81
+ # compatible (e.g., all of the URIs refer either to RDF models or
82
+ # non-RDF models, but not a combination of both).
76
83
  #
77
84
  # @param [Array<RDF::URI>] uris
78
- # @return [TrueClass or FalseClass] true if the models specified by `uris` are compatible
85
+ # @return [TrueClass or FalseClass] true if the models specified by
86
+ # `uris` are compatible
79
87
  def compatible?(uris)
80
- classes = uris.collect { |m| self.for(m) }
81
- (rdf,non_rdf) = classes.compact.partition { |c| c.ancestors.include?(RDFSource) }
82
- rdf.empty? or non_rdf.empty?
88
+ classes = uris.collect { |m| self.for(m) }
89
+ (rdf, non_rdf) =
90
+ classes.compact.partition { |c| c.ancestors.include?(RDFSource) }
91
+
92
+ rdf.empty? || non_rdf.empty?
83
93
  end
84
94
  end
85
95
  end
@@ -30,7 +30,9 @@ module RDF::LDP
30
30
  # @param [storage_adapter] a class implementing the StorageAdapter interface
31
31
  #
32
32
  # @see RDF::LDP::Resource#initialize
33
- def initialize(subject_uri, data = RDF::Repository.new, storage_adapter = DEFAULT_ADAPTER)
33
+ def initialize(subject_uri,
34
+ data = RDF::Repository.new,
35
+ storage_adapter = DEFAULT_ADAPTER)
34
36
  data ||= RDF::Repository.new # allows explict `nil` pass
35
37
  @storage = storage_adapter.new(self)
36
38
  super(subject_uri, data)
@@ -132,7 +134,7 @@ module RDF::LDP
132
134
  #
133
135
  # @raise [RDF::LDP::RequestError] when the request fails
134
136
  def to_response
135
- (exists? && !destroyed?) ? storage.io : []
137
+ exists? && !destroyed? ? storage.io : []
136
138
  end
137
139
 
138
140
  private
@@ -38,14 +38,15 @@ module RDF::LDP
38
38
  # resource.exists? # => true
39
39
  # resource.metagraph.dump :ttl
40
40
  # # => "<http://example.org/moomin> a <http://www.w3.org/ns/ldp#Resource>;
41
- # <http://purl.org/dc/terms/modified> "2015-10-25T14:24:56-07:00"^^<http://www.w3.org/2001/XMLSchema#dateTime> ."
41
+ # # <http://purl.org/dc/terms/modified>
42
+ # # "2015-10-25T14:24:56-07:00"^^xsd:dateTime ."
42
43
  #
43
44
  # @example updating a Resource updates the `#last_modified` date
44
45
  # resource.last_modified
45
- # # => #<DateTime: 2015-10-25T14:32:01-07:00 ((2457321j,77521s,571858283n),-25200s,2299161j)>
46
+ # # => #<DateTime: 2015-10-25T14:32:01-07:00...>
46
47
  # resource.update('blah', 'text/plain')
47
48
  # resource.last_modified
48
- # # => #<DateTime: 2015-10-25T14:32:04-07:00 ((2457321j,77524s,330658065n),-25200s,2299161j)>
49
+ # # => #<DateTime: 2015-10-25T14:32:04-07:00...>
49
50
  #
50
51
  # @example destroying a Resource
51
52
  # resource.exists? # => true
@@ -75,8 +76,8 @@ module RDF::LDP
75
76
  # #<RDF::LDP::Resource:0x00564f4a646028
76
77
  # @data=#<RDF::Repository:0x2b27a5391708()>,
77
78
  # @exists=true,
78
- # @metagraph=#<RDF::Graph:0x2b27a5322538(http://example.org/moomin#meta)>,
79
- # @subject_uri=#<RDF::URI:0x2b27a5322fec URI:http://example.org/moomin>>]
79
+ # @metagraph=#<RDF::Graph:0xea7(http://example.org/moomin#meta)>,
80
+ # @subject_uri=#<RDF::URI:0xea8 URI:http://example.org/moomin>>]
80
81
  #
81
82
  # resource.request(:put, 200, {}, {}) # RDF::LDP::MethodNotAllowed: put
82
83
  #
@@ -161,7 +162,7 @@ module RDF::LDP
161
162
  .map { |link| RDF::URI.intern(link.href) }
162
163
 
163
164
  return InteractionModel.default if models.empty?
164
-
165
+
165
166
  raise NotAcceptable unless InteractionModel.compatible?(models)
166
167
 
167
168
  InteractionModel.find(models)
@@ -302,9 +303,9 @@ module RDF::LDP
302
303
  #
303
304
  # @return [String] an HTTP Etag
304
305
  #
305
- # @note these etags are weak, but we allow clients to use them in
306
+ # @note these etags are weak, but we allow clients to use them in
306
307
  # `If-Match` headers, and use weak comparison. This is in conflict with
307
- # https://tools.ietf.org/html/rfc7232#section-3.1. See:
308
+ # https://tools.ietf.org/html/rfc7232#section-3.1. See:
308
309
  # https://github.com/ruby-rdf/rdf-ldp/issues/68
309
310
  #
310
311
  # @see http://www.w3.org/TR/ldp#h-ldpr-gen-etags LDP ETag clause for GET
@@ -558,24 +559,25 @@ module RDF::LDP
558
559
  ##
559
560
  # Sets the last modified date/time to now
560
561
  #
561
- # @param transaction [RDF::Transaction] the transaction scope in which to
562
+ # @param transaction [RDF::Transaction] the transaction scope in which to
562
563
  # apply changes. If none (or `nil`) is given, the change is made outside
563
564
  # any transaction scope.
564
565
  def set_last_modified(transaction = nil)
565
- if transaction
566
- # transactions do not support updates or pattern deletes, so we must
567
- # ask the Repository for the current last_modified to delete the statement
568
- # transactionally
566
+ return metagraph.update([subject_uri, MODIFIED_URI, DateTime.now]) unless
569
567
  transaction
570
- .delete RDF::Statement(subject_uri, MODIFIED_URI, last_modified,
571
- graph_name: metagraph_name) if last_modified
572
568
 
569
+ # transactions do not support updates or pattern deletes, so we must
570
+ # ask the Repository for the current last_modified to delete the
571
+ # statement transactionally
572
+ if last_modified
573
573
  transaction
574
- .insert RDF::Statement(subject_uri, MODIFIED_URI, DateTime.now,
574
+ .delete RDF::Statement(subject_uri, MODIFIED_URI, last_modified,
575
575
  graph_name: metagraph_name)
576
- else
577
- metagraph.update([subject_uri, MODIFIED_URI, DateTime.now])
578
576
  end
577
+
578
+ transaction
579
+ .insert RDF::Statement(subject_uri, MODIFIED_URI, DateTime.now,
580
+ graph_name: metagraph_name)
579
581
  end
580
582
 
581
583
  ##
@@ -0,0 +1,6 @@
1
+ require 'rdf/ldp/spec/resource'
2
+ require 'rdf/ldp/spec/rdf_source'
3
+ require 'rdf/ldp/spec/non_rdf_source'
4
+ require 'rdf/ldp/spec/container'
5
+ require 'rdf/ldp/spec/direct_container'
6
+ require 'rdf/ldp/spec/indirect_container'
@@ -0,0 +1,337 @@
1
+ require 'rdf/ldp/spec/rdf_source'
2
+
3
+ shared_examples 'a Container' do
4
+ it_behaves_like 'an RDFSource'
5
+
6
+ let(:uri) { RDF::URI('http://ex.org/moomin') }
7
+ subject { described_class.new(uri) }
8
+
9
+ it { is_expected.to be_container }
10
+
11
+ describe '#container_class' do
12
+ it 'returns a uri' do
13
+ expect(subject.container_class).to be_a RDF::URI
14
+ end
15
+ end
16
+
17
+ describe '#containment_triples' do
18
+ let(:resource) { RDF::URI('http://ex.org/mymble') }
19
+
20
+ it 'returns a uri' do
21
+ subject.add_containment_triple(resource)
22
+ expect(subject.containment_triples)
23
+ .to contain_exactly(an_instance_of(RDF::Statement))
24
+ end
25
+ end
26
+
27
+ describe '#add' do
28
+ let(:resource) { RDF::URI('http://ex.org/mymble') }
29
+ before { subject.create(StringIO.new, 'application/n-triples') }
30
+
31
+ it 'returns self' do
32
+ expect(subject.add(resource)).to eq subject
33
+ end
34
+
35
+ it 'containment triple is added to graph' do
36
+ expect(subject.add(resource).graph)
37
+ .to include subject.make_containment_triple(resource)
38
+ end
39
+ end
40
+
41
+ describe '#add_containment_triple' do
42
+ let(:resource) { RDF::URI('http://ex.org/mymble') }
43
+
44
+ it 'returns self' do
45
+ expect(subject.add_containment_triple(resource)).to eq subject
46
+ end
47
+
48
+ it 'containment triple is added to graph' do
49
+ expect(subject.add_containment_triple(resource).graph)
50
+ .to include subject.make_containment_triple(resource)
51
+ end
52
+ end
53
+
54
+ describe '#remove_containment_triple' do
55
+ before { subject.add_containment_triple(resource) }
56
+
57
+ let(:resource) { RDF::URI('http://ex.org/mymble') }
58
+
59
+ it 'returns self' do
60
+ expect(subject.remove_containment_triple(resource)).to eq subject
61
+ end
62
+
63
+ it 'membership triple is added to graph' do
64
+ expect(subject.remove_containment_triple(resource).graph)
65
+ .not_to include subject.make_containment_triple(resource)
66
+ end
67
+
68
+ it 'updates last_modified for container' do
69
+ expect { subject.remove_containment_triple(resource) }
70
+ .to change { subject.last_modified }
71
+ end
72
+
73
+ it 'updates etag for container' do
74
+ expect { subject.remove_containment_triple(resource) }
75
+ .to change { subject.etag }
76
+ end
77
+ end
78
+
79
+ describe '#make_containment_triple' do
80
+ let(:resource) { uri / 'papa' }
81
+
82
+ it 'returns a statement' do
83
+ expect(subject.make_containment_triple(resource)).to be_a RDF::Statement
84
+ end
85
+
86
+ it 'statement subject *or* object is #subject_uri' do
87
+ sub = subject.make_containment_triple(resource).subject
88
+ obj = subject.make_containment_triple(resource).object
89
+ expect([sub, obj]).to include subject.subject_uri
90
+ end
91
+
92
+ it 'converts Resource classes to URI' do
93
+ sub = subject.make_containment_triple(subject).subject
94
+ obj = subject.make_containment_triple(subject).object
95
+ expect([sub, obj]).to include subject.subject_uri
96
+ end
97
+ end
98
+
99
+ describe '#request' do
100
+ let(:graph) { RDF::Graph.new }
101
+
102
+ let(:env) do
103
+ { 'rack.input' => StringIO.new(graph.dump(:ntriples)),
104
+ 'CONTENT_TYPE' => 'application/n-triples' }
105
+ end
106
+
107
+ let(:statement) do
108
+ RDF::Statement(subject.subject_uri,
109
+ RDF::Vocab::LDP.contains,
110
+ 'moomin')
111
+ end
112
+
113
+ context 'with :PATCH',
114
+ if: described_class.private_method_defined?(:patch) do
115
+
116
+ it 'raises conflict error when editing containment triples' do
117
+ patch_statement = statement.clone
118
+ patch_statement.object = 'snorkmaiden'
119
+ patch = '@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .' \
120
+ "\n\nAdd { #{statement.subject.to_base} " \
121
+ "#{statement.predicate.to_base} #{statement.object.to_base} } ."
122
+ env = { 'CONTENT_TYPE' => 'text/ldpatch',
123
+ 'rack.input' => StringIO.new(patch) }
124
+
125
+ expect { subject.request(:patch, 200, {}, env) }
126
+ .to raise_error RDF::LDP::Conflict
127
+ end
128
+ end
129
+
130
+ context 'with :PUT',
131
+ if: described_class.private_method_defined?(:put) do
132
+ context 'when PUTing containment triples' do
133
+ it 'when creating a resource raises a Conflict error' do
134
+ graph << statement
135
+
136
+ expect { subject.request(:PUT, 200, { 'abc' => 'def' }, env) }
137
+ .to raise_error RDF::LDP::Conflict
138
+ end
139
+
140
+ it 'when resource exists raises a Conflict error' do
141
+ subject.create(StringIO.new, 'application/n-triples')
142
+ graph << statement
143
+ expect { subject.request(:PUT, 200, { 'abc' => 'def' }, env) }
144
+ .to raise_error RDF::LDP::Conflict
145
+ end
146
+
147
+ it 'can put existing containment triple' do
148
+ subject.create(StringIO.new, 'application/n-triples')
149
+ subject.graph << statement
150
+ graph << statement
151
+ expect(subject.request(:PUT, 200, { 'abc' => 'def' }, env).first)
152
+ .to eq 200
153
+ end
154
+
155
+ it 'writes data when putting existing containment triple' do
156
+ subject.create(StringIO.new, 'application/n-triples')
157
+ subject.graph << statement
158
+ graph << statement
159
+
160
+ new_st = RDF::Statement(RDF::URI('http://example.org/new_moomin'),
161
+ RDF::Vocab::DC.title,
162
+ 'moomin')
163
+ graph << new_st
164
+ expect(subject.request(:PUT, 200, { 'abc' => 'def' }, env).last.graph)
165
+ .to have_statement new_st
166
+ end
167
+
168
+ it 'raises conflict error when without existing containment triples' do
169
+ subject.create(StringIO.new, 'application/n-triples')
170
+ subject.graph << statement
171
+ expect { subject.request(:PUT, 200, { 'abc' => 'def' }, env) }
172
+ .to raise_error RDF::LDP::Conflict
173
+ end
174
+ end
175
+ end
176
+
177
+ context 'when POST is implemented',
178
+ if: described_class.private_method_defined?(:post) do
179
+ let(:graph) { RDF::Graph.new }
180
+ before { subject.create(StringIO.new, 'application/n-triples') }
181
+
182
+ let(:env) do
183
+ { 'rack.input' => StringIO.new(graph.dump(:ntriples)),
184
+ 'CONTENT_TYPE' => 'application/n-triples' }
185
+ end
186
+
187
+ it 'returns status 201' do
188
+ expect(subject.request(:POST, 200, {}, env).first).to eq 201
189
+ end
190
+
191
+ it 'gives created resource as body' do
192
+ expect(subject.request(:POST, 200, {}, env).last)
193
+ .to be_a RDF::LDP::Resource
194
+ end
195
+
196
+ it 'generates an id' do
197
+ expect(subject.request(:POST, 200, {}, env).last.subject_uri)
198
+ .to be_starts_with subject.subject_uri.to_s
199
+ end
200
+
201
+ it 'adds containment statement to resource' do
202
+ expect { subject.request(:POST, 200, {}, env) }
203
+ .to change { subject.containment_triples.count }.from(0).to(1)
204
+ end
205
+
206
+ it 'updates last_modified for container' do
207
+ expect { subject.request(:POST, 200, {}, env) }
208
+ .to change { subject.last_modified }
209
+ end
210
+
211
+ it 'updates etag for container' do
212
+ expect { subject.request(:POST, 200, {}, env) }
213
+ .to change { subject.etag }
214
+ end
215
+
216
+ context 'with Container interaction model' do
217
+ it 'creates a basic container' do
218
+ env['HTTP_LINK'] = "<#{RDF::LDP::Container.to_uri}>;rel=\"type\""
219
+ expect(subject.request(:POST, 200, {}, env).last)
220
+ .to be_a RDF::LDP::Container
221
+ end
222
+
223
+ context 'BasicContainer' do
224
+ it 'creates a basic container' do
225
+ env['HTTP_LINK'] =
226
+ '<http://www.w3.org/ns/ldp#BasicContainer>;rel="type"'
227
+ expect(subject.request(:POST, 200, {}, env).last)
228
+ .to be_a RDF::LDP::Container
229
+ end
230
+ end
231
+
232
+ context 'DirectContainer' do
233
+ it 'creates a direct container' do
234
+ env['HTTP_LINK'] =
235
+ "<#{RDF::LDP::DirectContainer.to_uri}>;rel=\"type\""
236
+
237
+ expect(subject.request(:POST, 200, {}, env).last)
238
+ .to be_a RDF::LDP::DirectContainer
239
+ end
240
+ end
241
+
242
+ context 'IndirectContainer' do
243
+ it 'creates a indirect container' do
244
+ env['HTTP_LINK'] =
245
+ "<#{RDF::LDP::IndirectContainer.to_uri}>;rel=\"type\""
246
+
247
+ expect(subject.request(:POST, 200, {}, env).last)
248
+ .to be_a RDF::LDP::IndirectContainer
249
+ end
250
+ end
251
+ end
252
+
253
+ context 'with a Slug' do
254
+ it 'creates resource with Slug' do
255
+ env['HTTP_SLUG'] = 'snork'
256
+ expect(subject.request(:POST, 200, {}, env).last.subject_uri)
257
+ .to eq subject.subject_uri / env['HTTP_SLUG']
258
+ end
259
+
260
+ it 'mints a uri when empty Slug is given' do
261
+ env['HTTP_SLUG'] = ''
262
+ expect(subject.request(:POST, 200, {}, env).last.subject_uri)
263
+ .to be_starts_with subject.subject_uri
264
+ end
265
+
266
+ it 'raises a 409 Conflict when slug is already taken' do
267
+ env['HTTP_SLUG'] = 'snork'
268
+ subject.request(:POST, 200, {}, env)
269
+
270
+ expect { subject.request(:POST, 200, {}, env) }
271
+ .to raise_error RDF::LDP::Conflict
272
+ end
273
+
274
+ it 'raises a 409 Conflict when slug is already taken but destroyed' do
275
+ env['HTTP_SLUG'] = 'snork'
276
+ created = subject.request(:POST, 200, {}, env).last
277
+ allow(created).to receive(:destroyed?).and_return true
278
+
279
+ expect { subject.request(:POST, 200, {}, env) }
280
+ .to raise_error RDF::LDP::Conflict
281
+ end
282
+
283
+ it 'raises a 406 NotAcceptable if slug has a uri fragment `#`' do
284
+ env['HTTP_SLUG'] = 'snork#maiden'
285
+
286
+ expect { subject.request(:POST, 200, {}, env) }
287
+ .to raise_error RDF::LDP::NotAcceptable
288
+ end
289
+
290
+ it 'url-encodes Slug' do
291
+ env['HTTP_SLUG'] = 'snork maiden'
292
+ expect(subject.request(:POST, 200, {}, env).last.subject_uri)
293
+ .to eq subject.subject_uri / 'snork%20maiden'
294
+ end
295
+ end
296
+
297
+ context 'with graph content' do
298
+ before do
299
+ graph << RDF::Statement(uri, RDF::Vocab::DC.title, 'moomin')
300
+ graph << RDF::Statement(RDF::Node.new, RDF::Vocab::DC.creator, 'tove')
301
+ graph <<
302
+ RDF::Statement(RDF::Node.new, RDF.type, RDF::Vocab::FOAF.Person)
303
+ end
304
+
305
+ it 'parses graph into created resource' do
306
+ expect(subject.request(:POST, 200, {}, env).last.to_response)
307
+ .to be_isomorphic_with graph
308
+ end
309
+
310
+ it 'adds a Location header' do
311
+ expect(subject.request(:POST, 200, {}, env)[1]['Location'])
312
+ .to start_with subject.subject_uri.to_s
313
+ end
314
+
315
+ context 'with quads' do
316
+ let(:graph) do
317
+ RDF::Graph.new(graph_name: subject.subject_uri,
318
+ data: RDF::Repository.new)
319
+ end
320
+
321
+ let(:env) do
322
+ { 'rack.input' => StringIO.new(graph.dump(:nquads)),
323
+ 'CONTENT_TYPE' => 'application/n-quads' }
324
+ end
325
+
326
+ it 'parses graph into created resource without regard for context' do
327
+ context_free_graph = RDF::Graph.new
328
+ context_free_graph << graph.statements
329
+
330
+ expect(subject.request(:POST, 200, {}, env).last.to_response)
331
+ .to be_isomorphic_with context_free_graph
332
+ end
333
+ end
334
+ end
335
+ end
336
+ end
337
+ end