mingle_events 0.0.4

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 (41) hide show
  1. data/Gemfile +4 -0
  2. data/LICENSE.txt +202 -0
  3. data/README.textile +117 -0
  4. data/lib/mingle_events.rb +26 -0
  5. data/lib/mingle_events/feed.rb +5 -0
  6. data/lib/mingle_events/feed/author.rb +28 -0
  7. data/lib/mingle_events/feed/category.rb +75 -0
  8. data/lib/mingle_events/feed/element_support.rb +19 -0
  9. data/lib/mingle_events/feed/entry.rb +96 -0
  10. data/lib/mingle_events/feed/page.rb +61 -0
  11. data/lib/mingle_events/http_error.rb +26 -0
  12. data/lib/mingle_events/mingle_basic_auth_access.rb +71 -0
  13. data/lib/mingle_events/mingle_oauth_access.rb +40 -0
  14. data/lib/mingle_events/poller.rb +32 -0
  15. data/lib/mingle_events/processors.rb +10 -0
  16. data/lib/mingle_events/processors/author_filter.rb +62 -0
  17. data/lib/mingle_events/processors/card_data.rb +102 -0
  18. data/lib/mingle_events/processors/card_type_filter.rb +28 -0
  19. data/lib/mingle_events/processors/category_filter.rb +19 -0
  20. data/lib/mingle_events/processors/custom_property_filter.rb +30 -0
  21. data/lib/mingle_events/processors/http_post_publisher.rb +20 -0
  22. data/lib/mingle_events/processors/pipeline.rb +20 -0
  23. data/lib/mingle_events/processors/puts_publisher.rb +17 -0
  24. data/lib/mingle_events/project_custom_properties.rb +33 -0
  25. data/lib/mingle_events/project_event_fetcher.rb +96 -0
  26. data/test/mingle_events/feed/author_test.rb +39 -0
  27. data/test/mingle_events/feed/category_test.rb +20 -0
  28. data/test/mingle_events/feed/entry_test.rb +140 -0
  29. data/test/mingle_events/feed/page_test.rb +82 -0
  30. data/test/mingle_events/poller_test.rb +47 -0
  31. data/test/mingle_events/processors/author_filter_test.rb +80 -0
  32. data/test/mingle_events/processors/card_data_test.rb +210 -0
  33. data/test/mingle_events/processors/card_type_filter_test.rb +51 -0
  34. data/test/mingle_events/processors/category_filter_test.rb +27 -0
  35. data/test/mingle_events/processors/custom_property_filter_test.rb +51 -0
  36. data/test/mingle_events/processors/pipeline_test.rb +32 -0
  37. data/test/mingle_events/project_custom_properties_test.rb +39 -0
  38. data/test/mingle_events/project_event_fetcher_test.rb +122 -0
  39. data/test/test_helper.rb +163 -0
  40. data/test/web_hook_server/web_hook_server.rb +6 -0
  41. metadata +140 -0
@@ -0,0 +1,82 @@
1
+ require File.expand_path(File.join(File.dirname(__FILE__), '..', '..', 'test_helper'))
2
+
3
+ module MingleEvents
4
+ module Feed
5
+
6
+ class PageTest < Test::Unit::TestCase
7
+
8
+ def test_entries_are_enumerable
9
+ latest_entries_page = Page.new('https://mingle.example.com/api/v2/projects/atlas/feeds/events.xml', stub_mingle_access)
10
+
11
+ assert_equal([
12
+ 'https://mingle.example.com/projects/atlas/events/index/103',
13
+ 'https://mingle.example.com/projects/atlas/events/index/101',
14
+ 'https://mingle.example.com/projects/atlas/events/index/100'
15
+ ], latest_entries_page.entries.map(&:entry_id))
16
+ end
17
+
18
+ def test_next_page_returns_the_page_of_entries_as_specified_by_next_link
19
+ latest_entries_page = Page.new('https://mingle.example.com/api/v2/projects/atlas/feeds/events.xml', stub_mingle_access)
20
+ next_page = latest_entries_page.next
21
+ assert_equal('https://mingle.example.com/api/v2/projects/atlas/feeds/events.xml?page=2', next_page.url)
22
+ assert_equal('https://mingle.example.com/projects/atlas/events/index/99', next_page.entries.first.entry_id)
23
+ end
24
+
25
+ def test_next_page_when_on_last_page
26
+ last_page = Page.new('https://mingle.example.com/api/v2/projects/atlas/feeds/events.xml?page=1', stub_mingle_access)
27
+ assert_nil(last_page.next)
28
+ end
29
+
30
+ def test_previous_page_returns_the_page_of_entries_as_specified_by_previous_link
31
+ current_page = Page.new('https://mingle.example.com/api/v2/projects/atlas/feeds/events.xml?page=1', stub_mingle_access)
32
+ previous_page = current_page.previous
33
+ assert_equal('https://mingle.example.com/api/v2/projects/atlas/feeds/events.xml?page=2', previous_page.url)
34
+ assert_equal('https://mingle.example.com/projects/atlas/events/index/99', previous_page.entries.first.entry_id)
35
+ end
36
+
37
+ def test_previous_page_when_on_latest_entries
38
+ latest_entries_page = Page.new('https://mingle.example.com/api/v2/projects/atlas/feeds/events.xml', stub_mingle_access)
39
+ assert_nil(latest_entries_page.previous)
40
+ end
41
+
42
+ def test_can_determine_whether_archived
43
+ assert !Page.new('https://mingle.example.com/api/v2/projects/atlas/feeds/events.xml', stub_mingle_access).archived?
44
+ assert !Page.new('https://mingle.example.com/api/v2/projects/atlas/feeds/events.xml?page=3', stub_mingle_access).archived?
45
+ assert Page.new('https://mingle.example.com/api/v2/projects/atlas/feeds/events.xml?page=1', stub_mingle_access).archived?
46
+ end
47
+
48
+ def test_can_determine_closest_archived_page
49
+ assert_equal 'https://mingle.example.com/api/v2/projects/atlas/feeds/events.xml?page=2',
50
+ Page.new('https://mingle.example.com/api/v2/projects/atlas/feeds/events.xml', stub_mingle_access).closest_archived_page.url
51
+ assert_equal 'https://mingle.example.com/api/v2/projects/atlas/feeds/events.xml?page=2',
52
+ Page.new('https://mingle.example.com/api/v2/projects/atlas/feeds/events.xml?page=2', stub_mingle_access).closest_archived_page.url
53
+ end
54
+
55
+ def test_can_determine_when_there_is_no_closest_archived_page
56
+ mingle_access = StubMingleAccess.new
57
+ mingle_access.register_page_content('https://mingle.example.com/api/v2/projects/atlas/feeds/events.xml?page=1',
58
+ %{
59
+ <feed xmlns="http://www.w3.org/2005/Atom" xmlns:mingle="http://www.thoughtworks-studios.com/ns/mingle">
60
+ <link href="https://mingle.example.com/api/v2/projects/event_tester/feeds/events.xml" rel="current"/>
61
+ <link href="https://mingle.example.com/api/v2/projects/event_tester/feeds/events.xml" rel="self"/>
62
+ <entry>
63
+ <id>https://mingle.example.com/projects/event_tester/events/index/390</id>
64
+ <title>Card #2 Card Two created</title>
65
+ <updated>2011-08-02T22:27:38Z</updated>
66
+ <author><name>David</name></author>
67
+ </entry>
68
+ <entry>
69
+ <id>https://mingle.example.com/projects/event_tester/events/index/389</id>
70
+ <title>Card #1 Card One created</title>
71
+ <updated>2011-08-02T22:27:36Z</updated>
72
+ <author><name>David</name></author>
73
+ </entry>
74
+ </feed>
75
+ })
76
+ assert_nil Page.new('https://mingle.example.com/api/v2/projects/atlas/feeds/events.xml?page=1', mingle_access).closest_archived_page
77
+ end
78
+
79
+ end
80
+
81
+ end
82
+ end
@@ -0,0 +1,47 @@
1
+ require File.expand_path(File.join(File.dirname(__FILE__), '..', 'test_helper'))
2
+
3
+ module MingleEvents
4
+ class PollerTest < Test::Unit::TestCase
5
+
6
+ def test_sends_all_events_to_all_processors
7
+ state_folder = temp_dir
8
+ mingle_access = stub_mingle_access
9
+ processor_1 = DummyProcessor.new
10
+ processor_2 = DummyProcessor.new
11
+ poller = Poller.new(mingle_access, {'atlas' => [processor_1, processor_2]})
12
+ poller.run_once(:clean => true)
13
+
14
+ expected_entry_ids = [
15
+ 'https://mingle.example.com/projects/atlas/events/index/23',
16
+ 'https://mingle.example.com/projects/atlas/events/index/97',
17
+ 'https://mingle.example.com/projects/atlas/events/index/98',
18
+ 'https://mingle.example.com/projects/atlas/events/index/99',
19
+ 'https://mingle.example.com/projects/atlas/events/index/100',
20
+ 'https://mingle.example.com/projects/atlas/events/index/101',
21
+ 'https://mingle.example.com/projects/atlas/events/index/103'
22
+ ]
23
+
24
+ [processor_1, processor_2].each do |processor|
25
+ assert_equal(expected_entry_ids, processor.processed_events.map(&:entry_id))
26
+ end
27
+ end
28
+
29
+ class DummyProcessor
30
+
31
+ def initialize
32
+ @processed_events = []
33
+ end
34
+
35
+ def process_events(events)
36
+ @processed_events = @processed_events + events
37
+ end
38
+
39
+ def processed_events
40
+ @processed_events
41
+ end
42
+
43
+ end
44
+
45
+ end
46
+
47
+ end
@@ -0,0 +1,80 @@
1
+ require File.expand_path(File.join(File.dirname(__FILE__), '..', '..', 'test_helper'))
2
+
3
+ module MingleEvents
4
+ module Processors
5
+ class AuthorFilterTest < Test::Unit::TestCase
6
+
7
+ def setup
8
+ @dummy_mingle_access = StubMingleAccess.new
9
+ @dummy_mingle_access.register_page_content(
10
+ '/api/v2/projects/atlas/team.xml',
11
+ %{
12
+ <projects_members type="array">
13
+ <projects_member>
14
+ <user url="http://example.com/users/10.xml">
15
+ <id type="integer">333</id>
16
+ <name>Chester Tester</name>
17
+ <login>ctester</login>
18
+ <email>chester.tester@example.com</email>
19
+ </user>
20
+ </proejcts_member>
21
+ <projects_member>
22
+ <user url="http://example.com/users/17.xml">
23
+ <id type="integer">444</id>
24
+ <name>Joe Developer</name>
25
+ <login>jdeveloper</login>
26
+ <email>joe.developer@example.com</email>
27
+ </user>
28
+ </proejcts_member>
29
+ <projects_members>
30
+ }
31
+ )
32
+
33
+ @event_1 = stub_event(1, {:uri => "http://example.com/users/10.xml", :login => 'ctester'})
34
+ @event_2 = stub_event(2, {:uri => "http://example.com/users/17.xml", :login => 'jdeveloper'})
35
+ @event_3 = stub_event(3, {:uri => "http://example.com/users/10.xml", :login => 'ctester'})
36
+ @unprocessed_events = [@event_1, @event_2, @event_3]
37
+ end
38
+
39
+ def test_filter_can_only_be_constructed_with_a_single_criteria
40
+ begin
41
+ AuthorFilter.new({:url => 'foo', :email => 'bar'}, nil, nil)
42
+ fail("Should not have been able to construct this filter!")
43
+ rescue StandardError => e
44
+ assert_equal(0, e.message.index("Author spec must contain 1 and only 1 piece of criteria"))
45
+ end
46
+ end
47
+
48
+ def test_filter_by_author_url
49
+ author_filter = AuthorFilter.new({:url => 'http://example.com/users/10.xml'}, @dummy_mingle_access, 'atlas')
50
+ filtered_events = author_filter.process_events(@unprocessed_events)
51
+ assert_equal([@event_1, @event_3], filtered_events)
52
+ end
53
+
54
+ def test_filter_by_author_login
55
+ author_filter = AuthorFilter.new({:login => 'ctester'}, @dummy_mingle_access, 'atlas')
56
+ filtered_events = author_filter.process_events(@unprocessed_events)
57
+ assert_equal([@event_1, @event_3], filtered_events)
58
+ end
59
+
60
+ def test_filter_by_author_email
61
+ author_filter = AuthorFilter.new({:email => 'joe.developer@example.com'}, @dummy_mingle_access, 'atlas')
62
+ filtered_events = author_filter.process_events(@unprocessed_events)
63
+ assert_equal([@event_2], filtered_events)
64
+ end
65
+
66
+ def test_filter_returns_empty_list_when_no_match
67
+ author_filter = AuthorFilter.new({:email => 'sammy.soso@example.com'}, @dummy_mingle_access, 'atlas')
68
+ filtered_events = author_filter.process_events(@unprocessed_events)
69
+ assert_equal([], filtered_events)
70
+ end
71
+
72
+ private
73
+
74
+ def stub_event(entry_id, author)
75
+ OpenStruct.new(:entry_id => entry_id, :author => OpenStruct.new(author))
76
+ end
77
+
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,210 @@
1
+ require File.expand_path(File.join(File.dirname(__FILE__), '..', '..', 'test_helper'))
2
+
3
+ module MingleEvents
4
+ module Processors
5
+
6
+ # TODO: better revision resource URIs in tests -- make tests more understandable
7
+ class CardDataTest < Test::Unit::TestCase
8
+
9
+ def test_load_basic_card_data_in_bulk
10
+ event_1 = stub_event(1, 100, 11, ['card', 'comment-addition'])
11
+ event_2 = stub_event(3, nil, nil, ['revision-commit'])
12
+ event_3 = stub_event(4, 103, 13, ['card', 'property-change'])
13
+ events = [event_1, event_2, event_3]
14
+
15
+ dummy_mingle_access = StubMingleAccess.new
16
+ dummy_mingle_access.register_page_content(
17
+ URI.escape('/api/v2/projects/atlas/cards/execute_mql.xml?mql=WHERE number IN (100,103)'),
18
+ %{
19
+ <?xml version="1.0" encoding="UTF-8"?>
20
+ <results type="array">
21
+ <result>
22
+ <number>100</number>
23
+ <card_type_name>story</card_type_name>
24
+ <version>11</version>
25
+ </result>
26
+ <result>
27
+ <number>103</number>
28
+ <card_type_name>bug</card_type_name>
29
+ <version>13</version>
30
+ </result>
31
+ </results>
32
+ })
33
+
34
+ card_data = CardData.new(dummy_mingle_access, 'atlas')
35
+ card_data.process_events(events)
36
+
37
+ assert_correct_basic_card_data_for_event(
38
+ {:number => 100, :card_type_name => 'story', :version => 11},
39
+ card_data, event_1)
40
+ assert_correct_basic_card_data_for_event(
41
+ {:number => 103, :card_type_name => 'bug', :version => 13},
42
+ card_data, event_3)
43
+ end
44
+
45
+ def test_load_custom_properties
46
+ event_1 = stub_event(1, 100, 11, ['card', 'comment-addition'])
47
+
48
+ dummy_mingle_access = StubMingleAccess.new
49
+ dummy_mingle_access.register_page_content(
50
+ URI.escape('/api/v2/projects/atlas/cards/execute_mql.xml?mql=WHERE number IN (100)'),
51
+ %{
52
+ <?xml version="1.0" encoding="UTF-8"?>
53
+ <results type="array">
54
+ <result>
55
+ <number>100</number>
56
+ <card_type_name>story</card_type_name>
57
+ <version>11</version>
58
+ <cp_priority>Low</cp_priority>
59
+ <cp_estimate>5</cp_estimate>
60
+ <cp_completed_on nil="true" />
61
+ </result>
62
+ </results>
63
+ })
64
+
65
+ dummy_custom_properties = StubProjectCustomProperties.new({
66
+ 'cp_priority' => 'Foo', 'cp_estimate' => 'Bar', 'cp_completed_on' => 'Completed On'
67
+ })
68
+
69
+ card_data = CardData.new(dummy_mingle_access, 'atlas', dummy_custom_properties)
70
+ card_data.process_events([event_1])
71
+
72
+ assert_equal("Low", card_data.for_card_event(event_1)[:custom_properties]['Foo'])
73
+ assert_equal("5", card_data.for_card_event(event_1)[:custom_properties]['Bar'])
74
+ assert_nil card_data.for_card_event(event_1)[:custom_properties]['Completed On']
75
+ end
76
+
77
+ def test_load_basic_card_data_when_card_has_been_updated_beyond_the_specific_event
78
+ event_1 = stub_event(1, 100, 11, ['card', 'comment-addition'])
79
+ event_2 = stub_event(3, nil, nil, ['revision-commit'])
80
+ event_3 = stub_event(4, 103, 13, ['card', 'property-change'])
81
+ events = [event_1, event_2, event_3]
82
+
83
+ dummy_mingle_access = StubMingleAccess.new
84
+ dummy_mingle_access.register_page_content(
85
+ URI.escape('/api/v2/projects/atlas/cards/execute_mql.xml?mql=WHERE number IN (100,103)'),
86
+ %{
87
+ <?xml version="1.0" encoding="UTF-8"?>
88
+ <results type="array">
89
+ <result>
90
+ <number>100</number>
91
+ <card_type_name>story</card_type_name>
92
+ <version>11</version>
93
+ </result>
94
+ <result>
95
+ <number>103</number>
96
+ <card_type_name>story</card_type_name>
97
+ <version>15</version>
98
+ </result>
99
+ </results>
100
+ })
101
+ dummy_mingle_access.register_page_content('http://example.com?version=13',%{
102
+ <card>
103
+ <number type="integer">103</number>
104
+ <card_type url="https://localhost:7071/api/v2/projects/atlas/card_types/21.xml">
105
+ <name>epic</name>
106
+ </card_type>
107
+ <version type="integer">13</version>
108
+ </card>
109
+ })
110
+
111
+ card_data = CardData.new(dummy_mingle_access, 'atlas')
112
+ card_data.process_events(events)
113
+
114
+ assert_correct_basic_card_data_for_event(
115
+ {:number => 100, :card_type_name => 'story', :version => 11},
116
+ card_data, event_1)
117
+ assert_correct_basic_card_data_for_event(
118
+ {:number => 103, :card_type_name => 'epic', :version => 13},
119
+ card_data, event_3)
120
+ end
121
+
122
+ def test_load_custom_properties_when_card_has_been_updated_beyond_the_specific_event
123
+ event_1 = stub_event(1, 100, 11, ['card', 'comment-addition'])
124
+ events = [event_1]
125
+
126
+ dummy_mingle_access = StubMingleAccess.new
127
+ dummy_mingle_access.register_page_content(
128
+ URI.escape('/api/v2/projects/atlas/cards/execute_mql.xml?mql=WHERE number IN (100)'),
129
+ %{
130
+ <?xml version="1.0" encoding="UTF-8"?>
131
+ <results type="array">
132
+ <result>
133
+ <number>100</number>
134
+ <card_type_name>story</card_type_name>
135
+ <version>12</version>
136
+ <cp_priority>Low</cp_priority>
137
+ </result>
138
+ </results>
139
+ }
140
+ )
141
+ dummy_mingle_access.register_page_content('http://example.com?version=11',
142
+ %{
143
+ <card>
144
+ <number type="integer">101</number>
145
+ <card_type url="https://localhost:7071/api/v2/projects/atlas/card_types/24.xml">
146
+ <name>issue</name>
147
+ </card_type>
148
+ <version type="integer">11</version>
149
+ <properties type="array">
150
+ <property type_description="Managed text list" hidden="false">
151
+ <name>Priority</name>
152
+ <value>High</value>
153
+ </property>
154
+ <property type_description="Managed text list" hidden="false">
155
+ <name>Feature</name>
156
+ <value nil="true" />
157
+ </property>
158
+ </properties>
159
+ </card>
160
+ }
161
+ )
162
+
163
+ dummy_custom_properties = StubProjectCustomProperties.new({'cp_priority' => 'Priority', 'cp_feature' => 'Feature'})
164
+
165
+ card_data = CardData.new(dummy_mingle_access, 'atlas', dummy_custom_properties)
166
+ card_data.process_events(events)
167
+
168
+ assert_equal 'High', card_data.for_card_event(event_1)[:custom_properties]['Priority']
169
+ assert_nil card_data.for_card_event(event_1)[:custom_properties]['Feature']
170
+ end
171
+
172
+ def test_load_card_data_when_card_has_been_deleted_before_event_processing
173
+ event_1 = stub_event(1, 100, 11, ['card', 'comment-addition'])
174
+ events = [event_1]
175
+
176
+ dummy_mingle_access = StubMingleAccess.new
177
+ dummy_mingle_access.register_page_content(
178
+ URI.escape('/api/v2/projects/atlas/cards/execute_mql.xml?mql=WHERE number IN (100)'),
179
+ %{
180
+ <?xml version="1.0" encoding="UTF-8"?>
181
+ <results type="array">
182
+ </results>
183
+ })
184
+ dummy_mingle_access.register_page_not_found('http://example.com?version=11')
185
+
186
+ card_data = CardData.new(dummy_mingle_access, 'atlas')
187
+ card_data.process_events(events)
188
+
189
+ assert_nil(card_data.for_card_event(event_1))
190
+ end
191
+
192
+ private
193
+
194
+ def stub_event(entry_id, card_number, version, categories)
195
+ OpenStruct.new(
196
+ :entry_id => entry_id,
197
+ :card_number => card_number,
198
+ :card? => !card_number.nil?,
199
+ :version => version,
200
+ :card_version_resource_uri => "http://example.com?version=#{version}",
201
+ :categories => categories.map{|c| OpenStruct.new(:term => c)})
202
+ end
203
+
204
+ def assert_correct_basic_card_data_for_event(expected_data, actual_card_data, card_event)
205
+ assert_equal(expected_data, actual_card_data.for_card_event(card_event).delete_if{|k,v| k == :custom_properties})
206
+ end
207
+
208
+ end
209
+ end
210
+ end
@@ -0,0 +1,51 @@
1
+ require File.expand_path(File.join(File.dirname(__FILE__), '..', '..', 'test_helper'))
2
+
3
+ module MingleEvents
4
+ module Processors
5
+
6
+ class CardTypeFilterTest < Test::Unit::TestCase
7
+
8
+ def test_filters_events_on_card_type
9
+ event_1 = stub_event(true)
10
+ event_2 = stub_event(false)
11
+ event_3 = stub_event(true)
12
+ event_4 = stub_event(true)
13
+ event_5 = stub_event(true)
14
+
15
+ card_data = {
16
+ event_1 => {:card_type_name => 'story'},
17
+ event_3 => {:card_type_name => 'bug'},
18
+ event_4 => {:card_type_name => 'story'},
19
+ event_5 => {:card_type_name => 'issue'}
20
+ }
21
+ def card_data.for_card_event(card_event)
22
+ self[card_event]
23
+ end
24
+
25
+ filter = CardTypeFilter.new(['story', 'issue'], card_data)
26
+ filtered_events = filter.process_events([event_1, event_2, event_3, event_4, event_5])
27
+ assert_equal([event_1, event_4, event_5], filtered_events)
28
+ end
29
+
30
+ def test_drops_events_for_deleted_cards
31
+ event_1 = stub_event(true)
32
+
33
+ card_data = {}
34
+ def card_data.for_card_event(card_event)
35
+ self[card_event]
36
+ end
37
+
38
+ filter = CardTypeFilter.new(['story', 'issue'], card_data)
39
+ filtered_events = filter.process_events([event_1])
40
+ assert_equal([], filtered_events)
41
+ end
42
+
43
+ private
44
+
45
+ def stub_event(is_card)
46
+ OpenStruct.new(:card? => is_card)
47
+ end
48
+
49
+ end
50
+ end
51
+ end