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.
@@ -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 nonsuccessful response and it is,
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
- Request
100
- The property '#/user/name' of type Fixnum did not match the following type: string
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.scaffold_mode?
7
- ENV['LURKER_SCAFFOLD']
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
- require 'kramdown'
206
+ Lurker.safe_require('kramdown')
208
207
  Kramdown::Document.new(open(content_fname).read).to_html
209
208
  else
210
209
  ''
@@ -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
- @current_scaffold = Lurker::EndpointScaffold.new(
22
- "#{endpoint_path}.new", extensions, service
23
- )
17
+ @persisted = false
18
+ @schema = File.exist?(endpoint_path) ? load_schema : build_schema
24
19
  end
25
20
 
26
21
  def persist!
27
- return unless ENV['LURKER_UPGRADE']
28
- schema.write_to(endpoint_path)
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
- unless validate(request_parameters, params, 'Request')
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
- response_code = response_codes.find do |rc|
51
- rc["successful"] == successful && (
52
- rc["status"] == status_code || # 200
53
- rc["status"].to_i == status_code # "200 OK"
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
- if !response_code
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
- if defined? ExecJS
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
- elsif defined? CodeRay
93
- @example_response = ::CodeRay.scan(@example_response, :json).html(wrap: nil, css: :class)
94
- #::CodeRay.scan(response.to_json, :jjson).html(wrap: nil, css: :class)
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
- KEY = 'extensions'
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
- @extensions = if extensions.blank? && @hash.has_key?(KEY)
20
- @hash.delete(KEY) || {}
21
- else
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
- KEY => @extensions
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