lurker 0.5.6 → 0.5.7

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