Module: Noiseless::TestHelper

Included in:
TestCase
Defined in:
lib/noiseless/test_helper.rb

Overview

The Ultimate Search Testing Experience

Provides automatic VCR cassette management, index reset helpers, debug utilities, and seamless test integration for Noiseless searches.

Usage:

class MySearchTest < Minitest::Test
  include Noiseless::TestHelper

  def test_searching_products
    noiseless_cassette do
      results = Search::Product.by_name("Ruby").execute
      assert results.any?
    end
  end
end

Or even simpler:

class MySearchTest < Noiseless::TestCase
  def test_searching_products
    results = Search::Product.by_name("Ruby").execute
    assert results.any?
  end
end

Defined Under Namespace

Modules: ClassMethods

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.included(base) ⇒ Object



31
32
33
34
# File 'lib/noiseless/test_helper.rb', line 31

def self.included(base)
  base.extend(ClassMethods)
  setup_vcr_configuration
end

.setup_vcr_configurationObject



36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
# File 'lib/noiseless/test_helper.rb', line 36

def self.setup_vcr_configuration
  return if @vcr_configured

  require "webmock"
  WebMock.disable_net_connect!(allow_localhost: true)

  VCR.configure do |config|
    config.cassette_library_dir = "test/cassettes"
    config.hook_into :webmock
    config.default_cassette_options = {
      record: :once,
      match_requests_on: %i[method uri body]
    }

    # Allow HTTP connections when no cassette is in use (local only in CI)
    config.allow_http_connections_when_no_cassette = !ENV["CI"]

    # Ignore localhost and CI service hostname connections for tests
    config.ignore_hosts "localhost", "127.0.0.1", "0.0.0.0",
                        "elasticsearch", "opensearch", "typesense", "postgres"

    # Filter sensitive data - disabled for localhost testing
    # config.filter_sensitive_data('<OPENSEARCH_HOST>') do |interaction|
    #   uri = URI(interaction.request.uri)
    #   # Only filter non-localhost hosts to avoid test issues
    #   uri.host unless %w[localhost 127.0.0.1 0.0.0.0].include?(uri.host)
    # end
  end

  @vcr_configured = true
end

Instance Method Details

#noiseless_cassette(options = {}, **kwargs) ⇒ Object Also known as: use_noiseless_cassette

Auto-VCR Integration Automatically generates cassette names from test class and method



70
71
72
73
74
75
76
77
78
79
80
81
# File 'lib/noiseless/test_helper.rb', line 70

def noiseless_cassette(options = {}, **kwargs, &)
  # Use provided cassette name or generate one
  cassette_name = kwargs[:cassette_name] || generate_cassette_name(test_method: kwargs[:test_method])

  # Extract VCR options from the hash
  vcr_options = options.except(:cassette_name, :test_method)
  vcr_options = default_vcr_options.merge(vcr_options)

  instrument_test_execution(cassette_name) do
    VCR.use_cassette(cassette_name, vcr_options, &)
  end
end

Debug Utilities



135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
# File 'lib/noiseless/test_helper.rb', line 135

def print_curl(search_or_ast, adapter: :primary)
  return if under_vcr_playback?

  client = Noiseless.connections.client(adapter)
  ast = search_or_ast.respond_to?(:to_ast) ? search_or_ast.to_ast : search_or_ast

  # Convert AST to query hash
  query_hash = client.send(:ast_to_hash, ast)
  index_name = ast.indexes.first || "unknown_index"
  host = client.instance_variable_get(:@hosts).first

  # Generate curl command
  curl_command = build_curl_command(host, index_name, query_hash)

  puts "\n[CURL] Debug cURL Command:"
  puts "=" * 50
  puts curl_command
  puts "=" * 50

  curl_command
end


157
158
159
160
161
162
163
164
165
166
167
168
# File 'lib/noiseless/test_helper.rb', line 157

def print_query(search_or_ast)
  ast = search_or_ast.respond_to?(:to_ast) ? search_or_ast.to_ast : search_or_ast

  puts "\n[DEBUG] Generated Query AST:"
  puts "=" * 30
  puts "Indexes: #{ast.indexes}"
  puts "Must clauses: #{ast.bool.must.size}"
  puts "Filter clauses: #{ast.bool.filter.size}"
  puts "Sort clauses: #{ast.sort.size}"
  puts "Pagination: #{ast.paginate ? "#{ast.paginate.page}/#{ast.paginate.per_page}" : 'default'}"
  puts "=" * 30
end

#reset_all_indexes!(adapter: :primary) ⇒ Object



97
98
99
100
101
102
103
104
105
106
# File 'lib/noiseless/test_helper.rb', line 97

def reset_all_indexes!(adapter: :primary)
  return if under_vcr_playback?

  # Find all registered search classes
  search_classes = find_search_classes
  search_classes.each do |klass|
    reset_index!(klass.index_name, adapter: adapter) if klass.respond_to?(:index_name) && klass.index_name
  end
  puts "[RESET] Reset all indexes (#{search_classes.size} classes)" if verbose_mode?
end

#reset_index!(index_name, adapter: :primary) ⇒ Object

🔧 Index Management Helpers



87
88
89
90
91
92
93
94
95
# File 'lib/noiseless/test_helper.rb', line 87

def reset_index!(index_name, adapter: :primary)
  return if under_vcr_playback?

  client = Noiseless.connections.client(adapter)
  client.indices.delete(index: index_name) if client.indices.exists(index: index_name)
  puts "[RESET] Reset index: #{index_name}" if verbose_mode?
rescue StandardError => e
  warn "[WARN] Failed to reset index #{index_name}: #{e.message}" if verbose_mode?
end

#seed_data!(index_name, records, adapter: :primary) ⇒ Object

Data Seeding Helpers



109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
# File 'lib/noiseless/test_helper.rb', line 109

def seed_data!(index_name, records, adapter: :primary)
  return if under_vcr_playback?

  client = Noiseless.connections.client(adapter)

  # Convert records to bulk format
  bulk_body = records.flat_map.with_index do |record, index|
    doc_id = record.respond_to?(:id) ? record.id : index
    [
      { index: { _index: index_name, _id: doc_id } },
      record.respond_to?(:to_h) ? record.to_h : record
    ]
  end

  response = client.bulk(body: bulk_body)

  # Refresh index to make documents searchable immediately
  client.indices.refresh(index: index_name)

  puts "[SEED] Seeded #{records.size} records to #{index_name}" if verbose_mode?
  response
rescue StandardError => e
  warn "[WARN] Failed to seed data to #{index_name}: #{e.message}" if verbose_mode?
end

#with_search_instrumentationObject

Test Instrumentation



171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
# File 'lib/noiseless/test_helper.rb', line 171

def with_search_instrumentation
  events = []
  subscription = ActiveSupport::Notifications.subscribe(/noiseless/) do |name, start, finish, _id, payload|
    events << {
      event: name,
      duration: ((finish - start) * 1000).round(2),
      payload: payload
    }
  end

  result = yield

  if verbose_mode? && events.any?
    puts "\n[EVENTS] Search Events:"
    events.each do |event|
      puts "   #{event[:event]}: #{event[:duration]}ms"
    end
  end

  result
ensure
  ActiveSupport::Notifications.unsubscribe(subscription) if subscription
end