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,27 @@
1
+ require File.expand_path(File.join(File.dirname(__FILE__), '..', '..', 'test_helper'))
2
+
3
+ module MingleEvents
4
+ module Processors
5
+ class CategoryFilterTest < Test::Unit::TestCase
6
+
7
+ def test_removes_events_without_matching_categories
8
+ event_1 = stub_event(1, [Feed::Category::CARD, Feed::Category::COMMENT_ADDITION])
9
+ event_2 = stub_event(2, [Feed::Category::CARD, Feed::Category::PROPERTY_CHANGE])
10
+ event_3 = stub_event(3, [Feed::Category::REVISION_COMMIT])
11
+ event_4 = stub_event(4, [Feed::Category::CARD, Feed::Category::PROPERTY_CHANGE])
12
+ event_5 = stub_event(5, [])
13
+ events = [event_1, event_2, event_3, event_4, event_5]
14
+
15
+ filter = CategoryFilter.new([Feed::Category::CARD, Feed::Category::PROPERTY_CHANGE])
16
+ assert_equal([event_2, event_4], filter.process_events(events))
17
+ end
18
+
19
+ private
20
+
21
+ def stub_event(id, categories)
22
+ OpenStruct.new(:entry_id => id, :categories => categories)
23
+ end
24
+
25
+ end
26
+ end
27
+ 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 CustomPropertyFilterTest < Test::Unit::TestCase
7
+
8
+ def test_filters_events_on_custom_property
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 => {:custom_properties => {'Priority' => 'High'}},
17
+ event_3 => {:custom_properties => {'Priority' => 'Low'}},
18
+ event_4 => {:custom_properties => {'Priority' => 'High'}},
19
+ event_5 => {:custom_properties => {'Severity' => 'High'}}
20
+ }
21
+ def card_data.for_card_event(card_event)
22
+ self[card_event]
23
+ end
24
+
25
+ filter = CustomPropertyFilter.new('Priority', 'High', 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], 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 = CustomPropertyFilter.new('Priority', 'High', 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
@@ -0,0 +1,32 @@
1
+ require File.expand_path(File.join(File.dirname(__FILE__), '..', '..', 'test_helper'))
2
+
3
+ module MingleEvents
4
+ module Processors
5
+ class PipelineTest < Test::Unit::TestCase
6
+
7
+ def test_chains_all_processors
8
+ events = [stub_event(100), stub_event(101), stub_event(102)]
9
+ pipeline = Pipeline.new([DeleteLastProcessor.new, ReversingProcessor.new])
10
+ processed_events = pipeline.process_events(events)
11
+ assert_equal([101, 100], processed_events.map(&:entry_id))
12
+ end
13
+
14
+ def stub_event(id)
15
+ OpenStruct.new(:entry_id => id)
16
+ end
17
+
18
+ class ReversingProcessor
19
+ def process_events(events)
20
+ events.reverse
21
+ end
22
+ end
23
+
24
+ class DeleteLastProcessor
25
+ def process_events(events)
26
+ events[0..-2]
27
+ end
28
+ end
29
+
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,39 @@
1
+ require File.expand_path(File.join(File.dirname(__FILE__), '..', 'test_helper'))
2
+
3
+ module MingleEvents
4
+
5
+ class ProjectCustomPropertiesTest < Test::Unit::TestCase
6
+
7
+ def test_can_lookup_property_name_by_column_name
8
+ property_definitions_xml = %{
9
+ <?xml version="1.0" encoding="UTF-8"?>
10
+ <property_definitions type="array">
11
+ <property_definition>
12
+ <name>Account</name>
13
+ <data_type>string</data_type>
14
+ <is_numeric type="boolean">false</is_numeric>
15
+ <column_name>cp_account</column_name>
16
+ </property_definition>
17
+ <property_definition>
18
+ <name>Account ID</name>
19
+ <data_type>string</data_type>
20
+ <is_numeric type="boolean">false</is_numeric>
21
+ <column_name>cp_account_id</column_name>
22
+ </property_definition>
23
+ </property_definitions>
24
+ }
25
+
26
+ dummy_mingle_access = StubMingleAccess.new
27
+ dummy_mingle_access.register_page_content(
28
+ URI.escape('/api/v2/projects/atlas/property_definitions.xml'),
29
+ property_definitions_xml
30
+ )
31
+
32
+ project_custom_properties = ProjectCustomProperties.new(dummy_mingle_access, "atlas")
33
+ assert_equal("Account ID", project_custom_properties.property_name_for_column("cp_account_id"))
34
+
35
+ end
36
+
37
+ end
38
+
39
+ end
@@ -0,0 +1,122 @@
1
+ require File.expand_path(File.join(File.dirname(__FILE__), '..', 'test_helper'))
2
+
3
+ module MingleEvents
4
+ class ProjectEventFetcherTest < Test::Unit::TestCase
5
+
6
+ def test_can_fetch_all_entries_and_write_to_disk_when_no_initial_state
7
+ state_dir = temp_dir
8
+ fetcher = ProjectEventFetcher.new('atlas', stub_mingle_access, state_dir)
9
+
10
+ file_for_first_new_entry = fetcher.fetch_latest
11
+
12
+ expected_entry_ids = [
13
+ 'https://mingle.example.com/projects/atlas/events/index/23',
14
+ 'https://mingle.example.com/projects/atlas/events/index/97',
15
+ 'https://mingle.example.com/projects/atlas/events/index/98',
16
+ 'https://mingle.example.com/projects/atlas/events/index/99',
17
+ 'https://mingle.example.com/projects/atlas/events/index/100',
18
+ 'https://mingle.example.com/projects/atlas/events/index/101',
19
+ 'https://mingle.example.com/projects/atlas/events/index/103'
20
+ ]
21
+
22
+ assert_expected_entry_chain_written_to_disk(expected_entry_ids, file_for_first_new_entry, fetcher)
23
+ end
24
+
25
+ def test_can_fetch_all_entries_and_write_to_disk_when_existing_state
26
+ state_dir = temp_dir
27
+ fetcher = ProjectEventFetcher.new('atlas', stub_mingle_access, state_dir)
28
+ setup_current_state(%{
29
+ <entry>
30
+ <id>https://mingle.example.com/projects/atlas/events/index/97</id>
31
+ <title>entry 97</title>
32
+ <updated>2011-02-03T01:10:52Z</updated>
33
+ <author><name>Harry</name></author>
34
+ </entry>}, fetcher)
35
+
36
+ file_for_first_new_entry = fetcher.fetch_latest
37
+
38
+ expected_entry_ids = [
39
+ 'https://mingle.example.com/projects/atlas/events/index/98',
40
+ 'https://mingle.example.com/projects/atlas/events/index/99',
41
+ 'https://mingle.example.com/projects/atlas/events/index/100',
42
+ 'https://mingle.example.com/projects/atlas/events/index/101',
43
+ 'https://mingle.example.com/projects/atlas/events/index/103'
44
+ ]
45
+
46
+ assert_expected_entry_chain_written_to_disk(expected_entry_ids, file_for_first_new_entry, fetcher)
47
+ end
48
+
49
+ def test_no_new_entries_with_current_state
50
+ state_dir = temp_dir
51
+ fetcher = ProjectEventFetcher.new('atlas', stub_mingle_access, state_dir)
52
+ setup_current_state(%{
53
+ <entry>
54
+ <id>https://mingle.example.com/projects/atlas/events/index/103</id>
55
+ <title>entry 103</title>
56
+ <updated>2011-02-03T01:10:52Z</updated>
57
+ <author><name>Harry</name></author>
58
+ </entry>}, fetcher)
59
+
60
+ assert_nil fetcher.fetch_latest
61
+
62
+ current_state = YAML.load(File.new(File.join(state_dir, 'current_state.yml')))
63
+ assert_equal(@last_fetched_entry_info_file, current_state[:last_fetched_entry_info_file])
64
+ end
65
+
66
+ def test_no_new_entries_with_no_current_state
67
+ state_dir = temp_dir
68
+ mingle_access = StubMingleAccess.new
69
+ mingle_access.register_page_content('/api/v2/projects/atlas/feeds/events.xml',%{
70
+ <?xml version="1.0" encoding="UTF-8"?>
71
+ <feed xmlns="http://www.w3.org/2005/Atom" xmlns:mingle="http://www.thoughtworks-studios.com/ns/mingle">
72
+ <title>Mingle Events: Blank Project</title>
73
+ <id>https://mingle.example.com/api/v2/projects/blank_project/feeds/events.xml</id>
74
+ <link href="https://mingle.example.com/api/v2/projects/blank_project/feeds/events.xml" rel="current"/>
75
+ <link href="https://mingle.example.com/api/v2/projects/blank_project/feeds/events.xml" rel="self"/>
76
+ <updated>2011-08-04T19:42:04Z</updated>
77
+ </feed>})
78
+ fetcher = ProjectEventFetcher.new('atlas', mingle_access, state_dir)
79
+
80
+ assert_nil fetcher.fetch_latest
81
+ assert !File.exist?(File.join(state_dir, 'current_state.yml'))
82
+ end
83
+
84
+ private
85
+
86
+ def setup_current_state(last_fetched_entry_xml, fetcher)
87
+ last_fetched_entry_info = {
88
+ :entry_xml => last_fetched_entry_xml,
89
+ :next_entry_file_path => nil
90
+ }
91
+ @last_fetched_entry_info_file = fetcher.file_for_entry(Feed::Entry.new(Nokogiri::XML(last_fetched_entry_xml).at('/entry')))
92
+ FileUtils.mkdir_p(File.dirname(@last_fetched_entry_info_file))
93
+ File.open(@last_fetched_entry_info_file, 'w') do |out|
94
+ YAML.dump(last_fetched_entry_info, out)
95
+ end
96
+ current_state = {:last_fetched_entry_info_file => @last_fetched_entry_info_file}
97
+ File.open(fetcher.current_state_file, 'w') do |out|
98
+ YAML.dump(current_state, out)
99
+ end
100
+
101
+ end
102
+
103
+ def assert_expected_entry_chain_written_to_disk(expected_entry_ids, file_for_first_new_entry, fetcher)
104
+
105
+ file_for_new_entry = file_for_first_new_entry
106
+ file_for_last_inspected_entry = nil
107
+ expected_entry_ids.each_with_index do |expected_entry_id, index|
108
+ file_for_last_inspected_entry = file_for_new_entry
109
+ new_entry_info = YAML.load(File.new(file_for_new_entry))
110
+ new_entry = Feed::Entry.new(Nokogiri::XML(new_entry_info[:entry_xml]).at('/entry'))
111
+ assert_equal(expected_entry_id, new_entry.entry_id)
112
+ file_for_new_entry = new_entry_info[:next_entry_file_path]
113
+ end
114
+
115
+ current_state = YAML.load(File.new(fetcher.current_state_file))
116
+ assert_equal(file_for_last_inspected_entry, current_state[:last_fetched_entry_info_file])
117
+
118
+ end
119
+
120
+
121
+ end
122
+ end
@@ -0,0 +1,163 @@
1
+ require 'test/unit'
2
+
3
+ require 'ostruct'
4
+ require 'fileutils'
5
+
6
+ require 'rubygems'
7
+ require 'active_support'
8
+
9
+ require File.expand_path(File.join(File.dirname(__FILE__), '..', 'lib', 'mingle_events'))
10
+
11
+ MingleEvents.log.level = Logger::WARN
12
+
13
+ class Test::Unit::TestCase
14
+
15
+ # page 3
16
+ LATEST_PAGE_CONTENT = %{
17
+ <feed xmlns="http://www.w3.org/2005/Atom" xmlns:mingle="http://www.thoughtworks-studios.com/ns/mingle">
18
+
19
+ <link href="https://mingle.example.com/api/v2/projects/atlas/feeds/events.xml" rel="current"/>
20
+ <link href="https://mingle.example.com/api/v2/projects/atlas/feeds/events.xml" rel="self"/>
21
+ <link href="https://mingle.example.com/api/v2/projects/atlas/feeds/events.xml?page=2" rel="next"/>
22
+
23
+ <entry>
24
+ <id>https://mingle.example.com/projects/atlas/events/index/103</id>
25
+ <title>entry 103</title>
26
+ <updated>2011-02-03T08:12:42Z</updated>
27
+ <author><name>Bob</name></author>
28
+ </entry>
29
+ <entry>
30
+ <id>https://mingle.example.com/projects/atlas/events/index/101</id>
31
+ <title>entry 101</title>
32
+ <updated>2011-02-03T02:09:16Z</updated>
33
+ <author><name>Bob</name></author>
34
+ </entry>
35
+ <entry>
36
+ <id>https://mingle.example.com/projects/atlas/events/index/100</id>
37
+ <title>entry 100</title>
38
+ <updated>2011-02-03T01:58:02Z</updated>
39
+ <author><name>Mary</name></author>
40
+ </entry>
41
+ </feed>
42
+ }
43
+
44
+ # page 2
45
+ PAGE_2_CONTENT = %{
46
+ <feed xmlns="http://www.w3.org/2005/Atom" xmlns:mingle="http://www.thoughtworks-studios.com/ns/mingle">
47
+
48
+ <link href="https://mingle.example.com/api/v2/projects/atlas/feeds/events.xml" rel="current"/>
49
+ <link href="https://mingle.example.com/api/v2/projects/atlas/feeds/events.xml?page=2" rel="self"/>
50
+ <link href="https://mingle.example.com/api/v2/projects/atlas/feeds/events.xml?page=1" rel="next"/>
51
+ <link href="https://mingle.example.com/api/v2/projects/atlas/feeds/events.xml?page=3" rel="previous"/>
52
+
53
+ <entry>
54
+ <id>https://mingle.example.com/projects/atlas/events/index/99</id>
55
+ <title>entry 99</title>
56
+ <updated>2011-02-03T01:30:52Z</updated>
57
+ <author><name>Harry</name></author>
58
+ </entry>
59
+
60
+ <entry>
61
+ <id>https://mingle.example.com/projects/atlas/events/index/98</id>
62
+ <title>entry 98</title>
63
+ <updated>2011-02-03T01:20:52Z</updated>
64
+ <author><name>Harry</name></author>
65
+ </entry>
66
+
67
+ <entry>
68
+ <id>https://mingle.example.com/projects/atlas/events/index/97</id>
69
+ <title>entry 97</title>
70
+ <updated>2011-02-03T01:10:52Z</updated>
71
+ <author><name>Harry</name></author>
72
+ </entry>
73
+ </feed>
74
+ }
75
+
76
+ # page 1
77
+ PAGE_1_CONTENT = %{
78
+ <feed xmlns="http://www.w3.org/2005/Atom" xmlns:mingle="http://www.thoughtworks-studios.com/ns/mingle">
79
+
80
+ <link href="https://mingle.example.com/api/v2/projects/atlas/feeds/events.xml" rel="current"/>
81
+ <link href="https://mingle.example.com/api/v2/projects/atlas/feeds/events.xml?page=1" rel="self"/>
82
+ <link href="https://mingle.example.com/api/v2/projects/atlas/feeds/events.xml?page=2" rel="previous"/>
83
+
84
+ <entry>
85
+ <id>https://mingle.example.com/projects/atlas/events/index/23</id>
86
+ <title>entry 23</title>
87
+ <updated>2011-02-01T01:00:52Z</updated>
88
+ <author><name>Bob</name></author>
89
+ </entry>
90
+ </feed>
91
+ }
92
+
93
+ def stub_mingle_access
94
+ stub = StubMingleAccess.new
95
+ stub.register_page_content('https://mingle.example.com/api/v2/projects/atlas/feeds/events.xml', LATEST_PAGE_CONTENT)
96
+ stub.register_page_content('https://mingle.example.com/api/v2/projects/atlas/feeds/events.xml?page=2', PAGE_2_CONTENT)
97
+ stub.register_page_content('https://mingle.example.com/api/v2/projects/atlas/feeds/events.xml?page=1', PAGE_1_CONTENT)
98
+ stub.register_page_content('https://mingle.example.com/api/v2/projects/atlas/feeds/events.xml?page=3', LATEST_PAGE_CONTENT)
99
+ stub.register_page_content('/api/v2/projects/atlas/feeds/events.xml', LATEST_PAGE_CONTENT)
100
+ stub.register_page_content('/api/v2/projects/atlas/feeds/events.xml?page=2', PAGE_2_CONTENT)
101
+ stub.register_page_content('/api/v2/projects/atlas/feeds/events.xml?page=1', PAGE_1_CONTENT)
102
+ stub.register_page_content('/api/v2/projects/atlas/feeds/events.xml?page=3', LATEST_PAGE_CONTENT)
103
+
104
+ stub
105
+ end
106
+
107
+ def temp_dir
108
+ path = File.expand_path(File.join(File.dirname(__FILE__), 'tmp', ActiveSupport::SecureRandom.hex(16)))
109
+ FileUtils.mkdir_p(path)
110
+ path
111
+ end
112
+
113
+ def temp_file
114
+ File.join(temp_dir, ActiveSupport::SecureRandom.hex(16))
115
+ end
116
+
117
+ class StubMingleAccess
118
+
119
+ def initialize
120
+ @pages_by_path = {}
121
+ @not_found_pages = []
122
+ end
123
+
124
+ def base_url
125
+ 'http://example.com/mingle'
126
+ end
127
+
128
+ def register_page_content(path, content)
129
+ @pages_by_path[path] = content
130
+ end
131
+
132
+ def register_page_not_found(path)
133
+ @not_found_pages << path
134
+ end
135
+
136
+ def fetch_page(path)
137
+ if @not_found_pages.include?(path)
138
+ rsp = Net::HTTPNotFound.new(nil, '404', 'Page not found!')
139
+ def rsp.body
140
+ "404!!!!!"
141
+ end
142
+ raise MingleEvents::HttpError.new(rsp, path)
143
+ end
144
+
145
+ raise "Attempting to fetch page at #{path}, but your test has not registered content for this path! Registered paths: #{@pages_by_path.keys.inspect}" unless @pages_by_path.key?(path)
146
+ @pages_by_path[path]
147
+ end
148
+
149
+ end
150
+
151
+ class StubProjectCustomProperties
152
+
153
+ def initialize(property_names_by_column_names)
154
+ @property_names_by_column_names = property_names_by_column_names
155
+ end
156
+
157
+ def property_name_for_column(column_name)
158
+ @property_names_by_column_names[column_name]
159
+ end
160
+
161
+ end
162
+
163
+ end
@@ -0,0 +1,6 @@
1
+ require 'rubygems'
2
+ require 'sinatra'
3
+
4
+ post '/' do
5
+ puts params[:event]
6
+ end