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.
Files changed (35) hide show
  1. checksums.yaml +4 -4
  2. data/.ruby-version +1 -0
  3. data/CHANGELOG.md +15 -0
  4. data/README.md +73 -24
  5. data/lib/configuration.rb +41 -0
  6. data/lib/generators/spec_producer/install/install_generator.rb +13 -0
  7. data/lib/generators/spec_producer/install/templates/spec_producer +1 -0
  8. data/lib/spec_producer.rb +140 -47
  9. data/lib/spec_producer/factories_production_module.rb +17 -11
  10. data/lib/spec_producer/missing_files_module.rb +104 -41
  11. data/lib/spec_producer/missing_gems_module.rb +77 -0
  12. data/lib/spec_producer/producers.rb +10 -0
  13. data/lib/spec_producer/producers/base.rb +129 -0
  14. data/lib/spec_producer/producers/controllers_producer.rb +31 -0
  15. data/lib/spec_producer/producers/helpers_producer.rb +26 -0
  16. data/lib/spec_producer/producers/jobs_producer.rb +34 -0
  17. data/lib/spec_producer/producers/mailers_producer.rb +24 -0
  18. data/lib/spec_producer/producers/models_producer.rb +89 -0
  19. data/lib/spec_producer/producers/registry.rb +66 -0
  20. data/lib/spec_producer/producers/routes_producer.rb +44 -0
  21. data/lib/spec_producer/producers/serializers_producer.rb +46 -0
  22. data/lib/spec_producer/producers/views_producer.rb +25 -0
  23. data/lib/spec_producer/railtie.rb +13 -0
  24. data/lib/spec_producer/rspec_builders.rb +1 -0
  25. data/lib/spec_producer/rspec_builders/base.rb +148 -0
  26. data/lib/spec_producer/rspec_builders/builder.rb +220 -0
  27. data/lib/spec_producer/rspec_builders/matchers.rb +256 -0
  28. data/lib/spec_producer/spec_production_module.rb +80 -331
  29. data/lib/spec_producer/spec_runner.rb +14 -0
  30. data/lib/spec_producer/utils.rb +1 -0
  31. data/lib/spec_producer/utils/file_utils.rb +69 -0
  32. data/lib/spec_producer/version.rb +1 -1
  33. data/lib/tasks/spec_producer_tasks.rake +103 -0
  34. data/spec_producer.gemspec +6 -0
  35. 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,13 @@
1
+ require 'rails'
2
+
3
+ module SpecProducer
4
+ class Railtie < ::Rails::Railtie
5
+ rake_tasks do
6
+ load 'tasks/spec_producer_tasks.rake'
7
+ end
8
+
9
+ initializer 'spec_producer.initialization' do
10
+ Rails.application.eager_load!
11
+ end
12
+ end
13
+ 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