omu-support 0.1.1
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/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
|