omu-support 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'minitest/association_helper'
4
+ require_relative 'minitest/callback_helper'
5
+ require_relative 'minitest/enumeration_helper'
6
+ require_relative 'minitest/validation_helper'
@@ -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,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module OMU
4
+ module Support
5
+ VERSION = '0.1.1'
6
+ end
7
+ 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