Commit 47d90f62 authored by GitLab Bot's avatar GitLab Bot

Automatic merge of gitlab-org/gitlab master

parents 9442e58d 8f792928
......@@ -12,6 +12,10 @@ export default {
required: true,
type: String,
},
selector: {
required: true,
type: String,
},
},
data() {
return {
......@@ -34,22 +38,24 @@ export default {
},
mounted() {
document.addEventListener('click', this.handleClick);
},
/*
* Here we're looking for every button that needs to launch a modal
* on click, and then attaching a click event handler to show the modal
* if it's correctly configured.
*
* TODO: Replace this with integrated modal components https://gitlab.com/gitlab-org/gitlab/-/issues/320922
*/
document.querySelectorAll(this.selector).forEach((button) => {
button.addEventListener('click', (e) => {
if (!button.dataset.glModalAction) return;
beforeDestroy() {
document.removeEventListener('click', this.handleClick);
e.preventDefault();
this.show(button.dataset);
});
});
},
methods: {
handleClick(e) {
const { glModalAction: action } = e.target.dataset;
if (!action) return;
this.show(e.target.dataset);
e.preventDefault();
},
show(modalData) {
const { glModalAction: requestedAction } = modalData;
......
......@@ -7,6 +7,7 @@ import { initAdminUsersApp, initCohortsEmptyState } from '~/admin/users';
import initTabs from '~/admin/users/tabs';
import ModalManager from './components/user_modal_manager.vue';
const CONFIRM_DELETE_BUTTON_SELECTOR = '.js-delete-user-modal-button';
const MODAL_TEXTS_CONTAINER_SELECTOR = '#js-modal-texts';
const MODAL_MANAGER_SELECTOR = '#js-delete-user-modal';
......@@ -50,6 +51,7 @@ document.addEventListener('DOMContentLoaded', () => {
return h(ModalManager, {
ref: 'manager',
props: {
selector: CONFIRM_DELETE_BUTTON_SELECTOR,
modalConfiguration,
csrfToken: csrf.token,
},
......
......@@ -17,6 +17,7 @@ class Packages::Package < ApplicationRecord
has_one :maven_metadatum, inverse_of: :package, class_name: 'Packages::Maven::Metadatum'
has_one :nuget_metadatum, inverse_of: :package, class_name: 'Packages::Nuget::Metadatum'
has_one :composer_metadatum, inverse_of: :package, class_name: 'Packages::Composer::Metadatum'
has_one :rubygems_metadatum, inverse_of: :package, class_name: 'Packages::Rubygems::Metadatum'
has_many :build_infos, inverse_of: :package
has_many :pipelines, through: :build_infos
has_one :debian_publication, inverse_of: :package, class_name: 'Packages::Debian::Publication'
......@@ -64,7 +65,9 @@ class Packages::Package < ApplicationRecord
if: :debian_package?
validate :forbidden_debian_changes, if: :debian?
enum package_type: { maven: 1, npm: 2, conan: 3, nuget: 4, pypi: 5, composer: 6, generic: 7, golang: 8, debian: 9 }
enum package_type: { maven: 1, npm: 2, conan: 3, nuget: 4, pypi: 5,
composer: 6, generic: 7, golang: 8, debian: 9,
rubygems: 10 }
scope :with_name, ->(name) { where(name: name) }
scope :with_name_like, ->(name) { where(arel_table[:name].matches(name)) }
......
# frozen_string_literal: true
module Packages
module Rubygems
class Metadatum < ApplicationRecord
self.table_name = 'packages_rubygems_metadata'
self.primary_key = :package_id
belongs_to :package, -> { where(package_type: :rubygems) }, inverse_of: :rubygems_metadatum
validates :package, presence: true
validate :rubygems_package_type
private
def rubygems_package_type
unless package&.rubygems?
errors.add(:base, _('Package type must be RubyGems'))
end
end
end
end
end
......@@ -59,13 +59,13 @@
%li.divider
- if user.can_be_removed?
%li
%button.delete-user-button.btn.btn-default-tertiary.text-danger{ data: { 'gl-modal-action': 'delete',
%button.js-delete-user-modal-button.btn.btn-default-tertiary.text-danger{ data: { 'gl-modal-action': 'delete',
delete_user_url: admin_user_path(user),
block_user_url: block_admin_user_path(user),
username: sanitize_name(user.name) } }
= s_('AdminUsers|Delete user')
%li
%button.delete-user-button.btn.btn-default-tertiary.text-danger{ data: { 'gl-modal-action': 'delete-with-contributions',
%button.js-delete-user-modal-button.btn.btn-default-tertiary.text-danger{ data: { 'gl-modal-action': 'delete-with-contributions',
delete_user_url: admin_user_path(user, hard_delete: true),
block_user_url: block_admin_user_path(user),
username: sanitize_name(user.name) } }
......
......@@ -205,7 +205,7 @@
%p Deleting a user has the following effects:
= render 'users/deletion_guidance', user: @user
%br
%button.delete-user-button.btn.gl-button.btn-danger{ data: { 'gl-modal-action': 'delete',
%button.js-delete-user-modal-button.btn.gl-button.btn-danger{ data: { 'gl-modal-action': 'delete',
delete_user_url: admin_user_path(@user),
block_user_url: block_admin_user_path(@user),
username: sanitize_name(@user.name) } }
......@@ -235,7 +235,7 @@
the user, and projects in them, will also be removed. Commits
to other projects are unaffected.
%br
%button.delete-user-button.btn.gl-button.btn-danger{ data: { 'gl-modal-action': 'delete-with-contributions',
%button.js-delete-user-modal-button.btn.gl-button.btn-danger{ data: { 'gl-modal-action': 'delete-with-contributions',
delete_user_url: admin_user_path(@user, hard_delete: true),
block_user_url: block_admin_user_path(@user),
username: @user.name } }
......
---
title: Add rubygems metadata table
merge_request: 52639
author:
type: added
---
title: Fix missing known usage data event
merge_request: 53729
author:
type: fixed
# frozen_string_literal: true
class AddRubygemsMaxFileSizeToPlanLimits < ActiveRecord::Migration[6.0]
DOWNTIME = false
def change
add_column :plan_limits, :rubygems_max_file_size, :bigint, default: 3.gigabytes, null: false
end
end
# frozen_string_literal: true
class CreatePackagesRubygemsMetadata < ActiveRecord::Migration[6.0]
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
def up
create_table_with_constraints :packages_rubygems_metadata, id: false do |t|
t.timestamps_with_timezone
t.references :package, primary_key: true, index: false, default: nil, null: false, foreign_key: { to_table: :packages_packages, on_delete: :cascade }, type: :bigint
t.text :authors
t.text :files
t.text :summary
t.text :description
t.text :email
t.text :homepage
t.text :licenses
t.text :metadata
t.text :author
t.text :bindir
t.text :cert_chain
t.text :executables
t.text :extensions
t.text :extra_rdoc_files
t.text :platform
t.text :post_install_message
t.text :rdoc_options
t.text :require_paths
t.text :required_ruby_version
t.text :required_rubygems_version
t.text :requirements
t.text :rubygems_version
t.text :signing_key
t.text_limit :authors, 255
t.text_limit :files, 255
t.text_limit :summary, 1024
t.text_limit :description, 1024
t.text_limit :email, 255
t.text_limit :homepage, 255
t.text_limit :licenses, 255
t.text_limit :metadata, 255
t.text_limit :author, 255
t.text_limit :bindir, 255
t.text_limit :cert_chain, 255
t.text_limit :executables, 255
t.text_limit :extensions, 255
t.text_limit :extra_rdoc_files, 255
t.text_limit :platform, 255
t.text_limit :post_install_message, 255
t.text_limit :rdoc_options, 255
t.text_limit :require_paths, 255
t.text_limit :required_ruby_version, 255
t.text_limit :required_rubygems_version, 255
t.text_limit :requirements, 255
t.text_limit :rubygems_version, 255
t.text_limit :signing_key, 255
end
end
def down
drop_table :packages_rubygems_metadata
end
end
4105ae45742c2eda67fe5c54256732e55555ab7832e4cbf0fcb041599c23bd29
\ No newline at end of file
ec6832ba26fca8d8427383cd0189765191a0a7f17bb78d61b900c5b541d5725e
\ No newline at end of file
......@@ -15256,6 +15256,58 @@ CREATE TABLE packages_pypi_metadata (
CONSTRAINT check_379019d5da CHECK ((char_length(required_python) <= 255))
);
CREATE TABLE packages_rubygems_metadata (
created_at timestamp with time zone NOT NULL,
updated_at timestamp with time zone NOT NULL,
package_id bigint NOT NULL,
authors text,
files text,
summary text,
description text,
email text,
homepage text,
licenses text,
metadata text,
author text,
bindir text,
cert_chain text,
executables text,
extensions text,
extra_rdoc_files text,
platform text,
post_install_message text,
rdoc_options text,
require_paths text,
required_ruby_version text,
required_rubygems_version text,
requirements text,
rubygems_version text,
signing_key text,
CONSTRAINT check_0154a18c82 CHECK ((char_length(description) <= 1024)),
CONSTRAINT check_22814c771b CHECK ((char_length(email) <= 255)),
CONSTRAINT check_242293030e CHECK ((char_length(extensions) <= 255)),
CONSTRAINT check_27619a7922 CHECK ((char_length(rubygems_version) <= 255)),
CONSTRAINT check_3d1b6f3a39 CHECK ((char_length(post_install_message) <= 255)),
CONSTRAINT check_545f7606f9 CHECK ((char_length(required_rubygems_version) <= 255)),
CONSTRAINT check_5988451714 CHECK ((char_length(executables) <= 255)),
CONSTRAINT check_5f9c84ea17 CHECK ((char_length(platform) <= 255)),
CONSTRAINT check_64f1cecf05 CHECK ((char_length(requirements) <= 255)),
CONSTRAINT check_6ac7043c50 CHECK ((char_length(extra_rdoc_files) <= 255)),
CONSTRAINT check_6ff3abe325 CHECK ((char_length(cert_chain) <= 255)),
CONSTRAINT check_7cb01436df CHECK ((char_length(licenses) <= 255)),
CONSTRAINT check_8be21d92e7 CHECK ((char_length(summary) <= 1024)),
CONSTRAINT check_946cb96acb CHECK ((char_length(homepage) <= 255)),
CONSTRAINT check_9824fc9efc CHECK ((char_length(bindir) <= 255)),
CONSTRAINT check_994b68eb64 CHECK ((char_length(authors) <= 255)),
CONSTRAINT check_9d42fa48ae CHECK ((char_length(signing_key) <= 255)),
CONSTRAINT check_b0f4f8c853 CHECK ((char_length(files) <= 255)),
CONSTRAINT check_b7b296b420 CHECK ((char_length(author) <= 255)),
CONSTRAINT check_bf16b21a47 CHECK ((char_length(rdoc_options) <= 255)),
CONSTRAINT check_ca641a3354 CHECK ((char_length(required_ruby_version) <= 255)),
CONSTRAINT check_ea02f4800f CHECK ((char_length(metadata) <= 255)),
CONSTRAINT check_f76bad1a9a CHECK ((char_length(require_paths) <= 255))
);
CREATE TABLE packages_tags (
id bigint NOT NULL,
package_id integer NOT NULL,
......@@ -15467,7 +15519,8 @@ CREATE TABLE plan_limits (
project_feature_flags integer DEFAULT 200 NOT NULL,
ci_max_artifact_size_api_fuzzing integer DEFAULT 0 NOT NULL,
ci_pipeline_deployments integer DEFAULT 500 NOT NULL,
pull_mirror_interval_seconds integer DEFAULT 300 NOT NULL
pull_mirror_interval_seconds integer DEFAULT 300 NOT NULL,
rubygems_max_file_size bigint DEFAULT '3221225472'::bigint NOT NULL
);
CREATE SEQUENCE plan_limits_id_seq
......@@ -20511,6 +20564,9 @@ ALTER TABLE ONLY packages_packages
ALTER TABLE ONLY packages_pypi_metadata
ADD CONSTRAINT packages_pypi_metadata_pkey PRIMARY KEY (package_id);
ALTER TABLE ONLY packages_rubygems_metadata
ADD CONSTRAINT packages_rubygems_metadata_pkey PRIMARY KEY (package_id);
ALTER TABLE ONLY packages_tags
ADD CONSTRAINT packages_tags_pkey PRIMARY KEY (id);
......@@ -25523,6 +25579,9 @@ ALTER TABLE ONLY scim_identities
ALTER TABLE ONLY packages_debian_project_distributions
ADD CONSTRAINT fk_rails_94b95e1f84 FOREIGN KEY (creator_id) REFERENCES users(id) ON DELETE SET NULL;
ALTER TABLE ONLY packages_rubygems_metadata
ADD CONSTRAINT fk_rails_95a3f5ce78 FOREIGN KEY (package_id) REFERENCES packages_packages(id) ON DELETE CASCADE;
ALTER TABLE ONLY packages_pypi_metadata
ADD CONSTRAINT fk_rails_9698717cdd FOREIGN KEY (package_id) REFERENCES packages_packages(id) ON DELETE CASCADE;
......
......@@ -17829,6 +17829,11 @@ enum PackageTypeEnum {
Packages from the PyPI package manager
"""
PYPI
"""
Packages from the Rubygems package manager
"""
RUBYGEMS
}
"""
......
......@@ -52437,6 +52437,12 @@
"description": "Packages from the Debian package manager",
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "RUBYGEMS",
"description": "Packages from the Rubygems package manager",
"isDeprecated": false,
"deprecationReason": null
}
],
"possibleTypes": null
......@@ -5138,6 +5138,7 @@ Rotation length unit of an on-call rotation.
| `NPM` | Packages from the NPM package manager |
| `NUGET` | Packages from the Nuget package manager |
| `PYPI` | Packages from the PyPI package manager |
| `RUBYGEMS` | Packages from the Rubygems package manager |
### PipelineConfigSourceEnum
......
......@@ -294,6 +294,7 @@ See [database guidelines](database/index.md).
- [Compatibility with multiple versions of the application running at the same time](multi_version_compatibility.md)
- [Features inside `.gitlab/`](features_inside_dot_gitlab.md)
- [Dashboards for stage groups](stage_group_dashboards.md)
- [Preventing transient bugs](transient/prevention-patterns.md)
## Other GitLab Development Kit (GDK) guides
......
---
stage: none
group: unassigned
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments
---
# Preventing Transient Bugs
This page will cover architectural patterns and tips for developers to follow to prevent [transient bugs.](https://about.gitlab.com/handbook/engineering/quality/issue-triage/#transient-bugs)
## Frontend
### Don't rely on response order
When working with multiple requests, it's easy to assume the order of the responses will match the order in which they are triggered.
That's not always the case and can cause bugs that only happen if the order is switched.
**Example:**
- `diffs_metadata.json` (lighter)
- `diffs_batch.json` (heavier)
If your feature requires data from both, ensure that the two have finished loading before working on it.
### Simulate slower connections when testing manually
Add a network condition template to your browser's dev tools to enable you to toggle between a slow and a fast connection.
**Example:**
- Turtle:
- Down: 50kb/s
- Up: 20kb/s
- Latency: 10000ms
### Collapsed elements
When setting event listeners, if not possible to use event delegation, ensure all relevant event listeners are set for expanded content.
Including when that expanded content is:
- **Invisible** (`display: none;`). Some JavaScript requires the element to be visible to work properly (eg.: when taking measurements).
- **Dynamic content** (AJAX/DOM manipulation).
### Using assertions to detect transient bugs caused by unmet conditions
Transient bugs happen in the context of code that executes under the assumption
that the application’s state meets one or more conditions. We may write a feature
that assumes a server-side API response always include a group of attributes or that
an operation only executes when the application has successfully transitioned to a new
state.
Transient bugs are difficult to debug because there isn’t any mechanism that alerts
the user or the developer about unsatisfied conditions. These conditions are usually
not expressed explicitly in the code. A useful debugging technique for such situations
is placing assertions to make any assumption explicit. They can help detect the cause
which unmet condition causes the bug.
#### Asserting pre-conditions on state mutations
A common scenario that leads to transient bugs is when there is a polling service
that should mutate state only if a user operation is completed. We can use
assertions to make this pre-condition explicit:
```javascript
// This action is called by a polling service. It assumes that all pre-conditions
// are satisfied by the time the action is dispatched.
export const updateMergeableStatus = ({ commit }, payload) => {
commit(types.SET_MERGEABLE_STATUS, payload);
};
// We can make any pre-condition explicit by adding an assertion
export const updateMergeableStatus = ({ state, commit }, payload) => {
console.assert(
state.isResolvingDiscussion === true,
'Resolve discussion request must be completed before updating mergeable status'
);
commit(types.SET_MERGEABLE_STATUS, payload);
};
```
#### Asserting API contracts
Another useful way of using assertions is to detect if the response payload returned
by the server-side endpoint satisfies the API contract.
#### Related reading
[Debug it!](https://pragprog.com/titles/pbdp/debug-it/) explores techniques to diagnose
and fix non-determinstic bugs and write software that is easier to debug.
## Backend
### Sidekiq jobs with locks
When dealing with asynchronous work via Sidekiq, it is possible to have 2 jobs with the same arguments
getting worked on at the same time. If not handled correctly, this can result in an outdated or inaccurate state.
For instance, consider a worker that updates a state of an object. Before the worker updates the state
(for example, `#update_state`) of the object, it needs to check what the appropriate state should be
(for example, `#check_state`).
When there are 2 jobs being worked on at the same time, it is possible that the order of operations will go like:
1. (Worker A) Calls `#check_state`
1. (Worker B) Calls `#check_state`
1. (Worker B) Calls `#update_state`
1. (Worker A) Calls `#update_state`
In this example, `Worker B` is meant to set the updated status. But `Worker A` calls `#update_state` a little too late.
This can be avoided by utilizing either database locks or `Gitlab::ExclusiveLease`. This way, jobs will be
worked on one at a time. This also allows them to be marked as [idempotent](../sidekiq_style_guide.md#idempotent-jobs).
### Retry mechanism handling
There are times that an object/record will be on a failed state which can be rechecked.
If an object is in a state that can be rechecked, ensure that appropriate messaging is shown to the user
so they know what to do. Also, make sure that the retry functionality will be able to reset the state
correctly when triggered.
### Error Logging
Error logging doesn't necessarily directly prevents transient bugs but it can help to debug them.
When coding, sometimes we expect some exceptions to be raised and we rescue them.
Logging whenever we rescue an error helps in case it's causing transient bugs that a user may see.
While investigating a bug report, it may require the engineer to look into logs of when it happened.
Seeing an error being logged can be a signal of something that went wrong which can be handled differently.
......@@ -603,6 +603,11 @@
redis_slot: ci_templates
aggregation: weekly
feature_flag: usage_data_track_ci_templates_unique_projects
- name: p_ci_templates_aws_deploy_ecs
category: ci_templates
redis_slot: ci_templates
aggregation: weekly
feature_flag: usage_data_track_ci_templates_unique_projects
- name: p_ci_templates_auto_devops_build
category: ci_templates
redis_slot: ci_templates
......
......@@ -20862,6 +20862,9 @@ msgstr ""
msgid "Package type must be PyPi"
msgstr ""
msgid "Package type must be RubyGems"
msgstr ""
msgid "PackageRegistry|%{boldStart}Allow duplicates%{boldEnd} - Packages with the same name and version are accepted."
msgstr ""
......
......@@ -21,6 +21,23 @@ FactoryBot.define do
end
end
factory :rubygems_package do
sequence(:name) { |n| "my_gem_#{n}" }
sequence(:version) { |n| "1.#{n}" }
package_type { :rubygems }
after :create do |package|
create :package_file, :gem, package: package
create :package_file, :gemspec, package: package
end
trait(:with_metadatum) do
after :build do |pkg|
pkg.rubygems_metadatum = build(:rubygems_metadatum)
end
end
end
factory :debian_package do
sequence(:name) { |n| "package-#{n}" }
sequence(:version) { |n| "1.0-#{n}" }
......
......@@ -221,6 +221,22 @@ FactoryBot.define do
size { 300.kilobytes }
end
trait(:gem) do
package
file_fixture { 'spec/fixtures/packages/rubygems/package-0.0.1.gem' }
file_name { 'package-0.0.1.gem' }
file_sha1 { '5fe852b2a6abd96c22c11fa1ff2fb19d9ce58b57' }
size { 4.kilobytes }
end
trait(:gemspec) do
package
file_fixture { 'spec/fixtures/packages/rubygems/package.gemspec' }
file_name { 'package.gemspec' }
file_sha1 { '5fe852b2a6abd96c22c11fa1ff2fb19d9ce58b57' }
size { 242.bytes }
end
trait(:pypi) do
package
file_fixture { 'spec/fixtures/packages/pypi/sample-project.tar.gz' }
......
# frozen_string_literal: true
FactoryBot.define do
factory :rubygems_metadatum, class: 'Packages::Rubygems::Metadatum' do
package { association(:rubygems_package) }
authors { FFaker::Name.name }
email { FFaker::Internet.email }
end
end
# frozen_string_literal: true
Gem::Specification.new do |s|
s.name = %q{package}
s.authors = ["Tanuki Steve"]
s.version = "0.0.1"
s.date = %q{2011-09-29}
s.summary = %q{package is the best}
s.files = [
"lib/package.rb"
]
s.required_ruby_version = '>= 2.7.0'
s.rubygems_version = '>= 1.8.11'
s.require_paths = ["lib"]
end
......@@ -3,6 +3,8 @@ import UserModalManager from '~/pages/admin/users/components/user_modal_manager.
import ModalStub from './stubs/modal_stub';
describe('Users admin page Modal Manager', () => {
let wrapper;
const modalConfiguration = {
action1: {
title: 'action1',
......@@ -14,11 +16,12 @@ describe('Users admin page Modal Manager', () => {
},
};
let wrapper;
const findModal = () => wrapper.find({ ref: 'modal' });
const createComponent = (props = {}) => {
wrapper = mount(UserModalManager, {
propsData: {
selector: '.js-delete-user-modal-button',
modalConfiguration,
csrfToken: 'dummyCSRF',
...props,
......@@ -37,7 +40,7 @@ describe('Users admin page Modal Manager', () => {
describe('render behavior', () => {
it('does not renders modal when initialized', () => {
createComponent();
expect(wrapper.find({ ref: 'modal' }).exists()).toBeFalsy();
expect(findModal().exists()).toBeFalsy();
});
it('throws if action has no proper configuration', () => {
......@@ -55,7 +58,7 @@ describe('Users admin page Modal Manager', () => {
});
return wrapper.vm.$nextTick().then(() => {
const modal = wrapper.find({ ref: 'modal' });
const modal = findModal();
expect(modal.exists()).toBeTruthy();
expect(modal.vm.$attrs.csrfToken).toEqual('dummyCSRF');
expect(modal.vm.$attrs.extraProp).toEqual('extraPropValue');
......@@ -64,68 +67,60 @@ describe('Users admin page Modal Manager', () => {
});
});
describe('global listener', () => {
describe('click handling', () => {
let button;
let button2;
const createButtons = () => {
button = document.createElement('button');
button2 = document.createElement('button');
button.setAttribute('class', 'js-delete-user-modal-button');
button.setAttribute('data-username', 'foo');
button.setAttribute('data-gl-modal-action', 'action1');
button.setAttribute('data-block-user-url', '/block');
button.setAttribute('data-delete-user-url', '/delete');
document.body.appendChild(button);
document.body.appendChild(button2);
};
const removeButtons = () => {
button.remove();
button = null;
button2.remove();
button2 = null;
};
beforeEach(() => {
jest.spyOn(document, 'addEventListener');
jest.spyOn(document, 'removeEventListener');
createButtons();
createComponent();
});
afterAll(() => {
jest.restoreAllMocks();
afterEach(() => {
removeButtons();
});
it('registers global listener on mount', () => {
createComponent();
expect(document.addEventListener).toHaveBeenCalledWith('click', expect.any(Function));
});
it('renders the modal when the button is clicked', async () => {
button.click();
it('removes global listener on destroy', () => {
createComponent();
wrapper.destroy();
expect(document.removeEventListener).toHaveBeenCalledWith('click', expect.any(Function));
await wrapper.vm.$nextTick();
expect(findModal().exists()).toBe(true);
});
});
describe('click handling', () => {
let node;
it('does not render the modal when a misconfigured button is clicked', async () => {
button.removeAttribute('data-gl-modal-action');
button.click();
beforeEach(() => {
node = document.createElement('div');
document.body.appendChild(node);
});
await wrapper.vm.$nextTick();
afterEach(() => {
node.remove();
node = null;
expect(findModal().exists()).toBe(false);
});
it('ignores wrong clicks', () => {
createComponent();
const event = new window.MouseEvent('click', {
bubbles: true,
cancellable: true,
});
jest.spyOn(event, 'preventDefault');
node.dispatchEvent(event);
expect(event.preventDefault).not.toHaveBeenCalled();
});
it('does not render the modal when a button without the selector class is clicked', async () => {
button2.click();
it('captures click with glModalAction', () => {
createComponent();
node.dataset.glModalAction = 'action1';
const event = new window.MouseEvent('click', {
bubbles: true,
cancellable: true,
});
jest.spyOn(event, 'preventDefault');
node.dispatchEvent(event);
await wrapper.vm.$nextTick();
expect(event.preventDefault).toHaveBeenCalled();
return wrapper.vm.$nextTick().then(() => {
const modal = wrapper.find({ ref: 'modal' });
expect(modal.exists()).toBeTruthy();
expect(modal.vm.showWasCalled).toBeTruthy();
});
expect(findModal().exists()).toBe(false);
});
});
});
......@@ -4,6 +4,6 @@ require 'spec_helper'
RSpec.describe GitlabSchema.types['PackageTypeEnum'] do
it 'exposes all package types' do
expect(described_class.values.keys).to contain_exactly(*%w[MAVEN NPM CONAN NUGET PYPI COMPOSER GENERIC GOLANG DEBIAN])
expect(described_class.values.keys).to contain_exactly(*%w[MAVEN NPM CONAN NUGET PYPI COMPOSER GENERIC GOLANG DEBIAN RUBYGEMS])
end
end
......@@ -17,6 +17,7 @@ RSpec.describe Packages::Package, type: :model do
it { is_expected.to have_one(:debian_publication).inverse_of(:package).class_name('Packages::Debian::Publication') }
it { is_expected.to have_one(:debian_distribution).through(:debian_publication).source(:distribution).inverse_of(:packages).class_name('Packages::Debian::ProjectDistribution') }
it { is_expected.to have_one(:nuget_metadatum).inverse_of(:package) }
it { is_expected.to have_one(:rubygems_metadatum).inverse_of(:package) }
end
describe '.with_composer_target' do
......
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Packages::Rubygems::Metadatum, type: :model do
describe 'relationships' do
it { is_expected.to belong_to(:package) }
end
describe 'validations' do
it { is_expected.to validate_presence_of(:package) }
describe '#rubygems_package_type' do
it 'will not allow a package with a different package_type' do
package = build('conan_package')
rubygems_metadatum = build('rubygems_metadatum', package: package)
expect(rubygems_metadatum).not_to be_valid
expect(rubygems_metadatum.errors.to_a).to include('Package type must be RubyGems')
end
end
end
end
......@@ -7,7 +7,11 @@
RSpec.shared_examples 'tracking unique hll events' do |feature_flag|
it 'tracks unique event' do
expect(Gitlab::UsageDataCounters::HLLRedisCounter).to receive(:track_event).with(target_id, values: expected_type)
expect(Gitlab::UsageDataCounters::HLLRedisCounter).to(
receive(:track_event)
.with(target_id, values: expected_type)
.and_call_original # we call original to trigger additional validations; otherwise the method is stubbed
)
request
end
......
......@@ -190,6 +190,7 @@ RSpec.shared_examples 'filters on each package_type' do |is_project: false|
let_it_be(:package7) { create(:generic_package, project: project) }
let_it_be(:package8) { create(:golang_package, project: project) }
let_it_be(:package9) { create(:debian_package, project: project) }
let_it_be(:package9) { create(:rubygems_package, project: project) }
Packages::Package.package_types.keys.each do |package_type|
context "for package type #{package_type}" do
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment