danger-samsao 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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