roadforest 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/examples/file-management.rb +98 -0
- data/lib/roadforest/application/dispatcher.rb +54 -0
- data/lib/roadforest/application/parameters.rb +39 -0
- data/lib/roadforest/application/path-provider.rb +18 -0
- data/lib/roadforest/application/route-adapter.rb +24 -0
- data/lib/roadforest/application/services-host.rb +10 -0
- data/lib/roadforest/application.rb +42 -0
- data/lib/roadforest/blob-model.rb +56 -0
- data/lib/roadforest/content-handling/engine.rb +113 -0
- data/lib/roadforest/content-handling/media-type.rb +222 -0
- data/lib/roadforest/content-handling/type-handlers/jsonld.rb +172 -0
- data/lib/roadforest/http/adapters/excon.rb +47 -0
- data/lib/roadforest/http/graph-response.rb +20 -0
- data/lib/roadforest/http/graph-transfer.rb +112 -0
- data/lib/roadforest/http/message.rb +91 -0
- data/lib/roadforest/model.rb +151 -0
- data/lib/roadforest/models.rb +2 -0
- data/lib/roadforest/rdf/context-fascade.rb +25 -0
- data/lib/roadforest/rdf/document.rb +23 -0
- data/lib/roadforest/rdf/focus-list.rb +19 -0
- data/lib/roadforest/rdf/focus-wrapping.rb +30 -0
- data/lib/roadforest/rdf/graph-copier.rb +16 -0
- data/lib/roadforest/rdf/graph-focus.rb +95 -0
- data/lib/roadforest/rdf/graph-reading.rb +145 -0
- data/lib/roadforest/rdf/graph-store.rb +217 -0
- data/lib/roadforest/rdf/investigation.rb +90 -0
- data/lib/roadforest/rdf/normalization.rb +150 -0
- data/lib/roadforest/rdf/parcel.rb +47 -0
- data/lib/roadforest/rdf/post-focus.rb +35 -0
- data/lib/roadforest/rdf/resource-pattern.rb +60 -0
- data/lib/roadforest/rdf/resource-query.rb +58 -0
- data/lib/roadforest/rdf/source-rigor/credence/any.rb +9 -0
- data/lib/roadforest/rdf/source-rigor/credence/none-if-role-absent.rb +19 -0
- data/lib/roadforest/rdf/source-rigor/credence/role-if-available.rb +19 -0
- data/lib/roadforest/rdf/source-rigor/credence-annealer.rb +22 -0
- data/lib/roadforest/rdf/source-rigor/credence.rb +29 -0
- data/lib/roadforest/rdf/source-rigor/http-investigator.rb +20 -0
- data/lib/roadforest/rdf/source-rigor/investigator.rb +17 -0
- data/lib/roadforest/rdf/source-rigor/null-investigator.rb +10 -0
- data/lib/roadforest/rdf/source-rigor.rb +44 -0
- data/lib/roadforest/rdf/update-focus.rb +73 -0
- data/lib/roadforest/rdf/vocabulary.rb +11 -0
- data/lib/roadforest/rdf.rb +6 -0
- data/lib/roadforest/remote-host.rb +96 -0
- data/lib/roadforest/resource/handlers.rb +43 -0
- data/lib/roadforest/resource/http/form-parsing.rb +81 -0
- data/lib/roadforest/resource/rdf/leaf-item.rb +21 -0
- data/lib/roadforest/resource/rdf/list.rb +19 -0
- data/lib/roadforest/resource/rdf/parent-item.rb +26 -0
- data/lib/roadforest/resource/rdf/read-only.rb +100 -0
- data/lib/roadforest/resource/rdf.rb +4 -0
- data/lib/roadforest/resource/role/has-children.rb +22 -0
- data/lib/roadforest/resource/role/writable.rb +43 -0
- data/lib/roadforest/server.rb +3 -0
- data/lib/roadforest/test-support/dispatcher-facade.rb +77 -0
- data/lib/roadforest/test-support/http-client.rb +151 -0
- data/lib/roadforest/test-support/matchers.rb +67 -0
- data/lib/roadforest/test-support/remote-host.rb +23 -0
- data/lib/roadforest/test-support/trace-formatter.rb +140 -0
- data/lib/roadforest/test-support.rb +2 -0
- data/lib/roadforest/utility/class-registry.rb +49 -0
- data/lib/roadforest.rb +2 -0
- data/spec/client.rb +152 -0
- data/spec/credence-annealer.rb +44 -0
- data/spec/graph-copier.rb +87 -0
- data/spec/graph-store.rb +142 -0
- data/spec/media-types.rb +14 -0
- data/spec/rdf-parcel.rb +158 -0
- data/spec/update-focus.rb +117 -0
- data/spec_support/gem_test_suite.rb +0 -0
- metadata +241 -0
@@ -0,0 +1,81 @@
|
|
1
|
+
=begin
|
2
|
+
I think this code might be useful soon
|
3
|
+
module RoadForest
|
4
|
+
module ResourceMixin
|
5
|
+
module FormParsing
|
6
|
+
def self.content_types_accepted
|
7
|
+
[
|
8
|
+
["application/x-www-form-urlencoded", :handle_url_encoded_form],
|
9
|
+
["multipart/form-data", :handle_multipart_form]
|
10
|
+
]
|
11
|
+
end
|
12
|
+
|
13
|
+
def process_post
|
14
|
+
content_type = Webmachine::MediaType.parse(request.content_type)
|
15
|
+
if content_type.match?("application/x-www-form-urlencoded")
|
16
|
+
handle_url_encoded_form
|
17
|
+
elsif content_type.match?("multipart/form-data")
|
18
|
+
handle_multipart_form
|
19
|
+
else
|
20
|
+
false
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def parse_url_encoded_form
|
25
|
+
Hash[URI::decode_www_form(request.body)]
|
26
|
+
end
|
27
|
+
|
28
|
+
def parse_multipart_form
|
29
|
+
form_data = {}
|
30
|
+
reader = multipart_reader(request.content_type, form_data)
|
31
|
+
|
32
|
+
request.body.each do |chunk|
|
33
|
+
reader.write(chunk)
|
34
|
+
end
|
35
|
+
|
36
|
+
return form_data
|
37
|
+
end
|
38
|
+
|
39
|
+
def multipart_reader(content_type, form_data)
|
40
|
+
content_type = Webmachine::MediaType.parse(content_type)
|
41
|
+
reader = MultipartParser::Reader.new(content_type.params["boundary"])
|
42
|
+
reader.on_error do |message|
|
43
|
+
raise message
|
44
|
+
end
|
45
|
+
|
46
|
+
reader.on_part do |part|
|
47
|
+
case part.mime
|
48
|
+
when nil
|
49
|
+
form_data[part.name] = ""
|
50
|
+
part.on_data do |data|
|
51
|
+
form_data[part.name] << data
|
52
|
+
end
|
53
|
+
else
|
54
|
+
temp_file = Tempfile.new(part.filename)
|
55
|
+
|
56
|
+
form_data[part.name] = [part, temp_file]
|
57
|
+
|
58
|
+
part.on_data do |data|
|
59
|
+
temp_file.write(data)
|
60
|
+
end
|
61
|
+
|
62
|
+
part.on_end do
|
63
|
+
temp_file.close
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
return reader
|
69
|
+
end
|
70
|
+
|
71
|
+
def handle_url_encoded_form
|
72
|
+
handle_form(parse_url_encoded_form)
|
73
|
+
end
|
74
|
+
|
75
|
+
def handle_multipart_form
|
76
|
+
handle_form(parse_multipart_form)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
=end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'roadforest/resource/rdf/read-only'
|
2
|
+
require 'roadforest/resource/role/writable'
|
3
|
+
|
4
|
+
module RoadForest
|
5
|
+
module Resource
|
6
|
+
module RDF
|
7
|
+
#Used for a simple resource that has properties that can be updated, but
|
8
|
+
#doesn't have any children - for example a comment
|
9
|
+
class LeafItem < ReadOnly
|
10
|
+
register :leaf
|
11
|
+
|
12
|
+
include Role::Writable
|
13
|
+
|
14
|
+
def allowed_methods
|
15
|
+
super + Role::Writable.allowed_methods
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'roadforest/resource/rdf/read-only'
|
2
|
+
require 'roadforest/resource/role/has-children'
|
3
|
+
module RoadForest
|
4
|
+
module Resource
|
5
|
+
module RDF
|
6
|
+
#Used for a resource that simply represents a list of other resources
|
7
|
+
#without having any properties itself - a list of posts, for instance
|
8
|
+
class List < ReadOnly
|
9
|
+
register :list
|
10
|
+
|
11
|
+
include Role::HasChildren
|
12
|
+
|
13
|
+
def allowed_methods
|
14
|
+
super + Role::HasChildren.allowed_methods
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require 'roadforest/resource/rdf/read-only'
|
2
|
+
require 'roadforest/resource/role/writable'
|
3
|
+
require 'roadforest/resource/role/has-children'
|
4
|
+
module RoadForest
|
5
|
+
module Resource
|
6
|
+
module RDF
|
7
|
+
#Used for a resource that has properties itself that can be updated, and
|
8
|
+
#also has one or more kinds of dependent or subordinate resources - can new
|
9
|
+
#resources be created by posting to this one? e.g. a post, which can have
|
10
|
+
#comments
|
11
|
+
class ParentItem < ReadOnly
|
12
|
+
register :parent
|
13
|
+
|
14
|
+
include Role::Writable
|
15
|
+
include Role::HasChildren
|
16
|
+
|
17
|
+
def allowed_methods
|
18
|
+
(super + [Role::Writable, Role::HasChildren].inject([]) do |list, mod|
|
19
|
+
list + mod.allowed_methods
|
20
|
+
end).uniq
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,100 @@
|
|
1
|
+
require 'roadforest/resource/handlers'
|
2
|
+
require 'roadforest/application/parameters'
|
3
|
+
|
4
|
+
module RoadForest
|
5
|
+
module Resource
|
6
|
+
module RDF
|
7
|
+
#Used for a resource that presents a read-only representation
|
8
|
+
class ReadOnly < Webmachine::Resource
|
9
|
+
def self.register(method_name)
|
10
|
+
Handlers.register(method_name, self)
|
11
|
+
end
|
12
|
+
|
13
|
+
register :read_only
|
14
|
+
|
15
|
+
attr_accessor :model, :trace
|
16
|
+
|
17
|
+
### RoadForest interface
|
18
|
+
|
19
|
+
def params
|
20
|
+
params = Application::Parameters.new do |params|
|
21
|
+
params.path_info = @request.path_info
|
22
|
+
params.query_params = @request.query_params
|
23
|
+
params.path_tokens = @request.path_tokens
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def request_accept_header
|
28
|
+
request.headers["Accept"] || "*/*"
|
29
|
+
end
|
30
|
+
|
31
|
+
def response_content_type=(type)
|
32
|
+
response.headers["Content-Type"] = type
|
33
|
+
end
|
34
|
+
|
35
|
+
def response_body=(body)
|
36
|
+
response.body = body
|
37
|
+
end
|
38
|
+
|
39
|
+
def retrieve_model
|
40
|
+
absolutize(@model.canonical_host, @model.retrieve)
|
41
|
+
end
|
42
|
+
alias retreive_model retrieve_model
|
43
|
+
|
44
|
+
# def known_methods
|
45
|
+
# super + ["PATCH"]
|
46
|
+
# end
|
47
|
+
|
48
|
+
def model_supports(action)
|
49
|
+
@model.respond_to?(action)
|
50
|
+
end
|
51
|
+
### Webmachine interface
|
52
|
+
|
53
|
+
def trace?
|
54
|
+
!!@trace
|
55
|
+
end
|
56
|
+
|
57
|
+
#Overridden rather than metaprogram content type methods
|
58
|
+
def send(*args)
|
59
|
+
if args.length == 1 and not model.nil?
|
60
|
+
model.type_handling.fetch(args.first).call(self)
|
61
|
+
else
|
62
|
+
super
|
63
|
+
end
|
64
|
+
rescue KeyError
|
65
|
+
super
|
66
|
+
end
|
67
|
+
|
68
|
+
def method(name)
|
69
|
+
if model.nil?
|
70
|
+
super
|
71
|
+
else
|
72
|
+
model.type_handling.fetch(name).method(:call)
|
73
|
+
end
|
74
|
+
rescue KeyError
|
75
|
+
super
|
76
|
+
end
|
77
|
+
|
78
|
+
def content_types_provided
|
79
|
+
model.type_handling.renderers.type_map
|
80
|
+
end
|
81
|
+
|
82
|
+
def resource_exists?
|
83
|
+
@model.exists?
|
84
|
+
end
|
85
|
+
|
86
|
+
def generate_etag
|
87
|
+
@model.etag
|
88
|
+
end
|
89
|
+
|
90
|
+
def last_modified
|
91
|
+
@model.last_modified
|
92
|
+
end
|
93
|
+
|
94
|
+
def expires
|
95
|
+
@model.expires
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module RoadForest
|
2
|
+
module Resource
|
3
|
+
module Role
|
4
|
+
module HasChildren
|
5
|
+
def self.allowed_methods
|
6
|
+
%w[POST]
|
7
|
+
end
|
8
|
+
|
9
|
+
def post_is_create
|
10
|
+
false
|
11
|
+
end
|
12
|
+
|
13
|
+
def process_post
|
14
|
+
parser = model.type_handling.choose_parser(request.content_type || 'application/octet-stream')
|
15
|
+
|
16
|
+
parser.add_child(self)
|
17
|
+
return true
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
require 'roadforest/resource/role/has-children'
|
2
|
+
|
3
|
+
module RoadForest
|
4
|
+
module Resource
|
5
|
+
module Role
|
6
|
+
module Writable
|
7
|
+
class IncludeOrder < StandardError; end
|
8
|
+
|
9
|
+
def self.allowed_methods
|
10
|
+
%w[POST PUT DELETE]
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.included(mod)
|
14
|
+
if mod.ancestors.include?(HasChildren)
|
15
|
+
#Might regret this later - some kind of "I promise to fix it?"
|
16
|
+
raise IncludeOrder, "Writable has to be included before HasChildren"
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def post_is_create
|
21
|
+
true
|
22
|
+
end
|
23
|
+
|
24
|
+
def content_types_accepted
|
25
|
+
model.type_handling.parsers.type_map
|
26
|
+
end
|
27
|
+
|
28
|
+
def request_body
|
29
|
+
@request.body
|
30
|
+
end
|
31
|
+
|
32
|
+
def known_content_type(content_type)
|
33
|
+
content_type = Webmachine::MediaType.parse(content_type)
|
34
|
+
content_types_accepted.any?{|ct, _| content_type.match?(ct)}
|
35
|
+
end
|
36
|
+
|
37
|
+
def delete_resource
|
38
|
+
@model.delete(params)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,77 @@
|
|
1
|
+
require 'roadforest/test-support/trace-formatter'
|
2
|
+
require 'webmachine/headers'
|
3
|
+
require 'webmachine/request'
|
4
|
+
require 'webmachine/response'
|
5
|
+
require 'webmachine/decision/flow'
|
6
|
+
module RoadForest
|
7
|
+
module TestSupport
|
8
|
+
class DispatcherFacade < BasicObject
|
9
|
+
def initialize(dispatcher)
|
10
|
+
@dispatcher = dispatcher
|
11
|
+
end
|
12
|
+
|
13
|
+
def method_missing(method, *args, &block)
|
14
|
+
@dispatcher.__send__(method, *args, &block)
|
15
|
+
end
|
16
|
+
|
17
|
+
def dispatch(request, response)
|
18
|
+
if resource = @dispatcher.find_resource(request, response)
|
19
|
+
FSM.new(resource, request, response).run
|
20
|
+
else
|
21
|
+
Webmachine.render_error(404, request, response)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
class FSM < ::Webmachine::Decision::FSM
|
27
|
+
def self.trace_on
|
28
|
+
unless ancestors.include? Webmachine::Trace::FSM
|
29
|
+
include Webmachine::Trace::FSM
|
30
|
+
end
|
31
|
+
Webmachine::Trace.trace_store = :memory
|
32
|
+
end
|
33
|
+
|
34
|
+
def self.dump_trace
|
35
|
+
puts trace_dump
|
36
|
+
end
|
37
|
+
|
38
|
+
def self.trace_dump
|
39
|
+
Webmachine::Trace.traces.map do |trace|
|
40
|
+
TraceFormatter.new(Webmachine::Trace.fetch(trace))
|
41
|
+
end.join("\n")
|
42
|
+
end
|
43
|
+
|
44
|
+
#Um, actually *don't* handle exceptions
|
45
|
+
def handle_exceptions
|
46
|
+
yield.tap do |result|
|
47
|
+
#p result #ok
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def initialize_tracing
|
52
|
+
return if self.class.ancestors.include? Webmachine::Trace::FSM
|
53
|
+
super
|
54
|
+
end
|
55
|
+
|
56
|
+
def run
|
57
|
+
state = Webmachine::Decision::Flow::START
|
58
|
+
trace_request(request)
|
59
|
+
loop do
|
60
|
+
trace_decision(state)
|
61
|
+
result = handle_exceptions { send(state) }
|
62
|
+
case result
|
63
|
+
when Fixnum # Response code
|
64
|
+
respond(result)
|
65
|
+
break
|
66
|
+
when Symbol # Next state
|
67
|
+
state = result
|
68
|
+
else # You bwoke it
|
69
|
+
raise InvalidResource, t('fsm_broke', :state => state, :result => result.inspect)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
ensure
|
73
|
+
trace_response(response)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
@@ -0,0 +1,151 @@
|
|
1
|
+
require 'addressable/uri'
|
2
|
+
require 'roadforest/test-support/dispatcher-facade'
|
3
|
+
module RoadForest
|
4
|
+
module TestSupport
|
5
|
+
class HTTPClient
|
6
|
+
def initialize(app, url)
|
7
|
+
@app = app
|
8
|
+
@default_url = Addressable::URI.parse(url)
|
9
|
+
@exchanges = []
|
10
|
+
@dispatcher = DispatcherFacade.new(@app.dispatcher)
|
11
|
+
end
|
12
|
+
attr_reader :exchanges
|
13
|
+
|
14
|
+
def inspect
|
15
|
+
"#<#{self.class.name}:#{"%0xd" % object_id} #{exchanges.length} exchanges>"
|
16
|
+
end
|
17
|
+
|
18
|
+
def do_request(request)
|
19
|
+
uri = request.url
|
20
|
+
|
21
|
+
uri = Addressable::URI.parse(uri)
|
22
|
+
uri = @default_url.join(uri)
|
23
|
+
|
24
|
+
exchange = Exchange.new
|
25
|
+
|
26
|
+
exchange.method = request.method
|
27
|
+
exchange.uri = uri
|
28
|
+
exchange.body = request.body
|
29
|
+
exchange.dispatcher = @dispatcher
|
30
|
+
|
31
|
+
@exchanges << exchange
|
32
|
+
|
33
|
+
exchange.header('Host', [uri.host, uri.port].compact.join(':'))
|
34
|
+
exchange.header('Accept', '*/*')
|
35
|
+
request.headers.each do |name, value|
|
36
|
+
exchange.header(name, value)
|
37
|
+
end
|
38
|
+
|
39
|
+
yield exchange if block_given?
|
40
|
+
|
41
|
+
#puts; puts "#{__FILE__}:#{__LINE__} => #{(request).inspect}"
|
42
|
+
|
43
|
+
exchange.do_request
|
44
|
+
|
45
|
+
response = HTTP::Response.new
|
46
|
+
response.headers = exchange.response.headers.dup
|
47
|
+
response.status = exchange.response.code
|
48
|
+
response.body_string = exchange.response.body
|
49
|
+
|
50
|
+
#puts; puts "#{__FILE__}:#{__LINE__} => #{(response).inspect}"
|
51
|
+
return response
|
52
|
+
end
|
53
|
+
|
54
|
+
class Exchange
|
55
|
+
def initialize
|
56
|
+
@uri = nil
|
57
|
+
@method = nil
|
58
|
+
@headers = Webmachine::Headers.new
|
59
|
+
@query_params = {}
|
60
|
+
@req = nil
|
61
|
+
@res = nil
|
62
|
+
end
|
63
|
+
attr_accessor :uri, :method, :body, :dispatcher
|
64
|
+
attr_reader :headers, :query_params
|
65
|
+
|
66
|
+
# Returns the request object.
|
67
|
+
def request
|
68
|
+
@req || webmachine_test_error('No request object yet. Issue a request first.')
|
69
|
+
end
|
70
|
+
|
71
|
+
# Returns the response object after a request has been made.
|
72
|
+
def response
|
73
|
+
@res || webmachine_test_error('No response yet. Issue a request first!')
|
74
|
+
end
|
75
|
+
|
76
|
+
# Set a single header for the next request.
|
77
|
+
def header(name, value)
|
78
|
+
@headers[name] = value
|
79
|
+
end
|
80
|
+
|
81
|
+
def headers=(hash)
|
82
|
+
hash.each do |key, value|
|
83
|
+
header(key, value)
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
def query_param(name, value)
|
88
|
+
@query_params[name] = value
|
89
|
+
end
|
90
|
+
|
91
|
+
def query_params=(hash)
|
92
|
+
hash.each do |key, value|
|
93
|
+
query_param(key, value)
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
def do_request
|
98
|
+
self.uri = Addressable::URI.parse(uri)
|
99
|
+
uri.query_values = (uri.query_values || {}).merge(query_params)
|
100
|
+
|
101
|
+
|
102
|
+
@req = Webmachine::Request.new(method, uri, headers, RequestBody.new(body))
|
103
|
+
@res = Webmachine::Response.new
|
104
|
+
|
105
|
+
dispatcher.dispatch(@req, @res)
|
106
|
+
|
107
|
+
return @res
|
108
|
+
end
|
109
|
+
|
110
|
+
class RequestBody
|
111
|
+
# @return the request from Mongrel
|
112
|
+
|
113
|
+
# @param request the request from Mongrel
|
114
|
+
def initialize(body)
|
115
|
+
@raw_body = body
|
116
|
+
end
|
117
|
+
|
118
|
+
def body
|
119
|
+
@body =
|
120
|
+
case @raw_body
|
121
|
+
when IO, StringIO
|
122
|
+
@raw_body.rewind
|
123
|
+
@raw_body.read
|
124
|
+
when String
|
125
|
+
@raw_body
|
126
|
+
else
|
127
|
+
raise "Can't handle body type: #{@raw_body.class}"
|
128
|
+
end
|
129
|
+
|
130
|
+
end
|
131
|
+
|
132
|
+
# @return [String] the request body as a string
|
133
|
+
def to_s
|
134
|
+
body
|
135
|
+
end
|
136
|
+
|
137
|
+
# @yield [chunk]
|
138
|
+
# @yieldparam [String] chunk a chunk of the request body
|
139
|
+
def each(&block)
|
140
|
+
yield(body)
|
141
|
+
end
|
142
|
+
end # class RequestBody
|
143
|
+
|
144
|
+
private
|
145
|
+
def webmachine_test_error(msg)
|
146
|
+
raise Webmachine::Test::Error.new(msg)
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
module RoadForest
|
2
|
+
module Testing
|
3
|
+
class MatchesQuery
|
4
|
+
def initialize(&block)
|
5
|
+
@query = ::RDF::Query.new(&block)
|
6
|
+
end
|
7
|
+
|
8
|
+
def matches?(actual)
|
9
|
+
@actual = actual
|
10
|
+
solutions = @query.execute(actual)
|
11
|
+
not solutions.empty?
|
12
|
+
end
|
13
|
+
|
14
|
+
def failure_message_for_should
|
15
|
+
"expected #{@query.inspect} to return solutions on #{@actual.inspect}, but didn't"
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
class ListEquivalence
|
20
|
+
def initialize(expected)
|
21
|
+
@expected = expected
|
22
|
+
end
|
23
|
+
|
24
|
+
def matches?(actual)
|
25
|
+
@actual = actual
|
26
|
+
@actual_extra = @actual - @expected
|
27
|
+
@expected_extra = @expected - @actual
|
28
|
+
@actual_extra.empty? and @expected_extra.empty?
|
29
|
+
end
|
30
|
+
|
31
|
+
def failure_message_for_should
|
32
|
+
"expected #{@actual.inspect} to have the same elements as #{@expected.inspect}"
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
class StatementsFromGraph
|
37
|
+
def initialize(graph)
|
38
|
+
@graph = graph
|
39
|
+
end
|
40
|
+
|
41
|
+
def that_match_query(query)
|
42
|
+
@graph.query(query).to_a
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
module HelperMethods
|
47
|
+
def statements_from_graph(graph)
|
48
|
+
StatementsFromGraph.new(graph)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
module MatcherMethods
|
53
|
+
def match_query(&block)
|
54
|
+
MatchesQuery.new(&block)
|
55
|
+
end
|
56
|
+
|
57
|
+
def be_equivalent_to(list)
|
58
|
+
ListEquivalence.new(list)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
RSpec::configure do |config|
|
65
|
+
config.include RoadForest::Testing::MatcherMethods
|
66
|
+
config.include RoadForest::Testing::HelperMethods
|
67
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'roadforest/rdf/graph-store'
|
2
|
+
require 'roadforest/remote-host'
|
3
|
+
require 'roadforest/test-support/http-client'
|
4
|
+
module RoadForest::TestSupport
|
5
|
+
class RemoteHost < ::RoadForest::RemoteHost
|
6
|
+
def initialize(app)
|
7
|
+
@app = app
|
8
|
+
super(app.canonical_host)
|
9
|
+
end
|
10
|
+
|
11
|
+
def build_graph_store
|
12
|
+
RoadForest::RDF::GraphStore.new
|
13
|
+
end
|
14
|
+
|
15
|
+
def http_client
|
16
|
+
@http_client ||= HTTPClient.new(@app, @url)
|
17
|
+
end
|
18
|
+
|
19
|
+
def http_exchanges
|
20
|
+
http_client.exchanges
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|