diff options
140 files changed, 1788 insertions, 1155 deletions
@@ -68,7 +68,7 @@ gem 'github-linguist', '~> 4.7.0', require: 'linguist' # API gem 'grape', '~> 0.15.0' -gem 'grape-entity', '~> 0.4.2' +gem 'grape-entity', '~> 0.6.0' gem 'rack-cors', '~> 0.4.0', require: 'rack/cors' # Pagination @@ -85,10 +85,8 @@ gem 'dropzonejs-rails', '~> 0.7.1' # for backups gem 'fog-aws', '~> 0.9' -gem 'fog-azure', '~> 0.0' gem 'fog-core', '~> 1.40' gem 'fog-local', '~> 0.3' -gem 'fog-google', '~> 0.3' gem 'fog-openstack', '~> 0.1' gem 'fog-rackspace', '~> 0.1.1' diff --git a/Gemfile.lock b/Gemfile.lock index bdc60552480..bf9702b2562 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -66,21 +66,6 @@ GEM descendants_tracker (~> 0.0.4) ice_nine (~> 0.11.0) thread_safe (~> 0.3, >= 0.3.1) - azure (0.7.5) - addressable (~> 2.3) - azure-core (~> 0.1) - faraday (~> 0.9) - faraday_middleware (~> 0.10) - json (~> 1.8) - mime-types (>= 1, < 3.0) - nokogiri (~> 1.6) - systemu (~> 2.6) - thor (~> 0.19) - uuid (~> 2.0) - azure-core (0.1.2) - faraday (~> 0.9) - faraday_middleware (~> 0.10) - nokogiri (~> 1.6) babel-source (5.8.35) babel-transpiler (0.7.0) babel-source (>= 4.0, < 6) @@ -217,19 +202,10 @@ GEM fog-json (~> 1.0) fog-xml (~> 0.1) ipaddress (~> 0.8) - fog-azure (0.0.2) - azure (~> 0.6) - fog-core (~> 1.27) - fog-json (~> 1.0) - fog-xml (~> 0.1) fog-core (1.42.0) builder excon (~> 0.49) formatador (~> 0.2) - fog-google (0.3.2) - fog-core - fog-json - fog-xml fog-json (1.0.2) fog-core (~> 1.0) multi_json (~> 1.10) @@ -316,7 +292,7 @@ GEM rack-accept rack-mount virtus (>= 1.0.0) - grape-entity (0.4.8) + grape-entity (0.6.0) activesupport multi_json (>= 1.3.2) haml (4.0.7) @@ -397,8 +373,6 @@ GEM rb-inotify (>= 0.9) loofah (2.0.3) nokogiri (>= 1.5.9) - macaddr (1.7.1) - systemu (~> 2.6.2) mail (2.6.4) mime-types (>= 1.16, < 4) mail_room (0.9.0) @@ -728,7 +702,6 @@ GEM sys-filesystem (1.1.6) ffi sysexits (1.2.0) - systemu (2.6.5) teaspoon (1.1.5) railties (>= 3.2.5, < 6) teaspoon-jasmine (2.2.0) @@ -768,8 +741,6 @@ GEM get_process_mem (~> 0) unicorn (>= 4, < 6) uniform_notifier (1.10.0) - uuid (2.3.8) - macaddr (~> 1.0) version_sorter (2.1.0) virtus (1.0.5) axiom-types (~> 0.1) @@ -849,9 +820,7 @@ DEPENDENCIES ffaker (~> 2.0.0) flay (~> 2.6.1) fog-aws (~> 0.9) - fog-azure (~> 0.0) fog-core (~> 1.40) - fog-google (~> 0.3) fog-local (~> 0.3) fog-openstack (~> 0.1) fog-rackspace (~> 0.1.1) @@ -869,7 +838,7 @@ DEPENDENCIES gollum-rugged_adapter (~> 0.4.2) gon (~> 6.1.0) grape (~> 0.15.0) - grape-entity (~> 0.4.2) + grape-entity (~> 0.6.0) haml_lint (~> 0.18.2) hamlit (~> 2.6.1) health_check (~> 2.2.0) diff --git a/app/assets/javascripts/boards/components/board_list.js.es6 b/app/assets/javascripts/boards/components/board_list.js.es6 index 8e91cbfac75..43ebeef39c4 100644 --- a/app/assets/javascripts/boards/components/board_list.js.es6 +++ b/app/assets/javascripts/boards/components/board_list.js.es6 @@ -80,6 +80,7 @@ }, mounted () { const options = gl.issueBoards.getBoardSortableDefaultOptions({ + scroll: document.querySelectorAll('.boards-list')[0], group: 'issues', sort: false, disabled: this.disabled, @@ -88,13 +89,13 @@ const card = this.$refs.issue[e.oldIndex]; card.showDetail = false; - Store.moving.issue = card.issue; Store.moving.list = card.list; + Store.moving.issue = Store.moving.list.findIssue(+e.item.dataset.issueId); gl.issueBoards.onStart(); }, onAdd: (e) => { - gl.issueBoards.BoardsStore.moveIssueToList(Store.moving.list, this.list, Store.moving.issue); + gl.issueBoards.BoardsStore.moveIssueToList(Store.moving.list, this.list, Store.moving.issue, e.newIndex); this.$nextTick(() => { e.item.remove(); diff --git a/app/assets/javascripts/boards/models/list.js.es6 b/app/assets/javascripts/boards/models/list.js.es6 index 8a7dc67409e..429bd27c3fb 100644 --- a/app/assets/javascripts/boards/models/list.js.es6 +++ b/app/assets/javascripts/boards/models/list.js.es6 @@ -106,9 +106,13 @@ class List { }); } - addIssue (issue, listFrom) { + addIssue (issue, listFrom, newIndex) { if (!this.findIssue(issue.id)) { - this.issues.push(issue); + if (newIndex !== undefined) { + this.issues.splice(newIndex, 0, issue); + } else { + this.issues.push(issue); + } if (this.label) { issue.addLabel(this.label); diff --git a/app/assets/javascripts/boards/stores/boards_store.js.es6 b/app/assets/javascripts/boards/stores/boards_store.js.es6 index 6bc95aa60f2..bb2a4de8b18 100644 --- a/app/assets/javascripts/boards/stores/boards_store.js.es6 +++ b/app/assets/javascripts/boards/stores/boards_store.js.es6 @@ -89,14 +89,14 @@ }); listFrom.update(); }, - moveIssueToList (listFrom, listTo, issue) { + moveIssueToList (listFrom, listTo, issue, newIndex) { const issueTo = listTo.findIssue(issue.id), issueLists = issue.getLists(), listLabels = issueLists.map( listIssue => listIssue.label ); // Add to new lists issues if it doesn't already exist if (!issueTo) { - listTo.addIssue(issue, listFrom); + listTo.addIssue(issue, listFrom, newIndex); } if (listTo.type === 'done' && listFrom.type !== 'backlog') { diff --git a/app/assets/javascripts/commits.js b/app/assets/javascripts/commits.js index 951fb338f9d..3627aaf5080 100644 --- a/app/assets/javascripts/commits.js +++ b/app/assets/javascripts/commits.js @@ -1,4 +1,4 @@ -/* eslint-disable func-names, space-before-function-paren, wrap-iife, quotes, consistent-return, no-undef, no-return-assign, no-param-reassign, one-var, no-var, one-var-declaration-per-line, no-unused-vars, prefer-template, object-shorthand, comma-dangle, padded-blocks, max-len */ +/* eslint-disable func-names, space-before-function-paren, wrap-iife, quotes, consistent-return, no-undef, no-return-assign, no-param-reassign, one-var, no-var, one-var-declaration-per-line, no-unused-vars, prefer-template, object-shorthand, comma-dangle, padded-blocks, max-len, prefer-arrow-callback */ (function() { this.CommitsList = (function() { function CommitsList() {} @@ -13,7 +13,9 @@ return false; } }); - Pager.init(limit, false); + Pager.init(limit, false, false, function() { + gl.utils.localTimeAgo($('.js-timeago')); + }); this.content = $("#commits-list"); this.searchField = $("#commits-search"); return this.initSearch(); diff --git a/app/assets/javascripts/cycle_analytics/cycle_analytics_store.js.es6 b/app/assets/javascripts/cycle_analytics/cycle_analytics_store.js.es6 index 9b905874167..be732971c7f 100644 --- a/app/assets/javascripts/cycle_analytics/cycle_analytics_store.js.es6 +++ b/app/assets/javascripts/cycle_analytics/cycle_analytics_store.js.es6 @@ -62,9 +62,11 @@ this.state.events = this.decorateEvents(events); }, decorateEvents(events) { - const newEvents = events; + const newEvents = []; + + events.forEach((item) => { + if (!item) return; - newEvents.forEach((item) => { item.totalTime = item.total_time; item.author.webUrl = item.author.web_url; item.author.avatarUrl = item.author.avatar_url; @@ -79,6 +81,8 @@ delete item.created_at; delete item.short_sha; delete item.commit_url; + + newEvents.push(item); }); return newEvents; diff --git a/app/assets/javascripts/diff_notes/models/discussion.js.es6 b/app/assets/javascripts/diff_notes/models/discussion.js.es6 index 439f55520ef..badcdccc840 100644 --- a/app/assets/javascripts/diff_notes/models/discussion.js.es6 +++ b/app/assets/javascripts/diff_notes/models/discussion.js.es6 @@ -57,14 +57,17 @@ class DiscussionModel { } updateHeadline (data) { - const $discussionHeadline = $(`.discussion[data-discussion-id="${this.id}"] .js-discussion-headline`); + const discussionSelector = `.discussion[data-discussion-id="${this.id}"]`; + const $discussionHeadline = $(`${discussionSelector} .js-discussion-headline`); if (data.discussion_headline_html) { if ($discussionHeadline.length) { $discussionHeadline.replaceWith(data.discussion_headline_html); } else { - $(`.discussion[data-discussion-id="${this.id}"] .discussion-header`).append(data.discussion_headline_html); + $(`${discussionSelector} .discussion-header`).append(data.discussion_headline_html); } + + gl.utils.localTimeAgo($('.js-timeago', `${discussionSelector}`)); } else { $discussionHeadline.remove(); } @@ -74,7 +77,7 @@ class DiscussionModel { if (!this.canResolve) { return false; } - + for (const noteId in this.notes) { const note = this.notes[noteId]; diff --git a/app/assets/javascripts/gfm_auto_complete.js.es6 b/app/assets/javascripts/gfm_auto_complete.js.es6 index 5d9ac4d350a..89fe13b7a45 100644 --- a/app/assets/javascripts/gfm_auto_complete.js.es6 +++ b/app/assets/javascripts/gfm_auto_complete.js.es6 @@ -53,6 +53,26 @@ } else { return value; } + }, + matcher: function (flag, subtext) { + // The below is taken from At.js source + // Tweaked to commands to start without a space only if char before is a non-word character + // https://github.com/ichord/At.js + var _a, _y, regexp, match; + flag = flag.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&"); + + _a = decodeURI("%C3%80"); + _y = decodeURI("%C3%BF"); + + regexp = new RegExp("(?:\\B|\\W|\\s)" + flag + "([A-Za-z" + _a + "-" + _y + "0-9_\'\.\+\-]*)|([^\\x00-\\xff]*)$", 'gi'); + + match = regexp.exec(subtext); + + if (match) { + return match[2] || match[1]; + } else { + return null; + } } }, setup: _.debounce(function(input) { @@ -91,10 +111,12 @@ })(this), insertTpl: ':${name}:', data: ['loading'], + startWithSpace: false, callbacks: { sorter: this.DefaultOptions.sorter, filter: this.DefaultOptions.filter, - beforeInsert: this.DefaultOptions.beforeInsert + beforeInsert: this.DefaultOptions.beforeInsert, + matcher: this.DefaultOptions.matcher } }); // Team Members @@ -112,11 +134,13 @@ insertTpl: '${atwho-at}${username}', searchKey: 'search', data: ['loading'], + startWithSpace: false, alwaysHighlightFirst: true, callbacks: { sorter: this.DefaultOptions.sorter, filter: this.DefaultOptions.filter, beforeInsert: this.DefaultOptions.beforeInsert, + matcher: this.DefaultOptions.matcher, beforeSave: function(members) { return $.map(members, function(m) { let title = ''; @@ -157,10 +181,12 @@ })(this), data: ['loading'], insertTpl: '${atwho-at}${id}', + startWithSpace: false, callbacks: { sorter: this.DefaultOptions.sorter, filter: this.DefaultOptions.filter, beforeInsert: this.DefaultOptions.beforeInsert, + matcher: this.DefaultOptions.matcher, beforeSave: function(issues) { return $.map(issues, function(i) { if (i.title == null) { @@ -190,7 +216,9 @@ })(this), insertTpl: '${atwho-at}"${title}"', data: ['loading'], + startWithSpace: false, callbacks: { + matcher: this.DefaultOptions.matcher, sorter: this.DefaultOptions.sorter, beforeSave: function(milestones) { return $.map(milestones, function(m) { @@ -220,11 +248,13 @@ }; })(this), data: ['loading'], + startWithSpace: false, insertTpl: '${atwho-at}${id}', callbacks: { sorter: this.DefaultOptions.sorter, filter: this.DefaultOptions.filter, beforeInsert: this.DefaultOptions.beforeInsert, + matcher: this.DefaultOptions.matcher, beforeSave: function(merges) { return $.map(merges, function(m) { if (m.title == null) { @@ -245,7 +275,9 @@ searchKey: 'search', displayTpl: this.Labels.template, insertTpl: '${atwho-at}${title}', + startWithSpace: false, callbacks: { + matcher: this.DefaultOptions.matcher, sorter: this.DefaultOptions.sorter, beforeSave: function(merges) { var sanitizeLabelTitle; diff --git a/app/assets/javascripts/notes.js b/app/assets/javascripts/notes.js index a84c514dac7..47e7b6f831b 100644 --- a/app/assets/javascripts/notes.js +++ b/app/assets/javascripts/notes.js @@ -333,7 +333,7 @@ gl.diffNotesCompileComponents(); } - gl.utils.localTimeAgo($('.js-timeago', note_html), false); + gl.utils.localTimeAgo($('.js-timeago'), false); return this.updateNotesCount(1); }; diff --git a/app/assets/stylesheets/pages/boards.scss b/app/assets/stylesheets/pages/boards.scss index 4f5753f6fc6..4327f8bf640 100644 --- a/app/assets/stylesheets/pages/boards.scss +++ b/app/assets/stylesheets/pages/boards.scss @@ -243,7 +243,7 @@ } .issue-boards-search { - width: 335px; + width: 290px; .form-control { display: inline-block; diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 2713c0f1dc8..bcc0b17bce2 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -49,6 +49,14 @@ class ApplicationController < ActionController::Base render_404 end + def route_not_found + if current_user + not_found + else + redirect_to new_user_session_path + end + end + protected # This filter handles both private tokens and personal access tokens diff --git a/app/controllers/projects/notes_controller.rb b/app/controllers/projects/notes_controller.rb index f029fde2a2f..15ca080c696 100644 --- a/app/controllers/projects/notes_controller.rb +++ b/app/controllers/projects/notes_controller.rb @@ -197,6 +197,7 @@ class Projects::NotesController < Projects::ApplicationController ) end + attrs[:commands_changes] = note.commands_changes unless attrs[:award] attrs end diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb index 3fee6c18770..4294a10e9e3 100644 --- a/app/models/ci/pipeline.rb +++ b/app/models/ci/pipeline.rb @@ -161,23 +161,27 @@ module Ci end def retryable? - builds.latest.any? do |build| - (build.failed? || build.canceled?) && build.retryable? - end + builds.latest.failed_or_canceled.any?(&:retryable?) end def cancelable? - builds.running_or_pending.any? + statuses.cancelable.any? end def cancel_running - builds.running_or_pending.each(&:cancel) + Gitlab::OptimisticLocking.retry_lock( + statuses.cancelable) do |cancelable| + cancelable.each(&:cancel) + end end def retry_failed(user) - builds.latest.failed.select(&:retryable?).each do |build| - Ci::Build.retry(build, user) - end + Gitlab::OptimisticLocking.retry_lock( + builds.latest.failed_or_canceled) do |failed_or_canceled| + failed_or_canceled.select(&:retryable?).each do |build| + Ci::Build.retry(build, user) + end + end end def mark_as_processable_after_stage(stage_idx) diff --git a/app/models/concerns/has_status.rb b/app/models/concerns/has_status.rb index ef3e73a4072..2f5aa91a964 100644 --- a/app/models/concerns/has_status.rb +++ b/app/models/concerns/has_status.rb @@ -73,6 +73,11 @@ module HasStatus scope :skipped, -> { where(status: 'skipped') } scope :running_or_pending, -> { where(status: [:running, :pending]) } scope :finished, -> { where(status: [:success, :failed, :canceled]) } + scope :failed_or_canceled, -> { where(status: [:failed, :canceled]) } + + scope :cancelable, -> do + where(status: [:running, :pending, :created]) + end end def started? diff --git a/app/models/namespace.rb b/app/models/namespace.rb index 99da26a89fb..891dffac648 100644 --- a/app/models/namespace.rb +++ b/app/models/namespace.rb @@ -104,6 +104,8 @@ class Namespace < ActiveRecord::Base gitlab_shell.add_namespace(repository_storage_path, path_was) unless gitlab_shell.mv_namespace(repository_storage_path, path_was, path) + Rails.logger.error "Exception moving path #{repository_storage_path} from #{path_was} to #{path}" + # if we cannot move namespace directory we should rollback # db changes in order to prevent out of sync between db and fs raise Exception.new('namespace directory cannot be moved') diff --git a/app/models/note.rb b/app/models/note.rb index 9ff5e308ed2..ed4224e3046 100644 --- a/app/models/note.rb +++ b/app/models/note.rb @@ -19,6 +19,9 @@ class Note < ActiveRecord::Base # Banzai::ObjectRenderer attr_accessor :user_visible_reference_count + # Attribute used to store the attributes that have ben changed by slash commands. + attr_accessor :commands_changes + default_value_for :system, false attr_mentionable :note, pipeline: :note diff --git a/app/models/project.rb b/app/models/project.rb index bd9fcb2f3b7..9256e9ddd95 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -177,6 +177,7 @@ class Project < ActiveRecord::Base message: Gitlab::Regex.project_name_regex_message } validates :path, presence: true, + project_path: true, length: { within: 0..255 }, format: { with: Gitlab::Regex.project_path_regex, message: Gitlab::Regex.project_path_regex_message } diff --git a/app/models/user.rb b/app/models/user.rb index 223d84ba916..513a19d81d2 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -291,8 +291,12 @@ class User < ActiveRecord::Base end end + def find_by_username(username) + iwhere(username: username).take + end + def find_by_username!(username) - find_by!('lower(username) = ?', username.downcase) + iwhere(username: username).take! end def find_by_personal_access_token(token_string) diff --git a/app/services/notes/create_service.rb b/app/services/notes/create_service.rb index 7935fabe2da..d75592e31f3 100644 --- a/app/services/notes/create_service.rb +++ b/app/services/notes/create_service.rb @@ -43,6 +43,8 @@ module Notes if only_commands note.errors.add(:commands_only, 'Your commands have been executed!') end + + note.commands_changes = command_params.keys end note diff --git a/app/services/notification_service.rb b/app/services/notification_service.rb index ecdcbf08ee1..9a7af5730d2 100644 --- a/app/services/notification_service.rb +++ b/app/services/notification_service.rb @@ -171,7 +171,6 @@ class NotificationService return true unless note.noteable_type.present? # ignore gitlab service messages - return true if note.note.start_with?('Status changed to closed') return true if note.cross_reference? && note.system? target = note.noteable diff --git a/app/services/system_note_service.rb b/app/services/system_note_service.rb index 1ce66d50368..a33845848b4 100644 --- a/app/services/system_note_service.rb +++ b/app/services/system_note_service.rb @@ -21,7 +21,7 @@ module SystemNoteService total_count = new_commits.length + existing_commits.length commits_text = "#{total_count} commit".pluralize(total_count) - body = "Added #{commits_text}:\n\n" + body = "added #{commits_text}\n\n" body << existing_commit_summary(noteable, existing_commits, oldrev) body << new_commit_summary(new_commits).join("\n") body << "\n\n[Compare with previous version](#{diff_comparison_url(noteable, project, oldrev)})" @@ -38,13 +38,13 @@ module SystemNoteService # # Example Note text: # - # "Assignee removed" + # "removed assignee" # - # "Reassigned to @rspeicher" + # "assigned to @rspeicher" # # Returns the created Note object def change_assignee(noteable, project, author, assignee) - body = assignee.nil? ? 'Assignee removed' : "Reassigned to #{assignee.to_reference}" + body = assignee.nil? ? 'removed assignee' : "assigned to #{assignee.to_reference}" create_note(noteable: noteable, project: project, author: author, note: body) end @@ -59,11 +59,11 @@ module SystemNoteService # # Example Note text: # - # "Added ~1 and removed ~2 ~3 labels" + # "added ~1 and removed ~2 ~3 labels" # - # "Added ~4 label" + # "added ~4 label" # - # "Removed ~5 label" + # "removed ~5 label" # # Returns the created Note object def change_label(noteable, project, author, added_labels, removed_labels) @@ -85,7 +85,6 @@ module SystemNoteService end body << ' ' << 'label'.pluralize(labels_count) - body = body.capitalize create_note(noteable: noteable, project: project, author: author, note: body) end @@ -99,14 +98,13 @@ module SystemNoteService # # Example Note text: # - # "Milestone removed" + # "removed milestone" # - # "Miletone changed to 7.11" + # "changed milestone to 7.11" # # Returns the created Note object def change_milestone(noteable, project, author, milestone) - body = 'Milestone ' - body += milestone.nil? ? 'removed' : "changed to #{milestone.to_reference(project)}" + body = milestone.nil? ? 'removed milestone' : "changed milestone to #{milestone.to_reference(project)}" create_note(noteable: noteable, project: project, author: author, note: body) end @@ -121,46 +119,46 @@ module SystemNoteService # # Example Note text: # - # "Status changed to merged" + # "merged" # - # "Status changed to closed by bc17db76" + # "closed via bc17db76" # # Returns the created Note object def change_status(noteable, project, author, status, source) - body = "Status changed to #{status}" - body << " by #{source.gfm_reference(project)}" if source + body = status.dup + body << " via #{source.gfm_reference(project)}" if source create_note(noteable: noteable, project: project, author: author, note: body) end # Called when 'merge when build succeeds' is executed def merge_when_build_succeeds(noteable, project, author, last_commit) - body = "Enabled an automatic merge when the build for #{last_commit.to_reference(project)} succeeds" + body = "enabled an automatic merge when the build for #{last_commit.to_reference(project)} succeeds" create_note(noteable: noteable, project: project, author: author, note: body) end # Called when 'merge when build succeeds' is canceled def cancel_merge_when_build_succeeds(noteable, project, author) - body = 'Canceled the automatic merge' + body = 'canceled the automatic merge' create_note(noteable: noteable, project: project, author: author, note: body) end def remove_merge_request_wip(noteable, project, author) - body = 'Unmarked this merge request as a Work In Progress' + body = 'unmarked as a Work In Progress' create_note(noteable: noteable, project: project, author: author, note: body) end def add_merge_request_wip(noteable, project, author) - body = 'Marked this merge request as a **Work In Progress**' + body = 'marked as a **Work In Progress**' create_note(noteable: noteable, project: project, author: author, note: body) end def self.resolve_all_discussions(merge_request, project, author) - body = "Resolved all discussions" + body = "resolved all discussions" create_note(noteable: merge_request, project: project, author: author, note: body) end @@ -174,7 +172,7 @@ module SystemNoteService # # Example Note text: # - # "Title changed from **Old** to **New**" + # "changed title from **Old** to **New**" # # Returns the created Note object def change_title(noteable, project, author, old_title) @@ -185,7 +183,7 @@ module SystemNoteService marked_old_title = Gitlab::Diff::InlineDiffMarker.new(old_title).mark(old_diffs, mode: :deletion, markdown: true) marked_new_title = Gitlab::Diff::InlineDiffMarker.new(new_title).mark(new_diffs, mode: :addition, markdown: true) - body = "Changed title: **#{marked_old_title}** → **#{marked_new_title}**" + body = "changed title from **#{marked_old_title}** to **#{marked_new_title}**" create_note(noteable: noteable, project: project, author: author, note: body) end @@ -197,11 +195,11 @@ module SystemNoteService # # Example Note text: # - # "Made the issue confidential" + # "made the issue confidential" # # Returns the created Note object def change_issue_confidentiality(issue, project, author) - body = issue.confidential ? 'Made the issue confidential' : 'Made the issue visible' + body = issue.confidential ? 'made the issue confidential' : 'made the issue visible to everyone' create_note(noteable: issue, project: project, author: author, note: body) end @@ -216,11 +214,11 @@ module SystemNoteService # # Example Note text: # - # "Target branch changed from `Old` to `New`" + # "changed target branch from `Old` to `New`" # # Returns the created Note object def change_branch(noteable, project, author, branch_type, old_branch, new_branch) - body = "#{branch_type} branch changed from `#{old_branch}` to `#{new_branch}`".capitalize + body = "changed #{branch_type} branch from `#{old_branch}` to `#{new_branch}`" create_note(noteable: noteable, project: project, author: author, note: body) end @@ -235,7 +233,7 @@ module SystemNoteService # # Example Note text: # - # "Restored target branch `feature`" + # "restored target branch `feature`" # # Returns the created Note object def change_branch_presence(noteable, project, author, branch_type, branch, presence) @@ -246,18 +244,18 @@ module SystemNoteService 'deleted' end - body = "#{verb} #{branch_type} branch `#{branch}`".capitalize + body = "#{verb} #{branch_type} branch `#{branch}`" create_note(noteable: noteable, project: project, author: author, note: body) end # Called when a branch is created from the 'new branch' button on a issue # Example note text: # - # "Started branch `201-issue-branch-button`" + # "created branch `201-issue-branch-button`" def new_issue_branch(issue, project, author, branch) link = url_helpers.namespace_project_compare_url(project.namespace, project, from: project.default_branch, to: branch) - body = "Started branch [`#{branch}`](#{link})" + body = "created branch [`#{branch}`](#{link})" create_note(noteable: issue, project: project, author: author, note: body) end @@ -269,11 +267,11 @@ module SystemNoteService # # Example Note text: # - # "Mentioned in #1" + # "mentioned in #1" # - # "Mentioned in !2" + # "mentioned in !2" # - # "Mentioned in 54f7727c" + # "mentioned in 54f7727c" # # See cross_reference_note_content. # @@ -303,12 +301,12 @@ module SystemNoteService end def cross_reference?(note_text) - note_text.start_with?(cross_reference_note_prefix) + note_text =~ /\A#{cross_reference_note_prefix}/i end # Check if a cross-reference is disallowed # - # This method prevents adding a "Mentioned in !1" note on every single commit + # This method prevents adding a "mentioned in !1" note on every single commit # in a merge request. Additionally, it prevents the creation of references to # external issues (which would fail). # @@ -370,12 +368,12 @@ module SystemNoteService # # Example Note text: # - # "Soandso marked the task Whatever as completed." + # "marked the task Whatever as completed." # # Returns the created Note object def change_task_status(noteable, project, author, new_task) status_label = new_task.complete? ? Taskable::COMPLETED : Taskable::INCOMPLETE - body = "Marked the task **#{new_task.source}** as #{status_label}" + body = "marked the task **#{new_task.source}** as #{status_label}" create_note(noteable: noteable, project: project, author: author, note: body) end @@ -388,7 +386,7 @@ module SystemNoteService # # Example Note text: # - # "Moved to some_namespace/project_new#11" + # "moved to some_namespace/project_new#11" # # Returns the created Note object def noteable_moved(noteable, project, noteable_ref, author, direction:) @@ -397,7 +395,7 @@ module SystemNoteService end cross_reference = noteable_ref.to_reference(project) - body = "Moved #{direction} #{cross_reference}" + body = "moved #{direction} #{cross_reference}" create_note(noteable: noteable, project: project, author: author, note: body) end @@ -405,10 +403,12 @@ module SystemNoteService def notes_for_mentioner(mentioner, noteable, notes) if mentioner.is_a?(Commit) - notes.where('note LIKE ?', "#{cross_reference_note_prefix}%#{mentioner.to_reference(nil)}") + text = "#{cross_reference_note_prefix}%#{mentioner.to_reference(nil)}" + notes.where('(note LIKE ? OR note LIKE ?)', text, text.capitalize) else gfm_reference = mentioner.gfm_reference(noteable.project) - notes.where(note: cross_reference_note_content(gfm_reference)) + text = cross_reference_note_content(gfm_reference) + notes.where(note: [text, text.capitalize]) end end @@ -417,7 +417,7 @@ module SystemNoteService end def cross_reference_note_prefix - 'Mentioned in ' + 'mentioned in ' end def cross_reference_note_content(gfm_reference) diff --git a/app/validators/namespace_validator.rb b/app/validators/namespace_validator.rb index 2821ecf0a88..eb3ed31b65b 100644 --- a/app/validators/namespace_validator.rb +++ b/app/validators/namespace_validator.rb @@ -35,8 +35,22 @@ class NamespaceValidator < ActiveModel::EachValidator users ].freeze + def self.valid?(value) + !reserved?(value) && follow_format?(value) + end + + def self.reserved?(value) + RESERVED.include?(value) + end + + def self.follow_format?(value) + value =~ Gitlab::Regex.namespace_regex + end + + delegate :reserved?, :follow_format?, to: :class + def validate_each(record, attribute, value) - unless value =~ Gitlab::Regex.namespace_regex + unless follow_format?(value) record.errors.add(attribute, Gitlab::Regex.namespace_regex_message) end @@ -44,10 +58,4 @@ class NamespaceValidator < ActiveModel::EachValidator record.errors.add(attribute, "#{value} is a reserved name") end end - - private - - def reserved?(value) - RESERVED.include?(value) - end end diff --git a/app/validators/project_path_validator.rb b/app/validators/project_path_validator.rb new file mode 100644 index 00000000000..927c67b65b0 --- /dev/null +++ b/app/validators/project_path_validator.rb @@ -0,0 +1,36 @@ +# ProjectPathValidator +# +# Custom validator for GitLab project path values. +# +# Values are checked for formatting and exclusion from a list of reserved path +# names. +class ProjectPathValidator < ActiveModel::EachValidator + # All project routes with wildcard argument must be listed here. + # Otherwise it can lead to routing issues when route considered as project name. + # + # Example: + # /group/project/tree/deploy_keys + # + # without tree as reserved name routing can match 'group/project' as group name, + # 'tree' as project name and 'deploy_keys' as route. + # + RESERVED = (NamespaceValidator::RESERVED + + %w[tree commits wikis new edit create update logs_tree + preview blob blame raw files create_dir find_file]).freeze + + def self.valid?(value) + !reserved?(value) + end + + def self.reserved?(value) + RESERVED.include?(value) + end + + delegate :reserved?, to: :class + + def validate_each(record, attribute, value) + if reserved?(value) + record.errors.add(attribute, "#{value} is a reserved name") + end + end +end diff --git a/app/views/discussions/_discussion.html.haml b/app/views/discussions/_discussion.html.haml index e4b4ea675d2..2bce2780484 100644 --- a/app/views/discussions/_discussion.html.haml +++ b/app/views/discussions/_discussion.html.haml @@ -32,6 +32,7 @@ an outdated diff = time_ago_with_tooltip(discussion.created_at, placement: "bottom", html_class: "note-created-ago") + = render "discussions/headline", discussion: discussion .discussion-body.js-toggle-content{ class: ("hide" unless expanded) } - if discussion.diff_discussion? && discussion.diff_file diff --git a/app/views/profiles/update_username.js.haml b/app/views/profiles/update_username.js.haml index de1337a2a24..5307e0b48cb 100644 --- a/app/views/profiles/update_username.js.haml +++ b/app/views/profiles/update_username.js.haml @@ -2,5 +2,6 @@ :plain new Flash("Username successfully changed", "notice") - else + - error = @user.errors.full_messages.first :plain - new Flash("Username change failed - #{@user.errors.full_messages.first}", "alert") + new Flash("Username change failed - #{escape_javascript error.html_safe}", "alert") diff --git a/app/views/projects/boards/components/_card.html.haml b/app/views/projects/boards/components/_card.html.haml index 34effac17b2..1f31496e73f 100644 --- a/app/views/projects/boards/components/_card.html.haml +++ b/app/views/projects/boards/components/_card.html.haml @@ -1,5 +1,6 @@ %li.card{ ":class" => '{ "user-can-drag": !disabled && issue.id, "is-disabled": disabled || !issue.id, "is-active": issueDetailVisible }', ":index" => "index", + ":data-issue-id" => "issue.id", "@mousedown" => "mouseDown", "@mousemove" => "mouseMove", "@mouseup" => "showIssue($event)" } diff --git a/app/views/projects/builds/show.html.haml b/app/views/projects/builds/show.html.haml index d8cbfd7173a..108674dbba6 100644 --- a/app/views/projects/builds/show.html.haml +++ b/app/views/projects/builds/show.html.haml @@ -40,13 +40,12 @@ This build is the most recent deployment to #{environment_link_for_build(@build.project, @build)}. - else This build is an out-of-date deployment to #{environment_link_for_build(@build.project, @build)}. - - if environment.last_deployment - View the most recent deployment #{deployment_link(environment.last_deployment)}. + View the most recent deployment #{deployment_link(environment.last_deployment)}. - elsif @build.complete? && !@build.success? The deployment of this build to #{environment_link_for_build(@build.project, @build)} did not succeed. - else This build is creating a deployment to #{environment_link_for_build(@build.project, @build)} - - if environment.last_deployment + - if environment.try(:last_deployment) and will overwrite the = link_to 'latest deployment', deployment_link(environment.last_deployment) diff --git a/app/views/projects/edit.html.haml b/app/views/projects/edit.html.haml index 0aa8801c2d8..3a5af2723c6 100644 --- a/app/views/projects/edit.html.haml +++ b/app/views/projects/edit.html.haml @@ -92,14 +92,15 @@ = project_feature_access_select(:wiki_access_level) - if Gitlab.config.lfs.enabled && current_user.admin? - .checkbox - = f.label :lfs_enabled do - = f.check_box :lfs_enabled - %strong LFS - %br - %span.descr + .row + .col-md-9 + = f.label :lfs_enabled, 'LFS', class: 'label-light' + %span.help-block Git Large File Storage = link_to icon('question-circle'), help_page_path('workflow/lfs/manage_large_binaries_with_git_lfs') + .col-md-3 + = f.select :lfs_enabled, [%w(Enabled true), %w(Disabled false)], {}, selected: @project.lfs_enabled?, class: 'pull-right form-control', data: { field: 'lfs_enabled' } + - if Gitlab.config.registry.enabled .form-group.js-container-registry{ style: ("display: none;" if @project.project_feature.send(:repository_access_level) == 0) } diff --git a/app/views/projects/tags/index.html.haml b/app/views/projects/tags/index.html.haml index 7a0d9dcc94f..b43b13de4ca 100644 --- a/app/views/projects/tags/index.html.haml +++ b/app/views/projects/tags/index.html.haml @@ -10,15 +10,16 @@ .nav-controls = form_tag(filter_tags_path, method: :get) do = search_field_tag :search, params[:search], { placeholder: 'Filter by tag name', id: 'tag-search', class: 'form-control search-text-input input-short', spellcheck: false } + .dropdown.inline %button.dropdown-toggle.btn{ type: 'button', data: { toggle: 'dropdown'} } %span.light - = @sort.humanize + = projects_sort_options_hash[@sort] = icon('caret-down') %ul.dropdown-menu.dropdown-menu-align-right %li - = link_to filter_tags_path(sort: nil) do - Name + = link_to filter_tags_path(sort: sort_value_name) do + = sort_title_name = link_to filter_tags_path(sort: sort_value_recently_updated) do = sort_title_recently_updated = link_to filter_tags_path(sort: sort_value_oldest_updated) do diff --git a/app/views/projects/wikis/_nav.html.haml b/app/views/projects/wikis/_nav.html.haml index 09c4411d67e..afdef70e1cf 100644 --- a/app/views/projects/wikis/_nav.html.haml +++ b/app/views/projects/wikis/_nav.html.haml @@ -7,7 +7,7 @@ = link_to 'Home', namespace_project_wiki_path(@project.namespace, @project, :home) = nav_link(path: 'wikis#pages') do - = link_to 'Pages', namespace_project_wiki_pages_path(@project.namespace, @project) + = link_to 'Pages', namespace_project_wikis_pages_path(@project.namespace, @project) = nav_link(path: 'wikis#git_access') do = link_to namespace_project_wikis_git_access_path(@project.namespace, @project) do diff --git a/app/views/shared/issuable/_filter.html.haml b/app/views/shared/issuable/_filter.html.haml index ed93857e6d4..b7e5e928993 100644 --- a/app/views/shared/issuable/_filter.html.haml +++ b/app/views/shared/issuable/_filter.html.haml @@ -40,9 +40,9 @@ - if can?(current_user, :admin_list, @project) .dropdown.pull-right %button.btn.btn-create.js-new-board-list{ type: "button", data: { toggle: "dropdown", labels: labels_filter_path, namespace_path: @project.try(:namespace).try(:path), project_path: @project.try(:path) } } - Create new list + Add list .dropdown-menu.dropdown-menu-paging.dropdown-menu-align-right.dropdown-menu-issues-board-new.dropdown-menu-selectable - = render partial: "shared/issuable/label_page_default", locals: { show_footer: true, show_create: true, show_boards_content: true, title: "Create a new list" } + = render partial: "shared/issuable/label_page_default", locals: { show_footer: true, show_create: true, show_boards_content: true, title: "Add list" } - if can?(current_user, :admin_label, @project) = render partial: "shared/issuable/label_page_create" = dropdown_loading diff --git a/app/views/shared/issuable/_label_page_default.html.haml b/app/views/shared/issuable/_label_page_default.html.haml index c0dc63be2bf..a8f01026ca5 100644 --- a/app/views/shared/issuable/_label_page_default.html.haml +++ b/app/views/shared/issuable/_label_page_default.html.haml @@ -1,17 +1,15 @@ - title = local_assigns.fetch(:title, 'Assign labels') - show_create = local_assigns.fetch(:show_create, true) - show_footer = local_assigns.fetch(:show_footer, true) -- filter_placeholder = local_assigns.fetch(:filter_placeholder, 'Search labels') +- filter_placeholder = local_assigns.fetch(:filter_placeholder, 'Search') - show_boards_content = local_assigns.fetch(:show_boards_content, false) .dropdown-page-one = dropdown_title(title) - if show_boards_content .issue-board-dropdown-content %p - Each label that exists in your issue tracker can have its own dedicated - list. Select a label below to add a list to your Board and it will - automatically be populated with issues that have that label. To create - a list for a label that doesn't exist yet, simply create the label below. + Create lists from the labels you use in your project. Issues with that + label will automatically be added to the list. = dropdown_filter(filter_placeholder) = dropdown_content - if @project && show_footer diff --git a/app/views/shared/issuable/_sidebar.html.haml b/app/views/shared/issuable/_sidebar.html.haml index f166fac105d..02427650219 100644 --- a/app/views/shared/issuable/_sidebar.html.haml +++ b/app/views/shared/issuable/_sidebar.html.haml @@ -9,12 +9,12 @@ %a.gutter-toggle.pull-right.js-sidebar-toggle{ role: "button", href: "#", aria: { label: "Toggle sidebar" } } = sidebar_gutter_toggle_icon - if current_user - %button.btn.btn-default.issuable-header-btn.pull-right.js-issuable-todo{ type: "button", aria: { label: (todo.nil? ? "Add Todo" : "Mark Done") }, data: { todo_text: "Add Todo", mark_text: "Mark Done", issuable_id: issuable.id, issuable_type: issuable.class.name.underscore, url: namespace_project_todos_path(@project.namespace, @project), delete_path: (dashboard_todo_path(todo) if todo) } } + %button.btn.btn-default.issuable-header-btn.pull-right.js-issuable-todo{ type: "button", aria: { label: (todo.nil? ? "Add todo" : "Mark done") }, data: { todo_text: "Add todo", mark_text: "Mark done", issuable_id: issuable.id, issuable_type: issuable.class.name.underscore, url: namespace_project_todos_path(@project.namespace, @project), delete_path: (dashboard_todo_path(todo) if todo) } } %span.js-issuable-todo-text - if todo - Mark Done + Mark done - else - Add Todo + Add todo = icon('spin spinner', class: 'hidden js-issuable-todo-loading') = form_for [@project.namespace.becomes(Namespace), @project, issuable], remote: true, format: :json, html: {class: 'issuable-context-form inline-update js-issuable-update'} do |f| diff --git a/changelogs/unreleased/24779-last-deployment-call-on-nil-environment-fix.yml b/changelogs/unreleased/24779-last-deployment-call-on-nil-environment-fix.yml new file mode 100644 index 00000000000..5e7580fb8f2 --- /dev/null +++ b/changelogs/unreleased/24779-last-deployment-call-on-nil-environment-fix.yml @@ -0,0 +1,4 @@ +--- +title: fixes last_deployment call environment is nil +merge_request: 7671 +author: diff --git a/changelogs/unreleased/boards-issue-sorting.yml b/changelogs/unreleased/boards-issue-sorting.yml new file mode 100644 index 00000000000..fb7dc2f9190 --- /dev/null +++ b/changelogs/unreleased/boards-issue-sorting.yml @@ -0,0 +1,4 @@ +--- +title: Fixed issue boards issue sorting when dragging issue into list +merge_request: +author: diff --git a/changelogs/unreleased/dz-allow-nested-group-routing.yml b/changelogs/unreleased/dz-allow-nested-group-routing.yml new file mode 100644 index 00000000000..9d8e6e17914 --- /dev/null +++ b/changelogs/unreleased/dz-allow-nested-group-routing.yml @@ -0,0 +1,4 @@ +--- +title: Add nested groups support to the routing +merge_request: 7459 +author: diff --git a/changelogs/unreleased/fix-cancelling-pipelines.yml b/changelogs/unreleased/fix-cancelling-pipelines.yml new file mode 100644 index 00000000000..c21e663093a --- /dev/null +++ b/changelogs/unreleased/fix-cancelling-pipelines.yml @@ -0,0 +1,4 @@ +--- +title: Fix cancelling created or external pipelines +merge_request: 7508 +author: diff --git a/changelogs/unreleased/fixed-commit-timeago.yml b/changelogs/unreleased/fixed-commit-timeago.yml new file mode 100644 index 00000000000..295d8db63d0 --- /dev/null +++ b/changelogs/unreleased/fixed-commit-timeago.yml @@ -0,0 +1,4 @@ +--- +title: Fixed commit timeago not rendering after initial page +merge_request: +author: diff --git a/changelogs/unreleased/issue-boards-scrollable-element.yml b/changelogs/unreleased/issue-boards-scrollable-element.yml new file mode 100644 index 00000000000..90edc30e791 --- /dev/null +++ b/changelogs/unreleased/issue-boards-scrollable-element.yml @@ -0,0 +1,4 @@ +--- +title: Fixed issue boards scrolling with a lot of lists & issues +merge_request: +author: diff --git a/changelogs/unreleased/issue_24748.yml b/changelogs/unreleased/issue_24748.yml new file mode 100644 index 00000000000..4c1df542c53 --- /dev/null +++ b/changelogs/unreleased/issue_24748.yml @@ -0,0 +1,4 @@ +--- +title: Fix title case to sentence case +merge_request: +author: Luis Alonso Chavez Armendariz diff --git a/changelogs/unreleased/issue_24958.yml b/changelogs/unreleased/issue_24958.yml new file mode 100644 index 00000000000..dbbbbf9d28d --- /dev/null +++ b/changelogs/unreleased/issue_24958.yml @@ -0,0 +1,4 @@ +--- +title: Fix bad selection on dropdown menu for tags filter +merge_request: +author: Luis Alonso Chavez Armendariz diff --git a/changelogs/unreleased/move-abuse-report-spinach-test-to-rspec.yml b/changelogs/unreleased/move-abuse-report-spinach-test-to-rspec.yml new file mode 100644 index 00000000000..9de7477c200 --- /dev/null +++ b/changelogs/unreleased/move-abuse-report-spinach-test-to-rspec.yml @@ -0,0 +1,4 @@ +--- +title: Move abuse report spinach test to rspec +merge_request: 7659 +author: Semyon Pupkov diff --git a/changelogs/unreleased/move-admin-abuse-report-spinach-test-to-rspec.yml b/changelogs/unreleased/move-admin-abuse-report-spinach-test-to-rspec.yml new file mode 100644 index 00000000000..fb70fa2955a --- /dev/null +++ b/changelogs/unreleased/move-admin-abuse-report-spinach-test-to-rspec.yml @@ -0,0 +1,4 @@ +--- +title: Move admin abuse report spinach test to rspec +merge_request: 7691 +author: Semyon Pupkov diff --git a/changelogs/unreleased/move-admin-spam-spinach-test-to-rspec.yml b/changelogs/unreleased/move-admin-spam-spinach-test-to-rspec.yml new file mode 100644 index 00000000000..a7ec2c20554 --- /dev/null +++ b/changelogs/unreleased/move-admin-spam-spinach-test-to-rspec.yml @@ -0,0 +1,4 @@ +--- +title: Move admin spam spinach test to Rspec +merge_request: 7708 +author: Semyon Pupkov diff --git a/changelogs/unreleased/remove-backup-strategies.yml b/changelogs/unreleased/remove-backup-strategies.yml new file mode 100644 index 00000000000..9f034613c2c --- /dev/null +++ b/changelogs/unreleased/remove-backup-strategies.yml @@ -0,0 +1,4 @@ +--- +title: Stop supporting Google and Azure as backup strategies +merge_request: +author: diff --git a/changelogs/unreleased/rephrase-system-notes.yml b/changelogs/unreleased/rephrase-system-notes.yml new file mode 100644 index 00000000000..e77c3a31cb4 --- /dev/null +++ b/changelogs/unreleased/rephrase-system-notes.yml @@ -0,0 +1,4 @@ +--- +title: Rephrase some system notes to be compatible with new system note style +merge_request: 7692 +author: diff --git a/changelogs/unreleased/resolve-discussions-timeago.yml b/changelogs/unreleased/resolve-discussions-timeago.yml new file mode 100644 index 00000000000..ffedeb93f1d --- /dev/null +++ b/changelogs/unreleased/resolve-discussions-timeago.yml @@ -0,0 +1,4 @@ +--- +title: Fixed timeago not rendering when resolving a discussion +merge_request: +author: diff --git a/changelogs/unreleased/simplify-create-new-list-issue-boards.yml b/changelogs/unreleased/simplify-create-new-list-issue-boards.yml new file mode 100644 index 00000000000..ca11e3b94a7 --- /dev/null +++ b/changelogs/unreleased/simplify-create-new-list-issue-boards.yml @@ -0,0 +1,4 @@ +--- +title: Simplify copy on "Create a new list" dropdown in Issue Boards +merge_request: 7605 +author: Victor Rodrigues diff --git a/changelogs/unreleased/zj-upgrade-grape.yml b/changelogs/unreleased/zj-upgrade-grape.yml new file mode 100644 index 00000000000..1df42d98733 --- /dev/null +++ b/changelogs/unreleased/zj-upgrade-grape.yml @@ -0,0 +1,4 @@ +--- +title: Update grape entity to 0.6.0 +merge_request: 7491 +author: diff --git a/config/routes.rb b/config/routes.rb index 7bf6c03e69b..03b47261e7e 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -1,6 +1,7 @@ require 'sidekiq/web' require 'sidekiq/cron/web' require 'api/api' +require 'constraints/group_url_constrainer' Rails.application.routes.draw do concern :access_requestable do @@ -78,10 +79,21 @@ Rails.application.routes.draw do draw :user draw :project - # Get all keys of user - get ':username.keys' => 'profiles/keys#get_keys', constraints: { username: /.*/ } - root to: "root#index" - get '*unmatched_route', to: 'application#not_found' + # Since group show page is wildcard routing + # we want all other routing to be checked before matching this one + constraints(GroupUrlConstrainer.new) do + scope(path: '*id', + as: :group, + constraints: { id: Gitlab::Regex.namespace_route_regex, format: /(html|json|atom)/ }, + controller: :groups) do + get '/', action: :show + patch '/', action: :update + put '/', action: :update + delete '/', action: :destroy + end + end + + get '*unmatched_route', to: 'application#route_not_found' end diff --git a/config/routes/git_http.rb b/config/routes/git_http.rb index 03adc4815f3..42d874eeebc 100644 --- a/config/routes/git_http.rb +++ b/config/routes/git_http.rb @@ -1,37 +1,47 @@ -scope constraints: { id: /.+\.git/, format: nil } do - # Git HTTP clients ('git clone' etc.) - get '/info/refs', to: 'git_http#info_refs' - post '/git-upload-pack', to: 'git_http#git_upload_pack' - post '/git-receive-pack', to: 'git_http#git_receive_pack' +scope(path: '*namespace_id/:project_id', constraints: { format: nil }) do + scope(constraints: { project_id: Gitlab::Regex.project_git_route_regex }, module: :projects) do + # Git HTTP clients ('git clone' etc.) + scope(controller: :git_http) do + get '/info/refs', action: :info_refs + post '/git-upload-pack', action: :git_upload_pack + post '/git-receive-pack', action: :git_receive_pack + end - # Git LFS API (metadata) - post '/info/lfs/objects/batch', to: 'lfs_api#batch' - post '/info/lfs/objects', to: 'lfs_api#deprecated' - get '/info/lfs/objects/*oid', to: 'lfs_api#deprecated' + # Git LFS API (metadata) + scope(path: 'info/lfs/objects', controller: :lfs_api) do + post :batch + post '/', action: :deprecated + get '/*oid', action: :deprecated + end - # GitLab LFS object storage - scope constraints: { oid: /[a-f0-9]{64}/ } do - get '/gitlab-lfs/objects/*oid', to: 'lfs_storage#download' + # GitLab LFS object storage + scope(path: 'gitlab-lfs/objects/*oid', controller: :lfs_storage, constraints: { oid: /[a-f0-9]{64}/ }) do + get '/', action: :download - scope constraints: { size: /[0-9]+/ } do - put '/gitlab-lfs/objects/*oid/*size/authorize', to: 'lfs_storage#upload_authorize' - put '/gitlab-lfs/objects/*oid/*size', to: 'lfs_storage#upload_finalize' + scope constraints: { size: /[0-9]+/ } do + put '/*size/authorize', action: :upload_authorize + put '/*size', action: :upload_finalize + end end end -end -# Allow /info/refs, /info/refs?service=git-upload-pack, and -# /info/refs?service=git-receive-pack, but nothing else. -# -git_http_handshake = lambda do |request| - request.query_string.blank? || - request.query_string.match(/\Aservice=git-(upload|receive)-pack\z/) -end + # Redirect /group/project/info/refs to /group/project.git/info/refs + scope(constraints: { project_id: Gitlab::Regex.project_route_regex }) do + # Allow /info/refs, /info/refs?service=git-upload-pack, and + # /info/refs?service=git-receive-pack, but nothing else. + # + git_http_handshake = lambda do |request| + ProjectUrlConstrainer.new.matches?(request) && + (request.query_string.blank? || + request.query_string.match(/\Aservice=git-(upload|receive)-pack\z/)) + end -ref_redirect = redirect do |params, request| - path = "#{params[:namespace_id]}/#{params[:project_id]}.git/info/refs" - path << "?#{request.query_string}" unless request.query_string.blank? - path -end + ref_redirect = redirect do |params, request| + path = "#{params[:namespace_id]}/#{params[:project_id]}.git/info/refs" + path << "?#{request.query_string}" unless request.query_string.blank? + path + end -get '/info/refs', constraints: git_http_handshake, to: ref_redirect + get '/info/refs', constraints: git_http_handshake, to: ref_redirect + end +end diff --git a/config/routes/group.rb b/config/routes/group.rb index a3a001178b4..9fe72990994 100644 --- a/config/routes/group.rb +++ b/config/routes/group.rb @@ -1,20 +1,6 @@ -require 'constraints/group_url_constrainer' - -constraints(GroupUrlConstrainer.new) do - scope(path: ':id', - as: :group, - constraints: { id: Gitlab::Regex.namespace_route_regex }, - controller: :groups) do - get '/', action: :show - patch '/', action: :update - put '/', action: :update - delete '/', action: :destroy - end -end - resources :groups, only: [:index, :new, :create] -scope(path: 'groups/:id', +scope(path: 'groups/*id', controller: :groups, constraints: { id: Gitlab::Regex.namespace_route_regex }) do get :edit, as: :edit_group @@ -24,7 +10,7 @@ scope(path: 'groups/:id', get :activity, as: :activity_group end -scope(path: 'groups/:group_id', +scope(path: 'groups/*group_id', module: :groups, as: :group, constraints: { group_id: Gitlab::Regex.namespace_route_regex }) do @@ -42,4 +28,4 @@ scope(path: 'groups/:group_id', end # Must be last route in this file -get 'groups/:id' => 'groups#show', as: :group_canonical, constraints: { id: Gitlab::Regex.namespace_route_regex } +get 'groups/*id' => 'groups#show', as: :group_canonical, constraints: { id: Gitlab::Regex.namespace_route_regex } diff --git a/config/routes/project.rb b/config/routes/project.rb index d6eae1c9fce..1336484a399 100644 --- a/config/routes/project.rb +++ b/config/routes/project.rb @@ -1,28 +1,15 @@ -resources :projects, constraints: { id: /[^\/]+/ }, only: [:index, :new, :create] - -resources :namespaces, path: '/', constraints: { id: /[a-zA-Z.0-9_\-]+/ }, only: [] do - resources(:projects, constraints: { id: /[a-zA-Z.0-9_\-]+(?<!\.atom)/ }, except: - [:new, :create, :index], path: "/") do - member do - put :transfer - delete :remove_fork - post :archive - post :unarchive - post :housekeeping - post :toggle_star - post :preview_markdown - post :export - post :remove_export - post :generate_new_export - get :download_export - get :autocomplete_sources - get :activity - get :refs - put :new_issue_address - end +require 'constraints/project_url_constrainer' + +resources :projects, only: [:index, :new, :create] + +draw :git_http - scope module: :projects do - draw :git_http +constraints(ProjectUrlConstrainer.new) do + scope(path: '*namespace_id', as: :namespace) do + scope(path: ':project_id', + constraints: { project_id: Gitlab::Regex.project_route_regex }, + module: :projects, + as: :project) do # # Templates @@ -311,5 +298,28 @@ resources :namespaces, path: '/', constraints: { id: /[a-zA-Z.0-9_\-]+/ }, only: draw :wiki draw :repository end + + resources(:projects, + path: '/', + constraints: { id: Gitlab::Regex.project_route_regex }, + only: [:edit, :show, :update, :destroy]) do + member do + put :transfer + delete :remove_fork + post :archive + post :unarchive + post :housekeeping + post :toggle_star + post :preview_markdown + post :export + post :remove_export + post :generate_new_export + get :download_export + get :autocomplete_sources + get :activity + get :refs + put :new_issue_address + end + end end end diff --git a/config/routes/repository.rb b/config/routes/repository.rb index 76dcf113aea..f8966c5ae75 100644 --- a/config/routes/repository.rb +++ b/config/routes/repository.rb @@ -29,82 +29,60 @@ get '/edit/*id', to: 'blob#edit', constraints: { id: /.+/ }, as: 'edit_blob' put '/update/*id', to: 'blob#update', constraints: { id: /.+/ }, as: 'update_blob' post '/preview/*id', to: 'blob#preview', constraints: { id: /.+/ }, as: 'preview_blob' -scope do - get( - '/blob/*id/diff', - to: 'blob#diff', - constraints: { id: /.+/, format: false }, - as: :blob_diff - ) - get( - '/blob/*id', - to: 'blob#show', - constraints: { id: /.+/, format: false }, - as: :blob - ) - delete( - '/blob/*id', - to: 'blob#destroy', - constraints: { id: /.+/, format: false } - ) - put( - '/blob/*id', - to: 'blob#update', - constraints: { id: /.+/, format: false } - ) - post( - '/blob/*id', - to: 'blob#create', - constraints: { id: /.+/, format: false } - ) +scope('/blob/*id', as: :blob, controller: :blob, constraints: { id: /.+/, format: false }) do + get :diff + get '/', action: :show + delete '/', action: :destroy + post '/', action: :create + put '/', action: :update +end - get( - '/raw/*id', - to: 'raw#show', - constraints: { id: /.+/, format: /(html|js)/ }, - as: :raw - ) +get( + '/raw/*id', + to: 'raw#show', + constraints: { id: /.+/, format: /(html|js)/ }, + as: :raw +) - get( - '/tree/*id', - to: 'tree#show', - constraints: { id: /.+/, format: /(html|js)/ }, - as: :tree - ) +get( + '/tree/*id', + to: 'tree#show', + constraints: { id: /.+/, format: /(html|js)/ }, + as: :tree +) - get( - '/find_file/*id', - to: 'find_file#show', - constraints: { id: /.+/, format: /html/ }, - as: :find_file - ) +get( + '/find_file/*id', + to: 'find_file#show', + constraints: { id: /.+/, format: /html/ }, + as: :find_file +) - get( - '/files/*id', - to: 'find_file#list', - constraints: { id: /(?:[^.]|\.(?!json$))+/, format: /json/ }, - as: :files - ) +get( + '/files/*id', + to: 'find_file#list', + constraints: { id: /(?:[^.]|\.(?!json$))+/, format: /json/ }, + as: :files +) - post( - '/create_dir/*id', - to: 'tree#create_dir', - constraints: { id: /.+/ }, - as: 'create_dir' - ) +post( + '/create_dir/*id', + to: 'tree#create_dir', + constraints: { id: /.+/ }, + as: 'create_dir' +) - get( - '/blame/*id', - to: 'blame#show', - constraints: { id: /.+/, format: /(html|js)/ }, - as: :blame - ) +get( + '/blame/*id', + to: 'blame#show', + constraints: { id: /.+/, format: /(html|js)/ }, + as: :blame +) - # File/dir history - get( - '/commits/*id', - to: 'commits#show', - constraints: { id: /.+/, format: false }, - as: :commits - ) -end +# File/dir history +get( + '/commits/*id', + to: 'commits#show', + constraints: { id: /.+/, format: false }, + as: :commits +) diff --git a/config/routes/user.rb b/config/routes/user.rb index dc1068af6f6..b064a15e802 100644 --- a/config/routes/user.rb +++ b/config/routes/user.rb @@ -12,6 +12,9 @@ devise_scope :user do end constraints(UserUrlConstrainer.new) do + # Get all keys of user + get ':username.keys' => 'profiles/keys#get_keys', constraints: { username: Gitlab::Regex.namespace_route_regex } + scope(path: ':username', as: :user, constraints: { username: Gitlab::Regex.namespace_route_regex }, diff --git a/config/routes/wiki.rb b/config/routes/wiki.rb index ecd4d395d66..dad746d59a1 100644 --- a/config/routes/wiki.rb +++ b/config/routes/wiki.rb @@ -1,16 +1,19 @@ WIKI_SLUG_ID = { id: /\S+/ } unless defined? WIKI_SLUG_ID -scope do - # Order matters to give priority to these matches - get '/wikis/git_access', to: 'wikis#git_access' - get '/wikis/pages', to: 'wikis#pages', as: 'wiki_pages' - post '/wikis', to: 'wikis#create' +scope(controller: :wikis) do + scope(path: 'wikis', as: :wikis) do + get :git_access + get :pages + get '/', to: redirect('/%{namespace_id}/%{project_id}/wikis/home') + post '/', to: 'wikis#create' + end - get '/wikis/*id/history', to: 'wikis#history', as: 'wiki_history', constraints: WIKI_SLUG_ID - get '/wikis/*id/edit', to: 'wikis#edit', as: 'wiki_edit', constraints: WIKI_SLUG_ID - - get '/wikis/*id', to: 'wikis#show', as: 'wiki', constraints: WIKI_SLUG_ID - delete '/wikis/*id', to: 'wikis#destroy', constraints: WIKI_SLUG_ID - put '/wikis/*id', to: 'wikis#update', constraints: WIKI_SLUG_ID - post '/wikis/*id/preview_markdown', to: 'wikis#preview_markdown', constraints: WIKI_SLUG_ID, as: 'wiki_preview_markdown' + scope(path: 'wikis/*id', as: :wiki, constraints: WIKI_SLUG_ID, format: false) do + get :edit + get :history + post :preview_markdown + get '/', action: :show + put '/', action: :update + delete '/', action: :destroy + end end diff --git a/doc/api/notes.md b/doc/api/notes.md index 58d40eecf3e..9971806be56 100644 --- a/doc/api/notes.md +++ b/doc/api/notes.md @@ -21,7 +21,7 @@ Parameters: [ { "id": 302, - "body": "Status changed to closed", + "body": "closed", "attachment": null, "author": { "id": 1, diff --git a/doc/api/projects.md b/doc/api/projects.md index de5d3b07c21..bd27a0a6fae 100644 --- a/doc/api/projects.md +++ b/doc/api/projects.md @@ -624,6 +624,7 @@ Parameters: | `user_id` | integer | yes | The user ID of the project owner | | `name` | string | yes | The name of the new project | | `path` | string | no | Custom repository name for new project. By default generated based on name | +| `default_branch` | string | no | `master` by default | | `namespace_id` | integer | no | Namespace for the new project (defaults to the current user's namespace) | | `description` | string | no | Short project description | | `issues_enabled` | boolean | no | Enable issues for this project | @@ -657,6 +658,7 @@ Parameters: | `id` | integer/string | yes | The ID or NAMESPACE/PROJECT_NAME of the project | | `name` | string | yes | The name of the project | | `path` | string | no | Custom repository name for the project. By default generated based on name | +| `default_branch` | string | no | `master` by default | | `description` | string | no | Short project description | | `issues_enabled` | boolean | no | Enable issues for this project | | `merge_requests_enabled` | boolean | no | Enable merge requests for this project | diff --git a/doc/development/testing.md b/doc/development/testing.md index 6106e47daa0..dbea6b9c9aa 100644 --- a/doc/development/testing.md +++ b/doc/development/testing.md @@ -63,6 +63,8 @@ the command line via `bundle exec teaspoon`, or via a web browser at - Use `.method` to describe class methods and `#method` to describe instance methods. - Use `context` to test branching logic. +- Use multi-line `do...end` blocks for `before` and `after`, even when it would + fit on a single line. - Don't `describe` symbols (see [Gotchas](gotchas.md#dont-describe-symbols)). - Don't assert against the absolute value of a sequence-generated attribute (see [Gotchas](gotchas.md#dont-assert-against-the-absolute-value-of-a-sequence-generated-attribute)). - Don't supply the `:each` argument to hooks since it's the default. diff --git a/doc/install/installation.md b/doc/install/installation.md index b5e2640b380..dabd69d7466 100644 --- a/doc/install/installation.md +++ b/doc/install/installation.md @@ -175,17 +175,17 @@ We recommend using a PostgreSQL database. For MySQL check the ```bash sudo -u postgres psql -d template1 -c "CREATE USER git CREATEDB;" ``` - -1. Create the GitLab production database and grant all privileges on database: + +1. Create the `pg_trgm` extension (required for GitLab 8.6+): ```bash - sudo -u postgres psql -d template1 -c "CREATE DATABASE gitlabhq_production OWNER git;" + sudo -u postgres psql -d template1 -c "CREATE EXTENSION IF NOT EXISTS pg_trgm;" ``` -1. Create the `pg_trgm` extension (required for GitLab 8.6+): +1. Create the GitLab production database and grant all privileges on database: ```bash - sudo -u postgres psql -d template1 -c "CREATE EXTENSION IF NOT EXISTS pg_trgm;" + sudo -u postgres psql -d template1 -c "CREATE DATABASE gitlabhq_production OWNER git;" ``` 1. Try connecting to the new database with the new user: diff --git a/doc/integration/jira.md b/doc/integration/jira.md new file mode 100644 index 00000000000..e2f136bcc35 --- /dev/null +++ b/doc/integration/jira.md @@ -0,0 +1,3 @@ +# GitLab JIRA integration + +This document was moved to [project_services/jira](../project_services/jira.md). diff --git a/doc/raketasks/backup_restore.md b/doc/raketasks/backup_restore.md index 7484bc2295e..17485b11c09 100644 --- a/doc/raketasks/backup_restore.md +++ b/doc/raketasks/backup_restore.md @@ -88,7 +88,7 @@ It uses the [Fog library](http://fog.io/) to perform the upload. In the example below we use Amazon S3 for storage, but Fog also lets you use [other storage providers](http://fog.io/storage/). GitLab [imports cloud drivers](https://gitlab.com/gitlab-org/gitlab-ce/blob/30f5b9a5b711b46f1065baf755e413ceced5646b/Gemfile#L88) -for AWS, Azure, Google, OpenStack Swift and Rackspace as well. A local driver is +for AWS, OpenStack Swift and Rackspace as well. A local driver is [also available](#uploading-to-locally-mounted-shares). For omnibus packages: diff --git a/doc/university/README.md b/doc/university/README.md index 4569bc72797..8917636c59b 100644 --- a/doc/university/README.md +++ b/doc/university/README.md @@ -19,7 +19,7 @@ The curriculum is composed of GitLab videos, screencasts, presentations, project --- -### 1. <a name="beginner"></a> GitLab Beginner +### 1. GitLab Beginner #### 1.1. Version Control and Git @@ -85,7 +85,7 @@ The curriculum is composed of GitLab videos, screencasts, presentations, project --- -### 2. <a name="intermediate"></a> GitLab Intermediate +### 2. GitLab Intermediate #### 2.1 GitLab Pages @@ -141,7 +141,7 @@ The curriculum is composed of GitLab videos, screencasts, presentations, project --- -### 3. <a name="advanced"></a> GitLab Advanced +### 3. GitLab Advanced #### 3.1. Dev Ops @@ -186,7 +186,7 @@ The curriculum is composed of GitLab videos, screencasts, presentations, project 1. [GitLab Cycle Analytics Overview](https://about.gitlab.com/2016/09/21/cycle-analytics-feature-highlight/) 1. [GitLab Cycle Analytics - Product Page](https://about.gitlab.com/solutions/cycle-analytics/) -#### 3.9. <a name="integrations"></a> Integrations +#### 3.9. Integrations 1. [How to Integrate JIRA and Jenkins with GitLab - Video](https://gitlabmeetings.webex.com/gitlabmeetings/ldr.php?RCID=44b548147a67ab4d8a62274047146415) 1. [How to Integrate Jira with GitLab](https://docs.gitlab.com/ee/integration/jira.html) @@ -198,7 +198,7 @@ The curriculum is composed of GitLab videos, screencasts, presentations, project --- -## 4. <a name="external"></a> External Articles +## 4. External Articles 1. [2011 WSJ article by Marc Andreessen - Software is Eating the World](http://www.wsj.com/articles/SB10001424053111903480904576512250915629460) 1. [2014 Blog post by Chris Dixon - Software eats software development](http://cdixon.org/2014/04/13/software-eats-software-development/) @@ -206,7 +206,7 @@ The curriculum is composed of GitLab videos, screencasts, presentations, project --- -## 5. <a name="team"></a> Resources for GitLab Team Members +## 5. Resources for GitLab Team Members *Some content can only be accessed by GitLab team members* diff --git a/doc/user/project/img/issue_board.png b/doc/user/project/img/issue_board.png Binary files differindex 2a35a615d70..95e8532e908 100644 --- a/doc/user/project/img/issue_board.png +++ b/doc/user/project/img/issue_board.png diff --git a/doc/user/project/img/issue_board_add_list.png b/doc/user/project/img/issue_board_add_list.png Binary files differindex aa1a4ca4cfa..cdfc466d23f 100644 --- a/doc/user/project/img/issue_board_add_list.png +++ b/doc/user/project/img/issue_board_add_list.png diff --git a/doc/user/project/img/issue_board_welcome_message.png b/doc/user/project/img/issue_board_welcome_message.png Binary files differindex aa25cfb5b37..5bfdac88dde 100644 --- a/doc/user/project/img/issue_board_welcome_message.png +++ b/doc/user/project/img/issue_board_welcome_message.png diff --git a/doc/user/project/issue_board.md b/doc/user/project/issue_board.md index 4a6c0d88241..d1ae57c00d7 100644 --- a/doc/user/project/issue_board.md +++ b/doc/user/project/issue_board.md @@ -72,7 +72,7 @@ the list will be created and filled with the issues that have that label. ## Creating a new list -Create a new list by clicking on the **Create new list** button at the upper +Create a new list by clicking on the **Add list** button at the upper right corner of the Issue Board. ![Issue Board welcome message](img/issue_board_add_list.png) diff --git a/doc/web_hooks/web_hooks.md b/doc/web_hooks/web_hooks.md index 33c1a79d59c..cd37189fdd2 100644 --- a/doc/web_hooks/web_hooks.md +++ b/doc/web_hooks/web_hooks.md @@ -922,6 +922,64 @@ X-Gitlab-Event: Pipeline Hook } ``` + +## Build events + +Triggered on status change of a Build. + +**Request Header**: + +``` +X-Gitlab-Event: Build Hook +``` + +**Request Body**: + +``` +{ + "object_kind": "build", + "ref": "gitlab-script-trigger", + "tag": false, + "before_sha": "2293ada6b400935a1378653304eaf6221e0fdb8f", + "sha": "2293ada6b400935a1378653304eaf6221e0fdb8f", + "build_id": 1977, + "build_name": "test", + "build_stage": "test", + "build_status": "created", + "build_started_at": null, + "build_finished_at": null, + "build_duration": null, + "build_allow_failure": false, + "project_id": 380, + "project_name": "gitlab-org/gitlab-test", + "user": { + "id": 3, + "name": "User", + "email": "user@gitlab.com" + }, + "commit": { + "id": 2366, + "sha": "2293ada6b400935a1378653304eaf6221e0fdb8f", + "message": "test\n", + "author_name": "User", + "author_email": "user@gitlab.com", + "status": "created", + "duration": null, + "started_at": null, + "finished_at": null + }, + "repository": { + "name": "gitlab_test", + "git_ssh_url": "git@192.168.64.1:gitlab-org/gitlab-test.git", + "description": "Atque in sunt eos similique dolores voluptatem.", + "homepage": "http://192.168.64.1:3005/gitlab-org/gitlab-test", + "git_ssh_url": "git@192.168.64.1:gitlab-org/gitlab-test.git", + "git_http_url": "http://192.168.64.1:3005/gitlab-org/gitlab-test.git", + "visibility_level": 20 + } +} +``` + #### Example webhook receiver If you want to see GitLab's webhooks in action for testing purposes you can use diff --git a/doc/workflow/img/todos_add_todo_sidebar.png b/doc/workflow/img/todos_add_todo_sidebar.png Binary files differindex 59175ae44c5..3fa37067d1e 100644 --- a/doc/workflow/img/todos_add_todo_sidebar.png +++ b/doc/workflow/img/todos_add_todo_sidebar.png diff --git a/doc/workflow/img/todos_mark_done_sidebar.png b/doc/workflow/img/todos_mark_done_sidebar.png Binary files differindex aa35bb672ea..a8e756a71db 100644 --- a/doc/workflow/img/todos_mark_done_sidebar.png +++ b/doc/workflow/img/todos_mark_done_sidebar.png diff --git a/doc/workflow/todos.md b/doc/workflow/todos.md index 54e7ae19ea5..1a8fc39bb33 100644 --- a/doc/workflow/todos.md +++ b/doc/workflow/todos.md @@ -35,7 +35,7 @@ A Todo appears in your Todos dashboard when: ### Manually creating a Todo You can also add an issue or merge request to your Todos dashboard by clicking -the "Add Todo" button in the issue or merge request sidebar. +the "Add todo" button in the issue or merge request sidebar. ![Adding a Todo from the issuable sidebar](img/todos_add_todo_sidebar.png) @@ -69,7 +69,7 @@ corresponding **Done** button, and it will disappear from your Todo list. ![A Todo in the Todos dashboard](img/todo_list_item.png) A Todo can also be marked as done from the issue or merge request sidebar using -the "Mark Done" button. +the "Mark done" button. ![Mark Done from the issuable sidebar](img/todos_mark_done_sidebar.png) diff --git a/features/abuse_report.feature b/features/abuse_report.feature deleted file mode 100644 index 212972a762a..00000000000 --- a/features/abuse_report.feature +++ /dev/null @@ -1,17 +0,0 @@ -Feature: Abuse reports - Background: - Given I sign in as a user - And user "Mike" exists - - Scenario: Report abuse - Given I visit "Mike" user page - And I click "Report abuse" button - When I fill and submit abuse form - Then I should see success message - - Scenario: Report abuse available only once - Given I visit "Mike" user page - And I click "Report abuse" button - When I fill and submit abuse form - And I visit "Mike" user page - Then I should see a red "Report abuse" button diff --git a/features/admin/abuse_report.feature b/features/admin/abuse_report.feature deleted file mode 100644 index 7d4ec2556e5..00000000000 --- a/features/admin/abuse_report.feature +++ /dev/null @@ -1,8 +0,0 @@ -Feature: Admin Abuse reports - Background: - Given I sign in as an admin - And abuse reports exist - - Scenario: Browse abuse reports - When I visit abuse reports page - Then I should see list of abuse reports diff --git a/features/admin/spam_logs.feature b/features/admin/spam_logs.feature deleted file mode 100644 index 92a5389e3a4..00000000000 --- a/features/admin/spam_logs.feature +++ /dev/null @@ -1,8 +0,0 @@ -Feature: Admin spam logs - Background: - Given I sign in as an admin - And spam logs exist - - Scenario: Browse spam logs - When I visit spam logs page - Then I should see list of spam logs diff --git a/features/steps/abuse_reports.rb b/features/steps/abuse_reports.rb deleted file mode 100644 index 499accb0b08..00000000000 --- a/features/steps/abuse_reports.rb +++ /dev/null @@ -1,32 +0,0 @@ -class Spinach::Features::AbuseReports < Spinach::FeatureSteps - include SharedAuthentication - - step 'I visit "Mike" user page' do - visit user_path(user_mike) - end - - step 'I click "Report abuse" button' do - click_link 'Report abuse' - end - - step 'I fill and submit abuse form' do - fill_in 'abuse_report_message', with: 'This user send spam' - click_button 'Send report' - end - - step 'I should see success message' do - page.should have_content 'Thank you for your report' - end - - step 'user "Mike" exists' do - user_mike - end - - step 'I should see a red "Report abuse" button' do - expect(page).to have_button("Already reported for abuse") - end - - def user_mike - @user_mike ||= create(:user, name: 'Mike') - end -end diff --git a/features/steps/admin/abuse_reports.rb b/features/steps/admin/abuse_reports.rb deleted file mode 100644 index 0149416c919..00000000000 --- a/features/steps/admin/abuse_reports.rb +++ /dev/null @@ -1,15 +0,0 @@ -class Spinach::Features::AdminAbuseReports < Spinach::FeatureSteps - include SharedAuthentication - include SharedPaths - include SharedAdmin - - step 'I should see list of abuse reports' do - page.should have_content("Abuse Reports") - page.should have_content AbuseReport.first.message - page.should have_link("Remove user") - end - - step 'abuse reports exist' do - create(:abuse_report) - end -end diff --git a/features/steps/admin/spam_logs.rb b/features/steps/admin/spam_logs.rb deleted file mode 100644 index ad825fd414c..00000000000 --- a/features/steps/admin/spam_logs.rb +++ /dev/null @@ -1,28 +0,0 @@ -class Spinach::Features::AdminSpamLogs < Spinach::FeatureSteps - include SharedAuthentication - include SharedPaths - include SharedAdmin - - step 'I should see list of spam logs' do - expect(page).to have_content('Spam Logs') - expect(page).to have_content spam_log.source_ip - expect(page).to have_content spam_log.noteable_type - expect(page).to have_content 'N' - expect(page).to have_content spam_log.title - expect(page).to have_content truncate(spam_log.description) - expect(page).to have_link('Remove user') - expect(page).to have_link('Block user') - end - - step 'spam logs exist' do - create(:spam_log) - end - - def spam_log - @spam_log ||= SpamLog.first - end - - def truncate(description) - "#{spam_log.description[0...97]}..." - end -end diff --git a/features/steps/project/labels.rb b/features/steps/project/labels.rb index 118ffef4774..dbeb07c78db 100644 --- a/features/steps/project/labels.rb +++ b/features/steps/project/labels.rb @@ -2,9 +2,7 @@ class Spinach::Features::Labels < Spinach::FeatureSteps include SharedAuthentication include SharedIssuable include SharedProject - include SharedNote include SharedPaths - include SharedMarkdown step 'And I visit project "Shop" labels page' do visit namespace_project_labels_path(project.namespace, project) diff --git a/features/steps/project/merge_requests.rb b/features/steps/project/merge_requests.rb index f728d243cdc..d2fa8cd39af 100644 --- a/features/steps/project/merge_requests.rb +++ b/features/steps/project/merge_requests.rb @@ -515,7 +515,7 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps step 'I should see new target branch changes' do expect(page).to have_content 'Request to merge fix into feature' - expect(page).to have_content 'Target branch changed from merge-test to feature' + expect(page).to have_content 'changed target branch from merge-test to feature' wait_for_ajax end diff --git a/features/steps/shared/diff_note.rb b/features/steps/shared/diff_note.rb index 35b71599708..11fa85ed2fe 100644 --- a/features/steps/shared/diff_note.rb +++ b/features/steps/shared/diff_note.rb @@ -1,6 +1,11 @@ module SharedDiffNote include Spinach::DSL include RepoHelpers + include WaitForAjax + + after do + wait_for_ajax if javascript_test? + end step 'I cancel the diff comment' do page.within(diff_file_selector) do diff --git a/features/steps/shared/issuable.rb b/features/steps/shared/issuable.rb index df9845ba569..aa666a954bc 100644 --- a/features/steps/shared/issuable.rb +++ b/features/steps/shared/issuable.rb @@ -179,7 +179,7 @@ module SharedIssuable project = Project.find_by(name: from_project_name) expect(page).to have_content(user_name) - expect(page).to have_content("Mentioned in #{issuable.class.to_s.titleize.downcase} #{issuable.to_reference(project)}") + expect(page).to have_content("mentioned in #{issuable.class.to_s.titleize.downcase} #{issuable.to_reference(project)}") end def expect_sidebar_content(content) diff --git a/features/steps/shared/note.rb b/features/steps/shared/note.rb index 9dc1fc41b3b..1870f6bc0c3 100644 --- a/features/steps/shared/note.rb +++ b/features/steps/shared/note.rb @@ -2,6 +2,10 @@ module SharedNote include Spinach::DSL include WaitForAjax + after do + wait_for_ajax if javascript_test? + end + step 'I delete a comment' do page.within('.main-notes-list') do find('.note').hover diff --git a/lib/api/commit_statuses.rb b/lib/api/commit_statuses.rb index f54d4f06627..492884d162b 100644 --- a/lib/api/commit_statuses.rb +++ b/lib/api/commit_statuses.rb @@ -77,7 +77,7 @@ module API ) begin - case params[:state].to_s + case params[:state] when 'pending' status.enqueue! when 'running' diff --git a/lib/api/commits.rb b/lib/api/commits.rb index 0319d076ecb..2670a2d413a 100644 --- a/lib/api/commits.rb +++ b/lib/api/commits.rb @@ -48,7 +48,7 @@ module API requires :id, type: Integer, desc: 'The project ID' requires :branch_name, type: String, desc: 'The name of branch' requires :commit_message, type: String, desc: 'Commit message' - requires :actions, type: Array, desc: 'Actions to perform in commit' + requires :actions, type: Array[Hash], desc: 'Actions to perform in commit' optional :author_email, type: String, desc: 'Author email for commit' optional :author_name, type: String, desc: 'Author name for commit' end diff --git a/lib/api/groups.rb b/lib/api/groups.rb index a3489c4eb92..5315c22e1e4 100644 --- a/lib/api/groups.rb +++ b/lib/api/groups.rb @@ -33,7 +33,7 @@ module API groups = groups.search(params[:search]) if params[:search].present? groups = groups.where.not(id: params[:skip_groups]) if params[:skip_groups].present? - groups = groups.reorder(params[:order_by] => params[:sort].to_sym) + groups = groups.reorder(params[:order_by] => params[:sort]) present paginate(groups), with: Entities::Group end diff --git a/lib/api/variables.rb b/lib/api/variables.rb index 90f904b8a12..f623b1dfe9f 100644 --- a/lib/api/variables.rb +++ b/lib/api/variables.rb @@ -30,7 +30,7 @@ module API end get ':id/variables/:key' do key = params[:key] - variable = user_project.variables.find_by(key: key.to_s) + variable = user_project.variables.find_by(key: key) return not_found!('Variable') unless variable diff --git a/lib/constraints/constrainer_helper.rb b/lib/constraints/constrainer_helper.rb deleted file mode 100644 index ab07a6793d9..00000000000 --- a/lib/constraints/constrainer_helper.rb +++ /dev/null @@ -1,15 +0,0 @@ -module ConstrainerHelper - def extract_resource_path(path) - id = path.dup - id.sub!(/\A#{relative_url_root}/, '') if relative_url_root - id.sub(/\A\/+/, '').sub(/\/+\z/, '').sub(/.atom\z/, '') - end - - private - - def relative_url_root - if defined?(Gitlab::Application.config.relative_url_root) - Gitlab::Application.config.relative_url_root - end - end -end diff --git a/lib/constraints/group_url_constrainer.rb b/lib/constraints/group_url_constrainer.rb index 2af6e1a11c8..5711d96a586 100644 --- a/lib/constraints/group_url_constrainer.rb +++ b/lib/constraints/group_url_constrainer.rb @@ -1,15 +1,17 @@ -require_relative 'constrainer_helper' - class GroupUrlConstrainer - include ConstrainerHelper - def matches?(request) - id = extract_resource_path(request.path) + id = request.params[:id] + + return false unless valid?(id) + + Group.find_by(path: id).present? + end + + private - if id =~ Gitlab::Regex.namespace_regex - Group.find_by(path: id).present? - else - false + def valid?(id) + id.split('/').all? do |namespace| + NamespaceValidator.valid?(namespace) end end end diff --git a/lib/constraints/project_url_constrainer.rb b/lib/constraints/project_url_constrainer.rb new file mode 100644 index 00000000000..730b05bed97 --- /dev/null +++ b/lib/constraints/project_url_constrainer.rb @@ -0,0 +1,13 @@ +class ProjectUrlConstrainer + def matches?(request) + namespace_path = request.params[:namespace_id] + project_path = request.params[:project_id] || request.params[:id] + full_path = namespace_path + '/' + project_path + + unless ProjectPathValidator.valid?(project_path) + return false + end + + Project.find_with_namespace(full_path).present? + end +end diff --git a/lib/constraints/user_url_constrainer.rb b/lib/constraints/user_url_constrainer.rb index 4d722ad5af2..9ab5bcb12ff 100644 --- a/lib/constraints/user_url_constrainer.rb +++ b/lib/constraints/user_url_constrainer.rb @@ -1,15 +1,5 @@ -require_relative 'constrainer_helper' - class UserUrlConstrainer - include ConstrainerHelper - def matches?(request) - id = extract_resource_path(request.path) - - if id =~ Gitlab::Regex.namespace_regex - User.find_by('lower(username) = ?', id.downcase).present? - else - false - end + User.find_by_username(request.params[:username]).present? end end diff --git a/lib/gitlab/identifier.rb b/lib/gitlab/identifier.rb index c5acf18beb5..94678b6ec40 100644 --- a/lib/gitlab/identifier.rb +++ b/lib/gitlab/identifier.rb @@ -21,10 +21,8 @@ module Gitlab return if !commit || !commit.author_email - email = commit.author_email - - identify_with_cache(:email, email) do - User.find_by_any_email(email) + identify_with_cache(:email, commit.author_email) do + commit.author end end diff --git a/lib/gitlab/regex.rb b/lib/gitlab/regex.rb index c12358ceef4..a06cf6a989c 100644 --- a/lib/gitlab/regex.rb +++ b/lib/gitlab/regex.rb @@ -8,8 +8,10 @@ module Gitlab # allow non-regex validatiions, etc), `NAMESPACE_REGEX_STR_SIMPLE` serves as a Javascript-compatible version of # `NAMESPACE_REGEX_STR`, with the negative lookbehind assertion removed. This means that the client-side validation # will pass for usernames ending in `.atom` and `.git`, but will be caught by the server-side validation. - NAMESPACE_REGEX_STR_SIMPLE = '[a-zA-Z0-9_\.][a-zA-Z0-9_\-\.]*[a-zA-Z0-9_\-]|[a-zA-Z0-9_]'.freeze + PATH_REGEX_STR = '[a-zA-Z0-9_\.][a-zA-Z0-9_\-\.]*'.freeze + NAMESPACE_REGEX_STR_SIMPLE = PATH_REGEX_STR + '[a-zA-Z0-9_\-]|[a-zA-Z0-9_]'.freeze NAMESPACE_REGEX_STR = '(?:' + NAMESPACE_REGEX_STR_SIMPLE + ')(?<!\.git|\.atom)'.freeze + PROJECT_REGEX_STR = PATH_REGEX_STR + '(?<!\.git|\.atom)'.freeze def namespace_regex @namespace_regex ||= /\A#{NAMESPACE_REGEX_STR}\z/.freeze @@ -42,7 +44,15 @@ module Gitlab end def project_path_regex - @project_path_regex ||= /\A[a-zA-Z0-9_.][a-zA-Z0-9_\-\.]*(?<!\.git|\.atom)\z/.freeze + @project_path_regex ||= /\A#{PROJECT_REGEX_STR}\z/.freeze + end + + def project_route_regex + @project_route_regex ||= /#{PROJECT_REGEX_STR}/.freeze + end + + def project_git_route_regex + @project_route_git_regex ||= /#{PATH_REGEX_STR}\.git/.freeze end def project_path_regex_message diff --git a/spec/controllers/application_controller_spec.rb b/spec/controllers/application_controller_spec.rb index 98e912f000c..81cbccd5436 100644 --- a/spec/controllers/application_controller_spec.rb +++ b/spec/controllers/application_controller_spec.rb @@ -1,8 +1,9 @@ require 'spec_helper' describe ApplicationController do + let(:user) { create(:user) } + describe '#check_password_expiration' do - let(:user) { create(:user) } let(:controller) { ApplicationController.new } it 'redirects if the user is over their password expiry' do @@ -39,8 +40,6 @@ describe ApplicationController do end end - let(:user) { create(:user) } - context "when the 'private_token' param is populated with the private token" do it "logs the user in" do get :index, private_token: user.private_token @@ -73,7 +72,6 @@ describe ApplicationController do end end - let(:user) { create(:user) } let(:personal_access_token) { create(:personal_access_token, user: user) } context "when the 'personal_access_token' param is populated with the personal access token" do @@ -100,4 +98,21 @@ describe ApplicationController do end end end + + describe '#route_not_found' do + let(:controller) { ApplicationController.new } + + it 'renders 404 if authenticated' do + allow(controller).to receive(:current_user).and_return(user) + expect(controller).to receive(:not_found) + controller.send(:route_not_found) + end + + it 'does redirect to login page if not authenticated' do + allow(controller).to receive(:current_user).and_return(nil) + expect(controller).to receive(:redirect_to) + expect(controller).to receive(:new_user_session_path) + controller.send(:route_not_found) + end + end end diff --git a/spec/controllers/projects/milestones_controller_spec.rb b/spec/controllers/projects/milestones_controller_spec.rb index 7c5f33c63b8..6d30d085056 100644 --- a/spec/controllers/projects/milestones_controller_spec.rb +++ b/spec/controllers/projects/milestones_controller_spec.rb @@ -31,7 +31,7 @@ describe Projects::MilestonesController do # Check system note left for milestone removal last_note = project.issues.find(issue.id).notes[-1].note - expect(last_note).to eq('Milestone removed') + expect(last_note).to eq('removed milestone') end end end diff --git a/spec/factories/ci/builds.rb b/spec/factories/ci/builds.rb index eb20bd7dd58..c443af09075 100644 --- a/spec/factories/ci/builds.rb +++ b/spec/factories/ci/builds.rb @@ -38,6 +38,10 @@ FactoryGirl.define do status 'canceled' end + trait :skipped do + status 'skipped' + end + trait :running do status 'running' end diff --git a/spec/factories/commit_statuses.rb b/spec/factories/commit_statuses.rb index 995f2080f10..756b341ecba 100644 --- a/spec/factories/commit_statuses.rb +++ b/spec/factories/commit_statuses.rb @@ -19,6 +19,10 @@ FactoryGirl.define do status 'canceled' end + trait :skipped do + status 'skipped' + end + trait :running do status 'running' end diff --git a/spec/features/abuse_report_spec.rb b/spec/features/abuse_report_spec.rb new file mode 100644 index 00000000000..1e11fb756b2 --- /dev/null +++ b/spec/features/abuse_report_spec.rb @@ -0,0 +1,24 @@ +require 'spec_helper' + +feature 'Abuse reports', feature: true do + let(:another_user) { create(:user) } + + before do + login_as :user + end + + scenario 'Report abuse' do + visit user_path(another_user) + + click_link 'Report abuse' + + fill_in 'abuse_report_message', with: 'This user send spam' + click_button 'Send report' + + expect(page).to have_content 'Thank you for your report' + + visit user_path(another_user) + + expect(page).to have_button("Already reported for abuse") + end +end diff --git a/spec/features/admin/admin_abuse_reports_spec.rb b/spec/features/admin/admin_abuse_reports_spec.rb index c1731e6414a..7fcfe5a54c7 100644 --- a/spec/features/admin/admin_abuse_reports_spec.rb +++ b/spec/features/admin/admin_abuse_reports_spec.rb @@ -4,17 +4,21 @@ describe "Admin::AbuseReports", feature: true, js: true do let(:user) { create(:user) } context 'as an admin' do + before do + login_as :admin + end + describe 'if a user has been reported for abuse' do - before do - create(:abuse_report, user: user) - login_as :admin - end + let!(:abuse_report) { create(:abuse_report, user: user) } describe 'in the abuse report view' do - it "presents a link to the user's profile" do + it 'presents information about abuse report' do visit admin_abuse_reports_path - expect(page).to have_link user.name, href: user_path(user) + expect(page).to have_content('Abuse Reports') + expect(page).to have_content(abuse_report.message) + expect(page).to have_link(user.name, href: user_path(user)) + expect(page).to have_link('Remove user') end end diff --git a/spec/features/admin/admin_browse_spam_logs_spec.rb b/spec/features/admin/admin_browse_spam_logs_spec.rb new file mode 100644 index 00000000000..562ace92598 --- /dev/null +++ b/spec/features/admin/admin_browse_spam_logs_spec.rb @@ -0,0 +1,22 @@ +require 'spec_helper' + +describe 'Admin browse spam logs' do + let!(:spam_log) { create(:spam_log) } + + before do + login_as :admin + end + + scenario 'Browse spam logs' do + visit admin_spam_logs_path + + expect(page).to have_content('Spam Logs') + expect(page).to have_content(spam_log.source_ip) + expect(page).to have_content(spam_log.noteable_type) + expect(page).to have_content('N') + expect(page).to have_content(spam_log.title) + expect(page).to have_content("#{spam_log.description[0...97]}...") + expect(page).to have_link('Remove user') + expect(page).to have_link('Block user') + end +end diff --git a/spec/features/boards/boards_spec.rb b/spec/features/boards/boards_spec.rb index 4aa84fb65d9..973d5b286e9 100644 --- a/spec/features/boards/boards_spec.rb +++ b/spec/features/boards/boards_spec.rb @@ -158,7 +158,7 @@ describe 'Issue Boards', feature: true, js: true do end it 'removes checkmark in new list dropdown after deleting' do - click_button 'Create new list' + click_button 'Add list' wait_for_ajax page.within(find('.board:nth-child(2)')) do @@ -304,7 +304,7 @@ describe 'Issue Boards', feature: true, js: true do context 'new list' do it 'shows all labels in new list dropdown' do - click_button 'Create new list' + click_button 'Add list' wait_for_ajax page.within('.dropdown-menu-issues-board-new') do @@ -315,7 +315,7 @@ describe 'Issue Boards', feature: true, js: true do end it 'creates new list for label' do - click_button 'Create new list' + click_button 'Add list' wait_for_ajax page.within('.dropdown-menu-issues-board-new') do @@ -328,7 +328,7 @@ describe 'Issue Boards', feature: true, js: true do end it 'creates new list for Backlog label' do - click_button 'Create new list' + click_button 'Add list' wait_for_ajax page.within('.dropdown-menu-issues-board-new') do @@ -341,7 +341,7 @@ describe 'Issue Boards', feature: true, js: true do end it 'creates new list for Done label' do - click_button 'Create new list' + click_button 'Add list' wait_for_ajax page.within('.dropdown-menu-issues-board-new') do @@ -354,7 +354,7 @@ describe 'Issue Boards', feature: true, js: true do end it 'keeps dropdown open after adding new list' do - click_button 'Create new list' + click_button 'Add list' wait_for_ajax page.within('.dropdown-menu-issues-board-new') do @@ -369,7 +369,7 @@ describe 'Issue Boards', feature: true, js: true do it 'moves issues from backlog into new list' do wait_for_board_cards(1, 6) - click_button 'Create new list' + click_button 'Add list' wait_for_ajax page.within('.dropdown-menu-issues-board-new') do @@ -382,7 +382,7 @@ describe 'Issue Boards', feature: true, js: true do end it 'creates new list from a new label' do - click_button 'Create new list' + click_button 'Add list' wait_for_ajax diff --git a/spec/features/issues/gfm_autocomplete_spec.rb b/spec/features/issues/gfm_autocomplete_spec.rb new file mode 100644 index 00000000000..c421da97d76 --- /dev/null +++ b/spec/features/issues/gfm_autocomplete_spec.rb @@ -0,0 +1,43 @@ +require 'rails_helper' + +feature 'GFM autocomplete', feature: true, js: true do + include WaitForAjax + let(:user) { create(:user) } + let(:project) { create(:project) } + let(:issue) { create(:issue, project: project) } + + before do + project.team << [user, :master] + login_as(user) + visit namespace_project_issue_path(project.namespace, project, issue) + + wait_for_ajax + end + + it 'opens autocomplete menu when field starts with text' do + page.within '.timeline-content-form' do + find('#note_note').native.send_keys('') + find('#note_note').native.send_keys('@') + end + + expect(page).to have_selector('.atwho-container') + end + + it 'opens autocomplete menu when field is prefixed with non-text character' do + page.within '.timeline-content-form' do + find('#note_note').native.send_keys('') + find('#note_note').native.send_keys('@') + end + + expect(page).to have_selector('.atwho-container') + 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('@') + end + + expect(page).not_to have_selector('.atwho-view') + end +end diff --git a/spec/features/issues/move_spec.rb b/spec/features/issues/move_spec.rb index 055210399a7..c9bec05a9da 100644 --- a/spec/features/issues/move_spec.rb +++ b/spec/features/issues/move_spec.rb @@ -27,7 +27,7 @@ feature 'issue move to another project' do let!(:mr) { create(:merge_request, source_project: old_project) } let(:new_project) { create(:project) } let(:new_project_search) { create(:project) } - let(:text) { 'Text with !1' } + let(:text) { "Text with #{mr.to_reference}" } let(:cross_reference) { old_project.to_reference } background do @@ -43,8 +43,8 @@ feature 'issue move to another project' do expect(current_url).to include project_path(new_project) - expect(page).to have_content("Text with #{cross_reference}!1") - expect(page).to have_content("Moved from #{cross_reference}#1") + expect(page).to have_content("Text with #{cross_reference}#{mr.to_reference}") + expect(page).to have_content("moved from #{cross_reference}#{issue.to_reference}") expect(page).to have_content(issue.title) end diff --git a/spec/features/issues/new_branch_button_spec.rb b/spec/features/issues/new_branch_button_spec.rb index ab901e74617..a4d3053d10c 100644 --- a/spec/features/issues/new_branch_button_spec.rb +++ b/spec/features/issues/new_branch_button_spec.rb @@ -20,12 +20,12 @@ feature 'Start new branch from an issue', feature: true do context "when there is a referenced merge request" do let!(:note) do create(:note, :on_issue, :system, project: project, noteable: issue, - note: "Mentioned in !#{referenced_mr.iid}") + note: "mentioned in #{referenced_mr.to_reference}") end let(:referenced_mr) do create(:merge_request, :simple, source_project: project, target_project: project, - description: "Fixes ##{issue.iid}", author: user) + description: "Fixes #{issue.to_reference}", author: user) end before do diff --git a/spec/features/issues/todo_spec.rb b/spec/features/issues/todo_spec.rb index de8fdda388d..41ff31d2b99 100644 --- a/spec/features/issues/todo_spec.rb +++ b/spec/features/issues/todo_spec.rb @@ -13,8 +13,8 @@ feature 'Manually create a todo item from issue', feature: true, js: true do it 'creates todo when clicking button' do page.within '.issuable-sidebar' do - click_button 'Add Todo' - expect(page).to have_content 'Mark Done' + click_button 'Add todo' + expect(page).to have_content 'Mark done' end page.within '.header-content .todos-pending-count' do @@ -30,8 +30,8 @@ feature 'Manually create a todo item from issue', feature: true, js: true do it 'marks a todo as done' do page.within '.issuable-sidebar' do - click_button 'Add Todo' - click_button 'Mark Done' + click_button 'Add todo' + click_button 'Mark done' end expect(page).to have_selector('.todos-pending-count', visible: false) diff --git a/spec/features/merge_requests/diff_notes_resolve_spec.rb b/spec/features/merge_requests/diff_notes_resolve_spec.rb index eab64bd4b54..d5e3d8e7eff 100644 --- a/spec/features/merge_requests/diff_notes_resolve_spec.rb +++ b/spec/features/merge_requests/diff_notes_resolve_spec.rb @@ -201,7 +201,7 @@ feature 'Diff notes resolve', feature: true, js: true do expect(first('.line-resolve-btn')['data-original-title']).to eq("Resolved by #{user.name}") end - expect(page).not_to have_content('Last updated') + expect(page).to have_content('Last updated') page.within '.line-resolve-all-container' do expect(page).to have_content('0/1 discussion resolved') diff --git a/spec/features/merge_requests/merge_when_build_succeeds_spec.rb b/spec/features/merge_requests/merge_when_build_succeeds_spec.rb index 8eceaad2457..9ad225e3a1b 100644 --- a/spec/features/merge_requests/merge_when_build_succeeds_spec.rb +++ b/spec/features/merge_requests/merge_when_build_succeeds_spec.rb @@ -44,7 +44,7 @@ feature 'Merge When Build Succeeds', feature: true, js: true do expect(page).to have_content "The source branch will not be removed." visit_merge_request(merge_request) # Needed to refresh the page - expect(page).to have_content /Enabled an automatic merge when the build for [0-9a-f]{8} succeeds/i + expect(page).to have_content /enabled an automatic merge when the build for \h{8} succeeds/i end end end diff --git a/spec/features/notes_on_merge_requests_spec.rb b/spec/features/notes_on_merge_requests_spec.rb index 5d7247e2a62..9fffbb43e87 100644 --- a/spec/features/notes_on_merge_requests_spec.rb +++ b/spec/features/notes_on_merge_requests_spec.rb @@ -141,7 +141,7 @@ describe 'Comments', feature: true do let(:project2) { create(:project, :private) } let(:issue) { create(:issue, project: project2) } let(:merge_request) { create(:merge_request, source_project: project, source_branch: 'markdown') } - let!(:note) { create(:note_on_merge_request, :system, noteable: merge_request, project: project, note: "Mentioned in #{issue.to_reference(project)}") } + let!(:note) { create(:note_on_merge_request, :system, noteable: merge_request, project: project, note: "mentioned in #{issue.to_reference(project)}") } it 'shows the system note' do login_as :admin diff --git a/spec/features/projects/pipelines_spec.rb b/spec/features/projects/pipelines_spec.rb index 002c6f6b359..10e5466fc85 100644 --- a/spec/features/projects/pipelines_spec.rb +++ b/spec/features/projects/pipelines_spec.rb @@ -90,13 +90,20 @@ describe "Pipelines" do visit namespace_project_pipelines_path(project.namespace, project) end - it 'is not cancelable' do - expect(page).not_to have_link('Cancel') + it 'is cancelable' do + expect(page).to have_link('Cancel') end it 'has pipeline running' do expect(page).to have_selector('.ci-running') end + + context 'when canceling' do + before { click_link('Cancel') } + + it { expect(page).not_to have_link('Cancel') } + it { expect(page).to have_selector('.ci-canceled') } + end end context 'when failed' do diff --git a/spec/javascripts/fixtures/right_sidebar.html.haml b/spec/javascripts/fixtures/right_sidebar.html.haml index d48b77cf0ce..d259b58f235 100644 --- a/spec/javascripts/fixtures/right_sidebar.html.haml +++ b/spec/javascripts/fixtures/right_sidebar.html.haml @@ -5,9 +5,9 @@ %div.block.issuable-sidebar-header %a.gutter-toggle.pull-right.js-sidebar-toggle %i.fa.fa-angle-double-left - %button.btn.btn-default.issuable-header-btn.pull-right.js-issuable-todo{ type: "button", data: { todo_text: "Add Todo", mark_text: "Mark Done", issuable_id: "1", issuable_type: "issue", url: "/todos" }} + %button.btn.btn-default.issuable-header-btn.pull-right.js-issuable-todo{ type: "button", data: { todo_text: "Add todo", mark_text: "Mark done", issuable_id: "1", issuable_type: "issue", url: "/todos" }} %span.js-issuable-todo-text - Add Todo + Add todo %i.fa.fa-spin.fa-spinner.js-issuable-todo-loading.hidden %form.issuable-context-form diff --git a/spec/lib/constraints/constrainer_helper_spec.rb b/spec/lib/constraints/constrainer_helper_spec.rb deleted file mode 100644 index 27c8d72aefc..00000000000 --- a/spec/lib/constraints/constrainer_helper_spec.rb +++ /dev/null @@ -1,20 +0,0 @@ -require 'spec_helper' - -describe ConstrainerHelper, lib: true do - include ConstrainerHelper - - describe '#extract_resource_path' do - it { expect(extract_resource_path('/gitlab/')).to eq('gitlab') } - it { expect(extract_resource_path('///gitlab//')).to eq('gitlab') } - it { expect(extract_resource_path('/gitlab.atom')).to eq('gitlab') } - - context 'relative url' do - before do - allow(Gitlab::Application.config).to receive(:relative_url_root) { '/gitlab' } - end - - it { expect(extract_resource_path('/gitlab/foo')).to eq('foo') } - it { expect(extract_resource_path('/foo/bar')).to eq('foo/bar') } - end - end -end diff --git a/spec/lib/constraints/group_url_constrainer_spec.rb b/spec/lib/constraints/group_url_constrainer_spec.rb index 42299b17c2b..892554f2870 100644 --- a/spec/lib/constraints/group_url_constrainer_spec.rb +++ b/spec/lib/constraints/group_url_constrainer_spec.rb @@ -4,16 +4,20 @@ describe GroupUrlConstrainer, lib: true do let!(:group) { create(:group, path: 'gitlab') } describe '#matches?' do - context 'root group' do - it { expect(subject.matches?(request '/gitlab')).to be_truthy } - it { expect(subject.matches?(request '/gitlab.atom')).to be_truthy } - it { expect(subject.matches?(request '/gitlab/edit')).to be_falsey } - it { expect(subject.matches?(request '/gitlab-ce')).to be_falsey } - it { expect(subject.matches?(request '/.gitlab')).to be_falsey } + context 'valid request' do + let(:request) { build_request(group.path) } + + it { expect(subject.matches?(request)).to be_truthy } + end + + context 'invalid request' do + let(:request) { build_request('foo') } + + it { expect(subject.matches?(request)).to be_falsey } end end - def request(path) - double(:request, path: path) + def build_request(path) + double(:request, params: { id: path }) end end diff --git a/spec/lib/constraints/project_url_constrainer_spec.rb b/spec/lib/constraints/project_url_constrainer_spec.rb new file mode 100644 index 00000000000..94266f6653b --- /dev/null +++ b/spec/lib/constraints/project_url_constrainer_spec.rb @@ -0,0 +1,32 @@ +require 'spec_helper' + +describe ProjectUrlConstrainer, lib: true do + let!(:project) { create(:project) } + let!(:namespace) { project.namespace } + + describe '#matches?' do + context 'valid request' do + let(:request) { build_request(namespace.path, project.path) } + + it { expect(subject.matches?(request)).to be_truthy } + end + + context 'invalid request' do + context "non-existing project" do + let(:request) { build_request('foo', 'bar') } + + it { expect(subject.matches?(request)).to be_falsey } + end + + context "project id ending with .git" do + let(:request) { build_request(namespace.path, project.path + '.git') } + + it { expect(subject.matches?(request)).to be_falsey } + end + end + end + + def build_request(namespace, project) + double(:request, params: { namespace_id: namespace, id: project }) + end +end diff --git a/spec/lib/constraints/user_url_constrainer_spec.rb b/spec/lib/constraints/user_url_constrainer_spec.rb index b3f8530c609..207b6fe6c9e 100644 --- a/spec/lib/constraints/user_url_constrainer_spec.rb +++ b/spec/lib/constraints/user_url_constrainer_spec.rb @@ -1,16 +1,23 @@ require 'spec_helper' describe UserUrlConstrainer, lib: true do - let!(:username) { create(:user, username: 'dz') } + let!(:user) { create(:user, username: 'dz') } describe '#matches?' do - it { expect(subject.matches?(request '/dz')).to be_truthy } - it { expect(subject.matches?(request '/dz.atom')).to be_truthy } - it { expect(subject.matches?(request '/dz/projects')).to be_falsey } - it { expect(subject.matches?(request '/gitlab')).to be_falsey } + context 'valid request' do + let(:request) { build_request(user.username) } + + it { expect(subject.matches?(request)).to be_truthy } + end + + context 'invalid request' do + let(:request) { build_request('foo') } + + it { expect(subject.matches?(request)).to be_falsey } + end end - def request(path) - double(:request, path: path) + def build_request(username) + double(:request, params: { username: username }) end end diff --git a/spec/lib/gitlab/identifier_spec.rb b/spec/lib/gitlab/identifier_spec.rb index f42c4453dd1..bb758a8a202 100644 --- a/spec/lib/gitlab/identifier_spec.rb +++ b/spec/lib/gitlab/identifier_spec.rb @@ -40,7 +40,7 @@ describe Gitlab::Identifier do describe '#identify_using_commit' do it "returns the User for an existing commit author's Email address" do - commit = double(:commit, author_email: user.email) + commit = double(:commit, author: user, author_email: user.email) expect(project).to receive(:commit).with('123').and_return(commit) @@ -62,10 +62,9 @@ describe Gitlab::Identifier do end it 'caches the found users per Email' do - commit = double(:commit, author_email: user.email) + commit = double(:commit, author: user, author_email: user.email) expect(project).to receive(:commit).with('123').twice.and_return(commit) - expect(User).to receive(:find_by_any_email).once.and_call_original 2.times do expect(identifier.identify_using_commit(project, '123')).to eq(user) diff --git a/spec/models/ci/pipeline_spec.rb b/spec/models/ci/pipeline_spec.rb index ea022e03608..3b12f16b4db 100644 --- a/spec/models/ci/pipeline_spec.rb +++ b/spec/models/ci/pipeline_spec.rb @@ -402,6 +402,160 @@ describe Ci::Pipeline, models: true do end end + describe '#cancelable?' do + %i[created running pending].each do |status0| + context "when there is a build #{status0}" do + before do + create(:ci_build, status0, pipeline: pipeline) + end + + it 'is cancelable' do + expect(pipeline.cancelable?).to be_truthy + end + end + + context "when there is an external job #{status0}" do + before do + create(:generic_commit_status, status0, pipeline: pipeline) + end + + it 'is cancelable' do + expect(pipeline.cancelable?).to be_truthy + end + end + + %i[success failed canceled].each do |status1| + context "when there are generic_commit_status jobs for #{status0} and #{status1}" do + before do + create(:generic_commit_status, status0, pipeline: pipeline) + create(:generic_commit_status, status1, pipeline: pipeline) + end + + it 'is cancelable' do + expect(pipeline.cancelable?).to be_truthy + end + end + + context "when there are generic_commit_status and ci_build jobs for #{status0} and #{status1}" do + before do + create(:generic_commit_status, status0, pipeline: pipeline) + create(:ci_build, status1, pipeline: pipeline) + end + + it 'is cancelable' do + expect(pipeline.cancelable?).to be_truthy + end + end + + context "when there are ci_build jobs for #{status0} and #{status1}" do + before do + create(:ci_build, status0, pipeline: pipeline) + create(:ci_build, status1, pipeline: pipeline) + end + + it 'is cancelable' do + expect(pipeline.cancelable?).to be_truthy + end + end + end + end + + %i[success failed canceled].each do |status| + context "when there is a build #{status}" do + before do + create(:ci_build, status, pipeline: pipeline) + end + + it 'is not cancelable' do + expect(pipeline.cancelable?).to be_falsey + end + end + + context "when there is an external job #{status}" do + before do + create(:generic_commit_status, status, pipeline: pipeline) + end + + it 'is not cancelable' do + expect(pipeline.cancelable?).to be_falsey + end + end + end + end + + describe '#cancel_running' do + let(:latest_status) { pipeline.statuses.pluck(:status) } + + context 'when there is a running external job and created build' do + before do + create(:ci_build, :running, pipeline: pipeline) + create(:generic_commit_status, :running, pipeline: pipeline) + + pipeline.cancel_running + end + + it 'cancels both jobs' do + expect(latest_status).to contain_exactly('canceled', 'canceled') + end + end + + context 'when builds are in different stages' do + before do + create(:ci_build, :running, stage_idx: 0, pipeline: pipeline) + create(:ci_build, :running, stage_idx: 1, pipeline: pipeline) + + pipeline.cancel_running + end + + it 'cancels both jobs' do + expect(latest_status).to contain_exactly('canceled', 'canceled') + end + end + end + + describe '#retry_failed' do + let(:latest_status) { pipeline.statuses.latest.pluck(:status) } + + context 'when there is a failed build and failed external status' do + before do + create(:ci_build, :failed, name: 'build', pipeline: pipeline) + create(:generic_commit_status, :failed, name: 'jenkins', pipeline: pipeline) + + pipeline.retry_failed(create(:user)) + end + + it 'retries only build' do + expect(latest_status).to contain_exactly('pending', 'failed') + end + end + + context 'when builds are in different stages' do + before do + create(:ci_build, :failed, name: 'build', stage_idx: 0, pipeline: pipeline) + create(:ci_build, :failed, name: 'jenkins', stage_idx: 1, pipeline: pipeline) + + pipeline.retry_failed(create(:user)) + end + + it 'retries both builds' do + expect(latest_status).to contain_exactly('pending', 'pending') + end + end + + context 'when there are canceled and failed' do + before do + create(:ci_build, :failed, name: 'build', stage_idx: 0, pipeline: pipeline) + create(:ci_build, :canceled, name: 'jenkins', stage_idx: 1, pipeline: pipeline) + + pipeline.retry_failed(create(:user)) + end + + it 'retries both builds' do + expect(latest_status).to contain_exactly('pending', 'pending') + end + end + end + describe '#execute_hooks' do let!(:build_a) { create_build('a', 0) } let!(:build_b) { create_build('b', 1) } diff --git a/spec/models/concerns/has_status_spec.rb b/spec/models/concerns/has_status_spec.rb index 87bffbdc54e..9defb17dc92 100644 --- a/spec/models/concerns/has_status_spec.rb +++ b/spec/models/concerns/has_status_spec.rb @@ -123,4 +123,100 @@ describe HasStatus do it_behaves_like 'build status summary' end end + + context 'for scope with one status' do + shared_examples 'having a job' do |status| + %i[ci_build generic_commit_status].each do |type| + context "when it's #{status} #{type} job" do + let!(:job) { create(type, status) } + + describe ".#{status}" do + it 'contains the job' do + expect(CommitStatus.public_send(status).all). + to contain_exactly(job) + end + end + + describe '.relevant' do + if status == :created + it 'contains nothing' do + expect(CommitStatus.relevant.all).to be_empty + end + else + it 'contains the job' do + expect(CommitStatus.relevant.all).to contain_exactly(job) + end + end + end + end + end + end + + %i[created running pending success + failed canceled skipped].each do |status| + it_behaves_like 'having a job', status + end + end + + context 'for scope with more statuses' do + shared_examples 'containing the job' do |status| + %i[ci_build generic_commit_status].each do |type| + context "when it's #{status} #{type} job" do + let!(:job) { create(type, status) } + + it 'contains the job' do + is_expected.to contain_exactly(job) + end + end + end + end + + shared_examples 'not containing the job' do |status| + %i[ci_build generic_commit_status].each do |type| + context "when it's #{status} #{type} job" do + let!(:job) { create(type, status) } + + it 'contains nothing' do + is_expected.to be_empty + end + end + end + end + + describe '.running_or_pending' do + subject { CommitStatus.running_or_pending } + + %i[running pending].each do |status| + it_behaves_like 'containing the job', status + end + + %i[created failed success].each do |status| + it_behaves_like 'not containing the job', status + end + end + + describe '.finished' do + subject { CommitStatus.finished } + + %i[success failed canceled].each do |status| + it_behaves_like 'containing the job', status + end + + %i[created running pending].each do |status| + it_behaves_like 'not containing the job', status + end + end + + describe '.cancelable' do + subject { CommitStatus.cancelable } + + %i[running pending created].each do |status| + it_behaves_like 'containing the job', status + end + + %i[failed success skipped canceled].each do |status| + it_behaves_like 'not containing the job', status + end + end + end end diff --git a/spec/models/note_spec.rb b/spec/models/note_spec.rb index e6b6e7c0634..17a15b12dcb 100644 --- a/spec/models/note_spec.rb +++ b/spec/models/note_spec.rb @@ -223,7 +223,7 @@ describe Note, models: true do let(:note) do create :note, noteable: ext_issue, project: ext_proj, - note: "Mentioned in issue #{private_issue.to_reference(ext_proj)}", + note: "mentioned in issue #{private_issue.to_reference(ext_proj)}", system: true end diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index e84042f8063..91826e5884d 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -752,6 +752,17 @@ describe User, models: true do end end + describe '.find_by_username' do + it 'returns nil if not found' do + expect(described_class.find_by_username('JohnDoe')).to be_nil + end + + it 'is case-insensitive' do + user = create(:user, username: 'JohnDoe') + expect(described_class.find_by_username('JOHNDOE')).to eq user + end + end + describe '.find_by_username!' do it 'raises RecordNotFound' do expect { described_class.find_by_username!('JohnDoe') }. diff --git a/spec/requests/api/notes_spec.rb b/spec/requests/api/notes_spec.rb index 0124b7271b3..b71a4c5a56e 100644 --- a/spec/requests/api/notes_spec.rb +++ b/spec/requests/api/notes_spec.rb @@ -25,7 +25,7 @@ describe API::API, api: true do let!(:cross_reference_note) do create :note, noteable: ext_issue, project: ext_proj, - note: "Mentioned in issue #{private_issue.to_reference(ext_proj)}", + note: "mentioned in issue #{private_issue.to_reference(ext_proj)}", system: true end diff --git a/spec/routing/project_routing_spec.rb b/spec/routing/project_routing_spec.rb index 2322430d212..b6e7da841b1 100644 --- a/spec/routing/project_routing_spec.rb +++ b/spec/routing/project_routing_spec.rb @@ -1,511 +1,531 @@ require 'spec_helper' -# Shared examples for a resource inside a Project -# -# By default it tests all the default REST actions: index, create, new, edit, -# show, update, and destroy. You can remove actions by customizing the -# `actions` variable. -# -# It also expects a `controller` variable to be available which defines both -# the path to the resource as well as the controller name. -# -# Examples -# -# # Default behavior -# it_behaves_like 'RESTful project resources' do -# let(:controller) { 'issues' } -# end -# -# # Customizing actions -# it_behaves_like 'RESTful project resources' do -# let(:actions) { [:index] } -# let(:controller) { 'issues' } -# end -shared_examples 'RESTful project resources' do - let(:actions) { [:index, :create, :new, :edit, :show, :update, :destroy] } - - it 'to #index' do - expect(get("/gitlab/gitlabhq/#{controller}")).to route_to("projects/#{controller}#index", namespace_id: 'gitlab', project_id: 'gitlabhq') if actions.include?(:index) - end - - it 'to #create' do - expect(post("/gitlab/gitlabhq/#{controller}")).to route_to("projects/#{controller}#create", namespace_id: 'gitlab', project_id: 'gitlabhq') if actions.include?(:create) - end - - it 'to #new' do - expect(get("/gitlab/gitlabhq/#{controller}/new")).to route_to("projects/#{controller}#new", namespace_id: 'gitlab', project_id: 'gitlabhq') if actions.include?(:new) - end - - it 'to #edit' do - expect(get("/gitlab/gitlabhq/#{controller}/1/edit")).to route_to("projects/#{controller}#edit", namespace_id: 'gitlab', project_id: 'gitlabhq', id: '1') if actions.include?(:edit) - end - - it 'to #show' do - expect(get("/gitlab/gitlabhq/#{controller}/1")).to route_to("projects/#{controller}#show", namespace_id: 'gitlab', project_id: 'gitlabhq', id: '1') if actions.include?(:show) - end - - it 'to #update' do - expect(put("/gitlab/gitlabhq/#{controller}/1")).to route_to("projects/#{controller}#update", namespace_id: 'gitlab', project_id: 'gitlabhq', id: '1') if actions.include?(:update) - end - - it 'to #destroy' do - expect(delete("/gitlab/gitlabhq/#{controller}/1")).to route_to("projects/#{controller}#destroy", namespace_id: 'gitlab', project_id: 'gitlabhq', id: '1') if actions.include?(:destroy) - end -end - -# projects POST /projects(.:format) projects#create -# new_project GET /projects/new(.:format) projects#new -# files_project GET /:id/files(.:format) projects#files -# edit_project GET /:id/edit(.:format) projects#edit -# project GET /:id(.:format) projects#show -# PUT /:id(.:format) projects#update -# DELETE /:id(.:format) projects#destroy -# preview_markdown_project POST /:id/preview_markdown(.:format) projects#preview_markdown -describe ProjectsController, 'routing' do - it 'to #create' do - expect(post('/projects')).to route_to('projects#create') - end - - it 'to #new' do - expect(get('/projects/new')).to route_to('projects#new') - end - - it 'to #edit' do - expect(get('/gitlab/gitlabhq/edit')).to route_to('projects#edit', namespace_id: 'gitlab', id: 'gitlabhq') - end - - it 'to #autocomplete_sources' do - expect(get('/gitlab/gitlabhq/autocomplete_sources')).to route_to('projects#autocomplete_sources', namespace_id: 'gitlab', id: 'gitlabhq') - end - - it 'to #show' do - expect(get('/gitlab/gitlabhq')).to route_to('projects#show', namespace_id: 'gitlab', id: 'gitlabhq') - expect(get('/gitlab/gitlabhq.keys')).to route_to('projects#show', namespace_id: 'gitlab', id: 'gitlabhq.keys') - end - - it 'to #update' do - expect(put('/gitlab/gitlabhq')).to route_to('projects#update', namespace_id: 'gitlab', id: 'gitlabhq') - end - - it 'to #destroy' do - expect(delete('/gitlab/gitlabhq')).to route_to('projects#destroy', namespace_id: 'gitlab', id: 'gitlabhq') - end - - it 'to #preview_markdown' do - expect(post('/gitlab/gitlabhq/preview_markdown')).to( - route_to('projects#preview_markdown', namespace_id: 'gitlab', id: 'gitlabhq') - ) - end -end - -# pages_project_wikis GET /:project_id/wikis/pages(.:format) projects/wikis#pages -# history_project_wiki GET /:project_id/wikis/:id/history(.:format) projects/wikis#history -# project_wikis POST /:project_id/wikis(.:format) projects/wikis#create -# edit_project_wiki GET /:project_id/wikis/:id/edit(.:format) projects/wikis#edit -# project_wiki GET /:project_id/wikis/:id(.:format) projects/wikis#show -# DELETE /:project_id/wikis/:id(.:format) projects/wikis#destroy -describe Projects::WikisController, 'routing' do - it 'to #pages' do - expect(get('/gitlab/gitlabhq/wikis/pages')).to route_to('projects/wikis#pages', namespace_id: 'gitlab', project_id: 'gitlabhq') - end - - it 'to #history' do - expect(get('/gitlab/gitlabhq/wikis/1/history')).to route_to('projects/wikis#history', namespace_id: 'gitlab', project_id: 'gitlabhq', id: '1') - end - - it_behaves_like 'RESTful project resources' do - let(:actions) { [:create, :edit, :show, :destroy] } - let(:controller) { 'wikis' } - end -end - -# branches_project_repository GET /:project_id/repository/branches(.:format) projects/repositories#branches -# tags_project_repository GET /:project_id/repository/tags(.:format) projects/repositories#tags -# archive_project_repository GET /:project_id/repository/archive(.:format) projects/repositories#archive -# edit_project_repository GET /:project_id/repository/edit(.:format) projects/repositories#edit -describe Projects::RepositoriesController, 'routing' do - it 'to #archive' do - expect(get('/gitlab/gitlabhq/repository/archive')).to route_to('projects/repositories#archive', namespace_id: 'gitlab', project_id: 'gitlabhq') - end - - it 'to #archive format:zip' do - expect(get('/gitlab/gitlabhq/repository/archive.zip')).to route_to('projects/repositories#archive', namespace_id: 'gitlab', project_id: 'gitlabhq', format: 'zip') - end - - it 'to #archive format:tar.bz2' do - expect(get('/gitlab/gitlabhq/repository/archive.tar.bz2')).to route_to('projects/repositories#archive', namespace_id: 'gitlab', project_id: 'gitlabhq', format: 'tar.bz2') - end -end - -describe Projects::BranchesController, 'routing' do - it 'to #branches' do - expect(get('/gitlab/gitlabhq/branches')).to route_to('projects/branches#index', namespace_id: 'gitlab', project_id: 'gitlabhq') - expect(delete('/gitlab/gitlabhq/branches/feature%2345')).to route_to('projects/branches#destroy', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'feature#45') - expect(delete('/gitlab/gitlabhq/branches/feature%2B45')).to route_to('projects/branches#destroy', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'feature+45') - expect(delete('/gitlab/gitlabhq/branches/feature@45')).to route_to('projects/branches#destroy', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'feature@45') - expect(delete('/gitlab/gitlabhq/branches/feature%2345/foo/bar/baz')).to route_to('projects/branches#destroy', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'feature#45/foo/bar/baz') - expect(delete('/gitlab/gitlabhq/branches/feature%2B45/foo/bar/baz')).to route_to('projects/branches#destroy', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'feature+45/foo/bar/baz') - expect(delete('/gitlab/gitlabhq/branches/feature@45/foo/bar/baz')).to route_to('projects/branches#destroy', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'feature@45/foo/bar/baz') - end -end - -describe Projects::TagsController, 'routing' do - it 'to #tags' do - expect(get('/gitlab/gitlabhq/tags')).to route_to('projects/tags#index', namespace_id: 'gitlab', project_id: 'gitlabhq') - expect(delete('/gitlab/gitlabhq/tags/feature%2345')).to route_to('projects/tags#destroy', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'feature#45') - expect(delete('/gitlab/gitlabhq/tags/feature%2B45')).to route_to('projects/tags#destroy', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'feature+45') - expect(delete('/gitlab/gitlabhq/tags/feature@45')).to route_to('projects/tags#destroy', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'feature@45') - expect(delete('/gitlab/gitlabhq/tags/feature%2345/foo/bar/baz')).to route_to('projects/tags#destroy', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'feature#45/foo/bar/baz') - expect(delete('/gitlab/gitlabhq/tags/feature%2B45/foo/bar/baz')).to route_to('projects/tags#destroy', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'feature+45/foo/bar/baz') - expect(delete('/gitlab/gitlabhq/tags/feature@45/foo/bar/baz')).to route_to('projects/tags#destroy', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'feature@45/foo/bar/baz') - end -end - -# project_deploy_keys GET /:project_id/deploy_keys(.:format) deploy_keys#index -# POST /:project_id/deploy_keys(.:format) deploy_keys#create -# new_project_deploy_key GET /:project_id/deploy_keys/new(.:format) deploy_keys#new -# project_deploy_key GET /:project_id/deploy_keys/:id(.:format) deploy_keys#show -# DELETE /:project_id/deploy_keys/:id(.:format) deploy_keys#destroy -describe Projects::DeployKeysController, 'routing' do - it_behaves_like 'RESTful project resources' do - let(:actions) { [:index, :new, :create] } - let(:controller) { 'deploy_keys' } - end -end - -# project_protected_branches GET /:project_id/protected_branches(.:format) protected_branches#index -# POST /:project_id/protected_branches(.:format) protected_branches#create -# project_protected_branch DELETE /:project_id/protected_branches/:id(.:format) protected_branches#destroy -describe Projects::ProtectedBranchesController, 'routing' do - it_behaves_like 'RESTful project resources' do - let(:actions) { [:index, :create, :destroy] } - let(:controller) { 'protected_branches' } - end -end - -# switch_project_refs GET /:project_id/refs/switch(.:format) refs#switch -# logs_tree_project_ref GET /:project_id/refs/:id/logs_tree(.:format) refs#logs_tree -# logs_file_project_ref GET /:project_id/refs/:id/logs_tree/:path(.:format) refs#logs_tree -describe Projects::RefsController, 'routing' do - it 'to #switch' do - expect(get('/gitlab/gitlabhq/refs/switch')).to route_to('projects/refs#switch', namespace_id: 'gitlab', project_id: 'gitlabhq') - end - - it 'to #logs_tree' do - expect(get('/gitlab/gitlabhq/refs/stable/logs_tree')).to route_to('projects/refs#logs_tree', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'stable') - expect(get('/gitlab/gitlabhq/refs/feature%2345/logs_tree')).to route_to('projects/refs#logs_tree', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'feature#45') - expect(get('/gitlab/gitlabhq/refs/feature%2B45/logs_tree')).to route_to('projects/refs#logs_tree', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'feature+45') - expect(get('/gitlab/gitlabhq/refs/feature@45/logs_tree')).to route_to('projects/refs#logs_tree', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'feature@45') - expect(get('/gitlab/gitlabhq/refs/stable/logs_tree/foo/bar/baz')).to route_to('projects/refs#logs_tree', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'stable', path: 'foo/bar/baz') - expect(get('/gitlab/gitlabhq/refs/feature%2345/logs_tree/foo/bar/baz')).to route_to('projects/refs#logs_tree', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'feature#45', path: 'foo/bar/baz') - expect(get('/gitlab/gitlabhq/refs/feature%2B45/logs_tree/foo/bar/baz')).to route_to('projects/refs#logs_tree', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'feature+45', path: 'foo/bar/baz') - expect(get('/gitlab/gitlabhq/refs/feature@45/logs_tree/foo/bar/baz')).to route_to('projects/refs#logs_tree', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'feature@45', path: 'foo/bar/baz') - expect(get('/gitlab/gitlabhq/refs/stable/logs_tree/files.scss')).to route_to('projects/refs#logs_tree', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'stable', path: 'files.scss') - end -end - -# diffs_namespace_project_merge_request GET /:namespace_id/:project_id/merge_requests/:id/diffs(.:format) projects/merge_requests#diffs -# commits_namespace_project_merge_request GET /:namespace_id/:project_id/merge_requests/:id/commits(.:format) projects/merge_requests#commits -# merge_namespace_project_merge_request POST /:namespace_id/:project_id/merge_requests/:id/merge(.:format) projects/merge_requests#merge -# merge_check_namespace_project_merge_request GET /:namespace_id/:project_id/merge_requests/:id/merge_check(.:format) projects/merge_requests#merge_check -# ci_status_namespace_project_merge_request GET /:namespace_id/:project_id/merge_requests/:id/ci_status(.:format) projects/merge_requests#ci_status -# toggle_subscription_namespace_project_merge_request POST /:namespace_id/:project_id/merge_requests/:id/toggle_subscription(.:format) projects/merge_requests#toggle_subscription -# branch_from_namespace_project_merge_requests GET /:namespace_id/:project_id/merge_requests/branch_from(.:format) projects/merge_requests#branch_from -# branch_to_namespace_project_merge_requests GET /:namespace_id/:project_id/merge_requests/branch_to(.:format) projects/merge_requests#branch_to -# update_branches_namespace_project_merge_requests GET /:namespace_id/:project_id/merge_requests/update_branches(.:format) projects/merge_requests#update_branches -# namespace_project_merge_requests GET /:namespace_id/:project_id/merge_requests(.:format) projects/merge_requests#index -# POST /:namespace_id/:project_id/merge_requests(.:format) projects/merge_requests#create -# new_namespace_project_merge_request GET /:namespace_id/:project_id/merge_requests/new(.:format) projects/merge_requests#new -# edit_namespace_project_merge_request GET /:namespace_id/:project_id/merge_requests/:id/edit(.:format) projects/merge_requests#edit -# namespace_project_merge_request GET /:namespace_id/:project_id/merge_requests/:id(.:format) projects/merge_requests#show -# PATCH /:namespace_id/:project_id/merge_requests/:id(.:format) projects/merge_requests#update -# PUT /:namespace_id/:project_id/merge_requests/:id(.:format) projects/merge_requests#update -describe Projects::MergeRequestsController, 'routing' do - it 'to #diffs' do - expect(get('/gitlab/gitlabhq/merge_requests/1/diffs')).to route_to('projects/merge_requests#diffs', namespace_id: 'gitlab', project_id: 'gitlabhq', id: '1') - end - - it 'to #commits' do - expect(get('/gitlab/gitlabhq/merge_requests/1/commits')).to route_to('projects/merge_requests#commits', namespace_id: 'gitlab', project_id: 'gitlabhq', id: '1') - end - - it 'to #merge' do - expect(post('/gitlab/gitlabhq/merge_requests/1/merge')).to route_to( - 'projects/merge_requests#merge', - namespace_id: 'gitlab', project_id: 'gitlabhq', id: '1' - ) - end - - it 'to #merge_check' do - expect(get('/gitlab/gitlabhq/merge_requests/1/merge_check')).to route_to('projects/merge_requests#merge_check', namespace_id: 'gitlab', project_id: 'gitlabhq', id: '1') - end - - it 'to #branch_from' do - expect(get('/gitlab/gitlabhq/merge_requests/branch_from')).to route_to('projects/merge_requests#branch_from', namespace_id: 'gitlab', project_id: 'gitlabhq') - end - - it 'to #branch_to' do - expect(get('/gitlab/gitlabhq/merge_requests/branch_to')).to route_to('projects/merge_requests#branch_to', namespace_id: 'gitlab', project_id: 'gitlabhq') - end - - it 'to #show' do - expect(get('/gitlab/gitlabhq/merge_requests/1.diff')).to route_to('projects/merge_requests#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: '1', format: 'diff') - expect(get('/gitlab/gitlabhq/merge_requests/1.patch')).to route_to('projects/merge_requests#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: '1', format: 'patch') - end - - it_behaves_like 'RESTful project resources' do - let(:controller) { 'merge_requests' } - let(:actions) { [:index, :create, :new, :edit, :show, :update] } - end -end - -# raw_project_snippet GET /:project_id/snippets/:id/raw(.:format) snippets#raw -# project_snippets GET /:project_id/snippets(.:format) snippets#index -# POST /:project_id/snippets(.:format) snippets#create -# new_project_snippet GET /:project_id/snippets/new(.:format) snippets#new -# edit_project_snippet GET /:project_id/snippets/:id/edit(.:format) snippets#edit -# project_snippet GET /:project_id/snippets/:id(.:format) snippets#show -# PUT /:project_id/snippets/:id(.:format) snippets#update -# DELETE /:project_id/snippets/:id(.:format) snippets#destroy -describe SnippetsController, 'routing' do - it 'to #raw' do - expect(get('/gitlab/gitlabhq/snippets/1/raw')).to route_to('projects/snippets#raw', namespace_id: 'gitlab', project_id: 'gitlabhq', id: '1') - end - - it 'to #index' do - expect(get('/gitlab/gitlabhq/snippets')).to route_to('projects/snippets#index', namespace_id: 'gitlab', project_id: 'gitlabhq') - end - - it 'to #create' do - expect(post('/gitlab/gitlabhq/snippets')).to route_to('projects/snippets#create', namespace_id: 'gitlab', project_id: 'gitlabhq') - end - - it 'to #new' do - expect(get('/gitlab/gitlabhq/snippets/new')).to route_to('projects/snippets#new', namespace_id: 'gitlab', project_id: 'gitlabhq') - end - - it 'to #edit' do - expect(get('/gitlab/gitlabhq/snippets/1/edit')).to route_to('projects/snippets#edit', namespace_id: 'gitlab', project_id: 'gitlabhq', id: '1') - end - - it 'to #show' do - expect(get('/gitlab/gitlabhq/snippets/1')).to route_to('projects/snippets#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: '1') - end - - it 'to #update' do - expect(put('/gitlab/gitlabhq/snippets/1')).to route_to('projects/snippets#update', namespace_id: 'gitlab', project_id: 'gitlabhq', id: '1') - end - - it 'to #destroy' do - expect(delete('/gitlab/gitlabhq/snippets/1')).to route_to('projects/snippets#destroy', namespace_id: 'gitlab', project_id: 'gitlabhq', id: '1') - end -end - -# test_project_hook GET /:project_id/hooks/:id/test(.:format) hooks#test -# project_hooks GET /:project_id/hooks(.:format) hooks#index -# POST /:project_id/hooks(.:format) hooks#create -# project_hook DELETE /:project_id/hooks/:id(.:format) hooks#destroy -describe Projects::HooksController, 'routing' do - it 'to #test' do - expect(get('/gitlab/gitlabhq/hooks/1/test')).to route_to('projects/hooks#test', namespace_id: 'gitlab', project_id: 'gitlabhq', id: '1') - end - - it_behaves_like 'RESTful project resources' do - let(:actions) { [:index, :create, :destroy] } - let(:controller) { 'hooks' } - end -end - -# project_commit GET /:project_id/commit/:id(.:format) commit#show {id: /\h{7,40}/, project_id: /[^\/]+/} -describe Projects::CommitController, 'routing' do - it 'to #show' do - expect(get('/gitlab/gitlabhq/commit/4246fbd')).to route_to('projects/commit#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: '4246fbd') - expect(get('/gitlab/gitlabhq/commit/4246fbd.diff')).to route_to('projects/commit#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: '4246fbd', format: 'diff') - expect(get('/gitlab/gitlabhq/commit/4246fbd.patch')).to route_to('projects/commit#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: '4246fbd', format: 'patch') - expect(get('/gitlab/gitlabhq/commit/4246fbd13872934f72a8fd0d6fb1317b47b59cb5')).to route_to('projects/commit#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: '4246fbd13872934f72a8fd0d6fb1317b47b59cb5') - end -end - -# patch_project_commit GET /:project_id/commits/:id/patch(.:format) commits#patch -# project_commits GET /:project_id/commits(.:format) commits#index -# POST /:project_id/commits(.:format) commits#create -# project_commit GET /:project_id/commits/:id(.:format) commits#show -describe Projects::CommitsController, 'routing' do - it_behaves_like 'RESTful project resources' do - let(:actions) { [:show] } - let(:controller) { 'commits' } - end - - it 'to #show' do - expect(get('/gitlab/gitlabhq/commits/master.atom')).to route_to('projects/commits#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'master.atom') - end -end - -# project_project_members GET /:project_id/project_members(.:format) project_members#index -# POST /:project_id/project_members(.:format) project_members#create -# PUT /:project_id/project_members/:id(.:format) project_members#update -# DELETE /:project_id/project_members/:id(.:format) project_members#destroy -describe Projects::ProjectMembersController, 'routing' do - it_behaves_like 'RESTful project resources' do - let(:actions) { [:index, :create, :update, :destroy] } - let(:controller) { 'project_members' } - end -end - -# project_milestones GET /:project_id/milestones(.:format) milestones#index -# POST /:project_id/milestones(.:format) milestones#create -# new_project_milestone GET /:project_id/milestones/new(.:format) milestones#new -# edit_project_milestone GET /:project_id/milestones/:id/edit(.:format) milestones#edit -# project_milestone GET /:project_id/milestones/:id(.:format) milestones#show -# PUT /:project_id/milestones/:id(.:format) milestones#update -# DELETE /:project_id/milestones/:id(.:format) milestones#destroy -describe Projects::MilestonesController, 'routing' do - it_behaves_like 'RESTful project resources' do - let(:controller) { 'milestones' } - let(:actions) { [:index, :create, :new, :edit, :show, :update] } - end -end - -# project_labels GET /:project_id/labels(.:format) labels#index -describe Projects::LabelsController, 'routing' do - it 'to #index' do - expect(get('/gitlab/gitlabhq/labels')).to route_to('projects/labels#index', namespace_id: 'gitlab', project_id: 'gitlabhq') - end -end - -# sort_project_issues POST /:project_id/issues/sort(.:format) issues#sort -# bulk_update_project_issues POST /:project_id/issues/bulk_update(.:format) issues#bulk_update -# search_project_issues GET /:project_id/issues/search(.:format) issues#search -# project_issues GET /:project_id/issues(.:format) issues#index -# POST /:project_id/issues(.:format) issues#create -# new_project_issue GET /:project_id/issues/new(.:format) issues#new -# edit_project_issue GET /:project_id/issues/:id/edit(.:format) issues#edit -# project_issue GET /:project_id/issues/:id(.:format) issues#show -# PUT /:project_id/issues/:id(.:format) issues#update -# DELETE /:project_id/issues/:id(.:format) issues#destroy -describe Projects::IssuesController, 'routing' do - it 'to #bulk_update' do - expect(post('/gitlab/gitlabhq/issues/bulk_update')).to route_to('projects/issues#bulk_update', namespace_id: 'gitlab', project_id: 'gitlabhq') - end - - it_behaves_like 'RESTful project resources' do - let(:controller) { 'issues' } - let(:actions) { [:index, :create, :new, :edit, :show, :update] } - end -end - -# project_notes GET /:project_id/notes(.:format) notes#index -# POST /:project_id/notes(.:format) notes#create -# project_note DELETE /:project_id/notes/:id(.:format) notes#destroy -describe Projects::NotesController, 'routing' do - it_behaves_like 'RESTful project resources' do - let(:actions) { [:index, :create, :destroy] } - let(:controller) { 'notes' } - end -end - -# project_blame GET /:project_id/blame/:id(.:format) blame#show {id: /.+/, project_id: /[^\/]+/} -describe Projects::BlameController, 'routing' do - it 'to #show' do - expect(get('/gitlab/gitlabhq/blame/master/app/models/project.rb')).to route_to('projects/blame#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'master/app/models/project.rb') - expect(get('/gitlab/gitlabhq/blame/master/files.scss')).to route_to('projects/blame#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'master/files.scss') - end -end - -# project_blob GET /:project_id/blob/:id(.:format) blob#show {id: /.+/, project_id: /[^\/]+/} -describe Projects::BlobController, 'routing' do - it 'to #show' do - expect(get('/gitlab/gitlabhq/blob/master/app/models/project.rb')).to route_to('projects/blob#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'master/app/models/project.rb') - expect(get('/gitlab/gitlabhq/blob/master/app/models/compare.rb')).to route_to('projects/blob#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'master/app/models/compare.rb') - expect(get('/gitlab/gitlabhq/blob/master/app/models/diff.js')).to route_to('projects/blob#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'master/app/models/diff.js') - expect(get('/gitlab/gitlabhq/blob/master/files.scss')).to route_to('projects/blob#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'master/files.scss') - end -end - -# project_tree GET /:project_id/tree/:id(.:format) tree#show {id: /.+/, project_id: /[^\/]+/} -describe Projects::TreeController, 'routing' do - it 'to #show' do - expect(get('/gitlab/gitlabhq/tree/master/app/models/project.rb')).to route_to('projects/tree#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'master/app/models/project.rb') - expect(get('/gitlab/gitlabhq/tree/master/files.scss')).to route_to('projects/tree#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'master/files.scss') - end -end - -# project_find_file GET /:namespace_id/:project_id/find_file/*id(.:format) projects/find_file#show {:id=>/.+/, :namespace_id=>/[a-zA-Z.0-9_\-]+/, :project_id=>/[a-zA-Z.0-9_\-]+(?<!\.atom)/, :format=>/html/} -# project_files GET /:namespace_id/:project_id/files/*id(.:format) projects/find_file#list {:id=>/(?:[^.]|\.(?!json$))+/, :namespace_id=>/[a-zA-Z.0-9_\-]+/, :project_id=>/[a-zA-Z.0-9_\-]+(?<!\.atom)/, :format=>/json/} -describe Projects::FindFileController, 'routing' do - it 'to #show' do - expect(get('/gitlab/gitlabhq/find_file/master')).to route_to('projects/find_file#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'master') - end - - it 'to #list' do - expect(get('/gitlab/gitlabhq/files/master.json')).to route_to('projects/find_file#list', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'master', format: 'json') - end -end - -describe Projects::BlobController, 'routing' do - it 'to #edit' do - expect(get('/gitlab/gitlabhq/edit/master/app/models/project.rb')).to( - route_to('projects/blob#edit', - namespace_id: 'gitlab', project_id: 'gitlabhq', - id: 'master/app/models/project.rb')) - end - - it 'to #preview' do - expect(post('/gitlab/gitlabhq/preview/master/app/models/project.rb')).to( - route_to('projects/blob#preview', - namespace_id: 'gitlab', project_id: 'gitlabhq', - id: 'master/app/models/project.rb')) - end -end - -# project_compare_index GET /:project_id/compare(.:format) compare#index {id: /[^\/]+/, project_id: /[^\/]+/} -# POST /:project_id/compare(.:format) compare#create {id: /[^\/]+/, project_id: /[^\/]+/} -# project_compare /:project_id/compare/:from...:to(.:format) compare#show {from: /.+/, to: /.+/, id: /[^\/]+/, project_id: /[^\/]+/} -describe Projects::CompareController, 'routing' do - it 'to #index' do - expect(get('/gitlab/gitlabhq/compare')).to route_to('projects/compare#index', namespace_id: 'gitlab', project_id: 'gitlabhq') - end - - it 'to #compare' do - expect(post('/gitlab/gitlabhq/compare')).to route_to('projects/compare#create', namespace_id: 'gitlab', project_id: 'gitlabhq') - end - - it 'to #show' do - expect(get('/gitlab/gitlabhq/compare/master...stable')).to route_to('projects/compare#show', namespace_id: 'gitlab', project_id: 'gitlabhq', from: 'master', to: 'stable') - expect(get('/gitlab/gitlabhq/compare/issue/1234...stable')).to route_to('projects/compare#show', namespace_id: 'gitlab', project_id: 'gitlabhq', from: 'issue/1234', to: 'stable') - end -end - -describe Projects::NetworkController, 'routing' do - it 'to #show' do - expect(get('/gitlab/gitlabhq/network/master')).to route_to('projects/network#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'master') - expect(get('/gitlab/gitlabhq/network/ends-with.json')).to route_to('projects/network#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'ends-with.json') - expect(get('/gitlab/gitlabhq/network/master?format=json')).to route_to('projects/network#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'master', format: 'json') - end -end - -describe Projects::GraphsController, 'routing' do - it 'to #show' do - expect(get('/gitlab/gitlabhq/graphs/master')).to route_to('projects/graphs#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'master') - expect(get('/gitlab/gitlabhq/graphs/ends-with.json')).to route_to('projects/graphs#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'ends-with.json') - expect(get('/gitlab/gitlabhq/graphs/master?format=json')).to route_to('projects/graphs#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'master', format: 'json') - end -end - -describe Projects::ForksController, 'routing' do - it 'to #new' do - expect(get('/gitlab/gitlabhq/forks/new')).to route_to('projects/forks#new', namespace_id: 'gitlab', project_id: 'gitlabhq') - end - - it 'to #create' do - expect(post('/gitlab/gitlabhq/forks')).to route_to('projects/forks#create', namespace_id: 'gitlab', project_id: 'gitlabhq') - end -end - -# project_avatar DELETE /project/avatar(.:format) projects/avatars#destroy -describe Projects::AvatarsController, 'routing' do - it 'to #destroy' do - expect(delete('/gitlab/gitlabhq/avatar')).to route_to( - 'projects/avatars#destroy', namespace_id: 'gitlab', project_id: 'gitlabhq') +describe 'project routing' do + before do + allow(Project).to receive(:find_with_namespace).and_return(false) + allow(Project).to receive(:find_with_namespace).with('gitlab/gitlabhq').and_return(true) + end + + # Shared examples for a resource inside a Project + # + # By default it tests all the default REST actions: index, create, new, edit, + # show, update, and destroy. You can remove actions by customizing the + # `actions` variable. + # + # It also expects a `controller` variable to be available which defines both + # the path to the resource as well as the controller name. + # + # Examples + # + # # Default behavior + # it_behaves_like 'RESTful project resources' do + # let(:controller) { 'issues' } + # end + # + # # Customizing actions + # it_behaves_like 'RESTful project resources' do + # let(:actions) { [:index] } + # let(:controller) { 'issues' } + # end + shared_examples 'RESTful project resources' do + let(:actions) { [:index, :create, :new, :edit, :show, :update, :destroy] } + + it 'to #index' do + expect(get("/gitlab/gitlabhq/#{controller}")).to route_to("projects/#{controller}#index", namespace_id: 'gitlab', project_id: 'gitlabhq') if actions.include?(:index) + end + + it 'to #create' do + expect(post("/gitlab/gitlabhq/#{controller}")).to route_to("projects/#{controller}#create", namespace_id: 'gitlab', project_id: 'gitlabhq') if actions.include?(:create) + end + + it 'to #new' do + expect(get("/gitlab/gitlabhq/#{controller}/new")).to route_to("projects/#{controller}#new", namespace_id: 'gitlab', project_id: 'gitlabhq') if actions.include?(:new) + end + + it 'to #edit' do + expect(get("/gitlab/gitlabhq/#{controller}/1/edit")).to route_to("projects/#{controller}#edit", namespace_id: 'gitlab', project_id: 'gitlabhq', id: '1') if actions.include?(:edit) + end + + it 'to #show' do + expect(get("/gitlab/gitlabhq/#{controller}/1")).to route_to("projects/#{controller}#show", namespace_id: 'gitlab', project_id: 'gitlabhq', id: '1') if actions.include?(:show) + end + + it 'to #update' do + expect(put("/gitlab/gitlabhq/#{controller}/1")).to route_to("projects/#{controller}#update", namespace_id: 'gitlab', project_id: 'gitlabhq', id: '1') if actions.include?(:update) + end + + it 'to #destroy' do + expect(delete("/gitlab/gitlabhq/#{controller}/1")).to route_to("projects/#{controller}#destroy", namespace_id: 'gitlab', project_id: 'gitlabhq', id: '1') if actions.include?(:destroy) + end + end + + # projects POST /projects(.:format) projects#create + # new_project GET /projects/new(.:format) projects#new + # files_project GET /:id/files(.:format) projects#files + # edit_project GET /:id/edit(.:format) projects#edit + # project GET /:id(.:format) projects#show + # PUT /:id(.:format) projects#update + # DELETE /:id(.:format) projects#destroy + # preview_markdown_project POST /:id/preview_markdown(.:format) projects#preview_markdown + describe ProjectsController, 'routing' do + it 'to #create' do + expect(post('/projects')).to route_to('projects#create') + end + + it 'to #new' do + expect(get('/projects/new')).to route_to('projects#new') + end + + it 'to #edit' do + expect(get('/gitlab/gitlabhq/edit')).to route_to('projects#edit', namespace_id: 'gitlab', id: 'gitlabhq') + end + + it 'to #autocomplete_sources' do + expect(get('/gitlab/gitlabhq/autocomplete_sources')).to route_to('projects#autocomplete_sources', namespace_id: 'gitlab', id: 'gitlabhq') + end + + describe 'to #show' do + context 'regular name' do + it { expect(get('/gitlab/gitlabhq')).to route_to('projects#show', namespace_id: 'gitlab', id: 'gitlabhq') } + end + + context 'name with dot' do + before { allow(Project).to receive(:find_with_namespace).with('gitlab/gitlabhq.keys').and_return(true) } + + it { expect(get('/gitlab/gitlabhq.keys')).to route_to('projects#show', namespace_id: 'gitlab', id: 'gitlabhq.keys') } + end + + context 'with nested group' do + before { allow(Project).to receive(:find_with_namespace).with('gitlab/subgroup/gitlabhq').and_return(true) } + + it { expect(get('/gitlab/subgroup/gitlabhq')).to route_to('projects#show', namespace_id: 'gitlab/subgroup', id: 'gitlabhq') } + end + end + + it 'to #update' do + expect(put('/gitlab/gitlabhq')).to route_to('projects#update', namespace_id: 'gitlab', id: 'gitlabhq') + end + + it 'to #destroy' do + expect(delete('/gitlab/gitlabhq')).to route_to('projects#destroy', namespace_id: 'gitlab', id: 'gitlabhq') + end + + it 'to #preview_markdown' do + expect(post('/gitlab/gitlabhq/preview_markdown')).to( + route_to('projects#preview_markdown', namespace_id: 'gitlab', id: 'gitlabhq') + ) + end + end + + # pages_project_wikis GET /:project_id/wikis/pages(.:format) projects/wikis#pages + # history_project_wiki GET /:project_id/wikis/:id/history(.:format) projects/wikis#history + # project_wikis POST /:project_id/wikis(.:format) projects/wikis#create + # edit_project_wiki GET /:project_id/wikis/:id/edit(.:format) projects/wikis#edit + # project_wiki GET /:project_id/wikis/:id(.:format) projects/wikis#show + # DELETE /:project_id/wikis/:id(.:format) projects/wikis#destroy + describe Projects::WikisController, 'routing' do + it 'to #pages' do + expect(get('/gitlab/gitlabhq/wikis/pages')).to route_to('projects/wikis#pages', namespace_id: 'gitlab', project_id: 'gitlabhq') + end + + it 'to #history' do + expect(get('/gitlab/gitlabhq/wikis/1/history')).to route_to('projects/wikis#history', namespace_id: 'gitlab', project_id: 'gitlabhq', id: '1') + end + + it_behaves_like 'RESTful project resources' do + let(:actions) { [:create, :edit, :show, :destroy] } + let(:controller) { 'wikis' } + end + end + + # branches_project_repository GET /:project_id/repository/branches(.:format) projects/repositories#branches + # tags_project_repository GET /:project_id/repository/tags(.:format) projects/repositories#tags + # archive_project_repository GET /:project_id/repository/archive(.:format) projects/repositories#archive + # edit_project_repository GET /:project_id/repository/edit(.:format) projects/repositories#edit + describe Projects::RepositoriesController, 'routing' do + it 'to #archive' do + expect(get('/gitlab/gitlabhq/repository/archive')).to route_to('projects/repositories#archive', namespace_id: 'gitlab', project_id: 'gitlabhq') + end + + it 'to #archive format:zip' do + expect(get('/gitlab/gitlabhq/repository/archive.zip')).to route_to('projects/repositories#archive', namespace_id: 'gitlab', project_id: 'gitlabhq', format: 'zip') + end + + it 'to #archive format:tar.bz2' do + expect(get('/gitlab/gitlabhq/repository/archive.tar.bz2')).to route_to('projects/repositories#archive', namespace_id: 'gitlab', project_id: 'gitlabhq', format: 'tar.bz2') + end + end + + describe Projects::BranchesController, 'routing' do + it 'to #branches' do + expect(get('/gitlab/gitlabhq/branches')).to route_to('projects/branches#index', namespace_id: 'gitlab', project_id: 'gitlabhq') + expect(delete('/gitlab/gitlabhq/branches/feature%2345')).to route_to('projects/branches#destroy', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'feature#45') + expect(delete('/gitlab/gitlabhq/branches/feature%2B45')).to route_to('projects/branches#destroy', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'feature+45') + expect(delete('/gitlab/gitlabhq/branches/feature@45')).to route_to('projects/branches#destroy', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'feature@45') + expect(delete('/gitlab/gitlabhq/branches/feature%2345/foo/bar/baz')).to route_to('projects/branches#destroy', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'feature#45/foo/bar/baz') + expect(delete('/gitlab/gitlabhq/branches/feature%2B45/foo/bar/baz')).to route_to('projects/branches#destroy', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'feature+45/foo/bar/baz') + expect(delete('/gitlab/gitlabhq/branches/feature@45/foo/bar/baz')).to route_to('projects/branches#destroy', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'feature@45/foo/bar/baz') + end + end + + describe Projects::TagsController, 'routing' do + it 'to #tags' do + expect(get('/gitlab/gitlabhq/tags')).to route_to('projects/tags#index', namespace_id: 'gitlab', project_id: 'gitlabhq') + expect(delete('/gitlab/gitlabhq/tags/feature%2345')).to route_to('projects/tags#destroy', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'feature#45') + expect(delete('/gitlab/gitlabhq/tags/feature%2B45')).to route_to('projects/tags#destroy', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'feature+45') + expect(delete('/gitlab/gitlabhq/tags/feature@45')).to route_to('projects/tags#destroy', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'feature@45') + expect(delete('/gitlab/gitlabhq/tags/feature%2345/foo/bar/baz')).to route_to('projects/tags#destroy', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'feature#45/foo/bar/baz') + expect(delete('/gitlab/gitlabhq/tags/feature%2B45/foo/bar/baz')).to route_to('projects/tags#destroy', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'feature+45/foo/bar/baz') + expect(delete('/gitlab/gitlabhq/tags/feature@45/foo/bar/baz')).to route_to('projects/tags#destroy', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'feature@45/foo/bar/baz') + end + end + + # project_deploy_keys GET /:project_id/deploy_keys(.:format) deploy_keys#index + # POST /:project_id/deploy_keys(.:format) deploy_keys#create + # new_project_deploy_key GET /:project_id/deploy_keys/new(.:format) deploy_keys#new + # project_deploy_key GET /:project_id/deploy_keys/:id(.:format) deploy_keys#show + # DELETE /:project_id/deploy_keys/:id(.:format) deploy_keys#destroy + describe Projects::DeployKeysController, 'routing' do + it_behaves_like 'RESTful project resources' do + let(:actions) { [:index, :new, :create] } + let(:controller) { 'deploy_keys' } + end + end + + # project_protected_branches GET /:project_id/protected_branches(.:format) protected_branches#index + # POST /:project_id/protected_branches(.:format) protected_branches#create + # project_protected_branch DELETE /:project_id/protected_branches/:id(.:format) protected_branches#destroy + describe Projects::ProtectedBranchesController, 'routing' do + it_behaves_like 'RESTful project resources' do + let(:actions) { [:index, :create, :destroy] } + let(:controller) { 'protected_branches' } + end + end + + # switch_project_refs GET /:project_id/refs/switch(.:format) refs#switch + # logs_tree_project_ref GET /:project_id/refs/:id/logs_tree(.:format) refs#logs_tree + # logs_file_project_ref GET /:project_id/refs/:id/logs_tree/:path(.:format) refs#logs_tree + describe Projects::RefsController, 'routing' do + it 'to #switch' do + expect(get('/gitlab/gitlabhq/refs/switch')).to route_to('projects/refs#switch', namespace_id: 'gitlab', project_id: 'gitlabhq') + end + + it 'to #logs_tree' do + expect(get('/gitlab/gitlabhq/refs/stable/logs_tree')).to route_to('projects/refs#logs_tree', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'stable') + expect(get('/gitlab/gitlabhq/refs/feature%2345/logs_tree')).to route_to('projects/refs#logs_tree', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'feature#45') + expect(get('/gitlab/gitlabhq/refs/feature%2B45/logs_tree')).to route_to('projects/refs#logs_tree', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'feature+45') + expect(get('/gitlab/gitlabhq/refs/feature@45/logs_tree')).to route_to('projects/refs#logs_tree', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'feature@45') + expect(get('/gitlab/gitlabhq/refs/stable/logs_tree/foo/bar/baz')).to route_to('projects/refs#logs_tree', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'stable', path: 'foo/bar/baz') + expect(get('/gitlab/gitlabhq/refs/feature%2345/logs_tree/foo/bar/baz')).to route_to('projects/refs#logs_tree', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'feature#45', path: 'foo/bar/baz') + expect(get('/gitlab/gitlabhq/refs/feature%2B45/logs_tree/foo/bar/baz')).to route_to('projects/refs#logs_tree', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'feature+45', path: 'foo/bar/baz') + expect(get('/gitlab/gitlabhq/refs/feature@45/logs_tree/foo/bar/baz')).to route_to('projects/refs#logs_tree', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'feature@45', path: 'foo/bar/baz') + expect(get('/gitlab/gitlabhq/refs/stable/logs_tree/files.scss')).to route_to('projects/refs#logs_tree', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'stable', path: 'files.scss') + end + end + + # diffs_namespace_project_merge_request GET /:namespace_id/:project_id/merge_requests/:id/diffs(.:format) projects/merge_requests#diffs + # commits_namespace_project_merge_request GET /:namespace_id/:project_id/merge_requests/:id/commits(.:format) projects/merge_requests#commits + # merge_namespace_project_merge_request POST /:namespace_id/:project_id/merge_requests/:id/merge(.:format) projects/merge_requests#merge + # merge_check_namespace_project_merge_request GET /:namespace_id/:project_id/merge_requests/:id/merge_check(.:format) projects/merge_requests#merge_check + # ci_status_namespace_project_merge_request GET /:namespace_id/:project_id/merge_requests/:id/ci_status(.:format) projects/merge_requests#ci_status + # toggle_subscription_namespace_project_merge_request POST /:namespace_id/:project_id/merge_requests/:id/toggle_subscription(.:format) projects/merge_requests#toggle_subscription + # branch_from_namespace_project_merge_requests GET /:namespace_id/:project_id/merge_requests/branch_from(.:format) projects/merge_requests#branch_from + # branch_to_namespace_project_merge_requests GET /:namespace_id/:project_id/merge_requests/branch_to(.:format) projects/merge_requests#branch_to + # update_branches_namespace_project_merge_requests GET /:namespace_id/:project_id/merge_requests/update_branches(.:format) projects/merge_requests#update_branches + # namespace_project_merge_requests GET /:namespace_id/:project_id/merge_requests(.:format) projects/merge_requests#index + # POST /:namespace_id/:project_id/merge_requests(.:format) projects/merge_requests#create + # new_namespace_project_merge_request GET /:namespace_id/:project_id/merge_requests/new(.:format) projects/merge_requests#new + # edit_namespace_project_merge_request GET /:namespace_id/:project_id/merge_requests/:id/edit(.:format) projects/merge_requests#edit + # namespace_project_merge_request GET /:namespace_id/:project_id/merge_requests/:id(.:format) projects/merge_requests#show + # PATCH /:namespace_id/:project_id/merge_requests/:id(.:format) projects/merge_requests#update + # PUT /:namespace_id/:project_id/merge_requests/:id(.:format) projects/merge_requests#update + describe Projects::MergeRequestsController, 'routing' do + it 'to #diffs' do + expect(get('/gitlab/gitlabhq/merge_requests/1/diffs')).to route_to('projects/merge_requests#diffs', namespace_id: 'gitlab', project_id: 'gitlabhq', id: '1') + end + + it 'to #commits' do + expect(get('/gitlab/gitlabhq/merge_requests/1/commits')).to route_to('projects/merge_requests#commits', namespace_id: 'gitlab', project_id: 'gitlabhq', id: '1') + end + + it 'to #merge' do + expect(post('/gitlab/gitlabhq/merge_requests/1/merge')).to route_to( + 'projects/merge_requests#merge', + namespace_id: 'gitlab', project_id: 'gitlabhq', id: '1' + ) + end + + it 'to #merge_check' do + expect(get('/gitlab/gitlabhq/merge_requests/1/merge_check')).to route_to('projects/merge_requests#merge_check', namespace_id: 'gitlab', project_id: 'gitlabhq', id: '1') + end + + it 'to #branch_from' do + expect(get('/gitlab/gitlabhq/merge_requests/branch_from')).to route_to('projects/merge_requests#branch_from', namespace_id: 'gitlab', project_id: 'gitlabhq') + end + + it 'to #branch_to' do + expect(get('/gitlab/gitlabhq/merge_requests/branch_to')).to route_to('projects/merge_requests#branch_to', namespace_id: 'gitlab', project_id: 'gitlabhq') + end + + it 'to #show' do + expect(get('/gitlab/gitlabhq/merge_requests/1.diff')).to route_to('projects/merge_requests#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: '1', format: 'diff') + expect(get('/gitlab/gitlabhq/merge_requests/1.patch')).to route_to('projects/merge_requests#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: '1', format: 'patch') + end + + it_behaves_like 'RESTful project resources' do + let(:controller) { 'merge_requests' } + let(:actions) { [:index, :create, :new, :edit, :show, :update] } + end + end + + # raw_project_snippet GET /:project_id/snippets/:id/raw(.:format) snippets#raw + # project_snippets GET /:project_id/snippets(.:format) snippets#index + # POST /:project_id/snippets(.:format) snippets#create + # new_project_snippet GET /:project_id/snippets/new(.:format) snippets#new + # edit_project_snippet GET /:project_id/snippets/:id/edit(.:format) snippets#edit + # project_snippet GET /:project_id/snippets/:id(.:format) snippets#show + # PUT /:project_id/snippets/:id(.:format) snippets#update + # DELETE /:project_id/snippets/:id(.:format) snippets#destroy + describe SnippetsController, 'routing' do + it 'to #raw' do + expect(get('/gitlab/gitlabhq/snippets/1/raw')).to route_to('projects/snippets#raw', namespace_id: 'gitlab', project_id: 'gitlabhq', id: '1') + end + + it 'to #index' do + expect(get('/gitlab/gitlabhq/snippets')).to route_to('projects/snippets#index', namespace_id: 'gitlab', project_id: 'gitlabhq') + end + + it 'to #create' do + expect(post('/gitlab/gitlabhq/snippets')).to route_to('projects/snippets#create', namespace_id: 'gitlab', project_id: 'gitlabhq') + end + + it 'to #new' do + expect(get('/gitlab/gitlabhq/snippets/new')).to route_to('projects/snippets#new', namespace_id: 'gitlab', project_id: 'gitlabhq') + end + + it 'to #edit' do + expect(get('/gitlab/gitlabhq/snippets/1/edit')).to route_to('projects/snippets#edit', namespace_id: 'gitlab', project_id: 'gitlabhq', id: '1') + end + + it 'to #show' do + expect(get('/gitlab/gitlabhq/snippets/1')).to route_to('projects/snippets#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: '1') + end + + it 'to #update' do + expect(put('/gitlab/gitlabhq/snippets/1')).to route_to('projects/snippets#update', namespace_id: 'gitlab', project_id: 'gitlabhq', id: '1') + end + + it 'to #destroy' do + expect(delete('/gitlab/gitlabhq/snippets/1')).to route_to('projects/snippets#destroy', namespace_id: 'gitlab', project_id: 'gitlabhq', id: '1') + end + end + + # test_project_hook GET /:project_id/hooks/:id/test(.:format) hooks#test + # project_hooks GET /:project_id/hooks(.:format) hooks#index + # POST /:project_id/hooks(.:format) hooks#create + # project_hook DELETE /:project_id/hooks/:id(.:format) hooks#destroy + describe Projects::HooksController, 'routing' do + it 'to #test' do + expect(get('/gitlab/gitlabhq/hooks/1/test')).to route_to('projects/hooks#test', namespace_id: 'gitlab', project_id: 'gitlabhq', id: '1') + end + + it_behaves_like 'RESTful project resources' do + let(:actions) { [:index, :create, :destroy] } + let(:controller) { 'hooks' } + end + end + + # project_commit GET /:project_id/commit/:id(.:format) commit#show {id: /\h{7,40}/, project_id: /[^\/]+/} + describe Projects::CommitController, 'routing' do + it 'to #show' do + expect(get('/gitlab/gitlabhq/commit/4246fbd')).to route_to('projects/commit#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: '4246fbd') + expect(get('/gitlab/gitlabhq/commit/4246fbd.diff')).to route_to('projects/commit#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: '4246fbd', format: 'diff') + expect(get('/gitlab/gitlabhq/commit/4246fbd.patch')).to route_to('projects/commit#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: '4246fbd', format: 'patch') + expect(get('/gitlab/gitlabhq/commit/4246fbd13872934f72a8fd0d6fb1317b47b59cb5')).to route_to('projects/commit#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: '4246fbd13872934f72a8fd0d6fb1317b47b59cb5') + end + end + + # patch_project_commit GET /:project_id/commits/:id/patch(.:format) commits#patch + # project_commits GET /:project_id/commits(.:format) commits#index + # POST /:project_id/commits(.:format) commits#create + # project_commit GET /:project_id/commits/:id(.:format) commits#show + describe Projects::CommitsController, 'routing' do + it_behaves_like 'RESTful project resources' do + let(:actions) { [:show] } + let(:controller) { 'commits' } + end + + it 'to #show' do + expect(get('/gitlab/gitlabhq/commits/master.atom')).to route_to('projects/commits#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'master.atom') + end + end + + # project_project_members GET /:project_id/project_members(.:format) project_members#index + # POST /:project_id/project_members(.:format) project_members#create + # PUT /:project_id/project_members/:id(.:format) project_members#update + # DELETE /:project_id/project_members/:id(.:format) project_members#destroy + describe Projects::ProjectMembersController, 'routing' do + it_behaves_like 'RESTful project resources' do + let(:actions) { [:index, :create, :update, :destroy] } + let(:controller) { 'project_members' } + end + end + + # project_milestones GET /:project_id/milestones(.:format) milestones#index + # POST /:project_id/milestones(.:format) milestones#create + # new_project_milestone GET /:project_id/milestones/new(.:format) milestones#new + # edit_project_milestone GET /:project_id/milestones/:id/edit(.:format) milestones#edit + # project_milestone GET /:project_id/milestones/:id(.:format) milestones#show + # PUT /:project_id/milestones/:id(.:format) milestones#update + # DELETE /:project_id/milestones/:id(.:format) milestones#destroy + describe Projects::MilestonesController, 'routing' do + it_behaves_like 'RESTful project resources' do + let(:controller) { 'milestones' } + let(:actions) { [:index, :create, :new, :edit, :show, :update] } + end + end + + # project_labels GET /:project_id/labels(.:format) labels#index + describe Projects::LabelsController, 'routing' do + it 'to #index' do + expect(get('/gitlab/gitlabhq/labels')).to route_to('projects/labels#index', namespace_id: 'gitlab', project_id: 'gitlabhq') + end + end + + # sort_project_issues POST /:project_id/issues/sort(.:format) issues#sort + # bulk_update_project_issues POST /:project_id/issues/bulk_update(.:format) issues#bulk_update + # search_project_issues GET /:project_id/issues/search(.:format) issues#search + # project_issues GET /:project_id/issues(.:format) issues#index + # POST /:project_id/issues(.:format) issues#create + # new_project_issue GET /:project_id/issues/new(.:format) issues#new + # edit_project_issue GET /:project_id/issues/:id/edit(.:format) issues#edit + # project_issue GET /:project_id/issues/:id(.:format) issues#show + # PUT /:project_id/issues/:id(.:format) issues#update + # DELETE /:project_id/issues/:id(.:format) issues#destroy + describe Projects::IssuesController, 'routing' do + it 'to #bulk_update' do + expect(post('/gitlab/gitlabhq/issues/bulk_update')).to route_to('projects/issues#bulk_update', namespace_id: 'gitlab', project_id: 'gitlabhq') + end + + it_behaves_like 'RESTful project resources' do + let(:controller) { 'issues' } + let(:actions) { [:index, :create, :new, :edit, :show, :update] } + end + end + + # project_notes GET /:project_id/notes(.:format) notes#index + # POST /:project_id/notes(.:format) notes#create + # project_note DELETE /:project_id/notes/:id(.:format) notes#destroy + describe Projects::NotesController, 'routing' do + it_behaves_like 'RESTful project resources' do + let(:actions) { [:index, :create, :destroy] } + let(:controller) { 'notes' } + end + end + + # project_blame GET /:project_id/blame/:id(.:format) blame#show {id: /.+/, project_id: /[^\/]+/} + describe Projects::BlameController, 'routing' do + it 'to #show' do + expect(get('/gitlab/gitlabhq/blame/master/app/models/project.rb')).to route_to('projects/blame#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'master/app/models/project.rb') + expect(get('/gitlab/gitlabhq/blame/master/files.scss')).to route_to('projects/blame#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'master/files.scss') + end + end + + # project_blob GET /:project_id/blob/:id(.:format) blob#show {id: /.+/, project_id: /[^\/]+/} + describe Projects::BlobController, 'routing' do + it 'to #show' do + expect(get('/gitlab/gitlabhq/blob/master/app/models/project.rb')).to route_to('projects/blob#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'master/app/models/project.rb') + expect(get('/gitlab/gitlabhq/blob/master/app/models/compare.rb')).to route_to('projects/blob#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'master/app/models/compare.rb') + expect(get('/gitlab/gitlabhq/blob/master/app/models/diff.js')).to route_to('projects/blob#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'master/app/models/diff.js') + expect(get('/gitlab/gitlabhq/blob/master/files.scss')).to route_to('projects/blob#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'master/files.scss') + end + end + + # project_tree GET /:project_id/tree/:id(.:format) tree#show {id: /.+/, project_id: /[^\/]+/} + describe Projects::TreeController, 'routing' do + it 'to #show' do + expect(get('/gitlab/gitlabhq/tree/master/app/models/project.rb')).to route_to('projects/tree#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'master/app/models/project.rb') + expect(get('/gitlab/gitlabhq/tree/master/files.scss')).to route_to('projects/tree#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'master/files.scss') + end + end + + # project_find_file GET /:namespace_id/:project_id/find_file/*id(.:format) projects/find_file#show {:id=>/.+/, :namespace_id=>/[a-zA-Z.0-9_\-]+/, :project_id=>/[a-zA-Z.0-9_\-]+(?<!\.atom)/, :format=>/html/} + # project_files GET /:namespace_id/:project_id/files/*id(.:format) projects/find_file#list {:id=>/(?:[^.]|\.(?!json$))+/, :namespace_id=>/[a-zA-Z.0-9_\-]+/, :project_id=>/[a-zA-Z.0-9_\-]+(?<!\.atom)/, :format=>/json/} + describe Projects::FindFileController, 'routing' do + it 'to #show' do + expect(get('/gitlab/gitlabhq/find_file/master')).to route_to('projects/find_file#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'master') + end + + it 'to #list' do + expect(get('/gitlab/gitlabhq/files/master.json')).to route_to('projects/find_file#list', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'master', format: 'json') + end + end + + describe Projects::BlobController, 'routing' do + it 'to #edit' do + expect(get('/gitlab/gitlabhq/edit/master/app/models/project.rb')).to( + route_to('projects/blob#edit', + namespace_id: 'gitlab', project_id: 'gitlabhq', + id: 'master/app/models/project.rb')) + end + + it 'to #preview' do + expect(post('/gitlab/gitlabhq/preview/master/app/models/project.rb')).to( + route_to('projects/blob#preview', + namespace_id: 'gitlab', project_id: 'gitlabhq', + id: 'master/app/models/project.rb')) + end + end + + # project_compare_index GET /:project_id/compare(.:format) compare#index {id: /[^\/]+/, project_id: /[^\/]+/} + # POST /:project_id/compare(.:format) compare#create {id: /[^\/]+/, project_id: /[^\/]+/} + # project_compare /:project_id/compare/:from...:to(.:format) compare#show {from: /.+/, to: /.+/, id: /[^\/]+/, project_id: /[^\/]+/} + describe Projects::CompareController, 'routing' do + it 'to #index' do + expect(get('/gitlab/gitlabhq/compare')).to route_to('projects/compare#index', namespace_id: 'gitlab', project_id: 'gitlabhq') + end + + it 'to #compare' do + expect(post('/gitlab/gitlabhq/compare')).to route_to('projects/compare#create', namespace_id: 'gitlab', project_id: 'gitlabhq') + end + + it 'to #show' do + expect(get('/gitlab/gitlabhq/compare/master...stable')).to route_to('projects/compare#show', namespace_id: 'gitlab', project_id: 'gitlabhq', from: 'master', to: 'stable') + expect(get('/gitlab/gitlabhq/compare/issue/1234...stable')).to route_to('projects/compare#show', namespace_id: 'gitlab', project_id: 'gitlabhq', from: 'issue/1234', to: 'stable') + end + end + + describe Projects::NetworkController, 'routing' do + it 'to #show' do + expect(get('/gitlab/gitlabhq/network/master')).to route_to('projects/network#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'master') + expect(get('/gitlab/gitlabhq/network/ends-with.json')).to route_to('projects/network#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'ends-with.json') + expect(get('/gitlab/gitlabhq/network/master?format=json')).to route_to('projects/network#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'master', format: 'json') + end + end + + describe Projects::GraphsController, 'routing' do + it 'to #show' do + expect(get('/gitlab/gitlabhq/graphs/master')).to route_to('projects/graphs#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'master') + expect(get('/gitlab/gitlabhq/graphs/ends-with.json')).to route_to('projects/graphs#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'ends-with.json') + expect(get('/gitlab/gitlabhq/graphs/master?format=json')).to route_to('projects/graphs#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'master', format: 'json') + end + end + + describe Projects::ForksController, 'routing' do + it 'to #new' do + expect(get('/gitlab/gitlabhq/forks/new')).to route_to('projects/forks#new', namespace_id: 'gitlab', project_id: 'gitlabhq') + end + + it 'to #create' do + expect(post('/gitlab/gitlabhq/forks')).to route_to('projects/forks#create', namespace_id: 'gitlab', project_id: 'gitlabhq') + end + end + + # project_avatar DELETE /project/avatar(.:format) projects/avatars#destroy + describe Projects::AvatarsController, 'routing' do + it 'to #destroy' do + expect(delete('/gitlab/gitlabhq/avatar')).to route_to( + 'projects/avatars#destroy', namespace_id: 'gitlab', project_id: 'gitlabhq') + end end end diff --git a/spec/routing/routing_spec.rb b/spec/routing/routing_spec.rb index f15c45cbaac..9f6defe1450 100644 --- a/spec/routing/routing_spec.rb +++ b/spec/routing/routing_spec.rb @@ -9,7 +9,7 @@ require 'spec_helper' # user_calendar_activities GET /u/:username/calendar_activities(.:format) describe UsersController, "routing" do it "to #show" do - allow(User).to receive(:find_by).and_return(true) + allow_any_instance_of(UserUrlConstrainer).to receive(:matches?).and_return(true) expect(get("/User")).to route_to('users#show', username: 'User') end @@ -195,6 +195,8 @@ describe Profiles::KeysController, "routing" do # get all the ssh-keys of a user it "to #get_keys" do + allow_any_instance_of(UserUrlConstrainer).to receive(:matches?).and_return(true) + expect(get("/foo.keys")).to route_to('profiles/keys#get_keys', username: 'foo') end end @@ -263,13 +265,17 @@ end describe "Groups", "routing" do let(:name) { 'complex.group-namegit' } + before { allow_any_instance_of(GroupUrlConstrainer).to receive(:matches?).and_return(true) } + it "to #show" do expect(get("/groups/#{name}")).to route_to('groups#show', id: name) end - it "also display group#show on the short path" do - allow(Group).to receive(:find_by).and_return(true) + it "also supports nested groups" do + expect(get("/#{name}/#{name}")).to route_to('groups#show', id: "#{name}/#{name}") + end + it "also display group#show on the short path" do expect(get("/#{name}")).to route_to('groups#show', id: name) end @@ -284,6 +290,10 @@ describe "Groups", "routing" do it "to #members" do expect(get("/groups/#{name}/group_members")).to route_to('groups/group_members#index', group_id: name) end + + it "also display group#show with slash in the path" do + expect(get('/group/subgroup')).to route_to('groups#show', id: 'group/subgroup') + end end describe HealthCheckController, 'routing' do diff --git a/spec/serializers/analytics_build_serializer_spec.rb b/spec/serializers/analytics_build_serializer_spec.rb index a0a9d9a5f12..f0551c78671 100644 --- a/spec/serializers/analytics_build_serializer_spec.rb +++ b/spec/serializers/analytics_build_serializer_spec.rb @@ -10,10 +10,6 @@ describe AnalyticsBuildSerializer do let(:resource) { create(:ci_build) } context 'when there is a single object provided' do - it 'it generates payload for single object' do - expect(json).to be_an_instance_of Hash - end - it 'contains important elements of analyticsBuild' do expect(json) .to include(:name, :branch, :short_sha, :date, :total_time, :url, :author) diff --git a/spec/serializers/analytics_issue_serializer_spec.rb b/spec/serializers/analytics_issue_serializer_spec.rb index 2842e1ba52f..6afbb2df35c 100644 --- a/spec/serializers/analytics_issue_serializer_spec.rb +++ b/spec/serializers/analytics_issue_serializer_spec.rb @@ -22,10 +22,6 @@ describe AnalyticsIssueSerializer do end context 'when there is a single object provided' do - it 'it generates payload for single object' do - expect(json).to be_an_instance_of Hash - end - it 'contains important elements of the issue' do expect(json).to include(:title, :iid, :created_at, :total_time, :url, :author) end diff --git a/spec/serializers/analytics_merge_request_serializer_spec.rb b/spec/serializers/analytics_merge_request_serializer_spec.rb index 564207984df..cdfae27193f 100644 --- a/spec/serializers/analytics_merge_request_serializer_spec.rb +++ b/spec/serializers/analytics_merge_request_serializer_spec.rb @@ -23,10 +23,6 @@ describe AnalyticsMergeRequestSerializer do end context 'when there is a single object provided' do - it 'it generates payload for single object' do - expect(json).to be_an_instance_of Hash - end - it 'contains important elements of the merge request' do expect(json).to include(:title, :iid, :created_at, :total_time, :url, :author, :state) end diff --git a/spec/serializers/environment_serializer_spec.rb b/spec/serializers/environment_serializer_spec.rb index 8f95c9250b0..b7ed4eb0239 100644 --- a/spec/serializers/environment_serializer_spec.rb +++ b/spec/serializers/environment_serializer_spec.rb @@ -27,10 +27,6 @@ describe EnvironmentSerializer do let(:deployable) { create(:ci_build) } let(:resource) { deployment.environment } - it 'it generates payload for single object' do - expect(json).to be_an_instance_of Hash - end - it 'contains important elements of environment' do expect(json) .to include(:name, :external_url, :environment_path, :last_deployment) diff --git a/spec/services/issues/close_service_spec.rb b/spec/services/issues/close_service_spec.rb index 4465f22a001..7a54373963e 100644 --- a/spec/services/issues/close_service_spec.rb +++ b/spec/services/issues/close_service_spec.rb @@ -62,7 +62,7 @@ describe Issues::CloseService, services: true do it 'creates system note about issue reassign' do note = issue.notes.last - expect(note.note).to include "Status changed to closed" + expect(note.note).to include "closed" end it 'marks todos as done' do diff --git a/spec/services/issues/move_service_spec.rb b/spec/services/issues/move_service_spec.rb index f0ded06b785..c7de0d0c534 100644 --- a/spec/services/issues/move_service_spec.rb +++ b/spec/services/issues/move_service_spec.rb @@ -81,11 +81,11 @@ describe Issues::MoveService, services: true do end it 'adds system note to old issue at the end' do - expect(old_issue.notes.last.note).to match /^Moved to/ + expect(old_issue.notes.last.note).to start_with 'moved to' end it 'adds system note to new issue at the end' do - expect(new_issue.notes.last.note).to match /^Moved from/ + expect(new_issue.notes.last.note).to start_with 'moved from' end it 'closes old issue' do @@ -151,7 +151,7 @@ describe Issues::MoveService, services: true do end it 'adds a system note about move after rewritten notes' do - expect(system_notes.last.note).to match /^Moved from/ + expect(system_notes.last.note).to match /^moved from/ end it 'preserves orignal author of comment' do diff --git a/spec/services/issues/update_service_spec.rb b/spec/services/issues/update_service_spec.rb index 4777a90639e..4c878d748c0 100644 --- a/spec/services/issues/update_service_spec.rb +++ b/spec/services/issues/update_service_spec.rb @@ -91,24 +91,24 @@ describe Issues::UpdateService, services: true do end it 'creates system note about issue reassign' do - note = find_note('Reassigned to') + note = find_note('assigned to') expect(note).not_to be_nil - expect(note.note).to include "Reassigned to \@#{user2.username}" + expect(note.note).to include "assigned to #{user2.to_reference}" end it 'creates system note about issue label edit' do - note = find_note('Added ~') + note = find_note('added ~') expect(note).not_to be_nil - expect(note.note).to include "Added ~#{label.id} label" + expect(note.note).to include "added #{label.to_reference} label" end it 'creates system note about title change' do - note = find_note('Changed title:') + note = find_note('changed title') expect(note).not_to be_nil - expect(note.note).to eq 'Changed title: **{-Old-} title** → **{+New+} title**' + expect(note.note).to eq 'changed title from **{-Old-} title** to **{+New+} title**' end end end @@ -128,10 +128,10 @@ describe Issues::UpdateService, services: true do it 'creates system note about confidentiality change' do update_issue(confidential: true) - note = find_note('Made the issue confidential') + note = find_note('made the issue confidential') expect(note).not_to be_nil - expect(note.note).to eq 'Made the issue confidential' + expect(note.note).to eq 'made the issue confidential' end it 'executes confidential issue hooks' do @@ -269,8 +269,8 @@ describe Issues::UpdateService, services: true do before { update_issue(description: "- [x] Task 1\n- [X] Task 2") } it 'creates system note about task status change' do - note1 = find_note('Marked the task **Task 1** as completed') - note2 = find_note('Marked the task **Task 2** as completed') + note1 = find_note('marked the task **Task 1** as completed') + note2 = find_note('marked the task **Task 2** as completed') expect(note1).not_to be_nil expect(note2).not_to be_nil @@ -284,8 +284,8 @@ describe Issues::UpdateService, services: true do end it 'creates system note about task status change' do - note1 = find_note('Marked the task **Task 1** as incomplete') - note2 = find_note('Marked the task **Task 2** as incomplete') + note1 = find_note('marked the task **Task 1** as incomplete') + note2 = find_note('marked the task **Task 2** as incomplete') expect(note1).not_to be_nil expect(note2).not_to be_nil @@ -299,7 +299,7 @@ describe Issues::UpdateService, services: true do end it 'does not create a system note' do - note = find_note('Marked the task **Task 2** as incomplete') + note = find_note('marked the task **Task 2** as incomplete') expect(note).to be_nil end @@ -312,7 +312,7 @@ describe Issues::UpdateService, services: true do end it 'does not create a system note referencing the position the old item' do - note = find_note('Marked the task **Two** as incomplete') + note = find_note('marked the task **Two** as incomplete') expect(note).to be_nil end diff --git a/spec/services/merge_requests/close_service_spec.rb b/spec/services/merge_requests/close_service_spec.rb index 24c25e4350f..5f6a7716beb 100644 --- a/spec/services/merge_requests/close_service_spec.rb +++ b/spec/services/merge_requests/close_service_spec.rb @@ -42,7 +42,7 @@ describe MergeRequests::CloseService, services: true do it 'creates system note about merge_request reassign' do note = @merge_request.notes.last - expect(note.note).to include 'Status changed to closed' + expect(note.note).to include 'closed' end it 'marks todos as done' do diff --git a/spec/services/merge_requests/merge_service_spec.rb b/spec/services/merge_requests/merge_service_spec.rb index 7db32a33c93..dff1781d2aa 100644 --- a/spec/services/merge_requests/merge_service_spec.rb +++ b/spec/services/merge_requests/merge_service_spec.rb @@ -34,7 +34,7 @@ describe MergeRequests::MergeService, services: true do it 'creates system note about merge_request merge' do note = merge_request.notes.last - expect(note.note).to include 'Status changed to merged' + expect(note.note).to include 'merged' end end diff --git a/spec/services/merge_requests/merge_when_build_succeeds_service_spec.rb b/spec/services/merge_requests/merge_when_build_succeeds_service_spec.rb index 1f90efdbd6a..c0164138713 100644 --- a/spec/services/merge_requests/merge_when_build_succeeds_service_spec.rb +++ b/spec/services/merge_requests/merge_when_build_succeeds_service_spec.rb @@ -34,7 +34,7 @@ describe MergeRequests::MergeWhenBuildSucceedsService do it 'creates a system note' do note = merge_request.notes.last - expect(note.note).to match /Enabled an automatic merge when the build for (\w+\/\w+@)?[0-9a-z]{8}/ + expect(note.note).to match /enabled an automatic merge when the build for (\w+\/\w+@)?\h{8}/ end end @@ -113,7 +113,7 @@ describe MergeRequests::MergeWhenBuildSucceedsService do it 'Posts a system note' do note = mr_merge_if_green_enabled.notes.last - expect(note.note).to include 'Canceled the automatic merge' + expect(note.note).to include 'canceled the automatic merge' end end diff --git a/spec/services/merge_requests/refresh_service_spec.rb b/spec/services/merge_requests/refresh_service_spec.rb index e515bc9f89c..bc340ff9d3c 100644 --- a/spec/services/merge_requests/refresh_service_spec.rb +++ b/spec/services/merge_requests/refresh_service_spec.rb @@ -76,10 +76,10 @@ describe MergeRequests::RefreshService, services: true do reload_mrs end - it { expect(@merge_request.notes.last.note).to include('changed to merged') } + it { expect(@merge_request.notes.last.note).to include('merged') } it { expect(@merge_request).to be_merged } it { expect(@fork_merge_request).to be_merged } - it { expect(@fork_merge_request.notes.last.note).to include('changed to merged') } + it { expect(@fork_merge_request.notes.last.note).to include('merged') } it { expect(@build_failed_todo).to be_done } it { expect(@fork_build_failed_todo).to be_done } end @@ -95,11 +95,11 @@ describe MergeRequests::RefreshService, services: true do reload_mrs end - it { expect(@merge_request.notes.last.note).to include('changed to merged') } + it { expect(@merge_request.notes.last.note).to include('merged') } it { expect(@merge_request).to be_merged } it { expect(@merge_request.diffs.size).to be > 0 } it { expect(@fork_merge_request).to be_merged } - it { expect(@fork_merge_request.notes.last.note).to include('changed to merged') } + it { expect(@fork_merge_request.notes.last.note).to include('merged') } it { expect(@build_failed_todo).to be_done } it { expect(@fork_build_failed_todo).to be_done } end @@ -119,7 +119,7 @@ describe MergeRequests::RefreshService, services: true do it { expect(@merge_request.notes).to be_empty } it { expect(@merge_request).to be_open } - it { expect(@fork_merge_request.notes.last.note).to include('Added 28 commits') } + it { expect(@fork_merge_request.notes.last.note).to include('added 28 commits') } it { expect(@fork_merge_request).to be_open } it { expect(@build_failed_todo).to be_pending } it { expect(@fork_build_failed_todo).to be_pending } @@ -146,7 +146,7 @@ describe MergeRequests::RefreshService, services: true do reload_mrs end - it { expect(@merge_request.notes.last.note).to include('changed to merged') } + it { expect(@merge_request.notes.last.note).to include('merged') } it { expect(@merge_request).to be_merged } it { expect(@fork_merge_request).to be_open } it { expect(@fork_merge_request.notes).to be_empty } @@ -169,8 +169,8 @@ describe MergeRequests::RefreshService, services: true do expect(@merge_request).to be_open notes = @fork_merge_request.notes.reorder(:created_at).map(&:note) - expect(notes[0]).to include('Restored source branch `master`') - expect(notes[1]).to include('Added 28 commits') + expect(notes[0]).to include('restored source branch `master`') + expect(notes[1]).to include('added 28 commits') expect(@fork_merge_request).to be_open end end diff --git a/spec/services/merge_requests/reopen_service_spec.rb b/spec/services/merge_requests/reopen_service_spec.rb index af7424a76a9..a99d4eac9bd 100644 --- a/spec/services/merge_requests/reopen_service_spec.rb +++ b/spec/services/merge_requests/reopen_service_spec.rb @@ -41,7 +41,7 @@ describe MergeRequests::ReopenService, services: true do it 'creates system note about merge_request reopen' do note = merge_request.notes.last - expect(note.note).to include 'Status changed to reopened' + expect(note.note).to include 'reopened' end end diff --git a/spec/services/merge_requests/update_service_spec.rb b/spec/services/merge_requests/update_service_spec.rb index cb5d7cdb467..0bd6db1810a 100644 --- a/spec/services/merge_requests/update_service_spec.rb +++ b/spec/services/merge_requests/update_service_spec.rb @@ -79,31 +79,31 @@ describe MergeRequests::UpdateService, services: true do end it 'creates system note about merge_request reassign' do - note = find_note('Reassigned to') + note = find_note('assigned to') expect(note).not_to be_nil - expect(note.note).to include "Reassigned to \@#{user2.username}" + expect(note.note).to include "assigned to #{user2.to_reference}" end it 'creates system note about merge_request label edit' do - note = find_note('Added ~') + note = find_note('added ~') expect(note).not_to be_nil - expect(note.note).to include "Added ~#{label.id} label" + expect(note.note).to include "added #{label.to_reference} label" end it 'creates system note about title change' do - note = find_note('Changed title:') + note = find_note('changed title') expect(note).not_to be_nil - expect(note.note).to eq 'Changed title: **{-Old-} title** → **{+New+} title**' + expect(note.note).to eq 'changed title from **{-Old-} title** to **{+New+} title**' end it 'creates system note about branch change' do - note = find_note('Target') + note = find_note('changed target') expect(note).not_to be_nil - expect(note.note).to eq 'Target branch changed from `master` to `target`' + expect(note.note).to eq 'changed target branch from `master` to `target`' end context 'when not including source branch removal options' do @@ -258,8 +258,8 @@ describe MergeRequests::UpdateService, services: true do before { update_merge_request({ description: "- [x] Task 1\n- [X] Task 2" }) } it 'creates system note about task status change' do - note1 = find_note('Marked the task **Task 1** as completed') - note2 = find_note('Marked the task **Task 2** as completed') + note1 = find_note('marked the task **Task 1** as completed') + note2 = find_note('marked the task **Task 2** as completed') expect(note1).not_to be_nil expect(note2).not_to be_nil @@ -273,8 +273,8 @@ describe MergeRequests::UpdateService, services: true do end it 'creates system note about task status change' do - note1 = find_note('Marked the task **Task 1** as incomplete') - note2 = find_note('Marked the task **Task 2** as incomplete') + note1 = find_note('marked the task **Task 1** as incomplete') + note2 = find_note('marked the task **Task 2** as incomplete') expect(note1).not_to be_nil expect(note2).not_to be_nil diff --git a/spec/services/system_note_service_spec.rb b/spec/services/system_note_service_spec.rb index 2a5709c6322..4a8f6c321aa 100644 --- a/spec/services/system_note_service_spec.rb +++ b/spec/services/system_note_service_spec.rb @@ -50,7 +50,7 @@ describe SystemNoteService, services: true do context 'without existing commits' do it 'adds a message header' do - expect(note_lines[0]).to eq "Added #{new_commits.size} commits:" + expect(note_lines[0]).to eq "added #{new_commits.size} commits" end it 'adds a message line for each commit' do @@ -120,7 +120,7 @@ describe SystemNoteService, services: true do context 'when assignee added' do it 'sets the note text' do - expect(subject.note).to eq "Reassigned to @#{assignee.username}" + expect(subject.note).to eq "assigned to @#{assignee.username}" end end @@ -128,7 +128,7 @@ describe SystemNoteService, services: true do let(:assignee) { nil } it 'sets the note text' do - expect(subject.note).to eq 'Assignee removed' + expect(subject.note).to eq 'removed assignee' end end end @@ -147,7 +147,7 @@ describe SystemNoteService, services: true do let(:removed) { [] } it 'sets the note text' do - expect(subject.note).to eq "Added ~#{labels[0].id} ~#{labels[1].id} labels" + expect(subject.note).to eq "added ~#{labels[0].id} ~#{labels[1].id} labels" end end @@ -156,7 +156,7 @@ describe SystemNoteService, services: true do let(:removed) { labels } it 'sets the note text' do - expect(subject.note).to eq "Removed ~#{labels[0].id} ~#{labels[1].id} labels" + expect(subject.note).to eq "removed ~#{labels[0].id} ~#{labels[1].id} labels" end end @@ -165,7 +165,7 @@ describe SystemNoteService, services: true do let(:removed) { [labels[1]] } it 'sets the note text' do - expect(subject.note).to eq "Added ~#{labels[0].id} and removed ~#{labels[1].id} labels" + expect(subject.note).to eq "added ~#{labels[0].id} and removed ~#{labels[1].id} labels" end end end @@ -179,7 +179,7 @@ describe SystemNoteService, services: true do context 'when milestone added' do it 'sets the note text' do - expect(subject.note).to eq "Milestone changed to #{milestone.to_reference}" + expect(subject.note).to eq "changed milestone to #{milestone.to_reference}" end end @@ -187,7 +187,7 @@ describe SystemNoteService, services: true do let(:milestone) { nil } it 'sets the note text' do - expect(subject.note).to eq 'Milestone removed' + expect(subject.note).to eq 'removed milestone' end end end @@ -204,13 +204,13 @@ describe SystemNoteService, services: true do let(:source) { double('commit', gfm_reference: 'commit 123456') } it 'sets the note text' do - expect(subject.note).to eq "Status changed to #{status} by commit 123456" + expect(subject.note).to eq "#{status} via commit 123456" end end context 'without a source' do it 'sets the note text' do - expect(subject.note).to eq "Status changed to #{status}" + expect(subject.note).to eq status end end end @@ -226,7 +226,7 @@ describe SystemNoteService, services: true do it_behaves_like 'a system note' it "posts the Merge When Build Succeeds system note" do - expect(subject.note).to match /Enabled an automatic merge when the build for (\w+\/\w+@)?[0-9a-f]{40} succeeds/ + expect(subject.note).to match /enabled an automatic merge when the build for (\w+\/\w+@)?\h{40} succeeds/ end end @@ -240,7 +240,7 @@ describe SystemNoteService, services: true do it_behaves_like 'a system note' it "posts the Merge When Build Succeeds system note" do - expect(subject.note).to eq "Canceled the automatic merge" + expect(subject.note).to eq "canceled the automatic merge" end end @@ -252,7 +252,7 @@ describe SystemNoteService, services: true do it 'sets the note text' do expect(subject.note). - to eq "Changed title: **{-Old title-}** → **{+#{noteable.title}+}**" + to eq "changed title from **{-Old title-}** to **{+#{noteable.title}+}**" end end end @@ -264,7 +264,7 @@ describe SystemNoteService, services: true do it_behaves_like 'a system note' it 'sets the note text' do - expect(subject.note).to eq 'Made the issue visible' + expect(subject.note).to eq 'made the issue visible to everyone' end end end @@ -278,7 +278,7 @@ describe SystemNoteService, services: true do context 'when target branch name changed' do it 'sets the note text' do - expect(subject.note).to eq "Target branch changed from `#{old_branch}` to `#{new_branch}`" + expect(subject.note).to eq "changed target branch from `#{old_branch}` to `#{new_branch}`" end end end @@ -290,7 +290,7 @@ describe SystemNoteService, services: true do context 'when source branch deleted' do it 'sets the note text' do - expect(subject.note).to eq "Deleted source branch `feature`" + expect(subject.note).to eq "deleted source branch `feature`" end end end @@ -302,7 +302,7 @@ describe SystemNoteService, services: true do context 'when a branch is created from the new branch button' do it 'sets the note text' do - expect(subject.note).to match /\AStarted branch [`1-mepmep`]/ + expect(subject.note).to match /\Acreated branch [`1-mepmep`]/ end end end @@ -338,13 +338,13 @@ describe SystemNoteService, services: true do let(:mentioner) { project2.repository.commit } it 'references the mentioning commit' do - expect(subject.note).to eq "Mentioned in commit #{mentioner.to_reference(project)}" + expect(subject.note).to eq "mentioned in commit #{mentioner.to_reference(project)}" end end context 'from non-Commit' do it 'references the mentioning object' do - expect(subject.note).to eq "Mentioned in issue #{mentioner.to_reference(project)}" + expect(subject.note).to eq "mentioned in issue #{mentioner.to_reference(project)}" end end end @@ -354,13 +354,13 @@ describe SystemNoteService, services: true do let(:mentioner) { project.repository.commit } it 'references the mentioning commit' do - expect(subject.note).to eq "Mentioned in commit #{mentioner.to_reference}" + expect(subject.note).to eq "mentioned in commit #{mentioner.to_reference}" end end context 'from non-Commit' do it 'references the mentioning object' do - expect(subject.note).to eq "Mentioned in issue #{mentioner.to_reference}" + expect(subject.note).to eq "mentioned in issue #{mentioner.to_reference}" end end end @@ -370,7 +370,11 @@ describe SystemNoteService, services: true do describe '.cross_reference?' do it 'is truthy when text begins with expected text' do - expect(described_class.cross_reference?('Mentioned in something')).to be_truthy + expect(described_class.cross_reference?('mentioned in something')).to be_truthy + end + + it 'is truthy when text begins with legacy capitalized expected text' do + expect(described_class.cross_reference?('mentioned in something')).to be_truthy end it 'is falsey when text does not begin with expected text' do @@ -433,6 +437,19 @@ describe SystemNoteService, services: true do expect(described_class.cross_reference_exists?(noteable, commit1)). to be_falsey end + + context 'legacy capitalized cross reference' do + before do + # Mention issue (noteable) from commit0 + system_note = described_class.cross_reference(noteable, commit0, author) + system_note.update(note: system_note.note.capitalize) + end + + it 'is truthy when already mentioned' do + expect(described_class.cross_reference_exists?(noteable, commit0)). + to be_truthy + end + end end context 'commit from commit' do @@ -450,6 +467,19 @@ describe SystemNoteService, services: true do expect(described_class.cross_reference_exists?(commit1, commit0)). to be_falsey end + + context 'legacy capitalized cross reference' do + before do + # Mention commit1 from commit0 + system_note = described_class.cross_reference(commit0, commit1, author) + system_note.update(note: system_note.note.capitalize) + end + + it 'is truthy when already mentioned' do + expect(described_class.cross_reference_exists?(commit0, commit1)). + to be_truthy + end + end end context 'commit with cross-reference from fork' do @@ -465,6 +495,18 @@ describe SystemNoteService, services: true do expect(described_class.cross_reference_exists?(noteable, commit2)). to be true end + + context 'legacy capitalized cross reference' do + before do + system_note = described_class.cross_reference(noteable, commit0, author2) + system_note.update(note: system_note.note.capitalize) + end + + it 'is true when a fork mentions an external issue' do + expect(described_class.cross_reference_exists?(noteable, commit2)). + to be true + end + end end end @@ -498,7 +540,7 @@ describe SystemNoteService, services: true do it_behaves_like 'cross project mentionable' it 'notifies about noteable being moved to' do - expect(subject.note).to match /Moved to/ + expect(subject.note).to match /moved to/ end end @@ -508,7 +550,7 @@ describe SystemNoteService, services: true do it_behaves_like 'cross project mentionable' it 'notifies about noteable being moved from' do - expect(subject.note).to match /Moved from/ + expect(subject.note).to match /moved from/ end end diff --git a/spec/support/carrierwave.rb b/spec/support/carrierwave.rb index aa89afd8fb3..72af2c70324 100644 --- a/spec/support/carrierwave.rb +++ b/spec/support/carrierwave.rb @@ -1,7 +1,7 @@ CarrierWave.root = 'tmp/tests/uploads' RSpec.configure do |config| - config.after(:suite) do + config.after(:each) do FileUtils.rm_rf('tmp/tests/uploads') end end diff --git a/spec/support/setup_builds_storage.rb b/spec/support/setup_builds_storage.rb index a4f21e95338..2e7c88bfc09 100644 --- a/spec/support/setup_builds_storage.rb +++ b/spec/support/setup_builds_storage.rb @@ -1,19 +1,18 @@ RSpec.configure do |config| def builds_path - Rails.root.join('tmp/builds') + Rails.root.join('tmp/tests/builds') end - config.before(:each) do - FileUtils.mkdir_p(builds_path) - FileUtils.touch(File.join(builds_path, ".gitkeep")) + config.before(:suite) do Settings.gitlab_ci['builds_path'] = builds_path end - config.after(:suite) do - Dir[File.join(builds_path, '*')].each do |path| - next if File.basename(path) == '.gitkeep' + config.before(:all) do + FileUtils.mkdir_p(builds_path) + end - FileUtils.rm_rf(path) - end + config.before(:each) do + FileUtils.rm_rf(builds_path) + FileUtils.mkdir_p(builds_path) end end diff --git a/spec/views/projects/builds/show.html.haml_spec.rb b/spec/views/projects/builds/show.html.haml_spec.rb index e0c77201116..745d0c745bd 100644 --- a/spec/views/projects/builds/show.html.haml_spec.rb +++ b/spec/views/projects/builds/show.html.haml_spec.rb @@ -88,16 +88,46 @@ describe 'projects/builds/show', :view do create(:ci_build, :running, environment: 'staging', pipeline: pipeline) end - let!(:environment) do - create(:environment, name: 'staging', project: project) - end - - it 'shows deployment message' do - expected_text = 'This build is creating a deployment to staging' - render - - expect(rendered).to have_css( - '.environment-information', text: expected_text) + context 'when environment exists' do + let!(:environment) do + create(:environment, name: 'staging', project: project) + end + + it 'shows deployment message' do + expected_text = 'This build is creating a deployment to staging' + render + + expect(rendered).to have_css( + '.environment-information', text: expected_text) + end + + context 'when it has deployment' do + let!(:deployment) do + create(:deployment, environment: environment) + end + + it 'shows that deployment will be overwritten' do + expected_text = 'This build is creating a deployment to staging' + render + + expect(rendered).to have_css( + '.environment-information', text: expected_text) + expect(rendered).to have_css( + '.environment-information', text: 'latest deployment') + end + end + end + + context 'when environment does not exist' do + it 'shows deployment message' do + expected_text = 'This build is creating a deployment to staging' + render + + expect(rendered).to have_css( + '.environment-information', text: expected_text) + expect(rendered).not_to have_css( + '.environment-information', text: 'latest deployment') + end end end @@ -134,6 +164,8 @@ describe 'projects/builds/show', :view do expect(rendered).to have_css( '.environment-information', text: expected_text) + expect(rendered).not_to have_css( + '.environment-information', text: 'latest deployment') end end end diff --git a/spec/views/projects/edit.html.haml_spec.rb b/spec/views/projects/edit.html.haml_spec.rb new file mode 100644 index 00000000000..d2575702ecc --- /dev/null +++ b/spec/views/projects/edit.html.haml_spec.rb @@ -0,0 +1,24 @@ +require 'spec_helper' + +describe 'projects/edit' do + include Devise::Test::ControllerHelpers + + let(:project) { create(:empty_project) } + let(:user) { create(:admin) } + + before do + assign(:project, project) + + allow(controller).to receive(:current_user).and_return(user) + allow(view).to receive_messages(current_user: user, can?: true) + allow(Gitlab.config.lfs).to receive(:enabled).and_return(true) + end + + context 'LFS enabled setting' do + it 'displays the correct elements' do + render + expect(rendered).to have_select('project_lfs_enabled') + expect(rendered).to have_content('Git Large File Storage') + end + end +end |