webhookdb 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.
- checksums.yaml +7 -0
- data/data/messages/layouts/blank.email.liquid +10 -0
- data/data/messages/layouts/minimal.email.liquid +28 -0
- data/data/messages/layouts/standard.email.liquid +28 -0
- data/data/messages/partials/button.liquid +15 -0
- data/data/messages/partials/environment_banner.liquid +9 -0
- data/data/messages/partials/footer.liquid +22 -0
- data/data/messages/partials/greeting.liquid +3 -0
- data/data/messages/partials/logo_header.liquid +18 -0
- data/data/messages/partials/signoff.liquid +1 -0
- data/data/messages/styles/v1.liquid +346 -0
- data/data/messages/templates/errors/icalendar_fetch.email.liquid +29 -0
- data/data/messages/templates/invite.email.liquid +15 -0
- data/data/messages/templates/new_customer.email.liquid +24 -0
- data/data/messages/templates/org_database_migration_finished.email.liquid +7 -0
- data/data/messages/templates/org_database_migration_started.email.liquid +9 -0
- data/data/messages/templates/specs/_field_partial.liquid +1 -0
- data/data/messages/templates/specs/basic.email.liquid +2 -0
- data/data/messages/templates/specs/basic.fake.liquid +1 -0
- data/data/messages/templates/specs/with_field.email.liquid +2 -0
- data/data/messages/templates/specs/with_field.fake.liquid +1 -0
- data/data/messages/templates/specs/with_include.email.liquid +2 -0
- data/data/messages/templates/specs/with_partial.email.liquid +1 -0
- data/data/messages/templates/verification.email.liquid +14 -0
- data/data/messages/templates/verification.sms.liquid +1 -0
- data/data/messages/web/install-customer-login.liquid +48 -0
- data/data/messages/web/install-error.liquid +17 -0
- data/data/messages/web/install-success.liquid +35 -0
- data/data/messages/web/install.liquid +20 -0
- data/data/messages/web/partials/footer.liquid +4 -0
- data/data/messages/web/partials/form_error.liquid +1 -0
- data/data/messages/web/partials/header.liquid +3 -0
- data/data/messages/web/styles.liquid +134 -0
- data/data/windows_tz.txt +461 -0
- data/db/migrations/001_testing_pixies.rb +13 -0
- data/db/migrations/002_initial.rb +132 -0
- data/db/migrations/003_ux_overhaul.rb +20 -0
- data/db/migrations/004_incremental_backfill.rb +9 -0
- data/db/migrations/005_log_webhooks.rb +24 -0
- data/db/migrations/006_generalize_roles.rb +29 -0
- data/db/migrations/007_org_dns.rb +12 -0
- data/db/migrations/008_webhook_subscriptions.rb +19 -0
- data/db/migrations/009_nonunique_stripe_subscription_customer.rb +16 -0
- data/db/migrations/010_drop_integration_soft_delete.rb +14 -0
- data/db/migrations/011_webhook_subscriptions_created_at.rb +10 -0
- data/db/migrations/012_webhook_subscriptions_created_by.rb +9 -0
- data/db/migrations/013_default_org_membership.rb +30 -0
- data/db/migrations/014_webhook_subscription_deliveries.rb +26 -0
- data/db/migrations/015_dependent_integrations.rb +9 -0
- data/db/migrations/016_encrypted_columns.rb +9 -0
- data/db/migrations/017_skip_verification.rb +9 -0
- data/db/migrations/018_sync_targets.rb +25 -0
- data/db/migrations/019_org_schema.rb +9 -0
- data/db/migrations/020_org_database_migrations.rb +25 -0
- data/db/migrations/021_no_default_org_schema.rb +14 -0
- data/db/migrations/022_database_document.rb +15 -0
- data/db/migrations/023_sync_target_schema.rb +9 -0
- data/db/migrations/024_org_semaphore_jobs.rb +9 -0
- data/db/migrations/025_integration_backfill_cursor.rb +9 -0
- data/db/migrations/026_undo_integration_backfill_cursor.rb +9 -0
- data/db/migrations/027_sync_target_http_sync.rb +12 -0
- data/db/migrations/028_logged_webhook_path.rb +24 -0
- data/db/migrations/029_encrypt_columns.rb +97 -0
- data/db/migrations/030_org_sync_target_timeout.rb +9 -0
- data/db/migrations/031_org_max_query_rows.rb +9 -0
- data/db/migrations/032_remove_db_defaults.rb +12 -0
- data/db/migrations/033_backfill_jobs.rb +26 -0
- data/db/migrations/034_backfill_job_criteria.rb +9 -0
- data/db/migrations/035_synchronous_backfill.rb +9 -0
- data/db/migrations/036_oauth.rb +26 -0
- data/db/migrations/037_oauth_used.rb +9 -0
- data/lib/amigo/durable_job.rb +416 -0
- data/lib/pry/clipboard.rb +111 -0
- data/lib/sequel/advisory_lock.rb +65 -0
- data/lib/webhookdb/admin.rb +4 -0
- data/lib/webhookdb/admin_api/auth.rb +36 -0
- data/lib/webhookdb/admin_api/customers.rb +63 -0
- data/lib/webhookdb/admin_api/database_documents.rb +20 -0
- data/lib/webhookdb/admin_api/entities.rb +66 -0
- data/lib/webhookdb/admin_api/message_deliveries.rb +61 -0
- data/lib/webhookdb/admin_api/roles.rb +15 -0
- data/lib/webhookdb/admin_api.rb +34 -0
- data/lib/webhookdb/aggregate_result.rb +63 -0
- data/lib/webhookdb/api/auth.rb +122 -0
- data/lib/webhookdb/api/connstr_auth.rb +36 -0
- data/lib/webhookdb/api/db.rb +188 -0
- data/lib/webhookdb/api/demo.rb +14 -0
- data/lib/webhookdb/api/entities.rb +198 -0
- data/lib/webhookdb/api/helpers.rb +253 -0
- data/lib/webhookdb/api/install.rb +296 -0
- data/lib/webhookdb/api/me.rb +53 -0
- data/lib/webhookdb/api/organizations.rb +254 -0
- data/lib/webhookdb/api/replay.rb +64 -0
- data/lib/webhookdb/api/service_integrations.rb +402 -0
- data/lib/webhookdb/api/services.rb +27 -0
- data/lib/webhookdb/api/stripe.rb +22 -0
- data/lib/webhookdb/api/subscriptions.rb +67 -0
- data/lib/webhookdb/api/sync_targets.rb +232 -0
- data/lib/webhookdb/api/system.rb +37 -0
- data/lib/webhookdb/api/webhook_subscriptions.rb +96 -0
- data/lib/webhookdb/api.rb +92 -0
- data/lib/webhookdb/apps.rb +93 -0
- data/lib/webhookdb/async/audit_logger.rb +38 -0
- data/lib/webhookdb/async/autoscaler.rb +84 -0
- data/lib/webhookdb/async/job.rb +18 -0
- data/lib/webhookdb/async/job_logger.rb +45 -0
- data/lib/webhookdb/async/scheduled_job.rb +18 -0
- data/lib/webhookdb/async.rb +142 -0
- data/lib/webhookdb/aws.rb +98 -0
- data/lib/webhookdb/backfill_job.rb +107 -0
- data/lib/webhookdb/backfiller.rb +107 -0
- data/lib/webhookdb/cloudflare.rb +39 -0
- data/lib/webhookdb/connection_cache.rb +177 -0
- data/lib/webhookdb/console.rb +71 -0
- data/lib/webhookdb/convertkit.rb +14 -0
- data/lib/webhookdb/crypto.rb +66 -0
- data/lib/webhookdb/customer/reset_code.rb +94 -0
- data/lib/webhookdb/customer.rb +347 -0
- data/lib/webhookdb/database_document.rb +72 -0
- data/lib/webhookdb/db_adapter/column_types.rb +37 -0
- data/lib/webhookdb/db_adapter/default_sql.rb +187 -0
- data/lib/webhookdb/db_adapter/pg.rb +96 -0
- data/lib/webhookdb/db_adapter/snowflake.rb +137 -0
- data/lib/webhookdb/db_adapter.rb +208 -0
- data/lib/webhookdb/dbutil.rb +92 -0
- data/lib/webhookdb/demo_mode.rb +100 -0
- data/lib/webhookdb/developer_alert.rb +51 -0
- data/lib/webhookdb/email_octopus.rb +21 -0
- data/lib/webhookdb/enumerable.rb +18 -0
- data/lib/webhookdb/fixtures/backfill_jobs.rb +72 -0
- data/lib/webhookdb/fixtures/customers.rb +65 -0
- data/lib/webhookdb/fixtures/database_documents.rb +27 -0
- data/lib/webhookdb/fixtures/faker.rb +41 -0
- data/lib/webhookdb/fixtures/logged_webhooks.rb +56 -0
- data/lib/webhookdb/fixtures/message_deliveries.rb +59 -0
- data/lib/webhookdb/fixtures/oauth_sessions.rb +24 -0
- data/lib/webhookdb/fixtures/organization_database_migrations.rb +37 -0
- data/lib/webhookdb/fixtures/organization_memberships.rb +54 -0
- data/lib/webhookdb/fixtures/organizations.rb +32 -0
- data/lib/webhookdb/fixtures/reset_codes.rb +23 -0
- data/lib/webhookdb/fixtures/service_integrations.rb +42 -0
- data/lib/webhookdb/fixtures/subscriptions.rb +33 -0
- data/lib/webhookdb/fixtures/sync_targets.rb +32 -0
- data/lib/webhookdb/fixtures/webhook_subscriptions.rb +35 -0
- data/lib/webhookdb/fixtures.rb +15 -0
- data/lib/webhookdb/formatting.rb +56 -0
- data/lib/webhookdb/front.rb +49 -0
- data/lib/webhookdb/github.rb +22 -0
- data/lib/webhookdb/google_calendar.rb +29 -0
- data/lib/webhookdb/heroku.rb +21 -0
- data/lib/webhookdb/http.rb +114 -0
- data/lib/webhookdb/icalendar.rb +17 -0
- data/lib/webhookdb/id.rb +17 -0
- data/lib/webhookdb/idempotency.rb +90 -0
- data/lib/webhookdb/increase.rb +42 -0
- data/lib/webhookdb/intercom.rb +23 -0
- data/lib/webhookdb/jobs/amigo_test_jobs.rb +118 -0
- data/lib/webhookdb/jobs/backfill.rb +32 -0
- data/lib/webhookdb/jobs/create_mirror_table.rb +18 -0
- data/lib/webhookdb/jobs/create_stripe_customer.rb +17 -0
- data/lib/webhookdb/jobs/customer_created_notify_internal.rb +22 -0
- data/lib/webhookdb/jobs/demo_mode_sync_data.rb +19 -0
- data/lib/webhookdb/jobs/deprecated_jobs.rb +19 -0
- data/lib/webhookdb/jobs/developer_alert_handle.rb +14 -0
- data/lib/webhookdb/jobs/durable_job_recheck_poller.rb +17 -0
- data/lib/webhookdb/jobs/emailer.rb +15 -0
- data/lib/webhookdb/jobs/icalendar_enqueue_syncs.rb +25 -0
- data/lib/webhookdb/jobs/icalendar_sync.rb +23 -0
- data/lib/webhookdb/jobs/logged_webhook_replay.rb +17 -0
- data/lib/webhookdb/jobs/logged_webhook_resilient_replay.rb +15 -0
- data/lib/webhookdb/jobs/message_dispatched.rb +16 -0
- data/lib/webhookdb/jobs/organization_database_migration_notify_finished.rb +21 -0
- data/lib/webhookdb/jobs/organization_database_migration_notify_started.rb +21 -0
- data/lib/webhookdb/jobs/organization_database_migration_run.rb +24 -0
- data/lib/webhookdb/jobs/prepare_database_connections.rb +22 -0
- data/lib/webhookdb/jobs/process_webhook.rb +47 -0
- data/lib/webhookdb/jobs/renew_watch_channel.rb +24 -0
- data/lib/webhookdb/jobs/replication_migration.rb +24 -0
- data/lib/webhookdb/jobs/reset_code_create_dispatch.rb +23 -0
- data/lib/webhookdb/jobs/scheduled_backfills.rb +77 -0
- data/lib/webhookdb/jobs/send_invite.rb +15 -0
- data/lib/webhookdb/jobs/send_test_webhook.rb +25 -0
- data/lib/webhookdb/jobs/send_webhook.rb +20 -0
- data/lib/webhookdb/jobs/sync_target_enqueue_scheduled.rb +16 -0
- data/lib/webhookdb/jobs/sync_target_run_sync.rb +38 -0
- data/lib/webhookdb/jobs/trim_logged_webhooks.rb +15 -0
- data/lib/webhookdb/jobs/webhook_resource_notify_integrations.rb +30 -0
- data/lib/webhookdb/jobs/webhook_subscription_delivery_attempt.rb +29 -0
- data/lib/webhookdb/jobs.rb +4 -0
- data/lib/webhookdb/json.rb +113 -0
- data/lib/webhookdb/liquid/expose.rb +27 -0
- data/lib/webhookdb/liquid/filters.rb +16 -0
- data/lib/webhookdb/liquid/liquification.rb +26 -0
- data/lib/webhookdb/liquid/partial.rb +12 -0
- data/lib/webhookdb/logged_webhook/resilient.rb +95 -0
- data/lib/webhookdb/logged_webhook.rb +194 -0
- data/lib/webhookdb/message/body.rb +25 -0
- data/lib/webhookdb/message/delivery.rb +127 -0
- data/lib/webhookdb/message/email_transport.rb +133 -0
- data/lib/webhookdb/message/fake_transport.rb +54 -0
- data/lib/webhookdb/message/liquid_drops.rb +29 -0
- data/lib/webhookdb/message/template.rb +89 -0
- data/lib/webhookdb/message/transport.rb +43 -0
- data/lib/webhookdb/message.rb +150 -0
- data/lib/webhookdb/messages/error_icalendar_fetch.rb +42 -0
- data/lib/webhookdb/messages/invite.rb +23 -0
- data/lib/webhookdb/messages/new_customer.rb +14 -0
- data/lib/webhookdb/messages/org_database_migration_finished.rb +23 -0
- data/lib/webhookdb/messages/org_database_migration_started.rb +24 -0
- data/lib/webhookdb/messages/specs.rb +57 -0
- data/lib/webhookdb/messages/verification.rb +23 -0
- data/lib/webhookdb/method_utilities.rb +82 -0
- data/lib/webhookdb/microsoft_calendar.rb +36 -0
- data/lib/webhookdb/nextpax.rb +14 -0
- data/lib/webhookdb/oauth/front.rb +58 -0
- data/lib/webhookdb/oauth/intercom.rb +58 -0
- data/lib/webhookdb/oauth/session.rb +24 -0
- data/lib/webhookdb/oauth.rb +80 -0
- data/lib/webhookdb/organization/alerting.rb +35 -0
- data/lib/webhookdb/organization/database_migration.rb +151 -0
- data/lib/webhookdb/organization/db_builder.rb +429 -0
- data/lib/webhookdb/organization.rb +506 -0
- data/lib/webhookdb/organization_membership.rb +58 -0
- data/lib/webhookdb/phone_number.rb +38 -0
- data/lib/webhookdb/plaid.rb +23 -0
- data/lib/webhookdb/platform.rb +27 -0
- data/lib/webhookdb/plivo.rb +52 -0
- data/lib/webhookdb/postgres/maintenance.rb +166 -0
- data/lib/webhookdb/postgres/model.rb +82 -0
- data/lib/webhookdb/postgres/model_utilities.rb +382 -0
- data/lib/webhookdb/postgres/testing_pixie.rb +16 -0
- data/lib/webhookdb/postgres/validations.rb +46 -0
- data/lib/webhookdb/postgres.rb +176 -0
- data/lib/webhookdb/postmark.rb +20 -0
- data/lib/webhookdb/redis.rb +35 -0
- data/lib/webhookdb/replicator/atom_single_feed_v1.rb +116 -0
- data/lib/webhookdb/replicator/aws_pricing_v1.rb +488 -0
- data/lib/webhookdb/replicator/base.rb +1185 -0
- data/lib/webhookdb/replicator/column.rb +482 -0
- data/lib/webhookdb/replicator/convertkit_broadcast_v1.rb +69 -0
- data/lib/webhookdb/replicator/convertkit_subscriber_v1.rb +200 -0
- data/lib/webhookdb/replicator/convertkit_tag_v1.rb +66 -0
- data/lib/webhookdb/replicator/convertkit_v1_mixin.rb +65 -0
- data/lib/webhookdb/replicator/docgen.rb +167 -0
- data/lib/webhookdb/replicator/email_octopus_campaign_v1.rb +84 -0
- data/lib/webhookdb/replicator/email_octopus_contact_v1.rb +159 -0
- data/lib/webhookdb/replicator/email_octopus_event_v1.rb +244 -0
- data/lib/webhookdb/replicator/email_octopus_list_v1.rb +101 -0
- data/lib/webhookdb/replicator/fake.rb +453 -0
- data/lib/webhookdb/replicator/front_conversation_v1.rb +45 -0
- data/lib/webhookdb/replicator/front_marketplace_root_v1.rb +55 -0
- data/lib/webhookdb/replicator/front_message_v1.rb +45 -0
- data/lib/webhookdb/replicator/front_v1_mixin.rb +22 -0
- data/lib/webhookdb/replicator/github_issue_comment_v1.rb +58 -0
- data/lib/webhookdb/replicator/github_issue_v1.rb +83 -0
- data/lib/webhookdb/replicator/github_pull_v1.rb +84 -0
- data/lib/webhookdb/replicator/github_release_v1.rb +47 -0
- data/lib/webhookdb/replicator/github_repo_v1_mixin.rb +250 -0
- data/lib/webhookdb/replicator/github_repository_event_v1.rb +45 -0
- data/lib/webhookdb/replicator/icalendar_calendar_v1.rb +465 -0
- data/lib/webhookdb/replicator/icalendar_event_v1.rb +334 -0
- data/lib/webhookdb/replicator/increase_account_number_v1.rb +77 -0
- data/lib/webhookdb/replicator/increase_account_transfer_v1.rb +61 -0
- data/lib/webhookdb/replicator/increase_account_v1.rb +63 -0
- data/lib/webhookdb/replicator/increase_ach_transfer_v1.rb +78 -0
- data/lib/webhookdb/replicator/increase_check_transfer_v1.rb +64 -0
- data/lib/webhookdb/replicator/increase_limit_v1.rb +78 -0
- data/lib/webhookdb/replicator/increase_transaction_v1.rb +74 -0
- data/lib/webhookdb/replicator/increase_v1_mixin.rb +121 -0
- data/lib/webhookdb/replicator/increase_wire_transfer_v1.rb +61 -0
- data/lib/webhookdb/replicator/intercom_contact_v1.rb +36 -0
- data/lib/webhookdb/replicator/intercom_conversation_v1.rb +38 -0
- data/lib/webhookdb/replicator/intercom_marketplace_root_v1.rb +69 -0
- data/lib/webhookdb/replicator/intercom_v1_mixin.rb +105 -0
- data/lib/webhookdb/replicator/oauth_refresh_access_token_mixin.rb +65 -0
- data/lib/webhookdb/replicator/plivo_sms_inbound_v1.rb +102 -0
- data/lib/webhookdb/replicator/postmark_inbound_message_v1.rb +94 -0
- data/lib/webhookdb/replicator/postmark_outbound_message_event_v1.rb +107 -0
- data/lib/webhookdb/replicator/schema_modification.rb +42 -0
- data/lib/webhookdb/replicator/shopify_customer_v1.rb +58 -0
- data/lib/webhookdb/replicator/shopify_order_v1.rb +64 -0
- data/lib/webhookdb/replicator/shopify_v1_mixin.rb +161 -0
- data/lib/webhookdb/replicator/signalwire_message_v1.rb +169 -0
- data/lib/webhookdb/replicator/sponsy_customer_v1.rb +54 -0
- data/lib/webhookdb/replicator/sponsy_placement_v1.rb +34 -0
- data/lib/webhookdb/replicator/sponsy_publication_v1.rb +125 -0
- data/lib/webhookdb/replicator/sponsy_slot_v1.rb +41 -0
- data/lib/webhookdb/replicator/sponsy_status_v1.rb +35 -0
- data/lib/webhookdb/replicator/sponsy_v1_mixin.rb +165 -0
- data/lib/webhookdb/replicator/state_machine_step.rb +69 -0
- data/lib/webhookdb/replicator/stripe_charge_v1.rb +77 -0
- data/lib/webhookdb/replicator/stripe_coupon_v1.rb +62 -0
- data/lib/webhookdb/replicator/stripe_customer_v1.rb +60 -0
- data/lib/webhookdb/replicator/stripe_dispute_v1.rb +77 -0
- data/lib/webhookdb/replicator/stripe_invoice_item_v1.rb +82 -0
- data/lib/webhookdb/replicator/stripe_invoice_v1.rb +116 -0
- data/lib/webhookdb/replicator/stripe_payout_v1.rb +67 -0
- data/lib/webhookdb/replicator/stripe_price_v1.rb +60 -0
- data/lib/webhookdb/replicator/stripe_product_v1.rb +60 -0
- data/lib/webhookdb/replicator/stripe_refund_v1.rb +101 -0
- data/lib/webhookdb/replicator/stripe_subscription_item_v1.rb +56 -0
- data/lib/webhookdb/replicator/stripe_subscription_v1.rb +75 -0
- data/lib/webhookdb/replicator/stripe_v1_mixin.rb +116 -0
- data/lib/webhookdb/replicator/transistor_episode_stats_v1.rb +141 -0
- data/lib/webhookdb/replicator/transistor_episode_v1.rb +169 -0
- data/lib/webhookdb/replicator/transistor_show_v1.rb +68 -0
- data/lib/webhookdb/replicator/transistor_v1_mixin.rb +65 -0
- data/lib/webhookdb/replicator/twilio_sms_v1.rb +156 -0
- data/lib/webhookdb/replicator/webhook_request.rb +5 -0
- data/lib/webhookdb/replicator/webhookdb_customer_v1.rb +74 -0
- data/lib/webhookdb/replicator.rb +224 -0
- data/lib/webhookdb/role.rb +42 -0
- data/lib/webhookdb/sentry.rb +35 -0
- data/lib/webhookdb/service/auth.rb +138 -0
- data/lib/webhookdb/service/collection.rb +91 -0
- data/lib/webhookdb/service/entities.rb +97 -0
- data/lib/webhookdb/service/helpers.rb +270 -0
- data/lib/webhookdb/service/middleware.rb +124 -0
- data/lib/webhookdb/service/types.rb +30 -0
- data/lib/webhookdb/service/validators.rb +32 -0
- data/lib/webhookdb/service/view_api.rb +63 -0
- data/lib/webhookdb/service.rb +219 -0
- data/lib/webhookdb/service_integration.rb +332 -0
- data/lib/webhookdb/shopify.rb +35 -0
- data/lib/webhookdb/signalwire.rb +13 -0
- data/lib/webhookdb/slack.rb +68 -0
- data/lib/webhookdb/snowflake.rb +90 -0
- data/lib/webhookdb/spec_helpers/async.rb +122 -0
- data/lib/webhookdb/spec_helpers/citest.rb +88 -0
- data/lib/webhookdb/spec_helpers/integration.rb +121 -0
- data/lib/webhookdb/spec_helpers/message.rb +41 -0
- data/lib/webhookdb/spec_helpers/postgres.rb +220 -0
- data/lib/webhookdb/spec_helpers/service.rb +432 -0
- data/lib/webhookdb/spec_helpers/shared_examples_for_columns.rb +56 -0
- data/lib/webhookdb/spec_helpers/shared_examples_for_replicators.rb +915 -0
- data/lib/webhookdb/spec_helpers/whdb.rb +139 -0
- data/lib/webhookdb/spec_helpers.rb +63 -0
- data/lib/webhookdb/sponsy.rb +14 -0
- data/lib/webhookdb/stripe.rb +37 -0
- data/lib/webhookdb/subscription.rb +203 -0
- data/lib/webhookdb/sync_target.rb +491 -0
- data/lib/webhookdb/tasks/admin.rb +49 -0
- data/lib/webhookdb/tasks/annotate.rb +36 -0
- data/lib/webhookdb/tasks/db.rb +82 -0
- data/lib/webhookdb/tasks/docs.rb +42 -0
- data/lib/webhookdb/tasks/fixture.rb +35 -0
- data/lib/webhookdb/tasks/message.rb +50 -0
- data/lib/webhookdb/tasks/regress.rb +87 -0
- data/lib/webhookdb/tasks/release.rb +27 -0
- data/lib/webhookdb/tasks/sidekiq.rb +23 -0
- data/lib/webhookdb/tasks/specs.rb +64 -0
- data/lib/webhookdb/theranest.rb +15 -0
- data/lib/webhookdb/transistor.rb +13 -0
- data/lib/webhookdb/twilio.rb +13 -0
- data/lib/webhookdb/typed_struct.rb +44 -0
- data/lib/webhookdb/version.rb +5 -0
- data/lib/webhookdb/webhook_response.rb +50 -0
- data/lib/webhookdb/webhook_subscription/delivery.rb +82 -0
- data/lib/webhookdb/webhook_subscription.rb +226 -0
- data/lib/webhookdb/windows_tz.rb +32 -0
- data/lib/webhookdb/xml.rb +92 -0
- data/lib/webhookdb.rb +224 -0
- data/lib/webterm/apps.rb +45 -0
- metadata +1129 -0
|
@@ -0,0 +1,432 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "active_support/json"
|
|
4
|
+
require "appydays/loggable"
|
|
5
|
+
require "pathname"
|
|
6
|
+
require "rack/test"
|
|
7
|
+
require "rspec"
|
|
8
|
+
require "warden"
|
|
9
|
+
|
|
10
|
+
require "webhookdb/spec_helpers"
|
|
11
|
+
|
|
12
|
+
module Webhookdb::SpecHelpers::Service
|
|
13
|
+
def self.included(context)
|
|
14
|
+
context.include(WebhookdbTestMethods)
|
|
15
|
+
|
|
16
|
+
::Warden.test_mode!
|
|
17
|
+
|
|
18
|
+
super
|
|
19
|
+
|
|
20
|
+
context.after(:each) { Warden.test_reset! }
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def last_session_id
|
|
24
|
+
(set_cookie = last_response["Set-Cookie"]) or return nil
|
|
25
|
+
(session = Webhookdb::Service.decode_cookie(set_cookie)) or return nil
|
|
26
|
+
return session["session_id"]
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def login_as(customer, opts=nil)
|
|
30
|
+
opts ||= {scope: :customer}
|
|
31
|
+
Warden.on_next_request do |proxy|
|
|
32
|
+
opts[:event] ||= :authentication
|
|
33
|
+
proxy.set_user(customer, opts)
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def login_as_admin(customer, opts={})
|
|
38
|
+
login_as(customer, opts.merge(scope: :customer))
|
|
39
|
+
login_as(customer, opts.merge(scope: :admin))
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def impersonate(admin: nil, target: nil)
|
|
43
|
+
admin ||= Webhookdb::Fixtures.customer.admin.create
|
|
44
|
+
target ||= Webhookdb::Fixtures.customer.create
|
|
45
|
+
Warden.on_next_request do |proxy|
|
|
46
|
+
proxy.set_user(admin, event: :authentication, scope: :admin)
|
|
47
|
+
proxy.set_user(target, event: :authentication, scope: :customer)
|
|
48
|
+
Webhookdb::Service::Auth::Impersonation.new(proxy).on(target)
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def logout(*scopes)
|
|
53
|
+
Warden.on_next_request do |proxy|
|
|
54
|
+
proxy.logout(*scopes)
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def fake_request(input: "", env: {})
|
|
59
|
+
req = Rack::Request.new(env.merge({"rack.input" => Rewindable.new(input)}))
|
|
60
|
+
return req
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
class Rewindable < String
|
|
64
|
+
def initialize(s)
|
|
65
|
+
super
|
|
66
|
+
@s = s
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def read(*)
|
|
70
|
+
return @s
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def rewind(*)
|
|
74
|
+
nil
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
class FakeSentryScope
|
|
79
|
+
attr_accessor :user, :tags
|
|
80
|
+
|
|
81
|
+
def initialize
|
|
82
|
+
@user = {}
|
|
83
|
+
@tags = {}
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def set_user(user)
|
|
87
|
+
@user.merge!(user)
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def set_tags(tags)
|
|
91
|
+
@tags.merge!(tags)
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
# RSpec matcher for matching Rack::Test response body
|
|
96
|
+
#
|
|
97
|
+
# Expect that the response consists of JSON of some sort:
|
|
98
|
+
#
|
|
99
|
+
# expect( last_response ).to have_json_body
|
|
100
|
+
#
|
|
101
|
+
# Expect that it's a JSON body that deserializes as an Object:
|
|
102
|
+
#
|
|
103
|
+
# expect( last_response ).to have_json_body( Object )
|
|
104
|
+
# # -or-
|
|
105
|
+
# expect( last_response ).to have_json_body( Hash )
|
|
106
|
+
#
|
|
107
|
+
# Expect that it's a JSON body that deserializes as an Array:
|
|
108
|
+
#
|
|
109
|
+
# expect( last_response ).to have_json_body( Array )
|
|
110
|
+
#
|
|
111
|
+
# Expect that it's a JSON body that deserializes as an Object that has
|
|
112
|
+
# expected keys:
|
|
113
|
+
#
|
|
114
|
+
# expect( last_response ).to have_json_body( Object ).
|
|
115
|
+
# that_includes( :id, :first_name, :last_name )
|
|
116
|
+
#
|
|
117
|
+
# Expect that it's a JSON body that deserializes as an Object that has
|
|
118
|
+
# expected keys and values:
|
|
119
|
+
#
|
|
120
|
+
# expect( last_response ).to have_json_body( Object ).
|
|
121
|
+
# that_includes(
|
|
122
|
+
# id: 118,
|
|
123
|
+
# first_name: 'Princess',
|
|
124
|
+
# last_name: 'Buttercup'
|
|
125
|
+
# )
|
|
126
|
+
#
|
|
127
|
+
# Expect that it's a JSON body that has other expected stuff:
|
|
128
|
+
#
|
|
129
|
+
# expect( last_response ).to have_json_body( Object ).
|
|
130
|
+
# that_includes(
|
|
131
|
+
# last_name: a_string_matching(/humperdink/i),
|
|
132
|
+
# profile: a_hash_including(:age, :eyecolor, :tracking_ability)
|
|
133
|
+
# )
|
|
134
|
+
#
|
|
135
|
+
# Expect a JSON Array with objects that all match the criteria:
|
|
136
|
+
#
|
|
137
|
+
# expect( last_response ).to have_json_body( Array ).
|
|
138
|
+
# of_lenth( 20 ).
|
|
139
|
+
# and( all( be_an(Integer) ) )
|
|
140
|
+
#
|
|
141
|
+
class HaveJSONBodyMatcher
|
|
142
|
+
include Appydays::Loggable
|
|
143
|
+
include RSpec::Matchers
|
|
144
|
+
|
|
145
|
+
### Create a new matcher that expects a response with a JSON body. If +expected_type+
|
|
146
|
+
### is not specified, any JSON body will be sufficient for a match.
|
|
147
|
+
def initialize(expected_type=nil)
|
|
148
|
+
@expected_type = expected_type
|
|
149
|
+
@additional_expectations = []
|
|
150
|
+
@response = nil
|
|
151
|
+
@failure_description = nil
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
attr_reader :expected_type,
|
|
155
|
+
:additional_expectations,
|
|
156
|
+
:response,
|
|
157
|
+
:failure_description
|
|
158
|
+
|
|
159
|
+
### RSpec matcher API -- returns +true+ if all expectations of the specified
|
|
160
|
+
### +response+ are met.
|
|
161
|
+
def matches?(response)
|
|
162
|
+
@response = response
|
|
163
|
+
return self.correct_content_type? &&
|
|
164
|
+
self.correct_json_type? &&
|
|
165
|
+
self.matches_additional_expectations?
|
|
166
|
+
rescue StandardError => e
|
|
167
|
+
return self.fail_with "Response has invalid JSON body: %s: %s" % [e.class.name, e.message]
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
### RSpec matcher API -- return a message describing an expectation failure.
|
|
171
|
+
def failure_message
|
|
172
|
+
return "\n---\n%s\n---\n\nReason: %s\n" % [
|
|
173
|
+
self.pretty_print_response,
|
|
174
|
+
self.failure_description,
|
|
175
|
+
]
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
### RSpec matcher API -- return a message describing an expectation being met
|
|
179
|
+
### when the matcher was used in a negated context.
|
|
180
|
+
def failure_message_when_negated
|
|
181
|
+
msg = "expected response not to have a %s" % [self.describe_type_expectation]
|
|
182
|
+
msg << " and " << self.describe_additional_expectations.join(", ") unless
|
|
183
|
+
self.additional_expectations.emtpy?
|
|
184
|
+
msg << ", but it did."
|
|
185
|
+
|
|
186
|
+
return "\n---\n%s\n---\n\nReason: %s\n" % [
|
|
187
|
+
self.pretty_print_response,
|
|
188
|
+
msg,
|
|
189
|
+
]
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
### Return the response's body parsed as JSON.
|
|
193
|
+
def parsed_response_body
|
|
194
|
+
return @parsed_response_body ||= JSON.parse(self.response.body, symbolize_names: true)
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
#
|
|
198
|
+
# Mutators
|
|
199
|
+
#
|
|
200
|
+
|
|
201
|
+
### Add an additional expectation that the JSON body contains the specified +members+.
|
|
202
|
+
def that_includes(*memberset)
|
|
203
|
+
@additional_expectations << include(*memberset)
|
|
204
|
+
return self
|
|
205
|
+
end
|
|
206
|
+
alias which_includes that_includes
|
|
207
|
+
|
|
208
|
+
### Add an additional expectation that the JSON body does not contain the
|
|
209
|
+
### specified +members+.
|
|
210
|
+
def that_excludes(*memberset)
|
|
211
|
+
@additional_expectations << exclude(*memberset)
|
|
212
|
+
return self
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
### Add an additional expectation that the JSON body contain the specified
|
|
216
|
+
### +number+ of members.
|
|
217
|
+
def of_length(number)
|
|
218
|
+
@additional_expectations << have_attributes(length: number)
|
|
219
|
+
return self
|
|
220
|
+
end
|
|
221
|
+
alias of_size of_length
|
|
222
|
+
|
|
223
|
+
### Add the specified +matchers+ as expectations of the Hash or Array that's
|
|
224
|
+
### parsed from the JSON body.
|
|
225
|
+
def and(*matchers)
|
|
226
|
+
@additional_expectations.concat(matchers)
|
|
227
|
+
return self
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
### Return a String that contains a pretty-printed version of the response object.
|
|
231
|
+
protected def pretty_print_response
|
|
232
|
+
return "%d %s HTTP/1.1\n%s\n\n%s" % [
|
|
233
|
+
self.response.status,
|
|
234
|
+
Rack::Utils::HTTP_STATUS_CODES[self.response.status],
|
|
235
|
+
self.pretty_print_response_headers,
|
|
236
|
+
self.pretty_print_response_body.encode("utf-8", invalid: :replace, undef: :replace),
|
|
237
|
+
]
|
|
238
|
+
end
|
|
239
|
+
|
|
240
|
+
### Return a String that contains a pretty-printed version of the response headers.
|
|
241
|
+
protected def pretty_print_response_headers
|
|
242
|
+
s = self.response.headers.map do |name, val|
|
|
243
|
+
"%s: %s" % [name, val]
|
|
244
|
+
end.join("\n")
|
|
245
|
+
return s
|
|
246
|
+
end
|
|
247
|
+
|
|
248
|
+
### Return a String that contains a pretty-printed version of the response body.
|
|
249
|
+
protected def pretty_print_response_body
|
|
250
|
+
return Webhookdb::Json.pretty_generate(@parsed_response_body) if
|
|
251
|
+
@parsed_response_body
|
|
252
|
+
|
|
253
|
+
data = self.response.body
|
|
254
|
+
return data ? data[0, 1000] : "(empty body)"
|
|
255
|
+
end
|
|
256
|
+
|
|
257
|
+
### Return +false+ after setting the failure message to +message+.
|
|
258
|
+
protected def fail_with(message)
|
|
259
|
+
@failure_description = message
|
|
260
|
+
self.logger.error "Failing with: %s" % [message]
|
|
261
|
+
return false
|
|
262
|
+
end
|
|
263
|
+
|
|
264
|
+
### Returns +true+ if the response has a JSON content-type header.
|
|
265
|
+
protected def correct_content_type?
|
|
266
|
+
content_type = self.response["content-type"]
|
|
267
|
+
return self.fail_with "response doesn't have a Content-type header" unless content_type
|
|
268
|
+
|
|
269
|
+
return fail_with "response's Content-type is %p" % [content_type] unless
|
|
270
|
+
content_type.start_with?("application/json")
|
|
271
|
+
|
|
272
|
+
return true
|
|
273
|
+
end
|
|
274
|
+
|
|
275
|
+
### Return an Array of text describing the expectation that the body be an
|
|
276
|
+
### Object or an Array, if a type was expected. If no type was expected, returns
|
|
277
|
+
### an empty Array.
|
|
278
|
+
protected def describe_type_expectation
|
|
279
|
+
return case self.expected_type
|
|
280
|
+
when Object, Hash
|
|
281
|
+
"a JSON Object/Hash body"
|
|
282
|
+
when Array
|
|
283
|
+
"a JSON Array body"
|
|
284
|
+
else
|
|
285
|
+
"a JSON body"
|
|
286
|
+
end
|
|
287
|
+
end
|
|
288
|
+
|
|
289
|
+
### Check that the JSON body of the response has the correct type, if a type
|
|
290
|
+
### was specified.
|
|
291
|
+
protected def correct_json_type?
|
|
292
|
+
return self.parsed_response_body unless self.expected_type
|
|
293
|
+
|
|
294
|
+
if self.expected_type == Array
|
|
295
|
+
return self.fail_with("response body isn't a JSON Array") unless
|
|
296
|
+
self.parsed_response_body.is_a?(Array)
|
|
297
|
+
elsif self.expected_type == Object || self.expected_type == Hash
|
|
298
|
+
return self.fail_with("response body isn't a JSON Object") unless
|
|
299
|
+
self.parsed_response_body.is_a?(Hash)
|
|
300
|
+
else
|
|
301
|
+
warn "A valid JSON response can't be a %p!" % [self.expected_type]
|
|
302
|
+
end
|
|
303
|
+
|
|
304
|
+
return true
|
|
305
|
+
end
|
|
306
|
+
|
|
307
|
+
### Return an Array of descriptions of the members that were expected to be included in the
|
|
308
|
+
### response body, if any were specified. If none were specified, returns an empty
|
|
309
|
+
### Array.
|
|
310
|
+
protected def describe_additional_expectations
|
|
311
|
+
return self.additional_expectations.map(&:description)
|
|
312
|
+
end
|
|
313
|
+
|
|
314
|
+
### Check that any additional matchers registered via the `.and` mutator also
|
|
315
|
+
### match the parsed response body.
|
|
316
|
+
protected def matches_additional_expectations?
|
|
317
|
+
r = self.additional_expectations.all? do |matcher|
|
|
318
|
+
matcher.matches?(self.parsed_response_body) ||
|
|
319
|
+
fail_with(matcher.failure_message)
|
|
320
|
+
end
|
|
321
|
+
return r
|
|
322
|
+
end
|
|
323
|
+
end
|
|
324
|
+
|
|
325
|
+
class HaveStatusMatcher
|
|
326
|
+
include RSpec::Matchers
|
|
327
|
+
|
|
328
|
+
def initialize(expected_status)
|
|
329
|
+
@expected_status = expected_status
|
|
330
|
+
end
|
|
331
|
+
|
|
332
|
+
def matches?(response)
|
|
333
|
+
@response = response
|
|
334
|
+
unless response.respond_to?(:status)
|
|
335
|
+
raise "response has no .status method, did you pass in last_response.status " \
|
|
336
|
+
"instead of last_response?"
|
|
337
|
+
end
|
|
338
|
+
return response.status == @expected_status
|
|
339
|
+
end
|
|
340
|
+
|
|
341
|
+
def failure_message
|
|
342
|
+
parsed = self.parsed_body
|
|
343
|
+
msg = "expected response status %d, got a %d response instead\n" % [@expected_status, @response.status]
|
|
344
|
+
if parsed&.include?("error")
|
|
345
|
+
suffix = +""
|
|
346
|
+
if (errmsg = parsed["error"].delete("message"))
|
|
347
|
+
suffix << ("\nMessage: %s" % [errmsg])
|
|
348
|
+
end
|
|
349
|
+
if (backtrace = parsed["error"].delete("backtrace"))
|
|
350
|
+
suffix << ("\nBacktrace:\n%s" % [backtrace])
|
|
351
|
+
end
|
|
352
|
+
msg << ("Body: %s%s" % [parsed.to_json, suffix])
|
|
353
|
+
else
|
|
354
|
+
msg << ("Body: %s" % [@response.body])
|
|
355
|
+
end
|
|
356
|
+
return msg
|
|
357
|
+
end
|
|
358
|
+
|
|
359
|
+
protected def parsed_body
|
|
360
|
+
return Oj.load(@response.body)
|
|
361
|
+
rescue StandardError
|
|
362
|
+
return nil
|
|
363
|
+
end
|
|
364
|
+
end
|
|
365
|
+
|
|
366
|
+
# Matcher that will have a failure message if the response does not have the expected status.
|
|
367
|
+
#
|
|
368
|
+
# expect( last_response ).to have_status( 200 )
|
|
369
|
+
#
|
|
370
|
+
module_function def have_status(expected_status)
|
|
371
|
+
return HaveStatusMatcher.new(expected_status)
|
|
372
|
+
end
|
|
373
|
+
|
|
374
|
+
# Matcher for matching a session cookie
|
|
375
|
+
#
|
|
376
|
+
# expect(last_response).to have_session_cookie
|
|
377
|
+
#
|
|
378
|
+
RSpec::Matchers.define(:have_session_cookie) do
|
|
379
|
+
look_for = Webhookdb::Service::SESSION_COOKIE + "="
|
|
380
|
+
|
|
381
|
+
match do |response|
|
|
382
|
+
response["Set-Cookie"]&.include?(look_for)
|
|
383
|
+
end
|
|
384
|
+
|
|
385
|
+
failure_message do |string|
|
|
386
|
+
"expected response Set-Cookie to include %p but got: %p" % [look_for, string]
|
|
387
|
+
end
|
|
388
|
+
end
|
|
389
|
+
|
|
390
|
+
### Create a new matcher that will expect the response to have a JSON body of
|
|
391
|
+
### the +expected_type+. If +expected_type+ is omitted, any JSON body will be sufficient
|
|
392
|
+
### for a match.
|
|
393
|
+
module_function def have_json_body(expected_type=nil)
|
|
394
|
+
return HaveJSONBodyMatcher.new(expected_type)
|
|
395
|
+
end
|
|
396
|
+
|
|
397
|
+
### Parse the body of the last response and return it as a Ruby object.
|
|
398
|
+
module_function def last_response_json_body(expected_type=nil)
|
|
399
|
+
matcher = have_json_body(expected_type)
|
|
400
|
+
expect(last_response).to(matcher)
|
|
401
|
+
return matcher.parsed_response_body
|
|
402
|
+
end
|
|
403
|
+
|
|
404
|
+
#
|
|
405
|
+
# Rack::Test overrides: Makes posts, puts, and patches JSON requests by default.
|
|
406
|
+
#
|
|
407
|
+
|
|
408
|
+
module WebhookdbTestMethods
|
|
409
|
+
include Rack::Test::Methods
|
|
410
|
+
|
|
411
|
+
def post(uri, params={}, env={}, &)
|
|
412
|
+
env, params = make_json_request(env, params)
|
|
413
|
+
super
|
|
414
|
+
end
|
|
415
|
+
|
|
416
|
+
def put(uri, params={}, env={}, &)
|
|
417
|
+
env, params = make_json_request(env, params)
|
|
418
|
+
super
|
|
419
|
+
end
|
|
420
|
+
|
|
421
|
+
def patch(uri, params={}, env={}, &)
|
|
422
|
+
env, params = make_json_request(env, params)
|
|
423
|
+
super
|
|
424
|
+
end
|
|
425
|
+
|
|
426
|
+
def make_json_request(env, params)
|
|
427
|
+
env["CONTENT_TYPE"] ||= "application/json"
|
|
428
|
+
params = Webhookdb::Json.encode(params) if env["CONTENT_TYPE"] == "application/json" && !params.is_a?(String)
|
|
429
|
+
return env, params
|
|
430
|
+
end
|
|
431
|
+
end
|
|
432
|
+
end
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
RSpec.shared_examples "a service column converter" do |isomorphic_proc|
|
|
4
|
+
let(:initial_value) { super() }
|
|
5
|
+
let(:resource) { nil }
|
|
6
|
+
let(:event) { nil }
|
|
7
|
+
let(:enrichment) { nil }
|
|
8
|
+
let(:service_integration) { nil }
|
|
9
|
+
let(:expected_value) { super() }
|
|
10
|
+
let(:db) { Webhookdb::Postgres::Model.db }
|
|
11
|
+
|
|
12
|
+
it "returns expected value using ruby proc" do
|
|
13
|
+
v = isomorphic_proc.ruby.call(initial_value, resource:, event:, enrichment:, service_integration:)
|
|
14
|
+
expect(v).to eq(expected_value)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
it "handles nil as the value to its ruby proc without erroring" do
|
|
18
|
+
expect do
|
|
19
|
+
isomorphic_proc.ruby.call(nil, resource:, event:, enrichment:, service_integration:)
|
|
20
|
+
end.to_not raise_error
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
it "returns expected value using sql proc" do
|
|
24
|
+
e = isomorphic_proc.sql.call(initial_value)
|
|
25
|
+
v = db.select(e).first.to_a[0][1]
|
|
26
|
+
expect(v).to eq(expected_value)
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
RSpec.shared_examples "a service column defaulter" do |isomorphic_proc|
|
|
31
|
+
let(:resource) { nil }
|
|
32
|
+
let(:event) { nil }
|
|
33
|
+
let(:enrichment) { nil }
|
|
34
|
+
let(:service_integration) { nil }
|
|
35
|
+
let(:expected_value) { super() }
|
|
36
|
+
let(:expected) { eq(expected_value) }
|
|
37
|
+
let(:expected_query) { nil }
|
|
38
|
+
let(:db) { Webhookdb::Postgres::Model.db }
|
|
39
|
+
|
|
40
|
+
it "returns expected value using ruby proc" do
|
|
41
|
+
v = isomorphic_proc.ruby.call(resource:, event:, enrichment:, service_integration:)
|
|
42
|
+
expect(v).to expected
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
it "returns expected value using sql proc" do
|
|
46
|
+
e = isomorphic_proc.sql.call(service_integration:)
|
|
47
|
+
if expected_query.respond_to?(:match)
|
|
48
|
+
expect(db.select(e).sql).to match(expected_query)
|
|
49
|
+
elsif expected_query
|
|
50
|
+
expect(db.select(e).sql).to eq(expected_query)
|
|
51
|
+
else
|
|
52
|
+
v = db.select(e).first.to_a[0][1]
|
|
53
|
+
expect(v).to expected
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|