omu-support 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/CHANGELOG.md +0 -0
- data/LICENSE.md +674 -0
- data/Rakefile +16 -0
- data/lib/omu-support.rb +3 -0
- data/lib/omu_support.rb +13 -0
- data/lib/omu_support/core_ext.rb +9 -0
- data/lib/omu_support/core_ext/array.rb +13 -0
- data/lib/omu_support/core_ext/class.rb +27 -0
- data/lib/omu_support/core_ext/hash.rb +21 -0
- data/lib/omu_support/core_ext/integer.rb +7 -0
- data/lib/omu_support/core_ext/object.rb +90 -0
- data/lib/omu_support/core_ext/securerandom.rb +11 -0
- data/lib/omu_support/core_ext/string.rb +126 -0
- data/lib/omu_support/data/en.yml +1266 -0
- data/lib/omu_support/data/tr.yml +931 -0
- data/lib/omu_support/minitest.rb +6 -0
- data/lib/omu_support/minitest/association_helper.rb +48 -0
- data/lib/omu_support/minitest/callback_helper.rb +42 -0
- data/lib/omu_support/minitest/enumeration_helper.rb +21 -0
- data/lib/omu_support/minitest/validation_helper.rb +94 -0
- data/lib/omu_support/rest_client.rb +114 -0
- data/lib/omu_support/sensitive.rb +54 -0
- data/lib/omu_support/version.rb +7 -0
- data/test/core_ext/array_test.rb +14 -0
- data/test/core_ext/class_test.rb +63 -0
- data/test/core_ext/hash_test.rb +28 -0
- data/test/core_ext/integer_test.rb +9 -0
- data/test/core_ext/object_test.rb +20 -0
- data/test/core_ext/securerandom_test.rb +10 -0
- data/test/core_ext/string_test.rb +39 -0
- data/test/support_test.rb +9 -0
- data/test/test_helper.rb +7 -0
- metadata +103 -0
@@ -0,0 +1,48 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module OMU
|
4
|
+
module Support
|
5
|
+
module Minitest
|
6
|
+
module AssociationHelper
|
7
|
+
SUFFIX = 'Test'
|
8
|
+
RELATIONS = %i[belongs_to has_many has_one has_and_belongs_to_many].freeze
|
9
|
+
|
10
|
+
RELATIONS.each do |relation|
|
11
|
+
define_method(relation) do |attribute, **options|
|
12
|
+
association = relations_for(relation, attribute)
|
13
|
+
|
14
|
+
test "can respond to #{relation} #{attribute}" do
|
15
|
+
assert association
|
16
|
+
options.each do |key, value|
|
17
|
+
assert_equal association.options[key], value, "Option: #{key}"
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def accepts_nested_attributes_for(attribute, **options)
|
24
|
+
nested_attributes_options = klass.nested_attributes_options[attribute]
|
25
|
+
|
26
|
+
test "#{attribute} must be nested attribute" do
|
27
|
+
assert nested_attributes_options
|
28
|
+
options.each do |key, value|
|
29
|
+
assert_equal nested_attributes_options[key], value, "Option: #{key}"
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
def relations_for(relation, attribute)
|
37
|
+
klass.reflect_on_all_associations(relation).find do |association|
|
38
|
+
association.name == attribute
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def klass
|
43
|
+
to_s.delete_suffix(SUFFIX).constantize
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module OMU
|
4
|
+
module Support
|
5
|
+
module Minitest
|
6
|
+
module CallbackHelper
|
7
|
+
CALLBACKS = %i[
|
8
|
+
after_commit
|
9
|
+
after_create
|
10
|
+
after_destroy
|
11
|
+
after_find
|
12
|
+
after_initialize
|
13
|
+
after_rollback
|
14
|
+
after_save
|
15
|
+
after_touch
|
16
|
+
after_update
|
17
|
+
after_validation
|
18
|
+
around_create
|
19
|
+
around_destroy
|
20
|
+
around_save
|
21
|
+
around_update
|
22
|
+
before_create
|
23
|
+
before_destroy
|
24
|
+
before_save
|
25
|
+
before_update
|
26
|
+
before_validation
|
27
|
+
].freeze
|
28
|
+
|
29
|
+
CALLBACKS.each do |callback|
|
30
|
+
define_method(callback) do |action|
|
31
|
+
test "has #{callback} for #{action}" do
|
32
|
+
kind, method = callback.to_s.split('_')
|
33
|
+
klass = class_name.delete_suffix('Test').constantize
|
34
|
+
callbacks = klass.public_send("_#{method}_callbacks")
|
35
|
+
assert callbacks.select { |cb| cb.filter.eql?(action.to_sym) && cb.kind.eql?(kind.to_sym) }.any?
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module OMU
|
4
|
+
module Support
|
5
|
+
module Minitest
|
6
|
+
module EnumerationHelper
|
7
|
+
def enum(definitions)
|
8
|
+
definitions.each do |attribute, values|
|
9
|
+
values.each do |key, value|
|
10
|
+
test "has a enum key (#{key}) with a value of #{value}" do
|
11
|
+
klass = class_name.delete_suffix('Test').constantize
|
12
|
+
defined_value = klass.defined_enums.dig(attribute.to_s, key.to_s)
|
13
|
+
assert_equal defined_value, value, "Enum: #{attribute}"
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,94 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module OMU
|
4
|
+
module Support
|
5
|
+
module Minitest
|
6
|
+
module ValidationHelper
|
7
|
+
SUFFIX = 'Test'
|
8
|
+
|
9
|
+
def validates_presence_of(*attributes)
|
10
|
+
attributes.each do |attribute|
|
11
|
+
test "#{attribute} must be present (presence: true)" do
|
12
|
+
object.public_send("#{attribute}=", nil)
|
13
|
+
assert_not object.valid?
|
14
|
+
assert_not_empty object.errors[attribute]
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def validates_presence_of_nested_model(attribute, ids: nil)
|
20
|
+
test "nested model (#{attribute}) must be present" do
|
21
|
+
ids ||= "#{attribute.to_s.singularize}_ids"
|
22
|
+
object.public_send("#{ids}=", nil)
|
23
|
+
assert_not object.valid?
|
24
|
+
assert_not_empty object.errors[attribute]
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def validates_uniqueness_of(*attributes)
|
29
|
+
attributes.each do |attribute|
|
30
|
+
test "#{attribute} must be unique (uniqueness: true)" do
|
31
|
+
duplicate_object = object.dup
|
32
|
+
assert_not duplicate_object.valid?
|
33
|
+
assert_not_empty duplicate_object.errors[attribute]
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def validates_length_of(attribute, **option) # rubocop:disable Metrics/AbcSize,Metrics/MethodLength
|
39
|
+
option[:maximum] = 255 if option.blank?
|
40
|
+
|
41
|
+
controls = {
|
42
|
+
is: { value: 1, error_key: :wrong_length },
|
43
|
+
minimum: { value: -1, error_key: :too_short },
|
44
|
+
maximum: { value: 1, error_key: :too_long }
|
45
|
+
}
|
46
|
+
|
47
|
+
key = option.keys.first
|
48
|
+
value = option[key].to_i + controls.dig(key, :value).to_i
|
49
|
+
error_key = controls.dig(key, :error_key)
|
50
|
+
|
51
|
+
test "#{attribute} length must be #{option}" do
|
52
|
+
object.public_send("#{attribute}=", (0..value).map { ('a'..'z').to_a[rand(26)] }.join)
|
53
|
+
assert_not object.valid?
|
54
|
+
assert object.errors.details[attribute].pluck(:error).include?(error_key)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def validates_numericality_of(attribute)
|
59
|
+
test "#{attribute} must be a number" do
|
60
|
+
object.public_send("#{attribute}=", 'some string')
|
61
|
+
assert_not object.valid?
|
62
|
+
assert object.errors.details[attribute].pluck(:error).include?(:not_a_number)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def validates_numerical_range(attribute, **option) # rubocop:disable Metrics/AbcSize,Metrics/MethodLength
|
67
|
+
controls = {
|
68
|
+
greater_than: 0,
|
69
|
+
less_than: 0,
|
70
|
+
greater_than_or_equal_to: -1,
|
71
|
+
less_than_or_equal_to: 1
|
72
|
+
}
|
73
|
+
|
74
|
+
key = option.keys.first
|
75
|
+
value = option[key].to_i + controls[key].to_i
|
76
|
+
|
77
|
+
test "#{attribute} must be #{key} #{value}" do
|
78
|
+
object.public_send("#{attribute}=", value)
|
79
|
+
assert_not object.valid?
|
80
|
+
assert object.errors.details[attribute].pluck(:error).include?(key)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def self.extended(base)
|
85
|
+
super
|
86
|
+
|
87
|
+
base.define_method :object do
|
88
|
+
@object ||= class_name.delete_suffix(SUFFIX).constantize.take
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
@@ -0,0 +1,114 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'openssl'
|
4
|
+
|
5
|
+
module OMU
|
6
|
+
module Support
|
7
|
+
module RestClient
|
8
|
+
SUPPORTED_HTTP_METHODS = %i[
|
9
|
+
delete
|
10
|
+
get
|
11
|
+
patch
|
12
|
+
post
|
13
|
+
put
|
14
|
+
].freeze
|
15
|
+
|
16
|
+
private_constant :SUPPORTED_HTTP_METHODS
|
17
|
+
|
18
|
+
class Error < StandardError; end
|
19
|
+
|
20
|
+
class HTTPMethodError < Error; end
|
21
|
+
|
22
|
+
class UnsupportedHTTPOptionError < Error; end
|
23
|
+
|
24
|
+
class HTTPError < Error; end
|
25
|
+
|
26
|
+
class Request
|
27
|
+
SUPPORTED_HTTP_OPTIONS = {
|
28
|
+
open_timeout: 10,
|
29
|
+
read_timeout: 10,
|
30
|
+
use_ssl: false,
|
31
|
+
verify_mode: OpenSSL::SSL::VERIFY_NONE
|
32
|
+
}.freeze
|
33
|
+
|
34
|
+
HEADERS = {
|
35
|
+
'Content-Type' => 'application/json'
|
36
|
+
}.freeze
|
37
|
+
|
38
|
+
private_constant :SUPPORTED_HTTP_OPTIONS, :HEADERS
|
39
|
+
|
40
|
+
# rubocop:disable Style/IfUnlessModifier
|
41
|
+
def initialize(method, url, headers = {}, **http_options)
|
42
|
+
@method = method
|
43
|
+
@url = url
|
44
|
+
@headers = HEADERS.merge(headers)
|
45
|
+
|
46
|
+
unless method.in?(SUPPORTED_HTTP_METHODS)
|
47
|
+
raise HTTPMethodError, "unsupported HTTP method: #{method}"
|
48
|
+
end
|
49
|
+
|
50
|
+
build_http_object http_options
|
51
|
+
end
|
52
|
+
# rubocop:enable Style/IfUnlessModifier
|
53
|
+
|
54
|
+
def execute(payload = nil)
|
55
|
+
klass = "Net::HTTP::#{method.capitalize}".constantize
|
56
|
+
request = klass.new url, headers
|
57
|
+
Response.new @http.request(request, payload)
|
58
|
+
end
|
59
|
+
|
60
|
+
protected
|
61
|
+
|
62
|
+
attr_reader :method, :url, :headers
|
63
|
+
|
64
|
+
private
|
65
|
+
|
66
|
+
def build_http_object(http_options)
|
67
|
+
uri = URI.parse(url)
|
68
|
+
@http = Net::HTTP.new uri.host, uri.port
|
69
|
+
|
70
|
+
unsupported_options = http_options.keys - SUPPORTED_HTTP_OPTIONS.keys
|
71
|
+
unless unsupported_options.empty?
|
72
|
+
raise UnsupportedHTTPOptionError, "unsupported HTTP options: #{unsupported_options}"
|
73
|
+
end
|
74
|
+
|
75
|
+
http_options.each { |option, value| @http.public_send "#{option}=", value }
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
private_constant :Request
|
80
|
+
|
81
|
+
Response = Struct.new(:http_response) do
|
82
|
+
delegate :body, to: :http_response
|
83
|
+
|
84
|
+
def decode
|
85
|
+
body && JSON.parse(body, symbolize_names: true)
|
86
|
+
rescue JSON::JSONError => e
|
87
|
+
Rails.logger.warn("JSON parse error: #{e}")
|
88
|
+
nil
|
89
|
+
end
|
90
|
+
|
91
|
+
def error!
|
92
|
+
http_response.error! unless ok?
|
93
|
+
rescue Net::HTTPError, Net::HTTPFatalError => e
|
94
|
+
Rails.logger.warn("HTTP error: #{e}")
|
95
|
+
raise HTTPError
|
96
|
+
end
|
97
|
+
|
98
|
+
def ok?
|
99
|
+
http_response.is_a?(Net::HTTPOK)
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
private_constant :Response
|
104
|
+
|
105
|
+
module_function
|
106
|
+
|
107
|
+
SUPPORTED_HTTP_METHODS.each do |method|
|
108
|
+
define_method(method) do |url, headers: {}, payload: nil, **http_options|
|
109
|
+
Request.new(method, url, headers, **http_options).execute(payload)
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'fileutils'
|
4
|
+
|
5
|
+
module OMU
|
6
|
+
module Support
|
7
|
+
# Sensitive file read/write
|
8
|
+
module Sensitive
|
9
|
+
module_function
|
10
|
+
|
11
|
+
def read(path)
|
12
|
+
encryptor(expand_path(path)).read
|
13
|
+
end
|
14
|
+
|
15
|
+
def readlines(path)
|
16
|
+
read(path).split "\n"
|
17
|
+
end
|
18
|
+
|
19
|
+
def write(path, content)
|
20
|
+
unless Dir.exist?(dir = File.dirname(file = expand_path(path)))
|
21
|
+
FileUtils.mkdir_p(dir)
|
22
|
+
end
|
23
|
+
encryptor(file).write(content)
|
24
|
+
end
|
25
|
+
|
26
|
+
def writelines(path, contents)
|
27
|
+
write(path, contents.join("\n"))
|
28
|
+
end
|
29
|
+
|
30
|
+
def read_write(path)
|
31
|
+
write(path, File.read(path))
|
32
|
+
end
|
33
|
+
|
34
|
+
def content_decrypt(content)
|
35
|
+
encryptor('').__send__(:decrypt, content)
|
36
|
+
end
|
37
|
+
|
38
|
+
EXT = '.enc'
|
39
|
+
|
40
|
+
def expand_path(path)
|
41
|
+
File.expand_path(path, Rails.root) + EXT
|
42
|
+
end
|
43
|
+
|
44
|
+
def encryptor(file)
|
45
|
+
ActiveSupport::EncryptedFile.new(
|
46
|
+
content_path: file,
|
47
|
+
env_key: 'RAILS_MASTER_KEY',
|
48
|
+
key_path: Rails.root.join('config/master.key'),
|
49
|
+
raise_if_missing_key: true
|
50
|
+
)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'test_helper'
|
4
|
+
|
5
|
+
class ArrayTest < ActiveSupport::TestCase
|
6
|
+
test 'clip works' do
|
7
|
+
assert_equal %w[foo bar], %w[foo bar baz quux].clip(2)
|
8
|
+
end
|
9
|
+
|
10
|
+
test 'join_affixed works' do
|
11
|
+
assert_equal 'aaa.foo-bar-baz-quux_zzz',
|
12
|
+
%w[foo bar baz quux].join_affixed(prefix: 'aaa.', interfix: '-', suffix: '_zzz')
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'test_helper'
|
4
|
+
|
5
|
+
class ClassTest < ActiveSupport::TestCase
|
6
|
+
module TestClassesOne
|
7
|
+
class NonConveyedBase
|
8
|
+
class_attribute :array_attr, default: %w[foo bar]
|
9
|
+
class_attribute :hash_attr, default: { x: 'foo', y: 'bar' }
|
10
|
+
end
|
11
|
+
|
12
|
+
class ConveyedBase
|
13
|
+
class_attribute :array_attr, default: %w[foo bar]
|
14
|
+
class_attribute :hash_attr, default: { x: 'foo', y: 'bar' }
|
15
|
+
|
16
|
+
inherited_by_conveying_attributes :array_attr, :hash_attr
|
17
|
+
end
|
18
|
+
|
19
|
+
class ChildFromNonConveyedBase < NonConveyedBase
|
20
|
+
array_attr << 'child_addition'
|
21
|
+
hash_attr.merge! z: 'child_addition'
|
22
|
+
end
|
23
|
+
|
24
|
+
class ChildFromConveyedBase < ConveyedBase
|
25
|
+
array_attr << 'child_addition'
|
26
|
+
hash_attr.merge! z: 'child_addition'
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
test 'inherited_by_conveying_attributes works' do
|
31
|
+
assert_equal %w[foo bar child_addition], TestClassesOne::NonConveyedBase.array_attr
|
32
|
+
assert_equal %w[foo bar], TestClassesOne::ConveyedBase.array_attr
|
33
|
+
assert_equal %w[foo bar child_addition], TestClassesOne::ChildFromNonConveyedBase.array_attr
|
34
|
+
assert_equal %w[foo bar child_addition], TestClassesOne::ChildFromConveyedBase.array_attr
|
35
|
+
|
36
|
+
assert_equal Hash[x: 'foo', y: 'bar', z: 'child_addition'], TestClassesOne::NonConveyedBase.hash_attr
|
37
|
+
assert_equal Hash[x: 'foo', y: 'bar'], TestClassesOne::ConveyedBase.hash_attr
|
38
|
+
assert_equal Hash[x: 'foo', y: 'bar', z: 'child_addition'], TestClassesOne::ChildFromNonConveyedBase.hash_attr
|
39
|
+
assert_equal Hash[x: 'foo', y: 'bar', z: 'child_addition'], TestClassesOne::ChildFromConveyedBase.hash_attr
|
40
|
+
end
|
41
|
+
|
42
|
+
module TestClassesTwo
|
43
|
+
class ConveyedBase
|
44
|
+
class_attribute :array_attr, default: %w[foo bar]
|
45
|
+
|
46
|
+
inherited_by_conveying_attributes :array_attr do |child|
|
47
|
+
child.class_attribute :other, default: 'ok'
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
class ChildFromConveyedBase < ConveyedBase
|
52
|
+
array_attr << 'child_addition'
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
test 'inherited_by_conveying_attributes with block works' do
|
57
|
+
assert_equal %w[foo bar], TestClassesTwo::ConveyedBase.array_attr
|
58
|
+
assert_equal %w[foo bar child_addition], TestClassesTwo::ChildFromConveyedBase.array_attr
|
59
|
+
|
60
|
+
assert_not TestClassesTwo::ConveyedBase.respond_to? :other
|
61
|
+
assert_equal 'ok', TestClassesTwo::ChildFromConveyedBase.other
|
62
|
+
end
|
63
|
+
end
|