effective_test_bot 0.4.1 → 0.4.2

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: e103116aeed8578d3cb62239ab87759208152860
4
- data.tar.gz: a4978f5794f46d577111f8bbfbfbf32c0aafa9d6
3
+ metadata.gz: faf3170be69a804110713bd4fffd0d2802841fa2
4
+ data.tar.gz: 3ac29840a0b524fad45cded4d7d81c3bcb56bee4
5
5
  SHA512:
6
- metadata.gz: a18ae0e701b4e5abcd1275070c9347c10116b626896c6b59edcb7bfe2dcdfd987e7cf17a1fad08b8f6879b4de3eaa0182ccd81603d8f7b22e4108f84d56f0d92
7
- data.tar.gz: c764261c4bbfbef91ac98864877dbae1f2e549b24b3cc01ed215a934c14cc3c68891fa821ea6367ddd5ef89e8af6d1cfa242b8f742b2fccf5ad300d6b6e0a7aa
6
+ metadata.gz: e1bc8fe65d07f530b193e9498e5d3537944d09903727f0407cc7c93bccfeed52addaa1383a56faea2bc005be2429b1e5d856da74da5bc7209e5949425dfb3d7c
7
+ data.tar.gz: 4174f5a21a7cfa52081df5d86de979b48cfe1a44fe167a5974b84653cb64f4bcd24555d0787b269336f33540376684bafc566937c46df7186e91b6a6c48c607e
@@ -1,4 +1,5 @@
1
1
  module EffectiveTestBotControllerHelper
2
+ # This is included as an after_filter
2
3
  def assign_test_bot_http_headers
3
4
  response.headers['Test-Bot-Flash'] = Base64.encode64(flash.to_hash.to_json)
4
5
 
@@ -2,8 +2,69 @@ require "effective_test_bot/engine"
2
2
  require "effective_test_bot/version"
3
3
 
4
4
  module EffectiveTestBot
5
+ mattr_accessor :except
6
+ mattr_accessor :only
7
+
5
8
  def self.setup
6
9
  yield self
7
10
  end
8
11
 
12
+ def self.skip?(test, assertion = nil)
13
+ value = [test.to_s.presence, assertion.to_s.presence].compact.join(' ')
14
+ return false if value.blank?
15
+
16
+ if onlies.present?
17
+ onlies.find { |only| value.start_with?(only) }.blank? # Let partial matches work
18
+ elsif excepts.present?
19
+ excepts.find { |except| except == value }.present?
20
+ else
21
+ false
22
+ end
23
+ end
24
+
25
+ private
26
+
27
+ def self.onlies
28
+ @@onlines ||= begin
29
+ flatten_and_sort(
30
+ if ENV['TEST_BOT_TEST'].present?
31
+ ENV['TEST_BOT_TEST'].to_s.gsub('[', '').gsub(']', '').split(',').map { |str| str.strip }
32
+ else
33
+ only
34
+ end
35
+ )
36
+ end
37
+ end
38
+
39
+ def self.excepts
40
+ @@excepts ||= flatten_and_sort(except)
41
+ end
42
+
43
+ # config.except = [
44
+ # 'assert_path',
45
+ # 'users#show',
46
+ # 'users#create_invalid' => ['assert_path'],
47
+ # 'users#create_invalid' => 'assert_unpermitted_params',
48
+ # 'report_total_allocation_index_path'
49
+ # ]
50
+
51
+ # We need to flatten any Hashes into
52
+ # 'users#create_invalid' => ['assert_path', 'assert_page_title'],
53
+ # into this
54
+ # ['users#create_invalid assert_path'
55
+ # 'users#create_invalid assert_page_title']
56
+
57
+ def self.flatten_and_sort(skips)
58
+ Array(skips).flat_map do |skip|
59
+ case skip
60
+ when Symbol
61
+ skip.to_s
62
+ when Hash
63
+ skip.keys.product(skip.values.flatten).map { |p| p.join(' ') }
64
+ else
65
+ skip
66
+ end
67
+ end.compact.sort
68
+ end
69
+
9
70
  end
@@ -14,10 +14,23 @@ module EffectiveTestBot
14
14
 
15
15
  initializer 'effective_test_bot.test_suite' do |app|
16
16
  Rails.application.config.to_prepare do
17
+ # test/test_botable/
18
+ ActionDispatch::IntegrationTest.include BaseTest
17
19
  ActionDispatch::IntegrationTest.include CrudTest
18
- ActionDispatch::IntegrationTest.include TestBotable::CrudTest
19
-
20
- # A whole bunch of helper methods
20
+ ActionDispatch::IntegrationTest.include MemberTest
21
+ ActionDispatch::IntegrationTest.include PageTest
22
+ ActionDispatch::IntegrationTest.include RedirectTest
23
+ ActionDispatch::IntegrationTest.include WizardTest
24
+
25
+ # test/concerns/test_botable/
26
+ ActionDispatch::IntegrationTest.include TestBotable::BaseDsl
27
+ ActionDispatch::IntegrationTest.include TestBotable::CrudDsl
28
+ ActionDispatch::IntegrationTest.include TestBotable::MemberDsl
29
+ ActionDispatch::IntegrationTest.include TestBotable::PageDsl
30
+ ActionDispatch::IntegrationTest.include TestBotable::RedirectDsl
31
+ ActionDispatch::IntegrationTest.include TestBotable::WizardDsl
32
+
33
+ # test/support/
21
34
  ActionDispatch::IntegrationTest.include EffectiveTestBotAssertions
22
35
  ActionDispatch::IntegrationTest.include EffectiveTestBotFormHelper
23
36
  ActionDispatch::IntegrationTest.include EffectiveTestBotLoginHelper
@@ -29,6 +42,8 @@ module EffectiveTestBot
29
42
  ActiveSupport.on_load :action_controller do
30
43
  if Rails.env.test?
31
44
  ActionController::Base.send :include, ::EffectiveTestBotControllerHelper
45
+
46
+ ActionController::Base.send :before_filter, :expires_now # Prevent 304 Not Modified caching
32
47
  ActionController::Base.send :after_filter, :assign_test_bot_http_headers
33
48
 
34
49
  ApplicationController.instance_exec do
@@ -1,3 +1,3 @@
1
1
  module EffectiveTestBot
2
- VERSION = '0.4.1'.freeze
2
+ VERSION = '0.4.2'.freeze
3
3
  end
@@ -34,7 +34,7 @@ class ActionDispatch::IntegrationTest
34
34
  # end
35
35
 
36
36
  def after_teardown # I reset sessions here so capybara-screenshot can still make screenshots when tests fail
37
- super() and Capybara.reset_sessions!
37
+ super(); Capybara.reset_sessions!
38
38
  end
39
39
 
40
40
  end
@@ -1,9 +1,19 @@
1
1
  require 'rake/testtask'
2
2
  require 'rails/test_unit/sub_test_task'
3
3
 
4
+ # rake test:bot
5
+ # rake test:bot TEST=documents#new
6
+ # rake test:bot TEST=documents#new,documents#show
7
+ # rake test:bot TEST=documents#new path,documents#show,documents#update_valid no_unpermitted_params
8
+
4
9
  namespace :test do
5
10
  desc 'Runs Effective Test Bot'
6
11
  task :bot do
12
+ if ENV['TEST'].present?
13
+ ENV['TEST_BOT_TEST'] = ENV['TEST']
14
+ ENV['TEST'] = nil
15
+ end
16
+
7
17
  Rake::Task["test:effective_test_bot"].invoke
8
18
  end
9
19
 
@@ -18,5 +28,4 @@ namespace :test do
18
28
  load(seeds) if File.exists?(seeds)
19
29
  end
20
30
 
21
-
22
31
  end
@@ -0,0 +1,142 @@
1
+ module TestBotable
2
+ module BaseDsl
3
+ extend ActiveSupport::Concern
4
+
5
+ module ClassMethods
6
+ TEST_BOT_TEST_PREFIXES = ['crud_test', 'member_test', 'page_test', 'redirect_test', 'wizard_test']
7
+
8
+ # Parses and validates lots of options
9
+ # This is a big manual merge wherein we translate some DSL methods into one consistent Hash here
10
+ # The output is what gets sent to each test and defined as lets
11
+
12
+ # {
13
+ # user: User<1> instance
14
+ # controller_namespace: 'admin'
15
+ # controller: 'jobs'
16
+ # skips: []
17
+ # resource: Post<1> instance
18
+ # resource_class: Post
19
+ # resource_name: 'post'
20
+ # resource_attributes: {:user_id => 3} attributes, if called with an instance of ActiveRecord with non-default attributes
21
+ # current_test: posts#new (if from the class level) or NIL if from the instance level
22
+ # }
23
+ #
24
+
25
+ def normalize_test_bot_options!(options)
26
+ raise 'expected options to be a Hash' unless options.kind_of?(Hash)
27
+ raise 'expected key :user to be a User' unless options[:user].kind_of?(User)
28
+ #raise 'expected key :current_test to be a String' unless options[:current_test].kind_of?(String)
29
+
30
+ # Controller stuff
31
+ options[:controller_namespace] ||= options[:namespace]
32
+
33
+ # Skip stuff
34
+ skips = options[:skip] || options[:skips]
35
+ unless skips.blank? || skips.kind_of?(Symbol) || (skips.kind_of?(Array) && skips.all? { |s| s.kind_of?(Symbol) })
36
+ raise 'expected skips to be a Symbol or Array of Symbols'
37
+ end
38
+ options[:skips] = Array(skips)
39
+
40
+ # Resource could be an ActiveRecord Class, Instance of a class, or String
41
+ # Build a resource stuff
42
+ if options[:resource].present?
43
+ obj = options[:resource]
44
+ raise 'expected resource to be a Class or Instance or String' unless obj.kind_of?(Class) || obj.kind_of?(ActiveRecord::Base) || obj.kind_of?(String)
45
+
46
+ if obj.kind_of?(String) # Let's assume this is a controller, 'admin/jobs', or just 'jobs', or 'jobs_controller'
47
+ obj.sub!('_controller', '')
48
+
49
+ (*namespace, klass) = obj.split('/')
50
+ namespace = Array(namespace).join('/').presence
51
+
52
+ # See if I can turn it into a model
53
+ klass = klass.classify.safe_constantize
54
+
55
+ raise "failed to constantize resource from string #{obj}, unable to proceed" unless klass.present?
56
+
57
+ options[:controller_namespace] ||= namespace
58
+ options[:controller] = obj.dup
59
+
60
+ obj = klass
61
+ end
62
+
63
+ # Make sure Obj.new() works
64
+ if obj.kind_of?(Class) && (obj.new() rescue false) == false
65
+ raise "failed to initialize resource with #{obj}.new(), unable to proceed"
66
+ end
67
+
68
+ # Parse the resource and resource class
69
+ resource = obj.kind_of?(Class) ? obj.new() : obj
70
+ resource_class = obj.kind_of?(Class) ? obj : obj.class
71
+
72
+ # If obj is an ActiveRecord object with attributes, Post.new(:title => 'My Title')
73
+ # then compute any explicit attributes, so forms will be filled with those values
74
+ resource_attributes = if obj.kind_of?(ActiveRecord::Base)
75
+ empty = resource_class.new()
76
+ {}.tap { |atts| resource.attributes.each { |k, v| atts[k] = v if empty.attributes[k] != v } }
77
+ end || {}
78
+
79
+ options[:resource] = resource
80
+ options[:resource_class] = resource_class
81
+ options[:resource_name] = resource_class.name.underscore
82
+ options[:resource_attributes] = resource_attributes
83
+
84
+ options[:normalized] = true
85
+ end
86
+
87
+ options
88
+ end
89
+
90
+ # Run any test_bot tests first, in the order they're defined
91
+ # then the rest of the tests with whatever order they come in
92
+ def runnable_methods
93
+ public_instance_methods.select do |name|
94
+ name = name.to_s
95
+ TEST_BOT_TEST_PREFIXES.any? { |prefix| name.starts_with?(prefix) }
96
+ end.map(&:to_s) + super
97
+ end
98
+
99
+ protected
100
+
101
+ # You can't define multiple methods with the same name
102
+ # So we need to create a unique name, where appropriate, that still looks good in MiniTest output
103
+ def test_bot_method_name(test_family, current_test)
104
+ number_of_tests = if current_test.blank?
105
+ @num_defined_test_bot_tests ||= {}
106
+ @num_defined_test_bot_tests[test_family] = (@num_defined_test_bot_tests[test_family] || 0) + 1
107
+ end
108
+
109
+ if current_test.present?
110
+ "#{test_family}: (#{current_test})"
111
+ elsif number_of_tests > 1
112
+ "#{test_family}: (#{number_of_tests})"
113
+ else
114
+ "#{test_family}:"
115
+ end
116
+ end
117
+ end
118
+
119
+ # Instance Methods
120
+
121
+
122
+ # Using reverse_merge! in the dsl action_tests makes sure that the
123
+ # class level can assign a current_test variable
124
+ # wheras the action level ones it's not present.
125
+ def assign_test_bot_lets!(options)
126
+ lets = if options.kind_of?(Hash) && options[:normalized]
127
+ options
128
+ else
129
+ self.class.normalize_test_bot_options!(options)
130
+ end
131
+
132
+ lets.each { |k, v| self.class.let(k) { v } } # Using the minitest spec let(:foo) { 'bar' } syntax
133
+
134
+ # test_bot may leak some lets from one test to the next, if they're not overridden
135
+ # I take special care to undefine just current test so far
136
+ self.class.let(:current_test) { nil } if options[:current_test].blank?
137
+
138
+ lets
139
+ end
140
+
141
+ end
142
+ end
@@ -0,0 +1,92 @@
1
+ # This DSL gives a class level and an instance level way of calling specific test suite
2
+ #
3
+ # class PostsTest < ActionDispatch::IntegrationTest
4
+ # crud_test(Post || 'admin/posts', User.first, except: :show, skip: {create_valid: :path, update_invalid: [:path, :flash]})
5
+ #
6
+ # test 'a one-off action' do
7
+ # crud_action_test(:new, Post, User.first, skip: :title)
8
+ # end
9
+ # end
10
+
11
+ module TestBotable
12
+ module CrudDsl
13
+ extend ActiveSupport::Concern
14
+
15
+ CRUD_TESTS = [:new, :create_valid, :create_invalid, :edit, :update_valid, :update_invalid, :index, :show, :destroy]
16
+
17
+ module ClassMethods
18
+
19
+ # All this does is define a 'test_bot' method for each required action on this class
20
+ # So that MiniTest will see the test functions and run them
21
+ def crud_test(resource, user, options = {})
22
+ # This skips paramaters is different than the initializer skips, which affect just the rake task
23
+
24
+ # These are specificially for the DSL
25
+ # In the class method, this value is a Hash, in the instance method it's expecting an Array
26
+ skips = options.delete(:skip) || options.delete(:skips) || {} # So you can skip sub tests
27
+ raise 'invalid skip syntax, expecting skip: {create_invalid: [:path]}' unless skips.kind_of?(Hash)
28
+
29
+ label = options.delete(:label).presence
30
+ only = options.delete(:only)
31
+ except = options.delete(:except)
32
+
33
+ begin
34
+ normalize_test_bot_options!(options.merge!(user: user, resource: resource))
35
+ rescue => e
36
+ raise "Error: #{e.message}. Expected usage: crud_test(Post || Post.new, User.first, only: [:new, :create], skip: {create_invalid: [:path]})"
37
+ end
38
+
39
+ crud_tests_to_define(only, except).each do |test|
40
+ options_for_method = options.dup
41
+
42
+ options_for_method[:skips] = Array(skips[test]) if skips[test]
43
+ options_for_method[:current_test] = [
44
+ options[:controller_namespace].presence,
45
+ options[:resource_name].pluralize
46
+ ].compact.join('/') + '#' + test.to_s
47
+
48
+ method_name = test_bot_method_name('crud_test', label || options_for_method[:current_test])
49
+
50
+ define_method(method_name) { crud_action_test(test, resource, user, options_for_method) }
51
+ end
52
+ end
53
+
54
+ private
55
+
56
+ # Parses the incoming options[:only] and [:except]
57
+ # To only define the appropriate methods
58
+ # This guarantees the functions will be defined in the same order as CRUD_TESTS
59
+ def crud_tests_to_define(only, except)
60
+ if only
61
+ only = Array(only).flatten.compact.map { |x| x.to_sym }
62
+ only = only + [:create_valid, :create_invalid] if only.delete(:create)
63
+ only = only + [:update_valid, :update_invalid] if only.delete(:update)
64
+
65
+ CRUD_TESTS & only
66
+ elsif except
67
+ except = Array(except).flatten.compact.map { |x| x.to_sym }
68
+ except = except + [:create_valid, :create_invalid] if except.delete(:create)
69
+ except = except + [:update_valid, :update_invalid] if except.delete(:update)
70
+
71
+ CRUD_TESTS - except
72
+ else
73
+ CRUD_TESTS
74
+ end
75
+ end
76
+ end
77
+
78
+ # Instance Methods - Call me from within a test
79
+ #
80
+ # If obj is a Hash {:resource => ...} just skip over parsing options
81
+ # And assume it's already been done (by the ClassMethod crud_test)
82
+ def crud_action_test(test, resource, user = nil, options = {})
83
+ begin
84
+ assign_test_bot_lets!(options.reverse_merge!(user: user, resource: resource))
85
+ rescue => e
86
+ raise "Error: #{e.message}. Expected usage: crud_action_test(:new, Post || Post.new, User.first, options_hash)"
87
+ end
88
+
89
+ self.send("test_bot_#{test}_test")
90
+ end
91
+ end
92
+ end
@@ -0,0 +1,41 @@
1
+ # This DSL gives a class level and an instance level way of calling specific test suite
2
+ #
3
+ # class PostsTest < ActionDispatch::IntegrationTest
4
+ # member_test('admin/jobs', 'unarchive', User.first, Post.first)
5
+ #
6
+ # test 'a one-off action' do
7
+ # member_action_test('admin/jobs', 'unarchive', User.first)
8
+ # end
9
+ # end
10
+
11
+ # A member_test assumes assumes route.name.present? && route.verb.to_s.include?('GET') && route.path.required_names == ['id']
12
+ # we HAVE TO build or have available one of these resources so we can pass the ID to it and see what happens :)
13
+
14
+ module TestBotable
15
+ module MemberDsl
16
+ extend ActiveSupport::Concern
17
+
18
+ module ClassMethods
19
+
20
+ def member_test(controller, action, user, obj_to_param = nil, options = {})
21
+ options[:current_test] = "#{controller}##{action}"
22
+ method_name = test_bot_method_name('member_test', options.delete(:label) || options[:current_test])
23
+
24
+ define_method(method_name) { member_action_test(controller, action, user, obj_to_param, options) }
25
+ end
26
+
27
+ end
28
+
29
+ # Instance Methods - Call me from within a test
30
+ def member_action_test(controller, action, user, member = nil, options = {})
31
+ begin
32
+ assign_test_bot_lets!(options.reverse_merge!(resource: controller, action: action, user: user, member: member))
33
+ rescue => e
34
+ raise "Error: #{e.message}. Expected usage: member_test('admin/jobs', 'unarchive', User.first, Post.first || nil)"
35
+ end
36
+
37
+ self.send(:test_bot_member_test)
38
+ end
39
+
40
+ end
41
+ end