lurker 0.6.0 → 0.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- checksums.yaml.gz.sig +0 -0
- data.tar.gz.sig +0 -0
- data/.hound.yml +15 -0
- data/.rubocop.yml +15 -0
- data/README.md +19 -4
- data/Rakefile +1 -1
- data/certs/razum2um.pem +21 -0
- data/features/atom_persistent_within_the_same_type.feature +138 -0
- data/features/controller_schema_scaffolding.feature +5 -1
- data/features/multitype_request_support.feature +139 -0
- data/features/request_schema_scaffolding.feature +6 -4
- data/features/schema_updating_within_test_suite.feature +138 -0
- data/features/test_endpoint.feature +6 -3
- data/lib/lurker.rb +20 -4
- data/lib/lurker/cli.rb +2 -3
- data/lib/lurker/endpoint.rb +63 -28
- data/lib/lurker/presenters/endpoint_presenter.rb +10 -4
- data/lib/lurker/schema.rb +14 -16
- data/lib/lurker/schema_modifier.rb +46 -0
- data/lib/lurker/schema_modifier/array.rb +28 -0
- data/lib/lurker/schema_modifier/atom.rb +88 -0
- data/lib/lurker/schema_modifier/hash.rb +30 -0
- data/lib/lurker/service.rb +2 -6
- data/lib/lurker/spy.rb +1 -1
- data/lib/lurker/templates/layouts/application.html.erb +1 -0
- data/lib/lurker/version.rb +1 -1
- data/lurker.gemspec +13 -10
- data/templates/generate_stuff.rb +25 -3
- data/templates/lurker_app.rb +17 -2
- metadata +135 -68
- metadata.gz.sig +2 -0
- checksums.yaml.gz.asc +0 -12
- data.tar.gz.asc +0 -12
- data/lib/lurker/endpoint_scaffold.rb +0 -135
- metadata.gz.asc +0 -12
@@ -0,0 +1,138 @@
|
|
1
|
+
Feature: schema updating within test suite
|
2
|
+
|
3
|
+
If your API is stable and the new code isn't breaking it
|
4
|
+
you'll see nothing special, just passing specs
|
5
|
+
|
6
|
+
Background:
|
7
|
+
Given a file named "lurker/api/v2/users/__id-PATCH.json.yml" with:
|
8
|
+
"""yml
|
9
|
+
---
|
10
|
+
description: user updating
|
11
|
+
prefix: users management
|
12
|
+
requestParameters:
|
13
|
+
description: ''
|
14
|
+
type: object
|
15
|
+
additionalProperties: false
|
16
|
+
required: []
|
17
|
+
properties:
|
18
|
+
user:
|
19
|
+
description: ''
|
20
|
+
type: object
|
21
|
+
additionalProperties: false
|
22
|
+
required: []
|
23
|
+
properties:
|
24
|
+
name:
|
25
|
+
description: ''
|
26
|
+
type: string
|
27
|
+
example: 'Bob'
|
28
|
+
responseCodes:
|
29
|
+
- status: 200
|
30
|
+
successful: true
|
31
|
+
description: ''
|
32
|
+
responseParameters:
|
33
|
+
description: ''
|
34
|
+
type: object
|
35
|
+
additionalProperties: false
|
36
|
+
required: []
|
37
|
+
properties:
|
38
|
+
id:
|
39
|
+
description: ''
|
40
|
+
type: integer
|
41
|
+
example: 1
|
42
|
+
name:
|
43
|
+
description: ''
|
44
|
+
type: string
|
45
|
+
example: razum2um
|
46
|
+
surname:
|
47
|
+
description: ''
|
48
|
+
type: string
|
49
|
+
example: Unknown
|
50
|
+
extensions:
|
51
|
+
method: PATCH
|
52
|
+
path_info: "/api/v2/users/1"
|
53
|
+
path_params:
|
54
|
+
id: '1'
|
55
|
+
controller: api/v2/users
|
56
|
+
action: update
|
57
|
+
suffix: ''
|
58
|
+
"""
|
59
|
+
|
60
|
+
Scenario: json schema tests response parameters and update request parameters using "users/update"
|
61
|
+
Given a file named "spec/controllers/api/v2/users_controller_spec.rb" with:
|
62
|
+
"""ruby
|
63
|
+
require "spec_helper"
|
64
|
+
|
65
|
+
describe Api::V2::UsersController, :lurker do
|
66
|
+
render_views
|
67
|
+
|
68
|
+
let(:user) do
|
69
|
+
User.where(name: 'razum2um', surname: 'Unknown').first_or_create!
|
70
|
+
end
|
71
|
+
|
72
|
+
it "updates a user surname as string" do
|
73
|
+
patch :update, id: user.id, user: { surname: 'Marley' }
|
74
|
+
expect(response).to be_success
|
75
|
+
end
|
76
|
+
end
|
77
|
+
"""
|
78
|
+
|
79
|
+
When I run `bin/rspec spec/controllers/api/v2/users_controller_spec.rb`
|
80
|
+
Then the example should pass
|
81
|
+
Then a file named "lurker/api/v2/users/__id-PATCH.json.yml" should exist
|
82
|
+
Then the file "lurker/api/v2/users/__id-PATCH.json.yml" should contain exactly:
|
83
|
+
"""yml
|
84
|
+
---
|
85
|
+
description: user updating
|
86
|
+
prefix: users management
|
87
|
+
requestParameters:
|
88
|
+
description: ''
|
89
|
+
type: object
|
90
|
+
additionalProperties: false
|
91
|
+
required: []
|
92
|
+
properties:
|
93
|
+
user:
|
94
|
+
description: ''
|
95
|
+
type: object
|
96
|
+
additionalProperties: false
|
97
|
+
required: []
|
98
|
+
properties:
|
99
|
+
name:
|
100
|
+
description: ''
|
101
|
+
type: string
|
102
|
+
example: Bob
|
103
|
+
surname:
|
104
|
+
description: ''
|
105
|
+
type: string
|
106
|
+
example: Marley
|
107
|
+
responseCodes:
|
108
|
+
- status: 200
|
109
|
+
successful: true
|
110
|
+
description: ''
|
111
|
+
responseParameters:
|
112
|
+
description: ''
|
113
|
+
type: object
|
114
|
+
additionalProperties: false
|
115
|
+
required: []
|
116
|
+
properties:
|
117
|
+
id:
|
118
|
+
description: ''
|
119
|
+
type: integer
|
120
|
+
example: 1
|
121
|
+
name:
|
122
|
+
description: ''
|
123
|
+
type: string
|
124
|
+
example: razum2um
|
125
|
+
surname:
|
126
|
+
description: ''
|
127
|
+
type: string
|
128
|
+
example: Unknown
|
129
|
+
extensions:
|
130
|
+
method: PATCH
|
131
|
+
path_info: "/api/v2/users/1"
|
132
|
+
path_params:
|
133
|
+
id: '1'
|
134
|
+
controller: api/v2/users
|
135
|
+
action: update
|
136
|
+
suffix: ''
|
137
|
+
|
138
|
+
"""
|
@@ -3,7 +3,7 @@ Feature: test endpoint
|
|
3
3
|
If your API is stable and the new code isn't breaking it
|
4
4
|
you'll see nothing special, just passing specs
|
5
5
|
|
6
|
-
**NOTE:** the second request example is expecting a
|
6
|
+
**NOTE:** the second request example is expecting a successful response and it is,
|
7
7
|
but specs are NOT passing because of nonsufficient `name` parameter
|
8
8
|
|
9
9
|
**NOTE:** the third response example is expecting a successful response and it is,
|
@@ -41,6 +41,9 @@ Feature: test endpoint
|
|
41
41
|
name:
|
42
42
|
type: string
|
43
43
|
example: Bob
|
44
|
+
surname:
|
45
|
+
type: string
|
46
|
+
example: Marley
|
44
47
|
extensions:
|
45
48
|
path_info: "/api/v1/users/1"
|
46
49
|
method: PATCH
|
@@ -96,8 +99,8 @@ Feature: test endpoint
|
|
96
99
|
Then the output should contain failures:
|
97
100
|
"""
|
98
101
|
Lurker::ValidationError:
|
99
|
-
|
100
|
-
The property '#/
|
102
|
+
Response
|
103
|
+
The property '#/name' of type Fixnum did not match the following type: string
|
101
104
|
|
102
105
|
1 example, 1 failure
|
103
106
|
"""
|
data/lib/lurker.rb
CHANGED
@@ -1,10 +1,23 @@
|
|
1
1
|
$:.unshift(File.dirname(__FILE__))
|
2
2
|
|
3
3
|
module Lurker
|
4
|
-
DEFAULT_SERVICE_PATH = DEFAULT_URL_BASE = "lurker"
|
4
|
+
DEFAULT_SERVICE_PATH = DEFAULT_URL_BASE = "lurker".freeze
|
5
|
+
LURKER_UPGRADE = "LURKER_UPGRADE".freeze
|
5
6
|
|
6
|
-
def self.
|
7
|
-
|
7
|
+
def self.safe_require(gem, desc=nil)
|
8
|
+
begin
|
9
|
+
require gem
|
10
|
+
rescue LoadError => e
|
11
|
+
$stderr.puts(e.message)
|
12
|
+
$stderr.puts(desc) if desc
|
13
|
+
$stderr.puts("Please, bundle `gem #{gem}` in your Gemfile")
|
14
|
+
exit 1 unless block_given?
|
15
|
+
end
|
16
|
+
yield if block_given?
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.upgrade?
|
20
|
+
!!ENV[LURKER_UPGRADE]
|
8
21
|
end
|
9
22
|
|
10
23
|
def self.service_path=(service_path)
|
@@ -35,6 +48,10 @@ module Lurker
|
|
35
48
|
end
|
36
49
|
|
37
50
|
require 'lurker/schema'
|
51
|
+
require 'lurker/schema_modifier'
|
52
|
+
require 'lurker/schema_modifier/hash'
|
53
|
+
require 'lurker/schema_modifier/array'
|
54
|
+
require 'lurker/schema_modifier/atom'
|
38
55
|
require 'lurker/ref_object'
|
39
56
|
require 'lurker/erb_schema_context'
|
40
57
|
require 'lurker/service'
|
@@ -42,7 +59,6 @@ require 'lurker/jaml_descriptor'
|
|
42
59
|
require 'lurker/validator'
|
43
60
|
require 'lurker/validation_error'
|
44
61
|
require 'lurker/endpoint'
|
45
|
-
require 'lurker/endpoint_scaffold'
|
46
62
|
require 'lurker/rendering_controller'
|
47
63
|
require 'lurker/form_builder'
|
48
64
|
require 'lurker/presenters/json_presenter'
|
data/lib/lurker/cli.rb
CHANGED
@@ -1,7 +1,5 @@
|
|
1
1
|
require 'thor'
|
2
2
|
require 'execjs'
|
3
|
-
require 'pdfkit'
|
4
|
-
# require 'coderay'
|
5
3
|
require 'digest/sha1'
|
6
4
|
require 'lurker/service'
|
7
5
|
|
@@ -60,6 +58,7 @@ module Lurker
|
|
60
58
|
|
61
59
|
no_tasks do
|
62
60
|
def convert_to_pdf
|
61
|
+
Lurker.safe_require('pdfkit')
|
63
62
|
css = File.expand_path('application.css', self.class.precompiled_static_root)
|
64
63
|
in_root do
|
65
64
|
service_presenters.each do |service_presenter|
|
@@ -204,7 +203,7 @@ module Lurker
|
|
204
203
|
return unless content_fname
|
205
204
|
content_fname = File.expand_path(content_fname)
|
206
205
|
if content_fname.ends_with? 'md'
|
207
|
-
|
206
|
+
Lurker.safe_require('kramdown')
|
208
207
|
Kramdown::Document.new(open(content_fname).read).to_html
|
209
208
|
else
|
210
209
|
''
|
data/lib/lurker/endpoint.rb
CHANGED
@@ -12,20 +12,15 @@ class Lurker::Endpoint
|
|
12
12
|
def initialize(endpoint_path, extensions={}, service=Lurker::Service.default_service)
|
13
13
|
@endpoint_path = endpoint_path
|
14
14
|
@extensions = extensions
|
15
|
-
@schema = Lurker::Schema.new(
|
16
|
-
load_file(@endpoint_path),
|
17
|
-
stringify_keys(extensions)
|
18
|
-
)
|
19
15
|
@service = service
|
20
16
|
@errors = []
|
21
|
-
@
|
22
|
-
|
23
|
-
)
|
17
|
+
@persisted = false
|
18
|
+
@schema = File.exist?(endpoint_path) ? load_schema : build_schema
|
24
19
|
end
|
25
20
|
|
26
21
|
def persist!
|
27
|
-
|
28
|
-
|
22
|
+
schema.ordered!.write_to(endpoint_path)
|
23
|
+
@persisted = true
|
29
24
|
end
|
30
25
|
|
31
26
|
def indexed?
|
@@ -40,32 +35,25 @@ class Lurker::Endpoint
|
|
40
35
|
|
41
36
|
def consume_request(params, successful=true)
|
42
37
|
if successful
|
43
|
-
|
44
|
-
current_scaffold.consume_request(params, successful)
|
45
|
-
end
|
38
|
+
Lurker::SchemaModifier.merge!(request_parameters, stringify_keys(params))
|
46
39
|
end
|
47
40
|
end
|
48
41
|
|
49
42
|
def consume_response(params, status_code, successful=true)
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
)
|
43
|
+
return validate_response(params, status_code, successful) if persisted?
|
44
|
+
|
45
|
+
if successful
|
46
|
+
Lurker::SchemaModifier.merge!(response_parameters, stringify_keys(params))
|
55
47
|
end
|
56
48
|
|
49
|
+
if !status_code_exists?(status_code, successful)
|
50
|
+
response_code = {
|
51
|
+
"status" => status_code,
|
52
|
+
"successful" => successful,
|
53
|
+
"description" => ""
|
54
|
+
}
|
57
55
|
|
58
|
-
|
59
|
-
raise Lurker::UndocumentedResponseCode,
|
60
|
-
'Undocumented response: %s, successful: %s' % [
|
61
|
-
status_code, successful
|
62
|
-
]
|
63
|
-
elsif successful
|
64
|
-
unless validate(response_parameters, params, 'Response')
|
65
|
-
current_scaffold.consume_response(params, status_code, successful)
|
66
|
-
end
|
67
|
-
else
|
68
|
-
true
|
56
|
+
Lurker::SchemaModifier.append!(response_codes, response_code)
|
69
57
|
end
|
70
58
|
end
|
71
59
|
|
@@ -116,6 +104,32 @@ class Lurker::Endpoint
|
|
116
104
|
|
117
105
|
protected
|
118
106
|
|
107
|
+
def persisted?
|
108
|
+
!!@persisted
|
109
|
+
end
|
110
|
+
|
111
|
+
def load_schema
|
112
|
+
@persisted = true
|
113
|
+
|
114
|
+
Lurker::Schema.new(
|
115
|
+
load_file(endpoint_path),
|
116
|
+
stringify_keys(extensions)
|
117
|
+
)
|
118
|
+
end
|
119
|
+
|
120
|
+
def build_schema
|
121
|
+
@persisted = false
|
122
|
+
|
123
|
+
Lurker::Schema.new(
|
124
|
+
{
|
125
|
+
"prefix" => "",
|
126
|
+
"description" => "",
|
127
|
+
"responseCodes" => []
|
128
|
+
},
|
129
|
+
stringify_keys(extensions)
|
130
|
+
)
|
131
|
+
end
|
132
|
+
|
119
133
|
def load_file(fname)
|
120
134
|
if fname.match(/\.erb$/)
|
121
135
|
context = Lurker::ErbSchemaContext.new
|
@@ -137,6 +151,27 @@ class Lurker::Endpoint
|
|
137
151
|
true
|
138
152
|
end
|
139
153
|
|
154
|
+
def validate_response(params, status_code, successful)
|
155
|
+
if !status_code_exists?(status_code, successful)
|
156
|
+
raise Lurker::UndocumentedResponseCode,
|
157
|
+
'Undocumented response: %s, successful: %s' % [
|
158
|
+
status_code, successful
|
159
|
+
]
|
160
|
+
elsif successful
|
161
|
+
validate(response_parameters, params, 'Response')
|
162
|
+
else
|
163
|
+
true
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
def status_code_exists?(status_code, successful)
|
168
|
+
!!response_codes.detect do |code|
|
169
|
+
code["successful"] == successful &&
|
170
|
+
(code["status"] == status_code || # 200
|
171
|
+
code["status"].to_i == status_code) # "200 OK"
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
140
175
|
def raise_errors!
|
141
176
|
unless errors.empty?
|
142
177
|
raise Lurker::ValidationError.new(word_wrap((
|
@@ -84,14 +84,20 @@ class Lurker::EndpointPresenter < Lurker::BasePresenter
|
|
84
84
|
return if endpoint.response_parameters.empty?
|
85
85
|
response = example_from_schema(endpoint.response_parameters, endpoint.schema)
|
86
86
|
@example_response = response.to_json
|
87
|
-
|
87
|
+
@highlighted = false
|
88
|
+
Lurker.safe_require("execjs", "to get samples highlighted") do
|
88
89
|
jsfile = File.expand_path('javascripts/highlight.pack.js', Lurker::Cli.source_root)
|
89
90
|
source = open(jsfile).read
|
90
91
|
context = ExecJS.compile(source)
|
91
92
|
@example_response = context.exec("return hljs.highlightAuto(JSON.stringify(#{@example_response}, null, 2)).value")
|
92
|
-
|
93
|
-
|
94
|
-
|
93
|
+
@highlighted = true
|
94
|
+
end
|
95
|
+
unless @highlighted
|
96
|
+
Lurker.safe_require("coderay", "to get samples highlighted") do
|
97
|
+
#::CodeRay.scan(response.to_json, :jjson).html(wrap: nil, css: :class) # forked compatible version
|
98
|
+
@example_response = ::CodeRay.scan(@example_response, :json).html(wrap: nil, css: :class)
|
99
|
+
@highlighted = true
|
100
|
+
end
|
95
101
|
end
|
96
102
|
@example_response
|
97
103
|
end
|
data/lib/lurker/schema.rb
CHANGED
@@ -1,9 +1,8 @@
|
|
1
|
-
require 'diffy'
|
2
1
|
require 'yaml'
|
3
2
|
|
4
3
|
module Lurker
|
5
4
|
class Schema
|
6
|
-
|
5
|
+
EXTENSIONS = "extensions".freeze
|
7
6
|
DESCRPTIONS = {
|
8
7
|
'index' => 'listing',
|
9
8
|
'show' => '',
|
@@ -14,13 +13,11 @@ module Lurker
|
|
14
13
|
}
|
15
14
|
attr_reader :extensions
|
16
15
|
|
17
|
-
def initialize(json_schema_hash, extensions={})
|
16
|
+
def initialize(json_schema_hash, extensions = {})
|
18
17
|
@hash = json_schema_hash
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
extensions
|
23
|
-
end
|
18
|
+
|
19
|
+
existing_extensions = @hash.delete(EXTENSIONS) || {}
|
20
|
+
@extensions = select_extensions(existing_extensions, extensions)
|
24
21
|
end
|
25
22
|
|
26
23
|
def respond_to_missing?(method, include_private = false)
|
@@ -31,13 +28,6 @@ module Lurker
|
|
31
28
|
@hash.send method, *args, &block
|
32
29
|
end
|
33
30
|
|
34
|
-
def diff(schema)
|
35
|
-
::Diffy::Diff.new(
|
36
|
-
schema.serialized_for_diff,
|
37
|
-
serialized_for_diff,
|
38
|
-
context: 1).to_s(:color)
|
39
|
-
end
|
40
|
-
|
41
31
|
def write_to(path)
|
42
32
|
if @hash['prefix'].blank?
|
43
33
|
@hash['prefix'] = "#{default_subject} management"
|
@@ -56,7 +46,7 @@ module Lurker
|
|
56
46
|
|
57
47
|
def to_yaml
|
58
48
|
YAML.dump(@hash.merge(
|
59
|
-
|
49
|
+
EXTENSIONS => @extensions
|
60
50
|
))
|
61
51
|
end
|
62
52
|
|
@@ -76,6 +66,14 @@ module Lurker
|
|
76
66
|
|
77
67
|
private
|
78
68
|
|
69
|
+
def select_extensions(existing, given)
|
70
|
+
if Lurker.upgrade? || (existing.blank? && given.present?)
|
71
|
+
given
|
72
|
+
else
|
73
|
+
existing
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
79
77
|
def default_descrption
|
80
78
|
"#{default_subject.singularize} #{DESCRPTIONS[path_params['action']]}"
|
81
79
|
end
|