lurker 0.5.6 → 0.5.7

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.
Files changed (42) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +1 -0
  3. data.tar.gz.sig +0 -0
  4. data/README.md +60 -24
  5. data/Rakefile +1 -1
  6. data/certs/razum2um.pem +21 -0
  7. data/features/controller_nested_schema_scaffolding.feature +5 -9
  8. data/features/controller_schema_scaffolding.feature +5 -9
  9. data/features/html_generation.feature +1 -1
  10. data/features/minitest.feature +140 -0
  11. data/features/multidomain_support.feature +1 -1
  12. data/features/partials.feature +3 -3
  13. data/features/request_nested_schema_scaffolding.feature +3 -3
  14. data/features/request_schema_scaffolding.feature +2 -2
  15. data/features/schema_suffixes.feature +4 -4
  16. data/features/step_definitions/additional_cli_steps.rb +9 -0
  17. data/features/support/env.rb +1 -1
  18. data/features/test_endpoint.feature +5 -5
  19. data/lib/lurker.rb +15 -0
  20. data/lib/lurker/cli.rb +2 -3
  21. data/lib/lurker/presenters/endpoint_presenter.rb +10 -4
  22. data/lib/lurker/request.rb +41 -0
  23. data/lib/lurker/response.rb +16 -0
  24. data/lib/lurker/schema.rb +0 -8
  25. data/lib/lurker/service.rb +2 -2
  26. data/lib/lurker/spec_helper.rb +2 -0
  27. data/lib/lurker/spec_helper/rails.rb +40 -0
  28. data/lib/lurker/spec_helper/rspec.rb +66 -0
  29. data/lib/lurker/spy.rb +88 -0
  30. data/lib/lurker/version.rb +1 -1
  31. data/lurker.gemspec +12 -9
  32. data/templates/generate_stuff.rb +29 -2
  33. data/templates/lurker_app.rb +27 -4
  34. data/templates/rails32_http_patch_support.rb +125 -0
  35. metadata +119 -57
  36. metadata.gz.sig +3 -0
  37. checksums.yaml.gz.asc +0 -12
  38. data.tar.gz.asc +0 -12
  39. data/lib/lurker/controller_spec_watcher.rb +0 -70
  40. data/lib/lurker/request_spec_watcher.rb +0 -80
  41. data/lib/lurker/spec_watcher.rb +0 -124
  42. metadata.gz.asc +0 -12
@@ -11,12 +11,12 @@ Feature: request nested schema scaffolding
11
11
 
12
12
  let!(:user) do
13
13
  User.where(name: 'razum2um').first_or_create!.tap do |u|
14
- u.repos.create!(name: 'lurker')
14
+ u.repos.first_or_create!(name: 'lurker')
15
15
  end
16
16
  end
17
17
 
18
18
  it "shows a user's repo" do
19
- get "api/v1/users/#{user.id}/repos/#{user.repos.first.id}"
19
+ get "api/v1/users/#{user.id}/repos/#{user.repos.first.id}.json"
20
20
  expect(response).to be_success
21
21
  end
22
22
  end
@@ -60,7 +60,7 @@ Feature: request nested schema scaffolding
60
60
  example: 1
61
61
  extensions:
62
62
  method: GET
63
- path_info: "/api/v1/users/1/repos/1"
63
+ path_info: "/api/v1/users/1/repos/1.json"
64
64
  path_params:
65
65
  action: show
66
66
  controller: api/v1/repos
@@ -19,7 +19,7 @@ Feature: request schema scaffolding
19
19
  end
20
20
 
21
21
  it "lists all the users" do
22
- get "api/v1/users?limit=1"
22
+ get "api/v1/users.json?limit=1"
23
23
  expect(response).to be_success
24
24
  expect(JSON.parse(response.body).size).to eq 1
25
25
  end
@@ -66,7 +66,7 @@ Feature: request schema scaffolding
66
66
  example: razum2um
67
67
  extensions:
68
68
  method: GET
69
- path_info: "/api/v1/users"
69
+ path_info: "/api/v1/users.json"
70
70
  path_params:
71
71
  action: index
72
72
  controller: api/v1/users
@@ -46,7 +46,7 @@ Feature: schema suffixes
46
46
  example: 1
47
47
  required: []
48
48
  extensions:
49
- path_info: "/api/v1/users/razum2um/repos/lurker"
49
+ path_info: "/api/v1/users/razum2um/repos/lurker.json"
50
50
  method: PATCH
51
51
  suffix: ''
52
52
  path_params:
@@ -71,7 +71,7 @@ Feature: schema suffixes
71
71
 
72
72
  it "updates a repo name" do
73
73
  expect {
74
- patch "/api/v1/users/#{user.name}/repos/#{repo.name}", repo: { name: 'updated-name' }
74
+ patch "/api/v1/users/#{user.name}/repos/#{repo.name}.json", repo: { name: 'updated-name' }
75
75
  expect(response).to be_success
76
76
  }.to change { repo.reload.name } .from('lurker').to('updated-name')
77
77
  end
@@ -117,7 +117,7 @@ Feature: schema suffixes
117
117
  type: string
118
118
  example: can't be blank
119
119
  extensions:
120
- path_info: "/api/v1/users/razum2um/repos/lurker"
120
+ path_info: "/api/v1/users/razum2um/repos.json"
121
121
  method: PATCH
122
122
  suffix: 'failed'
123
123
  path_params:
@@ -142,7 +142,7 @@ Feature: schema suffixes
142
142
 
143
143
  it "fails to update a repo with a blank name", lurker: 'failed' do
144
144
  expect {
145
- patch "/api/v1/users/#{user.name}/repos/#{repo.name}", repo: { name: '' }
145
+ patch "/api/v1/users/#{user.name}/repos/#{repo.name}.json", repo: { name: '' }
146
146
  expect(response).not_to be_success
147
147
  }.not_to change { repo.reload.name }
148
148
  end
@@ -69,6 +69,15 @@ Then /^the output should contain (failures|these lines):$/ do |_, lines|
69
69
  end
70
70
  end
71
71
 
72
+ Then /^the output should contain unescaped (failures|these lines):$/ do |_, lines|
73
+ out = all_output.dup
74
+ lines.split(/\n/).map(&:strip).each do |line|
75
+ next if line.blank?
76
+ expect(out).to match /#{line}/
77
+ out.gsub!(/.*?#{line}/m, '')
78
+ end
79
+ end
80
+
72
81
  Then(/^I should see JSON response with "([^"]*)"$/) do |name|
73
82
  within(find(:xpath, "//*[@id='show-api-response-div']")) do
74
83
  expect(page).to have_content name
@@ -41,7 +41,7 @@ Before do
41
41
  system "bin/spring stop"
42
42
  FileUtils.rm_rf File.expand_path('../../../tmp/lurker_app/lurker', __FILE__)
43
43
  FileUtils.rm_rf File.expand_path('../../../tmp/lurker_app/html', __FILE__)
44
- FileUtils.rm_rf File.expand_path('../../../tmp/lurker_app/spec/request', __FILE__)
44
+ FileUtils.rm_rf File.expand_path('../../../tmp/lurker_app/spec/requests', __FILE__)
45
45
  FileUtils.rm_rf File.expand_path('../../../tmp/lurker_app/spec/controllers', __FILE__)
46
46
  end
47
47
  end
@@ -86,8 +86,8 @@ Feature: test endpoint
86
86
  end
87
87
 
88
88
  it "updates a user" do
89
- patch :update, id: user.id, user: { name: 1 }
90
- expect(response).not_to be_success
89
+ patch :update, id: user.id, user: { name: 1 }, format: 'json'
90
+ expect(response).to be_success
91
91
  end
92
92
  end
93
93
  """
@@ -103,7 +103,7 @@ Feature: test endpoint
103
103
  """
104
104
 
105
105
  Scenario: json schema tests response parameters and tell what fails using "users/update"
106
- Given a file named "spec/controllers/api/v1/users_controller_spec.rb" with:
106
+ Given a file named "spec/controllers/api/v1/users_controller_blank_spec.rb" with:
107
107
  """ruby
108
108
  require "spec_helper"
109
109
 
@@ -115,13 +115,13 @@ Feature: test endpoint
115
115
  end
116
116
 
117
117
  it "updates a user" do
118
- patch :update, id: user.id, user: { name: '' }
118
+ patch :update, id: user.id, user: { name: '' }, format: 'json'
119
119
  expect(response).not_to be_success
120
120
  end
121
121
  end
122
122
  """
123
123
 
124
- When I run `bin/rspec spec/controllers/api/v1/users_controller_spec.rb`
124
+ When I run `bin/rspec spec/controllers/api/v1/users_controller_blank_spec.rb`
125
125
  Then the output should contain failures:
126
126
  """
127
127
  Lurker::ValidationError:
data/lib/lurker.rb CHANGED
@@ -3,6 +3,18 @@ $:.unshift(File.dirname(__FILE__))
3
3
  module Lurker
4
4
  DEFAULT_SERVICE_PATH = DEFAULT_URL_BASE = "lurker"
5
5
 
6
+ def self.safe_require(gem, desc=nil)
7
+ begin
8
+ require gem
9
+ rescue LoadError => e
10
+ $stderr.puts(e.message)
11
+ $stderr.puts(desc) if desc
12
+ $stderr.puts("Please, bundle `gem #{gem}` in your Gemfile")
13
+ exit 1 unless block_given?
14
+ end
15
+ yield if block_given?
16
+ end
17
+
6
18
  def self.scaffold_mode?
7
19
  ENV['LURKER_SCAFFOLD']
8
20
  end
@@ -51,6 +63,9 @@ require 'lurker/presenters/service_presenter'
51
63
  require 'lurker/presenters/endpoint_presenter'
52
64
  require 'lurker/presenters/schema_presenter'
53
65
  require 'lurker/presenters/response_code_presenter'
66
+ require 'lurker/spy'
67
+ require 'lurker/request'
68
+ require 'lurker/response'
54
69
 
55
70
  if defined? Rails
56
71
  require 'lurker/engine'
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
  ''
@@ -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
@@ -0,0 +1,41 @@
1
+ require 'hashie/dash'
2
+
3
+ module Lurker
4
+ class Request < Hashie::Dash
5
+ PREFIX = 'action_dispatch.request'
6
+
7
+ property :verb, required: true
8
+ property :endpoint_path, required: true
9
+ property :payload, default: {}
10
+
11
+ # ext
12
+ property :path_info, required: true
13
+ property :path_params, default: {}
14
+ property :query_params, default: {}
15
+
16
+ def self.build_from_action_dispatch(request)
17
+ new(
18
+ verb: request.method,
19
+ endpoint_path: route_name(request),
20
+ path_info: request.path_info,
21
+ path_params: request.env["#{PREFIX}.path_parameters"].stringify_keys.except('format'),
22
+ query_params: request.env["#{PREFIX}.query_parameters"],
23
+ payload: request.env["#{PREFIX}.request_parameters"].merge(
24
+ request.env["#{PREFIX}.query_parameters"]
25
+ ).stringify_keys.except('action', "controller", 'format', '_method')
26
+ )
27
+ end
28
+
29
+ def self.reject_internal(hash)
30
+
31
+ end
32
+
33
+ def self.route_name(request)
34
+ if defined? Rails
35
+ Rails.application.routes.router.recognize(request) do |route, _|
36
+ return route.path.spec.to_s.sub('(.:format)', '')
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,16 @@
1
+ require 'json'
2
+ require 'hashie/dash'
3
+
4
+ module Lurker
5
+ class Response < Hashie::Dash
6
+ property :status, required: true
7
+ property :body, default: {}
8
+
9
+ def self.build_from_action_dispatch(response)
10
+ new(
11
+ status: response.status,
12
+ body: (JSON.parse(response.body) rescue {})
13
+ )
14
+ end
15
+ end
16
+ end
data/lib/lurker/schema.rb CHANGED
@@ -1,4 +1,3 @@
1
- require 'diffy'
2
1
  require 'yaml'
3
2
 
4
3
  module Lurker
@@ -31,13 +30,6 @@ module Lurker
31
30
  @hash.send method, *args, &block
32
31
  end
33
32
 
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
33
  def write_to(path)
42
34
  if @hash['prefix'].blank?
43
35
  @hash['prefix'] = "#{default_subject} management"
@@ -43,8 +43,8 @@ class Lurker::Service
43
43
  @opened_endpoints.each { |e| e.persist! if e.respond_to? :persist! }
44
44
  end
45
45
 
46
- def verify!(verb, path, extensions, request_params, response_params,
47
- response_status, successful)
46
+ def verify!(verb, path, request_params,
47
+ extensions, response_status, response_params, successful=true)
48
48
  endpoint = open(verb, path, extensions)
49
49
  endpoint.consume!(request_params, response_params, response_status, successful)
50
50
  end
@@ -0,0 +1,2 @@
1
+ require 'lurker/spec_helper/rails'
2
+ require 'lurker/spec_helper/rspec'
@@ -0,0 +1,40 @@
1
+ require 'lurker/spy'
2
+
3
+ module Lurker
4
+ module SpecHelper
5
+ module Rails
6
+ extend ActiveSupport::Concern
7
+
8
+ module ClassMethods
9
+ module LurkerSession
10
+ def process(*)
11
+ super.tap do
12
+ if Lurker::Spy.enabled?
13
+ [:@request, :@response].each do |iv_name|
14
+ if !instance_variable_defined?(iv_name) || instance_variable_get(iv_name).nil?
15
+ raise Lurker::Spy::BlindError.new("#{iv_name} is nil: make sure you set it in your test's setup method.")
16
+ end
17
+ end
18
+
19
+ Lurker::Spy.current.request = Lurker::Request.build_from_action_dispatch(@request)
20
+ Lurker::Spy.current.response = Lurker::Response.build_from_action_dispatch(@response)
21
+ end
22
+ end
23
+ end
24
+ end
25
+
26
+ def new(*)
27
+ super.extend(LurkerSession)
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
33
+
34
+ if defined?(ActionDispatch::Integration::Session)
35
+ ActionDispatch::Integration::Session.send :include, Lurker::SpecHelper::Rails
36
+ end
37
+
38
+ if defined?(ActionController::TestCase::Behavior)
39
+ ActionController::TestCase::Behavior.send :include, Lurker::SpecHelper::Rails
40
+ end
@@ -0,0 +1,66 @@
1
+ require 'lurker/spy'
2
+
3
+ module Lurker
4
+ module SpecHelper
5
+ module Rspec
6
+ extend ActiveSupport::Concern
7
+ ACTIONS = [:get, :post, :put, :delete, :patch].freeze
8
+
9
+ included do
10
+ ACTIONS.each do |verb|
11
+ send(:define_method, "#{verb}_with_lurker") do |*params|
12
+ action, request_params, env = params
13
+
14
+ request_params ||= {}
15
+ env ||= {}
16
+
17
+ # obsolete, support controller -> request migration
18
+ # supports `get :index` in request specs
19
+ if @_example && @_example.metadata[:type] == :request && action.is_a?(Symbol)
20
+ unless @_example.metadata.described_class.is_a?(Class)
21
+ raise 'cannot determine request url: provide proper described class like: "describe MyController do"'
22
+ end
23
+ controller_name = @_example.metadata.described_class.name.tableize.gsub(/_controllers$/, '')
24
+ action = URI.parse(url_for({ controller: controller_name, action: action }.merge(request_params))).path
25
+ end
26
+
27
+ send("#{verb}_without_lurker", action, request_params, env)
28
+ end
29
+
30
+ begin
31
+ send :alias_method_chain, verb, :lurker
32
+ rescue NameError
33
+ # no patch in Rails3.2
34
+ if verb == :patch
35
+ alias_method :patch_without_lurker, :put_without_lurker
36
+ alias_method :patch, :patch_with_lurker
37
+ else
38
+ raise
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
46
+
47
+ if defined?(RSpec) && RSpec.respond_to?(:configure)
48
+ RSpec.configure do |config|
49
+ config.include Lurker::SpecHelper::Rspec, type: :controller
50
+ config.include Lurker::SpecHelper::Rspec, type: :request
51
+
52
+ lurker = ->(example) {
53
+ # RSpec::Core::ExampleGroup::Nested_1 === self
54
+ @_example = example
55
+
56
+ if (metadata = example.metadata[:lurker]).present?
57
+ Lurker::Spy.on(suffix: metadata, &example)
58
+ else
59
+ example.call
60
+ end
61
+ }
62
+
63
+ config.around(:each, type: :controller, &lurker)
64
+ config.around(:each, type: :request, &lurker)
65
+ end
66
+ end
data/lib/lurker/spy.rb ADDED
@@ -0,0 +1,88 @@
1
+ require 'json'
2
+
3
+ module Lurker
4
+ class Spy
5
+ class BlindError < StandardError; end
6
+
7
+ attr_reader :block, :service
8
+ attr_accessor :request, :response
9
+
10
+ extend Forwardable
11
+ delegate [:verb, :payload] => :request
12
+ delegate [:status, :body] => :response
13
+
14
+ def initialize(options={}, &block)
15
+ @options = options
16
+ @block = block
17
+
18
+ @service = if defined?(Rails)
19
+ Service.new(Rails.root.join(DEFAULT_SERVICE_PATH).to_s, Rails.application.class.parent_name)
20
+ else
21
+ Service.default_service
22
+ end
23
+ end
24
+
25
+ def call
26
+ @request = @response = nil # fill in while test
27
+ @block.call.tap do |result|
28
+ if @request && @response
29
+ @service.verify!(
30
+ verb, endpoint_path, payload,
31
+ extensions, status, body
32
+ )
33
+
34
+ @service.persist! if success?(result)
35
+ end
36
+ end
37
+ end
38
+
39
+ def endpoint_path
40
+ [request.endpoint_path, suffix].compact.join('-')
41
+ end
42
+
43
+ def extensions
44
+ extensions = {
45
+ path_params: request.path_params,
46
+ path_info: request.path_info,
47
+ method: request.verb,
48
+ suffix: suffix.to_s,
49
+ }
50
+ unless request.query_params.empty?
51
+ extensions[:query_params] = request.query_params
52
+ end
53
+ extensions
54
+ end
55
+
56
+ def suffix
57
+ if (suffix = @options[:suffix]).is_a?(String)
58
+ suffix.gsub(/[^[[:alnum:]]]/, '_')
59
+ end
60
+ end
61
+
62
+ def success?(result)
63
+ if defined?(::Minitest::Test) && result.is_a?(::Minitest::Test)
64
+ result.failure.nil?
65
+ elsif result.is_a?(Exception)
66
+ false
67
+ else
68
+ result
69
+ end
70
+ end
71
+ private :success?
72
+
73
+ def self.on(options={}, &block)
74
+ require 'lurker/spec_helper' unless defined? Lurker::SpecHelper
75
+ (Thread.current[:lurker_spy] ||= new(options, &block)).tap do |spy|
76
+ spy.call
77
+ end
78
+ end
79
+
80
+ def self.enabled?
81
+ current.present?
82
+ end
83
+
84
+ def self.current
85
+ Thread.current[:lurker_spy]
86
+ end
87
+ end
88
+ end