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.
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