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
e8879bc4
Commit
e8879bc4
authored
Apr 21, 2020
by
Mario de la Ossa
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Move Milestone methods to Timebox
Move all methods Milestone and Sprint will share to Timebox
parent
6891abfe
Changes
9
Hide whitespace changes
Inline
Side-by-side
Showing
9 changed files
with
425 additions
and
302 deletions
+425
-302
app/models/concerns/timebox.rb
app/models/concerns/timebox.rb
+139
-0
app/models/global_milestone.rb
app/models/global_milestone.rb
+1
-1
app/models/milestone.rb
app/models/milestone.rb
+9
-124
app/models/sprint.rb
app/models/sprint.rb
+15
-0
ee/app/views/shared/milestones/_burndown.html.haml
ee/app/views/shared/milestones/_burndown.html.haml
+1
-1
locale/gitlab.pot
locale/gitlab.pot
+4
-4
spec/models/milestone_spec.rb
spec/models/milestone_spec.rb
+2
-141
spec/models/sprint_spec.rb
spec/models/sprint_spec.rb
+4
-31
spec/support/shared_examples/models/concerns/timebox_shared_examples.rb
...hared_examples/models/concerns/timebox_shared_examples.rb
+250
-0
No files found.
app/models/concerns/timebox.rb
0 → 100644
View file @
e8879bc4
# frozen_string_literal: true
module
Timebox
extend
ActiveSupport
::
Concern
include
AtomicInternalId
include
CacheMarkdownField
include
IidRoutes
include
StripAttribute
included
do
alias_method
:timebox_id
,
:id
validates
:group
,
presence:
true
,
unless: :project
validates
:project
,
presence:
true
,
unless: :group
validates
:title
,
presence:
true
validate
:uniqueness_of_title
,
if: :title_changed?
validate
:timebox_type_check
validate
:start_date_should_be_less_than_due_date
,
if:
proc
{
|
m
|
m
.
start_date
.
present?
&&
m
.
due_date
.
present?
}
validate
:dates_within_4_digits
cache_markdown_field
:title
,
pipeline: :single_line
cache_markdown_field
:description
belongs_to
:project
belongs_to
:group
has_many
:issues
has_many
:labels
,
->
{
distinct
.
reorder
(
'labels.title'
)
},
through: :issues
has_many
:merge_requests
scope
:of_projects
,
->
(
ids
)
{
where
(
project_id:
ids
)
}
scope
:of_groups
,
->
(
ids
)
{
where
(
group_id:
ids
)
}
scope
:active
,
->
{
with_state
(
:active
)
}
scope
:closed
,
->
{
with_state
(
:closed
)
}
scope
:for_projects
,
->
{
where
(
group:
nil
).
includes
(
:project
)
}
scope
:for_projects_and_groups
,
->
(
projects
,
groups
)
do
projects
=
projects
.
compact
if
projects
.
is_a?
Array
projects
=
[]
if
projects
.
nil?
groups
=
groups
.
compact
if
groups
.
is_a?
Array
groups
=
[]
if
groups
.
nil?
where
(
project_id:
projects
).
or
(
where
(
group_id:
groups
))
end
scope
:within_timeframe
,
->
(
start_date
,
end_date
)
do
where
(
'start_date is not NULL or due_date is not NULL'
)
.
where
(
'start_date is NULL or start_date <= ?'
,
end_date
)
.
where
(
'due_date is NULL or due_date >= ?'
,
start_date
)
end
strip_attributes
:title
alias_attribute
:name
,
:title
end
def
title
=
(
value
)
write_attribute
(
:title
,
sanitize_title
(
value
))
if
value
.
present?
end
def
timebox_name
model_name
.
singular
end
def
group_timebox?
group_id
.
present?
end
def
project_timebox?
project_id
.
present?
end
def
safe_title
title
.
to_slug
.
normalize
.
to_s
end
def
resource_parent
group
||
project
end
def
to_ability_name
model_name
.
singular
end
def
merge_requests_enabled?
if
group_timebox?
# Assume that groups have at least one project with merge requests enabled.
# Otherwise, we would need to load all of the projects from the database.
true
elsif
project_timebox?
project
&
.
merge_requests_enabled?
end
end
private
# Timebox titles must be unique across project and group timeboxes
def
uniqueness_of_title
if
project
relation
=
self
.
class
.
for_projects_and_groups
([
project_id
],
[
project
.
group
&
.
id
])
elsif
group
relation
=
self
.
class
.
for_projects_and_groups
(
group
.
projects
.
select
(
:id
),
[
group
.
id
])
end
title_exists
=
relation
.
find_by_title
(
title
)
errors
.
add
(
:title
,
_
(
"already being used for another group or project %{timebox_name}."
)
%
{
timebox_name:
timebox_name
})
if
title_exists
end
# Timebox should be either a project timebox or a group timebox
def
timebox_type_check
if
group_id
&&
project_id
field
=
project_id_changed?
?
:project_id
:
:group_id
errors
.
add
(
field
,
_
(
"%{timebox_name} should belong either to a project or a group."
)
%
{
timebox_name:
timebox_name
})
end
end
def
start_date_should_be_less_than_due_date
if
due_date
<=
start_date
errors
.
add
(
:due_date
,
_
(
"must be greater than start date"
))
end
end
def
dates_within_4_digits
if
start_date
&&
start_date
>
Date
.
new
(
9999
,
12
,
31
)
errors
.
add
(
:start_date
,
_
(
"date must not be after 9999-12-31"
))
end
if
due_date
&&
due_date
>
Date
.
new
(
9999
,
12
,
31
)
errors
.
add
(
:due_date
,
_
(
"date must not be after 9999-12-31"
))
end
end
def
sanitize_title
(
value
)
CGI
.
unescape_html
(
Sanitize
.
clean
(
value
.
to_s
))
end
end
app/models/global_milestone.rb
View file @
e8879bc4
...
...
@@ -11,7 +11,7 @@ class GlobalMilestone
delegate
:title
,
:state
,
:due_date
,
:start_date
,
:participants
,
:project
,
:group
,
:expires_at
,
:closed?
,
:iid
,
:group_milestone?
,
:safe_title
,
:milestoneish_id
,
:resource_parent
,
:releases
,
to: :milestone
:
timebox_id
,
:
milestoneish_id
,
:resource_parent
,
:releases
,
to: :milestone
def
to_hash
{
...
...
app/models/milestone.rb
View file @
e8879bc4
...
...
@@ -15,12 +15,9 @@ class Milestone < ApplicationRecord
Upcoming
=
MilestoneStruct
.
new
(
'Upcoming'
,
'#upcoming'
,
-
2
)
Started
=
MilestoneStruct
.
new
(
'Started'
,
'#started'
,
-
3
)
include
CacheMarkdownField
include
AtomicInternalId
include
IidRoutes
include
Sortable
include
Referable
include
StripAttribute
include
Timebox
include
Milestoneish
include
FromUnion
include
Importable
...
...
@@ -28,61 +25,21 @@ class Milestone < ApplicationRecord
prepend_if_ee
(
'::EE::Milestone'
)
# rubocop: disable Cop/InjectEnterpriseEditionModule
cache_markdown_field
:title
,
pipeline: :single_line
cache_markdown_field
:description
belongs_to
:project
belongs_to
:group
has_many
:milestone_releases
has_many
:releases
,
through: :milestone_releases
has_internal_id
:iid
,
scope: :project
,
track_if:
->
{
!
importing?
},
init:
->
(
s
)
{
s
&
.
project
&
.
milestones
&
.
maximum
(
:iid
)
}
has_internal_id
:iid
,
scope: :group
,
track_if:
->
{
!
importing?
},
init:
->
(
s
)
{
s
&
.
group
&
.
milestones
&
.
maximum
(
:iid
)
}
has_many
:issues
has_many
:labels
,
->
{
distinct
.
reorder
(
'labels.title'
)
},
through: :issues
has_many
:merge_requests
has_many
:events
,
as: :target
,
dependent: :delete_all
# rubocop:disable Cop/ActiveRecordDependent
scope
:of_projects
,
->
(
ids
)
{
where
(
project_id:
ids
)
}
scope
:of_groups
,
->
(
ids
)
{
where
(
group_id:
ids
)
}
scope
:active
,
->
{
with_state
(
:active
)
}
scope
:closed
,
->
{
with_state
(
:closed
)
}
scope
:for_projects
,
->
{
where
(
group:
nil
).
includes
(
:project
)
}
scope
:started
,
->
{
active
.
where
(
'milestones.start_date <= CURRENT_DATE'
)
}
scope
:for_projects_and_groups
,
->
(
projects
,
groups
)
do
projects
=
projects
.
compact
if
projects
.
is_a?
Array
projects
=
[]
if
projects
.
nil?
groups
=
groups
.
compact
if
groups
.
is_a?
Array
groups
=
[]
if
groups
.
nil?
where
(
project_id:
projects
).
or
(
where
(
group_id:
groups
))
end
scope
:within_timeframe
,
->
(
start_date
,
end_date
)
do
where
(
'start_date is not NULL or due_date is not NULL'
)
.
where
(
'start_date is NULL or start_date <= ?'
,
end_date
)
.
where
(
'due_date is NULL or due_date >= ?'
,
start_date
)
end
scope
:order_by_name_asc
,
->
{
order
(
Arel
::
Nodes
::
Ascending
.
new
(
arel_table
[
:title
].
lower
))
}
scope
:reorder_by_due_date_asc
,
->
{
reorder
(
Gitlab
::
Database
.
nulls_last_order
(
'due_date'
,
'ASC'
))
}
validates
:group
,
presence:
true
,
unless: :project
validates
:project
,
presence:
true
,
unless: :group
validates
:title
,
presence:
true
validate
:uniqueness_of_title
,
if: :title_changed?
validate
:milestone_type_check
validate
:start_date_should_be_less_than_due_date
,
if:
proc
{
|
m
|
m
.
start_date
.
present?
&&
m
.
due_date
.
present?
}
validate
:dates_within_4_digits
validates_associated
:milestone_releases
,
message:
->
(
_
,
obj
)
{
obj
[
:value
].
map
(
&
:errors
).
map
(
&
:full_messages
).
join
(
","
)
}
strip_attributes
:title
state_machine
:state
,
initial: :active
do
event
:close
do
transition
active: :closed
...
...
@@ -97,8 +54,6 @@ class Milestone < ApplicationRecord
state
:active
end
alias_attribute
:name
,
:title
class
<<
self
# Searches for milestones with a matching title or description.
#
...
...
@@ -220,7 +175,7 @@ class Milestone < ApplicationRecord
end
##
# Returns the String necessary to reference
this
Milestone in Markdown. Group
# Returns the String necessary to reference
a
Milestone in Markdown. Group
# milestones only support name references, and do not support cross-project
# references.
#
...
...
@@ -248,10 +203,6 @@ class Milestone < ApplicationRecord
self
.
class
.
reference_prefix
+
self
.
title
end
def
milestoneish_id
id
end
def
for_display
self
end
...
...
@@ -264,62 +215,16 @@ class Milestone < ApplicationRecord
nil
end
def
title
=
(
value
)
write_attribute
(
:title
,
sanitize_title
(
value
))
if
value
.
present?
end
def
safe_title
title
.
to_slug
.
normalize
.
to_s
end
def
resource_parent
group
||
project
end
def
to_ability_name
model_name
.
singular
end
def
group_milestone?
group_id
.
present?
end
def
project_milestone?
project_id
.
present?
end
def
merge_requests_enabled?
if
group_milestone?
# Assume that groups have at least one project with merge requests enabled.
# Otherwise, we would need to load all of the projects from the database.
true
elsif
project_milestone?
project
&
.
merge_requests_enabled?
end
end
# TODO: remove after all code paths use `timebox_id`
# https://gitlab.com/gitlab-org/gitlab/-/issues/215688
alias_method
:milestoneish_id
,
:timebox_id
# TODO: remove after all code paths use (group|project)_timebox?
# https://gitlab.com/gitlab-org/gitlab/-/issues/215690
alias_method
:group_milestone?
,
:group_timebox?
alias_method
:project_milestone?
,
:project_timebox?
private
# Milestone titles must be unique across project milestones and group milestones
def
uniqueness_of_title
if
project
relation
=
Milestone
.
for_projects_and_groups
([
project_id
],
[
project
.
group
&
.
id
])
elsif
group
relation
=
Milestone
.
for_projects_and_groups
(
group
.
projects
.
select
(
:id
),
[
group
.
id
])
end
title_exists
=
relation
.
find_by_title
(
title
)
errors
.
add
(
:title
,
_
(
"already being used for another group or project milestone."
))
if
title_exists
end
# Milestone should be either a project milestone or a group milestone
def
milestone_type_check
if
group_id
&&
project_id
field
=
project_id_changed?
?
:project_id
:
:group_id
errors
.
add
(
field
,
_
(
"milestone should belong either to a project or a group."
))
end
end
def
milestone_format_reference
(
format
=
:iid
)
raise
ArgumentError
,
_
(
'Unknown format'
)
unless
[
:iid
,
:name
].
include?
(
format
)
...
...
@@ -334,26 +239,6 @@ class Milestone < ApplicationRecord
end
end
def
sanitize_title
(
value
)
CGI
.
unescape_html
(
Sanitize
.
clean
(
value
.
to_s
))
end
def
start_date_should_be_less_than_due_date
if
due_date
<=
start_date
errors
.
add
(
:due_date
,
_
(
"must be greater than start date"
))
end
end
def
dates_within_4_digits
if
start_date
&&
start_date
>
Date
.
new
(
9999
,
12
,
31
)
errors
.
add
(
:start_date
,
_
(
"date must not be after 9999-12-31"
))
end
if
due_date
&&
due_date
>
Date
.
new
(
9999
,
12
,
31
)
errors
.
add
(
:due_date
,
_
(
"date must not be after 9999-12-31"
))
end
end
def
issues_finder_params
{
project_id:
project_id
,
group_id:
group_id
,
include_subgroups:
group_id
.
present?
}.
compact
end
...
...
app/models/sprint.rb
View file @
e8879bc4
# frozen_string_literal: true
class
Sprint
<
ApplicationRecord
include
Timebox
STATE_ID_MAP
=
{
active:
1
,
closed:
2
...
...
@@ -16,4 +18,17 @@ class Sprint < ApplicationRecord
has_internal_id
:iid
,
scope: :project
,
init:
->
(
s
)
{
s
&
.
project
&
.
sprints
&
.
maximum
(
:iid
)
}
has_internal_id
:iid
,
scope: :group
,
init:
->
(
s
)
{
s
&
.
group
&
.
sprints
&
.
maximum
(
:iid
)
}
state_machine
:state
,
initial: :active
do
event
:close
do
transition
active: :closed
end
event
:activate
do
transition
closed: :active
end
state
:active
,
value:
Sprint
::
STATE_ID_MAP
[
:active
]
state
:closed
,
value:
Sprint
::
STATE_ID_MAP
[
:closed
]
end
end
ee/app/views/shared/milestones/_burndown.html.haml
View file @
e8879bc4
-
milestone
=
local_assigns
[
:milestone
]
-
burndown
=
burndown_chart
(
milestone
)
-
warning
=
data_warning_for
(
burndown
)
-
burndown_endpoint
=
milestone
.
group_milestone?
?
api_v4_groups_milestones_burndown_events_path
(
id:
milestone
.
group
.
id
,
milestone_id:
milestone
.
id
)
:
api_v4_projects_milestones_burndown_events_path
(
id:
milestone
.
project
.
id
,
milestone_id:
milestone
.
milestoneish
_id
)
-
burndown_endpoint
=
milestone
.
group_milestone?
?
api_v4_groups_milestones_burndown_events_path
(
id:
milestone
.
group
.
id
,
milestone_id:
milestone
.
id
)
:
api_v4_projects_milestones_burndown_events_path
(
id:
milestone
.
project
.
id
,
milestone_id:
milestone
.
timebox
_id
)
=
warning
...
...
locale/gitlab.pot
View file @
e8879bc4
...
...
@@ -516,6 +516,9 @@ msgstr[1] ""
msgid "%{text} is available"
msgstr ""
msgid "%{timebox_name} should belong either to a project or a group."
msgstr ""
msgid "%{title} %{operator} %{threshold}"
msgstr ""
...
...
@@ -24473,7 +24476,7 @@ msgstr ""
msgid "allowed to fail"
msgstr ""
msgid "already being used for another group or project
milestone
."
msgid "already being used for another group or project
%{timebox_name}
."
msgstr ""
msgid "already has a \"created\" issue link"
...
...
@@ -25086,9 +25089,6 @@ msgstr[1] ""
msgid "merged %{time_ago}"
msgstr ""
msgid "milestone should belong either to a project or a group."
msgstr ""
msgid "missing"
msgstr ""
...
...
spec/models/milestone_spec.rb
View file @
e8879bc4
...
...
@@ -3,6 +3,8 @@
require
'spec_helper'
describe
Milestone
do
it_behaves_like
'a timebox'
,
:milestone
describe
'MilestoneStruct#serializable_hash'
do
let
(
:predefined_milestone
)
{
described_class
::
MilestoneStruct
.
new
(
'Test Milestone'
,
'#test'
,
1
)
}
...
...
@@ -15,69 +17,11 @@ describe Milestone do
end
end
describe
'modules'
do
context
'with a project'
do
it_behaves_like
'AtomicInternalId'
do
let
(
:internal_id_attribute
)
{
:iid
}
let
(
:instance
)
{
build
(
:milestone
,
project:
build
(
:project
),
group:
nil
)
}
let
(
:scope
)
{
:project
}
let
(
:scope_attrs
)
{
{
project:
instance
.
project
}
}
let
(
:usage
)
{
:milestones
}
end
end
context
'with a group'
do
it_behaves_like
'AtomicInternalId'
do
let
(
:internal_id_attribute
)
{
:iid
}
let
(
:instance
)
{
build
(
:milestone
,
project:
nil
,
group:
build
(
:group
))
}
let
(
:scope
)
{
:group
}
let
(
:scope_attrs
)
{
{
namespace:
instance
.
group
}
}
let
(
:usage
)
{
:milestones
}
end
end
end
describe
"Validation"
do
before
do
allow
(
subject
).
to
receive
(
:set_iid
).
and_return
(
false
)
end
describe
'start_date'
do
it
'adds an error when start_date is greater then due_date'
do
milestone
=
build
(
:milestone
,
start_date:
Date
.
tomorrow
,
due_date:
Date
.
yesterday
)
expect
(
milestone
).
not_to
be_valid
expect
(
milestone
.
errors
[
:due_date
]).
to
include
(
"must be greater than start date"
)
end
it
'adds an error when start_date is greater than 9999-12-31'
do
milestone
=
build
(
:milestone
,
start_date:
Date
.
new
(
10000
,
1
,
1
))
expect
(
milestone
).
not_to
be_valid
expect
(
milestone
.
errors
[
:start_date
]).
to
include
(
"date must not be after 9999-12-31"
)
end
end
describe
'due_date'
do
it
'adds an error when due_date is greater than 9999-12-31'
do
milestone
=
build
(
:milestone
,
due_date:
Date
.
new
(
10000
,
1
,
1
))
expect
(
milestone
).
not_to
be_valid
expect
(
milestone
.
errors
[
:due_date
]).
to
include
(
"date must not be after 9999-12-31"
)
end
end
describe
'title'
do
it
{
is_expected
.
to
validate_presence_of
(
:title
)
}
it
'is invalid if title would be empty after sanitation'
do
milestone
=
build
(
:milestone
,
project:
project
,
title:
'<img src=x onerror=prompt(1)>'
)
expect
(
milestone
).
not_to
be_valid
expect
(
milestone
.
errors
[
:title
]).
to
include
(
"can't be blank"
)
end
end
describe
'milestone_releases'
do
let
(
:milestone
)
{
build
(
:milestone
,
project:
project
)
}
...
...
@@ -99,8 +43,6 @@ describe Milestone do
end
describe
"Associations"
do
it
{
is_expected
.
to
belong_to
(
:project
)
}
it
{
is_expected
.
to
have_many
(
:issues
)
}
it
{
is_expected
.
to
have_many
(
:releases
)
}
it
{
is_expected
.
to
have_many
(
:milestone_releases
)
}
end
...
...
@@ -110,87 +52,6 @@ describe Milestone do
let
(
:issue
)
{
create
(
:issue
,
project:
project
)
}
let
(
:user
)
{
create
(
:user
)
}
describe
"#title"
do
let
(
:milestone
)
{
create
(
:milestone
,
title:
"<b>foo & bar -> 2.2</b>"
)
}
it
"sanitizes title"
do
expect
(
milestone
.
title
).
to
eq
(
"foo & bar -> 2.2"
)
end
end
describe
'#merge_requests_enabled?'
do
context
"per project"
do
it
"is true for projects with MRs enabled"
do
project
=
create
(
:project
,
:merge_requests_enabled
)
milestone
=
create
(
:milestone
,
project:
project
)
expect
(
milestone
.
merge_requests_enabled?
).
to
be
(
true
)
end
it
"is false for projects with MRs disabled"
do
project
=
create
(
:project
,
:repository_enabled
,
:merge_requests_disabled
)
milestone
=
create
(
:milestone
,
project:
project
)
expect
(
milestone
.
merge_requests_enabled?
).
to
be
(
false
)
end
it
"is false for projects with repository disabled"
do
project
=
create
(
:project
,
:repository_disabled
)
milestone
=
create
(
:milestone
,
project:
project
)
expect
(
milestone
.
merge_requests_enabled?
).
to
be
(
false
)
end
end
context
"per group"
do
let
(
:group
)
{
create
(
:group
)
}
let
(
:milestone
)
{
create
(
:milestone
,
group:
group
)
}
it
"is always true for groups, for performance reasons"
do
expect
(
milestone
.
merge_requests_enabled?
).
to
be
(
true
)
end
end
end
describe
"unique milestone title"
do
context
"per project"
do
it
"does not accept the same title in a project twice"
do
new_milestone
=
described_class
.
new
(
project:
milestone
.
project
,
title:
milestone
.
title
)
expect
(
new_milestone
).
not_to
be_valid
end
it
"accepts the same title in another project"
do
project
=
create
(
:project
)
new_milestone
=
described_class
.
new
(
project:
project
,
title:
milestone
.
title
)
expect
(
new_milestone
).
to
be_valid
end
end
context
"per group"
do
let
(
:group
)
{
create
(
:group
)
}
let
(
:milestone
)
{
create
(
:milestone
,
group:
group
)
}
before
do
project
.
update
(
group:
group
)
end
it
"does not accept the same title in a group twice"
do
new_milestone
=
described_class
.
new
(
group:
group
,
title:
milestone
.
title
)
expect
(
new_milestone
).
not_to
be_valid
end
it
"does not accept the same title of a child project milestone"
do
create
(
:milestone
,
project:
group
.
projects
.
first
)
new_milestone
=
described_class
.
new
(
group:
group
,
title:
milestone
.
title
)
expect
(
new_milestone
).
not_to
be_valid
end
end
end
describe
'.predefined_id?'
do
it
'returns true for a predefined Milestone ID'
do
expect
(
Milestone
.
predefined_id?
(
described_class
::
Upcoming
.
id
)).
to
be
true
...
...
spec/models/sprint_spec.rb
View file @
e8879bc4
...
...
@@ -3,39 +3,12 @@
require
'spec_helper'
describe
Sprint
do
let!
(
:project
)
{
create
(
:project
)
}
let!
(
:group
)
{
create
(
:group
)
}
describe
'modules'
do
context
'with a project'
do
it_behaves_like
'AtomicInternalId'
do
let
(
:internal_id_attribute
)
{
:iid
}
let
(
:instance
)
{
build
(
:sprint
,
project:
build
(
:project
),
group:
nil
)
}
let
(
:scope
)
{
:project
}
let
(
:scope_attrs
)
{
{
project:
instance
.
project
}
}
let
(
:usage
)
{
:sprints
}
end
end
context
'with a group'
do
it_behaves_like
'AtomicInternalId'
do
let
(
:internal_id_attribute
)
{
:iid
}
let
(
:instance
)
{
build
(
:sprint
,
project:
nil
,
group:
build
(
:group
))
}
let
(
:scope
)
{
:group
}
let
(
:scope_attrs
)
{
{
namespace:
instance
.
group
}
}
let
(
:usage
)
{
:sprints
}
end
end
end
describe
"Associations"
do
it
{
is_expected
.
to
belong_to
(
:project
)
}
it
{
is_expected
.
to
belong_to
(
:group
)
}
it
{
is_expected
.
to
have_many
(
:issues
)
}
it
{
is_expected
.
to
have_many
(
:merge_requests
)
}
end
it_behaves_like
'a timebox'
,
:sprint
describe
"#iid"
do
let!
(
:project
)
{
create
(
:project
)
}
let!
(
:group
)
{
create
(
:group
)
}
it
"is properly scoped on project and group"
do
sprint1
=
create
(
:sprint
,
project:
project
)
sprint2
=
create
(
:sprint
,
project:
project
)
...
...
spec/support/shared_examples/models/concerns/timebox_shared_examples.rb
0 → 100644
View file @
e8879bc4
# frozen_string_literal: true
RSpec
.
shared_examples
'a timebox'
do
|
timebox_type
|
let
(
:project
)
{
create
(
:project
,
:public
)
}
let
(
:group
)
{
create
(
:group
)
}
let
(
:timebox
)
{
create
(
timebox_type
,
project:
project
)
}
let
(
:issue
)
{
create
(
:issue
,
project:
project
)
}
let
(
:user
)
{
create
(
:user
)
}
let
(
:timebox_table_name
)
{
timebox_type
.
to_s
.
pluralize
.
to_sym
}
describe
'modules'
do
context
'with a project'
do
it_behaves_like
'AtomicInternalId'
do
let
(
:internal_id_attribute
)
{
:iid
}
let
(
:instance
)
{
build
(
timebox_type
,
project:
build
(
:project
),
group:
nil
)
}
let
(
:scope
)
{
:project
}
let
(
:scope_attrs
)
{
{
project:
instance
.
project
}
}
let
(
:usage
)
{
timebox_table_name
}
end
end
context
'with a group'
do
it_behaves_like
'AtomicInternalId'
do
let
(
:internal_id_attribute
)
{
:iid
}
let
(
:instance
)
{
build
(
timebox_type
,
project:
nil
,
group:
build
(
:group
))
}
let
(
:scope
)
{
:group
}
let
(
:scope_attrs
)
{
{
namespace:
instance
.
group
}
}
let
(
:usage
)
{
timebox_table_name
}
end
end
end
describe
"Validation"
do
before
do
allow
(
subject
).
to
receive
(
:set_iid
).
and_return
(
false
)
end
describe
'start_date'
do
it
'adds an error when start_date is greater then due_date'
do
timebox
=
build
(
timebox_type
,
start_date:
Date
.
tomorrow
,
due_date:
Date
.
yesterday
)
expect
(
timebox
).
not_to
be_valid
expect
(
timebox
.
errors
[
:due_date
]).
to
include
(
"must be greater than start date"
)
end
it
'adds an error when start_date is greater than 9999-12-31'
do
timebox
=
build
(
timebox_type
,
start_date:
Date
.
new
(
10000
,
1
,
1
))
expect
(
timebox
).
not_to
be_valid
expect
(
timebox
.
errors
[
:start_date
]).
to
include
(
"date must not be after 9999-12-31"
)
end
end
describe
'due_date'
do
it
'adds an error when due_date is greater than 9999-12-31'
do
timebox
=
build
(
timebox_type
,
due_date:
Date
.
new
(
10000
,
1
,
1
))
expect
(
timebox
).
not_to
be_valid
expect
(
timebox
.
errors
[
:due_date
]).
to
include
(
"date must not be after 9999-12-31"
)
end
end
describe
'title'
do
it
{
is_expected
.
to
validate_presence_of
(
:title
)
}
it
'is invalid if title would be empty after sanitation'
do
timebox
=
build
(
timebox_type
,
project:
project
,
title:
'<img src=x onerror=prompt(1)>'
)
expect
(
timebox
).
not_to
be_valid
expect
(
timebox
.
errors
[
:title
]).
to
include
(
"can't be blank"
)
end
end
describe
'#timebox_type_check'
do
it
'is invalid if it has both project_id and group_id'
do
timebox
=
build
(
timebox_type
,
group:
group
)
timebox
.
project
=
project
expect
(
timebox
).
not_to
be_valid
expect
(
timebox
.
errors
[
:project_id
]).
to
include
(
"
#{
timebox_type
}
should belong either to a project or a group."
)
end
end
describe
"#uniqueness_of_title"
do
context
"per project"
do
it
"does not accept the same title in a project twice"
do
new_timebox
=
described_class
.
new
(
project:
timebox
.
project
,
title:
timebox
.
title
)
expect
(
new_timebox
).
not_to
be_valid
end
it
"accepts the same title in another project"
do
project
=
create
(
:project
)
new_timebox
=
described_class
.
new
(
project:
project
,
title:
timebox
.
title
)
expect
(
new_timebox
).
to
be_valid
end
end
context
"per group"
do
let
(
:timebox
)
{
create
(
timebox_type
,
group:
group
)
}
before
do
project
.
update
(
group:
group
)
end
it
"does not accept the same title in a group twice"
do
new_timebox
=
described_class
.
new
(
group:
group
,
title:
timebox
.
title
)
expect
(
new_timebox
).
not_to
be_valid
end
it
"does not accept the same title of a child project timebox"
do
create
(
timebox_type
,
project:
group
.
projects
.
first
)
new_timebox
=
described_class
.
new
(
group:
group
,
title:
timebox
.
title
)
expect
(
new_timebox
).
not_to
be_valid
end
end
end
end
describe
"Associations"
do
it
{
is_expected
.
to
belong_to
(
:project
)
}
it
{
is_expected
.
to
belong_to
(
:group
)
}
it
{
is_expected
.
to
have_many
(
:issues
)
}
it
{
is_expected
.
to
have_many
(
:merge_requests
)
}
it
{
is_expected
.
to
have_many
(
:labels
)
}
end
describe
'#timebox_name'
do
it
'returns the name of the model'
do
expect
(
timebox
.
timebox_name
).
to
eq
(
timebox_type
.
to_s
)
end
end
describe
'#project_timebox?'
do
context
'when project_id is present'
do
it
'returns true'
do
expect
(
timebox
.
project_timebox?
).
to
be_truthy
end
end
context
'when project_id is not present'
do
let
(
:timebox
)
{
build
(
timebox_type
,
group:
group
)
}
it
'returns false'
do
expect
(
timebox
.
project_timebox?
).
to
be_falsey
end
end
end
describe
'#group_timebox?'
do
context
'when group_id is present'
do
let
(
:timebox
)
{
build
(
timebox_type
,
group:
group
)
}
it
'returns true'
do
expect
(
timebox
.
group_timebox?
).
to
be_truthy
end
end
context
'when group_id is not present'
do
it
'returns false'
do
expect
(
timebox
.
group_timebox?
).
to
be_falsey
end
end
end
describe
'#safe_title'
do
let
(
:timebox
)
{
create
(
timebox_type
,
title:
"<b>foo & bar -> 2.2</b>"
)
}
it
'normalizes the title for use as a slug'
do
expect
(
timebox
.
safe_title
).
to
eq
(
'foo-bar-22'
)
end
end
describe
'#resource_parent'
do
context
'when group is present'
do
let
(
:timebox
)
{
build
(
timebox_type
,
group:
group
)
}
it
'returns the group'
do
expect
(
timebox
.
resource_parent
).
to
eq
(
group
)
end
end
context
'when project is present'
do
it
'returns the project'
do
expect
(
timebox
.
resource_parent
).
to
eq
(
project
)
end
end
end
describe
"#title"
do
let
(
:timebox
)
{
create
(
timebox_type
,
title:
"<b>foo & bar -> 2.2</b>"
)
}
it
"sanitizes title"
do
expect
(
timebox
.
title
).
to
eq
(
"foo & bar -> 2.2"
)
end
end
describe
'#merge_requests_enabled?'
do
context
"per project"
do
it
"is true for projects with MRs enabled"
do
project
=
create
(
:project
,
:merge_requests_enabled
)
timebox
=
create
(
timebox_type
,
project:
project
)
expect
(
timebox
.
merge_requests_enabled?
).
to
be_truthy
end
it
"is false for projects with MRs disabled"
do
project
=
create
(
:project
,
:repository_enabled
,
:merge_requests_disabled
)
timebox
=
create
(
timebox_type
,
project:
project
)
expect
(
timebox
.
merge_requests_enabled?
).
to
be_falsey
end
it
"is false for projects with repository disabled"
do
project
=
create
(
:project
,
:repository_disabled
)
timebox
=
create
(
timebox_type
,
project:
project
)
expect
(
timebox
.
merge_requests_enabled?
).
to
be_falsey
end
end
context
"per group"
do
let
(
:timebox
)
{
create
(
timebox_type
,
group:
group
)
}
it
"is always true for groups, for performance reasons"
do
expect
(
timebox
.
merge_requests_enabled?
).
to
be_truthy
end
end
end
it_behaves_like
'within_timeframe scope'
do
let_it_be
(
:now
)
{
Time
.
now
}
let_it_be
(
:project
)
{
create
(
:project
,
:empty_repo
)
}
let_it_be
(
:resource_1
)
{
create
(
timebox_type
,
project:
project
,
start_date:
now
-
1
.
day
,
due_date:
now
+
1
.
day
)
}
let_it_be
(
:resource_2
)
{
create
(
timebox_type
,
project:
project
,
start_date:
now
+
2
.
days
,
due_date:
now
+
3
.
days
)
}
let_it_be
(
:resource_3
)
{
create
(
timebox_type
,
project:
project
,
due_date:
now
)
}
let_it_be
(
:resource_4
)
{
create
(
timebox_type
,
project:
project
,
start_date:
now
)
}
end
describe
'#to_ability_name'
do
it
'returns timebox'
do
timebox
=
build
(
timebox_type
)
expect
(
timebox
.
to_ability_name
).
to
eq
(
timebox_type
.
to_s
)
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