Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
0c6f357040
commit
a7f69f7481
|
|
@ -166,6 +166,7 @@ class Namespace < ApplicationRecord
|
|||
scope :sort_by_type, -> { order(arel_table[:type].asc.nulls_first) }
|
||||
scope :include_route, -> { includes(:route) }
|
||||
scope :by_parent, -> (parent) { where(parent_id: parent) }
|
||||
scope :by_root_id, -> (root_id) { where('traversal_ids[1] IN (?)', root_id) }
|
||||
scope :filter_by_path, -> (query) { where('lower(path) = :query', query: query.downcase) }
|
||||
scope :in_organization, -> (organization) { where(organization: organization) }
|
||||
|
||||
|
|
|
|||
|
|
@ -5,6 +5,8 @@ class Plan < MainClusterwide::ApplicationRecord
|
|||
|
||||
has_one :limits, class_name: 'PlanLimits'
|
||||
|
||||
scope :by_name, ->(name) { where(name: name) }
|
||||
|
||||
ALL_PLANS = [DEFAULT].freeze
|
||||
DEFAULT_PLANS = [DEFAULT].freeze
|
||||
private_constant :ALL_PLANS, :DEFAULT_PLANS
|
||||
|
|
|
|||
|
|
@ -0,0 +1,8 @@
|
|||
---
|
||||
name: optimize_group_template_query
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/129399
|
||||
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/422390
|
||||
milestone: '16.4'
|
||||
type: development
|
||||
group: group::source code
|
||||
default_enabled: false
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
---
|
||||
migration_job_name: BackfillUserPreferencesWithDefaults
|
||||
description: Backfills the user_preferences table columns with their default values
|
||||
feature_category: user_profile
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/125774
|
||||
milestone: 16.4
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class QueueBackfillUserPreferencesWithDefaults < Gitlab::Database::Migration[2.1]
|
||||
MIGRATION = "BackfillUserPreferencesWithDefaults"
|
||||
DELAY_INTERVAL = 2.minutes
|
||||
BATCH_SIZE = 3_000
|
||||
SUB_BATCH_SIZE = 200
|
||||
MAX_BATCH_SIZE = 10_000
|
||||
|
||||
disable_ddl_transaction!
|
||||
restrict_gitlab_migration gitlab_schema: :gitlab_main
|
||||
|
||||
def up
|
||||
queue_batched_background_migration(
|
||||
MIGRATION,
|
||||
:user_preferences,
|
||||
:id,
|
||||
job_interval: DELAY_INTERVAL,
|
||||
batch_size: BATCH_SIZE,
|
||||
sub_batch_size: SUB_BATCH_SIZE,
|
||||
max_batch_size: MAX_BATCH_SIZE
|
||||
)
|
||||
end
|
||||
|
||||
def down
|
||||
delete_batched_background_migration(MIGRATION, :user_preferences, :id, [])
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1 @@
|
|||
c69e2ffe6f6bec22fb182a5fc26c02717a1546f31777cc8167103639c0e48c65
|
||||
|
|
@ -39,14 +39,71 @@ To create a components repository, you must:
|
|||
|
||||
### Directory structure
|
||||
|
||||
A components repository can host one or more components.
|
||||
A components repository can host one or more components, and must follow a mandatory file structure.
|
||||
|
||||
Components repositories must follow a mandatory file structure, containing:
|
||||
Component configurations can be saved through the following directory structure, containing:
|
||||
|
||||
- A `templates` directory at the top level of your components repository. All component configuration files
|
||||
should be saved under this directory.
|
||||
- Files ending in `.yml` containing the component configurations, one file per component.
|
||||
- A Markdown `README.md` file explaining the details of all the components in the repository.
|
||||
|
||||
For example, if the project contains a single component and a pipeline to test the component,
|
||||
the file structure should be similar to:
|
||||
|
||||
```plaintext
|
||||
├── templates/
|
||||
│ └── only_template.yml
|
||||
├── README.md
|
||||
└── .gitlab-ci.yml
|
||||
```
|
||||
|
||||
This example component could be referenced with a path similar to `gitlab.com/my-username/my-component/only_template@<version>`,
|
||||
if the project is:
|
||||
|
||||
- On GitLab.com
|
||||
- Named `my-component`
|
||||
- In a personal namespace named `my-username`
|
||||
|
||||
The templates directory and the suffix of the configuration file should be excluded from the referenced path.
|
||||
|
||||
If the project contains multiple components, then the file structure should be similar to:
|
||||
|
||||
```plaintext
|
||||
├── README.md
|
||||
├── .gitlab-ci.yml
|
||||
└── templates/
|
||||
└── all-scans.yml
|
||||
└── secret-detection.yml
|
||||
```
|
||||
|
||||
These components would be referenced with these paths:
|
||||
|
||||
- `gitlab.com/my-username/my-component/all-scans`
|
||||
- `gitlab.com/my-username/my-component/secret-detection`
|
||||
|
||||
You can omit the filename in the path if the configuration file is named `template.yml`.
|
||||
For example, the following component could be referenced with `gitlab.com/my-username/my-component/dast`:
|
||||
|
||||
```plaintext
|
||||
├── README.md
|
||||
├── .gitlab-ci.yml
|
||||
├── templates/
|
||||
│ └── dast
|
||||
│ └── template.yml
|
||||
```
|
||||
|
||||
#### Component configurations saved in any directory (deprecated)
|
||||
|
||||
NOTE:
|
||||
Saving component configurations through this directory structure is [deprecated](https://gitlab.com/gitlab-org/gitlab/-/issues/415855).
|
||||
|
||||
Components configurations can be saved through the following directory structure, containing:
|
||||
|
||||
- `template.yml`: The component configuration, one file per component. If there is
|
||||
only one component, this file can be in the root of the project. If there are multiple
|
||||
components, each file must be in a separate subdirectory.
|
||||
- `README.md`: A documentation file explaining the details of the all the components in the repository.
|
||||
- `README.md`: A documentation file explaining the details of all the components in the repository.
|
||||
|
||||
For example, if the project is on GitLab.com, named `my-component`, and in a personal
|
||||
namespace named `my-username`:
|
||||
|
|
|
|||
|
|
@ -274,12 +274,7 @@ stage and the Project Management Group, so [create a file](#identify-the-devops-
|
|||
module QA
|
||||
RSpec.describe 'Plan' do
|
||||
describe 'Issues', product_group: :project_management do
|
||||
let(:issue) do
|
||||
Resource::Issue.fabricate_via_api! do |issue|
|
||||
issue.title = 'My issue'
|
||||
issue.description = 'This is an issue specific to this test'
|
||||
end
|
||||
end
|
||||
let(:issue) { create(:issue) }
|
||||
|
||||
before do
|
||||
Flow::Login.sign_in
|
||||
|
|
|
|||
|
|
@ -169,18 +169,14 @@ Page::Main::Menu.perform do |menu|
|
|||
end
|
||||
|
||||
#=> Good
|
||||
issue = Resource::Issue.fabricate_via_api! do |issue|
|
||||
issue.name = 'issue-name'
|
||||
end
|
||||
issue = create(:issue, name: 'issue-name')
|
||||
|
||||
Project::Issues::Index.perform do |index|
|
||||
expect(index).to have_issue(issue)
|
||||
end
|
||||
|
||||
#=> Bad
|
||||
issue = Resource::Issue.fabricate_via_api! do |issue|
|
||||
issue.name = 'issue-name'
|
||||
end
|
||||
issue = create(:issue, name: 'issue-name')
|
||||
|
||||
Project::Issues::Index.perform do |index|
|
||||
expect(index).to have_issue(issue)
|
||||
|
|
|
|||
|
|
@ -392,7 +392,7 @@ In this case, the result is similar to calling `Resource::Shirt.fabricate!`.
|
|||
|
||||
### Factories
|
||||
|
||||
You may also use FactoryBot invocations to create resources within your tests.
|
||||
You may also use [FactoryBot](https://github.com/thoughtbot/factory_bot/) invocations to create resources within your tests.
|
||||
|
||||
```ruby
|
||||
# create a project via the API to use in the test
|
||||
|
|
@ -405,7 +405,63 @@ let(:issue) { create(:issue, project: project) }
|
|||
let(:project) { create(:project, :private, name: 'my-project-name', add_name_uuid: false) }
|
||||
```
|
||||
|
||||
All factories are defined in [`qa/qa/factories`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/qa/qa/factories/).
|
||||
All factories are defined in [`qa/qa/factories`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/qa/qa/factories/) and are representative of
|
||||
their respective `QA::Resource::Base` class.
|
||||
|
||||
For example, a factory `:issue` can be found in `qa/resource/issue.rb`. A factory `:project` can be found in `qa/resource/project.rb`.
|
||||
|
||||
#### Create a new Factory
|
||||
|
||||
Given a resource:
|
||||
|
||||
```ruby
|
||||
# qa/resource/shirt.rb
|
||||
module QA
|
||||
module Resource
|
||||
class Shirt < Base
|
||||
attr_accessor :name
|
||||
attr_reader :read_only
|
||||
|
||||
attribute :brand
|
||||
|
||||
def api_post_body
|
||||
{ name: name, brand: brand }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
Define a factory with defaults and overrides:
|
||||
|
||||
```ruby
|
||||
# qa/factories/shirts.rb
|
||||
module QA
|
||||
FactoryBot.define do
|
||||
factory :shirt, class: 'QA::Resource::Shirt' do
|
||||
brand { 'BrandName' }
|
||||
|
||||
trait :with_name do
|
||||
name { 'Shirt Name' }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
In the test, create the resource via the API:
|
||||
|
||||
```ruby
|
||||
let(:my_shirt) { create(:shirt, brand: 'AnotherBrand') } #<Resource::Shirt @brand="AnotherBrand" @name=nil>
|
||||
let(:named_shirt) { create(:shirt, :with_name) } #<Resource::Shirt @brand="Brand Name" @name="Shirt Name">
|
||||
let(:invalid_shirt) { create(:shirt, read_only: true) } # NoMethodError
|
||||
|
||||
it 'creates a shirt' do
|
||||
expect(my_shirt.brand).to eq('AnotherBrand')
|
||||
expect(named_shirt.name).to eq('Shirt Name')
|
||||
expect(invalid_shirt).to raise_error(NoMethodError) # tries to call Resource::Shirt#read_only=
|
||||
end
|
||||
```
|
||||
|
||||
### Resources cleanup
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,21 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Gitlab
|
||||
module BackgroundMigration
|
||||
# Backfills the user_preferences table columns with their default values
|
||||
class BackfillUserPreferencesWithDefaults < BatchedMigrationJob
|
||||
operation_name :backfill_user_preferences_with_defaults
|
||||
feature_category :user_profile
|
||||
|
||||
def perform
|
||||
each_sub_batch do |sub_batch|
|
||||
connection.transaction do
|
||||
sub_batch.where(tab_width: nil).update_all(tab_width: 8)
|
||||
sub_batch.where(time_display_relative: nil).update_all(time_display_relative: true)
|
||||
sub_batch.where(render_whitespace_in_code: nil).update_all(render_whitespace_in_code: false)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -7,6 +7,7 @@ module Gitlab
|
|||
include Gitlab::Utils::StrongMemoize
|
||||
|
||||
LATEST_VERSION_KEYWORD = '~latest'
|
||||
TEMPLATES_DIR = 'templates'
|
||||
|
||||
def self.match?(address)
|
||||
address.include?('@') && address.start_with?(Settings.gitlab_ci['component_fqdn'])
|
||||
|
|
@ -26,7 +27,7 @@ module Gitlab
|
|||
|
||||
raise Gitlab::Access::AccessDeniedError unless Ability.allowed?(current_user, :download_code, project)
|
||||
|
||||
project.repository.blob_data_at(sha, project_file_path)
|
||||
templates_dir_path_content || content(sha, custom_dir_template_file_path)
|
||||
end
|
||||
|
||||
def project
|
||||
|
|
@ -34,13 +35,6 @@ module Gitlab
|
|||
end
|
||||
strong_memoize_attr :project
|
||||
|
||||
def project_file_path
|
||||
return unless project
|
||||
|
||||
component_dir = instance_path.delete_prefix(project.full_path)
|
||||
File.join(component_dir, @content_filename).delete_prefix('/')
|
||||
end
|
||||
|
||||
def sha
|
||||
return unless project
|
||||
return latest_version_sha if version == LATEST_VERSION_KEYWORD
|
||||
|
|
@ -49,6 +43,12 @@ module Gitlab
|
|||
end
|
||||
strong_memoize_attr :sha
|
||||
|
||||
def project_file_path
|
||||
return unless project
|
||||
|
||||
custom_dir_template_file_path
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
attr_reader :version, :path
|
||||
|
|
@ -57,6 +57,11 @@ module Gitlab
|
|||
@full_path.delete_prefix(host)
|
||||
end
|
||||
|
||||
def component_path
|
||||
instance_path.delete_prefix(project.full_path)
|
||||
end
|
||||
strong_memoize_attr :component_path
|
||||
|
||||
# Given a path like "my-org/sub-group/the-project/path/to/component"
|
||||
# find the project "my-org/sub-group/the-project" by looking at all possible paths.
|
||||
def find_project_by_component_path(path)
|
||||
|
|
@ -75,6 +80,34 @@ module Gitlab
|
|||
def latest_version_sha
|
||||
project.releases.latest&.sha
|
||||
end
|
||||
|
||||
def custom_dir_template_file_path
|
||||
File.join(component_path, @content_filename).delete_prefix('/')
|
||||
end
|
||||
|
||||
def templates_dir_file_path
|
||||
File.join(TEMPLATES_DIR, "#{component_path}.yml")
|
||||
end
|
||||
|
||||
# Given a path like "my-org/sub-group/the-project/templates/component"
|
||||
# returns "templates/component/template.yml"
|
||||
def templates_dir_template_file_path
|
||||
File.join(TEMPLATES_DIR, component_path, @content_filename)
|
||||
end
|
||||
|
||||
def templates_dir_exists?
|
||||
project.repository.tree.trees.map(&:name).include?(TEMPLATES_DIR)
|
||||
end
|
||||
|
||||
def templates_dir_path_content
|
||||
return unless templates_dir_exists?
|
||||
|
||||
content(sha, templates_dir_file_path) || content(sha, templates_dir_template_file_path)
|
||||
end
|
||||
|
||||
def content(sha, path)
|
||||
project.repository.blob_data_at(sha, path)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -5,11 +5,8 @@ module QA
|
|||
module Admin
|
||||
class Menu < Page::Base
|
||||
include SubMenus::Common
|
||||
|
||||
if QA::Runtime::Env.super_sidebar_enabled?
|
||||
prepend Sidebar::Overview
|
||||
prepend Sidebar::Settings
|
||||
end
|
||||
include Sidebar::Overview
|
||||
include Sidebar::Settings
|
||||
|
||||
view 'lib/sidebars/admin/menus/admin_overview_menu.rb' do
|
||||
element :admin_overview_submenu_content
|
||||
|
|
@ -23,73 +20,12 @@ module QA
|
|||
element :admin_monitoring_menu_link
|
||||
end
|
||||
|
||||
def go_to_preferences_settings
|
||||
hover_element(:admin_settings_menu_link) do
|
||||
click_element :admin_settings_preferences_link
|
||||
end
|
||||
end
|
||||
|
||||
def go_to_repository_settings
|
||||
hover_element(:admin_settings_menu_link) do
|
||||
click_element :admin_settings_repository_link
|
||||
end
|
||||
end
|
||||
|
||||
def go_to_integration_settings
|
||||
hover_element(:admin_settings_menu_link) do
|
||||
click_element :admin_settings_integrations_link
|
||||
end
|
||||
end
|
||||
|
||||
def go_to_general_settings
|
||||
hover_element(:admin_settings_menu_link) do
|
||||
click_element :admin_settings_general_link
|
||||
end
|
||||
end
|
||||
|
||||
def go_to_metrics_and_profiling_settings
|
||||
hover_element(:admin_settings_menu_link) do
|
||||
click_element :admin_settings_metrics_and_profiling_link
|
||||
end
|
||||
end
|
||||
|
||||
def go_to_network_settings
|
||||
hover_element(:admin_settings_menu_link) do
|
||||
click_element :admin_settings_network_link
|
||||
end
|
||||
end
|
||||
|
||||
def go_to_users_overview
|
||||
click_element :admin_overview_users_link
|
||||
end
|
||||
|
||||
def go_to_groups_overview
|
||||
click_element :admin_overview_groups_link
|
||||
end
|
||||
|
||||
def go_to_security_and_compliance
|
||||
hover_element(:admin_settings_menu_link) do
|
||||
click_element :admin_security_and_compliance_link
|
||||
end
|
||||
end
|
||||
|
||||
def go_to_applications
|
||||
return click_element(:nav_item_link, submenu_item: 'Applications') if Runtime::Env.super_sidebar_enabled?
|
||||
|
||||
click_element(:sidebar_menu_link, menu_item: 'Applications')
|
||||
click_element(:nav_item_link, submenu_item: 'Applications')
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def hover_element(element)
|
||||
within_sidebar do
|
||||
scroll_to_element(element)
|
||||
find_element(element).hover
|
||||
|
||||
yield
|
||||
end
|
||||
end
|
||||
|
||||
def within_sidebar(&block)
|
||||
page.within('.sidebar-top-level-items', &block)
|
||||
end
|
||||
|
|
|
|||
|
|
@ -7,57 +7,34 @@ module QA
|
|||
# We need to check phone_layout? instead of mobile_layout? here
|
||||
# since tablets have the regular top navigation bar
|
||||
prepend Mobile::Page::Main::Menu if Runtime::Env.phone_layout?
|
||||
include SubMenus::CreateNewMenu
|
||||
include SubMenus::SuperSidebar::ContextSwitcher
|
||||
|
||||
if Runtime::Env.super_sidebar_enabled?
|
||||
prepend SubMenus::CreateNewMenu
|
||||
include SubMenus::SuperSidebar::ContextSwitcher
|
||||
view 'app/assets/javascripts/super_sidebar/components/super_sidebar.vue' do
|
||||
element :navbar, required: true # TODO: rename to sidebar once it's default implementation
|
||||
end
|
||||
|
||||
if QA::Runtime::Env.super_sidebar_enabled?
|
||||
# Define alternative navbar (super sidebar) which does not yet implement all the same elements
|
||||
view 'app/assets/javascripts/super_sidebar/components/super_sidebar.vue' do
|
||||
element :navbar, required: true # TODO: rename to sidebar once it's default implementation
|
||||
end
|
||||
view 'app/assets/javascripts/super_sidebar/components/user_menu.vue' do
|
||||
element 'user-dropdown', required: !Runtime::Env.phone_layout?
|
||||
element :user_avatar_content, required: !Runtime::Env.phone_layout?
|
||||
element :sign_out_link
|
||||
element :edit_profile_link
|
||||
end
|
||||
|
||||
view 'app/assets/javascripts/super_sidebar/components/user_menu.vue' do
|
||||
element 'user-dropdown', required: !Runtime::Env.phone_layout?
|
||||
element :user_avatar_content, required: !Runtime::Env.phone_layout?
|
||||
element :sign_out_link
|
||||
element :edit_profile_link
|
||||
end
|
||||
view 'app/assets/javascripts/super_sidebar/components/user_name_group.vue' do
|
||||
element :user_profile_link
|
||||
end
|
||||
|
||||
view 'app/assets/javascripts/super_sidebar/components/user_name_group.vue' do
|
||||
element :user_profile_link
|
||||
end
|
||||
view 'app/assets/javascripts/super_sidebar/components/user_bar.vue' do
|
||||
element 'super-sidebar-search-button'
|
||||
element 'stop-impersonation-btn'
|
||||
element 'issues-shortcut-button', required: !Runtime::Env.phone_layout?
|
||||
element 'merge-requests-shortcut-button', required: !Runtime::Env.phone_layout?
|
||||
element 'todos-shortcut-button', required: !Runtime::Env.phone_layout?
|
||||
end
|
||||
|
||||
view 'app/assets/javascripts/super_sidebar/components/user_bar.vue' do
|
||||
element 'super-sidebar-search-button'
|
||||
element 'stop-impersonation-btn'
|
||||
element 'issues-shortcut-button', required: !Runtime::Env.phone_layout?
|
||||
element 'merge-requests-shortcut-button', required: !Runtime::Env.phone_layout?
|
||||
element 'todos-shortcut-button', required: !Runtime::Env.phone_layout?
|
||||
end
|
||||
|
||||
view 'app/assets/javascripts/super_sidebar/components/global_search/components/global_search.vue' do
|
||||
element 'global-search-input'
|
||||
end
|
||||
else
|
||||
view 'app/views/layouts/header/_default.html.haml' do
|
||||
element :navbar, required: true
|
||||
element :canary_badge_link
|
||||
element :user_avatar_content, required: !Runtime::Env.phone_layout?
|
||||
element 'user-dropdown', required: !Runtime::Env.phone_layout?
|
||||
element 'stop-impersonation-btn'
|
||||
element 'issues-shortcut-button', required: !Runtime::Env.phone_layout?
|
||||
element 'merge-requests-shortcut-button', required: !Runtime::Env.phone_layout?
|
||||
element 'todos-shortcut-button', required: !Runtime::Env.phone_layout?
|
||||
end
|
||||
|
||||
view 'app/views/layouts/header/_current_user_dropdown.html.haml' do
|
||||
element :sign_out_link
|
||||
element :edit_profile_link
|
||||
element :user_profile_link
|
||||
end
|
||||
view 'app/assets/javascripts/super_sidebar/components/global_search/components/global_search.vue' do
|
||||
element 'global-search-input'
|
||||
end
|
||||
|
||||
view 'app/assets/javascripts/nav/components/top_nav_app.vue' do
|
||||
|
|
@ -102,88 +79,31 @@ module QA
|
|||
end
|
||||
|
||||
def go_to_projects
|
||||
return click_element(:nav_item_link, submenu_item: 'Projects') if Runtime::Env.super_sidebar_enabled?
|
||||
|
||||
click_element(:sidebar_menu_link, menu_item: 'Projects')
|
||||
click_element(:nav_item_link, submenu_item: 'Projects')
|
||||
end
|
||||
|
||||
def go_to_groups
|
||||
# This needs to be fixed in the tests themselves. Fullfillment tests try to go to groups view from the
|
||||
# group. Instead of having a global hack, explicit test should navigate to correct view first.
|
||||
# see: https://gitlab.com/gitlab-org/gitlab/-/issues/403589#note_1383040061
|
||||
if Runtime::Env.super_sidebar_enabled?
|
||||
go_to_your_work unless has_element?(:nav_item_link, submenu_item: 'Groups', wait: 0)
|
||||
click_element(:nav_item_link, submenu_item: 'Groups')
|
||||
elsif has_element?(:sidebar_menu_link, menu_item: 'Groups')
|
||||
# Use new functionality to visit Groups where possible
|
||||
click_element(:sidebar_menu_link, menu_item: 'Groups')
|
||||
else
|
||||
# Otherwise fallback to previous functionality
|
||||
# See https://gitlab.com/gitlab-org/gitlab/-/issues/403589
|
||||
# and related issues
|
||||
within_groups_menu do
|
||||
click_element(:menu_item_link, title: 'View all groups')
|
||||
end
|
||||
end
|
||||
go_to_your_work unless has_element?(:nav_item_link, submenu_item: 'Groups', wait: 0)
|
||||
click_element(:nav_item_link, submenu_item: 'Groups')
|
||||
end
|
||||
|
||||
def go_to_snippets
|
||||
return click_element(:nav_item_link, submenu_item: 'Snippets') if Runtime::Env.super_sidebar_enabled?
|
||||
|
||||
click_element(:sidebar_menu_link, menu_item: 'Snippets')
|
||||
click_element(:nav_item_link, submenu_item: 'Snippets')
|
||||
end
|
||||
|
||||
def go_to_workspaces
|
||||
return click_element(:nav_item_link, submenu_item: 'Workspaces') if Runtime::Env.super_sidebar_enabled?
|
||||
|
||||
click_element(:sidebar_menu_link, menu_item: 'Workspaces')
|
||||
end
|
||||
|
||||
def go_to_create_project
|
||||
click_element('new-menu-toggle')
|
||||
click_element(:global_new_project_link)
|
||||
end
|
||||
|
||||
def go_to_create_group
|
||||
click_element('new-menu-toggle')
|
||||
click_element(:global_new_group_link)
|
||||
end
|
||||
|
||||
def go_to_create_snippet
|
||||
click_element('new-menu-toggle')
|
||||
click_element(:global_new_snippet_link)
|
||||
click_element(:nav_item_link, submenu_item: 'Workspaces')
|
||||
end
|
||||
|
||||
def go_to_menu_dropdown_option(option_name)
|
||||
return click_element(option_name) if QA::Runtime::Env.super_sidebar_enabled?
|
||||
|
||||
within_top_menu do
|
||||
click_element(:navbar_dropdown, title: 'Menu')
|
||||
click_element(option_name)
|
||||
end
|
||||
click_element(option_name)
|
||||
end
|
||||
|
||||
# To go to one of the popular pages using the provided shortcut buttons within top menu
|
||||
# @param [Symbol] the name of the element (e.g: `:issues_shortcut button`)
|
||||
# @example:
|
||||
# Menu.perform do |menu|
|
||||
# menu.go_to_page_by_shortcut('issues-shortcut-button') #=> Go to Issues page using shortcut button
|
||||
# end
|
||||
def go_to_page_by_shortcut(button)
|
||||
within_top_menu do
|
||||
click_element(button)
|
||||
end
|
||||
end
|
||||
|
||||
def go_to_admin_area
|
||||
Runtime::Env.super_sidebar_enabled? ? super : click_admin_area
|
||||
|
||||
return unless has_text?('Enter admin mode', wait: 1.0)
|
||||
|
||||
Admin::NewSession.perform do |new_session|
|
||||
new_session.set_password(Runtime::User.admin_password)
|
||||
new_session.click_enter_admin_mode
|
||||
end
|
||||
def go_to_todos
|
||||
click_element('todos-shortcut-button')
|
||||
end
|
||||
|
||||
def signed_in?
|
||||
|
|
@ -243,7 +163,7 @@ module QA
|
|||
end
|
||||
|
||||
def search_for(term)
|
||||
click_element(Runtime::Env.super_sidebar_enabled? ? 'super-sidebar-search-button' : :search_box)
|
||||
click_element('super-sidebar-search-button')
|
||||
fill_element('global-search-input', "#{term}\n")
|
||||
end
|
||||
|
||||
|
|
@ -255,15 +175,6 @@ module QA
|
|||
has_no_element?(:user_avatar_content, wait: wait)
|
||||
end
|
||||
|
||||
def has_admin_area_link?(wait: Capybara.default_max_wait_time)
|
||||
return super if Runtime::Env.super_sidebar_enabled?
|
||||
|
||||
within_top_menu do
|
||||
click_element(:navbar_dropdown, title: 'Menu')
|
||||
has_element?(:admin_area_link, wait: wait)
|
||||
end
|
||||
end
|
||||
|
||||
def click_stop_impersonation_link
|
||||
click_element('stop-impersonation-btn')
|
||||
end
|
||||
|
|
@ -278,55 +189,15 @@ module QA
|
|||
has_element?(:canary_badge_link)
|
||||
end
|
||||
|
||||
def enable_new_navigation
|
||||
Runtime::Logger.info("Enabling super sidebar!")
|
||||
return Runtime::Logger.info("User is not signed in, skipping") unless has_element?(:navbar, wait: 2)
|
||||
|
||||
return Runtime::Logger.info("Super sidebar is already enabled") if has_css?('[data-testid="super-sidebar"]')
|
||||
|
||||
within_user_menu { click_element(:new_navigation_toggle) }
|
||||
end
|
||||
|
||||
def disable_new_navigation
|
||||
Runtime::Logger.info("Disabling super sidebar!")
|
||||
return Runtime::Logger.info("User is not signed in, skipping") unless has_element?(:navbar, wait: 2)
|
||||
|
||||
unless has_css?('[data-testid="super-sidebar"]')
|
||||
return Runtime::Logger.info("Super sidebar is already disabled")
|
||||
end
|
||||
|
||||
within_user_menu { click_element(:new_navigation_toggle) }
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def within_top_menu(&block)
|
||||
within_element(:navbar, &block)
|
||||
end
|
||||
|
||||
def within_user_menu(&block)
|
||||
within_top_menu do
|
||||
within_element(:navbar) do
|
||||
click_element :user_avatar_content unless has_element?(:user_profile_link, wait: 1)
|
||||
|
||||
within_element('user-dropdown', &block)
|
||||
end
|
||||
end
|
||||
|
||||
def within_groups_menu(&block)
|
||||
go_to_menu_dropdown_option(:groups_dropdown)
|
||||
|
||||
within_element('menu-subview', &block)
|
||||
end
|
||||
|
||||
def within_projects_menu(&block)
|
||||
go_to_menu_dropdown_option(:projects_dropdown)
|
||||
|
||||
within_element('menu-subview', &block)
|
||||
end
|
||||
|
||||
def click_admin_area
|
||||
go_to_menu_dropdown_option(:admin_area_link)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -16,27 +16,12 @@ module QA
|
|||
end
|
||||
end
|
||||
|
||||
def hover_element(element)
|
||||
within_sidebar do
|
||||
find_element(element).hover
|
||||
yield
|
||||
end
|
||||
end
|
||||
|
||||
def within_sidebar(&block)
|
||||
wait_for_requests
|
||||
|
||||
within_element(:navbar, &block)
|
||||
end
|
||||
|
||||
def within_submenu(element = nil, &block)
|
||||
if element
|
||||
within_element(element, &block)
|
||||
else
|
||||
within_submenu_without_element(&block)
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# Opens the new item menu and yields to the block
|
||||
|
|
@ -64,10 +49,6 @@ module QA
|
|||
click_element(:nav_item_link, submenu_item: sub_menu)
|
||||
end
|
||||
end
|
||||
|
||||
def within_submenu_without_element(&block)
|
||||
has_css?('.fly-out-list') ? within('.fly-out-list', &block) : yield
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ module QA
|
|||
module CreateNewMenu
|
||||
extend QA::Page::PageConcern
|
||||
|
||||
def self.prepended(base)
|
||||
def self.included(base)
|
||||
super
|
||||
|
||||
base.class_eval do
|
||||
|
|
|
|||
|
|
@ -31,6 +31,12 @@ module QA
|
|||
|
||||
def go_to_admin_area
|
||||
go_to_context("Admin Area")
|
||||
return unless has_text?('Enter admin mode', wait: 1.0)
|
||||
|
||||
Admin::NewSession.perform do |new_session|
|
||||
new_session.set_password(Runtime::User.admin_password)
|
||||
new_session.click_enter_admin_mode
|
||||
end
|
||||
end
|
||||
|
||||
def has_admin_area_link?(wait: Capybara.default_max_wait_time)
|
||||
|
|
|
|||
|
|
@ -231,7 +231,7 @@ module QA
|
|||
# @example
|
||||
# wait_for_resource_availability('https://gitlab.com/api/v4/projects/1234')
|
||||
# @example
|
||||
# wait_for_resource_availability(resource_web_url(Resource::Issue.fabricate_via_api!))
|
||||
# wait_for_resource_availability(resource_web_url(create(:issue)))
|
||||
def wait_for_resource_availability(resource_web_url)
|
||||
return unless Runtime::Address.valid?(resource_web_url)
|
||||
|
||||
|
|
|
|||
|
|
@ -53,9 +53,7 @@ module QA
|
|||
it 'sends an issues and note event',
|
||||
testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/349723' do
|
||||
Resource::ProjectWebHook.setup(session: session, issues: true, note: true) do |webhook, smocker|
|
||||
issue = Resource::Issue.fabricate_via_api! do |issue_init|
|
||||
issue_init.project = webhook.project
|
||||
end
|
||||
issue = create(:issue, project: webhook.project)
|
||||
|
||||
Resource::ProjectIssueNote.fabricate_via_api! do |note|
|
||||
note.project = issue.project
|
||||
|
|
@ -118,9 +116,7 @@ module QA
|
|||
} do
|
||||
Resource::ProjectWebHook.setup(fail_mock, session: session, issues: true) do |webhook, smocker|
|
||||
hook_trigger_times.times do
|
||||
Resource::Issue.fabricate_via_api! do |issue_init|
|
||||
issue_init.project = webhook.project
|
||||
end
|
||||
create(:issue, project: webhook.project)
|
||||
|
||||
# using sleep to give rate limiter a chance to activate.
|
||||
sleep 0.5
|
||||
|
|
|
|||
|
|
@ -6,11 +6,7 @@ module QA
|
|||
include_context 'with gitlab project migration'
|
||||
|
||||
let!(:source_issue) do
|
||||
Resource::Issue.fabricate_via_api! do |issue|
|
||||
issue.api_client = source_admin_api_client
|
||||
issue.project = source_project
|
||||
issue.labels = %w[label_one label_two]
|
||||
end
|
||||
create(:issue, project: source_project, labels: %w[label_one label_two], api_client: source_admin_api_client)
|
||||
end
|
||||
|
||||
let(:source_issue_comments) do
|
||||
|
|
|
|||
|
|
@ -7,12 +7,8 @@ module QA
|
|||
include Support::API
|
||||
|
||||
describe 'Issue', product_group: :project_management do
|
||||
let(:issue) do
|
||||
Resource::Issue.fabricate_via_api!
|
||||
end
|
||||
|
||||
let(:issue) { create(:issue) }
|
||||
let(:issue_id) { issue.api_response[:iid] }
|
||||
|
||||
let(:api_client) { Runtime::API::Client.new(:gitlab) }
|
||||
|
||||
before do
|
||||
|
|
|
|||
|
|
@ -55,12 +55,7 @@ module QA
|
|||
end
|
||||
|
||||
context 'with gitlab issue' do
|
||||
let!(:issue) do
|
||||
Resource::Issue.fabricate_via_api! do |issue|
|
||||
issue.project = project
|
||||
end
|
||||
end
|
||||
|
||||
let!(:issue) { create(:issue, project: project) }
|
||||
let(:comment) { "Comment #{SecureRandom.hex(6)}" }
|
||||
|
||||
it 'displays an issue', testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/377891' do
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
module QA
|
||||
RSpec.describe 'Plan', product_group: :product_planning do
|
||||
describe 'Design Management' do
|
||||
let(:issue) { Resource::Issue.fabricate_via_api! }
|
||||
let(:issue) { create(:issue) }
|
||||
let(:design_filename) { 'banana_sample.gif' }
|
||||
let(:design) { Runtime::Path.fixture('designs', design_filename) }
|
||||
let(:annotation) { "This design is great!" }
|
||||
|
|
|
|||
|
|
@ -18,9 +18,7 @@ module QA
|
|||
|
||||
project.add_member(user)
|
||||
|
||||
Resource::Issue.fabricate_via_api! do |issue|
|
||||
issue.project = project
|
||||
end.visit!
|
||||
create(:issue, project: project).visit!
|
||||
end
|
||||
|
||||
after do
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ module QA
|
|||
describe 'collapse comments in issue discussions' do
|
||||
let(:my_first_reply) { 'My first reply' }
|
||||
let(:one_reply) { '1 reply' }
|
||||
let(:issue) { Resource::Issue.fabricate_via_api! }
|
||||
let(:issue) { create(:issue) }
|
||||
|
||||
before do
|
||||
Flow::Login.sign_in
|
||||
|
|
|
|||
|
|
@ -8,11 +8,7 @@ module QA
|
|||
before do
|
||||
Flow::Login.sign_in
|
||||
|
||||
2.times do
|
||||
Resource::Issue.fabricate_via_api! do |issue|
|
||||
issue.project = project
|
||||
end
|
||||
end
|
||||
create_list(:issue, 2, project: project)
|
||||
|
||||
project.visit!
|
||||
Page::Project::Menu.perform(&:go_to_issues)
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ module QA
|
|||
before do
|
||||
Flow::Login.sign_in
|
||||
|
||||
Resource::Issue.fabricate_via_api!.visit!
|
||||
create(:issue).visit!
|
||||
end
|
||||
|
||||
it 'filters comments and activities in an issue', testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347948' do
|
||||
|
|
|
|||
|
|
@ -8,9 +8,7 @@ module QA
|
|||
before do
|
||||
Flow::Login.sign_in
|
||||
|
||||
Resource::Issue.fabricate_via_api! do |issue|
|
||||
issue.title = issue_title
|
||||
end.project.visit!
|
||||
create(:issue, title: issue_title).project.visit!
|
||||
end
|
||||
|
||||
it 'shows issue suggestions when creating a new issue', testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347995' do
|
||||
|
|
|
|||
|
|
@ -36,9 +36,7 @@ module QA
|
|||
issue.project = project
|
||||
end.visit!
|
||||
else
|
||||
Resource::Issue.fabricate_via_api! do |issue|
|
||||
issue.project = project
|
||||
end.visit!
|
||||
create(:issue, project: project).visit!
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -15,11 +15,7 @@ module QA
|
|||
end
|
||||
|
||||
it 'update without refresh', testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347941' do
|
||||
issue = Resource::Issue.fabricate_via_api! do |issue|
|
||||
issue.project = project
|
||||
issue.assignee_ids = [user1.id]
|
||||
end
|
||||
|
||||
issue = create(:issue, project: project, assignee_ids: [user1.id])
|
||||
issue.visit!
|
||||
|
||||
Page::Project::Issue::Show.perform do |show|
|
||||
|
|
|
|||
|
|
@ -12,11 +12,7 @@ module QA
|
|||
|
||||
let(:project) { create(:project, name: "project-to-test-milestones-#{SecureRandom.hex(4)}", group: group) }
|
||||
|
||||
let(:issue) do
|
||||
Resource::Issue.fabricate_via_api! do |issue|
|
||||
issue.project = project
|
||||
end
|
||||
end
|
||||
let(:issue) { create(:issue, project: project) }
|
||||
|
||||
before do
|
||||
Flow::Login.sign_in
|
||||
|
|
|
|||
|
|
@ -4,35 +4,25 @@ module QA
|
|||
RSpec.describe 'Plan', :reliable, product_group: :project_management do
|
||||
describe 'Related issues' do
|
||||
let(:project) { create(:project, name: 'project-to-test-related-issues') }
|
||||
let(:issue_1) do
|
||||
Resource::Issue.fabricate_via_api! do |issue|
|
||||
issue.project = project
|
||||
end
|
||||
end
|
||||
|
||||
let(:issue_2) do
|
||||
Resource::Issue.fabricate_via_api! do |issue|
|
||||
issue.project = project
|
||||
end
|
||||
end
|
||||
let(:issues) { create_list(:issue, 2, project: project) }
|
||||
|
||||
before do
|
||||
Flow::Login.sign_in
|
||||
end
|
||||
|
||||
it 'relates and unrelates one issue to/from another', testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347994' do
|
||||
issue_1.visit!
|
||||
issues.first.visit!
|
||||
|
||||
Page::Project::Issue::Show.perform do |show|
|
||||
max_wait = 60
|
||||
|
||||
show.relate_issue(issue_2)
|
||||
show.relate_issue(issues.last)
|
||||
|
||||
expect(show.related_issuable_item).to have_text(issue_2.title, wait: max_wait)
|
||||
expect(show.related_issuable_item).to have_text(issues.last.title, wait: max_wait)
|
||||
|
||||
show.click_remove_related_issue_button
|
||||
|
||||
expect(show).not_to have_text(issue_2.title, wait: max_wait)
|
||||
expect(show).not_to have_text(issues.last.title, wait: max_wait)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ module QA
|
|||
Runtime::Env.transient_trials.times do |i|
|
||||
QA::Runtime::Logger.info("Transient bug test action - Trial #{i}")
|
||||
|
||||
Resource::Issue.fabricate_via_api!.visit!
|
||||
create(:issue).visit!
|
||||
|
||||
Page::Project::Issue::Show.perform do |issue_page|
|
||||
issue_page.select_all_activities_filter
|
||||
|
|
|
|||
|
|
@ -23,10 +23,7 @@ module QA
|
|||
|
||||
Flow::Login.sign_in_as_admin
|
||||
|
||||
Page::Main::Menu.perform do |menu|
|
||||
menu.go_to_page_by_shortcut('todos-shortcut-button')
|
||||
end
|
||||
|
||||
Page::Main::Menu.perform(&:go_to_todos)
|
||||
Page::Dashboard::Todos.perform do |todos|
|
||||
todos.filter_todos_by_group(group)
|
||||
end
|
||||
|
|
|
|||
|
|
@ -15,11 +15,7 @@ module QA
|
|||
|
||||
shared_examples 'adds user as owner' do |project_type, testcase|
|
||||
let!(:issue) do
|
||||
Resource::Issue.fabricate_via_api! do |issue|
|
||||
issue.api_client = owner_api_client
|
||||
issue.project = project
|
||||
issue.title = 'Test Owner Deletes Issue'
|
||||
end
|
||||
create(:issue, title: 'Test Owner Deletes Issue', project: project, api_client: owner_api_client)
|
||||
end
|
||||
|
||||
before do
|
||||
|
|
@ -46,11 +42,7 @@ module QA
|
|||
|
||||
shared_examples 'adds user as maintainer' do |testcase|
|
||||
let!(:issue) do
|
||||
Resource::Issue.fabricate_via_api! do |issue|
|
||||
issue.api_client = owner_api_client
|
||||
issue.project = project
|
||||
issue.title = 'Test Maintainer Deletes Issue'
|
||||
end
|
||||
create(:issue, title: 'Test Maintainer Deletes Issue', project: project, api_client: owner_api_client)
|
||||
end
|
||||
|
||||
before do
|
||||
|
|
|
|||
|
|
@ -45,12 +45,7 @@ module QA
|
|||
end
|
||||
end
|
||||
|
||||
let(:issue) do
|
||||
Resource::Issue.fabricate_via_api! do |issue|
|
||||
issue.project = project
|
||||
issue.api_client = followed_user_api_client
|
||||
end
|
||||
end
|
||||
let(:issue) { create(:issue, project: project, api_client: followed_user_api_client) }
|
||||
|
||||
let(:comment) do
|
||||
Resource::ProjectIssueNote.fabricate_via_api! do |project_issue_note|
|
||||
|
|
|
|||
|
|
@ -0,0 +1,66 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Gitlab::BackgroundMigration::BackfillUserPreferencesWithDefaults,
|
||||
schema: 20230818085219,
|
||||
feature_category: :user_profile do
|
||||
let(:user_preferences) { table(:user_preferences) }
|
||||
let(:users) { table(:users) }
|
||||
let(:columns) { [:tab_width, :time_display_relative, :render_whitespace_in_code] }
|
||||
let(:initial_column_values) do
|
||||
[
|
||||
[nil, nil, nil],
|
||||
[10, nil, nil],
|
||||
[nil, false, nil],
|
||||
[nil, nil, true]
|
||||
]
|
||||
.map { |row| columns.zip(row).to_h }
|
||||
end
|
||||
|
||||
let(:final_column_values) do
|
||||
[
|
||||
[8, true, false],
|
||||
[10, true, false],
|
||||
[8, false, false],
|
||||
[8, true, true]
|
||||
]
|
||||
.map { |row| columns.zip(row).to_h }
|
||||
end
|
||||
|
||||
subject(:perform_migration) do
|
||||
described_class
|
||||
.new(
|
||||
start_id: user_preferences.minimum(:id),
|
||||
end_id: user_preferences.maximum(:id),
|
||||
batch_table: :user_preferences,
|
||||
batch_column: :id,
|
||||
sub_batch_size: 2,
|
||||
pause_ms: 0,
|
||||
connection: ActiveRecord::Base.connection
|
||||
)
|
||||
.perform
|
||||
end
|
||||
|
||||
before do
|
||||
initial_column_values.each_with_index do |attributes, index|
|
||||
user = users.create!(projects_limit: 1, email: "user#{index}@gitlab.com")
|
||||
user_preference = user_preferences.create!(attributes.merge(user_id: user.id))
|
||||
final_column_values[index].merge!(id: user_preference.id)
|
||||
end
|
||||
end
|
||||
|
||||
it 'backfills the null values with the default values' do
|
||||
perform_migration
|
||||
|
||||
final_column_values.each { |attributes| match_attributes(attributes) }
|
||||
end
|
||||
|
||||
def match_attributes(attributes)
|
||||
migrated_user_preference = user_preferences.find(attributes[:id])
|
||||
|
||||
expect(migrated_user_preference.tab_width).to eq(attributes[:tab_width])
|
||||
expect(migrated_user_preference.time_display_relative).to eq(attributes[:time_display_relative])
|
||||
expect(migrated_user_preference.render_whitespace_in_code).to eq(attributes[:render_whitespace_in_code])
|
||||
end
|
||||
end
|
||||
|
|
@ -14,125 +14,248 @@ RSpec.describe Gitlab::Ci::Components::InstancePath, feature_category: :pipeline
|
|||
end
|
||||
|
||||
describe 'FQDN path' do
|
||||
let_it_be(:existing_project) { create(:project, :repository) }
|
||||
|
||||
let(:project_path) { existing_project.full_path }
|
||||
let(:address) { "acme.com/#{project_path}/component@#{version}" }
|
||||
let(:version) { 'master' }
|
||||
|
||||
context 'when project exists' do
|
||||
it 'provides the expected attributes', :aggregate_failures do
|
||||
expect(path.project).to eq(existing_project)
|
||||
expect(path.host).to eq(current_host)
|
||||
expect(path.sha).to eq(existing_project.commit('master').id)
|
||||
expect(path.project_file_path).to eq('component/template.yml')
|
||||
context 'when the project repository contains a templates directory' do
|
||||
let_it_be(:existing_project) do
|
||||
create(
|
||||
:project, :custom_repo,
|
||||
files: {
|
||||
'templates/file.yml' => 'image: alpine_1',
|
||||
'templates/dir/template.yml' => 'image: alpine_2'
|
||||
}
|
||||
)
|
||||
end
|
||||
|
||||
context 'when content exists' do
|
||||
let(:content) { 'image: alpine' }
|
||||
let(:project_path) { existing_project.full_path }
|
||||
let(:address) { "acme.com/#{project_path}/file@#{version}" }
|
||||
|
||||
before do
|
||||
allow_next_instance_of(Repository) do |instance|
|
||||
allow(instance)
|
||||
.to receive(:blob_data_at)
|
||||
.with(existing_project.commit('master').id, 'component/template.yml')
|
||||
.and_return(content)
|
||||
end
|
||||
before do
|
||||
existing_project.add_developer(user)
|
||||
end
|
||||
|
||||
context 'when user does not have permissions' do
|
||||
it 'raises an error when fetching the content' do
|
||||
expect { path.fetch_content!(current_user: build(:user)) }
|
||||
.to raise_error(Gitlab::Access::AccessDeniedError)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when templates directory is top level' do
|
||||
it 'fetches the content' do
|
||||
expect(path.fetch_content!(current_user: user)).to eq('image: alpine_1')
|
||||
end
|
||||
|
||||
context 'when user has permissions to read code' do
|
||||
before do
|
||||
existing_project.add_developer(user)
|
||||
end
|
||||
it 'provides the expected attributes', :aggregate_failures do
|
||||
expect(path.host).to eq(current_host)
|
||||
expect(path.project_file_path).to eq('file/template.yml')
|
||||
expect(path.project).to eq(existing_project)
|
||||
expect(path.sha).to eq(existing_project.commit('master').id)
|
||||
end
|
||||
|
||||
context 'when file name is `template.yml`' do
|
||||
let(:address) { "acme.com/#{project_path}/dir@#{version}" }
|
||||
|
||||
it 'fetches the content' do
|
||||
expect(path.fetch_content!(current_user: user)).to eq(content)
|
||||
expect(path.fetch_content!(current_user: user)).to eq('image: alpine_2')
|
||||
end
|
||||
end
|
||||
|
||||
context 'when user does not have permissions to download code' do
|
||||
it 'raises an error when fetching the content' do
|
||||
expect { path.fetch_content!(current_user: user) }
|
||||
.to raise_error(Gitlab::Access::AccessDeniedError)
|
||||
it 'provides the expected attributes', :aggregate_failures do
|
||||
expect(path.host).to eq(current_host)
|
||||
expect(path.project_file_path).to eq('dir/template.yml')
|
||||
expect(path.project).to eq(existing_project)
|
||||
expect(path.sha).to eq(existing_project.commit('master').id)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the project is nested under a subgroup' do
|
||||
let_it_be(:existing_group) { create(:group, :nested) }
|
||||
let_it_be(:existing_project) do
|
||||
create(
|
||||
:project, :custom_repo,
|
||||
files: {
|
||||
'templates/file.yml' => 'image: alpine_1'
|
||||
},
|
||||
group: existing_group
|
||||
)
|
||||
end
|
||||
|
||||
it 'fetches the content' do
|
||||
expect(path.fetch_content!(current_user: user)).to eq('image: alpine_1')
|
||||
end
|
||||
|
||||
it 'provides the expected attributes', :aggregate_failures do
|
||||
expect(path.host).to eq(current_host)
|
||||
expect(path.project_file_path).to eq('file/template.yml')
|
||||
expect(path.project).to eq(existing_project)
|
||||
expect(path.sha).to eq(existing_project.commit('master').id)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when fetching the latest version' do
|
||||
let_it_be(:existing_project) do
|
||||
create(
|
||||
:project, :custom_repo,
|
||||
files: {
|
||||
'templates/file.yml' => 'image: alpine_1'
|
||||
}
|
||||
)
|
||||
end
|
||||
|
||||
let(:version) { '~latest' }
|
||||
|
||||
let(:latest_sha) do
|
||||
existing_project.repository.find_commits_by_message('Updates image').commits.last.sha
|
||||
end
|
||||
|
||||
before do
|
||||
create(:release, project: existing_project, sha: existing_project.repository.root_ref_sha,
|
||||
released_at: Time.zone.now - 1.day)
|
||||
|
||||
existing_project.repository.update_file(
|
||||
user, 'templates/file.yml', 'image: alpine_2',
|
||||
message: 'Updates image', branch_name: existing_project.default_branch
|
||||
)
|
||||
|
||||
create(:release, project: existing_project, sha: latest_sha,
|
||||
released_at: Time.zone.now)
|
||||
end
|
||||
|
||||
it 'fetches the content' do
|
||||
expect(path.fetch_content!(current_user: user)).to eq('image: alpine_2')
|
||||
end
|
||||
|
||||
it 'provides the expected attributes', :aggregate_failures do
|
||||
expect(path.host).to eq(current_host)
|
||||
expect(path.project_file_path).to eq('file/template.yml')
|
||||
expect(path.project).to eq(existing_project)
|
||||
expect(path.sha).to eq(latest_sha)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when version does not exist' do
|
||||
let(:version) { 'non-existent' }
|
||||
|
||||
it 'returns nil when fetching the content' do
|
||||
expect(path.fetch_content!(current_user: user)).to be_nil
|
||||
end
|
||||
|
||||
it 'provides the expected attributes', :aggregate_failures do
|
||||
expect(path.host).to eq(current_host)
|
||||
expect(path.project_file_path).to eq('file/template.yml')
|
||||
expect(path.project).to eq(existing_project)
|
||||
expect(path.sha).to be_nil
|
||||
end
|
||||
end
|
||||
|
||||
context 'when current GitLab instance is installed on a relative URL' do
|
||||
let(:address) { "acme.com/gitlab/#{project_path}/file@#{version}" }
|
||||
let(:current_host) { 'acme.com/gitlab/' }
|
||||
|
||||
it 'fetches the content' do
|
||||
expect(path.fetch_content!(current_user: user)).to eq('image: alpine_1')
|
||||
end
|
||||
|
||||
it 'provides the expected attributes', :aggregate_failures do
|
||||
expect(path.host).to eq(current_host)
|
||||
expect(path.project_file_path).to eq('file/template.yml')
|
||||
expect(path.project).to eq(existing_project)
|
||||
expect(path.sha).to eq(existing_project.commit('master').id)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when project path is nested under a subgroup' do
|
||||
let(:existing_group) { create(:group, :nested) }
|
||||
let(:existing_project) { create(:project, :repository, group: existing_group) }
|
||||
# All the following tests are for deprecated code and will be removed
|
||||
# in https://gitlab.com/gitlab-org/gitlab/-/issues/415855
|
||||
context 'when the project does not contain a templates directory' do
|
||||
let(:project_path) { existing_project.full_path }
|
||||
let(:address) { "acme.com/#{project_path}/component@#{version}" }
|
||||
|
||||
let_it_be(:existing_project) do
|
||||
create(
|
||||
:project, :custom_repo,
|
||||
files: {
|
||||
'component/template.yml' => 'image: alpine'
|
||||
}
|
||||
)
|
||||
end
|
||||
|
||||
before do
|
||||
existing_project.add_developer(user)
|
||||
end
|
||||
|
||||
it 'fetches the content' do
|
||||
expect(path.fetch_content!(current_user: user)).to eq('image: alpine')
|
||||
end
|
||||
|
||||
it 'provides the expected attributes', :aggregate_failures do
|
||||
expect(path.project).to eq(existing_project)
|
||||
expect(path.host).to eq(current_host)
|
||||
expect(path.project_file_path).to eq('component/template.yml')
|
||||
expect(path.project).to eq(existing_project)
|
||||
expect(path.sha).to eq(existing_project.commit('master').id)
|
||||
expect(path.project_file_path).to eq('component/template.yml')
|
||||
end
|
||||
end
|
||||
|
||||
context 'when current GitLab instance is installed on a relative URL' do
|
||||
let(:address) { "acme.com/gitlab/#{project_path}/component@#{version}" }
|
||||
let(:current_host) { 'acme.com/gitlab/' }
|
||||
|
||||
it 'provides the expected attributes', :aggregate_failures do
|
||||
expect(path.project).to eq(existing_project)
|
||||
expect(path.host).to eq(current_host)
|
||||
expect(path.sha).to eq(existing_project.commit('master').id)
|
||||
expect(path.project_file_path).to eq('component/template.yml')
|
||||
end
|
||||
end
|
||||
|
||||
context 'when version does not exist' do
|
||||
let(:version) { 'non-existent' }
|
||||
|
||||
it 'provides the expected attributes', :aggregate_failures do
|
||||
expect(path.project).to eq(existing_project)
|
||||
expect(path.host).to eq(current_host)
|
||||
expect(path.sha).to be_nil
|
||||
expect(path.project_file_path).to eq('component/template.yml')
|
||||
end
|
||||
|
||||
it 'returns nil when fetching the content' do
|
||||
expect(path.fetch_content!(current_user: user)).to be_nil
|
||||
end
|
||||
end
|
||||
|
||||
context 'when version is `~latest`' do
|
||||
let(:version) { '~latest' }
|
||||
|
||||
context 'when project has releases' do
|
||||
let_it_be(:latest_release) do
|
||||
create(:release, project: existing_project, sha: 'sha-1', released_at: Time.zone.now)
|
||||
context 'when project path is nested under a subgroup' do
|
||||
let_it_be(:existing_group) { create(:group, :nested) }
|
||||
let_it_be(:existing_project) do
|
||||
create(
|
||||
:project, :custom_repo,
|
||||
files: {
|
||||
'component/template.yml' => 'image: alpine'
|
||||
},
|
||||
group: existing_group
|
||||
)
|
||||
end
|
||||
|
||||
before_all do
|
||||
# Previous release
|
||||
create(:release, project: existing_project, sha: 'sha-2', released_at: Time.zone.now - 1.day)
|
||||
it 'fetches the content' do
|
||||
expect(path.fetch_content!(current_user: user)).to eq('image: alpine')
|
||||
end
|
||||
|
||||
it 'returns the sha of the latest release' do
|
||||
expect(path.sha).to eq(latest_release.sha)
|
||||
it 'provides the expected attributes', :aggregate_failures do
|
||||
expect(path.host).to eq(current_host)
|
||||
expect(path.project_file_path).to eq('component/template.yml')
|
||||
expect(path.project).to eq(existing_project)
|
||||
expect(path.sha).to eq(existing_project.commit('master').id)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when project does not have releases' do
|
||||
it { expect(path.sha).to be_nil }
|
||||
end
|
||||
end
|
||||
context 'when current GitLab instance is installed on a relative URL' do
|
||||
let(:address) { "acme.com/gitlab/#{project_path}/component@#{version}" }
|
||||
let(:current_host) { 'acme.com/gitlab/' }
|
||||
|
||||
context 'when project does not exist' do
|
||||
let(:project_path) { 'non-existent/project' }
|
||||
it 'fetches the content' do
|
||||
expect(path.fetch_content!(current_user: user)).to eq('image: alpine')
|
||||
end
|
||||
|
||||
it 'provides the expected attributes', :aggregate_failures do
|
||||
expect(path.project).to be_nil
|
||||
expect(path.host).to eq(current_host)
|
||||
expect(path.sha).to be_nil
|
||||
expect(path.project_file_path).to be_nil
|
||||
it 'provides the expected attributes', :aggregate_failures do
|
||||
expect(path.host).to eq(current_host)
|
||||
expect(path.project_file_path).to eq('component/template.yml')
|
||||
expect(path.project).to eq(existing_project)
|
||||
expect(path.sha).to eq(existing_project.commit('master').id)
|
||||
end
|
||||
end
|
||||
|
||||
it 'returns nil when fetching the content' do
|
||||
expect(path.fetch_content!(current_user: user)).to be_nil
|
||||
context 'when version does not exist' do
|
||||
let(:version) { 'non-existent' }
|
||||
|
||||
it 'returns nil when fetching the content' do
|
||||
expect(path.fetch_content!(current_user: user)).to be_nil
|
||||
end
|
||||
|
||||
it 'provides the expected attributes', :aggregate_failures do
|
||||
expect(path.host).to eq(current_host)
|
||||
expect(path.project_file_path).to eq('component/template.yml')
|
||||
expect(path.project).to eq(existing_project)
|
||||
expect(path.sha).to be_nil
|
||||
end
|
||||
end
|
||||
|
||||
context 'when user does not have permissions' do
|
||||
it 'raises an error when fetching the content' do
|
||||
expect { path.fetch_content!(current_user: build(:user)) }
|
||||
.to raise_error(Gitlab::Access::AccessDeniedError)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -0,0 +1,27 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
require_migration!
|
||||
|
||||
RSpec.describe QueueBackfillUserPreferencesWithDefaults, feature_category: :user_profile do
|
||||
let!(:batched_migration) { described_class::MIGRATION }
|
||||
|
||||
it 'schedules a new batched migration' do
|
||||
reversible_migration do |migration|
|
||||
migration.before -> {
|
||||
expect(batched_migration).not_to have_scheduled_batched_migration
|
||||
}
|
||||
|
||||
migration.after -> {
|
||||
expect(batched_migration).to have_scheduled_batched_migration(
|
||||
table_name: :user_preferences,
|
||||
column_name: :id,
|
||||
interval: described_class::DELAY_INTERVAL,
|
||||
batch_size: described_class::BATCH_SIZE,
|
||||
sub_batch_size: described_class::SUB_BATCH_SIZE,
|
||||
max_batch_size: described_class::MAX_BATCH_SIZE
|
||||
)
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -443,6 +443,15 @@ RSpec.describe Namespace, feature_category: :groups_and_projects do
|
|||
end
|
||||
end
|
||||
|
||||
describe '.by_root_id' do
|
||||
it 'returns correct namespaces' do
|
||||
expect(described_class.by_root_id(namespace1.id)).to match_array([namespace1, namespace1sub])
|
||||
expect(described_class.by_root_id(namespace2.id)).to match_array([namespace2, namespace2sub])
|
||||
expect(described_class.by_root_id(namespace1sub.id)).to be_empty
|
||||
expect(described_class.by_root_id(nil)).to be_empty
|
||||
end
|
||||
end
|
||||
|
||||
describe '.filter_by_path' do
|
||||
it 'includes correct namespaces' do
|
||||
expect(described_class.filter_by_path(namespace1.path)).to eq([namespace1])
|
||||
|
|
|
|||
|
|
@ -3,6 +3,18 @@
|
|||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Plan do
|
||||
describe 'scopes', :aggregate_failures do
|
||||
let_it_be(:default_plan) { create(:default_plan) }
|
||||
|
||||
describe '.by_name' do
|
||||
it 'returns plans by their name' do
|
||||
expect(described_class.by_name('default')).to match_array([default_plan])
|
||||
expect(described_class.by_name(%w[default unknown])).to match_array([default_plan])
|
||||
expect(described_class.by_name(nil)).to be_empty
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#default?' do
|
||||
subject { plan.default? }
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue