Commit 170f0bdc authored by GitLab Bot's avatar GitLab Bot

Add latest changes from gitlab-org/gitlab@master

parent f1bb2a30
.except-deploys:
except:
refs:
- /^[\d-]+-stable(-ee)?$/
- /^\d+-\d+-auto-deploy-\d+$/
.review-docker:
......
# frozen_string_literal: true
class ZoomMeeting < ApplicationRecord
belongs_to :project, optional: false
belongs_to :issue, optional: false
validates :url, presence: true, length: { maximum: 255 }, zoom_url: true
validates :issue, same_project_association: true
enum issue_status: {
added: 1,
removed: 2
}
scope :added_to_issue, -> { where(issue_status: :added) }
scope :removed_from_issue, -> { where(issue_status: :removed) }
end
# frozen_string_literal: true
# SameProjectAssociationValidator
#
# Custom validator to validate that the same project associated with
# the record is also associated with the value
#
# Example:
# class ZoomMeeting < ApplicationRecord
# belongs_to :project, optional: false
# belongs_to :issue, optional: false
# validates :issue, same_project_association: true
# end
class SameProjectAssociationValidator < ActiveModel::EachValidator
def validate_each(record, attribute, value)
return if record.project == value&.project
record.errors[attribute] << 'must associate the same project'
end
end
# frozen_string_literal: true
# ZoomUrlValidator
#
# Custom validator for zoom urls
#
class ZoomUrlValidator < ActiveModel::EachValidator
def validate_each(record, attribute, value)
return if Gitlab::ZoomLinkExtractor.new(value).links.size == 1
record.errors.add(:url, 'must contain one valid Zoom URL')
end
end
# frozen_string_literal: true
class CreateZoomMeetings < ActiveRecord::Migration[5.2]
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
ZOOM_MEETING_STATUS_ADDED = 1
def change
create_table :zoom_meetings do |t|
t.references :project, foreign_key: { on_delete: :cascade },
null: false
t.references :issue, foreign_key: { on_delete: :cascade },
null: false
t.timestamps_with_timezone null: false
t.integer :issue_status, limit: 2, default: 1, null: false
t.string :url, limit: 255
t.index [:issue_id, :issue_status], unique: true,
where: "issue_status = #{ZOOM_MEETING_STATUS_ADDED}"
end
end
end
......@@ -3992,6 +3992,18 @@ ActiveRecord::Schema.define(version: 2019_10_16_220135) do
t.index ["type"], name: "index_web_hooks_on_type"
end
create_table "zoom_meetings", force: :cascade do |t|
t.bigint "project_id", null: false
t.bigint "issue_id", null: false
t.datetime_with_timezone "created_at", null: false
t.datetime_with_timezone "updated_at", null: false
t.integer "issue_status", limit: 2, default: 1, null: false
t.string "url", limit: 255
t.index ["issue_id", "issue_status"], name: "index_zoom_meetings_on_issue_id_and_issue_status", unique: true, where: "(issue_status = 1)"
t.index ["issue_id"], name: "index_zoom_meetings_on_issue_id"
t.index ["project_id"], name: "index_zoom_meetings_on_project_id"
end
add_foreign_key "alerts_service_data", "services", on_delete: :cascade
add_foreign_key "allowed_email_domains", "namespaces", column: "group_id", on_delete: :cascade
add_foreign_key "analytics_cycle_analytics_group_stages", "labels", column: "end_event_label_id", on_delete: :cascade
......@@ -4406,4 +4418,6 @@ ActiveRecord::Schema.define(version: 2019_10_16_220135) do
add_foreign_key "vulnerability_scanners", "projects", on_delete: :cascade
add_foreign_key "web_hook_logs", "web_hooks", on_delete: :cascade
add_foreign_key "web_hooks", "projects", name: "fk_0c8ca6d9d1", on_delete: :cascade
add_foreign_key "zoom_meetings", "issues", on_delete: :cascade
add_foreign_key "zoom_meetings", "projects", on_delete: :cascade
end
......@@ -419,6 +419,7 @@ module QA
autoload :Config, 'qa/specs/config'
autoload :Runner, 'qa/specs/runner'
autoload :ParallelRunner, 'qa/specs/parallel_runner'
autoload :LoopRunner, 'qa/specs/loop_runner'
module Helpers
autoload :Quarantine, 'qa/specs/helpers/quarantine'
......
......@@ -261,6 +261,10 @@ module QA
ENV['QA_RUNTIME_SCENARIO_ATTRIBUTES']
end
def gitlab_qa_loop_runner_minutes
ENV.fetch('GITLAB_QA_LOOP_RUNNER_MINUTES', 1).to_i
end
private
def remote_grid_credentials
......
......@@ -8,6 +8,7 @@ module QA
attribute :gitlab_address, '--address URL', 'Address of the instance to test'
attribute :enable_feature, '--enable-feature FEATURE_FLAG', 'Enable a feature before running tests'
attribute :parallel, '--parallel', 'Execute tests in parallel'
attribute :loop, '--loop', 'Execute test repeatedly'
end
end
end
# frozen_string_literal: true
module QA
module Specs
module LoopRunner
module_function
def run(args)
start = Time.now
loop_duration = 60 * QA::Runtime::Env.gitlab_qa_loop_runner_minutes
while Time.now - start < loop_duration
RSpec::Core::Runner.run(args.flatten, $stderr, $stdout).tap do |status|
abort if status.nonzero?
end
RSpec.clear_examples
end
end
end
end
end
......@@ -63,6 +63,8 @@ module QA
if Runtime::Scenario.attributes[:parallel]
ParallelRunner.run(args.flatten)
elsif Runtime::Scenario.attributes[:loop]
LoopRunner.run(args.flatten)
else
RSpec::Core::Runner.run(args.flatten, $stderr, $stdout).tap do |status|
abort if status.nonzero?
......
# frozen_string_literal: true
FactoryBot.define do
factory :zoom_meeting do
project { issue.project }
issue
url { 'https://zoom.us/j/123456789' }
issue_status { :added }
trait :added_to_issue do
issue_status { :added }
end
trait :removed_from_issue do
issue_status { :removed }
end
end
end
# frozen_string_literal: true
require 'spec_helper'
describe ZoomMeeting do
let(:project) { build(:project) }
describe 'Factory' do
subject { build(:zoom_meeting) }
it { is_expected.to be_valid }
end
describe 'Associations' do
it { is_expected.to belong_to(:project).required }
it { is_expected.to belong_to(:issue).required }
end
describe 'scopes' do
let(:issue) { create(:issue, project: project) }
let!(:added_meeting) { create(:zoom_meeting, :added_to_issue, issue: issue) }
let!(:removed_meeting) { create(:zoom_meeting, :removed_from_issue, issue: issue) }
describe '.added_to_issue' do
it 'gets only added meetings' do
meetings_added = described_class.added_to_issue.pluck(:id)
expect(meetings_added).to include(added_meeting.id)
expect(meetings_added).not_to include(removed_meeting.id)
end
end
describe '.removed_from_issue' do
it 'gets only removed meetings' do
meetings_removed = described_class.removed_from_issue.pluck(:id)
expect(meetings_removed).to include(removed_meeting.id)
expect(meetings_removed).not_to include(added_meeting.id)
end
end
end
describe 'Validations' do
describe 'url' do
it { is_expected.to validate_presence_of(:url) }
it { is_expected.to validate_length_of(:url).is_at_most(255) }
shared_examples 'invalid Zoom URL' do
it do
expect(subject).to be_invalid
expect(subject.errors[:url])
.to contain_exactly('must contain one valid Zoom URL')
end
end
context 'with non-Zoom URL' do
before do
subject.url = %{https://non-zoom.url}
end
include_examples 'invalid Zoom URL'
end
context 'with multiple Zoom-URLs' do
before do
subject.url = %{https://zoom.us/j/123 https://zoom.us/j/456}
end
include_examples 'invalid Zoom URL'
end
end
describe 'issue association' do
let(:issue) { build(:issue, project: project) }
subject { build(:zoom_meeting, project: project, issue: issue) }
context 'for the same project' do
it { is_expected.to be_valid }
end
context 'for a different project' do
let(:issue) { build(:issue) }
it do
expect(subject).to be_invalid
expect(subject.errors[:issue])
.to contain_exactly('must associate the same project')
end
end
end
end
describe 'limit number of meetings per issue' do
shared_examples 'can add meetings' do
it 'can add new Zoom meetings' do
create(:zoom_meeting, :added_to_issue, issue: issue)
end
end
shared_examples 'can remove meetings' do
it 'can remove Zoom meetings' do
create(:zoom_meeting, :removed_from_issue, issue: issue)
end
end
shared_examples 'cannot add meetings' do
it 'fails to add a new meeting' do
expect do
create(:zoom_meeting, :added_to_issue, issue: issue)
end.to raise_error ActiveRecord::RecordNotUnique
end
end
let(:issue) { create(:issue, project: project) }
context 'without meetings' do
it_behaves_like 'can add meetings'
end
context 'when no other meeting is added' do
before do
create(:zoom_meeting, :removed_from_issue, issue: issue)
end
it_behaves_like 'can add meetings'
end
context 'when meeting is added' do
before do
create(:zoom_meeting, :added_to_issue, issue: issue)
end
it_behaves_like 'cannot add meetings'
end
context 'when meeting is added to another issue' do
let(:another_issue) { create(:issue, project: project) }
before do
create(:zoom_meeting, :added_to_issue, issue: another_issue)
end
it_behaves_like 'can add meetings'
end
context 'when second meeting is removed' do
before do
create(:zoom_meeting, :removed_from_issue, issue: issue)
end
it_behaves_like 'can remove meetings'
end
end
end
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment