diff --git a/app/models/members/members/member_approval.rb b/app/models/members/members/member_approval.rb index c1f37e40b6280ef248fe50bed0fde41e5cd95d2c..a94260634f9c8d2d714538327e9db2c52b9db6ae 100644 --- a/app/models/members/members/member_approval.rb +++ b/app/models/members/members/member_approval.rb @@ -17,6 +17,7 @@ class MemberApproval < ApplicationRecord validates :new_access_level, presence: true validates :user, presence: true validates :member_namespace, presence: true + validates :metadata, json_schema: { filename: "members_approval_request_metadata" } end end diff --git a/app/validators/json_schemas/members_approval_request_metadata.json b/app/validators/json_schemas/members_approval_request_metadata.json new file mode 100644 index 0000000000000000000000000000000000000000..3c9ca4cf7b895909a944890585e3b486f22986c0 --- /dev/null +++ b/app/validators/json_schemas/members_approval_request_metadata.json @@ -0,0 +1,24 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "object", + "properties": { + "expires_at": { + "type": "string", + "oneOf": [ + { + "format": "date" + }, + { + "pattern": "^$" + } + ] + }, + "member_role_id": { + "type": [ + "number", + "null" + ] + } + }, + "additionalProperties": true +} diff --git a/db/migrate/20240519141301_add_metadata_to_member_approvals.rb b/db/migrate/20240519141301_add_metadata_to_member_approvals.rb new file mode 100644 index 0000000000000000000000000000000000000000..7ed3f83989c9b94825a2682e97c6e3fa2f7b8c94 --- /dev/null +++ b/db/migrate/20240519141301_add_metadata_to_member_approvals.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +class AddMetadataToMemberApprovals < Gitlab::Database::Migration[2.2] + milestone '17.1' + enable_lock_retries! + + def change + add_column :member_approvals, :metadata, :jsonb, default: {}, null: false + end +end diff --git a/db/schema_migrations/20240519141301 b/db/schema_migrations/20240519141301 new file mode 100644 index 0000000000000000000000000000000000000000..a6ae2cea4528b9e434e40445ec0fc634cf73d754 --- /dev/null +++ b/db/schema_migrations/20240519141301 @@ -0,0 +1 @@ +cd61987ea28b86a0c4280c8e0c743ecef2e1c6a45842281b4c7814ebe6e87057 \ No newline at end of file diff --git a/db/structure.sql b/db/structure.sql index 20b4d27d84a49d9113d6421d8ea7dd1d541260c0..49b6b30d4d8095c53f23d1aeaf258f13e87f8bf4 100644 --- a/db/structure.sql +++ b/db/structure.sql @@ -11247,7 +11247,8 @@ CREATE TABLE member_approvals ( old_access_level integer, status smallint DEFAULT 0 NOT NULL, user_id bigint NOT NULL, - member_role_id bigint + member_role_id bigint, + metadata jsonb DEFAULT '{}'::jsonb NOT NULL ); CREATE SEQUENCE member_approvals_id_seq diff --git a/spec/models/members/members/member_approval_spec.rb b/spec/models/members/members/member_approval_spec.rb index d456c75e2eb3f6c51361e9764eb293fcd3b2813f..1ac1e98c5611f5eecba7eb9e534ec7c19c1613b2 100644 --- a/spec/models/members/members/member_approval_spec.rb +++ b/spec/models/members/members/member_approval_spec.rb @@ -15,5 +15,91 @@ it { is_expected.to validate_presence_of(:new_access_level) } it { is_expected.to validate_presence_of(:user) } it { is_expected.to validate_presence_of(:member_namespace) } + + context 'with metadata' do + subject { build(:member_approval, metadata: attribute_mapping) } + + context 'with valid JSON schemas' do + let(:attribute_mapping) do + { + expires_at: expiry, + member_role_id: nil + } + end + + context 'with empty metadata' do + let(:attribute_mapping) { {} } + + it { is_expected.to be_valid } + end + + context 'with valid expiry' do + let(:expiry) { "1970-01-01" } + + it { is_expected.to be_valid } + end + + context 'with empty expiry' do + let(:expiry) { "" } + + it { is_expected.to be_valid } + end + + context 'with not null member_role_id' do + let(:attribute_mapping) do + { + member_role_id: 3 + } + end + + it { is_expected.to be_valid } + end + + context 'when property has extra attributes' do + let(:attribute_mapping) do + { access_level: 20 } + end + + it { is_expected.to be_valid } + end + end + + context 'with invalid JSON schemas' do + shared_examples 'is invalid record' do + it do + expect(subject).to be_invalid + expect(subject.errors.messages[:metadata]).to eq(['must be a valid json schema']) + end + end + + context 'when property is not an object' do + let(:attribute_mapping) do + "That is not a valid schema" + end + + it_behaves_like 'is invalid record' + end + + context 'with invalid expiry' do + let(:attribute_mapping) do + { + expires_at: "1242" + } + end + + it_behaves_like 'is invalid record' + end + + context 'with member_role_id' do + let(:attribute_mapping) do + { + member_role_id: "some role" + } + end + + it_behaves_like 'is invalid record' + end + end + end end end