redmine-mattermost 0.3 → 0.4.beta1

Sign up to get free protection for your applications and to get access to all the features.
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