diff options
author | Regis <boudinot.regis@yahoo.com> | 2017-01-20 10:07:16 -0700 |
---|---|---|
committer | Regis <boudinot.regis@yahoo.com> | 2017-01-20 10:07:16 -0700 |
commit | df3db8e859ee87abf9776c8799837536530eeb23 (patch) | |
tree | db86caeff10e11fa6e214ebfa35ec876ae4bd457 | |
parent | f90685b8d5a092e7079e9d7edeebb00abddc1e21 (diff) | |
parent | ff75bd0409f79c8b75cf49a219cfe3257526042d (diff) | |
download | gitlab-ce-keep_mini_graph_down_pipelines_index.tar.gz |
Merge branch 'master' into keep_mini_graph_down_pipelines_indexkeep_mini_graph_down_pipelines_index
106 files changed, 1122 insertions, 524 deletions
diff --git a/GITLAB_WORKHORSE_VERSION b/GITLAB_WORKHORSE_VERSION index 6085e946503..f0bb29e7638 100644 --- a/GITLAB_WORKHORSE_VERSION +++ b/GITLAB_WORKHORSE_VERSION @@ -1 +1 @@ -1.2.1 +1.3.0 diff --git a/app/assets/javascripts/environments/components/environment.js.es6 b/app/assets/javascripts/environments/components/environment.js.es6 index 8b6fafb6104..fea642467fa 100644 --- a/app/assets/javascripts/environments/components/environment.js.es6 +++ b/app/assets/javascripts/environments/components/environment.js.es6 @@ -1,6 +1,7 @@ -/* eslint-disable no-param-reassign */ +/* eslint-disable no-param-reassign, no-new */ /* global Vue */ /* global EnvironmentsService */ +/* global Flash */ //= require vue //= require vue-resource @@ -10,41 +11,6 @@ (() => { window.gl = window.gl || {}; - /** - * Given the visibility prop provided by the url query parameter and which - * changes according to the active tab we need to filter which environments - * should be visible. - * - * The environments array is a recursive tree structure and we need to filter - * both root level environments and children environments. - * - * In order to acomplish that, both `filterState` and `filterEnvironmentsByState` - * functions work together. - * The first one works as the filter that verifies if the given environment matches - * the given state. - * The second guarantees both root level and children elements are filtered as well. - */ - - const filterState = state => environment => environment.state === state && environment; - /** - * Given the filter function and the array of environments will return only - * the environments that match the state provided to the filter function. - * - * @param {Function} fn - * @param {Array} array - * @return {Array} - */ - const filterEnvironmentsByState = (fn, arr) => arr.map((item) => { - if (item.children) { - const filteredChildren = filterEnvironmentsByState(fn, item.children).filter(Boolean); - if (filteredChildren.length) { - item.children = filteredChildren; - return item; - } - } - return fn(item); - }).filter(Boolean); - gl.environmentsList.EnvironmentsComponent = Vue.component('environment-component', { props: { store: { @@ -81,10 +47,6 @@ }, computed: { - filteredEnvironments() { - return filterEnvironmentsByState(filterState(this.visibility), this.state.environments); - }, - scope() { return this.$options.getQueryParameter('scope'); }, @@ -111,7 +73,7 @@ const scope = this.$options.getQueryParameter('scope'); if (scope) { - this.visibility = scope; + this.store.storeVisibility(scope); } this.isLoading = true; @@ -121,6 +83,10 @@ .then((json) => { this.store.storeEnvironments(json); this.isLoading = false; + }) + .catch(() => { + this.isLoading = false; + new Flash('An error occurred while fetching the environments.', 'alert'); }); }, @@ -188,7 +154,7 @@ <div class="blank-state blank-state-no-icon" v-if="!isLoading && state.environments.length === 0"> - <h2 class="blank-state-title"> + <h2 class="blank-state-title js-blank-state-title"> You don't have any environments right now. </h2> <p class="blank-state-text"> @@ -202,13 +168,13 @@ <a v-if="canCreateEnvironmentParsed" :href="newEnvironmentPath" - class="btn btn-create"> + class="btn btn-create js-new-environment-button"> New Environment </a> </div> <div class="table-holder" - v-if="!isLoading && state.environments.length > 0"> + v-if="!isLoading && state.filteredEnvironments.length > 0"> <table class="table ci-table environments"> <thead> <tr> @@ -221,7 +187,7 @@ </tr> </thead> <tbody> - <template v-for="model in filteredEnvironments" + <template v-for="model in state.filteredEnvironments" v-bind:model="model"> <tr diff --git a/app/assets/javascripts/environments/stores/environments_store.js.es6 b/app/assets/javascripts/environments/stores/environments_store.js.es6 index 0204a903ab5..9b4090100da 100644 --- a/app/assets/javascripts/environments/stores/environments_store.js.es6 +++ b/app/assets/javascripts/environments/stores/environments_store.js.es6 @@ -10,6 +10,8 @@ this.state.environments = []; this.state.stoppedCounter = 0; this.state.availableCounter = 0; + this.state.visibility = 'available'; + this.state.filteredEnvironments = []; return this; }, @@ -59,7 +61,7 @@ if (occurs.length) { acc[acc.indexOf(occurs[0])].children.push(environment); - acc[acc.indexOf(occurs[0])].children.sort(this.sortByName); + acc[acc.indexOf(occurs[0])].children.slice().sort(this.sortByName); } else { acc.push({ name: environment.environment_type, @@ -73,13 +75,70 @@ } return acc; - }, []).sort(this.sortByName); + }, []).slice().sort(this.sortByName); this.state.environments = environmentsTree; + this.filterEnvironmentsByVisibility(this.state.environments); + return environmentsTree; }, + storeVisibility(visibility) { + this.state.visibility = visibility; + }, + /** + * Given the visibility prop provided by the url query parameter and which + * changes according to the active tab we need to filter which environments + * should be visible. + * + * The environments array is a recursive tree structure and we need to filter + * both root level environments and children environments. + * + * In order to acomplish that, both `filterState` and `filterEnvironmentsByVisibility` + * functions work together. + * The first one works as the filter that verifies if the given environment matches + * the given state. + * The second guarantees both root level and children elements are filtered as well. + * + * Given array of environments will return only + * the environments that match the state stored. + * + * @param {Array} array + * @return {Array} + */ + filterEnvironmentsByVisibility(arr) { + const filteredEnvironments = arr.map((item) => { + if (item.children) { + const filteredChildren = this.filterEnvironmentsByVisibility( + item.children, + ).filter(Boolean); + + if (filteredChildren.length) { + item.children = filteredChildren; + return item; + } + } + + return this.filterState(this.state.visibility, item); + }).filter(Boolean); + + this.state.filteredEnvironments = filteredEnvironments; + return filteredEnvironments; + }, + + /** + * Given the state and the environment, + * returns only if the environment state matches the one provided. + * + * @param {String} state + * @param {Object} environment + * @return {Object} + */ + filterState(state, environment) { + return environment.state === state && environment; + }, + /** * Toggles folder open property given the environment type. * diff --git a/app/assets/javascripts/mini_pipeline_graph_dropdown.js.es6 b/app/assets/javascripts/mini_pipeline_graph_dropdown.js.es6 index 90b3366f14b..80549532ea9 100644 --- a/app/assets/javascripts/mini_pipeline_graph_dropdown.js.es6 +++ b/app/assets/javascripts/mini_pipeline_graph_dropdown.js.es6 @@ -10,9 +10,9 @@ * The container should be the table element. * * The stage icon clicked needs to have the following HTML structure: - * <div> - * <button class="dropdown js-builds-dropdown-button"></button> - * <div class="js-builds-dropdown-container"></div> + * <div class="dropdown"> + * <button class="dropdown js-builds-dropdown-button" data-toggle="dropdown"></button> + * <div class="js-builds-dropdown-container dropdown-menu"></div> * </div> */ (() => { @@ -26,13 +26,11 @@ } /** - * Adds and removes the event listener. + * Adds the event listener when the dropdown is opened. + * All dropdown events are fired at the .dropdown-menu's parent element. */ bindEvents() { - const dropdownButtonSelector = 'button.js-builds-dropdown-button'; - - $(this.container).off('click', dropdownButtonSelector, this.getBuildsList) - .on('click', dropdownButtonSelector, this.getBuildsList); + $(this.container).on('shown.bs.dropdown', this.getBuildsList); } /** @@ -52,11 +50,14 @@ /** * For the clicked stage, gets the list of builds. * - * @param {Object} e + * All dropdown events have a relatedTarget property, + * whose value is the toggling anchor element. + * + * @param {Object} e bootstrap dropdown event * @return {Promise} */ getBuildsList(e) { - const button = e.currentTarget; + const button = e.relatedTarget; const endpoint = button.dataset.stageEndpoint; return $.ajax({ diff --git a/app/assets/javascripts/u2f/authenticate.js.es6 b/app/assets/javascripts/u2f/authenticate.js.es6 index 3ba70e7b439..500b78fc5d8 100644 --- a/app/assets/javascripts/u2f/authenticate.js.es6 +++ b/app/assets/javascripts/u2f/authenticate.js.es6 @@ -57,7 +57,7 @@ return function(response) { var error; if (response.errorCode) { - error = new U2FError(response.errorCode); + error = new U2FError(response.errorCode, 'authenticate'); return _this.renderError(error); } else { return _this.renderAuthenticated(JSON.stringify(response)); diff --git a/app/assets/javascripts/u2f/error.js b/app/assets/javascripts/u2f/error.js index 499a24f58df..86b459e1866 100644 --- a/app/assets/javascripts/u2f/error.js +++ b/app/assets/javascripts/u2f/error.js @@ -5,21 +5,21 @@ var bind = function(fn, me) { return function() { return fn.apply(me, arguments); }; }; this.U2FError = (function() { - function U2FError(errorCode) { + function U2FError(errorCode, u2fFlowType) { this.errorCode = errorCode; this.message = bind(this.message, this); this.httpsDisabled = window.location.protocol !== 'https:'; + this.u2fFlowType = u2fFlowType; } U2FError.prototype.message = function() { - switch (false) { - case !(this.errorCode === u2f.ErrorCodes.BAD_REQUEST && this.httpsDisabled): - return "U2F only works with HTTPS-enabled websites. Contact your administrator for more details."; - case this.errorCode !== u2f.ErrorCodes.DEVICE_INELIGIBLE: - return "This device has already been registered with us."; - default: - return "There was a problem communicating with your device."; + if (this.errorCode === u2f.ErrorCodes.BAD_REQUEST && this.httpsDisabled) { + return 'U2F only works with HTTPS-enabled websites. Contact your administrator for more details.'; + } else if (this.errorCode === u2f.ErrorCodes.DEVICE_INELIGIBLE) { + if (this.u2fFlowType === 'authenticate') return 'This device has not been registered with us.'; + if (this.u2fFlowType === 'register') return 'This device has already been registered with us.'; } + return "There was a problem communicating with your device."; }; return U2FError; diff --git a/app/assets/javascripts/u2f/register.js b/app/assets/javascripts/u2f/register.js index 87c1f5ff62c..69d1ff3a39e 100644 --- a/app/assets/javascripts/u2f/register.js +++ b/app/assets/javascripts/u2f/register.js @@ -39,7 +39,7 @@ return function(response) { var error; if (response.errorCode) { - error = new U2FError(response.errorCode); + error = new U2FError(response.errorCode, 'register'); return _this.renderError(error); } else { return _this.renderRegistered(JSON.stringify(response)); diff --git a/app/assets/stylesheets/framework/typography.scss b/app/assets/stylesheets/framework/typography.scss index bd58a26f429..54958973f15 100644 --- a/app/assets/stylesheets/framework/typography.scss +++ b/app/assets/stylesheets/framework/typography.scss @@ -10,7 +10,7 @@ max-width: 100%; } - *:first-child { + *:first-child:not(.katex-display) { margin-top: 0; } diff --git a/app/assets/stylesheets/pages/issuable.scss b/app/assets/stylesheets/pages/issuable.scss index 324c6cec96a..93cc5a8cf0a 100644 --- a/app/assets/stylesheets/pages/issuable.scss +++ b/app/assets/stylesheets/pages/issuable.scss @@ -377,6 +377,10 @@ display: inline-block; padding: 5px; + &:nth-of-type(7n) { + padding-right: 0; + } + .author_link { display: block; } diff --git a/app/controllers/projects/hooks_controller.rb b/app/controllers/projects/hooks_controller.rb index 0ae8ff98009..b668a9331e7 100644 --- a/app/controllers/projects/hooks_controller.rb +++ b/app/controllers/projects/hooks_controller.rb @@ -6,21 +6,15 @@ class Projects::HooksController < Projects::ApplicationController layout "project_settings" - def index - @hooks = @project.hooks - @hook = ProjectHook.new - end - def create @hook = @project.hooks.new(hook_params) @hook.save - if @hook.valid? - redirect_to namespace_project_hooks_path(@project.namespace, @project) - else + unless @hook.valid? @hooks = @project.hooks.select(&:persisted?) - render :index + flash[:alert] = @hook.errors.full_messages.join.html_safe end + redirect_to namespace_project_settings_integrations_path(@project.namespace, @project) end def test @@ -44,7 +38,7 @@ class Projects::HooksController < Projects::ApplicationController def destroy hook.destroy - redirect_to namespace_project_hooks_path(@project.namespace, @project) + redirect_to namespace_project_settings_integrations_path(@project.namespace, @project) end private diff --git a/app/controllers/projects/services_controller.rb b/app/controllers/projects/services_controller.rb index 30c2a5d9982..17cb1d5be24 100644 --- a/app/controllers/projects/services_controller.rb +++ b/app/controllers/projects/services_controller.rb @@ -9,10 +9,6 @@ class Projects::ServicesController < Projects::ApplicationController layout "project_settings" - def index - @services = @project.find_or_initialize_services - end - def edit end diff --git a/app/controllers/projects/settings/integrations_controller.rb b/app/controllers/projects/settings/integrations_controller.rb new file mode 100644 index 00000000000..fb2a4837735 --- /dev/null +++ b/app/controllers/projects/settings/integrations_controller.rb @@ -0,0 +1,18 @@ +module Projects + module Settings + class IntegrationsController < Projects::ApplicationController + include ServiceParams + + before_action :authorize_admin_project! + layout "project_settings" + + def show + @hooks = @project.hooks + @hook = ProjectHook.new + + # Services + @services = @project.find_or_initialize_services + end + end + end +end diff --git a/app/helpers/gitlab_routing_helper.rb b/app/helpers/gitlab_routing_helper.rb index 5742fec4458..2159e4ce21a 100644 --- a/app/helpers/gitlab_routing_helper.rb +++ b/app/helpers/gitlab_routing_helper.rb @@ -208,6 +208,10 @@ module GitlabRoutingHelper end # Settings + def project_settings_integrations_path(project, *args) + namespace_project_settings_integrations_path(project.namespace, project, *args) + end + def project_settings_members_path(project, *args) namespace_project_settings_members_path(project.namespace, project, *args) end diff --git a/app/models/application_setting.rb b/app/models/application_setting.rb index 8fab77cda0a..e33a58d3771 100644 --- a/app/models/application_setting.rb +++ b/app/models/application_setting.rb @@ -13,6 +13,49 @@ class ApplicationSetting < ActiveRecord::Base [\r\n] # any number of newline characters }x + DEFAULTS_CE = { + after_sign_up_text: nil, + akismet_enabled: false, + container_registry_token_expire_delay: 5, + default_branch_protection: Settings.gitlab['default_branch_protection'], + default_project_visibility: Settings.gitlab.default_projects_features['visibility_level'], + default_projects_limit: Settings.gitlab['default_projects_limit'], + default_snippet_visibility: Settings.gitlab.default_projects_features['visibility_level'], + disabled_oauth_sign_in_sources: [], + domain_whitelist: Settings.gitlab['domain_whitelist'], + gravatar_enabled: Settings.gravatar['enabled'], + help_page_text: nil, + housekeeping_bitmaps_enabled: true, + housekeeping_enabled: true, + housekeeping_full_repack_period: 50, + housekeeping_gc_period: 200, + housekeeping_incremental_repack_period: 10, + import_sources: Gitlab::ImportSources.values, + koding_enabled: false, + koding_url: nil, + max_artifacts_size: Settings.artifacts['max_size'], + max_attachment_size: Settings.gitlab['max_attachment_size'], + plantuml_enabled: false, + plantuml_url: nil, + recaptcha_enabled: false, + repository_checks_enabled: true, + repository_storages: ['default'], + require_two_factor_authentication: false, + restricted_visibility_levels: Settings.gitlab['restricted_visibility_levels'], + session_expire_delay: Settings.gitlab['session_expire_delay'], + send_user_confirmation_email: false, + shared_runners_enabled: Settings.gitlab_ci['shared_runners_enabled'], + shared_runners_text: nil, + sidekiq_throttling_enabled: false, + sign_in_text: nil, + signin_enabled: Settings.gitlab['signin_enabled'], + signup_enabled: Settings.gitlab['signup_enabled'], + two_factor_grace_period: 48, + user_default_external: false + } + + DEFAULTS = DEFAULTS_CE + serialize :restricted_visibility_levels serialize :import_sources serialize :disabled_oauth_sign_in_sources, Array @@ -163,46 +206,7 @@ class ApplicationSetting < ActiveRecord::Base end def self.create_from_defaults - create( - default_projects_limit: Settings.gitlab['default_projects_limit'], - default_branch_protection: Settings.gitlab['default_branch_protection'], - signup_enabled: Settings.gitlab['signup_enabled'], - signin_enabled: Settings.gitlab['signin_enabled'], - gravatar_enabled: Settings.gravatar['enabled'], - sign_in_text: nil, - after_sign_up_text: nil, - help_page_text: nil, - shared_runners_text: nil, - restricted_visibility_levels: Settings.gitlab['restricted_visibility_levels'], - max_attachment_size: Settings.gitlab['max_attachment_size'], - session_expire_delay: Settings.gitlab['session_expire_delay'], - default_project_visibility: Settings.gitlab.default_projects_features['visibility_level'], - default_snippet_visibility: Settings.gitlab.default_projects_features['visibility_level'], - domain_whitelist: Settings.gitlab['domain_whitelist'], - import_sources: Gitlab::ImportSources.values, - shared_runners_enabled: Settings.gitlab_ci['shared_runners_enabled'], - max_artifacts_size: Settings.artifacts['max_size'], - require_two_factor_authentication: false, - two_factor_grace_period: 48, - recaptcha_enabled: false, - akismet_enabled: false, - koding_enabled: false, - koding_url: nil, - plantuml_enabled: false, - plantuml_url: nil, - repository_checks_enabled: true, - disabled_oauth_sign_in_sources: [], - send_user_confirmation_email: false, - container_registry_token_expire_delay: 5, - repository_storages: ['default'], - user_default_external: false, - sidekiq_throttling_enabled: false, - housekeeping_enabled: true, - housekeeping_bitmaps_enabled: true, - housekeeping_incremental_repack_period: 10, - housekeeping_full_repack_period: 50, - housekeeping_gc_period: 200, - ) + create(DEFAULTS) end def home_page_url_column_exist diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb index ce23c2d1088..5fe8ddf69d7 100644 --- a/app/models/ci/build.rb +++ b/app/models/ci/build.rb @@ -92,6 +92,12 @@ module Ci end state_machine :status do + after_transition any => [:pending] do |build| + build.run_after_commit do + BuildQueueWorker.perform_async(id) + end + end + after_transition pending: :running do |build| build.run_after_commit do BuildHooksWorker.perform_async(id) diff --git a/app/models/ci/runner.rb b/app/models/ci/runner.rb index 123930273e0..ed1843ba005 100644 --- a/app/models/ci/runner.rb +++ b/app/models/ci/runner.rb @@ -2,6 +2,7 @@ module Ci class Runner < ActiveRecord::Base extend Ci::Model + RUNNER_QUEUE_EXPIRY_TIME = 60.minutes LAST_CONTACT_TIME = 1.hour.ago AVAILABLE_SCOPES = %w[specific shared active paused online] FORM_EDITABLE = %i[description tag_list active run_untagged locked] @@ -21,6 +22,8 @@ module Ci scope :online, ->() { where('contacted_at > ?', LAST_CONTACT_TIME) } scope :ordered, ->() { order(id: :desc) } + after_save :tick_runner_queue, if: :form_editable_changed? + scope :owned_or_shared, ->(project_id) do joins('LEFT JOIN ci_runner_projects ON ci_runner_projects.runner_id = ci_runners.id') .where("ci_runner_projects.gl_project_id = :project_id OR ci_runners.is_shared = true", project_id: project_id) @@ -122,8 +125,38 @@ module Ci ] end + def tick_runner_queue + SecureRandom.hex.tap do |new_update| + Gitlab::Redis.with do |redis| + redis.set(runner_queue_key, new_update, ex: RUNNER_QUEUE_EXPIRY_TIME) + end + end + end + + def ensure_runner_queue_value + Gitlab::Redis.with do |redis| + value = SecureRandom.hex + redis.set(runner_queue_key, value, ex: RUNNER_QUEUE_EXPIRY_TIME, nx: true) + redis.get(runner_queue_key) + end + end + + def is_runner_queue_value_latest?(value) + ensure_runner_queue_value == value if value.present? + end + private + def runner_queue_key + "runner:build_queue:#{self.token}" + end + + def form_editable_changed? + FORM_EDITABLE.any? do |editable| + public_send("#{editable}_changed?") + end + end + def tag_constraints unless has_tags? || run_untagged? errors.add(:tags_list, diff --git a/app/models/key.rb b/app/models/key.rb index 8be29c697f1..9c74ca84753 100644 --- a/app/models/key.rb +++ b/app/models/key.rb @@ -4,6 +4,8 @@ class Key < ActiveRecord::Base include AfterCommitQueue include Sortable + LAST_USED_AT_REFRESH_TIME = 1.day.to_i + belongs_to :user before_validation :generate_fingerprint @@ -50,7 +52,10 @@ class Key < ActiveRecord::Base end def update_last_used_at - UseKeyWorker.perform_async(self.id) + lease = Gitlab::ExclusiveLease.new("key_update_last_used_at:#{id}", timeout: LAST_USED_AT_REFRESH_TIME) + return unless lease.try_obtain + + UseKeyWorker.perform_async(id) end def add_to_shell diff --git a/app/services/ci/update_build_queue_service.rb b/app/services/ci/update_build_queue_service.rb new file mode 100644 index 00000000000..152c8ae5006 --- /dev/null +++ b/app/services/ci/update_build_queue_service.rb @@ -0,0 +1,19 @@ +module Ci + class UpdateBuildQueueService + def execute(build) + build.project.runners.each do |runner| + if runner.can_pick?(build) + runner.tick_runner_queue + end + end + + return unless build.project.shared_runners_enabled? + + Ci::Runner.shared.each do |runner| + if runner.can_pick?(build) + runner.tick_runner_queue + end + end + end + end +end diff --git a/app/views/layouts/nav/_project_settings.html.haml b/app/views/layouts/nav/_project_settings.html.haml index 0fb2bb460cb..c6df66d2c3c 100644 --- a/app/views/layouts/nav/_project_settings.html.haml +++ b/app/views/layouts/nav/_project_settings.html.haml @@ -8,14 +8,10 @@ = link_to namespace_project_deploy_keys_path(@project.namespace, @project), title: 'Deploy Keys' do %span Deploy Keys - = nav_link(controller: :hooks) do - = link_to namespace_project_hooks_path(@project.namespace, @project), title: 'Webhooks' do + = nav_link(controller: :integrations) do + = link_to namespace_project_settings_integrations_path(@project.namespace, @project), title: 'Integrations' do %span - Webhooks - = nav_link(controller: :services) do - = link_to namespace_project_services_path(@project.namespace, @project), title: 'Services' do - %span - Services + Integrations = nav_link(controller: :protected_branches) do = link_to namespace_project_protected_branches_path(@project.namespace, @project), title: 'Protected Branches' do %span diff --git a/app/views/projects/commit/_change.html.haml b/app/views/projects/commit/_change.html.haml index 990908211de..421b3db342d 100644 --- a/app/views/projects/commit/_change.html.haml +++ b/app/views/projects/commit/_change.html.haml @@ -13,7 +13,7 @@ %a.close{ href: "#", "data-dismiss" => "modal" } × %h3.page-title== #{label} this #{commit.change_type_title(current_user)} .modal-body - = form_tag [type.underscore, @project.namespace, @project, commit], method: :post, remote: false, class: "form-horizontal js-#{type}-form js-requires-input" do + = form_tag [type.underscore, @project.namespace.becomes(Namespace), @project, commit], method: :post, remote: false, class: "form-horizontal js-#{type}-form js-requires-input" do .form-group.branch = label_tag 'target_branch', target_label, class: 'control-label' .col-sm-10 diff --git a/app/views/projects/hooks/index.html.haml b/app/views/projects/hooks/_index.html.haml index 8faad351463..8faad351463 100644 --- a/app/views/projects/hooks/index.html.haml +++ b/app/views/projects/hooks/_index.html.haml diff --git a/app/views/projects/services/index.html.haml b/app/views/projects/services/_index.html.haml index 66fd3029dc9..964133504e6 100644 --- a/app/views/projects/services/index.html.haml +++ b/app/views/projects/services/_index.html.haml @@ -1,5 +1,3 @@ -- page_title "Services" - .row.prepend-top-default.append-bottom-default .col-lg-3 %h4.prepend-top-0 diff --git a/app/views/projects/hooks/_project_hook.html.haml b/app/views/projects/settings/integrations/_project_hook.html.haml index ceabe2eab3d..ceabe2eab3d 100644 --- a/app/views/projects/hooks/_project_hook.html.haml +++ b/app/views/projects/settings/integrations/_project_hook.html.haml diff --git a/app/views/projects/settings/integrations/show.html.haml b/app/views/projects/settings/integrations/show.html.haml new file mode 100644 index 00000000000..aa38a889cdd --- /dev/null +++ b/app/views/projects/settings/integrations/show.html.haml @@ -0,0 +1,3 @@ +- page_title 'Integrations' += render 'projects/hooks/index' += render 'projects/services/index' diff --git a/app/views/shared/web_hooks/_form.html.haml b/app/views/shared/web_hooks/_form.html.haml index 5d659eb83a9..13586a5a12a 100644 --- a/app/views/shared/web_hooks/_form.html.haml +++ b/app/views/shared/web_hooks/_form.html.haml @@ -1,6 +1,3 @@ -- page_title "Webhooks" -- context_title = @project ? 'project' : 'group' - .row.prepend-top-default .col-lg-3 %h4.prepend-top-0 diff --git a/app/workers/build_queue_worker.rb b/app/workers/build_queue_worker.rb new file mode 100644 index 00000000000..fa9e097e40a --- /dev/null +++ b/app/workers/build_queue_worker.rb @@ -0,0 +1,10 @@ +class BuildQueueWorker + include Sidekiq::Worker + include BuildQueue + + def perform(build_id) + Ci::Build.find_by(id: build_id).try do |build| + Ci::UpdateBuildQueueService.new.execute(build) + end + end +end diff --git a/changelogs/unreleased/21698-redis-runner-last-build.yml b/changelogs/unreleased/21698-redis-runner-last-build.yml new file mode 100644 index 00000000000..2aba3c353a3 --- /dev/null +++ b/changelogs/unreleased/21698-redis-runner-last-build.yml @@ -0,0 +1,4 @@ +--- +title: Reduce DB-load for build-queues by storing last_update in Redis +merge_request: 8084 +author: diff --git a/changelogs/unreleased/25507-handle-errors-environment-list.yml b/changelogs/unreleased/25507-handle-errors-environment-list.yml new file mode 100644 index 00000000000..4e9794f7917 --- /dev/null +++ b/changelogs/unreleased/25507-handle-errors-environment-list.yml @@ -0,0 +1,4 @@ +--- +title: Handle HTTP errors in environment list +merge_request: +author: diff --git a/changelogs/unreleased/26472-math-margin.yml b/changelogs/unreleased/26472-math-margin.yml new file mode 100644 index 00000000000..3999f521558 --- /dev/null +++ b/changelogs/unreleased/26472-math-margin.yml @@ -0,0 +1,4 @@ +--- +title: Add margin to markdown math blocks +merge_request: +author: diff --git a/changelogs/unreleased/26601-dropdown-makes-request-close.yml b/changelogs/unreleased/26601-dropdown-makes-request-close.yml new file mode 100644 index 00000000000..a810e04376d --- /dev/null +++ b/changelogs/unreleased/26601-dropdown-makes-request-close.yml @@ -0,0 +1,4 @@ +--- +title: Fixes builds dropdown making request when clicked to be closed +merge_request: 8545 +author: diff --git a/changelogs/unreleased/fix-import-users.yml b/changelogs/unreleased/fix-import-users.yml new file mode 100644 index 00000000000..bb483bb9417 --- /dev/null +++ b/changelogs/unreleased/fix-import-users.yml @@ -0,0 +1,4 @@ +--- +title: Fix import/export wrong user mapping +merge_request: +author: diff --git a/changelogs/unreleased/participants-list.yml b/changelogs/unreleased/participants-list.yml new file mode 100644 index 00000000000..5265a2bc780 --- /dev/null +++ b/changelogs/unreleased/participants-list.yml @@ -0,0 +1,4 @@ +--- +title: Fix participants margins to fit on one line +merge_request: +author: diff --git a/changelogs/unreleased/record-used-ssh-keys-once-per-day.yml b/changelogs/unreleased/record-used-ssh-keys-once-per-day.yml new file mode 100644 index 00000000000..9af9e3833c6 --- /dev/null +++ b/changelogs/unreleased/record-used-ssh-keys-once-per-day.yml @@ -0,0 +1,4 @@ +--- +title: Record used SSH keys only once per day +merge_request: 8655 +author: diff --git a/config/routes/project.rb b/config/routes/project.rb index 1fc6ed28c74..6620b765e02 100644 --- a/config/routes/project.rb +++ b/config/routes/project.rb @@ -307,9 +307,9 @@ constraints(ProjectUrlConstrainer.new) do end end end - namespace :settings do resource :members, only: [:show] + resource :integrations, only: [:show] end # Since both wiki and repository routing contains wildcard characters diff --git a/db/migrate/20161223034433_add_estimate_to_issuables_ce.rb b/db/migrate/20161223034433_add_estimate_to_issuables_ce.rb new file mode 100644 index 00000000000..2cbe626d752 --- /dev/null +++ b/db/migrate/20161223034433_add_estimate_to_issuables_ce.rb @@ -0,0 +1,15 @@ +class AddEstimateToIssuablesCe < ActiveRecord::Migration + include Gitlab::Database::MigrationHelpers + + DOWNTIME = false + + def change + unless column_exists?(:issues, :time_estimate) + add_column :issues, :time_estimate, :integer + end + + unless column_exists?(:merge_requests, :time_estimate) + add_column :merge_requests, :time_estimate, :integer + end + end +end diff --git a/db/migrate/20161223034433_add_time_estimate_to_issuables.rb b/db/migrate/20161223034433_add_time_estimate_to_issuables.rb deleted file mode 100644 index 8d89756a9bc..00000000000 --- a/db/migrate/20161223034433_add_time_estimate_to_issuables.rb +++ /dev/null @@ -1,30 +0,0 @@ -# See http://doc.gitlab.com/ce/development/migration_style_guide.html -# for more information on how to write migrations for GitLab. - -class AddTimeEstimateToIssuables < ActiveRecord::Migration - include Gitlab::Database::MigrationHelpers - - # Set this constant to true if this migration requires downtime. - DOWNTIME = false - - # When a migration requires downtime you **must** uncomment the following - # constant and define a short and easy to understand explanation as to why the - # migration requires downtime. - # DOWNTIME_REASON = '' - - # When using the methods "add_concurrent_index" or "add_column_with_default" - # you must disable the use of transactions as these methods can not run in an - # existing transaction. When using "add_concurrent_index" make sure that this - # method is the _only_ method called in the migration, any other changes - # should go in a separate migration. This ensures that upon failure _only_ the - # index creation fails and can be retried or reverted easily. - # - # To disable transactions uncomment the following line and remove these - # comments: - # disable_ddl_transaction! - - def change - add_column :issues, :time_estimate, :integer - add_column :merge_requests, :time_estimate, :integer - end -end diff --git a/db/migrate/20161223034646_create_timelogs.rb b/db/migrate/20161223034646_create_timelogs.rb deleted file mode 100644 index d3353a67eec..00000000000 --- a/db/migrate/20161223034646_create_timelogs.rb +++ /dev/null @@ -1,38 +0,0 @@ -# See http://doc.gitlab.com/ce/development/migration_style_guide.html -# for more information on how to write migrations for GitLab. - -class CreateTimelogs < ActiveRecord::Migration - include Gitlab::Database::MigrationHelpers - - # Set this constant to true if this migration requires downtime. - DOWNTIME = false - - # When a migration requires downtime you **must** uncomment the following - # constant and define a short and easy to understand explanation as to why the - # migration requires downtime. - # DOWNTIME_REASON = '' - - # When using the methods "add_concurrent_index" or "add_column_with_default" - # you must disable the use of transactions as these methods can not run in an - # existing transaction. When using "add_concurrent_index" make sure that this - # method is the _only_ method called in the migration, any other changes - # should go in a separate migration. This ensures that upon failure _only_ the - # index creation fails and can be retried or reverted easily. - # - # To disable transactions uncomment the following line and remove these - # comments: - # disable_ddl_transaction! - - def change - create_table :timelogs do |t| - t.integer :time_spent, null: false - t.references :trackable, polymorphic: true - t.references :user - - t.timestamps null: false - end - - add_index :timelogs, [:trackable_type, :trackable_id] - add_index :timelogs, :user_id - end -end diff --git a/db/migrate/20161223034646_create_timelogs_ce.rb b/db/migrate/20161223034646_create_timelogs_ce.rb new file mode 100644 index 00000000000..e8a4b406012 --- /dev/null +++ b/db/migrate/20161223034646_create_timelogs_ce.rb @@ -0,0 +1,20 @@ +class CreateTimelogsCe < ActiveRecord::Migration + include Gitlab::Database::MigrationHelpers + + DOWNTIME = false + + def change + unless table_exists?(:timelogs) + create_table :timelogs do |t| + t.integer :time_spent, null: false + t.references :trackable, polymorphic: true + t.references :user + + t.timestamps null: false + end + + add_index :timelogs, [:trackable_type, :trackable_id] + add_index :timelogs, :user_id + end + end +end diff --git a/doc/README.md b/doc/README.md index ee69684b53b..43261ef7457 100644 --- a/doc/README.md +++ b/doc/README.md @@ -1,5 +1,9 @@ # GitLab Community Edition documentation +## University + +[University](university/README.md) contain guides to learn Git and GitLab through courses and videos. + ## User documentation - [Account Security](user/account/security.md) Securing your account via two-factor authentication, etc. @@ -19,7 +23,6 @@ - [SSH](ssh/README.md) Setup your ssh keys and deploy keys for secure access to your projects. - [Webhooks](web_hooks/web_hooks.md) Let GitLab notify you when new code has been pushed to your project. - [Workflow](workflow/README.md) Using GitLab functionality and importing projects from GitHub and SVN. -- [University](university/README.md) Learn Git and GitLab through videos and courses. - [Git Attributes](user/project/git_attributes.md) Managing Git attributes using a `.gitattributes` file. - [Git cheatsheet](https://gitlab.com/gitlab-com/marketing/raw/master/design/print/git-cheatsheet/print-pdf/git-cheatsheet.pdf) Download a PDF describing the most used Git operations. diff --git a/doc/api/README.md b/doc/api/README.md index f65b934b9db..20f28e8d30e 100644 --- a/doc/api/README.md +++ b/doc/api/README.md @@ -104,6 +104,13 @@ that needs access to the GitLab API. Once you have your token, pass it to the API using either the `private_token` parameter or the `PRIVATE-TOKEN` header. +> [Introduced][ce-5951] in GitLab 8.15. + +Personal Access Tokens can be created with one or more scopes that allow various actions +that a given token can perform. Although there are only two scopes available at the +moment – `read_user` and `api` – the groundwork has been laid to add more scopes easily. + +At any time you can revoke any personal access token by just clicking **Revoke**. ### Session Cookie @@ -380,3 +387,4 @@ programming languages. Visit the [GitLab website] for a complete list. [GitLab website]: https://about.gitlab.com/applications/#api-clients "Clients using the GitLab API" [lib-api-url]: https://gitlab.com/gitlab-org/gitlab-ce/tree/master/lib/api/api.rb [ce-3749]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/3749 +[ce-5951]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/5951 diff --git a/doc/ci/git_submodules.md b/doc/ci/git_submodules.md index 1d782200cca..869743ce80a 100644 --- a/doc/ci/git_submodules.md +++ b/doc/ci/git_submodules.md @@ -61,7 +61,18 @@ correctly with your CI builds: 1. First, make sure you have used [relative URLs](#configuring-the-gitmodules-file) for the submodules located in the same GitLab server. -1. Then, use `git submodule sync/update` in `before_script`: +1. Next, if you are using `gitlab-ci-multi-runner` v1.10+, you can set the + `GIT_SUBMODULE_STRATEGY` variable to either `normal` or `recursive` to tell + the runner to fetch your submodules before the build: + ```yaml + variables: + GIT_SUBMODULE_STRATEGY: recursive + ``` + See the [`.gitlab-ci.yml` reference](yaml/README.md#git-submodule-strategy) + for more details about `GIT_SUBMODULE_STRATEGY`. + +1. If you are using an older version of `gitlab-ci-multi-runner`, then use + `git submodule sync/update` in `before_script`: ```yaml before_script: diff --git a/doc/ci/yaml/README.md b/doc/ci/yaml/README.md index 7158b2e7895..75a0897eb15 100644 --- a/doc/ci/yaml/README.md +++ b/doc/ci/yaml/README.md @@ -1034,6 +1034,41 @@ variables: GIT_STRATEGY: none ``` +## Git Submodule Strategy + +> Requires GitLab Runner v1.10+. + +The `GIT_SUBMODULE_STRATEGY` variable is used to control if / how Git +submodules are included when fetching the code before a build. Like +`GIT_STRATEGY`, it can be set in either the global [`variables`](#variables) +section or the [`variables`](#job-variables) section for individual jobs. + +There are three posible values: `none`, `normal`, and `recursive`: + +- `none` means that submodules will not be included when fetching the project + code. This is the default, which matches the pre-v1.10 behavior. + +- `normal` means that only the top-level submodules will be included. It is + equivalent to: + ``` + $ git submodule sync + $ git submodule update --init + ``` + +- `recursive` means that all submodules (including submodules of submodules) + will be included. It is equivalent to: + ``` + $ git submodule sync --recursive + $ git submodule update --init --recursive + ``` + +Note that for this feature to work correctly, the submodules must be configured +(in `.gitmodules`) with either: +- the HTTP(S) URL of a publicly-accessible repository, or +- a relative path to another repository on the same GitLab server. See the + [Git submodules](../git_submodules.md) documentation. + + ## Build stages attempts > Introduced in GitLab, it requires GitLab Runner v1.9+. diff --git a/doc/integration/oauth_provider.md b/doc/integration/oauth_provider.md index 0c53584d201..af8a1c4e5ed 100644 --- a/doc/integration/oauth_provider.md +++ b/doc/integration/oauth_provider.md @@ -74,8 +74,10 @@ in the **Authorized applications** section under **Profile Settings > Applicatio --- -As you can see, the default scope `api` is used, which is the only scope that -GitLab supports so far. At any time you can revoke any access by just clicking -**Revoke**. +GitLab's OAuth applications support scopes, which allow various actions that any given +application can perform. Although there are only two scopes available at the +moment – `read_user` and `api` – the groundwork has been laid to add more scopes easily. + +At any time you can revoke any access by just clicking **Revoke**. [oauth]: http://oauth.net/2/ "OAuth website" diff --git a/doc/public_access/img/restrict_visibility_levels.png b/doc/public_access/img/restrict_visibility_levels.png Binary files differnew file mode 100644 index 00000000000..c7d4d87981f --- /dev/null +++ b/doc/public_access/img/restrict_visibility_levels.png diff --git a/doc/public_access/public_access.md b/doc/public_access/public_access.md index a3921f1b89f..e8f4c73120c 100644 --- a/doc/public_access/public_access.md +++ b/doc/public_access/public_access.md @@ -52,7 +52,7 @@ for anonymous users. The group page now has a visibility level icon. ## Visibility of users -The public page of a user, located at `/u/username`, is always visible whether +The public page of a user, located at `/username`, is always visible whether you are logged in or not. When visiting the public page of a user, you can only see the projects which @@ -60,10 +60,13 @@ you are privileged to. If the public level is restricted, user profiles are only visible to logged in users. - ## Restricting the use of public or internal projects In the Admin area under **Settings** (`/admin/application_settings`), you can restrict the use of visibility levels for users when they create a project or a -snippet. This is useful to prevent people exposing their repositories to public +snippet: + +![Restrict visibility levels](img/restrict_visibility_levels.png) + +This is useful to prevent people exposing their repositories to public by accident. The restricted visibility settings do not apply to admin users. diff --git a/features/project/active_tab.feature b/features/project/active_tab.feature index 57dda9c2234..d033e6b167b 100644 --- a/features/project/active_tab.feature +++ b/features/project/active_tab.feature @@ -39,10 +39,10 @@ Feature: Project Active Tab # Sub Tabs: Settings - Scenario: On Project Settings/Hooks + Scenario: On Project Settings/Integrations Given I visit my project's settings page - And I click the "Hooks" tab - Then the active sub nav should be Hooks + And I click the "Integrations" tab + Then the active sub nav should be Integrations And no other sub navs should be active And the active main tab should be Settings diff --git a/features/steps/project/active_tab.rb b/features/steps/project/active_tab.rb index 58225032859..9f701840f1d 100644 --- a/features/steps/project/active_tab.rb +++ b/features/steps/project/active_tab.rb @@ -27,8 +27,8 @@ class Spinach::Features::ProjectActiveTab < Spinach::FeatureSteps end end - step 'I click the "Hooks" tab' do - click_link('Webhooks') + step 'I click the "Integrations" tab' do + click_link('Integrations') end step 'I click the "Deploy Keys" tab' do @@ -39,8 +39,8 @@ class Spinach::Features::ProjectActiveTab < Spinach::FeatureSteps ensure_active_sub_nav('Members') end - step 'the active sub nav should be Hooks' do - ensure_active_sub_nav('Webhooks') + step 'the active sub nav should be Integrations' do + ensure_active_sub_nav('Integrations') end step 'the active sub nav should be Deploy Keys' do diff --git a/features/steps/project/hooks.rb b/features/steps/project/hooks.rb index 13c0713669a..37b608ffbd3 100644 --- a/features/steps/project/hooks.rb +++ b/features/steps/project/hooks.rb @@ -36,12 +36,12 @@ class Spinach::Features::ProjectHooks < Spinach::FeatureSteps end step 'I should see newly created hook' do - expect(current_path).to eq namespace_project_hooks_path(current_project.namespace, current_project) + expect(current_path).to eq namespace_project_settings_integrations_path(current_project.namespace, current_project) expect(page).to have_content(@url) end step 'I should see newly created hook with SSL verification enabled' do - expect(current_path).to eq namespace_project_hooks_path(current_project.namespace, current_project) + expect(current_path).to eq namespace_project_settings_integrations_path(current_project.namespace, current_project) expect(page).to have_content(@url) expect(page).to have_content("SSL Verification: enabled") end @@ -57,7 +57,7 @@ class Spinach::Features::ProjectHooks < Spinach::FeatureSteps end step 'hook should be triggered' do - expect(current_path).to eq namespace_project_hooks_path(current_project.namespace, current_project) + expect(current_path).to eq namespace_project_settings_integrations_path(current_project.namespace, current_project) expect(page).to have_selector '.flash-notice', text: 'Hook executed successfully: HTTP 200' end diff --git a/features/steps/project/services.rb b/features/steps/project/services.rb index a4d29770922..772b07d0ad8 100644 --- a/features/steps/project/services.rb +++ b/features/steps/project/services.rb @@ -4,7 +4,7 @@ class Spinach::Features::ProjectServices < Spinach::FeatureSteps include SharedPaths step 'I visit project "Shop" services page' do - visit namespace_project_services_path(@project.namespace, @project) + visit namespace_project_settings_integrations_path(@project.namespace, @project) end step 'I should see list of available services' do diff --git a/features/steps/shared/paths.rb b/features/steps/shared/paths.rb index 670e6ca49a3..718cf924729 100644 --- a/features/steps/shared/paths.rb +++ b/features/steps/shared/paths.rb @@ -256,7 +256,7 @@ module SharedPaths end step 'I visit project hooks page' do - visit namespace_project_hooks_path(@project.namespace, @project) + visit namespace_project_settings_integrations_path(@project.namespace, @project) end step 'I visit project deploy keys page' do diff --git a/lib/api/helpers.rb b/lib/api/helpers.rb index 6b81fbf294e..49c5f0652ab 100644 --- a/lib/api/helpers.rb +++ b/lib/api/helpers.rb @@ -226,7 +226,7 @@ module API end def render_api_error!(message, status) - error!({ 'message' => message }, status) + error!({ 'message' => message }, status, header) end def handle_api_exception(exception) diff --git a/lib/ci/api/builds.rb b/lib/ci/api/builds.rb index 142bce82286..c4bdef781f7 100644 --- a/lib/ci/api/builds.rb +++ b/lib/ci/api/builds.rb @@ -16,6 +16,13 @@ module Ci not_found! unless current_runner.active? update_runner_info + if current_runner.is_runner_queue_value_latest?(params[:last_update]) + header 'X-GitLab-Last-Update', params[:last_update] + return build_not_found! + end + + new_update = current_runner.ensure_runner_queue_value + build = Ci::RegisterBuildService.new.execute(current_runner) if build @@ -26,6 +33,8 @@ module Ci else Gitlab::Metrics.add_event(:build_not_found) + header 'X-GitLab-Last-Update', new_update + build_not_found! end end diff --git a/lib/gitlab/current_settings.rb b/lib/gitlab/current_settings.rb index 2ff27e46d64..c79e17b57ee 100644 --- a/lib/gitlab/current_settings.rb +++ b/lib/gitlab/current_settings.rb @@ -9,7 +9,9 @@ module Gitlab end def ensure_application_settings! - if connect_to_db? + return fake_application_settings unless connect_to_db? + + unless ENV['IN_MEMORY_APPLICATION_SETTINGS'] == 'true' begin settings = ::ApplicationSetting.current # In case Redis isn't running or the Redis UNIX socket file is not available @@ -20,43 +22,23 @@ module Gitlab settings ||= ::ApplicationSetting.create_from_defaults unless ActiveRecord::Migrator.needs_migration? end - settings || fake_application_settings + settings || in_memory_application_settings end def sidekiq_throttling_enabled? current_application_settings.sidekiq_throttling_enabled? end + def in_memory_application_settings + @in_memory_application_settings ||= ApplicationSetting.new(ApplicationSetting::DEFAULTS) + # In case migrations the application_settings table is not created yet, + # we fallback to a simple OpenStruct + rescue ActiveRecord::StatementInvalid + fake_application_settings + end + def fake_application_settings - OpenStruct.new( - default_projects_limit: Settings.gitlab['default_projects_limit'], - default_branch_protection: Settings.gitlab['default_branch_protection'], - signup_enabled: Settings.gitlab['signup_enabled'], - signin_enabled: Settings.gitlab['signin_enabled'], - gravatar_enabled: Settings.gravatar['enabled'], - koding_enabled: false, - plantuml_enabled: false, - sign_in_text: nil, - after_sign_up_text: nil, - help_page_text: nil, - shared_runners_text: nil, - restricted_visibility_levels: Settings.gitlab['restricted_visibility_levels'], - max_attachment_size: Settings.gitlab['max_attachment_size'], - session_expire_delay: Settings.gitlab['session_expire_delay'], - default_project_visibility: Settings.gitlab.default_projects_features['visibility_level'], - default_snippet_visibility: Settings.gitlab.default_projects_features['visibility_level'], - domain_whitelist: Settings.gitlab['domain_whitelist'], - import_sources: %w[gitea github bitbucket gitlab google_code fogbugz git gitlab_project], - shared_runners_enabled: Settings.gitlab_ci['shared_runners_enabled'], - max_artifacts_size: Settings.artifacts['max_size'], - require_two_factor_authentication: false, - two_factor_grace_period: 48, - akismet_enabled: false, - repository_checks_enabled: true, - container_registry_token_expire_delay: 5, - user_default_external: false, - sidekiq_throttling_enabled: false, - ) + OpenStruct.new(ApplicationSetting::DEFAULTS) end private diff --git a/lib/gitlab/github_import/project_creator.rb b/lib/gitlab/github_import/project_creator.rb index 3f635be22ba..a55adc9b1c8 100644 --- a/lib/gitlab/github_import/project_creator.rb +++ b/lib/gitlab/github_import/project_creator.rb @@ -1,6 +1,8 @@ module Gitlab module GithubImport class ProjectCreator + include Gitlab::CurrentSettings + attr_reader :repo, :name, :namespace, :current_user, :session_data, :type def initialize(repo, name, namespace, current_user, session_data, type: 'github') @@ -34,7 +36,7 @@ module Gitlab end def visibility_level - repo.private ? Gitlab::VisibilityLevel::PRIVATE : ApplicationSetting.current.default_project_visibility + repo.private ? Gitlab::VisibilityLevel::PRIVATE : current_application_settings.default_project_visibility end # diff --git a/lib/gitlab/import_export/members_mapper.rb b/lib/gitlab/import_export/members_mapper.rb index b790733f4a7..2405b94db50 100644 --- a/lib/gitlab/import_export/members_mapper.rb +++ b/lib/gitlab/import_export/members_mapper.rb @@ -1,13 +1,10 @@ module Gitlab module ImportExport class MembersMapper - attr_reader :missing_author_ids - def initialize(exported_members:, user:, project:) - @exported_members = exported_members + @exported_members = user.admin? ? exported_members : [] @user = user @project = project - @missing_author_ids = [] # This needs to run first, as second call would be from #map # which means project members already exist. @@ -39,7 +36,6 @@ module Gitlab def missing_keys_tracking_hash Hash.new do |_, key| - @missing_author_ids << key default_user_id end end @@ -64,7 +60,7 @@ module Gitlab end def find_project_user_query(member) - user_arel[:username].eq(member['user']['username']).or(user_arel[:email].eq(member['user']['email'])) + user_arel[:email].eq(member['user']['email']).or(user_arel[:username].eq(member['user']['username'])) end def user_arel diff --git a/lib/gitlab/import_export/relation_factory.rb b/lib/gitlab/import_export/relation_factory.rb index 7a649f28340..19e43cce768 100644 --- a/lib/gitlab/import_export/relation_factory.rb +++ b/lib/gitlab/import_export/relation_factory.rb @@ -14,7 +14,7 @@ module Gitlab priorities: :label_priorities, label: :project_label }.freeze - USER_REFERENCES = %w[author_id assignee_id updated_by_id user_id created_by_id merge_user_id].freeze + USER_REFERENCES = %w[author_id assignee_id updated_by_id user_id created_by_id merge_user_id resolved_by_id].freeze PROJECT_REFERENCES = %w[project_id source_project_id gl_project_id target_project_id].freeze @@ -80,17 +80,13 @@ module Gitlab # is left. def set_note_author old_author_id = @relation_hash['author_id'] - - # Users with admin access can map users - @relation_hash['author_id'] = admin_user? ? @members_mapper.map[old_author_id] : @members_mapper.default_user_id - author = @relation_hash.delete('author') - update_note_for_missing_author(author['name']) if missing_author?(old_author_id) + update_note_for_missing_author(author['name']) unless has_author?(old_author_id) end - def missing_author?(old_author_id) - !admin_user? || @members_mapper.missing_author_ids.include?(old_author_id) + def has_author?(old_author_id) + admin_user? && @members_mapper.map.keys.include?(old_author_id) end def missing_author_note(updated_at, author_name) diff --git a/spec/controllers/health_check_controller_spec.rb b/spec/controllers/health_check_controller_spec.rb index 56ecf2bb644..cfe18dd4b6c 100644 --- a/spec/controllers/health_check_controller_spec.rb +++ b/spec/controllers/health_check_controller_spec.rb @@ -1,10 +1,16 @@ require 'spec_helper' describe HealthCheckController do + include StubENV + let(:token) { current_application_settings.health_check_access_token } let(:json_response) { JSON.parse(response.body) } let(:xml_response) { Hash.from_xml(response.body)['hash'] } + before do + stub_env('IN_MEMORY_APPLICATION_SETTINGS', 'false') + end + describe 'GET #index' do context 'when services are up but NO access token' do it 'returns a not found page' do diff --git a/spec/controllers/projects/settings/integrations_controller_spec.rb b/spec/controllers/projects/settings/integrations_controller_spec.rb new file mode 100644 index 00000000000..e0f9a5b24a6 --- /dev/null +++ b/spec/controllers/projects/settings/integrations_controller_spec.rb @@ -0,0 +1,20 @@ +require 'spec_helper' + +describe Projects::Settings::IntegrationsController do + let(:project) { create(:project, :public) } + let(:user) { create(:user) } + + before do + project.team << [user, :master] + sign_in(user) + end + + describe 'GET show' do + it 'renders show with 200 status code' do + get :show, namespace_id: project.namespace, project_id: project + + expect(response).to have_http_status(200) + expect(response).to render_template(:show) + end + end +end diff --git a/spec/features/admin/admin_disables_git_access_protocol_spec.rb b/spec/features/admin/admin_disables_git_access_protocol_spec.rb index 66044b44495..e8e080ce3e2 100644 --- a/spec/features/admin/admin_disables_git_access_protocol_spec.rb +++ b/spec/features/admin/admin_disables_git_access_protocol_spec.rb @@ -1,10 +1,13 @@ require 'rails_helper' feature 'Admin disables Git access protocol', feature: true do + include StubENV + let(:project) { create(:empty_project, :empty_repo) } let(:admin) { create(:admin) } background do + stub_env('IN_MEMORY_APPLICATION_SETTINGS', 'false') login_as(admin) end diff --git a/spec/features/admin/admin_health_check_spec.rb b/spec/features/admin/admin_health_check_spec.rb index dec2dedf2b5..f7e49a56deb 100644 --- a/spec/features/admin/admin_health_check_spec.rb +++ b/spec/features/admin/admin_health_check_spec.rb @@ -1,9 +1,11 @@ require 'spec_helper' feature "Admin Health Check", feature: true do + include StubENV include WaitForAjax before do + stub_env('IN_MEMORY_APPLICATION_SETTINGS', 'false') login_as :admin end @@ -12,11 +14,12 @@ feature "Admin Health Check", feature: true do visit admin_health_check_path end - it { page.has_text? 'Health Check' } - it { page.has_text? 'Health information can be retrieved' } - it 'has a health check access token' do + page.has_text? 'Health Check' + page.has_text? 'Health information can be retrieved' + token = current_application_settings.health_check_access_token + expect(page).to have_content("Access token is #{token}") expect(page).to have_selector('#health-check-token', text: token) end diff --git a/spec/features/admin/admin_runners_spec.rb b/spec/features/admin/admin_runners_spec.rb index d92c66b689d..f05fbe3d062 100644 --- a/spec/features/admin/admin_runners_spec.rb +++ b/spec/features/admin/admin_runners_spec.rb @@ -1,7 +1,10 @@ require 'spec_helper' describe "Admin Runners" do + include StubENV + before do + stub_env('IN_MEMORY_APPLICATION_SETTINGS', 'false') login_as :admin end diff --git a/spec/features/admin/admin_settings_spec.rb b/spec/features/admin/admin_settings_spec.rb index 47fa2f14307..de42ab81fac 100644 --- a/spec/features/admin/admin_settings_spec.rb +++ b/spec/features/admin/admin_settings_spec.rb @@ -1,7 +1,10 @@ require 'spec_helper' feature 'Admin updates settings', feature: true do - before(:each) do + include StubENV + + before do + stub_env('IN_MEMORY_APPLICATION_SETTINGS', 'false') login_as :admin visit admin_application_settings_path end diff --git a/spec/features/admin/admin_uses_repository_checks_spec.rb b/spec/features/admin/admin_uses_repository_checks_spec.rb index 661fb761809..855247de2ea 100644 --- a/spec/features/admin/admin_uses_repository_checks_spec.rb +++ b/spec/features/admin/admin_uses_repository_checks_spec.rb @@ -1,7 +1,12 @@ require 'rails_helper' feature 'Admin uses repository checks', feature: true do - before { login_as :admin } + include StubENV + + before do + stub_env('IN_MEMORY_APPLICATION_SETTINGS', 'false') + login_as :admin + end scenario 'to trigger a single check' do project = create(:empty_project) @@ -29,7 +34,7 @@ feature 'Admin uses repository checks', feature: true do scenario 'to clear all repository checks', js: true do visit admin_application_settings_path - + expect(RepositoryCheck::ClearWorker).to receive(:perform_async) click_link 'Clear all repository checks' diff --git a/spec/features/merge_requests/cherry_pick_spec.rb b/spec/features/merge_requests/cherry_pick_spec.rb index 82bc5226d07..dfe7c910a10 100644 --- a/spec/features/merge_requests/cherry_pick_spec.rb +++ b/spec/features/merge_requests/cherry_pick_spec.rb @@ -2,7 +2,8 @@ require 'spec_helper' describe 'Cherry-pick Merge Requests' do let(:user) { create(:user) } - let(:project) { create(:project) } + let(:group) { create(:group) } + let(:project) { create(:project, namespace: group) } let(:merge_request) { create(:merge_request_with_diffs, source_project: project, author: user) } before do diff --git a/spec/features/projects/commits/cherry_pick_spec.rb b/spec/features/projects/commits/cherry_pick_spec.rb index d46d9e9399e..7baf7913424 100644 --- a/spec/features/projects/commits/cherry_pick_spec.rb +++ b/spec/features/projects/commits/cherry_pick_spec.rb @@ -2,7 +2,8 @@ require 'spec_helper' include WaitForAjax describe 'Cherry-pick Commits' do - let(:project) { create(:project) } + let(:group) { create(:group) } + let(:project) { create(:project, namespace: group) } let(:master_pickable_commit) { project.commit('7d3b0f7cff5f37573aea97cebfd5692ea1689924') } let(:master_pickable_merge) { project.commit('e56497bb5f03a90a51293fc6d516788730953899') } diff --git a/spec/features/security/project/internal_access_spec.rb b/spec/features/security/project/internal_access_spec.rb index ecebabefff8..92d5a2fbc48 100644 --- a/spec/features/security/project/internal_access_spec.rb +++ b/spec/features/security/project/internal_access_spec.rb @@ -262,8 +262,8 @@ describe "Internal Project Access", feature: true do it { is_expected.to be_denied_for(:visitor) } end - describe "GET /:project_path/hooks" do - subject { namespace_project_hooks_path(project.namespace, project) } + describe "GET /:project_path/settings/integrations" do + subject { namespace_project_settings_integrations_path(project.namespace, project) } it { is_expected.to be_allowed_for(:admin) } it { is_expected.to be_allowed_for(:owner).of(project) } diff --git a/spec/features/security/project/private_access_spec.rb b/spec/features/security/project/private_access_spec.rb index 9bc59a7c4f9..b616e488487 100644 --- a/spec/features/security/project/private_access_spec.rb +++ b/spec/features/security/project/private_access_spec.rb @@ -234,8 +234,8 @@ describe "Private Project Access", feature: true do it { is_expected.to be_denied_for(:visitor) } end - describe "GET /:project_path/hooks" do - subject { namespace_project_hooks_path(project.namespace, project) } + describe "GET /:project_path/namespace/hooks" do + subject { namespace_project_settings_integrations_path(project.namespace, project) } it { is_expected.to be_allowed_for(:admin) } it { is_expected.to be_allowed_for(:owner).of(project) } diff --git a/spec/features/security/project/public_access_spec.rb b/spec/features/security/project/public_access_spec.rb index a8d43b3d581..ded85e837f4 100644 --- a/spec/features/security/project/public_access_spec.rb +++ b/spec/features/security/project/public_access_spec.rb @@ -400,8 +400,8 @@ describe "Public Project Access", feature: true do it { is_expected.to be_allowed_for(:visitor) } end - describe "GET /:project_path/hooks" do - subject { namespace_project_hooks_path(project.namespace, project) } + describe "GET /:project_path/settings/integrations" do + subject { namespace_project_settings_integrations_path(project.namespace, project) } it { is_expected.to be_allowed_for(:admin) } it { is_expected.to be_allowed_for(:owner).of(project) } diff --git a/spec/features/todos/todos_spec.rb b/spec/features/todos/todos_spec.rb index 4bda0927692..3850e930b6d 100644 --- a/spec/features/todos/todos_spec.rb +++ b/spec/features/todos/todos_spec.rb @@ -165,7 +165,7 @@ describe 'Dashboard Todos', feature: true do end it 'shows the todo' do - expect(page).to have_content 'The build failed for your merge request' + expect(page).to have_content 'The build failed for merge request' end it 'links to the pipelines for the merge request' do diff --git a/spec/finders/move_to_project_finder_spec.rb b/spec/finders/move_to_project_finder_spec.rb index fdce4e714ff..8488dbd2a16 100644 --- a/spec/finders/move_to_project_finder_spec.rb +++ b/spec/finders/move_to_project_finder_spec.rb @@ -2,13 +2,13 @@ require 'spec_helper' describe MoveToProjectFinder do let(:user) { create(:user) } - let(:project) { create(:project) } + let(:project) { create(:empty_project) } - let(:no_access_project) { create(:project) } - let(:guest_project) { create(:project) } - let(:reporter_project) { create(:project) } - let(:developer_project) { create(:project) } - let(:master_project) { create(:project) } + let(:no_access_project) { create(:empty_project) } + let(:guest_project) { create(:empty_project) } + let(:reporter_project) { create(:empty_project) } + let(:developer_project) { create(:empty_project) } + let(:master_project) { create(:empty_project) } subject { described_class.new(user) } @@ -37,7 +37,7 @@ describe MoveToProjectFinder do it 'does not return archived projects' do reporter_project.team << [user, :reporter] reporter_project.update_attributes(archived: true) - other_reporter_project = create(:project) + other_reporter_project = create(:empty_project) other_reporter_project.team << [user, :reporter] expect(subject.execute(project).to_a).to eq([other_reporter_project]) @@ -46,7 +46,7 @@ describe MoveToProjectFinder do it 'does not return projects for which issues are disabled' do reporter_project.team << [user, :reporter] reporter_project.update_attributes(issues_enabled: false) - other_reporter_project = create(:project) + other_reporter_project = create(:empty_project) other_reporter_project.team << [user, :reporter] expect(subject.execute(project).to_a).to eq([other_reporter_project]) @@ -83,10 +83,10 @@ describe MoveToProjectFinder do end it 'returns projects matching a search query' do - foo_project = create(:project) + foo_project = create(:empty_project) foo_project.team << [user, :master] - wadus_project = create(:project, name: 'wadus') + wadus_project = create(:empty_project, name: 'wadus') wadus_project.team << [user, :master] expect(subject.execute(project).to_a).to eq([wadus_project, foo_project]) diff --git a/spec/javascripts/environments/environment_spec.js.es6 b/spec/javascripts/environments/environment_spec.js.es6 new file mode 100644 index 00000000000..20e11ca3738 --- /dev/null +++ b/spec/javascripts/environments/environment_spec.js.es6 @@ -0,0 +1,127 @@ +/* global Vue, environment */ + +//= require vue +//= require vue-resource +//= require flash +//= require environments/stores/environments_store +//= require environments/components/environment +//= require ./mock_data + +describe('Environment', () => { + preloadFixtures('environments/environments'); + + let component; + + beforeEach(() => { + loadFixtures('environments/environments'); + }); + + describe('successfull request', () => { + describe('without environments', () => { + const environmentsEmptyResponseInterceptor = (request, next) => { + next(request.respondWith(JSON.stringify([]), { + status: 200, + })); + }; + + beforeEach(() => { + Vue.http.interceptors.push(environmentsEmptyResponseInterceptor); + }); + + afterEach(() => { + Vue.http.interceptors = _.without( + Vue.http.interceptors, environmentsEmptyResponseInterceptor, + ); + }); + + it('should render the empty state', (done) => { + component = new gl.environmentsList.EnvironmentsComponent({ + el: document.querySelector('#environments-list-view'), + propsData: { + store: gl.environmentsList.EnvironmentsStore.create(), + }, + }); + + setTimeout(() => { + expect( + component.$el.querySelector('.js-new-environment-button').textContent, + ).toContain('New Environment'); + + expect( + component.$el.querySelector('.js-blank-state-title').textContent, + ).toContain('You don\'t have any environments right now.'); + + done(); + }, 0); + }); + }); + + describe('with environments', () => { + const environmentsResponseInterceptor = (request, next) => { + next(request.respondWith(JSON.stringify([environment]), { + status: 200, + })); + }; + + beforeEach(() => { + Vue.http.interceptors.push(environmentsResponseInterceptor); + }); + + afterEach(() => { + Vue.http.interceptors = _.without( + Vue.http.interceptors, environmentsResponseInterceptor, + ); + }); + + it('should render a table with environments', (done) => { + component = new gl.environmentsList.EnvironmentsComponent({ + el: document.querySelector('#environments-list-view'), + propsData: { + store: gl.environmentsList.EnvironmentsStore.create(), + }, + }); + + setTimeout(() => { + expect( + component.$el.querySelectorAll('table tbody tr').length, + ).toEqual(1); + done(); + }, 0); + }); + }); + }); + + describe('unsuccessfull request', () => { + const environmentsErrorResponseInterceptor = (request, next) => { + next(request.respondWith(JSON.stringify([]), { + status: 500, + })); + }; + + beforeEach(() => { + Vue.http.interceptors.push(environmentsErrorResponseInterceptor); + }); + + afterEach(() => { + Vue.http.interceptors = _.without( + Vue.http.interceptors, environmentsErrorResponseInterceptor, + ); + }); + + it('should render empty state', (done) => { + component = new gl.environmentsList.EnvironmentsComponent({ + el: document.querySelector('#environments-list-view'), + propsData: { + store: gl.environmentsList.EnvironmentsStore.create(), + }, + }); + + setTimeout(() => { + expect( + component.$el.querySelector('.js-blank-state-title').textContent, + ).toContain('You don\'t have any environments right now.'); + done(); + }, 0); + }); + }); +}); diff --git a/spec/javascripts/environments/mock_data.js.es6 b/spec/javascripts/environments/mock_data.js.es6 index 9e16bc3e6a5..8ecd01f9a83 100644 --- a/spec/javascripts/environments/mock_data.js.es6 +++ b/spec/javascripts/environments/mock_data.js.es6 @@ -133,3 +133,17 @@ const environmentsList = [ updated_at: '2016-11-07T11:11:16.525Z', }, ]; + +const environment = { + id: 4, + name: 'production', + state: 'available', + external_url: 'http://production.', + environment_type: null, + last_deployment: {}, + 'stoppable?': false, + environment_path: '/root/review-app/environments/4', + stop_path: '/root/review-app/environments/4/stop', + created_at: '2016-12-16T11:51:04.690Z', + updated_at: '2016-12-16T12:04:51.133Z', +}; diff --git a/spec/javascripts/fixtures/environments/environments.html.haml b/spec/javascripts/fixtures/environments/environments.html.haml index d89bc50c1f0..e6000fbb553 100644 --- a/spec/javascripts/fixtures/environments/environments.html.haml +++ b/spec/javascripts/fixtures/environments/environments.html.haml @@ -1,5 +1,5 @@ %div - #environments-list-view{ data: { environments_data: "https://gitlab.com/foo/environments", + #environments-list-view{ data: { environments_data: "foo/environments", "can-create-deployment" => "true", "can-read-environment" => "true", "can-create-environment" => "true", diff --git a/spec/javascripts/fixtures/mini_dropdown_graph.html.haml b/spec/javascripts/fixtures/mini_dropdown_graph.html.haml index e9bf7568e95..29370b974af 100644 --- a/spec/javascripts/fixtures/mini_dropdown_graph.html.haml +++ b/spec/javascripts/fixtures/mini_dropdown_graph.html.haml @@ -1,8 +1,9 @@ -%div.js-builds-dropdown-tests - %button.dropdown.js-builds-dropdown-button{'data-stage-endpoint' => 'foobar'} +%div.js-builds-dropdown-tests.dropdown.dropdown.js-mini-pipeline-graph + %button.js-builds-dropdown-button{'data-stage-endpoint' => 'foobar', data: { toggle: 'dropdown'} } Dropdown - %div.js-builds-dropdown-container - %div.js-builds-dropdown-list - %div.js-builds-dropdown-loading.builds-dropdown-loading.hidden + %ul.dropdown-menu.mini-pipeline-graph-dropdown-menu.js-builds-dropdown-container + .js-builds-dropdown-list.scrollable-menu + + .js-builds-dropdown-loading.builds-dropdown-loading.hidden %span.fa.fa-spinner.fa-spin diff --git a/spec/lib/gitlab/current_settings_spec.rb b/spec/lib/gitlab/current_settings_spec.rb index 004341ffd02..b01c4805a34 100644 --- a/spec/lib/gitlab/current_settings_spec.rb +++ b/spec/lib/gitlab/current_settings_spec.rb @@ -1,36 +1,64 @@ require 'spec_helper' describe Gitlab::CurrentSettings do + include StubENV + + before do + stub_env('IN_MEMORY_APPLICATION_SETTINGS', 'false') + end + describe '#current_application_settings' do - it 'attempts to use cached values first' do - allow_any_instance_of(Gitlab::CurrentSettings).to receive(:connect_to_db?).and_return(true) - expect(ApplicationSetting).to receive(:current).and_return(::ApplicationSetting.create_from_defaults) - expect(ApplicationSetting).not_to receive(:last) + context 'with DB available' do + before do + allow_any_instance_of(Gitlab::CurrentSettings).to receive(:connect_to_db?).and_return(true) + end - expect(current_application_settings).to be_a(ApplicationSetting) - end + it 'attempts to use cached values first' do + expect(ApplicationSetting).to receive(:current) + expect(ApplicationSetting).not_to receive(:last) + + expect(current_application_settings).to be_a(ApplicationSetting) + end - it 'does not attempt to connect to DB or Redis' do - allow_any_instance_of(Gitlab::CurrentSettings).to receive(:connect_to_db?).and_return(false) - expect(ApplicationSetting).not_to receive(:current) - expect(ApplicationSetting).not_to receive(:last) + it 'falls back to DB if Redis returns an empty value' do + expect(ApplicationSetting).to receive(:last).and_call_original - expect(current_application_settings).to eq fake_application_settings + expect(current_application_settings).to be_a(ApplicationSetting) + end + + it 'falls back to DB if Redis fails' do + expect(ApplicationSetting).to receive(:current).and_raise(::Redis::BaseError) + expect(ApplicationSetting).to receive(:last).and_call_original + + expect(current_application_settings).to be_a(ApplicationSetting) + end end - it 'falls back to DB if Redis returns an empty value' do - allow_any_instance_of(Gitlab::CurrentSettings).to receive(:connect_to_db?).and_return(true) - expect(ApplicationSetting).to receive(:last).and_call_original + context 'with DB unavailable' do + before do + allow_any_instance_of(Gitlab::CurrentSettings).to receive(:connect_to_db?).and_return(false) + end - expect(current_application_settings).to be_a(ApplicationSetting) + it 'returns an in-memory ApplicationSetting object' do + expect(ApplicationSetting).not_to receive(:current) + expect(ApplicationSetting).not_to receive(:last) + + expect(current_application_settings).to be_a(OpenStruct) + end end - it 'falls back to DB if Redis fails' do - allow_any_instance_of(Gitlab::CurrentSettings).to receive(:connect_to_db?).and_return(true) - expect(ApplicationSetting).to receive(:current).and_raise(::Redis::BaseError) - expect(ApplicationSetting).to receive(:last).and_call_original + context 'when ENV["IN_MEMORY_APPLICATION_SETTINGS"] is true' do + before do + stub_env('IN_MEMORY_APPLICATION_SETTINGS', 'true') + end + + it 'returns an in-memory ApplicationSetting object' do + expect(ApplicationSetting).not_to receive(:current) + expect(ApplicationSetting).not_to receive(:last) - expect(current_application_settings).to be_a(ApplicationSetting) + expect(current_application_settings).to be_a(ApplicationSetting) + expect(current_application_settings).not_to be_persisted + end end end end diff --git a/spec/lib/gitlab/import_export/members_mapper_spec.rb b/spec/lib/gitlab/import_export/members_mapper_spec.rb index 1cb02f8e318..b069696b5c7 100644 --- a/spec/lib/gitlab/import_export/members_mapper_spec.rb +++ b/spec/lib/gitlab/import_export/members_mapper_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' describe Gitlab::ImportExport::MembersMapper, services: true do describe 'map members' do - let(:user) { create(:user, authorized_projects_populated: true) } + let(:user) { create(:admin, authorized_projects_populated: true) } let(:project) { create(:project, :public, name: 'searchable_project') } let(:user2) { create(:user, authorized_projects_populated: true) } let(:exported_user_id) { 99 } @@ -24,7 +24,7 @@ describe Gitlab::ImportExport::MembersMapper, services: true do { "id" => exported_user_id, "email" => user2.email, - "username" => user2.username + "username" => 'test' } }, { @@ -48,6 +48,10 @@ describe Gitlab::ImportExport::MembersMapper, services: true do exported_members: exported_members, user: user, project: project) end + it 'includes the exported user ID in the map' do + expect(members_mapper.map.keys).to include(exported_user_id) + end + it 'maps a project member' do expect(members_mapper.map[exported_user_id]).to eq(user2.id) end @@ -56,12 +60,6 @@ describe Gitlab::ImportExport::MembersMapper, services: true do expect(members_mapper.map[-1]).to eq(user.id) end - it 'updates missing author IDs on missing project member' do - members_mapper.map[-1] - - expect(members_mapper.missing_author_ids.first).to eq(-1) - end - it 'has invited members with no user' do members_mapper.map @@ -74,5 +72,25 @@ describe Gitlab::ImportExport::MembersMapper, services: true do expect(user.authorized_project?(project)).to be true expect(user2.authorized_project?(project)).to be true end + + context 'user is not an admin' do + let(:user) { create(:user, authorized_projects_populated: true) } + + it 'does not map a project member' do + expect(members_mapper.map[exported_user_id]).to eq(user.id) + end + + it 'defaults to importer project member if it does not exist' do + expect(members_mapper.map[-1]).to eq(user.id) + end + end + + context 'chooses the one with an email first' do + let(:user3) { create(:user, username: 'test') } + + it 'maps the project member that has a matching email first' do + expect(members_mapper.map[exported_user_id]).to eq(user2.id) + end + end end end diff --git a/spec/lib/gitlab/import_export/relation_factory_spec.rb b/spec/lib/gitlab/import_export/relation_factory_spec.rb index 3aa492a8ab1..db0084d6823 100644 --- a/spec/lib/gitlab/import_export/relation_factory_spec.rb +++ b/spec/lib/gitlab/import_export/relation_factory_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' describe Gitlab::ImportExport::RelationFactory, lib: true do let(:project) { create(:empty_project) } let(:members_mapper) { double('members_mapper').as_null_object } - let(:user) { create(:user) } + let(:user) { create(:admin) } let(:created_object) do described_class.create(relation_sym: relation_sym, relation_hash: relation_hash, @@ -122,4 +122,60 @@ describe Gitlab::ImportExport::RelationFactory, lib: true do expect(created_object.values).not_to include(99) end end + + context 'Notes user references' do + let(:relation_sym) { :notes } + let(:new_user) { create(:user) } + let(:exported_member) do + { + "id" => 111, + "access_level" => 30, + "source_id" => 1, + "source_type" => "Project", + "user_id" => 3, + "notification_level" => 3, + "created_at" => "2016-11-18T09:29:42.634Z", + "updated_at" => "2016-11-18T09:29:42.634Z", + "user" => { + "id" => 999, + "email" => new_user.email, + "username" => new_user.username + } + } + end + + let(:relation_hash) do + { + "id" => 4947, + "note" => "merged", + "noteable_type" => "MergeRequest", + "author_id" => 999, + "created_at" => "2016-11-18T09:29:42.634Z", + "updated_at" => "2016-11-18T09:29:42.634Z", + "project_id" => 1, + "attachment" => { + "url" => nil + }, + "noteable_id" => 377, + "system" => true, + "author" => { + "name" => "Administrator" + }, + "events" => [ + + ] + } + end + + let(:members_mapper) do + Gitlab::ImportExport::MembersMapper.new( + exported_members: [exported_member], + user: user, + project: project) + end + + it 'maps the right author to the imported note' do + expect(created_object.author).to eq(new_user) + end + end end diff --git a/spec/models/ci/build_spec.rb b/spec/models/ci/build_spec.rb index 3309a7fff9f..f031876e812 100644 --- a/spec/models/ci/build_spec.rb +++ b/spec/models/ci/build_spec.rb @@ -1401,4 +1401,14 @@ describe Ci::Build, :models do it { is_expected.to eq(%w[predefined project pipeline yaml secret]) } end end + + describe 'State transition: any => [:pending]' do + let(:build) { create(:ci_build, :created) } + + it 'queues BuildQueueWorker' do + expect(BuildQueueWorker).to receive(:perform_async).with(build.id) + + build.enqueue + end + end end diff --git a/spec/models/ci/runner_spec.rb b/spec/models/ci/runner_spec.rb index ef65eb99328..2b856ca7af7 100644 --- a/spec/models/ci/runner_spec.rb +++ b/spec/models/ci/runner_spec.rb @@ -263,6 +263,62 @@ describe Ci::Runner, models: true do end end + describe '#tick_runner_queue' do + let(:runner) { create(:ci_runner) } + + it 'returns a new last_update value' do + expect(runner.tick_runner_queue).not_to be_empty + end + end + + describe '#ensure_runner_queue_value' do + let(:runner) { create(:ci_runner) } + + it 'sets a new last_update value when it is called the first time' do + last_update = runner.ensure_runner_queue_value + + expect_value_in_redis.to eq(last_update) + end + + it 'does not change if it is not expired and called again' do + last_update = runner.ensure_runner_queue_value + + expect(runner.ensure_runner_queue_value).to eq(last_update) + expect_value_in_redis.to eq(last_update) + end + + context 'updates runner queue after changing editable value' do + let!(:last_update) { runner.ensure_runner_queue_value } + + before do + runner.update(description: 'new runner') + end + + it 'sets a new last_update value' do + expect_value_in_redis.not_to eq(last_update) + end + end + + context 'does not update runner value after save' do + let!(:last_update) { runner.ensure_runner_queue_value } + + before do + runner.touch + end + + it 'has an old last_update value' do + expect_value_in_redis.to eq(last_update) + end + end + + def expect_value_in_redis + Gitlab::Redis.with do |redis| + runner_queue_key = runner.send(:runner_queue_key) + expect(redis.get(runner_queue_key)) + end + end + end + describe '.assignable_for' do let(:runner) { create(:ci_runner) } let(:project) { create(:project) } diff --git a/spec/models/key_spec.rb b/spec/models/key_spec.rb index 5eaddd822be..7c40cfd8253 100644 --- a/spec/models/key_spec.rb +++ b/spec/models/key_spec.rb @@ -30,11 +30,30 @@ describe Key, models: true do end describe "#update_last_used_at" do - it "enqueues a UseKeyWorker job" do - key = create(:key) + let(:key) { create(:key) } + + context 'when key was not updated during the last day' do + before do + allow_any_instance_of(Gitlab::ExclusiveLease).to receive(:try_obtain). + and_return('000000') + end + + it 'enqueues a UseKeyWorker job' do + expect(UseKeyWorker).to receive(:perform_async).with(key.id) + key.update_last_used_at + end + end + + context 'when key was updated during the last day' do + before do + allow_any_instance_of(Gitlab::ExclusiveLease).to receive(:try_obtain). + and_return(false) + end - expect(UseKeyWorker).to receive(:perform_async).with(key.id) - key.update_last_used_at + it 'does not enqueue a UseKeyWorker job' do + expect(UseKeyWorker).not_to receive(:perform_async) + key.update_last_used_at + end end end end diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb index 38c80ba53ad..32ed1e96749 100644 --- a/spec/models/merge_request_spec.rb +++ b/spec/models/merge_request_spec.rb @@ -65,7 +65,7 @@ describe MergeRequest, models: true do end describe '#target_branch_sha' do - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } subject { create(:merge_request, source_project: project, target_project: project) } @@ -150,7 +150,7 @@ describe MergeRequest, models: true do end it 'supports a cross-project reference' do - another_project = build(:project, name: 'another-project', namespace: project.namespace) + another_project = build(:empty_project, name: 'another-project', namespace: project.namespace) expect(merge_request.to_reference(another_project)).to eq "sample-project!1" end @@ -245,8 +245,8 @@ describe MergeRequest, models: true do describe '#for_fork?' do it 'returns true if the merge request is for a fork' do - subject.source_project = create(:project, namespace: create(:group)) - subject.target_project = create(:project, namespace: create(:group)) + subject.source_project = build_stubbed(:empty_project, namespace: create(:group)) + subject.target_project = build_stubbed(:empty_project, namespace: create(:group)) expect(subject.for_fork?).to be_truthy end @@ -501,8 +501,8 @@ describe MergeRequest, models: true do end describe '#diverged_commits_count' do - let(:project) { create(:project) } - let(:fork_project) { create(:project, forked_from_project: project) } + let(:project) { create(:project, :repository) } + let(:fork_project) { create(:project, :repository, forked_from_project: project) } context 'when the target branch does not exist anymore' do subject { create(:merge_request, source_project: project, target_project: project) } @@ -727,7 +727,7 @@ describe MergeRequest, models: true do end describe '#participants' do - let(:project) { create(:project, :public) } + let(:project) { create(:empty_project, :public) } let(:mr) do create(:merge_request, source_project: project, target_project: project) @@ -768,7 +768,7 @@ describe MergeRequest, models: true do end describe '#check_if_can_be_merged' do - let(:project) { create(:project, only_allow_merge_if_build_succeeds: true) } + let(:project) { create(:empty_project, only_allow_merge_if_build_succeeds: true) } subject { create(:merge_request, source_project: project, merge_status: :unchecked) } @@ -789,7 +789,7 @@ describe MergeRequest, models: true do it 'becomes unmergeable' do expect { subject.check_if_can_be_merged }.to change { subject.merge_status }.to('cannot_be_merged') end - + it 'creates Todo on unmergeability' do expect_any_instance_of(TodoService).to receive(:merge_request_became_unmergeable).with(subject) @@ -810,7 +810,7 @@ describe MergeRequest, models: true do end describe '#mergeable?' do - let(:project) { create(:project) } + let(:project) { create(:empty_project) } subject { create(:merge_request, source_project: project) } @@ -830,7 +830,7 @@ describe MergeRequest, models: true do end describe '#mergeable_state?' do - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } subject { create(:merge_request, source_project: project) } @@ -957,7 +957,7 @@ describe MergeRequest, models: true do let(:merge_request) { create(:merge_request_with_diff_notes, source_project: project) } context 'when project.only_allow_merge_if_all_discussions_are_resolved == true' do - let(:project) { create(:project, only_allow_merge_if_all_discussions_are_resolved: true) } + let(:project) { create(:project, :repository, only_allow_merge_if_all_discussions_are_resolved: true) } context 'with all discussions resolved' do before do @@ -991,7 +991,7 @@ describe MergeRequest, models: true do end context 'when project.only_allow_merge_if_all_discussions_are_resolved == false' do - let(:project) { create(:project, only_allow_merge_if_all_discussions_are_resolved: false) } + let(:project) { create(:project, :repository, only_allow_merge_if_all_discussions_are_resolved: false) } context 'with unresolved discussions' do before do @@ -1006,7 +1006,7 @@ describe MergeRequest, models: true do end describe "#environments" do - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } let(:merge_request) { create(:merge_request, source_project: project) } context 'with multiple environments' do @@ -1024,7 +1024,7 @@ describe MergeRequest, models: true do context 'with environments on source project' do let(:source_project) do - create(:project) do |fork_project| + create(:project, :repository) do |fork_project| fork_project.create_forked_project_link(forked_to_project_id: fork_project.id, forked_from_project_id: project.id) end end @@ -1401,8 +1401,8 @@ describe MergeRequest, models: true do end describe "#source_project_missing?" do - let(:project) { create(:project) } - let(:fork_project) { create(:project, forked_from_project: project) } + let(:project) { create(:empty_project) } + let(:fork_project) { create(:empty_project, forked_from_project: project) } let(:user) { create(:user) } let(:unlink_project) { Projects::UnlinkForkService.new(fork_project, user) } @@ -1439,8 +1439,8 @@ describe MergeRequest, models: true do end describe "#closed_without_fork?" do - let(:project) { create(:project) } - let(:fork_project) { create(:project, forked_from_project: project) } + let(:project) { create(:empty_project) } + let(:fork_project) { create(:empty_project, forked_from_project: project) } let(:user) { create(:user) } let(:unlink_project) { Projects::UnlinkForkService.new(fork_project, user) } @@ -1485,9 +1485,9 @@ describe MergeRequest, models: true do end context 'forked project' do - let(:project) { create(:project) } + let(:project) { create(:empty_project) } let(:user) { create(:user) } - let(:fork_project) { create(:project, forked_from_project: project, namespace: user.namespace) } + let(:fork_project) { create(:empty_project, forked_from_project: project, namespace: user.namespace) } let!(:merge_request) do create(:closed_merge_request, @@ -1531,7 +1531,7 @@ describe MergeRequest, models: true do status: status) end - let(:project) { create(:project, :public, only_allow_merge_if_build_succeeds: true) } + let(:project) { create(:project, :public, :repository, only_allow_merge_if_build_succeeds: true) } let(:developer) { create(:user) } let(:user) { create(:user) } let(:merge_request) { create(:merge_request, source_project: project) } diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index e93a4e62244..8048e86fc3a 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -73,9 +73,7 @@ describe Project, models: true do context 'after initialized' do it "has a project_feature" do - project = FactoryGirl.build(:project) - - expect(project.project_feature.present?).to be_present + expect(Project.new.project_feature).to be_present end end @@ -129,7 +127,7 @@ describe Project, models: true do end describe 'validation' do - let!(:project) { create(:project) } + let!(:project) { create(:empty_project) } it { is_expected.to validate_presence_of(:name) } it { is_expected.to validate_uniqueness_of(:name).scoped_to(:namespace_id) } @@ -148,7 +146,7 @@ describe Project, models: true do it { is_expected.to validate_presence_of(:repository_storage) } it 'does not allow new projects beyond user limits' do - project2 = build(:project) + project2 = build(:empty_project) allow(project2).to receive(:creator).and_return(double(can_create_project?: false, projects_limit: 0).as_null_object) expect(project2).not_to be_valid expect(project2.errors[:limit_reached].first).to match(/Personal project creation is not allowed/) @@ -157,7 +155,7 @@ describe Project, models: true do describe 'wiki path conflict' do context "when the new path has been used by the wiki of other Project" do it 'has an error on the name attribute' do - new_project = build_stubbed(:project, namespace_id: project.namespace_id, path: "#{project.path}.wiki") + new_project = build_stubbed(:empty_project, namespace_id: project.namespace_id, path: "#{project.path}.wiki") expect(new_project).not_to be_valid expect(new_project.errors[:name].first).to eq('has already been taken') @@ -166,8 +164,8 @@ describe Project, models: true do context "when the new wiki path has been used by the path of other Project" do it 'has an error on the name attribute' do - project_with_wiki_suffix = create(:project, path: 'foo.wiki') - new_project = build_stubbed(:project, namespace_id: project_with_wiki_suffix.namespace_id, path: 'foo') + project_with_wiki_suffix = create(:empty_project, path: 'foo.wiki') + new_project = build_stubbed(:empty_project, namespace_id: project_with_wiki_suffix.namespace_id, path: 'foo') expect(new_project).not_to be_valid expect(new_project.errors[:name].first).to eq('has already been taken') @@ -176,7 +174,7 @@ describe Project, models: true do end context 'repository storages inclussion' do - let(:project2) { build(:project, repository_storage: 'missing') } + let(:project2) { build(:empty_project, repository_storage: 'missing') } before do storages = { 'custom' => 'tmp/tests/custom_repositories' } @@ -352,7 +350,7 @@ describe Project, models: true do end describe '#repository_storage_path' do - let(:project) { create(:project, repository_storage: 'custom') } + let(:project) { create(:empty_project, repository_storage: 'custom') } before do FileUtils.mkdir('tmp/tests/custom_repositories') @@ -412,7 +410,7 @@ describe Project, models: true do describe 'last_activity methods' do let(:timestamp) { 2.hours.ago } # last_activity_at gets set to created_at upon creation - let(:project) { create(:project, created_at: timestamp, updated_at: timestamp) } + let(:project) { create(:empty_project, created_at: timestamp, updated_at: timestamp) } describe 'last_activity' do it 'alias last_activity to last_event' do @@ -496,7 +494,7 @@ describe Project, models: true do context 'with namespace' do before do @group = create :group, name: 'gitlab' - @project = create(:project, name: 'gitlabhq', namespace: @group) + @project = create(:empty_project, name: 'gitlabhq', namespace: @group) end it { expect(@project.to_param).to eq('gitlabhq') } @@ -522,7 +520,7 @@ describe Project, models: true do end describe '#repository' do - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } it 'returns valid repo' do expect(project.repository).to be_kind_of(Repository) @@ -530,20 +528,22 @@ describe Project, models: true do end describe '#default_issues_tracker?' do - let(:project) { create(:project) } - let(:ext_project) { create(:redmine_project) } - it "is true if used internal tracker" do + project = build(:empty_project) + expect(project.default_issues_tracker?).to be_truthy end it "is false if used other tracker" do - expect(ext_project.default_issues_tracker?).to be_falsey + # NOTE: The current nature of this factory requires persistence + project = create(:redmine_project) + + expect(project.default_issues_tracker?).to be_falsey end end describe '#external_issue_tracker' do - let(:project) { create(:project) } + let(:project) { create(:empty_project) } let(:ext_project) { create(:redmine_project) } context 'on existing projects with no value for has_external_issue_tracker' do @@ -578,7 +578,7 @@ describe Project, models: true do end describe '#cache_has_external_issue_tracker' do - let(:project) { create(:project, has_external_issue_tracker: nil) } + let(:project) { create(:empty_project, has_external_issue_tracker: nil) } it 'stores true if there is any external_issue_tracker' do services = double(:service, external_issue_trackers: [RedmineService.new]) @@ -600,9 +600,9 @@ describe Project, models: true do end describe '#has_wiki?' do - let(:no_wiki_project) { create(:project, wiki_access_level: ProjectFeature::DISABLED, has_external_wiki: false) } - let(:wiki_enabled_project) { create(:project) } - let(:external_wiki_project) { create(:project, has_external_wiki: true) } + let(:no_wiki_project) { create(:empty_project, wiki_access_level: ProjectFeature::DISABLED, has_external_wiki: false) } + let(:wiki_enabled_project) { create(:empty_project) } + let(:external_wiki_project) { create(:empty_project, has_external_wiki: true) } it 'returns true if project is wiki enabled or has external wiki' do expect(wiki_enabled_project).to have_wiki @@ -612,7 +612,7 @@ describe Project, models: true do end describe '#external_wiki' do - let(:project) { create(:project) } + let(:project) { create(:empty_project) } context 'with an active external wiki' do before do @@ -663,7 +663,7 @@ describe Project, models: true do end describe '#open_branches' do - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } before do project.protected_branches.create(name: 'master') @@ -685,7 +685,7 @@ describe Project, models: true do it 'counts stars from multiple users' do user1 = create :user user2 = create :user - project = create :project, :public + project = create(:empty_project, :public) expect(project.star_count).to eq(0) @@ -707,8 +707,8 @@ describe Project, models: true do it 'counts stars on the right project' do user = create :user - project1 = create :project, :public - project2 = create :project, :public + project1 = create(:empty_project, :public) + project2 = create(:empty_project, :public) expect(project1.star_count).to eq(0) expect(project2.star_count).to eq(0) @@ -740,7 +740,7 @@ describe Project, models: true do end describe '#avatar_type' do - let(:project) { create(:project) } + let(:project) { create(:empty_project) } it 'is true if avatar is image' do project.update_attribute(:avatar, 'uploads/avatar.png') @@ -756,7 +756,7 @@ describe Project, models: true do describe '#avatar_url' do subject { project.avatar_url } - let(:project) { create(:project) } + let(:project) { create(:empty_project) } context 'When avatar file is uploaded' do before do @@ -791,7 +791,7 @@ describe Project, models: true do end describe '#pipeline_for' do - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } let!(:pipeline) { create_pipeline } shared_examples 'giving the correct pipeline' do @@ -825,7 +825,7 @@ describe Project, models: true do end describe '#builds_enabled' do - let(:project) { create :project } + let(:project) { create(:empty_project) } subject { project.builds_enabled } @@ -877,7 +877,7 @@ describe Project, models: true do end describe '.visible_to_user' do - let!(:project) { create(:project, :private) } + let!(:project) { create(:empty_project, :private) } let!(:user) { create(:user) } subject { described_class.visible_to_user(user) } @@ -975,7 +975,7 @@ describe Project, models: true do end describe '#visibility_level_allowed?' do - let(:project) { create(:project, :internal) } + let(:project) { create(:empty_project, :internal) } context 'when checking on non-forked project' do it { expect(project.visibility_level_allowed?(Gitlab::VisibilityLevel::PRIVATE)).to be_truthy } @@ -984,8 +984,8 @@ describe Project, models: true do end context 'when checking on forked project' do - let(:project) { create(:project, :internal) } - let(:forked_project) { create(:project, forked_from_project: project) } + let(:project) { create(:empty_project, :internal) } + let(:forked_project) { create(:empty_project, forked_from_project: project) } it { expect(forked_project.visibility_level_allowed?(Gitlab::VisibilityLevel::PRIVATE)).to be_truthy } it { expect(forked_project.visibility_level_allowed?(Gitlab::VisibilityLevel::INTERNAL)).to be_truthy } @@ -994,7 +994,7 @@ describe Project, models: true do end describe '.search' do - let(:project) { create(:project, description: 'kitten mittens') } + let(:project) { create(:empty_project, description: 'kitten mittens') } it 'returns projects with a matching name' do expect(described_class.search(project.name)).to eq([project]) @@ -1052,7 +1052,7 @@ describe Project, models: true do end describe '#rename_repo' do - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } let(:gitlab_shell) { Gitlab::Shell.new } before do @@ -1102,7 +1102,7 @@ describe Project, models: true do end describe '#expire_caches_before_rename' do - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } let(:repo) { double(:repo, exists?: true) } let(:wiki) { double(:wiki, exists?: true) } @@ -1123,7 +1123,7 @@ describe Project, models: true do end describe '.search_by_title' do - let(:project) { create(:project, name: 'kittens') } + let(:project) { create(:empty_project, name: 'kittens') } it 'returns projects with a matching name' do expect(described_class.search_by_title(project.name)).to eq([project]) @@ -1142,8 +1142,8 @@ describe Project, models: true do let(:private_group) { create(:group, visibility_level: 0) } let(:internal_group) { create(:group, visibility_level: 10) } - let(:private_project) { create :project, :private, group: private_group } - let(:internal_project) { create :project, :internal, group: internal_group } + let(:private_project) { create :empty_project, :private, group: private_group } + let(:internal_project) { create :empty_project, :internal, group: internal_group } context 'when group is private project can not be internal' do it { expect(private_project.visibility_level_allowed?(Gitlab::VisibilityLevel::INTERNAL)).to be_falsey } @@ -1155,7 +1155,7 @@ describe Project, models: true do end describe '#create_repository' do - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } let(:shell) { Gitlab::Shell.new } before do @@ -1197,7 +1197,7 @@ describe Project, models: true do describe '#protected_branch?' do context 'existing project' do - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } it 'returns true when the branch matches a protected branch via direct match' do create(:protected_branch, project: project, name: "foo") @@ -1381,7 +1381,7 @@ describe Project, models: true do name: name) end - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } let(:pipeline) { create_pipeline } context 'with many builds' do @@ -1461,7 +1461,7 @@ describe Project, models: true do end context 'not forked' do - let(:project) { create(:project) } + let(:project) { create(:empty_project) } it 'schedules a RepositoryImportWorker job' do expect(RepositoryImportWorker).to receive(:perform_async).with(project.id) @@ -1472,19 +1472,19 @@ describe Project, models: true do end describe '#gitlab_project_import?' do - subject(:project) { build(:project, import_type: 'gitlab_project') } + subject(:project) { build(:empty_project, import_type: 'gitlab_project') } it { expect(project.gitlab_project_import?).to be true } end describe '#gitea_import?' do - subject(:project) { build(:project, import_type: 'gitea') } + subject(:project) { build(:empty_project, import_type: 'gitea') } it { expect(project.gitea_import?).to be true } end describe '#lfs_enabled?' do - let(:project) { create(:project) } + let(:project) { create(:empty_project) } shared_examples 'project overrides group' do it 'returns true when enabled in project' do @@ -1546,7 +1546,7 @@ describe Project, models: true do end describe '#change_head' do - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } it 'calls the before_change_head and after_change_head methods' do expect(project.repository).to receive(:before_change_head) @@ -1574,7 +1574,7 @@ describe Project, models: true do end describe '#pushes_since_gc' do - let(:project) { create(:project) } + let(:project) { create(:empty_project) } after do project.reset_pushes_since_gc @@ -1596,7 +1596,7 @@ describe Project, models: true do end describe '#increment_pushes_since_gc' do - let(:project) { create(:project) } + let(:project) { create(:empty_project) } after do project.reset_pushes_since_gc @@ -1610,7 +1610,7 @@ describe Project, models: true do end describe '#reset_pushes_since_gc' do - let(:project) { create(:project) } + let(:project) { create(:empty_project) } after do project.reset_pushes_since_gc @@ -1626,7 +1626,7 @@ describe Project, models: true do end describe '#environments_for' do - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } let(:environment) { create(:environment, project: project) } context 'tagged deployment' do @@ -1678,7 +1678,7 @@ describe Project, models: true do end describe '#environments_recently_updated_on_branch' do - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } let(:environment) { create(:environment, project: project) } context 'when last deployment to environment is the most recent one' do diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index 8b20ee81614..ca3d4ff0aa9 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -48,7 +48,7 @@ describe User, models: true do describe '#project_members' do it 'does not include project memberships for which user is a requester' do user = create(:user) - project = create(:project, :public, :access_requestable) + project = create(:empty_project, :public, :access_requestable) project.request_access(user) expect(user.project_members).to be_empty @@ -386,13 +386,15 @@ describe User, models: true do describe 'projects' do before do - @user = create :user - @project = create :project, namespace: @user.namespace - @project_2 = create :project, group: create(:group) # Grant MASTER access to the user - @project_3 = create :project, group: create(:group) # Grant DEVELOPER access to the user + @user = create(:user) - @project_2.team << [@user, :master] - @project_3.team << [@user, :developer] + @project = create(:empty_project, namespace: @user.namespace) + @project_2 = create(:empty_project, group: create(:group)) do |project| + project.add_master(@user) + end + @project_3 = create(:empty_project, group: create(:group)) do |project| + project.add_developer(@user) + end end it { expect(@user.authorized_projects).to include(@project) } @@ -435,7 +437,7 @@ describe User, models: true do describe 'namespaced' do before do @user = create :user - @project = create :project, namespace: @user.namespace + @project = create(:empty_project, namespace: @user.namespace) end it { expect(@user.several_namespaces?).to be_falsey } @@ -517,7 +519,7 @@ describe User, models: true do before do User.delete_all @user = create :user - @project = create :project + @project = create(:empty_project) end it { expect(User.not_in_project(@project)).to include(@user, @project.owner) } @@ -927,8 +929,8 @@ describe User, models: true do describe "#starred?" do it "determines if user starred a project" do user = create :user - project1 = create :project, :public - project2 = create :project, :public + project1 = create(:empty_project, :public) + project2 = create(:empty_project, :public) expect(user.starred?(project1)).to be_falsey expect(user.starred?(project2)).to be_falsey @@ -954,7 +956,7 @@ describe User, models: true do describe "#toggle_star" do it "toggles stars" do user = create :user - project = create :project, :public + project = create(:empty_project, :public) expect(user.starred?(project)).to be_falsey user.toggle_star(project) @@ -994,9 +996,9 @@ describe User, models: true do describe "#contributed_projects" do subject { create(:user) } - let!(:project1) { create(:project) } - let!(:project2) { create(:project, forked_from_project: project3) } - let!(:project3) { create(:project) } + let!(:project1) { create(:empty_project) } + let!(:project2) { create(:empty_project, forked_from_project: project3) } + let!(:project3) { create(:empty_project) } let!(:merge_request) { create(:merge_request, source_project: project2, target_project: project3, author: subject) } let!(:push_event) { create(:event, action: Event::PUSHED, project: project1, target: project1, author: subject) } let!(:merge_event) { create(:event, action: Event::CREATED, project: project3, target: merge_request, author: subject) } @@ -1038,8 +1040,8 @@ describe User, models: true do describe "#recent_push" do subject { create(:user) } - let!(:project1) { create(:project) } - let!(:project2) { create(:project, forked_from_project: project1) } + let!(:project1) { create(:project, :repository) } + let!(:project2) { create(:project, :repository, forked_from_project: project1) } let!(:push_data) do Gitlab::DataBuilder::Push.build_sample(project2, subject) end @@ -1113,7 +1115,7 @@ describe User, models: true do it "includes user's personal projects" do user = create(:user) - project = create(:project, :private, namespace: user.namespace) + project = create(:empty_project, :private, namespace: user.namespace) expect(user.authorized_projects).to include(project) end @@ -1121,7 +1123,7 @@ describe User, models: true do it "includes personal projects user has been given access to" do user1 = create(:user) user2 = create(:user) - project = create(:project, :private, namespace: user1.namespace) + project = create(:empty_project, :private, namespace: user1.namespace) project.team << [user2, Gitlab::Access::DEVELOPER] @@ -1130,7 +1132,7 @@ describe User, models: true do it "includes projects of groups user has been added to" do group = create(:group) - project = create(:project, group: group) + project = create(:empty_project, group: group) user = create(:user) group.add_developer(user) @@ -1140,7 +1142,7 @@ describe User, models: true do it "does not include projects of groups user has been removed from" do group = create(:group) - project = create(:project, group: group) + project = create(:empty_project, group: group) user = create(:user) member = group.add_developer(user) @@ -1152,7 +1154,7 @@ describe User, models: true do it "includes projects shared with user's group" do user = create(:user) - project = create(:project, :private) + project = create(:empty_project, :private) group = create(:group) group.add_reporter(user) @@ -1164,7 +1166,7 @@ describe User, models: true do it "does not include destroyed projects user had access to" do user1 = create(:user) user2 = create(:user) - project = create(:project, :private, namespace: user1.namespace) + project = create(:empty_project, :private, namespace: user1.namespace) project.team << [user2, Gitlab::Access::DEVELOPER] expect(user2.authorized_projects).to include(project) @@ -1175,7 +1177,7 @@ describe User, models: true do it "does not include projects of destroyed groups user had access to" do group = create(:group) - project = create(:project, namespace: group) + project = create(:empty_project, namespace: group) user = create(:user) group.add_developer(user) @@ -1190,14 +1192,9 @@ describe User, models: true do let(:user) { create(:user) } it 'includes projects for which the user access level is above or equal to reporter' do - create(:project) - reporter_project = create(:project) - developer_project = create(:project) - master_project = create(:project) - - reporter_project.team << [user, :reporter] - developer_project.team << [user, :developer] - master_project.team << [user, :master] + reporter_project = create(:empty_project) { |p| p.add_reporter(user) } + developer_project = create(:empty_project) { |p| p.add_developer(user) } + master_project = create(:empty_project) { |p| p.add_master(user) } expect(user.projects_where_can_admin_issues.to_a).to eq([master_project, developer_project, reporter_project]) expect(user.can?(:admin_issue, master_project)).to eq(true) @@ -1206,10 +1203,8 @@ describe User, models: true do end it 'does not include for which the user access level is below reporter' do - project = create(:project) - guest_project = create(:project) - - guest_project.team << [user, :guest] + project = create(:empty_project) + guest_project = create(:empty_project) { |p| p.add_guest(user) } expect(user.projects_where_can_admin_issues.to_a).to be_empty expect(user.can?(:admin_issue, guest_project)).to eq(false) @@ -1217,15 +1212,14 @@ describe User, models: true do end it 'does not include archived projects' do - project = create(:project) - project.update_attributes(archived: true) + project = create(:empty_project, :archived) expect(user.projects_where_can_admin_issues.to_a).to be_empty expect(user.can?(:admin_issue, project)).to eq(false) end it 'does not include projects for which issues are disabled' do - project = create(:project, issues_access_level: ProjectFeature::DISABLED) + project = create(:empty_project, issues_access_level: ProjectFeature::DISABLED) expect(user.projects_where_can_admin_issues.to_a).to be_empty expect(user.can?(:admin_issue, project)).to eq(false) @@ -1241,7 +1235,7 @@ describe User, models: true do end context 'without any projects' do - let(:project) { create(:project) } + let(:project) { create(:empty_project) } it 'does not load' do expect(user.ci_authorized_runners).to be_empty @@ -1250,7 +1244,7 @@ describe User, models: true do context 'with personal projects runners' do let(:namespace) { create(:namespace, owner: user) } - let(:project) { create(:project, namespace: namespace) } + let(:project) { create(:empty_project, namespace: namespace) } it 'loads' do expect(user.ci_authorized_runners).to contain_exactly(runner) @@ -1281,7 +1275,7 @@ describe User, models: true do context 'with groups projects runners' do let(:group) { create(:group) } - let(:project) { create(:project, group: group) } + let(:project) { create(:empty_project, group: group) } def add_user(access) group.add_user(user, access) @@ -1291,7 +1285,7 @@ describe User, models: true do end context 'with other projects runners' do - let(:project) { create(:project) } + let(:project) { create(:empty_project) } def add_user(access) project.team << [user, access] @@ -1321,8 +1315,8 @@ describe User, models: true do end describe '#projects_with_reporter_access_limited_to' do - let(:project1) { create(:project) } - let(:project2) { create(:project) } + let(:project1) { create(:empty_project) } + let(:project2) { create(:empty_project) } let(:user) { create(:user) } before do diff --git a/spec/requests/api/access_requests_spec.rb b/spec/requests/api/access_requests_spec.rb index 1a771b3c87a..e487297748b 100644 --- a/spec/requests/api/access_requests_spec.rb +++ b/spec/requests/api/access_requests_spec.rb @@ -9,7 +9,7 @@ describe API::AccessRequests, api: true do let(:stranger) { create(:user) } let(:project) do - create(:project, :public, :access_requestable, creator_id: master.id, namespace: master.namespace) do |project| + create(:empty_project, :public, :access_requestable, creator_id: master.id, namespace: master.namespace) do |project| project.team << [developer, :developer] project.team << [master, :master] project.request_access(access_requester) diff --git a/spec/requests/api/boards_spec.rb b/spec/requests/api/boards_spec.rb index 3019724f52e..c14c3cb1ce7 100644 --- a/spec/requests/api/boards_spec.rb +++ b/spec/requests/api/boards_spec.rb @@ -8,7 +8,7 @@ describe API::Boards, api: true do let(:non_member) { create(:user) } let(:guest) { create(:user) } let(:admin) { create(:user, :admin) } - let!(:project) { create(:project, :public, creator_id: user.id, namespace: user.namespace ) } + let!(:project) { create(:empty_project, :public, creator_id: user.id, namespace: user.namespace ) } let!(:dev_label) do create(:label, title: 'Development', color: '#FFAABB', project: project) @@ -188,7 +188,7 @@ describe API::Boards, api: true do context "when the user is project owner" do let(:owner) { create(:user) } - let(:project) { create(:project, namespace: owner.namespace) } + let(:project) { create(:empty_project, namespace: owner.namespace) } it "deletes the list if an admin requests it" do delete api("#{base_url}/#{dev_list.id}", owner) diff --git a/spec/requests/api/deploy_keys_spec.rb b/spec/requests/api/deploy_keys_spec.rb index aabab8e6ae6..5c14db067a8 100644 --- a/spec/requests/api/deploy_keys_spec.rb +++ b/spec/requests/api/deploy_keys_spec.rb @@ -5,8 +5,8 @@ describe API::DeployKeys, api: true do let(:user) { create(:user) } let(:admin) { create(:admin) } - let(:project) { create(:project, creator_id: user.id) } - let(:project2) { create(:project, creator_id: user.id) } + let(:project) { create(:empty_project, creator_id: user.id) } + let(:project2) { create(:empty_project, creator_id: user.id) } let(:deploy_key) { create(:deploy_key, public: true) } let!(:deploy_keys_project) do diff --git a/spec/requests/api/environments_spec.rb b/spec/requests/api/environments_spec.rb index b9d535bc314..8168b613766 100644 --- a/spec/requests/api/environments_spec.rb +++ b/spec/requests/api/environments_spec.rb @@ -5,7 +5,7 @@ describe API::Environments, api: true do let(:user) { create(:user) } let(:non_member) { create(:user) } - let(:project) { create(:project, :private, namespace: user.namespace) } + let(:project) { create(:empty_project, :private, namespace: user.namespace) } let!(:environment) { create(:environment, project: project) } before do diff --git a/spec/requests/api/fork_spec.rb b/spec/requests/api/fork_spec.rb index e38d5745d44..df29099bc2f 100644 --- a/spec/requests/api/fork_spec.rb +++ b/spec/requests/api/fork_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe API::API, api: true do +describe API::Projects, api: true do include ApiHelpers let(:user) { create(:user) } let(:user2) { create(:user) } diff --git a/spec/requests/api/groups_spec.rb b/spec/requests/api/groups_spec.rb index e355d5e28bc..edbf0140583 100644 --- a/spec/requests/api/groups_spec.rb +++ b/spec/requests/api/groups_spec.rb @@ -10,9 +10,9 @@ describe API::Groups, api: true do let(:admin) { create(:admin) } let!(:group1) { create(:group, avatar: File.open(uploaded_image_temp_path)) } let!(:group2) { create(:group, :private) } - let!(:project1) { create(:project, namespace: group1) } - let!(:project2) { create(:project, namespace: group2) } - let!(:project3) { create(:project, namespace: group1, path: 'test', visibility_level: Gitlab::VisibilityLevel::PRIVATE) } + let!(:project1) { create(:empty_project, namespace: group1) } + let!(:project2) { create(:empty_project, namespace: group2) } + let!(:project3) { create(:empty_project, namespace: group1, path: 'test', visibility_level: Gitlab::VisibilityLevel::PRIVATE) } before do group1.add_owner(user1) @@ -163,7 +163,7 @@ describe API::Groups, api: true do describe "GET /groups/:id" do context "when authenticated as user" do it "returns one of user1's groups" do - project = create(:project, namespace: group2, path: 'Foo') + project = create(:empty_project, namespace: group2, path: 'Foo') create(:project_group_link, project: project, group: group1) get api("/groups/#{group1.id}", user1) @@ -287,7 +287,7 @@ describe API::Groups, api: true do expect(json_response.length).to eq(2) project_names = json_response.map { |proj| proj['name' ] } expect(project_names).to match_array([project1.name, project3.name]) - expect(json_response.first['default_branch']).to be_present + expect(json_response.first['visibility_level']).to be_present end it "returns the group's projects with simple representation" do @@ -297,11 +297,11 @@ describe API::Groups, api: true do expect(json_response.length).to eq(2) project_names = json_response.map { |proj| proj['name' ] } expect(project_names).to match_array([project1.name, project3.name]) - expect(json_response.first['default_branch']).not_to be_present + expect(json_response.first['visibility_level']).not_to be_present end it 'filters the groups projects' do - public_project = create(:project, :public, path: 'test1', group: group1) + public_project = create(:empty_project, :public, path: 'test1', group: group1) get api("/groups/#{group1.id}/projects", user1), visibility: 'public' @@ -462,7 +462,7 @@ describe API::Groups, api: true do end describe "POST /groups/:id/projects/:project_id" do - let(:project) { create(:project) } + let(:project) { create(:empty_project) } let(:project_path) { "#{project.namespace.path}%2F#{project.path}" } before(:each) do diff --git a/spec/requests/api/helpers_spec.rb b/spec/requests/api/helpers_spec.rb index b8ee2293a33..a89676fec93 100644 --- a/spec/requests/api/helpers_spec.rb +++ b/spec/requests/api/helpers_spec.rb @@ -12,6 +12,7 @@ describe API::Helpers, api: true do let(:params) { {} } let(:env) { { 'REQUEST_METHOD' => 'GET' } } let(:request) { Rack::Request.new(env) } + let(:header) { } def set_env(user_or_token, identifier) clear_env @@ -46,7 +47,7 @@ describe API::Helpers, api: true do allow_any_instance_of(self.class).to receive(:doorkeeper_guard){ value } end - def error!(message, status) + def error!(message, status, header) raise Exception.new("#{status} - #{message}") end diff --git a/spec/requests/api/internal_spec.rb b/spec/requests/api/internal_spec.rb index 35644bd8cc9..91202244227 100644 --- a/spec/requests/api/internal_spec.rb +++ b/spec/requests/api/internal_spec.rb @@ -239,7 +239,7 @@ describe API::Internal, api: true do end context "blocked user" do - let(:personal_project) { create(:project, namespace: user.namespace) } + let(:personal_project) { create(:empty_project, namespace: user.namespace) } before do user.block @@ -265,7 +265,7 @@ describe API::Internal, api: true do end context "archived project" do - let(:personal_project) { create(:project, namespace: user.namespace) } + let(:personal_project) { create(:empty_project, namespace: user.namespace) } before do project.team << [user, :developer] @@ -337,8 +337,7 @@ describe API::Internal, api: true do context 'ssh access has been disabled' do before do - settings = ::ApplicationSetting.create_from_defaults - settings.update_attribute(:enabled_git_access_protocol, 'http') + stub_application_setting(enabled_git_access_protocol: 'http') end it 'rejects the SSH push' do @@ -360,8 +359,7 @@ describe API::Internal, api: true do context 'http access has been disabled' do before do - settings = ::ApplicationSetting.create_from_defaults - settings.update_attribute(:enabled_git_access_protocol, 'ssh') + stub_application_setting(enabled_git_access_protocol: 'ssh') end it 'rejects the HTTP push' do @@ -383,8 +381,7 @@ describe API::Internal, api: true do context 'web actions are always allowed' do it 'allows WEB push' do - settings = ::ApplicationSetting.create_from_defaults - settings.update_attribute(:enabled_git_access_protocol, 'ssh') + stub_application_setting(enabled_git_access_protocol: 'ssh') project.team << [user, :developer] push(key, project, 'web') diff --git a/spec/requests/api/issues_spec.rb b/spec/requests/api/issues_spec.rb index 807c999b84a..62f1b8d7ca2 100644 --- a/spec/requests/api/issues_spec.rb +++ b/spec/requests/api/issues_spec.rb @@ -11,7 +11,7 @@ describe API::Issues, api: true do let(:author) { create(:author) } let(:assignee) { create(:assignee) } let(:admin) { create(:user, :admin) } - let!(:project) { create(:project, :public, creator_id: user.id, namespace: user.namespace ) } + let!(:project) { create(:empty_project, :public, creator_id: user.id, namespace: user.namespace ) } let!(:closed_issue) do create :closed_issue, author: user, @@ -224,7 +224,7 @@ describe API::Issues, api: true do describe "GET /groups/:id/issues" do let!(:group) { create(:group) } - let!(:group_project) { create(:project, :public, creator_id: user.id, namespace: group) } + let!(:group_project) { create(:empty_project, :public, creator_id: user.id, namespace: group) } let!(:group_closed_issue) do create :closed_issue, author: user, @@ -1052,7 +1052,7 @@ describe API::Issues, api: true do context "when the user is project owner" do let(:owner) { create(:user) } - let(:project) { create(:project, namespace: owner.namespace) } + let(:project) { create(:empty_project, namespace: owner.namespace) } it "deletes the issue if an admin requests it" do delete api("/projects/#{project.id}/issues/#{issue.id}", owner) @@ -1071,8 +1071,8 @@ describe API::Issues, api: true do end describe '/projects/:id/issues/:issue_id/move' do - let!(:target_project) { create(:project, path: 'project2', creator_id: user.id, namespace: user.namespace ) } - let!(:target_project2) { create(:project, creator_id: non_member.id, namespace: non_member.namespace ) } + let!(:target_project) { create(:empty_project, path: 'project2', creator_id: user.id, namespace: user.namespace ) } + let!(:target_project2) { create(:empty_project, creator_id: non_member.id, namespace: non_member.namespace ) } it 'moves an issue' do post api("/projects/#{project.id}/issues/#{issue.id}/move", user), diff --git a/spec/requests/api/labels_spec.rb b/spec/requests/api/labels_spec.rb index b29ce1ea25e..a8cd787f398 100644 --- a/spec/requests/api/labels_spec.rb +++ b/spec/requests/api/labels_spec.rb @@ -4,7 +4,7 @@ describe API::Labels, api: true do include ApiHelpers let(:user) { create(:user) } - let(:project) { create(:project, creator_id: user.id, namespace: user.namespace) } + let(:project) { create(:empty_project, creator_id: user.id, namespace: user.namespace) } let!(:label1) { create(:label, title: 'label1', project: project) } let!(:priority_label) { create(:label, title: 'bug', project: project, priority: 3) } diff --git a/spec/requests/api/members_spec.rb b/spec/requests/api/members_spec.rb index 2c94c86ccfa..9892e014cb9 100644 --- a/spec/requests/api/members_spec.rb +++ b/spec/requests/api/members_spec.rb @@ -9,7 +9,7 @@ describe API::Members, api: true do let(:stranger) { create(:user) } let(:project) do - create(:project, :public, :access_requestable, creator_id: master.id, namespace: master.namespace) do |project| + create(:empty_project, :public, :access_requestable, creator_id: master.id, namespace: master.namespace) do |project| project.team << [developer, :developer] project.team << [master, :master] project.request_access(access_requester) diff --git a/spec/requests/api/merge_requests_spec.rb b/spec/requests/api/merge_requests_spec.rb index 4e4fea1dad8..6f20ac49269 100644 --- a/spec/requests/api/merge_requests_spec.rb +++ b/spec/requests/api/merge_requests_spec.rb @@ -308,8 +308,8 @@ describe API::MergeRequests, api: true do context 'forked projects' do let!(:user2) { create(:user) } - let!(:fork_project) { create(:project, forked_from_project: project, namespace: user2.namespace, creator_id: user2.id) } - let!(:unrelated_project) { create(:project, namespace: create(:user).namespace, creator_id: user2.id) } + let!(:fork_project) { create(:empty_project, forked_from_project: project, namespace: user2.namespace, creator_id: user2.id) } + let!(:unrelated_project) { create(:empty_project, namespace: create(:user).namespace, creator_id: user2.id) } before :each do |each| fork_project.team << [user2, :reporter] diff --git a/spec/requests/api/notes_spec.rb b/spec/requests/api/notes_spec.rb index 028f93c8561..0f8d054b31e 100644 --- a/spec/requests/api/notes_spec.rb +++ b/spec/requests/api/notes_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' describe API::Notes, api: true do include ApiHelpers let(:user) { create(:user) } - let!(:project) { create(:project, :public, namespace: user.namespace) } + let!(:project) { create(:empty_project, :public, namespace: user.namespace) } let!(:issue) { create(:issue, project: project, author: user) } let!(:merge_request) { create(:merge_request, source_project: project, target_project: project, author: user) } let!(:snippet) { create(:project_snippet, project: project, author: user) } @@ -14,12 +14,12 @@ describe API::Notes, api: true do # For testing the cross-reference of a private issue in a public issue let(:private_user) { create(:user) } let(:private_project) do - create(:project, namespace: private_user.namespace). + create(:empty_project, namespace: private_user.namespace). tap { |p| p.team << [private_user, :master] } end let(:private_issue) { create(:issue, project: private_project) } - let(:ext_proj) { create(:project, :public) } + let(:ext_proj) { create(:empty_project, :public) } let(:ext_issue) { create(:issue, project: ext_proj) } let!(:cross_reference_note) do @@ -265,7 +265,7 @@ describe API::Notes, api: true do end context 'when user does not have access to create noteable' do - let(:private_issue) { create(:issue, project: create(:project, :private)) } + let(:private_issue) { create(:issue, project: create(:empty_project, :private)) } ## # We are posting to project user has access to, but we use issue id diff --git a/spec/requests/api/notification_settings_spec.rb b/spec/requests/api/notification_settings_spec.rb index 8691a81420f..39d3afcb78f 100644 --- a/spec/requests/api/notification_settings_spec.rb +++ b/spec/requests/api/notification_settings_spec.rb @@ -5,7 +5,7 @@ describe API::NotificationSettings, api: true do let(:user) { create(:user) } let!(:group) { create(:group) } - let!(:project) { create(:project, :public, creator_id: user.id, namespace: group) } + let!(:project) { create(:empty_project, :public, creator_id: user.id, namespace: group) } describe "GET /notification_settings" do it "returns global notification settings for the current user" do diff --git a/spec/requests/api/project_hooks_spec.rb b/spec/requests/api/project_hooks_spec.rb index 36fbcf088e7..f4973d71088 100644 --- a/spec/requests/api/project_hooks_spec.rb +++ b/spec/requests/api/project_hooks_spec.rb @@ -4,7 +4,7 @@ describe API::ProjectHooks, 'ProjectHooks', api: true do include ApiHelpers let(:user) { create(:user) } let(:user3) { create(:user) } - let!(:project) { create(:project, creator_id: user.id, namespace: user.namespace) } + let!(:project) { create(:empty_project, creator_id: user.id, namespace: user.namespace) } let!(:hook) do create(:project_hook, :all_events_enabled, @@ -204,7 +204,7 @@ describe API::ProjectHooks, 'ProjectHooks', api: true do it "returns a 404 if a user attempts to delete project hooks he/she does not own" do test_user = create(:user) - other_project = create(:project) + other_project = create(:empty_project) other_project.team << [test_user, :master] delete api("/projects/#{other_project.id}/hooks/#{hook.id}", test_user) diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb index cdb16b4c46b..cc5c532de83 100644 --- a/spec/requests/api/projects_spec.rb +++ b/spec/requests/api/projects_spec.rb @@ -8,8 +8,8 @@ describe API::Projects, api: true do let(:user2) { create(:user) } let(:user3) { create(:user) } let(:admin) { create(:admin) } - let(:project) { create(:project, creator_id: user.id, namespace: user.namespace) } - let(:project2) { create(:project, path: 'project2', creator_id: user.id, namespace: user.namespace) } + let(:project) { create(:empty_project, creator_id: user.id, namespace: user.namespace) } + let(:project2) { create(:empty_project, path: 'project2', creator_id: user.id, namespace: user.namespace) } let(:snippet) { create(:project_snippet, :public, author: user, project: project, title: 'example') } let(:project_member) { create(:project_member, :master, user: user, project: project) } let(:project_member2) { create(:project_member, :developer, user: user3, project: project) } @@ -32,7 +32,7 @@ describe API::Projects, api: true do access_level: ProjectMember::MASTER) end let(:project4) do - create(:project, + create(:empty_project, name: 'third_project', path: 'third_project', creator_id: user4.id, @@ -252,7 +252,7 @@ describe API::Projects, api: true do end end - let!(:public_project) { create(:project, :public) } + let!(:public_project) { create(:empty_project, :public) } before do project project2 @@ -283,7 +283,7 @@ describe API::Projects, api: true do end describe 'GET /projects/starred' do - let(:public_project) { create(:project, :public) } + let(:public_project) { create(:empty_project, :public) } before do project_member2 @@ -583,7 +583,7 @@ describe API::Projects, api: true do describe 'GET /projects/:id' do context 'when unauthenticated' do it 'returns the public projects' do - public_project = create(:project, :public) + public_project = create(:empty_project, :public) get api("/projects/#{public_project.id}") @@ -665,7 +665,7 @@ describe API::Projects, api: true do it 'handles users with dots' do dot_user = create(:user, username: 'dot.user') - project = create(:project, creator_id: dot_user.id, namespace: dot_user.namespace) + project = create(:empty_project, creator_id: dot_user.id, namespace: dot_user.namespace) get api("/projects/#{dot_user.namespace.name}%2F#{project.path}", dot_user) expect(response).to have_http_status(200) @@ -711,7 +711,7 @@ describe API::Projects, api: true do end context 'group project' do - let(:project2) { create(:project, group: create(:group)) } + let(:project2) { create(:empty_project, group: create(:group)) } before { project2.group.add_owner(user) } @@ -756,7 +756,7 @@ describe API::Projects, api: true do context 'when unauthenticated' do it_behaves_like 'project events response' do - let(:project) { create(:project, :public) } + let(:project) { create(:empty_project, :public) } let(:current_user) { nil } end end @@ -807,7 +807,7 @@ describe API::Projects, api: true do context 'when unauthenticated' do it_behaves_like 'project users response' do - let(:project) { create(:project, :public) } + let(:project) { create(:empty_project, :public) } let(:current_user) { nil } end end @@ -921,11 +921,11 @@ describe API::Projects, api: true do end describe :fork_admin do - let(:project_fork_target) { create(:project) } - let(:project_fork_source) { create(:project, :public) } + let(:project_fork_target) { create(:empty_project) } + let(:project_fork_source) { create(:empty_project, :public) } describe 'POST /projects/:id/fork/:forked_from_id' do - let(:new_project_fork_source) { create(:project, :public) } + let(:new_project_fork_source) { create(:empty_project, :public) } it "is not available for non admin users" do post api("/projects/#{project_fork_target.id}/fork/#{project_fork_source.id}", user) @@ -966,7 +966,7 @@ describe API::Projects, api: true do end context 'when users belong to project group' do - let(:project_fork_target) { create(:project, group: create(:group)) } + let(:project_fork_target) { create(:empty_project, group: create(:group)) } before do project_fork_target.group.add_owner user @@ -1121,7 +1121,6 @@ describe API::Projects, api: true do it_behaves_like 'project search response', query: 'one.dot.two', results: 1 do let(:current_user) { user } end - end context 'when authenticated as a different user' do diff --git a/spec/requests/api/runners_spec.rb b/spec/requests/api/runners_spec.rb index 99414270be6..f2d81a28cb8 100644 --- a/spec/requests/api/runners_spec.rb +++ b/spec/requests/api/runners_spec.rb @@ -7,8 +7,8 @@ describe API::Runners, api: true do let(:user) { create(:user) } let(:user2) { create(:user) } - let(:project) { create(:project, creator_id: user.id) } - let(:project2) { create(:project, creator_id: user.id) } + let(:project) { create(:empty_project, creator_id: user.id) } + let(:project2) { create(:empty_project, creator_id: user.id) } let!(:shared_runner) { create(:ci_runner, :shared) } let!(:unused_specific_runner) { create(:ci_runner) } diff --git a/spec/requests/api/todos_spec.rb b/spec/requests/api/todos_spec.rb index 887a2ba5b84..6fe695626ca 100644 --- a/spec/requests/api/todos_spec.rb +++ b/spec/requests/api/todos_spec.rb @@ -3,8 +3,8 @@ require 'spec_helper' describe API::Todos, api: true do include ApiHelpers - let(:project_1) { create(:project) } - let(:project_2) { create(:project) } + let(:project_1) { create(:empty_project) } + let(:project_2) { create(:empty_project) } let(:author_1) { create(:user) } let(:author_2) { create(:user) } let(:john_doe) { create(:user, username: 'john_doe') } diff --git a/spec/requests/api/triggers_spec.rb b/spec/requests/api/triggers_spec.rb index 67ec3168679..cd01283b655 100644 --- a/spec/requests/api/triggers_spec.rb +++ b/spec/requests/api/triggers_spec.rb @@ -15,7 +15,7 @@ describe API::Triggers do let!(:trigger_request) { create(:ci_trigger_request, trigger: trigger, created_at: '2015-01-01 12:13:14') } describe 'POST /projects/:project_id/trigger' do - let!(:project2) { create(:empty_project) } + let!(:project2) { create(:project) } let(:options) do { token: trigger_token diff --git a/spec/requests/api/variables_spec.rb b/spec/requests/api/variables_spec.rb index 7435f320607..769f04c5057 100644 --- a/spec/requests/api/variables_spec.rb +++ b/spec/requests/api/variables_spec.rb @@ -5,7 +5,7 @@ describe API::Variables, api: true do let(:user) { create(:user) } let(:user2) { create(:user) } - let!(:project) { create(:project, creator_id: user.id) } + let!(:project) { create(:empty_project, creator_id: user.id) } let!(:master) { create(:project_member, :master, user: user, project: project) } let!(:developer) { create(:project_member, :developer, user: user2, project: project) } let!(:variable) { create(:ci_variable, project: project) } diff --git a/spec/requests/ci/api/builds_spec.rb b/spec/requests/ci/api/builds_spec.rb index 3b5dc98e4d5..270c23e3f19 100644 --- a/spec/requests/ci/api/builds_spec.rb +++ b/spec/requests/ci/api/builds_spec.rb @@ -4,7 +4,8 @@ describe Ci::API::Builds do include ApiHelpers let(:runner) { FactoryGirl.create(:ci_runner, tag_list: ["mysql", "ruby"]) } - let(:project) { FactoryGirl.create(:empty_project) } + let(:project) { FactoryGirl.create(:empty_project, shared_runners_enabled: false) } + let(:last_update) { nil } describe "Builds API for runners" do let(:pipeline) { create(:ci_pipeline_without_jobs, project: project, ref: 'master') } @@ -16,6 +17,8 @@ describe Ci::API::Builds do describe "POST /builds/register" do let!(:build) { create(:ci_build, pipeline: pipeline, name: 'spinach', stage: 'test', stage_idx: 0) } let(:user_agent) { 'gitlab-ci-multi-runner 1.5.2 (1-5-stable; go1.6.3; linux/amd64)' } + let!(:last_update) { } + let!(:new_update) { } before do stub_container_registry_config(enabled: false) @@ -24,7 +27,31 @@ describe Ci::API::Builds do shared_examples 'no builds available' do context 'when runner sends version in User-Agent' do context 'for stable version' do - it { expect(response).to have_http_status(204) } + it 'gives 204 and set X-GitLab-Last-Update' do + expect(response).to have_http_status(204) + expect(response.header).to have_key('X-GitLab-Last-Update') + end + end + + context 'when last_update is up-to-date' do + let(:last_update) { runner.ensure_runner_queue_value } + + it 'gives 204 and set the same X-GitLab-Last-Update' do + expect(response).to have_http_status(204) + expect(response.header['X-GitLab-Last-Update']) + .to eq(last_update) + end + end + + context 'when last_update is outdated' do + let(:last_update) { runner.ensure_runner_queue_value } + let(:new_update) { runner.tick_runner_queue } + + it 'gives 204 and set a new X-GitLab-Last-Update' do + expect(response).to have_http_status(204) + expect(response.header['X-GitLab-Last-Update']) + .to eq(new_update) + end end context 'for beta version' do @@ -49,6 +76,7 @@ describe Ci::API::Builds do register_builds info: { platform: :darwin } expect(response).to have_http_status(201) + expect(response.headers).not_to have_key('X-GitLab-Last-Update') expect(json_response['sha']).to eq(build.sha) expect(runner.reload.platform).to eq("darwin") expect(json_response["options"]).to eq({ "image" => "ruby:2.1", "services" => ["postgres"] }) @@ -119,10 +147,10 @@ describe Ci::API::Builds do end context 'for shared runner' do - let(:shared_runner) { create(:ci_runner, token: "SharedRunner") } + let!(:runner) { create(:ci_runner, :shared, token: "SharedRunner") } before do - register_builds shared_runner.token + register_builds(runner.token) end it_behaves_like 'no builds available' @@ -224,7 +252,9 @@ describe Ci::API::Builds do end def register_builds(token = runner.token, **params) - post ci_api("/builds/register"), params.merge(token: token), { 'User-Agent' => user_agent } + new_params = params.merge(token: token, last_update: last_update) + + post ci_api("/builds/register"), new_params, { 'User-Agent' => user_agent } end end diff --git a/spec/services/ci/update_build_queue_service_spec.rb b/spec/services/ci/update_build_queue_service_spec.rb new file mode 100644 index 00000000000..f01a388b895 --- /dev/null +++ b/spec/services/ci/update_build_queue_service_spec.rb @@ -0,0 +1,47 @@ +require 'spec_helper' + +describe Ci::UpdateBuildQueueService, :services do + let(:project) { create(:project) } + let(:build) { create(:ci_build, pipeline: pipeline) } + let(:pipeline) { create(:ci_pipeline, project: project) } + + context 'when updating specific runners' do + let(:runner) { create(:ci_runner) } + + context 'when there are runner that can pick build' do + before { build.project.runners << runner } + + it 'ticks runner queue value' do + expect { subject.execute(build) } + .to change { runner.ensure_runner_queue_value } + end + end + + context 'when there are no runners that can pick build' do + it 'does not tick runner queue value' do + expect { subject.execute(build) } + .not_to change { runner.ensure_runner_queue_value } + end + end + end + + context 'when updating shared runners' do + let(:runner) { create(:ci_runner, :shared) } + + context 'when there are runner that can pick build' do + it 'ticks runner queue value' do + expect { subject.execute(build) } + .to change { runner.ensure_runner_queue_value } + end + end + + context 'when there are no runners that can pick build' do + before { build.tag_list = [:docker] } + + it 'does not tick runner queue value' do + expect { subject.execute(build) } + .not_to change { runner.ensure_runner_queue_value } + end + end + end +end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 6ee3307512d..f78899134d5 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -2,6 +2,7 @@ require './spec/simplecov_env' SimpleCovEnv.start! ENV["RAILS_ENV"] ||= 'test' +ENV["IN_MEMORY_APPLICATION_SETTINGS"] = 'true' require File.expand_path("../../config/environment", __FILE__) require 'rspec/rails' |