hanami-devtools 2023.02.16
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 +7 -0
- data/.gitignore +10 -0
- data/.rubocop.yml +158 -0
- data/Gemfile +4 -0
- data/README.md +35 -0
- data/Rakefile +4 -0
- data/bin/console +15 -0
- data/bin/setup +8 -0
- data/hanami-devtools.gemspec +42 -0
- data/lib/hanami/devtools/integration/bundler.rb +174 -0
- data/lib/hanami/devtools/integration/capybara.rb +20 -0
- data/lib/hanami/devtools/integration/cli.rb +57 -0
- data/lib/hanami/devtools/integration/coverage.rb +55 -0
- data/lib/hanami/devtools/integration/dns.rb +26 -0
- data/lib/hanami/devtools/integration/env.rb +97 -0
- data/lib/hanami/devtools/integration/files.rb +108 -0
- data/lib/hanami/devtools/integration/gemfile.rb +38 -0
- data/lib/hanami/devtools/integration/hanami_commands.rb +169 -0
- data/lib/hanami/devtools/integration/platform/engine.rb +32 -0
- data/lib/hanami/devtools/integration/platform/matcher.rb +79 -0
- data/lib/hanami/devtools/integration/platform/os.rb +21 -0
- data/lib/hanami/devtools/integration/platform.rb +22 -0
- data/lib/hanami/devtools/integration/project_without_hanami_model.rb +26 -0
- data/lib/hanami/devtools/integration/rack_test.rb +87 -0
- data/lib/hanami/devtools/integration/random_port.rb +46 -0
- data/lib/hanami/devtools/integration/retry.rb +36 -0
- data/lib/hanami/devtools/integration/silently.rb +35 -0
- data/lib/hanami/devtools/integration/with_clean_env_project.rb +29 -0
- data/lib/hanami/devtools/integration/with_directory.rb +30 -0
- data/lib/hanami/devtools/integration/with_project.rb +77 -0
- data/lib/hanami/devtools/integration/with_system_tmp_directory.rb +24 -0
- data/lib/hanami/devtools/integration/with_tmp_directory.rb +48 -0
- data/lib/hanami/devtools/integration/within_project_directory.rb +37 -0
- data/lib/hanami/devtools/integration.rb +4 -0
- data/lib/hanami/devtools/rake_helper.rb +31 -0
- data/lib/hanami/devtools/rake_tasks.rb +4 -0
- data/lib/hanami/devtools/unit/support/coverage.rb +10 -0
- data/lib/hanami/devtools/unit/support/silence_deprecations.rb +12 -0
- data/lib/hanami/devtools/unit.rb +4 -0
- data/lib/hanami/devtools/version.rb +7 -0
- data/lib/hanami/devtools.rb +8 -0
- data/script/setup +36 -0
- metadata +266 -0
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "singleton"
|
|
4
|
+
module RSpec
|
|
5
|
+
module Support
|
|
6
|
+
# Environment variables wrapper for:
|
|
7
|
+
#
|
|
8
|
+
# * CLI commands
|
|
9
|
+
# * Isolate `ENV` global state
|
|
10
|
+
#
|
|
11
|
+
# @since 0.2.0
|
|
12
|
+
#
|
|
13
|
+
# @see https://lucaguidi.com/2016/12/27/isolate-global-state/
|
|
14
|
+
class Env
|
|
15
|
+
include Singleton
|
|
16
|
+
|
|
17
|
+
def self.setup
|
|
18
|
+
instance.__send__(:setup)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def self.reset
|
|
22
|
+
instance.__send__(:setup)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def self.env
|
|
26
|
+
instance.to_h
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def self.[](key)
|
|
30
|
+
instance[key]
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def self.[]=(key, value)
|
|
34
|
+
instance[key] = value
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def self.fetch_from_original(key)
|
|
38
|
+
instance.__send__(:original).fetch(key)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def initialize
|
|
42
|
+
@original = ENV.to_hash
|
|
43
|
+
@mutex = Mutex.new
|
|
44
|
+
setup
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def [](key)
|
|
48
|
+
synchronize do
|
|
49
|
+
env[key]
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def []=(key, value)
|
|
54
|
+
synchronize do
|
|
55
|
+
env[key] = value
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def to_h
|
|
60
|
+
env.dup
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
private
|
|
64
|
+
|
|
65
|
+
attr_reader :original, :env
|
|
66
|
+
|
|
67
|
+
ENV_VARS = %w[RUBYOPT RUBYLIB RUBY_ROOT RUBY_ENGINE RUBY_VERSION GEM_ROOT GEM_HOME GEM_PATH BUNDLE_GEMFILE].freeze
|
|
68
|
+
|
|
69
|
+
def setup
|
|
70
|
+
synchronize do
|
|
71
|
+
@env = {}
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
ENV_VARS.each do |var|
|
|
75
|
+
original_value = original.fetch(var, nil)
|
|
76
|
+
next if original_value.nil?
|
|
77
|
+
|
|
78
|
+
self[var] = original_value
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def synchronize(&blk)
|
|
83
|
+
@mutex.synchronize(&blk)
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
RSpec.configure do |config|
|
|
90
|
+
config.before(:suite) do
|
|
91
|
+
RSpec::Support::Env.setup
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
config.after do
|
|
95
|
+
RSpec::Support::Env.reset
|
|
96
|
+
end
|
|
97
|
+
end
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "hanami/utils/files"
|
|
4
|
+
|
|
5
|
+
module RSpec
|
|
6
|
+
module Support
|
|
7
|
+
# File manipulation utilities
|
|
8
|
+
#
|
|
9
|
+
# @since 0.2.0
|
|
10
|
+
module Files
|
|
11
|
+
private
|
|
12
|
+
|
|
13
|
+
def touch(path)
|
|
14
|
+
write(path, "")
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def write(path, *content)
|
|
18
|
+
Pathname.new(path).dirname.mkpath
|
|
19
|
+
open(path, ::File::CREAT | ::File::WRONLY, *content)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def rewrite(path, *content)
|
|
23
|
+
open(path, ::File::TRUNC | ::File::WRONLY, *content)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def replace(path, target, replacement)
|
|
27
|
+
content = ::File.readlines(path)
|
|
28
|
+
content[index(content, path, target)] = "#{replacement}\n"
|
|
29
|
+
|
|
30
|
+
rewrite(path, content)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def replace_last(path, target, replacement)
|
|
34
|
+
content = ::File.readlines(path)
|
|
35
|
+
content[-index(content.reverse, path, target) - 1] = "#{replacement}\n"
|
|
36
|
+
|
|
37
|
+
rewrite(path, content)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def unshift(path, line)
|
|
41
|
+
content = ::File.readlines(path)
|
|
42
|
+
content.unshift("#{line}\n")
|
|
43
|
+
|
|
44
|
+
rewrite(path, content)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def append(path, contents)
|
|
48
|
+
content = ::File.readlines(path)
|
|
49
|
+
content << "#{contents}\n"
|
|
50
|
+
|
|
51
|
+
rewrite(path, content)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def remove_block(path, target) # rubocop:disable Metrics/AbcSize
|
|
55
|
+
content = ::File.readlines(path)
|
|
56
|
+
starting = index(content, path, target)
|
|
57
|
+
line = content[starting]
|
|
58
|
+
size = line[/\A[[:space:]]*/].bytesize
|
|
59
|
+
closing = (" " * size) + (target =~ /{/ ? "}" : "end")
|
|
60
|
+
ending = starting + index(content[starting..-1], path, closing)
|
|
61
|
+
|
|
62
|
+
content.slice!(starting..ending)
|
|
63
|
+
rewrite(path, content)
|
|
64
|
+
|
|
65
|
+
remove_block(path, target) if containts?(content, target)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def remove_line(path, target)
|
|
69
|
+
Hanami::Utils::Files.remove_line(path, target)
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def inject_line_before(path, target, contents)
|
|
73
|
+
Hanami::Utils::Files.inject_line_before(path, target, contents)
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def inject_line_after(path, target, contents)
|
|
77
|
+
Hanami::Utils::Files.inject_line_after(path, target, contents)
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def open(path, mode, *content)
|
|
81
|
+
::File.open(path, mode) do |file|
|
|
82
|
+
file.write(Array(content).flatten.join)
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def contents(path)
|
|
87
|
+
::IO.read(Hanami.root.join(path))
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def index(content, path, target)
|
|
91
|
+
line_number(content, target) or
|
|
92
|
+
raise ArgumentError.new("Cannot find `#{target}' inside `#{path}'.")
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def containts?(content, target)
|
|
96
|
+
!line_number(content, target).nil?
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def line_number(content, target)
|
|
100
|
+
content.index { |l| l.include?(target) }
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
RSpec.configure do |config|
|
|
107
|
+
config.include RSpec::Support::Files, type: :integration
|
|
108
|
+
end
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "digest"
|
|
4
|
+
require "hanami/utils/files"
|
|
5
|
+
|
|
6
|
+
module RSpec
|
|
7
|
+
module Support
|
|
8
|
+
# Gemfile utilities
|
|
9
|
+
#
|
|
10
|
+
# @since 0.2.0
|
|
11
|
+
module Gemfile
|
|
12
|
+
module_function
|
|
13
|
+
|
|
14
|
+
def changed?
|
|
15
|
+
return true unless gemfile.exist? && checksum.exist?
|
|
16
|
+
|
|
17
|
+
calculate_checksum(gemfile) != checksum.read
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def write_checksum
|
|
21
|
+
Hanami::Utils::Files.write(checksum, calculate_checksum(gemfile))
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def calculate_checksum(path)
|
|
25
|
+
return unless path.exist?
|
|
26
|
+
Digest::MD5.file(path).to_s
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def gemfile
|
|
30
|
+
Pathname.new("Gemfile.lock")
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def checksum
|
|
34
|
+
Pathname.new("tmp").join("gemfile")
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "hanami/devtools/integration/bundler"
|
|
4
|
+
require "hanami/devtools/integration/files"
|
|
5
|
+
require "hanami/devtools/integration/retry"
|
|
6
|
+
require "hanami/devtools/integration/random_port"
|
|
7
|
+
require "hanami/utils/string"
|
|
8
|
+
|
|
9
|
+
module RSpec
|
|
10
|
+
module Support
|
|
11
|
+
# Run Hanami CLI commands
|
|
12
|
+
#
|
|
13
|
+
# @since 0.2.0
|
|
14
|
+
module HanamiCommands # rubocop:disable Metrics/ModuleLength
|
|
15
|
+
if defined?(Hanami::Environment)
|
|
16
|
+
LISTEN_ALL_HOST = Hanami::Environment::LISTEN_ALL_HOST
|
|
17
|
+
DEFAULT_PORT = Hanami::Environment::DEFAULT_PORT
|
|
18
|
+
else
|
|
19
|
+
LISTEN_ALL_HOST = "0.0.0.0"
|
|
20
|
+
DEFAULT_PORT = 2300
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
private
|
|
24
|
+
|
|
25
|
+
def rackup(args = {}, &blk)
|
|
26
|
+
args[:port] ||= RandomPort.call
|
|
27
|
+
|
|
28
|
+
bundle_exec "rackup -p #{args[:port]}" do |_, _, wait_thr|
|
|
29
|
+
exec_server_tests(args, wait_thr, &blk)
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def server(args = {}, &blk)
|
|
34
|
+
args[:port] ||= RandomPort.call
|
|
35
|
+
|
|
36
|
+
hanami "server#{_hanami_server_args(args)}" do |_, _, wait_thr|
|
|
37
|
+
exec_server_tests(args, wait_thr, &blk)
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def hanami(cmd, env: nil, &blk)
|
|
42
|
+
bundle_exec("hanami #{cmd}", env: env, &blk)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def console(args = "", &blk)
|
|
46
|
+
hanami "console#{args}", &blk
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def db_console(&blk)
|
|
50
|
+
hanami "db console", &blk
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def generate(target)
|
|
54
|
+
hanami "generate #{target}"
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def destroy(target)
|
|
58
|
+
hanami "destroy #{target}"
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def migrate
|
|
62
|
+
hanami "db migrate"
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def generate_model(entity)
|
|
66
|
+
generate "model #{entity}"
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def generate_migration(name, content) # rubocop:disable Metrics/AbcSize
|
|
70
|
+
# Check if the migration already exist because `hanami generate model`
|
|
71
|
+
migration = Dir.glob(Pathname.new("db").join("migrations", "*_#{name}.rb")).sort.last
|
|
72
|
+
|
|
73
|
+
# If it doesn't exist, generate it
|
|
74
|
+
if migration.nil?
|
|
75
|
+
sleep 1 # prevent two migrations to have the same timestamp
|
|
76
|
+
generate "migration #{name}"
|
|
77
|
+
|
|
78
|
+
migration = Dir.glob(Pathname.new("db").join("migrations", "**", "*.rb")).sort.last
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# write the given content, then return the timestamp
|
|
82
|
+
rewrite(migration, content)
|
|
83
|
+
Integer(migration.scan(/[0-9]+/).first)
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# rubocop:disable Metrics/MethodLength
|
|
87
|
+
# rubocop:disable Style/ClosingParenthesisIndentation
|
|
88
|
+
def generate_migrations
|
|
89
|
+
versions = []
|
|
90
|
+
versions << generate_migration("create_users", <<~CODE
|
|
91
|
+
Hanami::Model.migration do
|
|
92
|
+
change do
|
|
93
|
+
create_table :users do
|
|
94
|
+
primary_key :id
|
|
95
|
+
column :name, String
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
CODE
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
versions << generate_migration("add_age_to_users", <<~CODE
|
|
103
|
+
Hanami::Model.migration do
|
|
104
|
+
change do
|
|
105
|
+
add_column :users, :age, Integer
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
CODE
|
|
109
|
+
)
|
|
110
|
+
versions
|
|
111
|
+
end
|
|
112
|
+
# rubocop:enable Style/ClosingParenthesisIndentation
|
|
113
|
+
# rubocop:enable Metrics/MethodLength
|
|
114
|
+
|
|
115
|
+
def setup_model # rubocop:disable Metrics/MethodLength
|
|
116
|
+
generate_model "book"
|
|
117
|
+
generate_migration "create_books", <<~CODE
|
|
118
|
+
Hanami::Model.migration do
|
|
119
|
+
change do
|
|
120
|
+
create_table :books do
|
|
121
|
+
primary_key :id
|
|
122
|
+
column :title, String
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
CODE
|
|
127
|
+
|
|
128
|
+
migrate
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
def exec_server_tests(args, wait_thr, &blk)
|
|
132
|
+
if block_given?
|
|
133
|
+
setup_capybara(args)
|
|
134
|
+
retry_exec(StandardError, &blk)
|
|
135
|
+
end
|
|
136
|
+
ensure
|
|
137
|
+
# Simulate Ctrl+C to stop the server
|
|
138
|
+
Process.kill "INT", wait_thr[:pid]
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
def setup_capybara(args)
|
|
142
|
+
host = args.fetch(:host, LISTEN_ALL_HOST)
|
|
143
|
+
port = args.fetch(:port, DEFAULT_PORT)
|
|
144
|
+
|
|
145
|
+
Capybara.configure do |config|
|
|
146
|
+
config.app_host = "http://#{host}:#{port}"
|
|
147
|
+
end
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
def _hanami_server_args(args)
|
|
151
|
+
return if args.empty?
|
|
152
|
+
|
|
153
|
+
result = args.map do |arg, value|
|
|
154
|
+
if value.nil?
|
|
155
|
+
"--#{arg}"
|
|
156
|
+
else
|
|
157
|
+
"--#{arg}=#{value}"
|
|
158
|
+
end
|
|
159
|
+
end.join(" ")
|
|
160
|
+
|
|
161
|
+
" #{result}"
|
|
162
|
+
end
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
RSpec.configure do |config|
|
|
168
|
+
config.include RSpec::Support::HanamiCommands, type: :integration
|
|
169
|
+
end
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "hanami/utils"
|
|
4
|
+
|
|
5
|
+
module Platform
|
|
6
|
+
# Detect current Ruby engine: MRI or JRuby
|
|
7
|
+
#
|
|
8
|
+
# @since 0.2.0
|
|
9
|
+
module Engine
|
|
10
|
+
def self.engine?(name)
|
|
11
|
+
current == name
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def self.current
|
|
15
|
+
if ruby? then :ruby
|
|
16
|
+
elsif jruby? then :jruby
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
class << self
|
|
21
|
+
private
|
|
22
|
+
|
|
23
|
+
def ruby?
|
|
24
|
+
RUBY_ENGINE == "ruby"
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def jruby?
|
|
28
|
+
Hanami::Utils.jruby?
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "dry/core/basic_object"
|
|
4
|
+
|
|
5
|
+
module Platform
|
|
6
|
+
# Match current platform variables like Ruby engine, current database.
|
|
7
|
+
#
|
|
8
|
+
# @since 0.2.0
|
|
9
|
+
class Matcher
|
|
10
|
+
# Represents a failing match
|
|
11
|
+
#
|
|
12
|
+
# @since 0.2.0
|
|
13
|
+
class Nope < Dry::Core::BasicObject
|
|
14
|
+
def or(other, &blk)
|
|
15
|
+
blk.nil? ? other : blk.call # rubocop:disable Performance/RedundantBlockCall
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def method_missing(*) # rubocop:disable Style/MethodMissing
|
|
19
|
+
self.class.new
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def self.match(&blk)
|
|
24
|
+
catch :match do
|
|
25
|
+
new.__send__(:match, &blk)
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def self.match?(os: Os.current, engine: Engine.current)
|
|
30
|
+
catch :match do
|
|
31
|
+
new.os(os).engine(engine) { true }.or(false)
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def initialize
|
|
36
|
+
freeze
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def os(name, &blk)
|
|
40
|
+
return nope unless os?(name)
|
|
41
|
+
block_given? ? resolve(&blk) : yep
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def engine(name, &blk)
|
|
45
|
+
return nope unless engine?(name)
|
|
46
|
+
block_given? ? resolve(&blk) : yep
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def default(&blk)
|
|
50
|
+
resolve(&blk)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
private
|
|
54
|
+
|
|
55
|
+
def match(&blk)
|
|
56
|
+
instance_exec(&blk)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def nope
|
|
60
|
+
Nope.new
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def yep
|
|
64
|
+
self.class.new
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def resolve
|
|
68
|
+
throw :match, yield
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def os?(name)
|
|
72
|
+
Os.os?(name)
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def engine?(name)
|
|
76
|
+
Engine.engine?(name)
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "rbconfig"
|
|
4
|
+
|
|
5
|
+
module Platform
|
|
6
|
+
# Detect current Operating System (OS): MacOS or Linux
|
|
7
|
+
#
|
|
8
|
+
# @since 0.2.0
|
|
9
|
+
module Os
|
|
10
|
+
def self.os?(name)
|
|
11
|
+
current == name
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def self.current
|
|
15
|
+
case RbConfig::CONFIG["host_os"]
|
|
16
|
+
when /linux/ then :linux
|
|
17
|
+
when /darwin/ then :macos
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Matchers for current OS, Ruby engine.
|
|
4
|
+
#
|
|
5
|
+
# @since 0.2.0
|
|
6
|
+
module Platform
|
|
7
|
+
require "hanami/devtools/integration/platform/os"
|
|
8
|
+
require "hanami/devtools/integration/platform/engine"
|
|
9
|
+
require "hanami/devtools/integration/platform/matcher"
|
|
10
|
+
|
|
11
|
+
def self.ci?
|
|
12
|
+
ENV.key?("CI")
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def self.match(&blk)
|
|
16
|
+
Matcher.match(&blk)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def self.match?(**args)
|
|
20
|
+
Matcher.match?(**args)
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "hanami/devtools/integration/with_clean_env_project"
|
|
4
|
+
|
|
5
|
+
module RSpec
|
|
6
|
+
module Support
|
|
7
|
+
# Generate a project without `hanami-model`
|
|
8
|
+
#
|
|
9
|
+
# @since 0.2.0
|
|
10
|
+
module ProjectWithoutHanamiModel
|
|
11
|
+
private
|
|
12
|
+
|
|
13
|
+
def project_without_hanami_model(project = "bookshelf", args = {})
|
|
14
|
+
with_clean_env_project(project, args.merge(exclude_gems: ["hanami-model"])) do
|
|
15
|
+
replace "config/environment.rb", "hanami/model", ""
|
|
16
|
+
replace "Rakefile", "hanami/rake_tasks", ""
|
|
17
|
+
yield
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
RSpec.configure do |config|
|
|
25
|
+
config.include RSpec::Support::ProjectWithoutHanamiModel, type: :integration
|
|
26
|
+
end
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "rack"
|
|
4
|
+
require "rack/test"
|
|
5
|
+
require "excon"
|
|
6
|
+
require "hanami/devtools/integration/retry"
|
|
7
|
+
|
|
8
|
+
module RSpec
|
|
9
|
+
module Support
|
|
10
|
+
# HTTP tests agains Rack endpoints
|
|
11
|
+
#
|
|
12
|
+
# @since 0.2.0
|
|
13
|
+
module RackTest
|
|
14
|
+
private
|
|
15
|
+
|
|
16
|
+
def app
|
|
17
|
+
Rack::Builder.new do
|
|
18
|
+
use Rack::Lint
|
|
19
|
+
run RSpec::Support::RackApp.new
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# Testing Rack application
|
|
25
|
+
#
|
|
26
|
+
# @since 0.2.0
|
|
27
|
+
class RackApp
|
|
28
|
+
include RSpec::Support::Retry
|
|
29
|
+
|
|
30
|
+
def initialize
|
|
31
|
+
@connection = Excon.new(Capybara.app_host, persistent: true, read_timeout: 5)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def call(env)
|
|
35
|
+
retry_exec(Excon::Errors::SocketError) do
|
|
36
|
+
response(
|
|
37
|
+
request(env)
|
|
38
|
+
)
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
private
|
|
43
|
+
|
|
44
|
+
attr_reader :connection
|
|
45
|
+
|
|
46
|
+
def request(env)
|
|
47
|
+
connection.request(options(env))
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def response(r)
|
|
51
|
+
[r.status, r.headers, [r.body]]
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def options(env) # rubocop:disable Metrics/MethodLength
|
|
55
|
+
result = Hash[
|
|
56
|
+
method: env["REQUEST_METHOD"],
|
|
57
|
+
path: env["PATH_INFO"],
|
|
58
|
+
headers: {
|
|
59
|
+
"Content-Type" => env["CONTENT_TYPE"],
|
|
60
|
+
"Accept" => env["HTTP_ACCEPT"]
|
|
61
|
+
}
|
|
62
|
+
]
|
|
63
|
+
|
|
64
|
+
unless get?(env)
|
|
65
|
+
env["rack.input"].rewind
|
|
66
|
+
result[:body] = env["rack.input"].read
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
result
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def get?(env)
|
|
73
|
+
%w[GET HEAD].include?(env["REQUEST_METHOD"])
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
Excon.defaults[:ssl_verify_peer] = false
|
|
80
|
+
Excon.defaults[:ssl_verify_peer_host] = false
|
|
81
|
+
# Excon.defaults[:ssl_version] = :SSLv3
|
|
82
|
+
Excon.defaults[:middlewares].push(Excon::Middleware::RedirectFollower)
|
|
83
|
+
|
|
84
|
+
RSpec.configure do |config|
|
|
85
|
+
config.include Rack::Test::Methods, type: :integration
|
|
86
|
+
config.include RSpec::Support::RackTest, type: :integration
|
|
87
|
+
end
|