hephaestus 0.6.4 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,89 @@
1
+ # typed: false
2
+ # frozen_string_literal: true
3
+
4
+ require "openapi_first"
5
+
6
+ module Hephaestus
7
+ module Middleware
8
+ class OpenapiValidation
9
+ API_PATH_PREFIX = "/api/"
10
+ API_SPEC_PATH = Rails.root.join("lib/schemas/api/2023-03-06/openapi.json")
11
+
12
+ def initialize(app, ignore_path: "/settings", match_path: API_PATH_PREFIX, limit_methods_to: nil, spec: API_SPEC_PATH)
13
+ @app = app
14
+ @ignore_path = ignore_path
15
+ @match_path = match_path
16
+ @limit_methods_to = limit_methods_to
17
+ @spec = OpenapiFirst.load(spec)
18
+ end
19
+
20
+ def call(env)
21
+ request = Rack::Request.new(env)
22
+
23
+ return @app.call(env) if request.path.include?(@ignore_path)
24
+ return @app.call(env) unless request.path.starts_with?(@match_path)
25
+
26
+ return @app.call(env) if @limit_methods_to.present? && @limit_methods_to.exclude?(request.request_method)
27
+
28
+ begin
29
+ # force content-type to JSON
30
+ env["CONTENT_TYPE"] = "application/json" if env["CONTENT_TYPE"] != "application/json"
31
+
32
+ validated_request = @spec.validate_request(request)
33
+
34
+ return @app.call(env) if validated_request.valid?
35
+
36
+ case validated_request.error
37
+ when OpenapiFirst::Schema::ValidationError
38
+ error_arr = format_arr(validated_request.error.errors.map(&:message))
39
+ Rails.logger.error(error_arr) if print_user_api_errors?
40
+ [Hephaestus::HTTP::BAD_REQUEST_I, { "Content-Type" => "application/json" }, [error_arr]]
41
+ else
42
+ case validated_request.error.type
43
+ when :not_found
44
+ [Hephaestus::HTTP::NOT_FOUND_I, { "Content-Type" => "application/json" }, [format_str("Not Found")]]
45
+ else
46
+ error_message = if validated_request.error.errors.present?
47
+ format_arr(validated_request.error.errors.map(&:message))
48
+ else
49
+ format_str(validated_request.error.message)
50
+ end
51
+
52
+ Rails.logger.error(error_message) if print_user_api_errors?
53
+ [Hephaestus::HTTP::BAD_REQUEST_I, { "Content-Type" => "application/json" }, [error_message]]
54
+ end
55
+ end
56
+ rescue StandardError => e
57
+ raise e unless Rails.env.production?
58
+
59
+ Rails.logger.error(
60
+ "openapi.request_validation.error",
61
+ path: request.path,
62
+ method: request.env["REQUEST_METHOD"],
63
+ error_message: e.message,
64
+ )
65
+ end
66
+ end
67
+
68
+ private def format_str(error)
69
+ {
70
+ errors: [
71
+ {
72
+ message: error,
73
+ },
74
+ ],
75
+ }.to_json
76
+ end
77
+
78
+ private def format_arr(errors)
79
+ {
80
+ errors: errors.each_with_object([]) do |error, arr|
81
+ arr << {
82
+ message: error,
83
+ }
84
+ end,
85
+ }.to_json
86
+ end
87
+ end
88
+ end
89
+ end
@@ -0,0 +1,50 @@
1
+ # typed: false
2
+ # frozen_string_literal: true
3
+
4
+ require "opentelemetry"
5
+ require "active_support/parameter_filter"
6
+
7
+ module Hephaestus
8
+ module Middleware
9
+ class TracingAttributes
10
+ attr_reader :app
11
+
12
+ HTTP_REQUEST_CONTENT_LENGTH = "http.request_content_length"
13
+ HTTP_REQUEST_BODY = "http.request.body"
14
+
15
+ def initialize(app)
16
+ @app = app
17
+ @filterer = ActiveSupport::ParameterFilter.new(Rails.application.config.filter_parameters)
18
+ end
19
+
20
+ def call(env)
21
+ request = ActionDispatch::Request.new(env.dup)
22
+
23
+ OpenTelemetry::Trace.current_span.add_attributes({
24
+ "version" => Hephaestus::Engine::GIT_SHA,
25
+ HTTP_REQUEST_CONTENT_LENGTH => env["CONTENT_LENGTH"].to_i,
26
+ HTTP_REQUEST_BODY => process_tracing_body(request),
27
+ })
28
+
29
+ app.call(env)
30
+ end
31
+
32
+ def process_tracing_body(request)
33
+ if Rails.configuration.respond_to?(:tracing_body_filters)
34
+ Rails.configuration.tracing_body_filters.each do |path, filter|
35
+ next unless request.path.starts_with?(path)
36
+
37
+ result = filter.call(request)
38
+
39
+ return result if result.is_a?(String)
40
+
41
+ return result.to_json
42
+ end
43
+ end
44
+
45
+ params = request.params
46
+ @filterer.filter(params).to_json
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,7 @@
1
+ # typed: false
2
+ # frozen_string_literal: true
3
+
4
+ module Hephaestus
5
+ module Middleware
6
+ end
7
+ end
@@ -0,0 +1,105 @@
1
+ # typed: false
2
+ # frozen_string_literal: true
3
+
4
+ module Hephaestus
5
+ module API
6
+ module TestHelpers
7
+ include Rack::Test::Methods
8
+
9
+ def plug(method, path, headers: {}, version: Rails.configuration.yetto_api_version, body: {}, parse: true)
10
+ prepended_path = prepend_plug_path(version, path)
11
+
12
+ http_call(method, prepended_path, headers: headers, version: version, body: body, parse: true)
13
+ end
14
+
15
+ def api(method, path, headers: {}, version: Rails.configuration.yetto_api_version, body: {}, parse: true)
16
+ prepended_path = prepend_api_path(version, path)
17
+
18
+ http_call(method, prepended_path, headers: headers, version: version, body: body, parse: true)
19
+ end
20
+
21
+ def http_call(method, path, headers: {}, version: nil, body: {}, parse: true)
22
+ # explicitly assert headers cannot be nil
23
+ if headers.nil?
24
+ send(method, path, body.to_json)
25
+ else
26
+ headers["HTTP_ACCEPT"] ||= headers.fetch("HTTP_ACCEPT", "application/json")
27
+ headers["CONTENT_TYPE"] ||= headers.fetch("CONTENT_TYPE", "application/json")
28
+ body = body.to_json if headers["CONTENT_TYPE"] == "application/json"
29
+ send(method, path, body, headers)
30
+ end
31
+
32
+ JSON.parse(last_response.body) if last_response.body.present? && parse
33
+ end
34
+
35
+ def assert_response(expected_status, expected_body = nil)
36
+ expected_status = case expected_status
37
+ when :ok, :okay
38
+ Hephaestus::HTTP::OK_I
39
+ when :created
40
+ Hephaestus::HTTP::CREATED_I
41
+ when :no_content
42
+ Hephaestus::HTTP::NO_CONTENT_I
43
+ when :redirect
44
+ Hephaestus::HTTP::FOUND_I
45
+ when :bad_request
46
+ Hephaestus::HTTP::BAD_REQUEST_I
47
+ when :unauthorized
48
+ Hephaestus::HTTP::UNAUTHORIZED_I
49
+ when :forbidden
50
+ Hephaestus::HTTP::FORBIDDEN_I
51
+ when :not_found
52
+ Hephaestus::HTTP::NOT_FOUND_I
53
+ when :not_acceptable
54
+ Hephaestus::HTTP::NOT_ACCEPTABLE_I
55
+ when :service_unavailable
56
+ Hephaestus::HTTP::SERVICE_UNAVAILABLE_I
57
+ else
58
+ raise ArgumentError, "Unknown status: #{expected_status}"
59
+ end
60
+
61
+ assert_equal(expected_status, last_response.status)
62
+ end
63
+
64
+ def prepend_plug_path(version, path)
65
+ "/#{plug_shortname}/#{version}#{path}"
66
+ end
67
+
68
+ def prepend_api_path(version, path)
69
+ "/api/#{version}#{path}"
70
+ end
71
+
72
+ def headers(event, body)
73
+ case event
74
+ when :after_create
75
+ {
76
+ Hephaestus::Headers::HEADER_EVENT => "after_create",
77
+ Hephaestus::Headers::HEADER_RECORD_TYPE => "plug_installation",
78
+ }
79
+ when :after_update_plug_installation
80
+ {
81
+ Hephaestus::Headers::HEADER_EVENT => "after_update",
82
+ Hephaestus::Headers::HEADER_RECORD_TYPE => "plug_installation",
83
+ }
84
+ when :after_update_inbox
85
+ {
86
+ Hephaestus::Headers::HEADER_EVENT => "after_update",
87
+ Hephaestus::Headers::HEADER_RECORD_TYPE => "inbox",
88
+ }
89
+ when :after_update_organization
90
+ {
91
+ Hephaestus::Headers::HEADER_EVENT => "after_update",
92
+ Hephaestus::Headers::HEADER_RECORD_TYPE => "organization",
93
+ }
94
+ when :after_destroy_plug_installation
95
+ {
96
+ Hephaestus::Headers::HEADER_EVENT => "after_destroy",
97
+ Hephaestus::Headers::HEADER_RECORD_TYPE => "plug_installation",
98
+ }
99
+ else
100
+ {}
101
+ end.merge(Hephaestus::Headers::HEADER_SIGNATURE => yetto_auth_header(body))
102
+ end
103
+ end
104
+ end
105
+ end
@@ -0,0 +1,21 @@
1
+ # typed: false
2
+ # frozen_string_literal: true
3
+
4
+ module Hephaestus
5
+ module Webmocks
6
+ module BrowserWebmock
7
+ def browser_mock(status: 200)
8
+ browser_mock = Object.new
9
+ Ferrum::Browser.stubs(:new).returns(browser_mock)
10
+ browser_mock.stubs(:go_to)
11
+ browser_mock.stubs(:quit)
12
+ response_mock = Ferrum::Network::Response.new("", "")
13
+ response_mock.stubs(:wait_for_idle).returns(true)
14
+ network_response_mock = Object.new
15
+ browser_mock.stubs(:network).returns(network_response_mock)
16
+ network_response_mock.stubs(:response).returns(response_mock)
17
+ response_mock.stubs(:status).returns(status)
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,21 @@
1
+ # typed: false
2
+ # frozen_string_literal: true
3
+
4
+ module Hephaestus
5
+ module Webmocks
6
+ module SlackWebmock
7
+ def assert_requested_send_to_slack_log
8
+ assert_requested(:post, "https://slack.com/the_log_room")
9
+ end
10
+
11
+ def stub_send_to_slack_log
12
+ stub_request(:post, "https://slack.com/the_log_room")
13
+ .to_return(
14
+ status: 200,
15
+ headers: { content_type: "application/json; charset=utf-8" },
16
+ body: {}.to_json,
17
+ )
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,195 @@
1
+ # typed: false
2
+ # frozen_string_literal: true
3
+
4
+ module Hephaestus
5
+ module Webmocks
6
+ module YettoWebmock
7
+ def yetto_auth_header(payload, signing_secret = SIGNING_SECRET)
8
+ "sha256=#{OpenSSL::HMAC.hexdigest(Hephaestus::ValidatesFromYetto::SHA256_DIGEST, signing_secret, payload.to_json)}"
9
+ end
10
+
11
+ def assert_requested_post_access_token(plug_installation_id, times: 1)
12
+ assert_requested(:post, "#{Hephaestus::YettoService::YETTO_API_VERSION_TLD}/plug/installations/#{plug_installation_id}/access_tokens", times: times)
13
+ end
14
+
15
+ def stub_post_access_token(plug_installation_id)
16
+ response = {
17
+ token: Faker::Alphanumeric.alphanumeric(number: 26).upcase,
18
+ }
19
+
20
+ stub_request(:post, "#{Hephaestus::YettoService::YETTO_API_VERSION_TLD}/plug/installations/#{plug_installation_id}/access_tokens")
21
+ .to_return(
22
+ body: response.to_json,
23
+ )
24
+ end
25
+
26
+ def assert_requested_get_plug_installation(plug_installation_id, times: 1)
27
+ assert_requested(:get, "#{Hephaestus::YettoService::YETTO_API_VERSION_TLD}/installations/#{plug_installation_id}", times:)
28
+ end
29
+
30
+ def stub_get_plug_installation(plug_installation_id, response: {}, status: 200)
31
+ stub_request(:get, "#{Hephaestus::YettoService::YETTO_API_VERSION_TLD}/installations/#{plug_installation_id}")
32
+ .to_return(
33
+ status: status,
34
+ headers: { content_type: "application/json; charset=utf-8" },
35
+ body: response.to_json,
36
+ )
37
+ end
38
+
39
+ def assert_requested_update_plug_installation(plug_installation_id)
40
+ assert_requested(:patch, "#{Hephaestus::YettoService::YETTO_API_VERSION_TLD}/installations/#{plug_installation_id}")
41
+ end
42
+
43
+ def assert_not_requested_update_plug_installation(plug_installation_id)
44
+ assert_requested(:patch, "#{Hephaestus::YettoService::YETTO_API_VERSION_TLD}/installations/#{plug_installation_id}", times: 0)
45
+ end
46
+
47
+ def stub_update_plug_installation(plug_installation_id, params, response: {}, status: 200)
48
+ stub_post_access_token(plug_installation_id)
49
+
50
+ response[:plug_installation] = { id: plug_installation_id }
51
+
52
+ stub_request(:patch, "#{Hephaestus::YettoService::YETTO_API_VERSION_TLD}/installations/#{plug_installation_id}")
53
+ .with(
54
+ body: params,
55
+ )
56
+ .to_return(
57
+ status: status,
58
+ headers: { content_type: "application/json; charset=utf-8" },
59
+ body: response.to_json,
60
+ )
61
+ end
62
+
63
+ def assert_requested_create_conversation(plug_installation_id, inbox_id)
64
+ assert_requested(:post, "#{Hephaestus::YettoService::YETTO_API_VERSION_TLD}/inboxes/#{inbox_id}/conversations")
65
+ end
66
+
67
+ def stub_create_conversation(plug_installation_id, inbox_id, payload, response: {})
68
+ stub_post_access_token(plug_installation_id)
69
+
70
+ response[:plug_installation] = { id: plug_installation_id }
71
+
72
+ stub_request(:post, "#{Hephaestus::YettoService::YETTO_API_VERSION_TLD}/inboxes/#{inbox_id}/conversations")
73
+ .with(
74
+ body: payload,
75
+ )
76
+ .to_return(
77
+ status: 200,
78
+ headers: { content_type: "application/json; charset=utf-8" },
79
+ body: response.to_json,
80
+ )
81
+ end
82
+
83
+ def assert_requested_get_plug_installations(filter: "")
84
+ assert_requested(:get, "#{Hephaestus::YettoService::YETTO_API_VERSION_TLD}/plug/installations?#{filter}")
85
+ end
86
+
87
+ def stub_get_plug_installations(response, status: 200, filter: "")
88
+ stub_request(:get, "#{Hephaestus::YettoService::YETTO_API_VERSION_TLD}/plug/installations?#{filter}")
89
+ .to_return(
90
+ status: status,
91
+ headers: { content_type: "application/json; charset=utf-8" },
92
+ body: response.to_json,
93
+ )
94
+ end
95
+
96
+ def assert_requested_get_messages_in_inbox(plug_installation_id, inbox_id, filter: "")
97
+ assert_requested(:get, "#{Hephaestus::YettoService::YETTO_API_VERSION_TLD}/inboxes/#{inbox_id}/messages?#{filter}")
98
+ end
99
+
100
+ def stub_get_messages_in_inbox(plug_installation_id, inbox_id, response, filter: "")
101
+ stub_post_access_token(plug_installation_id)
102
+ stub_request(:get, "#{Hephaestus::YettoService::YETTO_API_VERSION_TLD}/inboxes/#{inbox_id}/messages?#{filter}")
103
+ .to_return(
104
+ status: 200,
105
+ body: response.to_json,
106
+ )
107
+ end
108
+
109
+ def assert_requested_get_messages_in_conversation(plug_installation_id, conversation_id, filter: "")
110
+ assert_requested(:get, "#{::Hephaestus::YettoService::YETTO_API_VERSION_TLD}/conversations/#{conversation_id}/messages?#{filter}")
111
+ end
112
+
113
+ def stub_get_messages_in_conversation(plug_installation_id, conversation_id, response, status: 200, filter: "")
114
+ stub_post_access_token(plug_installation_id)
115
+
116
+ stub_request(:get, "#{::Hephaestus::YettoService::YETTO_API_VERSION_TLD}/conversations/#{conversation_id}/messages?#{filter}")
117
+ .to_return(
118
+ status: status,
119
+ headers: { content_type: "application/json; charset=utf-8" },
120
+ body: response.to_json,
121
+ )
122
+ end
123
+
124
+ def stub_add_message_to_conversation(plug_installation_id, conversation_id, payload, response: {}, status: 200)
125
+ stub_post_access_token(plug_installation_id)
126
+
127
+ response[:plug_installation] = { id: plug_installation_id }
128
+
129
+ stub_request(:post, "#{Hephaestus::YettoService::YETTO_API_VERSION_TLD}/conversations/#{conversation_id}/messages")
130
+ .with(
131
+ body: payload,
132
+ )
133
+ .to_return(
134
+ status: status,
135
+ headers: { content_type: "application/json; charset=utf-8" },
136
+ body: response.to_json,
137
+ )
138
+ end
139
+
140
+ def assert_requested_add_message_to_conversation(plug_installation_id, conversation_id)
141
+ assert_requested(:post, "#{Hephaestus::YettoService::YETTO_API_VERSION_TLD}/conversations/#{conversation_id}/messages")
142
+ end
143
+
144
+ def assert_requested_create_message(plug_installation_id, message_id)
145
+ assert_requested(:post, "#{::Hephaestus::YettoService::YETTO_API_VERSION_TLD}/messages/#{message_id}/replies")
146
+ end
147
+
148
+ def stub_create_message(plug_installation_id, message_id, payload)
149
+ stub_post_access_token(plug_installation_id)
150
+
151
+ stub_request(:post, "#{::Hephaestus::YettoService::YETTO_API_VERSION_TLD}/messages/#{message_id}/replies")
152
+ .with(
153
+ body: payload,
154
+ )
155
+ .to_return(
156
+ status: 200,
157
+ headers: { content_type: "application/json; charset=utf-8" },
158
+ body: {}.to_json,
159
+ )
160
+ end
161
+
162
+ def assert_requested_update_message(plug_installation_id, message_id)
163
+ assert_requested(:patch, "#{::Hephaestus::YettoService::YETTO_API_VERSION_TLD}/messages/#{message_id}")
164
+ end
165
+
166
+ def stub_update_message(plug_installation_id, message_id, payload)
167
+ stub_post_access_token(plug_installation_id)
168
+ stub_request(:patch, "#{::Hephaestus::YettoService::YETTO_API_VERSION_TLD}/messages/#{message_id}")
169
+ .with(
170
+ body: payload,
171
+ )
172
+ .to_return(
173
+ status: 200,
174
+ headers: { content_type: "application/json; charset=utf-8" },
175
+ body: {}.to_json,
176
+ )
177
+ end
178
+
179
+ def assert_requested_find_message_in_conversation_by_metadata(plug_installation_id, conversation_id)
180
+ assert_requested(:get, "#{Hephaestus::YettoService::YETTO_API_VERSION_TLD}/conversations/#{conversation_id}/messages?#{filter}")
181
+ end
182
+
183
+ def stub_find_message_in_conversation(plug_installation_id, conversation_id, response, status: 200, filter: "")
184
+ stub_post_access_token(plug_installation_id)
185
+
186
+ stub_request(:get, "#{Hephaestus::YettoService::YETTO_API_VERSION_TLD}/conversations/#{conversation_id}/messages?#{filter}")
187
+ .to_return(
188
+ status: status,
189
+ headers: { content_type: "application/json; charset=utf-8" },
190
+ body: response.to_json,
191
+ )
192
+ end
193
+ end
194
+ end
195
+ end
@@ -0,0 +1,35 @@
1
+ # typed: false
2
+ # frozen_string_literal: true
3
+
4
+ module ActiveSupport
5
+ class TestCase
6
+ # Run tests in parallel with specified workers
7
+ parallelize(workers: :number_of_processors)
8
+
9
+ def setup
10
+ @organization_id ||= "org_#{Faker::Alphanumeric.alphanumeric(number: 26).upcase}"
11
+ @plug_installation_id ||= "pli_#{Faker::Alphanumeric.alphanumeric(number: 26).upcase}"
12
+ @inbox_id ||= "ibx_#{Faker::Alphanumeric.alphanumeric(number: 26).upcase}"
13
+ @plug_id ||= "plg_#{Faker::Alphanumeric.alphanumeric(number: 26).upcase}"
14
+ @message_id ||= "msg_#{Faker::Alphanumeric.alphanumeric(number: 26).upcase}"
15
+ @conversation_id ||= "cnv_#{Faker::Alphanumeric.alphanumeric(number: 26).upcase}"
16
+ end
17
+
18
+ def file_fixture_path(dir, name)
19
+ Rails.root.join("test", "fixtures", "files", dir, name)
20
+ end
21
+
22
+ def assert_expected_args(expected_args)
23
+ lambda do |job_args_arr|
24
+ job_args_arr.first.each do |actual_args|
25
+ actual_key = actual_args.first
26
+ actual_value = actual_args.second
27
+
28
+ expected_value = expected_args[actual_key]
29
+
30
+ assert_equal(expected_value, actual_value, "Expected `#{actual_key}` to be `#{expected_value.nil? ? "nil" : expected_value}`, but was `#{actual_value.nil? ? "nil" : actual_value}`")
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,45 @@
1
+ # typed: false
2
+ # frozen_string_literal: true
3
+
4
+ ENV["RAILS_ENV"] ||= "test"
5
+
6
+ require_relative File.join($LOAD_PATH.last, "..", "config", "environment.rb")
7
+
8
+ module Hephaestus
9
+ module TestHelper
10
+ require "minitest/autorun"
11
+ require "minitest/pride"
12
+
13
+ require "debug" if ENV.fetch("DEBUG", false)
14
+
15
+ if ENV["COVERAGE"] == "1"
16
+ require "simplecov"
17
+ require "simplecov-console"
18
+
19
+ SimpleCov.start("rails")
20
+
21
+ # do not crash on failure; we want a distinct job to report the coverage error
22
+ module SimpleCov
23
+ class << self
24
+ def result_exit_status(_)
25
+ SimpleCov::ExitCodes::SUCCESS
26
+ end
27
+ end
28
+ end
29
+ end
30
+
31
+ require "webmock"
32
+ require "httpx/adapters/webmock"
33
+ require "webmock/minitest"
34
+ WebMock.enable!
35
+ WebMock.disable_net_connect!(allow_localhost: true)
36
+
37
+ require_relative "support/rails"
38
+
39
+ # Load everything else from engine's test/support
40
+ Dir[File.expand_path("support/hephaestus/**/*.rb", __dir__)].each { |rb| require(rb) }
41
+
42
+ # https://github.com/freerange/mocha#rails
43
+ require "mocha/minitest"
44
+ end
45
+ end
@@ -2,8 +2,8 @@
2
2
  # frozen_string_literal: true
3
3
 
4
4
  module Hephaestus
5
- VERSION = "0.6.4"
6
- RAILS_VERSION = "~> 7.0"
5
+ VERSION = "0.7.0"
6
+ RAILS_VERSION = ">= 8.0"
7
7
  RUBY_VERSION = File
8
8
  .read("#{File.dirname(__FILE__)}/../../.ruby-version")
9
9
  .strip
data/lib/hephaestus.rb CHANGED
@@ -10,6 +10,7 @@ end
10
10
  require "debug" if debugging?
11
11
 
12
12
  require "hephaestus/version"
13
+ require "hephaestus/engine"
13
14
  require "hephaestus/exit_on_failure"
14
15
 
15
16
  require "hephaestus/generators/app_generator"
@@ -0,0 +1,7 @@
1
+ # typed: false
2
+ # frozen_string_literal: true
3
+
4
+ # desc "Explaining what the task does"
5
+ # task :hephaestus do
6
+ # # Task goes here
7
+ # end
@@ -0,0 +1,9 @@
1
+ # typed: false
2
+ # frozen_string_literal: true
3
+
4
+ require "rubygems/package_task"
5
+
6
+ GEMSPEC = Bundler.load_gemspec(File.join(__dir__, "..", "..", "hephaestus.gemspec")) unless defined?(GEMSPEC)
7
+ gem_path = Gem::PackageTask.new(GEMSPEC).define
8
+ desc "Package the ruby gem"
9
+ task "package" => [gem_path]
@@ -0,0 +1,6 @@
1
+ # typed: false
2
+ # frozen_string_literal: true
3
+
4
+ require "rubocop/rake_task"
5
+
6
+ RuboCop::RakeTask.new
data/lib/version.rb ADDED
@@ -0,0 +1,6 @@
1
+ # typed: false
2
+ # frozen_string_literal: true
3
+
4
+ module Hephaestus
5
+ VERSION = "0.1.0"
6
+ end
@@ -48,6 +48,7 @@ class AppController < ApplicationController
48
48
  text_content: text_body,
49
49
  is_public: true,
50
50
  author: {
51
+ version: "2023-03-06",
51
52
  name: from_email,
52
53
  },
53
54
  attachments: bparam_attachments,