bidi2pdf-rails 0.0.1.alpha.1 → 0.1.0

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.
Files changed (49) hide show
  1. checksums.yaml +4 -4
  2. data/.idea/bidi2pdf-rails.iml +16 -8
  3. data/.rubocop.yml +11 -0
  4. data/CHANGELOG.md +33 -0
  5. data/README.md +104 -41
  6. data/Rakefile +2 -0
  7. data/cliff.toml +126 -0
  8. data/lib/bidi2pdf_rails/browser_console_log_subscriber.rb +1 -1
  9. data/lib/bidi2pdf_rails/chromedriver_manager_singleton.rb +10 -10
  10. data/lib/bidi2pdf_rails/config.rb +133 -0
  11. data/lib/bidi2pdf_rails/configurable.rb +106 -0
  12. data/lib/bidi2pdf_rails/main_log_subscriber.rb +1 -1
  13. data/lib/bidi2pdf_rails/network_log_subscriber.rb +1 -1
  14. data/lib/bidi2pdf_rails/railtie.rb +2 -0
  15. data/lib/bidi2pdf_rails/services/pdf_renderer.rb +9 -8
  16. data/lib/bidi2pdf_rails/version.rb +1 -1
  17. data/lib/bidi2pdf_rails.rb +28 -99
  18. data/lib/generators/bidi2pdf_rails/USAGE +12 -4
  19. data/lib/generators/bidi2pdf_rails/initializer_generator.rb +136 -31
  20. data/lib/generators/bidi2pdf_rails/templates/bidi2pdf_rails.rb.tt +15 -101
  21. data/spec/acceptance/user_can_download_report_pdf_spec.rb +133 -0
  22. data/spec/acceptance/user_can_generate_pdf_from_protected_remote_url_spec.rb +173 -0
  23. data/spec/dummy/app/controllers/reports_controller.rb +35 -2
  24. data/spec/dummy/app/controllers/secure_controller.rb +52 -0
  25. data/spec/dummy/app/views/layouts/simple.html.erb +17 -0
  26. data/spec/dummy/app/views/secure/show.html.erb +10 -0
  27. data/spec/dummy/config/initializers/bidi2pdf_rails.rb +64 -54
  28. data/spec/dummy/config/routes.rb +9 -1
  29. data/spec/dummy/log/development.log +3850 -0
  30. data/spec/dummy/log/test.log +53046 -0
  31. data/spec/dummy/tmp/pids/server.pid +1 -1
  32. data/spec/integration/generators/bidi2pdf_rails/initializer_generator_spec.rb +64 -0
  33. data/spec/rails_helper.rb +8 -1
  34. data/spec/spec_helper.rb +47 -5
  35. data/spec/support/default_dirs_helper.rb +32 -0
  36. data/spec/support/pdf_helper.rb +12 -0
  37. data/spec/support/render_setting_helpers.rb +28 -0
  38. data/spec/support/request_server_bootstrap.rb +44 -0
  39. data/spec/{bidi2pdf_rails → unit/bidi2pdf_rails}/bidi2pdf_rails_spec.rb +1 -1
  40. data/spec/unit/bidi2pdf_rails/configurable/base_nested_config_spec.rb +133 -0
  41. data/tasks/changelog.rake +29 -0
  42. data/tasks/coverage.rake +23 -0
  43. metadata +69 -20
  44. data/spec/dummy/spec/helpers/reports_helper_spec.rb +0 -15
  45. data/spec/dummy/spec/requests/reports_spec.rb +0 -10
  46. data/spec/dummy/spec/views/reports/show.html.erb_spec.rb +0 -5
  47. data/spec/generator/bidie2pdf_rails_initializer_generator_spec.rb +0 -5
  48. data/spec/generator/initializer_generator_spec.rb +0 -5
  49. data/spec/requests/reports_spec.rb +0 -17
@@ -1,44 +1,72 @@
1
1
  module Bidi2pdfRails
2
2
  class InitializerGenerator < Rails::Generators::Base
3
3
  source_root File.expand_path("templates", __dir__)
4
- desc "Generates a config/initializers/bidi2pdf.rb config file"
5
4
 
6
- class_option :force, type: :boolean, default: false, desc: "Overwrite existing file"
7
- class_option :quiet, type: :boolean, default: false, desc: "Skip prompts"
8
-
9
- def ask_questions
10
- return if options[:quiet]
5
+ def self.infer_option_type(default)
6
+ case default
7
+ when true, false then :boolean
8
+ when Numeric then :numeric
9
+ when Hash then :hash
10
+ when Array then :array
11
+ when Proc then :string
12
+ else :string
13
+ end
14
+ end
11
15
 
12
- @log_network_events = yes?("Log network traffic? (y/n)", :green)
13
- @log_browser_console = yes?("Log browser console output? (y/n)", :green)
14
- @headless = yes?("Run Chrome in headless mode? (y/n)", :green)
15
- @proxy = yes?("Use a proxy server? (y/n)", :green)
16
+ def self.normalize_group(key)
17
+ key.to_s.sub(/(_settings|_options)$/, "")
18
+ end
16
19
 
17
- if @proxy
18
- @proxy_addr = ask("Proxy address (e.g., 127.0.0.1):", :yellow)
19
- @proxy_port = ask("Proxy port (e.g., 8080):", :yellow)
20
+ def self.with_group_config
21
+ that = self
22
+ Bidi2pdfRails::Config::CONFIG_OPTIONS.each_pair do |group_key, group_config|
23
+ group_prefix = that.normalize_group(group_key)
24
+ yield group_key, group_prefix, group_config
20
25
  end
26
+ end
21
27
 
22
- @configure_pdf = yes?("Configure custom PDF settings? (y/n)", :green)
23
- if @configure_pdf
24
- @pdf_orientation = ask("PDF orientation (portrait/landscape):", :yellow)
25
- @pdf_margins = yes?("Configure PDF margins? (y/n)", :green)
26
- if @pdf_margins
27
- @pdf_margin_top = ask("PDF margin top (mm):", :yellow)
28
- @pdf_margin_bottom = ask("PDF margin bottom (mm):", :yellow)
29
- @pdf_margin_left = ask("PDF margin left (mm):", :yellow)
30
- @pdf_margin_right = ask("PDF margin right (mm):", :yellow)
28
+ def self.with_group_option
29
+ with_group_config do |group_key, group_prefix, group_config|
30
+ group_config[:options].each do |opt|
31
+ yield group_key, group_prefix, opt
31
32
  end
32
- @pdf_page = yes?("Configure PDF page size? (y/n)", :green)
33
- if @pdf_page
34
- @pdf_page_width = ask("PDF page width (mm):", :yellow)
35
- @pdf_page_height = ask("PDF page height (mm):", :yellow)
33
+ end
34
+ end
35
+
36
+ with_group_option do |_group_key, group_prefix, opt|
37
+ next unless opt[:ask]
38
+
39
+ name = "#{group_prefix}_#{opt[:name]}"
40
+ type = infer_option_type(opt[:default])
41
+ default = opt[:default_as_str] || opt[:default]
42
+
43
+ class_option name,
44
+ type: type,
45
+ desc: opt[:desc],
46
+ default: default,
47
+ enum: opt[:limited_to]
48
+ end
49
+
50
+ def ask_questions
51
+ @answers = {}
52
+
53
+ return if options[:quiet]
54
+
55
+ self.class.with_group_config do |_group_key, group_prefix, group_config|
56
+ next unless group_config[:ask]
57
+ if group_config[:ask]
58
+ next unless yes?(group_config[:ask], :green)
36
59
  end
37
60
 
38
- @pdf_print_background = yes?("Print background graphics? (y/n)", :green)
39
- @pdf_scale = ask("PDF scale (e.g., 1.0):", :yellow)
40
- @shrink_to_fit = ask("Shrink to fit? (y/n)", :green)
61
+ group_config[:options].select { |opt| opt[:ask] }.each do |opt|
62
+ option_key = "#{group_prefix}_#{opt[:name]}".to_sym
63
+ next if cli_option_set?(option_key, opt)
64
+
65
+ @answers[option_key] = prompt_for_option(opt)
66
+ end
41
67
  end
68
+
69
+ @answers.compact!
42
70
  end
43
71
 
44
72
  def generate_config
@@ -49,9 +77,10 @@ module Bidi2pdfRails
49
77
 
50
78
  def inject_environment_config
51
79
  env_file = "config/environments/development.rb"
80
+ env_path = File.join(destination_root, env_file)
52
81
 
53
- if File.exist?(env_file)
54
- if File.read(env_file).include?("config.x.bidi2pdf_rails")
82
+ if File.exist?(env_path)
83
+ if File.read(env_path).include?("config.x.bidi2pdf_rails")
55
84
  say_status :skipped, "Bidi2PDF settings already present in #{env_file}", :yellow
56
85
  else
57
86
  content = <<~RUBY.gsub(/^(?=[\w#])/, " ")
@@ -70,5 +99,81 @@ module Bidi2pdfRails
70
99
  say_status :error, "Could not find #{env_file}", :red
71
100
  end
72
101
  end
102
+
103
+ no_commands do
104
+ def cli_option_set?(option_key, opt_def)
105
+ return false unless options.key?(option_key.to_s)
106
+
107
+ default = opt_def[:default_as_str] || opt_def[:default]
108
+ options[option_key.to_s].to_s != default.to_s
109
+ end
110
+
111
+ def prompt_for_option(opt)
112
+ type = self.class.infer_option_type(opt[:default])
113
+
114
+ if type == :boolean
115
+ question = opt[:desc].match?(/y\/n/i) ? opt[:desc] : "#{opt[:desc]} (y/n)?"
116
+ yes?(question, opt[:color])
117
+ else
118
+ opts = {
119
+ default: opt[:default_as_str] || opt[:default],
120
+ limited_to: opt[:limited_to],
121
+ echo: !opt[:secret]
122
+ }.compact
123
+
124
+ ask(opt[:desc], opt[:color], **opts)
125
+ end
126
+ end
127
+
128
+ def normalize_group(key)
129
+ self.class.normalize_group(key)
130
+ end
131
+ end
132
+
133
+ private
134
+
135
+ def changed_by_user?(full_top_level_name, option_name)
136
+ top_level_name = normalize_group full_top_level_name
137
+
138
+ full_qual_option_name = "#{top_level_name}_#{option_name}".to_sym
139
+ return false unless options.key?(full_qual_option_name.to_s)
140
+
141
+ option_def = Bidi2pdfRails::Config::CONFIG_OPTIONS.dig(full_top_level_name.to_sym, :options).find { |opt| opt[:name] == option_name.to_sym }
142
+ default = option_def[:default_as_str] ? option_def[:default_as_str] : option_def[:default]
143
+
144
+ current_value = get_option_value(top_level_name, option_name)
145
+
146
+ return false if default.nil? && current_value.empty?
147
+ return true if default.nil? && current_value.present?
148
+
149
+ !current_value.to_s.match /#{Regexp.escape(default.to_s)}/
150
+ end
151
+
152
+ def get_option_value(top_level_name, option_name)
153
+ top_level_name = top_level_name.to_s.sub(/(_settings)|(_options)$/, "")
154
+ full_qual_option_name = "#{top_level_name}_#{option_name}".to_sym
155
+
156
+ result = if @answers.key?(full_qual_option_name)
157
+ @answers[full_qual_option_name]
158
+ elsif options.key?(full_qual_option_name.to_s)
159
+ options[full_qual_option_name.to_s]
160
+ else
161
+ nil
162
+ end
163
+
164
+ if result.is_a?(String)
165
+ if result.match(/(^\s+->\s+{)|(.*Rails\.)/) || numeric?(result)
166
+ return result
167
+ end
168
+ '"' + result + '"'
169
+ else
170
+ result
171
+ end
172
+ end
173
+
174
+ def numeric?(string)
175
+ # Float/Integer conversion with rescue catches most numeric formats
176
+ true if Float(string) rescue false
177
+ end
73
178
  end
74
179
  end
@@ -2,111 +2,25 @@
2
2
 
3
3
  Bidi2pdfRails.configure do |config|
4
4
  overrides = Rails.application.config.x.bidi2pdf_rails
5
+ <%
6
+ Bidi2pdfRails::Config::CONFIG_OPTIONS.each_pair do |top_level_option_name, top_level_option|
7
+ name = top_level_option[:name]
8
+ %>
5
9
 
6
- config.notification_service = ActiveSupport::Notifications
7
-
8
- config.logger = Rails.logger
9
- config.verbosity = overrides.verbosity.nil? ? :none : overrides.verbosity
10
- config.default_timeout = 10
11
-
12
- # Chrome & BiDi
13
- # config.remote_browser_url = nil
14
- config.headless = overrides.headless.nil? ? <%= @headless ? "true" : "false" %> : overrides.headless
15
- #
16
- # alternative to remote_browser_url
17
- #
18
- # config.chromedriver_port = 0
19
- # config.chrome_session_args = [
20
- # "--disable-gpu",
21
- # "--no-sandbox"
22
- # ]
23
-
24
- <% if @proxy %>
25
- config.proxy_addr = "<%= @proxy_addr %>"
26
- config.proxy_port = <%= @proxy_port %>
27
- config.proxy_user = nil
28
- config.proxy_pass = nil
29
- <% else %>
30
- # config.proxy_addr = nil
31
- # config.proxy_port = nil
32
- # config.proxy_user = nil
33
- # config.proxy_pass = nil
34
- <% end %>
35
-
36
- # PDF settings (css media settings take precedence over these)
37
- <% if @configure_pdf %>
38
-
39
- config.pdf_orientation = "<%= @pdf_orientation || "portrait" %>"
40
- <% if @pdf_margins %>
41
- config.pdf_margin_top = <%= @pdf_margin_top || 2.5 %>
42
- config.pdf_margin_bottom = <%= @pdf_margin_bottom || 2 %>
43
- config.pdf_margin_left = <%= @pdf_margin_left || 2 %>
44
- config.pdf_margin_right = <%= @pdf_margin_right || 2 %>
45
- <% else %>
46
- # config.pdf_margin_top = 2.5
47
- # config.pdf_margin_bottom = 2
48
- # config.pdf_margin_left = 2
49
- # config.pdf_margin_right = 2
50
- <% end %>
51
-
52
- <% if @pdf_page %>
53
- # Page settings
54
- config.pdf_page_width = <%= @pdf_page_width || Bidi2pdf::PAPER_FORMATS_CM[:a4][:width] %>
55
- config.pdf_page_height = <%= @pdf_page_height || Bidi2pdf::PAPER_FORMATS_CM[:a4][:height] %>
56
- <% else %>
57
- # Page settings
58
- # config.pdf_page_format = "A4"
59
- #
60
- # or custom page size
61
- #
62
- # config.pdf_page_width = <%= Bidi2pdf::PAPER_FORMATS_CM[:a4][:width] %>
63
- # config.pdf_page_height = <%= Bidi2pdf::PAPER_FORMATS_CM[:a4][:height] %>
64
- <% end %>
65
-
66
- config.pdf_print_background = <%= @pdf_print_background ? "true" : "false" %>
67
- config.pdf_scale = <%= @pdf_scale || 1.0 %>
68
- config.shrink_to_fit = <%= @shrink_to_fit ? "true" : "false" %>
69
- <% else %>
70
- # config.pdf_orientation = "portrait"
71
- # config.pdf_margin_top = 2.5
72
- # config.pdf_margin_bottom = 2
73
- # config.pdf_margin_left = 2
74
- # config.pdf_margin_right = 2
75
- # config.pdf_print_background = true
76
- # config.pdf_scale = 1.0
77
- # config.shrink_to_fit = true
78
-
79
- # Page settings
80
- # config.pdf_page_format = "A4"
81
10
  #
82
- # or custom page size
11
+ # <%= name %>
83
12
  #
84
- # config.pdf_page_width = <%= Bidi2pdf::PAPER_FORMATS_CM[:a4][:width] %>
85
- # config.pdf_page_height = <%= Bidi2pdf::PAPER_FORMATS_CM[:a4][:height] %>
13
+ <% top_level_option[:options].each do |option| %>
14
+ <% if option[:limited_to] %>
15
+ <%= "# Allowed values: #{option[:limited_to].map { |v| v.inspect }.join(", ")}" %>
16
+ <% end -%>
17
+ <% if changed_by_user?(top_level_option_name, option[:name]) -%>
18
+ <%= "config.#{top_level_option_name}.#{option[:name]} = #{get_option_value(top_level_option_name, option[:name]) } # #{option[:desc]}" -%>
19
+ <% else -%>
20
+ <%= "# config.#{top_level_option_name}.#{option[:name]} = #{option[:default_as_str] ? option[:default_as_str] : option[:default].inspect} # #{option[:desc]}" -%>
21
+ <% end -%>
22
+ <% end -%>
86
23
  <% end %>
87
-
88
- # config.cookies = [
89
- # { name: "session", value: "abc123", domain: "example.com" }
90
- # ]
91
-
92
- # config.headers = {
93
- # "X-API-KEY" => "topsecret"
94
- # }
95
-
96
- # config.auth = {
97
- # username: "admin",
98
- # password: "secret"
99
- # }
100
-
101
- # chromedriver install dir
102
- # config.install_dir = Rails.root.join("tmp", "bidi2pdf").to_s
103
-
104
- # wait for the page to load settings
105
- # config.wait_for_network_idle = true
106
- # config.wait_for_page_loaded = false
107
- # this script can be any valid JS script that returns a Promise
108
- # it will wait for the Promise to resolve
109
- # config.wait_for_page_check_script = nil
110
24
  end
111
25
 
112
26
  Rails.application.config.after_initialize do
@@ -0,0 +1,133 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rails_helper"
4
+ require "net/http"
5
+ require "rack/handler/puma"
6
+ require "socket"
7
+
8
+ RSpec.feature "As a devoloper, I want to generate PDF's with bidi2pdf-rails", :pdf, type: :request do
9
+ # This feature demonstrates how to use bidi2pdf-rails to generate PDFs
10
+ # from Rails views, remote URLs, and inline HTML. These specs double as
11
+ # living documentation for gem users.
12
+
13
+ before(:all) do
14
+ # Prepare the PDF rendering engine (chromium, etc.)
15
+ Bidi2pdfRails::ChromedriverManagerSingleton.initialize_manager force: true
16
+ end
17
+
18
+ after(:all) do
19
+ Bidi2pdfRails::ChromedriverManagerSingleton.shutdown
20
+ end
21
+
22
+ scenario "Rendering a controller view to PDF using layout: 'pdf'" do
23
+ # Controller example:
24
+ #
25
+ # def show
26
+ # respond_to do |format|
27
+ # format.html
28
+ # format.pdf { render pdf: 'my-report', layout: 'pdf' }
29
+ # end
30
+ # end
31
+
32
+ when_ "I visit the PDF version of a report" do
33
+ before do
34
+ @response = get_pdf_response "/reports/1.pdf"
35
+ end
36
+
37
+ then_ "I receive a successful HTTP response" do
38
+ expect(@response.code).to eq("200")
39
+ end
40
+
41
+ and_ "I receive a PDF file in response" do
42
+ expect(@response['Content-Type']).to eq("application/pdf")
43
+ end
44
+
45
+ and_ "the PDF contains the expected number of pages" do
46
+ expect(@response.body).to have_pdf_page_count(5)
47
+ end
48
+
49
+ and_ "the disposition header is set to attachment" do
50
+ expect(@response['Content-Disposition']).to start_with('inline; filename="my-report.pdf"')
51
+ end
52
+
53
+ and_ "the PDF contains the expected content" do
54
+ expect(@response.body).to contains_pdf_text("Section Two").at_page(2)
55
+ end
56
+ end
57
+ end
58
+
59
+ scenario "Converting a remote URL into a PDF with custom options" do
60
+ # Controller usage:
61
+ #
62
+ # def convert_remote_url
63
+ # render pdf: 'convert-remote-url', url: "http://example.com", wait_for_page_loaded: false, print_options: { page: { format: :A4 } }
64
+ # end
65
+
66
+ when_ "I visit the PDF version of a report" do
67
+ before do
68
+ @response = get_pdf_response "/convert-remote-url.pdf"
69
+ end
70
+
71
+ then_ "I receive a successful HTTP response" do
72
+ expect(@response.code).to eq("200")
73
+ end
74
+
75
+ and_ "I receive a PDF file in response" do
76
+ expect(@response['Content-Type']).to eq("application/pdf")
77
+ end
78
+
79
+ and_ "the PDF contains the expected number of pages" do
80
+ expect(@response.body).to have_pdf_page_count(1)
81
+ end
82
+
83
+ and_ "the disposition header is set to attachment" do
84
+ expect(@response['Content-Disposition']).to start_with('inline; filename="convert-remote-url.pdf"')
85
+ end
86
+ end
87
+ end
88
+
89
+ scenario "Rendering inline HTML directly to PDF" do
90
+ # Controller usage:
91
+ #
92
+ # def inline_html
93
+ # html = <<~HTML
94
+ # <html>
95
+ # <head>
96
+ # </head>
97
+ # <body>
98
+ # <h1>PDF Rendering Sample</h1>
99
+ # <p style="page-break-after: always;">Page break</p>
100
+ # <p>Content Page 2</p>
101
+ # </body>
102
+ # </html>
103
+ #
104
+ # render pdf: 'inline-html', inline: html, wait_for_page_loaded: false, print_options: { page: { format: :A4 } }
105
+ # end
106
+
107
+ when_ "I visit the PDF version of a report" do
108
+ before do
109
+ @response = get_pdf_response "/inline-html.pdf"
110
+ end
111
+
112
+ then_ "I receive a successful HTTP response" do
113
+ expect(@response.code).to eq("200")
114
+ end
115
+
116
+ and_ "I receive a PDF file in response" do
117
+ expect(@response['Content-Type']).to eq("application/pdf")
118
+ end
119
+
120
+ and_ "the PDF contains the expected number of pages" do
121
+ expect(@response.body).to have_pdf_page_count(2)
122
+ end
123
+
124
+ and_ "the disposition header is set to attachment" do
125
+ expect(@response['Content-Disposition']).to start_with('inline; filename="inline-html.pdf"')
126
+ end
127
+
128
+ and_ "the PDF contains the expected content" do
129
+ expect(@response.body).to contains_pdf_text("PDF Rendering Sample")
130
+ end
131
+ end
132
+ end
133
+ end
@@ -0,0 +1,173 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rails_helper"
4
+ require "net/http"
5
+ require "rack/handler/puma"
6
+ require "socket"
7
+ require "base64"
8
+
9
+ RSpec.feature "As a user, I want to generate a PDF from a protected remote URL", :pdf, type: :request do
10
+ before(:all) do
11
+ # Bidi2pdfRails.config.general_options.headless = false
12
+ Bidi2pdfRails::ChromedriverManagerSingleton.initialize_manager force: true
13
+ end
14
+
15
+ after(:all) do
16
+ Bidi2pdfRails::ChromedriverManagerSingleton.shutdown
17
+ end
18
+
19
+ scenario "Using basic auth for remote PDF rendering" do
20
+ # Controller setup:
21
+ #
22
+ # You can configure basic auth in two ways:
23
+ #
24
+ # 1. In an initializer (global config):
25
+ #
26
+ # Bidi2pdfRails.configure do |config|
27
+ # config.render_remote_settings.basic_auth_user = ->(_controller) { "admin" }
28
+ # config.render_remote_settings.basic_auth_pass = ->(_controller) { "secret" }
29
+ # end
30
+ #
31
+ # 2. Inline within controller action:
32
+ #
33
+ # render pdf: 'convert-remote-url-basic-auth',
34
+ # url: basic_auth_endpoint_url(only_path: false),
35
+ # auth: { username: "admin", password: "secret" }
36
+
37
+ when_ "I request a PDF generated from a basic-auth protected page" do
38
+ before do
39
+ with_render_setting :basic_auth_user, ->(_controller) { "admin" }
40
+ # in prod better to use:
41
+ # Bidi2pdfRails.config.render_remote_settings.basic_auth_pass = ->(_controller) { Rails.application.credentials.dig('bidi2pdf_rails', 'basic_auth_pass') }
42
+ with_render_setting :basic_auth_pass, ->(_controller) { "secret" }
43
+
44
+ @response = get_pdf_response "/convert-remote-url-basic-auth"
45
+ end
46
+
47
+ then_ "I receive a successful HTTP response" do
48
+ expect(@response.code).to eq("200")
49
+ end
50
+
51
+ and_ "I receive a PDF file in response" do
52
+ expect(@response['Content-Type']).to eq("application/pdf")
53
+ end
54
+
55
+ and_ "the PDF contains the expected number of pages" do
56
+ expect(@response.body).to have_pdf_page_count(1)
57
+ end
58
+
59
+ and_ "the disposition header is set to attachment" do
60
+ expect(@response['Content-Disposition']).to start_with('inline; filename="convert-remote-url-basic-auth.pdf"')
61
+ end
62
+
63
+ and_ "the PDF contains the expected content" do
64
+ expect(@response.body).to contains_pdf_text("This page is secured with: HTTPBasicAuthentication").at_page(1)
65
+ end
66
+ end
67
+ end
68
+
69
+ scenario "Using a session cookie for remote PDF rendering" do
70
+ # Controller setup:
71
+ #
72
+ # You can configure cookies in two ways:
73
+ #
74
+ # 1. In an initializer (global config):
75
+ #
76
+ # Bidi2pdfRails.configure do |config|
77
+ # config.render_remote_settings.cookies = = ->(controller) { { "auth_token" => signed_cookie_value("valid-authentication-token", controller) } }
78
+ # end
79
+ #
80
+ # 2. Inline within controller action:
81
+ #
82
+ # render pdf: 'convert-remote-url-cookie',
83
+ # url: cookie_endpoint_url(only_path: false),
84
+ # wait_for_page_loaded: false,
85
+ # cookies: { "auth_token" => signed_cookie_value("valid-authentication-token", controller) }
86
+ #
87
+
88
+ when_ "I request a PDF from a page that requires a session cookie" do
89
+ def signed_cookie_value(value, controller)
90
+ request = controller.request
91
+ secret = request.key_generator.generate_key(request.signed_cookie_salt)
92
+ # Sign the value
93
+ verifier = ActiveSupport::MessageVerifier.new(secret)
94
+ verifier.generate(value)
95
+ end
96
+
97
+ before do
98
+ with_render_setting :cookies, ->(controller) { { "auth_token" => signed_cookie_value("valid-authentication-token", controller) } }
99
+
100
+ @response = get_pdf_response "/convert-remote-url-cookie"
101
+ end
102
+
103
+ then_ "I receive a successful HTTP response" do
104
+ expect(@response.code).to eq("200")
105
+ end
106
+
107
+ and_ "I receive a PDF file in response" do
108
+ expect(@response['Content-Type']).to eq("application/pdf")
109
+ end
110
+
111
+ and_ "the PDF contains the expected number of pages" do
112
+ expect(@response.body).to have_pdf_page_count(1)
113
+ end
114
+
115
+ and_ "the disposition header is set to attachment" do
116
+ expect(@response['Content-Disposition']).to start_with('inline; filename="convert-remote-url-cookie.pdf"')
117
+ end
118
+
119
+ and_ "the PDF contains the expected content" do
120
+ expect(@response.body).to contains_pdf_text("Protected Resource This page is secured with: CookieAuthentication").at_page(1)
121
+ end
122
+ end
123
+ end
124
+
125
+ scenario "Using custom headers for remote PDF rendering" do
126
+ # Controller setup:
127
+ #
128
+ # You can configure headers in two ways:
129
+ #
130
+ # 1. In an initializer (global config):
131
+ #
132
+ # Bidi2pdfRails.configure do |config|
133
+ # config.render_remote_settings.headers = ->(controller) { { "X-API-Key" => "your-secret-api-key" } }
134
+ # end
135
+ #
136
+ # 2. Inline within controller action:
137
+ #
138
+ # render pdf: 'convert-remote-url-cookie',
139
+ # url: api_endpoint_url(only_path: false),
140
+ # wait_for_page_loaded: false,
141
+ # headers: { "X-API-Key" => "your-secret-api-key" }
142
+ #
143
+
144
+ when_ "I request a PDF from a page that requires an auth header" do
145
+ before do
146
+ @old_headers = Bidi2pdfRails.config.render_remote_settings.headers
147
+ with_render_setting :headers, ->(_controller) { { "X-API-Key" => "your-secret-api-key" } }
148
+
149
+ @response = get_pdf_response "/convert-remote-url-header"
150
+ end
151
+
152
+ then_ "I receive a successful HTTP response" do
153
+ expect(@response.code).to eq("200")
154
+ end
155
+
156
+ and_ "I receive a PDF file in response" do
157
+ expect(@response['Content-Type']).to eq("application/pdf")
158
+ end
159
+
160
+ and_ "the PDF contains the expected number of pages" do
161
+ expect(@response.body).to have_pdf_page_count(1)
162
+ end
163
+
164
+ and_ "the disposition header is set to attachment" do
165
+ expect(@response['Content-Disposition']).to start_with('inline; filename="convert-remote-url-cookie.pdf"')
166
+ end
167
+
168
+ and_ "the PDF contains the expected content" do
169
+ expect(@response.body).to contains_pdf_text("Protected Resource This page is secured with: API KeyAuthentication").at_page(1)
170
+ end
171
+ end
172
+ end
173
+ end
@@ -6,7 +6,40 @@ class ReportsController < ApplicationController
6
6
  end
7
7
  end
8
8
 
9
- def print_remote
10
- render pdf: 'remote-report', url: "http://example.com", wait_for_page_loaded: false, print_options: { page: { format: :A4 } }
9
+ def convert_remote_url
10
+ render pdf: 'convert-remote-url', url: "http://example.com", wait_for_page_loaded: false, print_options: { page: { format: :A4 } }
11
+ end
12
+
13
+ def inline_html
14
+ html = <<~HTML
15
+ <html>
16
+ <head>
17
+ </head>
18
+ <body>
19
+ <h1>PDF Rendering Sample</h1>
20
+ <p style="page-break-after: always;">Page break</p>
21
+ <p>Content Page 2</p>
22
+ </body>
23
+ </html>
24
+ HTML
25
+ render pdf: 'inline-html', inline: html, wait_for_page_loaded: false, print_options: { page: { format: :A4 } }
26
+ end
27
+
28
+ def convert_remote_url_basic_auth
29
+ render pdf: 'convert-remote-url-basic-auth',
30
+ url: basic_auth_endpoint_url(only_path: false),
31
+ wait_for_page_loaded: false
32
+ end
33
+
34
+ def convert_remote_url_cookie
35
+ render pdf: 'convert-remote-url-cookie',
36
+ url: cookie_endpoint_url(only_path: false),
37
+ wait_for_page_loaded: false
38
+ end
39
+
40
+ def convert_remote_url_header
41
+ render pdf: 'convert-remote-url-cookie',
42
+ url: api_endpoint_url(only_path: false),
43
+ wait_for_page_loaded: false
11
44
  end
12
45
  end