tent-validator 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +17 -0
- data/Gemfile +12 -0
- data/LICENSE +27 -0
- data/README.md +92 -0
- data/Rakefile +2 -0
- data/lib/tent-validator.rb +44 -0
- data/lib/tent-validator/faraday/tent_net_http_adapter.rb +17 -0
- data/lib/tent-validator/faraday/tent_rack_adapter.rb +11 -0
- data/lib/tent-validator/runner.rb +40 -0
- data/lib/tent-validator/runner/cli.rb +128 -0
- data/lib/tent-validator/spec.rb +88 -0
- data/lib/tent-validator/validators/new_post_validator.rb +215 -0
- data/lib/tent-validator/validators/post_validator.rb +12 -0
- data/lib/tent-validator/validators/support/error_header_expectation.rb +3 -0
- data/lib/tent-validator/validators/support/post_header_expectation.rb +11 -0
- data/lib/tent-validator/validators/support/tent_header_expectation.rb +3 -0
- data/lib/tent-validator/validators/support/tent_schemas.rb +5 -0
- data/lib/tent-validator/validators/without_authentication/app_validator.rb +59 -0
- data/lib/tent-validator/version.rb +3 -0
- data/tent-validator.gemspec +35 -0
- metadata +289 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
source 'https://rubygems.org'
|
2
|
+
|
3
|
+
# Specify your gem's dependencies in tent-validator.gemspec
|
4
|
+
gemspec
|
5
|
+
|
6
|
+
gem 'json-pointer', :git => 'git://github.com/tent/json-pointer-ruby.git', :branch => 'master'
|
7
|
+
gem 'tent-canonical-json', :git => 'git://github.com/tent/tent-canonical-json-ruby.git', :branch => 'master'
|
8
|
+
gem 'tent-client', :git => 'git://github.com/tent/tent-client-ruby.git', :branch => '0.3'
|
9
|
+
gem 'api-validator', :git => 'git://github.com/tent/api-validator.git', :branch => 'master'
|
10
|
+
gem 'tentd', :git => 'git://github.com/tent/tentd.git', :branch => '0.3'
|
11
|
+
gem 'rack-putty', :git => 'git://github.com/tent/rack-putty.git', :branch => 'master'
|
12
|
+
gem 'tent-schemas', :git => 'git://github.com/tent/tent-schemas.git', :branch => '0.3'
|
data/LICENSE
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
Copyright (c) 2013 Apollic Software, LLC. All rights reserved.
|
2
|
+
|
3
|
+
Redistribution and use in source and binary forms, with or without
|
4
|
+
modification, are permitted provided that the following conditions are
|
5
|
+
met:
|
6
|
+
|
7
|
+
* Redistributions of source code must retain the above copyright
|
8
|
+
notice, this list of conditions and the following disclaimer.
|
9
|
+
* Redistributions in binary form must reproduce the above
|
10
|
+
copyright notice, this list of conditions and the following disclaimer
|
11
|
+
in the documentation and/or other materials provided with the
|
12
|
+
distribution.
|
13
|
+
* Neither the name of Apollic Software, LLC nor the names of its
|
14
|
+
contributors may be used to endorse or promote products derived from
|
15
|
+
this software without specific prior written permission.
|
16
|
+
|
17
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
18
|
+
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
19
|
+
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
20
|
+
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
21
|
+
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
22
|
+
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
23
|
+
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
24
|
+
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
25
|
+
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
26
|
+
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
27
|
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
data/README.md
ADDED
@@ -0,0 +1,92 @@
|
|
1
|
+
# TentValidator
|
2
|
+
|
3
|
+
Tent v0.3 protocol validator.
|
4
|
+
|
5
|
+
## Usage
|
6
|
+
|
7
|
+
### Integration testing any Tent server implementation
|
8
|
+
|
9
|
+
It's assumed you have redis and postgres running.
|
10
|
+
|
11
|
+
```bash
|
12
|
+
cd tent-validator
|
13
|
+
bundle
|
14
|
+
createdb tent-validator
|
15
|
+
createdb tent-validator-tentd && DATABASE_URL=postgres://localhost/tent-validator-tentd bundle exec rake tentd:db:migrate
|
16
|
+
|
17
|
+
echo "VALIDATOR_DATABASE_URL=postgres://localhost/tent-validator
|
18
|
+
TENT_DATABASE_URL=postgres://localhost/tent-validator-tentd
|
19
|
+
REDIS_URL=redis://127.0.0.1:6379/0
|
20
|
+
REDIS_NAMESPACE=tent-validator " >> .env
|
21
|
+
```
|
22
|
+
|
23
|
+
#### Commandline Runner
|
24
|
+
|
25
|
+
```ruby
|
26
|
+
require 'tent-validator'
|
27
|
+
|
28
|
+
# ... code to run your server implementation ...
|
29
|
+
|
30
|
+
server_url = "http://127.0.0.1:3000" # change to wherever the server is running
|
31
|
+
|
32
|
+
TentValidator.setup!(
|
33
|
+
:remote_entity_uri => server_url,
|
34
|
+
:remote_server_meta => { # change to suite your server setup
|
35
|
+
"entity" => server_url,
|
36
|
+
"previous_entities" => [],
|
37
|
+
"servers" => [
|
38
|
+
{
|
39
|
+
"version" => "0.3",
|
40
|
+
"urls" => {
|
41
|
+
"app_auth_request" => "#{server_url}/oauth/authorize",
|
42
|
+
"app_token_request" => "#{server_url}/oauth/token",
|
43
|
+
"posts_feed" => "#{server_url}/posts",
|
44
|
+
"new_post" => "#{server_url}/posts",
|
45
|
+
"post" => "#{server_url}/posts/{entity}/{post}",
|
46
|
+
"post_attachment" => "#{server_url}/posts/{entity}/{post}/attachments/{name}?version={version}",
|
47
|
+
"batch" => "#{server_url}/batch",
|
48
|
+
"server_info" => "#{server_url}/server"
|
49
|
+
},
|
50
|
+
"preference" => 0
|
51
|
+
}
|
52
|
+
]
|
53
|
+
},
|
54
|
+
:remote_auth_details => {
|
55
|
+
# ...
|
56
|
+
},
|
57
|
+
:tent_database_url => ENV['VALIDATOR_TENTD_DATABASE_URL'] # tent-validator uses tentd
|
58
|
+
)
|
59
|
+
|
60
|
+
TentValidator::Runner::CLI.run
|
61
|
+
```
|
62
|
+
|
63
|
+
#### Browser Runner
|
64
|
+
|
65
|
+
You will also need JavaScript runtime (e.g. nodejs).
|
66
|
+
|
67
|
+
```bash
|
68
|
+
echo "VALIDATOR_NOTIFICATION_URL=http://localhost:9292/webhooks
|
69
|
+
COOKIE_SECRET=$(openssl rand -hex 16 | tr -d '\r\n')
|
70
|
+
VALIDATOR_HOST=http://localhost:9292" >> .env
|
71
|
+
|
72
|
+
gem install foreman
|
73
|
+
foreman run bundle exec puma -p 3000
|
74
|
+
```
|
75
|
+
|
76
|
+
```bash
|
77
|
+
open http://localhost:3000
|
78
|
+
```
|
79
|
+
|
80
|
+
Enter your entity URI when propted and authorize with your Tent server.
|
81
|
+
|
82
|
+
**WARNING: This app will create lots of posts (many of them public) and not delete all of them. For best results wipe the database for your entity's server between validation runs and don't use an entity with any followers.**
|
83
|
+
|
84
|
+
The app will then run validations against your server and display the results.
|
85
|
+
|
86
|
+
## Contributing
|
87
|
+
|
88
|
+
1. Fork it
|
89
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
90
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
91
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
92
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1,44 @@
|
|
1
|
+
require 'tent-validator/version'
|
2
|
+
require 'tentd/utils'
|
3
|
+
require 'api-validator'
|
4
|
+
require 'faraday'
|
5
|
+
require 'tent-client'
|
6
|
+
|
7
|
+
module TentValidator
|
8
|
+
|
9
|
+
require 'tent-validator/spec'
|
10
|
+
|
11
|
+
require 'tent-validator/runner'
|
12
|
+
|
13
|
+
require 'tent-validator/faraday/tent_rack_adapter'
|
14
|
+
require 'tent-validator/faraday/tent_net_http_adapter'
|
15
|
+
|
16
|
+
class << self
|
17
|
+
attr_writer :remote_auth_details
|
18
|
+
attr_accessor :remote_server_meta, :remote_entity_uri
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.setup!(options = {})
|
22
|
+
require 'tentd'
|
23
|
+
TentD.setup!(:database_url => options[:tent_database_url] || ENV['TENT_DATABASE_URL'])
|
24
|
+
|
25
|
+
[:remote_entity_uri, :remote_auth_details, :remote_server_meta].each do |key|
|
26
|
+
if options.has_key?(key)
|
27
|
+
self.send("#{key}=", options.delete(key))
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def self.remote_auth_details
|
33
|
+
@remote_auth_details || Hash.new
|
34
|
+
end
|
35
|
+
|
36
|
+
def self.remote_adapter
|
37
|
+
@remote_adapter ||= :tent_net_http
|
38
|
+
end
|
39
|
+
|
40
|
+
def self.validators
|
41
|
+
@validators ||= []
|
42
|
+
end
|
43
|
+
|
44
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module TentValidator
|
2
|
+
|
3
|
+
class TentNetHttpFaradayAdapter < Faraday::Adapter::NetHttp
|
4
|
+
def call(env)
|
5
|
+
if Faraday::CompositeReadIO === env[:body]
|
6
|
+
env[:request_body] = env[:body].read
|
7
|
+
env[:body].rewind
|
8
|
+
elsif env[:body]
|
9
|
+
env[:request_body] = env[:body]
|
10
|
+
end
|
11
|
+
|
12
|
+
super
|
13
|
+
end
|
14
|
+
end
|
15
|
+
Faraday.register_middleware :adapter, :tent_net_http => TentNetHttpFaradayAdapter
|
16
|
+
|
17
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
require 'api-validator'
|
2
|
+
|
3
|
+
module TentValidator
|
4
|
+
module Runner
|
5
|
+
|
6
|
+
class Results
|
7
|
+
include ApiValidator::Mixins::DeepMerge
|
8
|
+
|
9
|
+
attr_reader :results
|
10
|
+
def initialize
|
11
|
+
@results = {}
|
12
|
+
end
|
13
|
+
|
14
|
+
def merge!(validator_results)
|
15
|
+
deep_merge!(results, validator_results.results)
|
16
|
+
end
|
17
|
+
|
18
|
+
def as_json(options = {})
|
19
|
+
results
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
require 'tent-validator/runner/cli'
|
24
|
+
|
25
|
+
def self.run(&block)
|
26
|
+
paths = Dir[File.expand_path(File.join(File.dirname(__FILE__), 'validators', '**', '*_validator.rb'))]
|
27
|
+
paths.each { |path| require path }
|
28
|
+
|
29
|
+
results = Results.new
|
30
|
+
|
31
|
+
TentValidator.validators.each do |validator|
|
32
|
+
results.merge!(validator.run)
|
33
|
+
block.call(results) if block
|
34
|
+
end
|
35
|
+
|
36
|
+
results
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,128 @@
|
|
1
|
+
require 'awesome_print'
|
2
|
+
module TentValidator
|
3
|
+
module Runner
|
4
|
+
|
5
|
+
class CLI
|
6
|
+
|
7
|
+
TRANSLATE_KEYS = {
|
8
|
+
:current_value => :actual
|
9
|
+
}.freeze
|
10
|
+
|
11
|
+
def self.run(options = {})
|
12
|
+
instance = self.new(options)
|
13
|
+
instance.run
|
14
|
+
end
|
15
|
+
|
16
|
+
def initialize(options = {})
|
17
|
+
end
|
18
|
+
|
19
|
+
def run
|
20
|
+
@valid = []
|
21
|
+
@invalid = []
|
22
|
+
|
23
|
+
puts "Running Protocol Validations..."
|
24
|
+
results = Runner.run do |results|
|
25
|
+
print_results(results.as_json)
|
26
|
+
end
|
27
|
+
print "\n"
|
28
|
+
validator_complete(results.as_json)
|
29
|
+
|
30
|
+
print "\n"
|
31
|
+
if @invalid.any?
|
32
|
+
puts green("#{@valid.uniq.size} validations valid\t") + red("#{@invalid.uniq.size} failed")
|
33
|
+
else
|
34
|
+
puts green("#{@valid.uniq.size} validations valid\t0 failed")
|
35
|
+
end
|
36
|
+
print "\n"
|
37
|
+
|
38
|
+
exit(1) unless @valid
|
39
|
+
end
|
40
|
+
|
41
|
+
def print_results(results, parent_names = [])
|
42
|
+
results.each_pair do |name, children|
|
43
|
+
next if name == :results
|
44
|
+
child_results = children[:results]
|
45
|
+
child_results.each do |r|
|
46
|
+
id = r.object_id.to_s
|
47
|
+
valid = result_valid?(r)
|
48
|
+
if valid
|
49
|
+
next if @valid.index(id)
|
50
|
+
@valid << id
|
51
|
+
print green(".")
|
52
|
+
else
|
53
|
+
next if @invalid.index(id)
|
54
|
+
@invalid << id
|
55
|
+
print red("F")
|
56
|
+
end
|
57
|
+
end
|
58
|
+
print_results(children, parent_names + [name])
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def validator_complete(results, parent_names = [])
|
63
|
+
parent_names.reject! { |n| n == "" }
|
64
|
+
results.each_pair do |name, children|
|
65
|
+
next if name == :results
|
66
|
+
child_results = children[:results]
|
67
|
+
child_results.each do |r|
|
68
|
+
next if result_valid?(r)
|
69
|
+
|
70
|
+
print "\n"
|
71
|
+
puts red((parent_names + [name]).join(" "))
|
72
|
+
print "\n"
|
73
|
+
|
74
|
+
actual = r.as_json[:actual]
|
75
|
+
puts "REQUEST:"
|
76
|
+
puts "#{actual[:request_method]} #{actual[:request_url]}"
|
77
|
+
puts actual[:request_headers].inject([]) { |m, (k,v)| m << "#{k}: #{v}"; m }.join("\n")
|
78
|
+
print "\n"
|
79
|
+
puts actual[:request_body]
|
80
|
+
print "\n"
|
81
|
+
|
82
|
+
puts "RESPONSE:"
|
83
|
+
puts actual[:response_status]
|
84
|
+
puts actual[:response_headers].inject([]) { |m, (k,v)| m << "#{k}: #{v}"; m }.join("\n")
|
85
|
+
print "\n"
|
86
|
+
puts Yajl::Encoder.encode(actual[:response_body])
|
87
|
+
print "\n"
|
88
|
+
|
89
|
+
puts "DIFF:"
|
90
|
+
r.as_json[:expected].each_pair do |key, val|
|
91
|
+
next if val[:valid]
|
92
|
+
|
93
|
+
puts key
|
94
|
+
ap val[:diff].map { |i| translate_keys(i.dup) }
|
95
|
+
end
|
96
|
+
end
|
97
|
+
validator_complete(children, parent_names + [name])
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
def result_valid?(result)
|
102
|
+
valid = result.as_json[:expected].inject(true) { |memo, (k,v)|
|
103
|
+
memo = false if v.has_key?(:valid) && !v[:valid]
|
104
|
+
memo
|
105
|
+
}
|
106
|
+
end
|
107
|
+
|
108
|
+
def translate_keys(hash)
|
109
|
+
TRANSLATE_KEYS.each_pair do |from, to|
|
110
|
+
next unless hash.has_key?(from)
|
111
|
+
hash[to] = hash[from]
|
112
|
+
hash.delete(from)
|
113
|
+
end
|
114
|
+
hash
|
115
|
+
end
|
116
|
+
|
117
|
+
def green(text); color(text, "\e[32m"); end
|
118
|
+
def red(text); color(text, "\e[31m"); end
|
119
|
+
def yellow(text); color(text, "\e[33m"); end
|
120
|
+
def blue(text); color(text, "\e[34m"); end
|
121
|
+
|
122
|
+
def color(text, color_code)
|
123
|
+
"#{color_code}#{text}\e[0m"
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
end
|
128
|
+
end
|
@@ -0,0 +1,88 @@
|
|
1
|
+
require 'tent-canonical-json'
|
2
|
+
|
3
|
+
module TentValidator
|
4
|
+
class Spec < ApiValidator::Spec
|
5
|
+
|
6
|
+
def clients(type, options = {})
|
7
|
+
server = options.delete(:server) || :remote
|
8
|
+
if server == :remote
|
9
|
+
TentClient.new(TentValidator.remote_entity_uri, auth_details_for_app_type(type, options).merge(
|
10
|
+
:faraday_adapter => TentValidator.remote_adapter,
|
11
|
+
:server_meta => TentValidator.remote_server_meta
|
12
|
+
))
|
13
|
+
else
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def generate_version_signature(post)
|
18
|
+
canonical_post_json = TentCanonicalJson.encode(post)
|
19
|
+
hex_digest(canonical_post_json)
|
20
|
+
end
|
21
|
+
|
22
|
+
def hex_digest(data)
|
23
|
+
Digest::SHA512.new.update(data).to_s[0...64]
|
24
|
+
end
|
25
|
+
|
26
|
+
def invalid_value(type, format = nil)
|
27
|
+
case type
|
28
|
+
when "array"
|
29
|
+
Hash.new
|
30
|
+
when "boolean"
|
31
|
+
"false"
|
32
|
+
when "number", "integer"
|
33
|
+
"123"
|
34
|
+
when "null"
|
35
|
+
true
|
36
|
+
when "object"
|
37
|
+
["My parent should be an object!"]
|
38
|
+
when "string"
|
39
|
+
if format
|
40
|
+
case format
|
41
|
+
when 'uri'
|
42
|
+
"I'm not a uri!"
|
43
|
+
end
|
44
|
+
else
|
45
|
+
421
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def valid_value(type, format = nil)
|
51
|
+
case type
|
52
|
+
when "array"
|
53
|
+
[]
|
54
|
+
when "boolean"
|
55
|
+
true
|
56
|
+
when "number", "integer"
|
57
|
+
123
|
58
|
+
when "null"
|
59
|
+
nil
|
60
|
+
when "object"
|
61
|
+
Hash.new
|
62
|
+
when "string"
|
63
|
+
if format
|
64
|
+
case format
|
65
|
+
when 'uri'
|
66
|
+
"https://example.com"
|
67
|
+
end
|
68
|
+
else
|
69
|
+
""
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
private
|
75
|
+
|
76
|
+
def auth_details_for_app_type(type, options={})
|
77
|
+
case type
|
78
|
+
when :app
|
79
|
+
TentValidator.remote_auth_details
|
80
|
+
when :custom
|
81
|
+
TentD::Utils::Hash.slice(options, :mac_key_id, :mac_algorithm, :mac_key)
|
82
|
+
else
|
83
|
+
Hash.new
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
end
|
88
|
+
end
|
@@ -0,0 +1,215 @@
|
|
1
|
+
require 'tent-validator/validators/post_validator'
|
2
|
+
|
3
|
+
module TentValidator
|
4
|
+
class PostValidator
|
5
|
+
|
6
|
+
shared_example :new_post do
|
7
|
+
context "with valid attributes" do
|
8
|
+
valid_post_expectation = proc do |post, expected_post|
|
9
|
+
expect_response(:headers => :tent, :status => 200, :schema => :post) do
|
10
|
+
expect_headers(:post)
|
11
|
+
expect_properties(expected_post)
|
12
|
+
expect_schema(get(:content_schema), "/content")
|
13
|
+
|
14
|
+
if attachments = get(:post_attachments)
|
15
|
+
expect_properties(
|
16
|
+
:attachments => attachments.map { |a|
|
17
|
+
a = a.dup
|
18
|
+
a.merge!(:digest => hex_digest(a[:data]), :size => a[:data].size)
|
19
|
+
a.delete(:data)
|
20
|
+
a
|
21
|
+
}
|
22
|
+
)
|
23
|
+
|
24
|
+
res = clients(:no_auth, :server => :remote).post.create(post, {}, :attachments => attachments)
|
25
|
+
else
|
26
|
+
res = clients(:no_auth, :server => :remote).post.create(post)
|
27
|
+
end
|
28
|
+
|
29
|
+
if Hash === res.body
|
30
|
+
expect_properties(:version => { :id => generate_version_signature(res.body) })
|
31
|
+
end
|
32
|
+
|
33
|
+
res
|
34
|
+
end
|
35
|
+
|
36
|
+
if attachments = get(:post_attachments)
|
37
|
+
context "with attachments" do
|
38
|
+
context "without Attachment-Digest header" do
|
39
|
+
expect_response(:headers => :tent, :status => 200, :schema => :post) do
|
40
|
+
expect_headers(:post)
|
41
|
+
expect_properties(expected_post)
|
42
|
+
expect_schema(get(:content_schema), "/content")
|
43
|
+
|
44
|
+
expect_properties(
|
45
|
+
:attachments => attachments.map { |a|
|
46
|
+
a = a.dup
|
47
|
+
a.merge!(:digest => hex_digest(a[:data]), :size => a[:data].size)
|
48
|
+
a.delete(:data)
|
49
|
+
a
|
50
|
+
}
|
51
|
+
)
|
52
|
+
|
53
|
+
res = clients(:no_auth, :server => :remote).post.create(post, {}, :attachments => attachments) do |request|
|
54
|
+
body = request.body.read
|
55
|
+
body.gsub!(/\bAttachment-Digest.*\r\n/, '')
|
56
|
+
request.body = body
|
57
|
+
end
|
58
|
+
|
59
|
+
if Hash === res.body
|
60
|
+
expect_properties(:version => { :id => generate_version_signature(res.body) })
|
61
|
+
end
|
62
|
+
|
63
|
+
res
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
valid_post_expectation.call(get(:post), get(:post))
|
71
|
+
|
72
|
+
context "when permissions.public member is null" do
|
73
|
+
post = get(:post)
|
74
|
+
pointer = JsonPointer.new(post, '/permissions/public', :symbolize_keys => true)
|
75
|
+
pointer.value = nil
|
76
|
+
|
77
|
+
valid_post_expectation.call(post, get(:post))
|
78
|
+
end
|
79
|
+
|
80
|
+
context "when permissions member is null" do
|
81
|
+
post = get(:post)
|
82
|
+
post[:permissions] = nil
|
83
|
+
|
84
|
+
valid_post_expectation.call(post, get(:post))
|
85
|
+
end
|
86
|
+
|
87
|
+
context "when member set that should be ignored" do
|
88
|
+
properties = ApiValidator::JsonSchemas[:post]["properties"]
|
89
|
+
%w( /id /received_at /entity /original_entity /app /version/id /version/published_at /version/received_at ).each do |path|
|
90
|
+
path_fragments = path.split('/')
|
91
|
+
property_path = path_fragments[0] + path_fragments[1..-1].join('/properties/')
|
92
|
+
property = JsonPointer.new(properties, property_path).value
|
93
|
+
|
94
|
+
post = get(:post)
|
95
|
+
pointer = JsonPointer.new(post, path, :symbolize_keys => true)
|
96
|
+
pointer.value = valid_value(property['type'], property['format'])
|
97
|
+
|
98
|
+
valid_post_expectation.call(post, get(:post))
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
context "with invalid attributes" do
|
104
|
+
if attachments = get(:post_attachments)
|
105
|
+
context "when attachment hash mismatch" do
|
106
|
+
expect_response(:headers => :error, :status => 400, :schema => :error) do
|
107
|
+
post = get(:post)
|
108
|
+
attachments = attachments.map do |attachment|
|
109
|
+
attachment[:headers] = {
|
110
|
+
'Attachment-Digest' => 'Invalid Digest!'
|
111
|
+
}
|
112
|
+
attachment
|
113
|
+
end
|
114
|
+
clients(:no_auth, :server => :remote).post.create(post, {}, :attachments => attachments)
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
context "when extra field in content" do
|
120
|
+
expect_response(:headers => :error, :status => 400, :schema => :error) do
|
121
|
+
data = get(:post)
|
122
|
+
data[:content][:extra_member] = "I shouldn't be here!"
|
123
|
+
clients(:no_auth, :server => :remote).post.create(data)
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
invalid_member_expectation = proc do |path, property|
|
128
|
+
expect_response(:headers => :error, :status => 400, :schema => :error) do
|
129
|
+
data = get(:post)
|
130
|
+
pointer = JsonPointer.new(data, path, :symbolize_keys => true)
|
131
|
+
pointer.value = invalid_value(property['type'], property['format'])
|
132
|
+
clients(:no_auth, :server => :remote).post.create(data)
|
133
|
+
end
|
134
|
+
|
135
|
+
if property['type'] == 'object' && property['properties']
|
136
|
+
property['properties'].each_pair do |name, property|
|
137
|
+
invalid_member_expectation.call(path + "/#{name}", property)
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
if property['type'] == 'array' && property['items']
|
142
|
+
invalid_member_expectation.call(path + "/-", { 'type' => property['items']['type'], 'format' => property['items']['format'] })
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
context "when content member is wrong type" do
|
147
|
+
ApiValidator::JsonSchemas[get(:content_schema)]["properties"].each_pair do |name, property|
|
148
|
+
invalid_member_expectation.call("/content/#{name}", property)
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
context "when post member is wrong type" do
|
153
|
+
properties = ApiValidator::JsonSchemas[:post]["properties"]
|
154
|
+
%w( published_at version mentions licenses content attachments permissions ).each do |name|
|
155
|
+
invalid_member_expectation.call("/#{name}", properties[name])
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
context "when extra post member" do
|
160
|
+
expect_response(:headers => :error, :status => 400, :schema => :error) do
|
161
|
+
data = get(:post)
|
162
|
+
data[:extra_member] = "I shouldn't be here!"
|
163
|
+
clients(:no_auth, :server => :remote).post.create(data)
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
context "when content is wrong type" do
|
168
|
+
expect_response(:headers => :error, :status => 400, :schema => :error) do
|
169
|
+
data = get(:post)
|
170
|
+
data[:content] = "I should be an object"
|
171
|
+
clients(:no_auth, :server => :remote).post.create(data)
|
172
|
+
end
|
173
|
+
|
174
|
+
expect_response(:headers => :error, :status => 400, :schema => :error) do
|
175
|
+
data = get(:post)
|
176
|
+
data[:content] = ["My parent should be an object!"]
|
177
|
+
clients(:no_auth, :server => :remote).post.create(data)
|
178
|
+
end
|
179
|
+
|
180
|
+
expect_response(:headers => :error, :status => 400, :schema => :error) do
|
181
|
+
data = get(:post)
|
182
|
+
data[:content] = true
|
183
|
+
clients(:no_auth, :server => :remote).post.create(data)
|
184
|
+
end
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
188
|
+
context "without request body" do
|
189
|
+
expect_response(:headers => :error, :status => 400, :schema => :error) do
|
190
|
+
clients(:no_auth, :server => :remote).post.create(nil) do |request|
|
191
|
+
request.headers['Content-Type'] = TentD::API::POST_CONTENT_TYPE % 'https://tent.io/types/app/v0#'
|
192
|
+
end
|
193
|
+
end
|
194
|
+
end
|
195
|
+
|
196
|
+
context "when request body is wrong type" do
|
197
|
+
expect_response(:headers => :error, :status => 400, :schema => :error) do
|
198
|
+
clients(:no_auth, :server => :remote).post.create("I should be an object") do |request|
|
199
|
+
request.headers['Content-Type'] = TentD::API::POST_CONTENT_TYPE % 'https://tent.io/types/app/v0#'
|
200
|
+
end
|
201
|
+
end
|
202
|
+
end
|
203
|
+
|
204
|
+
context "with invalid content-type header" do
|
205
|
+
data = get(:post)
|
206
|
+
expect_response(:headers => :error, :status => 415, :schema => :error) do
|
207
|
+
clients(:no_auth, :server => :remote).post.create(data) do |request|
|
208
|
+
request.headers['Content-Type'] = 'application/json'
|
209
|
+
end
|
210
|
+
end
|
211
|
+
end
|
212
|
+
end
|
213
|
+
|
214
|
+
end
|
215
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
require 'tent-validator/validators/support/tent_header_expectation'
|
2
|
+
require 'tent-validator/validators/support/post_header_expectation'
|
3
|
+
require 'tent-validator/validators/support/error_header_expectation'
|
4
|
+
require 'tent-validator/validators/support/tent_schemas'
|
5
|
+
|
6
|
+
module TentValidator
|
7
|
+
class PostValidator < TentValidator::Spec
|
8
|
+
|
9
|
+
require 'tent-validator/validators/new_post_validator'
|
10
|
+
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
ApiValidator::Header.register(:post, {
|
2
|
+
'Content-Type' => lambda { |response|
|
3
|
+
post_type = response.env['expected_post_type'] ? response.env['expected_post_type'] : nil
|
4
|
+
post_type ||= (Hash === response.body && response.body['type']) ? response.body['type'] : nil
|
5
|
+
if post_type
|
6
|
+
%r{\btype=['"]#{ Regexp.escape(post_type) }['"]}
|
7
|
+
else
|
8
|
+
%r{\btype=['"][^'"]+['"]}
|
9
|
+
end
|
10
|
+
}
|
11
|
+
})
|
@@ -0,0 +1,59 @@
|
|
1
|
+
require 'tent-validator/validators/post_validator'
|
2
|
+
|
3
|
+
module TentValidator
|
4
|
+
module WithoutAuthentication
|
5
|
+
|
6
|
+
class AppValidator < PostValidator
|
7
|
+
def generate_app_post
|
8
|
+
{
|
9
|
+
:type => "https://tent.io/types/app/v0#",
|
10
|
+
:content => {
|
11
|
+
:name => "Example App Name",
|
12
|
+
:description => "Example App Description",
|
13
|
+
:url => "http://someapp.example.com",
|
14
|
+
:redirect_uri => "http://someapp.example.com/oauth/callback",
|
15
|
+
:post_types => {
|
16
|
+
:read => %w( https://tent.io/types/status/v0# ),
|
17
|
+
:write => %w( https://tent.io/types/status/v0# )
|
18
|
+
},
|
19
|
+
:scopes => %w( import_posts )
|
20
|
+
},
|
21
|
+
:permissions => {
|
22
|
+
:public => false
|
23
|
+
}
|
24
|
+
}
|
25
|
+
end
|
26
|
+
|
27
|
+
def generate_app_icon_attachment
|
28
|
+
{
|
29
|
+
:content_type => "image/png",
|
30
|
+
:category => 'icon',
|
31
|
+
:name => 'appicon.png',
|
32
|
+
:data => "Fake image data"
|
33
|
+
}
|
34
|
+
end
|
35
|
+
|
36
|
+
describe "POST /posts" do
|
37
|
+
context "without authentication" do
|
38
|
+
|
39
|
+
context "when app registration post" do
|
40
|
+
set(:post) { generate_app_post }
|
41
|
+
set(:content_schema, :post_app)
|
42
|
+
|
43
|
+
behaves_as(:new_post)
|
44
|
+
|
45
|
+
context "with icon attachment" do
|
46
|
+
set(:post_attachments) { [generate_app_icon_attachment] }
|
47
|
+
|
48
|
+
behaves_as(:new_post)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
end
|
57
|
+
|
58
|
+
TentValidator.validators << WithoutAuthentication::AppValidator
|
59
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'tent-validator/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |gem|
|
7
|
+
gem.name = "tent-validator"
|
8
|
+
gem.version = TentValidator::VERSION
|
9
|
+
gem.authors = ["Jesse Stuart"]
|
10
|
+
gem.email = ["jesse@jessestuart.ca"]
|
11
|
+
gem.description = %q{Tent protocol validator}
|
12
|
+
gem.summary = %q{Tent protocol validator}
|
13
|
+
gem.homepage = ""
|
14
|
+
|
15
|
+
gem.files = `git ls-files`.split($/)
|
16
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
17
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
18
|
+
gem.require_paths = ["lib"]
|
19
|
+
|
20
|
+
gem.add_runtime_dependency 'yajl-ruby'
|
21
|
+
gem.add_runtime_dependency 'faraday', '0.8.4'
|
22
|
+
gem.add_runtime_dependency 'json-pointer'
|
23
|
+
gem.add_runtime_dependency 'api-validator'
|
24
|
+
gem.add_runtime_dependency 'tentd', '~> 0.2.0'
|
25
|
+
gem.add_runtime_dependency 'tent-client'
|
26
|
+
gem.add_runtime_dependency 'tent-schemas'
|
27
|
+
gem.add_runtime_dependency 'tent-canonical-json'
|
28
|
+
gem.add_runtime_dependency 'awesome_print'
|
29
|
+
|
30
|
+
gem.add_development_dependency 'rspec', '~> 2.11'
|
31
|
+
gem.add_development_dependency 'mocha', '0.12.6'
|
32
|
+
gem.add_development_dependency 'bundler'
|
33
|
+
gem.add_development_dependency 'rake'
|
34
|
+
gem.add_development_dependency 'hashie'
|
35
|
+
end
|
metadata
ADDED
@@ -0,0 +1,289 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: tent-validator
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.2.0
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Jesse Stuart
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2013-04-07 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: yajl-ruby
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ! '>='
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '0'
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: faraday
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - '='
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: 0.8.4
|
38
|
+
type: :runtime
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - '='
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: 0.8.4
|
46
|
+
- !ruby/object:Gem::Dependency
|
47
|
+
name: json-pointer
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
50
|
+
requirements:
|
51
|
+
- - ! '>='
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '0'
|
54
|
+
type: :runtime
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ! '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
- !ruby/object:Gem::Dependency
|
63
|
+
name: api-validator
|
64
|
+
requirement: !ruby/object:Gem::Requirement
|
65
|
+
none: false
|
66
|
+
requirements:
|
67
|
+
- - ! '>='
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: '0'
|
70
|
+
type: :runtime
|
71
|
+
prerelease: false
|
72
|
+
version_requirements: !ruby/object:Gem::Requirement
|
73
|
+
none: false
|
74
|
+
requirements:
|
75
|
+
- - ! '>='
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
version: '0'
|
78
|
+
- !ruby/object:Gem::Dependency
|
79
|
+
name: tentd
|
80
|
+
requirement: !ruby/object:Gem::Requirement
|
81
|
+
none: false
|
82
|
+
requirements:
|
83
|
+
- - ~>
|
84
|
+
- !ruby/object:Gem::Version
|
85
|
+
version: 0.2.0
|
86
|
+
type: :runtime
|
87
|
+
prerelease: false
|
88
|
+
version_requirements: !ruby/object:Gem::Requirement
|
89
|
+
none: false
|
90
|
+
requirements:
|
91
|
+
- - ~>
|
92
|
+
- !ruby/object:Gem::Version
|
93
|
+
version: 0.2.0
|
94
|
+
- !ruby/object:Gem::Dependency
|
95
|
+
name: tent-client
|
96
|
+
requirement: !ruby/object:Gem::Requirement
|
97
|
+
none: false
|
98
|
+
requirements:
|
99
|
+
- - ! '>='
|
100
|
+
- !ruby/object:Gem::Version
|
101
|
+
version: '0'
|
102
|
+
type: :runtime
|
103
|
+
prerelease: false
|
104
|
+
version_requirements: !ruby/object:Gem::Requirement
|
105
|
+
none: false
|
106
|
+
requirements:
|
107
|
+
- - ! '>='
|
108
|
+
- !ruby/object:Gem::Version
|
109
|
+
version: '0'
|
110
|
+
- !ruby/object:Gem::Dependency
|
111
|
+
name: tent-schemas
|
112
|
+
requirement: !ruby/object:Gem::Requirement
|
113
|
+
none: false
|
114
|
+
requirements:
|
115
|
+
- - ! '>='
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '0'
|
118
|
+
type: :runtime
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
none: false
|
122
|
+
requirements:
|
123
|
+
- - ! '>='
|
124
|
+
- !ruby/object:Gem::Version
|
125
|
+
version: '0'
|
126
|
+
- !ruby/object:Gem::Dependency
|
127
|
+
name: tent-canonical-json
|
128
|
+
requirement: !ruby/object:Gem::Requirement
|
129
|
+
none: false
|
130
|
+
requirements:
|
131
|
+
- - ! '>='
|
132
|
+
- !ruby/object:Gem::Version
|
133
|
+
version: '0'
|
134
|
+
type: :runtime
|
135
|
+
prerelease: false
|
136
|
+
version_requirements: !ruby/object:Gem::Requirement
|
137
|
+
none: false
|
138
|
+
requirements:
|
139
|
+
- - ! '>='
|
140
|
+
- !ruby/object:Gem::Version
|
141
|
+
version: '0'
|
142
|
+
- !ruby/object:Gem::Dependency
|
143
|
+
name: awesome_print
|
144
|
+
requirement: !ruby/object:Gem::Requirement
|
145
|
+
none: false
|
146
|
+
requirements:
|
147
|
+
- - ! '>='
|
148
|
+
- !ruby/object:Gem::Version
|
149
|
+
version: '0'
|
150
|
+
type: :runtime
|
151
|
+
prerelease: false
|
152
|
+
version_requirements: !ruby/object:Gem::Requirement
|
153
|
+
none: false
|
154
|
+
requirements:
|
155
|
+
- - ! '>='
|
156
|
+
- !ruby/object:Gem::Version
|
157
|
+
version: '0'
|
158
|
+
- !ruby/object:Gem::Dependency
|
159
|
+
name: rspec
|
160
|
+
requirement: !ruby/object:Gem::Requirement
|
161
|
+
none: false
|
162
|
+
requirements:
|
163
|
+
- - ~>
|
164
|
+
- !ruby/object:Gem::Version
|
165
|
+
version: '2.11'
|
166
|
+
type: :development
|
167
|
+
prerelease: false
|
168
|
+
version_requirements: !ruby/object:Gem::Requirement
|
169
|
+
none: false
|
170
|
+
requirements:
|
171
|
+
- - ~>
|
172
|
+
- !ruby/object:Gem::Version
|
173
|
+
version: '2.11'
|
174
|
+
- !ruby/object:Gem::Dependency
|
175
|
+
name: mocha
|
176
|
+
requirement: !ruby/object:Gem::Requirement
|
177
|
+
none: false
|
178
|
+
requirements:
|
179
|
+
- - '='
|
180
|
+
- !ruby/object:Gem::Version
|
181
|
+
version: 0.12.6
|
182
|
+
type: :development
|
183
|
+
prerelease: false
|
184
|
+
version_requirements: !ruby/object:Gem::Requirement
|
185
|
+
none: false
|
186
|
+
requirements:
|
187
|
+
- - '='
|
188
|
+
- !ruby/object:Gem::Version
|
189
|
+
version: 0.12.6
|
190
|
+
- !ruby/object:Gem::Dependency
|
191
|
+
name: bundler
|
192
|
+
requirement: !ruby/object:Gem::Requirement
|
193
|
+
none: false
|
194
|
+
requirements:
|
195
|
+
- - ! '>='
|
196
|
+
- !ruby/object:Gem::Version
|
197
|
+
version: '0'
|
198
|
+
type: :development
|
199
|
+
prerelease: false
|
200
|
+
version_requirements: !ruby/object:Gem::Requirement
|
201
|
+
none: false
|
202
|
+
requirements:
|
203
|
+
- - ! '>='
|
204
|
+
- !ruby/object:Gem::Version
|
205
|
+
version: '0'
|
206
|
+
- !ruby/object:Gem::Dependency
|
207
|
+
name: rake
|
208
|
+
requirement: !ruby/object:Gem::Requirement
|
209
|
+
none: false
|
210
|
+
requirements:
|
211
|
+
- - ! '>='
|
212
|
+
- !ruby/object:Gem::Version
|
213
|
+
version: '0'
|
214
|
+
type: :development
|
215
|
+
prerelease: false
|
216
|
+
version_requirements: !ruby/object:Gem::Requirement
|
217
|
+
none: false
|
218
|
+
requirements:
|
219
|
+
- - ! '>='
|
220
|
+
- !ruby/object:Gem::Version
|
221
|
+
version: '0'
|
222
|
+
- !ruby/object:Gem::Dependency
|
223
|
+
name: hashie
|
224
|
+
requirement: !ruby/object:Gem::Requirement
|
225
|
+
none: false
|
226
|
+
requirements:
|
227
|
+
- - ! '>='
|
228
|
+
- !ruby/object:Gem::Version
|
229
|
+
version: '0'
|
230
|
+
type: :development
|
231
|
+
prerelease: false
|
232
|
+
version_requirements: !ruby/object:Gem::Requirement
|
233
|
+
none: false
|
234
|
+
requirements:
|
235
|
+
- - ! '>='
|
236
|
+
- !ruby/object:Gem::Version
|
237
|
+
version: '0'
|
238
|
+
description: Tent protocol validator
|
239
|
+
email:
|
240
|
+
- jesse@jessestuart.ca
|
241
|
+
executables: []
|
242
|
+
extensions: []
|
243
|
+
extra_rdoc_files: []
|
244
|
+
files:
|
245
|
+
- .gitignore
|
246
|
+
- Gemfile
|
247
|
+
- LICENSE
|
248
|
+
- README.md
|
249
|
+
- Rakefile
|
250
|
+
- lib/tent-validator.rb
|
251
|
+
- lib/tent-validator/faraday/tent_net_http_adapter.rb
|
252
|
+
- lib/tent-validator/faraday/tent_rack_adapter.rb
|
253
|
+
- lib/tent-validator/runner.rb
|
254
|
+
- lib/tent-validator/runner/cli.rb
|
255
|
+
- lib/tent-validator/spec.rb
|
256
|
+
- lib/tent-validator/validators/new_post_validator.rb
|
257
|
+
- lib/tent-validator/validators/post_validator.rb
|
258
|
+
- lib/tent-validator/validators/support/error_header_expectation.rb
|
259
|
+
- lib/tent-validator/validators/support/post_header_expectation.rb
|
260
|
+
- lib/tent-validator/validators/support/tent_header_expectation.rb
|
261
|
+
- lib/tent-validator/validators/support/tent_schemas.rb
|
262
|
+
- lib/tent-validator/validators/without_authentication/app_validator.rb
|
263
|
+
- lib/tent-validator/version.rb
|
264
|
+
- tent-validator.gemspec
|
265
|
+
homepage: ''
|
266
|
+
licenses: []
|
267
|
+
post_install_message:
|
268
|
+
rdoc_options: []
|
269
|
+
require_paths:
|
270
|
+
- lib
|
271
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
272
|
+
none: false
|
273
|
+
requirements:
|
274
|
+
- - ! '>='
|
275
|
+
- !ruby/object:Gem::Version
|
276
|
+
version: '0'
|
277
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
278
|
+
none: false
|
279
|
+
requirements:
|
280
|
+
- - ! '>='
|
281
|
+
- !ruby/object:Gem::Version
|
282
|
+
version: '0'
|
283
|
+
requirements: []
|
284
|
+
rubyforge_project:
|
285
|
+
rubygems_version: 1.8.23
|
286
|
+
signing_key:
|
287
|
+
specification_version: 3
|
288
|
+
summary: Tent protocol validator
|
289
|
+
test_files: []
|