mingle_events 0.0.4

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