Module: Upkeep::Rails::Testing

Defined in:
lib/upkeep/rails/testing.rb

Overview

Test helpers for asserting the public Upkeep subscription lifecycle from Rails request, integration, and system tests.

Constant Summary collapse

CHANGE_FACTS_THREAD_KEY =
:upkeep_rails_testing_change_facts

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.capture_broadcastsObject

Captures rendered Upkeep delivery payloads while the block runs. This observes the batch after planning/rendering and before the app-specific transport adapter, so tests stay deterministic across ActionCable adapters.



57
58
59
60
61
62
63
64
65
66
# File 'lib/upkeep/rails/testing.rb', line 57

def capture_broadcasts
  broadcasts = []
  broadcast_capture_mutex.synchronize { broadcast_captures << broadcasts }

  yield
  drain_delivery!
  broadcasts.dup
ensure
  broadcast_capture_mutex.synchronize { broadcast_captures.delete(broadcasts) } if broadcasts
end

.capture_change_factsArray(Object, Array<Hash>)

Captures facts passed into Upkeep delivery while the block runs. This exposes the same committed-change payloads the planner sees, without broadcasting or altering application code.

Returns:

  • (Array(Object, Array<Hash>))

    the block result and captured facts.



29
30
31
32
33
34
35
36
37
# File 'lib/upkeep/rails/testing.rb', line 29

def capture_change_facts
  previous = Thread.current[CHANGE_FACTS_THREAD_KEY]
  facts = []
  Thread.current[CHANGE_FACTS_THREAD_KEY] = facts

  [yield, facts]
ensure
  Thread.current[CHANGE_FACTS_THREAD_KEY] = previous
end

.capturing_broadcasts?Boolean

Returns true when any thread is capturing delivery batches.

Returns:

  • (Boolean)

    true when any thread is capturing delivery batches.



69
70
71
# File 'lib/upkeep/rails/testing.rb', line 69

def capturing_broadcasts?
  broadcast_capture_mutex.synchronize { broadcast_captures.any? }
end

.capturing_change_facts?Boolean

Returns true when the current thread is capturing change facts.

Returns:

  • (Boolean)

    true when the current thread is capturing change facts.



49
50
51
# File 'lib/upkeep/rails/testing.rb', line 49

def capturing_change_facts?
  !!Thread.current[CHANGE_FACTS_THREAD_KEY]
end

.drain_delivery!void

This method returns an undefined value.

Drains the async delivery dispatcher when a test needs deterministic broadcast assertions.

Production code should not call this; normal app delivery runs through the configured adapter.



20
21
22
# File 'lib/upkeep/rails/testing.rb', line 20

def drain_delivery!
  Upkeep::Rails.send(:drain_delivery_dispatcher!)
end

.match_report(changes, store: Upkeep::Rails.subscriptions) ⇒ Hash

Runs the invalidation planner against committed-change facts without enqueueing delivery or broadcasting.

Parameters:

  • changes (Hash, Array<Hash>)

    one or more change facts.

  • store (#reverse_index) (defaults to: Upkeep::Rails.subscriptions)

    subscription store to inspect.

Returns:

  • (Hash)

    concise planner match report.



96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
# File 'lib/upkeep/rails/testing.rb', line 96

def match_report(changes, store: Upkeep::Rails.subscriptions)
  changes = changes.is_a?(Hash) ? [changes] : Array(changes)
  lookup_payloads = []
  subscription = ActiveSupport::Notifications.subscribe("lookup_subscription_index.upkeep") do |event|
    lookup_payloads << event.payload.dup
  end

  plan = Upkeep::Invalidation::Planner.new(store: store).plan(changes)

  {
    candidate_entries: plan.candidate_entries.size,
    matched_entries: plan.matched_entries.size,
    miss_reason: match_miss_reason(plan, changes, lookup_payloads),
    targets: plan.targets.map { |target| match_report_target(target) }
  }
ensure
  ActiveSupport::Notifications.unsubscribe(subscription) if subscription
end

.record_change_facts(changes) ⇒ Object

Records delivery facts for capture_change_facts. Internal test hook; production delivery calls this only when a capture is active.



41
42
43
44
45
46
# File 'lib/upkeep/rails/testing.rb', line 41

def record_change_facts(changes)
  facts = Thread.current[CHANGE_FACTS_THREAD_KEY]
  return unless facts

  facts.concat(Array(changes).map { |change| clone_change_fact(change) })
end

.record_delivery_batch(batch) ⇒ Object

Records a rendered delivery batch for active capture_broadcasts blocks. Internal test hook; production delivery calls this only when a capture is active.



76
77
78
79
80
81
82
83
84
85
86
87
88
# File 'lib/upkeep/rails/testing.rb', line 76

def record_delivery_batch(batch)
  captures = broadcast_capture_mutex.synchronize { broadcast_captures.dup }
  return if captures.empty?

  bodies = batch.envelopes.map(&:body)
  return if bodies.empty?

  broadcast_capture_mutex.synchronize do
    captures.each do |capture|
      capture.concat(bodies) if broadcast_captures.include?(capture)
    end
  end
end

Instance Method Details

#activate_upkeep_subscription!(subscription = upkeep_subscription) ⇒ Upkeep::Subscriptions::Subscription

Activates the registered subscription so delivery lookup can find it.

Parameters:

Returns:

Raises:



208
209
210
211
212
213
214
215
# File 'lib/upkeep/rails/testing.rb', line 208

def activate_upkeep_subscription!(subscription = upkeep_subscription)
  raise ArgumentError, "no Upkeep subscription is registered" unless subscription

  activated = Upkeep::Rails.subscriptions.activate(subscription.id)
  raise Upkeep::Subscriptions::NotFound, subscription.id unless activated

  subscription
end

#assert_upkeep_subscription_registered(message = nil) ⇒ void

This method returns an undefined value.

Asserts that the last successful HTML response injected an Upkeep subscription marker and registered a subscription in the configured store.

Parameters:

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

    optional assertion failure message.



177
178
179
180
181
# File 'lib/upkeep/rails/testing.rb', line 177

def assert_upkeep_subscription_registered(message = nil)
  assert_select "upkeep-subscription-source[data-upkeep-subscription]"
  assert Upkeep::Rails.subscriptions.subscriptions.any?,
    message || "expected Upkeep to register at least one subscription"
end

#capture_upkeep_broadcasts(subscription = upkeep_subscription, &block) ⇒ Array<String>

Captures rendered Upkeep broadcasts while the block runs. This observes Upkeep after planning/rendering and before the application ActionCable adapter, so tests stay deterministic regardless of the host app’s cable adapter.

Parameters:

Returns:

  • (Array<String>)

Raises:

  • (ArgumentError)

    when called without a block or subscription.



225
226
227
228
229
230
# File 'lib/upkeep/rails/testing.rb', line 225

def capture_upkeep_broadcasts(subscription = upkeep_subscription, &block)
  raise ArgumentError, "capture_upkeep_broadcasts requires a block" unless block
  raise ArgumentError, "no Upkeep subscription is registered" unless subscription

  Upkeep::Rails::Testing.capture_broadcasts(&block)
end

#capture_upkeep_change_facts(&block) ⇒ Array(Object, Array<Hash>)

Captures facts passed into Upkeep delivery while the block runs.

Returns:

  • (Array(Object, Array<Hash>))

    the block result and captured facts.

Raises:

  • (ArgumentError)


242
243
244
245
246
# File 'lib/upkeep/rails/testing.rb', line 242

def capture_upkeep_change_facts(&block)
  raise ArgumentError, "capture_upkeep_change_facts requires a block" unless block

  Upkeep::Rails::Testing.capture_change_facts(&block)
end

#drain_upkeep_delivery!void

This method returns an undefined value.

Drains async Upkeep delivery for deterministic test assertions.



235
236
237
# File 'lib/upkeep/rails/testing.rb', line 235

def drain_upkeep_delivery!
  Upkeep::Rails::Testing.drain_delivery!
end

#upkeep_match_report(changes) ⇒ Hash

Returns a dry-run invalidation planner report for one or more change facts against the configured Upkeep subscription store.

Parameters:

  • changes (Hash, Array<Hash>)

    one or more change facts.

Returns:

  • (Hash)


253
254
255
# File 'lib/upkeep/rails/testing.rb', line 253

def upkeep_match_report(changes)
  Upkeep::Rails::Testing.match_report(changes)
end

#upkeep_stream_names(subscription = upkeep_subscription) ⇒ Array<String>

Returns every ActionCable stream name that can receive broadcasts for a subscription, including shared streams.

Parameters:

Returns:

  • (Array<String>)

Raises:

  • (ArgumentError)

    when no subscription is registered.



196
197
198
199
200
# File 'lib/upkeep/rails/testing.rb', line 196

def upkeep_stream_names(subscription = upkeep_subscription)
  raise ArgumentError, "no Upkeep subscription is registered" unless subscription

  ([subscription..fetch(:stream_name)] + subscription..fetch(:shared_stream_names, [])).uniq
end

#upkeep_subscriptionUpkeep::Subscriptions::Subscription?

Returns the most recently registered Upkeep subscription.



186
187
188
# File 'lib/upkeep/rails/testing.rb', line 186

def upkeep_subscription
  Upkeep::Rails.subscriptions.subscriptions.last
end