softwear-lib 3.1.5 → 3.3.5

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