snapshot_inspector 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (43) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +227 -0
  4. data/Rakefile +8 -0
  5. data/app/assets/config/snapshot_inspector/manifest.js +2 -0
  6. data/app/assets/javascripts/snapshot_inspector/application.js +1 -0
  7. data/app/assets/stylesheets/snapshot_inspector/application.css +33 -0
  8. data/app/assets/stylesheets/snapshot_inspector/snapshots/mail.css +48 -0
  9. data/app/assets/stylesheets/snapshot_inspector/snapshots/not_found.css +15 -0
  10. data/app/assets/stylesheets/snapshot_inspector/snapshots/response.css +9 -0
  11. data/app/assets/stylesheets/snapshot_inspector/snapshots.css +73 -0
  12. data/app/controllers/snapshot_inspector/application_controller.rb +14 -0
  13. data/app/controllers/snapshot_inspector/snapshots/mail_controller.rb +57 -0
  14. data/app/controllers/snapshot_inspector/snapshots/response_controller.rb +15 -0
  15. data/app/controllers/snapshot_inspector/snapshots_controller.rb +7 -0
  16. data/app/helpers/snapshot_inspector/application_helper.rb +15 -0
  17. data/app/helpers/snapshot_inspector/snapshots_helper.rb +37 -0
  18. data/app/mailers/snapshot_inspector/application_mailer.rb +6 -0
  19. data/app/models/snapshot_inspector/snapshot/context.rb +49 -0
  20. data/app/models/snapshot_inspector/snapshot/mail_type.rb +35 -0
  21. data/app/models/snapshot_inspector/snapshot/response_type.rb +19 -0
  22. data/app/models/snapshot_inspector/snapshot/rspec_context.rb +52 -0
  23. data/app/models/snapshot_inspector/snapshot/test_unit_context.rb +44 -0
  24. data/app/models/snapshot_inspector/snapshot/type.rb +52 -0
  25. data/app/models/snapshot_inspector/snapshot.rb +86 -0
  26. data/app/views/layouts/snapshot_inspector/application.html.erb +18 -0
  27. data/app/views/snapshot_inspector/snapshots/index.html.erb +29 -0
  28. data/app/views/snapshot_inspector/snapshots/mail/show.html.erb +107 -0
  29. data/app/views/snapshot_inspector/snapshots/not_found.html.erb +8 -0
  30. data/app/views/snapshot_inspector/snapshots/response/raw.html.erb +1 -0
  31. data/app/views/snapshot_inspector/snapshots/response/show.html.erb +1 -0
  32. data/config/importmap.rb +12 -0
  33. data/config/routes.rb +9 -0
  34. data/lib/minitest/snapshot_inspector_plugin.rb +28 -0
  35. data/lib/snapshot_inspector/engine.rb +67 -0
  36. data/lib/snapshot_inspector/storage.rb +60 -0
  37. data/lib/snapshot_inspector/test/action_mailer_headers.rb +18 -0
  38. data/lib/snapshot_inspector/test/rspec_helpers.rb +45 -0
  39. data/lib/snapshot_inspector/test/test_unit_helpers.rb +48 -0
  40. data/lib/snapshot_inspector/version.rb +3 -0
  41. data/lib/snapshot_inspector.rb +42 -0
  42. data/lib/tasks/tmp.rake +10 -0
  43. metadata +159 -0
@@ -0,0 +1,35 @@
1
+ require "mail"
2
+
3
+ module SnapshotInspector
4
+ class Snapshot
5
+ class MailType < Type
6
+ snapshotee ActionMailer::MessageDelivery
7
+
8
+ # @private
9
+ def extract(snapshotee)
10
+ @message = snapshotee.to_s
11
+ @bcc = snapshotee.bcc
12
+ end
13
+
14
+ # @private
15
+ def from_hash(hash)
16
+ @message = hash[:message]
17
+ @bcc = hash[:bcc]
18
+ end
19
+
20
+ def message
21
+ message = Mail::Message.new(@message)
22
+ message.bcc = @bcc
23
+ message
24
+ end
25
+
26
+ def mailer_name
27
+ message.header["X-SnapshotInspector-Mailer-Name"].value
28
+ end
29
+
30
+ def action_name
31
+ message.header["X-SnapshotInspector-Action-Name"].value
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,19 @@
1
+ module SnapshotInspector
2
+ class Snapshot
3
+ class ResponseType < Type
4
+ snapshotee ActionDispatch::TestResponse
5
+
6
+ attr_reader :body
7
+
8
+ # @private
9
+ def extract(snapshotee)
10
+ @body = snapshotee.parsed_body
11
+ end
12
+
13
+ # @private
14
+ def from_hash(hash)
15
+ @body = hash[:body]
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,52 @@
1
+ module SnapshotInspector
2
+ class Snapshot
3
+ class RspecContext < Context
4
+ test_framework :rspec
5
+
6
+ attr_reader :test_framework, :example, :take_snapshot_index
7
+
8
+ # @private
9
+ def extract(context)
10
+ @test_framework = context[:test_framework]
11
+ @example = context[:example]
12
+ @take_snapshot_index = context[:take_snapshot_index]
13
+ end
14
+
15
+ # @private
16
+ def from_hash(hash)
17
+ @test_framework = hash[:test_framework].to_sym
18
+ @example = hash[:example]
19
+ @take_snapshot_index = hash[:take_snapshot_index]
20
+ end
21
+
22
+ def to_slug
23
+ spec_path_without_extension = @example[:file_path].delete_suffix(File.extname(@example[:file_path])).delete_prefix("./")
24
+ [spec_path_without_extension, @example[:line_number], @take_snapshot_index].join("_")
25
+ end
26
+
27
+ def name
28
+ @example[:full_description].gsub(test_group, "").strip
29
+ end
30
+
31
+ def test_group
32
+ root_example_group_description(@example)
33
+ end
34
+
35
+ def order_index
36
+ [@example[:file_path], @example[:line_number], @take_snapshot_index]
37
+ end
38
+
39
+ private
40
+
41
+ def root_example_group_description(hash)
42
+ if hash[:example_group].present?
43
+ root_example_group_description(hash[:example_group])
44
+ elsif hash[:parent_example_group].present?
45
+ root_example_group_description(hash[:parent_example_group])
46
+ else
47
+ hash[:description]
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,44 @@
1
+ module SnapshotInspector
2
+ class Snapshot
3
+ class TestUnitContext < Context
4
+ test_framework :test_unit
5
+
6
+ attr_reader :test_framework, :test_case_name, :method_name, :source_location, :take_snapshot_index
7
+
8
+ # @private
9
+ def extract(context)
10
+ @test_framework = context[:test_framework]
11
+ @test_case_name = context[:test_case_name]
12
+ @method_name = context[:method_name]
13
+ @source_location = context[:source_location]
14
+ @take_snapshot_index = context[:take_snapshot_index]
15
+ end
16
+
17
+ # @private
18
+ def from_hash(hash)
19
+ @test_framework = hash[:test_framework].to_sym
20
+ @test_case_name = hash[:test_case_name]
21
+ @method_name = hash[:method_name]
22
+ @source_location = hash[:source_location]
23
+ @take_snapshot_index = hash[:take_snapshot_index]
24
+ end
25
+
26
+ def to_slug
27
+ spec_path_without_extension = source_location[0].delete_suffix(File.extname(source_location[0])).delete_prefix(Rails.root.to_s + "/")
28
+ [spec_path_without_extension, source_location[1], take_snapshot_index].join("_")
29
+ end
30
+
31
+ def name
32
+ method_name.gsub(/^test_/, "").humanize(capitalize: false)
33
+ end
34
+
35
+ def test_group
36
+ test_case_name
37
+ end
38
+
39
+ def order_index
40
+ source_location.dup << take_snapshot_index
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,52 @@
1
+ module SnapshotInspector
2
+ class Snapshot
3
+ class Type
4
+ class UnknownSnapshotee < StandardError; end
5
+
6
+ class_attribute :registry, default: {}, instance_writer: false, instance_predicate: false
7
+
8
+ def self.snapshotee(class_name)
9
+ registry[class_name] = self
10
+ end
11
+
12
+ def self.extract(snapshotee)
13
+ record = type_class(snapshotee.class).new
14
+ record.extract(snapshotee)
15
+ record
16
+ end
17
+
18
+ def self.from_hash(hash)
19
+ record = type_class(hash[:snapshotee_class].constantize).new
20
+ record.from_hash(hash)
21
+ record
22
+ end
23
+
24
+ private_class_method def self.type_class(snapshotee_class)
25
+ registry[snapshotee_class] || raise(UnknownSnapshotee.new(unknown_snapshotee_class_message(snapshotee_class)))
26
+ end
27
+
28
+ private_class_method def self.unknown_snapshotee_class_message(snapshotee_class)
29
+ list_of_known_classes = registry.keys.map(&:to_s).sort.map { |class_name| "`#{class_name}`" }.join(" or ")
30
+ "#take_snapshot only accepts an argument of kind #{list_of_known_classes}. You provided `#{snapshotee_class}`."
31
+ end
32
+
33
+ # @private
34
+ def extract(_snapshotee)
35
+ raise "Implement in a child class."
36
+ end
37
+
38
+ # @private
39
+ def from_hash(_hash)
40
+ raise "Implement in a child class."
41
+ end
42
+
43
+ def type
44
+ self.class.to_s.underscore.split("/").last.gsub("_type", "")
45
+ end
46
+
47
+ def as_json(data = {})
48
+ {snapshotee_class: registry.key(self.class)}.merge(super)
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,86 @@
1
+ require "snapshot_inspector/snapshot/response_type"
2
+ require "snapshot_inspector/snapshot/mail_type"
3
+ require "snapshot_inspector/snapshot/test_unit_context"
4
+ require "snapshot_inspector/snapshot/rspec_context"
5
+
6
+ module SnapshotInspector
7
+ class Snapshot
8
+ class NotFound < StandardError; end
9
+
10
+ attr_reader :context, :slug, :created_at
11
+ delegate_missing_to :@type_data
12
+
13
+ def self.persist(snapshotee:, context:)
14
+ new.extract(snapshotee: snapshotee, context: context).persist
15
+ end
16
+
17
+ def self.find(slug)
18
+ hash = JSON.parse(Storage.read(slug), symbolize_names: true)
19
+ new.from_hash(hash)
20
+ rescue Errno::ENOENT
21
+ raise NotFound.new("Snapshot with a slug `#{slug}` can't be found.")
22
+ end
23
+
24
+ def self.grouped_by_test_case
25
+ all.group_by do |snapshot|
26
+ snapshot.context.test_group
27
+ end
28
+ end
29
+
30
+ private_class_method def self.all
31
+ snapshots = Storage.list.map { |slug| find(slug) }
32
+
33
+ order_by_line_number(snapshots)
34
+ end
35
+
36
+ private_class_method def self.order_by_line_number(snapshots)
37
+ snapshots.sort_by do |snapshot|
38
+ snapshot.context.order_index
39
+ end
40
+ end
41
+
42
+ # @private
43
+ def extract(snapshotee:, context:)
44
+ extract_type_specific_data(snapshotee)
45
+ extract_context(context)
46
+
47
+ @slug = @context.to_slug
48
+ @created_at = Time.current
49
+ self
50
+ end
51
+
52
+ # @private
53
+ def persist
54
+ Storage.write(slug, JSON.pretty_generate(as_json))
55
+ self
56
+ end
57
+
58
+ # @private
59
+ def from_hash(hash)
60
+ from_hash_type_specific_data(hash)
61
+ from_hash_context(hash)
62
+
63
+ @slug = hash[:slug]
64
+ @created_at = Time.zone.parse(hash[:created_at])
65
+ self
66
+ end
67
+
68
+ private
69
+
70
+ def extract_type_specific_data(snapshotee)
71
+ @type_data = Type.extract(snapshotee)
72
+ end
73
+
74
+ def extract_context(context)
75
+ @context = Context.extract(context)
76
+ end
77
+
78
+ def from_hash_type_specific_data(hash)
79
+ @type_data = Type.from_hash(hash[:type_data])
80
+ end
81
+
82
+ def from_hash_context(hash)
83
+ @context = Context.from_hash(hash[:context])
84
+ end
85
+ end
86
+ end
@@ -0,0 +1,18 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title>Snapshot Inspector</title>
5
+ <%= csrf_meta_tags %>
6
+ <%= csp_meta_tag %>
7
+
8
+ <%= stylesheet_link_tag "snapshot_inspector/application", media: "all" %>
9
+ <%= snapshot_inspector_importmap_tags %>
10
+ <%= javascript_import_module_tag "snapshot_inspector/application" %>
11
+ <% if defined?(Hotwire::Livereload::DISABLE_FILE) && !File.exist?(Rails.root.join(Hotwire::Livereload::DISABLE_FILE)) %>
12
+ <%= hotwire_livereload_tags %>
13
+ <% end %>
14
+ </head>
15
+ <body id="<%= "#{controller_name}_#{action_name}" %>">
16
+ <%= yield %>
17
+ </body>
18
+ </html>
@@ -0,0 +1,29 @@
1
+ <main>
2
+ <h1>Snapshots</h1>
3
+
4
+ <% if @grouped_by_test_class.blank? %>
5
+ <p>Place `take_screenshot response` in your integration tests after a `response` object is populated and run the tests. Snapshots will appear below.</p>
6
+ <% end %>
7
+
8
+ <%= form_tag "", method: :get, class: "enable_javascript" do %>
9
+ <%= label_tag :enable_javascript do %>
10
+ <%= check_box_tag :enable_javascript, "true", params[:enable_javascript] == "true", onclick: "submit()" %>
11
+ <span>Open snapshots with JavaScript enabled (by default all JavaScript tags are removed)</span>
12
+ <% end %>
13
+ <% end %>
14
+
15
+ <% @grouped_by_test_class.each do |test_group, snapshots| %>
16
+ <h2><%= test_group %></h2>
17
+
18
+ <ul>
19
+ <% snapshots.each do |snapshot| %>
20
+ <li>
21
+ <%= link_to SnapshotInspector::SnapshotsHelper.snapshot_path(snapshot, enable_javascript: params[:enable_javascript]) do %>
22
+ <%= snapshot.context.name %>
23
+ <%= if snapshot.context.take_snapshot_index > 0 then "(#{(snapshot.context.take_snapshot_index + 1).ordinalize} in the same test)" end %>
24
+ <% end %>
25
+ </li>
26
+ <% end %>
27
+ </ul>
28
+ <% end %>
29
+ </main>
@@ -0,0 +1,107 @@
1
+ <header>
2
+ <dl>
3
+ <% if @email.respond_to?(:smtp_envelope_from) && Array(@email.from) != Array(@email.smtp_envelope_from) %>
4
+ <dt>SMTP-From:</dt>
5
+ <dd id="smtp_from"><%= @email.smtp_envelope_from %></dd>
6
+ <% end %>
7
+
8
+ <% if @email.respond_to?(:smtp_envelope_to) && @email.to != @email.smtp_envelope_to %>
9
+ <dt>SMTP-To:</dt>
10
+ <dd id="smtp_to"><%= @email.smtp_envelope_to %></dd>
11
+ <% end %>
12
+
13
+ <dt>From:</dt>
14
+ <dd id="from"><%= @email.header['from'] %></dd>
15
+
16
+ <% if @email.reply_to %>
17
+ <dt>Reply-To:</dt>
18
+ <dd id="reply_to"><%= @email.header['reply-to'] %></dd>
19
+ <% end %>
20
+
21
+ <dt>To:</dt>
22
+ <dd id="to"><%= @email.header['to'] %></dd>
23
+
24
+ <% if @email.cc %>
25
+ <dt>CC:</dt>
26
+ <dd id="cc"><%= @email.header['cc'] %></dd>
27
+ <% end %>
28
+
29
+ <% if @email.bcc %>
30
+ <dt>BCC:</dt>
31
+ <dd id="bcc"><%= @email.header['bcc'] %></dd>
32
+ <% end %>
33
+
34
+ <dt>Date:</dt>
35
+ <dd id="date"><%= Time.current.rfc2822 %></dd>
36
+
37
+ <dt>Subject:</dt>
38
+ <dd><strong id="subject"><%= @email.subject %></strong></dd>
39
+
40
+ <% unless @email.attachments.nil? || @email.attachments.empty? %>
41
+ <dt>Attachments:</dt>
42
+ <dd>
43
+ <% @email.attachments.each do |a| %>
44
+ <% filename = a.respond_to?(:original_filename) ? a.original_filename : a.filename %>
45
+ <%= link_to filename, "data:application/octet-stream;charset=utf-8;base64,#{Base64.encode64(a.body.to_s)}", download: filename %>
46
+ <% end %>
47
+ </dd>
48
+ <% end %>
49
+
50
+ <dt>Format:</dt>
51
+ <% if @email.html_part && @email.text_part %>
52
+ <dd>
53
+ <select id="part" onchange="refreshBody();">
54
+ <option <%= request.format == Mime[:html] ? 'selected' : '' %> value="<%= part_query('text/html') %>">View as HTML email</option>
55
+ <option <%= request.format == Mime[:text] ? 'selected' : '' %> value="<%= part_query('text/plain') %>">View as plain-text email</option>
56
+ </select>
57
+ </dd>
58
+ <% elsif @part %>
59
+ <dd id="mime_type" data-mime-type="<%= part_query(@part.mime_type) %>"><%= @part.mime_type == 'text/html' ? 'HTML email' : 'plain-text email' %></dd>
60
+ <% else %>
61
+ <dd id="mime_type" data-mime-type=""></dd>
62
+ <% end %>
63
+
64
+ <% unless @email.header_fields.blank? %>
65
+ <dt>Headers:</dt>
66
+ <dd>
67
+ <details>
68
+ <summary>Show all headers</summary>
69
+ <table>
70
+ <% @email.header_fields.each do |field| %>
71
+ <tr>
72
+ <td align="right" style="color: #7f7f7f"><%= field.name %>:</td>
73
+ <td><%= field.value %></td>
74
+ </tr>
75
+ <% end %>
76
+ </table>
77
+ </details>
78
+ </dd>
79
+ <% end %>
80
+
81
+ <dt>EML File:</dt>
82
+ <dd><%= link_to "Download", format: :eml %></dd>
83
+ </dl>
84
+ </header>
85
+
86
+ <% if @part && @part.mime_type %>
87
+ <iframe name="messageBody" src="<%= raw_mail_snapshot_path(slug: @snapshot.slug, part: @part.mime_type) %>"></iframe>
88
+ <% else %>
89
+ <p>
90
+ You are trying to preview an email that does not have any content.
91
+ This is probably because the <em>mail</em> method has not been called in <em><%= @preview.preview_name %>#<%= @email_action %></em>.
92
+ </p>
93
+ <% end %>
94
+
95
+ <script>
96
+ function refreshBody() {
97
+ const part_select = document.querySelector('select#part');
98
+ const part_param = part_select ?
99
+ part_select.options[part_select.selectedIndex].value :
100
+ document.querySelector('#mime_type').dataset.mimeType;
101
+
102
+ const url = location.pathname.replace(/\.(txt|html)$/, '');
103
+ const format = /html/.test(part_param) ? '.html' : '.txt';
104
+
105
+ location.href = url + format;
106
+ }
107
+ </script>
@@ -0,0 +1,8 @@
1
+ <main id="not_found">
2
+ <h1>Not Found</h1>
3
+
4
+ <p>
5
+ <%= @error %>
6
+ Go back to <%= link_to "a list of snapshots", root_path, style: "text-decoration: underline; color: #666" %>.
7
+ </p>
8
+ </main>
@@ -0,0 +1 @@
1
+ <%= prepare_for_render(@snapshot.body, enable_javascript: params[:enable_javascript]) %>
@@ -0,0 +1 @@
1
+ <iframe id="body" src="<%= raw_response_snapshot_path(@snapshot.slug, enable_javascript: params[:enable_javascript]) %>"></iframe>
@@ -0,0 +1,12 @@
1
+ require "snapshot_inspector"
2
+
3
+ SnapshotInspector.configuration.importmap.draw do
4
+ pin "@hotwired/stimulus", to: "stimulus.min.js", preload: true
5
+ pin "@hotwired/stimulus-loading", to: "stimulus-loading.js", preload: true
6
+ pin "stimulus-use", to: "https://ga.jspm.io/npm:stimulus-use@0.51.3/dist/index.js"
7
+ pin "hotkeys-js", to: "https://ga.jspm.io/npm:hotkeys-js@3.10.1/dist/hotkeys.esm.js"
8
+
9
+ pin "application", to: "snapshot_inspector/application.js", preload: true
10
+
11
+ pin_all_from SnapshotInspector::Engine.root.join("app/assets/javascripts/snapshot_inspector/controllers"), under: "controllers", to: "snapshot_inspector/controllers"
12
+ end
data/config/routes.rb ADDED
@@ -0,0 +1,9 @@
1
+ SnapshotInspector::Engine.routes.draw do
2
+ root to: "snapshots#index"
3
+
4
+ get "mail/raw/*slug", to: "snapshots/mail#raw", as: :raw_mail_snapshot
5
+ get "mail/*slug", to: "snapshots/mail#show", as: :mail_snapshot
6
+
7
+ get "response/raw/*slug", to: "snapshots/response#raw", as: :raw_response_snapshot
8
+ get "response/*slug", to: "snapshots/response#show", as: :response_snapshot
9
+ end
@@ -0,0 +1,28 @@
1
+ require "snapshot_inspector"
2
+ require "snapshot_inspector/storage"
3
+ require "minitest"
4
+
5
+ module Minitest
6
+ class SnapshotInspectorReporter < Reporter
7
+ def report
8
+ SnapshotInspector::Storage.move_files_from_processing_directory_to_snapshots_directory if SnapshotInspector::Storage.processing_directory.exist?
9
+
10
+ io.print "\n\nInspect snapshots on #{SnapshotInspector.configuration.host + SnapshotInspector.configuration.route_path}"
11
+ end
12
+ end
13
+
14
+ class << self
15
+ def plugin_snapshot_inspector_options(opts, _options)
16
+ opts.on "--take-snapshots", "Take snapshots of responses for inspecting at #{SnapshotInspector.configuration.host + SnapshotInspector.configuration.route_path}" do
17
+ SnapshotInspector.configuration.snapshot_taking_enabled = true
18
+ end
19
+ end
20
+
21
+ def plugin_snapshot_inspector_init(_options)
22
+ return unless SnapshotInspector.configuration.snapshot_taking_enabled
23
+
24
+ reporter << SnapshotInspectorReporter.new
25
+ SnapshotInspector::Storage.clear(:processing)
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,67 @@
1
+ require "importmap-rails"
2
+ require "snapshot_inspector/test/action_mailer_headers"
3
+
4
+ module SnapshotInspector
5
+ class Engine < ::Rails::Engine
6
+ isolate_namespace SnapshotInspector
7
+
8
+ config.before_configuration do |_app|
9
+ SnapshotInspector.configuration.storage_directory = Rails.root.join(SnapshotInspector::STORAGE_DIRECTORY)
10
+ end
11
+
12
+ unless Rails.env.test?
13
+ rake_tasks do
14
+ load "tasks/tmp.rake"
15
+ end
16
+ end
17
+
18
+ initializer "snapshot_inspector.importmap", before: "importmap" do |app|
19
+ app.config.importmap.paths << root.join("config/importmap.rb")
20
+ app.config.importmap.cache_sweepers << root.join("app/assets/javascripts")
21
+ end
22
+
23
+ initializer "snapshot_inspector.assets.precompile" do |app|
24
+ app.config.assets.precompile += %w[snapshot_inspector/manifest]
25
+ end
26
+
27
+ initializer "snapshot_inspector.include_test_helpers" do |_app|
28
+ if defined?(RSpec)
29
+ RSpec.configure do |config|
30
+ config.include SnapshotInspector::Test::RSpecHelpers
31
+ config.after :suite do
32
+ SnapshotInspector::Storage.move_files_from_processing_directory_to_snapshots_directory if SnapshotInspector::Storage.processing_directory.exist?
33
+ end
34
+ end
35
+ else
36
+ ActiveSupport.on_load(:active_support_test_case) do
37
+ include SnapshotInspector::Test::TestUnitHelpers
38
+ end
39
+ end
40
+
41
+ ActiveSupport.on_load(:action_mailer) do
42
+ include SnapshotInspector::Test::ActionMailerHeaders
43
+ end
44
+ end
45
+
46
+ initializer "snapshot_inspector.configure_default_url_options" do |_app|
47
+ url_options =
48
+ if Rails.application.routes.default_url_options.present?
49
+ Rails.application.routes.default_url_options
50
+ elsif Rails.application.config.action_controller.default_url_options.present?
51
+ Rails.application.config.action_controller.default_url_options
52
+ end
53
+
54
+ SnapshotInspector.configuration.host = url_options ? [url_options[:host], url_options[:port]].join(":") : SnapshotInspector.configuration.host
55
+ end
56
+
57
+ initializer "snapshot_inspector.register_eml_mime_type" do |_app|
58
+ Mime::Type.register "application/octet-stream", :eml
59
+ end
60
+
61
+ config.after_initialize do |app|
62
+ app.routes.prepend do
63
+ mount SnapshotInspector::Engine, at: SnapshotInspector.configuration.route_path
64
+ end
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,60 @@
1
+ module SnapshotInspector
2
+ class Storage
3
+ class << self
4
+ def snapshots_directory
5
+ SnapshotInspector.configuration.storage_directory.join("snapshots")
6
+ end
7
+
8
+ def processing_directory
9
+ SnapshotInspector.configuration.storage_directory.join("processing")
10
+ end
11
+
12
+ def write(key, value)
13
+ file_path = to_file_path_for_writing(key)
14
+ file_path.dirname.mkpath
15
+ file_path.write(value)
16
+ end
17
+
18
+ def read(key)
19
+ File.read(to_file_path_for_reading(key))
20
+ end
21
+
22
+ def list
23
+ Dir
24
+ .glob("#{snapshots_directory}/**/*.{json}")
25
+ .map { |file_path| to_key(file_path) }
26
+ end
27
+
28
+ def clear(directory = nil)
29
+ case directory
30
+ when :snapshots
31
+ snapshots_directory.rmtree
32
+ when :processing
33
+ processing_directory.rmtree
34
+ else
35
+ snapshots_directory.rmtree
36
+ processing_directory.rmtree
37
+ end
38
+ end
39
+
40
+ def move_files_from_processing_directory_to_snapshots_directory
41
+ clear(:snapshots)
42
+ processing_directory.rename(snapshots_directory)
43
+ end
44
+
45
+ private
46
+
47
+ def to_key(file_path)
48
+ file_path.gsub(snapshots_directory.to_s + "/", "").gsub(".json", "")
49
+ end
50
+
51
+ def to_file_path_for_reading(key)
52
+ snapshots_directory.join("#{key}.json")
53
+ end
54
+
55
+ def to_file_path_for_writing(key)
56
+ processing_directory.join("#{key}.json")
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,18 @@
1
+ module SnapshotInspector
2
+ module Test
3
+ module ActionMailerHeaders
4
+ extend ActiveSupport::Concern
5
+
6
+ included do
7
+ before_action :snapshot_inspector_headers, if: -> { Rails.env.test? }
8
+ end
9
+
10
+ private
11
+
12
+ def snapshot_inspector_headers
13
+ headers["X-SnapshotInspector-Mailer-Name"] = mailer_name
14
+ headers["X-SnapshotInspector-Action-Name"] = action_name
15
+ end
16
+ end
17
+ end
18
+ end