web-ex-events 1.0.1

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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 6313292bccf157b4ad36a2cbf03c81641e4bd024
4
+ data.tar.gz: 256e43c16759d10915bf1b80bf89ab29cb2404b8
5
+ SHA512:
6
+ metadata.gz: d39ebb09c79cb01a8f18a02efcfe5645c67c7fce0ecaf3e549fa858fb078c4228407f010d5b4d4c482ab37a4cd3316414c6d3beff31d518bb9a1c8b182f0f408
7
+ data.tar.gz: 17931565713e2273947abc0298abbe5e123eb83ec66afc78ba0ccf5e5e8f7774b1384cf3f9414a2e01251257b3f1b6d945fdb5a44509bfc58e4b64d9d3d336e5
data/.gitignore ADDED
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in web-ex-events.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 Pete Nicholls
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,56 @@
1
+ # WebEx::Events
2
+
3
+ A library to read WebEx events through the WebEx API.
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ gem 'web-ex-events'
10
+
11
+ And then execute:
12
+
13
+ $ bundle
14
+
15
+ Or install it yourself as:
16
+
17
+ $ gem install web-ex-events
18
+
19
+ ## Usage
20
+
21
+ ```ruby
22
+ require 'web_ex/events'
23
+
24
+ # Set WebEx site name
25
+ ENV['WEB_EX_SITE_NAME'] = "…"
26
+
27
+ # Web Ex Site and Partner ID (available through WebEx support request)
28
+ ENV['WEB_EX_SITE_ID'] = "…"
29
+ ENV['WEB_EX_PARTNER_ID'] = "…"
30
+
31
+ # Your WebEx credentials
32
+ ENV['WEB_EX_USERNAME'] = "…"
33
+ ENV['WEB_EX_PASSWORD'] = "…"
34
+
35
+ next_30_days = 30 * 24 * 60 * 60
36
+ events = WebEx::Events::Event.fetch(next_30_days)
37
+
38
+ # Print event details
39
+ events.each do |event|
40
+ puts event.name
41
+ puts event.start_date
42
+ puts "#{event.duration} mins"
43
+ puts event.description
44
+ end
45
+
46
+ # Print all events as JSON
47
+ puts events.to_json
48
+ ```
49
+
50
+ ## Contributing
51
+
52
+ 1. Fork it
53
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
54
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
55
+ 4. Push to the branch (`git push origin my-new-feature`)
56
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
@@ -0,0 +1,10 @@
1
+ require 'nokogiri'
2
+ require 'httparty'
3
+ require 'tzinfo'
4
+
5
+ require "web_ex/events/version"
6
+ require "web_ex/events/configuration"
7
+ require "web_ex/events/event"
8
+ require "web_ex/events/event_list"
9
+ require "web_ex/events/event_summary_request"
10
+ require "web_ex/events/time_with_zone"
@@ -0,0 +1,15 @@
1
+ module WebEx
2
+ module Events
3
+ class Configuration
4
+
5
+ %w( site_name site_id username password partner_id ).each do |name|
6
+ define_singleton_method(name) { ENV["WEBEX_#{name.upcase}"] }
7
+ end
8
+
9
+ def self.xml_service_url
10
+ "https://#{site_name}.webex.com/WBXService/XMLService"
11
+ end
12
+
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,43 @@
1
+ module WebEx
2
+ module Events
3
+ class Event
4
+ attr_accessor :name, :start_date, :end_date, :key, :description, :duration
5
+
6
+ def initialize(attributes = {})
7
+ attributes.each do |k, v|
8
+ send("#{k}=", v)
9
+ end
10
+ end
11
+
12
+ def to_json(*args)
13
+ JSON.dump(
14
+ name: name,
15
+ start_date: start_date,
16
+ end_date: end_date,
17
+ key: key,
18
+ description: description,
19
+ duration: duration
20
+ )
21
+ end
22
+
23
+ def self.from_xml(xml)
24
+ node = Nokogiri::XML(xml)
25
+ element = node.xpath('event')
26
+
27
+ self.new(
28
+ name: element.xpath('sessionName').text,
29
+ start_date: TimeWithZone.new(element.xpath('startDate').text, element.xpath('timeZoneID').text).to_datetime,
30
+ end_date: TimeWithZone.new(element.xpath('endDate').text, element.xpath('timeZoneID').text).to_datetime,
31
+ key: element.xpath('sessionKey').text,
32
+ description: element.xpath('description').text,
33
+ duration: element.xpath('duration').text.to_i
34
+ )
35
+ end
36
+
37
+ def self.fetch(time_frame)
38
+ request = EventSummaryRequest.new(start_date: Time.now, end_date: Time.now + time_frame)
39
+ request.find_events!
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,31 @@
1
+ module WebEx
2
+ module Events
3
+ class EventList
4
+ include Enumerable
5
+
6
+ def initialize(*events)
7
+ @events = events.flatten
8
+ end
9
+
10
+ def length
11
+ @events.length
12
+ end
13
+
14
+ def each(&block)
15
+ @events.each(&block)
16
+ end
17
+
18
+ def last
19
+ @events.last
20
+ end
21
+
22
+ def empty?
23
+ @events.empty?
24
+ end
25
+
26
+ def to_json
27
+ JSON.dump(@events)
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,75 @@
1
+ module WebEx
2
+ module Events
3
+ class EventSummaryRequest
4
+ WEBEX_EMPTY_RESULTS_EXCEPTION_ID = "000015"
5
+
6
+ attr_accessor :start_date, :end_date
7
+
8
+ def initialize(options = {})
9
+ @start_date, @end_date = options[:start_date], options[:end_date]
10
+ end
11
+
12
+ def find_events!
13
+ events = xml_response.xpath('//event').map do |node|
14
+ Event.from_xml(node.to_xml)
15
+ end
16
+
17
+ EventList.new(events)
18
+ end
19
+
20
+ def payload
21
+ <<-XML
22
+ <?xml version="1.0" encoding="UTF-8"?>
23
+ <message xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
24
+ <header>
25
+ <securityContext>
26
+ <webExID>#{Configuration.username}</webExID>
27
+ <password>#{Configuration.password}</password>
28
+ <siteID>#{Configuration.site_id}</siteID>
29
+ <partnerID>#{Configuration.partner_id}</partnerID>
30
+ </securityContext>
31
+ </header>
32
+ <body>
33
+ <bodyContent xsi:type="java:com.webex.service.binding.event.LstsummaryEvent">
34
+ <listControl>
35
+ <startFrom/>
36
+ </listControl>
37
+ <order>
38
+ <orderBy>STARTTIME</orderBy>
39
+ </order>
40
+ <dateScope>
41
+ <startDateStart>#{formatted_date start_date}</startDateStart>
42
+ <endDateEnd>#{formatted_date end_date}</endDateEnd>
43
+ <timeZoneID>#{TimeWithZone::UTC_ZONE_ID}</timeZoneID>
44
+ </dateScope>
45
+ </bodyContent>
46
+ </body>
47
+ </message>
48
+ XML
49
+ end
50
+
51
+ private
52
+
53
+ def response
54
+ @response ||= HTTParty.post(Configuration.xml_service_url, body: payload)
55
+ end
56
+
57
+ def xml_response
58
+ @xml_response ||= begin
59
+ xml = Nokogiri::XML(response.body)
60
+ webex_exception = xml.at_xpath('//serv:exceptionID')
61
+
62
+ if webex_exception && webex_exception.content != WEBEX_EMPTY_RESULTS_EXCEPTION_ID
63
+ raise "WebEx Error: #{xml.at_xpath('//serv:reason').content}"
64
+ end
65
+
66
+ xml.remove_namespaces!
67
+ end
68
+ end
69
+
70
+ def formatted_date(date)
71
+ date.utc.strftime('%m/%d/%Y %H:%M:%S') if date
72
+ end
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,83 @@
1
+ module WebEx
2
+ module Events
3
+ class TimeWithZone
4
+ UTC_ZONE_ID = 21
5
+
6
+ TIME_ZONE_MAPPINGS = {
7
+ 0 => "Pacific/Niue",
8
+ 1 => "Pacific/Apia",
9
+ 2 => "Pacific/Honolulu",
10
+ 3 => "America/Juneau ",
11
+ 4 => "America/Los_Angeles",
12
+ 5 => "America/Phoenixr",
13
+ 6 => "America/Denver",
14
+ 7 => "America/Chicago",
15
+ 8 => "America/Tegucigalpa",
16
+ 9 => "America/Regina",
17
+ 10 => "America/Bogota",
18
+ 11 => "America/New_York",
19
+ 12 => "America/Indiana/Indianapolis",
20
+ 13 => "America/Halifax",
21
+ 14 => "America/Caracas",
22
+ 15 => "America/St_Johns",
23
+ 16 => "America/Sao_Paulo",
24
+ 17 => "America/Argentina/Buenos_Aires",
25
+ 18 => "Atlantic/South_Georgia",
26
+ 19 => "Atlantic/Azores",
27
+ 20 => "Africa/Casablanca",
28
+ 21 => "Europe/London",
29
+ 22 => "Europe/Amsterdam",
30
+ 23 => "Europe/Paris",
31
+ 24 => "Europe/Paris",
32
+ 25 => "Europe/Berlin",
33
+ 26 => "Europe/Athens",
34
+ 27 => "Europe/Athens",
35
+ 28 => "Africa/Cairo",
36
+ 29 => "Africa/Johannesburg",
37
+ 30 => "Europe/Helsinki",
38
+ 31 => "Asia/Amman",
39
+ 32 => "Asia/Baghdad",
40
+ 33 => "Europe/Moscow",
41
+ 34 => "Africa/Nairobi",
42
+ 35 => "Asia/Tehran",
43
+ 36 => "Asia/Muscat",
44
+ 37 => "Asia/Baku",
45
+ 38 => "Asia/Kabul",
46
+ 39 => "Asia/Yekaterinburg",
47
+ 40 => "Asia/Karachi",
48
+ 41 => "Asia/Kolkata",
49
+ 42 => "Asia/Colombo",
50
+ 43 => "Asia/Almaty",
51
+ 44 => "Asia/Bangkok",
52
+ 45 => "Asia/Shanghai",
53
+ 46 => "Australia/Perth",
54
+ 47 => "Asia/Singapore",
55
+ 48 => "Asia/Taipei",
56
+ 49 => "Asia/Tokyo",
57
+ 50 => "Asia/Seoul",
58
+ 51 => "Asia/Yakutsk",
59
+ 52 => "Australia/Adelaide",
60
+ 53 => "Australia/Darwin",
61
+ 54 => "Australia/Brisbane",
62
+ 55 => "Australia/Sydney",
63
+ 56 => "Pacific/Guam",
64
+ 57 => "Australia/Hobart",
65
+ 58 => "Asia/Vladivostok",
66
+ 59 => "Asia/Magadan",
67
+ 60 => "Pacific/Auckland",
68
+ 61 => "Pacific/Fiji"
69
+ }
70
+
71
+ def initialize(date_string, timezone_id)
72
+ @date_string = date_string
73
+ @timezone_id = timezone_id.to_i
74
+ end
75
+
76
+ def to_datetime
77
+ month, day, year, hour, minute, second = @date_string.split(/[\/ :]/).map(&:to_i)
78
+ time_zone = TZInfo::Timezone.get(TIME_ZONE_MAPPINGS[@timezone_id])
79
+ time_zone.local_to_utc DateTime.new(year, month, day, hour, minute, second)
80
+ end
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,5 @@
1
+ module WebEx
2
+ module Events
3
+ VERSION = "1.0.1"
4
+ end
5
+ end
@@ -0,0 +1,200 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <serv:message xmlns:serv="http://www.webex.com/schemas/2002/06/service" xmlns:com="http://www.webex.com/schemas/2002/06/common" xmlns:event="http://www.webex.com/schemas/2002/06/service/event">
3
+ <serv:header>
4
+ <serv:response>
5
+ <serv:result>SUCCESS</serv:result>
6
+ <serv:gsbStatus>PRIMARY</serv:gsbStatus>
7
+ </serv:response>
8
+ </serv:header>
9
+ <serv:body>
10
+ <serv:bodyContent xsi:type="event:lstsummaryEventResponse" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
11
+ <event:matchingRecords>
12
+ <serv:total>13</serv:total>
13
+ <serv:returned>13</serv:returned>
14
+ <serv:startFrom>1</serv:startFrom>
15
+ </event:matchingRecords>
16
+ <event:event>
17
+ <event:sessionKey>337167902</event:sessionKey>
18
+ <event:sessionName>Baking Basics</event:sessionName>
19
+ <event:sessionType>220</event:sessionType>
20
+ <event:hostWebExID>bakery</event:hostWebExID>
21
+ <event:startDate>05/01/2013 05:00:00</event:startDate>
22
+ <event:endDate>05/01/2013 06:30:00</event:endDate>
23
+ <event:timeZoneID>4</event:timeZoneID>
24
+ <event:duration>90</event:duration>
25
+ <event:description>Learn the intricacies of Japanese baking in this 90 minute webinar.</event:description>
26
+ <event:status>NOT_INPROGRESS</event:status>
27
+ <event:panelists></event:panelists>
28
+ <event:listStatus>UNLISTED</event:listStatus>
29
+ </event:event>
30
+ <event:event>
31
+ <event:sessionKey>241299796</event:sessionKey>
32
+ <event:sessionName>Advanced Baking</event:sessionName>
33
+ <event:sessionType>220</event:sessionType>
34
+ <event:hostWebExID>bakery</event:hostWebExID>
35
+ <event:startDate>05/01/2013 08:00:00</event:startDate>
36
+ <event:endDate>05/01/2013 09:00:00</event:endDate>
37
+ <event:timeZoneID>4</event:timeZoneID>
38
+ <event:duration>60</event:duration>
39
+ <event:description>Learn the intricacies of Japanese baking in this 90 minute webinar.</event:description>
40
+ <event:status>NOT_INPROGRESS</event:status>
41
+ <event:panelists>Satoshi Nakamoto</event:panelists>
42
+ <event:listStatus>UNLISTED</event:listStatus>
43
+ </event:event>
44
+ <event:event>
45
+ <event:sessionKey>741475588</event:sessionKey>
46
+ <event:sessionName>Baking Basics</event:sessionName>
47
+ <event:sessionType>220</event:sessionType>
48
+ <event:hostWebExID>bakery</event:hostWebExID>
49
+ <event:startDate>05/01/2013 11:00:00</event:startDate>
50
+ <event:endDate>05/01/2013 12:30:00</event:endDate>
51
+ <event:timeZoneID>4</event:timeZoneID>
52
+ <event:duration>90</event:duration>
53
+ <event:description>Learn the intricacies of Japanese baking in this 90 minute webinar.</event:description>
54
+ <event:status>NOT_INPROGRESS</event:status>
55
+ <event:panelists></event:panelists>
56
+ <event:listStatus>UNLISTED</event:listStatus>
57
+ </event:event>
58
+ <event:event>
59
+ <event:sessionKey>785125343</event:sessionKey>
60
+ <event:sessionName>Baking Basics</event:sessionName>
61
+ <event:sessionType>220</event:sessionType>
62
+ <event:hostWebExID>bakery</event:hostWebExID>
63
+ <event:startDate>05/01/2013 13:00:00</event:startDate>
64
+ <event:endDate>05/01/2013 14:30:00</event:endDate>
65
+ <event:timeZoneID>4</event:timeZoneID>
66
+ <event:duration>90</event:duration>
67
+ <event:description>Learn the intricacies of Japanese baking in this 90 minute webinar.</event:description>
68
+ <event:status>NOT_INPROGRESS</event:status>
69
+ <event:panelists></event:panelists>
70
+ <event:listStatus>UNLISTED</event:listStatus>
71
+ </event:event>
72
+ <event:event>
73
+ <event:sessionKey>516377344</event:sessionKey>
74
+ <event:sessionName>Baking Basics</event:sessionName>
75
+ <event:sessionType>220</event:sessionType>
76
+ <event:hostWebExID>bakery</event:hostWebExID>
77
+ <event:startDate>05/02/2013 05:00:00</event:startDate>
78
+ <event:endDate>05/02/2013 06:30:00</event:endDate>
79
+ <event:timeZoneID>4</event:timeZoneID>
80
+ <event:duration>90</event:duration>
81
+ <event:description>Learn the intricacies of Japanese baking in this 90 minute webinar.</event:description>
82
+ <event:status>NOT_INPROGRESS</event:status>
83
+ <event:panelists></event:panelists>
84
+ <event:listStatus>UNLISTED</event:listStatus>
85
+ </event:event>
86
+ <event:event>
87
+ <event:sessionKey>675735386</event:sessionKey>
88
+ <event:sessionName>Advanced Baking</event:sessionName>
89
+ <event:sessionType>220</event:sessionType>
90
+ <event:hostWebExID>bakery</event:hostWebExID>
91
+ <event:startDate>05/02/2013 08:00:00</event:startDate>
92
+ <event:endDate>05/02/2013 09:00:00</event:endDate>
93
+ <event:timeZoneID>4</event:timeZoneID>
94
+ <event:duration>60</event:duration>
95
+ <event:description>Learn the intricacies of Japanese baking in this 90 minute webinar.</event:description>
96
+ <event:status>NOT_INPROGRESS</event:status>
97
+ <event:panelists>Satoshi Nakamoto</event:panelists>
98
+ <event:listStatus>UNLISTED</event:listStatus>
99
+ </event:event>
100
+ <event:event>
101
+ <event:sessionKey>448544768</event:sessionKey>
102
+ <event:sessionName>Baking Basics</event:sessionName>
103
+ <event:sessionType>220</event:sessionType>
104
+ <event:hostWebExID>bakery</event:hostWebExID>
105
+ <event:startDate>05/02/2013 11:00:00</event:startDate>
106
+ <event:endDate>05/02/2013 12:30:00</event:endDate>
107
+ <event:timeZoneID>4</event:timeZoneID>
108
+ <event:duration>90</event:duration>
109
+ <event:description>Learn the intricacies of Japanese baking in this 90 minute webinar.</event:description>
110
+ <event:status>NOT_INPROGRESS</event:status>
111
+ <event:panelists></event:panelists>
112
+ <event:listStatus>UNLISTED</event:listStatus>
113
+ </event:event>
114
+ <event:event>
115
+ <event:sessionKey>548465686</event:sessionKey>
116
+ <event:sessionName>Baking Basics</event:sessionName>
117
+ <event:sessionType>220</event:sessionType>
118
+ <event:hostWebExID>bakery</event:hostWebExID>
119
+ <event:startDate>05/02/2013 13:00:00</event:startDate>
120
+ <event:endDate>05/02/2013 14:30:00</event:endDate>
121
+ <event:timeZoneID>4</event:timeZoneID>
122
+ <event:duration>90</event:duration>
123
+ <event:description>Learn the intricacies of Japanese baking in this 90 minute webinar.</event:description>
124
+ <event:status>NOT_INPROGRESS</event:status>
125
+ <event:panelists></event:panelists>
126
+ <event:listStatus>UNLISTED</event:listStatus>
127
+ </event:event>
128
+ <event:event>
129
+ <event:sessionKey>265268976</event:sessionKey>
130
+ <event:sessionName>Advanced Baking</event:sessionName>
131
+ <event:sessionType>220</event:sessionType>
132
+ <event:hostWebExID>bakery</event:hostWebExID>
133
+ <event:startDate>05/03/2013 08:00:00</event:startDate>
134
+ <event:endDate>05/03/2013 09:00:00</event:endDate>
135
+ <event:timeZoneID>4</event:timeZoneID>
136
+ <event:duration>60</event:duration>
137
+ <event:description>Learn the intricacies of Japanese baking in this 90 minute webinar.</event:description>
138
+ <event:status>NOT_INPROGRESS</event:status>
139
+ <event:panelists>Satoshi Nakamoto</event:panelists>
140
+ <event:listStatus>UNLISTED</event:listStatus>
141
+ </event:event>
142
+ <event:event>
143
+ <event:sessionKey>223574221</event:sessionKey>
144
+ <event:sessionName>Baking Basics</event:sessionName>
145
+ <event:sessionType>220</event:sessionType>
146
+ <event:hostWebExID>bakery</event:hostWebExID>
147
+ <event:startDate>05/07/2013 05:00:00</event:startDate>
148
+ <event:endDate>05/07/2013 06:30:00</event:endDate>
149
+ <event:timeZoneID>4</event:timeZoneID>
150
+ <event:duration>90</event:duration>
151
+ <event:description>Learn the intricacies of Japanese baking in this 90 minute webinar.</event:description>
152
+ <event:status>NOT_INPROGRESS</event:status>
153
+ <event:panelists></event:panelists>
154
+ <event:listStatus>UNLISTED</event:listStatus>
155
+ </event:event>
156
+ <event:event>
157
+ <event:sessionKey>495477388</event:sessionKey>
158
+ <event:sessionName>Advanced Baking</event:sessionName>
159
+ <event:sessionType>220</event:sessionType>
160
+ <event:hostWebExID>bakery</event:hostWebExID>
161
+ <event:startDate>05/07/2013 11:00:00</event:startDate>
162
+ <event:endDate>05/07/2013 12:00:00</event:endDate>
163
+ <event:timeZoneID>4</event:timeZoneID>
164
+ <event:duration>60</event:duration>
165
+ <event:description>Learn advanced Japanese baking techniques in this hour-long webinar.</event:description>
166
+ <event:status>NOT_INPROGRESS</event:status>
167
+ <event:panelists></event:panelists>
168
+ <event:listStatus>PUBLIC</event:listStatus>
169
+ </event:event>
170
+ <event:event>
171
+ <event:sessionKey>252653476</event:sessionKey>
172
+ <event:sessionName>Baking Basics</event:sessionName>
173
+ <event:sessionType>220</event:sessionType>
174
+ <event:hostWebExID>bakery</event:hostWebExID>
175
+ <event:startDate>05/07/2013 11:00:00</event:startDate>
176
+ <event:endDate>05/07/2013 12:30:00</event:endDate>
177
+ <event:timeZoneID>4</event:timeZoneID>
178
+ <event:duration>90</event:duration>
179
+ <event:description>Learn the intricacies of Japanese baking in this 90 minute webinar.</event:description>
180
+ <event:status>NOT_INPROGRESS</event:status>
181
+ <event:panelists></event:panelists>
182
+ <event:listStatus>UNLISTED</event:listStatus>
183
+ </event:event>
184
+ <event:event>
185
+ <event:sessionKey>356727241</event:sessionKey>
186
+ <event:sessionName>Baking Finale</event:sessionName>
187
+ <event:sessionType>220</event:sessionType>
188
+ <event:hostWebExID>bakery</event:hostWebExID>
189
+ <event:startDate>05/07/2013 13:00:00</event:startDate>
190
+ <event:endDate>05/07/2013 14:30:00</event:endDate>
191
+ <event:timeZoneID>4</event:timeZoneID>
192
+ <event:duration>90</event:duration>
193
+ <event:description>A 90 minute wrap-up event!</event:description>
194
+ <event:status>NOT_INPROGRESS</event:status>
195
+ <event:panelists></event:panelists>
196
+ <event:listStatus>UNLISTED</event:listStatus>
197
+ </event:event>
198
+ </serv:bodyContent>
199
+ </serv:body>
200
+ </serv:message>
@@ -0,0 +1,14 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <serv:message xmlns:serv="http://www.webex.com/schemas/2002/06/service" xmlns:com="http://www.webex.com/schemas/2002/06/common" xmlns:event="http://www.webex.com/schemas/2002/06/service/event">
3
+ <serv:header>
4
+ <serv:response>
5
+ <serv:result>FAILURE</serv:result>
6
+ <serv:reason>Corresponding User not found</serv:reason>
7
+ <serv:gsbStatus>PRIMARY</serv:gsbStatus>
8
+ <serv:exceptionID>030001</serv:exceptionID>
9
+ </serv:response>
10
+ </serv:header>
11
+ <serv:body>
12
+ <serv:bodyContent/>
13
+ </serv:body>
14
+ </serv:message>
@@ -0,0 +1,14 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <serv:message xmlns:serv="http://www.webex.com/schemas/2002/06/service" xmlns:com="http://www.webex.com/schemas/2002/06/common" xmlns:event="http://www.webex.com/schemas/2002/06/service/event">
3
+ <serv:header>
4
+ <serv:response>
5
+ <serv:result>FAILURE</serv:result>
6
+ <serv:reason>Sorry, no record found</serv:reason>
7
+ <serv:gsbStatus>PRIMARY</serv:gsbStatus>
8
+ <serv:exceptionID>000015</serv:exceptionID>
9
+ </serv:response>
10
+ </serv:header>
11
+ <serv:body>
12
+ <serv:bodyContent/>
13
+ </serv:body>
14
+ </serv:message>
@@ -0,0 +1,6 @@
1
+ require 'bundler/setup'
2
+ require 'web_ex/events'
3
+ require 'json'
4
+ require 'timecop'
5
+
6
+ include WebEx::Events
@@ -0,0 +1,20 @@
1
+ require 'spec_helper'
2
+
3
+ describe Configuration do
4
+ context "with all environment variables present" do
5
+ before :each do
6
+ ENV.stub(:[]).with("WEBEX_SITE_NAME").and_return("bakeryevents")
7
+ ENV.stub(:[]).with("WEBEX_USERNAME").and_return("bakery")
8
+ ENV.stub(:[]).with("WEBEX_PASSWORD").and_return("secret")
9
+ ENV.stub(:[]).with("WEBEX_SITE_ID").and_return("358562")
10
+ ENV.stub(:[]).with("WEBEX_PARTNER_ID").and_return("123ab")
11
+ end
12
+
13
+ it { Configuration.site_name.should == "bakeryevents" }
14
+ it { Configuration.username.should == "bakery" }
15
+ it { Configuration.password.should == "secret" }
16
+ it { Configuration.site_id.should == "358562" }
17
+ it { Configuration.partner_id.should == "123ab" }
18
+ it { Configuration.xml_service_url.should == "https://bakeryevents.webex.com/WBXService/XMLService" }
19
+ end
20
+ end
@@ -0,0 +1,57 @@
1
+ require 'spec_helper'
2
+
3
+ describe EventList do
4
+ let(:event_a) { stub }
5
+ let(:event_b) { stub }
6
+ let(:event_c) { stub }
7
+
8
+ describe "#each" do
9
+ let(:event_list) { EventList.new(event_a, event_b, event_c) }
10
+
11
+ it "iterates over each event" do
12
+ captured_events = []
13
+ event_list.each { |event| captured_events << event }
14
+ captured_events.should == [event_a, event_b, event_c]
15
+ end
16
+ end
17
+
18
+ describe "#to_json" do
19
+ let(:event_list) { EventList.new(event_a, event_b) }
20
+
21
+ before :each do
22
+ event_a.stub(to_json: '{ "event": "a" }')
23
+ event_b.stub(to_json: '{ "event": "b" }')
24
+ end
25
+
26
+ it "returns a JSON representation of all events" do
27
+ JSON.parse(event_list.to_json).should == [{ 'event' => 'a' }, { 'event' => 'b' }]
28
+ end
29
+ end
30
+
31
+ describe "#length" do
32
+ it "returns the number of events" do
33
+ EventList.new(event_a, event_b).length.should == 2
34
+ EventList.new(event_a, event_b, event_c).length.should == 3
35
+ end
36
+ end
37
+
38
+ context "with three events" do
39
+ let(:event_list) { EventList.new(event_a, event_b, event_c) }
40
+
41
+ describe "#first and #last" do
42
+ it { event_list.first.should == event_a }
43
+ it { event_list.last.should == event_c }
44
+ end
45
+ end
46
+
47
+ context "with no events" do
48
+ subject { EventList.new }
49
+ it { should be_empty }
50
+ end
51
+
52
+ context "with at least one event" do
53
+ subject { EventList.new(event_a) }
54
+ it { should_not be_empty }
55
+ end
56
+
57
+ end
@@ -0,0 +1,114 @@
1
+ require "spec_helper"
2
+
3
+ describe Event do
4
+ let(:valid_attributes) {
5
+ {
6
+ name: "Cupcake Giveaway",
7
+ start_date: DateTime.now,
8
+ end_date: DateTime.now + 3600,
9
+ key: "12345",
10
+ description: "Free cupcakes!",
11
+ duration: 60
12
+ }
13
+ }
14
+
15
+ let(:event) { Event.new(valid_attributes) }
16
+
17
+ it "can has accessors for valid attributes" do
18
+ valid_attributes.each do |name, value|
19
+ event.send(name).should == value
20
+ end
21
+ end
22
+
23
+ describe "#to_json" do
24
+ let(:json) { JSON.parse(event.to_json) }
25
+
26
+ it "contains all the valid attributes" do
27
+ json["name"].should == "Cupcake Giveaway"
28
+ json["start_date"].inspect.should == JSON.dump(DateTime.now)
29
+ json["end_date"].inspect.should == JSON.dump(DateTime.now + 3600)
30
+ json["key"].should == "12345"
31
+ json["duration"].should == 60
32
+ json["description"].should == "Free cupcakes!"
33
+ end
34
+ end
35
+
36
+ describe ".from_xml" do
37
+ let(:xml) {
38
+ <<-XML
39
+ <event>
40
+ <sessionKey>662050914</sessionKey>
41
+ <sessionName>NE Utilities - Transition Webinar Navtrak to Fleet</sessionName>
42
+ <sessionType>220</sessionType>
43
+ <hostWebExID>bakery</hostWebExID>
44
+ <startDate>04/30/2013 05:00:00</startDate>
45
+ <endDate>04/30/2013 06:30:00</endDate>
46
+ <timeZoneID>4</timeZoneID>
47
+ <duration>90</duration>
48
+ <description>During this webinar you will be trained on the bakery Fleet application.</description>
49
+ <status>NOT_INPROGRESS</status>
50
+ <panelists></panelists>
51
+ <listStatus>UNLISTED</listStatus>
52
+ </event>
53
+ XML
54
+ }
55
+
56
+ let(:parsed_event) { Event.from_xml(xml) }
57
+
58
+ it "can parse an event" do
59
+ parsed_event.name.should == "NE Utilities - Transition Webinar Navtrak to Fleet"
60
+ parsed_event.key.should == "662050914"
61
+ parsed_event.duration.should == 90
62
+ parsed_event.description.should == "During this webinar you will be trained on the bakery Fleet application."
63
+ end
64
+
65
+ context "with a timezone not in daylight savings" do
66
+ let(:xml) {
67
+ <<-XML
68
+ <event>
69
+ <startDate>02/15/2013 05:00:00</startDate>
70
+ <endDate>02/15/2013 06:30:00</endDate>
71
+ <timeZoneID>4</timeZoneID>
72
+ </event>
73
+ XML
74
+ }
75
+
76
+ it "parses the date correctly" do
77
+ parsed_event.start_date.should == DateTime.new(2013, 2, 15, 5, 0, 0, "-08:00")
78
+ parsed_event.end_date.should == DateTime.new(2013, 2, 15, 6, 30, 0, "-08:00")
79
+ end
80
+ end
81
+
82
+ context "with a timezone inside daylight savings" do
83
+ let(:xml) {
84
+ <<-XML
85
+ <event>
86
+ <startDate>04/30/2013 05:00:00</startDate>
87
+ <endDate>04/30/2013 06:30:00</endDate>
88
+ <timeZoneID>4</timeZoneID>
89
+ </event>
90
+ XML
91
+ }
92
+
93
+ it "corrects for daylight savings" do
94
+ parsed_event.start_date.should == DateTime.new(2013, 4, 30, 5, 0, 0, "-07:00")
95
+ parsed_event.end_date.should == DateTime.new(2013, 4, 30, 6, 30, 0, "-07:00")
96
+ end
97
+ end
98
+ end
99
+
100
+ describe ".fetch" do
101
+ it "creates a new event summary request using the time frame" do
102
+ Timecop.freeze do
103
+ request = stub(find_events!: "events")
104
+ ten_days = 10 * 24 * 60 * 60
105
+
106
+ EventSummaryRequest.should_receive(:new)
107
+ .with(start_date: Time.now, end_date: Time.now + ten_days)
108
+ .and_return(request)
109
+
110
+ Event.fetch(ten_days).should == "events"
111
+ end
112
+ end
113
+ end
114
+ end
@@ -0,0 +1,137 @@
1
+ require 'spec_helper'
2
+ require 'fakeweb'
3
+
4
+ describe EventSummaryRequest do
5
+ def fixture_path(name)
6
+ File.expand_path("../../../fixtures/#{name}", __FILE__)
7
+ end
8
+
9
+ context "with a start date of a day ago and end date in a weeks' time" do
10
+ let(:one_second) { 1 }
11
+ let(:one_day_ago) { Time.now - (24 * 60 * 60) }
12
+ let(:seven_days_from_now) { Time.now + (7 * 24 * 60 * 60) }
13
+
14
+ let(:req) { EventSummaryRequest.new(start_date: one_day_ago, end_date: seven_days_from_now) }
15
+
16
+ describe "#start_date" do
17
+ subject { req.start_date }
18
+ it { should be_within(one_second).of(one_day_ago) }
19
+ end
20
+
21
+ describe "#end_date" do
22
+ subject { req.end_date }
23
+ it { should be_within(one_second).of(seven_days_from_now) }
24
+ end
25
+ end
26
+
27
+ describe "#payload" do
28
+ let(:req) { EventSummaryRequest.new }
29
+ let(:xml) { Nokogiri::XML(req.payload) }
30
+
31
+ describe "<?xml ?> processing instruction" do
32
+ subject { xml.at_xpath('/processing-instruction("xml")').content }
33
+ it { should include("UTF-8") }
34
+ end
35
+
36
+ describe "namespaces" do
37
+ let(:namespaces) { xml.collect_namespaces }
38
+ it { namespaces.should have_key('xmlns:xsi') }
39
+ end
40
+
41
+ describe "security context" do
42
+ before :each do
43
+ Configuration.stub(
44
+ site_name: "wafflehut",
45
+ site_id: "12345",
46
+ username: "bobwaffles",
47
+ password: "maplesyrup",
48
+ partner_id: "p4rtn3r"
49
+ )
50
+ end
51
+
52
+ let(:security_context) { xml.at_xpath('/message/header/securityContext') }
53
+ it { security_context.at_xpath('./webExID').content.should == "bobwaffles" }
54
+ it { security_context.at_xpath('./password').content.should == "maplesyrup" }
55
+ it { security_context.at_xpath('./siteID').content.should == "12345" }
56
+ it { security_context.at_xpath('./partnerID').content.should == "p4rtn3r" }
57
+ end
58
+
59
+ describe "body content" do
60
+ let(:body_content) { xml.at_xpath('/message/body/bodyContent') }
61
+ it { body_content['xsi:type'].should include('LstsummaryEvent') }
62
+ it { body_content.at_xpath('./listControl/startFrom').should_not be_nil }
63
+ it { body_content.at_xpath('./order').should_not be_nil }
64
+ it { body_content.at_xpath('./dateScope').should_not be_nil }
65
+ end
66
+
67
+ context "with start and end dates in a time zone" do
68
+ before :each do
69
+ midday_pancake_tuesday = Time.new(2014, 3, 4, 12, 0, 0, "+02:00")
70
+ three_thirty_pm_pancake_tuesday = Time.new(2014, 3, 4, 15, 30, 0, "+02:00")
71
+ req.stub(start_date: midday_pancake_tuesday, end_date: three_thirty_pm_pancake_tuesday)
72
+ end
73
+
74
+ describe "date scope" do
75
+ let(:date_scope) { xml.at_xpath('/message/body/bodyContent/dateScope') }
76
+
77
+ it "formats dates appropriately and in UTC time" do
78
+ date_scope.at_xpath('./startDateStart').content.should == "03/04/2014 10:00:00"
79
+ date_scope.at_xpath('./endDateEnd').content.should == "03/04/2014 13:30:00"
80
+ end
81
+
82
+ it "uses the UTC time zone ID" do
83
+ date_scope.at_xpath('./timeZoneID').content.should == "21"
84
+ end
85
+ end
86
+ end
87
+ end
88
+
89
+ describe "#find_events!" do
90
+ let(:req) { EventSummaryRequest.new }
91
+
92
+ before :each do
93
+ Configuration.stub(xml_service_url: "https://webex-api.example.com/service")
94
+ FakeWeb.register_uri(:post, "https://webex-api.example.com/service", body: response_body)
95
+ req.stub(payload: "XML")
96
+ end
97
+
98
+ context "when the server responds with events" do
99
+ let(:response_body) { fixture_path("responses/webex_events.xml") }
100
+
101
+ it "creates a new event using from each event node" do
102
+ Event.should_receive(:from_xml).exactly(13).times
103
+ req.find_events!
104
+ end
105
+
106
+ it "returns the events in an event list" do
107
+ events = req.find_events!
108
+ events.should be_a(EventList)
109
+ events.length.should == 13
110
+ events.first.name.should == "Baking Basics"
111
+ events.last.name.should == "Baking Finale"
112
+ end
113
+ end
114
+
115
+ context "when the server responds without events" do
116
+ let(:response_body) { fixture_path("responses/webex_events_no_results.xml") }
117
+
118
+ it "returns an empty list" do
119
+ events = req.find_events!
120
+ events.should be_a(EventList)
121
+ events.should be_empty
122
+ end
123
+ end
124
+
125
+ context "when the server responds with an authentication error" do
126
+ let(:response_body) { fixture_path("responses/webex_events_auth_error.xml") }
127
+
128
+ it "raises an error with the server's reason for the error" do
129
+ -> {
130
+ req.find_events!
131
+ }.should raise_error { |error|
132
+ error.message.should include('Corresponding User not found')
133
+ }
134
+ end
135
+ end
136
+ end
137
+ end
@@ -0,0 +1,30 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'web_ex/events/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "web-ex-events"
8
+ spec.version = WebEx::Events::VERSION
9
+ spec.authors = ["Pete Nicholls"]
10
+ spec.email = ["pete@metanation.com"]
11
+ spec.description = "Allows fetching and parsing WebEx events through the WebEx API. Events can be queried and converted into JSON."
12
+ spec.summary = "A library to read WebEx events through the WebEx API."
13
+ spec.homepage = "https://github.com/Aupajo/web-ex-events"
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files`.split($/)
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_development_dependency "bundler", "~> 1.3"
22
+ spec.add_development_dependency "rake"
23
+ spec.add_development_dependency "rspec"
24
+ spec.add_development_dependency "timecop"
25
+ spec.add_development_dependency "fakeweb"
26
+
27
+ spec.add_dependency "tzinfo"
28
+ spec.add_dependency "httparty"
29
+ spec.add_dependency "nokogiri"
30
+ end
metadata ADDED
@@ -0,0 +1,186 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: web-ex-events
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Pete Nicholls
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2013-06-28 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ~>
18
+ - !ruby/object:Gem::Version
19
+ version: '1.3'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ~>
25
+ - !ruby/object:Gem::Version
26
+ version: '1.3'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - '>='
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - '>='
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rspec
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - '>='
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - '>='
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: timecop
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - '>='
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: fakeweb
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - '>='
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - '>='
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: tzinfo
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - '>='
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :runtime
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - '>='
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: httparty
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - '>='
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ type: :runtime
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - '>='
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
111
+ - !ruby/object:Gem::Dependency
112
+ name: nokogiri
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - '>='
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ type: :runtime
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - '>='
123
+ - !ruby/object:Gem::Version
124
+ version: '0'
125
+ description: Allows fetching and parsing WebEx events through the WebEx API. Events
126
+ can be queried and converted into JSON.
127
+ email:
128
+ - pete@metanation.com
129
+ executables: []
130
+ extensions: []
131
+ extra_rdoc_files: []
132
+ files:
133
+ - .gitignore
134
+ - Gemfile
135
+ - LICENSE.txt
136
+ - README.md
137
+ - Rakefile
138
+ - lib/web_ex/events.rb
139
+ - lib/web_ex/events/configuration.rb
140
+ - lib/web_ex/events/event.rb
141
+ - lib/web_ex/events/event_list.rb
142
+ - lib/web_ex/events/event_summary_request.rb
143
+ - lib/web_ex/events/time_with_zone.rb
144
+ - lib/web_ex/events/version.rb
145
+ - spec/fixtures/responses/webex_events.xml
146
+ - spec/fixtures/responses/webex_events_auth_error.xml
147
+ - spec/fixtures/responses/webex_events_no_results.xml
148
+ - spec/spec_helper.rb
149
+ - spec/web_ex/events/configuration_spec.rb
150
+ - spec/web_ex/events/event_list_spec.rb
151
+ - spec/web_ex/events/event_spec.rb
152
+ - spec/web_ex/events/event_summary_request_spec.rb
153
+ - web-ex-events.gemspec
154
+ homepage: https://github.com/Aupajo/web-ex-events
155
+ licenses:
156
+ - MIT
157
+ metadata: {}
158
+ post_install_message:
159
+ rdoc_options: []
160
+ require_paths:
161
+ - lib
162
+ required_ruby_version: !ruby/object:Gem::Requirement
163
+ requirements:
164
+ - - '>='
165
+ - !ruby/object:Gem::Version
166
+ version: '0'
167
+ required_rubygems_version: !ruby/object:Gem::Requirement
168
+ requirements:
169
+ - - '>='
170
+ - !ruby/object:Gem::Version
171
+ version: '0'
172
+ requirements: []
173
+ rubyforge_project:
174
+ rubygems_version: 2.0.3
175
+ signing_key:
176
+ specification_version: 4
177
+ summary: A library to read WebEx events through the WebEx API.
178
+ test_files:
179
+ - spec/fixtures/responses/webex_events.xml
180
+ - spec/fixtures/responses/webex_events_auth_error.xml
181
+ - spec/fixtures/responses/webex_events_no_results.xml
182
+ - spec/spec_helper.rb
183
+ - spec/web_ex/events/configuration_spec.rb
184
+ - spec/web_ex/events/event_list_spec.rb
185
+ - spec/web_ex/events/event_spec.rb
186
+ - spec/web_ex/events/event_summary_request_spec.rb