seams 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/CHANGELOG.md +335 -0
- data/LICENSE +21 -0
- data/README.md +104 -0
- data/lib/generators/seams/accounts/accounts_generator.rb +272 -0
- data/lib/generators/seams/accounts/templates/README.md.tt +219 -0
- data/lib/generators/seams/accounts/templates/app/models/account.rb.tt +124 -0
- data/lib/generators/seams/accounts/templates/app/models/application_record.rb.tt +7 -0
- data/lib/generators/seams/accounts/templates/app/models/current.rb.tt +38 -0
- data/lib/generators/seams/accounts/templates/app/models/membership.rb.tt +114 -0
- data/lib/generators/seams/accounts/templates/config/routes.rb.tt +8 -0
- data/lib/generators/seams/accounts/templates/db/migrate/create_accounts.rb.tt +29 -0
- data/lib/generators/seams/accounts/templates/db/migrate/create_accounts_memberships.rb.tt +49 -0
- data/lib/generators/seams/accounts/templates/lib/accounts.rb.tt +21 -0
- data/lib/generators/seams/accounts/templates/lib/concerns/account_scoped.rb.tt +97 -0
- data/lib/generators/seams/accounts/templates/lib/concerns/authorization.rb.tt +105 -0
- data/lib/generators/seams/accounts/templates/lib/configuration.rb.tt +26 -0
- data/lib/generators/seams/accounts/templates/lib/engine.rb.tt +55 -0
- data/lib/generators/seams/accounts/templates/spec/factories/accounts.rb.tt +49 -0
- data/lib/generators/seams/accounts/templates/spec/models/accounts/account_spec.rb.tt +64 -0
- data/lib/generators/seams/accounts/templates/spec/models/accounts/membership_spec.rb.tt +99 -0
- data/lib/generators/seams/accounts/templates/spec/runtime/accounts_boot_spec.rb.tt +181 -0
- data/lib/generators/seams/admin/admin_generator.rb +852 -0
- data/lib/generators/seams/admin/templates/README.md.tt +266 -0
- data/lib/generators/seams/admin/templates/app/controllers/admin/accounts_controller.rb.tt +16 -0
- data/lib/generators/seams/admin/templates/app/controllers/admin/accounts_memberships_controller.rb.tt +16 -0
- data/lib/generators/seams/admin/templates/app/controllers/admin/application_controller.rb.tt +282 -0
- data/lib/generators/seams/admin/templates/app/controllers/admin/identities_controller.rb.tt +26 -0
- data/lib/generators/seams/admin/templates/app/controllers/admin/invitations_controller.rb.tt +14 -0
- data/lib/generators/seams/admin/templates/app/controllers/admin/invoices_controller.rb.tt +14 -0
- data/lib/generators/seams/admin/templates/app/controllers/admin/lifetime_passes_controller.rb.tt +14 -0
- data/lib/generators/seams/admin/templates/app/controllers/admin/notification_preferences_controller.rb.tt +15 -0
- data/lib/generators/seams/admin/templates/app/controllers/admin/notifications_controller.rb.tt +18 -0
- data/lib/generators/seams/admin/templates/app/controllers/admin/plans_controller.rb.tt +14 -0
- data/lib/generators/seams/admin/templates/app/controllers/admin/subscriptions_controller.rb.tt +14 -0
- data/lib/generators/seams/admin/templates/app/controllers/admin/teams_controller.rb.tt +14 -0
- data/lib/generators/seams/admin/templates/app/controllers/admin/teams_memberships_controller.rb.tt +16 -0
- data/lib/generators/seams/admin/templates/app/dashboards/admin/account_dashboard.rb.tt +50 -0
- data/lib/generators/seams/admin/templates/app/dashboards/admin/accounts_membership_dashboard.rb.tt +58 -0
- data/lib/generators/seams/admin/templates/app/dashboards/admin/identity_dashboard.rb.tt +48 -0
- data/lib/generators/seams/admin/templates/app/dashboards/admin/invitation_dashboard.rb.tt +51 -0
- data/lib/generators/seams/admin/templates/app/dashboards/admin/invoice_dashboard.rb.tt +67 -0
- data/lib/generators/seams/admin/templates/app/dashboards/admin/lifetime_pass_dashboard.rb.tt +65 -0
- data/lib/generators/seams/admin/templates/app/dashboards/admin/notification_dashboard.rb.tt +58 -0
- data/lib/generators/seams/admin/templates/app/dashboards/admin/notification_preference_dashboard.rb.tt +43 -0
- data/lib/generators/seams/admin/templates/app/dashboards/admin/plan_dashboard.rb.tt +72 -0
- data/lib/generators/seams/admin/templates/app/dashboards/admin/subscription_dashboard.rb.tt +59 -0
- data/lib/generators/seams/admin/templates/app/dashboards/admin/team_dashboard.rb.tt +39 -0
- data/lib/generators/seams/admin/templates/app/dashboards/admin/teams_membership_dashboard.rb.tt +43 -0
- data/lib/generators/seams/admin/templates/app/policies/admin/platform/account_policy.rb.tt +10 -0
- data/lib/generators/seams/admin/templates/app/policies/admin/platform/accounts_membership_policy.rb.tt +10 -0
- data/lib/generators/seams/admin/templates/app/policies/admin/platform/application_policy.rb.tt +85 -0
- data/lib/generators/seams/admin/templates/app/policies/admin/platform/identity_policy.rb.tt +18 -0
- data/lib/generators/seams/admin/templates/app/policies/admin/platform/invitation_policy.rb.tt +9 -0
- data/lib/generators/seams/admin/templates/app/policies/admin/platform/invoice_policy.rb.tt +9 -0
- data/lib/generators/seams/admin/templates/app/policies/admin/platform/lifetime_pass_policy.rb.tt +9 -0
- data/lib/generators/seams/admin/templates/app/policies/admin/platform/notification_policy.rb.tt +9 -0
- data/lib/generators/seams/admin/templates/app/policies/admin/platform/notification_preference_policy.rb.tt +9 -0
- data/lib/generators/seams/admin/templates/app/policies/admin/platform/plan_policy.rb.tt +11 -0
- data/lib/generators/seams/admin/templates/app/policies/admin/platform/subscription_policy.rb.tt +9 -0
- data/lib/generators/seams/admin/templates/app/policies/admin/platform/team_policy.rb.tt +9 -0
- data/lib/generators/seams/admin/templates/app/policies/admin/platform/teams_membership_policy.rb.tt +9 -0
- data/lib/generators/seams/admin/templates/app/policies/admin/tenant/account_policy.rb.tt +33 -0
- data/lib/generators/seams/admin/templates/app/policies/admin/tenant/accounts_membership_policy.rb.tt +24 -0
- data/lib/generators/seams/admin/templates/app/policies/admin/tenant/application_policy.rb.tt +169 -0
- data/lib/generators/seams/admin/templates/app/policies/admin/tenant/identity_policy.rb.tt +67 -0
- data/lib/generators/seams/admin/templates/app/policies/admin/tenant/invitation_policy.rb.tt +24 -0
- data/lib/generators/seams/admin/templates/app/policies/admin/tenant/invoice_policy.rb.tt +21 -0
- data/lib/generators/seams/admin/templates/app/policies/admin/tenant/lifetime_pass_policy.rb.tt +21 -0
- data/lib/generators/seams/admin/templates/app/policies/admin/tenant/notification_policy.rb.tt +25 -0
- data/lib/generators/seams/admin/templates/app/policies/admin/tenant/notification_preference_policy.rb.tt +23 -0
- data/lib/generators/seams/admin/templates/app/policies/admin/tenant/plan_policy.rb.tt +47 -0
- data/lib/generators/seams/admin/templates/app/policies/admin/tenant/subscription_policy.rb.tt +22 -0
- data/lib/generators/seams/admin/templates/app/policies/admin/tenant/team_policy.rb.tt +28 -0
- data/lib/generators/seams/admin/templates/app/policies/admin/tenant/teams_membership_policy.rb.tt +24 -0
- data/lib/generators/seams/admin/templates/config/routes.rb.tt +38 -0
- data/lib/generators/seams/admin/templates/lib/admin.rb.tt +36 -0
- data/lib/generators/seams/admin/templates/lib/concerns/authenticator.rb.tt +66 -0
- data/lib/generators/seams/admin/templates/lib/configuration.rb.tt +90 -0
- data/lib/generators/seams/admin/templates/lib/context.rb.tt +44 -0
- data/lib/generators/seams/admin/templates/lib/engine.rb.tt +68 -0
- data/lib/generators/seams/admin/templates/spec/factories/admin.rb.tt +10 -0
- data/lib/generators/seams/admin/templates/spec/runtime/admin_boot_spec.rb.tt +604 -0
- data/lib/generators/seams/auth/add_oauth_provider/add_oauth_provider_generator.rb +157 -0
- data/lib/generators/seams/auth/add_oauth_provider/templates/adapter.rb.tt +95 -0
- data/lib/generators/seams/auth/add_oauth_provider/templates/adapter_spec.rb.tt +58 -0
- data/lib/generators/seams/auth/auth_generator.rb +311 -0
- data/lib/generators/seams/auth/templates/README.md.tt +289 -0
- data/lib/generators/seams/auth/templates/app/controllers/oauth/callbacks_controller.rb.tt +80 -0
- data/lib/generators/seams/auth/templates/app/controllers/password_resets_controller.rb.tt +44 -0
- data/lib/generators/seams/auth/templates/app/controllers/registrations_controller.rb.tt +34 -0
- data/lib/generators/seams/auth/templates/app/controllers/sessions_controller.rb.tt +49 -0
- data/lib/generators/seams/auth/templates/app/jobs/application_job.rb.tt +7 -0
- data/lib/generators/seams/auth/templates/app/jobs/cleanup_expired_sessions_job.rb.tt +30 -0
- data/lib/generators/seams/auth/templates/app/mailers/passwords_mailer.rb.tt +15 -0
- data/lib/generators/seams/auth/templates/app/models/api_token.rb.tt +62 -0
- data/lib/generators/seams/auth/templates/app/models/application_record.rb.tt +7 -0
- data/lib/generators/seams/auth/templates/app/models/current.rb.tt +15 -0
- data/lib/generators/seams/auth/templates/app/models/identity.rb.tt +74 -0
- data/lib/generators/seams/auth/templates/app/models/oauth/provider.rb.tt +48 -0
- data/lib/generators/seams/auth/templates/app/models/session.rb.tt +28 -0
- data/lib/generators/seams/auth/templates/app/services/authenticate_identity.rb.tt +31 -0
- data/lib/generators/seams/auth/templates/app/services/generate_api_token.rb.tt +35 -0
- data/lib/generators/seams/auth/templates/app/services/oauth/authenticator.rb.tt +94 -0
- data/lib/generators/seams/auth/templates/app/services/register_identity.rb.tt +57 -0
- data/lib/generators/seams/auth/templates/app/services/reset_password.rb.tt +41 -0
- data/lib/generators/seams/auth/templates/app/services/revoke_api_token.rb.tt +38 -0
- data/lib/generators/seams/auth/templates/app/views/password_resets/edit.html.erb.tt +12 -0
- data/lib/generators/seams/auth/templates/app/views/password_resets/new.html.erb.tt +11 -0
- data/lib/generators/seams/auth/templates/app/views/passwords_mailer/reset_email.html.erb.tt +7 -0
- data/lib/generators/seams/auth/templates/app/views/registrations/new.html.erb.tt +26 -0
- data/lib/generators/seams/auth/templates/app/views/sessions/_oauth_buttons.html.erb.tt +18 -0
- data/lib/generators/seams/auth/templates/app/views/sessions/new.html.erb.tt +17 -0
- data/lib/generators/seams/auth/templates/config/routes.rb.tt +21 -0
- data/lib/generators/seams/auth/templates/db/migrate/create_auth_api_tokens.rb.tt +26 -0
- data/lib/generators/seams/auth/templates/db/migrate/create_auth_identities.rb.tt +29 -0
- data/lib/generators/seams/auth/templates/db/migrate/create_auth_oauth_providers.rb.tt +35 -0
- data/lib/generators/seams/auth/templates/db/migrate/create_auth_sessions.rb.tt +19 -0
- data/lib/generators/seams/auth/templates/lib/auth.rb.tt +39 -0
- data/lib/generators/seams/auth/templates/lib/concerns/api_authenticatable.rb.tt +58 -0
- data/lib/generators/seams/auth/templates/lib/concerns/authenticatable.rb.tt +32 -0
- data/lib/generators/seams/auth/templates/lib/concerns/authentication.rb.tt +60 -0
- data/lib/generators/seams/auth/templates/lib/configuration.rb.tt +45 -0
- data/lib/generators/seams/auth/templates/lib/engine.rb.tt +46 -0
- data/lib/generators/seams/auth/templates/lib/oauth/abstract.rb.tt +87 -0
- data/lib/generators/seams/auth/templates/lib/oauth/github.rb.tt +112 -0
- data/lib/generators/seams/auth/templates/lib/oauth/google.rb.tt +78 -0
- data/lib/generators/seams/auth/templates/lib/tasks/auth_pii.rake.tt +68 -0
- data/lib/generators/seams/auth/templates/spec/factories/auth.rb.tt +38 -0
- data/lib/generators/seams/auth/templates/spec/mailers/passwords_mailer_spec.rb.tt +37 -0
- data/lib/generators/seams/auth/templates/spec/models/api_token_spec.rb.tt +84 -0
- data/lib/generators/seams/auth/templates/spec/models/identity_spec.rb.tt +56 -0
- data/lib/generators/seams/auth/templates/spec/models/oauth/provider_spec.rb.tt +64 -0
- data/lib/generators/seams/auth/templates/spec/models/session_spec.rb.tt +34 -0
- data/lib/generators/seams/auth/templates/spec/runtime/boot_spec.rb.tt +30 -0
- data/lib/generators/seams/auth/templates/spec/runtime/event_payload_spec.rb.tt +29 -0
- data/lib/generators/seams/auth/templates/spec/runtime/login_flow_spec.rb.tt +45 -0
- data/lib/generators/seams/billing/billing_generator.rb +476 -0
- data/lib/generators/seams/billing/templates/README.md.tt +355 -0
- data/lib/generators/seams/billing/templates/app/controllers/admin/lifetime_passes_controller.rb.tt +84 -0
- data/lib/generators/seams/billing/templates/app/controllers/checkout_controller.rb.tt +92 -0
- data/lib/generators/seams/billing/templates/app/controllers/invoices_controller.rb.tt +63 -0
- data/lib/generators/seams/billing/templates/app/controllers/plans_controller.rb.tt +14 -0
- data/lib/generators/seams/billing/templates/app/controllers/portal_controller.rb.tt +45 -0
- data/lib/generators/seams/billing/templates/app/controllers/subscriptions_controller.rb.tt +119 -0
- data/lib/generators/seams/billing/templates/app/controllers/webhooks_controller.rb.tt +98 -0
- data/lib/generators/seams/billing/templates/app/helpers/currency_helper.rb.tt +44 -0
- data/lib/generators/seams/billing/templates/app/jobs/application_job.rb.tt +6 -0
- data/lib/generators/seams/billing/templates/app/jobs/cancel_subscription_job.rb.tt +39 -0
- data/lib/generators/seams/billing/templates/app/jobs/start_subscription_job.rb.tt +32 -0
- data/lib/generators/seams/billing/templates/app/jobs/webhooks/process_event_job.rb.tt +37 -0
- data/lib/generators/seams/billing/templates/app/models/application_record.rb.tt +7 -0
- data/lib/generators/seams/billing/templates/app/models/invoice.rb.tt +35 -0
- data/lib/generators/seams/billing/templates/app/models/lifetime_pass.rb.tt +60 -0
- data/lib/generators/seams/billing/templates/app/models/plan.rb.tt +95 -0
- data/lib/generators/seams/billing/templates/app/models/subscription.rb.tt +31 -0
- data/lib/generators/seams/billing/templates/app/models/webhook_event.rb.tt +13 -0
- data/lib/generators/seams/billing/templates/app/services/checkout_session_service.rb.tt +25 -0
- data/lib/generators/seams/billing/templates/app/services/customers/find_or_create_service.rb.tt +73 -0
- data/lib/generators/seams/billing/templates/app/services/invoices/sync_service.rb.tt +50 -0
- data/lib/generators/seams/billing/templates/app/services/lifetime/create_lifetime_session_service.rb.tt +82 -0
- data/lib/generators/seams/billing/templates/app/services/lifetime/create_pass_from_checkout_service.rb.tt +88 -0
- data/lib/generators/seams/billing/templates/app/services/lifetime/grant_pass_service.rb.tt +80 -0
- data/lib/generators/seams/billing/templates/app/services/lifetime/revoke_pass_service.rb.tt +59 -0
- data/lib/generators/seams/billing/templates/app/services/portal_session_service.rb.tt +23 -0
- data/lib/generators/seams/billing/templates/app/services/service_result.rb.tt +38 -0
- data/lib/generators/seams/billing/templates/app/services/stripe_service.rb.tt +67 -0
- data/lib/generators/seams/billing/templates/app/services/subscriptions/cancel_service.rb.tt +42 -0
- data/lib/generators/seams/billing/templates/app/services/subscriptions/change_plan_service.rb.tt +48 -0
- data/lib/generators/seams/billing/templates/app/services/subscriptions/reactivate_service.rb.tt +28 -0
- data/lib/generators/seams/billing/templates/app/services/webhooks/event_router.rb.tt +54 -0
- data/lib/generators/seams/billing/templates/app/services/webhooks/handler.rb.tt +93 -0
- data/lib/generators/seams/billing/templates/app/services/webhooks/handlers/charge_refunded_handler.rb.tt +18 -0
- data/lib/generators/seams/billing/templates/app/services/webhooks/handlers/checkout_session_completed_handler.rb.tt +58 -0
- data/lib/generators/seams/billing/templates/app/services/webhooks/handlers/invoice_created_handler.rb.tt +16 -0
- data/lib/generators/seams/billing/templates/app/services/webhooks/handlers/invoice_finalized_handler.rb.tt +14 -0
- data/lib/generators/seams/billing/templates/app/services/webhooks/handlers/invoice_handler_base.rb.tt +80 -0
- data/lib/generators/seams/billing/templates/app/services/webhooks/handlers/invoice_paid_handler.rb.tt +12 -0
- data/lib/generators/seams/billing/templates/app/services/webhooks/handlers/invoice_payment_failed_handler.rb.tt +12 -0
- data/lib/generators/seams/billing/templates/app/services/webhooks/handlers/invoice_voided_handler.rb.tt +12 -0
- data/lib/generators/seams/billing/templates/app/services/webhooks/handlers/payment_failed_handler.rb.tt +15 -0
- data/lib/generators/seams/billing/templates/app/services/webhooks/handlers/payment_succeeded_handler.rb.tt +19 -0
- data/lib/generators/seams/billing/templates/app/services/webhooks/handlers/subscription_created_handler.rb.tt +11 -0
- data/lib/generators/seams/billing/templates/app/services/webhooks/handlers/subscription_deleted_handler.rb.tt +15 -0
- data/lib/generators/seams/billing/templates/app/services/webhooks/handlers/subscription_handler_base.rb.tt +92 -0
- data/lib/generators/seams/billing/templates/app/services/webhooks/handlers/subscription_trial_will_end_handler.rb.tt +15 -0
- data/lib/generators/seams/billing/templates/app/services/webhooks/handlers/subscription_updated_handler.rb.tt +11 -0
- data/lib/generators/seams/billing/templates/app/views/admin/lifetime_passes/index.html.erb.tt +36 -0
- data/lib/generators/seams/billing/templates/app/views/admin/lifetime_passes/new.html.erb.tt +37 -0
- data/lib/generators/seams/billing/templates/app/views/checkout/success.html.erb.tt +5 -0
- data/lib/generators/seams/billing/templates/app/views/invoices/index.html.erb.tt +22 -0
- data/lib/generators/seams/billing/templates/app/views/invoices/show.html.erb.tt +14 -0
- data/lib/generators/seams/billing/templates/app/views/plans/index.html.erb.tt +51 -0
- data/lib/generators/seams/billing/templates/app/views/subscriptions/index.html.erb.tt +16 -0
- data/lib/generators/seams/billing/templates/app/views/subscriptions/show.html.erb.tt +25 -0
- data/lib/generators/seams/billing/templates/config/routes.rb.tt +39 -0
- data/lib/generators/seams/billing/templates/db/migrate/create_billing_invoices.rb.tt +32 -0
- data/lib/generators/seams/billing/templates/db/migrate/create_billing_lifetime_passes.rb.tt +43 -0
- data/lib/generators/seams/billing/templates/db/migrate/create_billing_plans.rb.tt +31 -0
- data/lib/generators/seams/billing/templates/db/migrate/create_billing_subscriptions.rb.tt +33 -0
- data/lib/generators/seams/billing/templates/db/migrate/create_billing_webhook_events.rb.tt +24 -0
- data/lib/generators/seams/billing/templates/lib/billing.rb.tt +34 -0
- data/lib/generators/seams/billing/templates/lib/concerns/billable.rb.tt +100 -0
- data/lib/generators/seams/billing/templates/lib/configuration.rb.tt +52 -0
- data/lib/generators/seams/billing/templates/lib/engine.rb.tt +72 -0
- data/lib/generators/seams/billing/templates/lib/gateways/abstract.rb.tt +65 -0
- data/lib/generators/seams/billing/templates/lib/gateways/adyen.rb.tt +16 -0
- data/lib/generators/seams/billing/templates/lib/gateways/paddle.rb.tt +22 -0
- data/lib/generators/seams/billing/templates/lib/gateways/stripe.rb.tt +155 -0
- data/lib/generators/seams/billing/templates/lib/stripe/client.rb.tt +101 -0
- data/lib/generators/seams/billing/templates/lib/stripe/webhook_signature.rb.tt +43 -0
- data/lib/generators/seams/billing/templates/lib/tasks/billing_check.rake.tt +34 -0
- data/lib/generators/seams/billing/templates/spec/factories/billing.rb.tt +65 -0
- data/lib/generators/seams/billing/templates/spec/fixtures/stripe/charge_refunded.json.tt +19 -0
- data/lib/generators/seams/billing/templates/spec/fixtures/stripe/checkout_session_completed.json.tt +17 -0
- data/lib/generators/seams/billing/templates/spec/fixtures/stripe/customer_subscription_created.json.tt +25 -0
- data/lib/generators/seams/billing/templates/spec/fixtures/stripe/customer_subscription_deleted.json.tt +17 -0
- data/lib/generators/seams/billing/templates/spec/fixtures/stripe/customer_subscription_trial_will_end.json.tt +17 -0
- data/lib/generators/seams/billing/templates/spec/fixtures/stripe/customer_subscription_updated.json.tt +28 -0
- data/lib/generators/seams/billing/templates/spec/fixtures/stripe/invoice_created.json.tt +18 -0
- data/lib/generators/seams/billing/templates/spec/fixtures/stripe/invoice_finalized.json.tt +18 -0
- data/lib/generators/seams/billing/templates/spec/fixtures/stripe/invoice_paid.json.tt +19 -0
- data/lib/generators/seams/billing/templates/spec/fixtures/stripe/invoice_payment_failed.json.tt +20 -0
- data/lib/generators/seams/billing/templates/spec/fixtures/stripe/invoice_voided.json.tt +18 -0
- data/lib/generators/seams/billing/templates/spec/fixtures/stripe/payment_intent_payment_failed.json.tt +21 -0
- data/lib/generators/seams/billing/templates/spec/fixtures/stripe/payment_intent_succeeded.json.tt +17 -0
- data/lib/generators/seams/billing/templates/spec/gateways/contract_spec.rb.tt +11 -0
- data/lib/generators/seams/billing/templates/spec/gateways/stripe_spec.rb.tt +53 -0
- data/lib/generators/seams/billing/templates/spec/models/plan_spec.rb.tt +81 -0
- data/lib/generators/seams/billing/templates/spec/models/subscription_spec.rb.tt +43 -0
- data/lib/generators/seams/billing/templates/spec/runtime/boot_spec.rb.tt +38 -0
- data/lib/generators/seams/billing/templates/spec/runtime/webhook_handlers_spec.rb.tt +382 -0
- data/lib/generators/seams/billing/templates/spec/support/shared_examples/a_billing_gateway.rb.tt +100 -0
- data/lib/generators/seams/billing/templates/spec/support/stripe_helpers.rb.tt +59 -0
- data/lib/generators/seams/core/core_generator.rb +191 -0
- data/lib/generators/seams/core/templates/README.md.tt +45 -0
- data/lib/generators/seams/core/templates/app/controllers/concerns/has_current_attributes.rb.tt +71 -0
- data/lib/generators/seams/core/templates/app/models/application_record.rb.tt +7 -0
- data/lib/generators/seams/core/templates/app/models/audit_log.rb.tt +19 -0
- data/lib/generators/seams/core/templates/app/models/concerns/auditable.rb.tt +64 -0
- data/lib/generators/seams/core/templates/app/models/concerns/sluggable.rb.tt +53 -0
- data/lib/generators/seams/core/templates/app/models/concerns/soft_deletable.rb.tt +37 -0
- data/lib/generators/seams/core/templates/app/models/concerns/tenant_scoped.rb.tt +39 -0
- data/lib/generators/seams/core/templates/app/models/current.rb.tt +16 -0
- data/lib/generators/seams/core/templates/app/services/event_publisher.rb.tt +23 -0
- data/lib/generators/seams/core/templates/app/validators/email_format_validator.rb.tt +21 -0
- data/lib/generators/seams/core/templates/db/migrate/create_core_audit_logs.rb.tt +29 -0
- data/lib/generators/seams/core/templates/lib/core.rb.tt +8 -0
- data/lib/generators/seams/core/templates/lib/engine.rb.tt +28 -0
- data/lib/generators/seams/core/templates/spec/concerns/auditable_spec.rb.tt +39 -0
- data/lib/generators/seams/core/templates/spec/concerns/sluggable_spec.rb.tt +29 -0
- data/lib/generators/seams/core/templates/spec/models/audit_log_spec.rb.tt +22 -0
- data/lib/generators/seams/core/templates/spec/runtime/boot_spec.rb.tt +25 -0
- data/lib/generators/seams/core/templates/spec/validators/email_format_validator_spec.rb.tt +29 -0
- data/lib/generators/seams/engine/engine_generator.rb +165 -0
- data/lib/generators/seams/engine/templates/Gemfile.tt +19 -0
- data/lib/generators/seams/engine/templates/LICENSE.tt +21 -0
- data/lib/generators/seams/engine/templates/README.md.tt +40 -0
- data/lib/generators/seams/engine/templates/Rakefile.tt +14 -0
- data/lib/generators/seams/engine/templates/app/application_controller.rb.tt +6 -0
- data/lib/generators/seams/engine/templates/app/application_record.rb.tt +16 -0
- data/lib/generators/seams/engine/templates/config/locales/en.yml.tt +14 -0
- data/lib/generators/seams/engine/templates/config/routes.rb.tt +4 -0
- data/lib/generators/seams/engine/templates/gemspec.tt +20 -0
- data/lib/generators/seams/engine/templates/host_initializer.rb.tt +13 -0
- data/lib/generators/seams/engine/templates/lib/engine.rb.tt +27 -0
- data/lib/generators/seams/engine/templates/lib/root.rb.tt +7 -0
- data/lib/generators/seams/engine/templates/lib/version.rb.tt +5 -0
- data/lib/generators/seams/engine/templates/rubocop.yml.tt +55 -0
- data/lib/generators/seams/engine/templates/spec/example_spec.rb.tt +16 -0
- data/lib/generators/seams/engine/templates/spec/spec_helper.rb.tt +23 -0
- data/lib/generators/seams/install/install_generator.rb +211 -0
- data/lib/generators/seams/install/templates/Dockerfile.tt +52 -0
- data/lib/generators/seams/install/templates/Procfile.tt +14 -0
- data/lib/generators/seams/install/templates/bin_seams.tt +107 -0
- data/lib/generators/seams/install/templates/ci.yml.tt +123 -0
- data/lib/generators/seams/install/templates/deploy.yml.tt +63 -0
- data/lib/generators/seams/install/templates/doc/ARCHITECTURE.md.tt +86 -0
- data/lib/generators/seams/install/templates/docker-entrypoint.tt +27 -0
- data/lib/generators/seams/install/templates/rubocop.yml.tt +33 -0
- data/lib/generators/seams/install/templates/ruby-version.tt +1 -0
- data/lib/generators/seams/install/templates/script/collate_coverage.rb.tt +33 -0
- data/lib/generators/seams/install/templates/script/run_affected_tests.sh.tt +64 -0
- data/lib/generators/seams/install/templates/seams.rake.tt +65 -0
- data/lib/generators/seams/install/templates/seams.rb.tt +9 -0
- data/lib/generators/seams/install/templates/seams_engines.rb.tt +15 -0
- data/lib/generators/seams/notifications/notifications_generator.rb +395 -0
- data/lib/generators/seams/notifications/templates/README.md.tt +269 -0
- data/lib/generators/seams/notifications/templates/app/channels/notification_channel.rb.tt +36 -0
- data/lib/generators/seams/notifications/templates/app/controllers/notifications_controller.rb.tt +58 -0
- data/lib/generators/seams/notifications/templates/app/controllers/preferences_controller.rb.tt +54 -0
- data/lib/generators/seams/notifications/templates/app/javascript/controllers/notification_bell_controller.js.tt +34 -0
- data/lib/generators/seams/notifications/templates/app/jobs/application_job.rb.tt +6 -0
- data/lib/generators/seams/notifications/templates/app/jobs/create_notification_job.rb.tt +31 -0
- data/lib/generators/seams/notifications/templates/app/jobs/send_due_notifications_job.rb.tt +22 -0
- data/lib/generators/seams/notifications/templates/app/jobs/send_notification_job.rb.tt +13 -0
- data/lib/generators/seams/notifications/templates/app/mailers/application_mailer.rb.tt +12 -0
- data/lib/generators/seams/notifications/templates/app/mailers/notification_mailer.rb.tt +23 -0
- data/lib/generators/seams/notifications/templates/app/models/application_record.rb.tt +7 -0
- data/lib/generators/seams/notifications/templates/app/models/delivery.rb.tt +13 -0
- data/lib/generators/seams/notifications/templates/app/models/notification.rb.tt +218 -0
- data/lib/generators/seams/notifications/templates/app/models/notification_preference.rb.tt +29 -0
- data/lib/generators/seams/notifications/templates/app/models/strategies/email.rb.tt +38 -0
- data/lib/generators/seams/notifications/templates/app/models/strategies/in_app.rb.tt +26 -0
- data/lib/generators/seams/notifications/templates/app/models/strategies/sms.rb.tt +33 -0
- data/lib/generators/seams/notifications/templates/app/subscribers/auth_subscriber.rb.tt +71 -0
- data/lib/generators/seams/notifications/templates/app/subscribers/billing_subscriber.rb.tt +127 -0
- data/lib/generators/seams/notifications/templates/app/views/layouts/notifications/mailer.html.erb.tt +22 -0
- data/lib/generators/seams/notifications/templates/app/views/layouts/notifications/mailer.text.erb.tt +4 -0
- data/lib/generators/seams/notifications/templates/app/views/notifications/_bell.html.erb.tt +15 -0
- data/lib/generators/seams/notifications/templates/app/views/notifications/index.html.erb.tt +15 -0
- data/lib/generators/seams/notifications/templates/app/views/templates/billing/invoice_failed.html.erb.tt +4 -0
- data/lib/generators/seams/notifications/templates/app/views/templates/billing/invoice_failed.text.erb.tt +4 -0
- data/lib/generators/seams/notifications/templates/app/views/templates/billing/invoice_paid.html.erb.tt +3 -0
- data/lib/generators/seams/notifications/templates/app/views/templates/billing/invoice_paid.text.erb.tt +3 -0
- data/lib/generators/seams/notifications/templates/app/views/templates/billing/lifetime_granted.html.erb.tt +5 -0
- data/lib/generators/seams/notifications/templates/app/views/templates/billing/lifetime_granted.text.erb.tt +5 -0
- data/lib/generators/seams/notifications/templates/app/views/templates/billing/lifetime_purchased.html.erb.tt +5 -0
- data/lib/generators/seams/notifications/templates/app/views/templates/billing/lifetime_purchased.text.erb.tt +5 -0
- data/lib/generators/seams/notifications/templates/app/views/templates/billing/subscription_canceled.html.erb.tt +4 -0
- data/lib/generators/seams/notifications/templates/app/views/templates/billing/subscription_canceled.text.erb.tt +4 -0
- data/lib/generators/seams/notifications/templates/app/views/templates/billing/subscription_started.html.erb.tt +4 -0
- data/lib/generators/seams/notifications/templates/app/views/templates/billing/subscription_started.text.erb.tt +5 -0
- data/lib/generators/seams/notifications/templates/app/views/templates/billing/subscription_updated.html.erb.tt +3 -0
- data/lib/generators/seams/notifications/templates/app/views/templates/billing/subscription_updated.text.erb.tt +3 -0
- data/lib/generators/seams/notifications/templates/app/views/templates/default.html.erb.tt +10 -0
- data/lib/generators/seams/notifications/templates/app/views/templates/default.text.erb.tt +11 -0
- data/lib/generators/seams/notifications/templates/app/views/templates/welcome.html.erb.tt +6 -0
- data/lib/generators/seams/notifications/templates/app/views/templates/welcome.text.erb.tt +6 -0
- data/lib/generators/seams/notifications/templates/config/initializers/notifications.rb.tt +58 -0
- data/lib/generators/seams/notifications/templates/config/routes.rb.tt +17 -0
- data/lib/generators/seams/notifications/templates/db/migrate/create_notification_deliveries.rb.tt +16 -0
- data/lib/generators/seams/notifications/templates/db/migrate/create_notification_preferences.rb.tt +25 -0
- data/lib/generators/seams/notifications/templates/db/migrate/create_notifications.rb.tt +35 -0
- data/lib/generators/seams/notifications/templates/lib/adapters/abstract.rb.tt +20 -0
- data/lib/generators/seams/notifications/templates/lib/adapters/action_mailer.rb.tt +17 -0
- data/lib/generators/seams/notifications/templates/lib/adapters/null_sms.rb.tt +23 -0
- data/lib/generators/seams/notifications/templates/lib/concerns/notifiable.rb.tt +135 -0
- data/lib/generators/seams/notifications/templates/lib/configuration.rb.tt +24 -0
- data/lib/generators/seams/notifications/templates/lib/engine.rb.tt +35 -0
- data/lib/generators/seams/notifications/templates/lib/notifications.rb.tt +75 -0
- data/lib/generators/seams/notifications/templates/lib/type_registry.rb.tt +74 -0
- data/lib/generators/seams/notifications/templates/spec/factories/notifications.rb.tt +53 -0
- data/lib/generators/seams/notifications/templates/spec/models/delivery_spec.rb.tt +28 -0
- data/lib/generators/seams/notifications/templates/spec/models/notification_preference_spec.rb.tt +46 -0
- data/lib/generators/seams/notifications/templates/spec/models/notification_spec.rb.tt +60 -0
- data/lib/generators/seams/notifications/templates/spec/runtime/bell_broadcast_spec.rb.tt +59 -0
- data/lib/generators/seams/notifications/templates/spec/runtime/billing_subscriber_skip_spec.rb.tt +87 -0
- data/lib/generators/seams/notifications/templates/spec/runtime/boot_spec.rb.tt +39 -0
- data/lib/generators/seams/notifications/templates/spec/runtime/schedule_round_trip_spec.rb.tt +55 -0
- data/lib/generators/seams/remove/remove_generator.rb +259 -0
- data/lib/generators/seams/teams/teams_generator.rb +298 -0
- data/lib/generators/seams/teams/templates/README.md.tt +88 -0
- data/lib/generators/seams/teams/templates/app/controllers/invitations_controller.rb.tt +102 -0
- data/lib/generators/seams/teams/templates/app/controllers/memberships_controller.rb.tt +54 -0
- data/lib/generators/seams/teams/templates/app/controllers/teams_controller.rb.tt +68 -0
- data/lib/generators/seams/teams/templates/app/jobs/application_job.rb.tt +6 -0
- data/lib/generators/seams/teams/templates/app/mailers/invitation_mailer.rb.tt +34 -0
- data/lib/generators/seams/teams/templates/app/models/application_record.rb.tt +7 -0
- data/lib/generators/seams/teams/templates/app/models/current.rb.tt +30 -0
- data/lib/generators/seams/teams/templates/app/models/invitation.rb.tt +36 -0
- data/lib/generators/seams/teams/templates/app/models/membership.rb.tt +36 -0
- data/lib/generators/seams/teams/templates/app/models/team.rb.tt +32 -0
- data/lib/generators/seams/teams/templates/app/subscribers/invitation_subscriber.rb.tt +36 -0
- data/lib/generators/seams/teams/templates/app/views/invitation_mailer/invite.text.erb.tt +8 -0
- data/lib/generators/seams/teams/templates/app/views/invitations/index.html.erb.tt +44 -0
- data/lib/generators/seams/teams/templates/app/views/memberships/index.html.erb.tt +32 -0
- data/lib/generators/seams/teams/templates/app/views/teams/edit.html.erb.tt +28 -0
- data/lib/generators/seams/teams/templates/app/views/teams/index.html.erb.tt +15 -0
- data/lib/generators/seams/teams/templates/app/views/teams/new.html.erb.tt +24 -0
- data/lib/generators/seams/teams/templates/app/views/teams/show.html.erb.tt +17 -0
- data/lib/generators/seams/teams/templates/config/routes.rb.tt +19 -0
- data/lib/generators/seams/teams/templates/db/migrate/create_team_invitations.rb.tt +24 -0
- data/lib/generators/seams/teams/templates/db/migrate/create_team_memberships.rb.tt +25 -0
- data/lib/generators/seams/teams/templates/db/migrate/create_teams.rb.tt +18 -0
- data/lib/generators/seams/teams/templates/lib/concerns/account_scoped.rb.tt +79 -0
- data/lib/generators/seams/teams/templates/lib/concerns/authorization.rb.tt +55 -0
- data/lib/generators/seams/teams/templates/lib/configuration.rb.tt +45 -0
- data/lib/generators/seams/teams/templates/lib/engine.rb.tt +51 -0
- data/lib/generators/seams/teams/templates/lib/teams.rb.tt +22 -0
- data/lib/generators/seams/teams/templates/spec/factories/teams.rb.tt +47 -0
- data/lib/generators/seams/teams/templates/spec/models/invitation_spec.rb.tt +25 -0
- data/lib/generators/seams/teams/templates/spec/models/membership_spec.rb.tt +29 -0
- data/lib/generators/seams/teams/templates/spec/models/team_spec.rb.tt +23 -0
- data/lib/generators/seams/teams/templates/spec/runtime/boot_spec.rb.tt +32 -0
- data/lib/seams/cli/list.rb +111 -0
- data/lib/seams/cli/quality.rb +99 -0
- data/lib/seams/cli/resolve.rb +276 -0
- data/lib/seams/cli/test_changed.rb +116 -0
- data/lib/seams/cli.rb +48 -0
- data/lib/seams/configuration.rb +19 -0
- data/lib/seams/cops/known_queue_names.rb +42 -0
- data/lib/seams/cops/migration_comments.rb +68 -0
- data/lib/seams/cops/no_cross_engine_dependency.rb +58 -0
- data/lib/seams/cops/no_cross_engine_model_access.rb +153 -0
- data/lib/seams/cops.rb +18 -0
- data/lib/seams/event_registry.rb +49 -0
- data/lib/seams/events/adapter.rb +24 -0
- data/lib/seams/events/adapters/active_support.rb +31 -0
- data/lib/seams/events/publisher.rb +178 -0
- data/lib/seams/events.rb +39 -0
- data/lib/seams/generators/dummy_app_writer.rb +424 -0
- data/lib/seams/generators/eject_aware.rb +102 -0
- data/lib/seams/generators/follow_up_generator.rb +148 -0
- data/lib/seams/generators/host_injector.rb +124 -0
- data/lib/seams/generators/sibling_rubocop_writer.rb +77 -0
- data/lib/seams/generators/splicer.rb +217 -0
- data/lib/seams/observability/adapter.rb +33 -0
- data/lib/seams/observability/adapters/rails_logger.rb +59 -0
- data/lib/seams/observability.rb +34 -0
- data/lib/seams/runtime.rb +23 -0
- data/lib/seams/version.rb +5 -0
- data/lib/seams.rb +23 -0
- metadata +493 -0
|
@@ -0,0 +1,604 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../rails_helper"
|
|
4
|
+
|
|
5
|
+
# Boot spec for the seams admin engine. Wave 11A — Phase 1 (foundation)
|
|
6
|
+
# + Phase 2 (dashboards). Covers:
|
|
7
|
+
#
|
|
8
|
+
# - the engine constant loads
|
|
9
|
+
# - the four configuration knobs are wired with sensible defaults
|
|
10
|
+
# - the default authenticator returns truthy for a staff Identity
|
|
11
|
+
# and falsey for a non-staff Identity
|
|
12
|
+
# - the authenticator concern is present and gates correctly
|
|
13
|
+
# - Seams::Admin::ApplicationController inherits from
|
|
14
|
+
# Administrate::ApplicationController (the real gem; the Phase 1
|
|
15
|
+
# stub is gone now that `gem "administrate"` is in the Gemfile)
|
|
16
|
+
# - every Phase 2 dashboard class loads
|
|
17
|
+
# - every Phase 2 dashboard exposes a non-empty ATTRIBUTE_TYPES
|
|
18
|
+
# - the engine's routes contain mount points for each of the twelve
|
|
19
|
+
# dashboards
|
|
20
|
+
#
|
|
21
|
+
# Phase 3 will add Pundit policy specs once the platform-vs-tenant
|
|
22
|
+
# split lands.
|
|
23
|
+
RSpec.describe "Seams::Admin engine boot", type: :integration do
|
|
24
|
+
it "loads the engine constant" do
|
|
25
|
+
expect(defined?(Seams::Admin::Engine)).to eq("constant")
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
it "exposes the configuration class + the configure helper" do
|
|
29
|
+
expect(defined?(Seams::Admin::Configuration)).to eq("constant")
|
|
30
|
+
expect(Seams::Admin).to respond_to(:configure)
|
|
31
|
+
expect(Seams::Admin).to respond_to(:configuration)
|
|
32
|
+
expect(Seams::Admin).to respond_to(:config)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
it "exposes the Authenticator concern" do
|
|
36
|
+
expect(defined?(Seams::Admin::Authenticator)).to eq("constant")
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
it "creates the schema tables the admin engine reads from" do
|
|
40
|
+
%i[
|
|
41
|
+
auth_identities
|
|
42
|
+
accounts
|
|
43
|
+
accounts_memberships
|
|
44
|
+
teams
|
|
45
|
+
team_memberships
|
|
46
|
+
team_invitations
|
|
47
|
+
notifications
|
|
48
|
+
notification_preferences
|
|
49
|
+
notification_deliveries
|
|
50
|
+
billing_plans
|
|
51
|
+
billing_subscriptions
|
|
52
|
+
billing_invoices
|
|
53
|
+
billing_lifetime_passes
|
|
54
|
+
billing_webhook_events
|
|
55
|
+
core_audit_logs
|
|
56
|
+
].each do |t|
|
|
57
|
+
expect(ActiveRecord::Base.connection.table_exists?(t)).to be(true), "missing #{t}"
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
describe "Seams::Admin::Configuration defaults" do
|
|
62
|
+
let(:config) { Seams::Admin::Configuration.new }
|
|
63
|
+
|
|
64
|
+
it "ships the documented knobs" do
|
|
65
|
+
expect(config).to respond_to(:authenticator)
|
|
66
|
+
expect(config).to respond_to(:tenancy_scope)
|
|
67
|
+
expect(config).to respond_to(:theme_css_path)
|
|
68
|
+
expect(config).to respond_to(:before_admin_action)
|
|
69
|
+
# Phase 4 knob — host-pluggable resolver for the active
|
|
70
|
+
# Accounts::Membership.
|
|
71
|
+
expect(config).to respond_to(:current_membership_resolver)
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
it "defaults current_membership_resolver to a callable that returns nil when Accounts::Current is undefined" do
|
|
75
|
+
expect(config.current_membership_resolver).to respond_to(:call)
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
it "defaults tenancy_scope to :platform" do
|
|
79
|
+
expect(config.tenancy_scope).to eq(:platform)
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
it "defaults theme_css_path to nil" do
|
|
83
|
+
expect(config.theme_css_path).to be_nil
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
it "defaults before_admin_action to nil" do
|
|
87
|
+
expect(config.before_admin_action).to be_nil
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
it "defaults the authenticator to a callable that gates on staff?" do
|
|
91
|
+
expect(config.authenticator).to respond_to(:call)
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
describe "default authenticator behaviour" do
|
|
96
|
+
let(:gate) { Seams::Admin::Configuration.new.authenticator }
|
|
97
|
+
let(:staff_identity) { Auth::Identity.create!(email: "staff-#{SecureRandom.hex(4)}@example.com", password: "verysecret", staff: true) }
|
|
98
|
+
let(:other_identity) { Auth::Identity.create!(email: "other-#{SecureRandom.hex(4)}@example.com", password: "verysecret", staff: false) }
|
|
99
|
+
let(:staff_ctrl) { Struct.new(:current_identity).new(staff_identity) }
|
|
100
|
+
let(:other_ctrl) { Struct.new(:current_identity).new(other_identity) }
|
|
101
|
+
let(:nil_ctrl) { Struct.new(:current_identity).new(nil) }
|
|
102
|
+
|
|
103
|
+
it "returns truthy for a staff Identity" do
|
|
104
|
+
expect(gate.call(staff_ctrl)).to be(true)
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
it "returns falsey for a non-staff Identity" do
|
|
108
|
+
expect(gate.call(other_ctrl)).to be(false)
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
it "returns falsey when no Identity is signed in" do
|
|
112
|
+
expect(gate.call(nil_ctrl)).to be_nil.or be(false)
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
describe "Seams::Admin.configure" do
|
|
117
|
+
it "lets the host override the authenticator" do
|
|
118
|
+
original = Seams::Admin.config.authenticator
|
|
119
|
+
Seams::Admin.configure { |c| c.authenticator = ->(_ctrl) { :overridden } }
|
|
120
|
+
expect(Seams::Admin.config.authenticator.call(nil)).to eq(:overridden)
|
|
121
|
+
ensure
|
|
122
|
+
Seams::Admin.config.authenticator = original
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
it "lets the host switch tenancy_scope to :tenant" do
|
|
126
|
+
original = Seams::Admin.config.tenancy_scope
|
|
127
|
+
Seams::Admin.configure { |c| c.tenancy_scope = :tenant }
|
|
128
|
+
expect(Seams::Admin.config.tenancy_scope).to eq(:tenant)
|
|
129
|
+
ensure
|
|
130
|
+
Seams::Admin.config.tenancy_scope = original
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
# Phase 2 — verify the real Administrate gem is loaded (Phase 1's
|
|
135
|
+
# stub is gone) and every dashboard class is reachable. We assert
|
|
136
|
+
# the inheritance chain rather than just the constant — a stub that
|
|
137
|
+
# shadowed Administrate would still satisfy `defined?(...)`, but
|
|
138
|
+
# the ancestors list would be wrong.
|
|
139
|
+
describe "Administrate integration (Phase 2)" do
|
|
140
|
+
let(:dashboard_classes) do
|
|
141
|
+
%w[
|
|
142
|
+
Admin::IdentityDashboard
|
|
143
|
+
Admin::AccountDashboard
|
|
144
|
+
Admin::AccountsMembershipDashboard
|
|
145
|
+
Admin::TeamDashboard
|
|
146
|
+
Admin::TeamsMembershipDashboard
|
|
147
|
+
Admin::InvitationDashboard
|
|
148
|
+
Admin::NotificationDashboard
|
|
149
|
+
Admin::NotificationPreferenceDashboard
|
|
150
|
+
Admin::PlanDashboard
|
|
151
|
+
Admin::SubscriptionDashboard
|
|
152
|
+
Admin::InvoiceDashboard
|
|
153
|
+
Admin::LifetimePassDashboard
|
|
154
|
+
]
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
it "Seams::Admin::ApplicationController inherits from Administrate::ApplicationController" do
|
|
158
|
+
expect(Seams::Admin::ApplicationController.ancestors).to include(::Administrate::ApplicationController)
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
it "loads every Phase 2 dashboard and each subclasses Administrate::BaseDashboard" do
|
|
162
|
+
dashboard_classes.each do |class_name|
|
|
163
|
+
klass = class_name.constantize
|
|
164
|
+
expect(klass.ancestors).to include(::Administrate::BaseDashboard), "#{class_name} not loaded"
|
|
165
|
+
end
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
it "every Phase 2 dashboard declares a non-empty ATTRIBUTE_TYPES" do
|
|
169
|
+
dashboard_classes.each do |class_name|
|
|
170
|
+
klass = class_name.constantize
|
|
171
|
+
expect(klass::ATTRIBUTE_TYPES).to be_a(Hash)
|
|
172
|
+
expect(klass::ATTRIBUTE_TYPES).not_to be_empty
|
|
173
|
+
end
|
|
174
|
+
end
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
describe "engine routes (Phase 2)" do
|
|
178
|
+
let(:route_paths) do
|
|
179
|
+
Seams::Admin::Engine.routes.routes.map { |r| r.path.spec.to_s }
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
%w[
|
|
183
|
+
/identities
|
|
184
|
+
/accounts
|
|
185
|
+
/accounts_memberships
|
|
186
|
+
/teams
|
|
187
|
+
/teams_memberships
|
|
188
|
+
/invitations
|
|
189
|
+
/notifications
|
|
190
|
+
/notification_preferences
|
|
191
|
+
/plans
|
|
192
|
+
/subscriptions
|
|
193
|
+
/invoices
|
|
194
|
+
/lifetime_passes
|
|
195
|
+
].each do |segment|
|
|
196
|
+
it "mounts #{segment}" do
|
|
197
|
+
expect(route_paths.any? { |p| p.start_with?(segment) }).to be(true), "missing route #{segment}"
|
|
198
|
+
end
|
|
199
|
+
end
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
# Phase 3 — Pundit policies under Admin::Platform::* and
|
|
203
|
+
# Admin::Tenant::*, plus the audit-log auto-write on
|
|
204
|
+
# create/update/destroy.
|
|
205
|
+
describe "Pundit integration (Phase 3)" do
|
|
206
|
+
let(:platform_policy_classes) do
|
|
207
|
+
%w[
|
|
208
|
+
Admin::Platform::ApplicationPolicy
|
|
209
|
+
Admin::Platform::IdentityPolicy
|
|
210
|
+
Admin::Platform::AccountPolicy
|
|
211
|
+
Admin::Platform::AccountsMembershipPolicy
|
|
212
|
+
Admin::Platform::TeamPolicy
|
|
213
|
+
Admin::Platform::TeamsMembershipPolicy
|
|
214
|
+
Admin::Platform::InvitationPolicy
|
|
215
|
+
Admin::Platform::NotificationPolicy
|
|
216
|
+
Admin::Platform::NotificationPreferencePolicy
|
|
217
|
+
Admin::Platform::PlanPolicy
|
|
218
|
+
Admin::Platform::SubscriptionPolicy
|
|
219
|
+
Admin::Platform::InvoicePolicy
|
|
220
|
+
Admin::Platform::LifetimePassPolicy
|
|
221
|
+
]
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
let(:tenant_policy_classes) do
|
|
225
|
+
%w[
|
|
226
|
+
Admin::Tenant::ApplicationPolicy
|
|
227
|
+
Admin::Tenant::IdentityPolicy
|
|
228
|
+
Admin::Tenant::AccountPolicy
|
|
229
|
+
Admin::Tenant::AccountsMembershipPolicy
|
|
230
|
+
Admin::Tenant::TeamPolicy
|
|
231
|
+
Admin::Tenant::TeamsMembershipPolicy
|
|
232
|
+
Admin::Tenant::InvitationPolicy
|
|
233
|
+
Admin::Tenant::NotificationPolicy
|
|
234
|
+
Admin::Tenant::NotificationPreferencePolicy
|
|
235
|
+
Admin::Tenant::PlanPolicy
|
|
236
|
+
Admin::Tenant::SubscriptionPolicy
|
|
237
|
+
Admin::Tenant::InvoicePolicy
|
|
238
|
+
Admin::Tenant::LifetimePassPolicy
|
|
239
|
+
]
|
|
240
|
+
end
|
|
241
|
+
|
|
242
|
+
it "Seams::Admin::ApplicationController includes Pundit::Authorization" do
|
|
243
|
+
# Both ancestors are expected: Administrate::Punditize itself
|
|
244
|
+
# `include`s Pundit::Authorization, so the controller transitively
|
|
245
|
+
# picks it up. Asserting both pins the canonical wiring.
|
|
246
|
+
expect(Seams::Admin::ApplicationController.ancestors).to include(::Pundit::Authorization)
|
|
247
|
+
expect(Seams::Admin::ApplicationController.ancestors).to include(::Administrate::Punditize)
|
|
248
|
+
end
|
|
249
|
+
|
|
250
|
+
it "policy_namespace returns the Array-form Pundit expects" do
|
|
251
|
+
# `Administrate::Punditize` calls
|
|
252
|
+
# `Pundit.policy!(user, [*policy_namespace, resource])` — the
|
|
253
|
+
# namespace MUST be an Array of constants (or symbols), not a
|
|
254
|
+
# single Module. A Module-form namespace silently falls back to
|
|
255
|
+
# the top-level `<Resource>Policy` lookup, which doesn't exist
|
|
256
|
+
# in this engine. Pin the shape.
|
|
257
|
+
ctrl = Seams::Admin::ApplicationController.allocate
|
|
258
|
+
original = Seams::Admin.config.tenancy_scope
|
|
259
|
+
begin
|
|
260
|
+
Seams::Admin.config.tenancy_scope = :platform
|
|
261
|
+
expect(ctrl.policy_namespace).to be_an(Array)
|
|
262
|
+
expect(ctrl.policy_namespace.first).to eq(::Admin::Platform)
|
|
263
|
+
|
|
264
|
+
Seams::Admin.config.tenancy_scope = :tenant
|
|
265
|
+
expect(ctrl.policy_namespace.first).to eq(::Admin::Tenant)
|
|
266
|
+
ensure
|
|
267
|
+
Seams::Admin.config.tenancy_scope = original
|
|
268
|
+
end
|
|
269
|
+
end
|
|
270
|
+
|
|
271
|
+
it "exposes Seams::Admin::Context for the pundit_user wrapper" do
|
|
272
|
+
expect(defined?(Seams::Admin::Context)).to eq("constant")
|
|
273
|
+
ctx = Seams::Admin::Context.new(nil, nil)
|
|
274
|
+
expect(ctx).to respond_to(:identity)
|
|
275
|
+
expect(ctx).to respond_to(:membership)
|
|
276
|
+
expect(ctx).to respond_to(:staff?)
|
|
277
|
+
expect(ctx).to respond_to(:role)
|
|
278
|
+
expect(ctx).to respond_to(:account_id)
|
|
279
|
+
end
|
|
280
|
+
|
|
281
|
+
it "loads every Phase 3 platform policy class" do
|
|
282
|
+
platform_policy_classes.each do |class_name|
|
|
283
|
+
expect { class_name.constantize }.not_to raise_error
|
|
284
|
+
end
|
|
285
|
+
end
|
|
286
|
+
|
|
287
|
+
it "loads every Phase 3 tenant policy class" do
|
|
288
|
+
tenant_policy_classes.each do |class_name|
|
|
289
|
+
expect { class_name.constantize }.not_to raise_error
|
|
290
|
+
end
|
|
291
|
+
end
|
|
292
|
+
|
|
293
|
+
it "every platform policy inherits from Admin::Platform::ApplicationPolicy" do
|
|
294
|
+
(platform_policy_classes - %w[Admin::Platform::ApplicationPolicy]).each do |class_name|
|
|
295
|
+
klass = class_name.constantize
|
|
296
|
+
expect(klass.ancestors).to include(Admin::Platform::ApplicationPolicy), "#{class_name} not a Platform::ApplicationPolicy"
|
|
297
|
+
end
|
|
298
|
+
end
|
|
299
|
+
|
|
300
|
+
it "every tenant policy inherits from Admin::Tenant::ApplicationPolicy" do
|
|
301
|
+
(tenant_policy_classes - %w[Admin::Tenant::ApplicationPolicy]).each do |class_name|
|
|
302
|
+
klass = class_name.constantize
|
|
303
|
+
expect(klass.ancestors).to include(Admin::Tenant::ApplicationPolicy), "#{class_name} not a Tenant::ApplicationPolicy"
|
|
304
|
+
end
|
|
305
|
+
end
|
|
306
|
+
end
|
|
307
|
+
|
|
308
|
+
describe "Admin::Platform::IdentityPolicy" do
|
|
309
|
+
let(:identity) { Auth::Identity.create!(email: "p1-#{SecureRandom.hex(4)}@example.com", password: "verysecret", staff: true) }
|
|
310
|
+
let(:other_identity) { Auth::Identity.create!(email: "p2-#{SecureRandom.hex(4)}@example.com", password: "verysecret", staff: false) }
|
|
311
|
+
let(:staff_ctx) { Seams::Admin::Context.new(identity, nil) }
|
|
312
|
+
let(:non_staff_ctx) { Seams::Admin::Context.new(other_identity, nil) }
|
|
313
|
+
|
|
314
|
+
it "allows index? for a staff Identity" do
|
|
315
|
+
policy = Admin::Platform::IdentityPolicy.new(staff_ctx, identity)
|
|
316
|
+
expect(policy.index?).to be(true)
|
|
317
|
+
end
|
|
318
|
+
|
|
319
|
+
it "denies index? for a non-staff Identity" do
|
|
320
|
+
policy = Admin::Platform::IdentityPolicy.new(non_staff_ctx, identity)
|
|
321
|
+
expect(policy.index?).to be(false)
|
|
322
|
+
end
|
|
323
|
+
|
|
324
|
+
it "allows destroy? for a staff Identity" do
|
|
325
|
+
policy = Admin::Platform::IdentityPolicy.new(staff_ctx, identity)
|
|
326
|
+
expect(policy.destroy?).to be(true)
|
|
327
|
+
end
|
|
328
|
+
|
|
329
|
+
it "Scope#resolve returns scope.all for any user" do
|
|
330
|
+
Auth::Identity.create!(email: "p3-#{SecureRandom.hex(4)}@example.com", password: "verysecret")
|
|
331
|
+
resolved = Admin::Platform::IdentityPolicy::Scope.new(staff_ctx, Auth::Identity.all).resolve
|
|
332
|
+
expect(resolved.count).to eq(Auth::Identity.count)
|
|
333
|
+
end
|
|
334
|
+
end
|
|
335
|
+
|
|
336
|
+
describe "Admin::Tenant::IdentityPolicy::Scope (subquery form)" do
|
|
337
|
+
# The Phase 4 fix replaced `joins(:memberships)` with a subquery
|
|
338
|
+
# on `accounts_memberships` so the policy works without a
|
|
339
|
+
# `has_many :memberships` declaration on Auth::Identity (which
|
|
340
|
+
# the canonical seams Identity does NOT declare). The subquery
|
|
341
|
+
# filters Identities to those holding a Membership in the active
|
|
342
|
+
# account_id.
|
|
343
|
+
let(:account) { Accounts::Account.create!(name: "Tenant", external_account_id: SecureRandom.random_number(2**31)) }
|
|
344
|
+
let(:other_account) { Accounts::Account.create!(name: "Other", external_account_id: SecureRandom.random_number(2**31)) }
|
|
345
|
+
let(:in_account) { Auth::Identity.create!(email: "in-#{SecureRandom.hex(4)}@example.com", password: "verysecret") }
|
|
346
|
+
let(:out_of_account) { Auth::Identity.create!(email: "out-#{SecureRandom.hex(4)}@example.com", password: "verysecret") }
|
|
347
|
+
let(:admin_identity) { Auth::Identity.create!(email: "ad-#{SecureRandom.hex(4)}@example.com", password: "verysecret") }
|
|
348
|
+
let(:admin_membership) { Accounts::Membership.create!(account: account, identity_id: admin_identity.id, name: "Admin", role: "admin") }
|
|
349
|
+
let(:tenant_ctx) { Seams::Admin::Context.new(admin_identity, admin_membership) }
|
|
350
|
+
|
|
351
|
+
it "filters Identities to those who hold a Membership in the current Account" do
|
|
352
|
+
Accounts::Membership.create!(account: account, identity_id: in_account.id, name: "M1", role: "member")
|
|
353
|
+
Accounts::Membership.create!(account: other_account, identity_id: out_of_account.id, name: "M2", role: "member")
|
|
354
|
+
|
|
355
|
+
resolved = Admin::Tenant::IdentityPolicy::Scope.new(tenant_ctx, Auth::Identity.all).resolve
|
|
356
|
+
expect(resolved.pluck(:id)).to include(in_account.id, admin_identity.id)
|
|
357
|
+
expect(resolved.pluck(:id)).not_to include(out_of_account.id)
|
|
358
|
+
end
|
|
359
|
+
end
|
|
360
|
+
|
|
361
|
+
describe "Admin::Tenant::AccountPolicy::Scope" do
|
|
362
|
+
let(:account) { Accounts::Account.create!(name: "Tenant A", external_account_id: SecureRandom.random_number(2**31)) }
|
|
363
|
+
let(:other_account) { Accounts::Account.create!(name: "Tenant B", external_account_id: SecureRandom.random_number(2**31)) }
|
|
364
|
+
let(:identity) { Auth::Identity.create!(email: "t1-#{SecureRandom.hex(4)}@example.com", password: "verysecret", staff: false) }
|
|
365
|
+
let(:membership) { Accounts::Membership.create!(account: account, identity_id: identity.id, name: "Owner", role: "admin") }
|
|
366
|
+
let(:tenant_ctx) { Seams::Admin::Context.new(identity, membership) }
|
|
367
|
+
|
|
368
|
+
it "filters Accounts to the current_membership.account_id" do
|
|
369
|
+
account
|
|
370
|
+
other_account
|
|
371
|
+
resolved = Admin::Tenant::AccountPolicy::Scope.new(tenant_ctx, Accounts::Account.all).resolve
|
|
372
|
+
expect(resolved.pluck(:id)).to eq([account.id])
|
|
373
|
+
end
|
|
374
|
+
|
|
375
|
+
it "returns scope.none when the membership is nil" do
|
|
376
|
+
empty_ctx = Seams::Admin::Context.new(identity, nil)
|
|
377
|
+
account
|
|
378
|
+
other_account
|
|
379
|
+
resolved = Admin::Tenant::AccountPolicy::Scope.new(empty_ctx, Accounts::Account.all).resolve
|
|
380
|
+
expect(resolved.count).to eq(0)
|
|
381
|
+
end
|
|
382
|
+
|
|
383
|
+
it "returns every Account when the Identity is staff" do
|
|
384
|
+
staff_identity = Auth::Identity.create!(email: "staff-t-#{SecureRandom.hex(4)}@example.com", password: "verysecret", staff: true)
|
|
385
|
+
staff_ctx = Seams::Admin::Context.new(staff_identity, nil)
|
|
386
|
+
account
|
|
387
|
+
other_account
|
|
388
|
+
resolved = Admin::Tenant::AccountPolicy::Scope.new(staff_ctx, Accounts::Account.all).resolve
|
|
389
|
+
expect(resolved.count).to eq(Accounts::Account.count)
|
|
390
|
+
end
|
|
391
|
+
end
|
|
392
|
+
|
|
393
|
+
describe "Admin::Tenant per-record guard (id-tampering protection)" do
|
|
394
|
+
# Phase 4: a tenant admin requesting
|
|
395
|
+
# `/admin/accounts/<other_account_id>` by id-tampering used to
|
|
396
|
+
# pass `permitted?` (their membership.role IS admin) and only
|
|
397
|
+
# got blocked by the Scope filter. Show/update/destroy now run a
|
|
398
|
+
# second check via `record_in_tenant_scope?` so the policy
|
|
399
|
+
# method itself denies the request — the consequence is a clean
|
|
400
|
+
# 403 from `respond_with_admin_unauthorised` rather than a 404
|
|
401
|
+
# from RecordNotFound. Both shapes prevent the leak; the 403
|
|
402
|
+
# form is more honest.
|
|
403
|
+
let(:account) { Accounts::Account.create!(name: "Mine", external_account_id: SecureRandom.random_number(2**31)) }
|
|
404
|
+
let(:other_account) { Accounts::Account.create!(name: "Other", external_account_id: SecureRandom.random_number(2**31)) }
|
|
405
|
+
let(:identity) { Auth::Identity.create!(email: "g-#{SecureRandom.hex(4)}@example.com", password: "verysecret") }
|
|
406
|
+
let(:membership) { Accounts::Membership.create!(account: account, identity_id: identity.id, name: "Admin", role: "admin") }
|
|
407
|
+
let(:tenant_ctx) { Seams::Admin::Context.new(identity, membership) }
|
|
408
|
+
|
|
409
|
+
it "denies update? for an Account belonging to another tenant" do
|
|
410
|
+
policy = Admin::Tenant::AccountPolicy.new(tenant_ctx, other_account)
|
|
411
|
+
expect(policy.update?).to be(false)
|
|
412
|
+
end
|
|
413
|
+
|
|
414
|
+
it "permits update? for the caller's own Account" do
|
|
415
|
+
policy = Admin::Tenant::AccountPolicy.new(tenant_ctx, account)
|
|
416
|
+
expect(policy.update?).to be(true)
|
|
417
|
+
end
|
|
418
|
+
|
|
419
|
+
it "denies update? for an Identity who is not a member of the caller's account" do
|
|
420
|
+
stranger = Auth::Identity.create!(email: "s-#{SecureRandom.hex(4)}@example.com", password: "verysecret")
|
|
421
|
+
policy = Admin::Tenant::IdentityPolicy.new(tenant_ctx, stranger)
|
|
422
|
+
expect(policy.update?).to be(false)
|
|
423
|
+
end
|
|
424
|
+
|
|
425
|
+
it "permits update? for an Identity who IS a member of the caller's account" do
|
|
426
|
+
member = Auth::Identity.create!(email: "m-#{SecureRandom.hex(4)}@example.com", password: "verysecret")
|
|
427
|
+
Accounts::Membership.create!(account: account, identity_id: member.id, name: "Member", role: "member")
|
|
428
|
+
|
|
429
|
+
policy = Admin::Tenant::IdentityPolicy.new(tenant_ctx, member)
|
|
430
|
+
expect(policy.update?).to be(true)
|
|
431
|
+
end
|
|
432
|
+
end
|
|
433
|
+
|
|
434
|
+
describe "Admin::Tenant::ApplicationPolicy permitted?" do
|
|
435
|
+
let(:identity) { Auth::Identity.create!(email: "tap-#{SecureRandom.hex(4)}@example.com", password: "verysecret", staff: false) }
|
|
436
|
+
let(:account) { Accounts::Account.create!(name: "Tenant", external_account_id: SecureRandom.random_number(2**31)) }
|
|
437
|
+
let(:membership) { Accounts::Membership.new(account: account, identity_id: identity.id, name: "Member", role: role) }
|
|
438
|
+
let(:ctx) { Seams::Admin::Context.new(identity, membership) }
|
|
439
|
+
let(:policy) { Admin::Tenant::ApplicationPolicy.new(ctx, account) }
|
|
440
|
+
|
|
441
|
+
context "with role=admin" do
|
|
442
|
+
let(:role) { "admin" }
|
|
443
|
+
|
|
444
|
+
it "permits update?" do
|
|
445
|
+
expect(policy.update?).to be(true)
|
|
446
|
+
end
|
|
447
|
+
end
|
|
448
|
+
|
|
449
|
+
context "with role=owner" do
|
|
450
|
+
let(:role) { "owner" }
|
|
451
|
+
|
|
452
|
+
it "permits update?" do
|
|
453
|
+
expect(policy.update?).to be(true)
|
|
454
|
+
end
|
|
455
|
+
end
|
|
456
|
+
|
|
457
|
+
context "with role=member" do
|
|
458
|
+
let(:role) { "member" }
|
|
459
|
+
|
|
460
|
+
it "denies update?" do
|
|
461
|
+
expect(policy.update?).to be(false)
|
|
462
|
+
end
|
|
463
|
+
end
|
|
464
|
+
end
|
|
465
|
+
|
|
466
|
+
describe "audit-log auto-write" do
|
|
467
|
+
# Unit-test the controller's `record_admin_audit` private method
|
|
468
|
+
# against a fake controller subclass — wiring a full Administrate
|
|
469
|
+
# request cycle through the dummy app is heavier than what the
|
|
470
|
+
# Phase 3 brief asks for, and the after_action hook is the
|
|
471
|
+
# behaviour worth pinning down. The hook reads
|
|
472
|
+
# `requested_resource`, `action_name`, `response`, and
|
|
473
|
+
# `Auth::Current.identity` — all four are stubbed below.
|
|
474
|
+
let(:identity) { Auth::Identity.create!(email: "actor-#{SecureRandom.hex(4)}@example.com", password: "verysecret", staff: true) }
|
|
475
|
+
let(:account) { Accounts::Account.create!(name: "Audited", external_account_id: SecureRandom.random_number(2**31)) }
|
|
476
|
+
let(:fake_response) { Struct.new(:successful?, :redirect?).new(true, false) }
|
|
477
|
+
let(:controller) do
|
|
478
|
+
ctrl = Seams::Admin::ApplicationController.allocate
|
|
479
|
+
ctrl.define_singleton_method(:requested_resource) { @resource }
|
|
480
|
+
ctrl.define_singleton_method(:resource=) { |r| @resource = r }
|
|
481
|
+
ctrl.define_singleton_method(:action_name) { @action_name }
|
|
482
|
+
ctrl.define_singleton_method(:action_name=) { |a| @action_name = a }
|
|
483
|
+
ctrl.define_singleton_method(:response) { @response_obj }
|
|
484
|
+
ctrl.define_singleton_method(:response=) { |r| @response_obj = r }
|
|
485
|
+
ctrl.send(:resource=, account)
|
|
486
|
+
ctrl.send(:action_name=, "update")
|
|
487
|
+
ctrl.send(:response=, fake_response)
|
|
488
|
+
ctrl
|
|
489
|
+
end
|
|
490
|
+
|
|
491
|
+
around do |example|
|
|
492
|
+
Auth::Current.set(identity: identity) do
|
|
493
|
+
example.run
|
|
494
|
+
end
|
|
495
|
+
end
|
|
496
|
+
|
|
497
|
+
it "creates a Core::AuditLog row keyed on the actor" do
|
|
498
|
+
expect do
|
|
499
|
+
controller.send(:record_admin_audit)
|
|
500
|
+
end.to change(Core::AuditLog, :count).by(1)
|
|
501
|
+
|
|
502
|
+
log = Core::AuditLog.last
|
|
503
|
+
expect(log.action).to eq("update")
|
|
504
|
+
expect(log.auditable_type).to eq("Accounts::Account")
|
|
505
|
+
expect(log.auditable_id).to eq(account.id)
|
|
506
|
+
expect(log.actor_id).to eq(identity.id)
|
|
507
|
+
end
|
|
508
|
+
|
|
509
|
+
it "skips the write when the response was not successful" do
|
|
510
|
+
controller.send(:response=, Struct.new(:successful?, :redirect?).new(false, false))
|
|
511
|
+
expect do
|
|
512
|
+
controller.send(:record_admin_audit)
|
|
513
|
+
end.not_to change(Core::AuditLog, :count)
|
|
514
|
+
end
|
|
515
|
+
|
|
516
|
+
it "swallows errors so admin responses are never broken by audit-log failures" do
|
|
517
|
+
controller.send(:resource=, nil)
|
|
518
|
+
expect do
|
|
519
|
+
controller.send(:record_admin_audit)
|
|
520
|
+
end.not_to raise_error
|
|
521
|
+
end
|
|
522
|
+
|
|
523
|
+
it "skips the write when the model already includes Core::Auditable (avoids double-log)" do
|
|
524
|
+
# Simulate a host model that opted in to Core::Auditable. The
|
|
525
|
+
# concern's after_commit hook would have written one row
|
|
526
|
+
# already; the controller's after_action must NOT write a second.
|
|
527
|
+
auditable_model = Class.new(Accounts::Account) do
|
|
528
|
+
# Concern installs `has_many :audit_logs, class_name:
|
|
529
|
+
# "Core::AuditLog", as: :auditable`. We declare the same
|
|
530
|
+
# association on this anonymous subclass so the controller's
|
|
531
|
+
# detection check (`reflect_on_association(:audit_logs)`)
|
|
532
|
+
# picks it up.
|
|
533
|
+
has_many :audit_logs, class_name: "Core::AuditLog", as: :auditable
|
|
534
|
+
end
|
|
535
|
+
|
|
536
|
+
auditable_account = auditable_model.first || auditable_model.create!(
|
|
537
|
+
name: "Auditable", external_account_id: SecureRandom.random_number(2**31)
|
|
538
|
+
)
|
|
539
|
+
controller.send(:resource=, auditable_account)
|
|
540
|
+
|
|
541
|
+
expect do
|
|
542
|
+
controller.send(:record_admin_audit)
|
|
543
|
+
end.not_to change(Core::AuditLog, :count)
|
|
544
|
+
end
|
|
545
|
+
|
|
546
|
+
it "instruments seams.admin.audit_failed when the write raises" do
|
|
547
|
+
# Force the create! to raise so the rescue branch executes.
|
|
548
|
+
allow(Core::AuditLog).to receive(:create!).and_raise(ActiveRecord::RecordInvalid)
|
|
549
|
+
|
|
550
|
+
events = []
|
|
551
|
+
subscription = ActiveSupport::Notifications.subscribe("seams.admin.audit_failed") do |*, payload|
|
|
552
|
+
events << payload
|
|
553
|
+
end
|
|
554
|
+
begin
|
|
555
|
+
controller.send(:record_admin_audit)
|
|
556
|
+
ensure
|
|
557
|
+
ActiveSupport::Notifications.unsubscribe(subscription)
|
|
558
|
+
end
|
|
559
|
+
|
|
560
|
+
expect(events.size).to eq(1)
|
|
561
|
+
expect(events.first[:error_class]).to eq("ActiveRecord::RecordInvalid")
|
|
562
|
+
end
|
|
563
|
+
end
|
|
564
|
+
|
|
565
|
+
describe "Pundit::NotAuthorizedError handling" do
|
|
566
|
+
it "exposes a respond_with_admin_unauthorised handler" do
|
|
567
|
+
# Pundit denials should render 403, not bubble up to the host.
|
|
568
|
+
# Smoke-test the responder method exists and is private; full
|
|
569
|
+
# integration through a controller request cycle is heavier
|
|
570
|
+
# than the boot-spec budget.
|
|
571
|
+
expect(Seams::Admin::ApplicationController.private_instance_methods)
|
|
572
|
+
.to include(:respond_with_admin_unauthorised)
|
|
573
|
+
end
|
|
574
|
+
|
|
575
|
+
it "registers rescue_from for Pundit::NotAuthorizedError" do
|
|
576
|
+
handlers = Seams::Admin::ApplicationController.rescue_handlers
|
|
577
|
+
registered = handlers.map(&:first)
|
|
578
|
+
expect(registered).to include("Pundit::NotAuthorizedError")
|
|
579
|
+
end
|
|
580
|
+
end
|
|
581
|
+
|
|
582
|
+
describe "current_membership_resolver knob" do
|
|
583
|
+
it "delegates to Seams::Admin.config.current_membership_resolver" do
|
|
584
|
+
original = Seams::Admin.config.current_membership_resolver
|
|
585
|
+
stubbed_membership = Object.new
|
|
586
|
+
Seams::Admin.configure { |c| c.current_membership_resolver = ->(_ctrl) { stubbed_membership } }
|
|
587
|
+
|
|
588
|
+
ctrl = Seams::Admin::ApplicationController.allocate
|
|
589
|
+
expect(ctrl.send(:current_membership_for_admin)).to be(stubbed_membership)
|
|
590
|
+
ensure
|
|
591
|
+
Seams::Admin.config.current_membership_resolver = original
|
|
592
|
+
end
|
|
593
|
+
|
|
594
|
+
it "swallows resolver exceptions and returns nil" do
|
|
595
|
+
original = Seams::Admin.config.current_membership_resolver
|
|
596
|
+
Seams::Admin.configure { |c| c.current_membership_resolver = ->(_ctrl) { raise "boom" } }
|
|
597
|
+
|
|
598
|
+
ctrl = Seams::Admin::ApplicationController.allocate
|
|
599
|
+
expect(ctrl.send(:current_membership_for_admin)).to be_nil
|
|
600
|
+
ensure
|
|
601
|
+
Seams::Admin.config.current_membership_resolver = original
|
|
602
|
+
end
|
|
603
|
+
end
|
|
604
|
+
end
|