From 06c66d896b84e0f983f5fac6a727c45a4d5bd867 Mon Sep 17 00:00:00 2001 From: Sylvain Date: Thu, 2 Mar 2023 09:27:06 +0100 Subject: [PATCH 01/59] (bug) members can't change/cancel their reservations --- CHANGELOG.md | 4 +++- app/views/api/availabilities/_slot.json.jbuilder | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 35ad6a2d4..c1f4b18e9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,10 +1,12 @@ # Changelog Fab-manager +- Fix a bug: members can't change/cancel their reservations + ## v5.7.2 2023 February 24 - Fix a bug: unable to update recurrent events - Fix a bug: invalid border color for slots -- Fix a bug: members can change/cancel their reservations +- Fix a bug: members can't change/cancel their reservations ## v5.7.1 2023 February 20 diff --git a/app/views/api/availabilities/_slot.json.jbuilder b/app/views/api/availabilities/_slot.json.jbuilder index 70f3b3f2c..b9afa9923 100644 --- a/app/views/api/availabilities/_slot.json.jbuilder +++ b/app/views/api/availabilities/_slot.json.jbuilder @@ -10,7 +10,7 @@ json.is_completed slot.full?(reservable) json.backgroundColor 'white' json.availability_id slot.availability_id -json.slots_reservations_ids Slots::ReservationsService.user_reservations(slot, user, reservable)[:reservations] +json.slots_reservations_ids Slots::ReservationsService.user_reservations(slot, user, reservable)[:reservations].map(&:id) json.tag_ids slot.availability.tag_ids json.tags slot.availability.tags do |t| From df725c6dbf3551bf51e2885cfc996d141b4787b0 Mon Sep 17 00:00:00 2001 From: Sylvain Date: Thu, 2 Mar 2023 09:45:13 +0100 Subject: [PATCH 02/59] (bug) admin events view defaults to the list tab --- CHANGELOG.md | 1 + app/frontend/src/javascript/controllers/admin/events.js | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c1f4b18e9..476f212ef 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ # Changelog Fab-manager - Fix a bug: members can't change/cancel their reservations +- Fix a bug: admin events view defaults to the list tab ## v5.7.2 2023 February 24 diff --git a/app/frontend/src/javascript/controllers/admin/events.js b/app/frontend/src/javascript/controllers/admin/events.js index 61a2bd6c3..6ce0cadee 100644 --- a/app/frontend/src/javascript/controllers/admin/events.js +++ b/app/frontend/src/javascript/controllers/admin/events.js @@ -105,7 +105,7 @@ Application.Controllers.controller('AdminEventsController', ['$scope', '$state', { selected: '' }; // default tab: events list - $scope.tabs = { active: 0 }; + $scope.tabs = { active: 1 }; /** * Adds a bucket of events to the bottom of the page, grouped by month From 1cfcf0a8b5085a2a89044ccb7a8e0cca79e810d5 Mon Sep 17 00:00:00 2001 From: Sylvain Date: Thu, 2 Mar 2023 11:35:35 +0100 Subject: [PATCH 03/59] (bug) prevent selecting same price category twice --- CHANGELOG.md | 3 +- .../components/events/event-form.tsx | 41 +++++++---- .../components/form/form-select.tsx | 3 +- app/frontend/src/javascript/models/event.ts | 2 +- app/frontend/src/javascript/models/select.ts | 2 +- test/frontend/__fixtures__/age_ranges.ts | 8 +++ .../frontend/__fixtures__/event_categories.ts | 8 +++ .../__fixtures__/event_price_categories.ts | 8 +++ test/frontend/__fixtures__/event_themes.ts | 8 +++ test/frontend/__lib__/fixtures.ts | 10 ++- test/frontend/__setup__/server.js | 14 +++- .../components/events/event-form.test.tsx | 68 +++++++++++++++++++ 12 files changed, 157 insertions(+), 18 deletions(-) create mode 100644 test/frontend/__fixtures__/age_ranges.ts create mode 100644 test/frontend/__fixtures__/event_categories.ts create mode 100644 test/frontend/__fixtures__/event_price_categories.ts create mode 100644 test/frontend/__fixtures__/event_themes.ts create mode 100644 test/frontend/components/events/event-form.test.tsx diff --git a/CHANGELOG.md b/CHANGELOG.md index 476f212ef..9da3222b1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,8 @@ # Changelog Fab-manager - Fix a bug: members can't change/cancel their reservations -- Fix a bug: admin events view defaults to the list tab +- Fix a bug: admin events view should default to the list tab +- Fix a bug: event creation form should not allow setting multiple times the same price category ## v5.7.2 2023 February 24 diff --git a/app/frontend/src/javascript/components/events/event-form.tsx b/app/frontend/src/javascript/components/events/event-form.tsx index 6052208c3..e8a89c29f 100644 --- a/app/frontend/src/javascript/components/events/event-form.tsx +++ b/app/frontend/src/javascript/components/events/event-form.tsx @@ -71,6 +71,19 @@ export const EventForm: React.FC = ({ action, event, onError, on SettingAPI.get('advanced_accounting').then(res => setIsActiveAccounting(res.value === 'true')).catch(onError); }, []); + useEffect(() => { + // When a new custom price is added to the current event, we mark it as disabled to prevent setting the same category twice + const selectedCategoriesId = output.event_price_categories_attributes + ?.filter(epc => !epc._destroy && epc.price_category_id) + ?.map(epc => epc.price_category_id) || []; + setPriceCategoriesOptions(priceCategoriesOptions?.map(pco => { + return { + ...pco, + disabled: selectedCategoriesId.includes(pco.value) + }; + })); + }, [output.event_price_categories_attributes]); + /** * Callback triggered when the user clicks on the 'remove' button, in the additional prices area */ @@ -278,12 +291,14 @@ export const EventForm: React.FC = ({ action, event, onError, on type="number" tooltip={t('app.admin.event_form.seats_help')} /> + type="number" + id="amount" + formState={formState} + rules={{ required: true, min: 0 }} + nullable + label={t('app.admin.event_form.standard_rate')} + tooltip={t('app.admin.event_form.0_equal_free')} + addOn={FormatLib.currencySymbol()} /> {priceCategoriesOptions &&
{fields.map((price, index) => ( @@ -293,14 +308,16 @@ export const EventForm: React.FC = ({ action, event, onError, on id={`event_price_categories_attributes.${index}.price_category_id`} rules={{ required: true }} formState={formState} + disabled={() => index < fields.length - 1} label={t('app.admin.event_form.fare_class')} /> + register={register} + type="number" + rules={{ required: true, min: 0 }} + nullable + formState={formState} + label={t('app.admin.event_form.price')} + addOn={FormatLib.currencySymbol()} /> handlePriceRemove(price, index)} icon={} />
))} diff --git a/app/frontend/src/javascript/components/form/form-select.tsx b/app/frontend/src/javascript/components/form/form-select.tsx index 9559e2b7a..ed62fb163 100644 --- a/app/frontend/src/javascript/components/form/form-select.tsx +++ b/app/frontend/src/javascript/components/form/form-select.tsx @@ -66,7 +66,8 @@ export const FormSelect = + options={options} + isOptionDisabled={(option) => option.disabled}/> } /> ); diff --git a/app/frontend/src/javascript/models/event.ts b/app/frontend/src/javascript/models/event.ts index 5e4fe83b5..5cca1d9e8 100644 --- a/app/frontend/src/javascript/models/event.ts +++ b/app/frontend/src/javascript/models/event.ts @@ -69,7 +69,7 @@ export interface Event { export interface EventDecoration { id?: number, name: string, - related_to?: number + related_to?: number // report the count of events related to the given decoration } export type EventTheme = EventDecoration; diff --git a/app/frontend/src/javascript/models/select.ts b/app/frontend/src/javascript/models/select.ts index e3dab0e1b..ce5938697 100644 --- a/app/frontend/src/javascript/models/select.ts +++ b/app/frontend/src/javascript/models/select.ts @@ -2,7 +2,7 @@ * Option format, expected by react-select * @see https://github.com/JedWatson/react-select */ -export type SelectOption = { value: TOptionValue, label: TOptionLabel } +export type SelectOption = { value: TOptionValue, label: TOptionLabel, disabled?: boolean } /** * Checklist Option format diff --git a/test/frontend/__fixtures__/age_ranges.ts b/test/frontend/__fixtures__/age_ranges.ts new file mode 100644 index 000000000..ad875b171 --- /dev/null +++ b/test/frontend/__fixtures__/age_ranges.ts @@ -0,0 +1,8 @@ +import { AgeRange } from 'models/event'; + +const ageRanges: Array = [ + { id: 1, name: 'Children' }, + { id: 2, name: 'Over 18' } +]; + +export default ageRanges; diff --git a/test/frontend/__fixtures__/event_categories.ts b/test/frontend/__fixtures__/event_categories.ts new file mode 100644 index 000000000..726889099 --- /dev/null +++ b/test/frontend/__fixtures__/event_categories.ts @@ -0,0 +1,8 @@ +import { EventCategory } from 'models/event'; + +const categories: Array = [ + { id: 1, name: 'Workshop' }, + { id: 2, name: 'Internship' } +]; + +export default categories; diff --git a/test/frontend/__fixtures__/event_price_categories.ts b/test/frontend/__fixtures__/event_price_categories.ts new file mode 100644 index 000000000..103c71b49 --- /dev/null +++ b/test/frontend/__fixtures__/event_price_categories.ts @@ -0,0 +1,8 @@ +import { EventPriceCategory } from 'models/event'; + +const categories: Array = [ + { id: 1, name: 'Students' }, + { id: 2, name: 'Partners' } +]; + +export default categories; diff --git a/test/frontend/__fixtures__/event_themes.ts b/test/frontend/__fixtures__/event_themes.ts new file mode 100644 index 000000000..229afe67f --- /dev/null +++ b/test/frontend/__fixtures__/event_themes.ts @@ -0,0 +1,8 @@ +import { EventTheme } from 'models/event'; + +const themes: Array = [ + { id: 1, name: 'Fabric week' }, + { id: 2, name: 'Everyone at the Fablab' } +]; + +export default themes; diff --git a/test/frontend/__lib__/fixtures.ts b/test/frontend/__lib__/fixtures.ts index ef7ed6663..9b9d57733 100644 --- a/test/frontend/__lib__/fixtures.ts +++ b/test/frontend/__lib__/fixtures.ts @@ -13,6 +13,10 @@ import spaces from '../__fixtures__/spaces'; import statuses from '../__fixtures__/statuses'; import notificationTypes from '../__fixtures__/notification_types'; import notifications from '../__fixtures__/notifications'; +import eventCategories from '../__fixtures__/event_categories'; +import eventThemes from '../__fixtures__/event_themes'; +import ageRanges from '../__fixtures__/age_ranges'; +import eventPriceCategories from '../__fixtures__/event_price_categories'; const FixturesLib = { init: () => { @@ -33,7 +37,11 @@ const FixturesLib = { spaces: JSON.parse(JSON.stringify(spaces)), statuses: JSON.parse(JSON.stringify(statuses)), notificationTypes: JSON.parse(JSON.stringify(notificationTypes)), - notifications: JSON.parse(JSON.stringify(notifications)) + notifications: JSON.parse(JSON.stringify(notifications)), + eventCategories: JSON.parse(JSON.stringify(eventCategories)), + eventThemes: JSON.parse(JSON.stringify(eventThemes)), + ageRanges: JSON.parse(JSON.stringify(ageRanges)), + eventPriceCategories: JSON.parse(JSON.stringify(eventPriceCategories)) }; } }; diff --git a/test/frontend/__setup__/server.js b/test/frontend/__setup__/server.js index 09e578c12..58a795bc7 100644 --- a/test/frontend/__setup__/server.js +++ b/test/frontend/__setup__/server.js @@ -113,10 +113,22 @@ export const server = setupServer( return res(ctx.json({ status })); }), rest.get('/api/notification_types', (req, res, ctx) => { - return res(ctx.json(fixtures.notification_types)); + return res(ctx.json(fixtures.notificationTypes)); }), rest.get('/api/notifications', (req, res, ctx) => { return res(ctx.json(fixtures.notifications)); + }), + rest.get('/api/categories', (req, res, ctx) => { + return res(ctx.json(fixtures.eventCategories)); + }), + rest.get('/api/event_themes', (req, res, ctx) => { + return res(ctx.json(fixtures.eventThemes)); + }), + rest.get('/api/age_ranges', (req, res, ctx) => { + return res(ctx.json(fixtures.ageRanges)); + }), + rest.get('/api/price_categories', (req, res, ctx) => { + return res(ctx.json(fixtures.eventPriceCategories)); }) ); diff --git a/test/frontend/components/events/event-form.test.tsx b/test/frontend/components/events/event-form.test.tsx new file mode 100644 index 000000000..7b3d53b6c --- /dev/null +++ b/test/frontend/components/events/event-form.test.tsx @@ -0,0 +1,68 @@ +import { EventForm } from 'components/events/event-form'; +import { fireEvent, render, screen, waitFor } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +import '@testing-library/jest-dom'; +import selectEvent from 'react-select-event'; +import eventPriceCategories from '../../__fixtures__/event_price_categories'; + +describe('EventForm', () => { + const onError = jest.fn(); + const onSuccess = jest.fn(); + + test('render create EventForm', async () => { + render(); + await waitFor(() => screen.getByRole('combobox', { name: /app.admin.event_form.event_category/ })); + expect(screen.getByLabelText(/app.admin.event_form.title/)).toBeInTheDocument(); + expect(screen.getByLabelText(/app.admin.event_form.matching_visual/)).toBeInTheDocument(); + expect(screen.getByLabelText(/app.admin.event_form.description/)).toBeInTheDocument(); + expect(screen.getByLabelText(/app.admin.event_form.event_category/)).toBeInTheDocument(); + expect(screen.getByLabelText(/app.admin.event_form.event_themes/)).toBeInTheDocument(); + expect(screen.getByLabelText(/app.admin.event_form.age_range/)).toBeInTheDocument(); + expect(screen.getByLabelText(/app.admin.event_form.start_date/)).toBeInTheDocument(); + expect(screen.getByLabelText(/app.admin.event_form.end_date/)).toBeInTheDocument(); + expect(screen.getByLabelText(/app.admin.event_form.all_day/)).toBeInTheDocument(); + expect(screen.getByLabelText(/app.admin.event_form.start_time/)).toBeInTheDocument(); + expect(screen.getByLabelText(/app.admin.event_form.end_time/)).toBeInTheDocument(); + expect(screen.getByLabelText(/app.admin.event_form.recurrence/)).toBeInTheDocument(); + expect(screen.getByLabelText(/app.admin.event_form._and_ends_on/)).toBeInTheDocument(); + expect(screen.getByLabelText(/app.admin.event_form.seats_available/)).toBeInTheDocument(); + expect(screen.getByLabelText(/app.admin.event_form.standard_rate/)).toBeInTheDocument(); + expect(screen.getByRole('button', { name: /app.admin.event_form.add_price/ })).toBeInTheDocument(); + expect(screen.getByRole('button', { name: /app.admin.event_form.add_a_new_file/ })).toBeInTheDocument(); + expect(screen.getByLabelText(/app.admin.advanced_accounting_form.code/)).toBeInTheDocument(); + expect(screen.getByLabelText(/app.admin.advanced_accounting_form.analytical_section/)).toBeInTheDocument(); + expect(screen.getByRole('button', { name: /app.admin.event_form.save/ })).toBeInTheDocument(); + }); + + test('all day event hides the time inputs', async () => { + render(); + await waitFor(() => screen.getByRole('combobox', { name: /app.admin.event_form.event_category/ })); + const user = userEvent.setup(); + await user.click(screen.getByLabelText(/app.admin.event_form.all_day/)); + expect(screen.queryByLabelText(/app.admin.event_form.start_time/)).toBeNull(); + expect(screen.queryByLabelText(/app.admin.event_form.end_time/)).toBeNull(); + }); + + test('recurrent event requires end date', async () => { + render(); + await waitFor(() => screen.getByRole('combobox', { name: /app.admin.event_form.event_category/ })); + await selectEvent.select(screen.getByLabelText(/app.admin.event_form.recurrence/), 'app.admin.event_form.recurring.every_week'); + expect(screen.getByLabelText(/app.admin.event_form._and_ends_on/).closest('label')).toHaveClass('is-required'); + }); + + test('adding a second custom rate', async () => { + render(); + await waitFor(() => screen.getByRole('combobox', { name: /app.admin.event_form.event_category/ })); + // add a first category + fireEvent.click(screen.getByRole('button', { name: /app.admin.event_form.add_price/ })); + expect(screen.getByLabelText(/app.admin.event_form.fare_class/)).toBeInTheDocument(); + expect(screen.getByLabelText(/app.admin.event_form.price/)).toBeInTheDocument(); + await selectEvent.select(screen.getByLabelText(/app.admin.event_form.fare_class/), eventPriceCategories[0].name); + fireEvent.change(screen.getByLabelText(/app.admin.event_form.price/), { target: { value: 10 } }); + // add a second category + fireEvent.click(screen.getByRole('button', { name: /app.admin.event_form.add_price/ })); + expect(screen.getAllByLabelText(/app.admin.event_form.fare_class/)[0]).toBeDisabled(); + await selectEvent.openMenu(screen.getAllByLabelText(/app.admin.event_form.fare_class/)[1]); + expect(screen.getAllByText(eventPriceCategories[0].name).find(element => element.classList.contains('rs__option'))).toHaveAttribute('aria-disabled', 'true'); + }); +}); From 33d8c2951e46bec151c369061c7d1c1e112faa45 Mon Sep 17 00:00:00 2001 From: Sylvain Date: Thu, 2 Mar 2023 11:38:00 +0100 Subject: [PATCH 04/59] (bug) MAX_SIZE env varibles should not be quoted (#438) --- CHANGELOG.md | 1 + setup/env.example | 8 ++++---- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9da3222b1..9889e50df 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ - Fix a bug: members can't change/cancel their reservations - Fix a bug: admin events view should default to the list tab - Fix a bug: event creation form should not allow setting multiple times the same price category +- Fix a bug: MAX_SIZE env varibles should not be quoted (#438) ## v5.7.2 2023 February 24 diff --git a/setup/env.example b/setup/env.example index bf29f5052..cdc9495ad 100644 --- a/setup/env.example +++ b/setup/env.example @@ -49,10 +49,10 @@ ALLOW_INSECURE_HTTP=false ENABLE_SENTRY=false # 5242880 = 5 megabytes -MAX_IMPORT_SIZE='5242880' +MAX_IMPORT_SIZE=5242880 # 10485760 = 10 megabytes -MAX_IMAGE_SIZE='10485760' +MAX_IMAGE_SIZE=10485760 # 20971520 = 20 megabytes -MAX_CAO_SIZE='20971520' +MAX_CAO_SIZE=20971520 # 5242880 = 5 megabytes -MAX_SUPPORTING_DOCUMENT_FILE_SIZE='5242880' +MAX_SUPPORTING_DOCUMENT_FILE_SIZE=5242880 From 66a55b62d9672d9ba533b473ec3adbfacfc9ccf4 Mon Sep 17 00:00:00 2001 From: Sylvain Date: Thu, 2 Mar 2023 11:53:09 +0100 Subject: [PATCH 05/59] (bug) do not crash if stripe does not answer --- .../javascript/components/payment/stripe/stripe-keys-form.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/frontend/src/javascript/components/payment/stripe/stripe-keys-form.tsx b/app/frontend/src/javascript/components/payment/stripe/stripe-keys-form.tsx index 73466f652..4f6db1b5d 100644 --- a/app/frontend/src/javascript/components/payment/stripe/stripe-keys-form.tsx +++ b/app/frontend/src/javascript/components/payment/stripe/stripe-keys-form.tsx @@ -113,7 +113,7 @@ const StripeKeysForm: React.FC = ({ onValidKeys, onInvalidK }, reason => { if (!mounted.current) return; - if (reason.response.status === 401) { + if (reason.response?.status === 401) { setSecretKeyAddOn(); setSecretKeyAddOnClassName('key-invalid'); } From 6ae8c965f390c145cf5444a52e39cc2e4fb891e0 Mon Sep 17 00:00:00 2001 From: Sylvain Date: Thu, 2 Mar 2023 13:26:05 +0100 Subject: [PATCH 06/59] (quality) add unique constraints in db Also: code lint in SSO models --- app/models/auth_provider.rb | 18 +++++++++--------- app/models/o_auth2_provider.rb | 3 +-- app/models/open_id_connect_provider.rb | 6 +++--- ...0230302120458_add_uniqueness_constraints.rb | 12 ++++++++++++ db/schema.rb | 9 +++++++-- 5 files changed, 32 insertions(+), 16 deletions(-) create mode 100644 db/migrate/20230302120458_add_uniqueness_constraints.rb diff --git a/app/models/auth_provider.rb b/app/models/auth_provider.rb index efa589618..126164938 100644 --- a/app/models/auth_provider.rb +++ b/app/models/auth_provider.rb @@ -41,10 +41,10 @@ class AuthProvider < ApplicationRecord provider = find_by(status: 'active') return local if provider.nil? - return provider + provider rescue ActiveRecord::StatementInvalid # we fall here on database creation because the table "active_providers" still does not exists at the moment - return local + local end end @@ -59,7 +59,7 @@ class AuthProvider < ApplicationRecord parsed = /^([^-]+)-(.+)$/.match(strategy_name) ret = nil - all.each do |strategy| + all.find_each do |strategy| if strategy.provider_type == parsed[1] && strategy.name.downcase.parameterize == parsed[2] ret = strategy break @@ -70,13 +70,13 @@ class AuthProvider < ApplicationRecord ## Return the name that should be registered in OmniAuth for the corresponding strategy def strategy_name - provider_type + '-' + name.downcase.parameterize + "#{provider_type}-#{name.downcase.parameterize}" end ## Return the provider type name without the "Provider" part. ## eg. DatabaseProvider will return 'database' def provider_type - providable_type[0..-9].downcase + providable_type[0..-9]&.downcase end ## Return the user's profile fields that are currently managed from the SSO @@ -84,7 +84,7 @@ class AuthProvider < ApplicationRecord def sso_fields fields = [] auth_provider_mappings.each do |mapping| - fields.push(mapping.local_model + '.' + mapping.local_field) + fields.push("#{mapping.local_model}.#{mapping.local_field}") end fields end @@ -96,10 +96,10 @@ class AuthProvider < ApplicationRecord end def safe_destroy - if status != 'active' - destroy - else + if status == 'active' false + else + destroy end end diff --git a/app/models/o_auth2_provider.rb b/app/models/o_auth2_provider.rb index 3064b265d..215db9c6e 100644 --- a/app/models/o_auth2_provider.rb +++ b/app/models/o_auth2_provider.rb @@ -3,6 +3,5 @@ # OAuth2Provider is a special type of AuthProvider which provides authentication through an external SSO server using # the oAuth 2.0 protocol. class OAuth2Provider < ApplicationRecord - has_one :auth_provider, as: :providable - + has_one :auth_provider, as: :providable, dependent: :destroy end diff --git a/app/models/open_id_connect_provider.rb b/app/models/open_id_connect_provider.rb index 3243a5bf8..e91095edc 100644 --- a/app/models/open_id_connect_provider.rb +++ b/app/models/open_id_connect_provider.rb @@ -3,7 +3,7 @@ # OpenIdConnectProvider is a special type of AuthProvider which provides authentication through an external SSO server using # the OpenID Connect protocol. class OpenIdConnectProvider < ApplicationRecord - has_one :auth_provider, as: :providable + has_one :auth_provider, as: :providable, dependent: :destroy validates :issuer, presence: true validates :client__identifier, presence: true @@ -28,8 +28,8 @@ class OpenIdConnectProvider < ApplicationRecord end def client_config - OpenIdConnectProvider.columns.map(&:name).filter { |n| n.start_with?('client__') }.map do |n| + OpenIdConnectProvider.columns.map(&:name).filter { |n| n.start_with?('client__') }.to_h do |n| [n.sub('client__', ''), send(n)] - end.to_h + end end end diff --git a/db/migrate/20230302120458_add_uniqueness_constraints.rb b/db/migrate/20230302120458_add_uniqueness_constraints.rb new file mode 100644 index 000000000..555f3ec02 --- /dev/null +++ b/db/migrate/20230302120458_add_uniqueness_constraints.rb @@ -0,0 +1,12 @@ +# frozen_string_literal: true + +# Add uniqueness constraint at database level +class AddUniquenessConstraints < ActiveRecord::Migration[5.2] + def change + add_index :credits, %i[plan_id creditable_id creditable_type], unique: true + add_index :prices, %i[plan_id priceable_id priceable_type group_id duration], unique: true, + name: 'index_prices_on_plan_priceable_group_and_duration' + add_index :price_categories, :name, unique: true + add_index :auth_providers, :name, unique: true + end +end diff --git a/db/schema.rb b/db/schema.rb index b96834799..c38ce2e08 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 2023_02_13_134954) do +ActiveRecord::Schema.define(version: 2023_03_02_120458) do # These are extensions that must be enabled in order to support this database enable_extension "fuzzystrmatch" @@ -122,6 +122,7 @@ ActiveRecord::Schema.define(version: 2023_02_13_134954) do t.datetime "updated_at", null: false t.string "providable_type" t.integer "providable_id" + t.index ["name"], name: "index_auth_providers_on_name", unique: true end create_table "availabilities", id: :serial, force: :cascade do |t| @@ -163,10 +164,10 @@ ActiveRecord::Schema.define(version: 2023_02_13_134954) do create_table "cart_item_event_reservation_tickets", force: :cascade do |t| t.integer "booked" - t.bigint "event_price_category_id" t.bigint "cart_item_event_reservation_id" t.datetime "created_at", null: false t.datetime "updated_at", null: false + t.bigint "event_price_category_id" t.index ["cart_item_event_reservation_id"], name: "index_cart_item_tickets_on_cart_item_event_reservation" t.index ["event_price_category_id"], name: "index_cart_item_tickets_on_event_price_category" end @@ -287,6 +288,7 @@ ActiveRecord::Schema.define(version: 2023_02_13_134954) do t.integer "hours" t.datetime "created_at" t.datetime "updated_at" + t.index ["plan_id", "creditable_id", "creditable_type"], name: "index_credits_on_plan_id_and_creditable_id_and_creditable_type", unique: true t.index ["plan_id"], name: "index_credits_on_plan_id" end @@ -787,6 +789,8 @@ ActiveRecord::Schema.define(version: 2023_02_13_134954) do t.text "conditions" t.datetime "created_at", null: false t.datetime "updated_at", null: false + t.index "btrim(lower((name)::text))", name: "index_price_categories_on_TRIM_BOTH_FROM_LOWER_name", unique: true + t.index ["name"], name: "index_price_categories_on_name", unique: true end create_table "prices", id: :serial, force: :cascade do |t| @@ -799,6 +803,7 @@ ActiveRecord::Schema.define(version: 2023_02_13_134954) do t.datetime "updated_at", null: false t.integer "duration", default: 60 t.index ["group_id"], name: "index_prices_on_group_id" + t.index ["plan_id", "priceable_id", "priceable_type", "group_id", "duration"], name: "index_prices_on_plan_priceable_group_and_duration", unique: true t.index ["plan_id"], name: "index_prices_on_plan_id" t.index ["priceable_type", "priceable_id"], name: "index_prices_on_priceable_type_and_priceable_id" end From 6d14a8dc7727db1adce9dfac2b952f428fe5445a Mon Sep 17 00:00:00 2001 From: Sylvain Date: Thu, 2 Mar 2023 13:39:23 +0100 Subject: [PATCH 07/59] (quality) use Mime module --- test/test_helper.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test_helper.rb b/test/test_helper.rb index 13e38174d..fadd22d3a 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -43,7 +43,7 @@ class ActiveSupport::TestCase end def upload_headers - { 'Accept' => Mime[:json], 'Content-Type' => 'multipart/form-data' } + { 'Accept' => Mime[:json], 'Content-Type' => Mime[:multipart_form].to_s } end def open_api_headers(token) From 18447f83716ed64715d89f3859f30227b79ee71d Mon Sep 17 00:00:00 2001 From: Sylvain Date: Thu, 2 Mar 2023 17:13:46 +0100 Subject: [PATCH 08/59] (test) OIDC testing --- app/views/application/sso_redirect.html.erb | 2 +- env.example | 2 + scripts/tests.sh | 2 +- test/fixtures/history_values.yml | 4 +- test/helpers/auth_provider_helper.rb | 28 ++++++++++ test/integration/open_id_connect_test.rb | 59 +++++++++++++++++++++ test/test_helper.rb | 6 ++- 7 files changed, 98 insertions(+), 5 deletions(-) create mode 100644 test/integration/open_id_connect_test.rb diff --git a/app/views/application/sso_redirect.html.erb b/app/views/application/sso_redirect.html.erb index 1d57b5c15..3ae666c26 100644 --- a/app/views/application/sso_redirect.html.erb +++ b/app/views/application/sso_redirect.html.erb @@ -7,7 +7,7 @@ <% param = @authorization_token ? "?auth_token=#{@authorization_token}" : '' %> - <% url_path = File.join(root_url, "users/auth/#{@active_provider.strategy_name}#{param}") %> + <% url_path = URI.join("#{ENV.fetch('DEFAULT_PROTOCOL')}://#{ENV.fetch('DEFAULT_HOST')}", "users/auth/#{@active_provider.strategy_name}#{param}") %>
<%= hidden_field_tag :authenticity_token, @authentication_token %>