Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
90e7f31698
commit
14facaa466
20 changed files with 140 additions and 80 deletions
|
@ -100,7 +100,7 @@ export const initNewAccessTokenApp = () => {
|
|||
export const initTokensApp = () => {
|
||||
const el = document.getElementById('js-tokens-app');
|
||||
|
||||
if (!el) return false;
|
||||
if (!el) return null;
|
||||
|
||||
const tokensData = convertObjectPropsToCamelCase(JSON.parse(el.dataset.tokensData), {
|
||||
deep: true,
|
||||
|
|
|
@ -17,6 +17,9 @@ module Routable
|
|||
def self.find_by_full_path(path, follow_redirects: false, route_scope: Route, redirect_route_scope: RedirectRoute)
|
||||
return unless path.present?
|
||||
|
||||
# Convert path to string to prevent DB error: function lower(integer) does not exist
|
||||
path = path.to_s
|
||||
|
||||
# Case sensitive match first (it's cheaper and the usual case)
|
||||
# If we didn't have an exact match, we perform a case insensitive search
|
||||
#
|
||||
|
|
|
@ -781,9 +781,9 @@ Gitlab.ee do
|
|||
Settings.cron_jobs['security_orchestration_policy_rule_schedule_worker'] ||= Settingslogic.new({})
|
||||
Settings.cron_jobs['security_orchestration_policy_rule_schedule_worker']['cron'] ||= '*/15 * * * *'
|
||||
Settings.cron_jobs['security_orchestration_policy_rule_schedule_worker']['job_class'] = 'Security::OrchestrationPolicyRuleScheduleWorker'
|
||||
Settings.cron_jobs['security_findings_cleanup_worker'] ||= Settingslogic.new({})
|
||||
Settings.cron_jobs['security_findings_cleanup_worker']['cron'] ||= '0 */4 * * 6,0'
|
||||
Settings.cron_jobs['security_findings_cleanup_worker']['job_class'] = 'Security::Findings::CleanupWorker'
|
||||
Settings.cron_jobs['security_scans_purge_worker'] ||= Settingslogic.new({})
|
||||
Settings.cron_jobs['security_scans_purge_worker']['cron'] ||= '0 */4 * * 6,0'
|
||||
Settings.cron_jobs['security_scans_purge_worker']['job_class'] = 'Security::Scans::PurgeWorker'
|
||||
Settings.cron_jobs['app_sec_dast_profile_schedule_worker'] ||= Settingslogic.new({})
|
||||
Settings.cron_jobs['app_sec_dast_profile_schedule_worker']['cron'] ||= '7-59/15 * * * *'
|
||||
Settings.cron_jobs['app_sec_dast_profile_schedule_worker']['job_class'] = 'AppSec::Dast::ProfileScheduleWorker'
|
||||
|
|
|
@ -453,14 +453,14 @@
|
|||
- 1
|
||||
- - security_auto_fix
|
||||
- 1
|
||||
- - security_findings_delete_by_job_id
|
||||
- 1
|
||||
- - security_orchestration_policy_rule_schedule_namespace
|
||||
- 1
|
||||
- - security_process_scan_result_policy
|
||||
- 1
|
||||
- - security_scans
|
||||
- 2
|
||||
- - security_scans_purge_by_job_id
|
||||
- 1
|
||||
- - security_sync_scan_policies
|
||||
- 1
|
||||
- - self_monitoring_project_create
|
||||
|
|
|
@ -0,0 +1,16 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class MigrateSecurityFindingsDeleteQueues < Gitlab::Database::Migration[2.0]
|
||||
restrict_gitlab_migration gitlab_schema: :gitlab_main
|
||||
disable_ddl_transaction!
|
||||
|
||||
def up
|
||||
sidekiq_queue_migrate 'security_findings_delete_by_job_id', to: 'security_scans_purge_by_job_id'
|
||||
sidekiq_queue_migrate 'cronjob:security_findings_cleanup', to: 'cronjob:security_scans_purge'
|
||||
end
|
||||
|
||||
def down
|
||||
sidekiq_queue_migrate 'security_scans_purge_by_job_id', to: 'security_findings_delete_by_job_id'
|
||||
sidekiq_queue_migrate 'cronjob:security_scans_purge', to: 'cronjob:security_findings_cleanup'
|
||||
end
|
||||
end
|
1
db/schema_migrations/20221010074914
Normal file
1
db/schema_migrations/20221010074914
Normal file
|
@ -0,0 +1 @@
|
|||
c5ef65edf6e87495bc4dc16c636b2f2d8cbd63f3903cf5ed1364206b83411ba9
|
|
@ -19,9 +19,9 @@ POST /import/github
|
|||
| `personal_access_token` | string | yes | GitHub personal access token |
|
||||
| `repo_id` | integer | yes | GitHub repository ID |
|
||||
| `new_name` | string | no | New repository name |
|
||||
| `target_namespace` | string | yes | Namespace to import repository into. Supports subgroups like `/namespace/subgroup`. |
|
||||
| `target_namespace` | string | yes | Namespace to import repository into. Supports subgroups like `/namespace/subgroup` |
|
||||
| `github_hostname` | string | no | Custom GitHub Enterprise hostname. Do not set for GitHub.com. |
|
||||
| `optional_stages` | object | no | [Additional items to import](../user/project/import/github.md#select-additional-items-to-import)|
|
||||
| `optional_stages` | object | no | [Additional items to import](../user/project/import/github.md#select-additional-items-to-import). [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/373705) in GitLab 15.5 |
|
||||
|
||||
```shell
|
||||
curl --request POST \
|
||||
|
|
|
@ -365,7 +365,8 @@ Example response:
|
|||
"last_activity_on": "2021-01-27",
|
||||
"membership_type": "group_member",
|
||||
"removable": true,
|
||||
"created_at": "2021-01-03T12:16:02.000Z"
|
||||
"created_at": "2021-01-03T12:16:02.000Z",
|
||||
"last_login_at": "2022-10-09T01:33:06.000Z"
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
|
@ -378,7 +379,8 @@ Example response:
|
|||
"last_activity_on": "2021-01-25",
|
||||
"membership_type": "group_member",
|
||||
"removable": true,
|
||||
"created_at": "2021-01-04T18:46:42.000Z"
|
||||
"created_at": "2021-01-04T18:46:42.000Z",
|
||||
"last_login_at": "2022-09-29T22:18:46.000Z"
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
|
@ -390,7 +392,8 @@ Example response:
|
|||
"last_activity_on": "2021-01-20",
|
||||
"membership_type": "group_invite",
|
||||
"removable": false,
|
||||
"created_at": "2021-01-09T07:12:31.000Z"
|
||||
"created_at": "2021-01-09T07:12:31.000Z",
|
||||
"last_login_at": "2022-10-10T07:28:56.000Z"
|
||||
}
|
||||
]
|
||||
```
|
||||
|
|
|
@ -823,19 +823,38 @@ needed in the GitLab test suite (under `app/assets/javascripts/locale/**/app.js`
|
|||
and `public/assets`).
|
||||
|
||||
- If the package URL returns a 404:
|
||||
1. It runs `bin/rake gitlab:assets:compile`, so that the GitLab assets are compiled.
|
||||
1. It then creates an archive which contains the assets and upload it [as a generic package](https://gitlab.com/gitlab-org/gitlab/-/packages/).
|
||||
1. It runs `bin/rake gitlab:assets:compile`, so that the GitLab assets are compiled.
|
||||
1. It then creates an archive which contains the assets and uploads it [as a generic package](https://gitlab.com/gitlab-org/gitlab/-/packages/).
|
||||
The package version is set to the assets folders' hash sum.
|
||||
- Otherwise, if the package already exists, it exits the job successfully.
|
||||
|
||||
#### `compile-*-assets`
|
||||
|
||||
We also changed the `compile-test-assets`, `compile-test-assets as-if-foss`,
|
||||
and `compile-production-assets` jobs to:
|
||||
|
||||
1. First download the GitLab assets generic package build and uploaded by `cache-assets:*`.
|
||||
1. If the package is retrieved successfully, assets aren't compiled.
|
||||
1. If the package URL returns a 404, the behavior doesn't change compared to the current one: the GitLab assets are compiled as part of `bin/rake gitlab:assets:compile`.
|
||||
1. First download the "native" cache assets, which contain:
|
||||
- The [compiled assets](https://gitlab.com/gitlab-org/gitlab/-/blob/a6910c9086bb28e553f5e747ec2dd50af6da3c6b/.gitlab/ci/global.gitlab-ci.yml#L86-87).
|
||||
- A [`cached-assets-hash.txt` file](https://gitlab.com/gitlab-org/gitlab/-/blob/a6910c9086bb28e553f5e747ec2dd50af6da3c6b/.gitlab/ci/global.gitlab-ci.yml#L85)
|
||||
containing the `SHA256` hexdigest of all the source files on which the assets depend on.
|
||||
This list of files is a pessimistic list and the assets might not depend on
|
||||
some of these files. At worst we compile the assets more often, which is better than
|
||||
using outdated assets.
|
||||
|
||||
NOTE:
|
||||
The version of the package is the assets folders hash sum.
|
||||
The file is [created after assets are compiled](https://gitlab.com/gitlab-org/gitlab/-/blob/a6910c9086bb28e553f5e747ec2dd50af6da3c6b/.gitlab/ci/frontend.gitlab-ci.yml#L83).
|
||||
1. We then we compute the `SHA256` hexdigest of all the source files the assets depend on, **for the current checked out branch**. We [store the hexdigest in the `GITLAB_ASSETS_HASH` variable](https://gitlab.com/gitlab-org/gitlab/-/blob/a6910c9086bb28e553f5e747ec2dd50af6da3c6b/.gitlab/ci/frontend.gitlab-ci.yml#L27).
|
||||
1. If `$CACHE_ASSETS_AS_PACKAGE == "true"`, we download the generic package built and uploaded by [`cache-assets:*`](#cache-assets).
|
||||
- If the cache is up-to-date for the checked out branch, we download the native cache
|
||||
**and** the cache package. We could optimize that by not downloading
|
||||
the genetic package but the native cache is actually very often outdated because it's
|
||||
rebuilt only every 2 hours.
|
||||
1. We [run the `assets_compile_script` function](https://gitlab.com/gitlab-org/gitlab/-/blob/a6910c9086bb28e553f5e747ec2dd50af6da3c6b/.gitlab/ci/frontend.gitlab-ci.yml#L35),
|
||||
which [itself runs](https://gitlab.com/gitlab-org/gitlab/-/blob/c023191ef412e868ae957f3341208a41ca678403/scripts/utils.sh#L76)
|
||||
the [`assets:compile` Rake task](https://gitlab.com/gitlab-org/gitlab/-/blob/c023191ef412e868ae957f3341208a41ca678403/lib/tasks/gitlab/assets.rake#L80-109).
|
||||
|
||||
This task is responsible for deciding if assets need to be compiled or not.
|
||||
It [compares the `HEAD` `SHA256` hexdigest from `$GITLAB_ASSETS_HASH` with the `master` hexdigest from `cached-assets-hash.txt`](https://gitlab.com/gitlab-org/gitlab/-/blob/c023191ef412e868ae957f3341208a41ca678403/lib/tasks/gitlab/assets.rake#L86).
|
||||
1. If the hashes are the same, we don't compile anything. If they're different, we compile the assets.
|
||||
|
||||
### Pre-clone step
|
||||
|
||||
|
|
|
@ -120,6 +120,8 @@ your GitLab account and sign in again, or revoke the older personal access token
|
|||
|
||||
### Select additional items to import
|
||||
|
||||
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/373705) in GitLab 15.5.
|
||||
|
||||
To make imports as fast as possible, the following items aren't imported from GitHub by default:
|
||||
|
||||
- Issue and pull request events. For example, _opened_ or _closed_, _renamed_, and _labeled_ or _unlabeled_.
|
||||
|
|
|
@ -20,6 +20,8 @@ module API
|
|||
use :pagination
|
||||
use :event_filter_params
|
||||
use :sort_params
|
||||
optional :scope, type: String, desc: 'Include all events across a user\'s projects',
|
||||
documentation: { example: 'all' }
|
||||
end
|
||||
|
||||
get do
|
||||
|
|
|
@ -18,6 +18,7 @@ module API
|
|||
API_TOKEN_ENV = 'gitlab.api.token'
|
||||
API_EXCEPTION_ENV = 'gitlab.api.exception'
|
||||
API_RESPONSE_STATUS_CODE = 'gitlab.api.response_status_code'
|
||||
INTEGER_ID_REGEX = /^-?\d+$/.freeze
|
||||
|
||||
def declared_params(options = {})
|
||||
options = { include_parent_namespaces: false }.merge(options)
|
||||
|
@ -139,7 +140,7 @@ module API
|
|||
|
||||
projects = Project.without_deleted.not_hidden
|
||||
|
||||
if id.is_a?(Integer) || id =~ /^\d+$/
|
||||
if id.is_a?(Integer) || id =~ INTEGER_ID_REGEX
|
||||
projects.find_by(id: id)
|
||||
elsif id.include?("/")
|
||||
projects.find_by_full_path(id)
|
||||
|
@ -168,7 +169,7 @@ module API
|
|||
|
||||
# rubocop: disable CodeReuse/ActiveRecord
|
||||
def find_group(id)
|
||||
if id.to_s =~ /^\d+$/
|
||||
if id.to_s =~ INTEGER_ID_REGEX
|
||||
Group.find_by(id: id)
|
||||
else
|
||||
Group.find_by_full_path(id)
|
||||
|
@ -203,7 +204,7 @@ module API
|
|||
|
||||
# rubocop: disable CodeReuse/ActiveRecord
|
||||
def find_namespace(id)
|
||||
if id.to_s =~ /^\d+$/
|
||||
if id.to_s =~ INTEGER_ID_REGEX
|
||||
Namespace.without_project_namespaces.find_by(id: id)
|
||||
else
|
||||
find_namespace_by_path(id)
|
||||
|
|
|
@ -6,7 +6,7 @@ module API
|
|||
extend Grape::API::Helpers
|
||||
|
||||
params :event_filter_params do
|
||||
optional :action, type: String, values: Event.actions, desc: 'Event action to filter on'
|
||||
optional :action, type: String, values: Event.actions.keys, desc: 'Event action to filter on'
|
||||
optional :target_type, type: String, values: Event.target_types, desc: 'Event target type to filter on'
|
||||
optional :before, type: Date, desc: 'Include only events created before this date'
|
||||
optional :after, type: Date, desc: 'Include only events created after this date'
|
||||
|
|
|
@ -3,6 +3,8 @@
|
|||
module QA
|
||||
module Resource
|
||||
class ProjectImportedFromGithub < Resource::Project
|
||||
attr_accessor :issue_events_import, :full_notes_import, :attachments_import
|
||||
|
||||
attribute :github_repo_id do
|
||||
github_client.repository(github_repository_path).id
|
||||
end
|
||||
|
@ -51,7 +53,12 @@ module QA
|
|||
new_name: name,
|
||||
target_namespace: @personal_namespace || group.full_path,
|
||||
personal_access_token: github_personal_access_token,
|
||||
ci_cd_only: false
|
||||
ci_cd_only: false,
|
||||
optional_stages: {
|
||||
single_endpoint_issue_events_import: issue_events_import,
|
||||
single_endpoint_notes_import: full_notes_import,
|
||||
attachments_import: attachments_import
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
|
|
|
@ -21,6 +21,8 @@ module QA
|
|||
project.github_personal_access_token = Runtime::Env.github_access_token
|
||||
project.github_repository_path = 'gitlab-qa-github/import-test'
|
||||
project.api_client = Runtime::API::Client.new(user: user)
|
||||
project.issue_events_import = true
|
||||
project.full_notes_import = true
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -199,13 +199,11 @@ module QA
|
|||
project.github_repository_path = github_repo
|
||||
project.personal_namespace = user.username
|
||||
project.api_client = Runtime::API::Client.new(user: user)
|
||||
project.issue_events_import = true
|
||||
project.full_notes_import = true
|
||||
end
|
||||
end
|
||||
|
||||
before do
|
||||
Runtime::Feature.enable(:github_importer_single_endpoint_issue_events_import)
|
||||
end
|
||||
|
||||
after do |example|
|
||||
next unless defined?(@import_time)
|
||||
|
||||
|
|
|
@ -1,7 +1,4 @@
|
|||
/* eslint-disable vue/require-prop-types */
|
||||
/* eslint-disable vue/one-component-per-file */
|
||||
import { createWrapper } from '@vue/test-utils';
|
||||
import Vue from 'vue';
|
||||
import { setHTMLFixture, resetHTMLFixture } from 'helpers/fixtures';
|
||||
|
||||
import {
|
||||
|
@ -10,10 +7,11 @@ import {
|
|||
initNewAccessTokenApp,
|
||||
initTokensApp,
|
||||
} from '~/access_tokens';
|
||||
import * as AccessTokenTableApp from '~/access_tokens/components/access_token_table_app.vue';
|
||||
import AccessTokenTableApp from '~/access_tokens/components/access_token_table_app.vue';
|
||||
import ExpiresAtField from '~/access_tokens/components/expires_at_field.vue';
|
||||
import * as NewAccessTokenApp from '~/access_tokens/components/new_access_token_app.vue';
|
||||
import * as TokensApp from '~/access_tokens/components/tokens_app.vue';
|
||||
import NewAccessTokenApp from '~/access_tokens/components/new_access_token_app.vue';
|
||||
import TokensApp from '~/access_tokens/components/tokens_app.vue';
|
||||
import { FORM_SELECTOR } from '~/access_tokens/components/constants';
|
||||
import { FEED_TOKEN, INCOMING_EMAIL_TOKEN, STATIC_OBJECT_TOKEN } from '~/access_tokens/constants';
|
||||
import { __, sprintf } from '~/locale';
|
||||
|
||||
|
@ -30,25 +28,6 @@ describe('access tokens', () => {
|
|||
const accessTokenTypePlural = 'personal access tokens';
|
||||
const initialActiveAccessTokens = [{ revoked_path: '1' }];
|
||||
|
||||
const FakeAccessTokenTableApp = Vue.component('FakeComponent', {
|
||||
inject: [
|
||||
'accessTokenType',
|
||||
'accessTokenTypePlural',
|
||||
'initialActiveAccessTokens',
|
||||
'noActiveTokensMessage',
|
||||
'showRole',
|
||||
],
|
||||
props: [
|
||||
'accessTokenType',
|
||||
'accessTokenTypePlural',
|
||||
'initialActiveAccessTokens',
|
||||
'noActiveTokensMessage',
|
||||
'showRole',
|
||||
],
|
||||
render: () => null,
|
||||
});
|
||||
AccessTokenTableApp.default = FakeAccessTokenTableApp;
|
||||
|
||||
it('mounts the component and provides required values', () => {
|
||||
setHTMLFixture(
|
||||
`<div id="js-access-token-table-app"
|
||||
|
@ -60,19 +39,18 @@ describe('access tokens', () => {
|
|||
);
|
||||
|
||||
const vueInstance = initAccessTokenTableApp();
|
||||
|
||||
wrapper = createWrapper(vueInstance);
|
||||
const component = wrapper.findComponent(FakeAccessTokenTableApp);
|
||||
const component = wrapper.findComponent({ name: 'AccessTokenTableRoot' });
|
||||
|
||||
expect(component.exists()).toBe(true);
|
||||
|
||||
expect(component.props()).toMatchObject({
|
||||
expect(wrapper.findComponent(AccessTokenTableApp).vm).toMatchObject({
|
||||
// Required value
|
||||
accessTokenType,
|
||||
accessTokenTypePlural,
|
||||
initialActiveAccessTokens,
|
||||
|
||||
// Default values
|
||||
information: undefined,
|
||||
noActiveTokensMessage: sprintf(__('This user has no active %{accessTokenTypePlural}.'), {
|
||||
accessTokenTypePlural,
|
||||
}),
|
||||
|
@ -81,12 +59,14 @@ describe('access tokens', () => {
|
|||
});
|
||||
|
||||
it('mounts the component and provides all values', () => {
|
||||
const information = 'Additional information';
|
||||
const noActiveTokensMessage = 'This group has no active access tokens.';
|
||||
setHTMLFixture(
|
||||
`<div id="js-access-token-table-app"
|
||||
data-access-token-type="${accessTokenType}"
|
||||
data-access-token-type-plural="${accessTokenTypePlural}"
|
||||
data-initial-active-access-tokens=${JSON.stringify(initialActiveAccessTokens)}
|
||||
data-information="${information}"
|
||||
data-no-active-tokens-message="${noActiveTokensMessage}"
|
||||
data-show-role
|
||||
>
|
||||
|
@ -94,15 +74,15 @@ describe('access tokens', () => {
|
|||
);
|
||||
|
||||
const vueInstance = initAccessTokenTableApp();
|
||||
|
||||
wrapper = createWrapper(vueInstance);
|
||||
const component = wrapper.findComponent(FakeAccessTokenTableApp);
|
||||
const component = wrapper.findComponent({ name: 'AccessTokenTableRoot' });
|
||||
|
||||
expect(component.exists()).toBe(true);
|
||||
expect(component.props()).toMatchObject({
|
||||
expect(component.findComponent(AccessTokenTableApp).vm).toMatchObject({
|
||||
accessTokenType,
|
||||
accessTokenTypePlural,
|
||||
initialActiveAccessTokens,
|
||||
information,
|
||||
noActiveTokensMessage,
|
||||
showRole: true,
|
||||
});
|
||||
|
@ -157,23 +137,16 @@ describe('access tokens', () => {
|
|||
it('mounts the component and sets `accessTokenType` prop', () => {
|
||||
const accessTokenType = 'personal access token';
|
||||
setHTMLFixture(
|
||||
`<div id="js-new-access-token-app" data-access-token-type="${accessTokenType}"></div>`,
|
||||
`<div id="js-new-access-token-app" data-access-token-type="${accessTokenType}"></div>
|
||||
<form id="${FORM_SELECTOR.slice(1)}"></form>`,
|
||||
);
|
||||
|
||||
const FakeNewAccessTokenApp = Vue.component('FakeComponent', {
|
||||
inject: ['accessTokenType'],
|
||||
props: ['accessTokenType'],
|
||||
render: () => null,
|
||||
});
|
||||
NewAccessTokenApp.default = FakeNewAccessTokenApp;
|
||||
|
||||
const vueInstance = initNewAccessTokenApp();
|
||||
|
||||
wrapper = createWrapper(vueInstance);
|
||||
const component = wrapper.findComponent(FakeNewAccessTokenApp);
|
||||
const component = wrapper.findComponent({ name: 'NewAccessTokenRoot' });
|
||||
|
||||
expect(component.exists()).toBe(true);
|
||||
expect(component.props('accessTokenType')).toEqual(accessTokenType);
|
||||
expect(component.findComponent(NewAccessTokenApp).vm).toMatchObject({ accessTokenType });
|
||||
});
|
||||
|
||||
it('returns `null`', () => {
|
||||
|
@ -192,20 +165,12 @@ describe('access tokens', () => {
|
|||
`<div id="js-tokens-app" data-tokens-data=${JSON.stringify(tokensData)}></div>`,
|
||||
);
|
||||
|
||||
const FakeTokensApp = Vue.component('FakeComponent', {
|
||||
inject: ['tokenTypes'],
|
||||
props: ['tokenTypes'],
|
||||
render: () => null,
|
||||
});
|
||||
TokensApp.default = FakeTokensApp;
|
||||
|
||||
const vueInstance = initTokensApp();
|
||||
|
||||
wrapper = createWrapper(vueInstance);
|
||||
const component = wrapper.findComponent(FakeTokensApp);
|
||||
const component = wrapper.findComponent(TokensApp);
|
||||
|
||||
expect(component.exists()).toBe(true);
|
||||
expect(component.props('tokenTypes')).toEqual(tokensData);
|
||||
expect(component.vm).toMatchObject({ tokenTypes: tokensData });
|
||||
});
|
||||
|
||||
it('returns `null`', () => {
|
||||
|
|
|
@ -110,6 +110,13 @@ RSpec.describe API::Helpers do
|
|||
end
|
||||
end
|
||||
|
||||
context 'when ID is a negative number' do
|
||||
let(:existing_id) { project.id }
|
||||
let(:non_existing_id) { -1 }
|
||||
|
||||
it_behaves_like 'project finder'
|
||||
end
|
||||
|
||||
context 'when project is pending delete' do
|
||||
let(:project_pending_delete) { create(:project, pending_delete: true) }
|
||||
|
||||
|
@ -325,6 +332,13 @@ RSpec.describe API::Helpers do
|
|||
|
||||
it_behaves_like 'group finder'
|
||||
end
|
||||
|
||||
context 'when ID is a negative number' do
|
||||
let(:existing_id) { group.id }
|
||||
let(:non_existing_id) { -1 }
|
||||
|
||||
it_behaves_like 'group finder'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -421,6 +435,13 @@ RSpec.describe API::Helpers do
|
|||
|
||||
it_behaves_like 'namespace finder'
|
||||
end
|
||||
|
||||
context 'when ID is a negative number' do
|
||||
let(:existing_id) { namespace.id }
|
||||
let(:non_existing_id) { -1 }
|
||||
|
||||
it_behaves_like 'namespace finder'
|
||||
end
|
||||
end
|
||||
|
||||
shared_examples 'user namespace finder' do
|
||||
|
|
|
@ -23,6 +23,12 @@ RSpec.shared_examples 'routable resource' do
|
|||
end.not_to exceed_all_query_limit(control_count)
|
||||
end
|
||||
|
||||
context 'when path is a negative number' do
|
||||
it 'returns nil' do
|
||||
expect(described_class.find_by_full_path(-1)).to be_nil
|
||||
end
|
||||
end
|
||||
|
||||
context 'with redirect routes' do
|
||||
let_it_be(:redirect_route) { create(:redirect_route, source: record) }
|
||||
|
||||
|
|
|
@ -1166,6 +1166,20 @@ RSpec.describe User do
|
|||
'ORDER BY "users"."last_activity_on" ASC NULLS FIRST, "users"."id" DESC')
|
||||
end
|
||||
end
|
||||
|
||||
describe '.order_recent_sign_in' do
|
||||
it 'sorts users by current_sign_in_at in descending order' do
|
||||
expect(described_class.order_recent_sign_in.to_sql).to include(
|
||||
'ORDER BY "users"."current_sign_in_at" DESC NULLS LAST')
|
||||
end
|
||||
end
|
||||
|
||||
describe '.order_oldest_sign_in' do
|
||||
it 'sorts users by current_sign_in_at in ascending order' do
|
||||
expect(described_class.order_oldest_sign_in.to_sql).to include(
|
||||
'ORDER BY "users"."current_sign_in_at" ASC NULLS LAST')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'strip attributes' do
|
||||
|
|
Loading…
Reference in a new issue