webspicy 0.1.0.pre.rc1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (38) hide show
  1. checksums.yaml +7 -0
  2. data/Gemfile +2 -0
  3. data/LICENSE.md +22 -0
  4. data/README.md +7 -0
  5. data/Rakefile +11 -0
  6. data/examples/restful/Gemfile +5 -0
  7. data/examples/restful/Gemfile.lock +69 -0
  8. data/examples/restful/Rakefile +21 -0
  9. data/examples/restful/app.rb +32 -0
  10. data/examples/restful/webspicy/schema.fio +4 -0
  11. data/examples/restful/webspicy/todo/getTodo.yml +52 -0
  12. data/examples/restful/webspicy/todo/getTodos.yml +39 -0
  13. data/lib/webspicy/checker.rb +26 -0
  14. data/lib/webspicy/client/http_client.rb +70 -0
  15. data/lib/webspicy/client.rb +20 -0
  16. data/lib/webspicy/configuration.rb +168 -0
  17. data/lib/webspicy/formaldoc.fio +47 -0
  18. data/lib/webspicy/resource/service/invocation.rb +158 -0
  19. data/lib/webspicy/resource/service/test_case.rb +74 -0
  20. data/lib/webspicy/resource/service.rb +49 -0
  21. data/lib/webspicy/resource.rb +43 -0
  22. data/lib/webspicy/scope.rb +113 -0
  23. data/lib/webspicy/tester/asserter.rb +94 -0
  24. data/lib/webspicy/tester/assertions.rb +103 -0
  25. data/lib/webspicy/tester.rb +96 -0
  26. data/lib/webspicy/version.rb +8 -0
  27. data/lib/webspicy.rb +112 -0
  28. data/spec/unit/resource/service/test_dress_params.rb +34 -0
  29. data/spec/unit/resource/test_instantiate_url.rb +20 -0
  30. data/spec/unit/resource/test_url_placeholders.rb +16 -0
  31. data/spec/unit/scope/test_each_resource.rb +59 -0
  32. data/spec/unit/scope/test_each_service.rb +51 -0
  33. data/spec/unit/scope/test_to_real_url.rb +75 -0
  34. data/spec/unit/spec_helper.rb +28 -0
  35. data/spec/unit/test_configuration.rb +84 -0
  36. data/spec/unit/tester/test_assertions.rb +108 -0
  37. data/tasks/test.rake +27 -0
  38. metadata +149 -0
@@ -0,0 +1,96 @@
1
+ module Webspicy
2
+ class Tester
3
+
4
+ def initialize(config)
5
+ @config = config
6
+ end
7
+ attr_reader :config
8
+
9
+ def call
10
+ Webspicy.with_scope_for(config) do |scope|
11
+ client = scope.get_client
12
+ scope.each_resource do |resource|
13
+ scope.each_service(resource) do |service|
14
+ RSpec.describe "#{service.method} `#{resource.url}`" do
15
+ scope.each_example(service) do |test_case|
16
+ describe test_case.description do
17
+
18
+ before(:each) do
19
+ client.before(test_case, service, resource)
20
+ end
21
+
22
+ subject do
23
+ client.call(test_case, service, resource)
24
+ end
25
+
26
+ it 'can be invoked successfuly' do
27
+ expect(subject.done?).to eq(true)
28
+ end
29
+
30
+ it 'meets the status and content type specification' do
31
+ expect(subject.expected_status_unmet).to(be_nil)
32
+ expect(subject.expected_content_type_unmet).to(be_nil)
33
+ end
34
+
35
+ it 'has the expected response headers' do
36
+ expect(subject.expected_headers_unmet).to(be_nil)
37
+ end if test_case.has_expected_headers?
38
+
39
+ it 'meets the output data schema' do
40
+ expect(subject.expected_schema_unmet).to(be_nil)
41
+ end
42
+
43
+ it 'meets all assertions' do
44
+ expect(subject.assertions_unmet).to(be_nil)
45
+ end if test_case.has_assertions?
46
+
47
+ end
48
+ end # service.examples
49
+
50
+ scope.each_counterexamples(service) do |test_case|
51
+ describe test_case.description do
52
+
53
+ before(:each) do
54
+ client.before(test_case, service, resource)
55
+ end
56
+
57
+ subject do
58
+ client.call(test_case, service, resource)
59
+ end
60
+
61
+ it 'can be invoked successfuly' do
62
+ expect(subject.done?).to eq(true)
63
+ end
64
+
65
+ it 'meets the status and content type specification' do
66
+ expect(subject.expected_status_unmet).to(be_nil)
67
+ expect(subject.expected_content_type_unmet).to(be_nil)
68
+ end
69
+
70
+ it 'has the expected response headers' do
71
+ expect(subject.expected_headers_unmet).to(be_nil)
72
+ end if test_case.has_expected_headers?
73
+
74
+ it 'meets the output data schema' do
75
+ expect(subject.expected_schema_unmet).to(be_nil)
76
+ end
77
+
78
+ it 'meets all assertions' do
79
+ expect(subject.assertions_unmet).to(be_nil)
80
+ end if test_case.has_assertions?
81
+
82
+ it 'has expected error' do
83
+ expect(subject.expected_error_unmet).to(be_nil)
84
+ end if test_case.has_expected_error?
85
+
86
+ end
87
+ end
88
+ end
89
+ end
90
+ end
91
+ RSpec::Core::Runner.run config.rspec_options
92
+ end
93
+ end
94
+
95
+ end # class Tester
96
+ end # module Webspicy
@@ -0,0 +1,8 @@
1
+ module Webspicy
2
+ module Version
3
+ TINY = "0-rc1"
4
+ MINOR = 1
5
+ MAJOR = 0
6
+ end
7
+ VERSION = "#{Version::MAJOR}.#{Version::MINOR}.#{Version::TINY}"
8
+ end
data/lib/webspicy.rb ADDED
@@ -0,0 +1,112 @@
1
+ require 'http'
2
+ require 'json'
3
+ require 'path'
4
+ require 'finitio'
5
+ require 'logger'
6
+ require 'ostruct'
7
+ require 'yaml'
8
+ require 'rspec'
9
+ module Webspicy
10
+
11
+ ###
12
+ ### Load library
13
+ ###
14
+
15
+ require 'webspicy/configuration'
16
+ require 'webspicy/scope'
17
+ require 'webspicy/client'
18
+ require 'webspicy/client/http_client'
19
+ require 'webspicy/resource'
20
+ require 'webspicy/checker'
21
+ require 'webspicy/tester'
22
+ require 'webspicy/tester/assertions'
23
+ require 'webspicy/tester/asserter'
24
+
25
+ ###
26
+ ### About folders
27
+ ###
28
+
29
+ ROOT_FOLDER = Path.backfind('.[Gemfile]')
30
+
31
+ EXAMPLES_FOLDER = ROOT_FOLDER/'examples'
32
+
33
+ ###
34
+ ### About formal doc and resources defined there
35
+ ###
36
+
37
+ FORMALDOC = Finitio::DEFAULT_SYSTEM.parse (Path.dir/"webspicy/formaldoc.fio").read
38
+
39
+ def resource(raw, file = nil)
40
+ FORMALDOC["Resource"].dress(raw)
41
+ end
42
+ module_function :resource
43
+
44
+ def service(raw)
45
+ FORMALDOC["Service"].dress(raw)
46
+ end
47
+ module_function :service
48
+
49
+ def test_case(raw)
50
+ FORMALDOC["TestCase"].dress(raw)
51
+ end
52
+ module_function :test_case
53
+
54
+ #
55
+ # Yields a Scope instance for the configuration passed as parameter.
56
+ #
57
+ # This method makes sure that the scope will also be accessible for
58
+ # Finitio world schema parsing/dressing. Given that some global state
59
+ # is required (see "Schema" ADT, the dresser in particular, which calls
60
+ # `schema` later), the scope is put as a thread-local variable...
61
+ #
62
+ def with_scope_for(config)
63
+ scope = set_current_scope(Scope.new(config))
64
+ yield scope
65
+ set_current_scope(nil)
66
+ end
67
+ module_function :with_scope_for
68
+
69
+ #
70
+ # Sets the current scope.
71
+ #
72
+ # See `with_scope_for`
73
+ #
74
+ def set_current_scope(scope)
75
+ Thread.current[:webspicy_scope] = scope
76
+ end
77
+ module_function :set_current_scope
78
+
79
+ #
80
+ # Parses a webservice schema (typically input or output) in the context
81
+ # of the current scope previously installed using `with_scope_for`.
82
+ #
83
+ # If no scope has previously been installed, Finitio's default system
84
+ # is used instead of another schema.
85
+ #
86
+ def schema(fio)
87
+ if scope = Thread.current[:webspicy_scope]
88
+ scope.parse_schema(fio)
89
+ else
90
+ Finitio::DEFAULT_SYSTEM.parse(fio)
91
+ end
92
+ end
93
+ module_function :schema
94
+
95
+ ###
96
+ ### Logging facade
97
+ ###
98
+
99
+ LOGGER = ::Logger.new(STDOUT)
100
+ LOGGER.level = Logger.const_get(ENV['LOG_LEVEL'] || 'WARN')
101
+
102
+ def info(*args, &bl)
103
+ LOGGER && LOGGER.info(*args, &bl)
104
+ end
105
+ module_function :info
106
+
107
+ def debug(*args, &bl)
108
+ LOGGER && LOGGER.debug(*args, &bl)
109
+ end
110
+ module_function :debug
111
+
112
+ end
@@ -0,0 +1,34 @@
1
+ require 'spec_helper'
2
+ module Webspicy
3
+ class Resource
4
+ describe Service, "dress_params" do
5
+
6
+ it 'symbolizes keys' do
7
+ service = Webspicy.service({
8
+ method: "GET",
9
+ description: "Test service",
10
+ preconditions: "Foo",
11
+ input_schema: "{ id: Integer }",
12
+ output_schema: "{}",
13
+ error_schema: "{}"
14
+ })
15
+ params = service.dress_params("id" => 1247)
16
+ expect(params).to eq(id: 1247)
17
+ end
18
+
19
+ it 'supports an array' do
20
+ service = Webspicy.service({
21
+ method: "GET",
22
+ description: "Test service",
23
+ preconditions: "Foo",
24
+ input_schema: "[{ id: Integer }]",
25
+ output_schema: "{}",
26
+ error_schema: "{}"
27
+ })
28
+ params = service.dress_params([{"id" => 1247}])
29
+ expect(params).to eq([{id: 1247}])
30
+ end
31
+
32
+ end
33
+ end # class Resource
34
+ end # module Webspicy
@@ -0,0 +1,20 @@
1
+ require "spec_helper"
2
+ module Webspicy
3
+ describe Resource, "instantiate_url" do
4
+
5
+ it 'does nothing when the url has no placeholder' do
6
+ r = Resource.new(url: "/test/a/url")
7
+ url, params = r.instantiate_url(foo: "bar")
8
+ expect(url).to eq("/test/a/url")
9
+ expect(params).to eq(foo: "bar")
10
+ end
11
+
12
+ it 'instantiates placeholders and strips corresponding params' do
13
+ r = Resource.new(url: "/test/{foo}/url")
14
+ url, params = r.instantiate_url(foo: "bar", baz: "coz")
15
+ expect(url).to eq("/test/bar/url")
16
+ expect(params).to eq(baz: "coz")
17
+ end
18
+
19
+ end
20
+ end
@@ -0,0 +1,16 @@
1
+ require "spec_helper"
2
+ module Webspicy
3
+ describe Resource, "url_placeholders" do
4
+
5
+ it 'returns an empty array on none' do
6
+ r = Resource.new(url: "/test/a/url")
7
+ expect(r.url_placeholders).to eq([])
8
+ end
9
+
10
+ it 'returns all placeholders' do
11
+ r = Resource.new(url: "/test/{foo}/url/{bar}")
12
+ expect(r.url_placeholders).to eq([:foo, :bar])
13
+ end
14
+
15
+ end
16
+ end
@@ -0,0 +1,59 @@
1
+ require 'spec_helper'
2
+ module Webspicy
3
+ describe Scope, 'each_resource' do
4
+
5
+ with_scope_management
6
+
7
+ let(:scope) {
8
+ Scope.new(configuration)
9
+ }
10
+
11
+ subject {
12
+ scope.each_resource.to_a
13
+ }
14
+
15
+ context 'without any filter' do
16
+
17
+ let(:configuration) {
18
+ Configuration.new{|c|
19
+ c.add_folder restful_folder
20
+ }
21
+ }
22
+
23
+ it 'returns all files' do
24
+ expect(subject.size).to eql(restful_folder.glob('**/*.yml').size)
25
+ end
26
+ end
27
+
28
+ context 'with a file filter as a proc' do
29
+
30
+ let(:configuration) {
31
+ Configuration.new{|c|
32
+ c.add_folder restful_folder
33
+ c.file_filter = ->(f) {
34
+ f.basename.to_s == "getTodo.yml"
35
+ }
36
+ }
37
+ }
38
+
39
+ it 'returns only that file' do
40
+ expect(subject.size).to eql(1)
41
+ end
42
+ end
43
+
44
+ context 'with a file filter as a Regex' do
45
+
46
+ let(:configuration) {
47
+ Configuration.new{|c|
48
+ c.add_folder restful_folder
49
+ c.file_filter = /getTodo.yml/
50
+ }
51
+ }
52
+
53
+ it 'returns only that file' do
54
+ expect(subject.size).to eql(1)
55
+ end
56
+ end
57
+
58
+ end
59
+ end
@@ -0,0 +1,51 @@
1
+ require 'spec_helper'
2
+ module Webspicy
3
+ describe Scope, 'each_service' do
4
+
5
+ with_scope_management
6
+
7
+ let(:scope) {
8
+ Scope.new(configuration)
9
+ }
10
+
11
+ let(:resource) {
12
+ scope.each_resource.first
13
+ }
14
+
15
+ subject {
16
+ scope.each_service(resource).to_a
17
+ }
18
+
19
+ context 'without any filter' do
20
+
21
+ let(:configuration) {
22
+ Configuration.new{|c|
23
+ c.add_folder restful_folder
24
+ c.file_filter = /getTodo.yml/
25
+ }
26
+ }
27
+
28
+ it 'returns all services' do
29
+ expect(subject.size).to eql(1)
30
+ end
31
+ end
32
+
33
+ context 'with a service filter as a proc' do
34
+
35
+ let(:configuration) {
36
+ Configuration.new{|c|
37
+ c.add_folder restful_folder
38
+ c.file_filter = /getTodo.yml/
39
+ c.service_filter = ->(s) {
40
+ s.method == "POST"
41
+ }
42
+ }
43
+ }
44
+
45
+ it 'returns nothing' do
46
+ expect(subject.size).to eql(0)
47
+ end
48
+ end
49
+
50
+ end
51
+ end
@@ -0,0 +1,75 @@
1
+ require 'spec_helper'
2
+ module Webspicy
3
+ describe Scope, 'to_real_url' do
4
+
5
+ let(:scope) {
6
+ Scope.new(configuration)
7
+ }
8
+
9
+ context 'when no host at all' do
10
+
11
+ let(:configuration) {
12
+ Configuration.new
13
+ }
14
+
15
+ it 'does nothing on absolute URLs already' do
16
+ url = 'http://127.0.0.1:4567/todo'
17
+ got = scope.to_real_url(url)
18
+ expect(got).to eql(url)
19
+ end
20
+
21
+ it 'fails on relative URLs' do
22
+ expect(->(){
23
+ scope.to_real_url("/todo")
24
+ }).to raise_error(/Unable to resolve `\/todo`/)
25
+ end
26
+ end
27
+
28
+ context 'with a static host' do
29
+
30
+ let(:configuration) {
31
+ Configuration.new do |c|
32
+ c.host = "http://127.0.0.1:4568"
33
+ end
34
+ }
35
+
36
+ it 'does nothing on absolute URLs already' do
37
+ url = 'http://127.0.0.1:4567/todo'
38
+ got = scope.to_real_url(url)
39
+ expect(got).to eql(url)
40
+ end
41
+
42
+ it 'resolves relative URLs as expected' do
43
+ url = '/todo'
44
+ got = scope.to_real_url(url)
45
+ expect(got).to eql("http://127.0.0.1:4568/todo")
46
+ end
47
+
48
+ end
49
+
50
+ context 'with a dynamic host resolver' do
51
+
52
+ let(:configuration) {
53
+ Configuration.new do |c|
54
+ c.host = ->(url) {
55
+ "http://127.0.0.1:4568#{url}"
56
+ }
57
+ end
58
+ }
59
+
60
+ it 'resolves absolute URLs' do
61
+ url = 'http://127.0.0.1:4567/todo'
62
+ got = scope.to_real_url(url)
63
+ expect(got).to eql("http://127.0.0.1:4568#{url}")
64
+ end
65
+
66
+ it 'resolves relative URLs as expected' do
67
+ url = '/todo'
68
+ got = scope.to_real_url(url)
69
+ expect(got).to eql("http://127.0.0.1:4568/todo")
70
+ end
71
+
72
+ end
73
+
74
+ end
75
+ end
@@ -0,0 +1,28 @@
1
+ require 'rspec'
2
+ require 'webspicy'
3
+
4
+ module SpecHelper
5
+
6
+ def restful_folder
7
+ Webspicy::EXAMPLES_FOLDER/'restful/webspicy'
8
+ end
9
+
10
+ end
11
+
12
+ module ScopeManagement
13
+
14
+ def with_scope_management
15
+ before(:each) {
16
+ Webspicy.set_current_scope(scope)
17
+ }
18
+ after(:each) {
19
+ Webspicy.set_current_scope(nil)
20
+ }
21
+ end
22
+
23
+ end
24
+
25
+ RSpec.configure do |c|
26
+ c.include SpecHelper
27
+ c.extend ScopeManagement
28
+ end
@@ -0,0 +1,84 @@
1
+ require 'spec_helper'
2
+ module Webspicy
3
+ describe Configuration do
4
+
5
+ it 'yields itself at construction' do
6
+ seen = nil
7
+ Configuration.new do |c|
8
+ seen = c
9
+ end
10
+ expect(seen).to be_a(Configuration)
11
+ end
12
+
13
+ describe 'run_counterexamples' do
14
+
15
+ it 'is true by default' do
16
+ config = Configuration.new
17
+ expect(config.run_counterexamples?).to eql(true)
18
+ end
19
+
20
+ it 'implements backward compatibility with ROBUST env variable' do
21
+ ENV['ROBUST'] = 'no'
22
+ config = Configuration.new
23
+ expect(config.run_counterexamples?).to eql(false)
24
+
25
+ ENV['ROBUST'] = 'yes'
26
+ config = Configuration.new
27
+ expect(config.run_counterexamples?).to eql(true)
28
+ end
29
+
30
+ it 'ignores the environment is set explicitly' do
31
+ ENV['ROBUST'] = 'yes'
32
+ config = Configuration.new do |c|
33
+ c.run_counterexamples = false
34
+ end
35
+ expect(config.run_counterexamples?).to eql(false)
36
+ end
37
+ end
38
+
39
+ describe 'file_filter' do
40
+
41
+ it 'is nil by default' do
42
+ config = Configuration.new
43
+ expect(config.file_filter).to be_nil
44
+ end
45
+
46
+ it 'is implements backward compatibility with the RESOURCE env variable' do
47
+ ENV['RESOURCE'] = 'getTodo.yml'
48
+ config = Configuration.new
49
+ expect(config.file_filter).to be_a(Regexp)
50
+ end
51
+
52
+ it 'ignores the environment is set explicitly' do
53
+ ENV['RESOURCE'] = 'getTodo.yml'
54
+ config = Configuration.new do |c|
55
+ c.file_filter = nil
56
+ end
57
+ expect(config.file_filter).to be_nil
58
+ end
59
+ end
60
+
61
+ describe 'service_filter' do
62
+
63
+ it 'is nil by default' do
64
+ config = Configuration.new
65
+ expect(config.service_filter).to be_nil
66
+ end
67
+
68
+ it 'is implements backward compatibility with the RESOURCE env variable' do
69
+ ENV['METHOD'] = 'GET'
70
+ config = Configuration.new
71
+ expect(config.service_filter).to be_a(Proc)
72
+ end
73
+
74
+ it 'ignores the environment is set explicitly' do
75
+ ENV['METHOD'] = 'POST'
76
+ config = Configuration.new do |c|
77
+ c.service_filter = nil
78
+ end
79
+ expect(config.service_filter).to be_nil
80
+ end
81
+ end
82
+
83
+ end
84
+ end
@@ -0,0 +1,108 @@
1
+ require 'spec_helper'
2
+
3
+ module Webspicy
4
+ class Tester
5
+ describe Assertions do
6
+ include Assertions
7
+
8
+ public :extract_path
9
+
10
+ it 'has an extract_path helper' do
11
+ target = { foo: "Hello", bar: { foo: "Hello" }, baz: [{ foo: "world" }] }
12
+ expect(extract_path(target)).to be(target)
13
+ expect(extract_path(target, nil)).to be(target)
14
+ expect(extract_path(target, '')).to be(target)
15
+ expect(extract_path(target, 'foo')).to eql("Hello")
16
+ expect(extract_path(target, 'bar/foo')).to eql("Hello")
17
+ expect(extract_path(target, 'baz/0')).to eql({ foo: "world" })
18
+ expect(extract_path(target, 'baz/0/foo')).to eql("world")
19
+ end
20
+
21
+ it 'has an exists() assertion' do
22
+ expect(exists nil).to be(false)
23
+ expect(exists []).to be(true)
24
+ expect(exists [1]).to be(true)
25
+ expect(exists({ foo: [] }, 'foo')).to be(true)
26
+ expect(exists({ foo: {} }, 'foo')).to be(true)
27
+ expect(exists({ foo: {} }, 'foo/bar')).to be(false)
28
+ end
29
+
30
+ it 'has an notExists() assertion' do
31
+ expect(notExists nil).to be(true)
32
+ expect(notExists []).to be(false)
33
+ expect(notExists [1]).to be(false)
34
+ expect(notExists({ foo: [] }, 'foo')).to be(false)
35
+ expect(notExists({ foo: {} }, 'foo')).to be(false)
36
+ expect(notExists({ foo: {} }, 'foo/bar')).to be(true)
37
+ end
38
+
39
+ it 'has an empty() assertion' do
40
+ expect(empty []).to be(true)
41
+ expect(empty [1]).to be(false)
42
+ expect(empty({ foo: [] }, 'foo')).to be(true)
43
+ expect(empty({ foo: [1] }, 'foo')).to be(false)
44
+ end
45
+
46
+ it 'has a notEmpty() assertion' do
47
+ expect(notEmpty []).to be(false)
48
+ expect(notEmpty [1]).to be(true)
49
+ expect(notEmpty({ foo: [] }, 'foo')).to be(false)
50
+ expect(notEmpty({ foo: [1] }, 'foo')).to be(true)
51
+ end
52
+
53
+ it 'has an size() assertion' do
54
+ expect(size [], 0).to be(true)
55
+ expect(size [], 1).to be(false)
56
+ expect(size [12], 1).to be(true)
57
+ expect(size({ foo: [] }, 'foo', 0)).to be(true)
58
+ expect(size({ foo: [] }, 'foo', 1)).to be(false)
59
+ expect(size({ foo: ['bar'] }, 'foo', 1)).to be(true)
60
+ end
61
+
62
+ it 'has an idIn assertion' do
63
+ expect(idIn [{id: 1}, {id: 2}], [1, 2]).to be(true)
64
+ expect(idIn [{id: 1}, {id: 2}], [2, 1]).to be(true)
65
+ expect(idIn [{id: 1}, {id: 2}], [1, 3]).to be(false)
66
+ expect(idIn [{id: 1}, {id: 2}], [1]).to be(false)
67
+ expect(idIn({ foo: [{id: 1}, {id: 2}] }, 'foo', [1, 2])).to be(true)
68
+
69
+ expect(idIn({id: 1}, [1])).to be(true)
70
+ expect(idIn({id: 1}, [2])).to be(false)
71
+ end
72
+
73
+ it 'has an idNotIn assertion' do
74
+ expect(idNotIn [{id: 1}, {id: 2}], [3]).to be(true)
75
+ expect(idNotIn [{id: 1}, {id: 2}], [3, 4]).to be(true)
76
+ expect(idNotIn [{id: 1}, {id: 2}], [1]).to be(false)
77
+ expect(idNotIn({ foo: [{id: 1}, {id: 2}] }, 'foo', [1])).to be(false)
78
+ expect(idNotIn({ foo: [{id: 1}, {id: 2}] }, 'foo', [3])).to be(true)
79
+
80
+ expect(idNotIn({id: 1}, [3])).to be(true)
81
+ expect(idNotIn({id: 1}, [1])).to be(false)
82
+ end
83
+
84
+ it 'has an idFD assertion' do
85
+ target = { foo: [
86
+ { id: 1, bar: "bar" },
87
+ { id: 2, bar: "baz" }
88
+ ] }
89
+ expect(idFD(target, 'foo', 1, bar: "bar")).to be(true)
90
+ expect(idFD(target, 'foo', 1, bar: "baz")).to be(false)
91
+ expect(idFD(target, 'foo', 1, baz: "boz")).to be(false)
92
+
93
+ target = { foo: { id: 1, bar: "bar" } }
94
+ expect(idFD(target, 'foo', 1, bar: "bar")).to be(true)
95
+ expect(idFD(target, 'foo', 1, bar: "baz")).to be(false)
96
+ expect(idFD(target, 'foo', 1, baz: "boz")).to be(false)
97
+ end
98
+
99
+ it 'has an pathFD assertion' do
100
+ target = { foo: { bar: "baz"} }
101
+ expect(pathFD(target, 'foo', bar: "baz")).to be(true)
102
+ expect(pathFD(target, 'foo', bar: "boz")).to be(false)
103
+ expect(pathFD(target, 'foo', boz: "biz")).to be(false)
104
+ end
105
+
106
+ end
107
+ end # class Tester
108
+ end # module Webspicy