diff options
Diffstat (limited to 'spec')
-rw-r--r-- | spec/factories/jira_import_states.rb | 28 | ||||
-rw-r--r-- | spec/lib/gitlab/import_export/all_models.yml | 1 | ||||
-rw-r--r-- | spec/models/jira_import_state_spec.rb | 149 | ||||
-rw-r--r-- | spec/models/project_spec.rb | 29 | ||||
-rw-r--r-- | spec/requests/api/internal/pages_spec.rb | 362 | ||||
-rw-r--r-- | spec/support/shared_examples/models/jira_import_state_shared_examples.rb | 40 |
6 files changed, 421 insertions, 188 deletions
diff --git a/spec/factories/jira_import_states.rb b/spec/factories/jira_import_states.rb new file mode 100644 index 00000000000..0fd6a58bbf1 --- /dev/null +++ b/spec/factories/jira_import_states.rb @@ -0,0 +1,28 @@ +# frozen_string_literal: true + +FactoryBot.define do + factory :jira_import_state do + project + user { project&.creator } + label + jira_project_name { generate(:name) } + jira_project_key { generate(:name) } + jira_project_xid { 1234 } + end + + trait :scheduled do + status { :scheduled } + end + + trait :started do + status { :started } + end + + trait :failed do + status { :failed } + end + + trait :finished do + status { :finished } + end +end diff --git a/spec/lib/gitlab/import_export/all_models.yml b/spec/lib/gitlab/import_export/all_models.yml index 1579a6ce2df..aaaf6458ee7 100644 --- a/spec/lib/gitlab/import_export/all_models.yml +++ b/spec/lib/gitlab/import_export/all_models.yml @@ -476,6 +476,7 @@ project: - requirements - export_jobs - daily_report_results +- jira_imports award_emoji: - awardable - user diff --git a/spec/models/jira_import_state_spec.rb b/spec/models/jira_import_state_spec.rb new file mode 100644 index 00000000000..f75a17f71b2 --- /dev/null +++ b/spec/models/jira_import_state_spec.rb @@ -0,0 +1,149 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe JiraImportState do + describe "associations" do + it { is_expected.to belong_to(:project) } + it { is_expected.to belong_to(:user) } + it { is_expected.to belong_to(:label) } + end + + describe 'modules' do + subject { described_class } + + it { is_expected.to include_module(AfterCommitQueue) } + end + + describe 'validations' do + it { is_expected.to validate_presence_of(:project) } + it { is_expected.to validate_presence_of(:jira_project_key) } + it { is_expected.to validate_presence_of(:jira_project_name) } + it { is_expected.to validate_presence_of(:jira_project_xid) } + + context 'when trying to run multiple imports' do + let(:project) { create(:project) } + + context 'when project has an initial jira_import' do + let!(:jira_import) { create(:jira_import_state, project: project)} + + it_behaves_like 'multiple running imports not allowed' + end + + context 'when project has a scheduled jira_import' do + let!(:jira_import) { create(:jira_import_state, :scheduled, project: project)} + + it_behaves_like 'multiple running imports not allowed' + end + + context 'when project has a started jira_import' do + let!(:jira_import) { create(:jira_import_state, :started, project: project)} + + it_behaves_like 'multiple running imports not allowed' + end + + context 'when project has a failed jira_import' do + let!(:jira_import) { create(:jira_import_state, :failed, project: project)} + + it 'returns valid' do + new_import = build(:jira_import_state, project: project) + + expect(new_import).to be_valid + expect(new_import.errors[:project]).to be_empty + end + end + + context 'when project has a finished jira_import' do + let!(:jira_import) { create(:jira_import_state, :finished, project: project)} + + it 'returns valid' do + new_import = build(:jira_import_state, project: project) + + expect(new_import).to be_valid + expect(new_import.errors[:project]).to be_empty + end + end + end + end + + describe '#in_progress?' do + context 'statuses that return in progress' do + it_behaves_like 'in progress', :scheduled + it_behaves_like 'in progress', :started + end + + context 'statuses that return not in progress' do + it_behaves_like 'not in progress', :initial + it_behaves_like 'not in progress', :failed + it_behaves_like 'not in progress', :finished + end + end + + describe 'states transition flow' do + let(:project) { create(:project) } + + context 'when jira import is in initial state' do + let!(:jira_import) { build(:jira_import_state, project: project)} + + it_behaves_like 'can transition', [:schedule, :do_fail] + it_behaves_like 'cannot transition', [:start, :finish] + end + + context 'when jira import is in scheduled state' do + let!(:jira_import) { build(:jira_import_state, :scheduled, project: project)} + + it_behaves_like 'can transition', [:start, :do_fail] + it_behaves_like 'cannot transition', [:finish] + end + + context 'when jira import is in started state' do + let!(:jira_import) { build(:jira_import_state, :started, project: project)} + + it_behaves_like 'can transition', [:finish, :do_fail] + it_behaves_like 'cannot transition', [:schedule] + end + + context 'when jira import is in failed state' do + let!(:jira_import) { build(:jira_import_state, :failed, project: project)} + + it_behaves_like 'cannot transition', [:schedule, :finish, :do_fail] + end + + context 'when jira import is in finished state' do + let!(:jira_import) { build(:jira_import_state, :finished, project: project)} + + it_behaves_like 'cannot transition', [:schedule, :do_fail, :start] + end + + context 'after transition to scheduled' do + let!(:jira_import) { build(:jira_import_state, project: project)} + + it 'triggers the import job' do + expect(Gitlab::JiraImport::Stage::StartImportWorker).to receive(:perform_async).and_return('some-job-id') + + jira_import.schedule + + expect(jira_import.jid).to eq('some-job-id') + end + end + + context 'after transition to finished' do + let!(:jira_import) { build(:jira_import_state, :started, jid: 'some-other-jid', project: project)} + + it 'triggers the import job' do + jira_import.finish + + expect(jira_import.jid).to be_nil + end + + it 'triggers the import job' do + jira_import.update!(status: :scheduled) + + jira_import.finish + + expect(jira_import.status).to eq('scheduled') + expect(jira_import.jid).to eq('some-other-jid') + end + end + end +end diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index 1b12550ebac..0904ebca670 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -110,6 +110,7 @@ describe Project do it { is_expected.to have_many(:source_pipelines) } it { is_expected.to have_many(:prometheus_alert_events) } it { is_expected.to have_many(:self_managed_prometheus_alert_events) } + it { is_expected.to have_many(:jira_imports) } it_behaves_like 'model with repository' do let_it_be(:container) { create(:project, :repository, path: 'somewhere') } @@ -5987,6 +5988,34 @@ describe Project do end end + describe '#latest_jira_import' do + let_it_be(:project) { create(:project) } + context 'when no jira imports' do + it 'returns nil' do + expect(project.latest_jira_import).to be nil + end + end + + context 'when single jira import' do + let!(:jira_import1) { create(:jira_import_state, project: project) } + + it 'returns the jira import' do + expect(project.latest_jira_import).to eq(jira_import1) + end + end + + context 'when multiple jira imports' do + let!(:jira_import1) { create(:jira_import_state, :finished, created_at: 1.day.ago, project: project) } + let!(:jira_import2) { create(:jira_import_state, :failed, created_at: 2.days.ago, project: project) } + let!(:jira_import3) { create(:jira_import_state, :started, created_at: 3.days.ago, project: project) } + + it 'returns latest jira import by created_at' do + expect(project.jira_imports.pluck(:id)).to eq([jira_import3.id, jira_import2.id, jira_import1.id]) + expect(project.latest_jira_import).to eq(jira_import1) + end + end + end + def finish_job(export_job) export_job.start export_job.finish diff --git a/spec/requests/api/internal/pages_spec.rb b/spec/requests/api/internal/pages_spec.rb index 44f7115f6a8..0c3c2fa22d6 100644 --- a/spec/requests/api/internal/pages_spec.rb +++ b/spec/requests/api/internal/pages_spec.rb @@ -14,241 +14,227 @@ describe API::Internal::Pages do get api("/internal/pages"), headers: headers, params: { host: host } end - context 'feature flag disabled' do - before do - stub_feature_flags(pages_internal_api: false) - end - - it 'responds with 404 Not Found' do + context 'not authenticated' do + it 'responds with 401 Unauthorized' do query_host('pages.gitlab.io') - expect(response).to have_gitlab_http_status(:not_found) + expect(response).to have_gitlab_http_status(:unauthorized) end end - context 'feature flag enabled' do - context 'not authenticated' do - it 'responds with 401 Unauthorized' do - query_host('pages.gitlab.io') + context 'authenticated' do + def query_host(host) + jwt_token = JWT.encode({ 'iss' => 'gitlab-pages' }, Gitlab::Pages.secret, 'HS256') + headers = { Gitlab::Pages::INTERNAL_API_REQUEST_HEADER => jwt_token } - expect(response).to have_gitlab_http_status(:unauthorized) - end + super(host, headers) end - context 'authenticated' do - def query_host(host) - jwt_token = JWT.encode({ 'iss' => 'gitlab-pages' }, Gitlab::Pages.secret, 'HS256') - headers = { Gitlab::Pages::INTERNAL_API_REQUEST_HEADER => jwt_token } + def deploy_pages(project) + project.mark_pages_as_deployed + end - super(host, headers) - end + context 'domain does not exist' do + it 'responds with 204 no content' do + query_host('pages.gitlab.io') - def deploy_pages(project) - project.mark_pages_as_deployed + expect(response).to have_gitlab_http_status(:no_content) + expect(response.body).to be_empty end + end + + context 'serverless domain' do + let(:namespace) { create(:namespace, name: 'gitlab-org') } + let(:project) { create(:project, namespace: namespace, name: 'gitlab-ce') } + let(:environment) { create(:environment, project: project) } + let(:pages_domain) { create(:pages_domain, domain: 'serverless.gitlab.io') } + let(:knative_without_ingress) { create(:clusters_applications_knative) } + let(:knative_with_ingress) { create(:clusters_applications_knative, external_ip: '10.0.0.1') } + + context 'without a knative ingress gateway IP' do + let!(:serverless_domain_cluster) do + create( + :serverless_domain_cluster, + uuid: 'abcdef12345678', + pages_domain: pages_domain, + knative: knative_without_ingress + ) + end + + let(:serverless_domain) do + create( + :serverless_domain, + serverless_domain_cluster: serverless_domain_cluster, + environment: environment + ) + end - context 'domain does not exist' do it 'responds with 204 no content' do - query_host('pages.gitlab.io') + query_host(serverless_domain.uri.host) expect(response).to have_gitlab_http_status(:no_content) expect(response.body).to be_empty end end - context 'serverless domain' do - let(:namespace) { create(:namespace, name: 'gitlab-org') } - let(:project) { create(:project, namespace: namespace, name: 'gitlab-ce') } - let(:environment) { create(:environment, project: project) } - let(:pages_domain) { create(:pages_domain, domain: 'serverless.gitlab.io') } - let(:knative_without_ingress) { create(:clusters_applications_knative) } - let(:knative_with_ingress) { create(:clusters_applications_knative, external_ip: '10.0.0.1') } - - context 'without a knative ingress gateway IP' do - let!(:serverless_domain_cluster) do - create( - :serverless_domain_cluster, - uuid: 'abcdef12345678', - pages_domain: pages_domain, - knative: knative_without_ingress - ) - end - - let(:serverless_domain) do - create( - :serverless_domain, - serverless_domain_cluster: serverless_domain_cluster, - environment: environment - ) - end - - it 'responds with 204 no content' do - query_host(serverless_domain.uri.host) - - expect(response).to have_gitlab_http_status(:no_content) - expect(response.body).to be_empty - end + context 'with a knative ingress gateway IP' do + let!(:serverless_domain_cluster) do + create( + :serverless_domain_cluster, + uuid: 'abcdef12345678', + pages_domain: pages_domain, + knative: knative_with_ingress + ) end - context 'with a knative ingress gateway IP' do - let!(:serverless_domain_cluster) do - create( - :serverless_domain_cluster, - uuid: 'abcdef12345678', - pages_domain: pages_domain, - knative: knative_with_ingress - ) - end - - let(:serverless_domain) do - create( - :serverless_domain, - serverless_domain_cluster: serverless_domain_cluster, - environment: environment - ) - end - - it 'responds with proxy configuration' do - query_host(serverless_domain.uri.host) - - expect(response).to have_gitlab_http_status(:ok) - expect(response).to match_response_schema('internal/serverless/virtual_domain') - - expect(json_response['certificate']).to eq(pages_domain.certificate) - expect(json_response['key']).to eq(pages_domain.key) - - expect(json_response['lookup_paths']).to eq( - [ - { - 'source' => { - 'type' => 'serverless', - 'service' => "test-function.#{project.name}-#{project.id}-#{environment.slug}.#{serverless_domain_cluster.knative.hostname}", - 'cluster' => { - 'hostname' => serverless_domain_cluster.knative.hostname, - 'address' => serverless_domain_cluster.knative.external_ip, - 'port' => 443, - 'cert' => serverless_domain_cluster.certificate, - 'key' => serverless_domain_cluster.key - } + let(:serverless_domain) do + create( + :serverless_domain, + serverless_domain_cluster: serverless_domain_cluster, + environment: environment + ) + end + + it 'responds with proxy configuration' do + query_host(serverless_domain.uri.host) + + expect(response).to have_gitlab_http_status(:ok) + expect(response).to match_response_schema('internal/serverless/virtual_domain') + + expect(json_response['certificate']).to eq(pages_domain.certificate) + expect(json_response['key']).to eq(pages_domain.key) + + expect(json_response['lookup_paths']).to eq( + [ + { + 'source' => { + 'type' => 'serverless', + 'service' => "test-function.#{project.name}-#{project.id}-#{environment.slug}.#{serverless_domain_cluster.knative.hostname}", + 'cluster' => { + 'hostname' => serverless_domain_cluster.knative.hostname, + 'address' => serverless_domain_cluster.knative.external_ip, + 'port' => 443, + 'cert' => serverless_domain_cluster.certificate, + 'key' => serverless_domain_cluster.key } } - ] - ) - end + } + ] + ) end end + end - context 'custom domain' do - let(:namespace) { create(:namespace, name: 'gitlab-org') } - let(:project) { create(:project, namespace: namespace, name: 'gitlab-ce') } - let!(:pages_domain) { create(:pages_domain, domain: 'pages.io', project: project) } + context 'custom domain' do + let(:namespace) { create(:namespace, name: 'gitlab-org') } + let(:project) { create(:project, namespace: namespace, name: 'gitlab-ce') } + let!(:pages_domain) { create(:pages_domain, domain: 'pages.io', project: project) } - context 'when there are no pages deployed for the related project' do - it 'responds with 204 No Content' do - query_host('pages.io') + context 'when there are no pages deployed for the related project' do + it 'responds with 204 No Content' do + query_host('pages.io') - expect(response).to have_gitlab_http_status(:no_content) - end + expect(response).to have_gitlab_http_status(:no_content) end + end - context 'when there are pages deployed for the related project' do - it 'domain lookup is case insensitive' do - deploy_pages(project) + context 'when there are pages deployed for the related project' do + it 'domain lookup is case insensitive' do + deploy_pages(project) - query_host('Pages.IO') + query_host('Pages.IO') - expect(response).to have_gitlab_http_status(:ok) - end + expect(response).to have_gitlab_http_status(:ok) + end - it 'responds with the correct domain configuration' do - deploy_pages(project) + it 'responds with the correct domain configuration' do + deploy_pages(project) - query_host('pages.io') + query_host('pages.io') - expect(response).to have_gitlab_http_status(:ok) - expect(response).to match_response_schema('internal/pages/virtual_domain') + expect(response).to have_gitlab_http_status(:ok) + expect(response).to match_response_schema('internal/pages/virtual_domain') - expect(json_response['certificate']).to eq(pages_domain.certificate) - expect(json_response['key']).to eq(pages_domain.key) + expect(json_response['certificate']).to eq(pages_domain.certificate) + expect(json_response['key']).to eq(pages_domain.key) - expect(json_response['lookup_paths']).to eq( - [ - { - 'project_id' => project.id, - 'access_control' => false, - 'https_only' => false, - 'prefix' => '/', - 'source' => { - 'type' => 'file', - 'path' => 'gitlab-org/gitlab-ce/public/' - } + expect(json_response['lookup_paths']).to eq( + [ + { + 'project_id' => project.id, + 'access_control' => false, + 'https_only' => false, + 'prefix' => '/', + 'source' => { + 'type' => 'file', + 'path' => 'gitlab-org/gitlab-ce/public/' } - ] - ) - end + } + ] + ) end end + end - context 'namespaced domain' do - let(:group) { create(:group, name: 'mygroup') } + context 'namespaced domain' do + let(:group) { create(:group, name: 'mygroup') } - before do - allow(Settings.pages).to receive(:host).and_return('gitlab-pages.io') - allow(Gitlab.config.pages).to receive(:url).and_return("http://gitlab-pages.io") - end + before do + allow(Settings.pages).to receive(:host).and_return('gitlab-pages.io') + allow(Gitlab.config.pages).to receive(:url).and_return("http://gitlab-pages.io") + end - context 'regular project' do - it 'responds with the correct domain configuration' do - project = create(:project, group: group, name: 'myproject') - deploy_pages(project) - - query_host('mygroup.gitlab-pages.io') - - expect(response).to have_gitlab_http_status(:ok) - expect(response).to match_response_schema('internal/pages/virtual_domain') - - expect(json_response['lookup_paths']).to eq( - [ - { - 'project_id' => project.id, - 'access_control' => false, - 'https_only' => false, - 'prefix' => '/myproject/', - 'source' => { - 'type' => 'file', - 'path' => 'mygroup/myproject/public/' - } + context 'regular project' do + it 'responds with the correct domain configuration' do + project = create(:project, group: group, name: 'myproject') + deploy_pages(project) + + query_host('mygroup.gitlab-pages.io') + + expect(response).to have_gitlab_http_status(:ok) + expect(response).to match_response_schema('internal/pages/virtual_domain') + + expect(json_response['lookup_paths']).to eq( + [ + { + 'project_id' => project.id, + 'access_control' => false, + 'https_only' => false, + 'prefix' => '/myproject/', + 'source' => { + 'type' => 'file', + 'path' => 'mygroup/myproject/public/' } - ] - ) - end + } + ] + ) end + end - context 'group root project' do - it 'responds with the correct domain configuration' do - project = create(:project, group: group, name: 'mygroup.gitlab-pages.io') - deploy_pages(project) - - query_host('mygroup.gitlab-pages.io') - - expect(response).to have_gitlab_http_status(:ok) - expect(response).to match_response_schema('internal/pages/virtual_domain') - - expect(json_response['lookup_paths']).to eq( - [ - { - 'project_id' => project.id, - 'access_control' => false, - 'https_only' => false, - 'prefix' => '/', - 'source' => { - 'type' => 'file', - 'path' => 'mygroup/mygroup.gitlab-pages.io/public/' - } + context 'group root project' do + it 'responds with the correct domain configuration' do + project = create(:project, group: group, name: 'mygroup.gitlab-pages.io') + deploy_pages(project) + + query_host('mygroup.gitlab-pages.io') + + expect(response).to have_gitlab_http_status(:ok) + expect(response).to match_response_schema('internal/pages/virtual_domain') + + expect(json_response['lookup_paths']).to eq( + [ + { + 'project_id' => project.id, + 'access_control' => false, + 'https_only' => false, + 'prefix' => '/', + 'source' => { + 'type' => 'file', + 'path' => 'mygroup/mygroup.gitlab-pages.io/public/' } - ] - ) - end + } + ] + ) end end end diff --git a/spec/support/shared_examples/models/jira_import_state_shared_examples.rb b/spec/support/shared_examples/models/jira_import_state_shared_examples.rb new file mode 100644 index 00000000000..f4643375c8e --- /dev/null +++ b/spec/support/shared_examples/models/jira_import_state_shared_examples.rb @@ -0,0 +1,40 @@ +# frozen_string_literal: true + +shared_examples 'multiple running imports not allowed' do + it 'returns not valid' do + new_import = build(:jira_import_state, project: project) + + expect(new_import).not_to be_valid + expect(new_import.errors[:project]).not_to be_nil + end +end + +shared_examples 'in progress' do |status| + it 'returns true' do + jira_import_state = build(:jira_import_state, status: status) + expect(jira_import_state).to be_in_progress + end +end + +shared_examples 'not in progress' do |status| + it 'returns false' do + jira_import_state = build(:jira_import_state, status: status) + expect(jira_import_state).not_to be_in_progress + end +end + +shared_examples 'can transition' do |states| + states.each do |state| + it 'returns true' do + expect(jira_import.send(state)).to be true + end + end +end + +shared_examples 'cannot transition' do |states| + states.each do |state| + it 'returns false' do + expect(jira_import.send(state)).to be false + end + end +end |