spec_producer 0.11.0 → 0.13.0
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 +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
|