github_changelog_generator 1.15.0.pre.alpha → 1.16.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (63) hide show
  1. checksums.yaml +5 -5
  2. data/LICENSE +1 -1
  3. data/README.md +332 -275
  4. data/Rakefile +3 -4
  5. data/bin/git-generate-changelog +1 -1
  6. data/lib/github_changelog_generator.rb +10 -6
  7. data/lib/github_changelog_generator/generator/entry.rb +218 -0
  8. data/lib/github_changelog_generator/generator/generator.rb +126 -104
  9. data/lib/github_changelog_generator/generator/generator_fetcher.rb +139 -23
  10. data/lib/github_changelog_generator/generator/generator_processor.rb +59 -27
  11. data/lib/github_changelog_generator/generator/generator_tags.rb +26 -22
  12. data/lib/github_changelog_generator/generator/section.rb +124 -0
  13. data/lib/github_changelog_generator/helper.rb +1 -1
  14. data/lib/github_changelog_generator/octo_fetcher.rb +261 -130
  15. data/lib/github_changelog_generator/options.rb +74 -1
  16. data/lib/github_changelog_generator/parser.rb +120 -176
  17. data/lib/github_changelog_generator/parser_file.rb +8 -3
  18. data/lib/github_changelog_generator/reader.rb +2 -2
  19. data/lib/github_changelog_generator/task.rb +5 -6
  20. data/lib/github_changelog_generator/version.rb +1 -1
  21. data/man/git-generate-changelog.1 +144 -45
  22. data/man/git-generate-changelog.1.html +157 -84
  23. data/man/git-generate-changelog.html +19 -7
  24. data/man/git-generate-changelog.md +151 -84
  25. data/spec/files/github-changelog-generator.md +114 -114
  26. data/spec/{install-gem-in-bundler.gemfile → install_gem_in_bundler.gemfile} +2 -0
  27. data/spec/spec_helper.rb +2 -6
  28. data/spec/unit/generator/entry_spec.rb +766 -0
  29. data/spec/unit/generator/generator_processor_spec.rb +103 -41
  30. data/spec/unit/generator/generator_spec.rb +47 -0
  31. data/spec/unit/generator/generator_tags_spec.rb +56 -24
  32. data/spec/unit/generator/section_spec.rb +34 -0
  33. data/spec/unit/octo_fetcher_spec.rb +247 -197
  34. data/spec/unit/options_spec.rb +28 -4
  35. data/spec/unit/parse_file_spec.rb +2 -2
  36. data/spec/unit/parser_spec.rb +0 -79
  37. data/spec/unit/reader_spec.rb +4 -4
  38. data/spec/vcr/GitHubChangelogGenerator_OctoFetcher/_commits/when_API_is_valid/returns_commits.json +1 -0
  39. data/spec/vcr/GitHubChangelogGenerator_OctoFetcher/_commits_before/when_API_is_valid/returns_commits.json +1 -1
  40. data/spec/vcr/GitHubChangelogGenerator_OctoFetcher/_fetch_closed_issues_and_pr/when_API_call_is_valid.json +1 -1
  41. data/spec/vcr/GitHubChangelogGenerator_OctoFetcher/_fetch_closed_issues_and_pr/when_API_call_is_valid/returns_issue_with_proper_key/values.json +1 -1
  42. data/spec/vcr/GitHubChangelogGenerator_OctoFetcher/_fetch_closed_issues_and_pr/when_API_call_is_valid/returns_issues.json +1 -1
  43. data/spec/vcr/GitHubChangelogGenerator_OctoFetcher/_fetch_closed_issues_and_pr/when_API_call_is_valid/returns_issues_with_labels.json +1 -1
  44. data/spec/vcr/GitHubChangelogGenerator_OctoFetcher/_fetch_closed_issues_and_pr/when_API_call_is_valid/returns_pull_request_with_proper_key/values.json +1 -1
  45. data/spec/vcr/GitHubChangelogGenerator_OctoFetcher/_fetch_closed_issues_and_pr/when_API_call_is_valid/returns_pull_requests_with_labels.json +1 -1
  46. data/spec/vcr/GitHubChangelogGenerator_OctoFetcher/_fetch_closed_pull_requests/when_API_call_is_valid.json +1 -1
  47. data/spec/vcr/GitHubChangelogGenerator_OctoFetcher/_fetch_closed_pull_requests/when_API_call_is_valid/returns_correct_pull_request_keys.json +1 -1
  48. data/spec/vcr/GitHubChangelogGenerator_OctoFetcher/_fetch_closed_pull_requests/when_API_call_is_valid/returns_pull_requests.json +1 -1
  49. data/spec/vcr/GitHubChangelogGenerator_OctoFetcher/_fetch_commit/when_API_call_is_valid.json +1 -1
  50. data/spec/vcr/GitHubChangelogGenerator_OctoFetcher/_fetch_commit/when_API_call_is_valid/returns_commit.json +1 -1
  51. data/spec/vcr/GitHubChangelogGenerator_OctoFetcher/_fetch_date_of_tag/when_API_call_is_valid.json +1 -1
  52. data/spec/vcr/GitHubChangelogGenerator_OctoFetcher/_fetch_date_of_tag/when_API_call_is_valid/returns_date.json +1 -1
  53. data/spec/vcr/GitHubChangelogGenerator_OctoFetcher/_fetch_events_async/when_API_call_is_valid.json +1 -1
  54. data/spec/vcr/GitHubChangelogGenerator_OctoFetcher/_fetch_events_async/when_API_call_is_valid/populates_issues.json +1 -1
  55. data/spec/vcr/GitHubChangelogGenerator_OctoFetcher/_github_fetch_tags/when_API_call_is_valid.json +1 -1
  56. data/spec/vcr/GitHubChangelogGenerator_OctoFetcher/_github_fetch_tags/when_API_call_is_valid/should_return_tags.json +1 -1
  57. data/spec/vcr/GitHubChangelogGenerator_OctoFetcher/_github_fetch_tags/when_API_call_is_valid/should_return_tags_count.json +1 -1
  58. data/spec/vcr/GitHubChangelogGenerator_OctoFetcher/_github_fetch_tags/when_wrong_token_provided.json +1 -1
  59. data/spec/vcr/GitHubChangelogGenerator_OctoFetcher/_github_fetch_tags/when_wrong_token_provided/should_raise_Unauthorized_error.json +1 -1
  60. metadata +71 -38
  61. data/bin/ghclgen +0 -5
  62. data/lib/github_changelog_generator/generator/generator_generation.rb +0 -180
  63. data/spec/unit/generator/generator_generation_spec.rb +0 -17
@@ -0,0 +1,766 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GitHubChangelogGenerator
4
+ RSpec.describe Entry do
5
+ def label(name)
6
+ { "name" => name }
7
+ end
8
+
9
+ def issue(title, labels, body = "", number = "1", user = { "login" => "user" })
10
+ {
11
+ "title" => "issue #{title}",
12
+ "labels" => labels.map { |l| label(l) },
13
+ "number" => number,
14
+ "html_url" => "https://github.com/owner/repo/issue/#{number}",
15
+ "user" => user,
16
+ "body" => body,
17
+ "events" => [{
18
+ "event" => "closed"
19
+ }]
20
+ }
21
+ end
22
+
23
+ def pr(title, labels, body = "", number = "1", user = { "login" => "user" })
24
+ {
25
+ "pull_request" => true,
26
+ "title" => "pr #{title}",
27
+ "labels" => labels.map { |l| label(l) },
28
+ "number" => number,
29
+ "html_url" => "https://github.com/owner/repo/pull/#{number}",
30
+ "user" => user.merge("html_url" => "https://github.com/#{user['login']}"),
31
+ "body" => body,
32
+ "merged_at" => Time.now.utc,
33
+ "events" => [{
34
+ "event" => "merged",
35
+ "commit_id" => "aaaaa#{number}"
36
+ }]
37
+ }
38
+ end
39
+
40
+ def tag(name, sha, shas_in_tag)
41
+ {
42
+ "name" => name,
43
+ "commit" => { "sha" => sha },
44
+ "shas_in_tag" => shas_in_tag
45
+ }
46
+ end
47
+
48
+ def titles_for(issues)
49
+ issues.map { |issue| issue["title"] }
50
+ end
51
+
52
+ def default_sections
53
+ %w[breaking enhancements bugs deprecated removed security issues]
54
+ end
55
+
56
+ # Default to no issues, PRs, or tags.
57
+ let(:issues) { [] }
58
+ let(:pull_requests) { [] }
59
+ let(:tags) { [] }
60
+
61
+ # Default to standard options minus verbose to avoid output during testing.
62
+ let(:options) do
63
+ Parser.default_options.merge(verbose: false)
64
+ end
65
+
66
+ # Mock out fake github fetching for the issues/pull_requests lets and then
67
+ # expose filtering from the GitHubChangelogGenerator::Generator class
68
+ # instance for end-to-end entry testing.
69
+ let(:generator) do
70
+ fake_fetcher = instance_double(
71
+ "fetcher",
72
+ fetch_closed_issues_and_pr: [issues, pull_requests],
73
+ fetch_closed_pull_requests: [],
74
+ fetch_events_async: issues + pull_requests,
75
+ fetch_tag_shas: nil,
76
+ fetch_comments_async: nil,
77
+ default_branch: "master",
78
+ oldest_commit: { "sha" => "aaaaa1" },
79
+ fetch_commit: { "commit" => { "author" => { "date" => Time.now.utc } } }
80
+ )
81
+ allow(fake_fetcher).to receive(:commits_in_branch) do
82
+ ["aaaaa1"]
83
+ end
84
+ allow(GitHubChangelogGenerator::OctoFetcher).to receive(:new).and_return(fake_fetcher)
85
+ generator = GitHubChangelogGenerator::Generator.new(options)
86
+ generator.instance_variable_set :@sorted_tags, tags
87
+ generator.send(:fetch_issues_and_pr)
88
+ generator
89
+ end
90
+ let(:filtered_issues) do
91
+ generator.instance_variable_get :@issues
92
+ end
93
+ let(:filtered_pull_requests) do
94
+ generator.instance_variable_get :@pull_requests
95
+ end
96
+ let(:entry_sections) do
97
+ subject.send(:create_sections)
98
+ # In normal usage, the entry generation would have received filtered
99
+ # issues and pull requests so replicate that here for ease of testing.
100
+ subject.send(:sort_into_sections, filtered_pull_requests, filtered_issues)
101
+ subject.instance_variable_get :@sections
102
+ end
103
+
104
+ describe "#generate_entry_for_tag" do
105
+ let(:issues) do
106
+ [
107
+ issue("no labels", [], "", "5", "login" => "user1"),
108
+ issue("breaking", ["breaking"], "", "8", "login" => "user5"),
109
+ issue("enhancement", ["enhancement"], "", "6", "login" => "user2"),
110
+ issue("bug", ["bug"], "", "7", "login" => "user1"),
111
+ issue("deprecated", ["deprecated"], "", "13", "login" => "user5"),
112
+ issue("removed", ["removed"], "", "14", "login" => "user2"),
113
+ issue("security", ["security"], "", "15", "login" => "user5"),
114
+ issue("all the labels", %w[breaking enhancement bug deprecated removed security], "", "9", "login" => "user9"),
115
+ issue("all the labels different order", %w[bug breaking enhancement security removed deprecated], "", "10", "login" => "user5"),
116
+ issue("some unmapped labels", %w[tests-fail bug], "", "11", "login" => "user5"),
117
+ issue("no mapped labels", %w[docs maintenance], "", "12", "login" => "user5")
118
+ ]
119
+ end
120
+
121
+ let(:pull_requests) do
122
+ [
123
+ pr("no labels", [], "", "20", "login" => "user1"),
124
+ pr("breaking", ["breaking"], "", "23", "login" => "user5"),
125
+ pr("enhancement", ["enhancement"], "", "21", "login" => "user5"),
126
+ pr("bug", ["bug"], "", "22", "login" => "user5"),
127
+ pr("deprecated", ["deprecated"], "", "28", "login" => "user5"),
128
+ pr("removed", ["removed"], "", "29", "login" => "user2"),
129
+ pr("security", ["security"], "", "30", "login" => "user5"),
130
+ pr("all the labels", %w[breaking enhancement bug deprecated removed security], "", "24", "login" => "user5"),
131
+ pr("all the labels different order", %w[bug breaking enhancement security remove deprecated], "", "25", "login" => "user5"),
132
+ pr("some unmapped labels", %w[tests-fail bug], "", "26", "login" => "user5"),
133
+ pr("no mapped labels", %w[docs maintenance], "", "27", "login" => "user5")
134
+ ]
135
+ end
136
+
137
+ let(:tags) do
138
+ [tag("1.0.0", "aaaaa30", (1..30).collect { |i| "aaaaa#{i}" })]
139
+ end
140
+
141
+ subject { described_class.new(options) }
142
+ describe "include issues without labels" do
143
+ let(:options) do
144
+ Parser.default_options.merge(
145
+ user: "owner",
146
+ project: "repo",
147
+ breaking_labels: ["breaking"],
148
+ enhancement_labels: ["enhancement"],
149
+ bug_labels: ["bug"],
150
+ deprecated_labels: ["deprecated"],
151
+ removed_labels: ["removed"],
152
+ security_labels: ["security"],
153
+ verbose: false
154
+ )
155
+ end
156
+
157
+ it "generates a header and body" do
158
+ changelog = <<-CHANGELOG.gsub(/^ {10}/, "")
159
+ ## [1.0.1](https://github.com/owner/repo/tree/1.0.1) (2017-12-04)
160
+
161
+ [Full Changelog](https://github.com/owner/repo/compare/1.0.0...1.0.1)
162
+
163
+ **Breaking changes:**
164
+
165
+ - issue breaking [\\#8](https://github.com/owner/repo/issue/8)
166
+ - issue all the labels [\\#9](https://github.com/owner/repo/issue/9)
167
+ - issue all the labels different order [\\#10](https://github.com/owner/repo/issue/10)
168
+ - pr breaking [\\#23](https://github.com/owner/repo/pull/23) ([user5](https://github.com/user5))
169
+ - pr all the labels [\\#24](https://github.com/owner/repo/pull/24) ([user5](https://github.com/user5))
170
+ - pr all the labels different order [\\#25](https://github.com/owner/repo/pull/25) ([user5](https://github.com/user5))
171
+
172
+ **Implemented enhancements:**
173
+
174
+ - issue enhancement [\\#6](https://github.com/owner/repo/issue/6)
175
+ - pr enhancement [\\#21](https://github.com/owner/repo/pull/21) ([user5](https://github.com/user5))
176
+
177
+ **Fixed bugs:**
178
+
179
+ - issue bug [\\#7](https://github.com/owner/repo/issue/7)
180
+ - issue some unmapped labels [\\#11](https://github.com/owner/repo/issue/11)
181
+ - pr bug [\\#22](https://github.com/owner/repo/pull/22) ([user5](https://github.com/user5))
182
+ - pr some unmapped labels [\\#26](https://github.com/owner/repo/pull/26) ([user5](https://github.com/user5))
183
+
184
+ **Deprecated:**
185
+
186
+ - issue deprecated [\\#13](https://github.com/owner/repo/issue/13)
187
+ - pr deprecated [\\#28](https://github.com/owner/repo/pull/28) ([user5](https://github.com/user5))
188
+
189
+ **Removed:**
190
+
191
+ - issue removed [\\#14](https://github.com/owner/repo/issue/14)
192
+ - pr removed [\\#29](https://github.com/owner/repo/pull/29) ([user2](https://github.com/user2))
193
+
194
+ **Security fixes:**
195
+
196
+ - issue security [\\#15](https://github.com/owner/repo/issue/15)
197
+ - pr security [\\#30](https://github.com/owner/repo/pull/30) ([user5](https://github.com/user5))
198
+
199
+ **Closed issues:**
200
+
201
+ - issue no labels [\\#5](https://github.com/owner/repo/issue/5)
202
+ - issue no mapped labels [\\#12](https://github.com/owner/repo/issue/12)
203
+
204
+ **Merged pull requests:**
205
+
206
+ - pr no labels [\\#20](https://github.com/owner/repo/pull/20) ([user1](https://github.com/user1))
207
+ - pr no mapped labels [\\#27](https://github.com/owner/repo/pull/27) ([user5](https://github.com/user5))
208
+
209
+ CHANGELOG
210
+
211
+ expect(subject.generate_entry_for_tag(pull_requests, issues, "1.0.1", "1.0.1", Time.new(2017, 12, 4, 12, 0, 0, "+00:00").utc, "1.0.0")).to eq(changelog)
212
+ end
213
+ end
214
+
215
+ describe "#create_entry_for_tag_with_body" do
216
+ let(:options) do
217
+ Parser.default_options.merge(
218
+ user: "owner",
219
+ project: "repo",
220
+ bug_labels: ["bug"],
221
+ enhancement_labels: ["enhancement"],
222
+ breaking_labels: ["breaking"],
223
+ issue_line_body: true
224
+ )
225
+ end
226
+
227
+ let(:issues_with_body) do
228
+ [
229
+ issue("no labels", [], "Issue body description", "5", "login" => "user1"),
230
+ issue("breaking", ["breaking"], "Issue body description very long: Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Donec quam felis, ultricies nec, pellentesque eu, pretium quis, sem. Nulla consequat massa quis enim. Donec pede justo, fringilla vel, aliquet nec, vulputate eget, arcu. In enim justo, rhoncus ut, imperdiet a, venenatis vitae, justo. Nullam dictum felis eu pede mollis pretium. Integer tincidunt. Cras dapibus. Vivamus elementum semper nisi. Aenean vulputate eleifend tellus. Aenean leo ligula, porttitor eu, consequat vitae, eleifend ac, enim.", "8", "login" => "user5"),
231
+ issue("enhancement", ["enhancement"], "Issue body description", "6", "login" => "user2"),
232
+ issue("bug", ["bug"], "Issue body description", "7", "login" => "user1"),
233
+ issue("deprecated", ["deprecated"], "Issue body description", "13", "login" => "user5"),
234
+ issue("removed", ["removed"], "Issue body description", "14", "login" => "user2"),
235
+ issue("security", ["security"], "Issue body description", "15", "login" => "user5"),
236
+ issue("all the labels", %w[breaking enhancement bug deprecated removed security], "Issue body description. \nThis part should not appear.", "9", "login" => "user9"),
237
+ issue("all the labels different order", %w[bug breaking enhancement security removed deprecated], "Issue body description", "10", "login" => "user5"),
238
+ issue("some unmapped labels", %w[tests-fail bug], "Issue body description", "11", "login" => "user5"),
239
+ issue("no mapped labels", %w[docs maintenance], "Issue body description", "12", "login" => "user5")
240
+ ]
241
+ end
242
+
243
+ let(:pull_requests_with_body) do
244
+ [
245
+ pr("no labels", [], "PR body description", "20", "login" => "user1"),
246
+ pr("breaking", ["breaking"], "PR body description very long: Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Donec quam felis, ultricies nec, pellentesque eu, pretium quis, sem. Nulla consequat massa quis enim. Donec pede justo, fringilla vel, aliquet nec, vulputate eget, arcu. In enim justo, rhoncus ut, imperdiet a, venenatis vitae, justo. Nullam dictum felis eu pede mollis pretium. Integer tincidunt. Cras dapibus. Vivamus elementum semper nisi. Aenean vulputate eleifend tellus. Aenean leo ligula, porttitor eu, consequat vitae, eleifend ac, enim.", "23", "login" => "user5"),
247
+ pr("enhancement", ["enhancement"], "PR body description", "21", "login" => "user5"),
248
+ pr("bug", ["bug"], "PR body description", "22", "login" => "user5"),
249
+ pr("deprecated", ["deprecated"], "PR body description", "28", "login" => "user5"),
250
+ pr("removed", ["removed"], "PR body description", "29", "login" => "user2"),
251
+ pr("security", ["security"], "PR body description", "30", "login" => "user5"),
252
+ pr("all the labels", %w[breaking enhancement bug deprecated removed security], "PR body description. \nThis part should not appear", "24", "login" => "user5"),
253
+ pr("all the labels different order", %w[bug breaking enhancement security remove deprecated], "PR body description", "25", "login" => "user5"),
254
+ pr("some unmapped labels", %w[tests-fail bug], "PR body description", "26", "login" => "user5"),
255
+ pr("no mapped labels", %w[docs maintenance], "PR body description", "27", "login" => "user5")
256
+ ]
257
+ end
258
+
259
+ subject { described_class.new(options) }
260
+ it "generates issues and pull requests with body" do
261
+ changelog = <<-CHANGELOG.gsub(/^ {10}/, "")
262
+ ## [1.0.1](https://github.com/owner/repo/tree/1.0.1) (2017-12-04)
263
+
264
+ [Full Changelog](https://github.com/owner/repo/compare/1.0.0...1.0.1)
265
+
266
+ **Breaking changes:**
267
+
268
+ - **issue breaking [\\#8](https://github.com/owner/repo/issue/8)** \nIssue body description very long: Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Donec quam felis, ultricies nec, pellentesque eu, pretium quis, sem. Nulla consequat massa quis enim. Donec pede justo, fringilla vel, aliquet nec, vulputate eget, arcu. In enim justo, rhoncus ut, imperdiet a, venenatis vitae, justo. Nullam dictum felis eu pede mollis pretium. Integer tincidunt. Cras dapibus. Vivamus elementum semper nisi. Aenean vulputate eleifend tellus. Aenean leo ligula, porttitor eu, consequat vitae, eleifend ac, enim.
269
+ - **issue all the labels [\\#9](https://github.com/owner/repo/issue/9)** \nIssue body description.
270
+ - **issue all the labels different order [\\#10](https://github.com/owner/repo/issue/10)** \nIssue body description
271
+ - **pr breaking [\\#23](https://github.com/owner/repo/pull/23) ([user5](https://github.com/user5))** \nPR body description very long: Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Donec quam felis, ultricies nec, pellentesque eu, pretium quis, sem. Nulla consequat massa quis enim. Donec pede justo, fringilla vel, aliquet nec, vulputate eget, arcu. In enim justo, rhoncus ut, imperdiet a, venenatis vitae, justo. Nullam dictum felis eu pede mollis pretium. Integer tincidunt. Cras dapibus. Vivamus elementum semper nisi. Aenean vulputate eleifend tellus. Aenean leo ligula, porttitor eu, consequat vitae, eleifend ac, enim.
272
+ - **pr all the labels [\\#24](https://github.com/owner/repo/pull/24) ([user5](https://github.com/user5))** \nPR body description.
273
+ - **pr all the labels different order [\\#25](https://github.com/owner/repo/pull/25) ([user5](https://github.com/user5))** \nPR body description
274
+
275
+ **Implemented enhancements:**
276
+
277
+ - **issue enhancement [\\#6](https://github.com/owner/repo/issue/6)** \nIssue body description
278
+ - **pr enhancement [\\#21](https://github.com/owner/repo/pull/21) ([user5](https://github.com/user5))** \nPR body description
279
+
280
+ **Fixed bugs:**
281
+
282
+ - **issue bug [\\#7](https://github.com/owner/repo/issue/7)** \nIssue body description
283
+ - **issue some unmapped labels [\\#11](https://github.com/owner/repo/issue/11)** \nIssue body description
284
+ - **pr bug [\\#22](https://github.com/owner/repo/pull/22) ([user5](https://github.com/user5))** \nPR body description
285
+ - **pr some unmapped labels [\\#26](https://github.com/owner/repo/pull/26) ([user5](https://github.com/user5))** \nPR body description
286
+
287
+ **Deprecated:**
288
+
289
+ - **issue deprecated [\\#13](https://github.com/owner/repo/issue/13)** \nIssue body description
290
+ - **pr deprecated [\\#28](https://github.com/owner/repo/pull/28) ([user5](https://github.com/user5))** \nPR body description
291
+
292
+ **Removed:**
293
+
294
+ - **issue removed [\\#14](https://github.com/owner/repo/issue/14)** \nIssue body description
295
+ - **pr removed [\\#29](https://github.com/owner/repo/pull/29) ([user2](https://github.com/user2))** \nPR body description
296
+
297
+ **Security fixes:**
298
+
299
+ - **issue security [\\#15](https://github.com/owner/repo/issue/15)** \nIssue body description
300
+ - **pr security [\\#30](https://github.com/owner/repo/pull/30) ([user5](https://github.com/user5))** \nPR body description
301
+
302
+ **Closed issues:**
303
+
304
+ - **issue no labels [\\#5](https://github.com/owner/repo/issue/5)** \nIssue body description
305
+ - **issue no mapped labels [\\#12](https://github.com/owner/repo/issue/12)** \nIssue body description
306
+
307
+ **Merged pull requests:**
308
+
309
+ - **pr no labels [\\#20](https://github.com/owner/repo/pull/20) ([user1](https://github.com/user1))** \nPR body description
310
+ - **pr no mapped labels [\\#27](https://github.com/owner/repo/pull/27) ([user5](https://github.com/user5))** \nPR body description
311
+
312
+ CHANGELOG
313
+ expect(subject.generate_entry_for_tag(pull_requests_with_body, issues_with_body, "1.0.1", "1.0.1", Time.new(2017, 12, 4, 12, 0, 0, "+00:00").utc, "1.0.0")).to eq(changelog)
314
+ end
315
+ end
316
+
317
+ describe "exclude issues without labels" do
318
+ let(:options) do
319
+ Parser.default_options.merge(
320
+ user: "owner",
321
+ project: "repo",
322
+ breaking_labels: ["breaking"],
323
+ enhancement_labels: ["enhancement"],
324
+ bug_labels: ["bug"],
325
+ deprecated_labels: ["deprecated"],
326
+ removed_labels: ["removed"],
327
+ security_labels: ["security"],
328
+ add_pr_wo_labels: false,
329
+ add_issues_wo_labels: false,
330
+ verbose: false
331
+ )
332
+ end
333
+
334
+ it "generates a header and body" do
335
+ changelog = <<-CHANGELOG.gsub(/^ {10}/, "")
336
+ ## [1.0.1](https://github.com/owner/repo/tree/1.0.1) (2017-12-04)
337
+
338
+ [Full Changelog](https://github.com/owner/repo/compare/1.0.0...1.0.1)
339
+
340
+ **Breaking changes:**
341
+
342
+ - issue breaking [\\#8](https://github.com/owner/repo/issue/8)
343
+ - issue all the labels [\\#9](https://github.com/owner/repo/issue/9)
344
+ - issue all the labels different order [\\#10](https://github.com/owner/repo/issue/10)
345
+ - pr breaking [\\#23](https://github.com/owner/repo/pull/23) ([user5](https://github.com/user5))
346
+ - pr all the labels [\\#24](https://github.com/owner/repo/pull/24) ([user5](https://github.com/user5))
347
+ - pr all the labels different order [\\#25](https://github.com/owner/repo/pull/25) ([user5](https://github.com/user5))
348
+
349
+ **Implemented enhancements:**
350
+
351
+ - issue enhancement [\\#6](https://github.com/owner/repo/issue/6)
352
+ - pr enhancement [\\#21](https://github.com/owner/repo/pull/21) ([user5](https://github.com/user5))
353
+
354
+ **Fixed bugs:**
355
+
356
+ - issue bug [\\#7](https://github.com/owner/repo/issue/7)
357
+ - issue some unmapped labels [\\#11](https://github.com/owner/repo/issue/11)
358
+ - pr bug [\\#22](https://github.com/owner/repo/pull/22) ([user5](https://github.com/user5))
359
+ - pr some unmapped labels [\\#26](https://github.com/owner/repo/pull/26) ([user5](https://github.com/user5))
360
+
361
+ **Deprecated:**
362
+
363
+ - issue deprecated [\\#13](https://github.com/owner/repo/issue/13)
364
+ - pr deprecated [\\#28](https://github.com/owner/repo/pull/28) ([user5](https://github.com/user5))
365
+
366
+ **Removed:**
367
+
368
+ - issue removed [\\#14](https://github.com/owner/repo/issue/14)
369
+ - pr removed [\\#29](https://github.com/owner/repo/pull/29) ([user2](https://github.com/user2))
370
+
371
+ **Security fixes:**
372
+
373
+ - issue security [\\#15](https://github.com/owner/repo/issue/15)
374
+ - pr security [\\#30](https://github.com/owner/repo/pull/30) ([user5](https://github.com/user5))
375
+
376
+ **Closed issues:**
377
+
378
+ - issue no mapped labels [\\#12](https://github.com/owner/repo/issue/12)
379
+
380
+ **Merged pull requests:**
381
+
382
+ - pr no mapped labels [\\#27](https://github.com/owner/repo/pull/27) ([user5](https://github.com/user5))
383
+
384
+ CHANGELOG
385
+
386
+ expect(subject.generate_entry_for_tag(pull_requests, issues, "1.0.1", "1.0.1", Time.new(2017, 12, 4, 12, 0, 0, "+00:00").utc, "1.0.0")).to eq(changelog)
387
+ end
388
+ end
389
+ end
390
+ describe "#parse_sections" do
391
+ before do
392
+ subject { described_class.new }
393
+ end
394
+ context "valid json" do
395
+ let(:sections_string) { "{ \"foo\": { \"prefix\": \"foofix\", \"labels\": [\"test1\", \"test2\"]}, \"bar\": { \"prefix\": \"barfix\", \"labels\": [\"test3\", \"test4\"]}}" }
396
+
397
+ let(:sections_array) do
398
+ [
399
+ Section.new(name: "foo", prefix: "foofix", labels: %w[test1 test2]),
400
+ Section.new(name: "bar", prefix: "barfix", labels: %w[test3 test4])
401
+ ]
402
+ end
403
+
404
+ it "returns an array with 2 objects" do
405
+ arr = subject.send(:parse_sections, sections_string)
406
+ expect(arr.size).to eq 2
407
+ arr.each { |section| expect(section).to be_an_instance_of Section }
408
+ end
409
+
410
+ it "returns correctly constructed sections" do
411
+ require "json"
412
+
413
+ sections_json = JSON.parse(sections_string)
414
+ sections_array.each_index do |i|
415
+ aggregate_failures "checks each component" do
416
+ expect(sections_array[i].name).to eq sections_json.first[0]
417
+ expect(sections_array[i].prefix).to eq sections_json.first[1]["prefix"]
418
+ expect(sections_array[i].labels).to eq sections_json.first[1]["labels"]
419
+ expect(sections_array[i].issues).to eq []
420
+ end
421
+ sections_json.shift
422
+ end
423
+ end
424
+ context "parse also body_only" do
425
+ let(:sections_string) { "{ \"foo\": { \"prefix\": \"foofix\", \"labels\": [\"test1\", \"test2\"]}, \"bar\": { \"prefix\": \"barfix\", \"labels\": [\"test3\", \"test4\"], \"body_only\": true}}" }
426
+
427
+ it "returns correctly constructed sections" do
428
+ require "json"
429
+
430
+ parsed_sections = subject.send(:parse_sections, sections_string)
431
+
432
+ expect(parsed_sections[0].body_only).to eq false
433
+ expect(parsed_sections[1].body_only).to eq true
434
+ end
435
+ end
436
+ end
437
+ context "hash" do
438
+ let(:sections_hash) do
439
+ {
440
+ breaking: {
441
+ prefix: "**Breaking**",
442
+ labels: ["breaking"]
443
+ },
444
+ enhancements: {
445
+ prefix: "**Enhancements**",
446
+ labels: %w[feature enhancement]
447
+ },
448
+ bugs: {
449
+ prefix: "**Bugs**",
450
+ labels: ["bug"]
451
+ },
452
+ deprecated: {
453
+ prefix: "**Deprecated**",
454
+ labels: ["deprecated"]
455
+ },
456
+ removed: {
457
+ prefix: "**Removed**",
458
+ labels: ["removed"]
459
+ },
460
+ security: {
461
+ prefix: "**Security**",
462
+ labels: ["security"]
463
+ }
464
+ }
465
+ end
466
+
467
+ let(:sections_array) do
468
+ [
469
+ Section.new(name: "breaking", prefix: "**Breaking**", labels: ["breaking"]),
470
+ Section.new(name: "enhancements", prefix: "**Enhancements**", labels: %w[feature enhancement]),
471
+ Section.new(name: "bugs", prefix: "**Bugs**", labels: ["bug"]),
472
+ Section.new(name: "deprecated", prefix: "**Deprecated**", labels: ["deprecated"]),
473
+ Section.new(name: "removed", prefix: "**Removed**", labels: ["removed"]),
474
+ Section.new(name: "security", prefix: "**Security**", labels: ["security"])
475
+ ]
476
+ end
477
+
478
+ it "returns an array with 6 objects" do
479
+ arr = subject.send(:parse_sections, sections_hash)
480
+ expect(arr.size).to eq 6
481
+ arr.each { |section| expect(section).to be_an_instance_of Section }
482
+ end
483
+
484
+ it "returns correctly constructed sections" do
485
+ sections_array.each_index do |i|
486
+ aggregate_failures "checks each component" do
487
+ expect(sections_array[i].name).to eq sections_hash.first[0].to_s
488
+ expect(sections_array[i].prefix).to eq sections_hash.first[1][:prefix]
489
+ expect(sections_array[i].labels).to eq sections_hash.first[1][:labels]
490
+ expect(sections_array[i].issues).to eq []
491
+ end
492
+ sections_hash.shift
493
+ end
494
+ end
495
+ end
496
+ end
497
+
498
+ describe "#sort_into_sections" do
499
+ context "default sections" do
500
+ let(:options) do
501
+ Parser.default_options.merge(
502
+ breaking_labels: ["breaking"],
503
+ enhancement_labels: ["enhancement"],
504
+ bug_labels: ["bug"],
505
+ deprecated_labels: ["deprecated"],
506
+ removed_labels: ["removed"],
507
+ security_labels: ["security"],
508
+ verbose: false
509
+ )
510
+ end
511
+
512
+ let(:issues) do
513
+ [
514
+ issue("breaking", ["breaking"]),
515
+ issue("no labels", []),
516
+ issue("enhancement", ["enhancement"]),
517
+ issue("bug", ["bug"]),
518
+ issue("deprecated", ["deprecated"]),
519
+ issue("removed", ["removed"]),
520
+ issue("security", ["security"]),
521
+ issue("all the labels", %w[breaking enhancement bug deprecated removed security]),
522
+ issue("some unmapped labels", %w[tests-fail bug]),
523
+ issue("no mapped labels", %w[docs maintenance]),
524
+ issue("excluded label", %w[wontfix]),
525
+ issue("excluded and included label", %w[breaking wontfix])
526
+ ]
527
+ end
528
+
529
+ let(:pull_requests) do
530
+ [
531
+ pr("no labels", []),
532
+ pr("breaking", ["breaking"]),
533
+ pr("enhancement", ["enhancement"]),
534
+ pr("bug", ["bug"]),
535
+ pr("deprecated", ["deprecated"]),
536
+ pr("removed", ["removed"]),
537
+ pr("security", ["security"]),
538
+ pr("all the labels", %w[breaking enhancement bug deprecated removed security]),
539
+ pr("some unmapped labels", %w[tests-fail bug]),
540
+ pr("no mapped labels", %w[docs maintenance]),
541
+ pr("excluded label", %w[wontfix]),
542
+ pr("excluded and included label", %w[breaking wontfix])
543
+ ]
544
+ end
545
+
546
+ subject { described_class.new(options) }
547
+
548
+ it "returns 9 sections" do
549
+ entry_sections.each { |sec| pp(sec.name) }
550
+ expect(entry_sections.size).to eq 9
551
+ end
552
+
553
+ it "returns default sections" do
554
+ default_sections.each { |default_section| expect(entry_sections.count { |section| section.name == default_section }).to eq 1 }
555
+ end
556
+
557
+ it "assigns issues to the correct sections" do
558
+ breaking_section = entry_sections.find { |section| section.name == "breaking" }
559
+ enhancement_section = entry_sections.find { |section| section.name == "enhancements" }
560
+ bug_section = entry_sections.find { |section| section.name == "bugs" }
561
+ deprecated_section = entry_sections.find { |section| section.name == "deprecated" }
562
+ removed_section = entry_sections.find { |section| section.name == "removed" }
563
+ security_section = entry_sections.find { |section| section.name == "security" }
564
+ issue_section = entry_sections.find { |section| section.name == "issues" }
565
+ merged_section = entry_sections.find { |section| section.name == "merged" }
566
+
567
+ expect(titles_for(breaking_section.issues)).to eq(["issue breaking", "issue all the labels", "pr breaking", "pr all the labels"])
568
+ expect(titles_for(enhancement_section.issues)).to eq(["issue enhancement", "pr enhancement"])
569
+ expect(titles_for(bug_section.issues)).to eq(["issue bug", "issue some unmapped labels", "pr bug", "pr some unmapped labels"])
570
+ expect(titles_for(deprecated_section.issues)).to eq(["issue deprecated", "pr deprecated"])
571
+ expect(titles_for(removed_section.issues)).to eq(["issue removed", "pr removed"])
572
+ expect(titles_for(security_section.issues)).to eq(["issue security", "pr security"])
573
+ expect(titles_for(issue_section.issues)).to eq(["issue no labels", "issue no mapped labels"])
574
+ expect(titles_for(merged_section.issues)).to eq(["pr no labels", "pr no mapped labels"])
575
+ end
576
+ end
577
+ context "configure sections and include labels" do
578
+ let(:options) do
579
+ Parser.default_options.merge(
580
+ configure_sections: "{ \"foo\": { \"prefix\": \"foofix\", \"labels\": [\"test1\", \"test2\"]}, \"bar\": { \"prefix\": \"barfix\", \"labels\": [\"test3\", \"test4\"]}}",
581
+ include_labels: %w[test1 test2 test3 test4],
582
+ verbose: false
583
+ )
584
+ end
585
+
586
+ let(:issues) do
587
+ [
588
+ issue("no labels", []),
589
+ issue("test1", ["test1"]),
590
+ issue("test3", ["test3"]),
591
+ issue("test4", ["test4"]),
592
+ issue("all the labels", %w[test4 test2 test3 test1]),
593
+ issue("some included labels", %w[unincluded test2]),
594
+ issue("no included labels", %w[unincluded again])
595
+ ]
596
+ end
597
+
598
+ let(:pull_requests) do
599
+ [
600
+ pr("no labels", []),
601
+ pr("test1", ["test1"]),
602
+ pr("test3", ["test3"]),
603
+ pr("test4", ["test4"]),
604
+ pr("all the labels", %w[test4 test2 test3 test1]),
605
+ pr("some included labels", %w[unincluded test2]),
606
+ pr("no included labels", %w[unincluded again])
607
+ ]
608
+ end
609
+
610
+ subject { described_class.new(options) }
611
+
612
+ it "returns 4 sections" do
613
+ expect(entry_sections.size).to eq 4
614
+ end
615
+
616
+ it "returns only configured sections" do
617
+ expect(entry_sections.count { |section| section.name == "foo" }).to eq 1
618
+ expect(entry_sections.count { |section| section.name == "bar" }).to eq 1
619
+ end
620
+
621
+ it "assigns issues to the correct sections" do
622
+ foo_section = entry_sections.find { |section| section.name == "foo" }
623
+ bar_section = entry_sections.find { |section| section.name == "bar" }
624
+ issue_section = entry_sections.find { |section| section.name == "issues" }
625
+ merged_section = entry_sections.find { |section| section.name == "merged" }
626
+
627
+ aggregate_failures "checks all sections" do
628
+ expect(titles_for(foo_section.issues)).to eq(["issue test1", "issue all the labels", "issue some included labels", "pr test1", "pr all the labels", "pr some included labels"])
629
+ expect(titles_for(bar_section.issues)).to eq(["issue test3", "issue test4", "pr test3", "pr test4"])
630
+ expect(titles_for(merged_section.issues)).to eq(["pr no labels"])
631
+ expect(titles_for(issue_section.issues)).to eq(["issue no labels"])
632
+ end
633
+ end
634
+ end
635
+ context "configure sections and exclude labels" do
636
+ let(:options) do
637
+ Parser.default_options.merge(
638
+ configure_sections: "{ \"foo\": { \"prefix\": \"foofix\", \"labels\": [\"test1\", \"test2\"]}, \"bar\": { \"prefix\": \"barfix\", \"labels\": [\"test3\", \"test4\"]}}",
639
+ exclude_labels: ["excluded"],
640
+ verbose: false
641
+ )
642
+ end
643
+
644
+ let(:issues) do
645
+ [
646
+ issue("no labels", []),
647
+ issue("test1", ["test1"]),
648
+ issue("test3", ["test3"]),
649
+ issue("test4", ["test4"]),
650
+ issue("all the labels", %w[test4 test2 test3 test1]),
651
+ issue("some excluded labels", %w[excluded test2]),
652
+ issue("excluded labels", %w[excluded again])
653
+ ]
654
+ end
655
+
656
+ let(:pull_requests) do
657
+ [
658
+ pr("no labels", []),
659
+ pr("test1", ["test1"]),
660
+ pr("test3", ["test3"]),
661
+ pr("test4", ["test4"]),
662
+ pr("all the labels", %w[test4 test2 test3 test1]),
663
+ pr("some excluded labels", %w[excluded test2]),
664
+ pr("excluded labels", %w[excluded again])
665
+ ]
666
+ end
667
+
668
+ subject { described_class.new(options) }
669
+
670
+ it "returns 4 sections" do
671
+ expect(entry_sections.size).to eq 4
672
+ end
673
+
674
+ it "returns only configured sections" do
675
+ expect(entry_sections.count { |section| section.name == "foo" }).to eq 1
676
+ expect(entry_sections.count { |section| section.name == "bar" }).to eq 1
677
+ end
678
+
679
+ it "assigns issues to the correct sections" do
680
+ foo_section = entry_sections.find { |section| section.name == "foo" }
681
+ bar_section = entry_sections.find { |section| section.name == "bar" }
682
+ issue_section = entry_sections.find { |section| section.name == "issues" }
683
+ merged_section = entry_sections.find { |section| section.name == "merged" }
684
+
685
+ aggregate_failures "checks all sections" do
686
+ expect(titles_for(foo_section.issues)).to eq(["issue test1", "issue all the labels", "pr test1", "pr all the labels"])
687
+ expect(titles_for(bar_section.issues)).to eq(["issue test3", "issue test4", "pr test3", "pr test4"])
688
+ expect(titles_for(merged_section.issues)).to eq(["pr no labels"])
689
+ expect(titles_for(issue_section.issues)).to eq(["issue no labels"])
690
+ end
691
+ end
692
+ end
693
+ context "add sections" do
694
+ let(:options) do
695
+ Parser.default_options.merge(
696
+ breaking_labels: ["breaking"],
697
+ enhancement_labels: ["enhancement"],
698
+ bug_labels: ["bug"],
699
+ deprecated_labels: ["deprecated"],
700
+ removed_labels: ["removed"],
701
+ security_labels: ["security"],
702
+ add_sections: "{ \"foo\": { \"prefix\": \"foofix\", \"labels\": [\"test1\", \"test2\"]}}",
703
+ verbose: false
704
+ )
705
+ end
706
+
707
+ let(:issues) do
708
+ [
709
+ issue("no labels", []),
710
+ issue("test1", ["test1"]),
711
+ issue("bugaboo", ["bug"]),
712
+ issue("all the labels", %w[test1 test2 breaking enhancement bug deprecated removed security]),
713
+ issue("default labels first", %w[enhancement bug test1 test2]),
714
+ issue("some excluded labels", %w[wontfix breaking]),
715
+ issue("excluded labels", %w[wontfix again])
716
+ ]
717
+ end
718
+
719
+ let(:pull_requests) do
720
+ [
721
+ pr("no labels", []),
722
+ pr("test1", ["test1"]),
723
+ pr("enhance", ["enhancement"]),
724
+ pr("all the labels", %w[test1 test2 breaking enhancement bug deprecated removed security]),
725
+ pr("default labels first", %w[enhancement bug test1 test2]),
726
+ pr("some excluded labels", %w[wontfix breaking]),
727
+ pr("excluded labels", %w[wontfix again])
728
+ ]
729
+ end
730
+
731
+ subject { described_class.new(options) }
732
+
733
+ it "returns 10 sections" do
734
+ entry_sections.each { |sec| pp(sec.name) }
735
+ expect(entry_sections.size).to eq 10
736
+ end
737
+
738
+ it "returns default sections" do
739
+ default_sections.each { |default_section| expect(entry_sections.count { |section| section.name == default_section }).to eq 1 }
740
+ end
741
+
742
+ it "returns added section" do
743
+ expect(entry_sections.count { |section| section.name == "foo" }).to eq 1
744
+ end
745
+
746
+ it "assigns issues to the correct sections" do
747
+ foo_section = entry_sections.find { |section| section.name == "foo" }
748
+ breaking_section = entry_sections.find { |section| section.name == "breaking" }
749
+ enhancement_section = entry_sections.find { |section| section.name == "enhancements" }
750
+ bug_section = entry_sections.find { |section| section.name == "bugs" }
751
+ issue_section = entry_sections.find { |section| section.name == "issues" }
752
+ merged_section = entry_sections.find { |section| section.name == "merged" }
753
+
754
+ aggregate_failures "checks all sections" do
755
+ expect(titles_for(breaking_section.issues)).to eq(["issue all the labels", "pr all the labels"])
756
+ expect(titles_for(enhancement_section.issues)).to eq(["issue default labels first", "pr enhance", "pr default labels first"])
757
+ expect(titles_for(bug_section.issues)).to eq(["issue bugaboo"])
758
+ expect(titles_for(foo_section.issues)).to eq(["issue test1", "pr test1"])
759
+ expect(titles_for(issue_section.issues)).to eq(["issue no labels"])
760
+ expect(titles_for(merged_section.issues)).to eq(["pr no labels"])
761
+ end
762
+ end
763
+ end
764
+ end
765
+ end
766
+ end