jason_spec 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/.document ADDED
@@ -0,0 +1,5 @@
1
+ lib/**/*.rb
2
+ bin/*
3
+ -
4
+ features/**/*.feature
5
+ LICENSE.txt
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --color
data/Gemfile ADDED
@@ -0,0 +1,12 @@
1
+ source "http://rubygems.org"
2
+
3
+ gem "rufus-json", "1.0.6"
4
+ gem "rspec", "~> 2.8.0"
5
+
6
+ group :development do
7
+ gem "yard", "~> 0.7"
8
+ gem "rdoc", "~> 3.12"
9
+ gem "bundler", "~> 1.0"
10
+ gem "jeweler", "~> 1.8.7"
11
+ gem "simplecov", ">= 0"
12
+ end
data/Gemfile.lock ADDED
@@ -0,0 +1,72 @@
1
+ GEM
2
+ remote: http://rubygems.org/
3
+ specs:
4
+ addressable (2.3.5)
5
+ builder (3.2.2)
6
+ diff-lcs (1.1.3)
7
+ faraday (0.8.8)
8
+ multipart-post (~> 1.2.0)
9
+ git (1.2.6)
10
+ github_api (0.10.1)
11
+ addressable
12
+ faraday (~> 0.8.1)
13
+ hashie (>= 1.2)
14
+ multi_json (~> 1.4)
15
+ nokogiri (~> 1.5.2)
16
+ oauth2
17
+ hashie (2.0.5)
18
+ highline (1.6.19)
19
+ httpauth (0.2.0)
20
+ jeweler (1.8.7)
21
+ builder
22
+ bundler (~> 1.0)
23
+ git (>= 1.2.5)
24
+ github_api (= 0.10.1)
25
+ highline (>= 1.6.15)
26
+ nokogiri (= 1.5.10)
27
+ rake
28
+ rdoc
29
+ json (1.8.0)
30
+ jwt (0.1.8)
31
+ multi_json (>= 1.5)
32
+ multi_json (1.8.0)
33
+ multi_xml (0.5.5)
34
+ multipart-post (1.2.0)
35
+ nokogiri (1.5.10)
36
+ oauth2 (0.9.2)
37
+ faraday (~> 0.8)
38
+ httpauth (~> 0.2)
39
+ jwt (~> 0.1.4)
40
+ multi_json (~> 1.0)
41
+ multi_xml (~> 0.5)
42
+ rack (~> 1.2)
43
+ rack (1.5.2)
44
+ rake (10.1.0)
45
+ rdoc (3.12.2)
46
+ json (~> 1.4)
47
+ rspec (2.8.0)
48
+ rspec-core (~> 2.8.0)
49
+ rspec-expectations (~> 2.8.0)
50
+ rspec-mocks (~> 2.8.0)
51
+ rspec-core (2.8.0)
52
+ rspec-expectations (2.8.0)
53
+ diff-lcs (~> 1.1.2)
54
+ rspec-mocks (2.8.0)
55
+ rufus-json (1.0.6)
56
+ simplecov (0.7.1)
57
+ multi_json (~> 1.0)
58
+ simplecov-html (~> 0.7.1)
59
+ simplecov-html (0.7.1)
60
+ yard (0.8.7.2)
61
+
62
+ PLATFORMS
63
+ ruby
64
+
65
+ DEPENDENCIES
66
+ bundler (~> 1.0)
67
+ jeweler (~> 1.8.7)
68
+ rdoc (~> 3.12)
69
+ rspec (~> 2.8.0)
70
+ rufus-json (= 1.0.6)
71
+ simplecov
72
+ yard (~> 0.7)
data/LICENSE.txt ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2013 OrganisedMinds GmbH
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,120 @@
1
+ # jason_spec
2
+
3
+ Write specs for JSON without writing JSON or data-structures
4
+
5
+ <a href="http://www.flickr.com/photos/frogdna/5534481247/" title="Friday the 13th - Jason Mask Replica by frogDNA, on Flickr"><img src="http://farm6.staticflickr.com/5094/5534481247_361aa64980.jpg" width="375" height="500" alt="Friday the 13th - Jason Mask Replica"></a>
6
+
7
+ ## Install
8
+
9
+ `gem install jason_spec`
10
+
11
+ or, in your Gemfile, possibly in the test group
12
+
13
+ `gem "jason_spec"`
14
+
15
+
16
+ ## Usage
17
+
18
+ ### Test by specification
19
+
20
+ Jason spec is designed to test json by specifying the desired result. This
21
+ means you should not type or create any JSON; or even data structures.
22
+
23
+ The following snippet:
24
+
25
+ ```ruby
26
+ %q({"first_name":"Jason","last_name":"Voorhees"}).should have_jason([:first_name,:last_name])
27
+ ```
28
+
29
+ would result in a match. The supplied JSON has a `:first_name` and a
30
+ `:last_name` key - that's all we wanted to know.
31
+
32
+ Off course, you can also specify your root object:
33
+
34
+ ```ruby
35
+ %q({"movie":{"title":"Friday the 13th","release_year":"1980"}}).should have_jason(
36
+ movie: [ :title, :release_year ]
37
+ )
38
+ ```
39
+
40
+ ### Test by object
41
+
42
+ Now it is also nice to test the actual values; you can do this by supplying an
43
+ object in the specifications key, like so:
44
+
45
+ ```ruby
46
+ # assuming ActiveRecord or so
47
+
48
+ my_movie = Movie.find(x)
49
+
50
+ %q({"movie":{"title":"Friday the 13th","release_year":"1980"}}).should have_jason(
51
+ { movie: { my_movie => [ :title, :release_year ] } }
52
+ )
53
+ ```
54
+
55
+ This will check the values of the JSON against the value of `my_movie.title`
56
+ and `my_movie.release_year`
57
+
58
+ ### Test by enhanced specification
59
+
60
+ Now; that is all pretty nice and dandy, but what about complex(er) JSON
61
+ structures? How can I test those, without supplying huge structures as a
62
+ specification?
63
+
64
+ **Jason::Spec** to the resque:
65
+
66
+ ```ruby
67
+ %q({"movies":[{"title":"Friday the 13th"},{"title":"Nightmare on Elm Street"}]}).should have_jason(
68
+ movies: Jason.spec( type: Array, size: 2, each: [ :title ] )
69
+ )
70
+ ```
71
+
72
+ Basicly we are saying; the JSON should have a `movies` key, it should hold
73
+ an `Array` with a size of `2` and each element should have a `title` key.
74
+
75
+ Off course you should combine all of it to make full use of the potential:
76
+
77
+ ```ruby
78
+ json = %q({"user":{
79
+ "user_name":"jason",
80
+ "favorite_movies":[
81
+ {"title":"Friday the 13th","id":1},
82
+ {"title":"Nightmare on Elm Street","id":2}
83
+ ]
84
+ },
85
+ "links":[
86
+ { "href":"/users/2", "rel": "self" },
87
+ { "href":"/users/2/movies", "rel": "favorite movies"}
88
+ ]
89
+ })
90
+
91
+ json.should have_jason(
92
+ user: {
93
+ user_name: "jason",
94
+ favorite_movies: Jason.spec(type: Array, each: [ :title, :id ])
95
+ },
96
+ links: Jason.spec(type: Array, each: [ :href, :rel ])
97
+ )
98
+ ```
99
+
100
+ ## State
101
+
102
+ Alpha - not even released to RubyGems.org.
103
+
104
+ The final example might just work, see `spec/readme_spec.rb`
105
+
106
+ ## Contributing to jason_spec
107
+
108
+ * Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet.
109
+ * Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it.
110
+ * Fork the project.
111
+ * Start a feature/bugfix branch.
112
+ * Commit and push until you are happy with your contribution.
113
+ * Make sure to add tests for it. This is important so I don't break it in a future version unintentionally.
114
+ * Please try not to mess with the Rakefile, version, or history. If you want to have your own version, or is otherwise necessary, that is fine, but please isolate to its own commit so I can cherry-pick around it.
115
+
116
+ ## Copyright
117
+
118
+ Copyright (c) 2013 OrganisedMinds GmbH. See LICENSE.txt for
119
+ further details.
120
+
data/Rakefile ADDED
@@ -0,0 +1,42 @@
1
+ # encoding: utf-8
2
+
3
+ require 'rubygems'
4
+ require 'bundler'
5
+ begin
6
+ Bundler.setup(:default, :development)
7
+ rescue Bundler::BundlerError => e
8
+ $stderr.puts e.message
9
+ $stderr.puts "Run `bundle install` to install missing gems"
10
+ exit e.status_code
11
+ end
12
+ require 'rake'
13
+
14
+ require 'jeweler'
15
+ Jeweler::Tasks.new do |gem|
16
+ # gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options
17
+ gem.name = "jason_spec"
18
+ gem.homepage = "http://github.com/coffeeaddict/jason_spec"
19
+ gem.license = "MIT"
20
+ gem.summary = %Q{Spec your JSON}
21
+ gem.description = %Q{Write specifications for the expected json, without writing json}
22
+ gem.email = "hartog@organisedminds.com"
23
+ gem.authors = ["Hartog C. de Mik"]
24
+ # dependencies defined in Gemfile
25
+ end
26
+ Jeweler::RubygemsDotOrgTasks.new
27
+
28
+ require 'rspec/core'
29
+ require 'rspec/core/rake_task'
30
+ RSpec::Core::RakeTask.new(:spec) do |spec|
31
+ spec.pattern = FileList['spec/**/*_spec.rb']
32
+ end
33
+
34
+ RSpec::Core::RakeTask.new(:rcov) do |spec|
35
+ spec.pattern = 'spec/**/*_spec.rb'
36
+ spec.rcov = true
37
+ end
38
+
39
+ task :default => :spec
40
+
41
+ require 'yard'
42
+ YARD::Rake::YardocTask.new
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.0.1
@@ -0,0 +1,158 @@
1
+ require 'pp'
2
+ require 'jason/spec'
3
+
4
+ module Jason
5
+
6
+ # Provides the have_jason functionality
7
+ class HaveJasonMatcher
8
+
9
+ # @param [Array,Hash] specs List of specifications
10
+ #
11
+ # @example Specs by array
12
+ # # will check if the actual JSON has a foo and a bar key
13
+ # HaveJasonMatcher.new([ :foo, :bar ])
14
+ #
15
+ # @example Specs by hash
16
+ # # will check if the actual has an item object with a foo and a bar key
17
+ # HaveJasonMatcher.new( item: [ :foo, :bar ])
18
+ #
19
+ # # will check if the actual has an item object with a foo key with a
20
+ # # bar value
21
+ # HaveJasonMatcher.new( item: { foo: "bar" } )
22
+ #
23
+ # @example Specs by hash with objects
24
+ # # will check if the actual has a attribute key with the value of
25
+ # # ruby_object.attribute()
26
+ # HaveJasonMatcher.new( ruby_object => [ :attribute ] )
27
+ #
28
+ # @example Specs by Jason.spec
29
+ # # will check the actual against the provided Jason::Spec
30
+ # HaveJasonMatcher.new( item: Jason.spec(type: Hash, fields: [ :foo, :bar ] ))
31
+ #
32
+ def initialize(specs)
33
+ @specs = specs
34
+ end
35
+
36
+ # Check if the given JSON matches the specifications
37
+ #
38
+ # @param [JSON, Hash] actual the value to check
39
+ #
40
+ # @return [Boolean]
41
+ #
42
+ def matches?(actual)
43
+ @actual = actual.is_a?(String) ? Rufus::Json.decode(actual) : actual
44
+
45
+ @misses = match_recursively(@specs, @actual)
46
+
47
+ @misses.empty?
48
+ end
49
+
50
+ # recursivly walk over the specs and the actual to find any misses
51
+ #
52
+ # @param [Array,Hash] specs List of specifications
53
+ # @param [Array,Hash] actual Provided data-structure (once JSON)
54
+ #
55
+ # @return [Hash] Hash with all the misses
56
+ #
57
+ def match_recursively(specs, actual, root="")
58
+ actual ||= {}
59
+
60
+ misses = {}
61
+
62
+ if specs.is_a?(Array)
63
+ specs.map(&:to_s).each do |key|
64
+ res_key = root != "" ? "#{root}.#{key}" : "#{key}"
65
+ if !actual.has_key? key
66
+ misses[res_key] = :missing
67
+ end
68
+ end
69
+
70
+ return misses
71
+ end
72
+
73
+ if specs.is_a?(Jason::Spec)
74
+ root = "root" if root == ""
75
+
76
+ if !actual
77
+ misses[root] = { expected: { key: root }, got: :not_present }
78
+ elsif !specs.fits?(actual)
79
+ misses[root] = { expected: specs.opts, got: specs.misses }
80
+ end
81
+
82
+ return misses
83
+ end
84
+
85
+ specs.each do |key, value|
86
+ res_key = root != "" ? "#{root}.#{key}" : "#{key}"
87
+ key = key.to_s if key.is_a?(Symbol)
88
+
89
+ if key.is_a?(String)
90
+ if value.is_a?(Jason::Spec)
91
+ if !actual[key]
92
+ misses[res_key] = { expected: { key: key }, got: :not_present }
93
+ next
94
+ end
95
+
96
+ if !value.fits?(actual[key])
97
+ misses[res_key] = { expected: value.opts, got: value.misses }
98
+ end
99
+
100
+ elsif value.is_a?(Array) or value.is_a?(Hash)
101
+ begin
102
+ misses.merge!(match_recursively(value, actual[key], res_key))
103
+ rescue => ex
104
+ $stderr.puts ex.backtrace.join("\n\t")
105
+ misses[res_key] = { error: ex.message }
106
+ end
107
+
108
+ elsif actual[key] != value
109
+ misses[res_key] = { expected: value, got: actual[key] }
110
+ end
111
+
112
+ else # the key is an object, the value is an array of methods
113
+ value.each do |attr|
114
+ res_key = root != "" ? "#{root}.#{attr}" : "#{attr}"
115
+
116
+ if attr.is_a?(Hash)
117
+ misses.merge!(match_recursively(attr, actual[attr], res_key))
118
+ next
119
+ end
120
+
121
+ attr = attr.to_s
122
+
123
+ expected = key.send(attr) rescue nil
124
+ found = actual[attr]
125
+
126
+ if found
127
+ if expected.is_a?(Time)
128
+ # revert to seconds - microseconds do not compare
129
+ found = Time.at(Time.parse(found).to_i)
130
+ expected = Time.at(expected.to_i)
131
+
132
+ elsif expected.is_a?(Date)
133
+ found = Date.parse(found)
134
+
135
+ end
136
+ end
137
+
138
+ if found != expected
139
+ misses[res_key] = { expected: expected, got: found }
140
+ end
141
+ end
142
+ end
143
+ end
144
+
145
+ return misses
146
+ end
147
+
148
+ # @return [String] Message to provide if it should have matched but didn't
149
+ def failure_message
150
+ "Jason misses: #{@misses.pretty_inspect}\n\tin #{@actual}"
151
+ end
152
+
153
+ # @return [String] Message to provide if it shouldn't have matched but did
154
+ def negative_failure_message
155
+ "Jason has: #{@actual}"
156
+ end
157
+ end
158
+ end
@@ -0,0 +1,14 @@
1
+ require 'jason/have_jason_matcher'
2
+
3
+ module Jason
4
+ module Matchers
5
+ def have_jason(spec)
6
+ Jason::HaveJasonMatcher.new(spec)
7
+ end
8
+ end
9
+ end
10
+
11
+ RSpec.configure do |config|
12
+ config.include Jason::Matchers
13
+ end
14
+
data/lib/jason/spec.rb ADDED
@@ -0,0 +1,264 @@
1
+ module Jason
2
+
3
+ # @returns [Jason::Spec] Specifications for JSON checking
4
+ def self.spec(spec)
5
+ Spec.new(spec)
6
+ end
7
+
8
+ # Class for holding and checking specifications for a JSON structure
9
+ #
10
+ # @attr_reader [Hash] specs Stored specifications
11
+ # @attr_reader [Array] misses List of collected failures
12
+ #
13
+ class Spec
14
+ attr_reader :specs, :misses
15
+
16
+ # @param [Hash] specs Specifications for testing
17
+ # @option specs [Class] :type Type of the actual
18
+ # @option specs [Integer] :size Size of the actual
19
+ # @option specs [Array] :each Each element in actual should have
20
+ # these fields
21
+ # @option specs [Array] :fields Fields that should be present in actual
22
+ #
23
+ def initialize(specs)
24
+ @specs = specs
25
+ @misses = []
26
+ end
27
+
28
+ # Check if the specs fit the actual
29
+ #
30
+ # @returns [Boolean]
31
+ #
32
+ def fits?(actual)
33
+ @misses = [] # reset misses
34
+ @specs.each do |key, value|
35
+ method = :"match_#{key}"
36
+ if !respond_to?(method)
37
+ @misses << "Unknown spec: #{key}"
38
+ break
39
+ end
40
+
41
+ begin
42
+ self.send(method, value, actual)
43
+ rescue => ex
44
+ @misses << "Error in #{key} check: #{ex.class}: #{ex.message}"
45
+ end
46
+
47
+ break if @misses.any?
48
+ end
49
+
50
+ @misses.empty?
51
+ end
52
+
53
+ # Does the value match the requested type, populates @misses in failure
54
+ #
55
+ # @param [Symbol, String, Class] type What type should the value be.
56
+ # Supported strings are: hash, array, string or boolean
57
+ #
58
+ # @param [Object] value
59
+ #
60
+ # @example Hash
61
+ # match_type(:hash, { foo: "bar" }) # match
62
+ # match_type("array", [0,1,2]) # match
63
+ # match_type(String, "meh") # match
64
+ # match_tyoe(:booelean, false) # match
65
+ #
66
+ # @return [void]
67
+ def match_type(type, value)
68
+ matched = case type
69
+ when :array, :Array, "array", "Array"
70
+ value.is_a?(Array)
71
+ when :hash, :Hash, "hash", "Hash"
72
+ value.is_a?(Hash)
73
+ when :string, "string", "String"
74
+ value.is_a?(String)
75
+ when :boolean, :Boolean, "boolean", "Boolean"
76
+ value.is_a?(TrueClass) || value.is_a?(FalseClass)
77
+ else
78
+ value.is_a?(type)
79
+ end
80
+
81
+ @misses << "Type mismatch; Expected #{type}, got #{value.class}: #{value}" if !matched
82
+ end
83
+
84
+ # Does the value match the requested size. Populates @misses in failure
85
+ #
86
+ # @param [Integer] size The needed size
87
+ # @param [Object] value
88
+ # @return [void]
89
+ #
90
+ def match_size(size, value)
91
+ if !value.respond_to?(:size)
92
+ @misses << "Size mismatch; #{value} has no size"
93
+ return
94
+ end
95
+
96
+ hit = if size.is_a?(Fixnum)
97
+ value.size == size
98
+ elsif size.is_a?(Range)
99
+ size.cover?(value.size)
100
+ elsif size.is_a?(Array)
101
+ size.include?(value.size)
102
+ end
103
+
104
+ @misses << "Size mismatch; Expected #{size}, got #{value.size}" if !hit
105
+ end
106
+
107
+ # Does the array contain each of the requested specs
108
+ #
109
+ # @param [Hash,Array,Jason::Spec] mapping What the array should contain
110
+ # @param [Array] value Array to check
111
+ # @param [Symbol] type How to check:
112
+ # * :each - all items must match)
113
+ # * :any - at least one item must match)
114
+ # * :none - no item may match
115
+ # @param [String] root Root for recursive checks
116
+ #
117
+ # @return [void]
118
+ #
119
+ # @example Shallow check for key
120
+ # match_each([ :id ], [ { 'id' => 1 }])
121
+ #
122
+ # @example Shallow check for key with any
123
+ # match_each([ :id ], [ { 'id' => 1 }, { 'bar' => 'beer' } ], :any )
124
+ #
125
+ # @example Shallow check for key with none
126
+ # not match_each([ :id ], [ { 'id' => 1 }, { 'bar' => 'beer' } ], :any )
127
+ #
128
+ # @example Deep check
129
+ # match_each(
130
+ # { item: [ :id, :name ] }
131
+ # [ { "item" => { "id" => "one", "name" => "two" } } ]
132
+ # )
133
+ #
134
+ def match_each(mapping, value, type=:each, root="")
135
+ misses = []
136
+ if mapping.is_a?(Array)
137
+ misses = match_each_shallow(mapping, value)
138
+ else
139
+ value.each_with_index do |val, index|
140
+ if !val.is_a?(Hash)
141
+ misses << "Each check failed. #{val} is no hash at #{root}[#{index}]"
142
+ break
143
+ end
144
+
145
+ mapping.each do |key, fields|
146
+ key = key.to_s
147
+ miss_key = root == "" ? key : "#{root}.#{key}"
148
+
149
+ if !val[key]
150
+ misses << "Each check failed. Key #{miss_key} is missing at #{root}[#{index}]"
151
+ end
152
+
153
+ if !val[key].is_a?(Hash)
154
+ misses << "Each check failed. #{miss_key} is no hash at #{root}[#{index}]"
155
+ end
156
+
157
+ if fields.is_a?(Hash)
158
+ match_each(fields,val[key],type,miss_key)
159
+ next
160
+ end
161
+
162
+ fields.each do |attr|
163
+ if !val[key].has_key?(attr.to_s)
164
+ misses << "Each check failed. #{miss_key}[#{attr}] is missing at #{root}[#{index}]"
165
+ end
166
+ end
167
+ end
168
+ end
169
+ end
170
+
171
+ case type
172
+ when :each
173
+ @misses += misses.compact
174
+ when :any
175
+ if misses.compact.size == value.size
176
+ @misses << "Shallow any check failed: #{misses}"
177
+ end
178
+ when :none
179
+ if misses.compact.size != value.size
180
+ @misses << "Shallow none check failed: #{misses}"
181
+ end
182
+ end
183
+ end
184
+
185
+ # Wrapper around #match_each
186
+ #
187
+ def match_any(fields, value)
188
+ match_each(fields, value, :any)
189
+ end
190
+
191
+ # Wrapper around #match_each
192
+ #
193
+ def match_none(fields, value)
194
+ match_each(fields, value, :none)
195
+ end
196
+
197
+ # Match each only for fields
198
+ #
199
+ # @param [Array<Symbol>] fields list of required fields
200
+ # @param [Array] list of objects that carry fields
201
+ # @return [void]
202
+ #
203
+ def match_each_shallow(fields, value)
204
+ misses = []
205
+ value.each_with_index do |val, index|
206
+ fields.each do |attr|
207
+ if !val.has_key?(attr.to_s)
208
+ misses[index] = "Shallow each check failed. Key #{attr} is missing at [#{index}]"
209
+ break
210
+ end
211
+ end
212
+ end
213
+
214
+ return misses
215
+ end
216
+
217
+ # Match fields on a hash
218
+ # @param [Hash,Array<Symbol>] fields list of required fields
219
+ # @param [Hash] value Hash to check fields in
220
+ # @return [void]
221
+ #
222
+ # @example Using array (each)
223
+ # # every field must be present
224
+ # spec = Jason.spec(fields: [ :id, :name ])
225
+ # spec.fits({ 'id' => 1, 'name' => "jason" })
226
+ # not spec.fits({ 'id' => 1 })
227
+ #
228
+ # @example Using each
229
+ # # same as passing the array
230
+ # spec = Jason.spec(fields: { each: [ :id, :name ] })
231
+ # spec.fits({ 'id' => 1, 'name' => "jason" })
232
+ # not spec.fits({ 'id' => 1 })
233
+ #
234
+ # @example Using any
235
+ # spec = Jason.spec(fields: { any: [ :id, :name ] })
236
+ # spec.fits({ 'id' => 1 })
237
+ #
238
+ # @example Using none
239
+ # spec = Jason.spec(fields: { none: [ :id, :name ] })
240
+ # not spec.fits({ 'id' => 1 })
241
+ #
242
+ def match_fields(fields, value, type=:each)
243
+ if fields.is_a?(Hash)
244
+ fields.each do |type, v_fields|
245
+ match_fields(v_fields, value, type)
246
+ end
247
+ return
248
+ end
249
+
250
+ fields.map!(&:to_s)
251
+ res = fields & value.keys
252
+
253
+ case type
254
+ when :each
255
+ @misses << "Field(s) #{res - fields} are missing" if res.sort != fields.sort
256
+ when :any
257
+ @misses << "None of the fields #{fields} where found" if res.empty?
258
+ when :none
259
+ @misses << "Fields #{res} found, none of them where expected" if res.any?
260
+ end
261
+ end
262
+
263
+ end
264
+ end
data/lib/jason_spec.rb ADDED
@@ -0,0 +1,4 @@
1
+ require 'rspec'
2
+ require 'rufus-json/automatic'
3
+
4
+ require 'jason/matchers'
@@ -0,0 +1,72 @@
1
+ require 'spec_helper'
2
+
3
+ require 'jason_spec'
4
+
5
+ describe Jason::HaveJasonMatcher do
6
+ it "should match non-rooted objects" do
7
+ %({"first_name":"Jason","last_name":"Voorhees"}).should have_jason(
8
+ [:first_name, :last_name]
9
+ )
10
+ end
11
+
12
+ it "should negate match non-rooted objects" do
13
+ %({"first_name":"Jason","last_name":"Voorhees"}).should_not have_jason(
14
+ [:url, :email]
15
+ )
16
+ end
17
+
18
+ it "should match rooted objects" do
19
+ %({"user":{"first_name":"Jason","last_name":"Voorhees"}}).should have_jason(
20
+ { user: [:first_name, :last_name] }
21
+ )
22
+ end
23
+
24
+ it "should negate match rooted objects" do
25
+ %({"user":{"first_name":"Jason","last_name":"Voorhees"}}).should_not have_jason(
26
+ { user: [:email, :url] }
27
+ )
28
+ end
29
+
30
+ describe "With given object" do
31
+ class User
32
+ attr_reader :first_name, :last_name
33
+ def initialize(fn, ln)
34
+ @first_name = fn; @last_name = ln
35
+ end
36
+ end
37
+
38
+ let(:user) { User.new("Jason", "Voorhees") }
39
+
40
+ it "should exactly match rooted object" do
41
+ %({"user":{"first_name":"Jason","last_name":"Voorhees"}}).should have_jason(
42
+ { user: { user => [:first_name, :last_name] } }
43
+ )
44
+ end
45
+
46
+ it "should negate exactly match rooted object" do
47
+ %({"user":{"first_name":"Freddy","last_name":"Kruger"}}).should_not have_jason(
48
+ { user: { user => [:first_name, :last_name] } }
49
+ )
50
+ end
51
+
52
+ it "should exactly match non-rooted object" do
53
+ %({"first_name":"Jason","last_name":"Voorhees"}).should have_jason(
54
+ { user => [:first_name, :last_name] }
55
+ )
56
+ end
57
+
58
+ it "should negate exactly matched non-rooted object" do
59
+ %({"user":{"first_name":"Freddy","last_name":"Kruger"}}).should_not have_jason(
60
+ { user => [:first_name, :last_name] }
61
+ )
62
+ end
63
+ end
64
+
65
+ describe "With Jason::Spec" do
66
+ it "takes a Jason::Spec" do
67
+ %([{"name":"Alexis"},{"name":"Ali"},{"name":"Freddy Kruger"}]).should have_jason(
68
+ Jason.spec(type: :array, each: [ :name ])
69
+ )
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,174 @@
1
+ require 'spec_helper'
2
+
3
+ describe Jason::Spec do
4
+ it "provides a spec class method" do
5
+ Jason.should be_respond_to :spec
6
+ Jason.spec({}).should be_kind_of(Jason::Spec)
7
+ end
8
+
9
+ it "refuses unknown specs" do
10
+ spec = Jason.spec(unknown: 1)
11
+ spec.should_not be_fits(:anything)
12
+ end
13
+
14
+ describe "Type" do
15
+ it "takes a type spec" do
16
+ spec = Jason::Spec.new(type: Array)
17
+ spec.fits?(:x)
18
+ spec.misses.should_not include("Unknown spec: type")
19
+ end
20
+
21
+ it "Matches by string/symbol" do
22
+ [ "array", "Array", :array, :Array ].each do |type|
23
+ spec = Jason::Spec.new(type: type)
24
+ spec.should be_fits([])
25
+ end
26
+
27
+ [ "hash", "Hash", :hash, :Hash ].each do |type|
28
+ spec = Jason::Spec.new(type: type)
29
+ spec.should be_fits({})
30
+ end
31
+
32
+ [ "boolean", "Boolean", :boolean, :Boolean ].each_with_index do |type,i|
33
+ spec = Jason::Spec.new(type: type)
34
+ spec.should be_fits(true)
35
+ spec.should be_fits(false)
36
+ end
37
+ end
38
+
39
+ it "Matches by class" do
40
+ spec = Jason::Spec.new(type: Jason::Spec)
41
+ spec.should be_fits(spec)
42
+ end
43
+ end
44
+
45
+ describe "Size" do
46
+ it "takes a size spec" do
47
+ spec = Jason::Spec.new(size: 1)
48
+ spec.fits?(:x)
49
+ spec.misses.should_not include("Unknown spec: size")
50
+ end
51
+
52
+ it "takes a fixnum for size" do
53
+ spec = Jason.spec(size: 1)
54
+ spec.should_not be_fits( [ ] )
55
+ spec.should be_fits( [ 1 ] )
56
+ end
57
+
58
+ it "takes a range for size" do
59
+ spec = Jason.spec(size: 1..3)
60
+ spec.should be_fits( [ 1 ] )
61
+ spec.should be_fits( [ 1, 2 ])
62
+ spec.should_not be_fits( [ ] )
63
+ spec.should_not be_fits( [ 1, 2, 3, 4 ] )
64
+ end
65
+
66
+ it "takes an array (as a range) for size" do
67
+ spec = Jason.spec(size: [ 1, 3, 5 ])
68
+ spec.should be_fits( [ 1 ] )
69
+ spec.should_not be_fits( [ 1, 2 ] )
70
+ spec.should be_fits( [ 1, 2, 3 ] )
71
+ end
72
+
73
+ it "matches hashes by key size" do
74
+ spec = Jason::Spec.new(size: 1)
75
+ spec.should_not be_fits({ a: 1, b: 2 })
76
+ spec.should be_fits({ c: 3 })
77
+ end
78
+ end
79
+
80
+ describe "Each" do
81
+ it "takes an each spec" do
82
+ spec = Jason.spec(each: [ :id ])
83
+ spec.fits?(:x)
84
+ spec.misses.should_not include("Unknown spec: each")
85
+ end
86
+
87
+ it "checks that each item in the array matches" do
88
+ spec = Jason.spec(each: [ :id ])
89
+ spec.should be_fits([ { 'id' => 1 }, { 'id' => 2 } ])
90
+ spec.should_not be_fits([ { 'id' => 1 }, { 'uid' => 2 } ])
91
+ end
92
+ end
93
+
94
+ describe "Complex each structures" do
95
+ it "should match hash structures" do
96
+ spec = Jason.spec(each: { item: [ :id, :name ], link: [ :href ] })
97
+ res = spec.fits?([ { "item" => { "id" => 1, "name" => "Jason" }, "link" => { "href" => "file:///" } } ] )
98
+
99
+ $stderr.puts spec.misses
100
+ res.should be_true
101
+
102
+ spec.should_not be_fits( [ { "item" => { "nid" => 1, "fame" => "Jason" }, "link" => { "href" => "file:///" } } ] )
103
+ end
104
+
105
+ it "should match deep hash structures"
106
+ it "should match Jason::Spec"
107
+ end
108
+
109
+ describe "Any" do
110
+ it "takes an any spec" do
111
+ spec = Jason.spec(any: [ :id ])
112
+ spec.fits?(:x)
113
+ spec.misses.should_not include("Unknown spec: any")
114
+ end
115
+
116
+ it "checks if there is at least one item in the array that match" do
117
+ spec = Jason.spec(any: [ :id ])
118
+ spec.should be_fits([ { 'id' => 1 }, { 'id' => 2 } ])
119
+ spec.should be_fits([ { 'id' => 1 }, { 'uid' => 2 } ])
120
+ spec.should_not be_fits([ { 'uid' => 1 }, { 'uid' => 2 } ])
121
+ end
122
+ end
123
+
124
+ describe "None" do
125
+ it "takes a none spec" do
126
+ spec = Jason.spec(none: [ :id ])
127
+ spec.fits?(:x)
128
+ spec.misses.should_not include("Unknown spec: none")
129
+ end
130
+
131
+ it "checks if there are no items in the array that match" do
132
+ spec = Jason.spec(none: [ :id ])
133
+ spec.should_not be_fits([ { 'id' => 1 }, { 'id' => 2 } ])
134
+ spec.should_not be_fits([ { 'id' => 1 }, { 'uid' => 2 } ])
135
+ spec.should be_fits([ { 'uid' => 1 }, { 'uid' => 2 } ])
136
+ end
137
+ end
138
+
139
+ describe "Fields" do
140
+ it "takes a fields spec" do
141
+ spec = Jason.spec(fields: [ :id ])
142
+ spec.fits?(:x)
143
+ spec.misses.should_not include("Unknown spec: fields")
144
+ end
145
+
146
+ it "checks if all the provided fields are in the hash" do
147
+ spec = Jason.spec(fields: [ :a, :b, :c ])
148
+ spec.should_not be_fits({ 'd' => 1 })
149
+ spec.should_not be_fits({ 'b' => 1 })
150
+ spec.should be_fits({ 'a' => 1, 'b' => 2, 'c' => 3 })
151
+ end
152
+
153
+ it "checks each" do
154
+ spec = Jason.spec(fields: { each: [ :a, :b, :c ] })
155
+ spec.should_not be_fits({ 'd' => 1 })
156
+ spec.should_not be_fits({ 'b' => 1 })
157
+ spec.should be_fits({ 'a' => 1, 'b' => 2, 'c' => 3 })
158
+ end
159
+
160
+ it "checks any" do
161
+ spec = Jason.spec(fields: { any: [ :a, :b, :c ] })
162
+ spec.should be_fits({ 'b' => 1 })
163
+ spec.should be_fits({ 'a' => 1, 'c' => 3 })
164
+ spec.should_not be_fits({ 'd' => 1 })
165
+ end
166
+
167
+ it "checks none" do
168
+ spec = Jason.spec(fields: { none: [ :a, :b, :c ] })
169
+ spec.should_not be_fits({ 'b' => 1 })
170
+ spec.should_not be_fits({ 'a' => 1, 'b' => 2, 'c' => 3 })
171
+ spec.should be_fits({ 'd' => 1 })
172
+ end
173
+ end
174
+ end
@@ -0,0 +1,23 @@
1
+ require "spec_helper"
2
+
3
+ describe Jason::Matchers do
4
+ let(:fake_rspec) do
5
+ fake = Class.new
6
+ fake.send(:include, Jason::Matchers)
7
+ fake.new
8
+ end
9
+
10
+ it "provides have_jason in rspec space" do
11
+ fake_rspec.should be_respond_to(:have_jason)
12
+ end
13
+
14
+ it "should return instantiate a Jason::Spec" do
15
+ spec = { type: Array }
16
+ Jason::HaveJasonMatcher.should_receive(:new).with(spec)
17
+ fake_rspec.have_jason(spec)
18
+ end
19
+
20
+ it "should return a Jason::Spec" do
21
+ fake_rspec.have_jason({}).should be_is_a(Jason::HaveJasonMatcher)
22
+ end
23
+ end
@@ -0,0 +1,62 @@
1
+ require 'spec_helper'
2
+
3
+ describe "Examples from readme" do
4
+ it "should do the most basic example" do
5
+ %q({"first_name":"Jason","last_name":"Voorhees"}).should have_jason([:first_name,:last_name])
6
+ end
7
+
8
+ it "should do the root example" do
9
+ %q({"movie":{"title":"Friday the 13th","release_year":"1980"}}).should have_jason(
10
+ movie: [ :title, :release_year ]
11
+ )
12
+ end
13
+
14
+ it "should do the object example" do
15
+ class Movie
16
+ attr_reader :title, :release_year
17
+ def self.find(x)
18
+ new
19
+ end
20
+ def initialize
21
+ @title = "Friday the 13th"
22
+ @release_year = "1980"
23
+ end
24
+ end
25
+
26
+ my_movie = Movie.find(1)
27
+
28
+ %q({"movie":{"title":"Friday the 13th","release_year":"1980"}}).should have_jason(
29
+ { movie: { my_movie => [ :title, :release_year ] } }
30
+ )
31
+ end
32
+
33
+ it "should do the basic Jason.spec example" do
34
+ %q({"movies":[{"title":"Friday the 13th"},{"title":"Nightmare on Elm Street"}]}).should have_jason(
35
+ movies: Jason.spec( type: Array, size: 2, each: [ :title ] )
36
+ )
37
+ end
38
+
39
+ it "should do the final example" do
40
+ json = %q({"user":{
41
+ "user_name":"jason",
42
+ "favorite_movies":[
43
+ {"title":"Friday the 13th","id":1},
44
+ {"title":"Nightmare on Elm Street","id":2}
45
+ ]
46
+ },
47
+ "links":[
48
+ { "href":"/users/2", "rel": "self" },
49
+ { "href":"/users/2/movies", "rel": "favorite movies"}
50
+ ]
51
+ })
52
+
53
+ json.should have_jason(
54
+ user: {
55
+ user_name: "jason",
56
+ favorite_movies: Jason.spec(type: Array, each: [ :title, :id ])
57
+ },
58
+ links: Jason.spec(type: Array, each: [ :href, :rel ])
59
+ )
60
+ end
61
+ end
62
+
@@ -0,0 +1,12 @@
1
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
2
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
3
+ require 'rspec'
4
+ require 'jason_spec'
5
+
6
+ # Requires supporting files with custom matchers and macros, etc,
7
+ # in ./support/ and its subdirectories.
8
+ Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each {|f| require f}
9
+
10
+ RSpec.configure do |config|
11
+
12
+ end
metadata ADDED
@@ -0,0 +1,144 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: jason_spec
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Hartog C. de Mik
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2013-10-08 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: rufus-json
16
+ requirement: &9558320 !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - =
20
+ - !ruby/object:Gem::Version
21
+ version: 1.0.6
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: *9558320
25
+ - !ruby/object:Gem::Dependency
26
+ name: rspec
27
+ requirement: &9574040 !ruby/object:Gem::Requirement
28
+ none: false
29
+ requirements:
30
+ - - ~>
31
+ - !ruby/object:Gem::Version
32
+ version: 2.8.0
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: *9574040
36
+ - !ruby/object:Gem::Dependency
37
+ name: yard
38
+ requirement: &9572560 !ruby/object:Gem::Requirement
39
+ none: false
40
+ requirements:
41
+ - - ~>
42
+ - !ruby/object:Gem::Version
43
+ version: '0.7'
44
+ type: :development
45
+ prerelease: false
46
+ version_requirements: *9572560
47
+ - !ruby/object:Gem::Dependency
48
+ name: rdoc
49
+ requirement: &9571440 !ruby/object:Gem::Requirement
50
+ none: false
51
+ requirements:
52
+ - - ~>
53
+ - !ruby/object:Gem::Version
54
+ version: '3.12'
55
+ type: :development
56
+ prerelease: false
57
+ version_requirements: *9571440
58
+ - !ruby/object:Gem::Dependency
59
+ name: bundler
60
+ requirement: &9570460 !ruby/object:Gem::Requirement
61
+ none: false
62
+ requirements:
63
+ - - ~>
64
+ - !ruby/object:Gem::Version
65
+ version: '1.0'
66
+ type: :development
67
+ prerelease: false
68
+ version_requirements: *9570460
69
+ - !ruby/object:Gem::Dependency
70
+ name: jeweler
71
+ requirement: &9569260 !ruby/object:Gem::Requirement
72
+ none: false
73
+ requirements:
74
+ - - ~>
75
+ - !ruby/object:Gem::Version
76
+ version: 1.8.7
77
+ type: :development
78
+ prerelease: false
79
+ version_requirements: *9569260
80
+ - !ruby/object:Gem::Dependency
81
+ name: simplecov
82
+ requirement: &9568280 !ruby/object:Gem::Requirement
83
+ none: false
84
+ requirements:
85
+ - - ! '>='
86
+ - !ruby/object:Gem::Version
87
+ version: '0'
88
+ type: :development
89
+ prerelease: false
90
+ version_requirements: *9568280
91
+ description: Write specifications for the expected json, without writing json
92
+ email: hartog@organisedminds.com
93
+ executables: []
94
+ extensions: []
95
+ extra_rdoc_files:
96
+ - LICENSE.txt
97
+ - README.md
98
+ files:
99
+ - .document
100
+ - .rspec
101
+ - Gemfile
102
+ - Gemfile.lock
103
+ - LICENSE.txt
104
+ - README.md
105
+ - Rakefile
106
+ - VERSION
107
+ - lib/jason/have_jason_matcher.rb
108
+ - lib/jason/matchers.rb
109
+ - lib/jason/spec.rb
110
+ - lib/jason_spec.rb
111
+ - spec/have_jason_matcher_spec.rb
112
+ - spec/jason_spec_spec.rb
113
+ - spec/matchers_spec.rb
114
+ - spec/readme_spec.rb
115
+ - spec/spec_helper.rb
116
+ homepage: http://github.com/coffeeaddict/jason_spec
117
+ licenses:
118
+ - MIT
119
+ post_install_message:
120
+ rdoc_options: []
121
+ require_paths:
122
+ - lib
123
+ required_ruby_version: !ruby/object:Gem::Requirement
124
+ none: false
125
+ requirements:
126
+ - - ! '>='
127
+ - !ruby/object:Gem::Version
128
+ version: '0'
129
+ segments:
130
+ - 0
131
+ hash: -1377093198486398749
132
+ required_rubygems_version: !ruby/object:Gem::Requirement
133
+ none: false
134
+ requirements:
135
+ - - ! '>='
136
+ - !ruby/object:Gem::Version
137
+ version: '0'
138
+ requirements: []
139
+ rubyforge_project:
140
+ rubygems_version: 1.8.11
141
+ signing_key:
142
+ specification_version: 3
143
+ summary: Spec your JSON
144
+ test_files: []