diff --git a/CHANGELOG.md b/CHANGELOG.md index 43a932a1e..6d11a2583 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,6 +23,7 @@ - Fix a bug: unsupported param[] syntax in OpenAPI - Fix a bug: unable to access in-system notifications if a slot was cancelled - Fix a bug: feature tour in admin/settings is broken +- Fix a bug: clearing the new expiration date field in the offer days modal result in errors - Updated react-modal to 3.16.1 - Updated tiptap editor and its dependencies to 2.0.0-beta.204 - [TODO DEPLOY] `rails db:seed` diff --git a/app/controllers/api/payments_controller.rb b/app/controllers/api/payments_controller.rb index ec3b2faa4..30cb5aa6c 100644 --- a/app/controllers/api/payments_controller.rb +++ b/app/controllers/api/payments_controller.rb @@ -4,7 +4,6 @@ class API::PaymentsController < API::ApiController before_action :authenticate_user! - # This method must be overridden by the the gateways controllers that inherits API::PaymentsControllers def confirm_payment raise NoMethodError @@ -41,5 +40,7 @@ class API::PaymentsController < API::ApiController else { json: res[:errors].drop_while(&:empty?), status: :unprocessable_entity } end + rescue StandardError => e + { json: e, status: :unprocessable_entity } end end diff --git a/app/frontend/src/javascript/components/subscriptions/free-extend-modal.tsx b/app/frontend/src/javascript/components/subscriptions/free-extend-modal.tsx index adbaec170..084263081 100644 --- a/app/frontend/src/javascript/components/subscriptions/free-extend-modal.tsx +++ b/app/frontend/src/javascript/components/subscriptions/free-extend-modal.tsx @@ -42,7 +42,7 @@ export const FreeExtendModal: React.FC = ({ isOpen, toggle setFreeDays(0); } // 86400000 = 1000 * 3600 * 24 = number of ms per day - setFreeDays(Math.ceil((expirationDate.getTime() - new Date(subscription.expired_at).getTime()) / 86400000)); + setFreeDays(Math.ceil((expirationDate.getTime() - new Date(subscription.expired_at).getTime()) / 86400000) || 0); }, [expirationDate]); /** @@ -56,7 +56,9 @@ export const FreeExtendModal: React.FC = ({ isOpen, toggle * Return the given date formatted for the HTML input-date */ const formatDefaultDate = (date: Date): string => { - return date.toISOString().substr(0, 10); + if (isNaN(date as unknown as number)) return null; + + return date.toISOString().substring(0, 10); }; /** diff --git a/app/models/cart_item/free_extension.rb b/app/models/cart_item/free_extension.rb index 9c11ae0ec..ce4a87d7d 100644 --- a/app/models/cart_item/free_extension.rb +++ b/app/models/cart_item/free_extension.rb @@ -13,7 +13,9 @@ class CartItem::FreeExtension < CartItem::BaseItem def start_at raise InvalidSubscriptionError if @subscription.nil? - raise InvalidSubscriptionError if @new_expiration_date <= @subscription.expired_at + if @new_expiration_date.nil? || @new_expiration_date <= @subscription.expired_at + raise InvalidSubscriptionError, I18n.t('cart_items.must_be_after_expiration') + end @subscription.expired_at end diff --git a/config/locales/en.yml b/config/locales/en.yml index 9cc966112..2d01b6799 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -483,6 +483,7 @@ en: reduced_fare_if_you_are_under_25_student_or_unemployed: "Reduced fare if you are under 25, student or unemployed." cart_items: free_extension: "Free extension of a subscription, until %{DATE}" + must_be_after_expiration: "The new expiration date must be set after the current expiration date" statistic_profile: birthday_in_past: "The date of birth must be in the past" order: diff --git a/test/integration/subscriptions/free_extension_test.rb b/test/integration/subscriptions/free_extension_test.rb new file mode 100644 index 000000000..33b9c34ec --- /dev/null +++ b/test/integration/subscriptions/free_extension_test.rb @@ -0,0 +1,86 @@ +# frozen_string_literal: true + +require 'test_helper' + +module Subscriptions; end + +class Subscriptions::FreeExtensionTest < ActionDispatch::IntegrationTest + setup do + @admin = User.find_by(username: 'admin') + login_as(@admin, scope: :user) + end + + test 'admin successfully offer free days' do + user = User.find_by(username: 'pdurand') + subscription = user.subscription.clone + new_date = (1.month.from_now - 4.days).utc + offer_days_count = OfferDay.count + + VCR.use_cassette('subscriptions_admin_offer_free_days') do + post '/api/local_payment/confirm_payment', + params: { + customer_id: user.id, + items: [ + { + free_extension: { + end_at: new_date.strftime('%Y-%m-%d %H:%M:%S.%9N Z') + } + } + ] + }.to_json, headers: default_headers + end + + # Check response format & status + assert_equal 201, response.status, response.body + assert_equal Mime[:json], response.content_type + + # Check that the subscribed plan was not altered + res = json_response(response.body) + assert_equal 'OfferDay', res[:main_object][:type] + assert_equal 0, res[:items][0][:amount] + + assert_equal subscription.id, user.subscription.id, 'subscription id has changed' + assert_equal subscription.plan_id, user.subscription.plan_id, 'subscribed plan does not match' + assert_dates_equal new_date, user.subscription.expired_at, 'subscription end date was not updated' + + # Check the subscription was correctly saved + assert_equal 1, user.subscriptions.count + assert_equal offer_days_count + 1, OfferDay.count + + # Check notification was sent to the user + notification = Notification.find_by( + notification_type_id: NotificationType.find_by(name: 'notify_member_subscription_extended'), + attached_object_type: 'Subscription', + attached_object_id: subscription[:id] + ) + assert_not_nil notification, 'user notification was not created' + assert_not_nil notification.get_meta_data(:free_days), + "notification didn't says to the user that her extent was for free" + assert_equal user.id, notification.receiver_id, 'wrong user notified' + end + + test 'admin cannot offer negative free days' do + user = User.find_by(username: 'pdurand') + new_date = (user.subscription.expiration_date - 4.days).utc + + post '/api/local_payment/confirm_payment', + params: { + customer_id: user.id, + items: [ + { + free_extension: { + end_at: new_date.strftime('%Y-%m-%d %H:%M:%S.%9N Z') + } + } + ] + }.to_json, headers: default_headers + + # Check response format & status + assert_equal 422, response.status, response.body + assert_equal Mime[:json], response.content_type + + # Check that the subscribed plan was not altered + res = json_response(response.body) + assert_equal I18n.t('cart_items.must_be_after_expiration'), res + end +end diff --git a/test/integration/subscriptions/renew_as_admin_test.rb b/test/integration/subscriptions/renew_as_admin_test.rb index f24e98ed0..80db5fca9 100644 --- a/test/integration/subscriptions/renew_as_admin_test.rb +++ b/test/integration/subscriptions/renew_as_admin_test.rb @@ -64,7 +64,7 @@ class Subscriptions::RenewAsAdminTest < ActionDispatch::IntegrationTest # Check notification was sent to the user notification = Notification.find_by( - notification_type_id: NotificationType.find_by_name('notify_member_subscribed_plan'), + notification_type_id: NotificationType.find_by(name: 'notify_member_subscribed_plan'), attached_object_type: 'Subscription', attached_object_id: subscription[:id] ) @@ -79,55 +79,6 @@ class Subscriptions::RenewAsAdminTest < ActionDispatch::IntegrationTest assert_equal plan.amount, invoice.total, 'Invoice total price does not match the bought subscription' end - test 'admin successfully offer free days' do - user = User.find_by(username: 'pdurand') - subscription = user.subscription.clone - new_date = (1.month.from_now - 4.days).utc - offer_days_count = OfferDay.count - - VCR.use_cassette('subscriptions_admin_offer_free_days') do - post '/api/local_payment/confirm_payment', - params: { - customer_id: user.id, - items: [ - { - free_extension: { - end_at: new_date.strftime('%Y-%m-%d %H:%M:%S.%9N Z') - } - } - ] - }.to_json, headers: default_headers - end - - # Check response format & status - assert_equal 201, response.status, response.body - assert_equal Mime[:json], response.content_type - - # Check that the subscribed plan was not altered - res = json_response(response.body) - assert_equal 'OfferDay', res[:main_object][:type] - assert_equal 0, res[:items][0][:amount] - - assert_equal subscription.id, user.subscription.id, 'subscription id has changed' - assert_equal subscription.plan_id, user.subscription.plan_id, 'subscribed plan does not match' - assert_dates_equal new_date, user.subscription.expired_at, 'subscription end date was not updated' - - # Check the subscription was correctly saved - assert_equal 1, user.subscriptions.count - assert_equal offer_days_count + 1, OfferDay.count - - # Check notification was sent to the user - notification = Notification.find_by( - notification_type_id: NotificationType.find_by_name('notify_member_subscription_extended'), - attached_object_type: 'Subscription', - attached_object_id: subscription[:id] - ) - assert_not_nil notification, 'user notification was not created' - assert_not_nil notification.get_meta_data(:free_days), - "notification didn't says to the user that her extent was for free" - assert_equal user.id, notification.receiver_id, 'wrong user notified' - end - test 'admin successfully extends a subscription' do user = User.find_by(username: 'pdurand') subscription = user.subscription.clone @@ -167,7 +118,7 @@ class Subscriptions::RenewAsAdminTest < ActionDispatch::IntegrationTest # Check notification was sent to the user notification = Notification.find_by( - notification_type_id: NotificationType.find_by_name('notify_member_subscribed_plan'), + notification_type_id: NotificationType.find_by(name: 'notify_member_subscribed_plan'), attached_object_type: 'Subscription', attached_object_id: subscription[:id] )