spec_producer 0.11.0 → 0.13.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.ruby-version +1 -0
- data/CHANGELOG.md +15 -0
- data/README.md +73 -24
- data/lib/configuration.rb +41 -0
- data/lib/generators/spec_producer/install/install_generator.rb +13 -0
- data/lib/generators/spec_producer/install/templates/spec_producer +1 -0
- data/lib/spec_producer.rb +140 -47
- data/lib/spec_producer/factories_production_module.rb +17 -11
- data/lib/spec_producer/missing_files_module.rb +104 -41
- data/lib/spec_producer/missing_gems_module.rb +77 -0
- data/lib/spec_producer/producers.rb +10 -0
- data/lib/spec_producer/producers/base.rb +129 -0
- data/lib/spec_producer/producers/controllers_producer.rb +31 -0
- data/lib/spec_producer/producers/helpers_producer.rb +26 -0
- data/lib/spec_producer/producers/jobs_producer.rb +34 -0
- data/lib/spec_producer/producers/mailers_producer.rb +24 -0
- data/lib/spec_producer/producers/models_producer.rb +89 -0
- data/lib/spec_producer/producers/registry.rb +66 -0
- data/lib/spec_producer/producers/routes_producer.rb +44 -0
- data/lib/spec_producer/producers/serializers_producer.rb +46 -0
- data/lib/spec_producer/producers/views_producer.rb +25 -0
- data/lib/spec_producer/railtie.rb +13 -0
- data/lib/spec_producer/rspec_builders.rb +1 -0
- data/lib/spec_producer/rspec_builders/base.rb +148 -0
- data/lib/spec_producer/rspec_builders/builder.rb +220 -0
- data/lib/spec_producer/rspec_builders/matchers.rb +256 -0
- data/lib/spec_producer/spec_production_module.rb +80 -331
- data/lib/spec_producer/spec_runner.rb +14 -0
- data/lib/spec_producer/utils.rb +1 -0
- data/lib/spec_producer/utils/file_utils.rb +69 -0
- data/lib/spec_producer/version.rb +1 -1
- data/lib/tasks/spec_producer_tasks.rake +103 -0
- data/spec_producer.gemspec +6 -0
- metadata +111 -2
@@ -0,0 +1,31 @@
|
|
1
|
+
module SpecProducer
|
2
|
+
module Producers
|
3
|
+
class ControllersProducer
|
4
|
+
prepend Base
|
5
|
+
|
6
|
+
def resources
|
7
|
+
(ApplicationController.descendants << ApplicationController).
|
8
|
+
reverse.
|
9
|
+
map { |desc| Resource.new(desc, desc.name, 'controller') }
|
10
|
+
end
|
11
|
+
|
12
|
+
def call(resource)
|
13
|
+
resource.obj.action_methods.each do |method_name|
|
14
|
+
builder.pending "##{method_name}"
|
15
|
+
end
|
16
|
+
|
17
|
+
if resource.obj.action_methods.size == 0
|
18
|
+
builder.pending 'controller tests'
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
#######
|
23
|
+
private
|
24
|
+
#######
|
25
|
+
|
26
|
+
def require_helper_string
|
27
|
+
@require_helper_string ||= Utils::FileUtils.collect_helper_strings
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module SpecProducer
|
2
|
+
module Producers
|
3
|
+
class HelpersProducer
|
4
|
+
prepend Base
|
5
|
+
|
6
|
+
def resources
|
7
|
+
ActionController::Base.modules_for_helpers(ActionController::Base.all_helpers_from_path 'app/helpers').
|
8
|
+
map { |desc| Resource.new(desc, desc.name, 'helper') }
|
9
|
+
end
|
10
|
+
|
11
|
+
def call(resource)
|
12
|
+
resource.obj.instance_methods.each do |method_name|
|
13
|
+
builder.pending method_name
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
#######
|
18
|
+
private
|
19
|
+
#######
|
20
|
+
|
21
|
+
def require_helper_string
|
22
|
+
@require_helper_string ||= Utils::FileUtils.collect_helper_strings
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module SpecProducer
|
2
|
+
module Producers
|
3
|
+
class JobsProducer
|
4
|
+
prepend Base
|
5
|
+
|
6
|
+
def resources
|
7
|
+
Dir["app/jobs/**/*.rb"].
|
8
|
+
map { |file| Resource.new(file, File.basename(file, ".rb").camelcase, 'job') }
|
9
|
+
end
|
10
|
+
|
11
|
+
def call(resource)
|
12
|
+
builder.subject('described_class.perform_later(123)')
|
13
|
+
|
14
|
+
builder.context('queues the job') do
|
15
|
+
builder.it("expect(subject).to change(ActiveJob::Base.queue_adapter.enqueued_jobs, :size).by(1)")
|
16
|
+
end
|
17
|
+
|
18
|
+
builder.context('is in proper queue') do
|
19
|
+
builder.it("expect(#{resource.name}.new.queue_name).to eq('default')")
|
20
|
+
end
|
21
|
+
|
22
|
+
builder.pending 'executes perform'
|
23
|
+
end
|
24
|
+
|
25
|
+
#######
|
26
|
+
private
|
27
|
+
#######
|
28
|
+
|
29
|
+
def require_helper_string
|
30
|
+
@require_helper_string ||= Utils::FileUtils.collect_helper_strings
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module SpecProducer
|
2
|
+
module Producers
|
3
|
+
class MailersProducer
|
4
|
+
prepend Base
|
5
|
+
|
6
|
+
def resources
|
7
|
+
Dir["app/mailers/**/*.rb"].
|
8
|
+
map { |file| Resource.new(file, File.basename(file, ".rb").camelcase, 'mailer') }
|
9
|
+
end
|
10
|
+
|
11
|
+
def call(resource)
|
12
|
+
builder.pending 'mailer tests'
|
13
|
+
end
|
14
|
+
|
15
|
+
#######
|
16
|
+
private
|
17
|
+
#######
|
18
|
+
|
19
|
+
def require_helper_string
|
20
|
+
@require_helper_string ||= Utils::FileUtils.collect_helper_strings
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,89 @@
|
|
1
|
+
require 'active_record'
|
2
|
+
|
3
|
+
module SpecProducer
|
4
|
+
module Producers
|
5
|
+
class ModelsProducer
|
6
|
+
prepend Base
|
7
|
+
|
8
|
+
# TODO Rethink this
|
9
|
+
CLASSES_TO_IGNORE = ['ActiveRecord::SchemaMigration',
|
10
|
+
'ApplicationRecord',
|
11
|
+
'Delayed::Backend::ActiveRecord::Job',
|
12
|
+
'ActiveRecord::InternalMetadata']
|
13
|
+
|
14
|
+
def resources
|
15
|
+
ActiveRecord::Base.descendants.reject do |descendant|
|
16
|
+
should_ignore?(descendant)
|
17
|
+
end.map { |desc| Resource.new(desc, desc.name, 'model') }
|
18
|
+
end
|
19
|
+
|
20
|
+
def call(resource)
|
21
|
+
builder.context('#respond_to?') do
|
22
|
+
respond_to_specs(resource.obj.attribute_names) { |attr| builder.responds_to(attr) }
|
23
|
+
read_only_attr_specs(resource.obj.readonly_attributes) { |attr| builder.responds_to(attr) }
|
24
|
+
end
|
25
|
+
|
26
|
+
if resource.obj.column_names.present?
|
27
|
+
builder.context('DB Columns') do
|
28
|
+
resource.obj.column_names.each do |column_name|
|
29
|
+
builder.it { has_db_column(column_name) }
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
if has_validators?(resource.obj)
|
35
|
+
builder.context 'validations' do
|
36
|
+
resource.obj.validators.each do |validator|
|
37
|
+
validator.attributes.each do |attribute|
|
38
|
+
builder.validates_with validator.kind, attribute
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
builder.context 'factories' do
|
45
|
+
builder.it { has_valid_factory(resource.obj.name.underscore) }
|
46
|
+
end
|
47
|
+
|
48
|
+
if resource.obj.reflections.keys.present?
|
49
|
+
builder.context 'Associations' do
|
50
|
+
resource.obj.reflections.each_pair do |_, reflection|
|
51
|
+
builder.it { has_association(reflection) }
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
#######
|
58
|
+
private
|
59
|
+
#######
|
60
|
+
|
61
|
+
def respond_to_specs(attrs = [])
|
62
|
+
return enum_for(:attrs) unless block_given?
|
63
|
+
attrs.each do |attr|
|
64
|
+
yield(attr)
|
65
|
+
yield(":#{attr}=")
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def read_only_attr_specs(attrs = [])
|
70
|
+
return enum_for(:attrs) unless block_given?
|
71
|
+
attrs.each do |attr|
|
72
|
+
yield(attr)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def has_validators?(desc)
|
77
|
+
desc.validators.reject { |validator| validator.kind == :associated }.present?
|
78
|
+
end
|
79
|
+
|
80
|
+
def should_ignore?(descendant)
|
81
|
+
CLASSES_TO_IGNORE.include?(descendant.to_s)
|
82
|
+
end
|
83
|
+
|
84
|
+
def require_helper_string
|
85
|
+
@require_helper_string ||= Utils::FileUtils.collect_helper_strings
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
module SpecProducer
|
2
|
+
module Producers
|
3
|
+
class Registry
|
4
|
+
include Enumerable
|
5
|
+
attr_reader :registrations
|
6
|
+
|
7
|
+
delegate :any?, :empty?, :size, :length, to: :registrations
|
8
|
+
|
9
|
+
def initialize
|
10
|
+
@registrations = Set.new
|
11
|
+
end
|
12
|
+
|
13
|
+
def register(spec_type, klass)
|
14
|
+
registrations << Registration.new(spec_type, klass)
|
15
|
+
end
|
16
|
+
|
17
|
+
def each
|
18
|
+
return enum_for(:registrations) { registrations.size } unless block_given?
|
19
|
+
registrations.each { |registration| yield(registration) }
|
20
|
+
end
|
21
|
+
|
22
|
+
def types
|
23
|
+
map(&:name)
|
24
|
+
end
|
25
|
+
alias_method :registerd_types, :types
|
26
|
+
|
27
|
+
def registered?(symbol)
|
28
|
+
!!find_registration(symbol)
|
29
|
+
end
|
30
|
+
|
31
|
+
def lookup!(symbol)
|
32
|
+
registration = find_registration(symbol)
|
33
|
+
|
34
|
+
if registration
|
35
|
+
registration.call(symbol)
|
36
|
+
else
|
37
|
+
raise ArgumentError, "Unknown spec type #{symbol.inspect}"
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
private
|
42
|
+
def find_registration(symbol)
|
43
|
+
registrations.find { |r| r.matches?(symbol) }
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
class Registration
|
48
|
+
attr_reader :name, :klass
|
49
|
+
|
50
|
+
def initialize(name, klass)
|
51
|
+
@name = name
|
52
|
+
@klass = klass
|
53
|
+
end
|
54
|
+
|
55
|
+
def matches?(type_name)
|
56
|
+
type_name == name
|
57
|
+
end
|
58
|
+
|
59
|
+
def call(args)
|
60
|
+
klass.call(args)
|
61
|
+
end
|
62
|
+
|
63
|
+
protected
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
module SpecProducer
|
2
|
+
module Producers
|
3
|
+
class RoutesProducer
|
4
|
+
prepend Base
|
5
|
+
|
6
|
+
def resources
|
7
|
+
Rails.application.routes.routes.
|
8
|
+
select { |route| route.defaults[:controller].present? && !/^rails/.match(route.defaults[:controller]) }.
|
9
|
+
map { |route| { :path => route.path.spec.to_s.gsub(/\(\.:format\)/, ""),
|
10
|
+
:verb => %W{ GET POST PUT PATCH DELETE }.grep(route.verb).first.downcase.to_sym,
|
11
|
+
:controller => route.defaults[:controller],
|
12
|
+
:action => route.defaults[:action] } }.
|
13
|
+
group_by { |route| route[:controller] }.
|
14
|
+
map { |route_group| Resource.new(route_group, route_group[0], 'routing') }
|
15
|
+
end
|
16
|
+
|
17
|
+
def call(resource)
|
18
|
+
resource.obj[1].each do |route|
|
19
|
+
builder.context("#{route[:verb].upcase} #{route[:path].gsub(/\(.*?\)/, '')} should route to '#{route[:controller]}##{route[:action]}'") do
|
20
|
+
|
21
|
+
route_specifics = { :controller => route[:controller],
|
22
|
+
:action => route[:action] }
|
23
|
+
|
24
|
+
route[:path].gsub(/\(.*?\)/, '').scan(/:[a-zA-Z_]+/).flatten.each do |parameter|
|
25
|
+
route_specifics[parameter.gsub(':','')] = "#{parameter.gsub(':','').upcase}"
|
26
|
+
end
|
27
|
+
|
28
|
+
route_requested = route[:path].gsub(/\(.*?\)/, '').gsub(/:[a-zA-Z_]+/){ |param| param.gsub(':','').upcase }
|
29
|
+
|
30
|
+
builder.it("expect(:#{route[:verb]} => '#{route_requested}').to route_to(#{route_specifics.map { |k,v| ":#{k} => '#{v}'"}.join(', ')})")
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
#######
|
36
|
+
private
|
37
|
+
#######
|
38
|
+
|
39
|
+
def require_helper_string
|
40
|
+
@require_helper_string ||= Utils::FileUtils.collect_helper_strings
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
require 'active_model_serializers'
|
2
|
+
|
3
|
+
module SpecProducer
|
4
|
+
module Producers
|
5
|
+
class SerializersProducer
|
6
|
+
prepend Base
|
7
|
+
|
8
|
+
# TODO Rethink this
|
9
|
+
CLASSES_TO_IGNORE = [ 'ActiveModel::Serializer::ErrorSerializer' ]
|
10
|
+
|
11
|
+
def resources
|
12
|
+
ActiveModel::Serializer.descendants.reject do |descendant|
|
13
|
+
should_ignore?(descendant)
|
14
|
+
end.map { |desc| Resource.new(desc, desc.name, 'serializer') }
|
15
|
+
end
|
16
|
+
|
17
|
+
def call(resource)
|
18
|
+
builder.context('should include the expected attribute keys') do
|
19
|
+
builder.subject(builder.initialize_serializer_for_object resource.obj)
|
20
|
+
|
21
|
+
builder.it("expect(subject.attributes.keys).to contain_exactly(#{resource.obj._attributes.map { |x| ":#{x.to_s}" }.join(', ')})")
|
22
|
+
end
|
23
|
+
|
24
|
+
builder.context('to_json should have the proper values') do
|
25
|
+
builder.subject(builder.json_parse_for_serialized_object resource.obj)
|
26
|
+
|
27
|
+
resource.obj._attributes.each do |attribute|
|
28
|
+
builder.it("expect(subject['#{attribute}']).to eq('')")
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
#######
|
34
|
+
private
|
35
|
+
#######
|
36
|
+
|
37
|
+
def should_ignore?(descendant)
|
38
|
+
CLASSES_TO_IGNORE.include?(descendant.to_s)
|
39
|
+
end
|
40
|
+
|
41
|
+
def require_helper_string
|
42
|
+
@require_helper_string ||= Utils::FileUtils.collect_helper_strings
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module SpecProducer
|
2
|
+
module Producers
|
3
|
+
class ViewsProducer
|
4
|
+
prepend Base
|
5
|
+
|
6
|
+
def resources
|
7
|
+
Dir["app/views/**/*.erb"].
|
8
|
+
map { |file| Resource.new(file, file.gsub('app/views/', ''), 'view') }
|
9
|
+
end
|
10
|
+
|
11
|
+
def call(resource)
|
12
|
+
builder.before_render
|
13
|
+
builder.pending 'view content test'
|
14
|
+
end
|
15
|
+
|
16
|
+
#######
|
17
|
+
private
|
18
|
+
#######
|
19
|
+
|
20
|
+
def require_helper_string
|
21
|
+
@require_helper_string ||= Utils::FileUtils.collect_helper_strings
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
require 'spec_producer/rspec_builders/base'
|
@@ -0,0 +1,148 @@
|
|
1
|
+
require_relative 'matchers'
|
2
|
+
require_relative 'builder'
|
3
|
+
module SpecProducer
|
4
|
+
module RspecBuilders
|
5
|
+
# == Spec \Producer \Rspec \Builders
|
6
|
+
#
|
7
|
+
# Provides all the the methods required to produce an RSpec string in
|
8
|
+
# order to create the corresponding spec files. The base class
|
9
|
+
# includes some methods to create a string like <tt>add</tt> method.
|
10
|
+
#
|
11
|
+
# An example on how to build a spec would be:
|
12
|
+
# resource = SomeResource.new ...
|
13
|
+
#
|
14
|
+
# def for_each_column
|
15
|
+
# resource.column_names.each { |c| yield(c) }
|
16
|
+
# end
|
17
|
+
# def for_each_attr
|
18
|
+
# resource.attributes.each { |a| ield(a) }
|
19
|
+
# end
|
20
|
+
#
|
21
|
+
# # Build the spec text
|
22
|
+
# text = RspecBuilder::Base.build do |b|
|
23
|
+
# b.write('require \'spec_helper\'')
|
24
|
+
# b.spec(resource.class.name, 'models') do
|
25
|
+
# b.subject { build(#{resource.class.name.underscore}) }
|
26
|
+
# b.context '#respond_to?' do
|
27
|
+
# for_each_attr do |attr|
|
28
|
+
# b.responds_to(attr)
|
29
|
+
# end
|
30
|
+
# end
|
31
|
+
# b.context 'DB columns' do
|
32
|
+
# for_each_column { |c|
|
33
|
+
# b.responds_to(c)
|
34
|
+
# }
|
35
|
+
# end
|
36
|
+
# b.context 'some other context' do
|
37
|
+
# b.subject { 'foo' }
|
38
|
+
# b.it { expect('subject').to eq 'foo' }
|
39
|
+
# context 'a nested context' do
|
40
|
+
# b.subject { 'create(:user).profile' }
|
41
|
+
# b.it { should_be(:present) }
|
42
|
+
# end
|
43
|
+
# end
|
44
|
+
# end
|
45
|
+
# end
|
46
|
+
#
|
47
|
+
# # Write the generated text to spec_file
|
48
|
+
# Utils::FileUtils.try_to_create_spec_file('models', resource.class.name.underscore, text)
|
49
|
+
#
|
50
|
+
# puts text
|
51
|
+
#
|
52
|
+
# => require 'spec_helper'
|
53
|
+
# describe SomeResource, type: :model do
|
54
|
+
# subject { builder(:resource) }
|
55
|
+
# context '#responds_to?' do
|
56
|
+
# it { is_expected.to respond_to(:attr1) }
|
57
|
+
# it { is_expected.to respond_to(:attr2) }
|
58
|
+
# end
|
59
|
+
#
|
60
|
+
# context 'DB columns' do
|
61
|
+
# it { is_expected.to have_db_column(:email) }
|
62
|
+
# it { is_expected.to respond_to(:name) }
|
63
|
+
# end
|
64
|
+
#
|
65
|
+
# context 'some other context' do
|
66
|
+
# subject { 'foo' }
|
67
|
+
# it { expect(subject).to eq 'foo' }
|
68
|
+
#
|
69
|
+
# context 'a nested context' do
|
70
|
+
# subject { create(:user).profile }
|
71
|
+
# it { expect(subject).to be_present }
|
72
|
+
# end
|
73
|
+
# end
|
74
|
+
# end
|
75
|
+
#
|
76
|
+
# Note that intentation is automatially done based on the current block
|
77
|
+
# we are.
|
78
|
+
class Base
|
79
|
+
include Matchers
|
80
|
+
include Builder
|
81
|
+
|
82
|
+
attr_reader :_text
|
83
|
+
attr_reader :intend
|
84
|
+
private :intend
|
85
|
+
|
86
|
+
def initialize(t = "")
|
87
|
+
@_text = t.to_s
|
88
|
+
@intend = 0
|
89
|
+
|
90
|
+
yield self if block_given?
|
91
|
+
end
|
92
|
+
|
93
|
+
# Adds a string to buffer. by default auto intent is handled here
|
94
|
+
# In order to bypas it we pas false as the current argument. For
|
95
|
+
# example when we add a new line we don't want to add a n times tabs
|
96
|
+
#
|
97
|
+
def add(text, add_intent = true)
|
98
|
+
add_tabs if add_intent == true
|
99
|
+
|
100
|
+
_text << text
|
101
|
+
self
|
102
|
+
end
|
103
|
+
alias_method :<<, :add
|
104
|
+
alias_method :append, :add
|
105
|
+
alias_method :write, :add
|
106
|
+
|
107
|
+
# Flushes the current buffer to an empty String. Also reset
|
108
|
+
# <tt>intend</tt> count to zero.
|
109
|
+
#
|
110
|
+
def flush!
|
111
|
+
@_text = ""
|
112
|
+
@intend = 0
|
113
|
+
end
|
114
|
+
|
115
|
+
# Adds <tt>@_intend</tt> tabs to the buffer.
|
116
|
+
#
|
117
|
+
def add_tabs
|
118
|
+
intend.times { _text << ' ' }
|
119
|
+
end
|
120
|
+
|
121
|
+
# Adds a new line to the buffer
|
122
|
+
#
|
123
|
+
def new_line
|
124
|
+
add "\n", false
|
125
|
+
end
|
126
|
+
|
127
|
+
def to_s
|
128
|
+
_text
|
129
|
+
end
|
130
|
+
|
131
|
+
def inspect
|
132
|
+
to_s
|
133
|
+
end
|
134
|
+
|
135
|
+
private
|
136
|
+
# Increases and decreases the intentation counter. We increase the counter
|
137
|
+
# when we are on nested contexts like nested context block.
|
138
|
+
def increase_intent
|
139
|
+
@intend += 1
|
140
|
+
end
|
141
|
+
|
142
|
+
def decrease_intent
|
143
|
+
@intend -= 1
|
144
|
+
end
|
145
|
+
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|