lurker 0.6.0 → 0.6.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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