Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2023-08-22 21:09:08 +00:00
parent 0c6f357040
commit a7f69f7481
41 changed files with 619 additions and 445 deletions

View File

@ -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) }

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -0,0 +1 @@
c69e2ffe6f6bec22fb182a5fc26c02717a1546f31777cc8167103639c0e48c65

View File

@ -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`:

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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!" }

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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|

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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|

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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])

View File

@ -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? }