diff options
Diffstat (limited to 'spec')
100 files changed, 1795 insertions, 290 deletions
diff --git a/spec/controllers/projects/releases_controller_spec.rb b/spec/controllers/projects/releases_controller_spec.rb new file mode 100644 index 00000000000..9fd5c3b85f6 --- /dev/null +++ b/spec/controllers/projects/releases_controller_spec.rb @@ -0,0 +1,55 @@ +require 'spec_helper' + +describe Projects::ReleasesController do + let!(:project) { create(:project) } + let!(:user) { create(:user) } + let!(:release) { create(:release, project: project) } + let!(:tag) { release.tag } + + before do + project.team << [user, :developer] + sign_in(user) + end + + describe 'GET #edit' do + it 'initializes a new release' do + tag_id = release.tag + project.releases.destroy_all + + get :edit, namespace_id: project.namespace.path, project_id: project.path, tag_id: tag_id + + release = assigns(:release) + expect(release).not_to be_nil + expect(release).not_to be_persisted + end + + it 'retrieves an existing release' do + get :edit, namespace_id: project.namespace.path, project_id: project.path, tag_id: release.tag + + release = assigns(:release) + expect(release).not_to be_nil + expect(release).to be_persisted + end + end + + describe 'PUT #update' do + it 'updates release note description' do + update_release('description updated') + + release = project.releases.find_by_tag(tag) + expect(release.description).to eq("description updated") + end + + it 'deletes release note when description is null' do + expect { update_release('') }.to change(project.releases, :count).by(-1) + end + end + + def update_release(description) + put :update, + namespace_id: project.namespace.to_param, + project_id: project.to_param, + tag_id: release.tag, + release: { description: description } + end +end diff --git a/spec/db/production/settings.rb b/spec/db/production/settings.rb new file mode 100644 index 00000000000..a7c5283df94 --- /dev/null +++ b/spec/db/production/settings.rb @@ -0,0 +1,16 @@ +require 'spec_helper' +require 'rainbow/ext/string' + +describe 'seed production settings', lib: true do + context 'GITLAB_SHARED_RUNNERS_REGISTRATION_TOKEN is set in the environment' do + before do + allow(ENV).to receive(:[]).and_call_original + allow(ENV).to receive(:[]).with('GITLAB_SHARED_RUNNERS_REGISTRATION_TOKEN').and_return('013456789') + end + + it 'writes the token to the database' do + load(File.join(__dir__, '../../../db/fixtures/production/010_settings.rb')) + expect(ApplicationSetting.current.runners_registration_token).to eq('013456789') + end + end +end diff --git a/spec/factories/ci/builds.rb b/spec/factories/ci/builds.rb index c443af09075..62466c06194 100644 --- a/spec/factories/ci/builds.rb +++ b/spec/factories/ci/builds.rb @@ -12,12 +12,14 @@ FactoryGirl.define do started_at 'Di 29. Okt 09:51:28 CET 2013' finished_at 'Di 29. Okt 09:53:28 CET 2013' commands 'ls -a' + options do { image: "ruby:2.1", services: ["postgres"] } end + yaml_variables do [ { key: :DB_NAME, value: 'postgres', public: true } @@ -60,15 +62,20 @@ FactoryGirl.define do end trait :teardown_environment do - options do - { environment: { action: 'stop' } } - end + environment 'staging' + options environment: { name: 'staging', + action: 'stop' } end trait :allowed_to_fail do allow_failure true end + trait :playable do + skipped + manual + end + after(:build) do |build, evaluator| build.project = build.pipeline.project end diff --git a/spec/factories/groups.rb b/spec/factories/groups.rb index ebd3595ea64..ece6beb9fa9 100644 --- a/spec/factories/groups.rb +++ b/spec/factories/groups.rb @@ -19,5 +19,9 @@ FactoryGirl.define do trait :access_requestable do request_access_enabled true end + + trait :nested do + parent factory: :group + end end end diff --git a/spec/factories/projects.rb b/spec/factories/projects.rb index 1166498ddff..0d072d6a690 100644 --- a/spec/factories/projects.rb +++ b/spec/factories/projects.rb @@ -133,4 +133,17 @@ FactoryGirl.define do ) end end + + factory :kubernetes_project, parent: :empty_project do + after :create do |project| + project.create_kubernetes_service( + active: true, + properties: { + namespace: project.path, + api_url: 'https://kubernetes.example.com/api', + token: 'a' * 40, + } + ) + end + end end diff --git a/spec/features/admin/admin_groups_spec.rb b/spec/features/admin/admin_groups_spec.rb index f6d625fa7f6..0aa01fc499a 100644 --- a/spec/features/admin/admin_groups_spec.rb +++ b/spec/features/admin/admin_groups_spec.rb @@ -21,7 +21,7 @@ feature 'Admin Groups', feature: true do scenario 'shows the visibility level radio populated with the group visibility_level value' do group = create(:group, :private) - visit edit_admin_group_path(group) + visit admin_group_edit_path(group) expect_selected_visibility(group.visibility_level) end diff --git a/spec/features/admin/admin_projects_spec.rb b/spec/features/admin/admin_projects_spec.rb index 30ded9202a4..a36bfd574cb 100644 --- a/spec/features/admin/admin_projects_spec.rb +++ b/spec/features/admin/admin_projects_spec.rb @@ -8,11 +8,11 @@ describe "Admin::Projects", feature: true do describe "GET /admin/projects" do before do - visit admin_namespaces_projects_path + visit admin_projects_path end it "is ok" do - expect(current_path).to eq(admin_namespaces_projects_path) + expect(current_path).to eq(admin_projects_path) end it "has projects list" do @@ -22,7 +22,7 @@ describe "Admin::Projects", feature: true do describe "GET /admin/projects/:id" do before do - visit admin_namespaces_projects_path + visit admin_projects_path click_link "#{@project.name}" end diff --git a/spec/features/admin/admin_settings_spec.rb b/spec/features/admin/admin_settings_spec.rb new file mode 100644 index 00000000000..8cd66f189be --- /dev/null +++ b/spec/features/admin/admin_settings_spec.rb @@ -0,0 +1,53 @@ +require 'spec_helper' + +feature 'Admin updates settings', feature: true do + before(:each) do + login_as :admin + visit admin_application_settings_path + end + + scenario 'Change application settings' do + uncheck 'Gravatar enabled' + fill_in 'Home page URL', with: 'https://about.gitlab.com/' + fill_in 'Help page text', with: 'Example text' + click_button 'Save' + + expect(current_application_settings.gravatar_enabled).to be_falsey + expect(current_application_settings.home_page_url).to eq "https://about.gitlab.com/" + expect(page).to have_content "Application settings saved successfully" + end + + scenario 'Change Slack Service template settings' do + click_link 'Service Templates' + click_link 'Slack' + fill_in 'Webhook', with: 'http://localhost' + fill_in 'Username', with: 'test_user' + fill_in 'service_push_channel', with: '#test_channel' + page.check('Notify only broken builds') + + check_all_events + click_on 'Save' + + expect(page).to have_content 'Application settings saved successfully' + + click_link 'Slack' + + page.all('input[type=checkbox]').each do |checkbox| + expect(checkbox).to be_checked + end + expect(find_field('Webhook').value).to eq 'http://localhost' + expect(find_field('Username').value).to eq 'test_user' + expect(find('#service_push_channel').value).to eq '#test_channel' + end + + def check_all_events + page.check('Active') + page.check('Push') + page.check('Tag push') + page.check('Note') + page.check('Issue') + page.check('Merge request') + page.check('Build') + page.check('Pipeline') + end +end diff --git a/spec/features/dashboard/datetime_on_tooltips_spec.rb b/spec/features/dashboard/datetime_on_tooltips_spec.rb index 365cb445df1..44dfc2dff45 100644 --- a/spec/features/dashboard/datetime_on_tooltips_spec.rb +++ b/spec/features/dashboard/datetime_on_tooltips_spec.rb @@ -36,7 +36,7 @@ feature 'Tooltips on .timeago dates', feature: true, js: true do visit user_snippets_path(user) wait_for_ajax() - page.find('.js-timeago').hover + page.find('.js-timeago.snippet-created-ago').hover end it 'has the datetime formated correctly' do diff --git a/spec/features/issues/gfm_autocomplete_spec.rb b/spec/features/issues/gfm_autocomplete_spec.rb index c421da97d76..cd0512a37e6 100644 --- a/spec/features/issues/gfm_autocomplete_spec.rb +++ b/spec/features/issues/gfm_autocomplete_spec.rb @@ -2,8 +2,9 @@ require 'rails_helper' feature 'GFM autocomplete', feature: true, js: true do include WaitForAjax - let(:user) { create(:user) } + let(:user) { create(:user, username: 'someone.special') } let(:project) { create(:project) } + let(:label) { create(:label, project: project, title: 'special+') } let(:issue) { create(:issue, project: project) } before do @@ -23,21 +24,69 @@ feature 'GFM autocomplete', feature: true, js: true do expect(page).to have_selector('.atwho-container') end - it 'opens autocomplete menu when field is prefixed with non-text character' do + it 'doesnt open autocomplete menu character is prefixed with text' do page.within '.timeline-content-form' do - find('#note_note').native.send_keys('') + find('#note_note').native.send_keys('testing') find('#note_note').native.send_keys('@') end - expect(page).to have_selector('.atwho-container') + expect(page).not_to have_selector('.atwho-view') end - it 'doesnt open autocomplete menu character is prefixed with text' do - page.within '.timeline-content-form' do - find('#note_note').native.send_keys('testing') - find('#note_note').native.send_keys('@') + context 'if a selected value has special characters' do + it 'wraps the result in double quotes' do + note = find('#note_note') + page.within '.timeline-content-form' do + note.native.send_keys('') + note.native.send_keys("~#{label.title[0]}") + sleep 1 + note.click + end + + label_item = find('.atwho-view li', text: label.title) + + expect_to_wrap(true, label_item, note, label.title) end - expect(page).not_to have_selector('.atwho-view') + it 'doesn\'t wrap for assignee values' do + note = find('#note_note') + page.within '.timeline-content-form' do + note.native.send_keys('') + note.native.send_keys("@#{user.username[0]}") + sleep 1 + note.click + end + + user_item = find('.atwho-view li', text: user.username) + + expect_to_wrap(false, user_item, note, user.username) + end + + it 'doesn\'t wrap for emoji values' do + note = find('#note_note') + page.within '.timeline-content-form' do + note.native.send_keys('') + note.native.send_keys(":cartwheel") + sleep 1 + note.click + end + + emoji_item = find('.atwho-view li', text: 'cartwheel_tone1') + + expect_to_wrap(false, emoji_item, note, 'cartwheel_tone1') + end + + def expect_to_wrap(should_wrap, item, note, value) + expect(item).to have_content(value) + expect(item).not_to have_content("\"#{value}\"") + + item.click + + if should_wrap + expect(note.value).to include("\"#{value}\"") + else + expect(note.value).not_to include("\"#{value}\"") + end + end end end diff --git a/spec/features/projects/builds_spec.rb b/spec/features/projects/builds_spec.rb index a0ccc472d11..8c4d4320dc5 100644 --- a/spec/features/projects/builds_spec.rb +++ b/spec/features/projects/builds_spec.rb @@ -227,6 +227,43 @@ feature 'Builds', :feature do expect(page).to have_selector('.js-build-value', text: 'TRIGGER_VALUE_1') end end + + context 'when build starts environment' do + let(:environment) { create(:environment, project: project) } + let(:pipeline) { create(:ci_pipeline, project: project) } + + context 'build is successfull and has deployment' do + let(:deployment) { create(:deployment) } + let(:build) { create(:ci_build, :success, environment: environment.name, deployments: [deployment], pipeline: pipeline) } + + it 'shows a link for the build' do + visit namespace_project_build_path(project.namespace, project, build) + + expect(page).to have_link environment.name + end + end + + context 'build is complete and not successfull' do + let(:build) { create(:ci_build, :failed, environment: environment.name, pipeline: pipeline) } + + it 'shows a link for the build' do + visit namespace_project_build_path(project.namespace, project, build) + + expect(page).to have_link environment.name + end + end + + context 'build creates a new deployment' do + let!(:deployment) { create(:deployment, environment: environment, sha: project.commit.id) } + let(:build) { create(:ci_build, :success, environment: environment.name, pipeline: pipeline) } + + it 'shows a link to lastest deployment' do + visit namespace_project_build_path(project.namespace, project, build) + + expect(page).to have_link('latest deployment') + end + end + end end describe "POST /:project/builds/:id/cancel" do diff --git a/spec/features/projects/files/creating_a_file_spec.rb b/spec/features/projects/files/creating_a_file_spec.rb new file mode 100644 index 00000000000..ae448706130 --- /dev/null +++ b/spec/features/projects/files/creating_a_file_spec.rb @@ -0,0 +1,44 @@ +require 'spec_helper' + +feature 'User wants to create a file', feature: true do + include WaitForAjax + + let(:project) { create(:project) } + let(:user) { create(:user) } + + background do + project.team << [user, :master] + login_as user + visit namespace_project_new_blob_path(project.namespace, project, project.default_branch) + end + + def submit_new_file(options) + file_name = find('#file_name') + file_name.set options[:file_name] || 'README.md' + + file_content = find('#file-content') + file_content.set options[:file_content] || 'Some content' + + click_button 'Commit Changes' + end + + scenario 'file name contains Chinese characters' do + submit_new_file(file_name: '测试.md') + expect(page).to have_content 'The file has been successfully created.' + end + + scenario 'directory name contains Chinese characters' do + submit_new_file(file_name: 'ä¸æ–‡/测试.md') + expect(page).to have_content 'The file has been successfully created.' + end + + scenario 'file name contains invalid characters' do + submit_new_file(file_name: '\\') + expect(page).to have_content 'Your changes could not be committed, because the file name can contain only' + end + + scenario 'file name contains directory traversal' do + submit_new_file(file_name: '../README.md') + expect(page).to have_content 'Your changes could not be committed, because the file name cannot include directory traversal.' + end +end diff --git a/spec/features/security/admin_access_spec.rb b/spec/features/security/admin_access_spec.rb index fe8cd7b7602..e180ca53eb5 100644 --- a/spec/features/security/admin_access_spec.rb +++ b/spec/features/security/admin_access_spec.rb @@ -4,7 +4,7 @@ describe "Admin::Projects", feature: true do include AccessMatchers describe "GET /admin/projects" do - subject { admin_namespaces_projects_path } + subject { admin_projects_path } it { is_expected.to be_allowed_for :admin } it { is_expected.to be_denied_for :user } diff --git a/spec/finders/snippets_finder_spec.rb b/spec/finders/snippets_finder_spec.rb index 4427443208a..975e99c5807 100644 --- a/spec/finders/snippets_finder_spec.rb +++ b/spec/finders/snippets_finder_spec.rb @@ -93,16 +93,39 @@ describe SnippetsFinder do expect(snippets).not_to include(@snippet1, @snippet2) end - it "returns public and internal snippets for none project members" do + it "returns public and internal snippets for non project members" do snippets = SnippetsFinder.new.execute(user, filter: :by_project, project: project1) expect(snippets).to include(@snippet2, @snippet3) expect(snippets).not_to include(@snippet1) end + it "returns public snippets for non project members" do + snippets = SnippetsFinder.new.execute(user, filter: :by_project, project: project1, scope: "are_public") + expect(snippets).to include(@snippet3) + expect(snippets).not_to include(@snippet1, @snippet2) + end + + it "returns internal snippets for non project members" do + snippets = SnippetsFinder.new.execute(user, filter: :by_project, project: project1, scope: "are_internal") + expect(snippets).to include(@snippet2) + expect(snippets).not_to include(@snippet1, @snippet3) + end + + it "does not return private snippets for non project members" do + snippets = SnippetsFinder.new.execute(user, filter: :by_project, project: project1, scope: "are_private") + expect(snippets).not_to include(@snippet1, @snippet2, @snippet3) + end + it "returns all snippets for project members" do project1.team << [user, :developer] snippets = SnippetsFinder.new.execute(user, filter: :by_project, project: project1) expect(snippets).to include(@snippet1, @snippet2, @snippet3) end + + it "returns private snippets for project members" do + project1.team << [user, :developer] + snippets = SnippetsFinder.new.execute(user, filter: :by_project, project: project1, scope: "are_private") + expect(snippets).to include(@snippet1) + end end end diff --git a/spec/javascripts/abuse_reports_spec.js.es6 b/spec/javascripts/abuse_reports_spec.js.es6 index a3171353bfb..9e94c9d1d74 100644 --- a/spec/javascripts/abuse_reports_spec.js.es6 +++ b/spec/javascripts/abuse_reports_spec.js.es6 @@ -1,6 +1,6 @@ -/* eslint-disable */ -/*= require abuse_reports */ +/* eslint-disable space-before-function-paren, no-new, padded-blocks */ +/*= require abuse_reports */ /*= require jquery */ ((global) => { diff --git a/spec/javascripts/activities_spec.js.es6 b/spec/javascripts/activities_spec.js.es6 index 8640cd44085..192da4ee8d9 100644 --- a/spec/javascripts/activities_spec.js.es6 +++ b/spec/javascripts/activities_spec.js.es6 @@ -1,4 +1,5 @@ -/* eslint-disable */ +/* eslint-disable no-unused-expressions, comma-spacing, prefer-const, no-prototype-builtins, semi, no-new, keyword-spacing, no-plusplus, no-shadow, max-len */ + /*= require js.cookie.js */ /*= require jquery.endless-scroll.js */ /*= require pager */ diff --git a/spec/javascripts/awards_handler_spec.js b/spec/javascripts/awards_handler_spec.js index ac1404f6e1c..89201c8cb8b 100644 --- a/spec/javascripts/awards_handler_spec.js +++ b/spec/javascripts/awards_handler_spec.js @@ -1,4 +1,5 @@ -/* eslint-disable space-before-function-paren, no-var, one-var, one-var-declaration-per-line, no-unused-expressions, comma-dangle, no-undef, new-parens, no-unused-vars, quotes, jasmine/no-spec-dupes, prefer-template, padded-blocks, max-len */ +/* eslint-disable space-before-function-paren, no-var, one-var, one-var-declaration-per-line, no-unused-expressions, comma-dangle, new-parens, no-unused-vars, quotes, jasmine/no-spec-dupes, prefer-template, padded-blocks, max-len */ +/* global AwardsHandler */ /*= require awards_handler */ /*= require jquery */ @@ -33,9 +34,9 @@ }; describe('AwardsHandler', function() { - fixture.preload('awards_handler.html'); + fixture.preload('issues/open-issue.html.raw'); beforeEach(function() { - fixture.load('awards_handler.html'); + fixture.load('issues/open-issue.html.raw'); awardsHandler = new AwardsHandler; spyOn(awardsHandler, 'postEmoji').and.callFake((function(_this) { return function(url, emoji, cb) { @@ -113,7 +114,7 @@ }); describe('::getAwardUrl', function() { return it('should return the url for request', function() { - return expect(awardsHandler.getAwardUrl()).toBe('/gitlab-org/gitlab-test/issues/8/toggle_award_emoji'); + return expect(awardsHandler.getAwardUrl()).toBe('http://test.host/frontend-fixtures/issues-project/issues/1/toggle_award_emoji'); }); }); describe('::addAward and ::checkMutuality', function() { @@ -209,7 +210,7 @@ $('.js-add-award').eq(0).click(); $menu = $('.emoji-menu'); $block = $('.js-awards-block'); - $emoji = $menu.find(".emoji-menu-list-item " + selector); + $emoji = $menu.find('.emoji-menu-list:not(.frequent-emojis) ' + selector); expect($emoji.length).toBe(1); expect($block.find(selector).length).toBe(0); $emoji.click(); @@ -224,7 +225,7 @@ openEmojiMenuAndAddEmoji(); $('.js-add-award').eq(0).click(); $block = $('.js-awards-block'); - $emoji = $('.emoji-menu').find(".emoji-menu-list-item " + selector); + $emoji = $('.emoji-menu').find(".emoji-menu-list:not(.frequent-emojis) " + selector); $emoji.click(); return expect($block.find(selector).length).toBe(0); }); diff --git a/spec/javascripts/behaviors/quick_submit_spec.js b/spec/javascripts/behaviors/quick_submit_spec.js index efb1203eb2f..0f61000bc37 100644 --- a/spec/javascripts/behaviors/quick_submit_spec.js +++ b/spec/javascripts/behaviors/quick_submit_spec.js @@ -1,4 +1,4 @@ -/* eslint-disable space-before-function-paren, no-var, no-return-assign, comma-dangle, no-undef, jasmine/no-spec-dupes, new-cap, padded-blocks, max-len */ +/* eslint-disable space-before-function-paren, no-var, no-return-assign, comma-dangle, jasmine/no-spec-dupes, new-cap, padded-blocks, max-len */ /*= require behaviors/quick_submit */ diff --git a/spec/javascripts/boards/boards_store_spec.js.es6 b/spec/javascripts/boards/boards_store_spec.js.es6 index b84dfc8197b..b3a1afa28a5 100644 --- a/spec/javascripts/boards/boards_store_spec.js.es6 +++ b/spec/javascripts/boards/boards_store_spec.js.es6 @@ -1,4 +1,11 @@ -/* eslint-disable */ +/* eslint-disable comma-dangle, one-var, no-unused-vars, indent */ +/* global Vue */ +/* global BoardService */ +/* global boardsMockInterceptor */ +/* global Cookies */ +/* global listObj */ +/* global listObjDuplicate */ + //= require jquery //= require jquery_ujs //= require js.cookie diff --git a/spec/javascripts/boards/issue_spec.js.es6 b/spec/javascripts/boards/issue_spec.js.es6 index 90cb8926545..c8a61a0a9b5 100644 --- a/spec/javascripts/boards/issue_spec.js.es6 +++ b/spec/javascripts/boards/issue_spec.js.es6 @@ -1,4 +1,7 @@ -/* eslint-disable */ +/* eslint-disable comma-dangle */ +/* global BoardService */ +/* global ListIssue */ + //= require jquery //= require jquery_ujs //= require js.cookie diff --git a/spec/javascripts/boards/list_spec.js.es6 b/spec/javascripts/boards/list_spec.js.es6 index dfbcbe3a7c1..7d942ec3d65 100644 --- a/spec/javascripts/boards/list_spec.js.es6 +++ b/spec/javascripts/boards/list_spec.js.es6 @@ -1,4 +1,10 @@ -/* eslint-disable */ +/* eslint-disable comma-dangle */ +/* global Vue */ +/* global boardsMockInterceptor */ +/* global BoardService */ +/* global List */ +/* global listObj */ + //= require jquery //= require jquery_ujs //= require js.cookie diff --git a/spec/javascripts/boards/mock_data.js.es6 b/spec/javascripts/boards/mock_data.js.es6 index fcb3d8f17d8..8d3e2237fda 100644 --- a/spec/javascripts/boards/mock_data.js.es6 +++ b/spec/javascripts/boards/mock_data.js.es6 @@ -1,4 +1,5 @@ -/* eslint-disable */ +/* eslint-disable comma-dangle, no-unused-vars, quote-props */ + const listObj = { id: 1, position: 0, diff --git a/spec/javascripts/dashboard_spec.js.es6 b/spec/javascripts/dashboard_spec.js.es6 index 93f73fa0e9a..aadf6f518a8 100644 --- a/spec/javascripts/dashboard_spec.js.es6 +++ b/spec/javascripts/dashboard_spec.js.es6 @@ -1,4 +1,5 @@ -/* eslint-disable */ +/* eslint-disable no-new, padded-blocks */ + /*= require sidebar */ /*= require jquery */ /*= require js.cookie */ diff --git a/spec/javascripts/datetime_utility_spec.js.es6 b/spec/javascripts/datetime_utility_spec.js.es6 index 9fdbab3a9e9..8ece24555c5 100644 --- a/spec/javascripts/datetime_utility_spec.js.es6 +++ b/spec/javascripts/datetime_utility_spec.js.es6 @@ -1,5 +1,5 @@ -/* eslint-disable */ //= require lib/utils/datetime_utility + (() => { describe('Date time utils', () => { describe('get day name', () => { diff --git a/spec/javascripts/diff_comments_store_spec.js.es6 b/spec/javascripts/diff_comments_store_spec.js.es6 index 9b2845af608..18805d26ac0 100644 --- a/spec/javascripts/diff_comments_store_spec.js.es6 +++ b/spec/javascripts/diff_comments_store_spec.js.es6 @@ -1,8 +1,11 @@ -/* eslint-disable */ +/* eslint-disable no-extra-semi, jasmine/no-global-setup, dot-notation, jasmine/no-expect-in-setup-teardown, max-len */ +/* global CommentsStore */ + //= require vue //= require diff_notes/models/discussion //= require diff_notes/models/note //= require diff_notes/stores/comments + (() => { function createDiscussion(noteId = 1, resolved = true) { CommentsStore.create('a', noteId, true, resolved, 'test'); diff --git a/spec/javascripts/fixtures/awards_handler.html.haml b/spec/javascripts/fixtures/awards_handler.html.haml deleted file mode 100644 index 1ef2e8f8624..00000000000 --- a/spec/javascripts/fixtures/awards_handler.html.haml +++ /dev/null @@ -1,52 +0,0 @@ -.issue-details.issuable-details - .detail-page-description.content-block - %h2.title Quibusdam sint officiis earum molestiae ipsa autem voluptatem nisi rem. - .description.js-task-list-container.is-task-list-enabled - .wiki - %p Qui exercitationem magnam optio quae fuga earum odio. - %textarea.hidden.js-task-list-field Qui exercitationem magnam optio quae fuga earum odio. - %small.edited-text - .content-block.content-block-small - .awards.js-awards-block{"data-award-url" => "/gitlab-org/gitlab-test/issues/8/toggle_award_emoji"} - %button.award-control.btn.js-emoji-btn{"data-placement" => "bottom", "data-title" => "", :type => "button"} - .icon.emoji-icon.emoji-1F44D{"data-aliases" => "", "data-emoji" => "thumbsup", "data-unicode-name" => "1F44D", :title => "thumbsup"} - %span.award-control-text.js-counter 0 - %button.award-control.btn.js-emoji-btn{"data-placement" => "bottom", "data-title" => "", :type => "button"} - .icon.emoji-icon.emoji-1F44E{"data-aliases" => "", "data-emoji" => "thumbsdown", "data-unicode-name" => "1F44E", :title => "thumbsdown"} - %span.award-control-text.js-counter 0 - .award-menu-holder.js-award-holder - %button.btn.award-control.js-add-award{:type => "button"} - %i.fa.fa-smile-o.award-control-icon.award-control-icon-normal - %i.fa.fa-spinner.fa-spin.award-control-icon.award-control-icon-loading - %span.award-control-text Add - %section.issuable-discussion - #notes - %ul#notes-list.notes.main-notes-list.timeline - %li#note_348.note.note-row-348.timeline-entry{"data-author-id" => "18", "data-editable" => ""} - .timeline-entry-inner - .timeline-icon - %a{:href => "/u/agustin"} - %img.avatar.s40{:alt => "", :src => "#"}/ - .timeline-content - .note-header - %a.author_link{:href => "/u/agustin"} - %span.author Brenna Stokes - .inline.note-headline-light - @agustin commented - %a{:href => "#note_348"} - %time 11 days ago - .note-actions - %span.note-role Reporter - %a.note-action-button.note-emoji-button.js-add-award.js-note-emoji{"data-position" => "right", :href => "#", :title => "Award Emoji"} - %i.fa.fa-spinner.fa-spin - %i.fa.fa-smile-o.link-highlight - .js-task-list-container.note-body.is-task-list-enabled - .note-text - %p Suscipit sunt quia quisquam sed eveniet ipsam. - .note-awards - .awards.hidden.js-awards-block{"data-award-url" => "/gitlab-org/gitlab-test/notes/348/toggle_award_emoji"} - .award-menu-holder.js-award-holder - %button.btn.award-control.js-add-award{:type => "button"} - %i.fa.fa-smile-o.award-control-icon.award-control-icon-normal - %i.fa.fa-spinner.fa-spin.award-control-icon.award-control-icon-loading - %span.award-control-text Add diff --git a/spec/javascripts/fixtures/pipeline_graph.html.haml b/spec/javascripts/fixtures/pipeline_graph.html.haml new file mode 100644 index 00000000000..deca50ceaa7 --- /dev/null +++ b/spec/javascripts/fixtures/pipeline_graph.html.haml @@ -0,0 +1,15 @@ +%div.pipeline-visualization.js-pipeline-graph + %ul.stage-column-list + %li.stage-column + .stage-name + %a{:href => "/"} + Test + .builds-container + %ul + %li.build + .curve + .build-content + %a + %svg + .ci-status-text + stop_review diff --git a/spec/javascripts/gl_dropdown_spec.js.es6 b/spec/javascripts/gl_dropdown_spec.js.es6 index 8ba238018cd..bfaf90e2aee 100644 --- a/spec/javascripts/gl_dropdown_spec.js.es6 +++ b/spec/javascripts/gl_dropdown_spec.js.es6 @@ -1,4 +1,6 @@ -/* eslint-disable */ +/* eslint-disable comma-dangle, prefer-const, no-param-reassign, no-plusplus, semi, no-unused-expressions, arrow-spacing, max-len */ +/* global Turbolinks */ + /*= require jquery */ /*= require gl_dropdown */ /*= require turbolinks */ diff --git a/spec/javascripts/gl_field_errors_spec.js.es6 b/spec/javascripts/gl_field_errors_spec.js.es6 index 0713e30e485..5018e87ad6c 100644 --- a/spec/javascripts/gl_field_errors_spec.js.es6 +++ b/spec/javascripts/gl_field_errors_spec.js.es6 @@ -1,4 +1,5 @@ -/* eslint-disable */ +/* eslint-disable space-before-function-paren, arrow-body-style, indent, padded-blocks */ + //= require jquery //= require gl_field_errors diff --git a/spec/javascripts/graphs/stat_graph_contributors_graph_spec.js b/spec/javascripts/graphs/stat_graph_contributors_graph_spec.js index a406e6cc36a..bc5cbeb6a40 100644 --- a/spec/javascripts/graphs/stat_graph_contributors_graph_spec.js +++ b/spec/javascripts/graphs/stat_graph_contributors_graph_spec.js @@ -1,4 +1,8 @@ -/* eslint-disable quotes, no-undef, indent, semi, object-curly-spacing, jasmine/no-suite-dupes, vars-on-top, no-var, padded-blocks, spaced-comment, max-len */ +/* eslint-disable quotes, indent, semi, object-curly-spacing, jasmine/no-suite-dupes, vars-on-top, no-var, padded-blocks, spaced-comment, max-len */ +/* global d3 */ +/* global ContributorsGraph */ +/* global ContributorsMasterGraph */ + //= require graphs/stat_graph_contributors_graph describe("ContributorsGraph", function () { diff --git a/spec/javascripts/graphs/stat_graph_contributors_util_spec.js b/spec/javascripts/graphs/stat_graph_contributors_util_spec.js index 96f39abe13e..751f3d175e2 100644 --- a/spec/javascripts/graphs/stat_graph_contributors_util_spec.js +++ b/spec/javascripts/graphs/stat_graph_contributors_util_spec.js @@ -1,4 +1,6 @@ -/* eslint-disable quotes, padded-blocks, no-var, camelcase, object-curly-spacing, semi, indent, object-property-newline, comma-dangle, comma-spacing, no-undef, spaced-comment, max-len, key-spacing, vars-on-top, quote-props, no-multi-spaces, max-len */ +/* eslint-disable quotes, padded-blocks, no-var, camelcase, object-curly-spacing, semi, indent, object-property-newline, comma-dangle, comma-spacing, spaced-comment, max-len, key-spacing, vars-on-top, quote-props, no-multi-spaces */ +/* global ContributorsStatGraphUtil */ + //= require graphs/stat_graph_contributors_util describe("ContributorsStatGraphUtil", function () { diff --git a/spec/javascripts/graphs/stat_graph_spec.js b/spec/javascripts/graphs/stat_graph_spec.js index f78573b992b..0da124632ae 100644 --- a/spec/javascripts/graphs/stat_graph_spec.js +++ b/spec/javascripts/graphs/stat_graph_spec.js @@ -1,4 +1,6 @@ -/* eslint-disable quotes, padded-blocks, no-undef, semi */ +/* eslint-disable quotes, padded-blocks, semi */ +/* global StatGraph */ + //= require graphs/stat_graph describe("StatGraph", function () { diff --git a/spec/javascripts/issue_spec.js b/spec/javascripts/issue_spec.js index 14af6644de1..faab5ae00c2 100644 --- a/spec/javascripts/issue_spec.js +++ b/spec/javascripts/issue_spec.js @@ -1,4 +1,5 @@ -/* eslint-disable space-before-function-paren, no-var, one-var, one-var-declaration-per-line, no-use-before-define, indent, no-undef, no-trailing-spaces, comma-dangle, padded-blocks, max-len */ +/* eslint-disable space-before-function-paren, no-var, one-var, one-var-declaration-per-line, no-use-before-define, indent, no-trailing-spaces, comma-dangle, padded-blocks, max-len */ +/* global Issue */ /*= require lib/utils/text_utility */ /*= require issue */ @@ -70,7 +71,7 @@ $('input[type=checkbox]').attr('checked', true).trigger('change'); expect($('.js-task-list-field').val()).toBe('- [x] Task List Item'); }); - + it('submits an ajax request on tasklist:changed', function() { spyOn(jQuery, 'ajax').and.callFake(function(req) { expect(req.type).toBe('PATCH'); diff --git a/spec/javascripts/labels_issue_sidebar_spec.js.es6 b/spec/javascripts/labels_issue_sidebar_spec.js.es6 index 49687048eb5..0c48d04776f 100644 --- a/spec/javascripts/labels_issue_sidebar_spec.js.es6 +++ b/spec/javascripts/labels_issue_sidebar_spec.js.es6 @@ -1,4 +1,7 @@ -/* eslint-disable */ +/* eslint-disable no-new, no-plusplus, object-curly-spacing, prefer-const, semi */ +/* global IssuableContext */ +/* global LabelsSelect */ + //= require lib/utils/type_utility //= require jquery //= require bootstrap diff --git a/spec/javascripts/lib/utils/custom_event_polyfill_spec.js.es6 b/spec/javascripts/lib/utils/custom_event_polyfill_spec.js.es6 new file mode 100644 index 00000000000..3645dd70c55 --- /dev/null +++ b/spec/javascripts/lib/utils/custom_event_polyfill_spec.js.es6 @@ -0,0 +1,43 @@ +//= require lib/utils/custom_event_polyfill + +describe('Custom Event Polyfill', () => { + it('should be defined', () => { + expect(window.CustomEvent).toBeDefined(); + }); + + it('should create a `CustomEvent` instance', () => { + const e = new window.CustomEvent('foo'); + + expect(e.type).toEqual('foo'); + expect(e.bubbles).toBe(false); + expect(e.cancelable).toBe(false); + expect(e.detail).toBeFalsy(); + }); + + it('should create a `CustomEvent` instance with a `details` object', () => { + const e = new window.CustomEvent('bar', { detail: { foo: 'bar' } }); + + expect(e.type).toEqual('bar'); + expect(e.bubbles).toBe(false); + expect(e.cancelable).toBe(false); + expect(e.detail.foo).toEqual('bar'); + }); + + it('should create a `CustomEvent` instance with a `bubbles` boolean', () => { + const e = new window.CustomEvent('bar', { bubbles: true }); + + expect(e.type).toEqual('bar'); + expect(e.bubbles).toBe(true); + expect(e.cancelable).toBe(false); + expect(e.detail).toBeFalsy(); + }); + + it('should create a `CustomEvent` instance with a `cancelable` boolean', () => { + const e = new window.CustomEvent('bar', { cancelable: true }); + + expect(e.type).toEqual('bar'); + expect(e.bubbles).toBe(false); + expect(e.cancelable).toBe(true); + expect(e.detail).toBeFalsy(); + }); +}); diff --git a/spec/javascripts/line_highlighter_spec.js b/spec/javascripts/line_highlighter_spec.js index b8b174a2e53..decdf583410 100644 --- a/spec/javascripts/line_highlighter_spec.js +++ b/spec/javascripts/line_highlighter_spec.js @@ -1,4 +1,5 @@ -/* eslint-disable space-before-function-paren, no-var, no-param-reassign, quotes, prefer-template, no-else-return, new-cap, dot-notation, no-undef, no-return-assign, comma-dangle, no-new, one-var, one-var-declaration-per-line, no-plusplus, jasmine/no-spec-dupes, no-underscore-dangle, padded-blocks, max-len */ +/* eslint-disable space-before-function-paren, no-var, no-param-reassign, quotes, prefer-template, no-else-return, new-cap, dot-notation, no-return-assign, comma-dangle, no-new, one-var, one-var-declaration-per-line, no-plusplus, jasmine/no-spec-dupes, no-underscore-dangle, padded-blocks, max-len */ +/* global LineHighlighter */ /*= require line_highlighter */ diff --git a/spec/javascripts/merge_request_spec.js b/spec/javascripts/merge_request_spec.js index cbe2634d3a4..4cf1693af1b 100644 --- a/spec/javascripts/merge_request_spec.js +++ b/spec/javascripts/merge_request_spec.js @@ -1,4 +1,5 @@ -/* eslint-disable space-before-function-paren, no-return-assign, no-undef, padded-blocks */ +/* eslint-disable space-before-function-paren, no-return-assign, padded-blocks */ +/* global MergeRequest */ /*= require merge_request */ diff --git a/spec/javascripts/new_branch_spec.js b/spec/javascripts/new_branch_spec.js index 8828970d984..a6cb9e47744 100644 --- a/spec/javascripts/new_branch_spec.js +++ b/spec/javascripts/new_branch_spec.js @@ -1,4 +1,5 @@ -/* eslint-disable space-before-function-paren, one-var, no-var, one-var-declaration-per-line, no-return-assign, no-undef, quotes, padded-blocks, max-len */ +/* eslint-disable space-before-function-paren, one-var, no-var, one-var-declaration-per-line, no-return-assign, quotes, padded-blocks, max-len */ +/* global NewBranchForm */ /*= require jquery-ui/autocomplete */ /*= require new_branch_form */ diff --git a/spec/javascripts/notes_spec.js b/spec/javascripts/notes_spec.js index 2db182d702b..d3bfa7730fa 100644 --- a/spec/javascripts/notes_spec.js +++ b/spec/javascripts/notes_spec.js @@ -1,4 +1,6 @@ -/* eslint-disable space-before-function-paren, no-unused-expressions, no-undef, no-var, object-shorthand, comma-dangle, semi, padded-blocks, max-len */ +/* eslint-disable space-before-function-paren, no-unused-expressions, no-var, object-shorthand, comma-dangle, semi, padded-blocks, max-len */ +/* global Notes */ + /*= require notes */ /*= require autosize */ /*= require gl_form */ diff --git a/spec/javascripts/pipelines_spec.js.es6 b/spec/javascripts/pipelines_spec.js.es6 new file mode 100644 index 00000000000..85c9cf4b4f1 --- /dev/null +++ b/spec/javascripts/pipelines_spec.js.es6 @@ -0,0 +1,25 @@ +//= require pipelines + +(() => { + describe('Pipelines', () => { + fixture.preload('pipeline_graph'); + + beforeEach(() => { + fixture.load('pipeline_graph'); + }); + + it('should be defined', () => { + expect(window.gl.Pipelines).toBeDefined(); + }); + + it('should create a `Pipelines` instance without options', () => { + expect(() => { new window.gl.Pipelines(); }).not.toThrow(); //eslint-disable-line + }); + + it('should create a `Pipelines` instance with options', () => { + const pipelines = new window.gl.Pipelines({ foo: 'bar' }); + + expect(pipelines.pipelineGraph).toBeDefined(); + }); + }); +})(); diff --git a/spec/javascripts/project_title_spec.js b/spec/javascripts/project_title_spec.js index 49211a6b852..bb802a4b5e3 100644 --- a/spec/javascripts/project_title_spec.js +++ b/spec/javascripts/project_title_spec.js @@ -1,4 +1,6 @@ -/* eslint-disable space-before-function-paren, no-unused-expressions, no-return-assign, no-undef, no-param-reassign, no-var, new-cap, wrap-iife, no-unused-vars, quotes, padded-blocks, max-len */ +/* eslint-disable space-before-function-paren, no-unused-expressions, no-return-assign, no-param-reassign, no-var, new-cap, wrap-iife, no-unused-vars, quotes, jasmine/no-expect-in-setup-teardown, padded-blocks, max-len */ + +/* global Project */ /*= require bootstrap */ /*= require select2 */ @@ -21,16 +23,18 @@ return this.project = new Project(); }); return describe('project list', function() { + var fakeAjaxResponse = function fakeAjaxResponse(req) { + var d; + expect(req.url).toBe('/api/v3/projects.json?simple=true'); + d = $.Deferred(); + d.resolve(this.projects_data); + return d.promise(); + }; + beforeEach((function(_this) { return function() { _this.projects_data = fixture.load('projects.json')[0]; - return spyOn(jQuery, 'ajax').and.callFake(function(req) { - var d; - expect(req.url).toBe('/api/v3/projects.json?simple=true'); - d = $.Deferred(); - d.resolve(_this.projects_data); - return d.promise(); - }); + return spyOn(jQuery, 'ajax').and.callFake(fakeAjaxResponse.bind(_this)); }; })(this)); it('to show on toggle click', (function(_this) { diff --git a/spec/javascripts/right_sidebar_spec.js b/spec/javascripts/right_sidebar_spec.js index 0a9bc546144..a083dbf033a 100644 --- a/spec/javascripts/right_sidebar_spec.js +++ b/spec/javascripts/right_sidebar_spec.js @@ -1,4 +1,5 @@ -/* eslint-disable space-before-function-paren, no-var, one-var, one-var-declaration-per-line, new-parens, no-undef, no-return-assign, new-cap, vars-on-top, semi, padded-blocks, max-len */ +/* eslint-disable space-before-function-paren, no-var, one-var, one-var-declaration-per-line, new-parens, no-return-assign, new-cap, vars-on-top, semi, padded-blocks, max-len */ +/* global Sidebar */ /*= require right_sidebar */ /*= require jquery */ diff --git a/spec/javascripts/shortcuts_issuable_spec.js b/spec/javascripts/shortcuts_issuable_spec.js index e37816b0a8c..7bc898aed5d 100644 --- a/spec/javascripts/shortcuts_issuable_spec.js +++ b/spec/javascripts/shortcuts_issuable_spec.js @@ -1,4 +1,5 @@ -/* eslint-disable space-before-function-paren, no-return-assign, no-undef, no-var, quotes, padded-blocks, max-len */ +/* eslint-disable space-before-function-paren, no-return-assign, no-var, quotes, padded-blocks */ +/* global ShortcutsIssuable */ /*= require shortcuts_issuable */ diff --git a/spec/javascripts/smart_interval_spec.js.es6 b/spec/javascripts/smart_interval_spec.js.es6 index ed6166a25a8..1b7ca97cde4 100644 --- a/spec/javascripts/smart_interval_spec.js.es6 +++ b/spec/javascripts/smart_interval_spec.js.es6 @@ -14,8 +14,9 @@ startingInterval: DEFAULT_STARTING_INTERVAL, maxInterval: DEFAULT_MAX_INTERVAL, incrementByFactorOf: DEFAULT_INCREMENT_FACTOR, - delayStartBy: 0, lazyStart: false, + immediateExecution: false, + hiddenInterval: null, }; if (config) { @@ -114,14 +115,31 @@ expect(interval.state.intervalId).toBeTruthy(); // simulates triggering of visibilitychange event - interval.state.pageVisibility = 'hidden'; - interval.handleVisibilityChange(); + interval.handleVisibilityChange({ target: { visibilityState: 'hidden' } }); expect(interval.state.intervalId).toBeUndefined(); done(); }, DEFAULT_SHORT_TIMEOUT); }); + it('should change to the hidden interval when page is not visible', function (done) { + const HIDDEN_INTERVAL = 1500; + const interval = createDefaultSmartInterval({ hiddenInterval: HIDDEN_INTERVAL }); + + setTimeout(() => { + expect(interval.state.intervalId).toBeTruthy(); + expect(interval.getCurrentInterval() >= DEFAULT_STARTING_INTERVAL && + interval.getCurrentInterval() <= DEFAULT_MAX_INTERVAL).toBeTruthy(); + + // simulates triggering of visibilitychange event + interval.handleVisibilityChange({ target: { visibilityState: 'hidden' } }); + + expect(interval.state.intervalId).toBeTruthy(); + expect(interval.getCurrentInterval()).toBe(HIDDEN_INTERVAL); + done(); + }, DEFAULT_SHORT_TIMEOUT); + }); + it('should resume when page is becomes visible at the previous interval', function (done) { const interval = this.smartInterval; @@ -129,14 +147,12 @@ expect(interval.state.intervalId).toBeTruthy(); // simulates triggering of visibilitychange event - interval.state.pageVisibility = 'hidden'; - interval.handleVisibilityChange(); + interval.handleVisibilityChange({ target: { visibilityState: 'hidden' } }); expect(interval.state.intervalId).toBeUndefined(); // simulates triggering of visibilitychange event - interval.state.pageVisibility = 'visible'; - interval.handleVisibilityChange(); + interval.handleVisibilityChange({ target: { visibilityState: 'visible' } }); expect(interval.state.intervalId).toBeTruthy(); @@ -154,6 +170,11 @@ done(); }, DEFAULT_SHORT_TIMEOUT); }); + + it('should execute callback before first interval', function () { + const interval = createDefaultSmartInterval({ immediateExecution: true }); + expect(interval.cfg.immediateExecution).toBeFalsy(); + }); }); }); })(window.gl || (window.gl = {})); diff --git a/spec/javascripts/subbable_resource_spec.js.es6 b/spec/javascripts/subbable_resource_spec.js.es6 index df395296791..6a70dd856a7 100644 --- a/spec/javascripts/subbable_resource_spec.js.es6 +++ b/spec/javascripts/subbable_resource_spec.js.es6 @@ -1,4 +1,5 @@ -/* eslint-disable */ +/* eslint-disable max-len, arrow-parens, comma-dangle, no-plusplus */ + //= vue //= vue-resource //= require jquery diff --git a/spec/javascripts/u2f/authenticate_spec.js b/spec/javascripts/u2f/authenticate_spec.js index 944df6d23f7..a8874ab12d3 100644 --- a/spec/javascripts/u2f/authenticate_spec.js +++ b/spec/javascripts/u2f/authenticate_spec.js @@ -1,4 +1,6 @@ -/* eslint-disable space-before-function-paren, new-parens, no-undef, quotes, comma-dangle, no-var, one-var, one-var-declaration-per-line, padded-blocks, max-len */ +/* eslint-disable space-before-function-paren, new-parens, quotes, comma-dangle, no-var, one-var, one-var-declaration-per-line, padded-blocks, max-len */ +/* global MockU2FDevice */ +/* global U2FAuthenticate */ /*= require u2f/authenticate */ /*= require u2f/util */ diff --git a/spec/javascripts/u2f/register_spec.js b/spec/javascripts/u2f/register_spec.js index 0c73c5772bd..189592ea87a 100644 --- a/spec/javascripts/u2f/register_spec.js +++ b/spec/javascripts/u2f/register_spec.js @@ -1,4 +1,6 @@ -/* eslint-disable space-before-function-paren, new-parens, no-undef, quotes, no-var, one-var, one-var-declaration-per-line, comma-dangle, padded-blocks, max-len */ +/* eslint-disable space-before-function-paren, new-parens, quotes, no-var, one-var, one-var-declaration-per-line, comma-dangle, padded-blocks, max-len */ +/* global MockU2FDevice */ +/* global U2FRegister */ /*= require u2f/register */ /*= require u2f/util */ diff --git a/spec/javascripts/zen_mode_spec.js b/spec/javascripts/zen_mode_spec.js index b9acaaa5a0d..d80ce5a7f7e 100644 --- a/spec/javascripts/zen_mode_spec.js +++ b/spec/javascripts/zen_mode_spec.js @@ -1,4 +1,7 @@ -/* eslint-disable space-before-function-paren, no-var, one-var, one-var-declaration-per-line, no-undef, object-shorthand, comma-dangle, no-return-assign, new-cap, padded-blocks, max-len */ +/* eslint-disable space-before-function-paren, no-var, one-var, one-var-declaration-per-line, object-shorthand, comma-dangle, no-return-assign, new-cap, padded-blocks, max-len */ +/* global Dropzone */ +/* global Mousetrap */ +/* global ZenMode */ /*= require zen_mode */ diff --git a/spec/lib/banzai/filter/relative_link_filter_spec.rb b/spec/lib/banzai/filter/relative_link_filter_spec.rb index 2bfa51deb20..df2dd173b57 100644 --- a/spec/lib/banzai/filter/relative_link_filter_spec.rb +++ b/spec/lib/banzai/filter/relative_link_filter_spec.rb @@ -175,7 +175,7 @@ describe Banzai::Filter::RelativeLinkFilter, lib: true do allow_any_instance_of(described_class).to receive(:uri_type).and_return(:raw) doc = filter(image(escaped)) - expect(doc.at_css('img')['src']).to match '/raw/' + expect(doc.at_css('img')['src']).to eq "/#{project_path}/raw/#{Addressable::URI.escape(ref)}/#{escaped}" end context 'when requested path is a file in the repo' do diff --git a/spec/lib/gitlab/allowable_spec.rb b/spec/lib/gitlab/allowable_spec.rb new file mode 100644 index 00000000000..87733d53e92 --- /dev/null +++ b/spec/lib/gitlab/allowable_spec.rb @@ -0,0 +1,27 @@ +require 'spec_helper' + +describe Gitlab::Allowable do + subject do + Class.new.include(described_class).new + end + + describe '#can?' do + let(:user) { create(:user) } + + context 'when user is allowed to do something' do + let(:project) { create(:empty_project, :public) } + + it 'reports correct ability to perform action' do + expect(subject.can?(user, :read_project, project)).to be true + end + end + + context 'when user is not allowed to do something' do + let(:project) { create(:empty_project, :private) } + + it 'reports correct ability to perform action' do + expect(subject.can?(user, :read_project, project)).to be false + end + end + end +end diff --git a/spec/lib/gitlab/ci/status/build/cancelable_spec.rb b/spec/lib/gitlab/ci/status/build/cancelable_spec.rb new file mode 100644 index 00000000000..9376bce17a1 --- /dev/null +++ b/spec/lib/gitlab/ci/status/build/cancelable_spec.rb @@ -0,0 +1,86 @@ +require 'spec_helper' + +describe Gitlab::Ci::Status::Build::Cancelable do + let(:status) { double('core status') } + let(:user) { double('user') } + + subject do + described_class.new(status) + end + + describe '#text' do + it 'does not override status text' do + expect(status).to receive(:text) + + subject.text + end + end + + describe '#icon' do + it 'does not override status icon' do + expect(status).to receive(:icon) + + subject.icon + end + end + + describe '#label' do + it 'does not override status label' do + expect(status).to receive(:label) + + subject.label + end + end + + describe 'action details' do + let(:user) { create(:user) } + let(:build) { create(:ci_build) } + let(:status) { Gitlab::Ci::Status::Core.new(build, user) } + + describe '#has_action?' do + context 'when user is allowed to update build' do + before { build.project.team << [user, :developer] } + + it { is_expected.to have_action } + end + + context 'when user is not allowed to update build' do + it { is_expected.not_to have_action } + end + end + + describe '#action_path' do + it { expect(subject.action_path).to include "#{build.id}/cancel" } + end + + describe '#action_icon' do + it { expect(subject.action_icon).to eq 'ban' } + end + + describe '#action_title' do + it { expect(subject.action_title).to eq 'Cancel' } + end + end + + describe '.matches?' do + subject { described_class.matches?(build, user) } + + context 'when build is cancelable' do + let(:build) do + create(:ci_build, :running) + end + + it 'is a correct match' do + expect(subject).to be true + end + end + + context 'when build is not cancelable' do + let(:build) { create(:ci_build, :success) } + + it 'does not match' do + expect(subject).to be false + end + end + end +end diff --git a/spec/lib/gitlab/ci/status/build/common_spec.rb b/spec/lib/gitlab/ci/status/build/common_spec.rb new file mode 100644 index 00000000000..40b96b1807b --- /dev/null +++ b/spec/lib/gitlab/ci/status/build/common_spec.rb @@ -0,0 +1,37 @@ +require 'spec_helper' + +describe Gitlab::Ci::Status::Build::Common do + let(:user) { create(:user) } + let(:build) { create(:ci_build) } + let(:project) { build.project } + + subject do + Gitlab::Ci::Status::Core + .new(build, user) + .extend(described_class) + end + + describe '#has_action?' do + it { is_expected.not_to have_action } + end + + describe '#has_details?' do + context 'when user has access to read build' do + before { project.team << [user, :developer] } + + it { is_expected.to have_details } + end + + context 'when user does not have access to read build' do + before { project.update(public_builds: false) } + + it { is_expected.not_to have_details } + end + end + + describe '#details_path' do + it 'links to the build details page' do + expect(subject.details_path).to include "builds/#{build.id}" + end + end +end diff --git a/spec/lib/gitlab/ci/status/build/factory_spec.rb b/spec/lib/gitlab/ci/status/build/factory_spec.rb new file mode 100644 index 00000000000..dccb29b5ef6 --- /dev/null +++ b/spec/lib/gitlab/ci/status/build/factory_spec.rb @@ -0,0 +1,141 @@ +require 'spec_helper' + +describe Gitlab::Ci::Status::Build::Factory do + let(:user) { create(:user) } + let(:project) { build.project } + + subject { described_class.new(build, user) } + let(:status) { subject.fabricate! } + + before { project.team << [user, :developer] } + + context 'when build is successful' do + let(:build) { create(:ci_build, :success) } + + it 'fabricates a retryable build status' do + expect(status).to be_a Gitlab::Ci::Status::Build::Retryable + end + + it 'fabricates status with correct details' do + expect(status.text).to eq 'passed' + expect(status.icon).to eq 'icon_status_success' + expect(status.label).to eq 'passed' + expect(status).to have_details + expect(status).to have_action + end + end + + context 'when build is failed' do + let(:build) { create(:ci_build, :failed) } + + it 'fabricates a retryable build status' do + expect(status).to be_a Gitlab::Ci::Status::Build::Retryable + end + + it 'fabricates status with correct details' do + expect(status.text).to eq 'failed' + expect(status.icon).to eq 'icon_status_failed' + expect(status.label).to eq 'failed' + expect(status).to have_details + expect(status).to have_action + end + end + + context 'when build is a canceled' do + let(:build) { create(:ci_build, :canceled) } + + it 'fabricates a retryable build status' do + expect(status).to be_a Gitlab::Ci::Status::Build::Retryable + end + + it 'fabricates status with correct details' do + expect(status.text).to eq 'canceled' + expect(status.icon).to eq 'icon_status_canceled' + expect(status.label).to eq 'canceled' + expect(status).to have_details + expect(status).to have_action + end + end + + context 'when build is running' do + let(:build) { create(:ci_build, :running) } + + it 'fabricates a canceable build status' do + expect(status).to be_a Gitlab::Ci::Status::Build::Cancelable + end + + it 'fabricates status with correct details' do + expect(status.text).to eq 'running' + expect(status.icon).to eq 'icon_status_running' + expect(status.label).to eq 'running' + expect(status).to have_details + expect(status).to have_action + end + end + + context 'when build is pending' do + let(:build) { create(:ci_build, :pending) } + + it 'fabricates a cancelable build status' do + expect(status).to be_a Gitlab::Ci::Status::Build::Cancelable + end + + it 'fabricates status with correct details' do + expect(status.text).to eq 'pending' + expect(status.icon).to eq 'icon_status_pending' + expect(status.label).to eq 'pending' + expect(status).to have_details + expect(status).to have_action + end + end + + context 'when build is skipped' do + let(:build) { create(:ci_build, :skipped) } + + it 'fabricates a core skipped status' do + expect(status).to be_a Gitlab::Ci::Status::Skipped + end + + it 'fabricates status with correct details' do + expect(status.text).to eq 'skipped' + expect(status.icon).to eq 'icon_status_skipped' + expect(status.label).to eq 'skipped' + expect(status).to have_details + expect(status).not_to have_action + end + end + + context 'when build is a manual action' do + context 'when build is a play action' do + let(:build) { create(:ci_build, :playable) } + + it 'fabricates a core skipped status' do + expect(status).to be_a Gitlab::Ci::Status::Build::Play + end + + it 'fabricates status with correct details' do + expect(status.text).to eq 'manual' + expect(status.icon).to eq 'icon_status_manual' + expect(status.label).to eq 'manual play action' + expect(status).to have_details + expect(status).to have_action + end + end + + context 'when build is an environment stop action' do + let(:build) { create(:ci_build, :playable, :teardown_environment) } + + it 'fabricates a core skipped status' do + expect(status).to be_a Gitlab::Ci::Status::Build::Stop + end + + it 'fabricates status with correct details' do + expect(status.text).to eq 'manual' + expect(status.icon).to eq 'icon_status_manual' + expect(status.label).to eq 'manual stop action' + expect(status).to have_details + expect(status).to have_action + end + end + end +end diff --git a/spec/lib/gitlab/ci/status/build/play_spec.rb b/spec/lib/gitlab/ci/status/build/play_spec.rb new file mode 100644 index 00000000000..4ddf04a8e11 --- /dev/null +++ b/spec/lib/gitlab/ci/status/build/play_spec.rb @@ -0,0 +1,82 @@ +require 'spec_helper' + +describe Gitlab::Ci::Status::Build::Play do + let(:status) { double('core') } + let(:user) { double('user') } + + subject { described_class.new(status) } + + describe '#text' do + it { expect(subject.text).to eq 'manual' } + end + + describe '#label' do + it { expect(subject.label).to eq 'manual play action' } + end + + describe '#icon' do + it { expect(subject.icon).to eq 'icon_status_manual' } + end + + describe 'action details' do + let(:user) { create(:user) } + let(:build) { create(:ci_build) } + let(:status) { Gitlab::Ci::Status::Core.new(build, user) } + + describe '#has_action?' do + context 'when user is allowed to update build' do + before { build.project.team << [user, :developer] } + + it { is_expected.to have_action } + end + + context 'when user is not allowed to update build' do + it { is_expected.not_to have_action } + end + end + + describe '#action_path' do + it { expect(subject.action_path).to include "#{build.id}/play" } + end + + describe '#action_icon' do + it { expect(subject.action_icon).to eq 'play' } + end + + describe '#action_title' do + it { expect(subject.action_title).to eq 'Play' } + end + end + + describe '.matches?' do + subject { described_class.matches?(build, user) } + + context 'when build is playable' do + context 'when build stops an environment' do + let(:build) do + create(:ci_build, :playable, :teardown_environment) + end + + it 'does not match' do + expect(subject).to be false + end + end + + context 'when build does not stop an environment' do + let(:build) { create(:ci_build, :playable) } + + it 'is a correct match' do + expect(subject).to be true + end + end + end + + context 'when build is not playable' do + let(:build) { create(:ci_build) } + + it 'does not match' do + expect(subject).to be false + end + end + end +end diff --git a/spec/lib/gitlab/ci/status/build/retryable_spec.rb b/spec/lib/gitlab/ci/status/build/retryable_spec.rb new file mode 100644 index 00000000000..d61e5bbaa6b --- /dev/null +++ b/spec/lib/gitlab/ci/status/build/retryable_spec.rb @@ -0,0 +1,86 @@ +require 'spec_helper' + +describe Gitlab::Ci::Status::Build::Retryable do + let(:status) { double('core status') } + let(:user) { double('user') } + + subject do + described_class.new(status) + end + + describe '#text' do + it 'does not override status text' do + expect(status).to receive(:text) + + subject.text + end + end + + describe '#icon' do + it 'does not override status icon' do + expect(status).to receive(:icon) + + subject.icon + end + end + + describe '#label' do + it 'does not override status label' do + expect(status).to receive(:label) + + subject.label + end + end + + describe 'action details' do + let(:user) { create(:user) } + let(:build) { create(:ci_build) } + let(:status) { Gitlab::Ci::Status::Core.new(build, user) } + + describe '#has_action?' do + context 'when user is allowed to update build' do + before { build.project.team << [user, :developer] } + + it { is_expected.to have_action } + end + + context 'when user is not allowed to update build' do + it { is_expected.not_to have_action } + end + end + + describe '#action_path' do + it { expect(subject.action_path).to include "#{build.id}/retry" } + end + + describe '#action_icon' do + it { expect(subject.action_icon).to eq 'refresh' } + end + + describe '#action_title' do + it { expect(subject.action_title).to eq 'Retry' } + end + end + + describe '.matches?' do + subject { described_class.matches?(build, user) } + + context 'when build is retryable' do + let(:build) do + create(:ci_build, :success) + end + + it 'is a correct match' do + expect(subject).to be true + end + end + + context 'when build is not retryable' do + let(:build) { create(:ci_build, :running) } + + it 'does not match' do + expect(subject).to be false + end + end + end +end diff --git a/spec/lib/gitlab/ci/status/build/stop_spec.rb b/spec/lib/gitlab/ci/status/build/stop_spec.rb new file mode 100644 index 00000000000..59a85b55f90 --- /dev/null +++ b/spec/lib/gitlab/ci/status/build/stop_spec.rb @@ -0,0 +1,84 @@ +require 'spec_helper' + +describe Gitlab::Ci::Status::Build::Stop do + let(:status) { double('core status') } + let(:user) { double('user') } + + subject do + described_class.new(status) + end + + describe '#text' do + it { expect(subject.text).to eq 'manual' } + end + + describe '#label' do + it { expect(subject.label).to eq 'manual stop action' } + end + + describe '#icon' do + it { expect(subject.icon).to eq 'icon_status_manual' } + end + + describe 'action details' do + let(:user) { create(:user) } + let(:build) { create(:ci_build) } + let(:status) { Gitlab::Ci::Status::Core.new(build, user) } + + describe '#has_action?' do + context 'when user is allowed to update build' do + before { build.project.team << [user, :developer] } + + it { is_expected.to have_action } + end + + context 'when user is not allowed to update build' do + it { is_expected.not_to have_action } + end + end + + describe '#action_path' do + it { expect(subject.action_path).to include "#{build.id}/play" } + end + + describe '#action_icon' do + it { expect(subject.action_icon).to eq 'stop' } + end + + describe '#action_title' do + it { expect(subject.action_title).to eq 'Stop' } + end + end + + describe '.matches?' do + subject { described_class.matches?(build, user) } + + context 'when build is playable' do + context 'when build stops an environment' do + let(:build) do + create(:ci_build, :playable, :teardown_environment) + end + + it 'is a correct match' do + expect(subject).to be true + end + end + + context 'when build does not stop an environment' do + let(:build) { create(:ci_build, :playable) } + + it 'does not match' do + expect(subject).to be false + end + end + end + + context 'when build is not playable' do + let(:build) { create(:ci_build) } + + it 'does not match' do + expect(subject).to be false + end + end + end +end diff --git a/spec/lib/gitlab/ci/status/canceled_spec.rb b/spec/lib/gitlab/ci/status/canceled_spec.rb index 619ecbcba67..4639278ad45 100644 --- a/spec/lib/gitlab/ci/status/canceled_spec.rb +++ b/spec/lib/gitlab/ci/status/canceled_spec.rb @@ -1,7 +1,9 @@ require 'spec_helper' describe Gitlab::Ci::Status::Canceled do - subject { described_class.new(double('subject')) } + subject do + described_class.new(double('subject'), double('user')) + end describe '#text' do it { expect(subject.label).to eq 'canceled' } @@ -14,8 +16,4 @@ describe Gitlab::Ci::Status::Canceled do describe '#icon' do it { expect(subject.icon).to eq 'icon_status_canceled' } end - - describe '#title' do - it { expect(subject.title).to eq 'Double: canceled' } - end end diff --git a/spec/lib/gitlab/ci/status/created_spec.rb b/spec/lib/gitlab/ci/status/created_spec.rb index 157302c65a8..2ce176a29d6 100644 --- a/spec/lib/gitlab/ci/status/created_spec.rb +++ b/spec/lib/gitlab/ci/status/created_spec.rb @@ -1,7 +1,9 @@ require 'spec_helper' describe Gitlab::Ci::Status::Created do - subject { described_class.new(double('subject')) } + subject do + described_class.new(double('subject'), double('user')) + end describe '#text' do it { expect(subject.label).to eq 'created' } @@ -14,8 +16,4 @@ describe Gitlab::Ci::Status::Created do describe '#icon' do it { expect(subject.icon).to eq 'icon_status_created' } end - - describe '#title' do - it { expect(subject.title).to eq 'Double: created' } - end end diff --git a/spec/lib/gitlab/ci/status/extended_spec.rb b/spec/lib/gitlab/ci/status/extended_spec.rb index 120e121aae5..c2d74ca5cde 100644 --- a/spec/lib/gitlab/ci/status/extended_spec.rb +++ b/spec/lib/gitlab/ci/status/extended_spec.rb @@ -2,11 +2,11 @@ require 'spec_helper' describe Gitlab::Ci::Status::Extended do subject do - Class.new.extend(described_class) + Class.new.include(described_class) end it 'requires subclass to implement matcher' do - expect { subject.matches?(double) } + expect { subject.matches?(double, double) } .to raise_error(NotImplementedError) end end diff --git a/spec/lib/gitlab/ci/status/factory_spec.rb b/spec/lib/gitlab/ci/status/factory_spec.rb index d5bd7f7102b..f92a1c149bf 100644 --- a/spec/lib/gitlab/ci/status/factory_spec.rb +++ b/spec/lib/gitlab/ci/status/factory_spec.rb @@ -2,15 +2,17 @@ require 'spec_helper' describe Gitlab::Ci::Status::Factory do subject do - described_class.new(object) + described_class.new(resource, user) end + let(:user) { create(:user) } + let(:status) { subject.fabricate! } context 'when object has a core status' do HasStatus::AVAILABLE_STATUSES.each do |core_status| context "when core status is #{core_status}" do - let(:object) { double(status: core_status) } + let(:resource) { double(status: core_status) } it "fabricates a core status #{core_status}" do expect(status).to be_a( diff --git a/spec/lib/gitlab/ci/status/failed_spec.rb b/spec/lib/gitlab/ci/status/failed_spec.rb index 0b3cb8168e6..9d527e6a7ef 100644 --- a/spec/lib/gitlab/ci/status/failed_spec.rb +++ b/spec/lib/gitlab/ci/status/failed_spec.rb @@ -1,7 +1,9 @@ require 'spec_helper' describe Gitlab::Ci::Status::Failed do - subject { described_class.new(double('subject')) } + subject do + described_class.new(double('subject'), double('user')) + end describe '#text' do it { expect(subject.label).to eq 'failed' } @@ -14,8 +16,4 @@ describe Gitlab::Ci::Status::Failed do describe '#icon' do it { expect(subject.icon).to eq 'icon_status_failed' } end - - describe '#title' do - it { expect(subject.title).to eq 'Double: failed' } - end end diff --git a/spec/lib/gitlab/ci/status/pending_spec.rb b/spec/lib/gitlab/ci/status/pending_spec.rb index 57c901c1202..d03f595d3c7 100644 --- a/spec/lib/gitlab/ci/status/pending_spec.rb +++ b/spec/lib/gitlab/ci/status/pending_spec.rb @@ -1,7 +1,9 @@ require 'spec_helper' describe Gitlab::Ci::Status::Pending do - subject { described_class.new(double('subject')) } + subject do + described_class.new(double('subject'), double('user')) + end describe '#text' do it { expect(subject.label).to eq 'pending' } @@ -14,8 +16,4 @@ describe Gitlab::Ci::Status::Pending do describe '#icon' do it { expect(subject.icon).to eq 'icon_status_pending' } end - - describe '#title' do - it { expect(subject.title).to eq 'Double: pending' } - end end diff --git a/spec/lib/gitlab/ci/status/pipeline/common_spec.rb b/spec/lib/gitlab/ci/status/pipeline/common_spec.rb index 21adee3f8e7..d665674bf70 100644 --- a/spec/lib/gitlab/ci/status/pipeline/common_spec.rb +++ b/spec/lib/gitlab/ci/status/pipeline/common_spec.rb @@ -1,23 +1,36 @@ require 'spec_helper' describe Gitlab::Ci::Status::Pipeline::Common do - let(:pipeline) { create(:ci_pipeline) } + let(:user) { create(:user) } + let(:project) { create(:empty_project, :private) } + let(:pipeline) { create(:ci_pipeline, project: project) } subject do - Class.new(Gitlab::Ci::Status::Core) - .new(pipeline).extend(described_class) + Gitlab::Ci::Status::Core + .new(pipeline, user) + .extend(described_class) end - it 'does not have action' do - expect(subject).not_to have_action + describe '#has_action?' do + it { is_expected.not_to have_action } end - it 'has details' do - expect(subject).to have_details + describe '#has_details?' do + context 'when user has access to read pipeline' do + before { project.team << [user, :developer] } + + it { is_expected.to have_details } + end + + context 'when user does not have access to read pipeline' do + it { is_expected.not_to have_details } + end end - it 'links to the pipeline details page' do - expect(subject.details_path) - .to include "pipelines/#{pipeline.id}" + describe '#details_path' do + it 'links to the pipeline details page' do + expect(subject.details_path) + .to include "pipelines/#{pipeline.id}" + end end end diff --git a/spec/lib/gitlab/ci/status/pipeline/factory_spec.rb b/spec/lib/gitlab/ci/status/pipeline/factory_spec.rb index d6243940f2e..d4a2dc7fcc1 100644 --- a/spec/lib/gitlab/ci/status/pipeline/factory_spec.rb +++ b/spec/lib/gitlab/ci/status/pipeline/factory_spec.rb @@ -1,14 +1,21 @@ require 'spec_helper' describe Gitlab::Ci::Status::Pipeline::Factory do + let(:user) { create(:user) } + let(:project) { pipeline.project } + subject do - described_class.new(pipeline) + described_class.new(pipeline, user) end let(:status) do subject.fabricate! end + before do + project.team << [user, :developer] + end + context 'when pipeline has a core status' do HasStatus::AVAILABLE_STATUSES.each do |core_status| context "when core status is #{core_status}" do diff --git a/spec/lib/gitlab/ci/status/pipeline/success_with_warnings_spec.rb b/spec/lib/gitlab/ci/status/pipeline/success_with_warnings_spec.rb index 02e526e3de2..7e3383c307f 100644 --- a/spec/lib/gitlab/ci/status/pipeline/success_with_warnings_spec.rb +++ b/spec/lib/gitlab/ci/status/pipeline/success_with_warnings_spec.rb @@ -29,13 +29,13 @@ describe Gitlab::Ci::Status::Pipeline::SuccessWithWarnings do end it 'is a correct match' do - expect(described_class.matches?(pipeline)).to eq true + expect(described_class.matches?(pipeline, double)).to eq true end end context 'when pipeline does not have warnings' do it 'does not match' do - expect(described_class.matches?(pipeline)).to eq false + expect(described_class.matches?(pipeline, double)).to eq false end end end @@ -51,13 +51,13 @@ describe Gitlab::Ci::Status::Pipeline::SuccessWithWarnings do end it 'does not match' do - expect(described_class.matches?(pipeline)).to eq false + expect(described_class.matches?(pipeline, double)).to eq false end end context 'when pipeline does not have warnings' do it 'does not match' do - expect(described_class.matches?(pipeline)).to eq false + expect(described_class.matches?(pipeline, double)).to eq false end end end diff --git a/spec/lib/gitlab/ci/status/running_spec.rb b/spec/lib/gitlab/ci/status/running_spec.rb index c023f1872cc..9f47090d396 100644 --- a/spec/lib/gitlab/ci/status/running_spec.rb +++ b/spec/lib/gitlab/ci/status/running_spec.rb @@ -1,7 +1,9 @@ require 'spec_helper' describe Gitlab::Ci::Status::Running do - subject { described_class.new(double('subject')) } + subject do + described_class.new(double('subject'), double('user')) + end describe '#text' do it { expect(subject.label).to eq 'running' } @@ -14,8 +16,4 @@ describe Gitlab::Ci::Status::Running do describe '#icon' do it { expect(subject.icon).to eq 'icon_status_running' } end - - describe '#title' do - it { expect(subject.title).to eq 'Double: running' } - end end diff --git a/spec/lib/gitlab/ci/status/skipped_spec.rb b/spec/lib/gitlab/ci/status/skipped_spec.rb index d4f7f4b3b70..94601648a8d 100644 --- a/spec/lib/gitlab/ci/status/skipped_spec.rb +++ b/spec/lib/gitlab/ci/status/skipped_spec.rb @@ -1,7 +1,9 @@ require 'spec_helper' describe Gitlab::Ci::Status::Skipped do - subject { described_class.new(double('subject')) } + subject do + described_class.new(double('subject'), double('user')) + end describe '#text' do it { expect(subject.label).to eq 'skipped' } @@ -14,8 +16,4 @@ describe Gitlab::Ci::Status::Skipped do describe '#icon' do it { expect(subject.icon).to eq 'icon_status_skipped' } end - - describe '#title' do - it { expect(subject.title).to eq 'Double: skipped' } - end end diff --git a/spec/lib/gitlab/ci/status/stage/common_spec.rb b/spec/lib/gitlab/ci/status/stage/common_spec.rb index f3259c6f23e..8814a7614a0 100644 --- a/spec/lib/gitlab/ci/status/stage/common_spec.rb +++ b/spec/lib/gitlab/ci/status/stage/common_spec.rb @@ -1,26 +1,43 @@ require 'spec_helper' describe Gitlab::Ci::Status::Stage::Common do - let(:pipeline) { create(:ci_empty_pipeline) } - let(:stage) { build(:ci_stage, pipeline: pipeline, name: 'test') } + let(:user) { create(:user) } + let(:project) { create(:empty_project) } + let(:pipeline) { create(:ci_empty_pipeline, project: project) } + + let(:stage) do + build(:ci_stage, pipeline: pipeline, name: 'test') + end subject do Class.new(Gitlab::Ci::Status::Core) - .new(stage).extend(described_class) + .new(stage, user).extend(described_class) end it 'does not have action' do expect(subject).not_to have_action end - it 'has details' do - expect(subject).to have_details - end - it 'links to the pipeline details page' do expect(subject.details_path) .to include "pipelines/#{pipeline.id}" expect(subject.details_path) .to include "##{stage.name}" end + + context 'when user has permission to read pipeline' do + before do + project.team << [user, :master] + end + + it 'has details' do + expect(subject).to have_details + end + end + + context 'when user does not have permission to read pipeline' do + it 'does not have details' do + expect(subject).not_to have_details + end + end end diff --git a/spec/lib/gitlab/ci/status/stage/factory_spec.rb b/spec/lib/gitlab/ci/status/stage/factory_spec.rb index 17929665c83..6f8721d30c2 100644 --- a/spec/lib/gitlab/ci/status/stage/factory_spec.rb +++ b/spec/lib/gitlab/ci/status/stage/factory_spec.rb @@ -1,17 +1,26 @@ require 'spec_helper' describe Gitlab::Ci::Status::Stage::Factory do - let(:pipeline) { create(:ci_empty_pipeline) } - let(:stage) { build(:ci_stage, pipeline: pipeline, name: 'test') } + let(:user) { create(:user) } + let(:project) { create(:empty_project) } + let(:pipeline) { create(:ci_empty_pipeline, project: project) } + + let(:stage) do + build(:ci_stage, pipeline: pipeline, name: 'test') + end subject do - described_class.new(stage) + described_class.new(stage, user) end let(:status) do subject.fabricate! end + before do + project.team << [user, :developer] + end + context 'when stage has a core status' do HasStatus::AVAILABLE_STATUSES.each do |core_status| context "when core status is #{core_status}" do diff --git a/spec/lib/gitlab/ci/status/success_spec.rb b/spec/lib/gitlab/ci/status/success_spec.rb index 9e261a3aa5f..90f9f615e0d 100644 --- a/spec/lib/gitlab/ci/status/success_spec.rb +++ b/spec/lib/gitlab/ci/status/success_spec.rb @@ -1,7 +1,9 @@ require 'spec_helper' describe Gitlab::Ci::Status::Success do - subject { described_class.new(double('subject')) } + subject do + described_class.new(double('subject'), double('user')) + end describe '#text' do it { expect(subject.label).to eq 'passed' } @@ -14,8 +16,4 @@ describe Gitlab::Ci::Status::Success do describe '#icon' do it { expect(subject.icon).to eq 'icon_status_success' } end - - describe '#title' do - it { expect(subject.title).to eq 'Double: passed' } - end end diff --git a/spec/lib/gitlab/import_export/all_models.yml b/spec/lib/gitlab/import_export/all_models.yml index 8e1a28f2723..c4ee838b7c9 100644 --- a/spec/lib/gitlab/import_export/all_models.yml +++ b/spec/lib/gitlab/import_export/all_models.yml @@ -147,6 +147,7 @@ project: - bugzilla_service - gitlab_issue_tracker_service - external_wiki_service +- kubernetes_service - forked_project_link - forked_from_project - forked_project_links diff --git a/spec/lib/gitlab/routing_spec.rb b/spec/lib/gitlab/routing_spec.rb new file mode 100644 index 00000000000..01d5acfc15b --- /dev/null +++ b/spec/lib/gitlab/routing_spec.rb @@ -0,0 +1,23 @@ +require 'spec_helper' + +describe Gitlab::Routing do + context 'when module is included' do + subject do + Class.new.include(described_class).new + end + + it 'makes it possible to access url helpers' do + expect(subject).to respond_to(:namespace_project_path) + end + end + + context 'when module is not included' do + subject do + Class.new.include(described_class.url_helpers).new + end + + it 'exposes url helpers module through a method' do + expect(subject).to respond_to(:namespace_project_path) + end + end +end diff --git a/spec/lib/mattermost/team_spec.rb b/spec/lib/mattermost/team_spec.rb new file mode 100644 index 00000000000..a3b0831659f --- /dev/null +++ b/spec/lib/mattermost/team_spec.rb @@ -0,0 +1,19 @@ +require 'spec_helper' + +describe Mattermost::Team do + let(:session) { Mattermost::Session.new('http://localhost:8065/', nil) } + + describe '.all' do + let(:result) { {id: 'abc', display_name: 'team'} } + before do + WebMock.stub_request(:get, 'http://localhost:8065/api/v3/teams/all'). + and_return({ abc: result }.to_json) + end + + xit 'gets the teams' do + allow(session).to receive(:with_session) { yield } + + expect(described_class.all).to eq(result) + end + end +end diff --git a/spec/migrations/migrate_process_commit_worker_jobs_spec.rb b/spec/migrations/migrate_process_commit_worker_jobs_spec.rb index 52428547a9f..6a93deb5412 100644 --- a/spec/migrations/migrate_process_commit_worker_jobs_spec.rb +++ b/spec/migrations/migrate_process_commit_worker_jobs_spec.rb @@ -1,3 +1,5 @@ +# encoding: utf-8 + require 'spec_helper' require Rails.root.join('db', 'migrate', '20161124141322_migrate_process_commit_worker_jobs.rb') @@ -59,6 +61,10 @@ describe MigrateProcessCommitWorkerJobs do Sidekiq.redis { |r| r.llen('queue:process_commit') } end + def pop_job + JSON.load(Sidekiq.redis { |r| r.lpop('queue:process_commit') }) + end + before do Sidekiq.redis do |redis| job = JSON.dump(args: [project.id, user.id, commit.oid]) @@ -92,11 +98,28 @@ describe MigrateProcessCommitWorkerJobs do expect(job_count).to eq(1) end + it 'encodes data to UTF-8' do + allow_any_instance_of(Rugged::Repository).to receive(:lookup). + with(commit.oid). + and_return(commit) + + allow(commit).to receive(:message). + and_return('김치'.force_encoding('BINARY')) + + migration.up + + job = pop_job + + # We don't care so much about what is being stored, instead we just want + # to make sure the encoding is right so that JSON encoding the data + # doesn't produce any errors. + expect(job['args'][2]['message'].encoding).to eq(Encoding::UTF_8) + end + context 'a migrated job' do let(:job) do migration.up - - JSON.load(Sidekiq.redis { |r| r.lpop('queue:process_commit') }) + pop_job end let(:commit_hash) do diff --git a/spec/models/build_spec.rb b/spec/models/build_spec.rb index d4970e38f7c..ea60c17a58a 100644 --- a/spec/models/build_spec.rb +++ b/spec/models/build_spec.rb @@ -254,6 +254,24 @@ describe Ci::Build, models: true do end end + describe '#ref_slug' do + { + 'master' => 'master', + '1-foo' => '1-foo', + 'fix/1-foo' => 'fix-1-foo', + 'fix-1-foo' => 'fix-1-foo', + 'a' * 63 => 'a' * 63, + 'a' * 64 => 'a' * 63, + 'FOO' => 'foo', + }.each do |ref, slug| + it "transforms #{ref} to #{slug}" do + build.ref = ref + + expect(build.ref_slug).to eq(slug) + end + end + end + describe '#variables' do let(:container_registry_enabled) { false } let(:predefined_variables) do @@ -265,6 +283,7 @@ describe Ci::Build, models: true do { key: 'CI_BUILD_REF', value: build.sha, public: true }, { key: 'CI_BUILD_BEFORE_SHA', value: build.before_sha, public: true }, { key: 'CI_BUILD_REF_NAME', value: 'master', public: true }, + { key: 'CI_BUILD_REF_SLUG', value: 'master', public: true }, { key: 'CI_BUILD_NAME', value: 'test', public: true }, { key: 'CI_BUILD_STAGE', value: 'test', public: true }, { key: 'CI_SERVER_NAME', value: 'GitLab', public: true }, @@ -899,21 +918,87 @@ describe Ci::Build, models: true do end end + describe '#cancelable?' do + subject { build } + + context 'when build is cancelable' do + context 'when build is pending' do + it { is_expected.to be_cancelable } + end + + context 'when build is running' do + before do + build.run! + end + + it { is_expected.to be_cancelable } + end + end + + context 'when build is not cancelable' do + context 'when build is successful' do + before do + build.success! + end + + it { is_expected.not_to be_cancelable } + end + + context 'when build is failed' do + before do + build.drop! + end + + it { is_expected.not_to be_cancelable } + end + end + end + describe '#retryable?' do - context 'when build is running' do - before do - build.run! + subject { build } + + context 'when build is retryable' do + context 'when build is successful' do + before do + build.success! + end + + it { is_expected.to be_retryable } + end + + context 'when build is failed' do + before do + build.drop! + end + + it { is_expected.to be_retryable } end - it { expect(build).not_to be_retryable } + context 'when build is canceled' do + before do + build.cancel! + end + + it { is_expected.to be_retryable } + end end - context 'when build is finished' do - before do - build.success! + context 'when build is not retryable' do + context 'when build is running' do + before do + build.run! + end + + it { is_expected.not_to be_retryable } end - it { expect(build).to be_retryable } + context 'when build is skipped' do + before do + build.skip! + end + + it { is_expected.not_to be_retryable } + end end end @@ -1180,4 +1265,13 @@ describe Ci::Build, models: true do it { is_expected.to eq('review/master') } end end + + describe '#detailed_status' do + let(:user) { create(:user) } + + it 'returns a detailed status' do + expect(build.detailed_status(user)) + .to be_a Gitlab::Ci::Status::Build::Cancelable + end + end end diff --git a/spec/models/ci/pipeline_spec.rb b/spec/models/ci/pipeline_spec.rb index 8158e71dd55..e78ae14b737 100644 --- a/spec/models/ci/pipeline_spec.rb +++ b/spec/models/ci/pipeline_spec.rb @@ -442,11 +442,15 @@ describe Ci::Pipeline, models: true do end describe '#detailed_status' do + let(:user) { create(:user) } + + subject { pipeline.detailed_status(user) } + context 'when pipeline is created' do let(:pipeline) { create(:ci_pipeline, status: :created) } it 'returns detailed status for created pipeline' do - expect(pipeline.detailed_status.text).to eq 'created' + expect(subject.text).to eq 'created' end end @@ -454,7 +458,7 @@ describe Ci::Pipeline, models: true do let(:pipeline) { create(:ci_pipeline, status: :pending) } it 'returns detailed status for pending pipeline' do - expect(pipeline.detailed_status.text).to eq 'pending' + expect(subject.text).to eq 'pending' end end @@ -462,7 +466,7 @@ describe Ci::Pipeline, models: true do let(:pipeline) { create(:ci_pipeline, status: :running) } it 'returns detailed status for running pipeline' do - expect(pipeline.detailed_status.text).to eq 'running' + expect(subject.text).to eq 'running' end end @@ -470,7 +474,7 @@ describe Ci::Pipeline, models: true do let(:pipeline) { create(:ci_pipeline, status: :success) } it 'returns detailed status for successful pipeline' do - expect(pipeline.detailed_status.text).to eq 'passed' + expect(subject.text).to eq 'passed' end end @@ -478,7 +482,7 @@ describe Ci::Pipeline, models: true do let(:pipeline) { create(:ci_pipeline, status: :failed) } it 'returns detailed status for failed pipeline' do - expect(pipeline.detailed_status.text).to eq 'failed' + expect(subject.text).to eq 'failed' end end @@ -486,7 +490,7 @@ describe Ci::Pipeline, models: true do let(:pipeline) { create(:ci_pipeline, status: :canceled) } it 'returns detailed status for canceled pipeline' do - expect(pipeline.detailed_status.text).to eq 'canceled' + expect(subject.text).to eq 'canceled' end end @@ -494,7 +498,7 @@ describe Ci::Pipeline, models: true do let(:pipeline) { create(:ci_pipeline, status: :skipped) } it 'returns detailed status for skipped pipeline' do - expect(pipeline.detailed_status.text).to eq 'skipped' + expect(subject.text).to eq 'skipped' end end @@ -506,7 +510,7 @@ describe Ci::Pipeline, models: true do end it 'retruns detailed status for successful pipeline with warnings' do - expect(pipeline.detailed_status.label).to eq 'passed with warnings' + expect(subject.label).to eq 'passed with warnings' end end end diff --git a/spec/models/ci/stage_spec.rb b/spec/models/ci/stage_spec.rb index f232761dba2..8fff38f7cda 100644 --- a/spec/models/ci/stage_spec.rb +++ b/spec/models/ci/stage_spec.rb @@ -68,7 +68,9 @@ describe Ci::Stage, models: true do end describe '#detailed_status' do - subject { stage.detailed_status } + let(:user) { create(:user) } + + subject { stage.detailed_status(user) } context 'when build is created' do let!(:stage_build) { create_job(:ci_build, status: :created) } diff --git a/spec/models/commit_status_spec.rb b/spec/models/commit_status_spec.rb index 1ec08c2a9d0..701f3323c0f 100644 --- a/spec/models/commit_status_spec.rb +++ b/spec/models/commit_status_spec.rb @@ -234,4 +234,13 @@ describe CommitStatus, models: true do end end end + + describe '#detailed_status' do + let(:user) { create(:user) } + + it 'returns a detailed status' do + expect(commit_status.detailed_status(user)) + .to be_a Gitlab::Ci::Status::Success + end + end end diff --git a/spec/models/concerns/routable_spec.rb b/spec/models/concerns/routable_spec.rb index 0acefc0c1d5..b556135532f 100644 --- a/spec/models/concerns/routable_spec.rb +++ b/spec/models/concerns/routable_spec.rb @@ -3,6 +3,10 @@ require 'spec_helper' describe Group, 'Routable' do let!(:group) { create(:group) } + describe 'Validations' do + it { is_expected.to validate_presence_of(:route) } + end + describe 'Associations' do it { is_expected.to have_one(:route).dependent(:destroy) } end @@ -35,16 +39,16 @@ describe Group, 'Routable' do it { expect(described_class.find_by_full_path('unknown')).to eq(nil) } end - describe '.where_paths_in' do + describe '.where_full_path_in' do context 'without any paths' do it 'returns an empty relation' do - expect(described_class.where_paths_in([])).to eq([]) + expect(described_class.where_full_path_in([])).to eq([]) end end context 'without any valid paths' do it 'returns an empty relation' do - expect(described_class.where_paths_in(%w[unknown])).to eq([]) + expect(described_class.where_full_path_in(%w[unknown])).to eq([]) end end @@ -52,13 +56,13 @@ describe Group, 'Routable' do let!(:nested_group) { create(:group, parent: group) } it 'returns the projects matching the paths' do - result = described_class.where_paths_in([group.to_param, nested_group.to_param]) + result = described_class.where_full_path_in([group.to_param, nested_group.to_param]) expect(result).to contain_exactly(group, nested_group) end it 'returns projects regardless of the casing of paths' do - result = described_class.where_paths_in([group.to_param.upcase, nested_group.to_param.upcase]) + result = described_class.where_full_path_in([group.to_param.upcase, nested_group.to_param.upcase]) expect(result).to contain_exactly(group, nested_group) end diff --git a/spec/models/concerns/token_authenticatable_spec.rb b/spec/models/concerns/token_authenticatable_spec.rb index eb64f3d0c83..4b0bfa43abf 100644 --- a/spec/models/concerns/token_authenticatable_spec.rb +++ b/spec/models/concerns/token_authenticatable_spec.rb @@ -6,6 +6,7 @@ shared_examples 'TokenAuthenticatable' do it { expect(described_class).to be_private_method_defined(:write_new_token) } it { expect(described_class).to respond_to("find_by_#{token_field}") } it { is_expected.to respond_to("ensure_#{token_field}") } + it { is_expected.to respond_to("set_#{token_field}") } it { is_expected.to respond_to("reset_#{token_field}!") } end end @@ -55,6 +56,12 @@ describe ApplicationSetting, 'TokenAuthenticatable' do end end + describe 'setting new token' do + subject { described_class.new.send("set_#{token_field}", '0123456789') } + + it { is_expected.to eq '0123456789' } + end + describe 'multiple token fields' do before do described_class.send(:add_authentication_token_field, :yet_another_token) diff --git a/spec/models/generic_commit_status_spec.rb b/spec/models/generic_commit_status_spec.rb index 615cfe3142b..6004bfdb7b7 100644 --- a/spec/models/generic_commit_status_spec.rb +++ b/spec/models/generic_commit_status_spec.rb @@ -1,8 +1,11 @@ require 'spec_helper' describe GenericCommitStatus, models: true do - let(:pipeline) { FactoryGirl.create :ci_pipeline } - let(:generic_commit_status) { FactoryGirl.create :generic_commit_status, pipeline: pipeline } + let(:pipeline) { create(:ci_pipeline) } + + let(:generic_commit_status) do + create(:generic_commit_status, pipeline: pipeline) + end describe '#context' do subject { generic_commit_status.context } @@ -17,6 +20,15 @@ describe GenericCommitStatus, models: true do it { is_expected.to eq([:external]) } end + describe '#detailed_status' do + let(:user) { create(:user) } + + it 'returns detailed status object' do + expect(generic_commit_status.detailed_status(user)) + .to be_a Gitlab::Ci::Status::Success + end + end + describe 'set_default_values' do before do generic_commit_status.context = nil diff --git a/spec/models/group_spec.rb b/spec/models/group_spec.rb index 1613a586a2c..893c6827a91 100644 --- a/spec/models/group_spec.rb +++ b/spec/models/group_spec.rb @@ -50,9 +50,8 @@ describe Group, models: true do describe 'validations' do it { is_expected.to validate_presence_of :name } - it { is_expected.to validate_uniqueness_of(:name) } + it { is_expected.to validate_uniqueness_of(:name).scoped_to(:parent_id) } it { is_expected.to validate_presence_of :path } - it { is_expected.to validate_uniqueness_of(:path) } it { is_expected.not_to validate_presence_of :owner } end @@ -271,4 +270,11 @@ describe Group, models: true do expect(group.web_url).to include("groups/#{group.name}") end end + + describe 'nested group' do + subject { create(:group, :nested) } + + it { is_expected.to be_valid } + it { expect(subject.parent).to be_kind_of(Group) } + end end diff --git a/spec/models/namespace_spec.rb b/spec/models/namespace_spec.rb index 7f82e85563b..069c59fb5ca 100644 --- a/spec/models/namespace_spec.rb +++ b/spec/models/namespace_spec.rb @@ -6,20 +6,16 @@ describe Namespace, models: true do it { is_expected.to have_many :projects } it { is_expected.to validate_presence_of(:name) } - it { is_expected.to validate_uniqueness_of(:name) } + it { is_expected.to validate_uniqueness_of(:name).scoped_to(:parent_id) } it { is_expected.to validate_length_of(:name).is_at_most(255) } it { is_expected.to validate_length_of(:description).is_at_most(255) } it { is_expected.to validate_presence_of(:path) } - it { is_expected.to validate_uniqueness_of(:path) } it { is_expected.to validate_length_of(:path).is_at_most(255) } it { is_expected.to validate_presence_of(:owner) } - describe "Mass assignment" do - end - describe "Respond to" do it { is_expected.to respond_to(:human_name) } it { is_expected.to respond_to(:to_param) } diff --git a/spec/models/project_services/kubernetes_service_spec.rb b/spec/models/project_services/kubernetes_service_spec.rb new file mode 100644 index 00000000000..ffb92012b89 --- /dev/null +++ b/spec/models/project_services/kubernetes_service_spec.rb @@ -0,0 +1,126 @@ +require 'spec_helper' + +describe KubernetesService, models: true do + let(:project) { create(:empty_project) } + + describe "Associations" do + it { is_expected.to belong_to :project } + end + + describe 'Validations' do + context 'when service is active' do + before { subject.active = true } + it { is_expected.to validate_presence_of(:namespace) } + it { is_expected.to validate_presence_of(:api_url) } + it { is_expected.to validate_presence_of(:token) } + + context 'namespace format' do + before do + subject.project = project + subject.api_url = "http://example.com" + subject.token = "test" + end + + { + 'foo' => true, + '1foo' => true, + 'foo1' => true, + 'foo-bar' => true, + '-foo' => false, + 'foo-' => false, + 'a' * 63 => true, + 'a' * 64 => false, + 'a.b' => false, + 'a*b' => false, + }.each do |namespace, validity| + it "should validate #{namespace} as #{validity ? 'valid' : 'invalid'}" do + subject.namespace = namespace + + expect(subject.valid?).to eq(validity) + end + end + end + end + + context 'when service is inactive' do + before { subject.active = false } + it { is_expected.not_to validate_presence_of(:namespace) } + it { is_expected.not_to validate_presence_of(:api_url) } + it { is_expected.not_to validate_presence_of(:token) } + end + end + + describe '#initialize_properties' do + context 'with a project' do + it 'defaults to the project name' do + expect(described_class.new(project: project).namespace).to eq(project.name) + end + end + + context 'without a project' do + it 'leaves the namespace unset' do + expect(described_class.new.namespace).to be_nil + end + end + end + + describe '#test' do + let(:project) { create(:kubernetes_project) } + let(:service) { project.kubernetes_service } + let(:discovery_url) { service.api_url + '/api/v1' } + + # JSON response body from Kubernetes GET /api/v1 request + let(:discovery_response) { { "kind" => "APIResourceList", "groupVersion" => "v1", "resources" => [] }.to_json } + + context 'with path prefix in api_url' do + let(:discovery_url) { 'https://kubernetes.example.com/prefix/api/v1' } + + before do + service.api_url = 'https://kubernetes.example.com/prefix/' + end + + it 'tests with the prefix' do + WebMock.stub_request(:get, discovery_url).to_return(body: discovery_response) + + expect(service.test[:success]).to be_truthy + expect(WebMock).to have_requested(:get, discovery_url).once + end + end + + context 'with custom CA certificate' do + let(:certificate) { "CA PEM DATA" } + before do + service.update_attributes!(ca_pem: certificate) + end + + it 'is added to the certificate store' do + cert = double("certificate") + + expect(OpenSSL::X509::Certificate).to receive(:new).with(certificate).and_return(cert) + expect_any_instance_of(OpenSSL::X509::Store).to receive(:add_cert).with(cert) + WebMock.stub_request(:get, discovery_url).to_return(body: discovery_response) + + expect(service.test[:success]).to be_truthy + expect(WebMock).to have_requested(:get, discovery_url).once + end + end + + context 'success' do + it 'reads the discovery endpoint' do + WebMock.stub_request(:get, discovery_url).to_return(body: discovery_response) + + expect(service.test[:success]).to be_truthy + expect(WebMock).to have_requested(:get, discovery_url).once + end + end + + context 'failure' do + it 'fails to read the discovery endpoint' do + WebMock.stub_request(:get, discovery_url).to_return(status: 404) + + expect(service.test[:success]).to be_falsy + expect(WebMock).to have_requested(:get, discovery_url).once + end + end + end +end diff --git a/spec/models/project_services/slack_service/pipeline_message_spec.rb b/spec/models/project_services/slack_service/pipeline_message_spec.rb index 363138a9454..4098500122f 100644 --- a/spec/models/project_services/slack_service/pipeline_message_spec.rb +++ b/spec/models/project_services/slack_service/pipeline_message_spec.rb @@ -2,6 +2,7 @@ require 'spec_helper' describe SlackService::PipelineMessage do subject { SlackService::PipelineMessage.new(args) } + let(:user) { { name: 'hacker' } } let(:args) do { @@ -15,7 +16,7 @@ describe SlackService::PipelineMessage do }, project: { path_with_namespace: 'project_name', web_url: 'example.gitlab.com' }, - user: { name: 'hacker' } + user: user } end @@ -28,9 +29,7 @@ describe SlackService::PipelineMessage do let(:message) { build_message('passed') } it 'returns a message with information about succeeded build' do - expect(subject.pretext).to be_empty - expect(subject.fallback).to eq(message) - expect(subject.attachments).to eq([text: message, color: color]) + verify_message end end @@ -40,16 +39,29 @@ describe SlackService::PipelineMessage do let(:duration) { 10 } it 'returns a message with information about failed build' do - expect(subject.pretext).to be_empty - expect(subject.fallback).to eq(message) - expect(subject.attachments).to eq([text: message, color: color]) + verify_message end + + context 'when triggered by API therefore lacking user' do + let(:user) { nil } + let(:message) { build_message(status, 'API') } + + it 'returns a message stating it is by API' do + verify_message + end + end + end + + def verify_message + expect(subject.pretext).to be_empty + expect(subject.fallback).to eq(message) + expect(subject.attachments).to eq([text: message, color: color]) end - def build_message(status_text = status) + def build_message(status_text = status, name = user[:name]) "<example.gitlab.com|project_name>:" \ " Pipeline <example.gitlab.com/pipelines/123|#123>" \ " of <example.gitlab.com/commits/develop|develop> branch" \ - " by hacker #{status_text} in #{duration} #{'second'.pluralize(duration)}" + " by #{name} #{status_text} in #{duration} #{'second'.pluralize(duration)}" end end diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index bad6ed9e146..8b20ee81614 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -727,17 +727,6 @@ describe User, models: true do end end - describe 'by_username_or_id' do - let(:user1) { create(:user, username: 'foo') } - - it "gets the correct user" do - expect(User.by_username_or_id(user1.id)).to eq(user1) - expect(User.by_username_or_id('foo')).to eq(user1) - expect(User.by_username_or_id(-1)).to be_nil - expect(User.by_username_or_id('bar')).to be_nil - end - end - describe '.find_by_ssh_key_id' do context 'using an existing SSH key ID' do let(:user) { create(:user) } diff --git a/spec/requests/api/branches_spec.rb b/spec/requests/api/branches_spec.rb index 55b8c8c0c69..2878e0cb59b 100644 --- a/spec/requests/api/branches_spec.rb +++ b/spec/requests/api/branches_spec.rb @@ -11,6 +11,7 @@ describe API::Branches, api: true do let!(:guest) { create(:project_member, :guest, user: user2, project: project) } let!(:branch_name) { 'feature' } let!(:branch_sha) { '0b4bc9a49b562e85de7cc9e834518ea6828729b9' } + let!(:branch_with_dot) { CreateBranchService.new(project, user).execute("with.1.2.3", "master") } describe "GET /projects/:id/repository/branches" do it "returns an array of project branches" do @@ -37,6 +38,13 @@ describe API::Branches, api: true do expect(json_response['developers_can_merge']).to eq(false) end + it "returns the branch information for a single branch with dots in the name" do + get api("/projects/#{project.id}/repository/branches/with.1.2.3", user) + + expect(response).to have_http_status(200) + expect(json_response['name']).to eq("with.1.2.3") + end + context 'on a merged branch' do it "returns the branch information for a single branch" do get api("/projects/#{project.id}/repository/branches/merge-test", user) @@ -71,6 +79,14 @@ describe API::Branches, api: true do expect(json_response['developers_can_merge']).to eq(false) end + it "protects a single branch with dots in the name" do + put api("/projects/#{project.id}/repository/branches/with.1.2.3/protect", user) + + expect(response).to have_http_status(200) + expect(json_response['name']).to eq("with.1.2.3") + expect(json_response['protected']).to eq(true) + end + it 'protects a single branch and developers can push' do put api("/projects/#{project.id}/repository/branches/#{branch_name}/protect", user), developers_can_push: true @@ -220,6 +236,14 @@ describe API::Branches, api: true do expect(json_response['protected']).to eq(false) end + it "update branches with dots in branch name" do + put api("/projects/#{project.id}/repository/branches/with.1.2.3/unprotect", user) + + expect(response).to have_http_status(200) + expect(json_response['name']).to eq("with.1.2.3") + expect(json_response['protected']).to eq(false) + end + it "returns success when unprotect branch" do put api("/projects/#{project.id}/repository/branches/unknown/unprotect", user) expect(response).to have_http_status(404) @@ -292,6 +316,13 @@ describe API::Branches, api: true do expect(json_response['branch_name']).to eq(branch_name) end + it "removes a branch with dots in the branch name" do + delete api("/projects/#{project.id}/repository/branches/with.1.2.3", user) + + expect(response).to have_http_status(200) + expect(json_response['branch_name']).to eq("with.1.2.3") + end + it 'returns 404 if branch not exists' do delete api("/projects/#{project.id}/repository/branches/foobar", user) expect(response).to have_http_status(404) diff --git a/spec/requests/api/commits_spec.rb b/spec/requests/api/commits_spec.rb index e497bce6943..964cded917c 100644 --- a/spec/requests/api/commits_spec.rb +++ b/spec/requests/api/commits_spec.rb @@ -456,6 +456,76 @@ describe API::Commits, api: true do end end + describe 'POST :id/repository/commits/:sha/cherry_pick' do + let(:master_pickable_commit) { project.commit('7d3b0f7cff5f37573aea97cebfd5692ea1689924') } + + context 'authorized user' do + it 'cherry picks a commit' do + post api("/projects/#{project.id}/repository/commits/#{master_pickable_commit.id}/cherry_pick", user), branch: 'master' + + expect(response).to have_http_status(201) + expect(json_response['title']).to eq(master_pickable_commit.title) + expect(json_response['message']).to eq(master_pickable_commit.message) + expect(json_response['author_name']).to eq(master_pickable_commit.author_name) + expect(json_response['committer_name']).to eq(user.name) + end + + it 'returns 400 if commit is already included in the target branch' do + post api("/projects/#{project.id}/repository/commits/#{master_pickable_commit.id}/cherry_pick", user), branch: 'markdown' + + expect(response).to have_http_status(400) + expect(json_response['message']).to eq('Sorry, we cannot cherry-pick this commit automatically. + A cherry-pick may have already been performed with this commit, or a more recent commit may have updated some of its content.') + end + + it 'returns 400 if you are not allowed to push to the target branch' do + project.team << [user2, :developer] + protected_branch = create(:protected_branch, project: project, name: 'feature') + + post api("/projects/#{project.id}/repository/commits/#{master_pickable_commit.id}/cherry_pick", user2), branch: protected_branch.name + + expect(response).to have_http_status(400) + expect(json_response['message']).to eq('You are not allowed to push into this branch') + end + + it 'returns 400 for missing parameters' do + post api("/projects/#{project.id}/repository/commits/#{master_pickable_commit.id}/cherry_pick", user) + + expect(response).to have_http_status(400) + expect(json_response['error']).to eq('branch is missing') + end + + it 'returns 404 if commit is not found' do + post api("/projects/#{project.id}/repository/commits/abcd0123/cherry_pick", user), branch: 'master' + + expect(response).to have_http_status(404) + expect(json_response['message']).to eq('404 Commit Not Found') + end + + it 'returns 404 if branch is not found' do + post api("/projects/#{project.id}/repository/commits/#{master_pickable_commit.id}/cherry_pick", user), branch: 'foo' + + expect(response).to have_http_status(404) + expect(json_response['message']).to eq('404 Branch Not Found') + end + + it 'returns 400 for missing parameters' do + post api("/projects/#{project.id}/repository/commits/#{master_pickable_commit.id}/cherry_pick", user) + + expect(response).to have_http_status(400) + expect(json_response['error']).to eq('branch is missing') + end + end + + context 'unauthorized user' do + it 'does not cherry pick the commit' do + post api("/projects/#{project.id}/repository/commits/#{master_pickable_commit.id}/cherry_pick"), branch: 'master' + + expect(response).to have_http_status(401) + end + end + end + describe 'Post comment to commit' do context 'authorized user' do it 'returns comment' do diff --git a/spec/requests/api/groups_spec.rb b/spec/requests/api/groups_spec.rb index 548ed8e1892..a75ba824e85 100644 --- a/spec/requests/api/groups_spec.rb +++ b/spec/requests/api/groups_spec.rb @@ -243,6 +243,28 @@ describe API::Groups, api: true do expect(json_response.length).to eq(2) project_names = json_response.map { |proj| proj['name' ] } expect(project_names).to match_array([project1.name, project3.name]) + expect(json_response.first['default_branch']).to be_present + end + + it "returns the group's projects with simple representation" do + get api("/groups/#{group1.id}/projects", user1), simple: true + + expect(response).to have_http_status(200) + expect(json_response.length).to eq(2) + project_names = json_response.map { |proj| proj['name' ] } + expect(project_names).to match_array([project1.name, project3.name]) + expect(json_response.first['default_branch']).not_to be_present + end + + it 'filters the groups projects' do + public_project = create(:project, :public, path: 'test1', group: group1) + + get api("/groups/#{group1.id}/projects", user1), visibility: 'public' + + expect(response).to have_http_status(200) + expect(json_response).to be_an(Array) + expect(json_response.length).to eq(1) + expect(json_response.first['name']).to eq(public_project.name) end it "does not return a non existing group" do diff --git a/spec/requests/api/api_helpers_spec.rb b/spec/requests/api/helpers_spec.rb index 3f34309f419..4035fd97af5 100644 --- a/spec/requests/api/api_helpers_spec.rb +++ b/spec/requests/api/helpers_spec.rb @@ -2,7 +2,6 @@ require 'spec_helper' describe API::Helpers, api: true do include API::Helpers - include ApiHelpers include SentryHelper let(:user) { create(:user) } @@ -13,18 +12,18 @@ describe API::Helpers, api: true do let(:env) { { 'REQUEST_METHOD' => 'GET' } } let(:request) { Rack::Request.new(env) } - def set_env(token_usr, identifier) + def set_env(user_or_token, identifier) clear_env clear_param - env[API::Helpers::PRIVATE_TOKEN_HEADER] = token_usr.private_token - env[API::Helpers::SUDO_HEADER] = identifier + env[API::Helpers::PRIVATE_TOKEN_HEADER] = user_or_token.respond_to?(:private_token) ? user_or_token.private_token : user_or_token + env[API::Helpers::SUDO_HEADER] = identifier.to_s end - def set_param(token_usr, identifier) + def set_param(user_or_token, identifier) clear_env clear_param - params[API::Helpers::PRIVATE_TOKEN_PARAM] = token_usr.private_token - params[API::Helpers::SUDO_PARAM] = identifier + params[API::Helpers::PRIVATE_TOKEN_PARAM] = user_or_token.respond_to?(:private_token) ? user_or_token.private_token : user_or_token + params[API::Helpers::SUDO_PARAM] = identifier.to_s end def clear_env @@ -163,6 +162,13 @@ describe API::Helpers, api: true do expect(current_user).to eq(user) end + it 'memoize the current_user: sudo permissions are not run against the sudoed user' do + set_env(admin, user.id) + + expect(current_user).to eq(user) + expect(current_user).to eq(user) + end + it 'handles sudo to oneself' do set_env(admin, admin.id) @@ -294,33 +300,48 @@ describe API::Helpers, api: true do end end - describe '.sudo_identifier' do - it "returns integers when input is an int" do - set_env(admin, '123') - expect(sudo_identifier).to eq(123) - set_env(admin, '0001234567890') - expect(sudo_identifier).to eq(1234567890) - - set_param(admin, '123') - expect(sudo_identifier).to eq(123) - set_param(admin, '0001234567890') - expect(sudo_identifier).to eq(1234567890) + describe '.sudo?' do + context 'when no sudo env or param is passed' do + before do + doorkeeper_guard_returns(nil) + end + + it 'returns false' do + expect(sudo?).to be_falsy + end end - it "returns string when input is an is not an int" do - set_env(admin, '12.30') - expect(sudo_identifier).to eq("12.30") - set_env(admin, 'hello') - expect(sudo_identifier).to eq('hello') - set_env(admin, ' 123') - expect(sudo_identifier).to eq(' 123') - - set_param(admin, '12.30') - expect(sudo_identifier).to eq("12.30") - set_param(admin, 'hello') - expect(sudo_identifier).to eq('hello') - set_param(admin, ' 123') - expect(sudo_identifier).to eq(' 123') + context 'when sudo env or param is passed', 'user is not an admin' do + before do + set_env(user, '123') + end + + it 'returns an 403 Forbidden' do + expect { sudo? }.to raise_error '403 - {"message"=>"403 Forbidden - Must be admin to use sudo"}' + end + end + + context 'when sudo env or param is passed', 'user is admin' do + context 'personal access token is used' do + before do + personal_access_token = create(:personal_access_token, user: admin) + set_env(personal_access_token.token, user.id) + end + + it 'returns an 403 Forbidden' do + expect { sudo? }.to raise_error '403 - {"message"=>"403 Forbidden - Private token must be specified in order to use sudo"}' + end + end + + context 'private access token is used' do + before do + set_env(admin.private_token, user.id) + end + + it 'returns true' do + expect(sudo?).to be_truthy + end + end end end diff --git a/spec/requests/api/merge_requests_spec.rb b/spec/requests/api/merge_requests_spec.rb index 75b270aa93c..f032d1b683d 100644 --- a/spec/requests/api/merge_requests_spec.rb +++ b/spec/requests/api/merge_requests_spec.rb @@ -533,6 +533,22 @@ describe API::MergeRequests, api: true do expect(json_response['labels']).to include '?' expect(json_response['labels']).to include '&' end + + it 'does not update state when title is empty' do + put api("/projects/#{project.id}/merge_requests/#{merge_request.id}", user), state_event: 'close', title: nil + + merge_request.reload + expect(response).to have_http_status(400) + expect(merge_request.state).to eq('opened') + end + + it 'does not update state when target_branch is empty' do + put api("/projects/#{project.id}/merge_requests/#{merge_request.id}", user), state_event: 'close', target_branch: nil + + merge_request.reload + expect(response).to have_http_status(400) + expect(merge_request.state).to eq('opened') + end end describe "POST /projects/:id/merge_requests/:merge_request_id/comments" do diff --git a/spec/requests/api/services_spec.rb b/spec/requests/api/services_spec.rb index d30361f53d4..668e39f9dba 100644 --- a/spec/requests/api/services_spec.rb +++ b/spec/requests/api/services_spec.rb @@ -2,6 +2,7 @@ require "spec_helper" describe API::Services, api: true do include ApiHelpers + let(:user) { create(:user) } let(:admin) { create(:admin) } let(:user2) { create(:user) } @@ -98,7 +99,7 @@ describe API::Services, api: true do post api("/projects/#{project.id}/services/idonotexist/trigger") expect(response).to have_http_status(404) - expect(json_response["message"]).to eq("404 Service Not Found") + expect(json_response["error"]).to eq("404 Not Found") end end @@ -114,7 +115,7 @@ describe API::Services, api: true do end it 'when the service is inactive' do - post api("/projects/#{project.id}/services/mattermost_slash_commands/trigger") + post api("/projects/#{project.id}/services/mattermost_slash_commands/trigger"), params expect(response).to have_http_status(404) end diff --git a/spec/requests/api/users_spec.rb b/spec/requests/api/users_spec.rb index c37dbfa0a33..9e317f3a7e9 100644 --- a/spec/requests/api/users_spec.rb +++ b/spec/requests/api/users_spec.rb @@ -651,13 +651,12 @@ describe API::Users, api: true do end describe "GET /user" do - let(:personal_access_token) { create(:personal_access_token, user: user) } - let(:private_token) { user.private_token } + let(:personal_access_token) { create(:personal_access_token, user: user).token } context 'with regular user' do context 'with personal access token' do it 'returns 403 without private token when sudo is defined' do - get api("/user?private_token=#{personal_access_token.token}&sudo=#{user.id}") + get api("/user?private_token=#{personal_access_token}&sudo=123") expect(response).to have_http_status(403) end @@ -665,7 +664,7 @@ describe API::Users, api: true do context 'with private token' do it 'returns 403 without private token when sudo defined' do - get api("/user?private_token=#{private_token}&sudo=#{user.id}") + get api("/user?private_token=#{user.private_token}&sudo=123") expect(response).to have_http_status(403) end @@ -676,40 +675,44 @@ describe API::Users, api: true do expect(response).to have_http_status(200) expect(response).to match_response_schema('user/public') + expect(json_response['id']).to eq(user.id) end end context 'with admin' do - let(:user) { create(:admin) } + let(:admin_personal_access_token) { create(:personal_access_token, user: admin).token } context 'with personal access token' do it 'returns 403 without private token when sudo defined' do - get api("/user?private_token=#{personal_access_token.token}&sudo=#{user.id}") + get api("/user?private_token=#{admin_personal_access_token}&sudo=#{user.id}") expect(response).to have_http_status(403) end - it 'returns current user without private token when sudo not defined' do - get api("/user?private_token=#{personal_access_token.token}") + it 'returns initial current user without private token when sudo not defined' do + get api("/user?private_token=#{admin_personal_access_token}") expect(response).to have_http_status(200) expect(response).to match_response_schema('user/public') + expect(json_response['id']).to eq(admin.id) end end context 'with private token' do - it 'returns current user with private token when sudo defined' do - get api("/user?private_token=#{private_token}&sudo=#{user.id}") + it 'returns sudoed user with private token when sudo defined' do + get api("/user?private_token=#{admin.private_token}&sudo=#{user.id}") expect(response).to have_http_status(200) expect(response).to match_response_schema('user/login') + expect(json_response['id']).to eq(user.id) end - it 'returns current user without private token when sudo not defined' do - get api("/user?private_token=#{private_token}") + it 'returns initial current user without private token when sudo not defined' do + get api("/user?private_token=#{admin.private_token}") expect(response).to have_http_status(200) expect(response).to match_response_schema('user/public') + expect(json_response['id']).to eq(admin.id) end end end diff --git a/spec/routing/admin_routing_spec.rb b/spec/routing/admin_routing_spec.rb index 69eeb45ed71..661b671301e 100644 --- a/spec/routing/admin_routing_spec.rb +++ b/spec/routing/admin_routing_spec.rb @@ -66,7 +66,8 @@ describe Admin::ProjectsController, "routing" do end it "to #show" do - expect(get("/admin/projects/gitlab")).to route_to('admin/projects#show', namespace_id: 'gitlab') + expect(get("/admin/projects/gitlab/gitlab-ce")).to route_to('admin/projects#show', namespace_id: 'gitlab', id: 'gitlab-ce') + expect(get("/admin/projects/gitlab/subgroup/gitlab-ce")).to route_to('admin/projects#show', namespace_id: 'gitlab/subgroup', id: 'gitlab-ce') end end @@ -119,3 +120,14 @@ describe Admin::HealthCheckController, "routing" do expect(get("/admin/health_check")).to route_to('admin/health_check#show') end end + +describe Admin::GroupsController, "routing" do + it "to #index" do + expect(get("/admin/groups")).to route_to('admin/groups#index') + end + + it "to #show" do + expect(get("/admin/groups/gitlab")).to route_to('admin/groups#show', id: 'gitlab') + expect(get("/admin/groups/gitlab/subgroup")).to route_to('admin/groups#show', id: 'gitlab/subgroup') + end +end diff --git a/spec/serializers/analytics_build_entity_spec.rb b/spec/serializers/analytics_build_entity_spec.rb index 6b33fe66a63..86e703a6448 100644 --- a/spec/serializers/analytics_build_entity_spec.rb +++ b/spec/serializers/analytics_build_entity_spec.rb @@ -13,6 +13,14 @@ describe AnalyticsBuildEntity do subject { entity.as_json } + before do + Timecop.freeze + end + + after do + Timecop.return + end + it 'contains the URL' do expect(subject).to include(:url) end diff --git a/spec/services/system_note_service_spec.rb b/spec/services/system_note_service_spec.rb index 90b7e62bc6f..0e8adb68721 100644 --- a/spec/services/system_note_service_spec.rb +++ b/spec/services/system_note_service_spec.rb @@ -694,7 +694,7 @@ describe SystemNoteService, services: true do describe "existing reference" do before do - message = "[#{author.name}|http://localhost/#{author.username}] mentioned this issue in [a commit of #{project.path_with_namespace}|http://localhost/#{project.path_with_namespace}/commit/#{commit.id}]:\n'#{commit.title}'" + message = "[#{author.name}|http://localhost/#{author.username}] mentioned this issue in [a commit of #{project.path_with_namespace}|http://localhost/#{project.path_with_namespace}/commit/#{commit.id}]:\n'#{commit.title.chomp}'" allow_any_instance_of(JIRA::Resource::Issue).to receive(:comments).and_return([OpenStruct.new(body: message)]) end diff --git a/spec/support/login_helpers.rb b/spec/support/login_helpers.rb index c0b3e83244d..ad1eed5b369 100644 --- a/spec/support/login_helpers.rb +++ b/spec/support/login_helpers.rb @@ -75,7 +75,8 @@ module LoginHelpers def logout find(".header-user-dropdown-toggle").click click_link "Sign out" - expect(page).to have_content('Signed out successfully') + # check the sign_in button + expect(page).to have_button('Sign in') end # Logout without JavaScript driver diff --git a/spec/support/services_shared_context.rb b/spec/support/services_shared_context.rb index d1c999cad4d..66c93890e31 100644 --- a/spec/support/services_shared_context.rb +++ b/spec/support/services_shared_context.rb @@ -16,8 +16,14 @@ Service.available_services_names.each do |service| hash.merge!(k => 'secrettoken') elsif k =~ /^(.*_url|url|webhook)/ hash.merge!(k => "http://example.com") + elsif service_klass.method_defined?("#{k}?") + hash.merge!(k => true) elsif service == 'irker' && k == :recipients hash.merge!(k => 'irc://irc.network.net:666/#channel') + elsif service == 'irker' && k == :server_port + hash.merge!(k => 1234) + elsif service == 'jira' && k == :jira_issue_transition_id + hash.merge!(k => 1234) else hash.merge!(k => "someword") end diff --git a/spec/views/projects/commit/_commit_box.html.haml_spec.rb b/spec/views/projects/commit/_commit_box.html.haml_spec.rb index 16bf0698c4b..e741e3cf9b6 100644 --- a/spec/views/projects/commit/_commit_box.html.haml_spec.rb +++ b/spec/views/projects/commit/_commit_box.html.haml_spec.rb @@ -13,7 +13,7 @@ describe 'projects/commit/_commit_box.html.haml' do it 'shows the commit SHA' do render - expect(rendered).to have_text("Commit #{Commit.truncate_sha(project.commit.sha)}") + expect(rendered).to have_text("#{Commit.truncate_sha(project.commit.sha)}") end it 'shows the last pipeline that ran for the commit' do diff --git a/spec/views/projects/pipelines/show.html.haml_spec.rb b/spec/views/projects/pipelines/show.html.haml_spec.rb index bf027499c94..a066ea078e6 100644 --- a/spec/views/projects/pipelines/show.html.haml_spec.rb +++ b/spec/views/projects/pipelines/show.html.haml_spec.rb @@ -28,7 +28,7 @@ describe 'projects/pipelines/show' do it 'shows a graph with grouped stages' do render - expect(rendered).to have_css('.pipeline-graph') + expect(rendered).to have_css('.js-pipeline-graph') expect(rendered).to have_css('.grouped-pipeline-dropdown') # stages |