redmine-mattermost 0.3 → 0.4.beta1

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.
Files changed (37) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +15 -0
  3. data/LICENSE +1 -0
  4. data/README.md +30 -17
  5. data/Rakefile +9 -0
  6. data/app/views/settings/_mattermost_settings.html.erb +60 -20
  7. data/lib/redmine-mattermost.rb +9 -1
  8. data/lib/redmine_mattermost/bridge.rb +63 -0
  9. data/lib/redmine_mattermost/client.rb +28 -0
  10. data/lib/redmine_mattermost/extractors.rb +88 -0
  11. data/lib/redmine_mattermost/extractors/applied_changeset.rb +55 -0
  12. data/lib/redmine_mattermost/extractors/changed_wiki_page.rb +39 -0
  13. data/lib/redmine_mattermost/extractors/new_issue.rb +44 -0
  14. data/lib/redmine_mattermost/extractors/updated_issue.rb +42 -0
  15. data/lib/redmine_mattermost/infos.rb +1 -2
  16. data/lib/redmine_mattermost/issue_patch.rb +14 -14
  17. data/lib/redmine_mattermost/listener.rb +38 -268
  18. data/lib/redmine_mattermost/message_builder.rb +61 -0
  19. data/lib/redmine_mattermost/redmine_plugin.rb +3 -4
  20. data/lib/redmine_mattermost/utils.rb +3 -0
  21. data/lib/redmine_mattermost/utils/journal_mapper.rb +75 -0
  22. data/lib/redmine_mattermost/utils/text.rb +37 -0
  23. data/lib/redmine_mattermost/utils/urls.rb +20 -0
  24. data/lib/redmine_mattermost/version.rb +1 -1
  25. data/redmine-mattermost.gemspec +4 -1
  26. data/spec/client_spec.rb +29 -0
  27. data/spec/extractors/applied_changeset_spec.rb +82 -0
  28. data/spec/extractors/changed_wiki_page_spec.rb +63 -0
  29. data/spec/extractors/new_issue_spec.rb +209 -0
  30. data/spec/extractors/updated_issue_spec.rb +169 -0
  31. data/spec/message_builder_spec.rb +71 -0
  32. data/spec/redmine_mattermost_spec.rb +9 -0
  33. data/spec/spec_helper.rb +17 -0
  34. data/spec/support/mock_support.rb +113 -0
  35. data/spec/utils/text_spec.rb +142 -0
  36. data/spec/utils/text_spec_alt.rb +142 -0
  37. metadata +87 -9
@@ -0,0 +1,169 @@
1
+ require "spec_helper"
2
+
3
+ describe RedmineMattermost::Extractors::UpdatedIssue do
4
+ subject { RedmineMattermost::Extractors::UpdatedIssue.new(bridge) }
5
+ let(:result) { subject.from_context(context) }
6
+ let(:msg) { result[:message] }
7
+ let(:context) do
8
+ { issue: issue, journal: journal }
9
+ end
10
+ let(:bridge) { MockBridge.new(settings) }
11
+ let(:issue) { MockIssue.new(issue_data) }
12
+ let(:journal) { mock(journal_data) }
13
+
14
+ let(:settings) { Defaults.settings }
15
+ let(:issue_data) do
16
+ {
17
+ title: "Some title",
18
+ project: EventMock.new(title: "Project A")
19
+ }
20
+ end
21
+ let(:journal_data) { Hash.new }
22
+
23
+ describe "empty context" do
24
+ let(:context) { Hash.new }
25
+
26
+ it "raises" do
27
+ proc { result }.must_raise KeyError
28
+ end
29
+ end
30
+
31
+ describe "no project" do
32
+ let(:issue_data) do
33
+ {
34
+ title: "Some title"
35
+ }
36
+ end
37
+
38
+ it "returns nil" do
39
+ result.must_be_nil
40
+ end
41
+ end
42
+
43
+ describe "minimal journal" do
44
+ let(:journal_data) do
45
+ {
46
+ details: [],
47
+ user: mock(title: "User A")
48
+ }
49
+ end
50
+
51
+ it "should return a message" do
52
+ msg[:text].must_equal(
53
+ "[#{link_for("Project A")}] User A updated #{link_for("Some title")}"
54
+ )
55
+ end
56
+ end
57
+
58
+ describe "full journal" do
59
+ let(:journal_data) do
60
+ {
61
+ details: [
62
+ mock(prop_key: "title", value: "Some title"),
63
+ mock(prop_key: "subject", value: "Some subject"),
64
+ mock(prop_key: "description", value: "Some description"),
65
+ mock(prop_key: "tracker_id", value: 1),
66
+ mock(prop_key: "project_id", value: 1),
67
+ mock(prop_key: "status_id", value: 1),
68
+ mock(prop_key: "priority_id", value: 1),
69
+ mock(prop_key: "category_id", value: 1),
70
+ mock(prop_key: "assigned_to", value: 1),
71
+ mock(prop_key: "fixed_version", value: 1),
72
+ mock(property: "attachment", prop_key: 1),
73
+ mock(prop_key: "parent_id", value: 1),
74
+ mock(prop_key: "other", value: "Some other"),
75
+ mock(property: "cf", prop_key: "empty_cf"),
76
+ mock(property: "cf", prop_key: "other_cf", value: "Some value")
77
+ ],
78
+ user: mock(title: "User A"),
79
+ notes: "This is a @note@"
80
+ }
81
+ end
82
+
83
+ it "should return a message" do
84
+ msg[:text].must_equal(
85
+ "[#{link_for("Project A")}] User A updated #{link_for("Some title")}"
86
+ )
87
+
88
+ attachments = msg[:attachments]
89
+ attachments.size.must_equal 1
90
+ attachment = attachments.shift
91
+ text = attachment[:text]
92
+ text.must_equal "This is a `note`"
93
+ fields = attachment[:fields]
94
+ fields.size.must_equal 15
95
+ fields.shift.must_equal({
96
+ title: "field_title",
97
+ value: "Some title"
98
+ })
99
+ fields.shift.must_equal({
100
+ title: "field_subject",
101
+ value: "Some subject"
102
+ })
103
+ fields.shift.must_equal({
104
+ title: "field_description",
105
+ value: "Some description"
106
+ })
107
+ fields.shift.must_equal({
108
+ title: "field_tracker",
109
+ value: "Tracker: 1",
110
+ short: true
111
+ })
112
+ fields.shift.must_equal({
113
+ title: "field_project",
114
+ value: "Project: 1",
115
+ short: true
116
+ })
117
+ fields.shift.must_equal({
118
+ title: "field_status",
119
+ value: "IssueStatus: 1",
120
+ short: true
121
+ })
122
+ fields.shift.must_equal({
123
+ title: "field_priority",
124
+ value: "IssuePriority: 1",
125
+ short: true
126
+ })
127
+ fields.shift.must_equal({
128
+ title: "field_category",
129
+ value: "IssueCategory: 1",
130
+ short: true
131
+ })
132
+ fields.shift.must_equal({
133
+ title: "field_assigned_to",
134
+ value: "User: 1",
135
+ short: true
136
+ })
137
+ fields.shift.must_equal({
138
+ title: "field_fixed_version",
139
+ value: "Version: 1",
140
+ short: true
141
+ })
142
+ fields.shift.must_equal({
143
+ title: "label_attachment",
144
+ value: link_for("file_1.png"),
145
+ short: true
146
+ })
147
+ fields.shift.must_equal({
148
+ title: "field_parent",
149
+ value: link_for("Issue: 1"),
150
+ short: true
151
+ })
152
+ fields.shift.must_equal({
153
+ title: "field_other",
154
+ value: "Some other",
155
+ short: true
156
+ })
157
+ fields.shift.must_equal({
158
+ title: "CustomField empty_cf",
159
+ value: "-",
160
+ short: true
161
+ })
162
+ fields.shift.must_equal({
163
+ title: "CustomField other_cf",
164
+ value: "Some value",
165
+ short: true
166
+ })
167
+ end
168
+ end
169
+ end
@@ -0,0 +1,71 @@
1
+ require "spec_helper"
2
+
3
+ describe RedmineMattermost::MessageBuilder do
4
+ subject { RedmineMattermost::MessageBuilder.new(text) }
5
+ let(:text) { "Some message" }
6
+ let(:expected) do
7
+ {
8
+ link_names: 1,
9
+ text: text
10
+ }
11
+ end
12
+ it "returns the text as default" do
13
+ result.must_equal(expected)
14
+ end
15
+
16
+ it "sets the username" do
17
+ subject.username("TestUser")
18
+ result.must_equal(expected.merge({
19
+ username: "TestUser"
20
+ }))
21
+ end
22
+
23
+ it "sets the channel" do
24
+ subject.channel("the-channel")
25
+ result.must_equal(expected.merge({
26
+ channel: "the-channel"
27
+ }))
28
+ end
29
+
30
+ it "adds attachments" do
31
+ subject.attachment.text("the text")
32
+ subject.attachment.text("other text")
33
+ .field("title", "value")
34
+ .field("foo", "bar", true)
35
+
36
+ result.must_equal(expected.merge({
37
+ attachments: [
38
+ {
39
+ text: "the text"
40
+ },
41
+ {
42
+ text: "other text",
43
+ fields: [
44
+ {
45
+ title: "title",
46
+ value: "value"
47
+ },
48
+ {
49
+ title: "foo",
50
+ value: "bar",
51
+ short: true
52
+ }
53
+ ]
54
+ }
55
+ ]
56
+ }))
57
+ end
58
+
59
+ it "set the icon as url" do
60
+ subject.icon("http://some.com/test.png")
61
+ result.must_equal(expected.merge({
62
+ icon_url: "http://some.com/test.png"
63
+ }))
64
+ end
65
+
66
+ private
67
+
68
+ def result
69
+ subject.to_hash
70
+ end
71
+ end
@@ -0,0 +1,9 @@
1
+ require "spec_helper"
2
+
3
+ describe RedmineMattermost do
4
+ subject { RedmineMattermost }
5
+
6
+ it "has a version" do
7
+ subject::VERSION.wont_be_nil
8
+ end
9
+ end
@@ -0,0 +1,17 @@
1
+ require "redmine-mattermost"
2
+
3
+ require "minitest/autorun"
4
+ require "webmock/minitest"
5
+
6
+ require_relative "support/mock_support"
7
+
8
+ class Minitest::Test
9
+ def mock(data)
10
+ Mock.new(data)
11
+ end
12
+
13
+ def link_for(label)
14
+ '<URL: {"host":"test.host","protocol":"http","port":null,' \
15
+ '"script_name":null}|' + label + '>'
16
+ end
17
+ end
@@ -0,0 +1,113 @@
1
+ require "ostruct"
2
+ require "redcloth"
3
+
4
+ class Mock < OpenStruct
5
+ def to_s
6
+ title
7
+ end
8
+ end
9
+
10
+ class EventMock < Mock
11
+ def event_url(options)
12
+ "URL: #{options.to_json}"
13
+ end
14
+ end
15
+
16
+ class MockIssue < EventMock
17
+ def is_private?
18
+ is_private
19
+ end
20
+
21
+ def valid?
22
+ valid
23
+ end
24
+ end
25
+
26
+ class MockBridge
27
+ attr_reader :settings
28
+
29
+ def initialize(settings)
30
+ @settings = settings
31
+ end
32
+
33
+ def custom_field_by_name(name)
34
+ OpenStruct.new(name: name)
35
+ end
36
+
37
+ def url_for(target)
38
+ target
39
+ end
40
+
41
+ def translate(key, options)
42
+ options[:locale].must_equal(settings.default_language)
43
+ key
44
+ end
45
+
46
+ def to_html(formatting, text)
47
+ RedCloth.new(text).to_html
48
+ end
49
+
50
+ def find_custom_field(id)
51
+ Mock.new(name: "CustomField #{id}")
52
+ end
53
+
54
+ def find_tracker(id)
55
+ "Tracker: #{id}"
56
+ end
57
+
58
+ def find_project(id)
59
+ "Project: #{id}"
60
+ end
61
+
62
+ def find_issue_status(id)
63
+ "IssueStatus: #{id}"
64
+ end
65
+
66
+ def find_issue_priority(id)
67
+ "IssuePriority: #{id}"
68
+ end
69
+
70
+ def find_issue_category(id)
71
+ "IssueCategory: #{id}"
72
+ end
73
+
74
+ def find_user(id)
75
+ "User: #{id}"
76
+ end
77
+
78
+ def find_version(id)
79
+ "Version: #{id}"
80
+ end
81
+
82
+ def find_attachement(id)
83
+ MockIssue.new(title: "Attachment: #{id}", filename: "file_#{id}.png")
84
+ end
85
+
86
+ def find_issue(id)
87
+ MockIssue.new(title: "Issue: #{id}")
88
+ end
89
+ end
90
+
91
+ class Defaults
92
+ def self.settings
93
+ Mock.new({
94
+ host_name: "https://test.host",
95
+ protocol: "http",
96
+ port: 80,
97
+ script_name: "some_script",
98
+ plugin_redmine_mattermost: plugin_settings,
99
+ text_formatting: "textile",
100
+ default_language: "en"
101
+ })
102
+ end
103
+
104
+ def self.plugin_settings
105
+ {
106
+ display_watchers: "1",
107
+ post_updates: "1",
108
+ post_wiki_updates: "1",
109
+ mattermost_url: "http://api.mattermost.org",
110
+ channel: "fallback-channel"
111
+ }
112
+ end
113
+ end
@@ -0,0 +1,142 @@
1
+ require "spec_helper"
2
+
3
+ describe RedmineMattermost::Utils::Text do
4
+ subject { Dummy.new(bridge) }
5
+ let(:bridge) { MockBridge.new(settings) }
6
+ let(:settings) { Defaults.settings }
7
+
8
+ describe "h" do
9
+ it "escapes special chars" do
10
+ text = subject.h("some & special <chars>")
11
+ text.must_equal("some &amp; special &lt;chars&gt;")
12
+ end
13
+
14
+ it "handles nil inputs" do
15
+ subject.h(nil).must_be_nil
16
+ end
17
+ end
18
+
19
+ describe "link" do
20
+ it "builds a markdown link" do
21
+ text = subject.link("the < label", "the_target")
22
+ text.must_equal("<the_target|the &lt; label>")
23
+ end
24
+ end
25
+
26
+ describe "to_markdown" do
27
+ it "transforms HTML" do
28
+ input = <<HTML.strip
29
+ <h1>Header1</h1>
30
+ <h2>Header2</h2>
31
+ <p>This is a new comment</p>
32
+ <ul>
33
+ <li>with multiple items</li>
34
+ <li>in a list</li>
35
+ </ul>
36
+
37
+ <p>or a</p>
38
+
39
+ <ol>
40
+ <li>Numbered</li>
41
+ <li>List</li>
42
+ </ol>
43
+
44
+ <p>
45
+ <del>strike</del> <ins>underline</ins> <em>italic</em> <strong>bold</strong>
46
+ </p>
47
+
48
+ <blockquote>
49
+ <p>Block quote</p>
50
+ </blockquote>
51
+
52
+ <p>
53
+ <img src="https://www.example.org/image.png" alt="Example Image">
54
+ </p>
55
+
56
+ <p>
57
+ External link: <a href="https://www.example.org" class="external">ExampleORG</a>
58
+ </p>
59
+
60
+ <pre>
61
+ Some pre-indented text
62
+ in two lines
63
+ </pre>
64
+
65
+ <pre><code class="ruby syntaxhl">
66
+ class Ruby
67
+ end
68
+ </code></pre>
69
+
70
+ HTML
71
+ expected = <<MARKDOWN.strip
72
+ # Header1
73
+
74
+ ## Header2
75
+
76
+ This is a new comment
77
+
78
+ - with multiple items
79
+ - in a list
80
+
81
+ or a
82
+
83
+ 1. Numbered
84
+ 2. List
85
+
86
+ ~~strike~~ underline _italic_ **bold**
87
+
88
+ > Block quote
89
+
90
+ ![Example Image](https://www.example.org/image.png)
91
+
92
+ External link: [ExampleORG](https://www.example.org)
93
+
94
+ ```
95
+ Some pre-indented text
96
+ in two lines
97
+ ```
98
+
99
+ ```
100
+ class Ruby
101
+ end
102
+ ```
103
+ MARKDOWN
104
+ text = subject.to_markdown(input)
105
+ text.must_equal(expected)
106
+ end
107
+
108
+ it "passes already used markdown" do
109
+ settings.text_formatting = "markdown"
110
+ text = subject.to_markdown("> Markdown")
111
+ text.must_equal("> Markdown")
112
+ end
113
+
114
+ it "rescues to escaped test" do
115
+ stub = proc do |formatting, text|
116
+ raise "some error"
117
+ end
118
+ bridge.stub(:to_html, stub) do
119
+ text = subject.to_markdown("<code>")
120
+ text.must_equal("&lt;code&gt;")
121
+ end
122
+ end
123
+ end
124
+
125
+ private
126
+
127
+ class Dummy
128
+ include RedmineMattermost::Utils::Text
129
+
130
+ attr_reader :bridge
131
+
132
+ def initialize(bridge)
133
+ @bridge = bridge
134
+ end
135
+
136
+ # just use the input (assuming the test already)
137
+ # provides valid HTML
138
+ def to_html(formatting, text)
139
+ text
140
+ end
141
+ end
142
+ end