Module: Glib::TestHelpers

Extended by:
ActiveSupport::Concern
Defined in:
lib/glib/test_helpers.rb

Defined Under Namespace

Modules: ClassMethods

Constant Summary collapse

HOST =

The best practice is to avoid using lvh.me due to security and performance concerns.

'www.localhost:3000'

Instance Method Summary collapse

Instance Method Details

#assert_attachable_types_crawl_covered(attachable_types:, router_dir:, signed_id_from:, exempt: [], pending: []) ⇒ Object

Asserts that every model with ActiveStorage attachments is actually exercised by the crawler: at least one of its file endpoints must appear in the recorded crawler output. A structural “is this type authorized?” check can only prove a type is handled, not that its file authorization is covered end-to-end. This proves the latter – the crawler recorded the endpoint, so the permission test replays it per user – which is what catches a wrong authorization on a newly-added attachable type (or a fixture that was never made reachable in the crawl).

attachable_types: model classes (or names) that must be covered.
router_dir:       dir holding the crawler router CSVs (the dump_path).
signed_id_from:   ->(url) returning the blob signed_id for a file URL, or
                  nil for non-file URLs. Route-specific, e.g.
                  ->(u) { u[%r{/blobs/([^/]+)/}, 1] }.
exempt:           type names whose files are served outside the crawl.
pending:          type names known-uncovered -- an explicit burn-down
                  list, kept honest by the stale-pending check below.


86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
# File 'lib/glib/test_helpers.rb', line 86

def assert_attachable_types_crawl_covered(attachable_types:, router_dir:, signed_id_from:, exempt: [], pending: [])
  required = attachable_types.map(&:to_s) - exempt.map(&:to_s) - pending.map(&:to_s)
  covered = __crawler_covered_record_types(router_dir, signed_id_from)

  uncovered = (required - covered).sort
  assert_empty(
    uncovered,
    'No crawled file endpoint covers these attachable types, so the permission test ' \
    'never exercises their file authorization (a wrong policy on them would ship ' \
    'silently). Give each a fixture whose file renders in a crawled page and regenerate ' \
    "the crawler snapshots, or list it in `exempt:`/`pending:` -- #{uncovered.join(', ')}"
  )

  graduated = (pending.map(&:to_s) & covered).sort
  assert_empty(
    graduated,
    'These `pending:` types are now crawl-covered -- remove them from the burn-down ' \
    "list so it keeps reflecting the real gap: #{graduated.join(', ')}"
  )
end

#crawl_json_pages(user, log_file: nil, dump_actions: false, dump_path: nil, skip_similar_page: false, &block) ⇒ Object



39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
# File 'lib/glib/test_helpers.rb', line 39

def crawl_json_pages(user, log_file: nil, dump_actions: false, dump_path: nil, skip_similar_page: false, &block)
  __execute_crawler(user, inspect_http: true) do |router, http|
    path = user[:path] ? "#{user[:path]}?format=json" : '/users/me?format=json&redirect=default'
    router.host = HOST
    router.skip_similar_page = skip_similar_page
    router.step(
      http,
      'onClick' => {
        'action' => user[:action] || 'initiate_navigation',
        'url' => user[:url] || "http://#{HOST}#{path}"
      }
    )

    if dump_actions
      csv_string = router.http_actions.map do |row|
        action, url, params = row
        [action, url, JSON.generate(params)].to_csv
      end.join

      filepath = crawler_output_file(user, file_dir: dump_path)
      File.write(filepath, csv_string)
    end
  end
end

#crawler_output_file(user, file_dir: nil) ⇒ Object



64
65
66
67
# File 'lib/glib/test_helpers.rb', line 64

def crawler_output_file(user, file_dir: nil)
  filename = "#{user[:email]}[#{user[:device]}][#{user[:version] || 'current'}].csv"
  File.join(file_dir || __crawler_log_dir, filename)
end

#glib_travel(*args, &block) ⇒ Object



176
177
178
# File 'lib/glib/test_helpers.rb', line 176

def glib_travel(*args, &block)
  Timecop.travel(*args, &block)
end

#glib_travel_backObject



184
185
186
# File 'lib/glib/test_helpers.rb', line 184

def glib_travel_back
  Timecop.return
end

#glib_travel_freeze(*args, &block) ⇒ Object



180
181
182
# File 'lib/glib/test_helpers.rb', line 180

def glib_travel_freeze(*args, &block)
  Timecop.freeze(*args, &block)
end

#logoutObject



115
116
117
118
# File 'lib/glib/test_helpers.rb', line 115

def logout
  delete logout_url
  assert_response :success
end

#logout_urlObject



120
121
122
# File 'lib/glib/test_helpers.rb', line 120

def logout_url
  "http://#{HOST}/users/sign_out.json"
end

#response_assert_equalObject

LOG_DIR = File.expand_path(

File.join(File.dirname(__FILE__), 'integration/json_ui_crawler_test_results')

)



33
34
35
36
37
# File 'lib/glib/test_helpers.rb', line 33

def response_assert_equal
  expected = __get_previous_result_from_git
  result = __log_controller_data(response.body)
  assert_equal JSON.parse(expected), JSON.parse(result), "Result mismatch! #{__git_is_available? ? `git diff #{__controller_log_dir}/#{__controller_log_file}` : ''}"
end

#retrace_json_pages(user, past_actions:) ⇒ Object



107
108
109
110
111
112
113
# File 'lib/glib/test_helpers.rb', line 107

def retrace_json_pages(user, past_actions:)
  __execute_crawler(user, inspect_http: false) do |router, http|
    # router.follow(http, guiding_routers)
    # router.follow_v2(Glib::JsonCrawler::Http.new(self, user, router), actions)
    router.follow_v2(http, past_actions)
  end
end

#submit_form(page_payload, data, url: nil, method: nil) ⇒ Object

Submits a form by parsing the page payload and extracting form data. This ensures the view renders successfully before submitting.

Examples:

get new_chat_url(post_id: @post.id), params: json_params
submit_form(response.body, { chat: { message_body: 'Hello' } })

Parameters:

  • page_payload (String)

    The JSON response body containing the form

  • data (Hash)

    The form data to submit (e.g., { chat: { message_body: ‘hello’ } })

  • url (String, nil) (defaults to: nil)

    Optional URL override (if form URL is nil)

  • method (Symbol, nil) (defaults to: nil)

    Optional HTTP method override



136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
# File 'lib/glib/test_helpers.rb', line 136

def submit_form(page_payload, data, url: nil, method: nil)
  json = JSON.parse(page_payload)
  form = __find_form(json)

  raise 'No form found in page. Ensure the form is rendered properly.' unless form

  # Get URL from form, or use provided URL
  form_url = form['url']
  if form_url.blank? && form['onSubmit']
    on_submit = form['onSubmit']
    form_url = on_submit['httpPost']&.[]('url') || on_submit['http']&.[]('url')
  end
  url ||= form_url

  raise "Form missing 'url'. Please provide url: parameter. Form keys: #{form.keys.join(', ')}" if url.blank?

  # Get method from parameter, form, or default to post
  method ||= form['method']
  method = 'post' if method.blank?

  # Build params: hidden fields from form + provided data
  params = __build_form_params(form, data)

  # Submit using the appropriate HTTP method
  case method.to_sym
  when :get
    get url, params: params
  when :post
    post url, params: params
  when :patch
    patch url, params: params
  when :put
    put url, params: params
  else
    send(method, url, params: params)
  end

  assert_response :success
end