From 1e5464358c4f2a06e3402ed7420902bb5b42d190 Mon Sep 17 00:00:00 2001 From: Vincent Pochet Date: Mon, 30 Sep 2024 16:08:38 +0200 Subject: [PATCH] misc: Refact some services to the call pattern --- app/controllers/api/v1/add_ons_controller.rb | 11 +- .../api/v1/billable_metrics_controller.rb | 15 +- app/controllers/api/v1/coupons_controller.rb | 9 +- app/graphql/mutations/add_ons/create.rb | 10 +- .../mutations/billable_metrics/create.rb | 9 +- app/graphql/mutations/coupons/create.rb | 10 +- app/graphql/mutations/coupons/terminate.rb | 9 +- app/jobs/clock/terminate_coupons_job.rb | 4 +- app/services/add_ons/create_service.rb | 11 +- app/services/base_service.rb | 6 +- .../billable_metrics/create_service.rb | 11 +- app/services/coupons/create_service.rb | 17 +- app/services/coupons/terminate_service.rb | 28 ++- spec/services/add_ons/create_service_spec.rb | 42 ++-- .../billable_metrics/create_service_spec.rb | 92 ++++---- spec/services/coupons/create_service_spec.rb | 206 +++++++++--------- .../coupons/terminate_service_spec.rb | 30 +-- 17 files changed, 278 insertions(+), 242 deletions(-) diff --git a/app/controllers/api/v1/add_ons_controller.rb b/app/controllers/api/v1/add_ons_controller.rb index 41578e6d2e2..4ba3a3f5a3a 100644 --- a/app/controllers/api/v1/add_ons_controller.rb +++ b/app/controllers/api/v1/add_ons_controller.rb @@ -4,9 +4,8 @@ module Api module V1 class AddOnsController < Api::BaseController def create - service = AddOns::CreateService.new - result = service.create( - **input_params + result = AddOns::CreateService.call( + input_params .merge(organization_id: current_organization.id) .to_h .symbolize_keys @@ -46,7 +45,7 @@ def show code: params[:code] ) - return not_found_error(resource: 'add_on') unless add_on + return not_found_error(resource: "add_on") unless add_on render_add_on(add_on) end @@ -61,7 +60,7 @@ def index json: ::CollectionSerializer.new( add_ons.includes(:taxes), ::V1::AddOnSerializer, - collection_name: 'add_ons', + collection_name: "add_ons", meta: pagination_metadata(add_ons), includes: %i[taxes] ) @@ -86,7 +85,7 @@ def render_add_on(add_on) render( json: ::V1::AddOnSerializer.new( add_on, - root_name: 'add_on', + root_name: "add_on", includes: %i[taxes] ) ) diff --git a/app/controllers/api/v1/billable_metrics_controller.rb b/app/controllers/api/v1/billable_metrics_controller.rb index 8fb537a4918..804e934cc88 100644 --- a/app/controllers/api/v1/billable_metrics_controller.rb +++ b/app/controllers/api/v1/billable_metrics_controller.rb @@ -4,8 +4,7 @@ module Api module V1 class BillableMetricsController < Api::BaseController def create - service = ::BillableMetrics::CreateService.new - result = service.create( + result = ::BillableMetrics::CreateService.call( input_params.merge(organization_id: current_organization.id).to_h.deep_symbolize_keys ) @@ -13,7 +12,7 @@ def create render( json: ::V1::BillableMetricSerializer.new( result.billable_metric, - root_name: 'billable_metric' + root_name: "billable_metric" ) ) else @@ -36,7 +35,7 @@ def update render( json: ::V1::BillableMetricSerializer.new( result.billable_metric, - root_name: 'billable_metric' + root_name: "billable_metric" ) ) else @@ -53,7 +52,7 @@ def destroy render( json: ::V1::BillableMetricSerializer.new( result.billable_metric, - root_name: 'billable_metric' + root_name: "billable_metric" ) ) else @@ -66,12 +65,12 @@ def show code: params[:code] ) - return not_found_error(resource: 'billable_metric') unless metric + return not_found_error(resource: "billable_metric") unless metric render( json: ::V1::BillableMetricSerializer.new( metric, - root_name: 'billable_metric' + root_name: "billable_metric" ) ) end @@ -87,7 +86,7 @@ def index json: ::CollectionSerializer.new( metrics, ::V1::BillableMetricSerializer, - collection_name: 'billable_metrics', + collection_name: "billable_metrics", meta: pagination_metadata(metrics) ) ) diff --git a/app/controllers/api/v1/coupons_controller.rb b/app/controllers/api/v1/coupons_controller.rb index 0776002ec57..2b8fd6867ff 100644 --- a/app/controllers/api/v1/coupons_controller.rb +++ b/app/controllers/api/v1/coupons_controller.rb @@ -4,8 +4,7 @@ module Api module V1 class CouponsController < Api::BaseController def create - service = Coupons::CreateService.new - result = service.create( + result = Coupons::CreateService.call( input_params.merge(organization_id: current_organization.id).to_h ) @@ -47,7 +46,7 @@ def show code: params[:code] ) - return not_found_error(resource: 'coupon') unless coupon + return not_found_error(resource: "coupon") unless coupon render_coupon(coupon) end @@ -62,7 +61,7 @@ def index json: ::CollectionSerializer.new( coupons, ::V1::CouponSerializer, - collection_name: 'coupons', + collection_name: "coupons", meta: pagination_metadata(coupons) ) ) @@ -95,7 +94,7 @@ def render_coupon(coupon) render( json: ::V1::CouponSerializer.new( coupon, - root_name: 'coupon' + root_name: "coupon" ) ) end diff --git a/app/graphql/mutations/add_ons/create.rb b/app/graphql/mutations/add_ons/create.rb index cc1c99e99ce..e7949afdd03 100644 --- a/app/graphql/mutations/add_ons/create.rb +++ b/app/graphql/mutations/add_ons/create.rb @@ -6,19 +6,17 @@ class Create < BaseMutation include AuthenticableApiUser include RequiredOrganization - REQUIRED_PERMISSION = 'addons:create' + REQUIRED_PERMISSION = "addons:create" - graphql_name 'CreateAddOn' - description 'Creates a new add-on' + graphql_name "CreateAddOn" + description "Creates a new add-on" input_object_class Types::AddOns::CreateInput type Types::AddOns::Object def resolve(**args) - result = ::AddOns::CreateService - .new(context[:current_user]) - .create(**args.merge(organization_id: current_organization.id)) + result = ::AddOns::CreateService.call(args.merge(organization_id: current_organization.id)) result.success? ? result.add_on : result_error(result) end diff --git a/app/graphql/mutations/billable_metrics/create.rb b/app/graphql/mutations/billable_metrics/create.rb index 0c87a932f42..fb3f6cd798f 100644 --- a/app/graphql/mutations/billable_metrics/create.rb +++ b/app/graphql/mutations/billable_metrics/create.rb @@ -6,10 +6,10 @@ class Create < BaseMutation include AuthenticableApiUser include RequiredOrganization - REQUIRED_PERMISSION = 'billable_metrics:create' + REQUIRED_PERMISSION = "billable_metrics:create" - graphql_name 'CreateBillableMetric' - description 'Creates a new Billable metric' + graphql_name "CreateBillableMetric" + description "Creates a new Billable metric" input_object_class Types::BillableMetrics::CreateInput @@ -17,8 +17,7 @@ class Create < BaseMutation def resolve(**args) result = ::BillableMetrics::CreateService - .new(context[:current_user]) - .create(**args.merge(organization_id: current_organization.id)) + .call(**args.merge(organization_id: current_organization.id)) result.success? ? result.billable_metric : result_error(result) end diff --git a/app/graphql/mutations/coupons/create.rb b/app/graphql/mutations/coupons/create.rb index 48797411b0f..2355c50282e 100644 --- a/app/graphql/mutations/coupons/create.rb +++ b/app/graphql/mutations/coupons/create.rb @@ -6,19 +6,17 @@ class Create < BaseMutation include AuthenticableApiUser include RequiredOrganization - REQUIRED_PERMISSION = 'coupons:create' + REQUIRED_PERMISSION = "coupons:create" - graphql_name 'CreateCoupon' - description 'Creates a new Coupon' + graphql_name "CreateCoupon" + description "Creates a new Coupon" input_object_class Types::Coupons::CreateInput type Types::Coupons::Object def resolve(**args) - result = ::Coupons::CreateService - .new(context[:current_user]) - .create(args.merge(organization_id: current_organization.id)) + result = ::Coupons::CreateService.call(args.merge(organization_id: current_organization.id)) result.success? ? result.coupon : result_error(result) end diff --git a/app/graphql/mutations/coupons/terminate.rb b/app/graphql/mutations/coupons/terminate.rb index c320da799ff..e3e00904cd1 100644 --- a/app/graphql/mutations/coupons/terminate.rb +++ b/app/graphql/mutations/coupons/terminate.rb @@ -5,17 +5,18 @@ module Coupons class Terminate < BaseMutation include AuthenticableApiUser - REQUIRED_PERMISSION = 'coupons:update' + REQUIRED_PERMISSION = "coupons:update" - graphql_name 'TerminateCoupon' - description 'Deletes a coupon' + graphql_name "TerminateCoupon" + description "Deletes a coupon" argument :id, ID, required: true type Types::Coupons::Object def resolve(id:) - result = ::Coupons::TerminateService.new(context[:current_user]).terminate(id) + coupon = context[:current_user].coupons.find_by(id:) + result = ::Coupons::TerminateService.call(coupon) result.success? ? result.coupon : result_error(result) end diff --git a/app/jobs/clock/terminate_coupons_job.rb b/app/jobs/clock/terminate_coupons_job.rb index 04202923398..088fc31d4bd 100644 --- a/app/jobs/clock/terminate_coupons_job.rb +++ b/app/jobs/clock/terminate_coupons_job.rb @@ -4,10 +4,10 @@ module Clock class TerminateCouponsJob < ApplicationJob include SentryCronConcern - queue_as 'clock' + queue_as "clock" def perform - Coupons::TerminateService.new.terminate_all_expired + Coupons::TerminateService.terminate_all_expired end end end diff --git a/app/services/add_ons/create_service.rb b/app/services/add_ons/create_service.rb index 37f69799ac1..f6960dbb794 100644 --- a/app/services/add_ons/create_service.rb +++ b/app/services/add_ons/create_service.rb @@ -2,7 +2,12 @@ module AddOns class CreateService < BaseService - def create(**args) + def initialize(args) + @args = args + super + end + + def call ActiveRecord::Base.transaction do add_on = AddOn.create!( organization_id: args[:organization_id], @@ -32,10 +37,12 @@ def create(**args) private + attr_reader :args + def track_add_on_created(add_on) SegmentTrackJob.perform_later( membership_id: CurrentContext.membership, - event: 'add_on_created', + event: "add_on_created", properties: { addon_code: add_on.code, addon_name: add_on.name, diff --git a/app/services/base_service.rb b/app/services/base_service.rb index 0d7d0e09544..bf5eab19173 100644 --- a/app/services/base_service.rb +++ b/app/services/base_service.rb @@ -125,11 +125,11 @@ def service_failure!(code:, message:) fail_with_error!(ServiceFailure.new(self, code:, error_message: message)) end - def forbidden_failure!(code: 'feature_unavailable') + def forbidden_failure!(code: "feature_unavailable") fail_with_error!(ForbiddenFailure.new(self, code:)) end - def unauthorized_failure!(message: 'unauthorized') + def unauthorized_failure!(message: "unauthorized") fail_with_error!(UnauthorizedFailure.new(self, message:)) end @@ -186,7 +186,7 @@ def graphql_context? source&.to_sym == :graphql end - def at_time_zone(customer: 'customers', organization: 'organizations') + def at_time_zone(customer: "customers", organization: "organizations") Utils::Timezone.at_time_zone_sql(customer:, organization:) end end diff --git a/app/services/billable_metrics/create_service.rb b/app/services/billable_metrics/create_service.rb index 2d920a88bcc..9127bc32731 100644 --- a/app/services/billable_metrics/create_service.rb +++ b/app/services/billable_metrics/create_service.rb @@ -2,7 +2,12 @@ module BillableMetrics class CreateService < BaseService - def create(args) + def initialize(args = {}) + @args = args + super + end + + def call organization = Organization.find_by(id: args[:organization_id]) if args[:aggregation_type]&.to_sym == :custom_agg && !organization&.custom_aggregation @@ -40,10 +45,12 @@ def create(args) private + attr_reader :args + def track_billable_metric_created(metric) SegmentTrackJob.perform_later( membership_id: CurrentContext.membership, - event: 'billable_metric_created', + event: "billable_metric_created", properties: { code: metric.code, name: metric.name, diff --git a/app/services/coupons/create_service.rb b/app/services/coupons/create_service.rb index 21a534f8f64..4805b85f8f0 100644 --- a/app/services/coupons/create_service.rb +++ b/app/services/coupons/create_service.rb @@ -2,7 +2,12 @@ module Coupons class CreateService < BaseService - def create(args) + def initialize(args) + @args = args + super + end + + def call return result unless valid?(args) @limitations = args[:applies_to]&.to_h&.deep_symbolize_keys || {} @@ -29,15 +34,15 @@ def create(args) ) if plan_identifiers.present? && plans.count != plan_identifiers.count - return result.not_found_failure!(resource: 'plans') + return result.not_found_failure!(resource: "plans") end if billable_metric_identifiers.present? && billable_metrics.count != billable_metric_identifiers.count - return result.not_found_failure!(resource: 'billable_metrics') + return result.not_found_failure!(resource: "billable_metrics") end if billable_metrics.present? && plans.present? - return result.not_allowed_failure!(code: 'only_one_limitation_type_per_coupon_allowed') + return result.not_allowed_failure!(code: "only_one_limitation_type_per_coupon_allowed") end ActiveRecord::Base.transaction do @@ -59,12 +64,12 @@ def create(args) private - attr_reader :limitations, :organization_id + attr_reader :args, :limitations, :organization_id def track_coupon_created(coupon) SegmentTrackJob.perform_later( membership_id: CurrentContext.membership, - event: 'coupon_created', + event: "coupon_created", properties: { coupon_code: coupon.code, coupon_name: coupon.name, diff --git a/app/services/coupons/terminate_service.rb b/app/services/coupons/terminate_service.rb index 7669f75e8e3..0d30ee62cf1 100644 --- a/app/services/coupons/terminate_service.rb +++ b/app/services/coupons/terminate_service.rb @@ -2,9 +2,21 @@ module Coupons class TerminateService < BaseService - def terminate(id) - coupon = result.user.coupons.find_by(id:) - return result.not_found_failure!(resource: 'coupon') unless coupon + def self.terminate_all_expired + Coupon + .active + .time_limit + .expired + .find_each(&:mark_as_terminated!) + end + + def initialize(coupon) + @coupon = coupon + super + end + + def call + return result.not_found_failure!(resource: "coupon") unless coupon coupon.mark_as_terminated! unless coupon.terminated? @@ -14,12 +26,8 @@ def terminate(id) result.record_validation_failure!(record: e.record) end - def terminate_all_expired - Coupon - .active - .time_limit - .expired - .find_each(&:mark_as_terminated!) - end + private + + attr_reader :coupon end end diff --git a/spec/services/add_ons/create_service_spec.rb b/spec/services/add_ons/create_service_spec.rb index 6d250cd48b9..75acd6b3db3 100644 --- a/spec/services/add_ons/create_service_spec.rb +++ b/spec/services/add_ons/create_service_spec.rb @@ -1,25 +1,25 @@ # frozen_string_literal: true -require 'rails_helper' +require "rails_helper" RSpec.describe AddOns::CreateService, type: :service do - subject(:create_service) { described_class.new(membership.user) } + subject(:create_service) { described_class.new(create_args) } let(:membership) { create(:membership) } let(:organization) { membership.organization } - let(:add_on_code) { 'free-beer-for-us' } + let(:add_on_code) { "free-beer-for-us" } let(:tax) { create(:tax, organization:) } - describe 'create' do + describe "create" do let(:create_args) do { - name: 'Super Add-on', - invoice_display_name: 'Super Add-on Invoice Name', + name: "Super Add-on", + invoice_display_name: "Super Add-on Invoice Name", code: add_on_code, - description: 'This is description', + description: "This is description", organization_id: organization.id, amount_cents: 100, - amount_currency: 'EUR', + amount_currency: "EUR", tax_codes: [tax.code] } end @@ -28,20 +28,20 @@ allow(SegmentTrackJob).to receive(:perform_later) end - it 'creates an add-on' do - expect { create_service.create(**create_args) } + it "creates an add-on" do + expect { create_service.call } .to change(AddOn, :count).by(1) add_on = AddOn.order(:created_at).last expect(add_on.taxes.pluck(:code)).to eq([tax.code]) end - it 'calls SegmentTrackJob' do - add_on = create_service.create(**create_args).add_on + it "calls SegmentTrackJob" do + add_on = create_service.call.add_on expect(SegmentTrackJob).to have_received(:perform_later).with( membership_id: CurrentContext.membership, - event: 'add_on_created', + event: "add_on_created", properties: { addon_code: add_on.code, addon_name: add_on.name, @@ -51,11 +51,11 @@ ) end - context 'with code already used by a deleted add_on' do - it 'creates an add_on with the same code' do + context "with code already used by a deleted add_on" do + it "creates an add_on with the same code" do create(:add_on, :deleted, organization:, code: add_on_code) - expect { create_service.create(**create_args) }.to change(AddOn, :count).by(1) + expect { create_service.call }.to change(AddOn, :count).by(1) add_ons = organization.add_ons.with_discarded expect(add_ons.count).to eq(2) @@ -63,22 +63,22 @@ end end - context 'with validation error' do + context "with validation error" do before do create( :add_on, organization:, - code: 'free-beer-for-us' + code: "free-beer-for-us" ) end - it 'returns an error' do - result = create_service.create(**create_args) + it "returns an error" do + result = create_service.call aggregate_failures do expect(result).not_to be_success expect(result.error).to be_a(BaseService::ValidationFailure) - expect(result.error.messages[:code]).to eq(['value_already_exist']) + expect(result.error.messages[:code]).to eq(["value_already_exist"]) end end end diff --git a/spec/services/billable_metrics/create_service_spec.rb b/spec/services/billable_metrics/create_service_spec.rb index 178d7d55f49..e7253006e87 100644 --- a/spec/services/billable_metrics/create_service_spec.rb +++ b/spec/services/billable_metrics/create_service_spec.rb @@ -1,79 +1,95 @@ # frozen_string_literal: true -require 'rails_helper' +require "rails_helper" RSpec.describe BillableMetrics::CreateService, type: :service do - subject(:create_service) { described_class.new(membership.user) } + subject(:create_service) { described_class.new(create_args) } let(:membership) { create(:membership) } let(:organization) { membership.organization } - describe 'create' do + describe "create" do before do allow(SegmentTrackJob).to receive(:perform_later) end let(:create_args) do { - name: 'New Metric', - code: 'new_metric', - description: 'New metric description', + name: "New Metric", + code: "new_metric", + description: "New metric description", organization_id: organization.id, - aggregation_type: 'count_agg', + aggregation_type: "count_agg", recurring: false } end - it 'creates a billable metric' do - expect { create_service.create(**create_args) } + it "creates a billable metric" do + expect { create_service.call } .to change(BillableMetric, :count).by(1) end - context 'with code already used by a deleted metric' do - it 'creates a billable metric with the same code' do - create(:billable_metric, organization:, code: 'new_metric', deleted_at: Time.current) + context "with code already used by a deleted metric" do + it "creates a billable metric with the same code" do + create(:billable_metric, organization:, code: "new_metric", deleted_at: Time.current) - expect { create_service.create(**create_args) } + expect { create_service.call } .to change(BillableMetric, :count).by(1) metrics = organization.billable_metrics.with_discarded expect(metrics.count).to eq(2) - expect(metrics.pluck(:code).uniq).to eq(['new_metric']) + expect(metrics.pluck(:code).uniq).to eq(["new_metric"]) end end - context 'with filters arguments' do + context "with filters arguments" do + let(:create_args) do + { + name: "New Metric", + code: "new_metric", + description: "New metric description", + organization_id: organization.id, + aggregation_type: "count_agg", + recurring: false, + filters: + } + end + let(:filters) do [ { - key: 'cloud', + key: "cloud", values: %w[aws google] } ] end - it 'creates billable metric\'s filters' do - expect { create_service.create(**create_args.merge(filters:)) } + it "creates billable metric's filters" do + expect { create_service.call } .to change(BillableMetricFilter, :count).by(1) end - it 'returns an error if a filter is invalid' do - result = create_service.create(**create_args.merge(filters: [{key: 'foo'}])) + context "with invalid filters" do + let(:filters) { [{key: "foo"}] } - aggregate_failures do - expect(result).not_to be_success - expect(result.error).to be_a(BaseService::ValidationFailure) - expect(result.error.messages[:values]).to eq(['value_is_mandatory']) + it "returns an error if a filter is invalid" do + result = create_service.call + + aggregate_failures do + expect(result).not_to be_success + expect(result.error).to be_a(BaseService::ValidationFailure) + expect(result.error.messages[:values]).to eq(["value_is_mandatory"]) + end end end end - it 'calls SegmentTrackJob' do - metric = create_service.create(**create_args).billable_metric + it "calls SegmentTrackJob" do + metric = create_service.call.billable_metric expect(SegmentTrackJob).to have_received(:perform_later).with( membership_id: CurrentContext.membership, - event: 'billable_metric_created', + event: "billable_metric_created", properties: { code: metric.code, name: metric.name, @@ -85,7 +101,7 @@ ) end - context 'with validation error' do + context "with validation error" do before do create( :billable_metric, @@ -94,31 +110,31 @@ ) end - it 'returns an error' do - result = create_service.create(**create_args) + it "returns an error" do + result = create_service.call aggregate_failures do expect(result).not_to be_success expect(result.error).to be_a(BaseService::ValidationFailure) - expect(result.error.messages[:code]).to eq(['value_already_exist']) + expect(result.error.messages[:code]).to eq(["value_already_exist"]) end end end - context 'with custom aggregation' do + context "with custom aggregation" do let(:create_args) do { - name: 'New Metric', - code: 'new_metric', - description: 'New metric description', + name: "New Metric", + code: "new_metric", + description: "New metric description", organization_id: organization.id, - aggregation_type: 'custom_agg', + aggregation_type: "custom_agg", recurring: false } end - it 'returns a forbidden failure' do - result = create_service.create(**create_args) + it "returns a forbidden failure" do + result = create_service.call expect(result).not_to be_success expect(result.error).to be_a(BaseService::ForbiddenFailure) diff --git a/spec/services/coupons/create_service_spec.rb b/spec/services/coupons/create_service_spec.rb index 314a1706804..0ea9ed955f8 100644 --- a/spec/services/coupons/create_service_spec.rb +++ b/spec/services/coupons/create_service_spec.rb @@ -1,27 +1,27 @@ # frozen_string_literal: true -require 'rails_helper' +require "rails_helper" RSpec.describe Coupons::CreateService, type: :service do - subject(:create_service) { described_class.new(membership.user) } + subject(:create_service) { described_class.new(create_args) } let(:membership) { create(:membership) } let(:organization) { membership.organization } - let(:coupon_code) { 'free-beer' } + let(:coupon_code) { "free-beer" } - describe 'create' do + describe "create" do let(:expiration_at) { (Time.current + 3.days).end_of_day } let(:create_args) do { - name: 'Super Coupon', + name: "Super Coupon", code: coupon_code, - description: 'This is a description', + description: "This is a description", organization_id: organization.id, - coupon_type: 'fixed_amount', - frequency: 'once', + coupon_type: "fixed_amount", + frequency: "once", amount_cents: 100, - amount_currency: 'EUR', - expiration: 'time_limit', + amount_currency: "EUR", + expiration: "time_limit", reusable: false, expiration_at: } @@ -31,17 +31,17 @@ allow(SegmentTrackJob).to receive(:perform_later) end - it 'creates a coupon' do - expect { create_service.create(**create_args) } + it "creates a coupon" do + expect { create_service.call } .to change(Coupon, :count).by(1) end - it 'calls SegmentTrackJob' do - coupon = create_service.create(**create_args).coupon + it "calls SegmentTrackJob" do + coupon = create_service.call.coupon expect(SegmentTrackJob).to have_received(:perform_later).with( membership_id: CurrentContext.membership, - event: 'coupon_created', + event: "coupon_created", properties: { coupon_code: coupon.code, coupon_name: coupon.name, @@ -50,11 +50,11 @@ ) end - context 'with code already used by a deleted coupon' do - it 'creates an coupon with the same code' do + context "with code already used by a deleted coupon" do + it "creates an coupon with the same code" do create(:coupon, :deleted, organization:, code: coupon_code) - expect { create_service.create(**create_args) }.to change(Coupon, :count).by(1) + expect { create_service.call }.to change(Coupon, :count).by(1) coupons = organization.coupons.with_discarded expect(coupons.count).to eq(2) @@ -62,68 +62,68 @@ end end - context 'when coupon type is percentage' do + context "when coupon type is percentage" do let(:create_args) do { - name: 'Super Coupon', - code: 'free-beer', + name: "Super Coupon", + code: "free-beer", organization_id: organization.id, - coupon_type: 'percentage', - frequency: 'once', + coupon_type: "percentage", + frequency: "once", percentage_rate: 20.00, - expiration: 'time_limit', + expiration: "time_limit", expiration_at: (Time.current + 3.days).iso8601 } end - it 'creates a coupon' do - expect { create_service.create(**create_args) } + it "creates a coupon" do + expect { create_service.call } .to change(Coupon, :count).by(1) end end - context 'with validation error' do + context "with validation error" do before do create(:coupon, organization:, code: coupon_code) end - it 'returns an error' do - result = create_service.create(**create_args) + it "returns an error" do + result = create_service.call aggregate_failures do expect(result).not_to be_success expect(result.error).to be_a(BaseService::ValidationFailure) - expect(result.error.messages[:code]).to eq(['value_already_exist']) + expect(result.error.messages[:code]).to eq(["value_already_exist"]) end end end - context 'with invalid expiration_at' do + context "with invalid expiration_at" do let(:expiration_at) { (Time.current - 3.days).end_of_day } - it 'returns an error' do - result = create_service.create(**create_args) + it "returns an error" do + result = create_service.call aggregate_failures do expect(result).not_to be_success expect(result.error).to be_a(BaseService::ValidationFailure) - expect(result.error.messages[:expiration_at]).to eq(['invalid_date']) + expect(result.error.messages[:expiration_at]).to eq(["invalid_date"]) end end end - context 'with plan limitations in graphql context' do + context "with plan limitations in graphql context" do let(:plan) { create(:plan, organization:) } let(:create_args) do { - name: 'Super Coupon', - code: 'free-beer', + name: "Super Coupon", + code: "free-beer", organization_id: organization.id, - coupon_type: 'fixed_amount', - frequency: 'once', + coupon_type: "fixed_amount", + frequency: "once", amount_cents: 100, - amount_currency: 'EUR', - expiration: 'time_limit', + amount_currency: "EUR", + expiration: "time_limit", reusable: false, expiration_at:, applies_to: { @@ -132,31 +132,31 @@ } end - before { CurrentContext.source = 'graphql' } + before { CurrentContext.source = "graphql" } - it 'creates a coupon' do - expect { create_service.create(**create_args) } + it "creates a coupon" do + expect { create_service.call } .to change(Coupon, :count).by(1) end - it 'creates a coupon target' do - expect { create_service.create(**create_args) } + it "creates a coupon target" do + expect { create_service.call } .to change(CouponTarget, :count).by(1) end end - context 'with plan limitations in api context' do + context "with plan limitations in api context" do let(:plan) { create(:plan, organization:) } let(:create_args) do { - name: 'Super Coupon', - code: 'free-beer', + name: "Super Coupon", + code: "free-beer", organization_id: organization.id, - coupon_type: 'fixed_amount', - frequency: 'once', + coupon_type: "fixed_amount", + frequency: "once", amount_cents: 100, - amount_currency: 'EUR', - expiration: 'time_limit', + amount_currency: "EUR", + expiration: "time_limit", reusable: false, expiration_at:, applies_to: { @@ -165,31 +165,31 @@ } end - before { CurrentContext.source = 'api' } + before { CurrentContext.source = "api" } - it 'creates a coupon' do - expect { create_service.create(**create_args) } + it "creates a coupon" do + expect { create_service.call } .to change(Coupon, :count).by(1) end - it 'creates a coupon target' do - expect { create_service.create(**create_args) } + it "creates a coupon target" do + expect { create_service.call } .to change(CouponTarget, :count).by(1) end end - context 'with billable metric limitations in graphql context' do + context "with billable metric limitations in graphql context" do let(:billable_metric) { create(:billable_metric, organization:) } let(:create_args) do { - name: 'Super Coupon', - code: 'free-beer', + name: "Super Coupon", + code: "free-beer", organization_id: organization.id, - coupon_type: 'fixed_amount', - frequency: 'once', + coupon_type: "fixed_amount", + frequency: "once", amount_cents: 100, - amount_currency: 'EUR', - expiration: 'time_limit', + amount_currency: "EUR", + expiration: "time_limit", reusable: false, expiration_at:, applies_to: { @@ -198,30 +198,30 @@ } end - before { CurrentContext.source = 'graphql' } + before { CurrentContext.source = "graphql" } - it 'creates a coupon' do - expect { create_service.create(**create_args) } + it "creates a coupon" do + expect { create_service.call } .to change(Coupon, :count).by(1) end - it 'creates a coupon target' do - expect { create_service.create(**create_args) } + it "creates a coupon target" do + expect { create_service.call } .to change(CouponTarget, :count).by(1) end - context 'with multiple limitation types' do + context "with multiple limitation types" do let(:plan) { create(:plan, organization:) } let(:create_args) do { - name: 'Super Coupon', - code: 'free-beer', + name: "Super Coupon", + code: "free-beer", organization_id: organization.id, - coupon_type: 'fixed_amount', - frequency: 'once', + coupon_type: "fixed_amount", + frequency: "once", amount_cents: 100, - amount_currency: 'EUR', - expiration: 'time_limit', + amount_currency: "EUR", + expiration: "time_limit", reusable: false, expiration_at:, applies_to: { @@ -231,60 +231,60 @@ } end - it 'returns an error' do - result = create_service.create(**create_args) + it "returns an error" do + result = create_service.call aggregate_failures do expect(result).not_to be_success expect(result.error).to be_a(BaseService::MethodNotAllowedFailure) - expect(result.error.code).to eq('only_one_limitation_type_per_coupon_allowed') + expect(result.error.code).to eq("only_one_limitation_type_per_coupon_allowed") end end end - context 'with invalid billable metric' do + context "with invalid billable metric" do let(:create_args) do { - name: 'Super Coupon', - code: 'free-beer', + name: "Super Coupon", + code: "free-beer", organization_id: organization.id, - coupon_type: 'fixed_amount', - frequency: 'once', + coupon_type: "fixed_amount", + frequency: "once", amount_cents: 100, - amount_currency: 'EUR', - expiration: 'time_limit', + amount_currency: "EUR", + expiration: "time_limit", reusable: false, expiration_at:, applies_to: { - billable_metric_ids: [billable_metric.id, 'invalid'] + billable_metric_ids: [billable_metric.id, "invalid"] } } end - it 'returns an error' do - result = create_service.create(**create_args) + it "returns an error" do + result = create_service.call aggregate_failures do expect(result).not_to be_success expect(result.error).to be_a(BaseService::NotFoundFailure) - expect(result.error.message).to eq('billable_metrics_not_found') + expect(result.error.message).to eq("billable_metrics_not_found") end end end end - context 'with billable metric limitations in api context' do + context "with billable metric limitations in api context" do let(:billable_metric) { create(:billable_metric, organization:) } let(:create_args) do { - name: 'Super Coupon', - code: 'free-beer', + name: "Super Coupon", + code: "free-beer", organization_id: organization.id, - coupon_type: 'fixed_amount', - frequency: 'once', + coupon_type: "fixed_amount", + frequency: "once", amount_cents: 100, - amount_currency: 'EUR', - expiration: 'time_limit', + amount_currency: "EUR", + expiration: "time_limit", reusable: false, expiration_at:, applies_to: { @@ -293,15 +293,15 @@ } end - before { CurrentContext.source = 'api' } + before { CurrentContext.source = "api" } - it 'creates a coupon' do - expect { create_service.create(**create_args) } + it "creates a coupon" do + expect { create_service.call } .to change(Coupon, :count).by(1) end - it 'creates a coupon target' do - expect { create_service.create(**create_args) } + it "creates a coupon target" do + expect { create_service.call } .to change(CouponTarget, :count).by(1) end end diff --git a/spec/services/coupons/terminate_service_spec.rb b/spec/services/coupons/terminate_service_spec.rb index c69032482b4..1956b3f103b 100644 --- a/spec/services/coupons/terminate_service_spec.rb +++ b/spec/services/coupons/terminate_service_spec.rb @@ -1,29 +1,29 @@ # frozen_string_literal: true -require 'rails_helper' +require "rails_helper" RSpec.describe Coupons::TerminateService, type: :service do - subject(:terminate_service) { described_class.new(membership.user) } + subject(:terminate_service) { described_class.new(coupon) } let(:membership) { create(:membership) } let(:organization) { membership.organization } let(:coupon) { create(:coupon, organization:) } - describe 'terminate' do - it 'terminates the coupon' do - result = terminate_service.terminate(coupon.id) + describe "terminate" do + it "terminates the coupon" do + result = terminate_service.call expect(result).to be_success expect(result.coupon).to be_terminated end - context 'when coupon is already terminated' do + context "when coupon is already terminated" do before { coupon.mark_as_terminated! } - it 'does not impact the coupon' do + it "does not impact the coupon" do terminated_at = coupon.terminated_at - result = terminate_service.terminate(coupon.id) + result = terminate_service.call expect(result).to be_success expect(result.coupon).to be_terminated @@ -32,14 +32,14 @@ end end - describe 'terminate_all_expired' do + describe "terminate_all_expired" do let(:to_expire_coupons) do create_list( :coupon, 3, organization:, - status: 'active', - expiration: 'time_limit', + status: "active", + expiration: "time_limit", expiration_at: Time.current - 30.days, created_at: Time.zone.now - 40.days ) @@ -50,8 +50,8 @@ :coupon, 3, organization:, - status: 'active', - expiration: 'time_limit', + status: "active", + expiration: "time_limit", expiration_at: Time.current + 15.days, created_at: Time.zone.now ) @@ -61,10 +61,10 @@ to_expire_coupons to_keep_active_coupons - terminate_service.terminate_all_expired + described_class.terminate_all_expired end - it 'terminates the expired coupons' do + it "terminates the expired coupons" do expect(Coupon.terminated.count).to eq(3) end end