webhookdb 1.3.1 → 1.5.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 (164) hide show
  1. checksums.yaml +4 -4
  2. data/admin-dist/assets/{index-6aebf805.js → index-9306dd28.js} +39 -39
  3. data/admin-dist/index.html +1 -1
  4. data/data/messages/templates/errors/generic_backfill.email.liquid +30 -0
  5. data/data/messages/templates/errors/icalendar_fetch.email.liquid +8 -2
  6. data/data/messages/templates/specs/with_fields.email.liquid +6 -0
  7. data/db/migrations/026_undo_integration_backfill_cursor.rb +2 -0
  8. data/db/migrations/032_remove_db_defaults.rb +2 -0
  9. data/db/migrations/043_text_search.rb +2 -0
  10. data/db/migrations/045_system_log.rb +15 -0
  11. data/db/migrations/046_indices.rb +14 -0
  12. data/db/migrations/047_sync_parallelism.rb +9 -0
  13. data/db/migrations/048_sync_stats.rb +9 -0
  14. data/db/migrations/049_error_handlers.rb +18 -0
  15. data/db/migrations/050_logged_webhook_indices.rb +25 -0
  16. data/db/migrations/051_partitioning.rb +9 -0
  17. data/integration/async_spec.rb +0 -2
  18. data/integration/service_integrations_spec.rb +0 -2
  19. data/lib/amigo/durable_job.rb +2 -2
  20. data/lib/amigo/job_in_context.rb +12 -0
  21. data/lib/webhookdb/admin.rb +6 -0
  22. data/lib/webhookdb/admin_api/data_provider.rb +1 -0
  23. data/lib/webhookdb/admin_api/entities.rb +8 -0
  24. data/lib/webhookdb/aggregate_result.rb +1 -1
  25. data/lib/webhookdb/api/entities.rb +6 -2
  26. data/lib/webhookdb/api/error_handlers.rb +104 -0
  27. data/lib/webhookdb/api/helpers.rb +25 -1
  28. data/lib/webhookdb/api/icalproxy.rb +22 -0
  29. data/lib/webhookdb/api/install.rb +2 -1
  30. data/lib/webhookdb/api/organizations.rb +6 -0
  31. data/lib/webhookdb/api/saved_queries.rb +1 -0
  32. data/lib/webhookdb/api/saved_views.rb +1 -0
  33. data/lib/webhookdb/api/service_integrations.rb +2 -1
  34. data/lib/webhookdb/api/sync_targets.rb +1 -1
  35. data/lib/webhookdb/api/system.rb +5 -0
  36. data/lib/webhookdb/api/webhook_subscriptions.rb +1 -0
  37. data/lib/webhookdb/api.rb +4 -1
  38. data/lib/webhookdb/apps.rb +4 -0
  39. data/lib/webhookdb/async/autoscaler.rb +10 -0
  40. data/lib/webhookdb/async/job.rb +4 -0
  41. data/lib/webhookdb/async/scheduled_job.rb +4 -0
  42. data/lib/webhookdb/async.rb +2 -0
  43. data/lib/webhookdb/backfiller.rb +17 -4
  44. data/lib/webhookdb/concurrent.rb +96 -0
  45. data/lib/webhookdb/connection_cache.rb +57 -10
  46. data/lib/webhookdb/console.rb +1 -1
  47. data/lib/webhookdb/customer/reset_code.rb +1 -1
  48. data/lib/webhookdb/customer.rb +5 -4
  49. data/lib/webhookdb/database_document.rb +1 -1
  50. data/lib/webhookdb/db_adapter/default_sql.rb +1 -14
  51. data/lib/webhookdb/db_adapter/partition.rb +14 -0
  52. data/lib/webhookdb/db_adapter/partitioning.rb +8 -0
  53. data/lib/webhookdb/db_adapter/pg.rb +77 -5
  54. data/lib/webhookdb/db_adapter/snowflake.rb +15 -6
  55. data/lib/webhookdb/db_adapter.rb +25 -3
  56. data/lib/webhookdb/dbutil.rb +2 -0
  57. data/lib/webhookdb/errors.rb +34 -0
  58. data/lib/webhookdb/fixtures/logged_webhooks.rb +4 -0
  59. data/lib/webhookdb/fixtures/organization_error_handlers.rb +20 -0
  60. data/lib/webhookdb/http.rb +30 -16
  61. data/lib/webhookdb/icalendar.rb +30 -9
  62. data/lib/webhookdb/jobs/amigo_test_jobs.rb +1 -1
  63. data/lib/webhookdb/jobs/backfill.rb +21 -25
  64. data/lib/webhookdb/jobs/create_mirror_table.rb +3 -4
  65. data/lib/webhookdb/jobs/deprecated_jobs.rb +3 -0
  66. data/lib/webhookdb/jobs/emailer.rb +2 -1
  67. data/lib/webhookdb/jobs/front_signalwire_message_channel_sync_inbound.rb +15 -0
  68. data/lib/webhookdb/jobs/icalendar_delete_stale_cancelled_events.rb +7 -2
  69. data/lib/webhookdb/jobs/icalendar_enqueue_syncs.rb +74 -11
  70. data/lib/webhookdb/jobs/icalendar_enqueue_syncs_for_urls.rb +22 -0
  71. data/lib/webhookdb/jobs/icalendar_sync.rb +21 -9
  72. data/lib/webhookdb/jobs/increase_event_handler.rb +3 -2
  73. data/lib/webhookdb/jobs/{logged_webhook_replay.rb → logged_webhooks_replay.rb} +5 -3
  74. data/lib/webhookdb/jobs/message_dispatched.rb +1 -0
  75. data/lib/webhookdb/jobs/model_event_system_log_tracker.rb +112 -0
  76. data/lib/webhookdb/jobs/monitor_metrics.rb +29 -0
  77. data/lib/webhookdb/jobs/organization_database_migration_notify.rb +32 -0
  78. data/lib/webhookdb/jobs/organization_database_migration_run.rb +4 -6
  79. data/lib/webhookdb/jobs/organization_error_handler_dispatch.rb +26 -0
  80. data/lib/webhookdb/jobs/prepare_database_connections.rb +1 -0
  81. data/lib/webhookdb/jobs/process_webhook.rb +11 -12
  82. data/lib/webhookdb/jobs/renew_watch_channel.rb +10 -10
  83. data/lib/webhookdb/jobs/replication_migration.rb +5 -2
  84. data/lib/webhookdb/jobs/reset_code_create_dispatch.rb +1 -2
  85. data/lib/webhookdb/jobs/scheduled_backfills.rb +2 -2
  86. data/lib/webhookdb/jobs/send_invite.rb +3 -2
  87. data/lib/webhookdb/jobs/send_test_webhook.rb +1 -3
  88. data/lib/webhookdb/jobs/send_webhook.rb +4 -5
  89. data/lib/webhookdb/jobs/stale_row_deleter.rb +31 -0
  90. data/lib/webhookdb/jobs/sync_target_enqueue_scheduled.rb +3 -0
  91. data/lib/webhookdb/jobs/sync_target_run_sync.rb +9 -15
  92. data/lib/webhookdb/jobs/{webhook_subscription_delivery_attempt.rb → webhook_subscription_delivery_event.rb} +5 -8
  93. data/lib/webhookdb/liquid/expose.rb +1 -1
  94. data/lib/webhookdb/liquid/filters.rb +1 -1
  95. data/lib/webhookdb/liquid/partial.rb +2 -2
  96. data/lib/webhookdb/logged_webhook/resilient.rb +3 -3
  97. data/lib/webhookdb/logged_webhook.rb +16 -2
  98. data/lib/webhookdb/message/email_transport.rb +1 -1
  99. data/lib/webhookdb/message/transport.rb +1 -1
  100. data/lib/webhookdb/message.rb +55 -4
  101. data/lib/webhookdb/messages/error_generic_backfill.rb +47 -0
  102. data/lib/webhookdb/messages/error_icalendar_fetch.rb +5 -0
  103. data/lib/webhookdb/messages/error_signalwire_send_sms.rb +2 -0
  104. data/lib/webhookdb/messages/specs.rb +16 -0
  105. data/lib/webhookdb/organization/alerting.rb +56 -6
  106. data/lib/webhookdb/organization/database_migration.rb +2 -2
  107. data/lib/webhookdb/organization/db_builder.rb +5 -4
  108. data/lib/webhookdb/organization/error_handler.rb +141 -0
  109. data/lib/webhookdb/organization.rb +76 -10
  110. data/lib/webhookdb/postgres/model.rb +1 -0
  111. data/lib/webhookdb/postgres/model_utilities.rb +2 -0
  112. data/lib/webhookdb/postgres.rb +3 -4
  113. data/lib/webhookdb/replicator/base.rb +202 -68
  114. data/lib/webhookdb/replicator/base_stale_row_deleter.rb +165 -0
  115. data/lib/webhookdb/replicator/column.rb +2 -0
  116. data/lib/webhookdb/replicator/email_octopus_contact_v1.rb +0 -1
  117. data/lib/webhookdb/replicator/fake.rb +106 -88
  118. data/lib/webhookdb/replicator/front_signalwire_message_channel_app_v1.rb +131 -61
  119. data/lib/webhookdb/replicator/github_repo_v1_mixin.rb +17 -0
  120. data/lib/webhookdb/replicator/icalendar_calendar_v1.rb +197 -32
  121. data/lib/webhookdb/replicator/icalendar_event_v1.rb +20 -44
  122. data/lib/webhookdb/replicator/icalendar_event_v1_partitioned.rb +33 -0
  123. data/lib/webhookdb/replicator/intercom_contact_v1.rb +1 -0
  124. data/lib/webhookdb/replicator/intercom_conversation_v1.rb +1 -0
  125. data/lib/webhookdb/replicator/intercom_v1_mixin.rb +49 -6
  126. data/lib/webhookdb/replicator/partitionable_mixin.rb +116 -0
  127. data/lib/webhookdb/replicator/shopify_v1_mixin.rb +1 -1
  128. data/lib/webhookdb/replicator/signalwire_message_v1.rb +31 -1
  129. data/lib/webhookdb/replicator/sponsy_v1_mixin.rb +1 -1
  130. data/lib/webhookdb/replicator/transistor_episode_stats_v1.rb +0 -1
  131. data/lib/webhookdb/replicator/transistor_episode_v1.rb +11 -5
  132. data/lib/webhookdb/replicator/webhook_request.rb +8 -0
  133. data/lib/webhookdb/replicator.rb +6 -3
  134. data/lib/webhookdb/service/helpers.rb +4 -0
  135. data/lib/webhookdb/service/middleware.rb +6 -2
  136. data/lib/webhookdb/service/view_api.rb +1 -1
  137. data/lib/webhookdb/service.rb +10 -10
  138. data/lib/webhookdb/service_integration.rb +19 -1
  139. data/lib/webhookdb/signalwire.rb +1 -1
  140. data/lib/webhookdb/spec_helpers/async.rb +0 -4
  141. data/lib/webhookdb/spec_helpers/sentry.rb +32 -0
  142. data/lib/webhookdb/spec_helpers/shared_examples_for_replicators.rb +239 -64
  143. data/lib/webhookdb/spec_helpers.rb +1 -0
  144. data/lib/webhookdb/sync_target.rb +202 -34
  145. data/lib/webhookdb/system_log_event.rb +9 -0
  146. data/lib/webhookdb/tasks/admin.rb +1 -1
  147. data/lib/webhookdb/tasks/annotate.rb +1 -1
  148. data/lib/webhookdb/tasks/db.rb +13 -1
  149. data/lib/webhookdb/tasks/docs.rb +1 -1
  150. data/lib/webhookdb/tasks/fixture.rb +1 -1
  151. data/lib/webhookdb/tasks/message.rb +1 -1
  152. data/lib/webhookdb/tasks/regress.rb +1 -1
  153. data/lib/webhookdb/tasks/release.rb +1 -1
  154. data/lib/webhookdb/tasks/sidekiq.rb +1 -1
  155. data/lib/webhookdb/tasks/specs.rb +1 -1
  156. data/lib/webhookdb/version.rb +1 -1
  157. data/lib/webhookdb/webhook_subscription.rb +3 -4
  158. data/lib/webhookdb.rb +34 -8
  159. metadata +114 -64
  160. data/lib/webhookdb/jobs/customer_created_notify_internal.rb +0 -22
  161. data/lib/webhookdb/jobs/organization_database_migration_notify_finished.rb +0 -21
  162. data/lib/webhookdb/jobs/organization_database_migration_notify_started.rb +0 -21
  163. /data/lib/webhookdb/jobs/{logged_webhook_resilient_replay.rb → logged_webhooks_resilient_replay.rb} +0 -0
  164. /data/lib/webhookdb/jobs/{webhook_resource_notify_integrations.rb → webhookdb_resource_notify_integrations.rb} +0 -0
@@ -4,12 +4,18 @@ require "webhookdb/spec_helpers/whdb"
4
4
 
5
5
  # The basics: these shared examples are among the most commonly used.
6
6
 
7
- RSpec.shared_examples "a replicator" do |name|
8
- let(:sint) { Webhookdb::Fixtures.service_integration.create(service_name: name) }
7
+ # @param supports_rowupsert: If true, this replicator will emit the rowupsert event when rows change.
8
+ # Nearly all replicators support this, but in some cases, replicators may not want to,
9
+ # especially when they otherwise do not adhere to normal replicator design.
10
+ # Usually this is only the case in enterprise intergrations.
11
+ #
12
+ # @param supports_row_diff: If true, test that the rowupsert event is not emitted when the row has not changed.
13
+ RSpec.shared_examples "a replicator" do |supports_rowupsert: true, supports_row_diff: true|
14
+ let(:service_name) { described_class.descriptor.name }
15
+ let(:sint) { Webhookdb::Fixtures.service_integration.create(service_name:) }
9
16
  let(:svc) { Webhookdb::Replicator.create(sint) }
10
17
  let(:body) { raise NotImplementedError }
11
18
  let(:expected_data) { body }
12
- let(:supports_row_diff) { true }
13
19
  let(:expected_row) { nil }
14
20
  Webhookdb::SpecHelpers::Whdb.setup_upsert_webhook_example(self)
15
21
 
@@ -70,48 +76,50 @@ RSpec.shared_examples "a replicator" do |name|
70
76
  end
71
77
  end
72
78
 
73
- it "emits the rowupsert event if the row has changed", :async, :do_not_defer_events, sidekiq: :fake do
74
- Webhookdb::Fixtures.webhook_subscription(service_integration: sint).create
75
- svc.create_table
76
- upsert_webhook(svc, body:)
77
- expect(Sidekiq).to have_queue.consisting_of(
78
- job_hash(
79
- Webhookdb::Jobs::SendWebhook,
80
- args: contain_exactly(
81
- hash_including(
82
- "id",
83
- "name" => "webhookdb.serviceintegration.rowupsert",
84
- "payload" => [
85
- sint.id,
86
- hash_including("external_id", "external_id_column", "row" => hash_including("data")),
87
- ],
79
+ if supports_rowupsert
80
+ it "emits the rowupsert event if the row has changed", :async, :do_not_defer_events, sidekiq: :fake do
81
+ Webhookdb::Fixtures.webhook_subscription(service_integration: sint).create
82
+ svc.create_table
83
+ upsert_webhook(svc, body:)
84
+ expect(Sidekiq).to have_queue.consisting_of(
85
+ job_hash(
86
+ Webhookdb::Jobs::SendWebhook,
87
+ args: contain_exactly(
88
+ hash_including(
89
+ "id",
90
+ "name" => "webhookdb.serviceintegration.rowupsert",
91
+ "payload" => [
92
+ sint.id,
93
+ hash_including("external_id", "external_id_column", "row" => hash_including("data")),
94
+ ],
95
+ ),
88
96
  ),
89
97
  ),
90
- ),
91
- )
92
- end
98
+ )
99
+ end
93
100
 
94
- it "does not emit the rowupsert event if the row has not changed", :async, :do_not_defer_events, sidekiq: :fake do
95
101
  if supports_row_diff
96
- Webhookdb::Fixtures.webhook_subscription(service_integration: sint).create
97
- expect(Webhookdb::Jobs::SendWebhook).to receive(:perform_async).once
102
+ it "does not emit the rowupsert event if the row has not changed", :async, :do_not_defer_events, sidekiq: :fake do
103
+ Webhookdb::Fixtures.webhook_subscription(service_integration: sint).create
104
+ expect(Webhookdb::Jobs::SendWebhook).to receive(:perform_async).once
105
+ svc.create_table
106
+ upsert_webhook(svc, body:) # Upsert and make sure the next does not run
107
+ expect do
108
+ upsert_webhook(svc, body:)
109
+ end.to_not publish("webhookdb.serviceintegration.rowupsert")
110
+ expect(Sidekiq).to have_empty_queues
111
+ end
112
+ end
113
+
114
+ it "does not emit the rowupsert event if there are no subscriptions", :async, :do_not_defer_events do
115
+ # No subscription is created so should not publish
98
116
  svc.create_table
99
- upsert_webhook(svc, body:) # Upsert and make sure the next does not run
100
117
  expect do
101
118
  upsert_webhook(svc, body:)
102
119
  end.to_not publish("webhookdb.serviceintegration.rowupsert")
103
- expect(Sidekiq).to have_empty_queues
104
120
  end
105
121
  end
106
122
 
107
- it "does not emit the rowupsert event if there are no subscriptions", :async, :do_not_defer_events do
108
- # No subscription is created so should not publish
109
- svc.create_table
110
- expect do
111
- upsert_webhook(svc, body:)
112
- end.to_not publish("webhookdb.serviceintegration.rowupsert")
113
- end
114
-
115
123
  it "can serve a webhook response" do
116
124
  create_all_dependencies(sint)
117
125
  request = fake_request
@@ -174,7 +182,8 @@ RSpec.shared_examples "a replicator" do |name|
174
182
  end
175
183
  end
176
184
 
177
- RSpec.shared_examples "a replicator with dependents" do |service_name, dependent_service_name|
185
+ RSpec.shared_examples "a replicator with dependents" do |dependent_service_name|
186
+ let(:service_name) { described_class.descriptor.name }
178
187
  let(:sint) { Webhookdb::Fixtures.service_integration.create(service_name:) }
179
188
  let(:svc) { Webhookdb::Replicator.create(sint) }
180
189
  let(:body) { raise NotImplementedError }
@@ -218,7 +227,8 @@ RSpec.shared_examples "a replicator with dependents" do |service_name, dependent
218
227
  end
219
228
  end
220
229
 
221
- RSpec.shared_examples "a replicator dependent on another" do |service_name, dependency_service_name|
230
+ RSpec.shared_examples "a replicator dependent on another" do |dependency_service_name|
231
+ let(:service_name) { described_class.descriptor.name }
222
232
  let(:sint) { Webhookdb::Fixtures.service_integration.create(service_name:) }
223
233
  let(:svc) { Webhookdb::Replicator.create(sint) }
224
234
 
@@ -250,8 +260,9 @@ RSpec.shared_examples "a replicator dependent on another" do |service_name, depe
250
260
  end
251
261
  end
252
262
 
253
- RSpec.shared_examples "a replicator that prevents overwriting new data with old" do |name|
254
- let(:sint) { Webhookdb::Fixtures.service_integration.create(service_name: name) }
263
+ RSpec.shared_examples "a replicator that prevents overwriting new data with old" do
264
+ let(:service_name) { described_class.descriptor.name }
265
+ let(:sint) { Webhookdb::Fixtures.service_integration.create(service_name:) }
255
266
  let(:svc) { Webhookdb::Replicator.create(sint) }
256
267
  let(:old_body) { raise NotImplementedError }
257
268
  let(:new_body) { raise NotImplementedError }
@@ -313,11 +324,12 @@ RSpec.shared_examples "a replicator that prevents overwriting new data with old"
313
324
  end
314
325
  end
315
326
 
316
- RSpec.shared_examples "a replicator that can backfill" do |name|
327
+ RSpec.shared_examples "a replicator that can backfill" do
317
328
  let(:api_url) { "https://fake-url.com" }
329
+ let(:service_name) { described_class.descriptor.name }
318
330
  let(:sint) do
319
331
  Webhookdb::Fixtures.service_integration.create(
320
- service_name: name,
332
+ service_name:,
321
333
  backfill_key: "bfkey",
322
334
  backfill_secret: "bfsek",
323
335
  api_url:,
@@ -427,8 +439,9 @@ end
427
439
 
428
440
  # These shared examples test the way a replicator synthesizes and retrieves information from the API.
429
441
 
430
- RSpec.shared_examples "a replicator that may have a minimal body" do |name|
431
- let(:sint) { Webhookdb::Fixtures.service_integration.create(service_name: name) }
442
+ RSpec.shared_examples "a replicator that may have a minimal body" do
443
+ let(:service_name) { described_class.descriptor.name }
444
+ let(:sint) { Webhookdb::Fixtures.service_integration.create(service_name:) }
432
445
  let(:svc) { Webhookdb::Replicator.create(sint) }
433
446
  let(:body) { raise NotImplementedError }
434
447
  let(:other_bodies) { [] }
@@ -453,8 +466,9 @@ RSpec.shared_examples "a replicator that may have a minimal body" do |name|
453
466
  end
454
467
  end
455
468
 
456
- RSpec.shared_examples "a replicator that deals with resources and wrapped events" do |name|
457
- let(:sint) { Webhookdb::Fixtures.service_integration.create(service_name: name) }
469
+ RSpec.shared_examples "a replicator that deals with resources and wrapped events" do
470
+ let(:service_name) { described_class.descriptor.name }
471
+ let(:sint) { Webhookdb::Fixtures.service_integration.create(service_name:) }
458
472
  let(:svc) { Webhookdb::Replicator.create(sint) }
459
473
  let(:resource_json) { raise NotImplementedError }
460
474
  let(:resource_in_envelope_json) { raise NotImplementedError }
@@ -489,8 +503,9 @@ RSpec.shared_examples "a replicator that deals with resources and wrapped events
489
503
  end
490
504
  end
491
505
 
492
- RSpec.shared_examples "a replicator that uses enrichments" do |name, stores_enrichment_column: true|
493
- let(:sint) { Webhookdb::Fixtures.service_integration.create(service_name: name) }
506
+ RSpec.shared_examples "a replicator that uses enrichments" do |stores_enrichment_column: true|
507
+ let(:service_name) { described_class.descriptor.name }
508
+ let(:sint) { Webhookdb::Fixtures.service_integration.create(service_name:) }
494
509
  let(:svc) { Webhookdb::Replicator.create(sint) }
495
510
  let(:body) { raise NotImplementedError }
496
511
  # Needed if stores_enrichment_column is true
@@ -549,8 +564,9 @@ RSpec.shared_examples "a replicator that uses enrichments" do |name, stores_enri
549
564
  end
550
565
  end
551
566
 
552
- RSpec.shared_examples "a replicator that upserts webhooks only under specific conditions" do |name|
553
- let(:sint) { Webhookdb::Fixtures.service_integration.create(service_name: name) }
567
+ RSpec.shared_examples "a replicator that upserts webhooks only under specific conditions" do
568
+ let(:service_name) { described_class.descriptor.name }
569
+ let(:sint) { Webhookdb::Fixtures.service_integration.create(service_name:) }
554
570
  let(:svc) { Webhookdb::Replicator.create(sint) }
555
571
  let(:incorrect_webhook) { raise NotImplementedError }
556
572
  Webhookdb::SpecHelpers::Whdb.setup_upsert_webhook_example(self)
@@ -574,8 +590,9 @@ end
574
590
 
575
591
  # These shared examples can be used to test replicators that support webhooks.
576
592
 
577
- RSpec.shared_examples "a webhook validating replicator that uses credentials from a dependency" do |name|
578
- let(:sint) { Webhookdb::Fixtures.service_integration.create(service_name: name) }
593
+ RSpec.shared_examples "a webhook validating replicator that uses credentials from a dependency" do
594
+ let(:service_name) { described_class.descriptor.name }
595
+ let(:sint) { Webhookdb::Fixtures.service_integration.create(service_name:) }
579
596
 
580
597
  before(:each) do
581
598
  create_all_dependencies(sint)
@@ -597,8 +614,9 @@ RSpec.shared_examples "a webhook validating replicator that uses credentials fro
597
614
  end
598
615
  end
599
616
 
600
- RSpec.shared_examples "a replicator that processes webhooks synchronously" do |name|
601
- let(:sint) { Webhookdb::Fixtures.service_integration.create(service_name: name) }
617
+ RSpec.shared_examples "a replicator that processes webhooks synchronously" do
618
+ let(:service_name) { described_class.descriptor.name }
619
+ let(:sint) { Webhookdb::Fixtures.service_integration.create(service_name:) }
602
620
  let(:svc) { Webhookdb::Replicator.create(sint) }
603
621
  let(:expected_synchronous_response) { raise NotImplementedError }
604
622
  Webhookdb::SpecHelpers::Whdb.setup_upsert_webhook_example(self)
@@ -620,8 +638,9 @@ end
620
638
 
621
639
  # These shared examples test the intricacies of backfill logic.
622
640
 
623
- RSpec.shared_examples "a backfill replicator that requires credentials from a dependency" do |name|
624
- let(:sint) { Webhookdb::Fixtures.service_integration.create(service_name: name) }
641
+ RSpec.shared_examples "a backfill replicator that requires credentials from a dependency" do
642
+ let(:service_name) { described_class.descriptor.name }
643
+ let(:sint) { Webhookdb::Fixtures.service_integration.create(service_name:) }
625
644
  let(:error_message) { raise NotImplementedError }
626
645
 
627
646
  before(:each) do
@@ -640,12 +659,13 @@ RSpec.shared_examples "a backfill replicator that requires credentials from a de
640
659
  end
641
660
  end
642
661
 
643
- RSpec.shared_examples "a replicator that can backfill incrementally" do |name|
662
+ RSpec.shared_examples "a replicator that can backfill incrementally" do
663
+ let(:service_name) { described_class.descriptor.name }
644
664
  let(:last_backfilled) { raise NotImplementedError, "what should the last_backfilled_at value be to start?" }
645
665
  let(:api_url) { "https://fake-url.com" }
646
666
  let(:sint) do
647
667
  Webhookdb::Fixtures.service_integration.create(
648
- service_name: name,
668
+ service_name:,
649
669
  backfill_key: "bfkey",
650
670
  backfill_secret: "bfsek",
651
671
  api_url:,
@@ -707,6 +727,86 @@ RSpec.shared_examples "a replicator that can backfill incrementally" do |name|
707
727
  end
708
728
  end
709
729
 
730
+ RSpec.shared_examples "a replicator that alerts on backfill auth errors" do
731
+ let(:service_name) { described_class.descriptor.name }
732
+ let(:sint_params) { {} }
733
+ let(:sint) do
734
+ Webhookdb::Fixtures.service_integration.create(
735
+ service_name:,
736
+ backfill_key: "bfkey",
737
+ backfill_secret: "bfsek",
738
+ api_url: "https://fake-url.com",
739
+ **sint_params,
740
+ )
741
+ end
742
+ let(:svc) { Webhookdb::Replicator.create(sint) }
743
+ let(:template_name) { raise NotImplementedError }
744
+
745
+ def stub_service_request
746
+ raise NotImplementedError, "stub the request without setting the return response"
747
+ end
748
+
749
+ def handled_responses
750
+ raise NotImplementedError, "Something like: [[:and_return, {status: 401}], [:and_raise, SocketError.new('hi')]]"
751
+ end
752
+
753
+ def unhandled_response
754
+ raise NotImplementedError, "Something like: [:and_return, {status: 500}]"
755
+ end
756
+
757
+ def insert_required_data_callback
758
+ # See backfiller example
759
+ return ->(*) { return }
760
+ end
761
+
762
+ before(:each) do
763
+ sint.organization.prepare_database_connections
764
+ end
765
+
766
+ after(:each) do
767
+ sint.organization.remove_related_database
768
+ end
769
+
770
+ it "dispatches an alert and returns true for handled errors (using default alert)" do
771
+ create_all_dependencies(sint)
772
+ setup_dependencies(sint, insert_required_data_callback)
773
+ Webhookdb::Fixtures.organization_membership.org(sint.organization).verified.admin.create
774
+ req = stub_service_request
775
+ handled_responses.each { |(m, arg)| req.send(m, arg) }
776
+ handled_responses.count.times do
777
+ backfill(sint)
778
+ end
779
+ expect(req).to have_been_made.times(handled_responses.count)
780
+ expect(Webhookdb::Message::Delivery.all).to contain_exactly(
781
+ have_attributes(template: template_name),
782
+ )
783
+ end
784
+
785
+ it "dispatches an alert and returns true for handled errors (using error handler)" do
786
+ create_all_dependencies(sint)
787
+ setup_dependencies(sint, insert_required_data_callback)
788
+ Webhookdb::Fixtures.organization_membership.org(sint.organization).verified.admin.create
789
+ req = stub_service_request
790
+ eh = Webhookdb::Fixtures.organization_error_handler(organization: sint.organization).create
791
+ error_handle_req = stub_request(:post, eh.url).and_return(status: 204)
792
+ handled_responses.each { |(m, arg)| req.send(m, arg) }
793
+ handled_responses.count.times do
794
+ backfill(sint)
795
+ end
796
+ expect(req).to have_been_made.times(handled_responses.count)
797
+ expect(error_handle_req).to have_been_made.times(handled_responses.count)
798
+ end
799
+
800
+ it "does not dispatch an alert, and raises the original error, if unhandled" do
801
+ create_all_dependencies(sint)
802
+ setup_dependencies(sint, insert_required_data_callback)
803
+ Webhookdb::Fixtures.organization_membership.org(sint.organization).verified.admin.create
804
+ req = stub_service_request.send(*unhandled_response)
805
+ expect { backfill(sint) }.to raise_error(Amigo::Retry::OrDie)
806
+ expect(req).to have_been_made
807
+ end
808
+ end
809
+
710
810
  RSpec.shared_examples "a replicator that verifies backfill secrets" do
711
811
  let(:correct_creds_sint) { raise NotImplementedError, "what sint should we use to test correct creds?" }
712
812
  let(:incorrect_creds_sint) { raise NotImplementedError, "what sint should we use to test incorrect creds?" }
@@ -748,19 +848,21 @@ RSpec.shared_examples "a replicator that verifies backfill secrets" do
748
848
  end
749
849
  end
750
850
 
751
- RSpec.shared_examples "a replicator with a custom backfill not supported message" do |name|
851
+ RSpec.shared_examples "a replicator with a custom backfill not supported message" do
852
+ let(:service_name) { described_class.descriptor.name }
752
853
  it "has a custom message" do
753
- sint = Webhookdb::Fixtures.service_integration.create(service_name: name)
854
+ sint = Webhookdb::Fixtures.service_integration.create(service_name:)
754
855
  expect(sint.replicator.backfill_not_supported_message).to_not include("You may be looking for one of the following")
755
856
  end
756
857
  end
757
858
 
758
- RSpec.shared_examples "a backfill replicator that marks missing rows as deleted" do |name|
859
+ RSpec.shared_examples "a backfill replicator that marks missing rows as deleted" do
860
+ let(:service_name) { described_class.descriptor.name }
759
861
  let(:deleted_column_name) { raise NotImplementedError }
760
862
  let(:api_url) { "https://fake-url.com" }
761
863
  let(:sint) do
762
864
  Webhookdb::Fixtures.service_integration.create(
763
- service_name: name,
865
+ service_name:,
764
866
  backfill_key: "bfkey",
765
867
  backfill_secret: "bfsek",
766
868
  api_url:,
@@ -813,11 +915,12 @@ RSpec.shared_examples "a backfill replicator that marks missing rows as deleted"
813
915
  end
814
916
  end
815
917
 
816
- RSpec.shared_examples "a replicator that ignores HTTP errors during backfill" do |name|
918
+ RSpec.shared_examples "a replicator that ignores HTTP errors during backfill" do
919
+ let(:service_name) { described_class.descriptor.name }
817
920
  let(:api_url) { "https://fake-url.com" }
818
921
  let(:sint) do
819
922
  Webhookdb::Fixtures.service_integration.create(
820
- service_name: name,
923
+ service_name:,
821
924
  backfill_key: "bfkey",
822
925
  backfill_secret: "bfsek",
823
926
  api_url:,
@@ -856,8 +959,9 @@ RSpec.shared_examples "a replicator that ignores HTTP errors during backfill" do
856
959
  end
857
960
  end
858
961
 
859
- RSpec.shared_examples "a replicator backfilling against the table of its dependency" do |name|
860
- let(:sint) { Webhookdb::Fixtures.service_integration.create(service_name: name) }
962
+ RSpec.shared_examples "a replicator backfilling against the table of its dependency" do
963
+ let(:service_name) { described_class.descriptor.name }
964
+ let(:sint) { Webhookdb::Fixtures.service_integration.create(service_name:) }
861
965
  let(:svc) { Webhookdb::Replicator.create(sint) }
862
966
  let(:dep_svc) { @dep_svc }
863
967
  let(:external_id_col) { raise NotImplementedError }
@@ -913,3 +1017,74 @@ RSpec.shared_examples "a replicator backfilling against the table of its depende
913
1017
  expect(svc.readonly_dataset(&:all)).to have_length(3)
914
1018
  end
915
1019
  end
1020
+
1021
+ # These shared examples test partitioning
1022
+
1023
+ RSpec.shared_examples "a replicator that supports hash partitioning" do
1024
+ let(:service_name) { described_class.descriptor.name }
1025
+ let(:partitions) { 4 }
1026
+ let(:sint) { Webhookdb::Fixtures.service_integration.create(service_name:, partition_value: partitions) }
1027
+ let(:svc) { Webhookdb::Replicator.create(sint) }
1028
+ Webhookdb::SpecHelpers::Whdb.setup_upsert_webhook_example(self)
1029
+
1030
+ before(:each) do
1031
+ sint.organization.prepare_database_connections
1032
+ end
1033
+
1034
+ after(:each) do
1035
+ sint.organization.remove_related_database
1036
+ end
1037
+
1038
+ def body(_i) = raise NotImplementedError
1039
+
1040
+ it "creates and uses the specified number of hash partitions" do
1041
+ expect(svc).to be_partition
1042
+ expect(svc.partitioning).to be_a(Webhookdb::DBAdapter::Partitioning)
1043
+ m = svc.create_table_modification
1044
+ expect(m.transaction_statements).to include(
1045
+ match(/CREATE TABLE .*_0 PARTITION OF .* FOR VALUES WITH \(MODULUS \d, REMAINDER 0\)/),
1046
+ match(/CREATE TABLE .*_1 PARTITION OF .* FOR VALUES WITH \(MODULUS \d, REMAINDER 1\)/),
1047
+ match(/CREATE TABLE .*_2 PARTITION OF .* FOR VALUES WITH \(MODULUS \d, REMAINDER 2\)/),
1048
+ match(/CREATE TABLE .*_3 PARTITION OF .* FOR VALUES WITH \(MODULUS \d, REMAINDER 3\)/),
1049
+ )
1050
+ expect { svc.create_table }.to_not raise_error
1051
+ rowcount = partitions + 2 # Make sure we hit all the remainders and extra
1052
+ Array.new(2) do
1053
+ (0...rowcount).each do |i|
1054
+ upsert_webhook(svc, body: body(i))
1055
+ end
1056
+ end
1057
+ svc.readonly_dataset do |ds|
1058
+ expect(ds.all).to have_length(rowcount)
1059
+ end
1060
+ end
1061
+ end
1062
+
1063
+ RSpec.shared_examples "a replicator that supports range partitioning" do
1064
+ let(:service_name) { described_class.descriptor.name }
1065
+ let(:sint) { Webhookdb::Fixtures.service_integration.create(service_name:) }
1066
+ let(:svc) { Webhookdb::Replicator.create(sint) }
1067
+ Webhookdb::SpecHelpers::Whdb.setup_upsert_webhook_example(self)
1068
+
1069
+ before(:each) do
1070
+ sint.organization.prepare_database_connections
1071
+ end
1072
+
1073
+ after(:each) do
1074
+ sint.organization.remove_related_database
1075
+ end
1076
+
1077
+ def body(_i) = raise NotImplementedError
1078
+
1079
+ it "prepares partitions" do
1080
+ expect(svc).to be_partition
1081
+ expect(svc.partitioning).to be_a(Webhookdb::DBAdapter::Partitioning)
1082
+ m = svc.create_table_modification
1083
+ expect(m.transaction_statements).to include(
1084
+ match(/PARTITION BY RANGE \(/),
1085
+ )
1086
+ expect { svc.create_table }.to_not raise_error
1087
+ # Awaiting implementation
1088
+ expect { upsert_webhook(svc, body: body(1)) }.to raise_error(/no partition of relation/)
1089
+ end
1090
+ end
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "oj"
4
+ require "rspec"
4
5
  require "webhookdb"
5
6
 
6
7
  RSpec::Matchers.define_negated_matcher(:exclude, :include)