Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
G
gitlab-ce
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
1
Merge Requests
1
Analytics
Analytics
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Commits
Issue Boards
Open sidebar
nexedi
gitlab-ce
Commits
1b4cd17b
Commit
1b4cd17b
authored
Dec 07, 2020
by
GitLab Release Tools Bot
Browse files
Options
Browse Files
Download
Plain Diff
Merge remote-tracking branch 'dev/master'
parents
a1349765
52cfce63
Changes
50
Hide whitespace changes
Inline
Side-by-side
Showing
50 changed files
with
1192 additions
and
60 deletions
+1192
-60
CHANGELOG-EE.md
CHANGELOG-EE.md
+21
-0
CHANGELOG.md
CHANGELOG.md
+48
-0
app/assets/javascripts/behaviors/markdown/render_mermaid.js
app/assets/javascripts/behaviors/markdown/render_mermaid.js
+26
-6
app/controllers/explore/projects_controller.rb
app/controllers/explore/projects_controller.rb
+3
-1
app/controllers/projects/feature_flags_controller.rb
app/controllers/projects/feature_flags_controller.rb
+3
-3
app/controllers/search_controller.rb
app/controllers/search_controller.rb
+0
-1
app/controllers/users_controller.rb
app/controllers/users_controller.rb
+1
-1
app/finders/projects_finder.rb
app/finders/projects_finder.rb
+4
-0
app/finders/starred_projects_finder.rb
app/finders/starred_projects_finder.rb
+11
-0
app/graphql/types/user_type.rb
app/graphql/types/user_type.rb
+4
-5
app/models/operations/feature_flags/user_list.rb
app/models/operations/feature_flags/user_list.rb
+5
-0
app/presenters/user_presenter.rb
app/presenters/user_presenter.rb
+14
-0
app/services/feature_flags/update_service.rb
app/services/feature_flags/update_service.rb
+11
-0
app/services/todos/destroy/entity_leave_service.rb
app/services/todos/destroy/entity_leave_service.rb
+4
-2
app/validators/zoom_url_validator.rb
app/validators/zoom_url_validator.rb
+6
-1
app/views/devise/confirmations/new.html.haml
app/views/devise/confirmations/new.html.haml
+1
-1
app/views/explore/projects/_projects.html.haml
app/views/explore/projects/_projects.html.haml
+5
-1
config/application.rb
config/application.rb
+1
-0
config/feature_categories.yml
config/feature_categories.yml
+1
-0
db/post_migrate/20201109114603_schedule_remove_inaccessible_epic_todos.rb
...20201109114603_schedule_remove_inaccessible_epic_todos.rb
+29
-0
db/schema_migrations/20201109114603
db/schema_migrations/20201109114603
+1
-0
doc/api/graphql/reference/gitlab_schema.graphql
doc/api/graphql/reference/gitlab_schema.graphql
+2
-2
doc/api/graphql/reference/gitlab_schema.json
doc/api/graphql/reference/gitlab_schema.json
+3
-3
doc/api/graphql/reference/index.md
doc/api/graphql/reference/index.md
+1
-1
doc/user/todos.md
doc/user/todos.md
+1
-1
ee/app/services/ee/todos/destroy/entity_leave_service.rb
ee/app/services/ee/todos/destroy/entity_leave_service.rb
+33
-0
ee/app/services/epics/update_service.rb
ee/app/services/epics/update_service.rb
+5
-0
ee/app/services/todos/destroy/confidential_epic_service.rb
ee/app/services/todos/destroy/confidential_epic_service.rb
+34
-0
ee/app/workers/all_queues.yml
ee/app/workers/all_queues.yml
+8
-0
ee/app/workers/todos_destroyer/confidential_epic_worker.rb
ee/app/workers/todos_destroyer/confidential_epic_worker.rb
+16
-0
ee/lib/ee/gitlab/background_migration/remove_inaccessible_epic_todos.rb
...ab/background_migration/remove_inaccessible_epic_todos.rb
+132
-0
ee/spec/lib/ee/gitlab/background_migration/remove_inaccessible_epic_todos_spec.rb
...ckground_migration/remove_inaccessible_epic_todos_spec.rb
+112
-0
ee/spec/migrations/schedule_remove_inaccessible_epic_todos_spec.rb
...igrations/schedule_remove_inaccessible_epic_todos_spec.rb
+30
-0
ee/spec/services/ee/todos/destroy/entity_leave_service_spec.rb
...ec/services/ee/todos/destroy/entity_leave_service_spec.rb
+45
-0
ee/spec/services/epics/update_service_spec.rb
ee/spec/services/epics/update_service_spec.rb
+6
-0
ee/spec/services/todos/destroy/confidential_epic_service_spec.rb
.../services/todos/destroy/confidential_epic_service_spec.rb
+50
-0
ee/spec/workers/todos_destroyer/confidential_epic_worker_spec.rb
.../workers/todos_destroyer/confidential_epic_worker_spec.rb
+14
-0
lib/gitlab/background_migration/remove_inaccessible_epic_todos.rb
...ab/background_migration/remove_inaccessible_epic_todos.rb
+13
-0
spec/controllers/confirmations_controller_spec.rb
spec/controllers/confirmations_controller_spec.rb
+80
-0
spec/controllers/projects/feature_flags_controller_spec.rb
spec/controllers/projects/feature_flags_controller_spec.rb
+34
-0
spec/controllers/search_controller_spec.rb
spec/controllers/search_controller_spec.rb
+1
-1
spec/controllers/users_controller_spec.rb
spec/controllers/users_controller_spec.rb
+78
-11
spec/features/explore/user_explores_projects_spec.rb
spec/features/explore/user_explores_projects_spec.rb
+11
-0
spec/features/markdown/mermaid_spec.rb
spec/features/markdown/mermaid_spec.rb
+65
-0
spec/finders/projects_finder_spec.rb
spec/finders/projects_finder_spec.rb
+23
-0
spec/finders/starred_projects_finder_spec.rb
spec/finders/starred_projects_finder_spec.rb
+48
-11
spec/requests/api/graphql/user/starred_projects_query_spec.rb
.../requests/api/graphql/user/starred_projects_query_spec.rb
+27
-0
spec/requests/api/graphql/user_query_spec.rb
spec/requests/api/graphql/user_query_spec.rb
+46
-2
spec/requests/api/projects_spec.rb
spec/requests/api/projects_spec.rb
+39
-6
spec/validators/zoom_url_validator_spec.rb
spec/validators/zoom_url_validator_spec.rb
+36
-0
No files found.
CHANGELOG-EE.md
View file @
1b4cd17b
Please view this file on the master branch, on stable branches it's out of date.
## 13.6.2 (2020-12-07)
### Security (1 change)
-
Cleanup todos for confidential epics that are no longer accessible by the user.
## 13.6.1 (2020-11-23)
-
No changes.
...
...
@@ -184,6 +191,13 @@ Please view this file on the master branch, on stable branches it's out of date.
-
Remove duplicated BS display properties from member overriding UI. !47126 (Takuya Noguchi)
## 13.5.5 (2020-12-07)
### Security (1 change)
-
Cleanup todos for confidential epics that are no longer accessible by the user.
## 13.5.4 (2020-11-13)
### Fixed (1 change)
...
...
@@ -429,6 +443,13 @@ Please view this file on the master branch, on stable branches it's out of date.
-
Remove bootstrap class in licensed user count. !45443
## 13.4.7 (2020-12-07)
### Security (1 change)
-
Cleanup todos for confidential epics that are no longer accessible by the user.
## 13.4.6 (2020-11-03)
### Fixed (1 change)
...
...
CHANGELOG.md
View file @
1b4cd17b
...
...
@@ -2,6 +2,22 @@
documentation
](
doc/development/changelog.md
)
for instructions on adding your own
entry.
## 13.6.2 (2020-12-07)
### Security (10 changes)
-
Validate zoom links to start with https only. !1055
-
Require at least 3 characters when searching for project in the Explore page.
-
Do not show emails of users in confirmation page.
-
Forbid setting a gitlabUserList strategy to a list from another project.
-
Fix mermaid resource consumption in GFM fields.
-
Ensure group and project memberships are not leaked via API for users with private profiles.
-
GraphQL User: do not expose email if set to private.
-
Filter search parameter to prevent data leaks.
-
Do not expose starred projects of users with private profile via API.
-
Do not show starred & contributed projects of users with private profile.
## 13.6.1 (2020-11-23)
### Fixed (5 changes)
...
...
@@ -529,6 +545,22 @@ entry.
-
Change wording on the project remove fork page. !47878
## 13.5.5 (2020-12-07)
### Security (10 changes)
-
Validate zoom links to start with https only. !1055
-
Require at least 3 characters when searching for project in the Explore page.
-
Do not show emails of users in confirmation page.
-
Forbid setting a gitlabUserList strategy to a list from another project.
-
Fix mermaid resource consumption in GFM fields.
-
Ensure group and project memberships are not leaked via API for users with private profiles.
-
GraphQL User: do not expose email if set to private.
-
Filter search parameter to prevent data leaks.
-
Do not expose starred projects of users with private profile via API.
-
Do not show starred & contributed projects of users with private profile.
## 13.5.4 (2020-11-13)
### Fixed (4 changes)
...
...
@@ -1148,6 +1180,22 @@ entry.
-
Bump cluster applications CI template. !45472
## 13.4.7 (2020-12-07)
### Security (10 changes)
-
Validate zoom links to start with https only. !1055
-
Require at least 3 characters when searching for project in the Explore page.
-
Do not show emails of users in confirmation page.
-
Forbid setting a gitlabUserList strategy to a list from another project.
-
Fix mermaid resource consumption in GFM fields.
-
Ensure group and project memberships are not leaked via API for users with private profiles.
-
GraphQL User: do not expose email if set to private.
-
Filter search parameter to prevent data leaks.
-
Do not expose starred projects of users with private profile via API.
-
Do not show starred & contributed projects of users with private profile.
## 13.4.6 (2020-11-03)
### Fixed (1 change)
...
...
app/assets/javascripts/behaviors/markdown/render_mermaid.js
View file @
1b4cd17b
...
...
@@ -18,7 +18,13 @@ import { __, sprintf } from '~/locale';
//
// This is an arbitrary number; Can be iterated upon when suitable.
const
MAX_CHAR_LIMIT
=
5000
;
const
MAX_CHAR_LIMIT
=
2000
;
// Max # of mermaid blocks that can be rendered in a page.
const
MAX_MERMAID_BLOCK_LIMIT
=
50
;
// Keep a map of mermaid blocks we've already rendered.
const
elsProcessingMap
=
new
WeakMap
();
let
renderedMermaidBlocks
=
0
;
let
mermaidModule
=
{};
function
importMermaidModule
()
{
...
...
@@ -110,13 +116,22 @@ function renderMermaids($els) {
let
renderedChars
=
0
;
$els
.
each
((
i
,
el
)
=>
{
// Skipping all the elements which we've already queued in requestIdleCallback
if
(
elsProcessingMap
.
has
(
el
))
{
return
;
}
const
{
source
}
=
fixElementSource
(
el
);
/**
* Restrict the rendering to a certain amount of character
to
*
prevent mermaidjs from hanging up the entire thread and
* causing a DoS.
* Restrict the rendering to a certain amount of character
*
and mermaid blocks to prevent mermaidjs from hanging
*
up the entire thread and
causing a DoS.
*/
if
((
source
&&
source
.
length
>
MAX_CHAR_LIMIT
)
||
renderedChars
>
MAX_CHAR_LIMIT
)
{
if
(
(
source
&&
source
.
length
>
MAX_CHAR_LIMIT
)
||
renderedChars
>
MAX_CHAR_LIMIT
||
renderedMermaidBlocks
>=
MAX_MERMAID_BLOCK_LIMIT
)
{
const
html
=
`
<div class="alert gl-alert gl-alert-warning alert-dismissible lazy-render-mermaid-container js-lazy-render-mermaid-container fade show" role="alert">
<div>
...
...
@@ -146,8 +161,13 @@ function renderMermaids($els) {
}
renderedChars
+=
source
.
length
;
renderedMermaidBlocks
+=
1
;
const
requestId
=
window
.
requestIdleCallback
(()
=>
{
renderMermaidEl
(
el
);
});
renderMermaidEl
(
el
);
elsProcessingMap
.
set
(
el
,
requestId
);
});
})
.
catch
(
err
=>
{
...
...
app/controllers/explore/projects_controller.rb
View file @
1b4cd17b
...
...
@@ -8,6 +8,8 @@ class Explore::ProjectsController < Explore::ApplicationController
include
SortingHelper
include
SortingPreference
MIN_SEARCH_LENGTH
=
3
before_action
:set_non_archived_param
before_action
:set_sorting
...
...
@@ -72,7 +74,7 @@ class Explore::ProjectsController < Explore::ApplicationController
def
load_projects
load_project_counts
projects
=
ProjectsFinder
.
new
(
current_user:
current_user
,
params:
params
).
execute
projects
=
ProjectsFinder
.
new
(
current_user:
current_user
,
params:
params
.
merge
(
minimum_search_length:
MIN_SEARCH_LENGTH
)
).
execute
projects
=
preload_associations
(
projects
)
projects
=
projects
.
page
(
params
[
:page
]).
without_count
...
...
app/controllers/projects/feature_flags_controller.rb
View file @
1b4cd17b
...
...
@@ -76,7 +76,7 @@ class Projects::FeatureFlagsController < Projects::ApplicationController
end
else
respond_to
do
|
format
|
format
.
json
{
render_error_json
(
result
[
:message
])
}
format
.
json
{
render_error_json
(
result
[
:message
]
,
result
[
:http_status
]
)
}
end
end
end
...
...
@@ -158,8 +158,8 @@ class Projects::FeatureFlagsController < Projects::ApplicationController
render
json:
feature_flag_json
(
feature_flag
),
status: :ok
end
def
render_error_json
(
messages
)
def
render_error_json
(
messages
,
status
=
:bad_request
)
render
json:
{
message:
messages
},
status:
:bad_request
status:
status
end
end
app/controllers/search_controller.rb
View file @
1b4cd17b
...
...
@@ -122,7 +122,6 @@ class SearchController < ApplicationController
payload
[
:metadata
]
||=
{}
payload
[
:metadata
][
'meta.search.group_id'
]
=
params
[
:group_id
]
payload
[
:metadata
][
'meta.search.project_id'
]
=
params
[
:project_id
]
payload
[
:metadata
][
'meta.search.search'
]
=
params
[
:search
]
payload
[
:metadata
][
'meta.search.scope'
]
=
params
[
:scope
]
payload
[
:metadata
][
'meta.search.filters.confidential'
]
=
params
[
:confidential
]
payload
[
:metadata
][
'meta.search.filters.state'
]
=
params
[
:state
]
...
...
app/controllers/users_controller.rb
View file @
1b4cd17b
...
...
@@ -19,7 +19,7 @@ class UsersController < ApplicationController
prepend_before_action
(
only:
[
:show
])
{
authenticate_sessionless_user!
(
:rss
)
}
before_action
:user
,
except:
[
:exists
,
:suggests
]
before_action
:authorize_read_user_profile!
,
only:
[
:calendar
,
:calendar_activities
,
:groups
,
:projects
,
:contributed
_projects
,
:starred_projects
,
:snippets
]
only:
[
:calendar
,
:calendar_activities
,
:groups
,
:projects
,
:contributed
,
:starred
,
:snippets
]
feature_category
:users
...
...
app/finders/projects_finder.rb
View file @
1b4cd17b
...
...
@@ -18,6 +18,7 @@
# personal: boolean
# search: string
# search_namespaces: boolean
# minimum_search_length: int
# non_archived: boolean
# archived: 'only' or boolean
# min_access_level: integer
...
...
@@ -182,6 +183,9 @@ class ProjectsFinder < UnionFinder
def
by_search
(
items
)
params
[
:search
]
||=
params
[
:name
]
return
items
.
none
if
params
[
:search
].
present?
&&
params
[
:minimum_search_length
].
present?
&&
params
[
:search
].
length
<
params
[
:minimum_search_length
].
to_i
items
.
optionally_search
(
params
[
:search
],
include_namespace:
params
[
:search_namespaces
].
present?
)
end
...
...
app/finders/starred_projects_finder.rb
View file @
1b4cd17b
# frozen_string_literal: true
class
StarredProjectsFinder
<
ProjectsFinder
include
Gitlab
::
Allowable
def
initialize
(
user
,
params:
{},
current_user:
nil
)
@user
=
user
super
(
params:
params
,
current_user:
current_user
,
project_ids_relation:
user
.
starred_projects
.
select
(
:id
)
)
end
def
execute
# Do not show starred projects if the user has a private profile.
return
Project
.
none
unless
can?
(
current_user
,
:read_user_profile
,
@user
)
super
end
end
app/graphql/types/user_type.rb
View file @
1b4cd17b
...
...
@@ -19,7 +19,8 @@ module Types
field
:state
,
Types
::
UserStateEnum
,
null:
false
,
description:
'State of the user'
field
:email
,
GraphQL
::
STRING_TYPE
,
null:
true
,
description:
'User email'
description:
'User email'
,
method: :public_email
,
deprecated:
{
reason:
'Use public_email'
,
milestone:
'13.7'
}
field
:public_email
,
GraphQL
::
STRING_TYPE
,
null:
true
,
description:
"User's public email"
field
:avatar_url
,
GraphQL
::
STRING_TYPE
,
null:
true
,
...
...
@@ -32,8 +33,7 @@ module Types
resolver:
Resolvers
::
TodoResolver
,
description:
'Todos of the user'
field
:group_memberships
,
Types
::
GroupMemberType
.
connection_type
,
null:
true
,
description:
'Group memberships of the user'
,
method: :group_members
description:
'Group memberships of the user'
field
:group_count
,
GraphQL
::
INT_TYPE
,
null:
true
,
resolver:
Resolvers
::
Users
::
GroupCountResolver
,
description:
'Group count for the user'
,
...
...
@@ -43,8 +43,7 @@ module Types
field
:location
,
::
GraphQL
::
STRING_TYPE
,
null:
true
,
description:
'The location of the user.'
field
:project_memberships
,
Types
::
ProjectMemberType
.
connection_type
,
null:
true
,
description:
'Project memberships of the user'
,
method: :project_members
description:
'Project memberships of the user'
field
:starred_projects
,
Types
::
ProjectType
.
connection_type
,
null:
true
,
description:
'Projects starred by the user'
,
resolver:
Resolvers
::
UserStarredProjectsResolver
...
...
app/models/operations/feature_flags/user_list.rb
View file @
1b4cd17b
...
...
@@ -28,6 +28,11 @@ module Operations
fuzzy_search
(
query
,
[
:name
],
use_minimum_char_limit:
false
)
end
def
self
.
belongs_to?
(
project_id
,
user_list_ids
)
uniq_ids
=
user_list_ids
.
uniq
where
(
id:
uniq_ids
,
project_id:
project_id
).
count
==
uniq_ids
.
count
end
private
def
ensure_no_associated_strategies
...
...
app/presenters/user_presenter.rb
View file @
1b4cd17b
...
...
@@ -2,4 +2,18 @@
class
UserPresenter
<
Gitlab
::
View
::
Presenter
::
Delegated
presents
:user
def
group_memberships
should_be_private?
?
GroupMember
.
none
:
user
.
group_members
end
def
project_memberships
should_be_private?
?
ProjectMember
.
none
:
user
.
project_members
end
private
def
should_be_private?
!
can?
(
current_user
,
:read_user_profile
,
user
)
end
end
app/services/feature_flags/update_service.rb
View file @
1b4cd17b
...
...
@@ -10,6 +10,7 @@ module FeatureFlags
def
execute
(
feature_flag
)
return
error
(
'Access Denied'
,
403
)
unless
can_update?
(
feature_flag
)
return
error
(
'Not Found'
,
404
)
unless
valid_user_list_ids?
(
feature_flag
,
user_list_ids
(
params
))
ActiveRecord
::
Base
.
transaction
do
feature_flag
.
assign_attributes
(
params
)
...
...
@@ -87,5 +88,15 @@ module FeatureFlags
def
can_update?
(
feature_flag
)
Ability
.
allowed?
(
current_user
,
:update_feature_flag
,
feature_flag
)
end
def
user_list_ids
(
params
)
params
.
fetch
(
:strategies_attributes
,
[])
.
select
{
|
s
|
s
[
:user_list_id
].
present?
}
.
map
{
|
s
|
s
[
:user_list_id
]
}
end
def
valid_user_list_ids?
(
feature_flag
,
user_list_ids
)
user_list_ids
.
empty?
||
::
Operations
::
FeatureFlags
::
UserList
.
belongs_to?
(
feature_flag
.
project_id
,
user_list_ids
)
end
end
end
app/services/todos/destroy/entity_leave_service.rb
View file @
1b4cd17b
...
...
@@ -22,7 +22,7 @@ module Todos
# if at least reporter, all entities including confidential issues can be accessed
return
if
user_has_reporter_access?
remove_confidential_
issu
e_todos
remove_confidential_
resourc
e_todos
if
entity
.
private?
remove_project_todos
...
...
@@ -40,7 +40,7 @@ module Todos
end
end
def
remove_confidential_
issu
e_todos
def
remove_confidential_
resourc
e_todos
Todo
.
for_target
(
confidential_issues
.
select
(
:id
))
.
for_type
(
Issue
.
name
)
...
...
@@ -133,3 +133,5 @@ module Todos
end
end
end
Todos
::
Destroy
::
EntityLeaveService
.
prepend_if_ee
(
'EE::Todos::Destroy::EntityLeaveService'
)
app/validators/zoom_url_validator.rb
View file @
1b4cd17b
...
...
@@ -5,8 +5,13 @@
# Custom validator for zoom urls
#
class
ZoomUrlValidator
<
ActiveModel
::
EachValidator
ALLOWED_SCHEMES
=
%w(https)
.
freeze
def
validate_each
(
record
,
attribute
,
value
)
return
if
Gitlab
::
ZoomLinkExtractor
.
new
(
value
).
links
.
size
==
1
links_count
=
Gitlab
::
ZoomLinkExtractor
.
new
(
value
).
links
.
size
valid
=
Gitlab
::
UrlSanitizer
.
valid?
(
value
,
allowed_schemes:
ALLOWED_SCHEMES
)
return
if
links_count
==
1
&&
valid
record
.
errors
.
add
(
:url
,
'must contain one valid Zoom URL'
)
end
...
...
app/views/devise/confirmations/new.html.haml
View file @
1b4cd17b
...
...
@@ -6,7 +6,7 @@
=
render
"devise/shared/error_messages"
,
resource:
resource
.form-group
=
f
.
label
:email
=
f
.
email_field
:email
,
class:
"form-control"
,
required:
true
,
title:
'Please provide a valid email address.'
=
f
.
email_field
:email
,
class:
"form-control"
,
required:
true
,
title:
'Please provide a valid email address.'
,
value:
nil
.clearfix
=
f
.
submit
"Resend"
,
class:
'gl-button btn btn-success'
...
...
app/views/explore/projects/_projects.html.haml
View file @
1b4cd17b
=
render
'shared/projects/list'
,
projects:
projects
,
user:
current_user
,
explore_page:
true
,
pipeline_status:
Feature
.
enabled?
(
:dashboard_pipeline_status
,
default_enabled:
true
)
-
if
params
[
:name
].
present?
&&
params
[
:name
].
size
<
Explore
::
ProjectsController
::
MIN_SEARCH_LENGTH
.nothing-here-block
%h5
=
_
(
'Enter at least three characters to search'
)
-
else
=
render
'shared/projects/list'
,
projects:
projects
,
user:
current_user
,
explore_page:
true
,
pipeline_status:
Feature
.
enabled?
(
:dashboard_pipeline_status
,
default_enabled:
true
)
config/application.rb
View file @
1b4cd17b
...
...
@@ -137,6 +137,7 @@ module Gitlab
encrypted_key
import_url
elasticsearch_url
search
otp_attempt
sentry_dsn
trace
...
...
config/feature_categories.yml
View file @
1b4cd17b
...
...
@@ -43,6 +43,7 @@
-
dynamic_application_security_testing
-
editor_extension
-
epics
-
epic_tracking
-
error_tracking
-
feature_flags
-
five_minute_production_app
...
...
db/post_migrate/20201109114603_schedule_remove_inaccessible_epic_todos.rb
0 → 100644
View file @
1b4cd17b
# frozen_string_literal: true
class
ScheduleRemoveInaccessibleEpicTodos
<
ActiveRecord
::
Migration
[
6.0
]
include
Gitlab
::
Database
::
MigrationHelpers
DOWNTIME
=
false
INTERVAL
=
2
.
minutes
BATCH_SIZE
=
10
MIGRATION
=
'RemoveInaccessibleEpicTodos'
disable_ddl_transaction!
class
Epic
<
ActiveRecord
::
Base
include
EachBatch
end
def
up
return
unless
Gitlab
.
ee?
relation
=
Epic
.
where
(
confidential:
true
)
queue_background_migration_jobs_by_range_at_intervals
(
relation
,
MIGRATION
,
INTERVAL
,
batch_size:
BATCH_SIZE
)
end
def
down
# no-op
end
end
db/schema_migrations/20201109114603
0 → 100644
View file @
1b4cd17b
ae8034ec52df47ce2ce3397715dd18347e4d297a963c17c7b26321f414dfa632
\ No newline at end of file
doc/api/graphql/reference/gitlab_schema.graphql
View file @
1b4cd17b
...
...
@@ -23823,9 +23823,9 @@ type User {
avatarUrl
:
String
"""
User
email
User
email
.
Deprecated
in
13.7
:
Use
public_email
"""
email
:
String
email
:
String
@
deprecated
(
reason
:
"
Use
public_email
.
Deprecated
in
13.7"
)
"""
Group
count
for
the
user
.
Available
only
when
feature
flag
`
user_group_counts
`
is
enabled
...
...
doc/api/graphql/reference/gitlab_schema.json
View file @
1b4cd17b
...
...
@@ -69304,7 +69304,7 @@
},
{
"name": "email",
"description": "User email",
"description": "User email
. Deprecated in 13.7: Use public_email
",
"args": [
],
...
...
@@ -69313,8 +69313,8 @@
"name": "String",
"ofType": null
},
"isDeprecated":
fals
e,
"deprecationReason":
null
"isDeprecated":
tru
e,
"deprecationReason":
"Use public_email. Deprecated in 13.7"
},
{
"name": "groupCount",
doc/api/graphql/reference/index.md
View file @
1b4cd17b
...
...
@@ -3596,7 +3596,7 @@ Autogenerated return type of UpdateSnippet.
|
`assignedMergeRequests`
| MergeRequestConnection | Merge Requests assigned to the user |
|
`authoredMergeRequests`
| MergeRequestConnection | Merge Requests authored by the user |
|
`avatarUrl`
| String | URL of the user's avatar |
|
`email`
| String | User email
|
|
`email`
**{warning-solid}**
| String |
**Deprecated:**
Use public_email. Deprecated in 13.7
|
|
`groupCount`
| Int | Group count for the user. Available only when feature flag
`user_group_counts`
is enabled |
|
`groupMemberships`
| GroupMemberConnection | Group memberships of the user |
|
`id`
| ID! | ID of the user |
...
...
doc/user/todos.md
View file @
1b4cd17b
...
...
@@ -64,7 +64,7 @@ To-do item triggers aren't affected by [GitLab notification email settings](prof
NOTE:
When a user no longer has access to a resource related to a to-do item (such as
an issue, merge request, project, or group), for security reasons GitLab
an issue, merge request,
epic,
project, or group), for security reasons GitLab
deletes any related to-do items within the next hour. Deletion is delayed to
prevent data loss, in the case where a user's access is accidentally revoked.
...
...
ee/app/services/ee/todos/destroy/entity_leave_service.rb
0 → 100644
View file @
1b4cd17b
# frozen_string_literal: true
module
EE
module
Todos
module
Destroy
module
EntityLeaveService
extend
ActiveSupport
::
Concern
extend
::
Gitlab
::
Utils
::
Override
override
:remove_confidential_resource_todos
def
remove_confidential_resource_todos
super
return
unless
entity
.
is_a?
(
Namespace
)
::
Todo
.
for_target
(
confidential_epics
.
select
(
:id
))
.
for_type
(
::
Epic
.
name
)
.
for_user
(
user
)
.
delete_all
end
private
def
confidential_epics
::
Epic
.
in_selected_groups
(
non_authorized_reporter_groups
)
.
confidential
end
end
end
end
end
ee/app/services/epics/update_service.rb
View file @
1b4cd17b
...
...
@@ -34,6 +34,11 @@ module Epics
end
todo_service
.
update_epic
(
epic
,
current_user
,
old_mentioned_users
)
if
epic
.
previous_changes
.
include?
(
'confidential'
)
&&
epic
.
confidential?
# don't enqueue immediately to prevent todos removal in case of a mistake
::
TodosDestroyer
::
ConfidentialEpicWorker
.
perform_in
(
::
Todo
::
WAIT_FOR_DELETE
,
epic
.
id
)
end
end
def
handle_task_changes
(
epic
)
...
...
ee/app/services/todos/destroy/confidential_epic_service.rb
0 → 100644
View file @
1b4cd17b
# frozen_string_literal: true
module
Todos
module
Destroy
# Service class for deleting todos that belong to confidential epics.
# It deletes todos for users that are not at least reporters.
class
ConfidentialEpicService
<
::
Todos
::
Destroy
::
BaseService
extend
::
Gitlab
::
Utils
::
Override
attr_reader
:epic
def
initialize
(
epic_id
:)
@epic
=
::
Epic
.
find_by_id
(
epic_id
)
end
private
override
:todos
def
todos
epic
.
todos
end
override
:todos_to_remove?
def
todos_to_remove?
epic
&
.
confidential?
end
override
:authorized_users
def
authorized_users
epic
.
group
.
members_with_parents
.
non_guests
.
select
(
:user_id
)
end
end
end
end
ee/app/workers/all_queues.yml
View file @
1b4cd17b
...
...
@@ -581,6 +581,14 @@
:weight:
2
:idempotent:
:tags: []
-
:name: todos_destroyer:todos_destroyer_confidential_epic
:feature_category: :epic_tracking
:has_external_dependencies:
:urgency: :low
:resource_boundary: :unknown
:weight:
1
:idempotent:
:tags: []
-
:name: adjourned_project_deletion
:feature_category: :authentication_and_authorization
:has_external_dependencies:
...
...
ee/app/workers/todos_destroyer/confidential_epic_worker.rb
0 → 100644
View file @
1b4cd17b
# frozen_string_literal: true
module
TodosDestroyer
class
ConfidentialEpicWorker
# rubocop:disable Scalability/IdempotentWorker
include
ApplicationWorker
queue_namespace
:todos_destroyer
feature_category
:epic_tracking
def
perform
(
epic_id
)
return
unless
epic_id
::
Todos
::
Destroy
::
ConfidentialEpicService
.
new
(
epic_id:
epic_id
).
execute
end
end
end
ee/lib/ee/gitlab/background_migration/remove_inaccessible_epic_todos.rb
0 → 100644
View file @
1b4cd17b
# frozen_string_literal: true
# rubocop:disable Style/Documentation
module
EE
module
Gitlab
module
BackgroundMigration
module
RemoveInaccessibleEpicTodos
extend
::
Gitlab
::
Utils
::
Override
class
User
<
ActiveRecord
::
Base
end
class
Todo
<
ActiveRecord
::
Base
belongs_to
:epic
,
foreign_key: :target_id
belongs_to
:user
end
class
Member
<
ActiveRecord
::
Base
include
FromUnion
self
.
inheritance_column
=
:_type_disabled
end
class
GroupGroupLink
<
ActiveRecord
::
Base
end
class
Epic
<
ActiveRecord
::
Base
belongs_to
:group
def
can_read_confidential?
(
user
)
group
.
max_member_access_for_user
(
user
)
>=
::
Gitlab
::
Access
::
REPORTER
end
end
class
Group
<
ActiveRecord
::
Base
self
.
table_name
=
'namespaces'
self
.
inheritance_column
=
:_type_disabled
def
max_member_access_for_user
(
user
)
max_member_access
=
members_with_parents
.
where
(
user_id:
user
)
.
reorder
(
access_level: :desc
)
.
first
&
.
access_level
max_member_access
||
::
Gitlab
::
Access
::
NO_ACCESS
end
def
members_with_parents
group_hierarchy_members
=
Member
.
where
(
source_type:
'Namespace'
,
source_id:
source_ids
)
Member
.
from_union
([
group_hierarchy_members
,
members_from_self_and_ancestor_group_shares
])
end
# rubocop:disable Metrics/AbcSize
# this is taken from Group model, so instead of doing additional
# refactoring let's keep it close to the original
def
members_from_self_and_ancestor_group_shares
group_group_link_table
=
GroupGroupLink
.
arel_table
group_member_table
=
Member
.
arel_table
group_group_links_query
=
GroupGroupLink
.
where
(
shared_group_id:
source_ids
)
cte
=
::
Gitlab
::
SQL
::
CTE
.
new
(
:group_group_links_cte
,
group_group_links_query
)
cte_alias
=
cte
.
table
.
alias
(
GroupGroupLink
.
table_name
)
# Instead of members.access_level, we need to maximize that access_level at
# the respective group_group_links.group_access.
member_columns
=
Member
.
attribute_names
.
map
do
|
column_name
|
if
column_name
==
'access_level'
smallest_value_arel
([
cte_alias
[
:group_access
],
group_member_table
[
:access_level
]],
'access_level'
)
else
group_member_table
[
column_name
]
end
end
Member
.
with
(
cte
.
to_arel
)
.
select
(
*
member_columns
)
.
from
([
group_member_table
,
cte
.
alias_to
(
group_group_link_table
)])
.
where
(
group_member_table
[
:requested_at
].
eq
(
nil
))
.
where
(
group_member_table
[
:source_id
].
eq
(
group_group_link_table
[
:shared_with_group_id
]))
.
where
(
group_member_table
[
:source_type
].
eq
(
'Namespace'
))
end
# rubocop:enable Metrics/AbcSize
def
source_ids
return
id
unless
parent_id
::
Gitlab
::
ObjectHierarchy
.
new
(
self
.
class
.
where
(
id:
id
))
.
base_and_ancestors
.
reorder
(
nil
).
select
(
:id
)
end
def
smallest_value_arel
(
args
,
column_alias
)
Arel
::
Nodes
::
As
.
new
(
Arel
::
Nodes
::
NamedFunction
.
new
(
'LEAST'
,
args
),
Arel
::
Nodes
::
SqlLiteral
.
new
(
column_alias
))
end
end
override
:perform
def
perform
(
start_id
,
stop_id
)
confidential_epic_ids
=
Epic
.
where
(
confidential:
true
).
where
(
id:
start_id
..
stop_id
).
ids
epic_todos
=
Todo
.
where
(
target_type:
'Epic'
,
target_id:
confidential_epic_ids
)
.
includes
(
:epic
,
:user
)
ids_to_delete
=
not_readable_epic_todo_ids
(
epic_todos
)
logger
.
info
(
message:
'Deleting confidential epic todos'
,
todo_ids:
ids_to_delete
)
Todo
.
where
(
id:
ids_to_delete
).
delete_all
end
private
def
not_readable_epic_todo_ids
(
todos
)
todos
.
map
do
|
todo
|
next
todo
.
id
unless
todo
.
epic
next
if
todo
.
epic
.
can_read_confidential?
(
todo
.
user
)
todo
.
id
end
.
compact
end
def
logger
@logger
||=
::
Gitlab
::
BackgroundMigration
::
Logger
.
build
end
end
end
end
end
ee/spec/lib/ee/gitlab/background_migration/remove_inaccessible_epic_todos_spec.rb
0 → 100644
View file @
1b4cd17b
# frozen_string_literal: true
require
'spec_helper'
RSpec
.
describe
Gitlab
::
BackgroundMigration
::
RemoveInaccessibleEpicTodos
,
schema:
20201109114603
do
include
MigrationHelpers
::
NamespacesHelpers
let
(
:users
)
{
table
(
:users
)
}
let
(
:todos
)
{
table
(
:todos
)
}
let
(
:epics
)
{
table
(
:epics
)
}
let
(
:members_table
)
{
table
(
:members
)
}
let
(
:group_group_links
)
{
table
(
:group_group_links
)
}
let
(
:author
)
{
users
.
create!
(
email:
'author@example.com'
,
projects_limit:
10
)
}
let
(
:user
)
{
users
.
create!
(
email:
'user@example.com'
,
projects_limit:
10
)
}
let
(
:group_root
)
{
create_namespace
(
'root'
,
Gitlab
::
VisibilityLevel
::
PUBLIC
)
}
let
(
:group_level1
)
{
create_namespace
(
'level1'
,
Gitlab
::
VisibilityLevel
::
PUBLIC
,
parent_id:
group_root
.
id
)
}
let
(
:epic_conf1
)
{
epics
.
create!
(
iid:
1
,
title:
'confidential1'
,
title_html:
'confidential1'
,
confidential:
true
,
group_id:
group_root
.
id
,
author_id:
author
.
id
)
}
let
(
:epic_conf2
)
{
epics
.
create!
(
iid:
1
,
title:
'confidential2'
,
title_html:
'confidential2'
,
confidential:
true
,
group_id:
group_level1
.
id
,
author_id:
author
.
id
)
}
let
(
:epic_public1
)
{
epics
.
create!
(
iid:
2
,
title:
'public1'
,
title_html:
'epic_public1'
,
group_id:
group_root
.
id
,
author_id:
author
.
id
)
}
let
(
:epic_public2
)
{
epics
.
create!
(
iid:
2
,
title:
'public1'
,
title_html:
'epic_public2'
,
group_id:
group_level1
.
id
,
author_id:
author
.
id
)
}
let!
(
:todo1
)
{
todos
.
create!
(
target_type:
'Epic'
,
target_id:
epic_conf1
.
id
,
user_id:
user
.
id
,
author_id:
user
.
id
,
action:
2
,
state:
0
)
}
let!
(
:todo2
)
{
todos
.
create!
(
target_type:
'Epic'
,
target_id:
epic_conf2
.
id
,
user_id:
user
.
id
,
author_id:
user
.
id
,
action:
2
,
state:
0
)
}
let!
(
:todo3
)
{
todos
.
create!
(
target_type:
'Epic'
,
target_id:
epic_public1
.
id
,
user_id:
user
.
id
,
author_id:
user
.
id
,
action:
2
,
state:
0
)
}
let!
(
:todo4
)
{
todos
.
create!
(
target_type:
'Epic'
,
target_id:
epic_public2
.
id
,
user_id:
user
.
id
,
author_id:
user
.
id
,
action:
2
,
state:
0
)
}
describe
'#perform'
do
subject
(
:perform
)
{
described_class
.
new
.
perform
(
epics
.
first
.
id
,
epics
.
last
.
id
)
}
def
expect_todos
(
preserved
:)
expect
{
subject
}.
to
change
{
todos
.
count
}.
by
(
preserved
.
count
-
4
)
existing_ids
=
todos
.
pluck
(
:id
)
expect
(
existing_ids
).
to
match_array
(
preserved
)
end
context
'when user is not member of related groups'
do
it
'deletes only todos referencing confidential epics'
do
expect_todos
(
preserved:
[
todo3
.
id
,
todo4
.
id
])
end
end
context
'when user is only guest member of related groups'
do
let!
(
:member
)
do
members_table
.
create!
(
user_id:
user
.
id
,
source_id:
group_root
.
id
,
source_type:
'Namespace'
,
type:
'GroupMember'
,
access_level:
10
,
notification_level:
3
)
end
it
'deletes todos referencing confidential epics'
do
expect_todos
(
preserved:
[
todo3
.
id
,
todo4
.
id
])
end
end
context
'when user is member of subgroup'
do
let!
(
:member
)
do
members_table
.
create!
(
user_id:
user
.
id
,
source_id:
group_level1
.
id
,
source_type:
'Namespace'
,
type:
'GroupMember'
,
access_level:
20
,
notification_level:
3
)
end
it
'deletes only epic todos in the root group'
do
expect_todos
(
preserved:
[
todo2
.
id
,
todo3
.
id
,
todo4
.
id
])
end
end
context
'when user is member of root group'
do
let!
(
:member
)
do
members_table
.
create!
(
user_id:
user
.
id
,
source_id:
group_root
.
id
,
source_type:
'Namespace'
,
type:
'GroupMember'
,
access_level:
20
,
notification_level:
3
)
end
it
'does not delete any todos'
do
expect_todos
(
preserved:
[
todo1
.
id
,
todo2
.
id
,
todo3
.
id
,
todo4
.
id
])
end
end
context
'when user is only guest on root group'
do
let!
(
:root_member
)
do
members_table
.
create!
(
user_id:
user
.
id
,
source_id:
group_root
.
id
,
source_type:
'Namespace'
,
type:
'GroupMember'
,
access_level:
10
,
notification_level:
3
)
end
let!
(
:subgroup_member
)
do
members_table
.
create!
(
user_id:
user
.
id
,
source_id:
group_level1
.
id
,
source_type:
'Namespace'
,
type:
'GroupMember'
,
access_level:
20
,
notification_level:
3
)
end
it
'deletes only root confidential epic todo'
do
expect_todos
(
preserved:
[
todo2
.
id
,
todo3
.
id
,
todo4
.
id
])
end
end
context
'when root group is shared with other group'
do
let!
(
:other_group
)
{
create_namespace
(
'other_group'
,
Gitlab
::
VisibilityLevel
::
PRIVATE
)
}
let!
(
:member
)
do
members_table
.
create!
(
user_id:
user
.
id
,
source_id:
other_group
.
id
,
source_type:
'Namespace'
,
type:
'GroupMember'
,
access_level:
20
,
notification_level:
3
)
end
let!
(
:group_link
)
do
group_group_links
.
create!
(
shared_group_id:
group_root
.
id
,
shared_with_group_id:
other_group
.
id
,
group_access:
20
)
end
it
'does not delete any todos'
do
expect_todos
(
preserved:
[
todo1
.
id
,
todo2
.
id
,
todo3
.
id
,
todo4
.
id
])
end
end
end
end
ee/spec/migrations/schedule_remove_inaccessible_epic_todos_spec.rb
0 → 100644
View file @
1b4cd17b
# frozen_string_literal: true
require
'spec_helper'
require
Rails
.
root
.
join
(
'db'
,
'post_migrate'
,
'20201109114603_schedule_remove_inaccessible_epic_todos'
)
RSpec
.
describe
ScheduleRemoveInaccessibleEpicTodos
do
let
(
:group
)
{
table
(
:namespaces
).
create!
(
name:
'gitlab'
,
path:
'gitlab-org'
)
}
let
(
:user
)
{
table
(
:users
).
create!
(
email:
'user@example.com'
,
projects_limit:
10
)
}
let!
(
:epic1
)
{
table
(
:epics
).
create!
(
iid:
1
,
title:
'foo'
,
title_html:
'foo'
,
group_id:
group
.
id
,
author_id:
user
.
id
,
confidential:
true
)
}
let!
(
:epic2
)
{
table
(
:epics
).
create!
(
iid:
2
,
title:
'foo'
,
title_html:
'foo'
,
group_id:
group
.
id
,
author_id:
user
.
id
)
}
let!
(
:epic3
)
{
table
(
:epics
).
create!
(
iid:
3
,
title:
'foo'
,
title_html:
'foo'
,
group_id:
group
.
id
,
author_id:
user
.
id
,
confidential:
true
)
}
before
do
stub_const
(
"
#{
described_class
.
name
}
::BATCH_SIZE"
,
1
)
end
it
'schedules jobs for confidental epic todos'
do
Sidekiq
::
Testing
.
fake!
do
freeze_time
do
migrate!
expect
(
described_class
::
MIGRATION
).
to
be_scheduled_delayed_migration
(
2
.
minutes
,
epic1
.
id
,
epic1
.
id
)
expect
(
described_class
::
MIGRATION
).
to
be_scheduled_delayed_migration
(
4
.
minutes
,
epic3
.
id
,
epic3
.
id
)
expect
(
BackgroundMigrationWorker
.
jobs
.
size
).
to
eq
(
2
)
end
end
end
end
ee/spec/services/ee/todos/destroy/entity_leave_service_spec.rb
0 → 100644
View file @
1b4cd17b
# frozen_string_literal: true
require
'spec_helper'
RSpec
.
describe
Todos
::
Destroy
::
EntityLeaveService
do
let_it_be
(
:user
)
{
create
(
:user
)
}
let_it_be
(
:group
)
{
create
(
:group
)
}
let_it_be
(
:subgroup
)
{
create
(
:group
,
parent:
group
)
}
let_it_be
(
:epic1
)
{
create
(
:epic
,
confidential:
true
,
group:
subgroup
)
}
let_it_be
(
:epic2
)
{
create
(
:epic
,
group:
subgroup
)
}
let!
(
:todo1
)
{
create
(
:todo
,
target:
epic1
,
user:
user
,
group:
subgroup
)
}
let!
(
:todo2
)
{
create
(
:todo
,
target:
epic2
,
user:
user
,
group:
subgroup
)
}
describe
'#execute'
do
subject
{
described_class
.
new
(
user
.
id
,
subgroup
.
id
,
'Group'
).
execute
}
shared_examples
'removes only confidential epics todos'
do
it
'removes todos targeting confidential epics in the group'
do
expect
{
subject
}.
to
change
{
Todo
.
count
}.
by
(
-
1
)
expect
(
user
.
reload
.
todos
.
ids
).
to
match_array
(
todo2
.
id
)
end
end
it_behaves_like
'removes only confidential epics todos'
context
'when user is still member of ancestor group'
do
before
do
group
.
add_reporter
(
user
)
end
it
'does not remove todos targeting confidential epics in the group'
do
expect
{
subject
}.
not_to
change
{
Todo
.
count
}
end
end
context
'when user role is downgraded to guest'
do
before
do
subgroup
.
add_guest
(
user
)
end
it_behaves_like
'removes only confidential epics todos'
end
end
end
ee/spec/services/epics/update_service_spec.rb
View file @
1b4cd17b
...
...
@@ -216,6 +216,12 @@ RSpec.describe Epics::UpdateService do
end
end
end
it
'schedules deletion of todos when epic becomes confidential'
do
expect
(
TodosDestroyer
::
ConfidentialEpicWorker
).
to
receive
(
:perform_in
).
with
(
Todo
::
WAIT_FOR_DELETE
,
epic
.
id
)
update_epic
(
confidential:
true
)
end
end
context
'when Epic has tasks'
do
...
...
ee/spec/services/todos/destroy/confidential_epic_service_spec.rb
0 → 100644
View file @
1b4cd17b
# frozen_string_literal: true
require
'spec_helper'
RSpec
.
describe
Todos
::
Destroy
::
ConfidentialEpicService
do
let_it_be
(
:group
)
{
create
(
:group
,
:public
)
}
let_it_be
(
:user
)
{
create
(
:user
)
}
let_it_be
(
:author
)
{
create
(
:user
)
}
let_it_be
(
:guest
)
{
create
(
:user
)
}
let_it_be
(
:group_member
)
{
create
(
:user
)
}
let_it_be
(
:shared_user
)
{
create
(
:user
)
}
let_it_be
(
:group_link
)
{
create
(
:group_group_link
,
shared_group:
group
)
}
let_it_be
(
:epic_1
,
reload:
true
)
{
create
(
:epic
,
:confidential
,
group:
group
,
author:
author
)
}
let!
(
:todos
)
do
[
# todos not to be deleted
create
(
:todo
,
user:
group_member
,
target:
epic_1
,
group:
group
),
create
(
:todo
,
user:
user
,
group:
group
),
create
(
:todo
,
user:
shared_user
,
target:
epic_1
,
group:
group
),
# Todos to be deleted
create
(
:todo
,
user:
guest
,
target:
epic_1
,
group:
group
),
create
(
:todo
,
user:
user
,
target:
epic_1
,
group:
group
)
]
end
describe
'#execute'
do
before
do
group
.
add_reporter
(
group_member
)
group
.
add_guest
(
guest
)
group_link
.
shared_with_group
.
add_reporter
(
shared_user
)
end
subject
{
described_class
.
new
(
epic_id:
epic_1
.
id
).
execute
}
it
'removes epic todos for users who can not access the confidential epic'
do
expect
{
subject
}.
to
change
{
Todo
.
count
}.
by
(
-
2
)
end
context
'when provided epic is not confidential'
do
before
do
epic_1
.
update!
(
confidential:
false
)
end
it
'does not remove any todos'
do
expect
{
subject
}.
not_to
change
{
Todo
.
count
}
end
end
end
end
ee/spec/workers/todos_destroyer/confidential_epic_worker_spec.rb
0 → 100644
View file @
1b4cd17b
# frozen_string_literal: true
require
'spec_helper'
RSpec
.
describe
TodosDestroyer
::
ConfidentialEpicWorker
do
let
(
:service
)
{
double
}
it
'calls the Todos::Destroy::ConfidentialEpicService with epic_id parameter'
do
expect
(
::
Todos
::
Destroy
::
ConfidentialEpicService
).
to
receive
(
:new
).
with
(
epic_id:
100
).
and_return
(
service
)
expect
(
service
).
to
receive
(
:execute
)
described_class
.
new
.
perform
(
100
)
end
end
lib/gitlab/background_migration/remove_inaccessible_epic_todos.rb
0 → 100644
View file @
1b4cd17b
# frozen_string_literal: true
module
Gitlab
module
BackgroundMigration
# rubocop:disable Style/Documentation
class
RemoveInaccessibleEpicTodos
def
perform
(
start_id
,
stop_id
)
end
end
end
end
Gitlab
::
BackgroundMigration
::
RemoveInaccessibleEpicTodos
.
prepend_if_ee
(
'EE::Gitlab::BackgroundMigration::RemoveInaccessibleEpicTodos'
)
spec/controllers/confirmations_controller_spec.rb
0 → 100644
View file @
1b4cd17b
# frozen_string_literal: true
require
'spec_helper'
RSpec
.
describe
ConfirmationsController
do
include
DeviseHelpers
before
do
set_devise_mapping
(
context:
@request
)
end
describe
'#show'
do
render_views
subject
{
get
:show
,
params:
{
confirmation_token:
confirmation_token
}
}
context
'user is already confirmed'
do
let_it_be_with_reload
(
:user
)
{
create
(
:user
,
:unconfirmed
)
}
let
(
:confirmation_token
)
{
user
.
confirmation_token
}
before
do
user
.
confirm
subject
end
it
'renders `new`'
do
expect
(
response
).
to
render_template
(
:new
)
end
it
'displays an error message'
do
expect
(
response
.
body
).
to
include
(
'Email was already confirmed, please try signing in'
)
end
it
'does not display the email of the user'
do
expect
(
response
.
body
).
not_to
include
(
user
.
email
)
end
end
context
'user accesses the link after the expiry of confirmation token has passed'
do
let_it_be_with_reload
(
:user
)
{
create
(
:user
,
:unconfirmed
)
}
let
(
:confirmation_token
)
{
user
.
confirmation_token
}
before
do
allow
(
Devise
).
to
receive
(
:confirm_within
).
and_return
(
1
.
day
)
travel_to
(
3
.
days
.
from_now
)
do
subject
end
end
it
'renders `new`'
do
expect
(
response
).
to
render_template
(
:new
)
end
it
'displays an error message'
do
expect
(
response
.
body
).
to
include
(
'Email needs to be confirmed within 1 day, please request a new one below'
)
end
it
'does not display the email of the user'
do
expect
(
response
.
body
).
not_to
include
(
user
.
email
)
end
end
context
'with an invalid confirmation token'
do
let
(
:confirmation_token
)
{
'invalid_confirmation_token'
}
before
do
subject
end
it
'renders `new`'
do
expect
(
response
).
to
render_template
(
:new
)
end
it
'displays an error message'
do
expect
(
response
.
body
).
to
include
(
'Confirmation token is invalid'
)
end
end
end
end
spec/controllers/projects/feature_flags_controller_spec.rb
View file @
1b4cd17b
...
...
@@ -1419,6 +1419,40 @@ RSpec.describe Projects::FeatureFlagsController do
expect
(
response
).
to
have_gitlab_http_status
(
:not_found
)
end
it
'returns not found when trying to update a gitlabUserList strategy with a user list from another project'
do
user_list
=
create
(
:operations_feature_flag_user_list
,
project:
project
,
name:
'My List'
,
user_xids:
'user1,user2'
)
strategy
=
create
(
:operations_strategy
,
feature_flag:
new_version_flag
,
name:
'gitlabUserList'
,
parameters:
{},
user_list:
user_list
)
other_project
=
create
(
:project
)
other_user_list
=
create
(
:operations_feature_flag_user_list
,
project:
other_project
,
name:
'Other List'
,
user_xids:
'some,one'
)
put_request
(
new_version_flag
,
strategies_attributes:
[{
id:
strategy
.
id
,
user_list_id:
other_user_list
.
id
}])
expect
(
response
).
to
have_gitlab_http_status
(
:not_found
)
expect
(
strategy
.
reload
.
user_list
).
to
eq
(
user_list
)
end
it
'allows setting multiple gitlabUserList strategies to the same user list'
do
user_list_a
=
create
(
:operations_feature_flag_user_list
,
project:
project
,
name:
'My List A'
,
user_xids:
'user1,user2'
)
user_list_b
=
create
(
:operations_feature_flag_user_list
,
project:
project
,
name:
'My List B'
,
user_xids:
'user3,user4'
)
strategy_a
=
create
(
:operations_strategy
,
feature_flag:
new_version_flag
,
name:
'gitlabUserList'
,
parameters:
{},
user_list:
user_list_a
)
strategy_b
=
create
(
:operations_strategy
,
feature_flag:
new_version_flag
,
name:
'gitlabUserList'
,
parameters:
{},
user_list:
user_list_a
)
put_request
(
new_version_flag
,
strategies_attributes:
[{
id:
strategy_a
.
id
,
user_list_id:
user_list_b
.
id
},
{
id:
strategy_b
.
id
,
user_list_id:
user_list_b
.
id
}])
expect
(
response
).
to
have_gitlab_http_status
(
:ok
)
expect
(
strategy_a
.
reload
.
user_list
).
to
eq
(
user_list_b
)
expect
(
strategy_b
.
reload
.
user_list
).
to
eq
(
user_list_b
)
end
it
'updates an existing strategy'
do
strategy
=
create
(
:operations_strategy
,
feature_flag:
new_version_flag
,
name:
'default'
,
parameters:
{})
...
...
spec/controllers/search_controller_spec.rb
View file @
1b4cd17b
...
...
@@ -272,7 +272,7 @@ RSpec.describe SearchController do
expect
(
last_payload
[
:metadata
][
'meta.search.group_id'
]).
to
eq
(
'123'
)
expect
(
last_payload
[
:metadata
][
'meta.search.project_id'
]).
to
eq
(
'456'
)
expect
(
last_payload
[
:metadata
]
[
'meta.search.search'
]).
to
eq
(
'hello world
'
)
expect
(
last_payload
[
:metadata
]
).
not_to
have_key
(
'meta.search.search
'
)
expect
(
last_payload
[
:metadata
][
'meta.search.scope'
]).
to
eq
(
'issues'
)
expect
(
last_payload
[
:metadata
][
'meta.search.force_search_results'
]).
to
eq
(
'true'
)
expect
(
last_payload
[
:metadata
][
'meta.search.filters.confidential'
]).
to
eq
(
'true'
)
...
...
spec/controllers/users_controller_spec.rb
View file @
1b4cd17b
...
...
@@ -354,32 +354,99 @@ RSpec.describe UsersController do
describe
'GET #contributed'
do
let
(
:project
)
{
create
(
:project
,
:public
)
}
let
(
:current_user
)
{
create
(
:user
)
}
subject
do
get
:contributed
,
params:
{
username:
author
.
username
},
format:
format
end
before
do
sign_in
(
current_
user
)
sign_in
(
user
)
project
.
add_developer
(
public_user
)
project
.
add_developer
(
private_user
)
create
(
:push_event
,
project:
project
,
author:
author
)
subject
end
context
'with public profile
'
do
shared_examples_for
'renders contributed projects
'
do
it
'renders contributed projects'
do
create
(
:push_event
,
project:
project
,
author:
public_user
)
expect
(
assigns
[
:contributed_projects
]).
not_to
be_empty
expect
(
response
).
to
have_gitlab_http_status
(
:ok
)
end
end
get
:contributed
,
params:
{
username:
public_user
.
username
}
%i(html json)
.
each
do
|
format
|
context
"format:
#{
format
}
"
do
let
(
:format
)
{
format
}
expect
(
assigns
[
:contributed_projects
]).
not_to
be_empty
context
'with public profile'
do
let
(
:author
)
{
public_user
}
it_behaves_like
'renders contributed projects'
end
context
'with private profile'
do
let
(
:author
)
{
private_user
}
it
'returns 404'
do
expect
(
response
).
to
have_gitlab_http_status
(
:not_found
)
end
context
'with a user that has the ability to read private profiles'
,
:enable_admin_mode
do
let
(
:user
)
{
create
(
:admin
)
}
it_behaves_like
'renders contributed projects'
end
end
end
end
end
describe
'GET #starred'
do
let
(
:project
)
{
create
(
:project
,
:public
)
}
subject
do
get
:starred
,
params:
{
username:
author
.
username
},
format:
format
end
before
do
author
.
toggle_star
(
project
)
sign_in
(
user
)
subject
end
shared_examples_for
'renders starred projects'
do
it
'renders starred projects'
do
expect
(
response
).
to
have_gitlab_http_status
(
:ok
)
expect
(
assigns
[
:starred_projects
]).
not_to
be_empty
end
end
context
'with private profile'
do
it
'does not render contributed projects'
do
create
(
:push_event
,
project:
project
,
author:
private_user
)
%i(html json)
.
each
do
|
format
|
context
"format:
#{
format
}
"
do
let
(
:format
)
{
format
}
context
'with public profile'
do
let
(
:author
)
{
public_user
}
it_behaves_like
'renders starred projects'
end
context
'with private profile'
do
let
(
:author
)
{
private_user
}
it
'returns 404'
do
expect
(
response
).
to
have_gitlab_http_status
(
:not_found
)
end
get
:contributed
,
params:
{
username:
private_user
.
username
}
context
'with a user that has the ability to read private profiles'
,
:enable_admin_mode
do
let
(
:user
)
{
create
(
:admin
)
}
expect
(
assigns
[
:contributed_projects
]).
to
be_empty
it_behaves_like
'renders starred projects'
end
end
end
end
end
...
...
spec/features/explore/user_explores_projects_spec.rb
View file @
1b4cd17b
...
...
@@ -47,6 +47,14 @@ RSpec.describe 'User explores projects' do
end
end
shared_examples
'minimum search length'
do
it
'shows a prompt to enter a longer search term'
,
:js
do
fill_in
'name'
,
with:
'z'
expect
(
page
).
to
have_content
(
'Enter at least three characters to search'
)
end
end
context
'when viewing public projects'
do
before
do
visit
(
explore_projects_path
)
...
...
@@ -54,6 +62,7 @@ RSpec.describe 'User explores projects' do
include_examples
'shows public and internal projects'
include_examples
'empty search results'
include_examples
'minimum search length'
end
context
'when viewing most starred projects'
do
...
...
@@ -63,6 +72,7 @@ RSpec.describe 'User explores projects' do
include_examples
'shows public and internal projects'
include_examples
'empty search results'
include_examples
'minimum search length'
end
context
'when viewing trending projects'
do
...
...
@@ -76,6 +86,7 @@ RSpec.describe 'User explores projects' do
include_examples
'shows public projects'
include_examples
'empty search results'
include_examples
'minimum search length'
end
end
end
...
...
spec/features/markdown/mermaid_spec.rb
View file @
1b4cd17b
...
...
@@ -19,6 +19,9 @@ RSpec.describe 'Mermaid rendering', :js do
visit
project_issue_path
(
project
,
issue
)
wait_for_requests
wait_for_mermaid
%w[A B C D]
.
each
do
|
label
|
expect
(
page
).
to
have_selector
(
'svg text'
,
text:
label
)
end
...
...
@@ -39,6 +42,7 @@ RSpec.describe 'Mermaid rendering', :js do
visit
project_issue_path
(
project
,
issue
)
wait_for_requests
wait_for_mermaid
expected
=
'<text style=""><tspan xml:space="preserve" dy="1em" x="1">Line 1</tspan><tspan xml:space="preserve" dy="1em" x="1">Line 2</tspan></text>'
expect
(
page
.
html
.
scan
(
expected
).
count
).
to
be
(
4
)
...
...
@@ -65,6 +69,9 @@ RSpec.describe 'Mermaid rendering', :js do
visit
project_issue_path
(
project
,
issue
)
wait_for_requests
wait_for_mermaid
page
.
within
(
'.description'
)
do
expect
(
page
).
to
have_selector
(
'svg'
)
expect
(
page
).
to
have_selector
(
'pre.mermaid'
)
...
...
@@ -92,6 +99,9 @@ RSpec.describe 'Mermaid rendering', :js do
visit
project_issue_path
(
project
,
issue
)
wait_for_requests
wait_for_mermaid
page
.
within
(
'.description'
)
do
page
.
find
(
'summary'
).
click
svg
=
page
.
find
(
'svg.mermaid'
)
...
...
@@ -118,6 +128,9 @@ RSpec.describe 'Mermaid rendering', :js do
visit
project_issue_path
(
project
,
issue
)
wait_for_requests
wait_for_mermaid
expect
(
page
).
to
have_css
(
'svg.mermaid[style*="max-width"][width="100%"]'
)
end
...
...
@@ -147,6 +160,7 @@ RSpec.describe 'Mermaid rendering', :js do
end
wait_for_requests
wait_for_mermaid
find
(
'.js-lazy-render-mermaid'
).
click
...
...
@@ -156,4 +170,55 @@ RSpec.describe 'Mermaid rendering', :js do
expect
(
page
).
not_to
have_selector
(
'.js-lazy-render-mermaid-container'
)
end
end
it
'does not render more than 50 mermaid blocks'
,
:js
,
quarantine:
{
issue:
'https://gitlab.com/gitlab-org/gitlab/-/issues/234081'
}
do
graph_edges
=
"A-->B;B-->A;"
description
=
<<~
MERMAID
```mermaid
graph LR
#{
graph_edges
}
```
MERMAID
description
*=
51
project
=
create
(
:project
,
:public
)
issue
=
create
(
:issue
,
project:
project
,
description:
description
)
visit
project_issue_path
(
project
,
issue
)
wait_for_requests
wait_for_mermaid
page
.
within
(
'.description'
)
do
expect
(
page
).
to
have_selector
(
'svg'
)
expect
(
page
).
to
have_selector
(
'.lazy-alert-shown'
)
expect
(
page
).
to
have_selector
(
'.js-lazy-render-mermaid-container'
)
end
end
end
def
wait_for_mermaid
run_idle_callback
=
<<~
RUN_IDLE_CALLBACK
window.requestIdleCallback(() => {
window.__CAPYBARA_IDLE_CALLBACK_EXEC__ = 1;
})
RUN_IDLE_CALLBACK
page
.
evaluate_script
(
run_idle_callback
)
Timeout
.
timeout
(
Capybara
.
default_max_wait_time
)
do
loop
until
finished_rendering?
end
end
def
finished_rendering?
check_idle_callback
=
<<~
CHECK_IDLE_CALLBACK
window.__CAPYBARA_IDLE_CALLBACK_EXEC__
CHECK_IDLE_CALLBACK
page
.
evaluate_script
(
check_idle_callback
)
==
1
end
spec/finders/projects_finder_spec.rb
View file @
1b4cd17b
...
...
@@ -161,6 +161,29 @@ RSpec.describe ProjectsFinder, :do_not_mock_admin_mode do
it
{
is_expected
.
to
eq
([
public_project
])
}
end
describe
'filter by search with minimum search length'
do
context
'when search term is shorter than minimum length'
do
let
(
:params
)
{
{
search:
'C'
,
minimum_search_length:
3
}
}
it
{
is_expected
.
to
be_empty
}
end
context
'when search term is longer than minimum length'
do
let
(
:project
)
{
create
(
:project
,
:public
,
group:
group
,
name:
'test_project'
)
}
let
(
:params
)
{
{
search:
'test'
,
minimum_search_length:
3
}
}
it
{
is_expected
.
to
eq
([
project
])
}
end
context
'when minimum length is invalid'
do
let
(
:params
)
{
{
search:
'C'
,
minimum_search_length:
'x'
}
}
it
'ignores the minimum length param'
do
is_expected
.
to
eq
([
public_project
])
end
end
end
describe
'filter by group name'
do
let
(
:params
)
{
{
name:
group
.
name
,
search_namespaces:
true
}
}
...
...
spec/finders/starred_projects_finder_spec.rb
View file @
1b4cd17b
...
...
@@ -5,7 +5,7 @@ require 'spec_helper'
RSpec
.
describe
StarredProjectsFinder
do
let
(
:project1
)
{
create
(
:project
,
:public
,
:empty_repo
)
}
let
(
:project2
)
{
create
(
:project
,
:public
,
:empty_repo
)
}
let
(
:
other_project
)
{
create
(
:project
,
:public
,
:empty_repo
)
}
let
(
:
private_project
)
{
create
(
:project
,
:private
,
:empty_repo
)
}
let
(
:user
)
{
create
(
:user
)
}
let
(
:other_user
)
{
create
(
:user
)
}
...
...
@@ -13,6 +13,9 @@ RSpec.describe StarredProjectsFinder do
before
do
user
.
toggle_star
(
project1
)
user
.
toggle_star
(
project2
)
private_project
.
add_maintainer
(
user
)
user
.
toggle_star
(
private_project
)
end
describe
'#execute'
do
...
...
@@ -20,22 +23,56 @@ RSpec.describe StarredProjectsFinder do
subject
{
finder
.
execute
}
describe
'as same user'
do
let
(
:current_user
)
{
user
}
context
'user has a public profile'
do
describe
'as same user'
do
let
(
:current_user
)
{
user
}
it
{
is_expected
.
to
contain_exactly
(
project1
,
project2
)
}
end
it
{
is_expected
.
to
contain_exactly
(
project1
,
project2
,
private_project
)
}
end
describe
'as other user'
do
let
(
:current_user
)
{
other_user
}
describe
'as other user'
do
let
(
:current_user
)
{
other_user
}
it
{
is_expected
.
to
contain_exactly
(
project1
,
project2
)
}
end
it
{
is_expected
.
to
contain_exactly
(
project1
,
project2
)
}
describe
'as no user'
do
let
(
:current_user
)
{
nil
}
it
{
is_expected
.
to
contain_exactly
(
project1
,
project2
)
}
end
end
describe
'as no user'
do
let
(
:current_user
)
{
nil
}
context
'user has a private profile'
do
before
do
user
.
update!
(
private_profile:
true
)
end
describe
'as same user'
do
let
(
:current_user
)
{
user
}
it
{
is_expected
.
to
contain_exactly
(
project1
,
project2
,
private_project
)
}
end
describe
'as other user'
do
context
'user does not have access to view the private profile'
do
let
(
:current_user
)
{
other_user
}
it
{
is_expected
.
to
be_empty
}
end
context
'user has access to view the private profile'
,
:enable_admin_mode
do
let
(
:current_user
)
{
create
(
:admin
)
}
it
{
is_expected
.
to
contain_exactly
(
project1
,
project2
,
private_project
)
}
end
end
describe
'as no user'
do
let
(
:current_user
)
{
nil
}
it
{
is_expected
.
to
contain_exactly
(
project1
,
project2
)
}
it
{
is_expected
.
to
be_empty
}
end
end
end
end
spec/requests/api/graphql/user/starred_projects_query_spec.rb
View file @
1b4cd17b
...
...
@@ -70,4 +70,31 @@ RSpec.describe 'Getting starredProjects of the user' do
)
end
end
context
'the user has a private profile'
do
before
do
user
.
update!
(
private_profile:
true
)
post_graphql
(
query
,
current_user:
current_user
)
end
context
'the current user does not have access to view the private profile of the user'
do
let
(
:current_user
)
{
create
(
:user
)
}
it
'finds no projects'
do
expect
(
starred_projects
).
to
be_empty
end
end
context
'the current user has access to view the private profile of the user'
do
let
(
:current_user
)
{
create
(
:admin
)
}
it
'finds all projects starred by the user, which the current user has access to'
do
expect
(
starred_projects
).
to
contain_exactly
(
a_hash_including
(
'id'
=>
global_id_of
(
project_a
)),
a_hash_including
(
'id'
=>
global_id_of
(
project_b
)),
a_hash_including
(
'id'
=>
global_id_of
(
project_c
))
)
end
end
end
end
spec/requests/api/graphql/user_query_spec.rb
View file @
1b4cd17b
...
...
@@ -82,7 +82,7 @@ RSpec.describe 'getting user information' do
'username'
=>
presenter
.
username
,
'webUrl'
=>
presenter
.
web_url
,
'avatarUrl'
=>
presenter
.
avatar_url
,
'email'
=>
presenter
.
email
,
'email'
=>
presenter
.
public_
email
,
'publicEmail'
=>
presenter
.
public_email
))
...
...
@@ -251,7 +251,7 @@ RSpec.describe 'getting user information' do
context
'the user is private'
do
before
do
user
.
update
(
private_profile:
true
)
user
.
update
!
(
private_profile:
true
)
post_graphql
(
query
,
current_user:
current_user
)
end
...
...
@@ -261,6 +261,50 @@ RSpec.describe 'getting user information' do
it_behaves_like
'a working graphql query'
end
context
'we request the groupMemberships'
do
let_it_be
(
:membership_a
)
{
create
(
:group_member
,
user:
user
)
}
let
(
:group_memberships
)
{
graphql_data_at
(
:user
,
:group_memberships
,
:nodes
)
}
let
(
:user_fields
)
{
'groupMemberships { nodes { id } }'
}
it_behaves_like
'a working graphql query'
it
'cannot be found'
do
expect
(
group_memberships
).
to
be_empty
end
context
'the current user is the user'
do
let
(
:current_user
)
{
user
}
it
'can be found'
do
expect
(
group_memberships
).
to
include
(
a_hash_including
(
'id'
=>
global_id_of
(
membership_a
))
)
end
end
end
context
'we request the projectMemberships'
do
let_it_be
(
:membership_a
)
{
create
(
:project_member
,
user:
user
)
}
let
(
:project_memberships
)
{
graphql_data_at
(
:user
,
:project_memberships
,
:nodes
)
}
let
(
:user_fields
)
{
'projectMemberships { nodes { id } }'
}
it_behaves_like
'a working graphql query'
it
'cannot be found'
do
expect
(
project_memberships
).
to
be_empty
end
context
'the current user is the user'
do
let
(
:current_user
)
{
user
}
it
'can be found'
do
expect
(
project_memberships
).
to
include
(
a_hash_including
(
'id'
=>
global_id_of
(
membership_a
))
)
end
end
end
context
'we request the authoredMergeRequests'
do
let
(
:user_fields
)
{
'authoredMergeRequests { nodes { id } }'
}
...
...
spec/requests/api/projects_spec.rb
View file @
1b4cd17b
...
...
@@ -1255,13 +1255,46 @@ RSpec.describe API::Projects do
expect
(
json_response
[
'message'
]).
to
eq
(
'404 User Not Found'
)
end
it
'returns projects filtered by user'
do
get
api
(
"/users/
#{
user3
.
id
}
/starred_projects/"
,
user
)
context
'with a public profile'
do
it
'returns projects filtered by user'
do
get
api
(
"/users/
#{
user3
.
id
}
/starred_projects/"
,
user
)
expect
(
response
).
to
have_gitlab_http_status
(
:ok
)
expect
(
response
).
to
include_pagination_headers
expect
(
json_response
).
to
be_an
Array
expect
(
json_response
.
map
{
|
project
|
project
[
'id'
]
}).
to
contain_exactly
(
project
.
id
,
project2
.
id
,
project3
.
id
)
expect
(
response
).
to
have_gitlab_http_status
(
:ok
)
expect
(
response
).
to
include_pagination_headers
expect
(
json_response
).
to
be_an
Array
expect
(
json_response
.
map
{
|
project
|
project
[
'id'
]
})
.
to
contain_exactly
(
project
.
id
,
project2
.
id
,
project3
.
id
)
end
end
context
'with a private profile'
do
before
do
user3
.
update!
(
private_profile:
true
)
user3
.
reload
end
context
'user does not have access to view the private profile'
do
it
'returns no projects'
do
get
api
(
"/users/
#{
user3
.
id
}
/starred_projects/"
,
user
)
expect
(
response
).
to
have_gitlab_http_status
(
:ok
)
expect
(
response
).
to
include_pagination_headers
expect
(
json_response
).
to
be_an
Array
expect
(
json_response
).
to
be_empty
end
end
context
'user has access to view the private profile'
do
it
'returns projects filtered by user'
do
get
api
(
"/users/
#{
user3
.
id
}
/starred_projects/"
,
admin
)
expect
(
response
).
to
have_gitlab_http_status
(
:ok
)
expect
(
response
).
to
include_pagination_headers
expect
(
json_response
).
to
be_an
Array
expect
(
json_response
.
map
{
|
project
|
project
[
'id'
]
})
.
to
contain_exactly
(
project
.
id
,
project2
.
id
,
project3
.
id
)
end
end
end
end
...
...
spec/validators/zoom_url_validator_spec.rb
0 → 100644
View file @
1b4cd17b
# frozen_string_literal: true
require
'spec_helper'
RSpec
.
describe
ZoomUrlValidator
do
let
(
:zoom_meeting
)
{
build
(
:zoom_meeting
)
}
describe
'validations'
do
context
'when zoom link starts with https'
do
it
'passes validation'
do
zoom_meeting
.
url
=
'https://zoom.us/j/123456789'
expect
(
zoom_meeting
.
valid?
).
to
eq
(
true
)
expect
(
zoom_meeting
.
errors
).
to
be_empty
end
end
shared_examples
'zoom link does not start with https'
do
|
url
|
it
'fails validation'
do
zoom_meeting
.
url
=
url
expect
(
zoom_meeting
.
valid?
).
to
eq
(
false
)
expect
(
zoom_meeting
.
errors
).
to
be_present
expect
(
zoom_meeting
.
errors
.
first
[
1
]).
to
eq
'must contain one valid Zoom URL'
end
end
context
'when zoom link does not start with https'
do
include_examples
'zoom link does not start with https'
,
'http://zoom.us/j/123456789'
context
'when zoom link does not start with a scheme'
do
include_examples
'zoom link does not start with https'
,
'testinghttp://zoom.us/j/123456789'
end
end
end
end
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment