softwear-lib 3.1.5 → 3.3.5

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 0525b8c27ab7c6762367aa3ea551f1754ca70796
4
- data.tar.gz: fd7c8d8c48bc75f5061ae99cc8ceadfff1b3bf03
3
+ metadata.gz: 061c8fef433bb6d6ba33834ed5e769907dd54722
4
+ data.tar.gz: ff88cfad72f88e87eca1360c9cba5ae0df211e9a
5
5
  SHA512:
6
- metadata.gz: f3210964ea6c7a6d91aca1cef1ef18d80746bd25e4ea853edc02595ad6eb6f402e70312efcad6e170765eaf33aed316cc3ed0e27bdc96898ca10e118bf2a4ce5
7
- data.tar.gz: 6624741f606adad42442fb4ca5c1f6f750d7c7675c65be2868c1047ba99db5f94aee3ef7c48b01b5f283307a995f783fbe64043d5c4d89154ff379b85863f874
6
+ metadata.gz: 644e51672e6d4517e81f28bb1bfdf666b1fd6abcdf32b95378a1bf07dbda5030fafd1a5a3bb64553cd4d5da781d2039cc4d2aefb229c738d46f58d428a7ba4f5
7
+ data.tar.gz: 941059409053e3f121070319f85c7b5f8c14d6b8d04cde00cd17a94ad6172a69c0566b98c5446489890ac5295d7f0598b38ba80fcc11585c586e4fb9beb15e65
data/README.md CHANGED
@@ -1,7 +1,5 @@
1
1
  # Softwear::Lib
2
2
 
3
- TODO: Write a gem description
4
-
5
3
  ## Installation
6
4
 
7
5
  Add this line to your application's Gemfile:
@@ -12,17 +10,29 @@ gem 'softwear-lib'
12
10
 
13
11
  And then execute:
14
12
 
15
- $ bundle
13
+ ```bash
14
+ $ bundle install
15
+ ```
16
16
 
17
- Or install it yourself as:
17
+ # Usage
18
18
 
19
- $ gem install softwear-lib
19
+ ## Database capture
20
+ This feature will capture a generous subset of the production (or development)
21
+ database into a spec. This serves 2 main purposes:
20
22
 
21
- ## Usage
23
+ 1. to quickly convert hand-tested data into a spec without writing complex factories, and
24
+ 2. to isolate and begin testing a faulty set of live data before fully understanding the problem
22
25
 
23
- To use apps locally without auth_server:
24
- <br />
25
- <br />
26
+ To run:
27
+ ```bash
28
+ bundle exec softwear capture
29
+ ```
30
+ Then, follow the prompts. You will be asked for model name, record ID,
31
+ which spec file to append, and a few optional identifiers for the
32
+ generated spec.
33
+
34
+
35
+ ## Local authentication
26
36
  <code>$ AUTH_SERVER=false bundle exec rails s</code>
27
37
  <br />
28
38
  <br />
@@ -31,11 +41,11 @@ Uses data from local users.yml.
31
41
 
32
42
  Expected format for users.yml:
33
43
 
34
- <pre>
35
- user's email:
36
- first_name: user's first name
37
- last_name: user's last name
38
- profile_picture: local file location
44
+ ```yaml
45
+ "user's email":
46
+ first_name: "user's first name"
47
+ last_name: "user's last name"
48
+ profile_picture: local/file/location
39
49
  roles:
40
50
  - role1
41
51
  - role2
@@ -44,34 +54,25 @@ user's email:
44
54
  - right1
45
55
  - right2
46
56
  - rightn
47
- </pre>
57
+ ```
48
58
 
49
- To include error handling in your app, add:
50
- <br />
51
- <pre>
52
- Include Softwear::ErrorCatcher
59
+ ## Error handling
60
+ ```ruby
61
+ # In ApplicationController
62
+ include Softwear::ErrorCatcher
53
63
  helper Softwear::Auth::EmailsHelper
54
- </pre>
64
+ ```
55
65
  to your application controller.
56
- <br />
57
- <br />
66
+
67
+
58
68
  For error styling and animation add:
59
- <br />
60
- <pre>
69
+
70
+ ```css
61
71
  *= require animate/animate
62
72
 
63
73
  .box-info-error{
64
74
  background: "your desired color here";
65
75
  }
66
- </pre>
76
+ ```
67
77
  to your application.css file.
68
78
 
69
-
70
-
71
- ## Contributing
72
-
73
- 1. Fork it ( https://github.com/[my-github-username]/softwear-lib/fork )
74
- 2. Create your feature branch (`git checkout -b my-new-feature`)
75
- 3. Commit your changes (`git commit -am 'Add some feature'`)
76
- 4. Push to the branch (`git push origin my-new-feature`)
77
- 5. Create a new Pull Request
@@ -3,7 +3,12 @@ module Softwear
3
3
  skip_before_action :authenticate_user! rescue nil
4
4
  before_action :assign_current_user, only: :email_report
5
5
 
6
+ controller = self
6
7
  helper Softwear::Engine.helpers do
8
+ define_method :current_user do
9
+ controller.instance_variable_get(:@current_user)
10
+ end
11
+
7
12
  def method_missing(name, *args, &block)
8
13
  if main_app.respond_to?(name)
9
14
  main_app.send(name, *args, &block)
@@ -0,0 +1,22 @@
1
+ module Softwear
2
+ class SpecDumpController < ApplicationController
3
+ include Library::SpecDump
4
+
5
+ helper Softwear::Engine.helpers do
6
+ def method_missing(name, *args, &block)
7
+ if main_app.respond_to?(name)
8
+ main_app.send(name, *args, &block)
9
+ else
10
+ super
11
+ end
12
+ end
13
+ end
14
+
15
+ def dump
16
+ type = params[:type].classify
17
+ id = params[:id].to_i
18
+
19
+ render json: spec_dump(type.constantize.find(id))
20
+ end
21
+ end
22
+ end
@@ -64,12 +64,16 @@ class ErrorReportMailer < ActionMailer::Base
64
64
  private
65
65
 
66
66
  def app_name
67
+ name = Figaro.env.hub_app_name
68
+ return name if name.present?
69
+
67
70
  case Rails.application.class.parent.to_s
68
- when "CrmSoftwearcrmCom" then return "Softwear CRM"
69
- when "RetailPublishing" then return "Softwear Mockbot"
70
- when "SoftwearProduction" then return "Softwear Production"
71
- when "SoftwearHub" then return "Softwear Hub"
72
- when "AnnarborteesFba" then return "Softwear Fba"
71
+ when "CrmSoftwearcrmCom" then return "SoftWEAR CRM"
72
+ when "RetailPublishing" then return "SoftWEAR Mockbot"
73
+ when "SoftwearProduction" then return "SoftWEAR Production"
74
+ when "SoftwearHub" then return "SoftWEAR Hub"
75
+ when "AnnarborteesFba" then return "SoftWEAR Fba"
76
+ when "SoftwearRetail" then return "SoftWEAR Retail"
73
77
  when "AnnarborteesWww" then return "Ann Arbor Tees WWW"
74
78
  else
75
79
  return "Unknown App"
data/bin/softwear CHANGED
@@ -1,51 +1,11 @@
1
1
  #!/usr/bin/env ruby
2
-
3
2
  require 'softwear/lib'
4
3
 
5
- if ARGV.length > 0 && ARGV[0] == 'update'
6
- old_gemfile = File.open('Gemfile').read.gsub(/\r\n?/, "\n")
7
- gemfile = []
8
-
9
- handler = nil
10
- append_line = nil
11
-
12
- injected_gems = false
13
-
14
- ignore_line = lambda do |line|
15
- if line.include? Softwear::Lib::GEMFILE_CLOSER
16
- gemfile << line
17
- handler = append_line
18
- end
19
- end
20
-
21
- append_line = lambda do |line|
22
- gemfile << line
23
- if line.include? Softwear::Lib::GEMFILE_OPENER
24
- puts "Updating common gems"
25
- gemfile << Softwear::Lib::COMMON_GEMS
26
- injected_gems = true
27
- handler = ignore_line
28
- end
29
- end
30
-
31
- handler = append_line
32
- old_gemfile.each_line do |line|
33
- handler.call(line)
34
- end
35
-
36
- unless injected_gems
37
- puts "Adding common gems - check for duplicates!"
38
- gemfile << "\n" + Softwear::Lib::GEMFILE_OPENER + "\n"
39
- gemfile << Softwear::Lib::COMMON_GEMS
40
- gemfile << Softwear::Lib::GEMFILE_CLOSER + "\n"
41
- end
4
+ if ARGV.length > 0
5
+ dir = File.dirname(__FILE__)
6
+ file = "#{dir}/softwear-#{ARGV[0]}"
42
7
 
43
- File.open('Gemfile', 'w') do |file|
44
- gemfile.each do |line|
45
- file.write(line)
46
- end
47
- end
48
- puts "Done!"
8
+ exec "bundle exec #{file} #{ARGV[1..-1].map(&:inspect).join(' ')}"
49
9
  else
50
10
  puts "Run `softwear update` to update your gemfile's common dependencies"
51
11
  end
@@ -0,0 +1,225 @@
1
+ #!/usr/bin/env ruby
2
+ require 'softwear/library/spec_dump'
3
+ require 'tty/prompt'
4
+ require 'tty/spinner'
5
+ require 'pastel'
6
+ require 'liquid'
7
+ include Softwear::Library::SpecDump
8
+
9
+ LIB_DIR = File.expand_path File.join(File.dirname(__FILE__), '..')
10
+ PROMPT = TTY::Prompt.new
11
+ PASTEL = Pastel.new
12
+
13
+ class Capture
14
+ attr_reader :model_name
15
+ attr_reader :model
16
+ attr_reader :ids
17
+ attr_reader :records
18
+ attr_reader :data
19
+ attr_reader :capture_name
20
+ attr_reader :capture_path
21
+ attr_reader :capture_fullpath
22
+ attr_reader :spec_file
23
+ attr_reader :spec_desc
24
+ attr_reader :spec_fullpath
25
+ attr_reader :whitelist
26
+ attr_accessor :step
27
+ attr_reader :complete
28
+ @@steps = []
29
+
30
+ def self.step(method_name)
31
+ @@steps << method_name
32
+ end
33
+
34
+ def self.steps
35
+ @@steps
36
+ end
37
+
38
+ def human_step
39
+ step.to_s.humanize
40
+ end
41
+
42
+ def step_index
43
+ self.class.steps.index(step)
44
+ end
45
+
46
+ def prompt
47
+ PROMPT
48
+ end
49
+
50
+ def initialize
51
+ @step = @@steps.first
52
+ @complete = false
53
+ end
54
+
55
+ def increment_step
56
+ @step = @@steps[@@steps.index(@step) + 1]
57
+ if @step == nil
58
+ @complete = true
59
+ end
60
+ @step
61
+ end
62
+
63
+ def decrement_step
64
+ @step = @@steps[@@steps.index(@step) - 1]
65
+ @step = @@steps.first if @step.nil?
66
+ @step
67
+ end
68
+
69
+ def done?
70
+ @step.nil?
71
+ end
72
+
73
+ def complete?
74
+ complete
75
+ end
76
+
77
+ def execute!
78
+ send(@step)
79
+ end
80
+
81
+ def ignored_regexps
82
+ return [] if @ignored.try(:strip).blank?
83
+ @ignored.split(',').map { |x| Regexp.new(x.strip) }
84
+ end
85
+
86
+
87
+ step def initialize_rails
88
+ ENV['RAILS_ENV'] ||= prompt.ask "RAILS_ENV?", default: "development"
89
+ puts "Okay one sec (loading rails environment)"
90
+ Kernel.require "#{Dir.pwd}/config/environment"
91
+ end
92
+
93
+
94
+ step def find_records
95
+ @model_name = (ENV['MODEL'] || prompt.ask("Model to pull?", default: @model_name)).classify
96
+ @model = model_name.constantize
97
+ @ids = prompt.ask("#{model_name} ID?", default: @ids.try(:join, ',')).split(',').map(&:strip)
98
+
99
+ @records = model.where(id: @ids)
100
+ end
101
+
102
+
103
+ step def ignore_models
104
+ @ignored = prompt.ask "Enter comma-separated regexps of associations to ignore"
105
+ whitelist_file = prompt.ask "Enter whitelist JSON file"
106
+ @whitelist = JSON.parse(IO.read(whitelist_file)) if whitelist_file.present?
107
+ end
108
+
109
+
110
+ step def capture_data
111
+ spinner = TTY::Spinner.new("[:spinner] :title", format: TTY::Formats::FORMATS.keys.sample)
112
+
113
+ @data = spec_dump(records, ignored_regexps, whitelist) do |x|
114
+ spinner.update title: "Loaded #{x.history.join(' -> ')}"
115
+ spinner.spin
116
+ end
117
+ puts "\nCapture complete!\n"
118
+ end
119
+
120
+
121
+ step def save_capture
122
+ FileUtils.mkdir_p "#{Dir.pwd}/spec/fixtures/captures"
123
+
124
+ @capture_name = prompt.ask "Enter a name for the capture",
125
+ default: @capture_name || "#{model_name.underscore}-#{ids.join('-')}_#{DateTime.now.strftime("%Y-%m-%d")}"
126
+
127
+ @capture_path = "spec/fixtures/captures/#{capture_name.underscore.dasherize}.json"
128
+ @capture_fullpath = "#{Dir.pwd}/#{capture_path}"
129
+
130
+ IO.write capture_fullpath, JSON.pretty_generate(data)
131
+ puts "Wrote capture to #{capture_fullpath}"
132
+ end
133
+
134
+
135
+ step def generate_spec
136
+ unless prompt.yes?("Generate spec?")
137
+ puts <<-END
138
+ let(:capture) { JSON.parse(IO.read(Rails.root.join(#{capture_path.inspect}))) }
139
+
140
+ before :each do
141
+ Softwear::Library::SpecDump.expand_spec_dump(capture)
142
+ end
143
+ END
144
+ return
145
+ end
146
+
147
+ @spec_file = prompt.ask "Which spec file should this be added to?",
148
+ default: @spec_file || "spec/features/#{model_name.underscore.pluralize}_spec.rb"
149
+ @spec_desc = prompt.ask "Enter a name for the spec.",
150
+ default: @spec_desc || "#{model_name} problems #{DateTime.now.strftime("%Y-%m-%d")}"
151
+
152
+ @spec_fullpath = "#{Dir.pwd}/#{spec_file}"
153
+
154
+ template = Liquid::Template.parse(
155
+ IO.read("#{LIB_DIR}/lib/softwear/templates/captured_spec.rb.liquid"),
156
+ error_mode: :strict
157
+ )
158
+ new_spec = template.render!({
159
+ feature_or_describe: spec_file.include?("features") ? "feature" : "describe",
160
+ scenario_or_specify: spec_file.include?("features") ? "scenario" : "specify",
161
+ record_ids: ids.inspect,
162
+ model_name: model_name,
163
+ model_name_singular: model_name.underscore,
164
+ model_name_plural: model_name.underscore.pluralize,
165
+ spec_desc_str: spec_desc.inspect,
166
+ capture_path_str: capture_path.inspect,
167
+ capture_name: capture_name
168
+ }.stringify_keys, {
169
+ strict_variables: true
170
+ })
171
+
172
+
173
+ # Insert the new spec content into the existing spec file
174
+ spec_lines = IO.readlines(spec_fullpath)
175
+
176
+ index_of_last_end = spec_lines.size - 1
177
+ index_of_last_end -= 1 while spec_lines[index_of_last_end] !~ /end/
178
+
179
+ spec_lines.insert(index_of_last_end, new_spec)
180
+
181
+ IO.write(spec_fullpath, spec_lines.join)
182
+ puts "Wrote generated spec content to the end of #{spec_fullpath}"
183
+ end
184
+ end
185
+
186
+
187
+ ##################
188
+ # Script logic #
189
+ ##################
190
+
191
+ cap = Capture.new
192
+
193
+ until cap.done?
194
+ begin
195
+ cap.execute!
196
+ cap.increment_step
197
+
198
+ rescue Exception => error
199
+ retry if error.is_a?(ActiveRecord::StatementInvalid) && error.message.include?("closed MySQL connection")
200
+
201
+ puts PASTEL.red "\nERROR! #{error.class.name} \"#{error.message}\""
202
+ puts error.backtrace.join("\n")
203
+ puts PASTEL.red "#{error.class} encountered during #{cap.human_step.inspect} step\n"
204
+
205
+ choice = PROMPT.expand("Continue execution?") do |q|
206
+ q.choice key: 'q', name: 'Abort', value: :abort
207
+ q.choice key: 'r', name: 'Retry', value: :retry
208
+ q.choice key: 'R', name: 'Reload Rails and Retry', value: :reload
209
+ q.choice key: 'b', name: 'Go back one step', value: :back
210
+ end
211
+
212
+ case choice
213
+ when :abort then cap.step = nil
214
+ when :retry then nil
215
+ when :back then cap.decrement_step
216
+ when :reload then cap.step = :initialize_rails
217
+ end
218
+ end
219
+ end
220
+
221
+ if cap.complete?
222
+ puts "DONE"
223
+ else
224
+ puts "ABORTED"
225
+ end
@@ -0,0 +1,47 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'softwear/lib'
4
+
5
+ old_gemfile = File.open('Gemfile').read.gsub(/\r\n?/, "\n")
6
+ gemfile = []
7
+
8
+ handler = nil
9
+ append_line = nil
10
+
11
+ injected_gems = false
12
+
13
+ ignore_line = lambda do |line|
14
+ if line.include? Softwear::Lib::GEMFILE_CLOSER
15
+ gemfile << line
16
+ handler = append_line
17
+ end
18
+ end
19
+
20
+ append_line = lambda do |line|
21
+ gemfile << line
22
+ if line.include? Softwear::Lib::GEMFILE_OPENER
23
+ puts "Updating common gems"
24
+ gemfile << Softwear::Lib::COMMON_GEMS
25
+ injected_gems = true
26
+ handler = ignore_line
27
+ end
28
+ end
29
+
30
+ handler = append_line
31
+ old_gemfile.each_line do |line|
32
+ handler.call(line)
33
+ end
34
+
35
+ unless injected_gems
36
+ puts "Adding common gems - check for duplicates!"
37
+ gemfile << "\n" + Softwear::Lib::GEMFILE_OPENER + "\n"
38
+ gemfile << Softwear::Lib::COMMON_GEMS
39
+ gemfile << Softwear::Lib::GEMFILE_CLOSER + "\n"
40
+ end
41
+
42
+ File.open('Gemfile', 'w') do |file|
43
+ gemfile.each do |line|
44
+ file.write(line)
45
+ end
46
+ end
47
+ puts "Done!"
data/bin/sw ADDED
@@ -0,0 +1,11 @@
1
+ #!/usr/bin/env ruby
2
+ require 'softwear/lib'
3
+
4
+ if ARGV.length > 0
5
+ dir = File.dirname(__FILE__)
6
+ file = "#{dir}/softwear-#{ARGV[0]}"
7
+
8
+ exec "bundle exec #{file} #{ARGV[1..-1].map(&:inspect).join(' ')}"
9
+ else
10
+ puts "Run `softwear update` to update your gemfile's common dependencies"
11
+ end
@@ -403,6 +403,16 @@ module Softwear
403
403
  objects.each { |u| u.instance_variable_set(:@persisted, true) }
404
404
  end
405
405
 
406
+ # ====================
407
+ # Returns array of all users in the given group
408
+ # ====================
409
+ def of_group(group_code)
410
+ json = validate_response query "ofgroup #{Figaro.env.hub_app_name} #{group_code}"
411
+
412
+ objects = JSON.parse(json).map(&method(:new))
413
+ objects.each { |u| u.instance_variable_set(:@persisted, true) }
414
+ end
415
+
406
416
  # ====================
407
417
  # Given a valid signin token:
408
418
  # Returns the authenticated user for the given token
@@ -432,7 +442,7 @@ module Softwear
432
442
 
433
443
  REMOTE_ATTRIBUTES = [
434
444
  :id, :email, :first_name, :last_name,
435
- :roles, :profile_picture_url,
445
+ :roles, :groups, :profile_picture_url,
436
446
  :default_view, :rights
437
447
  ]
438
448
  REMOTE_ATTRIBUTES.each(&method(:attr_accessor))
@@ -504,6 +514,14 @@ module Softwear
504
514
  wanted_roles.any? { |r| @roles.include?(r.to_s) }
505
515
  end
506
516
  end
517
+
518
+ def group?(group)
519
+ if @groups.nil?
520
+ query("group #{Figaro.env.hub_app_name} #{id} #{group}") == 'yes'
521
+ else
522
+ @groups.include?(group)
523
+ end
524
+ end
507
525
  end
508
526
  end
509
527
  end
@@ -103,6 +103,11 @@ users:
103
103
  all.select { |u| !u.roles.nil? && roles.any? { |r| u.roles.include?(r) } }
104
104
  end
105
105
 
106
+ def of_group(group_code)
107
+ roles = Array(roles).map(&:to_s)
108
+ all.select { |u| !u.groups.nil? && u.groups.include?(group_code) }
109
+ end
110
+
106
111
  def auth(_token, _appname = nil)
107
112
  signed_in = users_yml[:signed_in]
108
113
 
@@ -9,8 +9,8 @@ module Softwear
9
9
  end
10
10
 
11
11
  def token_authenticate_user!
12
- user_class = self.class.user_class || base_class.user_class || User
13
- options = (self.class.token_auth_options || base_class.token_auth_options || {}).with_indifferent_access
12
+ user_class = self.class.user_class || try(:base_class).try(:user_class) || User
13
+ options = (self.class.token_auth_options || try(:base_class).try(:token_auth_options) || {}).with_indifferent_access
14
14
  params_options = (options[:params] || {}).with_indifferent_access
15
15
  headers_options = (options[:headers] || {}).with_indifferent_access
16
16
 
@@ -1,7 +1,7 @@
1
1
  module Softwear
2
2
  class Engine < ::Rails::Engine
3
3
  isolate_namespace Softwear
4
-
4
+
5
5
  initializer "softwear_lib.assets.precompile" do |app|
6
6
  app.config.assets.precompile += %w(softwear.css softwear.js style.css)
7
7
  end
@@ -41,10 +41,15 @@ module Softwear
41
41
  params.each do |key, value|
42
42
  new_value = value
43
43
 
44
- case key.to_s
45
- when /cc_number/ then new_value = "<FILTERED>"
46
- when /cc_cvc/ then new_value = "<FILTERED>"
47
- when /password/ then new_value = "<FILTERED>"
44
+ case value
45
+ when Hash
46
+ new_value = filter_params(value)
47
+ else
48
+ case key.to_s
49
+ when /cc_number/ then new_value = "<FILTERED>"
50
+ when /cc_cvc/ then new_value = "<FILTERED>"
51
+ when /password/ then new_value = "<FILTERED>"
52
+ end
48
53
  end
49
54
 
50
55
  new_hash[key] = new_value
@@ -56,12 +61,21 @@ module Softwear
56
61
  JSON.pretty_generate(filter_params(params)) + "|||" +
57
62
  instance_variables
58
63
  .reject { |v| /^@_/ =~ v.to_s || %i(@view_renderer @output_buffer @view_flow @error).include?(v) }
59
- .map { |v| "#{v}: #{instance_variable_get(v).inspect}" }
64
+ .map { |v| "#{v}: #{instance_variable_get(v).inspect rescue '(ERROR)'}" }
60
65
  .join("|||")
61
66
  end
62
67
 
63
68
  def layout_for_error
64
- current_user ? 'application' : 'no_overlay'
69
+ user = try(:current_user) || @current_user
70
+ partner = Figaro.env.partner_namespace
71
+
72
+ if user
73
+ "application"
74
+ elsif !partner.nil? && request.url.include?(partner)
75
+ "partners/application"
76
+ else
77
+ "no_overlay"
78
+ end
65
79
  end
66
80
  end
67
81
  end
data/lib/softwear/lib.rb CHANGED
@@ -1,19 +1,22 @@
1
- require "softwear/engine"
2
- require "softwear/error_catcher"
3
- require "softwear/library/version"
4
- require "softwear/library/spec"
5
- require "softwear/library/api_controller"
6
- begin
7
- require "softwear/library/enqueue"
8
- rescue LoadError
1
+ if (::Rails rescue false)
2
+ require "softwear/engine"
3
+ require "softwear/library/api_controller"
4
+ require "softwear/error_catcher"
5
+ require "softwear/library/version"
6
+ require "softwear/library/spec"
7
+ require "softwear/auth/helper"
8
+ require "softwear/auth/model"
9
+ require "softwear/auth/belongs_to_user"
10
+ require "softwear/auth/spec"
11
+ require "softwear/auth/token_authentication"
12
+ require "softwear/library/controller_authentication"
13
+ begin
14
+ require "softwear/library/enqueue"
15
+ rescue LoadError
16
+ end
9
17
  end
10
18
 
11
- require "softwear/library/controller_authentication"
12
- require "softwear/auth/helper"
13
- require "softwear/auth/model"
14
- require "softwear/auth/belongs_to_user"
15
- require "softwear/auth/spec"
16
- require "softwear/auth/token_authentication"
19
+ require "softwear/library/spec_dump"
17
20
 
18
21
  module Softwear
19
22
  module Library
@@ -1,121 +1,145 @@
1
1
  require 'rbczmq'
2
2
 
3
- def split(string, limit = nil)
4
- string.split(/\s+/, limit)
5
- end
3
+ module Softwear
4
+ module Library
5
+ class LightServer
6
+ def self.test!(file_path)
7
+ Object.class_eval do
8
+ def start_server!(*args)
9
+ Thread.new do
10
+ Softwear::Library::LightServer.new.start_server!(*args)
11
+ end
12
+ end
13
+ end
14
+ load file_path
15
+ end
6
16
 
7
- def report_error(rep, whole_command, error)
8
- $stderr.puts "=== ERROR WHILE PROCESSING THE COMMAND \"#{whole_command}\" ===\n"\
9
- "#{error.class.name}: #{error.message}\n#{error.backtrace.join("\n")}"
17
+ def split(string, limit = nil)
18
+ string.split(/\s+/, limit)
19
+ end
10
20
 
11
- begin
12
- rep.send "sorry" if rep
13
- rescue StandardError => e
14
- $stderr.puts "(could not send 'sorry' message: \"#{e.class} #{e.message}\")"
15
- end
16
- end
21
+ def report_error(rep, whole_command, error)
22
+ $stderr.puts "=== ERROR WHILE PROCESSING THE COMMAND \"#{whole_command}\" ===\n"\
23
+ "#{error.class.name}: #{error.message}\n#{error.backtrace.join("\n")}"
17
24
 
18
- def dev_log(*a)
19
- $stdout.puts(*a) #if Rails.env.development?
20
- end
25
+ begin
26
+ rep.send "sorry" if rep
27
+ rescue StandardError => e
28
+ $stderr.puts "(could not send 'sorry' message: \"#{e.class} #{e.message}\")"
29
+ end
30
+ end
21
31
 
22
- def log(*a)
23
- $stdout.puts(*a) #unless Rails.env.test?
24
- end
32
+ def dev_log(*a)
33
+ $stdout.puts(*a) #if Rails.env.development?
34
+ end
25
35
 
26
- # ==== Send Format: =======
27
- # One line strings: "#{command} #{arg1} #{arg2} #{etc}"
28
- # The last argument, depending on the command, may contain spaces (but usually does not need to)
29
- # =========================
30
- # === Receive Format: =====
31
- # Usually one string, like "yes", or "no".
32
- # Returns "denied" if an unauthorized command was attempted.
33
- # Returns "invalid" if an invalid command was attempted.
34
- # Returns "sorry" if an error was raised while processing the command.
35
- # Can be a json argument, often following "yes ".
36
- # =========================
37
- def start_server!(*args)
38
- log "Connecting...!"
39
-
40
- if args.size > 1
41
- port = args.first
42
- else
43
- port = ENV['port'] || ENV['PORT'] || 2900
44
- end
45
- address = ENV['SOCKET_ADDR'] || "tcp://*"
36
+ def log(*a)
37
+ $stdout.puts(*a) #unless Rails.env.test?
38
+ end
46
39
 
47
- if address =~ /:$/
48
- socket_address = address
49
- else
50
- socket_address = "#{address}:#{port}"
51
- end
40
+ # ==== Send Format: =======
41
+ # One line strings: "#{command} #{arg1} #{arg2} #{etc}"
42
+ # The last argument, depending on the command, may contain spaces (but usually does not need to)
43
+ # =========================
44
+ # === Receive Format: =====
45
+ # Usually one string, like "yes", or "no".
46
+ # Returns "denied" if an unauthorized command was attempted.
47
+ # Returns "invalid" if an invalid command was attempted.
48
+ # Returns "sorry" if an error was raised while processing the command.
49
+ # Can be a json argument, often following "yes ".
50
+ # =========================
51
+ def start_server!(*args)
52
+ log "Connecting...!"
53
+
54
+ if args.size > 1
55
+ port = args.first
56
+ else
57
+ port = ENV['port'] || ENV['PORT'] || 2900
58
+ end
59
+ address = ENV['SOCKET_ADDR'] || "tcp://*"
52
60
 
53
- ctx = ZMQ::Context.new
54
- rep = ctx.bind(:REP, socket_address)
61
+ if address =~ /:$/
62
+ socket_address = address
63
+ else
64
+ socket_address = "#{address}:#{port}"
65
+ end
55
66
 
56
- log "Ready! Using \"#{ActiveRecord::Base.connection.current_database}\" database"
67
+ rep = zmq.bind(:REP, socket_address)
57
68
 
58
- commands = args.last
69
+ log "Ready! Using \"#{ActiveRecord::Base.connection.try(:current_database) || 'in-memory'}\" database"
59
70
 
60
- loop do
61
- begin
62
- loop do
63
- line_in = rep.recv
64
- raise "Got nil response (ZMQ REP/REQ out of sync?)" if line_in.nil?
65
- command, rest_of_command = split(line_in, 2)
71
+ commands = args.last
66
72
 
67
- before = Time.now
68
- begin
69
- command = commands[command.downcase.to_sym]
70
-
71
- if command.nil?
72
- log "Received invalid command: \"#{line_in}\""
73
- else
74
- dev_log "<== #{line_in}"
75
- ActiveRecord::Base.connection_pool.with_connection do
76
-
77
- # The ZMQ socket requires that a reply be send after every
78
- # message -- so we pass a lambda for the client code to
79
- # call to send a message and make sure it only happens
80
- # once.
81
- replied = false
82
- reply = lambda do |msg|
83
- if replied
84
- raise "Reply sent twice"
73
+ loop do
74
+ begin
75
+ loop do
76
+ line_in = rep.recv
77
+ raise "Got nil response (ZMQ REP/REQ out of sync?)" if line_in.nil?
78
+ command, rest_of_command = split(line_in, 2)
79
+
80
+ before = Time.now
81
+ begin
82
+ command = commands[command.downcase.to_sym]
83
+
84
+ if command.nil?
85
+ log "Received invalid command: \"#{line_in}\""
85
86
  else
86
- rep.send(msg)
87
- replied = true
87
+ dev_log "<== #{line_in}"
88
+ ActiveRecord::Base.connection_pool.with_connection do
89
+
90
+ # The ZMQ socket requires that a reply be send after every
91
+ # message -- so we pass a lambda for the client code to
92
+ # call to send a message and make sure it only happens
93
+ # once.
94
+ replied = false
95
+ reply = lambda do |msg|
96
+ if replied
97
+ raise "Reply sent twice"
98
+ else
99
+ rep.send(msg)
100
+ replied = true
101
+ end
102
+ end
103
+ instance_exec(reply, rest_of_command, &command)
104
+
105
+ if !replied
106
+ rep.send "noreply"
107
+ end
108
+
109
+ end
88
110
  end
89
- end
90
- command.call(reply, rest_of_command)
91
111
 
92
- if !replied
93
- rep.send "noreply"
112
+ rescue StandardError => e
113
+ report_error(rep, line_in, e)
114
+ rescue Exception => e
115
+ report_error(rep, line_in, e)
116
+ break
94
117
  end
118
+ after = Time.now
95
119
 
120
+ ms = (after - before) * 1000
121
+ dev_log %[(#{'%.2f' % ms}ms)]
122
+ dev_log ""
96
123
  end
97
- end
98
124
 
99
- rescue StandardError => e
100
- report_error(rep, line_in, e)
101
- rescue Exception => e
102
- report_error(rep, line_in, e)
103
- break
104
- end
105
- after = Time.now
125
+ rescue StandardError => error
126
+ $stderr.puts "=== ERROR -- RESTARTING SERVER ===\n"\
127
+ "#{error.class.name}: #{error.message}\n#{error.backtrace.join("\n")}"
106
128
 
107
- ms = (after - before) * 1000
108
- dev_log %[(#{'%.2f' % ms}ms)]
109
- dev_log ""
129
+ rep.destroy
130
+ log "Reconnecting...!"
131
+ rep = zmq.bind(:REP, socket_address)
132
+ end
133
+ end
110
134
  end
111
135
 
112
- rescue StandardError => error
113
- $stderr.puts "=== ERROR -- RESTARTING SERVER ===\n"\
114
- "#{error.class.name}: #{error.message}\n#{error.backtrace.join("\n")}"
115
-
116
- rep.destroy
117
- log "Reconnecting...!"
118
- rep = ctx.bind(:REP, socket_address)
136
+ def zmq
137
+ $zmq_context ||= ZMQ::Context.new
138
+ end
119
139
  end
120
140
  end
121
141
  end
142
+
143
+ def start_server!(*args)
144
+ Softwear::Library::LightServer.new.start_server!(*args)
145
+ end
@@ -100,7 +100,7 @@ module Softwear::Library
100
100
  old_scopes = page.instance_variable_get(:@scopes)
101
101
  page.instance_variable_set(:@scopes, [nil])
102
102
 
103
- find('input.select2-search__field').set(text)
103
+ find('input.select2-search__field[tabindex="0"]').set(text)
104
104
  sleep options[:wait_before_click] if options[:wait_before_click]
105
105
  result = first('li.select2-results__option')
106
106
  if result.nil? || result.text == "No results found"
@@ -0,0 +1,143 @@
1
+ module Softwear
2
+ module Library
3
+ module SpecDump
4
+ Record = Struct.new(:object, :history) do
5
+ def model; object.class; end
6
+ end
7
+
8
+ def spec_dump(root, ignored_models = [], whitelist_array = nil)
9
+ types_recorded = {}
10
+ records_by_type = {}
11
+ record_queue = Queue.new
12
+ whitelist = whitelist_array ? whitelist_array.reduce({}) { |h,n| h.merge(n => true) } : Hash.new(true)
13
+
14
+ is_ignored = lambda do |name|
15
+ next true if ignored_models.any? { |i| name =~ i }
16
+ next true if !whitelist[name]
17
+ false
18
+ end
19
+
20
+ # Begin dump routine. This will return true when we added a new entry.
21
+ dump = lambda do |record|
22
+ cache = (records_by_type[record.model.name] ||= {})
23
+ identifier = record.object.id
24
+
25
+ next false if cache[identifier].present?
26
+
27
+ attributes = {}
28
+ record.model.column_names.each do |col|
29
+ value = record.object[col]
30
+ if value.respond_to?(:iso8601)
31
+ # DateTimes don't serialize properly in attributes_before_type_cast
32
+ # for some reason, so we explicitly call to_s(:db) to make sure
33
+ # they can be loaded again correctly.
34
+ raw_value = value.to_s(:db)
35
+ else
36
+ raw_value = record.object.attributes_before_type_cast[col]
37
+ end
38
+
39
+ attributes[col] = raw_value
40
+ end
41
+
42
+ cache[identifier] = attributes
43
+ true
44
+ end
45
+ # end dump routine
46
+
47
+ # Begin actual dumping of records
48
+ Array(root).each do |record|
49
+ record_queue << Record.new(record, ["#{record.class.name}##{record.id}"])
50
+ end
51
+
52
+ while record_queue.present?
53
+ record = record_queue.pop
54
+ next if record.object.nil?
55
+ next unless record.model.respond_to?(:column_names)
56
+
57
+ # If dump returns false, that means we've already dumped this record
58
+ next unless dump.(record)
59
+ types_recorded[record.model.name] = true
60
+
61
+ yield record, types_recorded if block_given?
62
+
63
+ record.model.reflect_on_all_associations.each do |assoc|
64
+ next if assoc.is_a?(ActiveRecord::Reflection::ThroughReflection)
65
+
66
+ next if is_ignored["#{record.model.name}##{assoc.name}"]
67
+
68
+ case assoc
69
+ when ActiveRecord::Reflection::BelongsToReflection
70
+ # A belongs_to association will never cause an infinite loop
71
+ record_queue << Record.new(
72
+ record.object.send(assoc.name),
73
+ record.history + ["#{record.model.name}##{record.object.id}##{assoc.name}"]
74
+ )
75
+
76
+ when ActiveRecord::Reflection::HasManyReflection
77
+ # A has_many association can cause an infinite loop, so we only
78
+ # process these if we've never seen the record type before.
79
+ #
80
+ # If there's a whitelist, no need to care about that
81
+ next if whitelist_array.blank? && types_recorded[assoc.klass.name]
82
+
83
+ record.object.send(assoc.name).each_with_index do |child, i|
84
+ next if child.nil?
85
+ record_queue << Record.new(
86
+ child,
87
+ record.history + ["#{record.model.name}##{record.object.id}##{assoc.name}[#{i}]"]
88
+ )
89
+ end
90
+ end
91
+ end
92
+ end
93
+ # end actual dumping of records
94
+
95
+ records_by_type
96
+ end
97
+
98
+ def expand_spec_dump(dump, use_outside_of_test = false)
99
+ if !Rails.env.test? && !use_outside_of_test
100
+ raise "Tried to call expand_spec_dump outside of test environment. "\
101
+ "If you really want to do this, pass `true` as the second parameter."
102
+ end
103
+
104
+ if ActiveRecord::Base.configurations[Rails.env]['adapter'].include?('sqlite')
105
+ insert_cmd = lambda do |model|
106
+ "INSERT OR REPLACE INTO #{model.table_name} (#{model.column_names.map { |c| "`#{c}`" }.join(', ')}) VALUES\n"
107
+ end
108
+ cmd_suffix = ->_{ "" }
109
+ else
110
+ insert_cmd = lambda do |model|
111
+ "INSERT INTO #{model.table_name} (#{model.column_names.map { |c| "`#{c}`" }.join(', ')}) VALUES\n"
112
+ end
113
+ cmd_suffix = lambda do |model|
114
+ "\nON DUPLICATE KEY UPDATE\n" +
115
+ model.column_names
116
+ .map { |col| "`#{col}` = VALUES(`#{col}`)" }
117
+ .join(",\n")
118
+ end
119
+ end
120
+
121
+ dump.each do |class_name, entries|
122
+ model = class_name.constantize
123
+ sql = insert_cmd[model]
124
+ sanitize = model.connection.method(:quote)
125
+
126
+ sql += entries.map do |entry|
127
+ _id, attributes = entry
128
+
129
+ '(' +
130
+ model.column_names.map { |col| sanitize[attributes[col]] }.join(', ') +
131
+ ')'
132
+ end.join(",\n")
133
+
134
+ sql += cmd_suffix[model]
135
+
136
+ model.connection.execute sql
137
+ end
138
+ end
139
+
140
+ extend self
141
+ end
142
+ end
143
+ end
@@ -1,5 +1,5 @@
1
1
  module Softwear
2
2
  module Library
3
- VERSION = "3.1.5"
3
+ VERSION = "3.3.5"
4
4
  end
5
5
  end
@@ -0,0 +1,17 @@
1
+
2
+ {{feature_or_describe}} "CAPTURE: {{capture_name}}", captured: true do
3
+ let(:capture) { JSON.parse(IO.read(Rails.root.join({{capture_path_str}}))) }
4
+ let(:{{model_name_plural}}) { {{model_name}}.where(id: {{record_ids}}) }
5
+
6
+ before :each do
7
+ Softwear::Library::SpecDump.expand_spec_dump(capture)
8
+ end
9
+
10
+ {{scenario_or_specify}} {{spec_desc_str}} do
11
+ visit edit_{{model_name_singular}}_path({{model_name_singular}})
12
+
13
+ # TODO fill in generated spec!
14
+ byebug
15
+ expect(false).to eq true
16
+ end
17
+ end
data/softwear-lib.gemspec CHANGED
@@ -24,4 +24,7 @@ Gem::Specification.new do |spec|
24
24
 
25
25
  spec.add_dependency "activesupport", ">= 4.2.1"
26
26
  spec.add_dependency "rbczmq"
27
+ spec.add_dependency "tty-prompt"
28
+ spec.add_dependency "tty-spinner"
29
+ spec.add_dependency "liquid"
27
30
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: softwear-lib
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.1.5
4
+ version: 3.3.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Nigel Baillie
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2017-05-18 00:00:00.000000000 Z
11
+ date: 2017-10-10 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -80,12 +80,57 @@ dependencies:
80
80
  - - ">="
81
81
  - !ruby/object:Gem::Version
82
82
  version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: tty-prompt
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :runtime
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: tty-spinner
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ type: :runtime
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
111
+ - !ruby/object:Gem::Dependency
112
+ name: liquid
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - ">="
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ type: :runtime
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - ">="
123
+ - !ruby/object:Gem::Version
124
+ version: '0'
83
125
  description:
84
126
  email:
85
127
  - nigel@annarbortees.com
86
128
  executables:
87
129
  - softwear
130
+ - softwear-capture
88
131
  - softwear-deploy
132
+ - softwear-update
133
+ - sw
89
134
  extensions: []
90
135
  extra_rdoc_files: []
91
136
  files:
@@ -100,6 +145,7 @@ files:
100
145
  - app/assets/stylesheets/softwear.css
101
146
  - app/controllers/softwear/application_controller.rb
102
147
  - app/controllers/softwear/error_reports_controller.rb
148
+ - app/controllers/softwear/spec_dump_controller.rb
103
149
  - app/helpers/softwear/application_helper.rb
104
150
  - app/mailers/error_report_mailer.rb
105
151
  - app/views/error_report_mailer/send_report.html.erb
@@ -108,7 +154,10 @@ files:
108
154
  - app/views/softwear/errors/internal_server_error.html.erb
109
155
  - app/views/softwear/errors/internal_server_error.js.erb
110
156
  - bin/softwear
157
+ - bin/softwear-capture
111
158
  - bin/softwear-deploy
159
+ - bin/softwear-update
160
+ - bin/sw
112
161
  - config/routes.rb
113
162
  - lib/softwear/auth/belongs_to_user.rb
114
163
  - lib/softwear/auth/controller.rb
@@ -129,7 +178,9 @@ files:
129
178
  - lib/softwear/library/enqueue.rb
130
179
  - lib/softwear/library/light_server.rb
131
180
  - lib/softwear/library/spec.rb
181
+ - lib/softwear/library/spec_dump.rb
132
182
  - lib/softwear/library/version.rb
183
+ - lib/softwear/templates/captured_spec.rb.liquid
133
184
  - softwear-lib.gemspec
134
185
  - spec/spec_helper.rb
135
186
  - vendor/assets/stylesheets/animate/animate.css