summaryrefslogtreecommitdiff
path: root/spec
diff options
context:
space:
mode:
Diffstat (limited to 'spec')
-rw-r--r--spec/controllers/projects/jobs_controller_spec.rb59
-rw-r--r--spec/fixtures/api/schemas/job/job_details.json3
-rw-r--r--spec/fixtures/api/schemas/job/runner.json17
-rw-r--r--spec/javascripts/ide/components/file_row_extra_spec.js159
-rw-r--r--spec/javascripts/ide/components/repo_file_spec.js145
-rw-r--r--spec/javascripts/vue_shared/components/file_row_spec.js74
-rw-r--r--spec/lib/gitlab/git/repository_spec.rb26
-rw-r--r--spec/lib/gitlab/gitaly_client/commit_service_spec.rb16
-rw-r--r--spec/models/concerns/from_union_spec.rb40
-rw-r--r--spec/rubocop/cop/gitlab/union_spec.rb25
-rw-r--r--spec/support/helpers/markdown_feature.rb8
11 files changed, 425 insertions, 147 deletions
diff --git a/spec/controllers/projects/jobs_controller_spec.rb b/spec/controllers/projects/jobs_controller_spec.rb
index 706f801a285..19c46e2f309 100644
--- a/spec/controllers/projects/jobs_controller_spec.rb
+++ b/spec/controllers/projects/jobs_controller_spec.rb
@@ -229,6 +229,65 @@ describe Projects::JobsController, :clean_gitlab_redis_shared_state do
expect(json_response['deployment_status']["environment"]).not_to be_nil
end
end
+
+ context 'when user can edit runner' do
+ context 'that belongs to the project' do
+ let(:runner) { create(:ci_runner, :project, projects: [project]) }
+ let(:job) { create(:ci_build, :success, pipeline: pipeline, runner: runner) }
+
+ before do
+ project.add_maintainer(user)
+ sign_in(user)
+
+ get_show(id: job.id, format: :json)
+ end
+
+ it 'user can edit runner' do
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response).to match_response_schema('job/job_details')
+ expect(json_response['runner']).to have_key('edit_path')
+ end
+ end
+
+ context 'that belongs to group' do
+ let(:group) { create(:group) }
+ let(:runner) { create(:ci_runner, :group, groups: [group]) }
+ let(:job) { create(:ci_build, :success, pipeline: pipeline, runner: runner) }
+ let(:user) { create(:user, :admin) }
+
+ before do
+ project.add_maintainer(user)
+ sign_in(user)
+
+ get_show(id: job.id, format: :json)
+ end
+
+ it 'user can not edit runner' do
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response).to match_response_schema('job/job_details')
+ expect(json_response['runner']).not_to have_key('edit_path')
+ end
+ end
+
+ context 'that belongs to instance' do
+ let(:runner) { create(:ci_runner, :instance) }
+ let(:job) { create(:ci_build, :success, pipeline: pipeline, runner: runner) }
+ let(:user) { create(:user, :admin) }
+
+ before do
+ project.add_maintainer(user)
+ sign_in(user)
+
+ get_show(id: job.id, format: :json)
+ end
+
+ it 'user can not edit runner' do
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response).to match_response_schema('job/job_details')
+ expect(json_response['runner']).not_to have_key('edit_path')
+ end
+ end
+ end
end
context 'when requesting JSON job is triggered' do
diff --git a/spec/fixtures/api/schemas/job/job_details.json b/spec/fixtures/api/schemas/job/job_details.json
index ab0ade55bbe..78c0fdf83b9 100644
--- a/spec/fixtures/api/schemas/job/job_details.json
+++ b/spec/fixtures/api/schemas/job/job_details.json
@@ -7,6 +7,7 @@
"artifact": { "$ref": "artifact.json" },
"terminal_path": { "type": "string" },
"trigger": { "$ref": "trigger.json" },
- "deployment_status": { "$ref": "deployment_status.json" }
+ "deployment_status": { "$ref": "deployment_status.json" },
+ "runner": { "$ref": "runner.json" }
}
}
diff --git a/spec/fixtures/api/schemas/job/runner.json b/spec/fixtures/api/schemas/job/runner.json
new file mode 100644
index 00000000000..acfeeeeb808
--- /dev/null
+++ b/spec/fixtures/api/schemas/job/runner.json
@@ -0,0 +1,17 @@
+{
+ "oneOf": [
+ { "type": "null" },
+ {
+ "type": "object",
+ "required": [
+ "id",
+ "description"
+ ],
+ "properties": {
+ "id": { "type": "integer" },
+ "description": { "type": "string" },
+ "edit_path": { "type": "string" }
+ }
+ }
+ ]
+}
diff --git a/spec/javascripts/ide/components/file_row_extra_spec.js b/spec/javascripts/ide/components/file_row_extra_spec.js
new file mode 100644
index 00000000000..60dabe28045
--- /dev/null
+++ b/spec/javascripts/ide/components/file_row_extra_spec.js
@@ -0,0 +1,159 @@
+import Vue from 'vue';
+import { createStore } from '~/ide/stores';
+import { createComponentWithStore } from 'spec/helpers/vue_mount_component_helper';
+import FileRowExtra from '~/ide/components/file_row_extra.vue';
+import { file, resetStore } from '../helpers';
+
+describe('IDE extra file row component', () => {
+ let Component;
+ let vm;
+ let unstagedFilesCount = 0;
+ let stagedFilesCount = 0;
+ let changesCount = 0;
+
+ beforeAll(() => {
+ Component = Vue.extend(FileRowExtra);
+ });
+
+ beforeEach(() => {
+ vm = createComponentWithStore(Component, createStore(), {
+ file: {
+ ...file('test'),
+ },
+ mouseOver: false,
+ });
+
+ spyOnProperty(vm, 'getUnstagedFilesCountForPath').and.returnValue(() => unstagedFilesCount);
+ spyOnProperty(vm, 'getStagedFilesCountForPath').and.returnValue(() => stagedFilesCount);
+ spyOnProperty(vm, 'getChangesInFolder').and.returnValue(() => changesCount);
+
+ vm.$mount();
+ });
+
+ afterEach(() => {
+ vm.$destroy();
+ resetStore(vm.$store);
+
+ stagedFilesCount = 0;
+ unstagedFilesCount = 0;
+ changesCount = 0;
+ });
+
+ describe('folderChangesTooltip', () => {
+ it('returns undefined when changes count is 0', () => {
+ expect(vm.folderChangesTooltip).toBe(undefined);
+ });
+
+ it('returns unstaged changes text', () => {
+ changesCount = 1;
+ unstagedFilesCount = 1;
+
+ expect(vm.folderChangesTooltip).toBe('1 unstaged change');
+ });
+
+ it('returns staged changes text', () => {
+ changesCount = 1;
+ stagedFilesCount = 1;
+
+ expect(vm.folderChangesTooltip).toBe('1 staged change');
+ });
+
+ it('returns staged and unstaged changes text', () => {
+ changesCount = 1;
+ stagedFilesCount = 1;
+ unstagedFilesCount = 1;
+
+ expect(vm.folderChangesTooltip).toBe('1 unstaged and 1 staged changes');
+ });
+ });
+
+ describe('show tree changes count', () => {
+ it('does not show for blobs', () => {
+ vm.file.type = 'blob';
+
+ expect(vm.$el.querySelector('.ide-tree-changes')).toBe(null);
+ });
+
+ it('does not show when changes count is 0', () => {
+ vm.file.type = 'tree';
+
+ expect(vm.$el.querySelector('.ide-tree-changes')).toBe(null);
+ });
+
+ it('does not show when tree is open', done => {
+ vm.file.type = 'tree';
+ vm.file.opened = true;
+ changesCount = 1;
+
+ vm.$nextTick(() => {
+ expect(vm.$el.querySelector('.ide-tree-changes')).toBe(null);
+
+ done();
+ });
+ });
+
+ it('shows for trees with changes', done => {
+ vm.file.type = 'tree';
+ vm.file.opened = false;
+ changesCount = 1;
+
+ vm.$nextTick(() => {
+ expect(vm.$el.querySelector('.ide-tree-changes')).not.toBe(null);
+
+ done();
+ });
+ });
+ });
+
+ describe('changes file icon', () => {
+ it('hides when file is not changed', () => {
+ expect(vm.$el.querySelector('.ide-file-changed-icon')).toBe(null);
+ });
+
+ it('shows when file is changed', done => {
+ vm.file.changed = true;
+
+ vm.$nextTick(() => {
+ expect(vm.$el.querySelector('.ide-file-changed-icon')).not.toBe(null);
+
+ done();
+ });
+ });
+
+ it('shows when file is staged', done => {
+ vm.file.staged = true;
+
+ vm.$nextTick(() => {
+ expect(vm.$el.querySelector('.ide-file-changed-icon')).not.toBe(null);
+
+ done();
+ });
+ });
+
+ it('shows when file is a tempFile', done => {
+ vm.file.tempFile = true;
+
+ vm.$nextTick(() => {
+ expect(vm.$el.querySelector('.ide-file-changed-icon')).not.toBe(null);
+
+ done();
+ });
+ });
+ });
+
+ describe('merge request icon', () => {
+ it('hides when not a merge request change', () => {
+ expect(vm.$el.querySelector('.ic-git-merge')).toBe(null);
+ });
+
+ it('shows when a merge request change', done => {
+ vm.file.mrChange = true;
+
+ vm.$nextTick(() => {
+ expect(vm.$el.querySelector('.ic-git-merge')).not.toBe(null);
+
+ done();
+ });
+ });
+ });
+});
diff --git a/spec/javascripts/ide/components/repo_file_spec.js b/spec/javascripts/ide/components/repo_file_spec.js
deleted file mode 100644
index fc639a672e2..00000000000
--- a/spec/javascripts/ide/components/repo_file_spec.js
+++ /dev/null
@@ -1,145 +0,0 @@
-import Vue from 'vue';
-import store from '~/ide/stores';
-import repoFile from '~/ide/components/repo_file.vue';
-import router from '~/ide/ide_router';
-import { createComponentWithStore } from '../../helpers/vue_mount_component_helper';
-import { file } from '../helpers';
-
-describe('RepoFile', () => {
- let vm;
-
- function createComponent(propsData) {
- const RepoFile = Vue.extend(repoFile);
-
- vm = createComponentWithStore(RepoFile, store, propsData);
-
- vm.$mount();
- }
-
- afterEach(() => {
- vm.$destroy();
- });
-
- it('renders link, icon and name', () => {
- createComponent({
- file: file('t4'),
- level: 0,
- });
-
- const name = vm.$el.querySelector('.ide-file-name');
-
- expect(name.href).toMatch('');
- expect(name.textContent.trim()).toEqual(vm.file.name);
- });
-
- it('fires clickFile when the link is clicked', done => {
- spyOn(router, 'push');
- createComponent({
- file: file('t3'),
- level: 0,
- });
-
- vm.$el.querySelector('.file-name').click();
-
- setTimeout(() => {
- expect(router.push).toHaveBeenCalledWith(`/project${vm.file.url}`);
-
- done();
- });
- });
-
- describe('folder', () => {
- it('renders changes count inside folder', () => {
- const f = {
- ...file('folder'),
- path: 'testing',
- type: 'tree',
- branchId: 'master',
- projectId: 'project',
- };
-
- store.state.changedFiles.push({
- ...file('fileName'),
- path: 'testing/fileName',
- });
-
- createComponent({
- file: f,
- level: 0,
- });
-
- const treeChangesEl = vm.$el.querySelector('.ide-tree-changes');
-
- expect(treeChangesEl).not.toBeNull();
- expect(treeChangesEl.textContent).toContain('1');
- });
-
- it('renders action dropdown', done => {
- createComponent({
- file: {
- ...file('t4'),
- type: 'tree',
- branchId: 'master',
- projectId: 'project',
- },
- level: 0,
- });
-
- setTimeout(() => {
- expect(vm.$el.querySelector('.ide-new-btn')).not.toBeNull();
-
- done();
- });
- });
- });
-
- describe('locked file', () => {
- let f;
-
- beforeEach(() => {
- f = file('locked file');
- f.file_lock = {
- user: {
- name: 'testuser',
- updated_at: new Date(),
- },
- };
-
- createComponent({
- file: f,
- level: 0,
- });
- });
-
- it('renders lock icon', () => {
- expect(vm.$el.querySelector('.file-status-icon')).not.toBeNull();
- });
-
- it('renders a tooltip', () => {
- expect(
- vm.$el.querySelector('.ide-file-name span:nth-child(2)').dataset.originalTitle,
- ).toContain('Locked by testuser');
- });
- });
-
- it('calls scrollIntoView if made active', done => {
- createComponent({
- file: {
- ...file(),
- type: 'blob',
- active: false,
- },
- level: 0,
- });
-
- spyOn(vm, 'scrollIntoView');
-
- vm.file.active = true;
-
- vm.$nextTick(() => {
- expect(vm.scrollIntoView).toHaveBeenCalled();
-
- done();
- });
- });
-});
diff --git a/spec/javascripts/vue_shared/components/file_row_spec.js b/spec/javascripts/vue_shared/components/file_row_spec.js
new file mode 100644
index 00000000000..9914c0b70f3
--- /dev/null
+++ b/spec/javascripts/vue_shared/components/file_row_spec.js
@@ -0,0 +1,74 @@
+import Vue from 'vue';
+import FileRow from '~/vue_shared/components/file_row.vue';
+import { file } from 'spec/ide/helpers';
+import mountComponent from '../../helpers/vue_mount_component_helper';
+
+describe('RepoFile', () => {
+ let vm;
+
+ function createComponent(propsData) {
+ const FileRowComponent = Vue.extend(FileRow);
+
+ vm = mountComponent(FileRowComponent, propsData);
+ }
+
+ afterEach(() => {
+ vm.$destroy();
+ });
+
+ it('renders name', () => {
+ createComponent({
+ file: file('t4'),
+ level: 0,
+ });
+
+ const name = vm.$el.querySelector('.file-row-name');
+
+ expect(name.textContent.trim()).toEqual(vm.file.name);
+ });
+
+ it('emits toggleTreeOpen on click', () => {
+ createComponent({
+ file: {
+ ...file('t3'),
+ type: 'tree',
+ },
+ level: 0,
+ });
+ spyOn(vm, '$emit').and.stub();
+
+ vm.$el.querySelector('.file-row').click();
+
+ expect(vm.$emit).toHaveBeenCalledWith('toggleTreeOpen', vm.file.path);
+ });
+
+ it('calls scrollIntoView if made active', done => {
+ createComponent({
+ file: {
+ ...file(),
+ type: 'blob',
+ active: false,
+ },
+ level: 0,
+ });
+
+ spyOn(vm, 'scrollIntoView').and.stub();
+
+ vm.file.active = true;
+
+ vm.$nextTick(() => {
+ expect(vm.scrollIntoView).toHaveBeenCalled();
+
+ done();
+ });
+ });
+
+ it('indents row based on level', () => {
+ createComponent({
+ file: file('t4'),
+ level: 2,
+ });
+
+ expect(vm.$el.querySelector('.file-row-name').style.marginLeft).toBe('32px');
+ });
+});
diff --git a/spec/lib/gitlab/git/repository_spec.rb b/spec/lib/gitlab/git/repository_spec.rb
index ce4caf0c27d..58c260ee1f0 100644
--- a/spec/lib/gitlab/git/repository_spec.rb
+++ b/spec/lib/gitlab/git/repository_spec.rb
@@ -1112,6 +1112,32 @@ describe Gitlab::Git::Repository, :seed_helper do
end
end
+ describe '#diff_stats' do
+ let(:left_commit_id) { 'feature' }
+ let(:right_commit_id) { 'master' }
+
+ it 'returns a DiffStatsCollection' do
+ collection = repository.diff_stats(left_commit_id, right_commit_id)
+
+ expect(collection).to be_a(Gitlab::Git::DiffStatsCollection)
+ expect(collection).to be_a(Enumerable)
+ end
+
+ it 'yields Gitaly::DiffStats objects' do
+ collection = repository.diff_stats(left_commit_id, right_commit_id)
+
+ expect(collection.to_a).to all(be_a(Gitaly::DiffStats))
+ end
+
+ it 'returns no Gitaly::DiffStats when SHAs are invalid' do
+ collection = repository.diff_stats('foo', 'bar')
+
+ expect(collection).to be_a(Gitlab::Git::DiffStatsCollection)
+ expect(collection).to be_a(Enumerable)
+ expect(collection.to_a).to be_empty
+ end
+ end
+
describe "#ls_files" do
let(:master_file_paths) { repository.ls_files("master") }
let(:utf8_file_paths) { repository.ls_files("ls-files-utf8") }
diff --git a/spec/lib/gitlab/gitaly_client/commit_service_spec.rb b/spec/lib/gitlab/gitaly_client/commit_service_spec.rb
index bcdf12a00a0..d7bd757149d 100644
--- a/spec/lib/gitlab/gitaly_client/commit_service_spec.rb
+++ b/spec/lib/gitlab/gitaly_client/commit_service_spec.rb
@@ -118,6 +118,22 @@ describe Gitlab::GitalyClient::CommitService do
end
end
+ describe '#diff_stats' do
+ let(:left_commit_id) { 'master' }
+ let(:right_commit_id) { 'cfe32cf61b73a0d5e9f13e774abde7ff789b1660' }
+
+ it 'sends an RPC request' do
+ request = Gitaly::DiffStatsRequest.new(repository: repository_message,
+ left_commit_id: left_commit_id,
+ right_commit_id: right_commit_id)
+
+ expect_any_instance_of(Gitaly::DiffService::Stub).to receive(:diff_stats)
+ .with(request, kind_of(Hash)).and_return([])
+
+ described_class.new(repository).diff_stats(left_commit_id, right_commit_id)
+ end
+ end
+
describe '#tree_entries' do
let(:path) { '/' }
diff --git a/spec/models/concerns/from_union_spec.rb b/spec/models/concerns/from_union_spec.rb
new file mode 100644
index 00000000000..ee427a667c6
--- /dev/null
+++ b/spec/models/concerns/from_union_spec.rb
@@ -0,0 +1,40 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe FromUnion do
+ describe '.from_union' do
+ let(:model) do
+ Class.new(ActiveRecord::Base) do
+ self.table_name = 'users'
+
+ include FromUnion
+ end
+ end
+
+ it 'selects from the results of the UNION' do
+ query = model.from_union([model.where(id: 1), model.where(id: 2)])
+
+ expect(query.to_sql).to match(/FROM \(SELECT.+UNION.+SELECT.+\) users/m)
+ end
+
+ it 'supports the use of a custom alias for the sub query' do
+ query = model.from_union(
+ [model.where(id: 1), model.where(id: 2)],
+ alias_as: 'kittens'
+ )
+
+ expect(query.to_sql).to match(/FROM \(SELECT.+UNION.+SELECT.+\) kittens/m)
+ end
+
+ it 'supports keeping duplicate rows' do
+ query = model.from_union(
+ [model.where(id: 1), model.where(id: 2)],
+ remove_duplicates: false
+ )
+
+ expect(query.to_sql)
+ .to match(/FROM \(SELECT.+UNION ALL.+SELECT.+\) users/m)
+ end
+ end
+end
diff --git a/spec/rubocop/cop/gitlab/union_spec.rb b/spec/rubocop/cop/gitlab/union_spec.rb
new file mode 100644
index 00000000000..5b06f30b25f
--- /dev/null
+++ b/spec/rubocop/cop/gitlab/union_spec.rb
@@ -0,0 +1,25 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+require 'rubocop'
+require 'rubocop/rspec/support'
+require_relative '../../../../rubocop/cop/gitlab/union'
+
+describe RuboCop::Cop::Gitlab::Union do
+ include CopHelper
+
+ subject(:cop) { described_class.new }
+
+ it 'flags the use of Gitlab::SQL::Union.new' do
+ expect_offense(<<~SOURCE)
+ Gitlab::SQL::Union.new([foo])
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Use the `FromUnion` concern, instead of using `Gitlab::SQL::Union` directly
+ SOURCE
+ end
+
+ it 'does not flag the use of Gitlab::SQL::Union in a spec' do
+ allow(cop).to receive(:in_spec?).and_return(true)
+
+ expect_no_offenses('Gitlab::SQL::Union.new([foo])')
+ end
+end
diff --git a/spec/support/helpers/markdown_feature.rb b/spec/support/helpers/markdown_feature.rb
index 346f5b1cc4d..96401379cf0 100644
--- a/spec/support/helpers/markdown_feature.rb
+++ b/spec/support/helpers/markdown_feature.rb
@@ -10,6 +10,12 @@
class MarkdownFeature
include FactoryBot::Syntax::Methods
+ attr_reader :fixture_path
+
+ def initialize(fixture_path = Rails.root.join('spec/fixtures/markdown.md.erb'))
+ @fixture_path = fixture_path
+ end
+
def user
@user ||= create(:user)
end
@@ -122,7 +128,7 @@ class MarkdownFeature
end
def raw_markdown
- markdown = File.read(Rails.root.join('spec/fixtures/markdown.md.erb'))
+ markdown = File.read(fixture_path)
ERB.new(markdown).result(binding)
end
end