danger-samsao 0.2.0 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,52 +1,166 @@
1
1
  module Samsao
2
2
  # Actions mixin module
3
3
  module Actions
4
- # Fails when git branching model is not respected for PR branch name.
4
+ # Check if the git branching model is not respected for PR branch name.
5
5
  #
6
+ # @param [Symbol] level (Default: :fail)
7
+ # The report level (:fail, :warn, :message) if the check fails [report](#report)
6
8
  # @return [void]
7
- def fail_when_wrong_branching_model
9
+ #
10
+ def check_wrong_branching_model(level = :fail)
8
11
  message = 'Your branch should be prefixed with feature/, fix/, bugfix/, hotfix/, release/ or support/!'
9
12
 
10
- fail message unless respects_branching_model
13
+ report(level, message) unless respects_branching_model
11
14
  end
12
15
 
13
- # Fails when a feature branch have than one commit
16
+ # Check if a feature branch have more than one commit.
14
17
  #
18
+ # @param [Symbol] level (Default: :fail)
19
+ # The report level (:fail, :warn, :message) if the check fails [report](#report)
15
20
  # @return [void]
16
- def fail_when_non_single_commit_feature
21
+ #
22
+ def check_non_single_commit_feature(level = :fail)
17
23
  commit_count = git.commits.size
18
24
  message = "Your feature branch should have a single commit but found #{commit_count}, squash them together!"
19
25
 
20
- fail message if feature_branch? && commit_count > 1
26
+ report(level, message) if feature_branch? && commit_count > 1
21
27
  end
22
28
 
23
- # Fails when CHANGELOG is not updated on feature or fix branches
29
+ # Check if the CHANGELOG wasn't updated on feature or fix branches.
24
30
  #
31
+ # @param [Symbol] level (Default: :fail)
32
+ # The report level (:fail, :warn, :message) if the check fails [report](#report)
25
33
  # @return [void]
26
- def fail_when_changelog_update_missing
34
+ #
35
+ def check_changelog_update_missing(level = :fail)
27
36
  return if trivial_change?
28
37
  return if support_branch? && config.project_type == :application
29
38
 
30
- fail 'You did a change without updating CHANGELOG file!' unless changelog_modified?
39
+ report(level, 'You did a change without updating CHANGELOG file!') unless changelog_modified?
31
40
  end
32
41
 
33
- # Fails when one or more merge commit is detected.
42
+ # Check if one or more merge commit is detected.
34
43
  #
44
+ # @param [Symbol] level (Default: :fail)
45
+ # The report level (:fail, :warn, :message) if the check fails [report](#report)
35
46
  # @return [void]
36
- def fail_when_merge_commit_detected
47
+ #
48
+ def check_merge_commit_detected(level = :fail)
37
49
  message = 'Some merge commits were detected, you must use rebase to sync with base branch.'
38
50
  merge_commit_detector = /^Merge branch '#{github.branch_for_base}'/
39
51
 
40
- fail message if git.commits.any? { |commit| commit.message =~ merge_commit_detector }
52
+ report(level, message) if git.commits.any? { |commit| commit.message =~ merge_commit_detector }
53
+ end
54
+
55
+ # Check for work in progress commit message in PR.
56
+ #
57
+ # @param [Symbol] level (Default: :warn)
58
+ # The report level (:fail, :warn, :message) if the check fails [report](#report)
59
+ # @return [void]
60
+ #
61
+ def check_work_in_progess_pr(level = :warn)
62
+ report(level, 'Do not merge, PR is a work in progess [WIP]!') if github.pr_title.include?('[WIP]')
63
+ end
64
+
65
+ # Check if a feature branch contains a single JIRA issue number matching the jira project key.
66
+ #
67
+ # @param [Symbol] level (Default: :fail)
68
+ # The report level (:fail, :warn, :message) if the check fails [report](#report)
69
+ # @return [void]
70
+ #
71
+ def check_feature_jira_issue_number(level = :fail)
72
+ return if samsao.trivial_change? || !samsao.feature_branch?
73
+ return report(:fail, 'Your Danger config is missing a `jira_project_key` value.') unless jira_project_key?
74
+
75
+ message = 'The PR must starts with JIRA issue number between square brackets'\
76
+ " (i.e. [#{config.jira_project_key}-XXX])."
77
+
78
+ report(level, message) unless contains_jira_issue_number?(github.pr_title)
79
+ end
80
+
81
+ # Check if all fix branch commit's message contains any JIRA issue number matching the jira project key.
82
+ #
83
+ # @param [Symbol] level (Default: :warn)
84
+ # The report level (:fail, :warn, :message) if the check fails [report](#report)
85
+ # @return [void]
86
+ #
87
+ def check_fix_jira_issue_number(level = :warn)
88
+ return if samsao.trivial_change? || !samsao.fix_branch?
89
+ return report(:fail, 'Your Danger config is missing a `jira_project_key` value.') unless jira_project_key?
90
+
91
+ git.commits.each do |commit|
92
+ check_commit_contains_jira_issue_number(commit, level)
93
+ end
41
94
  end
42
95
 
43
- # Fails when CHANGELOG is not updated on feature or fix branches
96
+ # Check if it's a feature branch and if the PR body contains acceptance criteria.
44
97
  #
98
+ # @param [Symbol] level (Default: :warn)
99
+ # The report level (:fail, :warn, :message) if the check fails [report](#report)
45
100
  # @return [void]
46
- def warn_when_work_in_progess_pr
47
- message = 'Do not merge, PR is a work in progess [WIP]!'
101
+ #
102
+ def check_acceptance_criteria(level = :warn)
103
+ return unless samsao.feature_branch?
104
+
105
+ message = 'The PR should have the acceptance criteria in the body.'
106
+
107
+ report(level, message) if (/acceptance criteria/i =~ github.pr_body).nil?
108
+ end
109
+
110
+ # Check if the PR has at least one label added to it.
111
+ #
112
+ # @param [Symbol] level (Default: :fail)
113
+ # The report level (:fail, :warn, :message) if the check fails [report](#report)
114
+ # @return [void]
115
+ #
116
+ def check_label_pr(level = :fail)
117
+ message = 'The PR should have at least one label added to it.'
118
+
119
+ report(level, message) if github.pr_labels.nil? || github.pr_labels.empty?
120
+ end
121
+
122
+ # Send report to danger depending on the level.
123
+ #
124
+ # @param [Symbol] level
125
+ # The report level sent to Danger :
126
+ # :message > Comment a message to the table
127
+ # :warn > Declares a CI warning
128
+ # :fail > Declares a CI blocking error
129
+ # @param [String] content
130
+ # The message of the report sent to Danger
131
+ # @return [void]
132
+ #
133
+ def report(level, content)
134
+ case level
135
+ when :warn
136
+ warn content
137
+ when :fail
138
+ fail content
139
+ when :message
140
+ message content
141
+ else
142
+ raise "Report level '#{level}' is invalid."
143
+ end
144
+ end
145
+
146
+ private
147
+
148
+ # Check if the commit's message contains any JIRA issue number matching the jira project key.
149
+ #
150
+ # @param [Commit] commit
151
+ # The git commit to check
152
+ # @param [Symbol] level (Default: :warn)
153
+ # The report level (:fail, :warn, :message) if the check fails [report](#report)
154
+ # @return [void]
155
+ #
156
+ def check_commit_contains_jira_issue_number(commit, type)
157
+ commit_id = "#{shorten_sha(commit.sha)} ('#{truncate(commit.message)}')"
158
+ jira_project_key = config.jira_project_key
159
+ message = "The commit #{commit_id} should contain JIRA issue number" \
160
+ " between square brackets (i.e. [#{jira_project_key}-XXX]), multiple allowed" \
161
+ " (i.e. [#{jira_project_key}-XXX, #{jira_project_key}-YYY, #{jira_project_key}-ZZZ])"
48
162
 
49
- warn message if github.pr_title.include?('[WIP]')
163
+ report(type, message) unless contains_jira_issue_number?(commit.message)
50
164
  end
51
165
  end
52
166
  end
data/lib/samsao/config.rb CHANGED
@@ -7,6 +7,7 @@ module Danger
7
7
  @changelogs = ['CHANGELOG.md']
8
8
  @sources = []
9
9
  @project_type = :application
10
+ @jira_project_key = nil
10
11
  end
11
12
 
12
13
  def changelogs(*entries)
@@ -15,6 +16,12 @@ module Danger
15
16
  @changelogs = entries
16
17
  end
17
18
 
19
+ def jira_project_key(key = nil)
20
+ return @jira_project_key if key.nil?
21
+
22
+ @jira_project_key = validate_jira_project_key(key)
23
+ end
24
+
18
25
  def project_type(type = nil)
19
26
  return @project_type if type.nil?
20
27
 
@@ -29,6 +36,12 @@ module Danger
29
36
 
30
37
  private
31
38
 
39
+ def validate_jira_project_key(key)
40
+ return key unless (/^[A-Z]{1,10}$/ =~ key).nil?
41
+
42
+ raise "Jira project key '#{key}' is invalid, must be uppercase and between 1 and 10 characters"
43
+ end
44
+
32
45
  def validate_project_type(type)
33
46
  return type if valid_project_type?(type)
34
47
 
@@ -1,3 +1,3 @@
1
1
  module Samsao
2
- VERSION = '0.2.0'.freeze
2
+ VERSION = '0.3.0'.freeze
3
3
  end
@@ -6,52 +6,59 @@ module Samsao
6
6
  # Check if any changelog were modified. When the helper receives nothing,
7
7
  # changelogs defined by the config are used.
8
8
  #
9
- # @return [Boolean] True if any changelogs were modified in this commit
9
+ # @return [Bool] True
10
+ # If any changelogs were modified in this commit
11
+ #
10
12
  def changelog_modified?(*changelogs)
11
13
  changelogs = config.changelogs if changelogs.nil? || changelogs.empty?
12
14
 
13
15
  changelogs.any? { |changelog| git.modified_files.include?(changelog) }
14
16
  end
15
17
 
16
- # Return true if the current PR branch is a feature branch
18
+ # Return true if the current PR branch is a feature branch.
19
+ #
20
+ # @return [Bool]
17
21
  #
18
- # @return [void]
19
22
  def feature_branch?
20
23
  git_branch.start_with?('feature/')
21
24
  end
22
25
 
23
- # Return true if the current PR branch is a bug fix branch
26
+ # Return true if the current PR branch is a bug fix branch.
27
+ #
28
+ # @return [Bool]
24
29
  #
25
- # @return [void]
26
30
  def fix_branch?
27
31
  !(%r{^(bug|hot)?fix/} =~ git_branch).nil?
28
32
  end
29
33
 
30
- # Return true if the current PR branch is a release branch
34
+ # Return true if the current PR branch is a release branch.
35
+ #
36
+ # @return [Bool]
31
37
  #
32
- # @return [void]
33
38
  def release_branch?
34
39
  git_branch.start_with?('release/')
35
40
  end
36
41
 
37
- # Return true if the current PR branch is a support branch
42
+ # Return true if the current PR branch is a support branch.
43
+ #
44
+ # @return [Bool]
38
45
  #
39
- # @return [void]
40
46
  def support_branch?
41
47
  git_branch.start_with?('support/')
42
48
  end
43
49
 
44
- # Return true if the current PR is a trivial change, i.e. if
45
- # PR title contains #trivial or #typo markers.
50
+ # Return true if the current PR is a trivial change, i.e. if PR title contains #trivial or #typo markers.
51
+ #
52
+ # @return [Bool]
46
53
  #
47
- # @return [void]
48
54
  def trivial_change?
49
55
  !(/#(trivial|typo(s)?)/ =~ github.pr_title).nil?
50
56
  end
51
57
 
52
58
  # Return true if any source files are in the git modified files list.
53
59
  #
54
- # @return [void]
60
+ # @return [Bool]
61
+ #
55
62
  def has_app_changes?(*sources)
56
63
  sources = config.sources if sources.nil? || sources.empty?
57
64
 
@@ -62,6 +69,48 @@ module Samsao
62
69
  end
63
70
  end
64
71
 
72
+ # Return true if the config has a jira project key.
73
+ #
74
+ # @return [Bool]
75
+ #
76
+ def jira_project_key?
77
+ !config.jira_project_key.nil?
78
+ end
79
+
80
+ # Return true if PR title contains any number of JIRA issues.
81
+ #
82
+ # @return [Bool]
83
+ #
84
+ def contains_jira_issue_number?(input)
85
+ !(/\[#{config.jira_project_key}-\d+(,( *#{config.jira_project_key}-)? *\d+)*\]/ =~ input).nil?
86
+ end
87
+
88
+ # Shorten git commit's sha to seven characters.
89
+ #
90
+ # @param [String] sha
91
+ # A git commit's sha
92
+ # @return [String]
93
+ #
94
+ def shorten_sha(sha)
95
+ return sha if sha.nil?
96
+
97
+ sha[0..7]
98
+ end
99
+
100
+ # Truncate the string received.
101
+ #
102
+ # @param [String] input
103
+ # The string to truncate
104
+ # @param [Number] max (Default: 30)
105
+ # The max size of the truncated string
106
+ # @return [String]
107
+ #
108
+ def truncate(input, max = 30)
109
+ return input if input.nil? || input.length <= max
110
+
111
+ input[0..max - 1].gsub(/\s\w+\s*$/, '...')
112
+ end
113
+
65
114
  private
66
115
 
67
116
  def git_branch
data/lib/samsao/plugin.rb CHANGED
@@ -18,6 +18,7 @@ module Danger
18
18
  # Enable to configure the plugin configuration object.
19
19
  #
20
20
  # @return [Array<String>] (if no block given)
21
+ #
21
22
  def config(&block)
22
23
  @config = Danger::SamsaoConfig.new if @config.nil?
23
24
  return @config unless block_given?
data/lib/samsao/regexp.rb CHANGED
@@ -4,9 +4,11 @@ module Samsao
4
4
  # Turns a source entry input into a Regexp. Uses rules from [from_matcher](#from_matcher)
5
5
  # function and append a `^` to final regexp when the source if a pure String.
6
6
  #
7
- # @param matcher The source entry to transform into a Regexp
7
+ # @param [String] source
8
+ # The source entry to transform into a Regexp
9
+ # @return [Regexp]
10
+ # The source entry as a regexp
8
11
  #
9
- # @return [Regexp] The source entry as a regexp
10
12
  def self.from_source(source)
11
13
  from_matcher(source, when_string_pattern_prefix_with: '^')
12
14
  end
@@ -15,11 +17,13 @@ module Samsao
15
17
  # into a Regexp and escape all special characters. If the input is already
16
18
  # a Regexp, it returns it without modification.
17
19
  #
18
- # @param matcher The input matcher to transform into a Regexp
19
- # @keyword when_string_pattern_prefix_with (Default: '') The prefix that should be added to
20
- # final regexp when the input matcher is a string type
20
+ # @param [String] matcher
21
+ # The input matcher to transform into a Regexp
22
+ # @keyword [String] when_string_pattern_prefix_with (Default: '')
23
+ # The prefix that should be added to final regexp when the input matcher is a string type
24
+ # @return [Regexp]
25
+ # The input as a regexp
21
26
  #
22
- # @return [Regexp] The input as a regexp
23
27
  def self.from_matcher(matcher, when_string_pattern_prefix_with: '')
24
28
  return matcher if matcher.is_a?(::Regexp)
25
29
 
@@ -0,0 +1,25 @@
1
+ require 'rspec/expectations'
2
+
3
+ RSpec::Matchers.define :have_message do |expected|
4
+ match do |actual|
5
+ actual.status_report[:messages].any? do |message|
6
+ expected.is_a?(Regexp) ? message =~ matcher : message.start_with?(expected)
7
+ end
8
+ end
9
+
10
+ failure_message do |actual|
11
+ result = "expected that #{Danger} would have warning '#{expected}'"
12
+
13
+ messages = actual.status_report[:messages]
14
+ if messages.empty?
15
+ result += ' but there is none'
16
+ return result
17
+ end
18
+
19
+ actual.status_report[:messages].each do |message|
20
+ result += "\n * #{message}"
21
+ end
22
+
23
+ result
24
+ end
25
+ end
@@ -0,0 +1,14 @@
1
+ require 'rspec/expectations'
2
+
3
+ RSpec::Matchers.define :have_no_message do
4
+ match do |actual|
5
+ actual.status_report[:messages].empty?
6
+ end
7
+
8
+ failure_message do |actual|
9
+ result = "expected that #{Danger} would have no message but found '#{actual.status_report[:messages].size}'"
10
+ actual.status_report[:messages].each do |message|
11
+ result += "\n * #{message}"
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,54 @@
1
+ require File.expand_path('../spec_helper', __FILE__)
2
+
3
+ module Danger
4
+ describe Danger::DangerSamsao do
5
+ describe 'with Dangerfile' do
6
+ before do
7
+ @dangerfile = testing_dangerfile
8
+ @plugin = @dangerfile.samsao
9
+ @message = 'The PR should have the acceptance criteria in the body.'
10
+ end
11
+
12
+ describe 'check acceptance criteria' do
13
+ it 'continues on fix branch' do
14
+ allow(@plugin).to receive(:feature_branch?).and_return(false)
15
+
16
+ @plugin.check_acceptance_criteria
17
+
18
+ expect(@dangerfile).to have_no_warning
19
+ expect(@dangerfile).to have_no_error
20
+ end
21
+
22
+ it 'warns if acceptance criteria is missing' do
23
+ allow(@plugin).to receive(:feature_branch?).and_return(true)
24
+ allow(@plugin.github).to receive(:pr_body).and_return('Bad example')
25
+
26
+ @plugin.check_acceptance_criteria
27
+
28
+ expect(@dangerfile).to have_warning(@message)
29
+ expect(@dangerfile).to have_no_error
30
+ end
31
+
32
+ it 'fails if report level specified' do
33
+ allow(@plugin).to receive(:feature_branch?).and_return(true)
34
+ allow(@plugin.github).to receive(:pr_body).and_return('Bad example')
35
+
36
+ @plugin.check_acceptance_criteria :fail
37
+
38
+ expect(@dangerfile).to have_error(@message)
39
+ expect(@dangerfile).to have_no_warning
40
+ end
41
+
42
+ it 'continues if contains acceptance criteria' do
43
+ allow(@plugin).to receive(:feature_branch?).and_return(true)
44
+ allow(@plugin.github).to receive(:pr_body).and_return('acceptance criteria : Good example')
45
+
46
+ @plugin.check_acceptance_criteria
47
+
48
+ expect(@dangerfile).to have_no_warning
49
+ expect(@dangerfile).to have_no_error
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
@@ -15,7 +15,7 @@ module Danger
15
15
  it "continues on #{branch_prefix}/ prefix" do
16
16
  allow(@plugin.github).to receive(:branch_for_head).and_return("#{branch_prefix}/something")
17
17
 
18
- @plugin.fail_when_wrong_branching_model
18
+ @plugin.check_wrong_branching_model
19
19
 
20
20
  expect(@dangerfile).to have_no_error
21
21
  end
@@ -24,7 +24,7 @@ module Danger
24
24
  it 'fails on wrong prefix' do
25
25
  allow(@plugin.github).to receive(:branch_for_head).and_return('wrong/12')
26
26
 
27
- @plugin.fail_when_wrong_branching_model
27
+ @plugin.check_wrong_branching_model
28
28
 
29
29
  expect(@dangerfile).to have_error(@wrong_branching_model)
30
30
  end
@@ -32,7 +32,7 @@ module Danger
32
32
  it 'fails on good prefix but wrong format' do
33
33
  allow(@plugin.github).to receive(:branch_for_head).and_return('feature_12')
34
34
 
35
- @plugin.fail_when_wrong_branching_model
35
+ @plugin.check_wrong_branching_model
36
36
 
37
37
  expect(@dangerfile).to have_error(@wrong_branching_model)
38
38
  end
@@ -15,7 +15,7 @@ module Danger
15
15
  it 'continues on support branch' do
16
16
  allow(@plugin.github).to receive(:branch_for_head).and_return('support/a')
17
17
 
18
- @plugin.fail_when_changelog_update_missing
18
+ @plugin.check_changelog_update_missing
19
19
 
20
20
  expect(@dangerfile).to have_no_error
21
21
  end
@@ -25,7 +25,7 @@ module Danger
25
25
  allow(@plugin.github).to receive(:branch_for_head).and_return("#{branch}/a")
26
26
  allow(@plugin.git).to receive(:modified_files).and_return(['CHANGELOG.md'])
27
27
 
28
- @plugin.fail_when_changelog_update_missing
28
+ @plugin.check_changelog_update_missing
29
29
 
30
30
  expect(@dangerfile).to have_no_error
31
31
  end
@@ -34,7 +34,7 @@ module Danger
34
34
  allow(@plugin.github).to receive(:branch_for_head).and_return("#{branch}/a")
35
35
  allow(@plugin.git).to receive(:modified_files).and_return([])
36
36
 
37
- @plugin.fail_when_changelog_update_missing
37
+ @plugin.check_changelog_update_missing
38
38
 
39
39
  expect(@dangerfile).to have_error(@changelog_needs_update)
40
40
  end
@@ -48,7 +48,7 @@ module Danger
48
48
  allow(@plugin.github).to receive(:branch_for_head).and_return('fix/a')
49
49
  allow(@plugin.git).to receive(:modified_files).and_return(['CHANGELOG.yml'])
50
50
 
51
- @plugin.fail_when_changelog_update_missing
51
+ @plugin.check_changelog_update_missing
52
52
 
53
53
  expect(@dangerfile).to have_no_error
54
54
  end
@@ -61,7 +61,7 @@ module Danger
61
61
  allow(@plugin.github).to receive(:branch_for_head).and_return('fix/a')
62
62
  allow(@plugin.git).to receive(:modified_files).and_return(['web/CHANGELOG.md'])
63
63
 
64
- @plugin.fail_when_changelog_update_missing
64
+ @plugin.check_changelog_update_missing
65
65
 
66
66
  expect(@dangerfile).to have_no_error
67
67
  end
@@ -70,7 +70,7 @@ module Danger
70
70
  allow(@plugin).to receive(:trivial_change?).and_return(true)
71
71
  allow(@plugin.git).to receive(:modified_files).and_return([])
72
72
 
73
- @plugin.fail_when_changelog_update_missing
73
+ @plugin.check_changelog_update_missing
74
74
 
75
75
  expect(@dangerfile).to have_no_error
76
76
  end
@@ -83,7 +83,7 @@ module Danger
83
83
  allow(@plugin).to receive(:support_branch?).and_return(true)
84
84
  allow(@plugin.git).to receive(:modified_files).and_return([])
85
85
 
86
- @plugin.fail_when_changelog_update_missing
86
+ @plugin.check_changelog_update_missing
87
87
 
88
88
  expect(@dangerfile).to have_no_error
89
89
  end
@@ -96,7 +96,7 @@ module Danger
96
96
  allow(@plugin).to receive(:support_branch?).and_return(true)
97
97
  allow(@plugin.git).to receive(:modified_files).and_return([])
98
98
 
99
- @plugin.fail_when_changelog_update_missing
99
+ @plugin.check_changelog_update_missing
100
100
 
101
101
  expect(@dangerfile).to have_error(@changelog_needs_update)
102
102
  end
@@ -79,6 +79,34 @@ module Danger
79
79
  expect { @plugin.config { project_type 'custom' } }.to raise_error(message)
80
80
  end
81
81
  end
82
+
83
+ describe 'config jira_project_key' do
84
+ it 'accepts jira project key' do
85
+ jira_project_key = 'VER'
86
+
87
+ @plugin.config do
88
+ jira_project_key jira_project_key
89
+ end
90
+
91
+ expect(@plugin.config.jira_project_key).to eq(jira_project_key)
92
+ end
93
+
94
+ it 'reject invalid jira project key containing numbers' do
95
+ jira_project_key = '123'
96
+ message = "Jira project key '#{jira_project_key}' is invalid, must be"\
97
+ ' uppercase and between 1 and 10 characters'
98
+
99
+ expect { @plugin.config { jira_project_key jira_project_key } }.to raise_error(message)
100
+ end
101
+
102
+ it 'reject invalid jira project key containing lowercase characters' do
103
+ jira_project_key = 'invalid'
104
+ message = "Jira project key '#{jira_project_key}' is invalid, must be"\
105
+ ' uppercase and between 1 and 10 characters'
106
+
107
+ expect { @plugin.config { jira_project_key jira_project_key } }.to raise_error(message)
108
+ end
109
+ end
82
110
  end
83
111
  end
84
112
  end